// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/macros.h" #include "base/strings/stringprintf.h" #include "base/test/simple_test_tick_clock.h" #include "media/base/timestamp_constants.h" #include "media/base/video_frame_pool.h" #include "media/base/wall_clock_time_source.h" #include "media/filters/video_renderer_algorithm.h" #include "testing/gtest/include/gtest/gtest.h" namespace media { // Slows down the given |fps| according to NTSC field reduction standards; see // http://en.wikipedia.org/wiki/Frame_rate#Digital_video_and_television static double NTSC(double fps) { return fps / 1.001; } // Helper class for generating TimeTicks in a sequence according to a frequency. class TickGenerator { public: TickGenerator(base::TimeTicks base_timestamp, double hertz) : tick_count_(0), hertz_(hertz), microseconds_per_tick_(base::Time::kMicrosecondsPerSecond / hertz), base_time_(base_timestamp) {} base::TimeDelta interval(int tick_count) const { return base::TimeDelta::FromMicroseconds(tick_count * microseconds_per_tick_); } base::TimeTicks current() const { return base_time_ + interval(tick_count_); } base::TimeTicks step() { return step(1); } base::TimeTicks step(int n) { tick_count_ += n; return current(); } double hertz() const { return hertz_; } void Reset(base::TimeTicks base_timestamp) { base_time_ = base_timestamp; tick_count_ = 0; } private: // Track a tick count and seconds per tick value to ensure we don't drift too // far due to accumulated errors during testing. int64_t tick_count_; const double hertz_; const double microseconds_per_tick_; base::TimeTicks base_time_; DISALLOW_COPY_AND_ASSIGN(TickGenerator); }; class VideoRendererAlgorithmTest : public testing::Test { public: VideoRendererAlgorithmTest() : tick_clock_(new base::SimpleTestTickClock()), algorithm_(base::Bind(&WallClockTimeSource::GetWallClockTimes, base::Unretained(&time_source_))) { // Always start the TickClock at a non-zero value since null values have // special connotations. tick_clock_->Advance(base::TimeDelta::FromMicroseconds(10000)); time_source_.set_tick_clock_for_testing(tick_clock_.get()); } ~VideoRendererAlgorithmTest() override {} scoped_refptr CreateFrame(base::TimeDelta timestamp) { const gfx::Size natural_size(8, 8); return frame_pool_.CreateFrame(PIXEL_FORMAT_YV12, natural_size, gfx::Rect(natural_size), natural_size, timestamp); } base::TimeDelta minimum_glitch_time() const { return base::TimeDelta::FromSeconds( VideoRendererAlgorithm::kMinimumAcceptableTimeBetweenGlitchesSecs); } base::TimeDelta max_acceptable_drift() const { return algorithm_.max_acceptable_drift_; } void disable_cadence_hysteresis() { algorithm_.cadence_estimator_.set_cadence_hysteresis_threshold_for_testing( base::TimeDelta()); } bool last_render_had_glitch() const { return algorithm_.last_render_had_glitch_; } bool is_using_cadence() const { return algorithm_.cadence_estimator_.has_cadence(); } bool IsCadenceBelowOne() const { if (!is_using_cadence()) return false; size_t size = algorithm_.cadence_estimator_.cadence_size_for_testing(); for (size_t i = 0; i < size; ++i) { if (!algorithm_.cadence_estimator_.GetCadenceForFrame(i)) return true; } return false; } double CadenceValue() const { int num_render_intervals = 0; size_t size = algorithm_.cadence_estimator_.cadence_size_for_testing(); for (size_t i = 0; i < size; ++i) { num_render_intervals += algorithm_.cadence_estimator_.GetCadenceForFrame(i); } return (num_render_intervals + 0.0) / size; } size_t frames_queued() const { return algorithm_.frame_queue_.size(); } std::string GetCadence(double frame_rate, double display_rate) { TickGenerator display_tg(tick_clock_->NowTicks(), display_rate); TickGenerator frame_tg(base::TimeTicks(), frame_rate); time_source_.StartTicking(); // Enqueue enough frames for cadence detection. size_t frames_dropped = 0; disable_cadence_hysteresis(); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(0))); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(1))); EXPECT_TRUE(RenderAndStep(&display_tg, &frames_dropped)); // Store cadence before reseting the algorithm. const std::string cadence = algorithm_.cadence_estimator_.GetCadenceForTesting(); time_source_.StopTicking(); algorithm_.Reset(); return cadence; } base::TimeDelta CalculateAbsoluteDriftForFrame(base::TimeTicks deadline_min, int frame_index) { return algorithm_.CalculateAbsoluteDriftForFrame(deadline_min, frame_index); } bool DriftOfLastRenderWasWithinTolerance(base::TimeTicks deadline_min) { return CalculateAbsoluteDriftForFrame(deadline_min, 0) <= algorithm_.max_acceptable_drift_; } scoped_refptr RenderAndStep(TickGenerator* tg, size_t* frames_dropped) { const base::TimeTicks start = tg->current(); const base::TimeTicks end = tg->step(); return algorithm_.Render(start, end, frames_dropped); } // Allows tests to run a Render() loop with sufficient frames for the various // rendering modes. Upon each Render() |render_test_func| will be called with // the rendered frame and the number of frames dropped. template void RunFramePumpTest(bool reset, TickGenerator* frame_tg, TickGenerator* display_tg, OnRenderCallback render_test_func) { SCOPED_TRACE(base::StringPrintf("Rendering %.03f fps into %0.03f", frame_tg->hertz(), display_tg->hertz())); tick_clock_->Advance(display_tg->current() - tick_clock_->NowTicks()); time_source_.StartTicking(); const bool fresh_algorithm = !algorithm_.have_rendered_frames_; base::TimeDelta last_start_timestamp = kNoTimestamp(); bool should_use_cadence = false; int glitch_count = 0; const base::TimeTicks start_time = tick_clock_->NowTicks(); while (tick_clock_->NowTicks() - start_time < minimum_glitch_time()) { while (algorithm_.EffectiveFramesQueued() < 3 || frame_tg->current() - time_source_.CurrentMediaTime() < base::TimeTicks()) { algorithm_.EnqueueFrame( CreateFrame(frame_tg->current() - base::TimeTicks())); frame_tg->step(); } size_t frames_dropped = 0; const base::TimeTicks deadline_min = display_tg->current(); const base::TimeTicks deadline_max = display_tg->step(); scoped_refptr frame = algorithm_.Render(deadline_min, deadline_max, &frames_dropped); render_test_func(frame, frames_dropped); tick_clock_->Advance(display_tg->current() - tick_clock_->NowTicks()); if (HasFatalFailure()) return; // Render() should always return a frame within drift tolerances. ASSERT_TRUE(DriftOfLastRenderWasWithinTolerance(deadline_min)); // If we have a frame, the timestamps should always be monotonically // increasing. if (frame) { if (last_start_timestamp != kNoTimestamp()) ASSERT_LE(last_start_timestamp, frame->timestamp()); else last_start_timestamp = frame->timestamp(); } // Only verify certain properties for fresh instances. if (fresh_algorithm) { ASSERT_NEAR(frame_tg->interval(1).InMicroseconds(), algorithm_.average_frame_duration().InMicroseconds(), 1); if (is_using_cadence() && last_render_had_glitch()) ++glitch_count; // Once cadence starts, it should never stop for the current set of // tests. if (is_using_cadence()) should_use_cadence = true; ASSERT_EQ(is_using_cadence(), should_use_cadence); } // When there are no frames, we're not using cadence based selection, or a // frame is under cadence the two queue size reports should be equal to // the number of usable frames; i.e. those frames whose end time was not // within the last render interval. if (!is_using_cadence() || !frames_queued() || GetCurrentFrameDisplayCount() < GetCurrentFrameIdealDisplayCount()) { ASSERT_NEAR(GetUsableFrameCount(deadline_max), algorithm_.EffectiveFramesQueued(), fresh_algorithm ? 0 : 1); } else if (is_using_cadence() && !IsCadenceBelowOne()) { // If there was no glitch in the last render, the two queue sizes should // be off by exactly one frame; i.e., the current frame doesn't count. if (!last_render_had_glitch() && fresh_algorithm) ASSERT_EQ(frames_queued() - 1, algorithm_.EffectiveFramesQueued()); } else if (IsCadenceBelowOne()) { // The frame estimate should be off by at most one frame. const size_t estimated_frames_queued = std::floor(frames_queued() * CadenceValue()); ASSERT_NEAR(algorithm_.EffectiveFramesQueued(), estimated_frames_queued, 1); } } // When using cadence, the glitch count should be at most one for when // rendering for the less than minimum_glitch_time(). if (fresh_algorithm && is_using_cadence()) ASSERT_LE(glitch_count, 1); time_source_.StopTicking(); if (reset) { algorithm_.Reset(); time_source_.SetMediaTime(base::TimeDelta()); } } int FindBestFrameByCoverage(base::TimeTicks deadline_min, base::TimeTicks deadline_max, int* second_best) { return algorithm_.FindBestFrameByCoverage(deadline_min, deadline_max, second_best); } int FindBestFrameByDrift(base::TimeTicks deadline_min, base::TimeDelta* selected_frame_drift) { return algorithm_.FindBestFrameByDrift(deadline_min, selected_frame_drift); } int GetCurrentFrameDropCount() const { DCHECK_GT(frames_queued(), 0u); return algorithm_.frame_queue_[algorithm_.last_frame_index_].drop_count; } int GetCurrentFrameDisplayCount() const { DCHECK_GT(frames_queued(), 0u); return algorithm_.frame_queue_[algorithm_.last_frame_index_].render_count; } int GetCurrentFrameIdealDisplayCount() const { DCHECK_GT(frames_queued(), 0u); return algorithm_.frame_queue_[algorithm_.last_frame_index_] .ideal_render_count; } int AccountForMissedIntervalsAndStep(TickGenerator* tg) { const base::TimeTicks start = tg->current(); const base::TimeTicks end = tg->step(); return AccountForMissedIntervals(start, end); } int AccountForMissedIntervals(base::TimeTicks deadline_min, base::TimeTicks deadline_max) { algorithm_.AccountForMissedIntervals(deadline_min, deadline_max); return frames_queued() ? GetCurrentFrameDisplayCount() : -1; } size_t GetUsableFrameCount(base::TimeTicks deadline_max) { if (is_using_cadence()) return frames_queued(); for (size_t i = 0; i < frames_queued(); ++i) if (algorithm_.frame_queue_[i].end_time > deadline_max) return frames_queued() - i; return 0; } protected: VideoFramePool frame_pool_; scoped_ptr tick_clock_; WallClockTimeSource time_source_; VideoRendererAlgorithm algorithm_; private: DISALLOW_COPY_AND_ASSIGN(VideoRendererAlgorithmTest); }; TEST_F(VideoRendererAlgorithmTest, Empty) { TickGenerator tg(tick_clock_->NowTicks(), 50); size_t frames_dropped = 0; EXPECT_EQ(0u, frames_queued()); EXPECT_FALSE(RenderAndStep(&tg, &frames_dropped)); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(0u, frames_queued()); EXPECT_NE(base::TimeDelta(), max_acceptable_drift()); } TEST_F(VideoRendererAlgorithmTest, Reset) { TickGenerator tg(tick_clock_->NowTicks(), 50); algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); EXPECT_EQ(1u, frames_queued()); EXPECT_NE(base::TimeDelta(), max_acceptable_drift()); algorithm_.Reset(); EXPECT_EQ(0u, frames_queued()); EXPECT_NE(base::TimeDelta(), max_acceptable_drift()); } TEST_F(VideoRendererAlgorithmTest, AccountForMissingIntervals) { TickGenerator tg(tick_clock_->NowTicks(), 50); time_source_.StartTicking(); // Disable hysteresis since AccountForMissingIntervals() only affects cadence // based rendering. disable_cadence_hysteresis(); // Simulate Render() called before any frames are present. EXPECT_EQ(-1, AccountForMissedIntervalsAndStep(&tg)); algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(3))); // Simulate Render() called before any frames have been rendered. EXPECT_EQ(0, AccountForMissedIntervalsAndStep(&tg)); // Render one frame (several are in the past and will be dropped). base::TimeTicks deadline_min = tg.current(); base::TimeTicks deadline_max = tg.step(); size_t frames_dropped = 0; scoped_refptr frame = algorithm_.Render(deadline_min, deadline_max, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(2), frame->timestamp()); EXPECT_EQ(2u, frames_dropped); ASSERT_EQ(1, GetCurrentFrameDisplayCount()); // Now calling AccountForMissingIntervals with an interval which overlaps the // previous should do nothing. deadline_min += tg.interval(1) / 2; deadline_max += tg.interval(1) / 2; EXPECT_EQ(1, AccountForMissedIntervals(deadline_min, deadline_max)); // Steping by 1.5 intervals, is not enough to increase the count. deadline_min += tg.interval(1); deadline_max += tg.interval(1); EXPECT_EQ(1, AccountForMissedIntervals(deadline_min, deadline_max)); // Calling it after a full skipped interval should increase the count by 1 for // each skipped interval. tg.step(); EXPECT_EQ(2, AccountForMissedIntervalsAndStep(&tg)); // 4 because [tg.current(), tg.step()] now represents 2 additional intervals. EXPECT_EQ(4, AccountForMissedIntervalsAndStep(&tg)); // Frame should be way over cadence and no good frames remain, so last frame // should be returned. frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(3), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(1, GetCurrentFrameDisplayCount()); // Stop the time source and verify AccountForMissedIntervals() doesn't try to // account for intervals from pause behavior. time_source_.StopTicking(); frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(3), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(1, GetCurrentFrameDisplayCount()); tg.step(100); frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(3), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(1, GetCurrentFrameDisplayCount()); time_source_.StartTicking(); // Now run the same test using set_time_stopped(); frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(3), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(2, GetCurrentFrameDisplayCount()); algorithm_.set_time_stopped(); tg.step(100); frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(3), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(3, GetCurrentFrameDisplayCount()); } TEST_F(VideoRendererAlgorithmTest, OnLastFrameDropped) { TickGenerator frame_tg(base::TimeTicks(), 25); TickGenerator display_tg(tick_clock_->NowTicks(), 50); time_source_.StartTicking(); // Disable hysteresis since OnLastFrameDropped() only affects cadence based // rendering. disable_cadence_hysteresis(); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(0))); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(1))); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(2))); // Render one frame (several are in the past and will be dropped). size_t frames_dropped = 0; scoped_refptr frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(frame_tg.interval(0), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); // The frame should have its display count decremented once it's reported as // dropped. ASSERT_EQ(1, GetCurrentFrameDisplayCount()); ASSERT_EQ(0, GetCurrentFrameDropCount()); algorithm_.OnLastFrameDropped(); ASSERT_EQ(1, GetCurrentFrameDisplayCount()); ASSERT_EQ(1, GetCurrentFrameDropCount()); // Render the frame again and then force another drop. frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(frame_tg.interval(0), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); ASSERT_EQ(2, GetCurrentFrameDisplayCount()); ASSERT_EQ(1, GetCurrentFrameDropCount()); algorithm_.OnLastFrameDropped(); ASSERT_EQ(2, GetCurrentFrameDisplayCount()); ASSERT_EQ(2, GetCurrentFrameDropCount()); // The next Render() call should now count this frame as dropped. frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(frame_tg.interval(1), frame->timestamp()); EXPECT_EQ(1u, frames_dropped); ASSERT_EQ(1, GetCurrentFrameDisplayCount()); ASSERT_EQ(0, GetCurrentFrameDropCount()); // Rendering again should result in the same frame being displayed. frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(frame_tg.interval(1), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); // In this case, the drop count is less than the display count, so the frame // should not be counted as dropped. ASSERT_EQ(2, GetCurrentFrameDisplayCount()); ASSERT_EQ(0, GetCurrentFrameDropCount()); algorithm_.OnLastFrameDropped(); ASSERT_EQ(2, GetCurrentFrameDisplayCount()); ASSERT_EQ(1, GetCurrentFrameDropCount()); // The third frame should be rendered correctly now and the previous frame not // counted as having been dropped. frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(frame_tg.interval(2), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); } TEST_F(VideoRendererAlgorithmTest, EffectiveFramesQueued) { TickGenerator frame_tg(base::TimeTicks(), 50); TickGenerator display_tg(tick_clock_->NowTicks(), 25); // Disable hysteresis since EffectiveFramesQueued() is tested as part of the // normal frame pump tests when cadence is not present. disable_cadence_hysteresis(); EXPECT_EQ(0u, algorithm_.EffectiveFramesQueued()); time_source_.StartTicking(); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(0))); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(1))); EXPECT_EQ(2u, algorithm_.EffectiveFramesQueued()); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(2))); EXPECT_EQ(3u, algorithm_.EffectiveFramesQueued()); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(3))); EXPECT_EQ(4u, algorithm_.EffectiveFramesQueued()); EXPECT_EQ(4u, frames_queued()); // Render one frame which will detect cadence... size_t frames_dropped = 0; scoped_refptr frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(frame_tg.interval(0), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); // Fractional cadence should be detected and the count will decrease. ASSERT_TRUE(is_using_cadence()); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); EXPECT_EQ(4u, frames_queued()); // Dropping the last rendered frame should do nothing, since the last frame // is already excluded from the count if it has a display count of 1. algorithm_.OnLastFrameDropped(); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); } TEST_F(VideoRendererAlgorithmTest, EffectiveFramesQueuedWithoutCadence) { TickGenerator tg(tick_clock_->NowTicks(), 60); EXPECT_EQ(0u, algorithm_.EffectiveFramesQueued()); time_source_.StartTicking(); algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); EXPECT_EQ(2u, algorithm_.EffectiveFramesQueued()); algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); EXPECT_EQ(3u, algorithm_.EffectiveFramesQueued()); algorithm_.EnqueueFrame(CreateFrame(tg.interval(3))); EXPECT_EQ(4u, algorithm_.EffectiveFramesQueued()); EXPECT_EQ(4u, frames_queued()); EXPECT_EQ(384, algorithm_.GetMemoryUsage()); // Issue a render call that should drop the first two frames and mark the 3rd // as consumed. tg.step(2); size_t frames_dropped = 0; scoped_refptr frame = RenderAndStep(&tg, &frames_dropped); ASSERT_FALSE(is_using_cadence()); ASSERT_TRUE(frame); EXPECT_EQ(2u, frames_dropped); EXPECT_EQ(tg.interval(2), frame->timestamp()); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(192, algorithm_.GetMemoryUsage()); // Rendering one more frame should return 0 effective frames queued. frame = RenderAndStep(&tg, &frames_dropped); ASSERT_FALSE(is_using_cadence()); ASSERT_TRUE(frame); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(tg.interval(3), frame->timestamp()); EXPECT_EQ(0u, algorithm_.EffectiveFramesQueued()); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(96, algorithm_.GetMemoryUsage()); } // The maximum acceptable drift should be updated once we have two frames. TEST_F(VideoRendererAlgorithmTest, AcceptableDriftUpdated) { TickGenerator tg(tick_clock_->NowTicks(), 50); size_t frames_dropped = 0; const base::TimeDelta original_drift = max_acceptable_drift(); algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); EXPECT_EQ(1u, frames_queued()); EXPECT_TRUE(RenderAndStep(&tg, &frames_dropped)); EXPECT_EQ(original_drift, max_acceptable_drift()); // Time must be ticking to get wall clock times for frames. time_source_.StartTicking(); algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); EXPECT_EQ(2u, frames_queued()); EXPECT_TRUE(RenderAndStep(&tg, &frames_dropped)); EXPECT_NE(original_drift, max_acceptable_drift()); } // Verifies behavior when time stops. TEST_F(VideoRendererAlgorithmTest, TimeIsStopped) { TickGenerator tg(tick_clock_->NowTicks(), 50); // Prior to rendering the first frame, the algorithm should always return the // first available frame. size_t frames_dropped = 0; algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); EXPECT_EQ(1u, frames_queued()); scoped_refptr frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(0), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); // The same timestamp should be returned after time starts. tick_clock_->Advance(tg.interval(1)); time_source_.StartTicking(); frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(0), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); // Ensure the next suitable frame is vended as time advances. algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(2u, algorithm_.EffectiveFramesQueued()); frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(1), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(0u, algorithm_.EffectiveFramesQueued()); // Once time stops ticking, any further frames shouldn't be returned, even if // the interval requested more closely matches. algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); time_source_.StopTicking(); frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(1), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); } // Verify frames inserted out of order end up in the right spot and are rendered // according to the API contract. TEST_F(VideoRendererAlgorithmTest, SortedFrameQueue) { TickGenerator tg(tick_clock_->NowTicks(), 50); // Ensure frames handed in out of order before time starts ticking are sorted // and returned in the correct order upon Render(). algorithm_.EnqueueFrame(CreateFrame(tg.interval(3))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(2u, algorithm_.EffectiveFramesQueued()); time_source_.StartTicking(); // The first call should return the earliest frame appended. size_t frames_dropped = 0; scoped_refptr frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(tg.interval(2), frame->timestamp()); EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(2u, algorithm_.EffectiveFramesQueued()); // Since a frame has already been rendered, queuing this frame and calling // Render() should result in it being dropped; even though it's a better // candidate for the desired interval. The frame is dropped during enqueue so // it won't show up in frames_queued(). algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(2u, algorithm_.EffectiveFramesQueued()); frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(1u, frames_dropped); EXPECT_EQ(tg.interval(2), frame->timestamp()); EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(2u, algorithm_.EffectiveFramesQueued()); } // Run through integer cadence selection for 1, 2, 3, and 4. TEST_F(VideoRendererAlgorithmTest, BestFrameByCadence) { const double kTestRates[][2] = {{60, 60}, {30, 60}, {25, 75}, {25, 100}}; for (const auto& test_rate : kTestRates) { disable_cadence_hysteresis(); TickGenerator frame_tg(base::TimeTicks(), test_rate[0]); TickGenerator display_tg(tick_clock_->NowTicks(), test_rate[1]); int actual_frame_pattern = 0; const int desired_frame_pattern = test_rate[1] / test_rate[0]; scoped_refptr current_frame; RunFramePumpTest( true, &frame_tg, &display_tg, [¤t_frame, &actual_frame_pattern, desired_frame_pattern, this]( const scoped_refptr& frame, size_t frames_dropped) { ASSERT_TRUE(frame); ASSERT_EQ(0u, frames_dropped); // Each frame should display for exactly it's desired cadence pattern. if (!current_frame || current_frame == frame) { actual_frame_pattern++; } else { ASSERT_EQ(actual_frame_pattern, desired_frame_pattern); actual_frame_pattern = 1; } current_frame = frame; ASSERT_TRUE(is_using_cadence()); }); if (HasFatalFailure()) return; } } TEST_F(VideoRendererAlgorithmTest, BestFrameByCadenceOverdisplayed) { TickGenerator frame_tg(base::TimeTicks(), 25); TickGenerator display_tg(tick_clock_->NowTicks(), 50); time_source_.StartTicking(); disable_cadence_hysteresis(); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(0))); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(1))); // Render frames until we've exhausted available frames and the last frame is // forced to be over displayed. for (int i = 0; i < 5; ++i) { size_t frames_dropped = 0; scoped_refptr frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(frame_tg.interval(i < 4 ? i / 2 : 1), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); ASSERT_EQ(2, GetCurrentFrameIdealDisplayCount()); } // Verify last frame is above cadence (2 in this case) ASSERT_EQ(GetCurrentFrameIdealDisplayCount() + 1, GetCurrentFrameDisplayCount()); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(2))); algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(3))); // The next frame should only be displayed once, since the previous one was // over displayed by one frame. size_t frames_dropped = 0; scoped_refptr frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(frame_tg.interval(2), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); // Enqueuing a new frame should keep the correct cadence values. algorithm_.EnqueueFrame(CreateFrame(frame_tg.interval(4))); ASSERT_EQ(2, GetCurrentFrameDisplayCount()); ASSERT_EQ(1, GetCurrentFrameDropCount()); ASSERT_EQ(2, GetCurrentFrameIdealDisplayCount()); frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(frame_tg.interval(3), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); ASSERT_EQ(1, GetCurrentFrameDisplayCount()); ASSERT_EQ(0, GetCurrentFrameDropCount()); ASSERT_EQ(2, GetCurrentFrameIdealDisplayCount()); } TEST_F(VideoRendererAlgorithmTest, BestFrameByCadenceOverdisplayedForDrift) { // Use 24.94 to ensure drift expires pretty rapidly (8.36s in this case). TickGenerator frame_tg(base::TimeTicks(), 24.94); TickGenerator display_tg(tick_clock_->NowTicks(), 50); time_source_.StartTicking(); disable_cadence_hysteresis(); scoped_refptr last_frame; bool have_overdisplayed_frame = false; while (!have_overdisplayed_frame) { while (algorithm_.EffectiveFramesQueued() < 2) { algorithm_.EnqueueFrame( CreateFrame(frame_tg.current() - base::TimeTicks())); frame_tg.step(); } size_t frames_dropped = 0; last_frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_TRUE(last_frame); ASSERT_TRUE(is_using_cadence()); ASSERT_EQ(0u, frames_dropped); ASSERT_EQ(2, GetCurrentFrameIdealDisplayCount()); have_overdisplayed_frame = GetCurrentFrameDisplayCount() > 2; } ASSERT_TRUE(last_render_had_glitch()); // We've reached the point where the current frame is over displayed due to // drift, the next frame should resume cadence without accounting for the // overdisplayed frame. size_t frames_dropped = 0; scoped_refptr next_frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_EQ(0u, frames_dropped); ASSERT_NE(last_frame, next_frame); ASSERT_TRUE(is_using_cadence()); ASSERT_EQ(2, GetCurrentFrameIdealDisplayCount()); ASSERT_EQ(1, GetCurrentFrameDisplayCount()); last_frame = next_frame; next_frame = RenderAndStep(&display_tg, &frames_dropped); ASSERT_EQ(0u, frames_dropped); ASSERT_EQ(last_frame, next_frame); ASSERT_TRUE(is_using_cadence()); ASSERT_EQ(2, GetCurrentFrameIdealDisplayCount()); ASSERT_EQ(2, GetCurrentFrameDisplayCount()); } TEST_F(VideoRendererAlgorithmTest, BestFrameByCoverage) { TickGenerator tg(tick_clock_->NowTicks(), 50); time_source_.StartTicking(); algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); base::TimeTicks deadline_min = tg.current(); base::TimeTicks deadline_max = deadline_min + tg.interval(1); size_t frames_dropped = 0; scoped_refptr frame = algorithm_.Render(deadline_min, deadline_max, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(0), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); int second_best = 0; // Coverage should be 1 for if the frame overlaps the interval entirely, no // second best should be found. EXPECT_EQ(0, FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); EXPECT_EQ(-1, second_best); // 49/51 coverage for frame 0 and frame 1 should be within tolerance such that // the earlier frame should still be chosen. deadline_min = tg.current() + tg.interval(1) / 2 + base::TimeDelta::FromMicroseconds(250); deadline_max = deadline_min + tg.interval(1); EXPECT_EQ(0, FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); EXPECT_EQ(1, second_best); // 48/52 coverage should result in the second frame being chosen. deadline_min = tg.current() + tg.interval(1) / 2 + base::TimeDelta::FromMicroseconds(500); deadline_max = deadline_min + tg.interval(1); EXPECT_EQ(1, FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); EXPECT_EQ(0, second_best); // Overlapping three frames should choose the one with the most coverage and // the second best should be the earliest frame. deadline_min = tg.current() + tg.interval(1) / 2; deadline_max = deadline_min + tg.interval(2); EXPECT_EQ(1, FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); EXPECT_EQ(0, second_best); // Requesting coverage outside of all known frames should return -1 for both // best indices. deadline_min = tg.current() + tg.interval(frames_queued()); deadline_max = deadline_min + tg.interval(1); EXPECT_EQ(-1, FindBestFrameByCoverage(deadline_min, deadline_max, &second_best)); EXPECT_EQ(-1, second_best); } TEST_F(VideoRendererAlgorithmTest, BestFrameByDriftAndDriftCalculations) { TickGenerator tg(tick_clock_->NowTicks(), 50); time_source_.StartTicking(); algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); size_t frames_dropped = 0; scoped_refptr frame = algorithm_.Render( tg.current(), tg.current() + tg.interval(1), &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(0), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); base::TimeDelta zero_drift, half_drift = tg.interval(1) / 2; base::TimeDelta detected_drift; // Frame_0 overlaps the deadline, Frame_1 is a full interval away. base::TimeTicks deadline = tg.current(); EXPECT_EQ(zero_drift, CalculateAbsoluteDriftForFrame(deadline, 0)); EXPECT_EQ(tg.interval(1), CalculateAbsoluteDriftForFrame(deadline, 1)); EXPECT_EQ(0, FindBestFrameByDrift(deadline, &detected_drift)); EXPECT_EQ(zero_drift, detected_drift); // Frame_0 overlaps the deadline, Frame_1 is a half interval away. deadline += half_drift; EXPECT_EQ(zero_drift, CalculateAbsoluteDriftForFrame(deadline, 0)); EXPECT_EQ(half_drift, CalculateAbsoluteDriftForFrame(deadline, 1)); EXPECT_EQ(0, FindBestFrameByDrift(deadline, &detected_drift)); EXPECT_EQ(zero_drift, detected_drift); // Both frames overlap the deadline. deadline += half_drift; EXPECT_EQ(zero_drift, CalculateAbsoluteDriftForFrame(deadline, 0)); EXPECT_EQ(zero_drift, CalculateAbsoluteDriftForFrame(deadline, 1)); EXPECT_EQ(1, FindBestFrameByDrift(deadline, &detected_drift)); EXPECT_EQ(zero_drift, detected_drift); // Frame_0 is half an interval away, Frame_1 overlaps the deadline. deadline += half_drift; EXPECT_EQ(half_drift, CalculateAbsoluteDriftForFrame(deadline, 0)); EXPECT_EQ(zero_drift, CalculateAbsoluteDriftForFrame(deadline, 1)); EXPECT_EQ(1, FindBestFrameByDrift(deadline, &detected_drift)); EXPECT_EQ(zero_drift, detected_drift); // Frame_0 is a full interval away, Frame_1 overlaps the deadline. deadline += half_drift; EXPECT_EQ(tg.interval(1), CalculateAbsoluteDriftForFrame(deadline, 0)); EXPECT_EQ(zero_drift, CalculateAbsoluteDriftForFrame(deadline, 1)); EXPECT_EQ(1, FindBestFrameByDrift(deadline, &detected_drift)); EXPECT_EQ(zero_drift, detected_drift); // Both frames are entirely before the deadline. deadline += half_drift; EXPECT_EQ(tg.interval(1) + half_drift, CalculateAbsoluteDriftForFrame(deadline, 0)); EXPECT_EQ(half_drift, CalculateAbsoluteDriftForFrame(deadline, 1)); EXPECT_EQ(1, FindBestFrameByDrift(deadline, &detected_drift)); EXPECT_EQ(half_drift, detected_drift); } // Run through fractional cadence selection for 1/2, 1/3, and 1/4. TEST_F(VideoRendererAlgorithmTest, BestFrameByFractionalCadence) { const double kTestRates[][2] = {{120, 60}, {72, 24}, {100, 25}}; for (const auto& test_rate : kTestRates) { disable_cadence_hysteresis(); TickGenerator frame_tg(base::TimeTicks(), test_rate[0]); TickGenerator display_tg(tick_clock_->NowTicks(), test_rate[1]); const size_t desired_drop_pattern = test_rate[0] / test_rate[1] - 1; scoped_refptr current_frame; RunFramePumpTest( true, &frame_tg, &display_tg, [¤t_frame, desired_drop_pattern, this]( const scoped_refptr& frame, size_t frames_dropped) { ASSERT_TRUE(frame); // The first frame should have zero dropped frames, but each Render() // call after should drop the same number of frames based on the // fractional cadence. if (!current_frame) ASSERT_EQ(0u, frames_dropped); else ASSERT_EQ(desired_drop_pattern, frames_dropped); ASSERT_NE(current_frame, frame); ASSERT_TRUE(is_using_cadence()); current_frame = frame; }); if (HasFatalFailure()) return; } } // Verify a 3:2 frame pattern for 23.974fps and 24fps in 60Hz. TEST_F(VideoRendererAlgorithmTest, FilmCadence) { const double kTestRates[] = {NTSC(24), 24}; disable_cadence_hysteresis(); for (double frame_rate : kTestRates) { scoped_refptr current_frame; int actual_frame_pattern = 0, desired_frame_pattern = 3; TickGenerator frame_tg(base::TimeTicks(), frame_rate); TickGenerator display_tg(tick_clock_->NowTicks(), 60); RunFramePumpTest( true, &frame_tg, &display_tg, [¤t_frame, &actual_frame_pattern, &desired_frame_pattern, this]( const scoped_refptr& frame, size_t frames_dropped) { ASSERT_TRUE(frame); ASSERT_EQ(0u, frames_dropped); if (!current_frame || current_frame == frame) { actual_frame_pattern++; } else { ASSERT_EQ(actual_frame_pattern, desired_frame_pattern); actual_frame_pattern = 1; desired_frame_pattern = (desired_frame_pattern == 3 ? 2 : 3); } current_frame = frame; ASSERT_TRUE(is_using_cadence()); }); if (HasFatalFailure()) return; } } // Spot check common display and frame rate pairs for correctness. TEST_F(VideoRendererAlgorithmTest, CadenceCalculations) { ASSERT_EQ("[3:2]", GetCadence(24, 60)); ASSERT_EQ("[3:2]", GetCadence(NTSC(24), 60)); ASSERT_EQ("[2:3:2:3:2]", GetCadence(25, 60)); ASSERT_EQ("[2]", GetCadence(NTSC(30), 60)); ASSERT_EQ("[2]", GetCadence(30, 60)); ASSERT_EQ("[1:1:2:1:1]", GetCadence(50, 60)); ASSERT_EQ("[1]", GetCadence(NTSC(60), 60)); ASSERT_EQ("[1:0]", GetCadence(120, 60)); // 50Hz is common in the EU. ASSERT_EQ("[]", GetCadence(NTSC(24), 50)); ASSERT_EQ("[]", GetCadence(24, 50)); ASSERT_EQ("[2]", GetCadence(NTSC(25), 50)); ASSERT_EQ("[2]", GetCadence(25, 50)); ASSERT_EQ("[2:1:2]", GetCadence(NTSC(30), 50)); ASSERT_EQ("[2:1:2]", GetCadence(30, 50)); ASSERT_EQ("[]", GetCadence(NTSC(60), 50)); ASSERT_EQ("[]", GetCadence(60, 50)); ASSERT_EQ("[2:3:2:3:2]", GetCadence(25, NTSC(60))); ASSERT_EQ("[1:0]", GetCadence(120, NTSC(60))); ASSERT_EQ("[60]", GetCadence(1, NTSC(60))); } TEST_F(VideoRendererAlgorithmTest, RemoveExpiredFrames) { TickGenerator tg(tick_clock_->NowTicks(), 50); // Removing expired frames before anything is enqueued should do nothing. ASSERT_EQ(0u, algorithm_.RemoveExpiredFrames(tg.current())); algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); ASSERT_EQ(0u, algorithm_.RemoveExpiredFrames(tg.current())); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); time_source_.StartTicking(); size_t frames_dropped = 0; scoped_refptr frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(0), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(3))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(4))); EXPECT_EQ(5u, algorithm_.EffectiveFramesQueued()); tg.step(2); ASSERT_EQ(2u, algorithm_.RemoveExpiredFrames(tg.current())); frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(1u, frames_dropped); EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(3), frame->timestamp()); // Advance expiry enough that one frame is removed, but one remains and is // still counted as effective. ASSERT_EQ( 1u, algorithm_.RemoveExpiredFrames(tg.current() + tg.interval(1) * 0.9)); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); // Advancing expiry once more should mark the frame as ineffective. tg.step(); ASSERT_EQ(0u, algorithm_.RemoveExpiredFrames(tg.current())); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(0u, algorithm_.EffectiveFramesQueued()); } TEST_F(VideoRendererAlgorithmTest, RemoveExpiredFramesCadence) { TickGenerator tg(tick_clock_->NowTicks(), 50); disable_cadence_hysteresis(); algorithm_.EnqueueFrame(CreateFrame(tg.interval(0))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(1))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(2))); ASSERT_EQ(0u, algorithm_.RemoveExpiredFrames(tg.current())); EXPECT_EQ(3u, algorithm_.EffectiveFramesQueued()); time_source_.StartTicking(); size_t frames_dropped = 0; scoped_refptr frame = RenderAndStep(&tg, &frames_dropped); ASSERT_TRUE(frame); EXPECT_EQ(tg.interval(0), frame->timestamp()); EXPECT_EQ(0u, frames_dropped); ASSERT_TRUE(is_using_cadence()); EXPECT_EQ(2u, algorithm_.EffectiveFramesQueued()); // Advance expiry enough that some frames are removed, but one remains and is // still counted as effective. ASSERT_EQ(2u, algorithm_.RemoveExpiredFrames(tg.current() + tg.interval(1) + max_acceptable_drift() * 1.25)); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued()); // Advancing expiry once more should mark the frame as ineffective. tg.step(3); ASSERT_EQ(0u, algorithm_.RemoveExpiredFrames(tg.current())); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(0u, algorithm_.EffectiveFramesQueued()); } // TODO(crbug.com/570032): Test disabled for being flaky. TEST_F(VideoRendererAlgorithmTest, DISABLED_CadenceBasedTest) { // Common display rates. const double kDisplayRates[] = { NTSC(24), 24, NTSC(25), 25, NTSC(30), 30, 48, NTSC(50), 50, NTSC(60), 60, 75, 120, 144, }; // List of common frame rate values. Values pulled from local test media, // videostack test matrix, and Wikipedia. const double kTestRates[] = { 1, 10, 12.5, 15, NTSC(24), 24, NTSC(25), 25, NTSC(30), 30, 30.12, 48, NTSC(50), 50, 58.74, NTSC(60), 60, 72, 90, 100, 120, 144, 240, 300, }; for (double display_rate : kDisplayRates) { for (double frame_rate : kTestRates) { TickGenerator frame_tg(base::TimeTicks(), frame_rate); TickGenerator display_tg(tick_clock_->NowTicks(), display_rate); RunFramePumpTest( true, &frame_tg, &display_tg, [](const scoped_refptr& frame, size_t frames_dropped) {}); if (HasFatalFailure()) return; } } } // Rotate through various playback rates and ensure algorithm adapts correctly. TEST_F(VideoRendererAlgorithmTest, VariablePlaybackRateCadence) { TickGenerator frame_tg(base::TimeTicks(), NTSC(30)); TickGenerator display_tg(tick_clock_->NowTicks(), 60); const double kTestRates[] = {1.0, 2, 0.215, 0.5, 1.0, 3.15}; const bool kTestRateHasCadence[arraysize(kTestRates)] = {true, true, true, true, true, false}; for (size_t i = 0; i < arraysize(kTestRates); ++i) { const double playback_rate = kTestRates[i]; SCOPED_TRACE(base::StringPrintf("Playback Rate: %.03f", playback_rate)); time_source_.SetPlaybackRate(playback_rate); RunFramePumpTest(false, &frame_tg, &display_tg, [this](const scoped_refptr& frame, size_t frames_dropped) {}); if (HasFatalFailure()) return; ASSERT_EQ(kTestRateHasCadence[i], is_using_cadence()); } // TODO(dalecurtis): Is there more we can test here? } // Ensures media which only expresses timestamps in milliseconds, gets the right // cadence detection. TEST_F(VideoRendererAlgorithmTest, UglyTimestampsHaveCadence) { TickGenerator display_tg(tick_clock_->NowTicks(), 60); time_source_.StartTicking(); // 59.94fps, timestamp deltas from https://youtu.be/byoLvAo9qjs const int kBadTimestampsMs[] = { 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 17}; // Run throught ~1.6 seconds worth of frames. bool cadence_detected = false; base::TimeDelta timestamp; for (size_t i = 0; i < arraysize(kBadTimestampsMs) * 2; ++i) { while (algorithm_.EffectiveFramesQueued() < 3) { algorithm_.EnqueueFrame(CreateFrame(timestamp)); timestamp += base::TimeDelta::FromMilliseconds( kBadTimestampsMs[i % arraysize(kBadTimestampsMs)]); } size_t frames_dropped = 0; RenderAndStep(&display_tg, &frames_dropped); ASSERT_EQ(0u, frames_dropped); // Cadence won't be detected immediately on this clip, but it will after // enough frames are encountered; after which it should not drop out of // cadence. if (is_using_cadence()) cadence_detected = true; if (cadence_detected) ASSERT_TRUE(is_using_cadence()); } } // Ensures media with variable frame rate should not be applied with Cadence. TEST_F(VideoRendererAlgorithmTest, VariableFrameRateNoCadence) { TickGenerator display_tg(tick_clock_->NowTicks(), 60); time_source_.StartTicking(); const int kBadTimestampsMs[] = {200, 200, 200, 200, 200, 1000, 1000, 1000, 1000, 200, 200, 200, 200, 200, 1000, 1000, 1000, 1000}; // Run throught ~10 seconds worth of frames. bool cadence_detected = false; bool cadence_turned_off = false; base::TimeDelta timestamp; for (size_t i = 0; i < arraysize(kBadTimestampsMs);) { while (algorithm_.EffectiveFramesQueued() < 3) { algorithm_.EnqueueFrame(CreateFrame(timestamp)); timestamp += base::TimeDelta::FromMilliseconds( kBadTimestampsMs[i % arraysize(kBadTimestampsMs)]); ++i; } size_t frames_dropped = 0; RenderAndStep(&display_tg, &frames_dropped); ASSERT_EQ(0u, frames_dropped); // Cadence would be detected during the first second, and then // it should be off due to variable FPS detection, and then for this // sample, it should never be on. if (is_using_cadence()) cadence_detected = true; if (cadence_detected) { if (!is_using_cadence()) cadence_turned_off = true; } if (cadence_turned_off) { ASSERT_FALSE(is_using_cadence()); } } // Make sure Cadence is turned off somewhen, not always on. ASSERT_TRUE(cadence_turned_off); } TEST_F(VideoRendererAlgorithmTest, EnqueueFrames) { TickGenerator tg(base::TimeTicks(), 50); time_source_.StartTicking(); EXPECT_EQ(0u, frames_queued()); scoped_refptr frame_1 = CreateFrame(tg.interval(0)); algorithm_.EnqueueFrame(frame_1); EXPECT_EQ(1u, frames_queued()); // Enqueuing a frame with the same timestamp should always be dropped. scoped_refptr frame_2 = CreateFrame(tg.interval(0)); algorithm_.EnqueueFrame(frame_2); EXPECT_EQ(1u, frames_queued()); size_t frames_dropped = 0; scoped_refptr rendered_frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(frame_1, rendered_frame); EXPECT_EQ(1, GetCurrentFrameDisplayCount()); // The replaced frame should count as dropped. EXPECT_EQ(1u, frames_dropped); // Trying to replace frame_1 with frame_2 should do nothing. algorithm_.EnqueueFrame(frame_2); EXPECT_EQ(1u, frames_queued()); rendered_frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(frame_1, rendered_frame); EXPECT_EQ(1u, frames_dropped); EXPECT_EQ(2, GetCurrentFrameDisplayCount()); // Trying to add a frame < 1 ms after the last frame should drop the frame. algorithm_.EnqueueFrame(CreateFrame(base::TimeDelta::FromMicroseconds(999))); rendered_frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(frame_1, rendered_frame); EXPECT_EQ(1u, frames_dropped); EXPECT_EQ(3, GetCurrentFrameDisplayCount()); scoped_refptr frame_3 = CreateFrame(tg.interval(1)); algorithm_.EnqueueFrame(frame_3); EXPECT_EQ(2u, frames_queued()); // Trying to add a frame < 1 ms before the last frame should drop the frame. algorithm_.EnqueueFrame( CreateFrame(tg.interval(1) - base::TimeDelta::FromMicroseconds(999))); rendered_frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(frame_3, rendered_frame); EXPECT_EQ(1u, frames_dropped); EXPECT_EQ(1, GetCurrentFrameDisplayCount()); } TEST_F(VideoRendererAlgorithmTest, CadenceForFutureFrames) { TickGenerator tg(base::TimeTicks(), 50); time_source_.StartTicking(); disable_cadence_hysteresis(); algorithm_.EnqueueFrame(CreateFrame(tg.interval(10))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(11))); algorithm_.EnqueueFrame(CreateFrame(tg.interval(12))); EXPECT_EQ(3u, frames_queued()); // Call Render() a few times to increment the render count. for (int i = 0; i < 10; ++i) { size_t frames_dropped = 0; scoped_refptr rendered_frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(3u, frames_queued()); EXPECT_EQ(tg.interval(10), rendered_frame->timestamp()); ASSERT_TRUE(is_using_cadence()); } // Add some noise to the tick generator so it our first frame // doesn't line up evenly on a deadline. tg.Reset(tg.current() + base::TimeDelta::FromMilliseconds(5)); // We're now at the first frame, cadence should be one, so // it should only be displayed once. size_t frames_dropped = 0; scoped_refptr rendered_frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(3u, frames_queued()); EXPECT_EQ(tg.interval(10), rendered_frame->timestamp()); ASSERT_TRUE(is_using_cadence()); // Then the next frame should be displayed. rendered_frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(tg.interval(11), rendered_frame->timestamp()); ASSERT_TRUE(is_using_cadence()); // Finally the last frame. rendered_frame = RenderAndStep(&tg, &frames_dropped); EXPECT_EQ(1u, frames_queued()); EXPECT_EQ(tg.interval(12), rendered_frame->timestamp()); ASSERT_TRUE(is_using_cadence()); } } // namespace media