third_party: Add OWNERS for re2 library.
[chromium-blink-merge.git] / media / capture / animated_content_sampler_unittest.cc
blob2d96018947e4ccdee3be27dfbb450a235489909d
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/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 const base::TimeDelta since_epoch =
38 InitialTestTimeTicks() - base::TimeTicks::UnixEpoch();
39 rand_seed_ = abs(static_cast<int>(since_epoch.InMicroseconds()));
40 sampler_.reset(new AnimatedContentSampler(GetMinCapturePeriod()));
43 protected:
44 // Overridden by subclass for parameterized tests.
45 virtual base::TimeDelta GetMinCapturePeriod() const {
46 return base::TimeDelta::FromSeconds(1) / 30;
49 AnimatedContentSampler* sampler() const {
50 return sampler_.get();
53 int GetRandomInRange(int begin, int end) {
54 const int len = end - begin;
55 const int rand_offset = (len == 0) ? 0 : (NextRandomInt() % (end - begin));
56 return begin + rand_offset;
59 gfx::Rect GetRandomDamageRect() {
60 return gfx::Rect(0, 0, GetRandomInRange(1, 100), GetRandomInRange(1, 100));
63 gfx::Rect GetContentDamageRect() {
64 // This must be distinct from anything GetRandomDamageRect() could return.
65 return gfx::Rect(0, 0, 1280, 720);
68 // Directly inject an observation. Only used to test
69 // ElectMajorityDamageRect().
70 void ObserveDamageRect(const gfx::Rect& damage_rect) {
71 sampler_->observations_.push_back(
72 AnimatedContentSampler::Observation(damage_rect, base::TimeTicks()));
75 gfx::Rect ElectMajorityDamageRect() const {
76 return sampler_->ElectMajorityDamageRect();
79 static base::TimeDelta ComputeSamplingPeriod(
80 base::TimeDelta detected_period,
81 base::TimeDelta target_sampling_period,
82 base::TimeDelta min_capture_period) {
83 return AnimatedContentSampler::ComputeSamplingPeriod(
84 detected_period, target_sampling_period, min_capture_period);
87 private:
88 // Note: Not using base::RandInt() because it is horribly slow on debug
89 // builds. The following is a very simple, deterministic LCG:
90 int NextRandomInt() {
91 rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31);
92 return rand_seed_;
95 int rand_seed_;
96 scoped_ptr<AnimatedContentSampler> sampler_;
99 TEST_F(AnimatedContentSamplerTest, ElectsNoneFromZeroDamageRects) {
100 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
103 TEST_F(AnimatedContentSamplerTest, ElectsMajorityFromOneDamageRect) {
104 const gfx::Rect the_one_rect(0, 0, 1, 1);
105 ObserveDamageRect(the_one_rect);
106 EXPECT_EQ(the_one_rect, ElectMajorityDamageRect());
109 TEST_F(AnimatedContentSamplerTest, ElectsNoneFromTwoDamageRectsOfSameArea) {
110 const gfx::Rect one_rect(0, 0, 1, 1);
111 const gfx::Rect another_rect(1, 1, 1, 1);
112 ObserveDamageRect(one_rect);
113 ObserveDamageRect(another_rect);
114 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
117 TEST_F(AnimatedContentSamplerTest, ElectsLargerOfTwoDamageRects_1) {
118 const gfx::Rect one_rect(0, 0, 1, 1);
119 const gfx::Rect another_rect(0, 0, 2, 2);
120 ObserveDamageRect(one_rect);
121 ObserveDamageRect(another_rect);
122 EXPECT_EQ(another_rect, ElectMajorityDamageRect());
125 TEST_F(AnimatedContentSamplerTest, ElectsLargerOfTwoDamageRects_2) {
126 const gfx::Rect one_rect(0, 0, 2, 2);
127 const gfx::Rect another_rect(0, 0, 1, 1);
128 ObserveDamageRect(one_rect);
129 ObserveDamageRect(another_rect);
130 EXPECT_EQ(one_rect, ElectMajorityDamageRect());
133 TEST_F(AnimatedContentSamplerTest, ElectsSameAsMooreDemonstration) {
134 // A more complex sequence (from Moore's web site): Three different Rects with
135 // the same area, but occurring a different number of times. C should win the
136 // vote.
137 const gfx::Rect rect_a(0, 0, 1, 4);
138 const gfx::Rect rect_b(1, 1, 4, 1);
139 const gfx::Rect rect_c(2, 2, 2, 2);
140 for (int i = 0; i < 3; ++i)
141 ObserveDamageRect(rect_a);
142 for (int i = 0; i < 2; ++i)
143 ObserveDamageRect(rect_c);
144 for (int i = 0; i < 2; ++i)
145 ObserveDamageRect(rect_b);
146 for (int i = 0; i < 3; ++i)
147 ObserveDamageRect(rect_c);
148 ObserveDamageRect(rect_b);
149 for (int i = 0; i < 2; ++i)
150 ObserveDamageRect(rect_c);
151 EXPECT_EQ(rect_c, ElectMajorityDamageRect());
154 TEST_F(AnimatedContentSamplerTest, Elects24FpsVideoInsteadOf48FpsSpinner) {
155 // Scenario: 24 FPS 720x480 Video versus 48 FPS 96x96 "Busy Spinner"
156 const gfx::Rect video_rect(100, 100, 720, 480);
157 const gfx::Rect spinner_rect(360, 0, 96, 96);
158 for (int i = 0; i < 100; ++i) {
159 // |video_rect| occurs once for every two |spinner_rect|. Vary the order
160 // of events between the two:
161 ObserveDamageRect(video_rect);
162 ObserveDamageRect(spinner_rect);
163 ObserveDamageRect(spinner_rect);
164 ObserveDamageRect(video_rect);
165 ObserveDamageRect(spinner_rect);
166 ObserveDamageRect(spinner_rect);
167 ObserveDamageRect(spinner_rect);
168 ObserveDamageRect(video_rect);
169 ObserveDamageRect(spinner_rect);
170 ObserveDamageRect(spinner_rect);
171 ObserveDamageRect(video_rect);
172 ObserveDamageRect(spinner_rect);
174 EXPECT_EQ(video_rect, ElectMajorityDamageRect());
177 TEST_F(AnimatedContentSamplerTest, TargetsSamplingPeriod) {
178 struct Helper {
179 static void RunTargetSamplingPeriodTest(int target_fps) {
180 const base::TimeDelta min_capture_period = FpsAsPeriod(60);
181 const base::TimeDelta target_sampling_period = FpsAsPeriod(target_fps);
183 for (int content_fps = 1; content_fps <= 60; ++content_fps) {
184 const base::TimeDelta content_period = FpsAsPeriod(content_fps);
185 const base::TimeDelta sampling_period =
186 ComputeSamplingPeriod(content_period,
187 target_sampling_period,
188 min_capture_period);
189 if (content_period >= target_sampling_period) {
190 ASSERT_EQ(content_period, sampling_period);
191 } else {
192 ASSERT_LE(min_capture_period, sampling_period);
194 // Check that the sampling rate is as close (or closer) to the target
195 // sampling rate than any integer-subsampling of the content frame
196 // rate.
197 const double absolute_diff =
198 std::abs(1.0 / sampling_period.InSecondsF() - target_fps);
199 const double fudge_for_acceptable_rounding_error = 0.005;
200 for (double divisor = 1; divisor < 4; ++divisor) {
201 SCOPED_TRACE(::testing::Message() << "target_fps=" << target_fps
202 << ", content_fps=" << content_fps
203 << ", divisor=" << divisor);
204 ASSERT_GE(std::abs(content_fps / divisor - target_fps),
205 absolute_diff - fudge_for_acceptable_rounding_error);
212 for (int target_fps = 1; target_fps <= 60; ++target_fps)
213 Helper::RunTargetSamplingPeriodTest(target_fps);
216 namespace {
218 // A test scenario for AnimatedContentSamplerParameterizedTest.
219 struct Scenario {
220 base::TimeDelta vsync_interval; // Reflects compositor's update rate.
221 base::TimeDelta min_capture_period; // Reflects maximum capture rate.
222 base::TimeDelta content_period; // Reflects content animation rate.
223 base::TimeDelta target_sampling_period;
225 Scenario(int compositor_frequency,
226 int max_frame_rate,
227 int content_frame_rate)
228 : vsync_interval(FpsAsPeriod(compositor_frequency)),
229 min_capture_period(FpsAsPeriod(max_frame_rate)),
230 content_period(FpsAsPeriod(content_frame_rate)) {
231 CHECK(content_period >= vsync_interval)
232 << "Bad test params: Impossible to animate faster than the compositor.";
235 Scenario(int compositor_frequency,
236 int max_frame_rate,
237 int content_frame_rate,
238 int target_sampling_rate)
239 : vsync_interval(FpsAsPeriod(compositor_frequency)),
240 min_capture_period(FpsAsPeriod(max_frame_rate)),
241 content_period(FpsAsPeriod(content_frame_rate)),
242 target_sampling_period(FpsAsPeriod(target_sampling_rate)) {
243 CHECK(content_period >= vsync_interval)
244 << "Bad test params: Impossible to animate faster than the compositor.";
248 // Value printer for Scenario.
249 ::std::ostream& operator<<(::std::ostream& os, const Scenario& s) {
250 return os << "{ vsync_interval=" << s.vsync_interval.InMicroseconds()
251 << ", min_capture_period=" << s.min_capture_period.InMicroseconds()
252 << ", content_period=" << s.content_period.InMicroseconds()
253 << " }";
256 } // namespace
258 class AnimatedContentSamplerParameterizedTest
259 : public AnimatedContentSamplerTest,
260 public ::testing::WithParamInterface<Scenario> {
261 public:
262 AnimatedContentSamplerParameterizedTest()
263 : count_dropped_frames_(0), count_sampled_frames_(0) {}
264 virtual ~AnimatedContentSamplerParameterizedTest() {}
266 void SetUp() override {
267 AnimatedContentSamplerTest::SetUp();
268 sampler()->SetTargetSamplingPeriod(GetParam().target_sampling_period);
271 protected:
272 typedef std::pair<gfx::Rect, base::TimeTicks> Event;
274 base::TimeDelta GetMinCapturePeriod() const override {
275 return GetParam().min_capture_period;
278 base::TimeDelta ComputeExpectedSamplingPeriod() const {
279 return AnimatedContentSamplerTest::ComputeSamplingPeriod(
280 GetParam().content_period,
281 GetParam().target_sampling_period,
282 GetParam().min_capture_period);
285 // Generate a sequence of events from the compositor pipeline. The event
286 // times will all be at compositor vsync boundaries.
287 std::vector<Event> GenerateEventSequence(base::TimeTicks begin,
288 base::TimeTicks end,
289 bool include_content_frame_events,
290 bool include_random_events,
291 base::TimeTicks* next_begin_time) {
292 DCHECK(GetParam().content_period >= GetParam().vsync_interval);
293 base::TimeTicks next_content_time = begin;
294 std::vector<Event> events;
295 base::TimeTicks compositor_time;
296 for (compositor_time = begin; compositor_time < end;
297 compositor_time += GetParam().vsync_interval) {
298 if (next_content_time <= compositor_time) {
299 next_content_time += GetParam().content_period;
300 if (include_content_frame_events) {
301 events.push_back(Event(GetContentDamageRect(), compositor_time));
302 continue;
305 if (include_random_events && GetRandomInRange(0, 1) == 0) {
306 events.push_back(Event(GetRandomDamageRect(), compositor_time));
310 if (next_begin_time) {
311 while (compositor_time < next_content_time)
312 compositor_time += GetParam().vsync_interval;
313 *next_begin_time = compositor_time;
316 DCHECK(!events.empty());
317 return events;
320 // Feed |events| through the sampler, and detect whether the expected
321 // lock-in/out transition occurs. Also, track and measure the frame drop
322 // ratio and check it against the expected drop rate.
323 void RunEventSequence(const std::vector<Event> events,
324 bool was_detecting_before,
325 bool is_detecting_after,
326 bool simulate_pipeline_back_pressure,
327 const char* description) {
328 SCOPED_TRACE(::testing::Message() << "Description: " << description);
330 gfx::Rect first_detected_region;
332 EXPECT_EQ(was_detecting_before, sampler()->HasProposal());
333 bool has_detection_switched = false;
334 bool has_detection_flip_flopped_once = false;
335 ResetFrameCounters();
336 for (std::vector<Event>::const_iterator i = events.begin();
337 i != events.end(); ++i) {
338 sampler()->ConsiderPresentationEvent(i->first, i->second);
340 // Detect when the sampler locks in/out, and that it stays that way for
341 // all further iterations of this loop. It is permissible for the lock-in
342 // to flip-flop once, but no more than that.
343 if (!has_detection_switched &&
344 was_detecting_before != sampler()->HasProposal()) {
345 has_detection_switched = true;
346 } else if (has_detection_switched &&
347 is_detecting_after != sampler()->HasProposal()) {
348 ASSERT_FALSE(has_detection_flip_flopped_once);
349 has_detection_flip_flopped_once = true;
350 has_detection_switched = false;
352 ASSERT_EQ(
353 has_detection_switched ? is_detecting_after : was_detecting_before,
354 sampler()->HasProposal());
356 if (sampler()->HasProposal()) {
357 // Make sure the sampler doesn't flip-flop and keep proposing sampling
358 // based on locking into different regions.
359 if (first_detected_region.IsEmpty()) {
360 first_detected_region = sampler()->detected_region();
361 ASSERT_FALSE(first_detected_region.IsEmpty());
362 } else {
363 EXPECT_EQ(first_detected_region, sampler()->detected_region());
366 if (simulate_pipeline_back_pressure && GetRandomInRange(0, 2) == 0)
367 ClientCannotSampleFrame(*i);
368 else
369 ClientDoesWhatSamplerProposes(*i);
370 } else {
371 EXPECT_FALSE(sampler()->ShouldSample());
372 if (!simulate_pipeline_back_pressure || GetRandomInRange(0, 2) == 1)
373 sampler()->RecordSample(i->second);
376 EXPECT_EQ(is_detecting_after, sampler()->HasProposal());
377 ExpectFrameDropRatioIsCorrect();
380 void ResetFrameCounters() {
381 count_dropped_frames_ = 0;
382 count_sampled_frames_ = 0;
385 // Keep track what the sampler is proposing, and call RecordSample() if it
386 // proposes sampling |event|.
387 void ClientDoesWhatSamplerProposes(const Event& event) {
388 if (sampler()->ShouldSample()) {
389 EXPECT_EQ(GetContentDamageRect(), event.first);
390 sampler()->RecordSample(sampler()->frame_timestamp());
391 ++count_sampled_frames_;
392 } else if (event.first == GetContentDamageRect()) {
393 ++count_dropped_frames_;
397 // RecordSample() is not called, but for testing, keep track of what the
398 // sampler is proposing for |event|.
399 void ClientCannotSampleFrame(const Event& event) {
400 if (sampler()->ShouldSample()) {
401 EXPECT_EQ(GetContentDamageRect(), event.first);
402 ++count_sampled_frames_;
403 } else if (event.first == GetContentDamageRect()) {
404 ++count_dropped_frames_;
408 // Confirm the AnimatedContentSampler is not dropping more frames than
409 // expected, given current test parameters.
410 void ExpectFrameDropRatioIsCorrect() {
411 if (count_sampled_frames_ == 0) {
412 EXPECT_EQ(0, count_dropped_frames_);
413 return;
415 const double expected_sampling_ratio =
416 GetParam().content_period.InSecondsF() /
417 ComputeExpectedSamplingPeriod().InSecondsF();
418 const int total_frames = count_dropped_frames_ + count_sampled_frames_;
419 EXPECT_NEAR(total_frames * expected_sampling_ratio,
420 count_sampled_frames_,
421 1.5);
422 EXPECT_NEAR(total_frames * (1.0 - expected_sampling_ratio),
423 count_dropped_frames_,
424 1.5);
427 private:
428 // These counters only include the frames with the desired content.
429 int count_dropped_frames_;
430 int count_sampled_frames_;
433 // Tests that the implementation locks in/out of frames containing stable
434 // animated content, whether or not random events are also simultaneously
435 // present.
436 TEST_P(AnimatedContentSamplerParameterizedTest, DetectsAnimatedContent) {
437 // |begin| refers to the start of an event sequence in terms of the
438 // Compositor's clock.
439 base::TimeTicks begin = InitialTestTimeTicks();
441 // Provide random events and expect no lock-in.
442 RunEventSequence(
443 GenerateEventSequence(begin,
444 begin + base::TimeDelta::FromSeconds(5),
445 false,
446 true,
447 &begin),
448 false,
449 false,
450 false,
451 "Provide random events and expect no lock-in.");
452 if (HasFailure())
453 return;
455 // Provide content frame events with some random events mixed-in, and expect
456 // the sampler to lock-in.
457 RunEventSequence(
458 GenerateEventSequence(begin,
459 begin + base::TimeDelta::FromSeconds(5),
460 true,
461 true,
462 &begin),
463 false,
464 true,
465 false,
466 "Provide content frame events with some random events mixed-in, and "
467 "expect the sampler to lock-in.");
468 if (HasFailure())
469 return;
471 // Continue providing content frame events without the random events mixed-in
472 // and expect the lock-in to hold.
473 RunEventSequence(
474 GenerateEventSequence(begin,
475 begin + base::TimeDelta::FromSeconds(5),
476 true,
477 false,
478 &begin),
479 true,
480 true,
481 false,
482 "Continue providing content frame events without the random events "
483 "mixed-in and expect the lock-in to hold.");
484 if (HasFailure())
485 return;
487 // Continue providing just content frame events and expect the lock-in to
488 // hold. Also simulate the capture pipeline experiencing back pressure.
489 RunEventSequence(
490 GenerateEventSequence(begin,
491 begin + base::TimeDelta::FromSeconds(20),
492 true,
493 false,
494 &begin),
495 true,
496 true,
497 true,
498 "Continue providing just content frame events and expect the lock-in to "
499 "hold. Also simulate the capture pipeline experiencing back pressure.");
500 if (HasFailure())
501 return;
504 // Provide a half-second of random events only, and expect the lock-in to be
505 // broken.
506 RunEventSequence(
507 GenerateEventSequence(begin,
508 begin + base::TimeDelta::FromMilliseconds(500),
509 false,
510 true,
511 &begin),
512 true,
513 false,
514 false,
515 "Provide a half-second of random events only, and expect the lock-in to "
516 "be broken.");
517 if (HasFailure())
518 return;
520 // Now, go back to providing content frame events, and expect the sampler to
521 // lock-in once again.
522 RunEventSequence(
523 GenerateEventSequence(begin,
524 begin + base::TimeDelta::FromSeconds(5),
525 true,
526 false,
527 &begin),
528 false,
529 true,
530 false,
531 "Now, go back to providing content frame events, and expect the sampler "
532 "to lock-in once again.");
535 // Tests that AnimatedContentSampler won't lock in to, nor flip-flop between,
536 // two animations of the same pixel change rate. VideoCaptureOracle should
537 // revert to using the SmoothEventSampler for these kinds of situations, as
538 // there is no "right answer" as to which animation to lock into.
539 TEST_P(AnimatedContentSamplerParameterizedTest,
540 DoesNotLockInToTwoCompetingAnimations) {
541 // Don't test when the event stream cannot indicate two separate content
542 // animations under the current test parameters.
543 if (GetParam().content_period < 2 * GetParam().vsync_interval)
544 return;
546 // Start the first animation and run for a bit, and expect the sampler to
547 // lock-in.
548 base::TimeTicks begin = InitialTestTimeTicks();
549 RunEventSequence(
550 GenerateEventSequence(begin,
551 begin + base::TimeDelta::FromSeconds(5),
552 true,
553 false,
554 &begin),
555 false,
556 true,
557 false,
558 "Start the first animation and run for a bit, and expect the sampler to "
559 "lock-in.");
560 if (HasFailure())
561 return;
563 // Now, keep the first animation and blend in a second animation of the same
564 // size and frame rate, but at a different position. This will should cause
565 // the sampler to enter an "undetected" state since it's unclear which
566 // animation should be locked into.
567 std::vector<Event> first_animation_events =
568 GenerateEventSequence(begin,
569 begin + base::TimeDelta::FromSeconds(20),
570 true,
571 false,
572 &begin);
573 gfx::Rect second_animation_rect(
574 gfx::Point(0, GetContentDamageRect().height()),
575 GetContentDamageRect().size());
576 std::vector<Event> both_animations_events;
577 base::TimeDelta second_animation_offset = GetParam().vsync_interval;
578 for (std::vector<Event>::const_iterator i = first_animation_events.begin();
579 i != first_animation_events.end(); ++i) {
580 both_animations_events.push_back(*i);
581 both_animations_events.push_back(
582 Event(second_animation_rect, i->second + second_animation_offset));
584 RunEventSequence(
585 both_animations_events, true, false, false,
586 "Now, blend-in a second animation of the same size and frame rate, but "
587 "at a different position.");
588 if (HasFailure())
589 return;
591 // Now, run just the first animation, and expect the sampler to lock-in once
592 // again.
593 RunEventSequence(
594 GenerateEventSequence(begin,
595 begin + base::TimeDelta::FromSeconds(5),
596 true,
597 false,
598 &begin),
599 false,
600 true,
601 false,
602 "Now, run just the first animation, and expect the sampler to lock-in "
603 "once again.");
604 if (HasFailure())
605 return;
607 // Now, blend in the second animation again, but it has half the frame rate of
608 // the first animation and damage Rects with twice the area. This will should
609 // cause the sampler to enter an "undetected" state again. This tests that
610 // pixel-weighting is being accounted for in the sampler's logic.
611 first_animation_events =
612 GenerateEventSequence(begin,
613 begin + base::TimeDelta::FromSeconds(20),
614 true,
615 false,
616 &begin);
617 second_animation_rect.set_width(second_animation_rect.width() * 2);
618 both_animations_events.clear();
619 bool include_second_animation_frame = true;
620 for (std::vector<Event>::const_iterator i = first_animation_events.begin();
621 i != first_animation_events.end(); ++i) {
622 both_animations_events.push_back(*i);
623 if (include_second_animation_frame) {
624 both_animations_events.push_back(
625 Event(second_animation_rect, i->second + second_animation_offset));
627 include_second_animation_frame = !include_second_animation_frame;
629 RunEventSequence(
630 both_animations_events, true, false, false,
631 "Now, blend in the second animation again, but it has half the frame "
632 "rate of the first animation and damage Rects with twice the area.");
635 // Tests that the frame timestamps are smooth; meaning, that when run through a
636 // simulated compositor, each frame is held displayed for the right number of
637 // v-sync intervals.
638 TEST_P(AnimatedContentSamplerParameterizedTest, FrameTimestampsAreSmooth) {
639 // Generate 30 seconds of animated content events, run the events through
640 // AnimatedContentSampler, and record all frame timestamps being proposed
641 // once lock-in is continuous.
642 const base::TimeTicks begin = InitialTestTimeTicks();
643 std::vector<Event> events = GenerateEventSequence(
644 begin,
645 begin + base::TimeDelta::FromSeconds(20),
646 true,
647 false,
648 nullptr);
649 typedef std::vector<base::TimeTicks> Timestamps;
650 Timestamps frame_timestamps;
651 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end();
652 ++i) {
653 sampler()->ConsiderPresentationEvent(i->first, i->second);
654 if (sampler()->HasProposal()) {
655 if (sampler()->ShouldSample()) {
656 frame_timestamps.push_back(sampler()->frame_timestamp());
657 sampler()->RecordSample(sampler()->frame_timestamp());
659 } else {
660 frame_timestamps.clear(); // Reset until continuous lock-in.
663 ASSERT_LE(2u, frame_timestamps.size());
665 // Iterate through the |frame_timestamps|, building a histogram counting the
666 // number of times each frame was displayed k times. For example, 10 frames
667 // of 30 Hz content on a 60 Hz v-sync interval should result in
668 // display_counts[2] == 10. Quit early if any one frame was obviously
669 // repeated too many times.
670 const int64 max_expected_repeats_per_frame = 1 +
671 ComputeExpectedSamplingPeriod() / GetParam().vsync_interval;
672 std::vector<size_t> display_counts(max_expected_repeats_per_frame + 1, 0);
673 base::TimeTicks last_present_time = frame_timestamps.front();
674 for (Timestamps::const_iterator i = frame_timestamps.begin() + 1;
675 i != frame_timestamps.end(); ++i) {
676 const size_t num_vsync_intervals = static_cast<size_t>(
677 (*i - last_present_time) / GetParam().vsync_interval);
678 ASSERT_LT(0u, num_vsync_intervals);
679 ASSERT_GT(display_counts.size(), num_vsync_intervals); // Quit early.
680 ++display_counts[num_vsync_intervals];
681 last_present_time += num_vsync_intervals * GetParam().vsync_interval;
684 // Analyze the histogram for an expected result pattern. If the frame
685 // timestamps are smooth, there should only be one or two buckets with
686 // non-zero counts and they should be next to each other. Because the clock
687 // precision for the event_times provided to the sampler is very granular
688 // (i.e., the vsync_interval), it's okay if other buckets have a tiny "stray"
689 // count in this test.
690 size_t highest_count = 0;
691 size_t second_highest_count = 0;
692 for (size_t repeats = 1; repeats < display_counts.size(); ++repeats) {
693 DVLOG(1) << "display_counts[" << repeats << "] is "
694 << display_counts[repeats];
695 if (display_counts[repeats] >= highest_count) {
696 second_highest_count = highest_count;
697 highest_count = display_counts[repeats];
698 } else if (display_counts[repeats] > second_highest_count) {
699 second_highest_count = display_counts[repeats];
702 size_t stray_count_remaining =
703 (frame_timestamps.size() - 1) - (highest_count + second_highest_count);
704 // Expect no more than 0.75% of frames fall outside the two main buckets.
705 EXPECT_GT(frame_timestamps.size() * 75 / 10000, stray_count_remaining);
706 for (size_t repeats = 1; repeats < display_counts.size() - 1; ++repeats) {
707 if (display_counts[repeats] == highest_count) {
708 EXPECT_EQ(second_highest_count, display_counts[repeats + 1]);
709 ++repeats;
710 } else if (second_highest_count > 0 &&
711 display_counts[repeats] == second_highest_count) {
712 EXPECT_EQ(highest_count, display_counts[repeats + 1]);
713 ++repeats;
714 } else {
715 EXPECT_GE(stray_count_remaining, display_counts[repeats]);
716 stray_count_remaining -= display_counts[repeats];
721 // Tests that frame timestamps are "lightly pushed" back towards the original
722 // presentation event times, which tells us the AnimatedContentSampler can
723 // account for sources of timestamp drift and correct the drift.
724 // flaky: http://crbug.com/487491
725 TEST_P(AnimatedContentSamplerParameterizedTest,
726 DISABLED_FrameTimestampsConvergeTowardsEventTimes) {
727 const int max_drift_increment_millis = 3;
729 // Generate a full minute of events.
730 const base::TimeTicks begin = InitialTestTimeTicks();
731 std::vector<Event> events =
732 GenerateEventSequence(begin,
733 begin + base::TimeDelta::FromMinutes(1),
734 true,
735 false,
736 nullptr);
738 // Modify the event sequence so that 1-3 ms of additional drift is suddenly
739 // present every 100 events. This is meant to simulate that, external to
740 // AnimatedContentSampler, the video hardware vsync timebase is being
741 // refreshed and is showing severe drift from the system clock.
742 base::TimeDelta accumulated_drift;
743 for (size_t i = 1; i < events.size(); ++i) {
744 if (i % 100 == 0) {
745 accumulated_drift += base::TimeDelta::FromMilliseconds(
746 GetRandomInRange(1, max_drift_increment_millis + 1));
748 events[i].second += accumulated_drift;
751 // Run all the events through the sampler and track the last rewritten frame
752 // timestamp.
753 base::TimeTicks last_frame_timestamp;
754 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end();
755 ++i) {
756 sampler()->ConsiderPresentationEvent(i->first, i->second);
757 if (sampler()->ShouldSample())
758 last_frame_timestamp = sampler()->frame_timestamp();
761 // If drift was accounted for, the |last_frame_timestamp| should be close to
762 // the last event's timestamp.
763 const base::TimeDelta total_error =
764 events.back().second - last_frame_timestamp;
765 const base::TimeDelta max_acceptable_error = GetParam().min_capture_period +
766 base::TimeDelta::FromMilliseconds(max_drift_increment_millis);
767 EXPECT_NEAR(0.0,
768 total_error.InMicroseconds(),
769 max_acceptable_error.InMicroseconds());
772 INSTANTIATE_TEST_CASE_P(
774 AnimatedContentSamplerParameterizedTest,
775 ::testing::Values(
776 // Typical frame rate content: Compositor runs at 60 Hz, capture at 30
777 // Hz, and content video animates at 30, 25, or 24 Hz.
778 Scenario(60, 30, 30),
779 Scenario(60, 30, 25),
780 Scenario(60, 30, 24),
782 // High frame rate content that leverages the Compositor's
783 // capabilities, but capture is still at 30 Hz.
784 Scenario(60, 30, 60),
785 Scenario(60, 30, 50),
786 Scenario(60, 30, 48),
788 // High frame rate content that leverages the Compositor's
789 // capabilities, and capture is also a buttery 60 Hz.
790 Scenario(60, 60, 60),
791 Scenario(60, 60, 50),
792 Scenario(60, 60, 48),
794 // High frame rate content that leverages the Compositor's
795 // capabilities, but the client has disabled HFR sampling.
796 Scenario(60, 60, 60, 30),
797 Scenario(60, 60, 50, 30),
798 Scenario(60, 60, 48, 30),
800 // On some platforms, the Compositor runs at 50 Hz.
801 Scenario(50, 30, 30),
802 Scenario(50, 30, 25),
803 Scenario(50, 30, 24),
804 Scenario(50, 30, 50),
805 Scenario(50, 30, 48),
807 // Stable, but non-standard content frame rates.
808 Scenario(60, 30, 16),
809 Scenario(60, 30, 20),
810 Scenario(60, 30, 23),
811 Scenario(60, 30, 26),
812 Scenario(60, 30, 27),
813 Scenario(60, 30, 28),
814 Scenario(60, 30, 29),
815 Scenario(60, 30, 31),
816 Scenario(60, 30, 32),
817 Scenario(60, 30, 33)));
819 } // namespace media