1 // Copyright (c) 2013 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/video_capture_oracle.h"
11 #include "base/logging.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/time/time.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "ui/gfx/geometry/rect.h"
20 bool AddEventAndConsiderSampling(SmoothEventSampler
* sampler
,
21 base::TimeTicks event_time
) {
22 sampler
->ConsiderPresentationEvent(event_time
);
23 return sampler
->ShouldSample();
26 void SteadyStateSampleAndAdvance(base::TimeDelta vsync
,
27 SmoothEventSampler
* sampler
,
29 ASSERT_TRUE(AddEventAndConsiderSampling(sampler
, *t
));
30 ASSERT_TRUE(sampler
->HasUnrecordedEvent());
31 sampler
->RecordSample();
32 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
33 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
35 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
38 void SteadyStateNoSampleAndAdvance(base::TimeDelta vsync
,
39 SmoothEventSampler
* sampler
,
41 ASSERT_FALSE(AddEventAndConsiderSampling(sampler
, *t
));
42 ASSERT_TRUE(sampler
->HasUnrecordedEvent());
43 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
45 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
48 base::TimeTicks
InitialTestTimeTicks() {
49 return base::TimeTicks() + base::TimeDelta::FromSeconds(1);
52 void TestRedundantCaptureStrategy(base::TimeDelta capture_period
,
53 int redundant_capture_goal
,
54 SmoothEventSampler
* sampler
,
56 // Before any events have been considered, we're overdue for sampling.
57 ASSERT_TRUE(sampler
->IsOverdueForSamplingAt(*t
));
59 // Consider the first event. We want to sample that.
60 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
61 ASSERT_TRUE(AddEventAndConsiderSampling(sampler
, *t
));
62 ASSERT_TRUE(sampler
->HasUnrecordedEvent());
63 sampler
->RecordSample();
64 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
66 // After more than 250 ms has passed without considering an event, we should
67 // repeatedly be overdue for sampling. However, once the redundant capture
68 // goal is achieved, we should no longer be overdue for sampling.
69 *t
+= base::TimeDelta::FromMilliseconds(250);
70 for (int i
= 0; i
< redundant_capture_goal
; i
++) {
71 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
72 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
73 ASSERT_TRUE(sampler
->IsOverdueForSamplingAt(*t
))
74 << "Should sample until redundant capture goal is hit";
75 sampler
->RecordSample();
76 *t
+= capture_period
; // Timer fires once every capture period.
78 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
))
79 << "Should not be overdue once redundant capture goal achieved.";
84 // 60Hz sampled at 30Hz should produce 30Hz. In addition, this test contains
85 // much more comprehensive before/after/edge-case scenarios than the others.
86 TEST(SmoothEventSamplerTest
, Sample60HertzAt30Hertz
) {
87 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
88 const int redundant_capture_goal
= 200;
89 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 60;
91 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
92 base::TimeTicks t
= InitialTestTimeTicks();
94 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
97 // Steady state, we should capture every other vsync, indefinitely.
98 for (int i
= 0; i
< 100; i
++) {
99 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
100 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
101 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
104 // Now pretend we're limited by backpressure in the pipeline. In this scenario
105 // case we are adding events but not sampling them.
106 for (int i
= 0; i
< 20; i
++) {
107 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
108 ASSERT_EQ(i
>= 14, sampler
.IsOverdueForSamplingAt(t
));
109 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
110 ASSERT_TRUE(sampler
.HasUnrecordedEvent());
114 // Now suppose we can sample again. We should be back in the steady state,
115 // but at a different phase.
116 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
117 for (int i
= 0; i
< 100; i
++) {
118 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
119 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
120 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
124 // 50Hz sampled at 30Hz should produce a sequence where some frames are skipped.
125 TEST(SmoothEventSamplerTest
, Sample50HertzAt30Hertz
) {
126 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
127 const int redundant_capture_goal
= 2;
128 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 50;
130 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
131 base::TimeTicks t
= InitialTestTimeTicks();
133 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
136 // Steady state, we should capture 1st, 2nd and 4th frames out of every five
137 // frames, indefinitely.
138 for (int i
= 0; i
< 100; i
++) {
139 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
140 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
141 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
142 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
143 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
144 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
147 // Now pretend we're limited by backpressure in the pipeline. In this scenario
148 // case we are adding events but not sampling them.
149 for (int i
= 0; i
< 20; i
++) {
150 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
151 ASSERT_EQ(i
>= 11, sampler
.IsOverdueForSamplingAt(t
));
152 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
156 // Now suppose we can sample again. We should be back in the steady state
158 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
159 for (int i
= 0; i
< 100; i
++) {
160 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
161 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
162 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
163 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
164 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
165 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
169 // 75Hz sampled at 30Hz should produce a sequence where some frames are skipped.
170 TEST(SmoothEventSamplerTest
, Sample75HertzAt30Hertz
) {
171 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
172 const int redundant_capture_goal
= 32;
173 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 75;
175 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
176 base::TimeTicks t
= InitialTestTimeTicks();
178 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
181 // Steady state, we should capture 1st and 3rd frames out of every five
182 // frames, indefinitely.
183 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
184 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
185 for (int i
= 0; i
< 100; i
++) {
186 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
187 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
188 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
189 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
190 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
191 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
194 // Now pretend we're limited by backpressure in the pipeline. In this scenario
195 // case we are adding events but not sampling them.
196 for (int i
= 0; i
< 20; i
++) {
197 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
198 ASSERT_EQ(i
>= 16, sampler
.IsOverdueForSamplingAt(t
));
199 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
203 // Now suppose we can sample again. We capture the next frame, and not the one
204 // after that, and then we're back in the steady state again.
205 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
206 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
207 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
208 for (int i
= 0; i
< 100; i
++) {
209 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
210 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
211 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
212 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
213 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
214 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
218 // 30Hz sampled at 30Hz should produce 30Hz.
219 TEST(SmoothEventSamplerTest
, Sample30HertzAt30Hertz
) {
220 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
221 const int redundant_capture_goal
= 1;
222 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 30;
224 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
225 base::TimeTicks t
= InitialTestTimeTicks();
227 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
230 // Steady state, we should capture every vsync, indefinitely.
231 for (int i
= 0; i
< 200; i
++) {
232 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
233 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
236 // Now pretend we're limited by backpressure in the pipeline. In this scenario
237 // case we are adding events but not sampling them.
238 for (int i
= 0; i
< 10; i
++) {
239 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
240 ASSERT_EQ(i
>= 7, sampler
.IsOverdueForSamplingAt(t
));
241 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
245 // Now suppose we can sample again. We should be back in the steady state.
246 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
247 for (int i
= 0; i
< 100; i
++) {
248 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
249 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
253 // 24Hz sampled at 30Hz should produce 24Hz.
254 TEST(SmoothEventSamplerTest
, Sample24HertzAt30Hertz
) {
255 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
256 const int redundant_capture_goal
= 333;
257 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 24;
259 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
260 base::TimeTicks t
= InitialTestTimeTicks();
262 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
265 // Steady state, we should capture every vsync, indefinitely.
266 for (int i
= 0; i
< 200; i
++) {
267 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
268 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
271 // Now pretend we're limited by backpressure in the pipeline. In this scenario
272 // case we are adding events but not sampling them.
273 for (int i
= 0; i
< 10; i
++) {
274 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
275 ASSERT_EQ(i
>= 6, sampler
.IsOverdueForSamplingAt(t
));
276 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
280 // Now suppose we can sample again. We should be back in the steady state.
281 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
282 for (int i
= 0; i
< 100; i
++) {
283 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
284 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
288 TEST(SmoothEventSamplerTest
, DoubleDrawAtOneTimeStillDirties
) {
289 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
290 const base::TimeDelta overdue_period
= base::TimeDelta::FromSeconds(1);
292 SmoothEventSampler
sampler(capture_period
, 1);
293 base::TimeTicks t
= InitialTestTimeTicks();
295 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
296 sampler
.RecordSample();
297 ASSERT_FALSE(sampler
.IsOverdueForSamplingAt(t
))
298 << "Sampled last event; should not be dirty.";
301 // Now simulate 2 events with the same clock value.
302 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
303 sampler
.RecordSample();
304 ASSERT_FALSE(AddEventAndConsiderSampling(&sampler
, t
))
305 << "Two events at same time -- expected second not to be sampled.";
306 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
+ overdue_period
))
307 << "Second event should dirty the capture state.";
308 sampler
.RecordSample();
309 ASSERT_FALSE(sampler
.IsOverdueForSamplingAt(t
+ overdue_period
));
319 void ReplayCheckingSamplerDecisions(const DataPoint
* data_points
,
320 size_t num_data_points
,
321 SmoothEventSampler
* sampler
) {
322 base::TimeTicks t
= InitialTestTimeTicks();
323 for (size_t i
= 0; i
< num_data_points
; ++i
) {
324 t
+= base::TimeDelta::FromMicroseconds(
325 static_cast<int64
>(data_points
[i
].increment_ms
* 1000));
326 ASSERT_EQ(data_points
[i
].should_capture
,
327 AddEventAndConsiderSampling(sampler
, t
))
328 << "at data_points[" << i
<< ']';
329 if (data_points
[i
].should_capture
)
330 sampler
->RecordSample();
336 TEST(SmoothEventSamplerTest
, DrawingAt24FpsWith60HzVsyncSampledAt30Hertz
) {
337 // Actual capturing of timing data: Initial instability as a 24 FPS video was
338 // started from a still screen, then clearly followed by steady-state.
339 static const DataPoint data_points
[] = {
340 { true, 1437.93 }, { true, 150.484 }, { true, 217.362 }, { true, 50.161 },
341 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 66.88 },
342 { true, 50.161 }, { false, 0 }, { false, 0 }, { true, 50.16 },
343 { true, 33.441 }, { true, 16.72 }, { false, 16.72 }, { true, 117.041 },
344 { true, 16.72 }, { false, 16.72 }, { true, 50.161 }, { true, 50.16 },
345 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 16.72 },
346 { false, 0 }, { true, 50.161 }, { false, 0 }, { true, 33.44 },
347 { true, 16.72 }, { false, 16.721 }, { true, 66.881 }, { false, 0 },
348 { true, 33.441 }, { true, 16.72 }, { true, 50.16 }, { true, 16.72 },
349 { false, 16.721 }, { true, 50.161 }, { true, 50.16 }, { false, 0 },
350 { true, 33.441 }, { true, 50.337 }, { true, 50.183 }, { true, 16.722 },
351 { true, 50.161 }, { true, 33.441 }, { true, 50.16 }, { true, 33.441 },
352 { true, 50.16 }, { true, 33.441 }, { true, 50.16 }, { true, 33.44 },
353 { true, 50.161 }, { true, 50.16 }, { true, 33.44 }, { true, 33.441 },
354 { true, 50.16 }, { true, 50.161 }, { true, 33.44 }, { true, 33.441 },
355 { true, 50.16 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 },
356 { true, 50.161 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 },
357 { true, 83.601 }, { true, 16.72 }, { true, 33.44 }, { false, 0 }
360 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
361 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
364 TEST(SmoothEventSamplerTest
, DrawingAt30FpsWith60HzVsyncSampledAt30Hertz
) {
365 // Actual capturing of timing data: Initial instability as a 30 FPS video was
366 // started from a still screen, then followed by steady-state. Drawing
367 // framerate from the video rendering was a bit volatile, but averaged 30 FPS.
368 static const DataPoint data_points
[] = {
369 { true, 2407.69 }, { true, 16.733 }, { true, 217.362 }, { true, 33.441 },
370 { true, 33.44 }, { true, 33.44 }, { true, 33.441 }, { true, 33.44 },
371 { true, 33.44 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 },
372 { true, 16.721 }, { true, 33.44 }, { false, 0 }, { true, 50.161 },
373 { true, 50.16 }, { false, 0 }, { true, 50.161 }, { true, 33.44 },
374 { true, 16.72 }, { false, 0 }, { false, 16.72 }, { true, 66.881 },
375 { false, 0 }, { true, 33.44 }, { true, 16.72 }, { true, 50.161 },
376 { false, 0 }, { true, 33.538 }, { true, 33.526 }, { true, 33.447 },
377 { true, 33.445 }, { true, 33.441 }, { true, 16.721 }, { true, 33.44 },
378 { true, 33.44 }, { true, 50.161 }, { true, 16.72 }, { true, 33.44 },
379 { true, 33.441 }, { true, 33.44 }, { false, 0 }, { false, 16.72 },
380 { true, 66.881 }, { true, 16.72 }, { false, 16.72 }, { true, 50.16 },
381 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 33.44 },
382 { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { false, 0 },
383 { true, 33.44 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 },
384 { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 66.88 },
385 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 },
386 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 },
387 { true, 16.72 }, { true, 50.161 }, { false, 0 }, { true, 50.16 },
388 { false, 0.001 }, { true, 16.721 }, { true, 66.88 }, { true, 33.44 },
389 { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 },
390 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 66.881 },
391 { true, 33.44 }, { true, 16.72 }, { true, 33.441 }, { false, 16.72 },
392 { true, 66.88 }, { true, 16.721 }, { true, 50.16 }, { true, 33.44 },
393 { true, 16.72 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 }
396 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
397 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
400 TEST(SmoothEventSamplerTest
, DrawingAt60FpsWith60HzVsyncSampledAt30Hertz
) {
401 // Actual capturing of timing data: WebGL Acquarium demo
402 // (http://webglsamples.googlecode.com/hg/aquarium/aquarium.html) which ran
403 // between 55-60 FPS in the steady-state.
404 static const DataPoint data_points
[] = {
405 { true, 16.72 }, { true, 16.72 }, { true, 4163.29 }, { true, 50.193 },
406 { true, 117.041 }, { true, 50.161 }, { true, 50.16 }, { true, 33.441 },
407 { true, 50.16 }, { true, 33.44 }, { false, 0 }, { false, 0 },
408 { true, 50.161 }, { true, 83.601 }, { true, 50.16 }, { true, 16.72 },
409 { true, 33.441 }, { false, 16.72 }, { true, 50.16 }, { true, 16.72 },
410 { false, 0.001 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 },
411 { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
412 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 },
413 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 },
414 { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 },
415 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
416 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 33.44 },
417 { false, 0 }, { true, 16.721 }, { true, 50.161 }, { false, 0 },
418 { true, 33.44 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
419 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 },
420 { true, 50.16 }, { false, 0 }, { true, 16.721 }, { true, 33.44 },
421 { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 },
422 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 },
423 { false, 0 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 },
424 { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
425 { true, 33.44 }, { false, 0 }, { true, 33.44 }, { true, 33.441 },
426 { false, 0 }, { true, 33.44 }, { true, 33.441 }, { false, 0 },
427 { true, 33.44 }, { false, 0 }, { true, 33.44 }, { false, 16.72 },
428 { true, 16.721 }, { true, 50.161 }, { false, 0 }, { true, 16.72 },
429 { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 33.44 },
430 { true, 33.44 }, { false, 0 }, { true, 33.441 }, { false, 16.72 },
431 { true, 16.72 }, { true, 50.16 }, { false, 0 }, { true, 16.72 },
432 { true, 33.441 }, { false, 0 }, { true, 33.44 }, { false, 16.72 },
433 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 50.161 },
434 { false, 0 }, { true, 16.72 }, { true, 33.44 }, { false, 0 },
435 { true, 33.441 }, { false, 16.72 }, { true, 16.72 }, { true, 50.16 }
438 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
439 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
442 class AnimatedContentSamplerTest
: public ::testing::Test
{
444 AnimatedContentSamplerTest() {}
445 ~AnimatedContentSamplerTest() override
{}
447 void SetUp() override
{
448 const base::TimeDelta since_epoch
=
449 InitialTestTimeTicks() - base::TimeTicks::UnixEpoch();
450 rand_seed_
= abs(static_cast<int>(since_epoch
.InMicroseconds()));
451 sampler_
.reset(new AnimatedContentSampler(GetMinCapturePeriod()));
455 // Overridden by subclass for parameterized tests.
456 virtual base::TimeDelta
GetMinCapturePeriod() const {
457 return base::TimeDelta::FromSeconds(1) / 30;
460 AnimatedContentSampler
* sampler() const {
461 return sampler_
.get();
464 int GetRandomInRange(int begin
, int end
) {
465 const int len
= end
- begin
;
466 const int rand_offset
= (len
== 0) ? 0 : (NextRandomInt() % (end
- begin
));
467 return begin
+ rand_offset
;
470 gfx::Rect
GetRandomDamageRect() {
471 return gfx::Rect(0, 0, GetRandomInRange(1, 100), GetRandomInRange(1, 100));
474 gfx::Rect
GetContentDamageRect() {
475 // This must be distinct from anything GetRandomDamageRect() could return.
476 return gfx::Rect(0, 0, 1280, 720);
479 // Directly inject an observation. Only used to test
480 // ElectMajorityDamageRect().
481 void ObserveDamageRect(const gfx::Rect
& damage_rect
) {
482 sampler_
->observations_
.push_back(
483 AnimatedContentSampler::Observation(damage_rect
, base::TimeTicks()));
486 gfx::Rect
ElectMajorityDamageRect() const {
487 return sampler_
->ElectMajorityDamageRect();
491 // Note: Not using base::RandInt() because it is horribly slow on debug
492 // builds. The following is a very simple, deterministic LCG:
493 int NextRandomInt() {
494 rand_seed_
= (1103515245 * rand_seed_
+ 12345) % (1 << 31);
499 scoped_ptr
<AnimatedContentSampler
> sampler_
;
502 TEST_F(AnimatedContentSamplerTest
, ElectsNoneFromZeroDamageRects
) {
503 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
506 TEST_F(AnimatedContentSamplerTest
, ElectsMajorityFromOneDamageRect
) {
507 const gfx::Rect
the_one_rect(0, 0, 1, 1);
508 ObserveDamageRect(the_one_rect
);
509 EXPECT_EQ(the_one_rect
, ElectMajorityDamageRect());
512 TEST_F(AnimatedContentSamplerTest
, ElectsNoneFromTwoDamageRectsOfSameArea
) {
513 const gfx::Rect
one_rect(0, 0, 1, 1);
514 const gfx::Rect
another_rect(1, 1, 1, 1);
515 ObserveDamageRect(one_rect
);
516 ObserveDamageRect(another_rect
);
517 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
520 TEST_F(AnimatedContentSamplerTest
, ElectsLargerOfTwoDamageRects_1
) {
521 const gfx::Rect
one_rect(0, 0, 1, 1);
522 const gfx::Rect
another_rect(0, 0, 2, 2);
523 ObserveDamageRect(one_rect
);
524 ObserveDamageRect(another_rect
);
525 EXPECT_EQ(another_rect
, ElectMajorityDamageRect());
528 TEST_F(AnimatedContentSamplerTest
, ElectsLargerOfTwoDamageRects_2
) {
529 const gfx::Rect
one_rect(0, 0, 2, 2);
530 const gfx::Rect
another_rect(0, 0, 1, 1);
531 ObserveDamageRect(one_rect
);
532 ObserveDamageRect(another_rect
);
533 EXPECT_EQ(one_rect
, ElectMajorityDamageRect());
536 TEST_F(AnimatedContentSamplerTest
, ElectsSameAsMooreDemonstration
) {
537 // A more complex sequence (from Moore's web site): Three different Rects with
538 // the same area, but occurring a different number of times. C should win the
540 const gfx::Rect
rect_a(0, 0, 1, 4);
541 const gfx::Rect
rect_b(1, 1, 4, 1);
542 const gfx::Rect
rect_c(2, 2, 2, 2);
543 for (int i
= 0; i
< 3; ++i
)
544 ObserveDamageRect(rect_a
);
545 for (int i
= 0; i
< 2; ++i
)
546 ObserveDamageRect(rect_c
);
547 for (int i
= 0; i
< 2; ++i
)
548 ObserveDamageRect(rect_b
);
549 for (int i
= 0; i
< 3; ++i
)
550 ObserveDamageRect(rect_c
);
551 ObserveDamageRect(rect_b
);
552 for (int i
= 0; i
< 2; ++i
)
553 ObserveDamageRect(rect_c
);
554 EXPECT_EQ(rect_c
, ElectMajorityDamageRect());
557 TEST_F(AnimatedContentSamplerTest
, Elects24FpsVideoInsteadOf48FpsSpinner
) {
558 // Scenario: 24 FPS 720x480 Video versus 48 FPS 96x96 "Busy Spinner"
559 const gfx::Rect
video_rect(100, 100, 720, 480);
560 const gfx::Rect
spinner_rect(360, 0, 96, 96);
561 for (int i
= 0; i
< 100; ++i
) {
562 // |video_rect| occurs once for every two |spinner_rect|. Vary the order
563 // of events between the two:
564 ObserveDamageRect(video_rect
);
565 ObserveDamageRect(spinner_rect
);
566 ObserveDamageRect(spinner_rect
);
567 ObserveDamageRect(video_rect
);
568 ObserveDamageRect(spinner_rect
);
569 ObserveDamageRect(spinner_rect
);
570 ObserveDamageRect(spinner_rect
);
571 ObserveDamageRect(video_rect
);
572 ObserveDamageRect(spinner_rect
);
573 ObserveDamageRect(spinner_rect
);
574 ObserveDamageRect(video_rect
);
575 ObserveDamageRect(spinner_rect
);
577 EXPECT_EQ(video_rect
, ElectMajorityDamageRect());
582 // A test scenario for AnimatedContentSamplerParameterizedTest.
584 base::TimeDelta vsync_interval
; // Reflects compositor's update rate.
585 base::TimeDelta min_capture_period
; // Reflects maximum capture rate.
586 base::TimeDelta content_period
; // Reflects content animation rate.
588 Scenario(base::TimeDelta v
, base::TimeDelta m
, base::TimeDelta c
)
589 : vsync_interval(v
), min_capture_period(m
), content_period(c
) {
590 CHECK(content_period
>= vsync_interval
)
591 << "Bad test params: Impossible to animate faster than the compositor.";
595 // Value printer for Scenario.
596 ::std::ostream
& operator<<(::std::ostream
& os
, const Scenario
& s
) {
597 return os
<< "{ vsync_interval=" << s
.vsync_interval
.InMicroseconds()
598 << ", min_capture_period=" << s
.min_capture_period
.InMicroseconds()
599 << ", content_period=" << s
.content_period
.InMicroseconds()
603 base::TimeDelta
FpsAsPeriod(int frame_rate
) {
604 return base::TimeDelta::FromSeconds(1) / frame_rate
;
609 class AnimatedContentSamplerParameterizedTest
610 : public AnimatedContentSamplerTest
,
611 public ::testing::WithParamInterface
<Scenario
> {
613 AnimatedContentSamplerParameterizedTest()
614 : count_dropped_frames_(0), count_sampled_frames_(0) {}
615 virtual ~AnimatedContentSamplerParameterizedTest() {}
618 typedef std::pair
<gfx::Rect
, base::TimeTicks
> Event
;
620 base::TimeDelta
GetMinCapturePeriod() const override
{
621 return GetParam().min_capture_period
;
624 // Generate a sequence of events from the compositor pipeline. The event
625 // times will all be at compositor vsync boundaries.
626 std::vector
<Event
> GenerateEventSequence(base::TimeTicks begin
,
628 bool include_content_frame_events
,
629 bool include_random_events
) {
630 DCHECK(GetParam().content_period
>= GetParam().vsync_interval
);
631 base::TimeTicks next_content_time
= begin
- GetParam().content_period
;
632 std::vector
<Event
> events
;
633 for (base::TimeTicks compositor_time
= begin
; compositor_time
< end
;
634 compositor_time
+= GetParam().vsync_interval
) {
635 if (include_content_frame_events
&& next_content_time
< compositor_time
) {
636 events
.push_back(Event(GetContentDamageRect(), compositor_time
));
637 next_content_time
+= GetParam().content_period
;
638 } else if (include_random_events
&& GetRandomInRange(0, 1) == 0) {
639 events
.push_back(Event(GetRandomDamageRect(), compositor_time
));
643 DCHECK(!events
.empty());
647 // Feed |events| through the sampler, and detect whether the expected
648 // lock-in/out transition occurs. Also, track and measure the frame drop
649 // ratio and check it against the expected drop rate.
650 void RunEventSequence(const std::vector
<Event
> events
,
651 bool was_detecting_before
,
652 bool is_detecting_after
,
653 bool simulate_pipeline_back_pressure
) {
654 gfx::Rect first_detected_region
;
656 EXPECT_EQ(was_detecting_before
, sampler()->HasProposal());
657 bool has_detection_switched
= false;
658 ResetFrameCounters();
659 for (std::vector
<Event
>::const_iterator i
= events
.begin();
660 i
!= events
.end(); ++i
) {
661 sampler()->ConsiderPresentationEvent(i
->first
, i
->second
);
663 // Detect when the sampler locks in/out, and that it stays that way for
664 // all further iterations of this loop.
665 if (!has_detection_switched
&&
666 was_detecting_before
!= sampler()->HasProposal()) {
667 has_detection_switched
= true;
670 has_detection_switched
? is_detecting_after
: was_detecting_before
,
671 sampler()->HasProposal());
673 if (sampler()->HasProposal()) {
674 // Make sure the sampler doesn't flip-flop and keep proposing sampling
675 // based on locking into different regions.
676 if (first_detected_region
.IsEmpty()) {
677 first_detected_region
= sampler()->detected_region();
678 ASSERT_FALSE(first_detected_region
.IsEmpty());
680 EXPECT_EQ(first_detected_region
, sampler()->detected_region());
683 if (simulate_pipeline_back_pressure
&& GetRandomInRange(0, 2) == 0)
684 ClientCannotSampleFrame(*i
);
686 ClientDoesWhatSamplerProposes(*i
);
688 EXPECT_FALSE(sampler()->ShouldSample());
689 if (!simulate_pipeline_back_pressure
|| GetRandomInRange(0, 2) == 1)
690 sampler()->RecordSample(i
->second
);
693 EXPECT_EQ(is_detecting_after
, sampler()->HasProposal());
694 ExpectFrameDropRatioIsCorrect();
697 void ResetFrameCounters() {
698 count_dropped_frames_
= 0;
699 count_sampled_frames_
= 0;
702 // Keep track what the sampler is proposing, and call RecordSample() if it
703 // proposes sampling |event|.
704 void ClientDoesWhatSamplerProposes(const Event
& event
) {
705 if (sampler()->ShouldSample()) {
706 EXPECT_EQ(GetContentDamageRect(), event
.first
);
707 sampler()->RecordSample(sampler()->frame_timestamp());
708 ++count_sampled_frames_
;
709 } else if (event
.first
== GetContentDamageRect()) {
710 ++count_dropped_frames_
;
714 // RecordSample() is not called, but for testing, keep track of what the
715 // sampler is proposing for |event|.
716 void ClientCannotSampleFrame(const Event
& event
) {
717 if (sampler()->ShouldSample()) {
718 EXPECT_EQ(GetContentDamageRect(), event
.first
);
719 ++count_sampled_frames_
;
720 } else if (event
.first
== GetContentDamageRect()) {
721 ++count_dropped_frames_
;
725 // Confirm the AnimatedContentSampler is not dropping more frames than
726 // expected, given current test parameters.
727 void ExpectFrameDropRatioIsCorrect() {
728 if (count_sampled_frames_
== 0) {
729 EXPECT_EQ(0, count_dropped_frames_
);
732 const double content_framerate
=
733 1000000.0 / GetParam().content_period
.InMicroseconds();
734 const double capture_framerate
=
735 1000000.0 / GetParam().min_capture_period
.InMicroseconds();
736 const double expected_drop_rate
= std::max(
737 0.0, (content_framerate
- capture_framerate
) / capture_framerate
);
738 const double actual_drop_rate
=
739 static_cast<double>(count_dropped_frames_
) / count_sampled_frames_
;
740 EXPECT_NEAR(expected_drop_rate
, actual_drop_rate
, 0.015);
744 // These counters only include the frames with the desired content.
745 int count_dropped_frames_
;
746 int count_sampled_frames_
;
749 // Tests that the implementation locks in/out of frames containing stable
750 // animated content, whether or not random events are also simultaneously
752 TEST_P(AnimatedContentSamplerParameterizedTest
, DetectsAnimatedContent
) {
753 // |begin| refers to the start of an event sequence in terms of the
754 // Compositor's clock.
755 base::TimeTicks begin
= InitialTestTimeTicks();
757 // Provide random events and expect no lock-in.
758 base::TimeTicks end
= begin
+ base::TimeDelta::FromSeconds(5);
759 RunEventSequence(GenerateEventSequence(begin
, end
, false, true),
765 // Provide content frame events with some random events mixed-in, and expect
766 // the sampler to lock-in.
767 end
= begin
+ base::TimeDelta::FromSeconds(5);
768 RunEventSequence(GenerateEventSequence(begin
, end
, true, true),
774 // Continue providing content frame events without the random events mixed-in
775 // and expect the lock-in to hold.
776 end
= begin
+ base::TimeDelta::FromSeconds(5);
777 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
783 // Continue providing just content frame events and expect the lock-in to
784 // hold. Also simulate the capture pipeline experiencing back pressure.
785 end
= begin
+ base::TimeDelta::FromSeconds(20);
786 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
792 // Provide a half-second of random events only, and expect the lock-in to be
794 end
= begin
+ base::TimeDelta::FromMilliseconds(500);
795 RunEventSequence(GenerateEventSequence(begin
, end
, false, true),
801 // Now, go back to providing content frame events, and expect the sampler to
802 // lock-in once again.
803 end
= begin
+ base::TimeDelta::FromSeconds(5);
804 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
811 // Tests that AnimatedContentSampler won't lock in to, nor flip-flop between,
812 // two animations of the same pixel change rate. VideoCaptureOracle should
813 // revert to using the SmoothEventSampler for these kinds of situations, as
814 // there is no "right answer" as to which animation to lock into.
815 TEST_P(AnimatedContentSamplerParameterizedTest
,
816 DoesNotLockInToTwoCompetingAnimations
) {
817 // Don't test when the event stream cannot indicate two separate content
818 // animations under the current test parameters.
819 if (GetParam().content_period
< 2 * GetParam().vsync_interval
)
822 // Start the first animation and run for a bit, and expect the sampler to
824 base::TimeTicks begin
= InitialTestTimeTicks();
825 base::TimeTicks end
= begin
+ base::TimeDelta::FromSeconds(5);
826 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
832 // Now, keep the first animation and blend in an second animation of the same
833 // size and frame rate, but at a different position. This will should cause
834 // the sampler to enter an "undetected" state since it's unclear which
835 // animation should be locked into.
836 end
= begin
+ base::TimeDelta::FromSeconds(20);
837 std::vector
<Event
> first_animation_events
=
838 GenerateEventSequence(begin
, end
, true, false);
839 gfx::Rect
second_animation_rect(
840 gfx::Point(0, GetContentDamageRect().height()),
841 GetContentDamageRect().size());
842 std::vector
<Event
> both_animations_events
;
843 base::TimeDelta second_animation_offset
= GetParam().vsync_interval
;
844 for (std::vector
<Event
>::const_iterator i
= first_animation_events
.begin();
845 i
!= first_animation_events
.end(); ++i
) {
846 both_animations_events
.push_back(*i
);
847 both_animations_events
.push_back(
848 Event(second_animation_rect
, i
->second
+ second_animation_offset
));
850 RunEventSequence(both_animations_events
, true, false, false);
853 // Now, run just the first animation, and expect the sampler to lock-in once
855 end
= begin
+ base::TimeDelta::FromSeconds(5);
856 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
862 // Now, blend in the second animation again, but it has half the frame rate of
863 // the first animation and damage Rects with twice the area. This will should
864 // cause the sampler to enter an "undetected" state again. This tests that
865 // pixel-weighting is being accounted for in the sampler's logic.
866 end
= begin
+ base::TimeDelta::FromSeconds(20);
867 first_animation_events
= GenerateEventSequence(begin
, end
, true, false);
868 second_animation_rect
.set_width(second_animation_rect
.width() * 2);
869 both_animations_events
.clear();
870 bool include_second_animation_frame
= true;
871 for (std::vector
<Event
>::const_iterator i
= first_animation_events
.begin();
872 i
!= first_animation_events
.end(); ++i
) {
873 both_animations_events
.push_back(*i
);
874 if (include_second_animation_frame
) {
875 both_animations_events
.push_back(
876 Event(second_animation_rect
, i
->second
+ second_animation_offset
));
878 include_second_animation_frame
= !include_second_animation_frame
;
880 RunEventSequence(both_animations_events
, true, false, false);
884 // Tests that the frame timestamps are smooth; meaning, that when run through a
885 // simulated compositor, each frame is held displayed for the right number of
887 TEST_P(AnimatedContentSamplerParameterizedTest
, FrameTimestampsAreSmooth
) {
888 // Generate 30 seconds of animated content events, run the events through
889 // AnimatedContentSampler, and record all frame timestamps being proposed
890 // once lock-in is continuous.
891 base::TimeTicks begin
= InitialTestTimeTicks();
892 std::vector
<Event
> events
= GenerateEventSequence(
894 begin
+ base::TimeDelta::FromSeconds(20),
897 typedef std::vector
<base::TimeTicks
> Timestamps
;
898 Timestamps frame_timestamps
;
899 for (std::vector
<Event
>::const_iterator i
= events
.begin(); i
!= events
.end();
901 sampler()->ConsiderPresentationEvent(i
->first
, i
->second
);
902 if (sampler()->HasProposal()) {
903 if (sampler()->ShouldSample()) {
904 frame_timestamps
.push_back(sampler()->frame_timestamp());
905 sampler()->RecordSample(sampler()->frame_timestamp());
908 frame_timestamps
.clear(); // Reset until continuous lock-in.
911 ASSERT_LE(2u, frame_timestamps
.size());
913 // Iterate through the |frame_timestamps|, building a histogram counting the
914 // number of times each frame was displayed k times. For example, 10 frames
915 // of 30 Hz content on a 60 Hz v-sync interval should result in
916 // display_counts[2] == 10. Quit early if any one frame was obviously
917 // repeated too many times.
918 const int64 max_expected_repeats_per_frame
= 1 +
919 std::max(GetParam().min_capture_period
, GetParam().content_period
) /
920 GetParam().vsync_interval
;
921 std::vector
<size_t> display_counts(max_expected_repeats_per_frame
+ 1, 0);
922 base::TimeTicks last_present_time
= frame_timestamps
.front();
923 for (Timestamps::const_iterator i
= frame_timestamps
.begin() + 1;
924 i
!= frame_timestamps
.end(); ++i
) {
925 const size_t num_vsync_intervals
= static_cast<size_t>(
926 (*i
- last_present_time
) / GetParam().vsync_interval
);
927 ASSERT_LT(0u, num_vsync_intervals
);
928 ASSERT_GT(display_counts
.size(), num_vsync_intervals
); // Quit early.
929 ++display_counts
[num_vsync_intervals
];
930 last_present_time
+= num_vsync_intervals
* GetParam().vsync_interval
;
933 // Analyze the histogram for an expected result pattern. If the frame
934 // timestamps are smooth, there should only be one or two buckets with
935 // non-zero counts and they should be next to each other. Because the clock
936 // precision for the event_times provided to the sampler is very granular
937 // (i.e., the vsync_interval), it's okay if other buckets have a tiny "stray"
938 // count in this test.
939 size_t highest_count
= 0;
940 size_t second_highest_count
= 0;
941 for (size_t repeats
= 1; repeats
< display_counts
.size(); ++repeats
) {
942 DVLOG(1) << "display_counts[" << repeats
<< "] is "
943 << display_counts
[repeats
];
944 if (display_counts
[repeats
] >= highest_count
) {
945 second_highest_count
= highest_count
;
946 highest_count
= display_counts
[repeats
];
947 } else if (display_counts
[repeats
] > second_highest_count
) {
948 second_highest_count
= display_counts
[repeats
];
951 size_t stray_count_remaining
=
952 (frame_timestamps
.size() - 1) - (highest_count
+ second_highest_count
);
953 // Expect no more than 0.75% of frames fall outside the two main buckets.
954 EXPECT_GT(frame_timestamps
.size() * 75 / 10000, stray_count_remaining
);
955 for (size_t repeats
= 1; repeats
< display_counts
.size() - 1; ++repeats
) {
956 if (display_counts
[repeats
] == highest_count
) {
957 EXPECT_EQ(second_highest_count
, display_counts
[repeats
+ 1]);
959 } else if (display_counts
[repeats
] == second_highest_count
) {
960 EXPECT_EQ(highest_count
, display_counts
[repeats
+ 1]);
963 EXPECT_GE(stray_count_remaining
, display_counts
[repeats
]);
964 stray_count_remaining
-= display_counts
[repeats
];
969 // Tests that frame timestamps are "lightly pushed" back towards the original
970 // presentation event times, which tells us the AnimatedContentSampler can
971 // account for sources of timestamp drift and correct the drift.
972 TEST_P(AnimatedContentSamplerParameterizedTest
,
973 FrameTimestampsConvergeTowardsEventTimes
) {
974 const int max_drift_increment_millis
= 3;
976 // Generate a full minute of events.
977 const base::TimeTicks begin
= InitialTestTimeTicks();
978 const base::TimeTicks end
= begin
+ base::TimeDelta::FromMinutes(1);
979 std::vector
<Event
> events
= GenerateEventSequence(begin
, end
, true, false);
981 // Modify the event sequence so that 1-3 ms of additional drift is suddenly
982 // present every 100 events. This is meant to simulate that, external to
983 // AnimatedContentSampler, the video hardware vsync timebase is being
984 // refreshed and is showing severe drift from the system clock.
985 base::TimeDelta accumulated_drift
;
986 for (size_t i
= 1; i
< events
.size(); ++i
) {
988 accumulated_drift
+= base::TimeDelta::FromMilliseconds(
989 GetRandomInRange(1, max_drift_increment_millis
+ 1));
991 events
[i
].second
+= accumulated_drift
;
994 // Run all the events through the sampler and track the last rewritten frame
996 base::TimeTicks last_frame_timestamp
;
997 for (std::vector
<Event
>::const_iterator i
= events
.begin(); i
!= events
.end();
999 sampler()->ConsiderPresentationEvent(i
->first
, i
->second
);
1000 if (sampler()->ShouldSample())
1001 last_frame_timestamp
= sampler()->frame_timestamp();
1004 // If drift was accounted for, the |last_frame_timestamp| should be close to
1005 // the last event's timestamp.
1006 const base::TimeDelta total_error
=
1007 events
.back().second
- last_frame_timestamp
;
1008 const base::TimeDelta max_acceptable_error
= GetParam().min_capture_period
+
1009 base::TimeDelta::FromMilliseconds(max_drift_increment_millis
);
1011 total_error
.InMicroseconds(),
1012 max_acceptable_error
.InMicroseconds());
1015 INSTANTIATE_TEST_CASE_P(
1017 AnimatedContentSamplerParameterizedTest
,
1019 // Typical frame rate content: Compositor runs at 60 Hz, capture at 30
1020 // Hz, and content video animates at 30, 25, or 24 Hz.
1021 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(30)),
1022 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(25)),
1023 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(24)),
1025 // High frame rate content that leverages the Compositor's
1026 // capabilities, but capture is still at 30 Hz.
1027 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(60)),
1028 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(50)),
1029 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(48)),
1031 // High frame rate content that leverages the Compositor's
1032 // capabilities, and capture is also a buttery 60 Hz.
1033 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(60)),
1034 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(50)),
1035 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(48)),
1037 // On some platforms, the Compositor runs at 50 Hz.
1038 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(30)),
1039 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(25)),
1040 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(24)),
1041 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(50)),
1042 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(48)),
1044 // Stable, but non-standard content frame rates.
1045 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(16)),
1046 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(20)),
1047 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(23)),
1048 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(26)),
1049 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(27)),
1050 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(28)),
1051 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(29)),
1052 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(31)),
1053 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(32)),
1054 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(33))));
1056 // Tests that VideoCaptureOracle filters out events whose timestamps are
1058 TEST(VideoCaptureOracleTest
, EnforcesEventTimeMonotonicity
) {
1059 const base::TimeDelta min_capture_period
=
1060 base::TimeDelta::FromSeconds(1) / 30;
1061 const gfx::Rect
damage_rect(0, 0, 1280, 720);
1062 const base::TimeDelta event_increment
= min_capture_period
* 2;
1064 VideoCaptureOracle
oracle(min_capture_period
);
1066 base::TimeTicks t
= InitialTestTimeTicks();
1067 for (int i
= 0; i
< 10; ++i
) {
1068 t
+= event_increment
;
1069 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1070 VideoCaptureOracle::kCompositorUpdate
,
1074 base::TimeTicks furthest_event_time
= t
;
1075 for (int i
= 0; i
< 10; ++i
) {
1076 t
-= event_increment
;
1077 ASSERT_FALSE(oracle
.ObserveEventAndDecideCapture(
1078 VideoCaptureOracle::kCompositorUpdate
,
1082 t
= furthest_event_time
;
1083 for (int i
= 0; i
< 10; ++i
) {
1084 t
+= event_increment
;
1085 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1086 VideoCaptureOracle::kCompositorUpdate
,
1091 // Tests that VideoCaptureOracle is enforcing the requirement that captured
1092 // frames are delivered in order. Otherwise, downstream consumers could be
1093 // tripped-up by out-of-order frames or frame timestamps.
1094 TEST(VideoCaptureOracleTest
, EnforcesFramesDeliveredInOrder
) {
1095 const base::TimeDelta min_capture_period
=
1096 base::TimeDelta::FromSeconds(1) / 30;
1097 const gfx::Rect
damage_rect(0, 0, 1280, 720);
1098 const base::TimeDelta event_increment
= min_capture_period
* 2;
1100 VideoCaptureOracle
oracle(min_capture_period
);
1102 // Most basic scenario: Frames delivered one at a time, with no additional
1103 // captures in-between deliveries.
1104 base::TimeTicks t
= InitialTestTimeTicks();
1105 int last_frame_number
;
1106 base::TimeTicks ignored
;
1107 for (int i
= 0; i
< 10; ++i
) {
1108 t
+= event_increment
;
1109 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1110 VideoCaptureOracle::kCompositorUpdate
,
1112 last_frame_number
= oracle
.RecordCapture();
1113 ASSERT_TRUE(oracle
.CompleteCapture(last_frame_number
, &ignored
));
1116 // Basic pipelined scenario: More than one frame in-flight at delivery points.
1117 for (int i
= 0; i
< 50; ++i
) {
1118 const int num_in_flight
= 1 + i
% 3;
1119 for (int j
= 0; j
< num_in_flight
; ++j
) {
1120 t
+= event_increment
;
1121 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1122 VideoCaptureOracle::kCompositorUpdate
,
1124 last_frame_number
= oracle
.RecordCapture();
1126 for (int j
= num_in_flight
- 1; j
>= 0; --j
) {
1127 ASSERT_TRUE(oracle
.CompleteCapture(last_frame_number
- j
, &ignored
));
1131 // Pipelined scenario with out-of-order delivery attempts rejected.
1132 for (int i
= 0; i
< 50; ++i
) {
1133 const int num_in_flight
= 1 + i
% 3;
1134 for (int j
= 0; j
< num_in_flight
; ++j
) {
1135 t
+= event_increment
;
1136 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1137 VideoCaptureOracle::kCompositorUpdate
,
1139 last_frame_number
= oracle
.RecordCapture();
1141 ASSERT_TRUE(oracle
.CompleteCapture(last_frame_number
, &ignored
));
1142 for (int j
= 1; j
< num_in_flight
; ++j
) {
1143 ASSERT_FALSE(oracle
.CompleteCapture(last_frame_number
- j
, &ignored
));
1148 // Tests that VideoCaptureOracle transitions between using its two samplers in a
1149 // way that does not introduce severe jank, pauses, etc.
1150 TEST(VideoCaptureOracleTest
, TransitionsSmoothlyBetweenSamplers
) {
1151 const base::TimeDelta min_capture_period
=
1152 base::TimeDelta::FromSeconds(1) / 30;
1153 const gfx::Rect
animation_damage_rect(0, 0, 1280, 720);
1154 const base::TimeDelta event_increment
= min_capture_period
* 2;
1156 VideoCaptureOracle
oracle(min_capture_period
);
1158 // Run sequences of animation events and non-animation events through the
1159 // oracle. As the oracle transitions between each sampler, make sure the
1160 // frame timestamps won't trip-up downstream consumers.
1161 base::TimeTicks t
= InitialTestTimeTicks();
1162 base::TimeTicks last_frame_timestamp
;
1163 for (int i
= 0; i
< 1000; ++i
) {
1164 t
+= event_increment
;
1166 // For every 100 events, provide 50 that will cause the
1167 // AnimatedContentSampler to lock-in, followed by 50 that will cause it to
1168 // lock-out (i.e., the oracle will use the SmoothEventSampler instead).
1169 const bool provide_animated_content_event
=
1170 (i
% 100) >= 25 && (i
% 100) < 75;
1172 // Only the few events that trigger the lock-out transition should be
1173 // dropped, because the AnimatedContentSampler doesn't yet realize the
1174 // animation ended. Otherwise, the oracle should always decide to sample
1175 // because one of its samplers says to.
1176 const bool require_oracle_says_sample
= (i
% 100) < 75 || (i
% 100) >= 78;
1177 const bool oracle_says_sample
= oracle
.ObserveEventAndDecideCapture(
1178 VideoCaptureOracle::kCompositorUpdate
,
1179 provide_animated_content_event
? animation_damage_rect
: gfx::Rect(),
1181 if (require_oracle_says_sample
)
1182 ASSERT_TRUE(oracle_says_sample
);
1183 if (!oracle_says_sample
)
1186 const int frame_number
= oracle
.RecordCapture();
1188 base::TimeTicks frame_timestamp
;
1189 ASSERT_TRUE(oracle
.CompleteCapture(frame_number
, &frame_timestamp
));
1190 ASSERT_FALSE(frame_timestamp
.is_null());
1191 if (!last_frame_timestamp
.is_null()) {
1192 const base::TimeDelta delta
= frame_timestamp
- last_frame_timestamp
;
1193 EXPECT_LE(event_increment
.InMicroseconds(), delta
.InMicroseconds());
1194 // Right after the AnimatedContentSampler lock-out transition, there were
1195 // a few frames dropped, so allow a gap in the timestamps. Otherwise, the
1196 // delta between frame timestamps should never be more than 2X the
1197 // |event_increment|.
1198 const base::TimeDelta max_acceptable_delta
= (i
% 100) == 78 ?
1199 event_increment
* 5 : event_increment
* 2;
1200 EXPECT_GE(max_acceptable_delta
.InMicroseconds(), delta
.InMicroseconds());
1202 last_frame_timestamp
= frame_timestamp
;
1206 } // namespace content