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. 4 5 #include "content/browser/media/capture/video_capture_oracle.h" 6 7 #include <cstdlib> 8 #include <utility> 9 #include <vector> 10 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" 16 17 namespace content { 18 namespace { 19 20 bool AddEventAndConsiderSampling(SmoothEventSampler* sampler, 21 base::TimeTicks event_time) { 22 sampler->ConsiderPresentationEvent(event_time); 23 return sampler->ShouldSample(); 24 } 25 26 void SteadyStateSampleAndAdvance(base::TimeDelta vsync, 27 SmoothEventSampler* sampler, 28 base::TimeTicks* t) { 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)); 34 *t += vsync; 35 ASSERT_FALSE(sampler->IsOverdueForSamplingAt(*t)); 36 } 37 38 void SteadyStateNoSampleAndAdvance(base::TimeDelta vsync, 39 SmoothEventSampler* sampler, 40 base::TimeTicks* t) { 41 ASSERT_FALSE(AddEventAndConsiderSampling(sampler, *t)); 42 ASSERT_TRUE(sampler->HasUnrecordedEvent()); 43 ASSERT_FALSE(sampler->IsOverdueForSamplingAt(*t)); 44 *t += vsync; 45 ASSERT_FALSE(sampler->IsOverdueForSamplingAt(*t)); 46 } 47 48 base::TimeTicks InitialTestTimeTicks() { 49 return base::TimeTicks() + base::TimeDelta::FromSeconds(1); 50 } 51 52 void TestRedundantCaptureStrategy(base::TimeDelta capture_period, 53 int redundant_capture_goal, 54 SmoothEventSampler* sampler, 55 base::TimeTicks* t) { 56 // Before any events have been considered, we're overdue for sampling. 57 ASSERT_TRUE(sampler->IsOverdueForSamplingAt(*t)); 58 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()); 65 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. 77 } 78 ASSERT_FALSE(sampler->IsOverdueForSamplingAt(*t)) 79 << "Should not be overdue once redundant capture goal achieved."; 80 } 81 82 } // namespace 83 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; 90 91 SmoothEventSampler sampler(capture_period, true, redundant_capture_goal); 92 base::TimeTicks t = InitialTestTimeTicks(); 93 94 TestRedundantCaptureStrategy(capture_period, redundant_capture_goal, 95 &sampler, &t); 96 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); 102 } 103 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()); 111 t += vsync; 112 } 113 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); 121 } 122 } 123 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; 129 130 SmoothEventSampler sampler(capture_period, true, redundant_capture_goal); 131 base::TimeTicks t = InitialTestTimeTicks(); 132 133 TestRedundantCaptureStrategy(capture_period, redundant_capture_goal, 134 &sampler, &t); 135 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); 145 } 146 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)); 153 t += vsync; 154 } 155 156 // Now suppose we can sample again. We should be back in the steady state 157 // again. 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); 166 } 167 } 168 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; 174 175 SmoothEventSampler sampler(capture_period, true, redundant_capture_goal); 176 base::TimeTicks t = InitialTestTimeTicks(); 177 178 TestRedundantCaptureStrategy(capture_period, redundant_capture_goal, 179 &sampler, &t); 180 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); 192 } 193 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)); 200 t += vsync; 201 } 202 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); 215 } 216 } 217 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; 223 224 SmoothEventSampler sampler(capture_period, true, redundant_capture_goal); 225 base::TimeTicks t = InitialTestTimeTicks(); 226 227 TestRedundantCaptureStrategy(capture_period, redundant_capture_goal, 228 &sampler, &t); 229 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); 234 } 235 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)); 242 t += vsync; 243 } 244 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); 250 } 251 } 252 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; 258 259 SmoothEventSampler sampler(capture_period, true, redundant_capture_goal); 260 base::TimeTicks t = InitialTestTimeTicks(); 261 262 TestRedundantCaptureStrategy(capture_period, redundant_capture_goal, 263 &sampler, &t); 264 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); 269 } 270 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)); 277 t += vsync; 278 } 279 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); 285 } 286 } 287 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); 291 292 SmoothEventSampler sampler(capture_period, true, 1); 293 base::TimeTicks t = InitialTestTimeTicks(); 294 295 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler, t)); 296 sampler.RecordSample(); 297 ASSERT_FALSE(sampler.IsOverdueForSamplingAt(t)) 298 << "Sampled last event; should not be dirty."; 299 t += overdue_period; 300 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)); 310 } 311 312 TEST(SmoothEventSamplerTest, FallbackToPollingIfUpdatesUnreliable) { 313 const base::TimeDelta timer_interval = base::TimeDelta::FromSeconds(1) / 30; 314 315 SmoothEventSampler should_not_poll(timer_interval, true, 1); 316 SmoothEventSampler should_poll(timer_interval, false, 1); 317 base::TimeTicks t = InitialTestTimeTicks(); 318 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(); 325 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++) { 331 t += timer_interval; 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."; 336 } 337 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(); 348 349 // Forever more, the non-polling sampler returns false while the polling one 350 // returns true. 351 for (int i = 0; i < 100; ++i) { 352 t += timer_interval; 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(); 358 } 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(); 365 } 366 367 namespace { 368 369 struct DataPoint { 370 bool should_capture; 371 double increment_ms; 372 }; 373 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(); 386 } 387 } 388 389 } // namespace 390 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 } 413 }; 414 415 SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, true, 3); 416 ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler); 417 } 418 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 } 449 }; 450 451 SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, true, 3); 452 ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler); 453 } 454 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 } 491 }; 492 493 SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, true, 3); 494 ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler); 495 } 496 497 class AnimatedContentSamplerTest : public ::testing::Test { 498 public: 499 AnimatedContentSamplerTest() {} 500 virtual ~AnimatedContentSamplerTest() {} 501 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())); 507 } 508 509 protected: 510 // Overridden by subclass for parameterized tests. 511 virtual base::TimeDelta GetMinCapturePeriod() const { 512 return base::TimeDelta::FromSeconds(1) / 30; 513 } 514 515 AnimatedContentSampler* sampler() const { 516 return sampler_.get(); 517 } 518 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; 523 } 524 525 gfx::Rect GetRandomDamageRect() { 526 return gfx::Rect(0, 0, GetRandomInRange(1, 100), GetRandomInRange(1, 100)); 527 } 528 529 gfx::Rect GetContentDamageRect() { 530 // This must be distinct from anything GetRandomDamageRect() could return. 531 return gfx::Rect(0, 0, 1280, 720); 532 } 533 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())); 539 } 540 541 gfx::Rect ElectMajorityDamageRect() const { 542 return sampler_->ElectMajorityDamageRect(); 543 } 544 545 private: 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); 550 return rand_seed_; 551 } 552 553 int rand_seed_; 554 scoped_ptr<AnimatedContentSampler> sampler_; 555 }; 556 557 TEST_F(AnimatedContentSamplerTest, ElectsNoneFromZeroDamageRects) { 558 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect()); 559 } 560 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()); 565 } 566 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()); 573 } 574 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()); 581 } 582 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()); 589 } 590 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 594 // vote. 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()); 610 } 611 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); 631 } 632 EXPECT_EQ(video_rect, ElectMajorityDamageRect()); 633 } 634 635 namespace { 636 637 // A test scenario for AnimatedContentSamplerParameterizedTest. 638 struct Scenario { 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. 642 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."; 647 } 648 }; 649 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() 655 << " }"; 656 } 657 658 base::TimeDelta FpsAsPeriod(int frame_rate) { 659 return base::TimeDelta::FromSeconds(1) / frame_rate; 660 } 661 662 } // namespace 663 664 class AnimatedContentSamplerParameterizedTest 665 : public AnimatedContentSamplerTest, 666 public ::testing::WithParamInterface<Scenario> { 667 public: 668 AnimatedContentSamplerParameterizedTest() 669 : count_dropped_frames_(0), count_sampled_frames_(0) {} 670 virtual ~AnimatedContentSamplerParameterizedTest() {} 671 672 protected: 673 typedef std::pair<gfx::Rect, base::TimeTicks> Event; 674 675 virtual base::TimeDelta GetMinCapturePeriod() const OVERRIDE { 676 return GetParam().min_capture_period; 677 } 678 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, 682 base::TimeTicks end, 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)); 695 } 696 } 697 698 DCHECK(!events.empty()); 699 return events; 700 } 701 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; 710 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); 717 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; 723 } 724 ASSERT_EQ( 725 has_detection_switched ? is_detecting_after : was_detecting_before, 726 sampler()->HasProposal()); 727 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()); 734 } else { 735 EXPECT_EQ(first_detected_region, sampler()->detected_region()); 736 } 737 738 if (simulate_pipeline_back_pressure && GetRandomInRange(0, 2) == 0) 739 ClientCannotSampleFrame(*i); 740 else 741 ClientDoesWhatSamplerProposes(*i); 742 } else { 743 EXPECT_FALSE(sampler()->ShouldSample()); 744 if (!simulate_pipeline_back_pressure || GetRandomInRange(0, 2) == 1) 745 sampler()->RecordSample(i->second); 746 } 747 } 748 EXPECT_EQ(is_detecting_after, sampler()->HasProposal()); 749 ExpectFrameDropRatioIsCorrect(); 750 } 751 752 void ResetFrameCounters() { 753 count_dropped_frames_ = 0; 754 count_sampled_frames_ = 0; 755 } 756 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_; 766 } 767 } 768 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_; 777 } 778 } 779 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_); 785 return; 786 } 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); 796 } 797 798 private: 799 // These counters only include the frames with the desired content. 800 int count_dropped_frames_; 801 int count_sampled_frames_; 802 }; 803 804 // Tests that the implementation locks in/out of frames containing stable 805 // animated content, whether or not random events are also simultaneously 806 // present. 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(); 811 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), 815 false, 816 false, 817 false); 818 begin = end; 819 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), 824 false, 825 true, 826 false); 827 begin = end; 828 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), 833 true, 834 true, 835 false); 836 begin = end; 837 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), 842 true, 843 true, 844 true); 845 begin = end; 846 847 // Provide a half-second of random events only, and expect the lock-in to be 848 // broken. 849 end = begin + base::TimeDelta::FromMilliseconds(500); 850 RunEventSequence(GenerateEventSequence(begin, end, false, true), 851 true, 852 false, 853 false); 854 begin = end; 855 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), 860 false, 861 true, 862 false); 863 begin = end; 864 } 865 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) 875 return; 876 877 // Start the first animation and run for a bit, and expect the sampler to 878 // lock-in. 879 base::TimeTicks begin = InitialTestTimeTicks(); 880 base::TimeTicks end = begin + base::TimeDelta::FromSeconds(5); 881 RunEventSequence(GenerateEventSequence(begin, end, true, false), 882 false, 883 true, 884 false); 885 begin = end; 886 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)); 904 } 905 RunEventSequence(both_animations_events, true, false, false); 906 begin = end; 907 908 // Now, run just the first animation, and expect the sampler to lock-in once 909 // again. 910 end = begin + base::TimeDelta::FromSeconds(5); 911 RunEventSequence(GenerateEventSequence(begin, end, true, false), 912 false, 913 true, 914 false); 915 begin = end; 916 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)); 932 } 933 include_second_animation_frame = !include_second_animation_frame; 934 } 935 RunEventSequence(both_animations_events, true, false, false); 936 begin = end; 937 } 938 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 941 // v-sync intervals. 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( 948 begin, 949 begin + base::TimeDelta::FromSeconds(20), 950 true, 951 false); 952 typedef std::vector<base::TimeTicks> Timestamps; 953 Timestamps frame_timestamps; 954 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end(); 955 ++i) { 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()); 961 } 962 } else { 963 frame_timestamps.clear(); // Reset until continuous lock-in. 964 } 965 } 966 ASSERT_LE(2u, frame_timestamps.size()); 967 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; 986 } 987 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]; 1004 } 1005 } 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]); 1013 ++repeats; 1014 } else if (display_counts[repeats] == second_highest_count) { 1015 EXPECT_EQ(highest_count, display_counts[repeats + 1]); 1016 ++repeats; 1017 } else { 1018 EXPECT_GE(stray_count_remaining, display_counts[repeats]); 1019 stray_count_remaining -= display_counts[repeats]; 1020 } 1021 } 1022 } 1023 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; 1030 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); 1035 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) { 1042 if (i % 100 == 0) { 1043 accumulated_drift += base::TimeDelta::FromMilliseconds( 1044 GetRandomInRange(1, max_drift_increment_millis + 1)); 1045 } 1046 events[i].second += accumulated_drift; 1047 } 1048 1049 // Run all the events through the sampler and track the last rewritten frame 1050 // timestamp. 1051 base::TimeTicks last_frame_timestamp; 1052 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end(); 1053 ++i) { 1054 sampler()->ConsiderPresentationEvent(i->first, i->second); 1055 if (sampler()->ShouldSample()) 1056 last_frame_timestamp = sampler()->frame_timestamp(); 1057 } 1058 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); 1065 EXPECT_NEAR(0.0, 1066 total_error.InMicroseconds(), 1067 max_acceptable_error.InMicroseconds()); 1068 } 1069 1070 INSTANTIATE_TEST_CASE_P( 1071 , 1072 AnimatedContentSamplerParameterizedTest, 1073 ::testing::Values( 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)), 1079 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)), 1085 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)), 1091 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)), 1098 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)))); 1110 1111 // Tests that VideoCaptureOracle filters out events whose timestamps are 1112 // decreasing. 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; 1118 1119 VideoCaptureOracle oracle(min_capture_period, true); 1120 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, 1126 damage_rect, t)); 1127 } 1128 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, 1134 damage_rect, t)); 1135 } 1136 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, 1142 damage_rect, t)); 1143 } 1144 } 1145 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; 1154 1155 VideoCaptureOracle oracle(min_capture_period, true); 1156 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, 1166 damage_rect, t)); 1167 last_frame_number = oracle.RecordCapture(); 1168 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, &ignored)); 1169 } 1170 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, 1178 damage_rect, t)); 1179 last_frame_number = oracle.RecordCapture(); 1180 } 1181 for (int j = num_in_flight - 1; j >= 0; --j) { 1182 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number - j, &ignored)); 1183 } 1184 } 1185 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, 1193 damage_rect, t)); 1194 last_frame_number = oracle.RecordCapture(); 1195 } 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)); 1199 } 1200 } 1201 } 1202 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; 1210 1211 VideoCaptureOracle oracle(min_capture_period, true); 1212 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; 1220 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; 1226 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(), 1235 t); 1236 if (require_oracle_says_sample) 1237 ASSERT_TRUE(oracle_says_sample); 1238 if (!oracle_says_sample) 1239 continue; 1240 1241 const int frame_number = oracle.RecordCapture(); 1242 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()); 1256 } 1257 last_frame_timestamp = frame_timestamp; 1258 } 1259 } 1260 1261 } // namespace content 1262