diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-12 18:48:39 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-12 18:48:39 +0000 |
commit | 353132b98a665e7f93b110c36888c4eef2002a9b (patch) | |
tree | e29892735a9d2ed4861a59134d8566ed05da49fb /media | |
parent | 987f80913e6f2514b74da84540410ff1b06b12e5 (diff) | |
download | chromium_src-353132b98a665e7f93b110c36888c4eef2002a9b.zip chromium_src-353132b98a665e7f93b110c36888c4eef2002a9b.tar.gz chromium_src-353132b98a665e7f93b110c36888c4eef2002a9b.tar.bz2 |
Introduce audio/video BufferingState to Pipeline.
This is a stepping stone towards having audio/video renderers accurately
report their buffering state. For now we use the Preroll() callback to
signal that enough data has been buffered.
Notable changes:
- The kStarting/kStarted states have been merged into kPlaying
- DoPlay() is now done implicitly after entering the kPlaying state
- Transitioning from waiting to non-waiting states (or vice versa)
now controls the starting and stopping of playback
BUG=144683
Review URL: https://codereview.chromium.org/276973004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@269828 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/base/buffering_state.h | 26 | ||||
-rw-r--r-- | media/base/pipeline.cc | 191 | ||||
-rw-r--r-- | media/base/pipeline.h | 35 | ||||
-rw-r--r-- | media/base/pipeline_unittest.cc | 16 | ||||
-rw-r--r-- | media/media.gyp | 1 |
5 files changed, 179 insertions, 90 deletions
diff --git a/media/base/buffering_state.h b/media/base/buffering_state.h new file mode 100644 index 0000000..3140505 --- /dev/null +++ b/media/base/buffering_state.h @@ -0,0 +1,26 @@ +// 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_BASE_BUFFERING_STATE_H_ +#define MEDIA_BASE_BUFFERING_STATE_H_ + +#include "base/callback_forward.h" + +namespace media { + +enum BufferingState { + // Indicates that there is no data buffered. + // + // Typical reason is data underflow and hence playback should be paused. + BUFFERING_HAVE_NOTHING, + + // Indicates that enough data has been buffered. + // + // Typical reason is enough data has been prerolled to start playback. + BUFFERING_HAVE_ENOUGH, +}; + +} // namespace media + +#endif // MEDIA_BASE_BUFFERING_STATE_H_ diff --git a/media/base/pipeline.cc b/media/base/pipeline.cc index 3cfe296..841a49b 100644 --- a/media/base/pipeline.cc +++ b/media/base/pipeline.cc @@ -48,6 +48,8 @@ Pipeline::Pipeline( audio_ended_(false), video_ended_(false), text_ended_(false), + audio_buffering_state_(BUFFERING_HAVE_NOTHING), + video_buffering_state_(BUFFERING_HAVE_NOTHING), demuxer_(NULL), creation_time_(default_tick_clock_.NowTicks()) { media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); @@ -188,7 +190,7 @@ void Pipeline::SetErrorForTesting(PipelineStatus status) { } void Pipeline::SetState(State next_state) { - if (state_ != kStarted && next_state == kStarted && + if (state_ != kPlaying && next_state == kPlaying && !creation_time_.is_null()) { UMA_HISTOGRAM_TIMES("Media.TimeToPipelineStarted", default_tick_clock_.NowTicks() - creation_time_); @@ -211,8 +213,7 @@ const char* Pipeline::GetStateString(State state) { RETURN_STRING(kInitVideoRenderer); RETURN_STRING(kInitPrerolling); RETURN_STRING(kSeeking); - RETURN_STRING(kStarting); - RETURN_STRING(kStarted); + RETURN_STRING(kPlaying); RETURN_STRING(kStopping); RETURN_STRING(kStopped); } @@ -249,15 +250,12 @@ Pipeline::State Pipeline::GetNextState() const { return kInitPrerolling; case kInitPrerolling: - return kStarting; + return kPlaying; case kSeeking: - return kStarting; + return kPlaying; - case kStarting: - return kStarted; - - case kStarted: + case kPlaying: case kStopping: case kStopped: break; @@ -364,11 +362,9 @@ void Pipeline::StateTransitionTask(PipelineStatus status) { // Guard against accidentally clearing |pending_callbacks_| for states that // use it as well as states that should not be using it. - // - // TODO(scherkus): Make every state transition use |pending_callbacks_|. DCHECK_EQ(pending_callbacks_.get() != NULL, - (state_ == kInitPrerolling || state_ == kStarting || - state_ == kSeeking)); + (state_ == kInitPrerolling || state_ == kSeeking)); + pending_callbacks_.reset(); PipelineStatusCB done_cb = base::Bind( @@ -412,29 +408,24 @@ void Pipeline::StateTransitionTask(PipelineStatus status) { return DoInitialPreroll(done_cb); - case kStarting: - return DoPlay(done_cb); - - case kStarted: - { - base::AutoLock l(lock_); - // We use audio stream to update the clock. So if there is such a - // stream, we pause the clock until we receive a valid timestamp. - waiting_for_clock_update_ = true; - if (!audio_renderer_) { - clock_->SetMaxTime(clock_->Duration()); - StartClockIfWaitingForTimeUpdate_Locked(); - } - } - - DCHECK(!seek_cb_.is_null()); - DCHECK_EQ(status_, PIPELINE_OK); - - // Fire canplaythrough immediately after playback begins because of - // crbug.com/106480. - // TODO(vrk): set ready state to HaveFutureData when bug above is fixed. - preroll_completed_cb_.Run(); - return base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); + case kPlaying: + 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 + DCHECK(WaitingForEnoughData()); + if (audio_renderer_) + BufferingStateChanged(&audio_buffering_state_, BUFFERING_HAVE_ENOUGH); + if (video_renderer_) + BufferingStateChanged(&video_buffering_state_, BUFFERING_HAVE_ENOUGH); + return; case kStopping: case kStopped: @@ -469,6 +460,16 @@ void Pipeline::DoInitialPreroll(const PipelineStatusCB& done_cb) { 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()))); } pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); @@ -495,10 +496,24 @@ void Pipeline::DoSeek( if (audio_renderer_) { bound_fns.Push(base::Bind( &AudioRenderer::Flush, base::Unretained(audio_renderer_.get()))); + + // TODO(scherkus): Remove after AudioRenderer is taught to fire buffering + // state callbacks http://crbug.com/144683 + bound_fns.Push(base::Bind(&Pipeline::BufferingStateChanged, + base::Unretained(this), + &audio_buffering_state_, + BUFFERING_HAVE_NOTHING)); } 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 (text_renderer_) { bound_fns.Push(base::Bind( @@ -520,27 +535,11 @@ void Pipeline::DoSeek( bound_fns.Push(base::Bind( &VideoRenderer::Preroll, base::Unretained(video_renderer_.get()), seek_timestamp)); - } - - pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); -} - -void Pipeline::DoPlay(const PipelineStatusCB& done_cb) { - DCHECK(task_runner_->BelongsToCurrentThread()); - DCHECK(!pending_callbacks_.get()); - SerialRunner::Queue bound_fns; - - PlaybackRateChangedTask(GetPlaybackRate()); - VolumeChangedTask(GetVolume()); - - if (audio_renderer_) { - bound_fns.Push(base::Bind( - &AudioRenderer::Play, base::Unretained(audio_renderer_.get()))); - } - if (video_renderer_) { - bound_fns.Push(base::Bind( - &VideoRenderer::Play, base::Unretained(video_renderer_.get()))); + // 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_) { @@ -706,7 +705,7 @@ void Pipeline::PlaybackRateChangedTask(float playback_rate) { DCHECK(task_runner_->BelongsToCurrentThread()); // Playback rate changes are only carried out while playing. - if (state_ != kStarting && state_ != kStarted) + if (state_ != kPlaying) return; { @@ -724,7 +723,7 @@ void Pipeline::VolumeChangedTask(float volume) { DCHECK(task_runner_->BelongsToCurrentThread()); // Volume changes are only carried out while playing. - if (state_ != kStarting && state_ != kStarted) + if (state_ != kPlaying) return; if (audio_renderer_) @@ -736,7 +735,7 @@ void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { DCHECK(stop_cb_.is_null()); // Suppress seeking if we're not fully started. - if (state_ != kStarted) { + if (state_ != kPlaying) { DCHECK(state_ == kStopping || state_ == kStopped) << "Receive extra seek in unexpected state: " << state_; @@ -770,7 +769,7 @@ void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { void Pipeline::DoAudioRendererEnded() { DCHECK(task_runner_->BelongsToCurrentThread()); - if (state_ != kStarted) + if (state_ != kPlaying) return; DCHECK(!audio_ended_); @@ -789,7 +788,7 @@ void Pipeline::DoAudioRendererEnded() { void Pipeline::DoVideoRendererEnded() { DCHECK(task_runner_->BelongsToCurrentThread()); - if (state_ != kStarted) + if (state_ != kPlaying) return; DCHECK(!video_ended_); @@ -801,7 +800,7 @@ void Pipeline::DoVideoRendererEnded() { void Pipeline::DoTextRendererEnded() { DCHECK(task_runner_->BelongsToCurrentThread()); - if (state_ != kStarted) + if (state_ != kPlaying) return; DCHECK(!text_ended_); @@ -888,13 +887,79 @@ void Pipeline::OnAudioUnderflow() { return; } - if (state_ != kStarted) + if (state_ != kPlaying) return; if (audio_renderer_) audio_renderer_->ResumeAfterUnderflow(); } +void Pipeline::BufferingStateChanged(BufferingState* buffering_state, + BufferingState new_buffering_state) { + DVLOG(1) << __FUNCTION__ << "(" << *buffering_state << ", " + << " " << new_buffering_state << ") " + << (buffering_state == &audio_buffering_state_ ? "audio" : "video"); + DCHECK(task_runner_->BelongsToCurrentThread()); + bool was_waiting_for_enough_data = WaitingForEnoughData(); + *buffering_state = new_buffering_state; + + // Renderer underflowed. + if (!was_waiting_for_enough_data && WaitingForEnoughData()) { + StartWaitingForEnoughData(); + return; + } + + // Renderer prerolled. + if (was_waiting_for_enough_data && !WaitingForEnoughData()) { + StartPlayback(); + return; + } +} + +bool Pipeline::WaitingForEnoughData() const { + DCHECK(task_runner_->BelongsToCurrentThread()); + if (state_ != kPlaying) + return false; + if (audio_renderer_ && audio_buffering_state_ != BUFFERING_HAVE_ENOUGH) + return true; + if (video_renderer_ && video_buffering_state_ != BUFFERING_HAVE_ENOUGH) + return true; + return false; +} + +void Pipeline::StartWaitingForEnoughData() { + DCHECK_EQ(state_, kPlaying); + DCHECK(WaitingForEnoughData()); + + if (audio_renderer_) + audio_renderer_->Pause(); + + base::AutoLock auto_lock(lock_); + clock_->Pause(); +} + +void Pipeline::StartPlayback() { + DCHECK_EQ(state_, kPlaying); + DCHECK(!WaitingForEnoughData()); + + if (audio_renderer_) { + audio_renderer_->Play(); + + base::AutoLock auto_lock(lock_); + // We use audio stream to update the clock. So if there is such a + // stream, we pause the clock until we receive a valid timestamp. + waiting_for_clock_update_ = true; + } else { + base::AutoLock auto_lock(lock_); + clock_->SetMaxTime(clock_->Duration()); + clock_->Play(); + } + + preroll_completed_cb_.Run(); + if (!seek_cb_.is_null()) + base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); +} + void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { lock_.AssertAcquired(); if (!waiting_for_clock_update_) diff --git a/media/base/pipeline.h b/media/base/pipeline.h index c4de1ce..1464cc2 100644 --- a/media/base/pipeline.h +++ b/media/base/pipeline.h @@ -13,6 +13,7 @@ #include "base/threading/thread_checker.h" #include "base/time/default_tick_clock.h" #include "media/base/audio_renderer.h" +#include "media/base/buffering_state.h" #include "media/base/demuxer.h" #include "media/base/media_export.h" #include "media/base/pipeline_status.h" @@ -58,14 +59,13 @@ typedef base::Callback<void(PipelineMetadata)> PipelineMetadataCB; // [ InitXXX (for each filter) ] [ Stopping ] // | | // V V -// [ InitPreroll ] [ Stopped ] +// [ InitPrerolling ] [ Stopped ] // | // V -// [ Starting ] <-- [ Seeking ] +// [ Playing ] <-- [ Seeking ] // | ^ -// V | -// [ Started ] ----------' -// Seek() +// `---------------' +// Seek() // // Initialization is a series of state transitions from "Created" through each // filter initialization state. When all filter initialization states have @@ -194,8 +194,7 @@ class MEDIA_EXPORT Pipeline : public DemuxerHost { kInitVideoRenderer, kInitPrerolling, kSeeking, - kStarting, - kStarted, + kPlaying, kStopping, kStopped, }; @@ -305,10 +304,6 @@ class MEDIA_EXPORT Pipeline : public DemuxerHost { // indepentent from seeking. void DoSeek(base::TimeDelta seek_timestamp, const PipelineStatusCB& done_cb); - // Updates playback rate and volume and initiates an asynchronous play call - // sequence executing |done_cb| with the final status when completed. - void DoPlay(const PipelineStatusCB& done_cb); - // Initiates an asynchronous pause-flush-stop call sequence executing // |done_cb| when completed. void DoStop(const PipelineStatusCB& done_cb); @@ -316,6 +311,21 @@ class MEDIA_EXPORT Pipeline : public DemuxerHost { void OnAudioUnderflow(); + // Collection of callback methods and helpers for tracking changes in + // buffering state and transition from paused/underflow states and playing + // states. + // + // While in the kPlaying state: + // - A waiting to non-waiting transition indicates preroll has completed + // and StartPlayback() should be called + // - A non-waiting to waiting transition indicates underflow has occurred + // and StartWaitingForEnoughData() should be called + void BufferingStateChanged(BufferingState* buffering_state, + BufferingState new_buffering_state); + bool WaitingForEnoughData() const; + void StartWaitingForEnoughData(); + void StartPlayback(); + void StartClockIfWaitingForTimeUpdate_Locked(); // Task runner used to execute pipeline tasks. @@ -377,6 +387,9 @@ class MEDIA_EXPORT Pipeline : public DemuxerHost { bool video_ended_; bool text_ended_; + BufferingState audio_buffering_state_; + BufferingState video_buffering_state_; + // Temporary callback used for Start() and Seek(). PipelineStatusCB seek_cb_; diff --git a/media/base/pipeline_unittest.cc b/media/base/pipeline_unittest.cc index d2a7a8f..81575e3 100644 --- a/media/base/pipeline_unittest.cc +++ b/media/base/pipeline_unittest.cc @@ -860,7 +860,6 @@ class PipelineTeardownTest : public PipelineTest { kFlushing, kSeeking, kPrerolling, - kStarting, kPlaying, }; @@ -885,7 +884,6 @@ class PipelineTeardownTest : public PipelineTest { case kFlushing: case kSeeking: case kPrerolling: - case kStarting: DoInitialize(state, stop_or_error); DoSeek(state, stop_or_error); break; @@ -1107,18 +1105,6 @@ class PipelineTeardownTest : public PipelineTest { EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f)); EXPECT_CALL(*audio_renderer_, SetVolume(1.0f)); - if (state == kStarting) { - if (stop_or_error == kStop) { - EXPECT_CALL(*audio_renderer_, Play()) - .WillOnce(Stop(pipeline_.get(), stop_cb)); - } else { - status = PIPELINE_ERROR_READ; - EXPECT_CALL(*audio_renderer_, Play()) - .WillOnce(SetError(pipeline_.get(), status)); - } - return status; - } - NOTREACHED() << "State not supported: " << state; return status; } @@ -1168,7 +1154,6 @@ INSTANTIATE_TEARDOWN_TEST(Stop, Pausing); INSTANTIATE_TEARDOWN_TEST(Stop, Flushing); INSTANTIATE_TEARDOWN_TEST(Stop, Seeking); INSTANTIATE_TEARDOWN_TEST(Stop, Prerolling); -INSTANTIATE_TEARDOWN_TEST(Stop, Starting); INSTANTIATE_TEARDOWN_TEST(Stop, Playing); INSTANTIATE_TEARDOWN_TEST(Error, InitDemuxer); @@ -1178,7 +1163,6 @@ INSTANTIATE_TEARDOWN_TEST(Error, Pausing); INSTANTIATE_TEARDOWN_TEST(Error, Flushing); INSTANTIATE_TEARDOWN_TEST(Error, Seeking); INSTANTIATE_TEARDOWN_TEST(Error, Prerolling); -INSTANTIATE_TEARDOWN_TEST(Error, Starting); INSTANTIATE_TEARDOWN_TEST(Error, Playing); INSTANTIATE_TEARDOWN_TEST(ErrorAndStop, Playing); diff --git a/media/media.gyp b/media/media.gyp index 5a974aa..c42d270 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -248,6 +248,7 @@ 'base/bit_reader_core.cc', 'base/bit_reader_core.h', 'base/bitstream_buffer.h', + 'base/buffering_state.h', 'base/buffers.h', 'base/byte_queue.cc', 'base/byte_queue.h', |