Updating XTBs based on .GRDs from branch master
[chromium-blink-merge.git] / media / capture / content / animated_content_sampler_unittest.cc
blobbc3d595d002da4e87332b01acf80d90703822374
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/capture/content/animated_content_sampler.h"
7 #include <cmath>
8 #include <utility>
9 #include <vector>
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/time/time.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "ui/gfx/geometry/rect.h"
17 namespace media {
19 namespace {
21 base::TimeTicks InitialTestTimeTicks() {
22 return base::TimeTicks() + base::TimeDelta::FromSeconds(1);
25 base::TimeDelta FpsAsPeriod(int frame_rate) {
26 return base::TimeDelta::FromSeconds(1) / frame_rate;
29 } // namespace
31 class AnimatedContentSamplerTest : public ::testing::Test {
32 public:
33 AnimatedContentSamplerTest() {}
34 ~AnimatedContentSamplerTest() override {}
36 void SetUp() override {
37 rand_seed_ = static_cast<int>(
38 (InitialTestTimeTicks() - base::TimeTicks()).InMicroseconds());
39 sampler_.reset(new AnimatedContentSampler(GetMinCapturePeriod()));
42 protected:
43 // Overridden by subclass for parameterized tests.
44 virtual base::TimeDelta GetMinCapturePeriod() const {
45 return base::TimeDelta::FromSeconds(1) / 30;
48 AnimatedContentSampler* sampler() const { return sampler_.get(); }
50 int GetRandomInRange(int begin, int end) {
51 const int len = end - begin;
52 const int rand_offset = (len == 0) ? 0 : (NextRandomInt() % (end - begin));
53 return begin + rand_offset;
56 gfx::Rect GetRandomDamageRect() {
57 return gfx::Rect(0, 0, GetRandomInRange(1, 100), GetRandomInRange(1, 100));
60 gfx::Rect GetContentDamageRect() {
61 // This must be distinct from anything GetRandomDamageRect() could return.
62 return gfx::Rect(0, 0, 1280, 720);
65 // Directly inject an observation. Only used to test
66 // ElectMajorityDamageRect().
67 void ObserveDamageRect(const gfx::Rect& damage_rect) {
68 sampler_->observations_.push_back(
69 AnimatedContentSampler::Observation(damage_rect, base::TimeTicks()));
72 gfx::Rect ElectMajorityDamageRect() const {
73 return sampler_->ElectMajorityDamageRect();
76 static base::TimeDelta ComputeSamplingPeriod(
77 base::TimeDelta detected_period,
78 base::TimeDelta target_sampling_period,
79 base::TimeDelta min_capture_period) {
80 return AnimatedContentSampler::ComputeSamplingPeriod(
81 detected_period, target_sampling_period, min_capture_period);
84 private:
85 // Note: Not using base::RandInt() because it is horribly slow on debug
86 // builds. The following is a very simple, deterministic LCG:
87 int NextRandomInt() {
88 rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31);
89 return rand_seed_;
92 int rand_seed_;
93 scoped_ptr<AnimatedContentSampler> sampler_;
96 TEST_F(AnimatedContentSamplerTest, ElectsNoneFromZeroDamageRects) {
97 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
100 TEST_F(AnimatedContentSamplerTest, ElectsMajorityFromOneDamageRect) {
101 const gfx::Rect the_one_rect(0, 0, 1, 1);
102 ObserveDamageRect(the_one_rect);
103 EXPECT_EQ(the_one_rect, ElectMajorityDamageRect());
106 TEST_F(AnimatedContentSamplerTest, ElectsNoneFromTwoDamageRectsOfSameArea) {
107 const gfx::Rect one_rect(0, 0, 1, 1);
108 const gfx::Rect another_rect(1, 1, 1, 1);
109 ObserveDamageRect(one_rect);
110 ObserveDamageRect(another_rect);
111 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
114 TEST_F(AnimatedContentSamplerTest, ElectsLargerOfTwoDamageRects_1) {
115 const gfx::Rect one_rect(0, 0, 1, 1);
116 const gfx::Rect another_rect(0, 0, 2, 2);
117 ObserveDamageRect(one_rect);
118 ObserveDamageRect(another_rect);
119 EXPECT_EQ(another_rect, ElectMajorityDamageRect());
122 TEST_F(AnimatedContentSamplerTest, ElectsLargerOfTwoDamageRects_2) {
123 const gfx::Rect one_rect(0, 0, 2, 2);
124 const gfx::Rect another_rect(0, 0, 1, 1);
125 ObserveDamageRect(one_rect);
126 ObserveDamageRect(another_rect);
127 EXPECT_EQ(one_rect, ElectMajorityDamageRect());
130 TEST_F(AnimatedContentSamplerTest, ElectsSameAsMooreDemonstration) {
131 // A more complex sequence (from Moore's web site): Three different Rects with
132 // the same area, but occurring a different number of times. C should win the
133 // vote.
134 const gfx::Rect rect_a(0, 0, 1, 4);
135 const gfx::Rect rect_b(1, 1, 4, 1);
136 const gfx::Rect rect_c(2, 2, 2, 2);
137 for (int i = 0; i < 3; ++i)
138 ObserveDamageRect(rect_a);
139 for (int i = 0; i < 2; ++i)
140 ObserveDamageRect(rect_c);
141 for (int i = 0; i < 2; ++i)
142 ObserveDamageRect(rect_b);
143 for (int i = 0; i < 3; ++i)
144 ObserveDamageRect(rect_c);
145 ObserveDamageRect(rect_b);
146 for (int i = 0; i < 2; ++i)
147 ObserveDamageRect(rect_c);
148 EXPECT_EQ(rect_c, ElectMajorityDamageRect());
151 TEST_F(AnimatedContentSamplerTest, Elects24FpsVideoInsteadOf48FpsSpinner) {
152 // Scenario: 24 FPS 720x480 Video versus 48 FPS 96x96 "Busy Spinner"
153 const gfx::Rect video_rect(100, 100, 720, 480);
154 const gfx::Rect spinner_rect(360, 0, 96, 96);
155 for (int i = 0; i < 100; ++i) {
156 // |video_rect| occurs once for every two |spinner_rect|. Vary the order
157 // of events between the two:
158 ObserveDamageRect(video_rect);
159 ObserveDamageRect(spinner_rect);
160 ObserveDamageRect(spinner_rect);
161 ObserveDamageRect(video_rect);
162 ObserveDamageRect(spinner_rect);
163 ObserveDamageRect(spinner_rect);
164 ObserveDamageRect(spinner_rect);
165 ObserveDamageRect(video_rect);
166 ObserveDamageRect(spinner_rect);
167 ObserveDamageRect(spinner_rect);
168 ObserveDamageRect(video_rect);
169 ObserveDamageRect(spinner_rect);
171 EXPECT_EQ(video_rect, ElectMajorityDamageRect());
174 TEST_F(AnimatedContentSamplerTest, TargetsSamplingPeriod) {
175 struct Helper {
176 static void RunTargetSamplingPeriodTest(int target_fps) {
177 const base::TimeDelta min_capture_period = FpsAsPeriod(60);
178 const base::TimeDelta target_sampling_period = FpsAsPeriod(target_fps);
180 for (int content_fps = 1; content_fps <= 60; ++content_fps) {
181 const base::TimeDelta content_period = FpsAsPeriod(content_fps);
182 const base::TimeDelta sampling_period = ComputeSamplingPeriod(
183 content_period, target_sampling_period, min_capture_period);
184 if (content_period >= target_sampling_period) {
185 ASSERT_EQ(content_period, sampling_period);
186 } else {
187 ASSERT_LE(min_capture_period, sampling_period);
189 // Check that the sampling rate is as close (or closer) to the target
190 // sampling rate than any integer-subsampling of the content frame
191 // rate.
192 const double absolute_diff =
193 std::abs(1.0 / sampling_period.InSecondsF() - target_fps);
194 const double fudge_for_acceptable_rounding_error = 0.005;
195 for (double divisor = 1; divisor < 4; ++divisor) {
196 SCOPED_TRACE(::testing::Message() << "target_fps=" << target_fps
197 << ", content_fps=" << content_fps
198 << ", divisor=" << divisor);
199 ASSERT_GE(std::abs(content_fps / divisor - target_fps),
200 absolute_diff - fudge_for_acceptable_rounding_error);
207 for (int target_fps = 1; target_fps <= 60; ++target_fps)
208 Helper::RunTargetSamplingPeriodTest(target_fps);
211 namespace {
213 // A test scenario for AnimatedContentSamplerParameterizedTest.
214 struct Scenario {
215 base::TimeDelta vsync_interval; // Reflects compositor's update rate.
216 base::TimeDelta min_capture_period; // Reflects maximum capture rate.
217 base::TimeDelta content_period; // Reflects content animation rate.
218 base::TimeDelta target_sampling_period;
220 Scenario(int compositor_frequency, int max_frame_rate, int content_frame_rate)
221 : vsync_interval(FpsAsPeriod(compositor_frequency)),
222 min_capture_period(FpsAsPeriod(max_frame_rate)),
223 content_period(FpsAsPeriod(content_frame_rate)) {
224 CHECK(content_period >= vsync_interval)
225 << "Bad test params: Impossible to animate faster than the compositor.";
228 Scenario(int compositor_frequency,
229 int max_frame_rate,
230 int content_frame_rate,
231 int target_sampling_rate)
232 : vsync_interval(FpsAsPeriod(compositor_frequency)),
233 min_capture_period(FpsAsPeriod(max_frame_rate)),
234 content_period(FpsAsPeriod(content_frame_rate)),
235 target_sampling_period(FpsAsPeriod(target_sampling_rate)) {
236 CHECK(content_period >= vsync_interval)
237 << "Bad test params: Impossible to animate faster than the compositor.";
241 // Value printer for Scenario.
242 ::std::ostream& operator<<(::std::ostream& os, const Scenario& s) {
243 return os << "{ vsync_interval=" << s.vsync_interval.InMicroseconds()
244 << ", min_capture_period=" << s.min_capture_period.InMicroseconds()
245 << ", content_period=" << s.content_period.InMicroseconds() << " }";
248 } // namespace
250 class AnimatedContentSamplerParameterizedTest
251 : public AnimatedContentSamplerTest,
252 public ::testing::WithParamInterface<Scenario> {
253 public:
254 AnimatedContentSamplerParameterizedTest()
255 : count_dropped_frames_(0), count_sampled_frames_(0) {}
256 virtual ~AnimatedContentSamplerParameterizedTest() {}
258 void SetUp() override {
259 AnimatedContentSamplerTest::SetUp();
260 sampler()->SetTargetSamplingPeriod(GetParam().target_sampling_period);
263 protected:
264 typedef std::pair<gfx::Rect, base::TimeTicks> Event;
266 base::TimeDelta GetMinCapturePeriod() const override {
267 return GetParam().min_capture_period;
270 base::TimeDelta ComputeExpectedSamplingPeriod() const {
271 return AnimatedContentSamplerTest::ComputeSamplingPeriod(
272 GetParam().content_period, GetParam().target_sampling_period,
273 GetParam().min_capture_period);
276 // Generate a sequence of events from the compositor pipeline. The event
277 // times will all be at compositor vsync boundaries.
278 std::vector<Event> GenerateEventSequence(base::TimeTicks begin,
279 base::TimeTicks end,
280 bool include_content_frame_events,
281 bool include_random_events,
282 base::TimeTicks* next_begin_time) {
283 DCHECK(GetParam().content_period >= GetParam().vsync_interval);
284 base::TimeTicks next_content_time = begin;
285 std::vector<Event> events;
286 base::TimeTicks compositor_time;
287 for (compositor_time = begin; compositor_time < end;
288 compositor_time += GetParam().vsync_interval) {
289 if (next_content_time <= compositor_time) {
290 next_content_time += GetParam().content_period;
291 if (include_content_frame_events) {
292 events.push_back(Event(GetContentDamageRect(), compositor_time));
293 continue;
296 if (include_random_events && GetRandomInRange(0, 1) == 0) {
297 events.push_back(Event(GetRandomDamageRect(), compositor_time));
301 if (next_begin_time) {
302 while (compositor_time < next_content_time)
303 compositor_time += GetParam().vsync_interval;
304 *next_begin_time = compositor_time;
307 DCHECK(!events.empty());
308 return events;
311 // Feed |events| through the sampler, and detect whether the expected
312 // lock-in/out transition occurs. Also, track and measure the frame drop
313 // ratio and check it against the expected drop rate.
314 void RunEventSequence(const std::vector<Event> events,
315 bool was_detecting_before,
316 bool is_detecting_after,
317 bool simulate_pipeline_back_pressure,
318 const char* description) {
319 SCOPED_TRACE(::testing::Message() << "Description: " << description);
321 gfx::Rect first_detected_region;
323 EXPECT_EQ(was_detecting_before, sampler()->HasProposal());
324 bool has_detection_switched = false;
325 bool has_detection_flip_flopped_once = false;
326 ResetFrameCounters();
327 for (std::vector<Event>::const_iterator i = events.begin();
328 i != events.end(); ++i) {
329 sampler()->ConsiderPresentationEvent(i->first, i->second);
331 // Detect when the sampler locks in/out, and that it stays that way for
332 // all further iterations of this loop. It is permissible for the lock-in
333 // to flip-flop once, but no more than that.
334 if (!has_detection_switched &&
335 was_detecting_before != sampler()->HasProposal()) {
336 has_detection_switched = true;
337 } else if (has_detection_switched &&
338 is_detecting_after != sampler()->HasProposal()) {
339 ASSERT_FALSE(has_detection_flip_flopped_once);
340 has_detection_flip_flopped_once = true;
341 has_detection_switched = false;
343 ASSERT_EQ(
344 has_detection_switched ? is_detecting_after : was_detecting_before,
345 sampler()->HasProposal());
347 if (sampler()->HasProposal()) {
348 // Make sure the sampler doesn't flip-flop and keep proposing sampling
349 // based on locking into different regions.
350 if (first_detected_region.IsEmpty()) {
351 first_detected_region = sampler()->detected_region();
352 ASSERT_FALSE(first_detected_region.IsEmpty());
353 } else {
354 EXPECT_EQ(first_detected_region, sampler()->detected_region());
357 if (simulate_pipeline_back_pressure && GetRandomInRange(0, 2) == 0)
358 ClientCannotSampleFrame(*i);
359 else
360 ClientDoesWhatSamplerProposes(*i);
361 } else {
362 EXPECT_FALSE(sampler()->ShouldSample());
363 if (!simulate_pipeline_back_pressure || GetRandomInRange(0, 2) == 1)
364 sampler()->RecordSample(i->second);
367 EXPECT_EQ(is_detecting_after, sampler()->HasProposal());
368 ExpectFrameDropRatioIsCorrect();
371 void ResetFrameCounters() {
372 count_dropped_frames_ = 0;
373 count_sampled_frames_ = 0;
376 // Keep track what the sampler is proposing, and call RecordSample() if it
377 // proposes sampling |event|.
378 void ClientDoesWhatSamplerProposes(const Event& event) {
379 if (sampler()->ShouldSample()) {
380 EXPECT_EQ(GetContentDamageRect(), event.first);
381 sampler()->RecordSample(sampler()->frame_timestamp());
382 ++count_sampled_frames_;
383 } else if (event.first == GetContentDamageRect()) {
384 ++count_dropped_frames_;
388 // RecordSample() is not called, but for testing, keep track of what the
389 // sampler is proposing for |event|.
390 void ClientCannotSampleFrame(const Event& event) {
391 if (sampler()->ShouldSample()) {
392 EXPECT_EQ(GetContentDamageRect(), event.first);
393 ++count_sampled_frames_;
394 } else if (event.first == GetContentDamageRect()) {
395 ++count_dropped_frames_;
399 // Confirm the AnimatedContentSampler is not dropping more frames than
400 // expected, given current test parameters.
401 void ExpectFrameDropRatioIsCorrect() {
402 if (count_sampled_frames_ == 0) {
403 EXPECT_EQ(0, count_dropped_frames_);
404 return;
406 const double expected_sampling_ratio =
407 GetParam().content_period.InSecondsF() /
408 ComputeExpectedSamplingPeriod().InSecondsF();
409 const int total_frames = count_dropped_frames_ + count_sampled_frames_;
410 EXPECT_NEAR(total_frames * expected_sampling_ratio, count_sampled_frames_,
411 1.5);
412 EXPECT_NEAR(total_frames * (1.0 - expected_sampling_ratio),
413 count_dropped_frames_, 1.5);
416 private:
417 // These counters only include the frames with the desired content.
418 int count_dropped_frames_;
419 int count_sampled_frames_;
422 // Tests that the implementation locks in/out of frames containing stable
423 // animated content, whether or not random events are also simultaneously
424 // present.
425 TEST_P(AnimatedContentSamplerParameterizedTest, DetectsAnimatedContent) {
426 // |begin| refers to the start of an event sequence in terms of the
427 // Compositor's clock.
428 base::TimeTicks begin = InitialTestTimeTicks();
430 // Provide random events and expect no lock-in.
431 RunEventSequence(
432 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
433 false, true, &begin),
434 false, false, false, "Provide random events and expect no lock-in.");
435 if (HasFailure())
436 return;
438 // Provide content frame events with some random events mixed-in, and expect
439 // the sampler to lock-in.
440 RunEventSequence(
441 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
442 true, true, &begin),
443 false, true, false,
444 "Provide content frame events with some random events mixed-in, and "
445 "expect the sampler to lock-in.");
446 if (HasFailure())
447 return;
449 // Continue providing content frame events without the random events mixed-in
450 // and expect the lock-in to hold.
451 RunEventSequence(
452 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
453 true, false, &begin),
454 true, true, false,
455 "Continue providing content frame events without the random events "
456 "mixed-in and expect the lock-in to hold.");
457 if (HasFailure())
458 return;
460 // Continue providing just content frame events and expect the lock-in to
461 // hold. Also simulate the capture pipeline experiencing back pressure.
462 RunEventSequence(
463 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(20),
464 true, false, &begin),
465 true, true, true,
466 "Continue providing just content frame events and expect the lock-in to "
467 "hold. Also simulate the capture pipeline experiencing back pressure.");
468 if (HasFailure())
469 return;
471 // Provide a half-second of random events only, and expect the lock-in to be
472 // broken.
473 RunEventSequence(
474 GenerateEventSequence(begin,
475 begin + base::TimeDelta::FromMilliseconds(500),
476 false, true, &begin),
477 true, false, false,
478 "Provide a half-second of random events only, and expect the lock-in to "
479 "be broken.");
480 if (HasFailure())
481 return;
483 // Now, go back to providing content frame events, and expect the sampler to
484 // lock-in once again.
485 RunEventSequence(
486 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
487 true, false, &begin),
488 false, true, false,
489 "Now, go back to providing content frame events, and expect the sampler "
490 "to lock-in once again.");
493 // Tests that AnimatedContentSampler won't lock in to, nor flip-flop between,
494 // two animations of the same pixel change rate. VideoCaptureOracle should
495 // revert to using the SmoothEventSampler for these kinds of situations, as
496 // there is no "right answer" as to which animation to lock into.
497 TEST_P(AnimatedContentSamplerParameterizedTest,
498 DoesNotLockInToTwoCompetingAnimations) {
499 // Don't test when the event stream cannot indicate two separate content
500 // animations under the current test parameters.
501 if (GetParam().content_period < 2 * GetParam().vsync_interval)
502 return;
504 // Start the first animation and run for a bit, and expect the sampler to
505 // lock-in.
506 base::TimeTicks begin = InitialTestTimeTicks();
507 RunEventSequence(
508 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
509 true, false, &begin),
510 false, true, false,
511 "Start the first animation and run for a bit, and expect the sampler to "
512 "lock-in.");
513 if (HasFailure())
514 return;
516 // Now, keep the first animation and blend in a second animation of the same
517 // size and frame rate, but at a different position. This will should cause
518 // the sampler to enter an "undetected" state since it's unclear which
519 // animation should be locked into.
520 std::vector<Event> first_animation_events = GenerateEventSequence(
521 begin, begin + base::TimeDelta::FromSeconds(20), true, false, &begin);
522 gfx::Rect second_animation_rect(
523 gfx::Point(0, GetContentDamageRect().height()),
524 GetContentDamageRect().size());
525 std::vector<Event> both_animations_events;
526 base::TimeDelta second_animation_offset = GetParam().vsync_interval;
527 for (std::vector<Event>::const_iterator i = first_animation_events.begin();
528 i != first_animation_events.end(); ++i) {
529 both_animations_events.push_back(*i);
530 both_animations_events.push_back(
531 Event(second_animation_rect, i->second + second_animation_offset));
533 RunEventSequence(
534 both_animations_events, true, false, false,
535 "Now, blend-in a second animation of the same size and frame rate, but "
536 "at a different position.");
537 if (HasFailure())
538 return;
540 // Now, run just the first animation, and expect the sampler to lock-in once
541 // again.
542 RunEventSequence(
543 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
544 true, false, &begin),
545 false, true, false,
546 "Now, run just the first animation, and expect the sampler to lock-in "
547 "once again.");
548 if (HasFailure())
549 return;
551 // Now, blend in the second animation again, but it has half the frame rate of
552 // the first animation and damage Rects with twice the area. This will should
553 // cause the sampler to enter an "undetected" state again. This tests that
554 // pixel-weighting is being accounted for in the sampler's logic.
555 first_animation_events = GenerateEventSequence(
556 begin, begin + base::TimeDelta::FromSeconds(20), true, false, &begin);
557 second_animation_rect.set_width(second_animation_rect.width() * 2);
558 both_animations_events.clear();
559 bool include_second_animation_frame = true;
560 for (std::vector<Event>::const_iterator i = first_animation_events.begin();
561 i != first_animation_events.end(); ++i) {
562 both_animations_events.push_back(*i);
563 if (include_second_animation_frame) {
564 both_animations_events.push_back(
565 Event(second_animation_rect, i->second + second_animation_offset));
567 include_second_animation_frame = !include_second_animation_frame;
569 RunEventSequence(
570 both_animations_events, true, false, false,
571 "Now, blend in the second animation again, but it has half the frame "
572 "rate of the first animation and damage Rects with twice the area.");
575 // Tests that the frame timestamps are smooth; meaning, that when run through a
576 // simulated compositor, each frame is held displayed for the right number of
577 // v-sync intervals.
578 TEST_P(AnimatedContentSamplerParameterizedTest, FrameTimestampsAreSmooth) {
579 // Generate 30 seconds of animated content events, run the events through
580 // AnimatedContentSampler, and record all frame timestamps being proposed
581 // once lock-in is continuous.
582 const base::TimeTicks begin = InitialTestTimeTicks();
583 std::vector<Event> events = GenerateEventSequence(
584 begin, begin + base::TimeDelta::FromSeconds(20), true, false, nullptr);
585 typedef std::vector<base::TimeTicks> Timestamps;
586 Timestamps frame_timestamps;
587 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end();
588 ++i) {
589 sampler()->ConsiderPresentationEvent(i->first, i->second);
590 if (sampler()->HasProposal()) {
591 if (sampler()->ShouldSample()) {
592 frame_timestamps.push_back(sampler()->frame_timestamp());
593 sampler()->RecordSample(sampler()->frame_timestamp());
595 } else {
596 frame_timestamps.clear(); // Reset until continuous lock-in.
599 ASSERT_LE(2u, frame_timestamps.size());
601 // Iterate through the |frame_timestamps|, building a histogram counting the
602 // number of times each frame was displayed k times. For example, 10 frames
603 // of 30 Hz content on a 60 Hz v-sync interval should result in
604 // display_counts[2] == 10. Quit early if any one frame was obviously
605 // repeated too many times.
606 const int64 max_expected_repeats_per_frame =
607 1 + ComputeExpectedSamplingPeriod() / GetParam().vsync_interval;
608 std::vector<size_t> display_counts(max_expected_repeats_per_frame + 1, 0);
609 base::TimeTicks last_present_time = frame_timestamps.front();
610 for (Timestamps::const_iterator i = frame_timestamps.begin() + 1;
611 i != frame_timestamps.end(); ++i) {
612 const size_t num_vsync_intervals = static_cast<size_t>(
613 (*i - last_present_time) / GetParam().vsync_interval);
614 ASSERT_LT(0u, num_vsync_intervals);
615 ASSERT_GT(display_counts.size(), num_vsync_intervals); // Quit early.
616 ++display_counts[num_vsync_intervals];
617 last_present_time += num_vsync_intervals * GetParam().vsync_interval;
620 // Analyze the histogram for an expected result pattern. If the frame
621 // timestamps are smooth, there should only be one or two buckets with
622 // non-zero counts and they should be next to each other. Because the clock
623 // precision for the event_times provided to the sampler is very granular
624 // (i.e., the vsync_interval), it's okay if other buckets have a tiny "stray"
625 // count in this test.
626 size_t highest_count = 0;
627 size_t second_highest_count = 0;
628 for (size_t repeats = 1; repeats < display_counts.size(); ++repeats) {
629 DVLOG(1) << "display_counts[" << repeats << "] is "
630 << display_counts[repeats];
631 if (display_counts[repeats] >= highest_count) {
632 second_highest_count = highest_count;
633 highest_count = display_counts[repeats];
634 } else if (display_counts[repeats] > second_highest_count) {
635 second_highest_count = display_counts[repeats];
638 size_t stray_count_remaining =
639 (frame_timestamps.size() - 1) - (highest_count + second_highest_count);
640 // Expect no more than 0.75% of frames fall outside the two main buckets.
641 EXPECT_GT(frame_timestamps.size() * 75 / 10000, stray_count_remaining);
642 for (size_t repeats = 1; repeats < display_counts.size() - 1; ++repeats) {
643 if (display_counts[repeats] == highest_count) {
644 EXPECT_EQ(second_highest_count, display_counts[repeats + 1]);
645 ++repeats;
646 } else if (second_highest_count > 0 &&
647 display_counts[repeats] == second_highest_count) {
648 EXPECT_EQ(highest_count, display_counts[repeats + 1]);
649 ++repeats;
650 } else {
651 EXPECT_GE(stray_count_remaining, display_counts[repeats]);
652 stray_count_remaining -= display_counts[repeats];
657 // Tests that frame timestamps are "lightly pushed" back towards the original
658 // presentation event times, which tells us the AnimatedContentSampler can
659 // account for sources of timestamp drift and correct the drift.
660 TEST_P(AnimatedContentSamplerParameterizedTest,
661 FrameTimestampsConvergeTowardsEventTimes) {
662 const int max_drift_increment_millis = 3;
664 // Generate a full minute of events.
665 const base::TimeTicks begin = InitialTestTimeTicks();
666 std::vector<Event> events = GenerateEventSequence(
667 begin, begin + base::TimeDelta::FromMinutes(1), true, false, nullptr);
669 // Modify the event sequence so that 1-3 ms of additional drift is suddenly
670 // present every 100 events. This is meant to simulate that, external to
671 // AnimatedContentSampler, the video hardware vsync timebase is being
672 // refreshed and is showing severe drift from the system clock.
673 base::TimeDelta accumulated_drift;
674 for (size_t i = 1; i < events.size(); ++i) {
675 if (i % 100 == 0) {
676 accumulated_drift += base::TimeDelta::FromMilliseconds(
677 GetRandomInRange(1, max_drift_increment_millis + 1));
679 events[i].second += accumulated_drift;
682 // Run all the events through the sampler and track the last rewritten frame
683 // timestamp.
684 base::TimeTicks last_frame_timestamp;
685 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end();
686 ++i) {
687 sampler()->ConsiderPresentationEvent(i->first, i->second);
688 if (sampler()->ShouldSample())
689 last_frame_timestamp = sampler()->frame_timestamp();
692 // If drift was accounted for, the |last_frame_timestamp| should be close to
693 // the last event's timestamp.
694 const base::TimeDelta total_error =
695 events.back().second - last_frame_timestamp;
696 const base::TimeDelta max_acceptable_error =
697 GetParam().min_capture_period +
698 base::TimeDelta::FromMilliseconds(max_drift_increment_millis);
699 EXPECT_NEAR(0.0, total_error.InMicroseconds(),
700 max_acceptable_error.InMicroseconds());
703 INSTANTIATE_TEST_CASE_P(
705 AnimatedContentSamplerParameterizedTest,
706 ::testing::Values(
707 // Typical frame rate content: Compositor runs at 60 Hz, capture at 30
708 // Hz, and content video animates at 30, 25, or 24 Hz.
709 Scenario(60, 30, 30),
710 Scenario(60, 30, 25),
711 Scenario(60, 30, 24),
713 // High frame rate content that leverages the Compositor's
714 // capabilities, but capture is still at 30 Hz.
715 Scenario(60, 30, 60),
716 Scenario(60, 30, 50),
717 Scenario(60, 30, 48),
719 // High frame rate content that leverages the Compositor's
720 // capabilities, and capture is also a buttery 60 Hz.
721 Scenario(60, 60, 60),
722 Scenario(60, 60, 50),
723 Scenario(60, 60, 48),
725 // High frame rate content that leverages the Compositor's
726 // capabilities, but the client has disabled HFR sampling.
727 Scenario(60, 60, 60, 30),
728 Scenario(60, 60, 50, 30),
729 Scenario(60, 60, 48, 30),
731 // On some platforms, the Compositor runs at 50 Hz.
732 Scenario(50, 30, 30),
733 Scenario(50, 30, 25),
734 Scenario(50, 30, 24),
735 Scenario(50, 30, 50),
736 Scenario(50, 30, 48),
738 // Stable, but non-standard content frame rates.
739 Scenario(60, 30, 16),
740 Scenario(60, 30, 20),
741 Scenario(60, 30, 23),
742 Scenario(60, 30, 26),
743 Scenario(60, 30, 27),
744 Scenario(60, 30, 28),
745 Scenario(60, 30, 29),
746 Scenario(60, 30, 31),
747 Scenario(60, 30, 32),
748 Scenario(60, 30, 33)));
750 } // namespace media