diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-16 02:03:43 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-16 02:03:43 +0000 |
commit | 5981c66c7b9f8d84471f9f44d3b8a88a5fdd7c2c (patch) | |
tree | 0c9934080de2e90972b07f7b7abe18f95e936dcf /media | |
parent | 21cef747bba35a718dedc88106a7664df89f67b2 (diff) | |
download | chromium_src-5981c66c7b9f8d84471f9f44d3b8a88a5fdd7c2c.zip chromium_src-5981c66c7b9f8d84471f9f44d3b8a88a5fdd7c2c.tar.gz chromium_src-5981c66c7b9f8d84471f9f44d3b8a88a5fdd7c2c.tar.bz2 |
Introduce VideoFrameScheduler{Impl,Proxy}.
VideoFrameScheduler defines an interface for scheduling video frames to be displayed at specific a wall clock time. A single-threaded implementation that uses delayed tasks on a message loop is provided as well as a thread-safe proxy for coordinating scheduling between threads.
BUG=110814
Review URL: https://codereview.chromium.org/237093007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@264066 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/video_frame_scheduler.h | 43 | ||||
-rw-r--r-- | media/filters/video_frame_scheduler_impl.cc | 108 | ||||
-rw-r--r-- | media/filters/video_frame_scheduler_impl.h | 74 | ||||
-rw-r--r-- | media/filters/video_frame_scheduler_impl_unittest.cc | 148 | ||||
-rw-r--r-- | media/filters/video_frame_scheduler_proxy.cc | 47 | ||||
-rw-r--r-- | media/filters/video_frame_scheduler_proxy.h | 51 | ||||
-rw-r--r-- | media/media.gyp | 6 |
7 files changed, 477 insertions, 0 deletions
diff --git a/media/filters/video_frame_scheduler.h b/media/filters/video_frame_scheduler.h new file mode 100644 index 0000000..6ccebe7 --- /dev/null +++ b/media/filters/video_frame_scheduler.h @@ -0,0 +1,43 @@ +// Copyright 2014 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. + +#ifndef MEDIA_FILTERS_VIDEO_FRAME_SCHEDULER_H_ +#define MEDIA_FILTERS_VIDEO_FRAME_SCHEDULER_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "media/base/media_export.h" + +namespace media { + +class VideoFrame; + +// Defines an abstract video frame scheduler that is capable of managing the +// display of video frames at explicit times. +class MEDIA_EXPORT VideoFrameScheduler { + public: + VideoFrameScheduler() {} + virtual ~VideoFrameScheduler() {} + + enum Reason { + DISPLAYED, // Frame was displayed. + DROPPED, // Frame was dropped. + RESET, // Scheduler was reset before frame was scheduled for display. + }; + typedef base::Callback<void(const scoped_refptr<VideoFrame>&, Reason)> DoneCB; + + // Schedule |frame| to be displayed at |wall_ticks|, firing |done_cb| when + // the scheduler has finished with the frame. + virtual void ScheduleVideoFrame(const scoped_refptr<VideoFrame>& frame, + base::TimeTicks wall_ticks, + const DoneCB& done_cb) = 0; + + // Causes the scheduler to release all previously scheduled frames. Frames + // will be returned as RESET. + virtual void Reset() = 0; +}; + +} // namespace media + +#endif // MEDIA_FILTERS_VIDEO_FRAME_SCHEDULER_H_ diff --git a/media/filters/video_frame_scheduler_impl.cc b/media/filters/video_frame_scheduler_impl.cc new file mode 100644 index 0000000..a505164 --- /dev/null +++ b/media/filters/video_frame_scheduler_impl.cc @@ -0,0 +1,108 @@ +// Copyright 2014 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 "media/filters/video_frame_scheduler_impl.h" + +#include <list> + +#include "base/single_thread_task_runner.h" +#include "base/time/default_tick_clock.h" +#include "media/base/video_frame.h" + +namespace media { + +VideoFrameSchedulerImpl::VideoFrameSchedulerImpl( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const DisplayCB& display_cb) + : task_runner_(task_runner), + display_cb_(display_cb), + tick_clock_(new base::DefaultTickClock()) { +} + +VideoFrameSchedulerImpl::~VideoFrameSchedulerImpl() { +} + +void VideoFrameSchedulerImpl::ScheduleVideoFrame( + const scoped_refptr<VideoFrame>& frame, + base::TimeTicks wall_ticks, + const DoneCB& done_cb) { + DCHECK(task_runner_->BelongsToCurrentThread()); + DCHECK(!frame->end_of_stream()); + pending_frames_.push(PendingFrame(frame, wall_ticks, done_cb)); + ResetTimerIfNecessary(); +} + +void VideoFrameSchedulerImpl::Reset() { + DCHECK(task_runner_->BelongsToCurrentThread()); + while (!pending_frames_.empty()) { + pending_frames_.top().done_cb.Run(pending_frames_.top().frame, RESET); + pending_frames_.pop(); + } +} + +void VideoFrameSchedulerImpl::SetTickClockForTesting( + scoped_ptr<base::TickClock> tick_clock) { + tick_clock_.swap(tick_clock); +} + +void VideoFrameSchedulerImpl::ResetTimerIfNecessary() { + if (pending_frames_.empty()) { + DCHECK(!timer_.IsRunning()); + return; + } + + // Negative times will schedule the callback to run immediately. + timer_.Stop(); + timer_.Start(FROM_HERE, + pending_frames_.top().wall_ticks - tick_clock_->NowTicks(), + base::Bind(&VideoFrameSchedulerImpl::OnTimerFired, + base::Unretained(this))); +} + +void VideoFrameSchedulerImpl::OnTimerFired() { + base::TimeTicks now = tick_clock_->NowTicks(); + + // Move all frames that have reached their deadline into a separate queue. + std::list<PendingFrame> expired_frames; + while (!pending_frames_.empty() && pending_frames_.top().wall_ticks <= now) { + expired_frames.push_back(pending_frames_.top()); + pending_frames_.pop(); + } + + // Signal that all frames except for the last one as dropped. + while (expired_frames.size() > 1) { + expired_frames.front().done_cb.Run(expired_frames.front().frame, DROPPED); + expired_frames.pop_front(); + } + + // Display the last expired frame. + if (!expired_frames.empty()) { + display_cb_.Run(expired_frames.front().frame); + expired_frames.front().done_cb.Run(expired_frames.front().frame, DISPLAYED); + expired_frames.pop_front(); + } + + ResetTimerIfNecessary(); +} + +VideoFrameSchedulerImpl::PendingFrame::PendingFrame( + const scoped_refptr<VideoFrame>& frame, + base::TimeTicks wall_ticks, + const DoneCB& done_cb) + : frame(frame), wall_ticks(wall_ticks), done_cb(done_cb) { +} + +VideoFrameSchedulerImpl::PendingFrame::~PendingFrame() { +} + +bool VideoFrameSchedulerImpl::PendingFrame::operator<( + const PendingFrame& other) const { + // Flip the comparison as std::priority_queue<T>::top() returns the largest + // element. + // + // Assume video frames with identical timestamps contain identical content. + return wall_ticks > other.wall_ticks; +} + +} // namespace media diff --git a/media/filters/video_frame_scheduler_impl.h b/media/filters/video_frame_scheduler_impl.h new file mode 100644 index 0000000..f6bc78d --- /dev/null +++ b/media/filters/video_frame_scheduler_impl.h @@ -0,0 +1,74 @@ +// Copyright 2014 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. + +#ifndef MEDIA_FILTERS_VIDEO_FRAME_SCHEDULER_IMPL_H_ +#define MEDIA_FILTERS_VIDEO_FRAME_SCHEDULER_IMPL_H_ + +#include <queue> + +#include "base/memory/ref_counted.h" +#include "base/timer/timer.h" +#include "media/filters/video_frame_scheduler.h" + +namespace base { +class SingleThreadTaskRunner; +class TickClock; +} + +namespace media { + +// A scheduler that uses delayed tasks on a task runner for timing the display +// of video frames. +// +// Single threaded. Calls must be on |task_runner|. +class MEDIA_EXPORT VideoFrameSchedulerImpl : public VideoFrameScheduler { + public: + typedef base::Callback<void(const scoped_refptr<VideoFrame>&)> DisplayCB; + + // |task_runner| is used for scheduling the delayed tasks. + // |display_cb| is run when a frame is to be displayed. + VideoFrameSchedulerImpl( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const DisplayCB& display_cb); + virtual ~VideoFrameSchedulerImpl(); + + // VideoFrameScheduler implementation. + virtual void ScheduleVideoFrame(const scoped_refptr<VideoFrame>& frame, + base::TimeTicks wall_ticks, + const DoneCB& done_cb) OVERRIDE; + virtual void Reset() OVERRIDE; + + void SetTickClockForTesting(scoped_ptr<base::TickClock> tick_clock); + + private: + void ResetTimerIfNecessary(); + void OnTimerFired(); + + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + DisplayCB display_cb_; + scoped_ptr<base::TickClock> tick_clock_; + base::OneShotTimer<VideoFrameScheduler> timer_; + + struct PendingFrame { + PendingFrame(const scoped_refptr<VideoFrame>& frame, + base::TimeTicks wall_ticks, + const DoneCB& done_cb); + ~PendingFrame(); + + // For use with std::priority_queue<T>. + bool operator<(const PendingFrame& other) const; + + scoped_refptr<VideoFrame> frame; + base::TimeTicks wall_ticks; + DoneCB done_cb; + }; + typedef std::priority_queue<PendingFrame> PendingFrameQueue; + PendingFrameQueue pending_frames_; + + DISALLOW_COPY_AND_ASSIGN(VideoFrameSchedulerImpl); +}; + +} // namespace media + +#endif // MEDIA_FILTERS_VIDEO_FRAME_SCHEDULER_IMPL_H_ diff --git a/media/filters/video_frame_scheduler_impl_unittest.cc b/media/filters/video_frame_scheduler_impl_unittest.cc new file mode 100644 index 0000000..b423776 --- /dev/null +++ b/media/filters/video_frame_scheduler_impl_unittest.cc @@ -0,0 +1,148 @@ +// Copyright 2014 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 "base/message_loop/message_loop.h" +#include "base/test/simple_test_tick_clock.h" +#include "media/base/test_helpers.h" +#include "media/base/video_frame.h" +#include "media/filters/video_frame_scheduler_impl.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +// NOTE: millisecond-level resolution is used for times as real delayed tasks +// are posted. Don't use large values if you want to keep tests running fast. +class VideoFrameSchedulerImplTest : public testing::Test { + public: + VideoFrameSchedulerImplTest() + : scheduler_(message_loop_.message_loop_proxy(), + base::Bind(&VideoFrameSchedulerImplTest::OnDisplay, + base::Unretained(this))), + tick_clock_(new base::SimpleTestTickClock()) { + scheduler_.SetTickClockForTesting(scoped_ptr<base::TickClock>(tick_clock_)); + } + + virtual ~VideoFrameSchedulerImplTest() {} + + MOCK_METHOD1(OnDisplay, void(const scoped_refptr<VideoFrame>&)); + MOCK_METHOD2(OnFrameDone, + void(const scoped_refptr<VideoFrame>&, + VideoFrameScheduler::Reason)); + + void Schedule(const scoped_refptr<VideoFrame>& frame, int64 target_ms) { + scheduler_.ScheduleVideoFrame( + frame, + base::TimeTicks() + base::TimeDelta::FromMilliseconds(target_ms), + base::Bind(&VideoFrameSchedulerImplTest::OnFrameDone, + base::Unretained(this))); + } + + void RunUntilTimeHasElapsed(int64 ms) { + WaitableMessageLoopEvent waiter; + message_loop_.PostDelayedTask( + FROM_HERE, waiter.GetClosure(), base::TimeDelta::FromMilliseconds(ms)); + waiter.RunAndWait(); + } + + void AdvanceTime(int64 ms) { + tick_clock_->Advance(base::TimeDelta::FromMilliseconds(ms)); + } + + void Reset() { + scheduler_.Reset(); + } + + private: + base::MessageLoop message_loop_; + VideoFrameSchedulerImpl scheduler_; + base::SimpleTestTickClock* tick_clock_; // Owned by |scheduler_|. + + DISALLOW_COPY_AND_ASSIGN(VideoFrameSchedulerImplTest); +}; + +TEST_F(VideoFrameSchedulerImplTest, ImmediateDisplay) { + scoped_refptr<VideoFrame> frame = + VideoFrame::CreateBlackFrame(gfx::Size(8, 8)); + Schedule(frame, 0); + + EXPECT_CALL(*this, OnDisplay(frame)); + EXPECT_CALL(*this, OnFrameDone(frame, VideoFrameScheduler::DISPLAYED)); + RunUntilTimeHasElapsed(0); +} + +TEST_F(VideoFrameSchedulerImplTest, EventualDisplay) { + scoped_refptr<VideoFrame> frame = + VideoFrame::CreateBlackFrame(gfx::Size(8, 8)); + Schedule(frame, 10); + + // Nothing should happen. + RunUntilTimeHasElapsed(10); + + // Now we should get the frame. + EXPECT_CALL(*this, OnDisplay(frame)); + EXPECT_CALL(*this, OnFrameDone(frame, VideoFrameScheduler::DISPLAYED)); + AdvanceTime(10); + RunUntilTimeHasElapsed(10); +} + +TEST_F(VideoFrameSchedulerImplTest, DroppedFrame) { + scoped_refptr<VideoFrame> dropped = + VideoFrame::CreateBlackFrame(gfx::Size(8, 8)); + scoped_refptr<VideoFrame> displayed = + VideoFrame::CreateBlackFrame(gfx::Size(8, 8)); + Schedule(dropped, 10); + Schedule(displayed, 20); + + // The frame past its deadline will get dropped. + EXPECT_CALL(*this, OnDisplay(displayed)); + EXPECT_CALL(*this, OnFrameDone(dropped, VideoFrameScheduler::DROPPED)); + EXPECT_CALL(*this, OnFrameDone(displayed, VideoFrameScheduler::DISPLAYED)); + AdvanceTime(20); + RunUntilTimeHasElapsed(20); +} + +TEST_F(VideoFrameSchedulerImplTest, SingleFrameLate) { + scoped_refptr<VideoFrame> frame = + VideoFrame::CreateBlackFrame(gfx::Size(8, 8)); + Schedule(frame, 10); + + // Despite frame being late it should still get displayed as it's the only + // one. + EXPECT_CALL(*this, OnDisplay(frame)); + EXPECT_CALL(*this, OnFrameDone(frame, VideoFrameScheduler::DISPLAYED)); + AdvanceTime(20); + RunUntilTimeHasElapsed(20); +} + +TEST_F(VideoFrameSchedulerImplTest, ManyFramesLate) { + scoped_refptr<VideoFrame> dropped = + VideoFrame::CreateBlackFrame(gfx::Size(8, 8)); + scoped_refptr<VideoFrame> displayed = + VideoFrame::CreateBlackFrame(gfx::Size(8, 8)); + Schedule(dropped, 10); + Schedule(displayed, 20); + + // Despite both being late, the scheduler should always displays the latest + // expired frame. + EXPECT_CALL(*this, OnDisplay(displayed)); + EXPECT_CALL(*this, OnFrameDone(dropped, VideoFrameScheduler::DROPPED)); + EXPECT_CALL(*this, OnFrameDone(displayed, VideoFrameScheduler::DISPLAYED)); + AdvanceTime(30); + RunUntilTimeHasElapsed(30); +} + +TEST_F(VideoFrameSchedulerImplTest, Reset) { + scoped_refptr<VideoFrame> frame = + VideoFrame::CreateBlackFrame(gfx::Size(8, 8)); + Schedule(frame, 10); + + // Despite being on time, frames are returned immediately. + EXPECT_CALL(*this, OnFrameDone(frame, VideoFrameScheduler::RESET)); + AdvanceTime(10); + Reset(); + RunUntilTimeHasElapsed(10); +} + +} // namespace media diff --git a/media/filters/video_frame_scheduler_proxy.cc b/media/filters/video_frame_scheduler_proxy.cc new file mode 100644 index 0000000..7499dc3 --- /dev/null +++ b/media/filters/video_frame_scheduler_proxy.cc @@ -0,0 +1,47 @@ +// Copyright 2014 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 "media/filters/video_frame_scheduler_proxy.h" + +#include "base/single_thread_task_runner.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/video_frame.h" + +namespace media { + +VideoFrameSchedulerProxy::VideoFrameSchedulerProxy( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& scheduler_runner, + VideoFrameScheduler* scheduler) + : task_runner_(task_runner), + scheduler_runner_(scheduler_runner), + scheduler_(scheduler), + weak_factory_(this) { +} + +VideoFrameSchedulerProxy::~VideoFrameSchedulerProxy() { +} + +void VideoFrameSchedulerProxy::ScheduleVideoFrame( + const scoped_refptr<VideoFrame>& frame, + base::TimeTicks wall_ticks, + const DoneCB& done_cb) { + DCHECK(task_runner_->BelongsToCurrentThread()); + scheduler_runner_->PostTask( + FROM_HERE, + base::Bind(&VideoFrameScheduler::ScheduleVideoFrame, + base::Unretained(scheduler_), + frame, + wall_ticks, + BindToCurrentLoop(done_cb))); +} + +void VideoFrameSchedulerProxy::Reset() { + DCHECK(task_runner_->BelongsToCurrentThread()); + scheduler_runner_->PostTask( + FROM_HERE, + base::Bind(&VideoFrameScheduler::Reset, base::Unretained(scheduler_))); +} + +} // namespace media diff --git a/media/filters/video_frame_scheduler_proxy.h b/media/filters/video_frame_scheduler_proxy.h new file mode 100644 index 0000000..d17fb56 --- /dev/null +++ b/media/filters/video_frame_scheduler_proxy.h @@ -0,0 +1,51 @@ +// Copyright 2014 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. + +#ifndef MEDIA_FILTERS_VIDEO_FRAME_SCHEDULER_PROXY_H_ +#define MEDIA_FILTERS_VIDEO_FRAME_SCHEDULER_PROXY_H_ + +#include "base/memory/weak_ptr.h" +#include "media/filters/video_frame_scheduler.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace media { + +// Provides a thread-safe proxy for a VideoFrameScheduler. Typical use is to +// use a real VideoFrameScheduler on the task runner responsible for graphics +// display and provide a proxy on the task runner responsible for background +// video decoding. +class MEDIA_EXPORT VideoFrameSchedulerProxy : public VideoFrameScheduler { + public: + // |task_runner| is the runner that this object will be called on. + // |scheduler_runner| is the runner that |scheduler| will be called on. + // |scheduler| must out-live the lifetime of this object. + VideoFrameSchedulerProxy( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& scheduler_runner, + VideoFrameScheduler* scheduler); + virtual ~VideoFrameSchedulerProxy(); + + // VideoFrameScheduler implementation. + virtual void ScheduleVideoFrame(const scoped_refptr<VideoFrame>& frame, + base::TimeTicks wall_ticks, + const DoneCB& done_cb) OVERRIDE; + virtual void Reset() OVERRIDE; + + private: + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + scoped_refptr<base::SingleThreadTaskRunner> scheduler_runner_; + VideoFrameScheduler* scheduler_; // Not owned. + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<VideoFrameSchedulerProxy> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(VideoFrameSchedulerProxy); +}; + +} // namespace media + +#endif // MEDIA_FILTERS_VIDEO_FRAME_SCHEDULER_PROXY_H_ diff --git a/media/media.gyp b/media/media.gyp index 3b03664..2a7d66b 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -413,6 +413,11 @@ 'filters/source_buffer_stream.h', 'filters/stream_parser_factory.cc', 'filters/stream_parser_factory.h', + 'filters/video_frame_scheduler.h', + 'filters/video_frame_scheduler_impl.cc', + 'filters/video_frame_scheduler_impl.h', + 'filters/video_frame_scheduler_proxy.cc', + 'filters/video_frame_scheduler_proxy.h', 'filters/video_renderer_impl.cc', 'filters/video_renderer_impl.h', 'filters/vpx_video_decoder.cc', @@ -1039,6 +1044,7 @@ 'filters/skcanvas_video_renderer_unittest.cc', 'filters/source_buffer_stream_unittest.cc', 'filters/video_decoder_selector_unittest.cc', + 'filters/video_frame_scheduler_impl_unittest.cc', 'filters/video_frame_stream_unittest.cc', 'filters/video_renderer_impl_unittest.cc', 'midi/midi_manager_usb_unittest.cc', |