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 "content/browser/media/capture/animated_content_sampler.h"
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"
21 base::TimeTicks
InitialTestTimeTicks() {
22 return base::TimeTicks() + base::TimeDelta::FromSeconds(1);
27 class AnimatedContentSamplerTest
: public ::testing::Test
{
29 AnimatedContentSamplerTest() {}
30 ~AnimatedContentSamplerTest() override
{}
32 void SetUp() override
{
33 const base::TimeDelta since_epoch
=
34 InitialTestTimeTicks() - base::TimeTicks::UnixEpoch();
35 rand_seed_
= abs(static_cast<int>(since_epoch
.InMicroseconds()));
36 sampler_
.reset(new AnimatedContentSampler(GetMinCapturePeriod()));
40 // Overridden by subclass for parameterized tests.
41 virtual base::TimeDelta
GetMinCapturePeriod() const {
42 return base::TimeDelta::FromSeconds(1) / 30;
45 AnimatedContentSampler
* sampler() const {
46 return sampler_
.get();
49 int GetRandomInRange(int begin
, int end
) {
50 const int len
= end
- begin
;
51 const int rand_offset
= (len
== 0) ? 0 : (NextRandomInt() % (end
- begin
));
52 return begin
+ rand_offset
;
55 gfx::Rect
GetRandomDamageRect() {
56 return gfx::Rect(0, 0, GetRandomInRange(1, 100), GetRandomInRange(1, 100));
59 gfx::Rect
GetContentDamageRect() {
60 // This must be distinct from anything GetRandomDamageRect() could return.
61 return gfx::Rect(0, 0, 1280, 720);
64 // Directly inject an observation. Only used to test
65 // ElectMajorityDamageRect().
66 void ObserveDamageRect(const gfx::Rect
& damage_rect
) {
67 sampler_
->observations_
.push_back(
68 AnimatedContentSampler::Observation(damage_rect
, base::TimeTicks()));
71 gfx::Rect
ElectMajorityDamageRect() const {
72 return sampler_
->ElectMajorityDamageRect();
76 // Note: Not using base::RandInt() because it is horribly slow on debug
77 // builds. The following is a very simple, deterministic LCG:
79 rand_seed_
= (1103515245 * rand_seed_
+ 12345) % (1 << 31);
84 scoped_ptr
<AnimatedContentSampler
> sampler_
;
87 TEST_F(AnimatedContentSamplerTest
, ElectsNoneFromZeroDamageRects
) {
88 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
91 TEST_F(AnimatedContentSamplerTest
, ElectsMajorityFromOneDamageRect
) {
92 const gfx::Rect
the_one_rect(0, 0, 1, 1);
93 ObserveDamageRect(the_one_rect
);
94 EXPECT_EQ(the_one_rect
, ElectMajorityDamageRect());
97 TEST_F(AnimatedContentSamplerTest
, ElectsNoneFromTwoDamageRectsOfSameArea
) {
98 const gfx::Rect
one_rect(0, 0, 1, 1);
99 const gfx::Rect
another_rect(1, 1, 1, 1);
100 ObserveDamageRect(one_rect
);
101 ObserveDamageRect(another_rect
);
102 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
105 TEST_F(AnimatedContentSamplerTest
, ElectsLargerOfTwoDamageRects_1
) {
106 const gfx::Rect
one_rect(0, 0, 1, 1);
107 const gfx::Rect
another_rect(0, 0, 2, 2);
108 ObserveDamageRect(one_rect
);
109 ObserveDamageRect(another_rect
);
110 EXPECT_EQ(another_rect
, ElectMajorityDamageRect());
113 TEST_F(AnimatedContentSamplerTest
, ElectsLargerOfTwoDamageRects_2
) {
114 const gfx::Rect
one_rect(0, 0, 2, 2);
115 const gfx::Rect
another_rect(0, 0, 1, 1);
116 ObserveDamageRect(one_rect
);
117 ObserveDamageRect(another_rect
);
118 EXPECT_EQ(one_rect
, ElectMajorityDamageRect());
121 TEST_F(AnimatedContentSamplerTest
, ElectsSameAsMooreDemonstration
) {
122 // A more complex sequence (from Moore's web site): Three different Rects with
123 // the same area, but occurring a different number of times. C should win the
125 const gfx::Rect
rect_a(0, 0, 1, 4);
126 const gfx::Rect
rect_b(1, 1, 4, 1);
127 const gfx::Rect
rect_c(2, 2, 2, 2);
128 for (int i
= 0; i
< 3; ++i
)
129 ObserveDamageRect(rect_a
);
130 for (int i
= 0; i
< 2; ++i
)
131 ObserveDamageRect(rect_c
);
132 for (int i
= 0; i
< 2; ++i
)
133 ObserveDamageRect(rect_b
);
134 for (int i
= 0; i
< 3; ++i
)
135 ObserveDamageRect(rect_c
);
136 ObserveDamageRect(rect_b
);
137 for (int i
= 0; i
< 2; ++i
)
138 ObserveDamageRect(rect_c
);
139 EXPECT_EQ(rect_c
, ElectMajorityDamageRect());
142 TEST_F(AnimatedContentSamplerTest
, Elects24FpsVideoInsteadOf48FpsSpinner
) {
143 // Scenario: 24 FPS 720x480 Video versus 48 FPS 96x96 "Busy Spinner"
144 const gfx::Rect
video_rect(100, 100, 720, 480);
145 const gfx::Rect
spinner_rect(360, 0, 96, 96);
146 for (int i
= 0; i
< 100; ++i
) {
147 // |video_rect| occurs once for every two |spinner_rect|. Vary the order
148 // of events between the two:
149 ObserveDamageRect(video_rect
);
150 ObserveDamageRect(spinner_rect
);
151 ObserveDamageRect(spinner_rect
);
152 ObserveDamageRect(video_rect
);
153 ObserveDamageRect(spinner_rect
);
154 ObserveDamageRect(spinner_rect
);
155 ObserveDamageRect(spinner_rect
);
156 ObserveDamageRect(video_rect
);
157 ObserveDamageRect(spinner_rect
);
158 ObserveDamageRect(spinner_rect
);
159 ObserveDamageRect(video_rect
);
160 ObserveDamageRect(spinner_rect
);
162 EXPECT_EQ(video_rect
, ElectMajorityDamageRect());
167 // A test scenario for AnimatedContentSamplerParameterizedTest.
169 base::TimeDelta vsync_interval
; // Reflects compositor's update rate.
170 base::TimeDelta min_capture_period
; // Reflects maximum capture rate.
171 base::TimeDelta content_period
; // Reflects content animation rate.
173 Scenario(base::TimeDelta v
, base::TimeDelta m
, base::TimeDelta c
)
174 : vsync_interval(v
), min_capture_period(m
), content_period(c
) {
175 CHECK(content_period
>= vsync_interval
)
176 << "Bad test params: Impossible to animate faster than the compositor.";
180 // Value printer for Scenario.
181 ::std::ostream
& operator<<(::std::ostream
& os
, const Scenario
& s
) {
182 return os
<< "{ vsync_interval=" << s
.vsync_interval
.InMicroseconds()
183 << ", min_capture_period=" << s
.min_capture_period
.InMicroseconds()
184 << ", content_period=" << s
.content_period
.InMicroseconds()
188 base::TimeDelta
FpsAsPeriod(int frame_rate
) {
189 return base::TimeDelta::FromSeconds(1) / frame_rate
;
194 class AnimatedContentSamplerParameterizedTest
195 : public AnimatedContentSamplerTest
,
196 public ::testing::WithParamInterface
<Scenario
> {
198 AnimatedContentSamplerParameterizedTest()
199 : count_dropped_frames_(0), count_sampled_frames_(0) {}
200 virtual ~AnimatedContentSamplerParameterizedTest() {}
203 typedef std::pair
<gfx::Rect
, base::TimeTicks
> Event
;
205 base::TimeDelta
GetMinCapturePeriod() const override
{
206 return GetParam().min_capture_period
;
209 // Generate a sequence of events from the compositor pipeline. The event
210 // times will all be at compositor vsync boundaries.
211 std::vector
<Event
> GenerateEventSequence(base::TimeTicks begin
,
213 bool include_content_frame_events
,
214 bool include_random_events
) {
215 DCHECK(GetParam().content_period
>= GetParam().vsync_interval
);
216 base::TimeTicks next_content_time
= begin
- GetParam().content_period
;
217 std::vector
<Event
> events
;
218 for (base::TimeTicks compositor_time
= begin
; compositor_time
< end
;
219 compositor_time
+= GetParam().vsync_interval
) {
220 if (include_content_frame_events
&& next_content_time
< compositor_time
) {
221 events
.push_back(Event(GetContentDamageRect(), compositor_time
));
222 next_content_time
+= GetParam().content_period
;
223 } else if (include_random_events
&& GetRandomInRange(0, 1) == 0) {
224 events
.push_back(Event(GetRandomDamageRect(), compositor_time
));
228 DCHECK(!events
.empty());
232 // Feed |events| through the sampler, and detect whether the expected
233 // lock-in/out transition occurs. Also, track and measure the frame drop
234 // ratio and check it against the expected drop rate.
235 void RunEventSequence(const std::vector
<Event
> events
,
236 bool was_detecting_before
,
237 bool is_detecting_after
,
238 bool simulate_pipeline_back_pressure
) {
239 gfx::Rect first_detected_region
;
241 EXPECT_EQ(was_detecting_before
, sampler()->HasProposal());
242 bool has_detection_switched
= false;
243 ResetFrameCounters();
244 for (std::vector
<Event
>::const_iterator i
= events
.begin();
245 i
!= events
.end(); ++i
) {
246 sampler()->ConsiderPresentationEvent(i
->first
, i
->second
);
248 // Detect when the sampler locks in/out, and that it stays that way for
249 // all further iterations of this loop.
250 if (!has_detection_switched
&&
251 was_detecting_before
!= sampler()->HasProposal()) {
252 has_detection_switched
= true;
255 has_detection_switched
? is_detecting_after
: was_detecting_before
,
256 sampler()->HasProposal());
258 if (sampler()->HasProposal()) {
259 // Make sure the sampler doesn't flip-flop and keep proposing sampling
260 // based on locking into different regions.
261 if (first_detected_region
.IsEmpty()) {
262 first_detected_region
= sampler()->detected_region();
263 ASSERT_FALSE(first_detected_region
.IsEmpty());
265 EXPECT_EQ(first_detected_region
, sampler()->detected_region());
268 if (simulate_pipeline_back_pressure
&& GetRandomInRange(0, 2) == 0)
269 ClientCannotSampleFrame(*i
);
271 ClientDoesWhatSamplerProposes(*i
);
273 EXPECT_FALSE(sampler()->ShouldSample());
274 if (!simulate_pipeline_back_pressure
|| GetRandomInRange(0, 2) == 1)
275 sampler()->RecordSample(i
->second
);
278 EXPECT_EQ(is_detecting_after
, sampler()->HasProposal());
279 ExpectFrameDropRatioIsCorrect();
282 void ResetFrameCounters() {
283 count_dropped_frames_
= 0;
284 count_sampled_frames_
= 0;
287 // Keep track what the sampler is proposing, and call RecordSample() if it
288 // proposes sampling |event|.
289 void ClientDoesWhatSamplerProposes(const Event
& event
) {
290 if (sampler()->ShouldSample()) {
291 EXPECT_EQ(GetContentDamageRect(), event
.first
);
292 sampler()->RecordSample(sampler()->frame_timestamp());
293 ++count_sampled_frames_
;
294 } else if (event
.first
== GetContentDamageRect()) {
295 ++count_dropped_frames_
;
299 // RecordSample() is not called, but for testing, keep track of what the
300 // sampler is proposing for |event|.
301 void ClientCannotSampleFrame(const Event
& event
) {
302 if (sampler()->ShouldSample()) {
303 EXPECT_EQ(GetContentDamageRect(), event
.first
);
304 ++count_sampled_frames_
;
305 } else if (event
.first
== GetContentDamageRect()) {
306 ++count_dropped_frames_
;
310 // Confirm the AnimatedContentSampler is not dropping more frames than
311 // expected, given current test parameters.
312 void ExpectFrameDropRatioIsCorrect() {
313 if (count_sampled_frames_
== 0) {
314 EXPECT_EQ(0, count_dropped_frames_
);
317 const double content_framerate
=
318 1000000.0 / GetParam().content_period
.InMicroseconds();
319 const double capture_framerate
=
320 1000000.0 / GetParam().min_capture_period
.InMicroseconds();
321 const double expected_drop_rate
= std::max(
322 0.0, (content_framerate
- capture_framerate
) / capture_framerate
);
323 const double actual_drop_rate
=
324 static_cast<double>(count_dropped_frames_
) / count_sampled_frames_
;
325 EXPECT_NEAR(expected_drop_rate
, actual_drop_rate
, 0.015);
329 // These counters only include the frames with the desired content.
330 int count_dropped_frames_
;
331 int count_sampled_frames_
;
334 // Tests that the implementation locks in/out of frames containing stable
335 // animated content, whether or not random events are also simultaneously
337 TEST_P(AnimatedContentSamplerParameterizedTest
, DetectsAnimatedContent
) {
338 // |begin| refers to the start of an event sequence in terms of the
339 // Compositor's clock.
340 base::TimeTicks begin
= InitialTestTimeTicks();
342 // Provide random events and expect no lock-in.
343 base::TimeTicks end
= begin
+ base::TimeDelta::FromSeconds(5);
344 RunEventSequence(GenerateEventSequence(begin
, end
, false, true),
350 // Provide content frame events with some random events mixed-in, and expect
351 // the sampler to lock-in.
352 end
= begin
+ base::TimeDelta::FromSeconds(5);
353 RunEventSequence(GenerateEventSequence(begin
, end
, true, true),
359 // Continue providing content frame events without the random events mixed-in
360 // and expect the lock-in to hold.
361 end
= begin
+ base::TimeDelta::FromSeconds(5);
362 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
368 // Continue providing just content frame events and expect the lock-in to
369 // hold. Also simulate the capture pipeline experiencing back pressure.
370 end
= begin
+ base::TimeDelta::FromSeconds(20);
371 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
377 // Provide a half-second of random events only, and expect the lock-in to be
379 end
= begin
+ base::TimeDelta::FromMilliseconds(500);
380 RunEventSequence(GenerateEventSequence(begin
, end
, false, true),
386 // Now, go back to providing content frame events, and expect the sampler to
387 // lock-in once again.
388 end
= begin
+ base::TimeDelta::FromSeconds(5);
389 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
396 // Tests that AnimatedContentSampler won't lock in to, nor flip-flop between,
397 // two animations of the same pixel change rate. VideoCaptureOracle should
398 // revert to using the SmoothEventSampler for these kinds of situations, as
399 // there is no "right answer" as to which animation to lock into.
400 TEST_P(AnimatedContentSamplerParameterizedTest
,
401 DoesNotLockInToTwoCompetingAnimations
) {
402 // Don't test when the event stream cannot indicate two separate content
403 // animations under the current test parameters.
404 if (GetParam().content_period
< 2 * GetParam().vsync_interval
)
407 // Start the first animation and run for a bit, and expect the sampler to
409 base::TimeTicks begin
= InitialTestTimeTicks();
410 base::TimeTicks end
= begin
+ base::TimeDelta::FromSeconds(5);
411 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
417 // Now, keep the first animation and blend in an second animation of the same
418 // size and frame rate, but at a different position. This will should cause
419 // the sampler to enter an "undetected" state since it's unclear which
420 // animation should be locked into.
421 end
= begin
+ base::TimeDelta::FromSeconds(20);
422 std::vector
<Event
> first_animation_events
=
423 GenerateEventSequence(begin
, end
, true, false);
424 gfx::Rect
second_animation_rect(
425 gfx::Point(0, GetContentDamageRect().height()),
426 GetContentDamageRect().size());
427 std::vector
<Event
> both_animations_events
;
428 base::TimeDelta second_animation_offset
= GetParam().vsync_interval
;
429 for (std::vector
<Event
>::const_iterator i
= first_animation_events
.begin();
430 i
!= first_animation_events
.end(); ++i
) {
431 both_animations_events
.push_back(*i
);
432 both_animations_events
.push_back(
433 Event(second_animation_rect
, i
->second
+ second_animation_offset
));
435 RunEventSequence(both_animations_events
, true, false, false);
438 // Now, run just the first animation, and expect the sampler to lock-in once
440 end
= begin
+ base::TimeDelta::FromSeconds(5);
441 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
447 // Now, blend in the second animation again, but it has half the frame rate of
448 // the first animation and damage Rects with twice the area. This will should
449 // cause the sampler to enter an "undetected" state again. This tests that
450 // pixel-weighting is being accounted for in the sampler's logic.
451 end
= begin
+ base::TimeDelta::FromSeconds(20);
452 first_animation_events
= GenerateEventSequence(begin
, end
, true, false);
453 second_animation_rect
.set_width(second_animation_rect
.width() * 2);
454 both_animations_events
.clear();
455 bool include_second_animation_frame
= true;
456 for (std::vector
<Event
>::const_iterator i
= first_animation_events
.begin();
457 i
!= first_animation_events
.end(); ++i
) {
458 both_animations_events
.push_back(*i
);
459 if (include_second_animation_frame
) {
460 both_animations_events
.push_back(
461 Event(second_animation_rect
, i
->second
+ second_animation_offset
));
463 include_second_animation_frame
= !include_second_animation_frame
;
465 RunEventSequence(both_animations_events
, true, false, false);
469 // Tests that the frame timestamps are smooth; meaning, that when run through a
470 // simulated compositor, each frame is held displayed for the right number of
472 TEST_P(AnimatedContentSamplerParameterizedTest
, FrameTimestampsAreSmooth
) {
473 // Generate 30 seconds of animated content events, run the events through
474 // AnimatedContentSampler, and record all frame timestamps being proposed
475 // once lock-in is continuous.
476 base::TimeTicks begin
= InitialTestTimeTicks();
477 std::vector
<Event
> events
= GenerateEventSequence(
479 begin
+ base::TimeDelta::FromSeconds(20),
482 typedef std::vector
<base::TimeTicks
> Timestamps
;
483 Timestamps frame_timestamps
;
484 for (std::vector
<Event
>::const_iterator i
= events
.begin(); i
!= events
.end();
486 sampler()->ConsiderPresentationEvent(i
->first
, i
->second
);
487 if (sampler()->HasProposal()) {
488 if (sampler()->ShouldSample()) {
489 frame_timestamps
.push_back(sampler()->frame_timestamp());
490 sampler()->RecordSample(sampler()->frame_timestamp());
493 frame_timestamps
.clear(); // Reset until continuous lock-in.
496 ASSERT_LE(2u, frame_timestamps
.size());
498 // Iterate through the |frame_timestamps|, building a histogram counting the
499 // number of times each frame was displayed k times. For example, 10 frames
500 // of 30 Hz content on a 60 Hz v-sync interval should result in
501 // display_counts[2] == 10. Quit early if any one frame was obviously
502 // repeated too many times.
503 const int64 max_expected_repeats_per_frame
= 1 +
504 std::max(GetParam().min_capture_period
, GetParam().content_period
) /
505 GetParam().vsync_interval
;
506 std::vector
<size_t> display_counts(max_expected_repeats_per_frame
+ 1, 0);
507 base::TimeTicks last_present_time
= frame_timestamps
.front();
508 for (Timestamps::const_iterator i
= frame_timestamps
.begin() + 1;
509 i
!= frame_timestamps
.end(); ++i
) {
510 const size_t num_vsync_intervals
= static_cast<size_t>(
511 (*i
- last_present_time
) / GetParam().vsync_interval
);
512 ASSERT_LT(0u, num_vsync_intervals
);
513 ASSERT_GT(display_counts
.size(), num_vsync_intervals
); // Quit early.
514 ++display_counts
[num_vsync_intervals
];
515 last_present_time
+= num_vsync_intervals
* GetParam().vsync_interval
;
518 // Analyze the histogram for an expected result pattern. If the frame
519 // timestamps are smooth, there should only be one or two buckets with
520 // non-zero counts and they should be next to each other. Because the clock
521 // precision for the event_times provided to the sampler is very granular
522 // (i.e., the vsync_interval), it's okay if other buckets have a tiny "stray"
523 // count in this test.
524 size_t highest_count
= 0;
525 size_t second_highest_count
= 0;
526 for (size_t repeats
= 1; repeats
< display_counts
.size(); ++repeats
) {
527 DVLOG(1) << "display_counts[" << repeats
<< "] is "
528 << display_counts
[repeats
];
529 if (display_counts
[repeats
] >= highest_count
) {
530 second_highest_count
= highest_count
;
531 highest_count
= display_counts
[repeats
];
532 } else if (display_counts
[repeats
] > second_highest_count
) {
533 second_highest_count
= display_counts
[repeats
];
536 size_t stray_count_remaining
=
537 (frame_timestamps
.size() - 1) - (highest_count
+ second_highest_count
);
538 // Expect no more than 0.75% of frames fall outside the two main buckets.
539 EXPECT_GT(frame_timestamps
.size() * 75 / 10000, stray_count_remaining
);
540 for (size_t repeats
= 1; repeats
< display_counts
.size() - 1; ++repeats
) {
541 if (display_counts
[repeats
] == highest_count
) {
542 EXPECT_EQ(second_highest_count
, display_counts
[repeats
+ 1]);
544 } else if (display_counts
[repeats
] == second_highest_count
) {
545 EXPECT_EQ(highest_count
, display_counts
[repeats
+ 1]);
548 EXPECT_GE(stray_count_remaining
, display_counts
[repeats
]);
549 stray_count_remaining
-= display_counts
[repeats
];
554 // Tests that frame timestamps are "lightly pushed" back towards the original
555 // presentation event times, which tells us the AnimatedContentSampler can
556 // account for sources of timestamp drift and correct the drift.
557 TEST_P(AnimatedContentSamplerParameterizedTest
,
558 FrameTimestampsConvergeTowardsEventTimes
) {
559 const int max_drift_increment_millis
= 3;
561 // Generate a full minute of events.
562 const base::TimeTicks begin
= InitialTestTimeTicks();
563 const base::TimeTicks end
= begin
+ base::TimeDelta::FromMinutes(1);
564 std::vector
<Event
> events
= GenerateEventSequence(begin
, end
, true, false);
566 // Modify the event sequence so that 1-3 ms of additional drift is suddenly
567 // present every 100 events. This is meant to simulate that, external to
568 // AnimatedContentSampler, the video hardware vsync timebase is being
569 // refreshed and is showing severe drift from the system clock.
570 base::TimeDelta accumulated_drift
;
571 for (size_t i
= 1; i
< events
.size(); ++i
) {
573 accumulated_drift
+= base::TimeDelta::FromMilliseconds(
574 GetRandomInRange(1, max_drift_increment_millis
+ 1));
576 events
[i
].second
+= accumulated_drift
;
579 // Run all the events through the sampler and track the last rewritten frame
581 base::TimeTicks last_frame_timestamp
;
582 for (std::vector
<Event
>::const_iterator i
= events
.begin(); i
!= events
.end();
584 sampler()->ConsiderPresentationEvent(i
->first
, i
->second
);
585 if (sampler()->ShouldSample())
586 last_frame_timestamp
= sampler()->frame_timestamp();
589 // If drift was accounted for, the |last_frame_timestamp| should be close to
590 // the last event's timestamp.
591 const base::TimeDelta total_error
=
592 events
.back().second
- last_frame_timestamp
;
593 const base::TimeDelta max_acceptable_error
= GetParam().min_capture_period
+
594 base::TimeDelta::FromMilliseconds(max_drift_increment_millis
);
596 total_error
.InMicroseconds(),
597 max_acceptable_error
.InMicroseconds());
600 INSTANTIATE_TEST_CASE_P(
602 AnimatedContentSamplerParameterizedTest
,
604 // Typical frame rate content: Compositor runs at 60 Hz, capture at 30
605 // Hz, and content video animates at 30, 25, or 24 Hz.
606 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(30)),
607 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(25)),
608 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(24)),
610 // High frame rate content that leverages the Compositor's
611 // capabilities, but capture is still at 30 Hz.
612 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(60)),
613 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(50)),
614 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(48)),
616 // High frame rate content that leverages the Compositor's
617 // capabilities, and capture is also a buttery 60 Hz.
618 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(60)),
619 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(50)),
620 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(48)),
622 // On some platforms, the Compositor runs at 50 Hz.
623 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(30)),
624 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(25)),
625 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(24)),
626 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(50)),
627 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(48)),
629 // Stable, but non-standard content frame rates.
630 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(16)),
631 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(20)),
632 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(23)),
633 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(26)),
634 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(27)),
635 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(28)),
636 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(29)),
637 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(31)),
638 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(32)),
639 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(33))));
641 } // namespace content