summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-12 18:48:39 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-12 18:48:39 +0000
commit353132b98a665e7f93b110c36888c4eef2002a9b (patch)
treee29892735a9d2ed4861a59134d8566ed05da49fb /media
parent987f80913e6f2514b74da84540410ff1b06b12e5 (diff)
downloadchromium_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.h26
-rw-r--r--media/base/pipeline.cc191
-rw-r--r--media/base/pipeline.h35
-rw-r--r--media/base/pipeline_unittest.cc16
-rw-r--r--media/media.gyp1
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',