OLD | NEW |
(Empty) | |
| 1 // Copyright 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. |
| 4 |
| 5 #include <cmath> |
| 6 |
| 7 #include "base/bind.h" |
| 8 #include "base/bind_helpers.h" |
| 9 #include "base/strings/stringprintf.h" |
| 10 #include "base/test/simple_test_tick_clock.h" |
| 11 #include "media/base/video_frame_pool.h" |
| 12 #include "media/base/wall_clock_time_source.h" |
| 13 #include "media/filters/video_renderer_algorithm.h" |
| 14 #include "testing/gtest/include/gtest/gtest.h" |
| 15 |
| 16 namespace media { |
| 17 |
| 18 // Slows down the given |fps| according to NTSC field reduction standards; see |
| 19 // http://en.wikipedia.org/wiki/Frame_rate#Digital_video_and_television |
| 20 #define NTSC(fps) fps / 1.001 |
| 21 |
| 22 // Helper class for generating TimeTicks in a sequence according to a frequency. |
| 23 class TickGenerator { |
| 24 public: |
| 25 TickGenerator(base::TimeTicks base_timestamp, double hertz) |
| 26 : tick_count_(0), |
| 27 hertz_(hertz), |
| 28 microseconds_per_tick_(base::Time::kMicrosecondsPerSecond / hertz), |
| 29 base_time_(base_timestamp) {} |
| 30 |
| 31 base::TimeDelta interval(int tick_count) { |
| 32 return base::TimeDelta::FromMicroseconds(tick_count * |
| 33 microseconds_per_tick_); |
| 34 } |
| 35 |
| 36 base::TimeTicks current() { return base_time_ + interval(tick_count_); } |
| 37 base::TimeTicks step() { return step(1); } |
| 38 base::TimeTicks step(int n) { |
| 39 tick_count_ += n; |
| 40 return current(); |
| 41 } |
| 42 |
| 43 double hertz() const { return hertz_; } |
| 44 |
| 45 void Reset(base::TimeTicks base_timestamp) { |
| 46 base_time_ = base_timestamp; |
| 47 tick_count_ = 0; |
| 48 } |
| 49 |
| 50 private: |
| 51 // Track a tick count and seconds per tick value to ensure we don't drift too |
| 52 // far due to accumulated errors during testing. |
| 53 int64_t tick_count_; |
| 54 double hertz_; |
| 55 double microseconds_per_tick_; |
| 56 base::TimeTicks base_time_; |
| 57 |
| 58 DISALLOW_COPY_AND_ASSIGN(TickGenerator); |
| 59 }; |
| 60 |
| 61 class VideoRendererAlgorithmTest : public testing::Test { |
| 62 public: |
| 63 VideoRendererAlgorithmTest() |
| 64 : tick_clock_(new base::SimpleTestTickClock()), |
| 65 algorithm_(base::Bind(&WallClockTimeSource::GetWallClockTime, |
| 66 base::Unretained(&time_source_))) { |
| 67 // Always start the TickClock at a non-zero value since null values have |
| 68 // special connotations. |
| 69 tick_clock_->Advance(base::TimeDelta::FromMicroseconds(10000)); |
| 70 time_source_.SetTickClockForTesting( |
| 71 scoped_ptr<base::TickClock>(tick_clock_)); |
| 72 } |
| 73 ~VideoRendererAlgorithmTest() override {} |
| 74 |
| 75 scoped_refptr<VideoFrame> CreateFrame(base::TimeDelta timestamp) { |
| 76 const gfx::Size natural_size(8, 8); |
| 77 return frame_pool_.CreateFrame(VideoFrame::YV12, natural_size, |
| 78 gfx::Rect(natural_size), natural_size, |
| 79 timestamp); |
| 80 } |
| 81 |
| 82 base::TimeDelta minimum_glitch_time() const { |
| 83 return base::TimeDelta::FromSeconds( |
| 84 VideoRendererAlgorithm::kMinimumAcceptableTimeBetweenGlitchesSecs); |
| 85 } |
| 86 |
| 87 base::TimeDelta max_acceptable_drift() const { |
| 88 return algorithm_.max_acceptable_drift_; |
| 89 } |
| 90 |
| 91 void disable_cadence_hysteresis() { |
| 92 algorithm_.cadence_hysteresis_enabled_ = false; |
| 93 } |
| 94 |
| 95 bool last_render_had_glitch() const { |
| 96 return algorithm_.last_render_had_glitch_; |
| 97 } |
| 98 |
| 99 bool is_using_cadence() const { return algorithm_.ideal_cadence_ > 0; } |
| 100 |
| 101 size_t frames_queued() const { return algorithm_.frame_queue_.size(); } |
| 102 |
| 103 int GetCadence(double frame_rate, double display_rate) { |
| 104 TickGenerator display_tg(tick_clock_->NowTicks(), display_rate); |
| 105 TickGenerator frame_tg(base::TimeTicks(), frame_rate); |
| 106 time_source_.StartTicking(); |
| 107 |
| 108 // Enqueue enough frames for cadence detection. |
| 109 int frames_dropped = 0; |
| 110 disable_cadence_hysteresis(); |
| 111 algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(0))); |
| 112 algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(1))); |
| 113 EXPECT_TRUE(algorithm_.Render(display_tg.current(), display_tg.step(), |
| 114 &frames_dropped)); |
| 115 |
| 116 // Store cadence before reseting the algorithm. |
| 117 const int cadence = algorithm_.fractional_cadence_ |
| 118 ? algorithm_.fractional_cadence_ |
| 119 : algorithm_.ideal_cadence_; |
| 120 |
| 121 time_source_.StopTicking(); |
| 122 algorithm_.Reset(); |
| 123 return cadence; |
| 124 } |
| 125 |
| 126 bool DriftOfLastRenderWasWithinTolerance(base::TimeTicks deadline_min) { |
| 127 return algorithm_.CalculateDriftForFrame(deadline_min, 0) <= |
| 128 algorithm_.max_acceptable_drift_; |
| 129 } |
| 130 |
| 131 // Allows tests to run a Render() loop with sufficient frames for the various |
| 132 // rendering modes. Upon each Render() |render_test_func| will be called with |
| 133 // the rendered frame and the number of frames dropped. |
| 134 template <typename OnRenderCallback> |
| 135 void RunFramePumpTest(bool reset, |
| 136 TickGenerator* frame_tg, |
| 137 TickGenerator* display_tg, |
| 138 OnRenderCallback render_test_func) { |
| 139 SCOPED_TRACE(base::StringPrintf("Rendering %.03f fps into %0.03f", |
| 140 frame_tg->hertz(), display_tg->hertz())); |
| 141 tick_clock_->Advance(display_tg->current() - tick_clock_->NowTicks()); |
| 142 time_source_.StartTicking(); |
| 143 |
| 144 const bool fresh_algorithm = !algorithm_.have_rendered_frames_; |
| 145 |
| 146 base::TimeDelta last_frame_timestamp = kNoTimestamp(); |
| 147 bool should_use_cadence = false; |
| 148 int glitch_count = 0; |
| 149 const base::TimeTicks start_time = tick_clock_->NowTicks(); |
| 150 while (tick_clock_->NowTicks() - start_time < minimum_glitch_time()) { |
| 151 while (algorithm_.EffectiveFramesQueued() < 3 || |
| 152 frame_tg->current() - time_source_.CurrentMediaTime() < |
| 153 base::TimeTicks()) { |
| 154 algorithm_.EnqueueFrame( |
| 155 CreateFrame(frame_tg->current() - base::TimeTicks())); |
| 156 frame_tg->step(); |
| 157 } |
| 158 |
| 159 int frames_dropped = 0; |
| 160 const base::TimeTicks deadline_min = display_tg->current(); |
| 161 const base::TimeTicks deadline_max = display_tg->step(); |
| 162 scoped_refptr<VideoFrame> frame = |
| 163 algorithm_.Render(deadline_min, deadline_max, &frames_dropped); |
| 164 |
| 165 render_test_func(frame, frames_dropped); |
| 166 tick_clock_->Advance(display_tg->current() - tick_clock_->NowTicks()); |
| 167 |
| 168 if (HasFatalFailure()) |
| 169 return; |
| 170 |
| 171 // Render() should always return a frame within drift tolerances. |
| 172 ASSERT_TRUE(DriftOfLastRenderWasWithinTolerance(deadline_min)); |
| 173 |
| 174 // If we have a frame, the timestamps should always be monotonically |
| 175 // increasing. |
| 176 if (frame) { |
| 177 if (last_frame_timestamp != kNoTimestamp()) |
| 178 ASSERT_LE(last_frame_timestamp, frame->timestamp()); |
| 179 else |
| 180 last_frame_timestamp = frame->timestamp(); |
| 181 } |
| 182 |
| 183 // Only verify certain properties for fresh instances. |
| 184 if (fresh_algorithm) { |
| 185 ASSERT_EQ(frame_tg->interval(1), algorithm_.average_frame_duration()); |
| 186 |
| 187 if (is_using_cadence() && last_render_had_glitch()) |
| 188 ++glitch_count; |
| 189 |
| 190 // Once cadence starts, it should never stop for the current set of |
| 191 // tests. |
| 192 if (is_using_cadence()) |
| 193 should_use_cadence = true; |
| 194 ASSERT_EQ(is_using_cadence(), should_use_cadence); |
| 195 } |
| 196 } |
| 197 |
| 198 // When using cadence, the glitch count should be at most one for when |
| 199 // rendering for the less than minimum_glitch_time(). |
| 200 if (fresh_algorithm && is_using_cadence()) |
| 201 ASSERT_LE(glitch_count, 1); |
| 202 |
| 203 time_source_.StopTicking(); |
| 204 if (reset) { |
| 205 algorithm_.Reset(); |
| 206 time_source_.SetMediaTime(base::TimeDelta()); |
| 207 } |
| 208 } |
| 209 |
| 210 int FindBestFrameByCoverage(base::TimeTicks deadline_min, |
| 211 base::TimeTicks deadline_max, |
| 212 int* second_best) { |
| 213 return algorithm_.FindBestFrameByCoverage(deadline_min, deadline_max, |
| 214 second_best); |
| 215 } |
| 216 |
| 217 int FindBestFrameByDrift(base::TimeTicks deadline_min) { |
| 218 return algorithm_.FindBestFrameByDrift(deadline_min); |
| 219 } |
| 220 |
| 221 protected: |
| 222 VideoFramePool frame_pool_; |
| 223 WallClockTimeSource time_source_; |
| 224 base::SimpleTestTickClock* tick_clock_; // Owned by |time_source_|. |
| 225 VideoRendererAlgorithm algorithm_; |
| 226 |
| 227 private: |
| 228 DISALLOW_COPY_AND_ASSIGN(VideoRendererAlgorithmTest); |
| 229 }; |
| 230 |
| 231 TEST_F(VideoRendererAlgorithmTest, Empty) { |
| 232 TickGenerator tg(tick_clock_->NowTicks(), 50); |
| 233 int frames_dropped = 0; |
| 234 EXPECT_EQ(0u, frames_queued()); |
| 235 EXPECT_FALSE(algorithm_.Render(tg.current(), tg.step(), &frames_dropped)); |
| 236 EXPECT_EQ(0, frames_dropped); |
| 237 EXPECT_EQ(0u, frames_queued()); |
| 238 EXPECT_NE(base::TimeDelta(), max_acceptable_drift()); |
| 239 } |
| 240 |
| 241 TEST_F(VideoRendererAlgorithmTest, Reset) { |
| 242 TickGenerator tg(tick_clock_->NowTicks(), 50); |
| 243 algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); |
| 244 EXPECT_EQ(1u, frames_queued()); |
| 245 EXPECT_NE(base::TimeDelta(), max_acceptable_drift()); |
| 246 algorithm_.Reset(); |
| 247 EXPECT_EQ(0u, frames_queued()); |
| 248 EXPECT_NE(base::TimeDelta(), max_acceptable_drift()); |
| 249 } |
| 250 |
| 251 // The maximum acceptable drift should be updated once we have two frames. |
| 252 TEST_F(VideoRendererAlgorithmTest, AcceptableDriftUpdated) { |
| 253 TickGenerator tg(tick_clock_->NowTicks(), 50); |
| 254 |
| 255 int frames_dropped = 0; |
| 256 const base::TimeDelta original_drift = max_acceptable_drift(); |
| 257 algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); |
| 258 EXPECT_EQ(1u, frames_queued()); |
| 259 EXPECT_TRUE(algorithm_.Render(tg.current(), tg.step(), &frames_dropped)); |
| 260 EXPECT_EQ(original_drift, max_acceptable_drift()); |
| 261 |
| 262 // Time must be ticking to get wall clock times for frames. |
| 263 time_source_.StartTicking(); |
| 264 |
| 265 algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); |
| 266 EXPECT_EQ(2u, frames_queued()); |
| 267 EXPECT_TRUE(algorithm_.Render(tg.current(), tg.step(), &frames_dropped)); |
| 268 EXPECT_NE(original_drift, max_acceptable_drift()); |
| 269 } |
| 270 |
| 271 // Verifies behavior when time stops. |
| 272 TEST_F(VideoRendererAlgorithmTest, TimeIsStopped) { |
| 273 TickGenerator tg(tick_clock_->NowTicks(), 50); |
| 274 |
| 275 // Prior to rendering the first frame, the algorithm should always return the |
| 276 // first available frame. |
| 277 int frames_dropped = 0; |
| 278 algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); |
| 279 EXPECT_EQ(1u, frames_queued()); |
| 280 scoped_refptr<VideoFrame> frame = |
| 281 algorithm_.Render(tg.current(), tg.step(), &frames_dropped); |
| 282 ASSERT_TRUE(frame); |
| 283 EXPECT_EQ(tg.interval(0), frame->timestamp()); |
| 284 EXPECT_EQ(0, frames_dropped); |
| 285 EXPECT_EQ(1u, frames_queued()); |
| 286 |
| 287 // The same timestamp should be returned after time starts. |
| 288 tick_clock_->Advance(tg.interval(1)); |
| 289 time_source_.StartTicking(); |
| 290 frame = algorithm_.Render(tg.current(), tg.step(), &frames_dropped); |
| 291 ASSERT_TRUE(frame); |
| 292 EXPECT_EQ(tg.interval(0), frame->timestamp()); |
| 293 EXPECT_EQ(0, frames_dropped); |
| 294 EXPECT_EQ(1u, frames_queued()); |
| 295 |
| 296 // Ensure the next suitable frame is vended as time advances. |
| 297 algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); |
| 298 EXPECT_EQ(2u, frames_queued()); |
| 299 frame = algorithm_.Render(tg.current(), tg.step(), &frames_dropped); |
| 300 ASSERT_TRUE(frame); |
| 301 EXPECT_EQ(tg.interval(1), frame->timestamp()); |
| 302 EXPECT_EQ(0, frames_dropped); |
| 303 EXPECT_EQ(1u, frames_queued()); |
| 304 |
| 305 // Once time stops ticking, any further frames shouldn't be returned, even if |
| 306 // the interval requested more closely matches. |
| 307 algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); |
| 308 time_source_.StopTicking(); |
| 309 frame = algorithm_.Render(tg.current(), tg.step(), &frames_dropped); |
| 310 ASSERT_TRUE(frame); |
| 311 EXPECT_EQ(tg.interval(1), frame->timestamp()); |
| 312 EXPECT_EQ(0, frames_dropped); |
| 313 EXPECT_EQ(2u, frames_queued()); |
| 314 } |
| 315 |
| 316 // Verify frames inserted out of order end up in the right spot and are rendered |
| 317 // according to the API contract. |
| 318 TEST_F(VideoRendererAlgorithmTest, SortedFrameQueue) { |
| 319 TickGenerator tg(tick_clock_->NowTicks(), 50); |
| 320 |
| 321 // Ensure frames handed in out of order before time starts ticking are sorted |
| 322 // and returned in the correct order upon Render(). |
| 323 algorithm_.EnqueueFrame(CreateFrame(tg.interval(3))); |
| 324 algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); |
| 325 EXPECT_EQ(2u, frames_queued()); |
| 326 |
| 327 time_source_.StartTicking(); |
| 328 |
| 329 // The first call should return the earliest frame appended. |
| 330 int frames_dropped = 0; |
| 331 scoped_refptr<VideoFrame> frame = |
| 332 algorithm_.Render(tg.current(), tg.step(), &frames_dropped); |
| 333 EXPECT_EQ(0, frames_dropped); |
| 334 EXPECT_EQ(tg.interval(2), frame->timestamp()); |
| 335 EXPECT_EQ(2u, frames_queued()); |
| 336 |
| 337 // Since a frame has already been rendered, enqueing this frame and calling |
| 338 // Render() should result in it being dropped; even though it's a better |
| 339 // candidate for the desired interval. |
| 340 algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); |
| 341 frame = algorithm_.Render(tg.current(), tg.step(), &frames_dropped); |
| 342 EXPECT_EQ(1, frames_dropped); |
| 343 EXPECT_EQ(tg.interval(2), frame->timestamp()); |
| 344 EXPECT_EQ(2u, frames_queued()); |
| 345 } |
| 346 |
| 347 // Run through integer cadence selection for 1, 2, 3, and 4. |
| 348 TEST_F(VideoRendererAlgorithmTest, BestFrameByCadence) { |
| 349 const double kTestRates[][2] = {{60, 60}, {30, 60}, {25, 75}, {25, 100}}; |
| 350 |
| 351 for (const auto& test_rate : kTestRates) { |
| 352 disable_cadence_hysteresis(); |
| 353 |
| 354 TickGenerator frame_tg(base::TimeTicks(), test_rate[0]); |
| 355 TickGenerator display_tg(tick_clock_->NowTicks(), test_rate[1]); |
| 356 |
| 357 int actual_frame_pattern = 0; |
| 358 const int desired_frame_pattern = test_rate[1] / test_rate[0]; |
| 359 scoped_refptr<VideoFrame> current_frame; |
| 360 RunFramePumpTest( |
| 361 true, &frame_tg, &display_tg, |
| 362 [¤t_frame, &actual_frame_pattern, desired_frame_pattern, this]( |
| 363 const scoped_refptr<VideoFrame>& frame, int frames_dropped) { |
| 364 ASSERT_TRUE(frame); |
| 365 ASSERT_EQ(0, frames_dropped); |
| 366 |
| 367 // Each frame should display for exactly it's desired cadence pattern. |
| 368 if (!current_frame || current_frame == frame) { |
| 369 actual_frame_pattern++; |
| 370 } else { |
| 371 ASSERT_EQ(actual_frame_pattern, desired_frame_pattern); |
| 372 actual_frame_pattern = 1; |
| 373 } |
| 374 |
| 375 current_frame = frame; |
| 376 ASSERT_TRUE(is_using_cadence()); |
| 377 }); |
| 378 |
| 379 if (HasFatalFailure()) |
| 380 return; |
| 381 } |
| 382 } |
| 383 |
| 384 TEST_F(VideoRendererAlgorithmTest, BestFrameByCoverage) { |
| 385 TickGenerator tg(tick_clock_->NowTicks(), 50); |
| 386 time_source_.StartTicking(); |
| 387 |
| 388 algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); |
| 389 algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); |
| 390 algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); |
| 391 |
| 392 base::TimeTicks deadline_min = tg.current(); |
| 393 base::TimeTicks deadline_max = deadline_min + tg.interval(1); |
| 394 |
| 395 int frames_dropped = 0; |
| 396 scoped_refptr<VideoFrame> frame = |
| 397 algorithm_.Render(deadline_min, deadline_max, &frames_dropped); |
| 398 ASSERT_TRUE(frame); |
| 399 EXPECT_EQ(tg.interval(0), frame->timestamp()); |
| 400 EXPECT_EQ(0, frames_dropped); |
| 401 |
| 402 int second_best = 0; |
| 403 |
| 404 // Coverage should be 1 for if the frame overlaps the interval entirely, no |
| 405 // second best should be found. |
| 406 EXPECT_EQ(0, |
| 407 FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); |
| 408 EXPECT_EQ(-1, second_best); |
| 409 |
| 410 // 49/51 coverage for frame 0 and frame 1 should be within tolerance such that |
| 411 // the earlier frame should still be chosen. |
| 412 deadline_min = tg.current() + tg.interval(1) / 2 + |
| 413 base::TimeDelta::FromMicroseconds(250); |
| 414 deadline_max = deadline_min + tg.interval(1); |
| 415 EXPECT_EQ(0, |
| 416 FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); |
| 417 EXPECT_EQ(1, second_best); |
| 418 |
| 419 // 48/52 coverage should result in the second frame being chosen. |
| 420 deadline_min = tg.current() + tg.interval(1) / 2 + |
| 421 base::TimeDelta::FromMicroseconds(500); |
| 422 deadline_max = deadline_min + tg.interval(1); |
| 423 EXPECT_EQ(1, |
| 424 FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); |
| 425 EXPECT_EQ(0, second_best); |
| 426 |
| 427 // Overlapping three frames should choose the one with the most coverage and |
| 428 // the second best should be the earliest frame. |
| 429 deadline_min = tg.current() + tg.interval(1) / 2; |
| 430 deadline_max = deadline_min + tg.interval(2); |
| 431 EXPECT_EQ(1, |
| 432 FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); |
| 433 EXPECT_EQ(0, second_best); |
| 434 |
| 435 // Requesting coverage outside of all known frames should return -1 for both |
| 436 // best indices. |
| 437 deadline_min = tg.current() + tg.interval(frames_queued()); |
| 438 deadline_max = deadline_min + tg.interval(1); |
| 439 EXPECT_EQ(-1, |
| 440 FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); |
| 441 EXPECT_EQ(-1, second_best); |
| 442 } |
| 443 |
| 444 // Run through fractional cadence selection for 1/2, 1/3, and 1/4. |
| 445 TEST_F(VideoRendererAlgorithmTest, BestFrameByFractionalCadence) { |
| 446 const double kTestRates[][2] = {{120, 60}, {72, 24}, {100, 25}}; |
| 447 |
| 448 for (const auto& test_rate : kTestRates) { |
| 449 disable_cadence_hysteresis(); |
| 450 |
| 451 TickGenerator frame_tg(base::TimeTicks(), test_rate[0]); |
| 452 TickGenerator display_tg(tick_clock_->NowTicks(), test_rate[1]); |
| 453 |
| 454 const int desired_drop_pattern = test_rate[0] / test_rate[1] - 1; |
| 455 scoped_refptr<VideoFrame> current_frame; |
| 456 RunFramePumpTest( |
| 457 true, &frame_tg, &display_tg, |
| 458 [¤t_frame, desired_drop_pattern, this]( |
| 459 const scoped_refptr<VideoFrame>& frame, int frames_dropped) { |
| 460 ASSERT_TRUE(frame); |
| 461 |
| 462 // The first frame should have zero dropped frames, but each Render() |
| 463 // call after should drop the same number of frames based on the |
| 464 // fractional cadence. |
| 465 if (!current_frame) |
| 466 ASSERT_EQ(0, frames_dropped); |
| 467 else |
| 468 ASSERT_EQ(desired_drop_pattern, frames_dropped); |
| 469 |
| 470 ASSERT_NE(current_frame, frame); |
| 471 ASSERT_TRUE(is_using_cadence()); |
| 472 current_frame = frame; |
| 473 }); |
| 474 |
| 475 if (HasFatalFailure()) |
| 476 return; |
| 477 } |
| 478 } |
| 479 // Verify a 3:2 frame pattern for 23.974fps in 60Hz; doubles as a test for best |
| 480 // frame by coverage. |
| 481 TEST_F(VideoRendererAlgorithmTest, FilmCadence) { |
| 482 const double kTestRates[] = {NTSC(24), 24}; |
| 483 |
| 484 for (double frame_rate : kTestRates) { |
| 485 scoped_refptr<VideoFrame> current_frame; |
| 486 int actual_frame_pattern = 0, desired_frame_pattern = 3; |
| 487 |
| 488 TickGenerator frame_tg(base::TimeTicks(), frame_rate); |
| 489 TickGenerator display_tg(tick_clock_->NowTicks(), 60); |
| 490 |
| 491 RunFramePumpTest( |
| 492 true, &frame_tg, &display_tg, |
| 493 [¤t_frame, &actual_frame_pattern, &desired_frame_pattern, this]( |
| 494 const scoped_refptr<VideoFrame>& frame, int frames_dropped) { |
| 495 ASSERT_TRUE(frame); |
| 496 ASSERT_EQ(0, frames_dropped); |
| 497 |
| 498 if (!current_frame || current_frame == frame) { |
| 499 actual_frame_pattern++; |
| 500 } else { |
| 501 ASSERT_EQ(actual_frame_pattern, desired_frame_pattern); |
| 502 actual_frame_pattern = 1; |
| 503 desired_frame_pattern = (desired_frame_pattern == 3 ? 2 : 3); |
| 504 } |
| 505 |
| 506 current_frame = frame; |
| 507 ASSERT_FALSE(is_using_cadence()); |
| 508 }); |
| 509 |
| 510 if (HasFatalFailure()) |
| 511 return; |
| 512 } |
| 513 } |
| 514 |
| 515 // Spot check common display and frame rate pairs for correctness. |
| 516 TEST_F(VideoRendererAlgorithmTest, CadenceCalculations) { |
| 517 ASSERT_FALSE(GetCadence(24, 60)); |
| 518 ASSERT_FALSE(GetCadence(NTSC(24), 60)); |
| 519 ASSERT_FALSE(GetCadence(25, 60)); |
| 520 ASSERT_EQ(2, GetCadence(NTSC(30), 60)); |
| 521 ASSERT_EQ(2, GetCadence(30, 60)); |
| 522 ASSERT_FALSE(GetCadence(50, 60)); |
| 523 ASSERT_EQ(1, GetCadence(NTSC(60), 60)); |
| 524 ASSERT_EQ(2, GetCadence(120, 60)); |
| 525 |
| 526 // 50Hz is common in the EU. |
| 527 ASSERT_FALSE(GetCadence(NTSC(24), 50)); |
| 528 ASSERT_FALSE(GetCadence(24, 50)); |
| 529 ASSERT_EQ(2, GetCadence(NTSC(25), 50)); |
| 530 ASSERT_EQ(2, GetCadence(25, 50)); |
| 531 ASSERT_FALSE(GetCadence(NTSC(30), 50)); |
| 532 ASSERT_FALSE(GetCadence(30, 50)); |
| 533 ASSERT_FALSE(GetCadence(NTSC(60), 50)); |
| 534 ASSERT_FALSE(GetCadence(60, 50)); |
| 535 |
| 536 ASSERT_FALSE(GetCadence(25, NTSC(60))); |
| 537 ASSERT_EQ(2, GetCadence(120, NTSC(60))); |
| 538 ASSERT_EQ(60, GetCadence(1, NTSC(60))); |
| 539 } |
| 540 |
| 541 TEST_F(VideoRendererAlgorithmTest, RemoveExpiredFrames) { |
| 542 TickGenerator tg(tick_clock_->NowTicks(), 50); |
| 543 |
| 544 algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); |
| 545 ASSERT_EQ(0, algorithm_.RemoveExpiredFrames(tg.current())); |
| 546 |
| 547 time_source_.StartTicking(); |
| 548 |
| 549 int frames_dropped = 0; |
| 550 scoped_refptr<VideoFrame> frame = |
| 551 algorithm_.Render(tg.current(), tg.step(), &frames_dropped); |
| 552 ASSERT_TRUE(frame); |
| 553 EXPECT_EQ(tg.interval(0), frame->timestamp()); |
| 554 EXPECT_EQ(0, frames_dropped); |
| 555 |
| 556 algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); |
| 557 algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); |
| 558 algorithm_.EnqueueFrame(CreateFrame(tg.interval(3))); |
| 559 algorithm_.EnqueueFrame(CreateFrame(tg.interval(4))); |
| 560 |
| 561 tg.step(2); |
| 562 ASSERT_EQ(2, algorithm_.RemoveExpiredFrames(tg.current())); |
| 563 frame = algorithm_.Render(tg.current(), tg.step(), &frames_dropped); |
| 564 EXPECT_EQ(1, frames_dropped); |
| 565 EXPECT_EQ(2u, frames_queued()); |
| 566 ASSERT_TRUE(frame); |
| 567 EXPECT_EQ(tg.interval(3), frame->timestamp()); |
| 568 } |
| 569 |
| 570 TEST_F(VideoRendererAlgorithmTest, CadenceBasedTest) { |
| 571 // Common display rates. |
| 572 const double kDisplayRates[] = { |
| 573 NTSC(24), |
| 574 24, |
| 575 NTSC(25), |
| 576 25, |
| 577 NTSC(30), |
| 578 30, |
| 579 48, |
| 580 NTSC(50), |
| 581 50, |
| 582 NTSC(60), |
| 583 60, |
| 584 75, |
| 585 120, |
| 586 144, |
| 587 }; |
| 588 |
| 589 // List of common frame rate values. Values pulled from local test media, |
| 590 // videostack test matrix, and Wikipedia. |
| 591 const double kTestRates[] = { |
| 592 1, 10, 12.5, 15, NTSC(24), 24, NTSC(25), 25, |
| 593 NTSC(30), 30, 30.12, 48, NTSC(50), 50, 58.74, NTSC(60), |
| 594 60, 72, 90, 100, 120, 144, 240, 300, |
| 595 }; |
| 596 |
| 597 for (double display_rate : kDisplayRates) { |
| 598 for (double frame_rate : kTestRates) { |
| 599 TickGenerator frame_tg(base::TimeTicks(), frame_rate); |
| 600 TickGenerator display_tg(tick_clock_->NowTicks(), display_rate); |
| 601 RunFramePumpTest( |
| 602 true, &frame_tg, &display_tg, |
| 603 [](const scoped_refptr<VideoFrame>& frame, int frames_dropped) {}); |
| 604 if (HasFatalFailure()) |
| 605 return; |
| 606 } |
| 607 } |
| 608 } |
| 609 |
| 610 // Rotate through various playback rates and ensure algorithm adapts correctly. |
| 611 TEST_F(VideoRendererAlgorithmTest, VariableFrameRateCadence) { |
| 612 TickGenerator frame_tg(base::TimeTicks(), NTSC(30)); |
| 613 TickGenerator display_tg(tick_clock_->NowTicks(), 60); |
| 614 |
| 615 RunFramePumpTest( |
| 616 false, &frame_tg, &display_tg, |
| 617 [this](const scoped_refptr<VideoFrame>& frame, int frames_dropped) {}); |
| 618 if (HasFatalFailure()) |
| 619 return; |
| 620 ASSERT_TRUE(is_using_cadence()); |
| 621 |
| 622 time_source_.SetPlaybackRate(2); |
| 623 |
| 624 RunFramePumpTest( |
| 625 false, &frame_tg, &display_tg, |
| 626 [this](const scoped_refptr<VideoFrame>& frame, int frames_dropped) {}); |
| 627 if (HasFatalFailure()) |
| 628 return; |
| 629 ASSERT_TRUE(is_using_cadence()); |
| 630 |
| 631 time_source_.SetPlaybackRate(0.215); |
| 632 |
| 633 RunFramePumpTest( |
| 634 false, &frame_tg, &display_tg, |
| 635 [this](const scoped_refptr<VideoFrame>& frame, int frames_dropped) {}); |
| 636 if (HasFatalFailure()) |
| 637 return; |
| 638 ASSERT_FALSE(is_using_cadence()); |
| 639 |
| 640 time_source_.SetPlaybackRate(0.5); |
| 641 |
| 642 RunFramePumpTest( |
| 643 false, &frame_tg, &display_tg, |
| 644 [this](const scoped_refptr<VideoFrame>& frame, int frames_dropped) {}); |
| 645 if (HasFatalFailure()) |
| 646 return; |
| 647 ASSERT_TRUE(is_using_cadence()); |
| 648 |
| 649 time_source_.SetPlaybackRate(1.0); |
| 650 |
| 651 RunFramePumpTest( |
| 652 false, &frame_tg, &display_tg, |
| 653 [this](const scoped_refptr<VideoFrame>& frame, int frames_dropped) {}); |
| 654 if (HasFatalFailure()) |
| 655 return; |
| 656 ASSERT_TRUE(is_using_cadence()); |
| 657 |
| 658 // TODO(dalecurtis): It seems there should be some more things we can test |
| 659 // here... |
| 660 } |
| 661 |
| 662 } // namespace media |
OLD | NEW |