1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/capture/content/smooth_event_sampler.h"
7 #include "base/strings/stringprintf.h"
8 #include "testing/gtest/include/gtest/gtest.h"
14 bool AddEventAndConsiderSampling(SmoothEventSampler
* sampler
,
15 base::TimeTicks event_time
) {
16 sampler
->ConsiderPresentationEvent(event_time
);
17 return sampler
->ShouldSample();
20 void SteadyStateSampleAndAdvance(base::TimeDelta vsync
,
21 SmoothEventSampler
* sampler
,
23 ASSERT_TRUE(AddEventAndConsiderSampling(sampler
, *t
));
24 ASSERT_TRUE(sampler
->HasUnrecordedEvent());
25 sampler
->RecordSample();
26 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
27 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
29 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
32 void SteadyStateNoSampleAndAdvance(base::TimeDelta vsync
,
33 SmoothEventSampler
* sampler
,
35 ASSERT_FALSE(AddEventAndConsiderSampling(sampler
, *t
));
36 ASSERT_TRUE(sampler
->HasUnrecordedEvent());
37 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
39 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
42 base::TimeTicks
InitialTestTimeTicks() {
43 return base::TimeTicks() + base::TimeDelta::FromSeconds(1);
46 void TestRedundantCaptureStrategy(base::TimeDelta capture_period
,
47 int redundant_capture_goal
,
48 SmoothEventSampler
* sampler
,
50 // Before any events have been considered, we're overdue for sampling.
51 ASSERT_TRUE(sampler
->IsOverdueForSamplingAt(*t
));
53 // Consider the first event. We want to sample that.
54 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
55 ASSERT_TRUE(AddEventAndConsiderSampling(sampler
, *t
));
56 ASSERT_TRUE(sampler
->HasUnrecordedEvent());
57 sampler
->RecordSample();
58 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
60 // After more than 250 ms has passed without considering an event, we should
61 // repeatedly be overdue for sampling. However, once the redundant capture
62 // goal is achieved, we should no longer be overdue for sampling.
63 *t
+= base::TimeDelta::FromMilliseconds(250);
64 for (int i
= 0; i
< redundant_capture_goal
; i
++) {
65 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
66 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
67 ASSERT_TRUE(sampler
->IsOverdueForSamplingAt(*t
))
68 << "Should sample until redundant capture goal is hit";
69 sampler
->RecordSample();
70 *t
+= capture_period
; // Timer fires once every capture period.
72 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
))
73 << "Should not be overdue once redundant capture goal achieved.";
78 // 60Hz sampled at 30Hz should produce 30Hz. In addition, this test contains
79 // much more comprehensive before/after/edge-case scenarios than the others.
80 TEST(SmoothEventSamplerTest
, Sample60HertzAt30Hertz
) {
81 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
82 const int redundant_capture_goal
= 200;
83 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 60;
85 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
86 base::TimeTicks t
= InitialTestTimeTicks();
88 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
, &sampler
,
91 // Steady state, we should capture every other vsync, indefinitely.
92 for (int i
= 0; i
< 100; i
++) {
93 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
94 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
95 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
98 // Now pretend we're limited by backpressure in the pipeline. In this scenario
99 // case we are adding events but not sampling them.
100 for (int i
= 0; i
< 20; i
++) {
101 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
102 ASSERT_EQ(i
>= 14, sampler
.IsOverdueForSamplingAt(t
));
103 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
104 ASSERT_TRUE(sampler
.HasUnrecordedEvent());
108 // Now suppose we can sample again. We should be back in the steady state,
109 // but at a different phase.
110 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
111 for (int i
= 0; i
< 100; i
++) {
112 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
113 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
114 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
118 // 50Hz sampled at 30Hz should produce a sequence where some frames are skipped.
119 TEST(SmoothEventSamplerTest
, Sample50HertzAt30Hertz
) {
120 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
121 const int redundant_capture_goal
= 2;
122 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 50;
124 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
125 base::TimeTicks t
= InitialTestTimeTicks();
127 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
, &sampler
,
130 // Steady state, we should capture 1st, 2nd and 4th frames out of every five
131 // frames, indefinitely.
132 for (int i
= 0; i
< 100; i
++) {
133 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
134 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
135 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
136 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
137 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
138 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
141 // Now pretend we're limited by backpressure in the pipeline. In this scenario
142 // case we are adding events but not sampling them.
143 for (int i
= 0; i
< 20; i
++) {
144 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
145 ASSERT_EQ(i
>= 11, sampler
.IsOverdueForSamplingAt(t
));
146 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
150 // Now suppose we can sample again. We should be back in the steady state
152 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
153 for (int i
= 0; i
< 100; i
++) {
154 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
155 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
156 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
157 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
158 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
159 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
163 // 75Hz sampled at 30Hz should produce a sequence where some frames are skipped.
164 TEST(SmoothEventSamplerTest
, Sample75HertzAt30Hertz
) {
165 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
166 const int redundant_capture_goal
= 32;
167 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 75;
169 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
170 base::TimeTicks t
= InitialTestTimeTicks();
172 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
, &sampler
,
175 // Steady state, we should capture 1st and 3rd frames out of every five
176 // frames, indefinitely.
177 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
178 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
179 for (int i
= 0; i
< 100; i
++) {
180 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
181 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
182 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
183 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
184 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
185 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
188 // Now pretend we're limited by backpressure in the pipeline. In this scenario
189 // case we are adding events but not sampling them.
190 for (int i
= 0; i
< 20; i
++) {
191 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
192 ASSERT_EQ(i
>= 16, sampler
.IsOverdueForSamplingAt(t
));
193 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
197 // Now suppose we can sample again. We capture the next frame, and not the one
198 // after that, and then we're back in the steady state again.
199 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
200 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
201 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
202 for (int i
= 0; i
< 100; i
++) {
203 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
204 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
205 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
206 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
207 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
208 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
212 // 30Hz sampled at 30Hz should produce 30Hz.
213 TEST(SmoothEventSamplerTest
, Sample30HertzAt30Hertz
) {
214 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
215 const int redundant_capture_goal
= 1;
216 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 30;
218 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
219 base::TimeTicks t
= InitialTestTimeTicks();
221 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
, &sampler
,
224 // Steady state, we should capture every vsync, indefinitely.
225 for (int i
= 0; i
< 200; i
++) {
226 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
227 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
230 // Now pretend we're limited by backpressure in the pipeline. In this scenario
231 // case we are adding events but not sampling them.
232 for (int i
= 0; i
< 10; i
++) {
233 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
234 ASSERT_EQ(i
>= 7, sampler
.IsOverdueForSamplingAt(t
));
235 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
239 // Now suppose we can sample again. We should be back in the steady state.
240 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
241 for (int i
= 0; i
< 100; i
++) {
242 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
243 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
247 // 24Hz sampled at 30Hz should produce 24Hz.
248 TEST(SmoothEventSamplerTest
, Sample24HertzAt30Hertz
) {
249 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
250 const int redundant_capture_goal
= 333;
251 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 24;
253 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
254 base::TimeTicks t
= InitialTestTimeTicks();
256 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
, &sampler
,
259 // Steady state, we should capture every vsync, indefinitely.
260 for (int i
= 0; i
< 200; i
++) {
261 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
262 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
265 // Now pretend we're limited by backpressure in the pipeline. In this scenario
266 // case we are adding events but not sampling them.
267 for (int i
= 0; i
< 10; i
++) {
268 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
269 ASSERT_EQ(i
>= 6, sampler
.IsOverdueForSamplingAt(t
));
270 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
274 // Now suppose we can sample again. We should be back in the steady state.
275 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
276 for (int i
= 0; i
< 100; i
++) {
277 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
278 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
282 // Tests that changing the minimum capture period during usage results in the
284 TEST(SmoothEventSamplerTest
, Sample60HertzWithVariedCapturePeriods
) {
285 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 60;
286 const base::TimeDelta one_to_one_period
= vsync
;
287 const base::TimeDelta two_to_one_period
= vsync
* 2;
288 const base::TimeDelta two_and_three_to_one_period
=
289 base::TimeDelta::FromSeconds(1) / 24;
290 const int redundant_capture_goal
= 1;
292 SmoothEventSampler
sampler(one_to_one_period
, redundant_capture_goal
);
293 base::TimeTicks t
= InitialTestTimeTicks();
295 TestRedundantCaptureStrategy(one_to_one_period
, redundant_capture_goal
,
298 // With the capture rate at 60 Hz, we should capture every vsync.
299 for (int i
= 0; i
< 100; i
++) {
300 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
301 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
304 // Now change to the capture rate to 30 Hz, and we should capture every other
306 sampler
.SetMinCapturePeriod(two_to_one_period
);
307 for (int i
= 0; i
< 100; i
++) {
308 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
309 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
310 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
313 // Now change the capture rate back to 60 Hz, and we should capture every
315 sampler
.SetMinCapturePeriod(one_to_one_period
);
316 for (int i
= 0; i
< 100; i
++) {
317 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
318 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
321 // Now change the capture rate to 24 Hz, and we should capture with a 2-3-2-3
323 sampler
.SetMinCapturePeriod(two_and_three_to_one_period
);
324 for (int i
= 0; i
< 100; i
++) {
325 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
326 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
327 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
328 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
329 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
330 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
334 TEST(SmoothEventSamplerTest
, DoubleDrawAtOneTimeStillDirties
) {
335 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
336 const base::TimeDelta overdue_period
= base::TimeDelta::FromSeconds(1);
338 SmoothEventSampler
sampler(capture_period
, 1);
339 base::TimeTicks t
= InitialTestTimeTicks();
341 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
342 sampler
.RecordSample();
343 ASSERT_FALSE(sampler
.IsOverdueForSamplingAt(t
))
344 << "Sampled last event; should not be dirty.";
347 // Now simulate 2 events with the same clock value.
348 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
349 sampler
.RecordSample();
350 ASSERT_FALSE(AddEventAndConsiderSampling(&sampler
, t
))
351 << "Two events at same time -- expected second not to be sampled.";
352 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
+ overdue_period
))
353 << "Second event should dirty the capture state.";
354 sampler
.RecordSample();
355 ASSERT_FALSE(sampler
.IsOverdueForSamplingAt(t
+ overdue_period
));
365 void ReplayCheckingSamplerDecisions(const DataPoint
* data_points
,
366 size_t num_data_points
,
367 SmoothEventSampler
* sampler
) {
368 base::TimeTicks t
= InitialTestTimeTicks();
369 for (size_t i
= 0; i
< num_data_points
; ++i
) {
370 t
+= base::TimeDelta::FromMicroseconds(
371 static_cast<int64
>(data_points
[i
].increment_ms
* 1000));
372 ASSERT_EQ(data_points
[i
].should_capture
,
373 AddEventAndConsiderSampling(sampler
, t
))
374 << "at data_points[" << i
<< ']';
375 if (data_points
[i
].should_capture
)
376 sampler
->RecordSample();
382 TEST(SmoothEventSamplerTest
, DrawingAt24FpsWith60HzVsyncSampledAt30Hertz
) {
383 // Actual capturing of timing data: Initial instability as a 24 FPS video was
384 // started from a still screen, then clearly followed by steady-state.
385 static const DataPoint data_points
[] = {{true, 1437.93},
458 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
459 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
462 TEST(SmoothEventSamplerTest
, DrawingAt30FpsWith60HzVsyncSampledAt30Hertz
) {
463 // Actual capturing of timing data: Initial instability as a 30 FPS video was
464 // started from a still screen, then followed by steady-state. Drawing
465 // framerate from the video rendering was a bit volatile, but averaged 30 FPS.
466 static const DataPoint data_points
[] = {{true, 2407.69},
567 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
568 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
571 TEST(SmoothEventSamplerTest
, DrawingAt60FpsWith60HzVsyncSampledAt30Hertz
) {
572 // Actual capturing of timing data: WebGL Acquarium demo
573 // (http://webglsamples.googlecode.com/hg/aquarium/aquarium.html) which ran
574 // between 55-60 FPS in the steady-state.
575 static const DataPoint data_points
[] = {{true, 16.72},
700 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
701 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);