| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/browser/media/capture/video_capture_oracle.h" | 5 #include "content/browser/media/capture/animated_content_sampler.h" |
| 6 | 6 |
| 7 #include <cstdlib> | 7 #include <cstdlib> |
| 8 #include <utility> | 8 #include <utility> |
| 9 #include <vector> | 9 #include <vector> |
| 10 | 10 |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "base/strings/stringprintf.h" | 12 #include "base/memory/scoped_ptr.h" |
| 13 #include "base/time/time.h" | 13 #include "base/time/time.h" |
| 14 #include "testing/gtest/include/gtest/gtest.h" | 14 #include "testing/gtest/include/gtest/gtest.h" |
| 15 #include "ui/gfx/geometry/rect.h" | 15 #include "ui/gfx/geometry/rect.h" |
| 16 | 16 |
| 17 namespace content { | 17 namespace content { |
| 18 |
| 18 namespace { | 19 namespace { |
| 19 | 20 |
| 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() { | 21 base::TimeTicks InitialTestTimeTicks() { |
| 49 return base::TimeTicks() + base::TimeDelta::FromSeconds(1); | 22 return base::TimeTicks() + base::TimeDelta::FromSeconds(1); |
| 50 } | 23 } |
| 51 | 24 |
| 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 | 25 } // namespace |
| 83 | 26 |
| 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, 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, 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, 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, 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, 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, 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 namespace { | |
| 313 | |
| 314 struct DataPoint { | |
| 315 bool should_capture; | |
| 316 double increment_ms; | |
| 317 }; | |
| 318 | |
| 319 void ReplayCheckingSamplerDecisions(const DataPoint* data_points, | |
| 320 size_t num_data_points, | |
| 321 SmoothEventSampler* sampler) { | |
| 322 base::TimeTicks t = InitialTestTimeTicks(); | |
| 323 for (size_t i = 0; i < num_data_points; ++i) { | |
| 324 t += base::TimeDelta::FromMicroseconds( | |
| 325 static_cast<int64>(data_points[i].increment_ms * 1000)); | |
| 326 ASSERT_EQ(data_points[i].should_capture, | |
| 327 AddEventAndConsiderSampling(sampler, t)) | |
| 328 << "at data_points[" << i << ']'; | |
| 329 if (data_points[i].should_capture) | |
| 330 sampler->RecordSample(); | |
| 331 } | |
| 332 } | |
| 333 | |
| 334 } // namespace | |
| 335 | |
| 336 TEST(SmoothEventSamplerTest, DrawingAt24FpsWith60HzVsyncSampledAt30Hertz) { | |
| 337 // Actual capturing of timing data: Initial instability as a 24 FPS video was | |
| 338 // started from a still screen, then clearly followed by steady-state. | |
| 339 static const DataPoint data_points[] = { | |
| 340 { true, 1437.93 }, { true, 150.484 }, { true, 217.362 }, { true, 50.161 }, | |
| 341 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 66.88 }, | |
| 342 { true, 50.161 }, { false, 0 }, { false, 0 }, { true, 50.16 }, | |
| 343 { true, 33.441 }, { true, 16.72 }, { false, 16.72 }, { true, 117.041 }, | |
| 344 { true, 16.72 }, { false, 16.72 }, { true, 50.161 }, { true, 50.16 }, | |
| 345 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 16.72 }, | |
| 346 { false, 0 }, { true, 50.161 }, { false, 0 }, { true, 33.44 }, | |
| 347 { true, 16.72 }, { false, 16.721 }, { true, 66.881 }, { false, 0 }, | |
| 348 { true, 33.441 }, { true, 16.72 }, { true, 50.16 }, { true, 16.72 }, | |
| 349 { false, 16.721 }, { true, 50.161 }, { true, 50.16 }, { false, 0 }, | |
| 350 { true, 33.441 }, { true, 50.337 }, { true, 50.183 }, { true, 16.722 }, | |
| 351 { true, 50.161 }, { true, 33.441 }, { true, 50.16 }, { true, 33.441 }, | |
| 352 { true, 50.16 }, { true, 33.441 }, { true, 50.16 }, { true, 33.44 }, | |
| 353 { true, 50.161 }, { true, 50.16 }, { true, 33.44 }, { true, 33.441 }, | |
| 354 { true, 50.16 }, { true, 50.161 }, { true, 33.44 }, { true, 33.441 }, | |
| 355 { true, 50.16 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 }, | |
| 356 { true, 50.161 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 }, | |
| 357 { true, 83.601 }, { true, 16.72 }, { true, 33.44 }, { false, 0 } | |
| 358 }; | |
| 359 | |
| 360 SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, 3); | |
| 361 ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler); | |
| 362 } | |
| 363 | |
| 364 TEST(SmoothEventSamplerTest, DrawingAt30FpsWith60HzVsyncSampledAt30Hertz) { | |
| 365 // Actual capturing of timing data: Initial instability as a 30 FPS video was | |
| 366 // started from a still screen, then followed by steady-state. Drawing | |
| 367 // framerate from the video rendering was a bit volatile, but averaged 30 FPS. | |
| 368 static const DataPoint data_points[] = { | |
| 369 { true, 2407.69 }, { true, 16.733 }, { true, 217.362 }, { true, 33.441 }, | |
| 370 { true, 33.44 }, { true, 33.44 }, { true, 33.441 }, { true, 33.44 }, | |
| 371 { true, 33.44 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, | |
| 372 { true, 16.721 }, { true, 33.44 }, { false, 0 }, { true, 50.161 }, | |
| 373 { true, 50.16 }, { false, 0 }, { true, 50.161 }, { true, 33.44 }, | |
| 374 { true, 16.72 }, { false, 0 }, { false, 16.72 }, { true, 66.881 }, | |
| 375 { false, 0 }, { true, 33.44 }, { true, 16.72 }, { true, 50.161 }, | |
| 376 { false, 0 }, { true, 33.538 }, { true, 33.526 }, { true, 33.447 }, | |
| 377 { true, 33.445 }, { true, 33.441 }, { true, 16.721 }, { true, 33.44 }, | |
| 378 { true, 33.44 }, { true, 50.161 }, { true, 16.72 }, { true, 33.44 }, | |
| 379 { true, 33.441 }, { true, 33.44 }, { false, 0 }, { false, 16.72 }, | |
| 380 { true, 66.881 }, { true, 16.72 }, { false, 16.72 }, { true, 50.16 }, | |
| 381 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 33.44 }, | |
| 382 { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { false, 0 }, | |
| 383 { true, 33.44 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 }, | |
| 384 { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 66.88 }, | |
| 385 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 }, | |
| 386 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 }, | |
| 387 { true, 16.72 }, { true, 50.161 }, { false, 0 }, { true, 50.16 }, | |
| 388 { false, 0.001 }, { true, 16.721 }, { true, 66.88 }, { true, 33.44 }, | |
| 389 { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 }, | |
| 390 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 66.881 }, | |
| 391 { true, 33.44 }, { true, 16.72 }, { true, 33.441 }, { false, 16.72 }, | |
| 392 { true, 66.88 }, { true, 16.721 }, { true, 50.16 }, { true, 33.44 }, | |
| 393 { true, 16.72 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 } | |
| 394 }; | |
| 395 | |
| 396 SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, 3); | |
| 397 ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler); | |
| 398 } | |
| 399 | |
| 400 TEST(SmoothEventSamplerTest, DrawingAt60FpsWith60HzVsyncSampledAt30Hertz) { | |
| 401 // Actual capturing of timing data: WebGL Acquarium demo | |
| 402 // (http://webglsamples.googlecode.com/hg/aquarium/aquarium.html) which ran | |
| 403 // between 55-60 FPS in the steady-state. | |
| 404 static const DataPoint data_points[] = { | |
| 405 { true, 16.72 }, { true, 16.72 }, { true, 4163.29 }, { true, 50.193 }, | |
| 406 { true, 117.041 }, { true, 50.161 }, { true, 50.16 }, { true, 33.441 }, | |
| 407 { true, 50.16 }, { true, 33.44 }, { false, 0 }, { false, 0 }, | |
| 408 { true, 50.161 }, { true, 83.601 }, { true, 50.16 }, { true, 16.72 }, | |
| 409 { true, 33.441 }, { false, 16.72 }, { true, 50.16 }, { true, 16.72 }, | |
| 410 { false, 0.001 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 }, | |
| 411 { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 }, | |
| 412 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 }, | |
| 413 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 }, | |
| 414 { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 }, | |
| 415 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.441 }, | |
| 416 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 33.44 }, | |
| 417 { false, 0 }, { true, 16.721 }, { true, 50.161 }, { false, 0 }, | |
| 418 { true, 33.44 }, { false, 0 }, { true, 16.72 }, { true, 33.441 }, | |
| 419 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 }, | |
| 420 { true, 50.16 }, { false, 0 }, { true, 16.721 }, { true, 33.44 }, | |
| 421 { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 }, | |
| 422 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 }, | |
| 423 { false, 0 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 }, | |
| 424 { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 }, | |
| 425 { true, 33.44 }, { false, 0 }, { true, 33.44 }, { true, 33.441 }, | |
| 426 { false, 0 }, { true, 33.44 }, { true, 33.441 }, { false, 0 }, | |
| 427 { true, 33.44 }, { false, 0 }, { true, 33.44 }, { false, 16.72 }, | |
| 428 { true, 16.721 }, { true, 50.161 }, { false, 0 }, { true, 16.72 }, | |
| 429 { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 33.44 }, | |
| 430 { true, 33.44 }, { false, 0 }, { true, 33.441 }, { false, 16.72 }, | |
| 431 { true, 16.72 }, { true, 50.16 }, { false, 0 }, { true, 16.72 }, | |
| 432 { true, 33.441 }, { false, 0 }, { true, 33.44 }, { false, 16.72 }, | |
| 433 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 50.161 }, | |
| 434 { false, 0 }, { true, 16.72 }, { true, 33.44 }, { false, 0 }, | |
| 435 { true, 33.441 }, { false, 16.72 }, { true, 16.72 }, { true, 50.16 } | |
| 436 }; | |
| 437 | |
| 438 SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, 3); | |
| 439 ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler); | |
| 440 } | |
| 441 | |
| 442 class AnimatedContentSamplerTest : public ::testing::Test { | 27 class AnimatedContentSamplerTest : public ::testing::Test { |
| 443 public: | 28 public: |
| 444 AnimatedContentSamplerTest() {} | 29 AnimatedContentSamplerTest() {} |
| 445 ~AnimatedContentSamplerTest() override {} | 30 ~AnimatedContentSamplerTest() override {} |
| 446 | 31 |
| 447 void SetUp() override { | 32 void SetUp() override { |
| 448 const base::TimeDelta since_epoch = | 33 const base::TimeDelta since_epoch = |
| 449 InitialTestTimeTicks() - base::TimeTicks::UnixEpoch(); | 34 InitialTestTimeTicks() - base::TimeTicks::UnixEpoch(); |
| 450 rand_seed_ = abs(static_cast<int>(since_epoch.InMicroseconds())); | 35 rand_seed_ = abs(static_cast<int>(since_epoch.InMicroseconds())); |
| 451 sampler_.reset(new AnimatedContentSampler(GetMinCapturePeriod())); | 36 sampler_.reset(new AnimatedContentSampler(GetMinCapturePeriod())); |
| (...skipping 594 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1046 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(20)), | 631 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(20)), |
| 1047 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(23)), | 632 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(23)), |
| 1048 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(26)), | 633 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(26)), |
| 1049 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(27)), | 634 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(27)), |
| 1050 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(28)), | 635 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(28)), |
| 1051 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(29)), | 636 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(29)), |
| 1052 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(31)), | 637 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(31)), |
| 1053 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(32)), | 638 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(32)), |
| 1054 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(33)))); | 639 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(33)))); |
| 1055 | 640 |
| 1056 // Tests that VideoCaptureOracle filters out events whose timestamps are | |
| 1057 // decreasing. | |
| 1058 TEST(VideoCaptureOracleTest, EnforcesEventTimeMonotonicity) { | |
| 1059 const base::TimeDelta min_capture_period = | |
| 1060 base::TimeDelta::FromSeconds(1) / 30; | |
| 1061 const gfx::Rect damage_rect(0, 0, 1280, 720); | |
| 1062 const base::TimeDelta event_increment = min_capture_period * 2; | |
| 1063 | |
| 1064 VideoCaptureOracle oracle(min_capture_period); | |
| 1065 | |
| 1066 base::TimeTicks t = InitialTestTimeTicks(); | |
| 1067 for (int i = 0; i < 10; ++i) { | |
| 1068 t += event_increment; | |
| 1069 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( | |
| 1070 VideoCaptureOracle::kCompositorUpdate, | |
| 1071 damage_rect, t)); | |
| 1072 } | |
| 1073 | |
| 1074 base::TimeTicks furthest_event_time = t; | |
| 1075 for (int i = 0; i < 10; ++i) { | |
| 1076 t -= event_increment; | |
| 1077 ASSERT_FALSE(oracle.ObserveEventAndDecideCapture( | |
| 1078 VideoCaptureOracle::kCompositorUpdate, | |
| 1079 damage_rect, t)); | |
| 1080 } | |
| 1081 | |
| 1082 t = furthest_event_time; | |
| 1083 for (int i = 0; i < 10; ++i) { | |
| 1084 t += event_increment; | |
| 1085 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( | |
| 1086 VideoCaptureOracle::kCompositorUpdate, | |
| 1087 damage_rect, t)); | |
| 1088 } | |
| 1089 } | |
| 1090 | |
| 1091 // Tests that VideoCaptureOracle is enforcing the requirement that captured | |
| 1092 // frames are delivered in order. Otherwise, downstream consumers could be | |
| 1093 // tripped-up by out-of-order frames or frame timestamps. | |
| 1094 TEST(VideoCaptureOracleTest, EnforcesFramesDeliveredInOrder) { | |
| 1095 const base::TimeDelta min_capture_period = | |
| 1096 base::TimeDelta::FromSeconds(1) / 30; | |
| 1097 const gfx::Rect damage_rect(0, 0, 1280, 720); | |
| 1098 const base::TimeDelta event_increment = min_capture_period * 2; | |
| 1099 | |
| 1100 VideoCaptureOracle oracle(min_capture_period); | |
| 1101 | |
| 1102 // Most basic scenario: Frames delivered one at a time, with no additional | |
| 1103 // captures in-between deliveries. | |
| 1104 base::TimeTicks t = InitialTestTimeTicks(); | |
| 1105 int last_frame_number; | |
| 1106 base::TimeTicks ignored; | |
| 1107 for (int i = 0; i < 10; ++i) { | |
| 1108 t += event_increment; | |
| 1109 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( | |
| 1110 VideoCaptureOracle::kCompositorUpdate, | |
| 1111 damage_rect, t)); | |
| 1112 last_frame_number = oracle.RecordCapture(); | |
| 1113 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, &ignored)); | |
| 1114 } | |
| 1115 | |
| 1116 // Basic pipelined scenario: More than one frame in-flight at delivery points. | |
| 1117 for (int i = 0; i < 50; ++i) { | |
| 1118 const int num_in_flight = 1 + i % 3; | |
| 1119 for (int j = 0; j < num_in_flight; ++j) { | |
| 1120 t += event_increment; | |
| 1121 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( | |
| 1122 VideoCaptureOracle::kCompositorUpdate, | |
| 1123 damage_rect, t)); | |
| 1124 last_frame_number = oracle.RecordCapture(); | |
| 1125 } | |
| 1126 for (int j = num_in_flight - 1; j >= 0; --j) { | |
| 1127 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number - j, &ignored)); | |
| 1128 } | |
| 1129 } | |
| 1130 | |
| 1131 // Pipelined scenario with out-of-order delivery attempts rejected. | |
| 1132 for (int i = 0; i < 50; ++i) { | |
| 1133 const int num_in_flight = 1 + i % 3; | |
| 1134 for (int j = 0; j < num_in_flight; ++j) { | |
| 1135 t += event_increment; | |
| 1136 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( | |
| 1137 VideoCaptureOracle::kCompositorUpdate, | |
| 1138 damage_rect, t)); | |
| 1139 last_frame_number = oracle.RecordCapture(); | |
| 1140 } | |
| 1141 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, &ignored)); | |
| 1142 for (int j = 1; j < num_in_flight; ++j) { | |
| 1143 ASSERT_FALSE(oracle.CompleteCapture(last_frame_number - j, &ignored)); | |
| 1144 } | |
| 1145 } | |
| 1146 } | |
| 1147 | |
| 1148 // Tests that VideoCaptureOracle transitions between using its two samplers in a | |
| 1149 // way that does not introduce severe jank, pauses, etc. | |
| 1150 TEST(VideoCaptureOracleTest, TransitionsSmoothlyBetweenSamplers) { | |
| 1151 const base::TimeDelta min_capture_period = | |
| 1152 base::TimeDelta::FromSeconds(1) / 30; | |
| 1153 const gfx::Rect animation_damage_rect(0, 0, 1280, 720); | |
| 1154 const base::TimeDelta event_increment = min_capture_period * 2; | |
| 1155 | |
| 1156 VideoCaptureOracle oracle(min_capture_period); | |
| 1157 | |
| 1158 // Run sequences of animation events and non-animation events through the | |
| 1159 // oracle. As the oracle transitions between each sampler, make sure the | |
| 1160 // frame timestamps won't trip-up downstream consumers. | |
| 1161 base::TimeTicks t = InitialTestTimeTicks(); | |
| 1162 base::TimeTicks last_frame_timestamp; | |
| 1163 for (int i = 0; i < 1000; ++i) { | |
| 1164 t += event_increment; | |
| 1165 | |
| 1166 // For every 100 events, provide 50 that will cause the | |
| 1167 // AnimatedContentSampler to lock-in, followed by 50 that will cause it to | |
| 1168 // lock-out (i.e., the oracle will use the SmoothEventSampler instead). | |
| 1169 const bool provide_animated_content_event = | |
| 1170 (i % 100) >= 25 && (i % 100) < 75; | |
| 1171 | |
| 1172 // Only the few events that trigger the lock-out transition should be | |
| 1173 // dropped, because the AnimatedContentSampler doesn't yet realize the | |
| 1174 // animation ended. Otherwise, the oracle should always decide to sample | |
| 1175 // because one of its samplers says to. | |
| 1176 const bool require_oracle_says_sample = (i % 100) < 75 || (i % 100) >= 78; | |
| 1177 const bool oracle_says_sample = oracle.ObserveEventAndDecideCapture( | |
| 1178 VideoCaptureOracle::kCompositorUpdate, | |
| 1179 provide_animated_content_event ? animation_damage_rect : gfx::Rect(), | |
| 1180 t); | |
| 1181 if (require_oracle_says_sample) | |
| 1182 ASSERT_TRUE(oracle_says_sample); | |
| 1183 if (!oracle_says_sample) | |
| 1184 continue; | |
| 1185 | |
| 1186 const int frame_number = oracle.RecordCapture(); | |
| 1187 | |
| 1188 base::TimeTicks frame_timestamp; | |
| 1189 ASSERT_TRUE(oracle.CompleteCapture(frame_number, &frame_timestamp)); | |
| 1190 ASSERT_FALSE(frame_timestamp.is_null()); | |
| 1191 if (!last_frame_timestamp.is_null()) { | |
| 1192 const base::TimeDelta delta = frame_timestamp - last_frame_timestamp; | |
| 1193 EXPECT_LE(event_increment.InMicroseconds(), delta.InMicroseconds()); | |
| 1194 // Right after the AnimatedContentSampler lock-out transition, there were | |
| 1195 // a few frames dropped, so allow a gap in the timestamps. Otherwise, the | |
| 1196 // delta between frame timestamps should never be more than 2X the | |
| 1197 // |event_increment|. | |
| 1198 const base::TimeDelta max_acceptable_delta = (i % 100) == 78 ? | |
| 1199 event_increment * 5 : event_increment * 2; | |
| 1200 EXPECT_GE(max_acceptable_delta.InMicroseconds(), delta.InMicroseconds()); | |
| 1201 } | |
| 1202 last_frame_timestamp = frame_timestamp; | |
| 1203 } | |
| 1204 } | |
| 1205 | |
| 1206 } // namespace content | 641 } // namespace content |
| OLD | NEW |