diff options
author | dalecurtis <dalecurtis@chromium.org> | 2015-05-05 00:23:15 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-05-05 07:23:49 +0000 |
commit | 3e8bda613486ae81f53428ab9f41a7dbb7f9f0c3 (patch) | |
tree | 515411b1d64ba6bb8c318d7d4ac31ca7c8a55f2a /media | |
parent | c4a4946256cd0abd02b187bd9efbd8d9b3c99b2a (diff) | |
download | chromium_src-3e8bda613486ae81f53428ab9f41a7dbb7f9f0c3.zip chromium_src-3e8bda613486ae81f53428ab9f41a7dbb7f9f0c3.tar.gz chromium_src-3e8bda613486ae81f53428ab9f41a7dbb7f9f0c3.tar.bz2 |
Move background rendering pump to VideoFrameCompositor. Fixes tests.
Moving the pump to VFC cleans up a lot of code around dealing with stale
frames and makes it possible to ensure we always have a frame ready for
testing and upon ended without having to make an extra call during Stop.
Doing this required / exposed:
- Issues with firing the transition to HAVE_NOTHING, tests added.
- Adding a |background_rendering| flag to the render callback.
- NullAudioSink being built with non-test code.
BUG=439548
TEST=new tests, all layout tests pass.
Review URL: https://codereview.chromium.org/1121103002
Cr-Commit-Position: refs/heads/master@{#328295}
Diffstat (limited to 'media')
-rw-r--r-- | media/base/BUILD.gn | 4 | ||||
-rw-r--r-- | media/base/null_video_sink.cc | 21 | ||||
-rw-r--r-- | media/base/null_video_sink.h | 16 | ||||
-rw-r--r-- | media/base/null_video_sink_unittest.cc | 20 | ||||
-rw-r--r-- | media/base/video_renderer_sink.h | 7 | ||||
-rw-r--r-- | media/blink/video_frame_compositor.cc | 129 | ||||
-rw-r--r-- | media/blink/video_frame_compositor.h | 59 | ||||
-rw-r--r-- | media/blink/video_frame_compositor_unittest.cc | 125 | ||||
-rw-r--r-- | media/blink/webmediaplayer_impl.cc | 2 | ||||
-rw-r--r-- | media/media.gyp | 4 | ||||
-rw-r--r-- | media/renderers/video_renderer_impl.cc | 149 | ||||
-rw-r--r-- | media/renderers/video_renderer_impl.h | 42 | ||||
-rw-r--r-- | media/renderers/video_renderer_impl_unittest.cc | 139 |
13 files changed, 314 insertions, 403 deletions
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn index 0602408..a728cfa 100644 --- a/media/base/BUILD.gn +++ b/media/base/BUILD.gn @@ -123,8 +123,6 @@ source_set("base") { "moving_average.h", "multi_channel_resampler.cc", "multi_channel_resampler.h", - "null_video_sink.cc", - "null_video_sink.h", "pipeline.cc", "pipeline.h", "pipeline_status.h", @@ -335,6 +333,8 @@ source_set("test_support") { "mock_demuxer_host.h", "mock_filters.cc", "mock_filters.h", + "null_video_sink.cc", + "null_video_sink.h", "test_data_util.cc", "test_data_util.h", "test_helpers.cc", diff --git a/media/base/null_video_sink.cc b/media/base/null_video_sink.cc index 214c499..ea38d06 100644 --- a/media/base/null_video_sink.cc +++ b/media/base/null_video_sink.cc @@ -22,7 +22,8 @@ NullVideoSink::NullVideoSink( task_runner_(task_runner), started_(false), callback_(nullptr), - tick_clock_(&default_tick_clock_) { + tick_clock_(&default_tick_clock_), + background_render_(false) { } NullVideoSink::~NullVideoSink() { @@ -53,14 +54,12 @@ void NullVideoSink::CallRender() { DCHECK(started_); const base::TimeTicks end_of_interval = current_render_time_ + interval_; - if (current_render_time_ > pause_end_time_) { - scoped_refptr<VideoFrame> new_frame = - callback_->Render(current_render_time_, end_of_interval); - const bool is_new_frame = new_frame != last_frame_; - last_frame_ = new_frame; - if (is_new_frame) - new_frame_cb_.Run(new_frame); - } + scoped_refptr<VideoFrame> new_frame = callback_->Render( + current_render_time_, end_of_interval, background_render_); + const bool is_new_frame = new_frame != last_frame_; + last_frame_ = new_frame; + if (is_new_frame) + new_frame_cb_.Run(new_frame); current_render_time_ += interval_; @@ -94,8 +93,4 @@ void NullVideoSink::PaintFrameUsingOldRenderingPath( new_frame_cb_.Run(frame); } -void NullVideoSink::PauseRenderCallbacks(base::TimeTicks pause_until) { - pause_end_time_ = pause_until; -} - } // namespace media diff --git a/media/base/null_video_sink.h b/media/base/null_video_sink.h index f1e076c..66ff79f 100644 --- a/media/base/null_video_sink.h +++ b/media/base/null_video_sink.h @@ -6,7 +6,6 @@ #define MEDIA_AUDIO_NULL_VIDEO_SINK_H_ #include "base/cancelable_callback.h" -#include "base/md5.h" #include "base/memory/scoped_ptr.h" #include "base/time/default_tick_clock.h" #include "base/time/tick_clock.h" @@ -18,7 +17,7 @@ class SingleThreadTaskRunner; namespace media { -class MEDIA_EXPORT NullVideoSink : NON_EXPORTED_BASE(public VideoRendererSink) { +class NullVideoSink : public VideoRendererSink { public: using NewFrameCB = base::Callback<void(const scoped_refptr<VideoFrame>&)>; @@ -38,9 +37,6 @@ class MEDIA_EXPORT NullVideoSink : NON_EXPORTED_BASE(public VideoRendererSink) { void PaintFrameUsingOldRenderingPath( const scoped_refptr<VideoFrame>& frame) override; - // Allows tests to simulate suspension of Render() callbacks. - void PauseRenderCallbacks(base::TimeTicks pause_until); - void set_tick_clock_for_testing(base::TickClock* tick_clock) { tick_clock_ = tick_clock; } @@ -52,6 +48,10 @@ class MEDIA_EXPORT NullVideoSink : NON_EXPORTED_BASE(public VideoRendererSink) { bool is_started() const { return started_; } + void set_background_render(bool is_background_rendering) { + background_render_ = is_background_rendering; + } + private: // Task that periodically calls Render() to consume video data. void CallRender(); @@ -74,9 +74,6 @@ class MEDIA_EXPORT NullVideoSink : NON_EXPORTED_BASE(public VideoRendererSink) { // to maintain stable periodicity of callbacks. base::TimeTicks current_render_time_; - // Used to suspend Render() callbacks to |callback_| for some time. - base::TimeTicks pause_end_time_; - // Allow for an injectable tick clock for testing. base::DefaultTickClock default_tick_clock_; base::TimeTicks last_now_; @@ -87,6 +84,9 @@ class MEDIA_EXPORT NullVideoSink : NON_EXPORTED_BASE(public VideoRendererSink) { // If set, called when Stop() is called. base::Closure stop_cb_; + // Value passed to RenderCallback::Render(). + bool background_render_; + DISALLOW_COPY_AND_ASSIGN(NullVideoSink); }; diff --git a/media/base/null_video_sink_unittest.cc b/media/base/null_video_sink_unittest.cc index aebe328..0279695 100644 --- a/media/base/null_video_sink_unittest.cc +++ b/media/base/null_video_sink_unittest.cc @@ -48,8 +48,10 @@ class NullVideoSinkTest : public testing::Test, } // VideoRendererSink::RenderCallback implementation. - MOCK_METHOD2(Render, - scoped_refptr<VideoFrame>(base::TimeTicks, base::TimeTicks)); + MOCK_METHOD3(Render, + scoped_refptr<VideoFrame>(base::TimeTicks, + base::TimeTicks, + bool)); MOCK_METHOD0(OnFrameDropped, void()); MOCK_METHOD1(FrameReceived, void(const scoped_refptr<VideoFrame>&)); @@ -76,7 +78,7 @@ TEST_F(NullVideoSinkTest, BasicFunctionality) { sink->Start(this); const base::TimeTicks current_time = tick_clock_.NowTicks(); const base::TimeTicks current_interval_end = current_time + kInterval; - EXPECT_CALL(*this, Render(current_time, current_interval_end)) + EXPECT_CALL(*this, Render(current_time, current_interval_end, false)) .WillOnce(Return(test_frame)); WaitableMessageLoopEvent event; EXPECT_CALL(*this, FrameReceived(test_frame)) @@ -84,12 +86,16 @@ TEST_F(NullVideoSinkTest, BasicFunctionality) { event.RunAndWait(); } + // Verify that toggling background rendering mode issues the right bit to + // each Render() call. + sink->set_background_render(true); + // A second call returning the same frame should not result in a new call to // FrameReceived(). { SCOPED_TRACE("Waiting for second render call."); WaitableMessageLoopEvent event; - EXPECT_CALL(*this, Render(_, _)) + EXPECT_CALL(*this, Render(_, _, true)) .WillOnce(Return(test_frame)) .WillOnce(Return(nullptr)); EXPECT_CALL(*this, FrameReceived(test_frame)).Times(0); @@ -127,11 +133,11 @@ TEST_F(NullVideoSinkTest, ClocklessFunctionality) { for (int i = 0; i < kTestRuns; ++i) { if (i < kTestRuns - 1) { EXPECT_CALL(*this, Render(current_time + i * interval, - current_time + (i + 1) * interval)) + current_time + (i + 1) * interval, false)) .WillOnce(Return(test_frame)); } else { EXPECT_CALL(*this, Render(current_time + i * interval, - current_time + (i + 1) * interval)) + current_time + (i + 1) * interval, false)) .WillOnce(DoAll(RunClosure(event.GetClosure()), Return(nullptr))); } } @@ -140,4 +146,4 @@ TEST_F(NullVideoSinkTest, ClocklessFunctionality) { sink->Stop(); } -} +} // namespace media diff --git a/media/base/video_renderer_sink.h b/media/base/video_renderer_sink.h index 3f89856..5a51a73 100644 --- a/media/base/video_renderer_sink.h +++ b/media/base/video_renderer_sink.h @@ -27,8 +27,13 @@ class MEDIA_EXPORT VideoRendererSink { // rendering within the requested interval. Intervals are expected to be // regular, contiguous, and monotonically increasing. Irregular intervals // may affect the rendering decisions made by the underlying callback. + // + // If |background_rendering| is true, the VideoRenderSink is pumping + // callbacks at a lower frequency than normal and the results of the + // Render() call may not be used. virtual scoped_refptr<VideoFrame> Render(base::TimeTicks deadline_min, - base::TimeTicks deadline_max) = 0; + base::TimeTicks deadline_max, + bool background_rendering) = 0; // Called by the sink when a VideoFrame previously returned via Render() was // not actually rendered. Must be called before the next Render() call. diff --git a/media/blink/video_frame_compositor.cc b/media/blink/video_frame_compositor.cc index 8064d21..634eb4f 100644 --- a/media/blink/video_frame_compositor.cc +++ b/media/blink/video_frame_compositor.cc @@ -12,10 +12,9 @@ namespace media { -// The maximum time we'll allow to elapse between Render() callbacks if there is -// an external caller requesting frames via GetCurrentFrame(); i.e. there is a -// canvas which frames are being copied into. -const int kStaleFrameThresholdMs = 250; +// Amount of time to wait between UpdateCurrentFrame() callbacks before starting +// background rendering to keep the Render() callbacks moving. +const int kBackgroundRenderingTimeoutMs = 250; static bool IsOpaque(const scoped_refptr<VideoFrame>& frame) { switch (frame->format()) { @@ -48,14 +47,21 @@ VideoFrameCompositor::VideoFrameCompositor( tick_clock_(new base::DefaultTickClock()), natural_size_changed_cb_(natural_size_changed_cb), opacity_changed_cb_(opacity_changed_cb), - stale_frame_threshold_( - base::TimeDelta::FromMilliseconds(kStaleFrameThresholdMs)), + background_rendering_enabled_(true), + background_rendering_timer_( + FROM_HERE, + base::TimeDelta::FromMilliseconds(kBackgroundRenderingTimeoutMs), + base::Bind(&VideoFrameCompositor::BackgroundRender, + base::Unretained(this)), + // Task is not repeating, CallRender() will reset the task as needed. + false), client_(nullptr), rendering_(false), rendered_last_frame_(false), - callback_(nullptr), // Assume 60Hz before the first UpdateCurrentFrame() call. - last_interval_(base::TimeDelta::FromSecondsD(1.0 / 60)) { + last_interval_(base::TimeDelta::FromSecondsD(1.0 / 60)), + callback_(nullptr) { + background_rendering_timer_.SetTaskRunner(compositor_task_runner_); } VideoFrameCompositor::~VideoFrameCompositor() { @@ -70,7 +76,16 @@ void VideoFrameCompositor::OnRendererStateUpdate(bool new_state) { DCHECK(compositor_task_runner_->BelongsToCurrentThread()); DCHECK_NE(rendering_, new_state); rendering_ = new_state; - last_frame_update_time_ = base::TimeTicks(); + + if (rendering_) { + // Always start playback in background rendering mode, if |client_| kicks + // in right away it's okay. + BackgroundRender(); + } else if (background_rendering_enabled_) { + background_rendering_timer_.Stop(); + } else { + DCHECK(!background_rendering_timer_.IsRunning()); + } if (!client_) return; @@ -81,18 +96,6 @@ void VideoFrameCompositor::OnRendererStateUpdate(bool new_state) { client_->StopRendering(); } -scoped_refptr<VideoFrame> -VideoFrameCompositor::GetCurrentFrameAndUpdateIfStale() { - DCHECK(compositor_task_runner_->BelongsToCurrentThread()); - if (rendering_) { - const base::TimeTicks now = tick_clock_->NowTicks(); - if (now - last_frame_update_time_ > stale_frame_threshold_) - UpdateCurrentFrame(now, now + last_interval_); - } - - return GetCurrentFrame(); -} - void VideoFrameCompositor::SetVideoFrameProviderClient( cc::VideoFrameProvider::Client* client) { DCHECK(compositor_task_runner_->BelongsToCurrentThread()); @@ -100,7 +103,8 @@ void VideoFrameCompositor::SetVideoFrameProviderClient( client_->StopUsingProvider(); client_ = client; - if (rendering_) + // |client_| may now be null, so verify before calling it. + if (rendering_ && client_) client_->StartRendering(); } @@ -117,23 +121,11 @@ void VideoFrameCompositor::PutCurrentFrame() { bool VideoFrameCompositor::UpdateCurrentFrame(base::TimeTicks deadline_min, base::TimeTicks deadline_max) { DCHECK(compositor_task_runner_->BelongsToCurrentThread()); - base::AutoLock lock(lock_); - if (!callback_) - return false; - - DCHECK(rendering_); - - // If the previous frame was never rendered, let the client know. - if (!rendered_last_frame_ && current_frame_) - callback_->OnFrameDropped(); - - last_frame_update_time_ = tick_clock_->NowTicks(); - last_interval_ = deadline_max - deadline_min; - return ProcessNewFrame(callback_->Render(deadline_min, deadline_max), false); + return CallRender(deadline_min, deadline_max, false); } void VideoFrameCompositor::Start(RenderCallback* callback) { - TRACE_EVENT0("media", __FUNCTION__); + TRACE_EVENT0("media", "VideoFrameCompositor::Start"); // Called from the media thread, so acquire the callback under lock before // returning in case a Stop() call comes in before the PostTask is processed. @@ -146,27 +138,13 @@ void VideoFrameCompositor::Start(RenderCallback* callback) { } void VideoFrameCompositor::Stop() { - TRACE_EVENT0("media", __FUNCTION__); + TRACE_EVENT0("media", "VideoFrameCompositor::Stop"); // Called from the media thread, so release the callback under lock before // returning to avoid a pending UpdateCurrentFrame() call occurring before // the PostTask is processed. base::AutoLock lock(lock_); DCHECK(callback_); - - // Fire one more Render() callback if we're more than one render interval away - // to ensure that we have a good frame to display if Render() has never been - // called, or was called long enough ago that the frame is stale. We must - // always have a |current_frame_| to recover from "damage" to the video layer. - const base::TimeTicks now = tick_clock_->NowTicks(); - if (now - last_frame_update_time_ > last_interval_) { - compositor_task_runner_->PostTask( - FROM_HERE, - base::Bind(base::IgnoreResult(&VideoFrameCompositor::ProcessNewFrame), - base::Unretained(this), - callback_->Render(now, now + last_interval_), true)); - } - callback_ = nullptr; compositor_task_runner_->PostTask( FROM_HERE, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate, @@ -183,12 +161,12 @@ void VideoFrameCompositor::PaintFrameUsingOldRenderingPath( return; } - ProcessNewFrame(frame, true); + if (ProcessNewFrame(frame) && client_) + client_->DidReceiveFrame(); } bool VideoFrameCompositor::ProcessNewFrame( - const scoped_refptr<VideoFrame>& frame, - bool notify_client_of_new_frames) { + const scoped_refptr<VideoFrame>& frame) { DCHECK(compositor_task_runner_->BelongsToCurrentThread()); if (frame == current_frame_) @@ -207,10 +185,47 @@ bool VideoFrameCompositor::ProcessNewFrame( opacity_changed_cb_.Run(IsOpaque(frame)); current_frame_ = frame; - if (notify_client_of_new_frames && client_) - client_->DidReceiveFrame(); - return true; } +void VideoFrameCompositor::BackgroundRender() { + DCHECK(compositor_task_runner_->BelongsToCurrentThread()); + const base::TimeTicks now = tick_clock_->NowTicks(); + CallRender(now, now + last_interval_, true); +} + +bool VideoFrameCompositor::CallRender(base::TimeTicks deadline_min, + base::TimeTicks deadline_max, + bool background_rendering) { + DCHECK(compositor_task_runner_->BelongsToCurrentThread()); + + base::AutoLock lock(lock_); + if (!callback_) { + // Even if we no longer have a callback, return true if we have a frame + // which |client_| hasn't seen before. + return !rendered_last_frame_ && current_frame_; + } + + DCHECK(rendering_); + + // If the previous frame was never rendered and we're not in background + // rendering mode (nor have just exited it), let the client know. + if (!rendered_last_frame_ && current_frame_ && !background_rendering && + !is_background_rendering_) { + callback_->OnFrameDropped(); + } + + const bool new_frame = ProcessNewFrame( + callback_->Render(deadline_min, deadline_max, background_rendering)); + + is_background_rendering_ = background_rendering; + last_interval_ = deadline_max - deadline_min; + + // Restart the background rendering timer whether we're background rendering + // or not; in either case we should wait for |kBackgroundRenderingTimeoutMs|. + if (background_rendering_enabled_) + background_rendering_timer_.Reset(); + return new_frame; +} + } // namespace media diff --git a/media/blink/video_frame_compositor.h b/media/blink/video_frame_compositor.h index 2e8c2ccc..4eb2e7e 100644 --- a/media/blink/video_frame_compositor.h +++ b/media/blink/video_frame_compositor.h @@ -10,6 +10,7 @@ #include "base/single_thread_task_runner.h" #include "base/synchronization/lock.h" #include "base/time/tick_clock.h" +#include "base/timer/timer.h" #include "cc/layers/video_frame_provider.h" #include "media/base/media_export.h" #include "media/base/video_renderer_sink.h" @@ -38,6 +39,9 @@ class VideoFrame; // once new frames are expected or are no longer expected to be ready; this data // is relayed to the compositor to avoid extraneous callbacks. // +// VideoFrameCompositor is also responsible for pumping UpdateCurrentFrame() +// callbacks in the background when |client_| has decided to suspend them. +// // VideoFrameCompositor must live on the same thread as the compositor, though // it may be constructed on any thread. class MEDIA_EXPORT VideoFrameCompositor @@ -68,14 +72,6 @@ class MEDIA_EXPORT VideoFrameCompositor // called before destruction starts. ~VideoFrameCompositor() override; - // Returns |current_frame_| if it was refreshed recently; otherwise, if - // |callback_| is available, requests a new frame and returns that one. - // - // This is required for programmatic frame requests where the compositor may - // have stopped issuing UpdateCurrentFrame() callbacks in response to - // visibility changes in the output layer. - scoped_refptr<VideoFrame> GetCurrentFrameAndUpdateIfStale(); - // cc::VideoFrameProvider implementation. These methods must be called on the // |compositor_task_runner_|. void SetVideoFrameProviderClient( @@ -96,23 +92,37 @@ class MEDIA_EXPORT VideoFrameCompositor tick_clock_ = tick_clock.Pass(); } - base::TimeDelta get_stale_frame_threshold_for_testing() const { - return stale_frame_threshold_; - } - void clear_current_frame_for_testing() { current_frame_ = nullptr; } + // Enables or disables background rendering. If |enabled|, |timeout| is the + // amount of time to wait after the last Render() call before starting the + // background rendering mode. Note, this can not disable the background + // rendering call issues when a sink is started. + void set_background_rendering_for_testing(bool enabled) { + background_rendering_enabled_ = enabled; + } + private: // Called on the compositor thread in response to Start() or Stop() calls; // must be used to change |rendering_| state. void OnRendererStateUpdate(bool new_state); // Handles setting of |current_frame_| and fires |natural_size_changed_cb_| - // and |opacity_changed_cb_| when the frame properties changes. Will also - // call DidReceiveFrame() on |client_| if |notify_client_of_new_frames| is - // true and a new frame is encountered. - bool ProcessNewFrame(const scoped_refptr<VideoFrame>& frame, - bool notify_client_of_new_frames); + // and |opacity_changed_cb_| when the frame properties changes. + bool ProcessNewFrame(const scoped_refptr<VideoFrame>& frame); + + // Called by |background_rendering_timer_| when enough time elapses where we + // haven't seen a Render() call. + void BackgroundRender(); + + // If |callback_| is available, calls Render() with the provided properties. + // Updates |is_background_rendering_|, |last_interval_|, and resets + // |background_rendering_timer_|. Ensures that natural size and opacity + // changes are correctly fired. Returns true if there's a new frame available + // via GetCurrentFrame(). + bool CallRender(base::TimeTicks deadline_min, + base::TimeTicks deadline_max, + bool background_rendering); scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_; scoped_ptr<base::TickClock> tick_clock_; @@ -121,23 +131,26 @@ class MEDIA_EXPORT VideoFrameCompositor const base::Callback<void(gfx::Size)> natural_size_changed_cb_; const base::Callback<void(bool)> opacity_changed_cb_; - base::TimeDelta stale_frame_threshold_; + // Allows tests to disable the background rendering task. + bool background_rendering_enabled_; + + // Manages UpdateCurrentFrame() callbacks if |client_| has stopped sending + // them for various reasons. Runs on |compositor_task_runner_| and is reset + // after each successful UpdateCurrentFrame() call. + base::Timer background_rendering_timer_; // These values are only set and read on the compositor thread. cc::VideoFrameProvider::Client* client_; scoped_refptr<VideoFrame> current_frame_; bool rendering_; bool rendered_last_frame_; + bool is_background_rendering_; + base::TimeDelta last_interval_; // These values are updated and read from the media and compositor threads. base::Lock lock_; VideoRendererSink::RenderCallback* callback_; - // These values are set on the compositor thread under lock and may be read - // from the media thread under lock. - base::TimeTicks last_frame_update_time_; - base::TimeDelta last_interval_; - DISALLOW_COPY_AND_ASSIGN(VideoFrameCompositor); }; diff --git a/media/blink/video_frame_compositor_unittest.cc b/media/blink/video_frame_compositor_unittest.cc index 553222c..9f73cb4 100644 --- a/media/blink/video_frame_compositor_unittest.cc +++ b/media/blink/video_frame_compositor_unittest.cc @@ -4,6 +4,7 @@ #include "base/bind.h" #include "base/message_loop/message_loop.h" +#include "base/run_loop.h" #include "base/test/simple_test_tick_clock.h" #include "cc/layers/video_frame_provider.h" #include "media/base/video_frame.h" @@ -12,10 +13,15 @@ #include "testing/gtest/include/gtest/gtest.h" using testing::_; +using testing::DoAll; using testing::Return; namespace media { +ACTION_P(RunClosure, closure) { + closure.Run(); +} + class VideoFrameCompositorTest : public testing::Test, public cc::VideoFrameProvider::Client, public VideoRendererSink::RenderCallback { @@ -35,10 +41,12 @@ class VideoFrameCompositorTest : public testing::Test, compositor_->SetVideoFrameProviderClient(this); compositor_->set_tick_clock_for_testing( scoped_ptr<base::TickClock>(tick_clock_)); + // Disable background rendering by default. + compositor_->set_background_rendering_for_testing(false); } ~VideoFrameCompositorTest() override { - compositor_->SetVideoFrameProviderClient(NULL); + compositor_->SetVideoFrameProviderClient(nullptr); } VideoFrameCompositor* compositor() { return compositor_.get(); } @@ -58,8 +66,10 @@ class VideoFrameCompositorTest : public testing::Test, void DidUpdateMatrix(const float* matrix) override {} // VideoRendererSink::RenderCallback implementation. - MOCK_METHOD2(Render, - scoped_refptr<VideoFrame>(base::TimeTicks, base::TimeTicks)); + MOCK_METHOD3(Render, + scoped_refptr<VideoFrame>(base::TimeTicks, + base::TimeTicks, + bool)); MOCK_METHOD0(OnFrameDropped, void()); void NaturalSizeChanged(gfx::Size natural_size) { @@ -81,13 +91,14 @@ class VideoFrameCompositorTest : public testing::Test, message_loop.RunUntilIdle(); } - void StopVideoRendererSink() { - EXPECT_CALL(*this, StopRendering()); + void StopVideoRendererSink(bool have_client) { + if (have_client) + EXPECT_CALL(*this, StopRendering()); + const bool had_current_frame = !!compositor_->GetCurrentFrame(); compositor()->Stop(); + // If we previously had a frame, we should still have one now. + EXPECT_EQ(had_current_frame, !!compositor_->GetCurrentFrame()); message_loop.RunUntilIdle(); - - // We should still have a frame after stop is called. - EXPECT_TRUE(compositor_->GetCurrentFrame()); } void RenderFrame() { @@ -165,20 +176,19 @@ TEST_F(VideoFrameCompositorTest, NaturalSizeChanged) { natural_size_ = empty_size; compositor()->clear_current_frame_for_testing(); - StartVideoRendererSink(); - EXPECT_CALL(*this, Render(_, _)) + EXPECT_CALL(*this, Render(_, _, _)) .WillOnce(Return(initial_frame)) .WillOnce(Return(larger_frame)) .WillOnce(Return(initial_frame)) .WillOnce(Return(initial_frame)); - // Callback isn't fired for the first frame. + StartVideoRendererSink(); + + // Starting the sink will issue one Render() call, ensure the callback isn't + // fired for the first frame. EXPECT_EQ(0, natural_size_changed_count()); - EXPECT_TRUE( - compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks())); - RenderFrame(); EXPECT_EQ(empty_size, natural_size()); - EXPECT_EQ(0, natural_size_changed_count()); + // Once another frame is received with a different size it should fire. EXPECT_TRUE( compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks())); RenderFrame(); @@ -197,7 +207,7 @@ TEST_F(VideoFrameCompositorTest, NaturalSizeChanged) { EXPECT_EQ(2, natural_size_changed_count()); RenderFrame(); - StopVideoRendererSink(); + StopVideoRendererSink(true); } TEST_F(VideoFrameCompositorTest, OpacityChanged) { @@ -234,16 +244,12 @@ TEST_F(VideoFrameCompositorTest, OpacityChanged) { opacity_changed_count_ = 0; compositor()->clear_current_frame_for_testing(); - StartVideoRendererSink(); - EXPECT_CALL(*this, Render(_, _)) + EXPECT_CALL(*this, Render(_, _, _)) .WillOnce(Return(not_opaque_frame)) .WillOnce(Return(not_opaque_frame)) .WillOnce(Return(opaque_frame)) .WillOnce(Return(opaque_frame)); - EXPECT_EQ(0, opacity_changed_count()); - EXPECT_TRUE( - compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks())); - RenderFrame(); + StartVideoRendererSink(); EXPECT_FALSE(opaque()); EXPECT_EQ(1, opacity_changed_count()); @@ -265,7 +271,7 @@ TEST_F(VideoFrameCompositorTest, OpacityChanged) { EXPECT_EQ(2, opacity_changed_count()); RenderFrame(); - StopVideoRendererSink(); + StopVideoRendererSink(true); } TEST_F(VideoFrameCompositorTest, VideoRendererSinkFrameDropped) { @@ -273,12 +279,15 @@ TEST_F(VideoFrameCompositorTest, VideoRendererSinkFrameDropped) { scoped_refptr<VideoFrame> opaque_frame = VideoFrame::CreateFrame( VideoFrame::YV12, size, gfx::Rect(size), size, base::TimeDelta()); + EXPECT_CALL(*this, Render(_, _, _)).WillRepeatedly(Return(opaque_frame)); StartVideoRendererSink(); - EXPECT_CALL(*this, Render(_, _)).WillRepeatedly(Return(opaque_frame)); - EXPECT_TRUE( + + // The first UpdateCurrentFrame() after a background render, which starting + // the sink does automatically, won't report a dropped frame. + EXPECT_FALSE( compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks())); - // If we don't call RenderFrame() the frame should be reported as dropped. + // Another call should trigger a dropped frame callback. EXPECT_CALL(*this, OnFrameDropped()); EXPECT_FALSE( compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks())); @@ -300,60 +309,48 @@ TEST_F(VideoFrameCompositorTest, VideoRendererSinkFrameDropped) { EXPECT_FALSE( compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks())); - StopVideoRendererSink(); + StopVideoRendererSink(true); +} + +TEST_F(VideoFrameCompositorTest, VideoLayerShutdownWhileRendering) { + EXPECT_CALL(*this, Render(_, _, true)).WillOnce(Return(nullptr)); + StartVideoRendererSink(); + compositor_->SetVideoFrameProviderClient(nullptr); + StopVideoRendererSink(false); } -TEST_F(VideoFrameCompositorTest, GetCurrentFrameAndUpdateIfStale) { +TEST_F(VideoFrameCompositorTest, StartFiresBackgroundRender) { gfx::Size size(8, 8); scoped_refptr<VideoFrame> opaque_frame = VideoFrame::CreateFrame( VideoFrame::YV12, size, gfx::Rect(size), size, base::TimeDelta()); - scoped_refptr<VideoFrame> opaque_frame_2 = VideoFrame::CreateFrame( - VideoFrame::YV12, size, gfx::Rect(size), size, base::TimeDelta()); + EXPECT_CALL(*this, Render(_, _, true)).WillRepeatedly(Return(opaque_frame)); StartVideoRendererSink(); - EXPECT_CALL(*this, Render(_, _)) - .WillOnce(Return(opaque_frame)) - .WillOnce(Return(opaque_frame_2)); - EXPECT_TRUE( - compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks())); - - base::TimeDelta stale_frame_threshold = - compositor()->get_stale_frame_threshold_for_testing(); - - // Advancing time a little bit shouldn't cause the frame to be stale. - tick_clock_->Advance(stale_frame_threshold / 2); - EXPECT_EQ(opaque_frame, compositor()->GetCurrentFrameAndUpdateIfStale()); - - // Since rendering of frames is likely not happening, this will trigger a - // dropped frame call. - EXPECT_CALL(*this, OnFrameDropped()); - - // Advancing the clock over the threshold should cause a new frame request. - tick_clock_->Advance(stale_frame_threshold / 2 + - base::TimeDelta::FromMicroseconds(1)); - EXPECT_EQ(opaque_frame_2, compositor()->GetCurrentFrameAndUpdateIfStale()); - - StopVideoRendererSink(); + StopVideoRendererSink(true); } -TEST_F(VideoFrameCompositorTest, StopUpdatesCurrentFrameIfStale) { +TEST_F(VideoFrameCompositorTest, BackgroundRenderTicks) { gfx::Size size(8, 8); scoped_refptr<VideoFrame> opaque_frame = VideoFrame::CreateFrame( VideoFrame::YV12, size, gfx::Rect(size), size, base::TimeDelta()); - const base::TimeDelta interval = base::TimeDelta::FromSecondsD(1.0 / 60); + compositor_->set_background_rendering_for_testing(true); + base::RunLoop run_loop; + EXPECT_CALL(*this, Render(_, _, true)) + .WillOnce(Return(opaque_frame)) + .WillOnce( + DoAll(RunClosure(run_loop.QuitClosure()), Return(opaque_frame))); StartVideoRendererSink(); + run_loop.Run(); - // Expect two calls to Render(), one from UpdateCurrentFrame() and one from - // Stop() because the frame is too old. - EXPECT_CALL(*this, Render(_, _)) - .WillOnce(Return(opaque_frame)) - .WillOnce(Return(opaque_frame)); - EXPECT_TRUE(compositor()->UpdateCurrentFrame(base::TimeTicks(), - base::TimeTicks() + interval)); - tick_clock_->Advance(interval * 2); - StopVideoRendererSink(); + // UpdateCurrentFrame() calls should indicate they are not synthetic. + EXPECT_CALL(*this, Render(_, _, false)).WillOnce(Return(opaque_frame)); + EXPECT_FALSE( + compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks())); + + // Background rendering should tick another render callback. + StopVideoRendererSink(true); } } // namespace media diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc index 79c134f..bf938c2 100644 --- a/media/blink/webmediaplayer_impl.cc +++ b/media/blink/webmediaplayer_impl.cc @@ -985,7 +985,7 @@ static void GetCurrentFrameAndSignal( scoped_refptr<VideoFrame>* video_frame_out, base::WaitableEvent* event) { TRACE_EVENT0("media", "GetCurrentFrameAndSignal"); - *video_frame_out = compositor->GetCurrentFrameAndUpdateIfStale(); + *video_frame_out = compositor->GetCurrentFrame(); event->Signal(); } diff --git a/media/media.gyp b/media/media.gyp index 02d425b..758166b 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -334,8 +334,6 @@ 'base/moving_average.h', 'base/multi_channel_resampler.cc', 'base/multi_channel_resampler.h', - 'base/null_video_sink.cc', - 'base/null_video_sink.h', 'base/pipeline.cc', 'base/pipeline.h', 'base/pipeline_status.h', @@ -1506,6 +1504,8 @@ 'base/mock_demuxer_host.h', 'base/mock_filters.cc', 'base/mock_filters.h', + 'base/null_video_sink.cc', + 'base/null_video_sink.h', 'base/test_data_util.cc', 'base/test_data_util.h', 'base/test_helpers.cc', diff --git a/media/renderers/video_renderer_impl.cc b/media/renderers/video_renderer_impl.cc index 558c8e6..f493f45 100644 --- a/media/renderers/video_renderer_impl.cc +++ b/media/renderers/video_renderer_impl.cc @@ -22,10 +22,6 @@ namespace media { -// Wait a quarter of a second for Render() callbacks before starting background -// rendering to keep the decode pump moving. -const int kBackgroundRenderingTimeoutMs = 250; - VideoRendererImpl::VideoRendererImpl( const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, VideoRendererSink* sink, @@ -53,12 +49,9 @@ VideoRendererImpl::VideoRendererImpl( frames_dropped_(0), is_shutting_down_(false), tick_clock_(new base::DefaultTickClock()), - is_background_rendering_(false), - should_use_background_renderering_(true), + was_background_rendering_(false), time_progressing_(false), render_first_frame_and_stop_(false), - background_rendering_timeout_( - base::TimeDelta::FromMilliseconds(kBackgroundRenderingTimeoutMs)), weak_factory_(this) { } @@ -149,7 +142,7 @@ void VideoRendererImpl::Initialize( DCHECK(!wall_clock_time_cb.is_null()); DCHECK_EQ(kUninitialized, state_); DCHECK(!render_first_frame_and_stop_); - DCHECK(!is_background_rendering_); + DCHECK(!was_background_rendering_); DCHECK(!time_progressing_); low_delay_ = (stream->liveness() == DemuxerStream::LIVENESS_LIVE); @@ -175,11 +168,11 @@ void VideoRendererImpl::Initialize( scoped_refptr<VideoFrame> VideoRendererImpl::Render( base::TimeTicks deadline_min, - base::TimeTicks deadline_max) { + base::TimeTicks deadline_max, + bool background_rendering) { base::AutoLock auto_lock(lock_); DCHECK(use_new_video_renderering_path_); DCHECK_EQ(state_, kPlaying); - last_render_time_ = tick_clock_->NowTicks(); size_t frames_dropped = 0; scoped_refptr<VideoFrame> result = @@ -189,24 +182,31 @@ scoped_refptr<VideoFrame> VideoRendererImpl::Render( // we've had a proper startup sequence. DCHECK(result); - // See if it's time to fire the ended callback. + // Declare HAVE_NOTHING if we reach a state where we can't progress playback + // any further. We don't want to do this if we've already done so, reached + // end of stream, or have frames available. We also don't want to do this in + // background rendering mode unless this isn't the first background render + // tick and we haven't seen any decoded frames since the last one. const size_t effective_frames = MaybeFireEndedCallback(); - - // Declare HAVE_NOTHING if we have no more effective frames. - if (!effective_frames && !rendered_end_of_stream_ && - buffering_state_ == BUFFERING_HAVE_ENOUGH) { - buffering_state_ = BUFFERING_HAVE_NOTHING; + if (buffering_state_ == BUFFERING_HAVE_ENOUGH && !received_end_of_stream_ && + !effective_frames && (!background_rendering || + (!frames_decoded_ && was_background_rendering_))) { + // Do not set |buffering_state_| here as the lock in FrameReady() may be + // held already and it fire the state changes in the wrong order. task_runner_->PostTask( - FROM_HERE, base::Bind(buffering_state_cb_, BUFFERING_HAVE_NOTHING)); + FROM_HERE, base::Bind(&VideoRendererImpl::TransitionToHaveNothing, + weak_factory_.GetWeakPtr())); } - // As we resume from background rendering, don't count the initial batch of + // We don't count dropped frames in the background to avoid skewing the count + // and impacting JavaScript visible metrics used by web developers. + // + // Just after resuming from background rendering, we also don't count the // dropped frames since they are likely just dropped due to being too old. - if (!is_background_rendering_) { + if (!background_rendering && !was_background_rendering_) frames_dropped_ += frames_dropped; - UpdateStatsAndWait_Locked(base::TimeDelta()); - } - is_background_rendering_ = false; + UpdateStatsAndWait_Locked(base::TimeDelta()); + was_background_rendering_ = background_rendering; // After painting the first frame, if playback hasn't started, we request that // the sink be stopped. OnTimeStateChanged() will clear this flag if time @@ -232,13 +232,6 @@ void VideoRendererImpl::OnFrameDropped() { algorithm_->OnLastFrameDropped(); } -void VideoRendererImpl::SetBackgroundRenderingForTesting( - bool enabled, - base::TimeDelta timeout) { - should_use_background_renderering_ = enabled; - background_rendering_timeout_ = timeout; -} - void VideoRendererImpl::CreateVideoThread() { // This may fail and cause a crash if there are too many threads created in // the current process. See http://crbug.com/443291 @@ -468,8 +461,8 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, received_end_of_stream_ = true; // See if we can fire EOS immediately instead of waiting for Render() or - // BackgroundRender() to tick. We also want to fire EOS if we have no - // frames and received EOS. + // to tick. We also want to fire EOS if we have no frames and received + // EOS. if (use_new_video_renderering_path_ && (time_progressing_ || !algorithm_->frames_queued())) { MaybeFireEndedCallback(); @@ -496,12 +489,12 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, } } - // BackgroundRender may not be ticking fast enough by itself to remove - // expired frames, so give it a boost here by ensuring we don't exit the - // decoding cycle too early. - if (is_background_rendering_) { + // Background rendering updates may not be ticking fast enough by itself to + // remove expired frames, so give it a boost here by ensuring we don't exit + // the decoding cycle too early. + if (was_background_rendering_) { DCHECK(use_new_video_renderering_path_); - BackgroundRender_Locked(); + algorithm_->RemoveExpiredFrames(tick_clock_->NowTicks()); } // Always request more decoded video if we have capacity. This serves two @@ -528,6 +521,11 @@ bool VideoRendererImpl::HaveEnoughData_Locked() { if (HaveReachedBufferingCap()) return true; + if (use_new_video_renderering_path_ && was_background_rendering_ && + frames_decoded_) { + return true; + } + if (!low_delay_) return false; @@ -550,6 +548,17 @@ void VideoRendererImpl::TransitionToHaveEnough_Locked() { buffering_state_cb_.Run(BUFFERING_HAVE_ENOUGH); } +void VideoRendererImpl::TransitionToHaveNothing() { + DCHECK(task_runner_->BelongsToCurrentThread()); + + base::AutoLock auto_lock(lock_); + if (buffering_state_ != BUFFERING_HAVE_ENOUGH || HaveEnoughData_Locked()) + return; + + buffering_state_ = BUFFERING_HAVE_NOTHING; + buffering_state_cb_.Run(BUFFERING_HAVE_NOTHING); +} + void VideoRendererImpl::AddReadyFrame_Locked( const scoped_refptr<VideoFrame>& frame) { DCHECK(task_runner_->BelongsToCurrentThread()); @@ -649,67 +658,6 @@ void VideoRendererImpl::MaybeStopSinkAfterFirstPaint() { StopSink(); } -void VideoRendererImpl::StartBackgroundRenderTimer() { - DCHECK(use_new_video_renderering_path_); - if (!drop_frames_ || !should_use_background_renderering_) - return; - - // |last_render_time_| and |background_rendering_timer_| work in conjunction - // to ensure we don't exceed |kBackgroundRenderingTimeoutMs| between calls to - // Render(). BackgroundRender() will tick repeatedly and verify that Render() - // has fired within the allotted time, by checking |last_render_time_|. - last_render_time_ = tick_clock_->NowTicks(); - background_rendering_timer_.Start( - FROM_HERE, background_rendering_timeout_, - base::Bind(&VideoRendererImpl::BackgroundRender, - weak_factory_.GetWeakPtr())); -} - -void VideoRendererImpl::BackgroundRender() { - base::AutoLock auto_lock(lock_); - BackgroundRender_Locked(); -} - -void VideoRendererImpl::BackgroundRender_Locked() { - DCHECK(task_runner_->BelongsToCurrentThread()); - DCHECK_EQ(state_, kPlaying); - lock_.AssertAcquired(); - - // If a Render() call never occurs after starting playback for the first frame - // we need to carry out the duties of Render() and stop the sink. We don't - // call MaybeStopSinkAfterFirstPaint() since it may need to mutate |sink_|, - // which can't be done under lock. - if (render_first_frame_and_stop_) { - task_runner_->PostTask( - FROM_HERE, base::Bind(&VideoRendererImpl::MaybeStopSinkAfterFirstPaint, - weak_factory_.GetWeakPtr())); - - // MaybeStopSinkAfterFirstPaint isn't going to stop the sink if time is - // currently progressing, so only bail out if necessary. - if (!time_progressing_) - return; - } - - // Do nothing if Render() calls are progressing normally. - if (tick_clock_->NowTicks() - last_render_time_ < - background_rendering_timeout_) { - return; - } - - // First clear as many expired frames as we can. - algorithm_->RemoveExpiredFrames(tick_clock_->NowTicks()); - is_background_rendering_ = true; - - // See if we've run out of frames and need to fire the ended callback. - MaybeFireEndedCallback(); - if (rendered_end_of_stream_) - return; - - // Start queuing new frames and scheduled this process again otherwise. - AttemptRead_Locked(); - UpdateStatsAndWait_Locked(base::TimeDelta()); -} - bool VideoRendererImpl::HaveReachedBufferingCap() { DCHECK(task_runner_->BelongsToCurrentThread()); if (use_new_video_renderering_path_) { @@ -729,15 +677,14 @@ void VideoRendererImpl::StartSink() { DCHECK(task_runner_->BelongsToCurrentThread()); sink_->Start(this); sink_started_ = true; - StartBackgroundRenderTimer(); + was_background_rendering_ = false; } void VideoRendererImpl::StopSink() { DCHECK(task_runner_->BelongsToCurrentThread()); sink_->Stop(); sink_started_ = false; - background_rendering_timer_.Stop(); - is_background_rendering_ = false; + was_background_rendering_ = false; } size_t VideoRendererImpl::MaybeFireEndedCallback() { diff --git a/media/renderers/video_renderer_impl.h b/media/renderers/video_renderer_impl.h index 52a15fd..43ffd86 100644 --- a/media/renderers/video_renderer_impl.h +++ b/media/renderers/video_renderer_impl.h @@ -78,18 +78,14 @@ class MEDIA_EXPORT VideoRendererImpl // VideoRendererSink::RenderCallback implementation. scoped_refptr<VideoFrame> Render(base::TimeTicks deadline_min, - base::TimeTicks deadline_max) override; + base::TimeTicks deadline_max, + bool background_rendering) override; void OnFrameDropped() override; void enable_new_video_renderer_for_testing() { use_new_video_renderering_path_ = true; } - // Enables or disables background rendering. If |enabled|, |timeout| is the - // amount of time to wait after the last Render() call before starting the - // background rendering mode. - void SetBackgroundRenderingForTesting(bool enabled, base::TimeDelta timeout); - private: // Creates a dedicated |thread_| for video rendering. void CreateVideoThread(); @@ -128,6 +124,7 @@ class MEDIA_EXPORT VideoRendererImpl // Note that having enough data may be due to reaching end of stream. bool HaveEnoughData_Locked(); void TransitionToHaveEnough_Locked(); + void TransitionToHaveNothing(); // Runs |statistics_cb_| with |frames_decoded_| and |frames_dropped_|, resets // them to 0, and then waits on |frame_available_| for up to the @@ -138,15 +135,6 @@ class MEDIA_EXPORT VideoRendererImpl // false it Stop() on |sink_|. void MaybeStopSinkAfterFirstPaint(); - // Resets and primes the |background_rendering_timer_|, when the timer fires - // it calls the BackgroundRender() method below. - void StartBackgroundRenderTimer(); - - // Called by |background_rendering_timer_| when enough time elapses where we - // haven't seen a Render() call. - void BackgroundRender(); - void BackgroundRender_Locked(); - // Returns true if there is no more room for additional buffered frames. bool HaveReachedBufferingCap(); @@ -183,7 +171,7 @@ class MEDIA_EXPORT VideoRendererImpl bool low_delay_; // Queue of incoming frames yet to be painted. - typedef std::deque<scoped_refptr<VideoFrame> > VideoFrameQueue; + typedef std::deque<scoped_refptr<VideoFrame>> VideoFrameQueue; VideoFrameQueue ready_frames_; // Keeps track of whether we received the end of stream buffer and finished @@ -269,17 +257,10 @@ class MEDIA_EXPORT VideoRendererImpl // timing related information. scoped_ptr<VideoRendererAlgorithm> algorithm_; - // Indicates that Render() callbacks from |sink_| have timed out, so we've - // entered a background rendering mode where dropped frames are not counted. - bool is_background_rendering_; - - // Allows tests to disable the background rendering task. - bool should_use_background_renderering_; - - // Manages expiration of stale video frames if Render() callbacks timeout. Do - // not access from the thread Render() is called on. Must only be used on - // |task_runner_|. - base::RepeatingTimer<VideoRendererImpl> background_rendering_timer_; + // Indicates that Render() was called with |background_rendering| set to true, + // so we've entered a background rendering mode where dropped frames are not + // counted. + bool was_background_rendering_; // Indicates whether or not media time is currently progressing or not. bool time_progressing_; @@ -288,13 +269,6 @@ class MEDIA_EXPORT VideoRendererImpl // that the sink be stopped. bool render_first_frame_and_stop_; - // The time to wait for Render() before firing BackgroundRender(). - base::TimeDelta background_rendering_timeout_; - - // Updated on every Render() call, checked by |background_rendering_timer_| to - // determine if background rendering should start. - base::TimeTicks last_render_time_; - // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<VideoRendererImpl> weak_factory_; diff --git a/media/renderers/video_renderer_impl_unittest.cc b/media/renderers/video_renderer_impl_unittest.cc index 573ff33..f0df681 100644 --- a/media/renderers/video_renderer_impl_unittest.cc +++ b/media/renderers/video_renderer_impl_unittest.cc @@ -36,9 +36,6 @@ using ::testing::StrictMock; namespace media { -// Threshold for Render() callbacks used for testing; should not be zero. -static const int kTestBackgroundRenderTimeoutMs = 25; - ACTION_P(RunClosure, closure) { closure.Run(); } @@ -72,9 +69,6 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> { renderer_->SetTickClockForTesting(scoped_ptr<base::TickClock>(tick_clock_)); null_video_sink_->set_tick_clock_for_testing(tick_clock_); - // Disable background rendering for tests by default. - renderer_->SetBackgroundRenderingForTesting(false, base::TimeDelta()); - // Start wallclock time at a non-zero value. AdvanceWallclockTimeInMs(12345); @@ -152,11 +146,6 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> { message_loop_.RunUntilIdle(); } - void SuspendRenderCallbacks() { - null_video_sink_->PauseRenderCallbacks(base::TimeTicks() + - base::TimeDelta::Max()); - } - // Parses a string representation of video frames and generates corresponding // VideoFrame objects in |decode_results_|. // @@ -300,6 +289,8 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> { // Must be destroyed before |renderer_| since they share |tick_clock_|. scoped_ptr<NullVideoSink> null_video_sink_; + PipelineStatistics last_pipeline_statistics_; + private: base::TimeTicks GetWallClockTime(base::TimeDelta time) { base::AutoLock l(lock_); @@ -333,7 +324,9 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> { message_loop_.PostTask(FROM_HERE, callback); } - void OnStatisticsUpdate(const PipelineStatistics& stats) {} + void OnStatisticsUpdate(const PipelineStatistics& stats) { + last_pipeline_statistics_ = stats; + } MOCK_METHOD0(OnWaitingForDecryptionKey, void(void)); @@ -588,34 +581,30 @@ TEST_P(VideoRendererImplTest, Underflow) { Destroy(); } -// Tests the case where the video started in the background and never received -// any Render() calls and time never started progressing (so the sink should be -// stopped immediately). -TEST_P(VideoRendererImplTest, BackgroundRenderingStopsAfterFirstFrame) { +// Verifies that the sink is stopped after rendering the first frame if +// playback hasn't started. +TEST_P(VideoRendererImplTest, RenderingStopsAfterFirstFrame) { // This test is only for the new rendering path. if (!GetParam()) return; - // By default, tests disable background rendering, so enable it now and give - // a short, but non-zero, timeout. - renderer_->SetBackgroundRenderingForTesting( - true, base::TimeDelta::FromMilliseconds(kTestBackgroundRenderTimeoutMs)); - InitializeWithLowDelay(true); QueueFrames("0"); + EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(0))); EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); - // Pause callbacks forever, but don't start the sink. Ended should not fire. - SuspendRenderCallbacks(); - StartPlayingFrom(0); - EXPECT_TRUE(IsReadPending()); - SatisfyPendingReadWithEndOfStream(); - { SCOPED_TRACE("Waiting for sink to stop."); WaitableMessageLoopEvent event; + + null_video_sink_->set_background_render(true); null_video_sink_->set_stop_cb(event.GetClosure()); + StartPlayingFrom(0); + + EXPECT_TRUE(IsReadPending()); + SatisfyPendingReadWithEndOfStream(); + event.RunAndWait(); } @@ -623,87 +612,50 @@ TEST_P(VideoRendererImplTest, BackgroundRenderingStopsAfterFirstFrame) { Destroy(); } -// Tests the case where the video started in the background and never received -// any Render() calls, but time is progressing. -TEST_P(VideoRendererImplTest, BackgroundRenderingNeverStarted) { +// Verifies that the sink is stopped after rendering the first frame if +// playback ha started. +TEST_P(VideoRendererImplTest, RenderingStopsAfterOneFrameWithEOS) { // This test is only for the new rendering path. if (!GetParam()) return; - // By default, tests disable background rendering, so enable it now and give - // a short, but non-zero, timeout. - renderer_->SetBackgroundRenderingForTesting( - true, base::TimeDelta::FromMilliseconds(kTestBackgroundRenderTimeoutMs)); - - Initialize(); - QueueFrames("0 10 20 30"); + InitializeWithLowDelay(true); + QueueFrames("0"); + EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(0))); EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); - // Start the sink and pause callbacks forever. - renderer_->OnTimeStateChanged(true); - SuspendRenderCallbacks(); - StartPlayingFrom(0); - AdvanceTimeInMs(41); - AdvanceWallclockTimeInMs(kTestBackgroundRenderTimeoutMs); - - // Eventually background rendering should request new buffers and at that - // point fire the ended event if rendering has completed. - WaitForPendingRead(); - SatisfyPendingReadWithEndOfStream(); - WaitForEnded(); - Destroy(); -} - -// Tests the case where the video started in the background and never received -// any Render() calls, but time is progressing and there's only a single frame -// in the video. -TEST_P(VideoRendererImplTest, BackgroundRenderingNeverStartedSingleFrame) { - // This test is only for the new rendering path. - if (!GetParam()) - return; - - // By default, tests disable background rendering, so enable it now and give - // a short, but non-zero, timeout. - renderer_->SetBackgroundRenderingForTesting( - true, base::TimeDelta::FromMilliseconds(kTestBackgroundRenderTimeoutMs)); - - Initialize(); - QueueFrames("0"); + { + SCOPED_TRACE("Waiting for sink to stop."); + WaitableMessageLoopEvent event; - EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + null_video_sink_->set_stop_cb(event.GetClosure()); + StartPlayingFrom(0); + renderer_->OnTimeStateChanged(true); - // Start the sink and pause callbacks forever. - renderer_->OnTimeStateChanged(true); - SuspendRenderCallbacks(); - StartPlayingFrom(0); + EXPECT_TRUE(IsReadPending()); + SatisfyPendingReadWithEndOfStream(); - AdvanceWallclockTimeInMs(kTestBackgroundRenderTimeoutMs); + event.RunAndWait(); + } - // Eventually background rendering should request new buffers and at that - // point fire the ended event if rendering has completed. - WaitForPendingRead(); - SatisfyPendingReadWithEndOfStream(); WaitForEnded(); Destroy(); } // Tests the case where the video started and received a single Render() call, // then the video was put into the background. -TEST_P(VideoRendererImplTest, BackgroundRenderingRenderStartedThenStopped) { +TEST_P(VideoRendererImplTest, RenderingStartedThenStopped) { // This test is only for the new rendering path. if (!GetParam()) return; - // By default, tests disable background rendering, so enable it now and give - // a short, but non-zero, timeout. - renderer_->SetBackgroundRenderingForTesting( - true, base::TimeDelta::FromMilliseconds(kTestBackgroundRenderTimeoutMs)); - Initialize(); QueueFrames("0 10 20 30"); - // Start the sink and wait for the first callback. + // Start the sink and wait for the first callback. Set statistics to a non + // zero value, once we have some decoded frames they should be overwritten. + last_pipeline_statistics_.video_frames_dropped = 1; { WaitableMessageLoopEvent event; EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(0))); @@ -712,19 +664,26 @@ TEST_P(VideoRendererImplTest, BackgroundRenderingRenderStartedThenStopped) { StartPlayingFrom(0); event.RunAndWait(); Mock::VerifyAndClearExpectations(&mock_cb_); + EXPECT_EQ(0u, last_pipeline_statistics_.video_frames_dropped); } renderer_->OnTimeStateChanged(true); - // Suspend all future callbacks and synthetically advance the media time. - SuspendRenderCallbacks(); + // Suspend all future callbacks and synthetically advance the media time, + // because this is a background render, we won't underflow by waiting until + // a pending read is ready. + null_video_sink_->set_background_render(true); AdvanceTimeInMs(41); - AdvanceWallclockTimeInMs(kTestBackgroundRenderTimeoutMs); - - // Eventually background rendering should request new buffers and at that - // point fire the ended event if rendering has completed. + EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(30))); WaitForPendingRead(); SatisfyPendingReadWithEndOfStream(); + + // If this wasn't background rendering mode, this would result in two frames + // being dropped, but since we set background render to true, none should be + // reported + EXPECT_EQ(0u, last_pipeline_statistics_.video_frames_dropped); + EXPECT_EQ(4u, last_pipeline_statistics_.video_frames_decoded); + WaitForEnded(); Destroy(); } |