diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-01 19:39:17 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-01 19:39:17 +0000 |
commit | 28bc175c395aafc54d5d37e5b1b876ccc2c5c94d (patch) | |
tree | 0ae0709d349ff7e5cda84dd486df197cea22c646 | |
parent | 9242805d40f7da2b289ded36cf36fec28bca952b (diff) | |
download | chromium_src-28bc175c395aafc54d5d37e5b1b876ccc2c5c94d.zip chromium_src-28bc175c395aafc54d5d37e5b1b876ccc2c5c94d.tar.gz chromium_src-28bc175c395aafc54d5d37e5b1b876ccc2c5c94d.tar.bz2 |
Update VideoRenderer API to fire changes in BufferingState.
This is the video equivalent to r280140. Pipeline is now completely in
charge of prerolling and underflow/rebuffering.
BUG=144683
Review URL: https://codereview.chromium.org/288373002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@280867 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | media/base/audio_renderer.h | 3 | ||||
-rw-r--r-- | media/base/mock_filters.h | 24 | ||||
-rw-r--r-- | media/base/pipeline.cc | 48 | ||||
-rw-r--r-- | media/base/pipeline_unittest.cc | 83 | ||||
-rw-r--r-- | media/base/video_renderer.h | 24 | ||||
-rw-r--r-- | media/filters/video_renderer_impl.cc | 156 | ||||
-rw-r--r-- | media/filters/video_renderer_impl.h | 62 | ||||
-rw-r--r-- | media/filters/video_renderer_impl_unittest.cc | 193 |
8 files changed, 230 insertions, 363 deletions
diff --git a/media/base/audio_renderer.h b/media/base/audio_renderer.h index fd98664..48ab684 100644 --- a/media/base/audio_renderer.h +++ b/media/base/audio_renderer.h @@ -22,9 +22,6 @@ class MEDIA_EXPORT AudioRenderer { // Second parameter is the maximum time value that the clock cannot exceed. typedef base::Callback<void(base::TimeDelta, base::TimeDelta)> TimeCB; - // Used to indicate changes in the buffering state of this audio renderer. - typedef base::Callback<void(BufferingState)> BufferingStateCB; - AudioRenderer(); virtual ~AudioRenderer(); diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h index 76c56ca..be5062b 100644 --- a/media/base/mock_filters.h +++ b/media/base/mock_filters.h @@ -115,18 +115,18 @@ class MockVideoRenderer : public VideoRenderer { virtual ~MockVideoRenderer(); // VideoRenderer implementation. - MOCK_METHOD9(Initialize, void(DemuxerStream* stream, - bool low_delay, - const PipelineStatusCB& init_cb, - const StatisticsCB& statistics_cb, - const TimeCB& time_cb, - const base::Closure& ended_cb, - const PipelineStatusCB& error_cb, - const TimeDeltaCB& get_time_cb, - const TimeDeltaCB& get_duration_cb)); - MOCK_METHOD1(Play, void(const base::Closure& callback)); + MOCK_METHOD10(Initialize, void(DemuxerStream* stream, + bool low_delay, + const PipelineStatusCB& init_cb, + const StatisticsCB& statistics_cb, + const TimeCB& time_cb, + const BufferingStateCB& buffering_state_cb, + const base::Closure& ended_cb, + const PipelineStatusCB& error_cb, + const TimeDeltaCB& get_time_cb, + const TimeDeltaCB& get_duration_cb)); MOCK_METHOD1(Flush, void(const base::Closure& callback)); - MOCK_METHOD2(Preroll, void(base::TimeDelta time, const PipelineStatusCB& cb)); + MOCK_METHOD1(StartPlayingFrom, void(base::TimeDelta timestamp)); MOCK_METHOD1(Stop, void(const base::Closure& callback)); MOCK_METHOD1(SetPlaybackRate, void(float playback_rate)); @@ -152,7 +152,7 @@ class MockAudioRenderer : public AudioRenderer { MOCK_METHOD1(Flush, void(const base::Closure& callback)); MOCK_METHOD1(Stop, void(const base::Closure& callback)); MOCK_METHOD1(SetPlaybackRate, void(float playback_rate)); - MOCK_METHOD1(StartPlayingFrom, void(base::TimeDelta time)); + MOCK_METHOD1(StartPlayingFrom, void(base::TimeDelta timestamp)); MOCK_METHOD1(SetVolume, void(float volume)); MOCK_METHOD0(ResumeAfterUnderflow, void()); diff --git a/media/base/pipeline.cc b/media/base/pipeline.cc index b8b18db..a81e758 100644 --- a/media/base/pipeline.cc +++ b/media/base/pipeline.cc @@ -404,22 +404,11 @@ void Pipeline::StateTransitionTask(PipelineStatus status) { if (audio_renderer_) audio_renderer_->StartPlayingFrom(start_timestamp_); + if (video_renderer_) + video_renderer_->StartPlayingFrom(start_timestamp_); PlaybackRateChangedTask(GetPlaybackRate()); VolumeChangedTask(GetVolume()); - - // We enter this state from either kInitPrerolling or kSeeking. As of now - // both those states call Preroll(), which means by time we enter this - // state we've already buffered enough data. Forcefully update the - // buffering state, which start the clock and renderers and transition - // into kPlaying state. - // - // TODO(scherkus): Remove after renderers are taught to fire buffering - // state callbacks http://crbug.com/144683 - if (video_renderer_) { - DCHECK(WaitingForEnoughData()); - BufferingStateChanged(&video_buffering_state_, BUFFERING_HAVE_ENOUGH); - } return; case kStopping: @@ -442,20 +431,7 @@ void Pipeline::DoInitialPreroll(const PipelineStatusCB& done_cb) { DCHECK(!pending_callbacks_.get()); SerialRunner::Queue bound_fns; - const base::TimeDelta seek_timestamp = base::TimeDelta(); - // Preroll renderers. - if (video_renderer_) { - bound_fns.Push(base::Bind( - &VideoRenderer::Preroll, base::Unretained(video_renderer_.get()), - seek_timestamp)); - - // TODO(scherkus): Remove after VideoRenderer is taught to fire buffering - // state callbacks http://crbug.com/144683 - bound_fns.Push(base::Bind(&VideoRenderer::Play, - base::Unretained(video_renderer_.get()))); - } - if (text_renderer_) { bound_fns.Push(base::Bind( &TextRenderer::Play, base::Unretained(text_renderer_.get()))); @@ -494,13 +470,6 @@ void Pipeline::DoSeek( if (video_renderer_) { bound_fns.Push(base::Bind( &VideoRenderer::Flush, base::Unretained(video_renderer_.get()))); - - // TODO(scherkus): Remove after VideoRenderer is taught to fire buffering - // state callbacks http://crbug.com/144683 - bound_fns.Push(base::Bind(&Pipeline::BufferingStateChanged, - base::Unretained(this), - &video_buffering_state_, - BUFFERING_HAVE_NOTHING)); } #if DCHECK_IS_ON @@ -520,17 +489,6 @@ void Pipeline::DoSeek( &Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp)); // Preroll renderers. - if (video_renderer_) { - bound_fns.Push(base::Bind( - &VideoRenderer::Preroll, base::Unretained(video_renderer_.get()), - seek_timestamp)); - - // TODO(scherkus): Remove after renderers are taught to fire buffering - // state callbacks http://crbug.com/144683 - bound_fns.Push(base::Bind(&VideoRenderer::Play, - base::Unretained(video_renderer_.get()))); - } - if (text_renderer_) { bound_fns.Push(base::Bind( &TextRenderer::Play, base::Unretained(text_renderer_.get()))); @@ -864,6 +822,8 @@ void Pipeline::InitializeVideoRenderer(const PipelineStatusCB& done_cb) { done_cb, base::Bind(&Pipeline::OnUpdateStatistics, base::Unretained(this)), base::Bind(&Pipeline::OnVideoTimeUpdate, base::Unretained(this)), + base::Bind(&Pipeline::BufferingStateChanged, base::Unretained(this), + &video_buffering_state_), base::Bind(&Pipeline::OnVideoRendererEnded, base::Unretained(this)), base::Bind(&Pipeline::SetError, base::Unretained(this)), base::Bind(&Pipeline::GetMediaTime, base::Unretained(this)), diff --git a/media/base/pipeline_unittest.cc b/media/base/pipeline_unittest.cc index d9f2f1f..0c0e474 100644 --- a/media/base/pipeline_unittest.cc +++ b/media/base/pipeline_unittest.cc @@ -170,15 +170,9 @@ class PipelineTest : public ::testing::Test { // Sets up expectations to allow the video renderer to initialize. void InitializeVideoRenderer(DemuxerStream* stream) { - EXPECT_CALL(*video_renderer_, Initialize(stream, _, _, _, _, _, _, _, _)) - .WillOnce(RunCallback<2>(PIPELINE_OK)); - EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f)); - - // Startup sequence. - EXPECT_CALL(*video_renderer_, Preroll(base::TimeDelta(), _)) - .WillOnce(RunCallback<1>(PIPELINE_OK)); - EXPECT_CALL(*video_renderer_, Play(_)) - .WillOnce(RunClosure<0>()); + EXPECT_CALL(*video_renderer_, Initialize(stream, _, _, _, _, _, _, _, _, _)) + .WillOnce(DoAll(SaveArg<5>(&video_buffering_state_cb_), + RunCallback<2>(PIPELINE_OK))); } // Sets up expectations to allow the audio renderer to initialize. @@ -212,6 +206,14 @@ class PipelineTest : public ::testing::Test { BUFFERING_HAVE_ENOUGH)); EXPECT_CALL(*audio_renderer_, StartRendering()); } + + if (video_stream_) { + EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f)); + EXPECT_CALL(*video_renderer_, StartPlayingFrom(base::TimeDelta())) + .WillOnce(SetBufferingState(&video_buffering_state_cb_, + BUFFERING_HAVE_ENOUGH)); + } + EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH)); } @@ -277,12 +279,13 @@ class PipelineTest : public ::testing::Test { if (video_stream_) { EXPECT_CALL(*video_renderer_, Flush(_)) - .WillOnce(RunClosure<0>()); - EXPECT_CALL(*video_renderer_, Preroll(seek_time, _)) - .WillOnce(RunCallback<1>(PIPELINE_OK)); + .WillOnce(DoAll(SetBufferingState(&video_buffering_state_cb_, + BUFFERING_HAVE_NOTHING), + RunClosure<0>())); + EXPECT_CALL(*video_renderer_, StartPlayingFrom(seek_time)) + .WillOnce(SetBufferingState(&video_buffering_state_cb_, + BUFFERING_HAVE_ENOUGH)); EXPECT_CALL(*video_renderer_, SetPlaybackRate(_)); - EXPECT_CALL(*video_renderer_, Play(_)) - .WillOnce(RunClosure<0>()); } // We expect a successful seek callback followed by a buffering update. @@ -337,7 +340,8 @@ class PipelineTest : public ::testing::Test { scoped_ptr<StrictMock<MockDemuxerStream> > video_stream_; scoped_ptr<FakeTextTrackStream> text_stream_; AudioRenderer::TimeCB audio_time_cb_; - AudioRenderer::BufferingStateCB audio_buffering_state_cb_; + BufferingStateCB audio_buffering_state_cb_; + BufferingStateCB video_buffering_state_cb_; VideoDecoderConfig video_decoder_config_; PipelineMetadata metadata_; @@ -859,7 +863,6 @@ class PipelineTeardownTest : public PipelineTest { kInitVideoRenderer, kFlushing, kSeeking, - kPrerolling, kPlaying, }; @@ -882,7 +885,6 @@ class PipelineTeardownTest : public PipelineTest { case kFlushing: case kSeeking: - case kPrerolling: DoInitialize(state, stop_or_error); DoSeek(state, stop_or_error); break; @@ -968,13 +970,13 @@ class PipelineTeardownTest : public PipelineTest { if (state == kInitVideoRenderer) { if (stop_or_error == kStop) { - EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _)) + EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _, _)) .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), RunCallback<2>(PIPELINE_OK))); EXPECT_CALL(callbacks_, OnStop()); } else { status = PIPELINE_ERROR_INITIALIZATION_FAILED; - EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _)) + EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _, _)) .WillOnce(RunCallback<2>(status)); } @@ -984,8 +986,9 @@ class PipelineTeardownTest : public PipelineTest { return status; } - EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _)) - .WillOnce(RunCallback<2>(PIPELINE_OK)); + EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _, _)) + .WillOnce(DoAll(SaveArg<5>(&video_buffering_state_cb_), + RunCallback<2>(PIPELINE_OK))); EXPECT_CALL(callbacks_, OnMetadata(_)); @@ -993,16 +996,14 @@ class PipelineTeardownTest : public PipelineTest { EXPECT_CALL(*audio_renderer_, StartPlayingFrom(base::TimeDelta())) .WillOnce(SetBufferingState(&audio_buffering_state_cb_, BUFFERING_HAVE_ENOUGH)); - EXPECT_CALL(*video_renderer_, Preroll(base::TimeDelta(), _)) - .WillOnce(RunCallback<1>(PIPELINE_OK)); + EXPECT_CALL(*video_renderer_, StartPlayingFrom(base::TimeDelta())) + .WillOnce(SetBufferingState(&video_buffering_state_cb_, + BUFFERING_HAVE_ENOUGH)); EXPECT_CALL(*audio_renderer_, SetPlaybackRate(0.0f)); EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f)); EXPECT_CALL(*audio_renderer_, SetVolume(1.0f)); - EXPECT_CALL(*audio_renderer_, StartRendering()); - EXPECT_CALL(*video_renderer_, Play(_)) - .WillOnce(RunClosure<0>()); if (status == PIPELINE_OK) EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH)); @@ -1059,7 +1060,10 @@ class PipelineTeardownTest : public PipelineTest { .WillOnce(DoAll(SetBufferingState(&audio_buffering_state_cb_, BUFFERING_HAVE_NOTHING), RunClosure<0>())); - EXPECT_CALL(*video_renderer_, Flush(_)).WillOnce(RunClosure<0>()); + EXPECT_CALL(*video_renderer_, Flush(_)) + .WillOnce(DoAll(SetBufferingState(&video_buffering_state_cb_, + BUFFERING_HAVE_NOTHING), + RunClosure<0>())); if (state == kSeeking) { if (stop_or_error == kStop) { @@ -1075,29 +1079,6 @@ class PipelineTeardownTest : public PipelineTest { return status; } - EXPECT_CALL(*demuxer_, Seek(_, _)) - .WillOnce(RunCallback<1>(PIPELINE_OK)); - - if (state == kPrerolling) { - if (stop_or_error == kStop) { - EXPECT_CALL(*video_renderer_, Preroll(_, _)) - .WillOnce(Stop(pipeline_.get(), stop_cb)); - } else { - status = PIPELINE_ERROR_READ; - EXPECT_CALL(*video_renderer_, Preroll(_, _)) - .WillOnce(RunCallback<1>(status)); - } - - return status; - } - - EXPECT_CALL(*audio_renderer_, StartPlayingFrom(_)); - - // Playback rate and volume are updated prior to starting. - EXPECT_CALL(*audio_renderer_, SetPlaybackRate(0.0f)); - EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f)); - EXPECT_CALL(*audio_renderer_, SetVolume(1.0f)); - NOTREACHED() << "State not supported: " << state; return status; } @@ -1145,7 +1126,6 @@ INSTANTIATE_TEARDOWN_TEST(Stop, InitAudioRenderer); INSTANTIATE_TEARDOWN_TEST(Stop, InitVideoRenderer); INSTANTIATE_TEARDOWN_TEST(Stop, Flushing); INSTANTIATE_TEARDOWN_TEST(Stop, Seeking); -INSTANTIATE_TEARDOWN_TEST(Stop, Prerolling); INSTANTIATE_TEARDOWN_TEST(Stop, Playing); INSTANTIATE_TEARDOWN_TEST(Error, InitDemuxer); @@ -1153,7 +1133,6 @@ INSTANTIATE_TEARDOWN_TEST(Error, InitAudioRenderer); INSTANTIATE_TEARDOWN_TEST(Error, InitVideoRenderer); INSTANTIATE_TEARDOWN_TEST(Error, Flushing); INSTANTIATE_TEARDOWN_TEST(Error, Seeking); -INSTANTIATE_TEARDOWN_TEST(Error, Prerolling); INSTANTIATE_TEARDOWN_TEST(Error, Playing); INSTANTIATE_TEARDOWN_TEST(ErrorAndStop, Playing); diff --git a/media/base/video_renderer.h b/media/base/video_renderer.h index b4154a0..e92eca8 100644 --- a/media/base/video_renderer.h +++ b/media/base/video_renderer.h @@ -8,6 +8,7 @@ #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/time/time.h" +#include "media/base/buffering_state.h" #include "media/base/media_export.h" #include "media/base/pipeline_status.h" @@ -36,6 +37,9 @@ class MEDIA_EXPORT VideoRenderer { // // |time_cb| is executed whenever time has advanced by way of video rendering. // + // |buffering_state_cb| is executed when video rendering has either run out of + // data or has enough data to continue playback. + // // |ended_cb| is executed when video rendering has reached the end of stream. // // |error_cb| is executed if an error was encountered. @@ -48,28 +52,24 @@ class MEDIA_EXPORT VideoRenderer { const PipelineStatusCB& init_cb, const StatisticsCB& statistics_cb, const TimeCB& time_cb, + const BufferingStateCB& buffering_state_cb, const base::Closure& ended_cb, const PipelineStatusCB& error_cb, const TimeDeltaCB& get_time_cb, const TimeDeltaCB& get_duration_cb) = 0; - // Start audio decoding and rendering at the current playback rate, executing - // |callback| when playback is underway. - virtual void Play(const base::Closure& callback) = 0; - // Discard any video data and stop reading from |stream|, executing |callback| // when completed. + // + // Clients should expect |buffering_state_cb| to be called with + // BUFFERING_HAVE_NOTHING while flushing is in progress. virtual void Flush(const base::Closure& callback) = 0; - // Start prerolling video data. If |time| equals kNoTimestamp() then all - // samples delivered to the renderer are used to complete preroll. If |time| - // does not equal kNoTimestamp(), then any samples delivered to the renderer - // with timestamps less than |time| are silently dropped and not used to - // satisfy preroll. |callback| is executed when preroll has completed. + // Starts playback by reading from |stream| and decoding and rendering video. + // |timestamp| is the media timestamp playback should start rendering from. // - // Only valid to call after a successful Initialize(), Pause(), or Flush(). - virtual void Preroll(base::TimeDelta time, - const PipelineStatusCB& callback) = 0; + // Only valid to call after a successful Initialize() or Flush(). + virtual void StartPlayingFrom(base::TimeDelta timestamp) = 0; // Stop all operations in preparation for being deleted, executing |callback| // when complete. diff --git a/media/filters/video_renderer_impl.cc b/media/filters/video_renderer_impl.cc index 736a91f..8735a0a 100644 --- a/media/filters/video_renderer_impl.cc +++ b/media/filters/video_renderer_impl.cc @@ -35,6 +35,7 @@ VideoRendererImpl::VideoRendererImpl( pending_read_(false), drop_frames_(drop_frames), playback_rate_(0), + buffering_state_(BUFFERING_HAVE_NOTHING), paint_cb_(paint_cb), last_timestamp_(kNoTimestamp()), frames_decoded_(0), @@ -49,26 +50,23 @@ VideoRendererImpl::~VideoRendererImpl() { CHECK(thread_.is_null()); } -void VideoRendererImpl::Play(const base::Closure& callback) { - DCHECK(task_runner_->BelongsToCurrentThread()); - base::AutoLock auto_lock(lock_); - DCHECK_EQ(kPrerolled, state_); - state_ = kPlaying; - callback.Run(); -} - void VideoRendererImpl::Flush(const base::Closure& callback) { DCHECK(task_runner_->BelongsToCurrentThread()); base::AutoLock auto_lock(lock_); - DCHECK_NE(state_, kUninitialized); + DCHECK_EQ(state_, kPlaying); flush_cb_ = callback; state_ = kFlushing; // This is necessary if the |video_frame_stream_| has already seen an end of // stream and needs to drain it before flushing it. ready_frames_.clear(); + if (buffering_state_ != BUFFERING_HAVE_NOTHING) { + buffering_state_ = BUFFERING_HAVE_NOTHING; + buffering_state_cb_.Run(BUFFERING_HAVE_NOTHING); + } received_end_of_stream_ = false; rendered_end_of_stream_ = false; + video_frame_stream_.Reset( base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone, weak_factory_.GetWeakPtr())); @@ -114,31 +112,16 @@ void VideoRendererImpl::SetPlaybackRate(float playback_rate) { playback_rate_ = playback_rate; } -void VideoRendererImpl::Preroll(base::TimeDelta time, - const PipelineStatusCB& cb) { +void VideoRendererImpl::StartPlayingFrom(base::TimeDelta timestamp) { DCHECK(task_runner_->BelongsToCurrentThread()); base::AutoLock auto_lock(lock_); - DCHECK(!cb.is_null()); - DCHECK(preroll_cb_.is_null()); - DCHECK(state_ == kFlushed || state_ == kPlaying) << "state_ " << state_; - - if (state_ == kFlushed) { - DCHECK(time != kNoTimestamp()); - DCHECK(!pending_read_); - DCHECK(ready_frames_.empty()); - } else { - DCHECK(time == kNoTimestamp()); - } - - state_ = kPrerolling; - preroll_cb_ = cb; - preroll_timestamp_ = time; - - if (ShouldTransitionToPrerolled_Locked()) { - TransitionToPrerolled_Locked(); - return; - } + DCHECK_EQ(state_, kFlushed); + DCHECK(!pending_read_); + DCHECK(ready_frames_.empty()); + DCHECK_EQ(buffering_state_, BUFFERING_HAVE_NOTHING); + state_ = kPlaying; + start_timestamp_ = timestamp; AttemptRead_Locked(); } @@ -147,6 +130,7 @@ void VideoRendererImpl::Initialize(DemuxerStream* stream, const PipelineStatusCB& init_cb, const StatisticsCB& statistics_cb, const TimeCB& max_time_cb, + const BufferingStateCB& buffering_state_cb, const base::Closure& ended_cb, const PipelineStatusCB& error_cb, const TimeDeltaCB& get_time_cb, @@ -158,6 +142,7 @@ void VideoRendererImpl::Initialize(DemuxerStream* stream, DCHECK(!init_cb.is_null()); DCHECK(!statistics_cb.is_null()); DCHECK(!max_time_cb.is_null()); + DCHECK(!buffering_state_cb.is_null()); DCHECK(!ended_cb.is_null()); DCHECK(!get_time_cb.is_null()); DCHECK(!get_duration_cb.is_null()); @@ -168,6 +153,7 @@ void VideoRendererImpl::Initialize(DemuxerStream* stream, init_cb_ = init_cb; statistics_cb_ = statistics_cb; max_time_cb_ = max_time_cb; + buffering_state_cb_ = buffering_state_cb; ended_cb_ = ended_cb; error_cb_ = error_cb; get_time_cb_ = get_time_cb; @@ -235,7 +221,8 @@ void VideoRendererImpl::ThreadMain() { return; // Remain idle as long as we're not playing. - if (state_ != kPlaying || playback_rate_ == 0) { + if (state_ != kPlaying || playback_rate_ == 0 || + buffering_state_ != BUFFERING_HAVE_ENOUGH) { UpdateStatsAndWait_Locked(kIdleTimeDelta); continue; } @@ -338,12 +325,6 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, PipelineStatus error = PIPELINE_ERROR_DECODE; if (status == VideoFrameStream::DECRYPT_ERROR) error = PIPELINE_ERROR_DECRYPT; - - if (!preroll_cb_.is_null()) { - base::ResetAndReturn(&preroll_cb_).Run(error); - return; - } - error_cb_.Run(error); return; } @@ -353,38 +334,28 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, if (state_ == kStopped || state_ == kFlushing) return; - if (!frame.get()) { - // Abort preroll early for a NULL frame because we won't get more frames. - // A new preroll will be requested after this one completes so there is no - // point trying to collect more frames. - if (state_ == kPrerolling) - TransitionToPrerolled_Locked(); + DCHECK_EQ(state_, kPlaying); + // Can happen when demuxers are preparing for a new Seek(). + if (!frame) { + DCHECK_EQ(status, VideoFrameStream::DEMUXER_READ_ABORTED); return; } if (frame->end_of_stream()) { DCHECK(!received_end_of_stream_); received_end_of_stream_ = true; - max_time_cb_.Run(get_duration_cb_.Run()); - - if (state_ == kPrerolling) - TransitionToPrerolled_Locked(); - - return; - } - - // Maintain the latest frame decoded so the correct frame is displayed after - // prerolling has completed. - if (state_ == kPrerolling && preroll_timestamp_ != kNoTimestamp() && - frame->timestamp() <= preroll_timestamp_) { - ready_frames_.clear(); + } else { + // Maintain the latest frame decoded so the correct frame is displayed after + // prerolling has completed. + if (frame->timestamp() <= start_timestamp_) + ready_frames_.clear(); + AddReadyFrame_Locked(frame); } - AddReadyFrame_Locked(frame); - - if (ShouldTransitionToPrerolled_Locked()) - TransitionToPrerolled_Locked(); + // Signal buffering state if we've met our conditions for having enough data. + if (buffering_state_ != BUFFERING_HAVE_ENOUGH && HaveEnoughData_Locked()) + TransitionToHaveEnough_Locked(); // Always request more decoded video if we have capacity. This serves two // purposes: @@ -393,11 +364,39 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, AttemptRead_Locked(); } -bool VideoRendererImpl::ShouldTransitionToPrerolled_Locked() { - return state_ == kPrerolling && - (!video_frame_stream_.CanReadWithoutStalling() || - ready_frames_.size() >= static_cast<size_t>(limits::kMaxVideoFrames) || - (low_delay_ && ready_frames_.size() > 0)); +bool VideoRendererImpl::HaveEnoughData_Locked() { + DCHECK_EQ(state_, kPlaying); + return received_end_of_stream_ || + !video_frame_stream_.CanReadWithoutStalling() || + ready_frames_.size() >= static_cast<size_t>(limits::kMaxVideoFrames) || + (low_delay_ && ready_frames_.size() > 0); +} + +void VideoRendererImpl::TransitionToHaveEnough_Locked() { + DCHECK_EQ(buffering_state_, BUFFERING_HAVE_NOTHING); + + if (received_end_of_stream_) + max_time_cb_.Run(get_duration_cb_.Run()); + + if (!ready_frames_.empty()) { + // Max time isn't reported while we're in a have nothing state as we could + // be discarding frames to find |start_timestamp_|. + if (!received_end_of_stream_) { + base::TimeDelta max_timestamp = ready_frames_[0]->timestamp(); + for (size_t i = 1; i < ready_frames_.size(); ++i) { + if (ready_frames_[i]->timestamp() > max_timestamp) + max_timestamp = ready_frames_[i]->timestamp(); + } + max_time_cb_.Run(max_timestamp); + } + + // Because the clock might remain paused in for an undetermined amount + // of time (e.g., seeking while paused), paint the first frame. + PaintNextReadyFrame_Locked(); + } + + buffering_state_ = BUFFERING_HAVE_ENOUGH; + buffering_state_cb_.Run(BUFFERING_HAVE_ENOUGH); } void VideoRendererImpl::AddReadyFrame_Locked( @@ -419,7 +418,12 @@ void VideoRendererImpl::AddReadyFrame_Locked( DCHECK_LE(ready_frames_.size(), static_cast<size_t>(limits::kMaxVideoFrames)); - max_time_cb_.Run(frame->timestamp()); + // FrameReady() may add frames but discard them when we're decoding frames to + // reach |start_timestamp_|. In this case we'll only want to update the max + // time when we know we've reached |start_timestamp_| and have buffered enough + // frames to being playback. + if (buffering_state_ == BUFFERING_HAVE_ENOUGH) + max_time_cb_.Run(frame->timestamp()); // Avoid needlessly waking up |thread_| unless playing. if (state_ == kPlaying) @@ -441,8 +445,6 @@ void VideoRendererImpl::AttemptRead_Locked() { } switch (state_) { - case kPrerolling: - case kPrerolled: case kPlaying: pending_read_ = true; video_frame_stream_.Read(base::Bind(&VideoRendererImpl::FrameReady, @@ -468,6 +470,7 @@ void VideoRendererImpl::OnVideoFrameStreamResetDone() { DCHECK(ready_frames_.empty()); DCHECK(!received_end_of_stream_); DCHECK(!rendered_end_of_stream_); + DCHECK_EQ(buffering_state_, BUFFERING_HAVE_NOTHING); state_ = kFlushed; last_timestamp_ = kNoTimestamp(); @@ -493,21 +496,6 @@ void VideoRendererImpl::DoStopOrError_Locked() { ready_frames_.clear(); } -void VideoRendererImpl::TransitionToPrerolled_Locked() { - lock_.AssertAcquired(); - DCHECK_EQ(state_, kPrerolling); - - state_ = kPrerolled; - - // Because we might remain in the prerolled state for an undetermined amount - // of time (e.g., we seeked while paused), we'll paint the first prerolled - // frame. - if (!ready_frames_.empty()) - PaintNextReadyFrame_Locked(); - - base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK); -} - void VideoRendererImpl::UpdateStatsAndWait_Locked( base::TimeDelta wait_duration) { lock_.AssertAcquired(); diff --git a/media/filters/video_renderer_impl.h b/media/filters/video_renderer_impl.h index 4fef25d..bcba124 100644 --- a/media/filters/video_renderer_impl.h +++ b/media/filters/video_renderer_impl.h @@ -61,14 +61,13 @@ class MEDIA_EXPORT VideoRendererImpl const PipelineStatusCB& init_cb, const StatisticsCB& statistics_cb, const TimeCB& max_time_cb, + const BufferingStateCB& buffering_state_cb, const base::Closure& ended_cb, const PipelineStatusCB& error_cb, const TimeDeltaCB& get_time_cb, const TimeDeltaCB& get_duration_cb) OVERRIDE; - virtual void Play(const base::Closure& callback) OVERRIDE; virtual void Flush(const base::Closure& callback) OVERRIDE; - virtual void Preroll(base::TimeDelta time, - const PipelineStatusCB& cb) OVERRIDE; + virtual void StartPlayingFrom(base::TimeDelta timestamp) OVERRIDE; virtual void Stop(const base::Closure& callback) OVERRIDE; virtual void SetPlaybackRate(float playback_rate) OVERRIDE; @@ -117,11 +116,10 @@ class MEDIA_EXPORT VideoRendererImpl // A read is scheduled to replace the frame. void DropNextReadyFrame_Locked(); - void TransitionToPrerolled_Locked(); - - // Returns true of all conditions have been met to transition from - // kPrerolling to kPrerolled. - bool ShouldTransitionToPrerolled_Locked(); + // Returns true if the renderer has enough data for playback purposes. + // Note that having enough data may be due to reaching end of stream. + bool HaveEnoughData_Locked(); + void TransitionToHaveEnough_Locked(); // Runs |statistics_cb_| with |frames_decoded_| and |frames_dropped_|, resets // them to 0, and then waits on |frame_available_| for up to the @@ -152,36 +150,28 @@ class MEDIA_EXPORT VideoRendererImpl // always check |state_| to see if it was set to STOPPED after waking up! base::ConditionVariable frame_available_; - // State transition Diagram of this class: - // [kUninitialized] - // | - // | Initialize() - // [kInitializing] - // | - // V - // +------[kFlushed]<---------------OnVideoFrameStreamResetDone() - // | | Preroll() or upon ^ - // | V got first frame [kFlushing] - // | [kPrerolling] ^ - // | | | - // | V Got enough frames | - // | [kPrerolled]--------------------------| - // | | Flush() ^ - // | V Play() | - // | [kPlaying]---------------------------| - // | Flush() ^ Flush() - // | | - // +-----> [kStopped] [Any state other than] - // [ kUninitialized ] - - // Simple state tracking variable. + // Important detail: being in kPlaying doesn't imply that video is being + // rendered. Rather, it means that the renderer is ready to go. The actual + // rendering of video is controlled by time advancing via |time_cb_|. + // + // kUninitialized + // | Initialize() + // | + // V + // kInitializing + // | Decoders initialized + // | + // V Decoders reset + // kFlushed <------------------ kFlushing + // | StartPlayingFrom() ^ + // | | + // | | Flush() + // `---------> kPlaying --------' enum State { kUninitialized, kInitializing, - kPrerolled, kFlushing, kFlushed, - kPrerolling, kPlaying, kStopped, }; @@ -198,20 +188,22 @@ class MEDIA_EXPORT VideoRendererImpl float playback_rate_; + BufferingState buffering_state_; + // Playback operation callbacks. base::Closure flush_cb_; - PipelineStatusCB preroll_cb_; // Event callbacks. PipelineStatusCB init_cb_; StatisticsCB statistics_cb_; TimeCB max_time_cb_; + BufferingStateCB buffering_state_cb_; base::Closure ended_cb_; PipelineStatusCB error_cb_; TimeDeltaCB get_time_cb_; TimeDeltaCB get_duration_cb_; - base::TimeDelta preroll_timestamp_; + base::TimeDelta start_timestamp_; // Embedder callback for notifying a new frame is available for painting. PaintCB paint_cb_; diff --git a/media/filters/video_renderer_impl_unittest.cc b/media/filters/video_renderer_impl_unittest.cc index 355d875..c7eb556 100644 --- a/media/filters/video_renderer_impl_unittest.cc +++ b/media/filters/video_renderer_impl_unittest.cc @@ -57,13 +57,12 @@ class VideoRendererImplTest : public ::testing::Test { ScopedVector<VideoDecoder> decoders; decoders.push_back(decoder_); - renderer_.reset( - new VideoRendererImpl(message_loop_.message_loop_proxy(), - decoders.Pass(), - media::SetDecryptorReadyCB(), - base::Bind(&StrictMock<MockDisplayCB>::Display, - base::Unretained(&mock_display_cb_)), - true)); + renderer_.reset(new VideoRendererImpl( + message_loop_.message_loop_proxy(), + decoders.Pass(), + media::SetDecryptorReadyCB(), + base::Bind(&StrictMock<MockCB>::Display, base::Unretained(&mock_cb_)), + true)); demuxer_stream_.set_video_decoder_config(TestVideoConfig::Normal()); @@ -125,6 +124,8 @@ class VideoRendererImplTest : public ::testing::Test { base::Unretained(&statistics_cb_object_)), base::Bind(&VideoRendererImplTest::OnTimeUpdate, base::Unretained(this)), + base::Bind(&StrictMock<MockCB>::BufferingStateChange, + base::Unretained(&mock_cb_)), ended_event_.GetClosure(), error_event_.GetPipelineStatusCB(), base::Bind(&VideoRendererImplTest::GetTime, base::Unretained(this)), @@ -132,20 +133,11 @@ class VideoRendererImplTest : public ::testing::Test { base::Unretained(this))); } - void Play() { - SCOPED_TRACE("Play()"); - WaitableMessageLoopEvent event; - renderer_->Play(event.GetClosure()); - event.RunAndWait(); - } - - void Preroll(int timestamp_ms, PipelineStatus expected) { - SCOPED_TRACE(base::StringPrintf("Preroll(%d, %d)", timestamp_ms, expected)); - WaitableMessageLoopEvent event; - renderer_->Preroll( - base::TimeDelta::FromMilliseconds(timestamp_ms), - event.GetPipelineStatusCB()); - event.RunAndWaitForStatus(expected); + void StartPlayingFrom(int timestamp_ms) { + SCOPED_TRACE(base::StringPrintf("StartPlayingFrom(%d)", timestamp_ms)); + renderer_->StartPlayingFrom( + base::TimeDelta::FromMilliseconds(timestamp_ms)); + message_loop_.RunUntilIdle(); } void Flush() { @@ -163,7 +155,6 @@ class VideoRendererImplTest : public ::testing::Test { } void Shutdown() { - Flush(); Stop(); } @@ -290,12 +281,13 @@ class VideoRendererImplTest : public ::testing::Test { NiceMock<MockDemuxerStream> demuxer_stream_; MockStatisticsCB statistics_cb_object_; - // Use StrictMock<T> to catch missing/extra display callbacks. - class MockDisplayCB { + // Use StrictMock<T> to catch missing/extra callbacks. + class MockCB { public: MOCK_METHOD1(Display, void(const scoped_refptr<VideoFrame>&)); + MOCK_METHOD1(BufferingStateChange, void(BufferingState)); }; - StrictMock<MockDisplayCB> mock_display_cb_; + StrictMock<MockCB> mock_cb_; private: base::TimeDelta GetTime() { @@ -380,11 +372,12 @@ TEST_F(VideoRendererImplTest, Initialize) { Shutdown(); } -TEST_F(VideoRendererImplTest, InitializeAndPreroll) { +TEST_F(VideoRendererImplTest, InitializeAndStartPlayingFrom) { Initialize(); QueueFrames("0 10 20 30"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(0))); - Preroll(0, PIPELINE_OK); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(0))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(0); Shutdown(); } @@ -402,6 +395,11 @@ TEST_F(VideoRendererImplTest, StopWhileInitializing) { TEST_F(VideoRendererImplTest, StopWhileFlushing) { Initialize(); + QueueFrames("0 10 20 30"); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(0))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(0); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_NOTHING)); renderer_->Flush(base::Bind(&ExpectNotCalled, PIPELINE_OK)); Stop(); @@ -411,18 +409,28 @@ TEST_F(VideoRendererImplTest, StopWhileFlushing) { TEST_F(VideoRendererImplTest, Play) { Initialize(); QueueFrames("0 10 20 30"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(0))); - Preroll(0, PIPELINE_OK); - Play(); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(0))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(0); + Shutdown(); +} + +TEST_F(VideoRendererImplTest, FlushWithNothingBuffered) { + Initialize(); + StartPlayingFrom(0); + + // We shouldn't expect a buffering state change since we never reached + // BUFFERING_HAVE_ENOUGH. + Flush(); Shutdown(); } TEST_F(VideoRendererImplTest, EndOfStream_ClipDuration) { Initialize(); QueueFrames("0 10 20 30"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(0))); - Preroll(0, PIPELINE_OK); - Play(); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(0))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(0); // Next frame has timestamp way past duration. Its timestamp will be adjusted // to match the duration of the video. @@ -433,7 +441,7 @@ TEST_F(VideoRendererImplTest, EndOfStream_ClipDuration) { // Queue the end of stream frame and wait for the last frame to be rendered. SatisfyPendingReadWithEndOfStream(); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(kVideoDurationInMs))); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(kVideoDurationInMs))); AdvanceTimeInMs(kVideoDurationInMs); WaitForEnded(); @@ -443,9 +451,9 @@ TEST_F(VideoRendererImplTest, EndOfStream_ClipDuration) { TEST_F(VideoRendererImplTest, DecodeError_Playing) { Initialize(); QueueFrames("0 10 20 30"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(0))); - Preroll(0, PIPELINE_OK); - Play(); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(0))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(0); QueueFrames("error"); SatisfyPendingRead(); @@ -453,54 +461,57 @@ TEST_F(VideoRendererImplTest, DecodeError_Playing) { Shutdown(); } -TEST_F(VideoRendererImplTest, DecodeError_DuringPreroll) { +TEST_F(VideoRendererImplTest, DecodeError_DuringStartPlayingFrom) { Initialize(); QueueFrames("error"); - Preroll(0, PIPELINE_ERROR_DECODE); + StartPlayingFrom(0); Shutdown(); } -TEST_F(VideoRendererImplTest, Preroll_Exact) { +TEST_F(VideoRendererImplTest, StartPlayingFrom_Exact) { Initialize(); QueueFrames("50 60 70 80 90"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(60))); - Preroll(60, PIPELINE_OK); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(60))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(60); Shutdown(); } -TEST_F(VideoRendererImplTest, Preroll_RightBefore) { +TEST_F(VideoRendererImplTest, StartPlayingFrom_RightBefore) { Initialize(); QueueFrames("50 60 70 80 90"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(50))); - Preroll(59, PIPELINE_OK); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(50))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(59); Shutdown(); } -TEST_F(VideoRendererImplTest, Preroll_RightAfter) { +TEST_F(VideoRendererImplTest, StartPlayingFrom_RightAfter) { Initialize(); QueueFrames("50 60 70 80 90"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(60))); - Preroll(61, PIPELINE_OK); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(60))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(61); Shutdown(); } -TEST_F(VideoRendererImplTest, Preroll_LowDelay) { +TEST_F(VideoRendererImplTest, StartPlayingFrom_LowDelay) { // In low-delay mode only one frame is required to finish preroll. InitializeWithLowDelay(true); QueueFrames("0"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(0))); - Preroll(0, PIPELINE_OK); - Play(); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(0))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(0); QueueFrames("10"); SatisfyPendingRead(); WaitableMessageLoopEvent event; - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(10))) + EXPECT_CALL(mock_cb_, Display(HasTimestamp(10))) .WillOnce(RunClosure(event.GetClosure())); AdvanceTimeInMs(10); event.RunAndWait(); @@ -508,12 +519,12 @@ TEST_F(VideoRendererImplTest, Preroll_LowDelay) { Shutdown(); } -TEST_F(VideoRendererImplTest, PlayAfterPreroll) { +TEST_F(VideoRendererImplTest, PlayAfterStartPlayingFrom) { Initialize(); QueueFrames("0 10 20 30"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(0))); - Preroll(0, PIPELINE_OK); - Play(); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(0))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(0); // Check that there is an outstanding Read() request. EXPECT_TRUE(IsReadPending()); @@ -521,73 +532,13 @@ TEST_F(VideoRendererImplTest, PlayAfterPreroll) { Shutdown(); } -TEST_F(VideoRendererImplTest, Rebuffer) { - Initialize(); - QueueFrames("0 10 20 30"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(0))); - Preroll(0, PIPELINE_OK); - Play(); - - // Advance time past prerolled time drain the ready frame queue. - AdvanceTimeInMs(50); - WaitForPendingRead(); - - // Simulate a Preroll/Play rebuffer sequence. - WaitableMessageLoopEvent event; - renderer_->Preroll(kNoTimestamp(), - event.GetPipelineStatusCB()); - - // Queue enough frames to satisfy preroll. - QueueFrames("40 50 60 70"); - SatisfyPendingRead(); - - // TODO(scherkus): We shouldn't display the next ready frame in a rebuffer - // situation, see http://crbug.com/365516 - EXPECT_CALL(mock_display_cb_, Display(_)).Times(AtLeast(1)); - - event.RunAndWaitForStatus(PIPELINE_OK); - - Play(); - - Shutdown(); -} - -TEST_F(VideoRendererImplTest, Rebuffer_AlreadyHaveEnoughFrames) { - Initialize(); - QueueFrames("0 10 20 30"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(0))); - Preroll(0, PIPELINE_OK); - - // Queue an extra frame so that we'll have enough frames to satisfy - // preroll even after the first frame is painted. - QueueFrames("40"); - SatisfyPendingRead(); - Play(); - - // Simulate a Preroll/Play rebuffer sequence. - // - // TODO(scherkus): We shouldn't display the next ready frame in a rebuffer - // situation, see http://crbug.com/365516 - EXPECT_CALL(mock_display_cb_, Display(_)).Times(AtLeast(1)); - - WaitableMessageLoopEvent event; - renderer_->Preroll(kNoTimestamp(), - event.GetPipelineStatusCB()); - - event.RunAndWaitForStatus(PIPELINE_OK); - - Play(); - - Shutdown(); -} - // Verify that a late decoder response doesn't break invariants in the renderer. TEST_F(VideoRendererImplTest, StopDuringOutstandingRead) { Initialize(); QueueFrames("0 10 20 30"); - EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(0))); - Preroll(0, PIPELINE_OK); - Play(); + EXPECT_CALL(mock_cb_, Display(HasTimestamp(0))); + EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); + StartPlayingFrom(0); // Check that there is an outstanding Read() request. EXPECT_TRUE(IsReadPending()); |