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