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
, true, 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
, true, 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
, true, 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
, true, 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
, true, 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
, true, 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
));
312 TEST(SmoothEventSamplerTest
, FallbackToPollingIfUpdatesUnreliable
) {
313 const base::TimeDelta timer_interval
= base::TimeDelta::FromSeconds(1) / 30;
315 SmoothEventSampler
should_not_poll(timer_interval
, true, 1);
316 SmoothEventSampler
should_poll(timer_interval
, false, 1);
317 base::TimeTicks t
= InitialTestTimeTicks();
319 // Do one round of the "happy case" where an event was received and
320 // RecordSample() was called by the client.
321 ASSERT_TRUE(AddEventAndConsiderSampling(&should_not_poll
, t
));
322 ASSERT_TRUE(AddEventAndConsiderSampling(&should_poll
, t
));
323 should_not_poll
.RecordSample();
324 should_poll
.RecordSample();
326 // For the following time period, before 250 ms has elapsed, neither sampler
327 // says we're overdue.
328 const int non_overdue_intervals
= static_cast<int>(
329 base::TimeDelta::FromMilliseconds(250) / timer_interval
);
330 for (int i
= 0; i
< non_overdue_intervals
; i
++) {
332 ASSERT_FALSE(should_not_poll
.IsOverdueForSamplingAt(t
))
333 << "Sampled last event; should not be dirty.";
334 ASSERT_FALSE(should_poll
.IsOverdueForSamplingAt(t
))
335 << "Dirty interval has not elapsed yet.";
338 // Next time period ahead, both samplers say we're overdue. The non-polling
339 // sampler is returning true here because it has been configured to allow one
340 // redundant capture.
341 t
+= timer_interval
; // Step past the 250 ms threshold.
342 ASSERT_TRUE(should_not_poll
.IsOverdueForSamplingAt(t
))
343 << "Sampled last event; is dirty one time only to meet redundancy goal.";
344 ASSERT_TRUE(should_poll
.IsOverdueForSamplingAt(t
))
345 << "If updates are unreliable, must fall back to polling when idle.";
346 should_not_poll
.RecordSample();
347 should_poll
.RecordSample();
349 // Forever more, the non-polling sampler returns false while the polling one
351 for (int i
= 0; i
< 100; ++i
) {
353 ASSERT_FALSE(should_not_poll
.IsOverdueForSamplingAt(t
))
354 << "Sampled last event; should not be dirty.";
355 ASSERT_TRUE(should_poll
.IsOverdueForSamplingAt(t
))
356 << "If updates are unreliable, must fall back to polling when idle.";
357 should_poll
.RecordSample();
359 t
+= timer_interval
/ 3;
360 ASSERT_FALSE(should_not_poll
.IsOverdueForSamplingAt(t
))
361 << "Sampled last event; should not be dirty.";
362 ASSERT_TRUE(should_poll
.IsOverdueForSamplingAt(t
))
363 << "If updates are unreliable, must fall back to polling when idle.";
364 should_poll
.RecordSample();
374 void ReplayCheckingSamplerDecisions(const DataPoint
* data_points
,
375 size_t num_data_points
,
376 SmoothEventSampler
* sampler
) {
377 base::TimeTicks t
= InitialTestTimeTicks();
378 for (size_t i
= 0; i
< num_data_points
; ++i
) {
379 t
+= base::TimeDelta::FromMicroseconds(
380 static_cast<int64
>(data_points
[i
].increment_ms
* 1000));
381 ASSERT_EQ(data_points
[i
].should_capture
,
382 AddEventAndConsiderSampling(sampler
, t
))
383 << "at data_points[" << i
<< ']';
384 if (data_points
[i
].should_capture
)
385 sampler
->RecordSample();
391 TEST(SmoothEventSamplerTest
, DrawingAt24FpsWith60HzVsyncSampledAt30Hertz
) {
392 // Actual capturing of timing data: Initial instability as a 24 FPS video was
393 // started from a still screen, then clearly followed by steady-state.
394 static const DataPoint data_points
[] = {
395 { true, 1437.93 }, { true, 150.484 }, { true, 217.362 }, { true, 50.161 },
396 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 66.88 },
397 { true, 50.161 }, { false, 0 }, { false, 0 }, { true, 50.16 },
398 { true, 33.441 }, { true, 16.72 }, { false, 16.72 }, { true, 117.041 },
399 { true, 16.72 }, { false, 16.72 }, { true, 50.161 }, { true, 50.16 },
400 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 16.72 },
401 { false, 0 }, { true, 50.161 }, { false, 0 }, { true, 33.44 },
402 { true, 16.72 }, { false, 16.721 }, { true, 66.881 }, { false, 0 },
403 { true, 33.441 }, { true, 16.72 }, { true, 50.16 }, { true, 16.72 },
404 { false, 16.721 }, { true, 50.161 }, { true, 50.16 }, { false, 0 },
405 { true, 33.441 }, { true, 50.337 }, { true, 50.183 }, { true, 16.722 },
406 { true, 50.161 }, { true, 33.441 }, { true, 50.16 }, { true, 33.441 },
407 { true, 50.16 }, { true, 33.441 }, { true, 50.16 }, { true, 33.44 },
408 { true, 50.161 }, { true, 50.16 }, { true, 33.44 }, { true, 33.441 },
409 { true, 50.16 }, { true, 50.161 }, { true, 33.44 }, { true, 33.441 },
410 { true, 50.16 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 },
411 { true, 50.161 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 },
412 { true, 83.601 }, { true, 16.72 }, { true, 33.44 }, { false, 0 }
415 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, true, 3);
416 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
419 TEST(SmoothEventSamplerTest
, DrawingAt30FpsWith60HzVsyncSampledAt30Hertz
) {
420 // Actual capturing of timing data: Initial instability as a 30 FPS video was
421 // started from a still screen, then followed by steady-state. Drawing
422 // framerate from the video rendering was a bit volatile, but averaged 30 FPS.
423 static const DataPoint data_points
[] = {
424 { true, 2407.69 }, { true, 16.733 }, { true, 217.362 }, { true, 33.441 },
425 { true, 33.44 }, { true, 33.44 }, { true, 33.441 }, { true, 33.44 },
426 { true, 33.44 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 },
427 { true, 16.721 }, { true, 33.44 }, { false, 0 }, { true, 50.161 },
428 { true, 50.16 }, { false, 0 }, { true, 50.161 }, { true, 33.44 },
429 { true, 16.72 }, { false, 0 }, { false, 16.72 }, { true, 66.881 },
430 { false, 0 }, { true, 33.44 }, { true, 16.72 }, { true, 50.161 },
431 { false, 0 }, { true, 33.538 }, { true, 33.526 }, { true, 33.447 },
432 { true, 33.445 }, { true, 33.441 }, { true, 16.721 }, { true, 33.44 },
433 { true, 33.44 }, { true, 50.161 }, { true, 16.72 }, { true, 33.44 },
434 { true, 33.441 }, { true, 33.44 }, { false, 0 }, { false, 16.72 },
435 { true, 66.881 }, { true, 16.72 }, { false, 16.72 }, { true, 50.16 },
436 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 33.44 },
437 { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { false, 0 },
438 { true, 33.44 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 },
439 { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 66.88 },
440 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 },
441 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 },
442 { true, 16.72 }, { true, 50.161 }, { false, 0 }, { true, 50.16 },
443 { false, 0.001 }, { true, 16.721 }, { true, 66.88 }, { true, 33.44 },
444 { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 },
445 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 66.881 },
446 { true, 33.44 }, { true, 16.72 }, { true, 33.441 }, { false, 16.72 },
447 { true, 66.88 }, { true, 16.721 }, { true, 50.16 }, { true, 33.44 },
448 { true, 16.72 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 }
451 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, true, 3);
452 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
455 TEST(SmoothEventSamplerTest
, DrawingAt60FpsWith60HzVsyncSampledAt30Hertz
) {
456 // Actual capturing of timing data: WebGL Acquarium demo
457 // (http://webglsamples.googlecode.com/hg/aquarium/aquarium.html) which ran
458 // between 55-60 FPS in the steady-state.
459 static const DataPoint data_points
[] = {
460 { true, 16.72 }, { true, 16.72 }, { true, 4163.29 }, { true, 50.193 },
461 { true, 117.041 }, { true, 50.161 }, { true, 50.16 }, { true, 33.441 },
462 { true, 50.16 }, { true, 33.44 }, { false, 0 }, { false, 0 },
463 { true, 50.161 }, { true, 83.601 }, { true, 50.16 }, { true, 16.72 },
464 { true, 33.441 }, { false, 16.72 }, { true, 50.16 }, { true, 16.72 },
465 { false, 0.001 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 },
466 { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
467 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 },
468 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 },
469 { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 },
470 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
471 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 33.44 },
472 { false, 0 }, { true, 16.721 }, { true, 50.161 }, { false, 0 },
473 { true, 33.44 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
474 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 },
475 { true, 50.16 }, { false, 0 }, { true, 16.721 }, { true, 33.44 },
476 { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 },
477 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 },
478 { false, 0 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 },
479 { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
480 { true, 33.44 }, { false, 0 }, { true, 33.44 }, { true, 33.441 },
481 { false, 0 }, { true, 33.44 }, { true, 33.441 }, { false, 0 },
482 { true, 33.44 }, { false, 0 }, { true, 33.44 }, { false, 16.72 },
483 { true, 16.721 }, { true, 50.161 }, { false, 0 }, { true, 16.72 },
484 { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 33.44 },
485 { true, 33.44 }, { false, 0 }, { true, 33.441 }, { false, 16.72 },
486 { true, 16.72 }, { true, 50.16 }, { false, 0 }, { true, 16.72 },
487 { true, 33.441 }, { false, 0 }, { true, 33.44 }, { false, 16.72 },
488 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 50.161 },
489 { false, 0 }, { true, 16.72 }, { true, 33.44 }, { false, 0 },
490 { true, 33.441 }, { false, 16.72 }, { true, 16.72 }, { true, 50.16 }
493 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, true, 3);
494 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
497 class AnimatedContentSamplerTest
: public ::testing::Test
{
499 AnimatedContentSamplerTest() {}
500 virtual ~AnimatedContentSamplerTest() {}
502 virtual void SetUp() OVERRIDE
{
503 const base::TimeDelta since_epoch
=
504 InitialTestTimeTicks() - base::TimeTicks::UnixEpoch();
505 rand_seed_
= abs(static_cast<int>(since_epoch
.InMicroseconds()));
506 sampler_
.reset(new AnimatedContentSampler(GetMinCapturePeriod()));
510 // Overridden by subclass for parameterized tests.
511 virtual base::TimeDelta
GetMinCapturePeriod() const {
512 return base::TimeDelta::FromSeconds(1) / 30;
515 AnimatedContentSampler
* sampler() const {
516 return sampler_
.get();
519 int GetRandomInRange(int begin
, int end
) {
520 const int len
= end
- begin
;
521 const int rand_offset
= (len
== 0) ? 0 : (NextRandomInt() % (end
- begin
));
522 return begin
+ rand_offset
;
525 gfx::Rect
GetRandomDamageRect() {
526 return gfx::Rect(0, 0, GetRandomInRange(1, 100), GetRandomInRange(1, 100));
529 gfx::Rect
GetContentDamageRect() {
530 // This must be distinct from anything GetRandomDamageRect() could return.
531 return gfx::Rect(0, 0, 1280, 720);
534 // Directly inject an observation. Only used to test
535 // ElectMajorityDamageRect().
536 void ObserveDamageRect(const gfx::Rect
& damage_rect
) {
537 sampler_
->observations_
.push_back(
538 AnimatedContentSampler::Observation(damage_rect
, base::TimeTicks()));
541 gfx::Rect
ElectMajorityDamageRect() const {
542 return sampler_
->ElectMajorityDamageRect();
546 // Note: Not using base::RandInt() because it is horribly slow on debug
547 // builds. The following is a very simple, deterministic LCG:
548 int NextRandomInt() {
549 rand_seed_
= (1103515245 * rand_seed_
+ 12345) % (1 << 31);
554 scoped_ptr
<AnimatedContentSampler
> sampler_
;
557 TEST_F(AnimatedContentSamplerTest
, ElectsNoneFromZeroDamageRects
) {
558 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
561 TEST_F(AnimatedContentSamplerTest
, ElectsMajorityFromOneDamageRect
) {
562 const gfx::Rect
the_one_rect(0, 0, 1, 1);
563 ObserveDamageRect(the_one_rect
);
564 EXPECT_EQ(the_one_rect
, ElectMajorityDamageRect());
567 TEST_F(AnimatedContentSamplerTest
, ElectsNoneFromTwoDamageRectsOfSameArea
) {
568 const gfx::Rect
one_rect(0, 0, 1, 1);
569 const gfx::Rect
another_rect(1, 1, 1, 1);
570 ObserveDamageRect(one_rect
);
571 ObserveDamageRect(another_rect
);
572 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
575 TEST_F(AnimatedContentSamplerTest
, ElectsLargerOfTwoDamageRects_1
) {
576 const gfx::Rect
one_rect(0, 0, 1, 1);
577 const gfx::Rect
another_rect(0, 0, 2, 2);
578 ObserveDamageRect(one_rect
);
579 ObserveDamageRect(another_rect
);
580 EXPECT_EQ(another_rect
, ElectMajorityDamageRect());
583 TEST_F(AnimatedContentSamplerTest
, ElectsLargerOfTwoDamageRects_2
) {
584 const gfx::Rect
one_rect(0, 0, 2, 2);
585 const gfx::Rect
another_rect(0, 0, 1, 1);
586 ObserveDamageRect(one_rect
);
587 ObserveDamageRect(another_rect
);
588 EXPECT_EQ(one_rect
, ElectMajorityDamageRect());
591 TEST_F(AnimatedContentSamplerTest
, ElectsSameAsMooreDemonstration
) {
592 // A more complex sequence (from Moore's web site): Three different Rects with
593 // the same area, but occurring a different number of times. C should win the
595 const gfx::Rect
rect_a(0, 0, 1, 4);
596 const gfx::Rect
rect_b(1, 1, 4, 1);
597 const gfx::Rect
rect_c(2, 2, 2, 2);
598 for (int i
= 0; i
< 3; ++i
)
599 ObserveDamageRect(rect_a
);
600 for (int i
= 0; i
< 2; ++i
)
601 ObserveDamageRect(rect_c
);
602 for (int i
= 0; i
< 2; ++i
)
603 ObserveDamageRect(rect_b
);
604 for (int i
= 0; i
< 3; ++i
)
605 ObserveDamageRect(rect_c
);
606 ObserveDamageRect(rect_b
);
607 for (int i
= 0; i
< 2; ++i
)
608 ObserveDamageRect(rect_c
);
609 EXPECT_EQ(rect_c
, ElectMajorityDamageRect());
612 TEST_F(AnimatedContentSamplerTest
, Elects24FpsVideoInsteadOf48FpsSpinner
) {
613 // Scenario: 24 FPS 720x480 Video versus 48 FPS 96x96 "Busy Spinner"
614 const gfx::Rect
video_rect(100, 100, 720, 480);
615 const gfx::Rect
spinner_rect(360, 0, 96, 96);
616 for (int i
= 0; i
< 100; ++i
) {
617 // |video_rect| occurs once for every two |spinner_rect|. Vary the order
618 // of events between the two:
619 ObserveDamageRect(video_rect
);
620 ObserveDamageRect(spinner_rect
);
621 ObserveDamageRect(spinner_rect
);
622 ObserveDamageRect(video_rect
);
623 ObserveDamageRect(spinner_rect
);
624 ObserveDamageRect(spinner_rect
);
625 ObserveDamageRect(spinner_rect
);
626 ObserveDamageRect(video_rect
);
627 ObserveDamageRect(spinner_rect
);
628 ObserveDamageRect(spinner_rect
);
629 ObserveDamageRect(video_rect
);
630 ObserveDamageRect(spinner_rect
);
632 EXPECT_EQ(video_rect
, ElectMajorityDamageRect());
637 // A test scenario for AnimatedContentSamplerParameterizedTest.
639 base::TimeDelta vsync_interval
; // Reflects compositor's update rate.
640 base::TimeDelta min_capture_period
; // Reflects maximum capture rate.
641 base::TimeDelta content_period
; // Reflects content animation rate.
643 Scenario(base::TimeDelta v
, base::TimeDelta m
, base::TimeDelta c
)
644 : vsync_interval(v
), min_capture_period(m
), content_period(c
) {
645 CHECK(content_period
>= vsync_interval
)
646 << "Bad test params: Impossible to animate faster than the compositor.";
650 // Value printer for Scenario.
651 ::std::ostream
& operator<<(::std::ostream
& os
, const Scenario
& s
) {
652 return os
<< "{ vsync_interval=" << s
.vsync_interval
.InMicroseconds()
653 << ", min_capture_period=" << s
.min_capture_period
.InMicroseconds()
654 << ", content_period=" << s
.content_period
.InMicroseconds()
658 base::TimeDelta
FpsAsPeriod(int frame_rate
) {
659 return base::TimeDelta::FromSeconds(1) / frame_rate
;
664 class AnimatedContentSamplerParameterizedTest
665 : public AnimatedContentSamplerTest
,
666 public ::testing::WithParamInterface
<Scenario
> {
668 AnimatedContentSamplerParameterizedTest()
669 : count_dropped_frames_(0), count_sampled_frames_(0) {}
670 virtual ~AnimatedContentSamplerParameterizedTest() {}
673 typedef std::pair
<gfx::Rect
, base::TimeTicks
> Event
;
675 virtual base::TimeDelta
GetMinCapturePeriod() const OVERRIDE
{
676 return GetParam().min_capture_period
;
679 // Generate a sequence of events from the compositor pipeline. The event
680 // times will all be at compositor vsync boundaries.
681 std::vector
<Event
> GenerateEventSequence(base::TimeTicks begin
,
683 bool include_content_frame_events
,
684 bool include_random_events
) {
685 DCHECK(GetParam().content_period
>= GetParam().vsync_interval
);
686 base::TimeTicks next_content_time
= begin
- GetParam().content_period
;
687 std::vector
<Event
> events
;
688 for (base::TimeTicks compositor_time
= begin
; compositor_time
< end
;
689 compositor_time
+= GetParam().vsync_interval
) {
690 if (include_content_frame_events
&& next_content_time
< compositor_time
) {
691 events
.push_back(Event(GetContentDamageRect(), compositor_time
));
692 next_content_time
+= GetParam().content_period
;
693 } else if (include_random_events
&& GetRandomInRange(0, 1) == 0) {
694 events
.push_back(Event(GetRandomDamageRect(), compositor_time
));
698 DCHECK(!events
.empty());
702 // Feed |events| through the sampler, and detect whether the expected
703 // lock-in/out transition occurs. Also, track and measure the frame drop
704 // ratio and check it against the expected drop rate.
705 void RunEventSequence(const std::vector
<Event
> events
,
706 bool was_detecting_before
,
707 bool is_detecting_after
,
708 bool simulate_pipeline_back_pressure
) {
709 gfx::Rect first_detected_region
;
711 EXPECT_EQ(was_detecting_before
, sampler()->HasProposal());
712 bool has_detection_switched
= false;
713 ResetFrameCounters();
714 for (std::vector
<Event
>::const_iterator i
= events
.begin();
715 i
!= events
.end(); ++i
) {
716 sampler()->ConsiderPresentationEvent(i
->first
, i
->second
);
718 // Detect when the sampler locks in/out, and that it stays that way for
719 // all further iterations of this loop.
720 if (!has_detection_switched
&&
721 was_detecting_before
!= sampler()->HasProposal()) {
722 has_detection_switched
= true;
725 has_detection_switched
? is_detecting_after
: was_detecting_before
,
726 sampler()->HasProposal());
728 if (sampler()->HasProposal()) {
729 // Make sure the sampler doesn't flip-flop and keep proposing sampling
730 // based on locking into different regions.
731 if (first_detected_region
.IsEmpty()) {
732 first_detected_region
= sampler()->detected_region();
733 ASSERT_FALSE(first_detected_region
.IsEmpty());
735 EXPECT_EQ(first_detected_region
, sampler()->detected_region());
738 if (simulate_pipeline_back_pressure
&& GetRandomInRange(0, 2) == 0)
739 ClientCannotSampleFrame(*i
);
741 ClientDoesWhatSamplerProposes(*i
);
743 EXPECT_FALSE(sampler()->ShouldSample());
744 if (!simulate_pipeline_back_pressure
|| GetRandomInRange(0, 2) == 1)
745 sampler()->RecordSample(i
->second
);
748 EXPECT_EQ(is_detecting_after
, sampler()->HasProposal());
749 ExpectFrameDropRatioIsCorrect();
752 void ResetFrameCounters() {
753 count_dropped_frames_
= 0;
754 count_sampled_frames_
= 0;
757 // Keep track what the sampler is proposing, and call RecordSample() if it
758 // proposes sampling |event|.
759 void ClientDoesWhatSamplerProposes(const Event
& event
) {
760 if (sampler()->ShouldSample()) {
761 EXPECT_EQ(GetContentDamageRect(), event
.first
);
762 sampler()->RecordSample(sampler()->frame_timestamp());
763 ++count_sampled_frames_
;
764 } else if (event
.first
== GetContentDamageRect()) {
765 ++count_dropped_frames_
;
769 // RecordSample() is not called, but for testing, keep track of what the
770 // sampler is proposing for |event|.
771 void ClientCannotSampleFrame(const Event
& event
) {
772 if (sampler()->ShouldSample()) {
773 EXPECT_EQ(GetContentDamageRect(), event
.first
);
774 ++count_sampled_frames_
;
775 } else if (event
.first
== GetContentDamageRect()) {
776 ++count_dropped_frames_
;
780 // Confirm the AnimatedContentSampler is not dropping more frames than
781 // expected, given current test parameters.
782 void ExpectFrameDropRatioIsCorrect() {
783 if (count_sampled_frames_
== 0) {
784 EXPECT_EQ(0, count_dropped_frames_
);
787 const double content_framerate
=
788 1000000.0 / GetParam().content_period
.InMicroseconds();
789 const double capture_framerate
=
790 1000000.0 / GetParam().min_capture_period
.InMicroseconds();
791 const double expected_drop_rate
= std::max(
792 0.0, (content_framerate
- capture_framerate
) / capture_framerate
);
793 const double actual_drop_rate
=
794 static_cast<double>(count_dropped_frames_
) / count_sampled_frames_
;
795 EXPECT_NEAR(expected_drop_rate
, actual_drop_rate
, 0.015);
799 // These counters only include the frames with the desired content.
800 int count_dropped_frames_
;
801 int count_sampled_frames_
;
804 // Tests that the implementation locks in/out of frames containing stable
805 // animated content, whether or not random events are also simultaneously
807 TEST_P(AnimatedContentSamplerParameterizedTest
, DetectsAnimatedContent
) {
808 // |begin| refers to the start of an event sequence in terms of the
809 // Compositor's clock.
810 base::TimeTicks begin
= InitialTestTimeTicks();
812 // Provide random events and expect no lock-in.
813 base::TimeTicks end
= begin
+ base::TimeDelta::FromSeconds(5);
814 RunEventSequence(GenerateEventSequence(begin
, end
, false, true),
820 // Provide content frame events with some random events mixed-in, and expect
821 // the sampler to lock-in.
822 end
= begin
+ base::TimeDelta::FromSeconds(5);
823 RunEventSequence(GenerateEventSequence(begin
, end
, true, true),
829 // Continue providing content frame events without the random events mixed-in
830 // and expect the lock-in to hold.
831 end
= begin
+ base::TimeDelta::FromSeconds(5);
832 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
838 // Continue providing just content frame events and expect the lock-in to
839 // hold. Also simulate the capture pipeline experiencing back pressure.
840 end
= begin
+ base::TimeDelta::FromSeconds(20);
841 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
847 // Provide a half-second of random events only, and expect the lock-in to be
849 end
= begin
+ base::TimeDelta::FromMilliseconds(500);
850 RunEventSequence(GenerateEventSequence(begin
, end
, false, true),
856 // Now, go back to providing content frame events, and expect the sampler to
857 // lock-in once again.
858 end
= begin
+ base::TimeDelta::FromSeconds(5);
859 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
866 // Tests that AnimatedContentSampler won't lock in to, nor flip-flop between,
867 // two animations of the same pixel change rate. VideoCaptureOracle should
868 // revert to using the SmoothEventSampler for these kinds of situations, as
869 // there is no "right answer" as to which animation to lock into.
870 TEST_P(AnimatedContentSamplerParameterizedTest
,
871 DoesNotLockInToTwoCompetingAnimations
) {
872 // Don't test when the event stream cannot indicate two separate content
873 // animations under the current test parameters.
874 if (GetParam().content_period
< 2 * GetParam().vsync_interval
)
877 // Start the first animation and run for a bit, and expect the sampler to
879 base::TimeTicks begin
= InitialTestTimeTicks();
880 base::TimeTicks end
= begin
+ base::TimeDelta::FromSeconds(5);
881 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
887 // Now, keep the first animation and blend in an second animation of the same
888 // size and frame rate, but at a different position. This will should cause
889 // the sampler to enter an "undetected" state since it's unclear which
890 // animation should be locked into.
891 end
= begin
+ base::TimeDelta::FromSeconds(20);
892 std::vector
<Event
> first_animation_events
=
893 GenerateEventSequence(begin
, end
, true, false);
894 gfx::Rect
second_animation_rect(
895 gfx::Point(0, GetContentDamageRect().height()),
896 GetContentDamageRect().size());
897 std::vector
<Event
> both_animations_events
;
898 base::TimeDelta second_animation_offset
= GetParam().vsync_interval
;
899 for (std::vector
<Event
>::const_iterator i
= first_animation_events
.begin();
900 i
!= first_animation_events
.end(); ++i
) {
901 both_animations_events
.push_back(*i
);
902 both_animations_events
.push_back(
903 Event(second_animation_rect
, i
->second
+ second_animation_offset
));
905 RunEventSequence(both_animations_events
, true, false, false);
908 // Now, run just the first animation, and expect the sampler to lock-in once
910 end
= begin
+ base::TimeDelta::FromSeconds(5);
911 RunEventSequence(GenerateEventSequence(begin
, end
, true, false),
917 // Now, blend in the second animation again, but it has half the frame rate of
918 // the first animation and damage Rects with twice the area. This will should
919 // cause the sampler to enter an "undetected" state again. This tests that
920 // pixel-weighting is being accounted for in the sampler's logic.
921 end
= begin
+ base::TimeDelta::FromSeconds(20);
922 first_animation_events
= GenerateEventSequence(begin
, end
, true, false);
923 second_animation_rect
.set_width(second_animation_rect
.width() * 2);
924 both_animations_events
.clear();
925 bool include_second_animation_frame
= true;
926 for (std::vector
<Event
>::const_iterator i
= first_animation_events
.begin();
927 i
!= first_animation_events
.end(); ++i
) {
928 both_animations_events
.push_back(*i
);
929 if (include_second_animation_frame
) {
930 both_animations_events
.push_back(
931 Event(second_animation_rect
, i
->second
+ second_animation_offset
));
933 include_second_animation_frame
= !include_second_animation_frame
;
935 RunEventSequence(both_animations_events
, true, false, false);
939 // Tests that the frame timestamps are smooth; meaning, that when run through a
940 // simulated compositor, each frame is held displayed for the right number of
942 TEST_P(AnimatedContentSamplerParameterizedTest
, FrameTimestampsAreSmooth
) {
943 // Generate 30 seconds of animated content events, run the events through
944 // AnimatedContentSampler, and record all frame timestamps being proposed
945 // once lock-in is continuous.
946 base::TimeTicks begin
= InitialTestTimeTicks();
947 std::vector
<Event
> events
= GenerateEventSequence(
949 begin
+ base::TimeDelta::FromSeconds(20),
952 typedef std::vector
<base::TimeTicks
> Timestamps
;
953 Timestamps frame_timestamps
;
954 for (std::vector
<Event
>::const_iterator i
= events
.begin(); i
!= events
.end();
956 sampler()->ConsiderPresentationEvent(i
->first
, i
->second
);
957 if (sampler()->HasProposal()) {
958 if (sampler()->ShouldSample()) {
959 frame_timestamps
.push_back(sampler()->frame_timestamp());
960 sampler()->RecordSample(sampler()->frame_timestamp());
963 frame_timestamps
.clear(); // Reset until continuous lock-in.
966 ASSERT_LE(2u, frame_timestamps
.size());
968 // Iterate through the |frame_timestamps|, building a histogram counting the
969 // number of times each frame was displayed k times. For example, 10 frames
970 // of 30 Hz content on a 60 Hz v-sync interval should result in
971 // display_counts[2] == 10. Quit early if any one frame was obviously
972 // repeated too many times.
973 const int64 max_expected_repeats_per_frame
= 1 +
974 std::max(GetParam().min_capture_period
, GetParam().content_period
) /
975 GetParam().vsync_interval
;
976 std::vector
<size_t> display_counts(max_expected_repeats_per_frame
+ 1, 0);
977 base::TimeTicks last_present_time
= frame_timestamps
.front();
978 for (Timestamps::const_iterator i
= frame_timestamps
.begin() + 1;
979 i
!= frame_timestamps
.end(); ++i
) {
980 const size_t num_vsync_intervals
= static_cast<size_t>(
981 (*i
- last_present_time
) / GetParam().vsync_interval
);
982 ASSERT_LT(0u, num_vsync_intervals
);
983 ASSERT_GT(display_counts
.size(), num_vsync_intervals
); // Quit early.
984 ++display_counts
[num_vsync_intervals
];
985 last_present_time
+= num_vsync_intervals
* GetParam().vsync_interval
;
988 // Analyze the histogram for an expected result pattern. If the frame
989 // timestamps are smooth, there should only be one or two buckets with
990 // non-zero counts and they should be next to each other. Because the clock
991 // precision for the event_times provided to the sampler is very granular
992 // (i.e., the vsync_interval), it's okay if other buckets have a tiny "stray"
993 // count in this test.
994 size_t highest_count
= 0;
995 size_t second_highest_count
= 0;
996 for (size_t repeats
= 1; repeats
< display_counts
.size(); ++repeats
) {
997 DVLOG(1) << "display_counts[" << repeats
<< "] is "
998 << display_counts
[repeats
];
999 if (display_counts
[repeats
] >= highest_count
) {
1000 second_highest_count
= highest_count
;
1001 highest_count
= display_counts
[repeats
];
1002 } else if (display_counts
[repeats
] > second_highest_count
) {
1003 second_highest_count
= display_counts
[repeats
];
1006 size_t stray_count_remaining
=
1007 (frame_timestamps
.size() - 1) - (highest_count
+ second_highest_count
);
1008 // Expect no more than 0.75% of frames fall outside the two main buckets.
1009 EXPECT_GT(frame_timestamps
.size() * 75 / 10000, stray_count_remaining
);
1010 for (size_t repeats
= 1; repeats
< display_counts
.size() - 1; ++repeats
) {
1011 if (display_counts
[repeats
] == highest_count
) {
1012 EXPECT_EQ(second_highest_count
, display_counts
[repeats
+ 1]);
1014 } else if (display_counts
[repeats
] == second_highest_count
) {
1015 EXPECT_EQ(highest_count
, display_counts
[repeats
+ 1]);
1018 EXPECT_GE(stray_count_remaining
, display_counts
[repeats
]);
1019 stray_count_remaining
-= display_counts
[repeats
];
1024 // Tests that frame timestamps are "lightly pushed" back towards the original
1025 // presentation event times, which tells us the AnimatedContentSampler can
1026 // account for sources of timestamp drift and correct the drift.
1027 TEST_P(AnimatedContentSamplerParameterizedTest
,
1028 FrameTimestampsConvergeTowardsEventTimes
) {
1029 const int max_drift_increment_millis
= 3;
1031 // Generate a full minute of events.
1032 const base::TimeTicks begin
= InitialTestTimeTicks();
1033 const base::TimeTicks end
= begin
+ base::TimeDelta::FromMinutes(1);
1034 std::vector
<Event
> events
= GenerateEventSequence(begin
, end
, true, false);
1036 // Modify the event sequence so that 1-3 ms of additional drift is suddenly
1037 // present every 100 events. This is meant to simulate that, external to
1038 // AnimatedContentSampler, the video hardware vsync timebase is being
1039 // refreshed and is showing severe drift from the system clock.
1040 base::TimeDelta accumulated_drift
;
1041 for (size_t i
= 1; i
< events
.size(); ++i
) {
1043 accumulated_drift
+= base::TimeDelta::FromMilliseconds(
1044 GetRandomInRange(1, max_drift_increment_millis
+ 1));
1046 events
[i
].second
+= accumulated_drift
;
1049 // Run all the events through the sampler and track the last rewritten frame
1051 base::TimeTicks last_frame_timestamp
;
1052 for (std::vector
<Event
>::const_iterator i
= events
.begin(); i
!= events
.end();
1054 sampler()->ConsiderPresentationEvent(i
->first
, i
->second
);
1055 if (sampler()->ShouldSample())
1056 last_frame_timestamp
= sampler()->frame_timestamp();
1059 // If drift was accounted for, the |last_frame_timestamp| should be close to
1060 // the last event's timestamp.
1061 const base::TimeDelta total_error
=
1062 events
.back().second
- last_frame_timestamp
;
1063 const base::TimeDelta max_acceptable_error
= GetParam().min_capture_period
+
1064 base::TimeDelta::FromMilliseconds(max_drift_increment_millis
);
1066 total_error
.InMicroseconds(),
1067 max_acceptable_error
.InMicroseconds());
1070 INSTANTIATE_TEST_CASE_P(
1072 AnimatedContentSamplerParameterizedTest
,
1074 // Typical frame rate content: Compositor runs at 60 Hz, capture at 30
1075 // Hz, and content video animates at 30, 25, or 24 Hz.
1076 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(30)),
1077 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(25)),
1078 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(24)),
1080 // High frame rate content that leverages the Compositor's
1081 // capabilities, but capture is still at 30 Hz.
1082 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(60)),
1083 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(50)),
1084 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(48)),
1086 // High frame rate content that leverages the Compositor's
1087 // capabilities, and capture is also a buttery 60 Hz.
1088 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(60)),
1089 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(50)),
1090 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(48)),
1092 // On some platforms, the Compositor runs at 50 Hz.
1093 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(30)),
1094 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(25)),
1095 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(24)),
1096 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(50)),
1097 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(48)),
1099 // Stable, but non-standard content frame rates.
1100 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(16)),
1101 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(20)),
1102 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(23)),
1103 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(26)),
1104 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(27)),
1105 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(28)),
1106 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(29)),
1107 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(31)),
1108 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(32)),
1109 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(33))));
1111 // Tests that VideoCaptureOracle filters out events whose timestamps are
1113 TEST(VideoCaptureOracleTest
, EnforcesEventTimeMonotonicity
) {
1114 const base::TimeDelta min_capture_period
=
1115 base::TimeDelta::FromSeconds(1) / 30;
1116 const gfx::Rect
damage_rect(0, 0, 1280, 720);
1117 const base::TimeDelta event_increment
= min_capture_period
* 2;
1119 VideoCaptureOracle
oracle(min_capture_period
, true);
1121 base::TimeTicks t
= InitialTestTimeTicks();
1122 for (int i
= 0; i
< 10; ++i
) {
1123 t
+= event_increment
;
1124 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1125 VideoCaptureOracle::kCompositorUpdate
,
1129 base::TimeTicks furthest_event_time
= t
;
1130 for (int i
= 0; i
< 10; ++i
) {
1131 t
-= event_increment
;
1132 ASSERT_FALSE(oracle
.ObserveEventAndDecideCapture(
1133 VideoCaptureOracle::kCompositorUpdate
,
1137 t
= furthest_event_time
;
1138 for (int i
= 0; i
< 10; ++i
) {
1139 t
+= event_increment
;
1140 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1141 VideoCaptureOracle::kCompositorUpdate
,
1146 // Tests that VideoCaptureOracle is enforcing the requirement that captured
1147 // frames are delivered in order. Otherwise, downstream consumers could be
1148 // tripped-up by out-of-order frames or frame timestamps.
1149 TEST(VideoCaptureOracleTest
, EnforcesFramesDeliveredInOrder
) {
1150 const base::TimeDelta min_capture_period
=
1151 base::TimeDelta::FromSeconds(1) / 30;
1152 const gfx::Rect
damage_rect(0, 0, 1280, 720);
1153 const base::TimeDelta event_increment
= min_capture_period
* 2;
1155 VideoCaptureOracle
oracle(min_capture_period
, true);
1157 // Most basic scenario: Frames delivered one at a time, with no additional
1158 // captures in-between deliveries.
1159 base::TimeTicks t
= InitialTestTimeTicks();
1160 int last_frame_number
;
1161 base::TimeTicks ignored
;
1162 for (int i
= 0; i
< 10; ++i
) {
1163 t
+= event_increment
;
1164 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1165 VideoCaptureOracle::kCompositorUpdate
,
1167 last_frame_number
= oracle
.RecordCapture();
1168 ASSERT_TRUE(oracle
.CompleteCapture(last_frame_number
, &ignored
));
1171 // Basic pipelined scenario: More than one frame in-flight at delivery points.
1172 for (int i
= 0; i
< 50; ++i
) {
1173 const int num_in_flight
= 1 + i
% 3;
1174 for (int j
= 0; j
< num_in_flight
; ++j
) {
1175 t
+= event_increment
;
1176 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1177 VideoCaptureOracle::kCompositorUpdate
,
1179 last_frame_number
= oracle
.RecordCapture();
1181 for (int j
= num_in_flight
- 1; j
>= 0; --j
) {
1182 ASSERT_TRUE(oracle
.CompleteCapture(last_frame_number
- j
, &ignored
));
1186 // Pipelined scenario with out-of-order delivery attempts rejected.
1187 for (int i
= 0; i
< 50; ++i
) {
1188 const int num_in_flight
= 1 + i
% 3;
1189 for (int j
= 0; j
< num_in_flight
; ++j
) {
1190 t
+= event_increment
;
1191 ASSERT_TRUE(oracle
.ObserveEventAndDecideCapture(
1192 VideoCaptureOracle::kCompositorUpdate
,
1194 last_frame_number
= oracle
.RecordCapture();
1196 ASSERT_TRUE(oracle
.CompleteCapture(last_frame_number
, &ignored
));
1197 for (int j
= 1; j
< num_in_flight
; ++j
) {
1198 ASSERT_FALSE(oracle
.CompleteCapture(last_frame_number
- j
, &ignored
));
1203 // Tests that VideoCaptureOracle transitions between using its two samplers in a
1204 // way that does not introduce severe jank, pauses, etc.
1205 TEST(VideoCaptureOracleTest
, TransitionsSmoothlyBetweenSamplers
) {
1206 const base::TimeDelta min_capture_period
=
1207 base::TimeDelta::FromSeconds(1) / 30;
1208 const gfx::Rect
animation_damage_rect(0, 0, 1280, 720);
1209 const base::TimeDelta event_increment
= min_capture_period
* 2;
1211 VideoCaptureOracle
oracle(min_capture_period
, true);
1213 // Run sequences of animation events and non-animation events through the
1214 // oracle. As the oracle transitions between each sampler, make sure the
1215 // frame timestamps won't trip-up downstream consumers.
1216 base::TimeTicks t
= InitialTestTimeTicks();
1217 base::TimeTicks last_frame_timestamp
;
1218 for (int i
= 0; i
< 1000; ++i
) {
1219 t
+= event_increment
;
1221 // For every 100 events, provide 50 that will cause the
1222 // AnimatedContentSampler to lock-in, followed by 50 that will cause it to
1223 // lock-out (i.e., the oracle will use the SmoothEventSampler instead).
1224 const bool provide_animated_content_event
=
1225 (i
% 100) >= 25 && (i
% 100) < 75;
1227 // Only the few events that trigger the lock-out transition should be
1228 // dropped, because the AnimatedContentSampler doesn't yet realize the
1229 // animation ended. Otherwise, the oracle should always decide to sample
1230 // because one of its samplers says to.
1231 const bool require_oracle_says_sample
= (i
% 100) < 75 || (i
% 100) >= 78;
1232 const bool oracle_says_sample
= oracle
.ObserveEventAndDecideCapture(
1233 VideoCaptureOracle::kCompositorUpdate
,
1234 provide_animated_content_event
? animation_damage_rect
: gfx::Rect(),
1236 if (require_oracle_says_sample
)
1237 ASSERT_TRUE(oracle_says_sample
);
1238 if (!oracle_says_sample
)
1241 const int frame_number
= oracle
.RecordCapture();
1243 base::TimeTicks frame_timestamp
;
1244 ASSERT_TRUE(oracle
.CompleteCapture(frame_number
, &frame_timestamp
));
1245 ASSERT_FALSE(frame_timestamp
.is_null());
1246 if (!last_frame_timestamp
.is_null()) {
1247 const base::TimeDelta delta
= frame_timestamp
- last_frame_timestamp
;
1248 EXPECT_LE(event_increment
.InMicroseconds(), delta
.InMicroseconds());
1249 // Right after the AnimatedContentSampler lock-out transition, there were
1250 // a few frames dropped, so allow a gap in the timestamps. Otherwise, the
1251 // delta between frame timestamps should never be more than 2X the
1252 // |event_increment|.
1253 const base::TimeDelta max_acceptable_delta
= (i
% 100) == 78 ?
1254 event_increment
* 5 : event_increment
* 2;
1255 EXPECT_GE(max_acceptable_delta
.InMicroseconds(), delta
.InMicroseconds());
1257 last_frame_timestamp
= frame_timestamp
;
1261 } // namespace content