From d399866c2fe77410afed5be486f2b72581823bed Mon Sep 17 00:00:00 2001 From: "scherkus@chromium.org" Date: Wed, 18 Jan 2012 09:30:32 +0000 Subject: Fold media::PipelineImpl into media::Pipeline as there is only one implementation of the interface. Three years ago I thought that having a separate interface would be A Good Thing. Needless to say there has never been a case where having an interface defintion of Pipeline came in handy as all clients create and access PipelineImpl objects directly. Review URL: http://codereview.chromium.org/9243025 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@118082 0039d316-1c4b-4281-b951-d872f2087c98 --- media/base/filter_host.h | 8 +- media/base/media_log.cc | 35 +- media/base/media_log.h | 6 +- media/base/media_log_event.h | 6 +- media/base/pipeline.cc | 1441 +++++++++++++++++++++++++++ media/base/pipeline.h | 536 +++++++++- media/base/pipeline_impl.cc | 1448 ---------------------------- media/base/pipeline_impl.h | 518 ---------- media/base/pipeline_impl_unittest.cc | 927 ------------------ media/base/pipeline_unittest.cc | 928 ++++++++++++++++++ media/filters/ffmpeg_audio_decoder.cc | 3 +- media/filters/ffmpeg_video_decoder.cc | 3 +- media/filters/gpu_video_decoder.cc | 3 +- media/filters/pipeline_integration_test.cc | 7 +- media/filters/video_renderer_base.cc | 1 + media/media.gyp | 5 +- media/tools/player_wtl/movie.cc | 6 +- media/tools/player_wtl/movie.h | 6 +- media/tools/player_wtl/player_wtl.cc | 4 +- media/tools/player_x11/player_x11.cc | 10 +- 20 files changed, 2920 insertions(+), 2981 deletions(-) create mode 100644 media/base/pipeline.cc delete mode 100644 media/base/pipeline_impl.cc delete mode 100644 media/base/pipeline_impl.h delete mode 100644 media/base/pipeline_impl_unittest.cc create mode 100644 media/base/pipeline_unittest.cc (limited to 'media') diff --git a/media/base/filter_host.h b/media/base/filter_host.h index 864bc99..4719618 100644 --- a/media/base/filter_host.h +++ b/media/base/filter_host.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -17,8 +17,10 @@ #ifndef MEDIA_BASE_FILTER_HOST_H_ #define MEDIA_BASE_FILTER_HOST_H_ -#include "media/base/filters.h" -#include "media/base/pipeline.h" +#include "base/time.h" +#include "media/base/media_export.h" +#include "media/base/pipeline_status.h" +#include "ui/gfx/size.h" namespace media { diff --git a/media/base/media_log.cc b/media/base/media_log.cc index 43d9278..d223b51 100644 --- a/media/base/media_log.cc +++ b/media/base/media_log.cc @@ -11,6 +11,7 @@ #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" #include "base/values.h" namespace media { @@ -65,37 +66,37 @@ const char* MediaLog::EventTypeToString(MediaLogEvent::Type type) { return NULL; } -const char* MediaLog::PipelineStateToString(PipelineImpl::State state) { +const char* MediaLog::PipelineStateToString(Pipeline::State state) { switch (state) { - case PipelineImpl::kCreated: + case Pipeline::kCreated: return "created"; - case PipelineImpl::kInitDemuxer: + case Pipeline::kInitDemuxer: return "initDemuxer"; - case PipelineImpl::kInitAudioDecoder: + case Pipeline::kInitAudioDecoder: return "initAudioDecoder"; - case PipelineImpl::kInitAudioRenderer: + case Pipeline::kInitAudioRenderer: return "initAudioRenderer"; - case PipelineImpl::kInitVideoDecoder: + case Pipeline::kInitVideoDecoder: return "initVideoDecoder"; - case PipelineImpl::kInitVideoRenderer: + case Pipeline::kInitVideoRenderer: return "initVideoRenderer"; - case PipelineImpl::kPausing: + case Pipeline::kPausing: return "pausing"; - case PipelineImpl::kSeeking: + case Pipeline::kSeeking: return "seeking"; - case PipelineImpl::kFlushing: + case Pipeline::kFlushing: return "flushing"; - case PipelineImpl::kStarting: + case Pipeline::kStarting: return "starting"; - case PipelineImpl::kStarted: + case Pipeline::kStarted: return "started"; - case PipelineImpl::kEnded: + case Pipeline::kEnded: return "ended"; - case PipelineImpl::kStopping: + case Pipeline::kStopping: return "stopping"; - case PipelineImpl::kStopped: + case Pipeline::kStopped: return "stopped"; - case PipelineImpl::kError: + case Pipeline::kError: return "error"; } NOTREACHED(); @@ -199,7 +200,7 @@ scoped_ptr MediaLog::CreateSeekEvent(float seconds) { } scoped_ptr MediaLog::CreatePipelineStateChangedEvent( - PipelineImpl::State state) { + Pipeline::State state) { scoped_ptr event( CreateEvent(MediaLogEvent::PIPELINE_STATE_CHANGED)); event->params.SetString("pipeline_state", PipelineStateToString(state)); diff --git a/media/base/media_log.h b/media/base/media_log.h index 236c533..9b87361b 100644 --- a/media/base/media_log.h +++ b/media/base/media_log.h @@ -10,7 +10,7 @@ #include "base/synchronization/lock.h" #include "media/base/media_export.h" #include "media/base/media_log_event.h" -#include "media/base/pipeline_impl.h" +#include "media/base/pipeline.h" #include "media/base/pipeline_status.h" namespace media { @@ -19,7 +19,7 @@ class MEDIA_EXPORT MediaLog : public base::RefCountedThreadSafe { public: // Convert various enums to strings. static const char* EventTypeToString(MediaLogEvent::Type type); - static const char* PipelineStateToString(PipelineImpl::State); + static const char* PipelineStateToString(Pipeline::State); static const char* PipelineStatusToString(PipelineStatus); MediaLog(); @@ -39,7 +39,7 @@ class MEDIA_EXPORT MediaLog : public base::RefCountedThreadSafe { scoped_ptr CreateLoadEvent(const std::string& url); scoped_ptr CreateSeekEvent(float seconds); scoped_ptr CreatePipelineStateChangedEvent( - PipelineImpl::State state); + Pipeline::State state); scoped_ptr CreatePipelineErrorEvent(PipelineStatus error); scoped_ptr CreateVideoSizeSetEvent( size_t width, size_t height); diff --git a/media/base/media_log_event.h b/media/base/media_log_event.h index 384a2e3..4167c893 100644 --- a/media/base/media_log_event.h +++ b/media/base/media_log_event.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -18,7 +18,7 @@ struct MediaLogEvent { WEBMEDIAPLAYER_CREATED, WEBMEDIAPLAYER_DESTROYED, - // A PipelineImpl is being created or destroyed. + // A Pipeline is being created or destroyed. // params: none. PIPELINE_CREATED, PIPELINE_DESTROYED, @@ -36,7 +36,7 @@ struct MediaLogEvent { PLAY, PAUSE, - // The state of PipelineImpl has changed. + // The state of Pipeline has changed. // params: "pipeline_state": . PIPELINE_STATE_CHANGED, diff --git a/media/base/pipeline.cc b/media/base/pipeline.cc new file mode 100644 index 0000000..2eeefff --- /dev/null +++ b/media/base/pipeline.cc @@ -0,0 +1,1441 @@ +// Copyright (c) 2012 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/base/pipeline.h" + +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/metrics/histogram.h" +#include "base/message_loop.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "base/synchronization/condition_variable.h" +#include "media/base/clock.h" +#include "media/base/composite_filter.h" +#include "media/base/filter_collection.h" +#include "media/base/media_log.h" + +namespace media { + +PipelineStatusNotification::PipelineStatusNotification() + : cv_(&lock_), status_(PIPELINE_OK), notified_(false) { +} + +PipelineStatusNotification::~PipelineStatusNotification() { + DCHECK(notified_); +} + +PipelineStatusCB PipelineStatusNotification::Callback() { + return base::Bind(&PipelineStatusNotification::Notify, + base::Unretained(this)); +} + +void PipelineStatusNotification::Notify(media::PipelineStatus status) { + base::AutoLock auto_lock(lock_); + DCHECK(!notified_); + notified_ = true; + status_ = status; + cv_.Signal(); +} + +void PipelineStatusNotification::Wait() { + base::AutoLock auto_lock(lock_); + while (!notified_) + cv_.Wait(); +} + +media::PipelineStatus PipelineStatusNotification::status() { + base::AutoLock auto_lock(lock_); + DCHECK(notified_); + return status_; +} + +class Pipeline::PipelineInitState { + public: + scoped_refptr audio_decoder_; + scoped_refptr video_decoder_; + scoped_refptr composite_; +}; + +Pipeline::Pipeline(MessageLoop* message_loop, MediaLog* media_log) + : message_loop_(message_loop), + media_log_(media_log), + clock_(new Clock(&base::Time::Now)), + waiting_for_clock_update_(false), + state_(kCreated), + current_bytes_(0), + creation_time_(base::Time::Now()), + is_downloading_data_(false) { + media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); + ResetState(); + media_log_->AddEvent( + media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED)); +} + +Pipeline::~Pipeline() { + base::AutoLock auto_lock(lock_); + DCHECK(!running_) << "Stop() must complete before destroying object"; + DCHECK(!stop_pending_); + DCHECK(!seek_pending_); + + media_log_->AddEvent( + media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED)); +} + +void Pipeline::Init(const PipelineStatusCB& ended_callback, + const PipelineStatusCB& error_callback, + const NetworkEventCB& network_callback) { + DCHECK(!IsRunning()) + << "Init() should be called before the pipeline has started"; + ended_callback_ = ended_callback; + error_callback_ = error_callback; + network_callback_ = network_callback; +} + +// Creates the PipelineInternal and calls it's start method. +bool Pipeline::Start(scoped_ptr collection, + const std::string& url, + const PipelineStatusCB& start_callback) { + base::AutoLock auto_lock(lock_); + + if (running_) { + VLOG(1) << "Media pipeline is already running"; + return false; + } + + if (collection->IsEmpty()) { + return false; + } + + // Kick off initialization! + running_ = true; + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::StartTask, this, base::Passed(&collection), + url, start_callback)); + return true; +} + +void Pipeline::Stop(const PipelineStatusCB& stop_callback) { + base::AutoLock auto_lock(lock_); + if (!running_) { + VLOG(1) << "Media pipeline has already stopped"; + return; + } + + if (video_decoder_) { + video_decoder_->PrepareForShutdownHack(); + video_decoder_ = NULL; + } + + // Stop the pipeline, which will set |running_| to false on our behalf. + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::StopTask, this, stop_callback)); +} + +void Pipeline::Seek(base::TimeDelta time, + const PipelineStatusCB& seek_callback) { + base::AutoLock auto_lock(lock_); + if (!running_) { + VLOG(1) << "Media pipeline must be running"; + return; + } + + download_rate_monitor_.Stop(); + + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::SeekTask, this, time, seek_callback)); +} + +bool Pipeline::IsRunning() const { + base::AutoLock auto_lock(lock_); + return running_; +} + +bool Pipeline::IsInitialized() const { + // TODO(scherkus): perhaps replace this with a bool that is set/get under the + // lock, because this is breaching the contract that |state_| is only accessed + // on |message_loop_|. + base::AutoLock auto_lock(lock_); + switch (state_) { + case kPausing: + case kFlushing: + case kSeeking: + case kStarting: + case kStarted: + case kEnded: + return true; + default: + return false; + } +} + +bool Pipeline::HasAudio() const { + base::AutoLock auto_lock(lock_); + return has_audio_; +} + +bool Pipeline::HasVideo() const { + base::AutoLock auto_lock(lock_); + return has_video_; +} + +float Pipeline::GetPlaybackRate() const { + base::AutoLock auto_lock(lock_); + return playback_rate_; +} + +void Pipeline::SetPlaybackRate(float playback_rate) { + if (playback_rate < 0.0f) + return; + + base::AutoLock auto_lock(lock_); + playback_rate_ = playback_rate; + if (running_ && !tearing_down_) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::PlaybackRateChangedTask, this, playback_rate)); + } +} + +float Pipeline::GetVolume() const { + base::AutoLock auto_lock(lock_); + return volume_; +} + +void Pipeline::SetVolume(float volume) { + if (volume < 0.0f || volume > 1.0f) + return; + + base::AutoLock auto_lock(lock_); + volume_ = volume; + if (running_ && !tearing_down_) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::VolumeChangedTask, this, volume)); + } +} + +Preload Pipeline::GetPreload() const { + base::AutoLock auto_lock(lock_); + return preload_; +} + +void Pipeline::SetPreload(Preload preload) { + base::AutoLock auto_lock(lock_); + preload_ = preload; + if (running_ && !tearing_down_) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::PreloadChangedTask, this, preload)); + } +} + +base::TimeDelta Pipeline::GetCurrentTime() const { + base::AutoLock auto_lock(lock_); + return GetCurrentTime_Locked(); +} + +base::TimeDelta Pipeline::GetCurrentTime_Locked() const { + lock_.AssertAcquired(); + base::TimeDelta elapsed = clock_->Elapsed(); + if (elapsed > duration_) + return duration_; + + return elapsed; +} + +base::TimeDelta Pipeline::GetBufferedTime() { + base::AutoLock auto_lock(lock_); + + // If media is fully loaded, then return duration. + if (local_source_ || total_bytes_ == buffered_bytes_) { + max_buffered_time_ = duration_; + return duration_; + } + + base::TimeDelta current_time = GetCurrentTime_Locked(); + + // If buffered time was set, we report that value directly. + if (buffered_time_.ToInternalValue() > 0) + return std::max(buffered_time_, current_time); + + if (total_bytes_ == 0) + return base::TimeDelta(); + + // If buffered time was not set, we use current time, current bytes, and + // buffered bytes to estimate the buffered time. + double estimated_rate = duration_.InMillisecondsF() / total_bytes_; + double estimated_current_time = estimated_rate * current_bytes_; + DCHECK_GE(buffered_bytes_, current_bytes_); + base::TimeDelta buffered_time = base::TimeDelta::FromMilliseconds( + static_cast(estimated_rate * (buffered_bytes_ - current_bytes_) + + estimated_current_time)); + + // Cap approximated buffered time at the length of the video. + buffered_time = std::min(buffered_time, duration_); + + // Make sure buffered_time is at least the current time + buffered_time = std::max(buffered_time, current_time); + + // Only print the max buffered time for smooth buffering. + max_buffered_time_ = std::max(buffered_time, max_buffered_time_); + + return max_buffered_time_; +} + +base::TimeDelta Pipeline::GetMediaDuration() const { + base::AutoLock auto_lock(lock_); + return duration_; +} + +int64 Pipeline::GetBufferedBytes() const { + base::AutoLock auto_lock(lock_); + return buffered_bytes_; +} + +int64 Pipeline::GetTotalBytes() const { + base::AutoLock auto_lock(lock_); + return total_bytes_; +} + +void Pipeline::GetNaturalVideoSize(gfx::Size* out_size) const { + CHECK(out_size); + base::AutoLock auto_lock(lock_); + *out_size = natural_size_; +} + +bool Pipeline::IsStreaming() const { + base::AutoLock auto_lock(lock_); + return streaming_; +} + +bool Pipeline::IsLocalSource() const { + base::AutoLock auto_lock(lock_); + return local_source_; +} + +PipelineStatistics Pipeline::GetStatistics() const { + base::AutoLock auto_lock(lock_); + return statistics_; +} + +void Pipeline::SetClockForTesting(Clock* clock) { + clock_.reset(clock); +} + +void Pipeline::SetCurrentReadPosition(int64 offset) { + base::AutoLock auto_lock(lock_); + + // The current read position should never be ahead of the buffered byte + // position but threading issues between BufferedDataSource::DoneRead_Locked() + // and BufferedDataSource::NetworkEventCallback() can cause them to be + // temporarily out of sync. The easiest fix for this is to cap both + // buffered_bytes_ and current_bytes_ to always be legal values in + // SetCurrentReadPosition() and in SetBufferedBytes(). + if (offset > buffered_bytes_) + buffered_bytes_ = offset; + current_bytes_ = offset; +} + +void Pipeline::ResetState() { + base::AutoLock auto_lock(lock_); + const base::TimeDelta kZero; + running_ = false; + stop_pending_ = false; + seek_pending_ = false; + tearing_down_ = false; + error_caused_teardown_ = false; + playback_rate_change_pending_ = false; + duration_ = kZero; + buffered_time_ = kZero; + buffered_bytes_ = 0; + streaming_ = false; + local_source_ = false; + total_bytes_ = 0; + natural_size_.SetSize(0, 0); + volume_ = 1.0f; + preload_ = AUTO; + playback_rate_ = 0.0f; + pending_playback_rate_ = 0.0f; + status_ = PIPELINE_OK; + has_audio_ = false; + has_video_ = false; + waiting_for_clock_update_ = false; + audio_disabled_ = false; + clock_->SetTime(kZero); + download_rate_monitor_.Reset(); +} + +void Pipeline::SetState(State next_state) { + if (state_ != kStarted && next_state == kStarted && + !creation_time_.is_null()) { + UMA_HISTOGRAM_TIMES( + "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_); + creation_time_ = base::Time(); + } + state_ = next_state; + media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); +} + +bool Pipeline::IsPipelineOk() { + base::AutoLock auto_lock(lock_); + return status_ == PIPELINE_OK; +} + +bool Pipeline::IsPipelineStopped() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + return state_ == kStopped || state_ == kError; +} + +bool Pipeline::IsPipelineTearingDown() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + return tearing_down_; +} + +bool Pipeline::IsPipelineStopPending() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + return stop_pending_; +} + +bool Pipeline::IsPipelineSeeking() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + if (!seek_pending_) + return false; + DCHECK(kSeeking == state_ || kPausing == state_ || + kFlushing == state_ || kStarting == state_) + << "Current state : " << state_; + return true; +} + +void Pipeline::FinishInitialization() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + // Execute the seek callback, if present. Note that this might be the + // initial callback passed into Start(). + if (!seek_callback_.is_null()) { + seek_callback_.Run(status_); + seek_callback_.Reset(); + } +} + +// static +bool Pipeline::TransientState(State state) { + return state == kPausing || + state == kFlushing || + state == kSeeking || + state == kStarting || + state == kStopping; +} + +// static +Pipeline::State Pipeline::FindNextState(State current) { + // TODO(scherkus): refactor InitializeTask() to make use of this function. + if (current == kPausing) { + return kFlushing; + } else if (current == kFlushing) { + // We will always honor Seek() before Stop(). This is based on the + // assumption that we never accept Seek() after Stop(). + DCHECK(IsPipelineSeeking() || + IsPipelineStopPending() || + IsPipelineTearingDown()); + return IsPipelineSeeking() ? kSeeking : kStopping; + } else if (current == kSeeking) { + return kStarting; + } else if (current == kStarting) { + return kStarted; + } else if (current == kStopping) { + return error_caused_teardown_ ? kError : kStopped; + } else { + return current; + } +} + +void Pipeline::OnDemuxerError(PipelineStatus error) { + SetError(error); +} + +void Pipeline::SetError(PipelineStatus error) { + DCHECK(IsRunning()); + DCHECK_NE(PIPELINE_OK, error); + VLOG(1) << "Media pipeline error: " << error; + + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::ErrorChangedTask, this, error)); + + media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error)); +} + +base::TimeDelta Pipeline::GetTime() const { + DCHECK(IsRunning()); + return GetCurrentTime(); +} + +base::TimeDelta Pipeline::GetDuration() const { + DCHECK(IsRunning()); + return GetMediaDuration(); +} + +void Pipeline::SetTime(base::TimeDelta time) { + DCHECK(IsRunning()); + base::AutoLock auto_lock(lock_); + + // If we were waiting for a valid timestamp and such timestamp arrives, we + // need to clear the flag for waiting and start the clock. + if (waiting_for_clock_update_) { + if (time < clock_->Elapsed()) + return; + clock_->SetTime(time); + StartClockIfWaitingForTimeUpdate_Locked(); + return; + } + clock_->SetTime(time); +} + +void Pipeline::SetDuration(base::TimeDelta duration) { + DCHECK(IsRunning()); + media_log_->AddEvent( + media_log_->CreateTimeEvent( + MediaLogEvent::DURATION_SET, "duration", duration)); + UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration); + + base::AutoLock auto_lock(lock_); + duration_ = duration; +} + +void Pipeline::SetBufferedTime(base::TimeDelta buffered_time) { + DCHECK(IsRunning()); + base::AutoLock auto_lock(lock_); + buffered_time_ = buffered_time; +} + +void Pipeline::SetTotalBytes(int64 total_bytes) { + DCHECK(IsRunning()); + media_log_->AddEvent( + media_log_->CreateIntegerEvent( + MediaLogEvent::TOTAL_BYTES_SET, "total_bytes", total_bytes)); + int64 total_mbytes = total_bytes >> 20; + if (total_mbytes > kint32max) + total_mbytes = kint32max; + UMA_HISTOGRAM_CUSTOM_COUNTS( + "Media.TotalMBytes", static_cast(total_mbytes), 1, kint32max, 50); + + base::AutoLock auto_lock(lock_); + total_bytes_ = total_bytes; + download_rate_monitor_.set_total_bytes(total_bytes_); +} + +void Pipeline::SetBufferedBytes(int64 buffered_bytes) { + DCHECK(IsRunning()); + base::AutoLock auto_lock(lock_); + // See comments in SetCurrentReadPosition() about capping. + if (buffered_bytes < current_bytes_) + current_bytes_ = buffered_bytes; + buffered_bytes_ = buffered_bytes; + download_rate_monitor_.SetBufferedBytes(buffered_bytes, base::Time::Now()); +} + +void Pipeline::SetNaturalVideoSize(const gfx::Size& size) { + DCHECK(IsRunning()); + media_log_->AddEvent(media_log_->CreateVideoSizeSetEvent( + size.width(), size.height())); + + base::AutoLock auto_lock(lock_); + natural_size_ = size; +} + +void Pipeline::NotifyEnded() { + DCHECK(IsRunning()); + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::NotifyEndedTask, this)); + media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED)); +} + +void Pipeline::SetNetworkActivity(bool is_downloading_data) { + DCHECK(IsRunning()); + + NetworkEvent type = DOWNLOAD_PAUSED; + if (is_downloading_data) + type = DOWNLOAD_CONTINUED; + + { + base::AutoLock auto_lock(lock_); + download_rate_monitor_.SetNetworkActivity(is_downloading_data); + } + + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::NotifyNetworkEventTask, this, type)); + media_log_->AddEvent( + media_log_->CreateBooleanEvent( + MediaLogEvent::NETWORK_ACTIVITY_SET, + "is_downloading_data", is_downloading_data)); +} + +void Pipeline::DisableAudioRenderer() { + DCHECK(IsRunning()); + + // Disable renderer on the message loop. + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::DisableAudioRendererTask, this)); + media_log_->AddEvent( + media_log_->CreateEvent(MediaLogEvent::AUDIO_RENDERER_DISABLED)); +} + +// Called from any thread. +void Pipeline::OnFilterInitialize(PipelineStatus status) { + // Continue the initialize task by proceeding to the next stage. + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::InitializeTask, this, status)); +} + +// Called from any thread. +void Pipeline::OnFilterStateTransition() { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::FilterStateTransitionTask, this)); +} + +// Called from any thread. +// This method makes the FilterStatusCB behave like a Closure. It +// makes it look like a host()->SetError() call followed by a call to +// OnFilterStateTransition() when errors occur. +// +// TODO: Revisit this code when SetError() is removed from FilterHost and +// all the Closures are converted to FilterStatusCB. +void Pipeline::OnFilterStateTransitionWithStatus(PipelineStatus status) { + if (status != PIPELINE_OK) + SetError(status); + OnFilterStateTransition(); +} + +void Pipeline::OnTeardownStateTransition() { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::TeardownStateTransitionTask, this)); +} + +// Called from any thread. +void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) { + base::AutoLock auto_lock(lock_); + statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; + statistics_.video_bytes_decoded += stats.video_bytes_decoded; + statistics_.video_frames_decoded += stats.video_frames_decoded; + statistics_.video_frames_dropped += stats.video_frames_dropped; + media_log_->QueueStatisticsUpdatedEvent(statistics_); +} + +void Pipeline::StartTask(scoped_ptr filter_collection, + const std::string& url, + const PipelineStatusCB& start_callback) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK_EQ(kCreated, state_); + filter_collection_ = filter_collection.Pass(); + url_ = url; + seek_callback_ = start_callback; + + // Kick off initialization. + pipeline_init_state_.reset(new PipelineInitState()); + pipeline_init_state_->composite_ = new CompositeFilter(message_loop_); + pipeline_init_state_->composite_->set_host(this); + + SetState(kInitDemuxer); + InitializeDemuxer(); +} + +// Main initialization method called on the pipeline thread. This code attempts +// to use the specified filter factory to build a pipeline. +// Initialization step performed in this method depends on current state of this +// object, indicated by |state_|. After each step of initialization, this +// object transits to the next stage. It starts by creating a Demuxer, and then +// connects the Demuxer's audio stream to an AudioDecoder which is then +// connected to an AudioRenderer. If the media has video, then it connects a +// VideoDecoder to the Demuxer's video stream, and then connects the +// VideoDecoder to a VideoRenderer. +// +// When all required filters have been created and have called their +// FilterHost's InitializationComplete() method, the pipeline will update its +// state to kStarted and |init_callback_|, will be executed. +// +// TODO(hclam): InitializeTask() is now starting the pipeline asynchronously. It +// works like a big state change table. If we no longer need to start filters +// in order, we need to get rid of all the state change. +void Pipeline::InitializeTask(PipelineStatus last_stage_status) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + + if (last_stage_status != PIPELINE_OK) { + // Currently only VideoDecoders have a recoverable error code. + if (state_ == kInitVideoDecoder && + last_stage_status == DECODER_ERROR_NOT_SUPPORTED) { + pipeline_init_state_->composite_->RemoveFilter( + pipeline_init_state_->video_decoder_.get()); + state_ = kInitAudioRenderer; + } else { + SetError(last_stage_status); + } + } + + // If we have received the stop or error signal, return immediately. + if (IsPipelineStopPending() || IsPipelineStopped() || !IsPipelineOk()) + return; + + DCHECK(state_ == kInitDemuxer || + state_ == kInitAudioDecoder || + state_ == kInitAudioRenderer || + state_ == kInitVideoDecoder || + state_ == kInitVideoRenderer); + + // Demuxer created, create audio decoder. + if (state_ == kInitDemuxer) { + SetState(kInitAudioDecoder); + // If this method returns false, then there's no audio stream. + if (InitializeAudioDecoder(demuxer_)) + return; + } + + // Assuming audio decoder was created, create audio renderer. + if (state_ == kInitAudioDecoder) { + SetState(kInitAudioRenderer); + + // Returns false if there's no audio stream. + if (InitializeAudioRenderer(pipeline_init_state_->audio_decoder_)) { + base::AutoLock auto_lock(lock_); + has_audio_ = true; + return; + } + } + + // Assuming audio renderer was created, create video decoder. + if (state_ == kInitAudioRenderer) { + // Then perform the stage of initialization, i.e. initialize video decoder. + SetState(kInitVideoDecoder); + if (InitializeVideoDecoder(demuxer_)) + return; + } + + // Assuming video decoder was created, create video renderer. + if (state_ == kInitVideoDecoder) { + SetState(kInitVideoRenderer); + if (InitializeVideoRenderer(pipeline_init_state_->video_decoder_)) { + base::AutoLock auto_lock(lock_); + has_video_ = true; + return; + } + } + + if (state_ == kInitVideoRenderer) { + if (!IsPipelineOk() || !(HasAudio() || HasVideo())) { + SetError(PIPELINE_ERROR_COULD_NOT_RENDER); + return; + } + + // Clear the collection of filters. + filter_collection_->Clear(); + + pipeline_filter_ = pipeline_init_state_->composite_; + + // Clear init state since we're done initializing. + pipeline_init_state_.reset(); + + if (audio_disabled_) { + // Audio was disabled at some point during initialization. Notify + // the pipeline filter now that it has been initialized. + demuxer_->OnAudioRendererDisabled(); + pipeline_filter_->OnAudioRendererDisabled(); + } + + // Initialization was successful, we are now considered paused, so it's safe + // to set the initial playback rate and volume. + PreloadChangedTask(GetPreload()); + PlaybackRateChangedTask(GetPlaybackRate()); + VolumeChangedTask(GetVolume()); + + // Fire the seek request to get the filters to preroll. + seek_pending_ = true; + SetState(kSeeking); + seek_timestamp_ = demuxer_->GetStartTime(); + DoSeek(seek_timestamp_); + } +} + +// This method is called as a result of the client calling Pipeline::Stop() or +// as the result of an error condition. +// We stop the filters in the reverse order. +// +// TODO(scherkus): beware! this can get posted multiple times since we post +// Stop() tasks even if we've already stopped. Perhaps this should no-op for +// additional calls, however most of this logic will be changing. +void Pipeline::StopTask(const PipelineStatusCB& stop_callback) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK(!IsPipelineStopPending()); + DCHECK_NE(state_, kStopped); + + if (state_ == kStopped) { + // Already stopped so just run callback. + stop_callback.Run(status_); + return; + } + + if (IsPipelineTearingDown() && error_caused_teardown_) { + // If we are stopping due to SetError(), stop normally instead of + // going to error state and calling |error_callback_|. This converts + // the teardown in progress from an error teardown into one that acts + // like the error never occurred. + base::AutoLock auto_lock(lock_); + status_ = PIPELINE_OK; + error_caused_teardown_ = false; + } + + stop_callback_ = stop_callback; + + stop_pending_ = true; + if (!IsPipelineSeeking() && !IsPipelineTearingDown()) { + // We will tear down pipeline immediately when there is no seek operation + // pending and no teardown in progress. This should include the case where + // we are partially initialized. + TearDownPipeline(); + } +} + +void Pipeline::ErrorChangedTask(PipelineStatus error) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; + + // Suppress executing additional error logic. Note that if we are currently + // performing a normal stop, then we return immediately and continue the + // normal stop. + if (IsPipelineStopped() || IsPipelineTearingDown()) { + return; + } + + base::AutoLock auto_lock(lock_); + status_ = error; + + error_caused_teardown_ = true; + + // Posting TearDownPipeline() to message loop so that we can make sure + // it runs after any pending callbacks that are already queued. + // |tearing_down_| is set early here to make sure that pending callbacks + // don't modify the state before TeadDownPipeline() can run. + tearing_down_ = true; + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::TearDownPipeline, this)); +} + +void Pipeline::PlaybackRateChangedTask(float playback_rate) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + + if (!running_ || tearing_down_) + return; + + // Suppress rate change until after seeking. + if (IsPipelineSeeking()) { + pending_playback_rate_ = playback_rate; + playback_rate_change_pending_ = true; + return; + } + + { + base::AutoLock auto_lock(lock_); + clock_->SetPlaybackRate(playback_rate); + } + + // Notify |pipeline_filter_| if it has been initialized. If initialization + // hasn't completed yet, the playback rate will be set when initialization + // completes. + if (pipeline_filter_) { + DCHECK(demuxer_); + demuxer_->SetPlaybackRate(playback_rate); + pipeline_filter_->SetPlaybackRate(playback_rate); + } +} + +void Pipeline::VolumeChangedTask(float volume) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + if (!running_ || tearing_down_) + return; + + if (audio_renderer_) + audio_renderer_->SetVolume(volume); +} + +void Pipeline::PreloadChangedTask(Preload preload) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + if (!running_ || tearing_down_) + return; + + if (demuxer_) + demuxer_->SetPreload(preload); +} + +void Pipeline::SeekTask(base::TimeDelta time, + const PipelineStatusCB& seek_callback) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK(!IsPipelineStopPending()); + + // Suppress seeking if we're not fully started. + if (state_ != kStarted && state_ != kEnded) { + // TODO(scherkus): should we run the callback? I'm tempted to say the API + // will only execute the first Seek() request. + VLOG(1) << "Media pipeline has not started, ignoring seek to " + << time.InMicroseconds(); + return; + } + + DCHECK(!seek_pending_); + seek_pending_ = true; + + // We'll need to pause every filter before seeking. The state transition + // is as follows: + // kStarted/kEnded + // kPausing (for each filter) + // kSeeking (for each filter) + // kStarting (for each filter) + // kStarted + SetState(kPausing); + seek_timestamp_ = time; + seek_callback_ = seek_callback; + + // Kick off seeking! + { + base::AutoLock auto_lock(lock_); + if (clock_->IsPlaying()) + clock_->Pause(); + } + pipeline_filter_->Pause( + base::Bind(&Pipeline::OnFilterStateTransition, this)); +} + +void Pipeline::NotifyEndedTask() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + + // We can only end if we were actually playing. + if (state_ != kStarted) { + return; + } + + DCHECK(audio_renderer_ || video_renderer_); + + // Make sure every extant renderer has ended. + if (audio_renderer_ && !audio_disabled_) { + if (!audio_renderer_->HasEnded()) { + return; + } + + // Start clock since there is no more audio to + // trigger clock updates. + base::AutoLock auto_lock(lock_); + StartClockIfWaitingForTimeUpdate_Locked(); + } + + if (video_renderer_ && !video_renderer_->HasEnded()) { + return; + } + + // Transition to ended, executing the callback if present. + SetState(kEnded); + { + base::AutoLock auto_lock(lock_); + clock_->Pause(); + clock_->SetTime(duration_); + } + + if (!ended_callback_.is_null()) { + ended_callback_.Run(status_); + } +} + +void Pipeline::NotifyNetworkEventTask(NetworkEvent type) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + if (!network_callback_.is_null()) + network_callback_.Run(type); +} + +void Pipeline::DisableAudioRendererTask() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + + base::AutoLock auto_lock(lock_); + has_audio_ = false; + audio_disabled_ = true; + + // Notify all filters of disabled audio renderer. If the filter isn't + // initialized yet, OnAudioRendererDisabled() will be called when + // initialization is complete. + if (pipeline_filter_) { + DCHECK(demuxer_); + demuxer_->OnAudioRendererDisabled(); + pipeline_filter_->OnAudioRendererDisabled(); + } + + // Start clock since there is no more audio to + // trigger clock updates. + StartClockIfWaitingForTimeUpdate_Locked(); +} + +void Pipeline::FilterStateTransitionTask() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + + // No reason transitioning if we've errored or have stopped. + if (IsPipelineStopped()) { + return; + } + + // If we are tearing down, don't allow any state changes. Teardown + // state changes will come in via TeardownStateTransitionTask(). + if (IsPipelineTearingDown()) { + return; + } + + if (!TransientState(state_)) { + NOTREACHED() << "Invalid current state: " << state_; + SetError(PIPELINE_ERROR_ABORT); + return; + } + + // Decrement the number of remaining transitions, making sure to transition + // to the next state if needed. + SetState(FindNextState(state_)); + if (state_ == kSeeking) { + base::AutoLock auto_lock(lock_); + clock_->SetTime(seek_timestamp_); + } + + // Carry out the action for the current state. + if (TransientState(state_)) { + if (state_ == kPausing) { + pipeline_filter_->Pause( + base::Bind(&Pipeline::OnFilterStateTransition, this)); + } else if (state_ == kFlushing) { + pipeline_filter_->Flush( + base::Bind(&Pipeline::OnFilterStateTransition, this)); + } else if (state_ == kSeeking) { + DoSeek(seek_timestamp_); + } else if (state_ == kStarting) { + pipeline_filter_->Play( + base::Bind(&Pipeline::OnFilterStateTransition, this)); + } else if (state_ == kStopping) { + DoStop(base::Bind(&Pipeline::OnFilterStateTransition, this)); + } else { + NOTREACHED() << "Unexpected state: " << state_; + } + } else if (state_ == kStarted) { + FinishInitialization(); + + // Finally, reset our seeking timestamp back to zero. + seek_timestamp_ = base::TimeDelta(); + seek_pending_ = false; + + // If a playback rate change was requested during a seek, do it now that + // the seek has compelted. + if (playback_rate_change_pending_) { + playback_rate_change_pending_ = false; + PlaybackRateChangedTask(pending_playback_rate_); + } + + 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; + if (!has_audio_) + StartClockIfWaitingForTimeUpdate_Locked(); + + // Start monitoring rate of downloading. + int bitrate = 0; + if (demuxer_.get()) { + bitrate = demuxer_->GetBitrate(); + local_source_ = demuxer_->IsLocalSource(); + streaming_ = !demuxer_->IsSeekable(); + } + // Needs to be locked because most other calls to |download_rate_monitor_| + // occur on the renderer thread. + download_rate_monitor_.Start( + base::Bind(&Pipeline::OnCanPlayThrough, this), + bitrate, streaming_, local_source_); + download_rate_monitor_.SetBufferedBytes(buffered_bytes_, base::Time::Now()); + + if (IsPipelineStopPending()) { + // We had a pending stop request need to be honored right now. + TearDownPipeline(); + } + } else { + NOTREACHED() << "Unexpected state: " << state_; + } +} + +void Pipeline::TeardownStateTransitionTask() { + DCHECK(IsPipelineTearingDown()); + switch (state_) { + case kStopping: + SetState(error_caused_teardown_ ? kError : kStopped); + FinishDestroyingFiltersTask(); + break; + case kPausing: + SetState(kFlushing); + pipeline_filter_->Flush( + base::Bind(&Pipeline::OnTeardownStateTransition, this)); + break; + case kFlushing: + SetState(kStopping); + DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); + break; + + case kCreated: + case kError: + case kInitDemuxer: + case kInitAudioDecoder: + case kInitAudioRenderer: + case kInitVideoDecoder: + case kInitVideoRenderer: + case kSeeking: + case kStarting: + case kStopped: + case kStarted: + case kEnded: + NOTREACHED() << "Unexpected state for teardown: " << state_; + break; + // default: intentionally left out to force new states to cause compiler + // errors. + }; +} + +void Pipeline::FinishDestroyingFiltersTask() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK(IsPipelineStopped()); + + // Clear filter references. + audio_renderer_ = NULL; + video_renderer_ = NULL; + demuxer_ = NULL; + + pipeline_filter_ = NULL; + + if (error_caused_teardown_ && !IsPipelineOk() && !error_callback_.is_null()) + error_callback_.Run(status_); + + if (stop_pending_) { + stop_pending_ = false; + ResetState(); + PipelineStatusCB stop_cb; + std::swap(stop_cb, stop_callback_); + // Notify the client that stopping has finished. + if (!stop_cb.is_null()) { + stop_cb.Run(status_); + } + } + + tearing_down_ = false; + error_caused_teardown_ = false; +} + +bool Pipeline::PrepareFilter(scoped_refptr filter) { + bool ret = pipeline_init_state_->composite_->AddFilter(filter.get()); + if (!ret) + SetError(PIPELINE_ERROR_INITIALIZATION_FAILED); + return ret; +} + +void Pipeline::InitializeDemuxer() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK(IsPipelineOk()); + + filter_collection_->GetDemuxerFactory()->Build( + url_, base::Bind(&Pipeline::OnDemuxerBuilt, this)); +} + +void Pipeline::OnDemuxerBuilt(PipelineStatus status, Demuxer* demuxer) { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::OnDemuxerBuilt, this, status, make_scoped_refptr(demuxer))); + return; + } + + if (status != PIPELINE_OK) { + SetError(status); + return; + } + + if (!demuxer) { + SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); + return; + } + + demuxer_ = demuxer; + demuxer_->set_host(this); + + { + base::AutoLock auto_lock(lock_); + // We do not want to start the clock running. We only want to set the base + // media time so our timestamp calculations will be correct. + clock_->SetTime(demuxer_->GetStartTime()); + } + + OnFilterInitialize(PIPELINE_OK); +} + +bool Pipeline::InitializeAudioDecoder( + const scoped_refptr& demuxer) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK(IsPipelineOk()); + + scoped_refptr stream = + demuxer->GetStream(DemuxerStream::AUDIO); + + if (!stream) + return false; + + scoped_refptr audio_decoder; + filter_collection_->SelectAudioDecoder(&audio_decoder); + + if (!audio_decoder) { + SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); + return false; + } + + if (!PrepareFilter(audio_decoder)) + return false; + + pipeline_init_state_->audio_decoder_ = audio_decoder; + audio_decoder->Initialize( + stream, + base::Bind(&Pipeline::OnFilterInitialize, this, PIPELINE_OK), + base::Bind(&Pipeline::OnUpdateStatistics, this)); + return true; +} + +bool Pipeline::InitializeVideoDecoder( + const scoped_refptr& demuxer) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK(IsPipelineOk()); + + scoped_refptr stream; + + if (demuxer) { + stream = demuxer->GetStream(DemuxerStream::VIDEO); + + if (!stream) + return false; + } + + filter_collection_->SelectVideoDecoder(&video_decoder_); + + if (!video_decoder_) { + SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); + return false; + } + + if (!PrepareFilter(video_decoder_)) + return false; + + pipeline_init_state_->video_decoder_ = video_decoder_; + video_decoder_->Initialize( + stream, + base::Bind(&Pipeline::OnFilterInitialize, this), + base::Bind(&Pipeline::OnUpdateStatistics, this)); + return true; +} + +bool Pipeline::InitializeAudioRenderer( + const scoped_refptr& decoder) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK(IsPipelineOk()); + + if (!decoder) + return false; + + filter_collection_->SelectAudioRenderer(&audio_renderer_); + if (!audio_renderer_) { + SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); + return false; + } + + if (!PrepareFilter(audio_renderer_)) + return false; + + audio_renderer_->Initialize( + decoder, + base::Bind(&Pipeline::OnFilterInitialize, this, PIPELINE_OK), + base::Bind(&Pipeline::OnAudioUnderflow, this)); + return true; +} + +bool Pipeline::InitializeVideoRenderer( + const scoped_refptr& decoder) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK(IsPipelineOk()); + + if (!decoder) + return false; + + filter_collection_->SelectVideoRenderer(&video_renderer_); + if (!video_renderer_) { + SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); + return false; + } + + if (!PrepareFilter(video_renderer_)) + return false; + + video_renderer_->Initialize( + decoder, + base::Bind(&Pipeline::OnFilterInitialize, this, PIPELINE_OK), + base::Bind(&Pipeline::OnUpdateStatistics, this)); + return true; +} + +void Pipeline::TearDownPipeline() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + DCHECK_NE(kStopped, state_); + + DCHECK(!tearing_down_ || // Teardown on Stop(). + (tearing_down_ && error_caused_teardown_) || // Teardown on error. + (tearing_down_ && stop_pending_)); // Stop during teardown by error. + + // Mark that we already start tearing down operation. + tearing_down_ = true; + + switch (state_) { + case kCreated: + case kError: + SetState(kStopped); + // Need to put this in the message loop to make sure that it comes + // after any pending callback tasks that are already queued. + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::FinishDestroyingFiltersTask, this)); + break; + + case kInitDemuxer: + case kInitAudioDecoder: + case kInitAudioRenderer: + case kInitVideoDecoder: + case kInitVideoRenderer: + // Make it look like initialization was successful. + pipeline_filter_ = pipeline_init_state_->composite_; + pipeline_init_state_.reset(); + filter_collection_.reset(); + + SetState(kStopping); + DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); + + FinishInitialization(); + break; + + case kPausing: + case kSeeking: + case kFlushing: + case kStarting: + SetState(kStopping); + DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); + + if (seek_pending_) { + seek_pending_ = false; + FinishInitialization(); + } + + break; + + case kStarted: + case kEnded: + SetState(kPausing); + pipeline_filter_->Pause( + base::Bind(&Pipeline::OnTeardownStateTransition, this)); + break; + + case kStopping: + case kStopped: + NOTREACHED() << "Unexpected state for teardown: " << state_; + break; + // default: intentionally left out to force new states to cause compiler + // errors. + }; +} + +void Pipeline::DoStop(const base::Closure& callback) { + if (demuxer_) { + demuxer_->Stop(base::Bind( + &Pipeline::OnDemuxerStopDone, this, callback)); + return; + } + + OnDemuxerStopDone(callback); +} + +void Pipeline::OnDemuxerStopDone(const base::Closure& callback) { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::OnDemuxerStopDone, this, callback)); + return; + } + + if (pipeline_filter_) { + pipeline_filter_->Stop(callback); + return; + } + + callback.Run(); + +} + +void Pipeline::DoSeek(base::TimeDelta seek_timestamp) { + // TODO(acolwell) : We might be able to convert this if (demuxer_) into a + // DCHECK(). Further investigation is needed to make sure this won't introduce + // a bug. + if (demuxer_) { + demuxer_->Seek(seek_timestamp, + base::Bind(&Pipeline::OnDemuxerSeekDone, + this, + seek_timestamp)); + return; + } + + OnDemuxerSeekDone(seek_timestamp, PIPELINE_OK); +} + +void Pipeline::OnDemuxerSeekDone(base::TimeDelta seek_timestamp, + PipelineStatus status) { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::OnDemuxerSeekDone, this, seek_timestamp, status)); + return; + } + + PipelineStatusCB done_cb = + base::Bind(&Pipeline::OnFilterStateTransitionWithStatus, this); + + if (status == PIPELINE_OK && pipeline_filter_) { + pipeline_filter_->Seek(seek_timestamp, done_cb); + return; + } + + done_cb.Run(status); +} + +void Pipeline::OnAudioUnderflow() { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::OnAudioUnderflow, this)); + return; + } + + if (state_ != kStarted) + return; + + if (audio_renderer_) + audio_renderer_->ResumeAfterUnderflow(true); +} + +void Pipeline::OnCanPlayThrough() { + message_loop_->PostTask(FROM_HERE, base::Bind( + &Pipeline::NotifyCanPlayThrough, this)); +} + +void Pipeline::NotifyCanPlayThrough() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + NotifyNetworkEventTask(CAN_PLAY_THROUGH); +} + +void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { + lock_.AssertAcquired(); + if (!waiting_for_clock_update_) + return; + + waiting_for_clock_update_ = false; + clock_->Play(); +} + +} // namespace media diff --git a/media/base/pipeline.h b/media/base/pipeline.h index 7d382d0..1c6e102 100644 --- a/media/base/pipeline.h +++ b/media/base/pipeline.h @@ -2,32 +2,46 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// The pipeline is the public API clients use for playing back media. Clients -// provide a filter collection containing the filters they want the pipeline to -// use to render media. - #ifndef MEDIA_BASE_PIPELINE_H_ #define MEDIA_BASE_PIPELINE_H_ #include +#include "base/gtest_prod_util.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "media/base/demuxer.h" +#include "media/base/download_rate_monitor.h" +#include "media/base/filter_host.h" #include "media/base/media_export.h" #include "media/base/pipeline_status.h" #include "media/base/preload.h" #include "ui/gfx/size.h" +class MessageLoop; + namespace base { class TimeDelta; } namespace media { +class AudioDecoder; +class AudioRenderer; +class Clock; +class Filter; +class FilterCollection; +class MediaLog; +class VideoDecoder; +class VideoRenderer; + +// TODO(scherkus): this should be moved alongside host interface defintions. struct PipelineStatistics { - PipelineStatistics() : - audio_bytes_decoded(0), - video_bytes_decoded(0), - video_frames_decoded(0), - video_frames_dropped(0) { + PipelineStatistics() + : audio_bytes_decoded(0), + video_bytes_decoded(0), + video_frames_decoded(0), + video_frames_dropped(0) { } uint32 audio_bytes_decoded; // Should be uint64? @@ -42,10 +56,77 @@ enum NetworkEvent { CAN_PLAY_THROUGH }; -class FilterCollection; +// Adapter for using asynchronous Pipeline methods in code that wants to run +// synchronously. To use, construct an instance of this class and pass the +// |Callback()| to the Pipeline method requiring a callback. Then Wait() for +// the callback to get fired and call status() to see what the callback's +// argument was. This object is for one-time use; call |Callback()| exactly +// once. +class MEDIA_EXPORT PipelineStatusNotification { + public: + PipelineStatusNotification(); + ~PipelineStatusNotification(); + + // See class-level comment for usage. + PipelineStatusCB Callback(); + void Wait(); + PipelineStatus status(); + + private: + void Notify(media::PipelineStatus status); -class MEDIA_EXPORT Pipeline : public base::RefCountedThreadSafe { + base::Lock lock_; + base::ConditionVariable cv_; + media::PipelineStatus status_; + bool notified_; + + DISALLOW_COPY_AND_ASSIGN(PipelineStatusNotification); +}; + +// Pipeline runs the media pipeline. Filters are created and called on the +// message loop injected into this object. Pipeline works like a state +// machine to perform asynchronous initialization, pausing, seeking and playing. +// +// Here's a state diagram that describes the lifetime of this object. +// +// [ *Created ] [ Stopped ] +// | Start() ^ +// V SetError() | +// [ InitXXX (for each filter) ] -------->[ Stopping (for each filter) ] +// | ^ +// V | if Stop +// [ Seeking (for each filter) ] <--------[ Flushing (for each filter) ] +// | if Seek ^ +// V | +// [ Starting (for each filter) ] | +// | | +// V Seek()/Stop() | +// [ Started ] -------------------------> [ Pausing (for each filter) ] +// | ^ +// | NotifyEnded() Seek()/Stop() | +// `-------------> [ Ended ] ---------------------' +// ^ SetError() +// | +// [ Any State Other Than InitXXX ] + +// +// Initialization is a series of state transitions from "Created" through each +// filter initialization state. When all filter initialization states have +// completed, we are implicitly in a "Paused" state. At that point we simulate +// a Seek() to the beginning of the media to give filters a chance to preroll. +// From then on the normal Seek() transitions are carried out and we start +// playing the media. +// +// If any error ever happens, this object will transition to the "Error" state +// from any state. If Stop() is ever called, this object will transition to +// "Stopped" state. +class MEDIA_EXPORT Pipeline + : public base::RefCountedThreadSafe, + public FilterHost, + public DemuxerHost { public: + Pipeline(MessageLoop* message_loop, MediaLog* media_log); + // Callback that executes when a network event occurrs. // The parameter specifies the type of event that is being signaled. typedef base::Callback NetworkEventCB; @@ -54,9 +135,9 @@ class MEDIA_EXPORT Pipeline : public base::RefCountedThreadSafe { // |ended_callback| will be executed when the media reaches the end. // |error_callback_| will be executed upon an error in the pipeline. // |network_callback_| will be executed when there's a network event. - virtual void Init(const PipelineStatusCB& ended_callback, - const PipelineStatusCB& error_callback, - const NetworkEventCB& network_callback) = 0; + void Init(const PipelineStatusCB& ended_callback, + const PipelineStatusCB& error_callback, + const NetworkEventCB& network_callback); // Build a pipeline to render the given URL using the given filter collection // to construct a filter chain. Returns true if successful, false otherwise @@ -69,9 +150,9 @@ class MEDIA_EXPORT Pipeline : public base::RefCountedThreadSafe { // This method is asynchronous and can execute a callback when completed. // If the caller provides a |start_callback|, it will be called when the // pipeline initialization completes. - virtual bool Start(scoped_ptr filter_collection, - const std::string& url, - const PipelineStatusCB& start_callback) = 0; + bool Start(scoped_ptr filter_collection, + const std::string& url, + const PipelineStatusCB& start_callback); // Asynchronously stops the pipeline and resets it to an uninitialized state. // If provided, |stop_callback| will be executed when the pipeline has been @@ -83,98 +164,473 @@ class MEDIA_EXPORT Pipeline : public base::RefCountedThreadSafe { // // TODO(scherkus): ideally clients would destroy the pipeline after calling // Stop() and create a new pipeline as needed. - virtual void Stop(const PipelineStatusCB& stop_callback) = 0; + void Stop(const PipelineStatusCB& stop_callback); // Attempt to seek to the position specified by time. |seek_callback| will be // executed when the all filters in the pipeline have processed the seek. // // Clients are expected to call GetCurrentTime() to check whether the seek // succeeded. - virtual void Seek(base::TimeDelta time, - const PipelineStatusCB& seek_callback) = 0; + void Seek(base::TimeDelta time, const PipelineStatusCB& seek_callback); // Returns true if the pipeline has been started via Start(). If IsRunning() // returns true, it is expected that Stop() will be called before destroying // the pipeline. - virtual bool IsRunning() const = 0; + bool IsRunning() const; // Returns true if the pipeline has been started and fully initialized to a // point where playback controls will be respected. Note that it is possible // for a pipeline to be started but not initialized (i.e., an error occurred). - virtual bool IsInitialized() const = 0; + bool IsInitialized() const; // Returns true if the media has audio. - virtual bool HasAudio() const = 0; + bool HasAudio() const; // Returns true if the media has video. - virtual bool HasVideo() const = 0; + bool HasVideo() const; // Gets the current playback rate of the pipeline. When the pipeline is // started, the playback rate will be 0.0f. A rate of 1.0f indicates // that the pipeline is rendering the media at the standard rate. Valid // values for playback rate are >= 0.0f. - virtual float GetPlaybackRate() const = 0; + float GetPlaybackRate() const; // Attempt to adjust the playback rate. Setting a playback rate of 0.0f pauses // all rendering of the media. A rate of 1.0f indicates a normal playback // rate. Values for the playback rate must be greater than or equal to 0.0f. // // TODO(scherkus): What about maximum rate? Does HTML5 specify a max? - virtual void SetPlaybackRate(float playback_rate) = 0; + void SetPlaybackRate(float playback_rate); // Gets the current volume setting being used by the audio renderer. When // the pipeline is started, this value will be 1.0f. Valid values range // from 0.0f to 1.0f. - virtual float GetVolume() const = 0; + float GetVolume() const; // Attempt to set the volume of the audio renderer. Valid values for volume // range from 0.0f (muted) to 1.0f (full volume). This value affects all // channels proportionately for multi-channel audio streams. - virtual void SetVolume(float volume) = 0; + void SetVolume(float volume); // Set the preload value for the pipeline. - virtual void SetPreload(Preload preload) = 0; + void SetPreload(Preload preload); // Gets the current pipeline time. For a pipeline "time" progresses from 0 to // the end of the media. - virtual base::TimeDelta GetCurrentTime() const = 0; + base::TimeDelta GetCurrentTime() const; // Get the approximate amount of playable data buffered so far in micro- // seconds. - virtual base::TimeDelta GetBufferedTime() = 0; + base::TimeDelta GetBufferedTime(); // Get the duration of the media in microseconds. If the duration has not // been determined yet, then returns 0. - virtual base::TimeDelta GetMediaDuration() const = 0; + base::TimeDelta GetMediaDuration() const; // Get the total number of bytes that are buffered on the client and ready to // be played. - virtual int64 GetBufferedBytes() const = 0; + int64 GetBufferedBytes() const; // Get the total size of the media file. If the size has not yet been // determined or can not be determined, this value is 0. - virtual int64 GetTotalBytes() const = 0; + int64 GetTotalBytes() const; // Gets the natural size of the video output in pixel units. If there is no // video or the video has not been rendered yet, the width and height will // be 0. - virtual void GetNaturalVideoSize(gfx::Size* out_size) const = 0; + void GetNaturalVideoSize(gfx::Size* out_size) const; // If this method returns true, that means the data source is a streaming // data source. Seeking may not be possible. - virtual bool IsStreaming() const = 0; + bool IsStreaming() const; // If this method returns true, that means the data source is local and // the network is not needed. - virtual bool IsLocalSource() const = 0; + bool IsLocalSource() const; // Gets the current pipeline statistics. - virtual PipelineStatistics GetStatistics() const = 0; + PipelineStatistics GetStatistics() const; + + void SetClockForTesting(Clock* clock); + + private: + FRIEND_TEST_ALL_PREFIXES(PipelineTest, GetBufferedTime); + friend class MediaLog; - protected: // Only allow ourselves to be deleted by reference counting. friend class base::RefCountedThreadSafe; - virtual ~Pipeline() {} + virtual ~Pipeline(); + + // Pipeline states, as described above. + enum State { + kCreated, + kInitDemuxer, + kInitAudioDecoder, + kInitAudioRenderer, + kInitVideoDecoder, + kInitVideoRenderer, + kPausing, + kSeeking, + kFlushing, + kStarting, + kStarted, + kEnded, + kStopping, + kStopped, + kError, + }; + + // Reset the state of the pipeline object to the initial state. This method + // is used by the constructor, and the Stop() method. + void ResetState(); + + // Updates |state_|. All state transitions should use this call. + void SetState(State next_state); + + // Simple method used to make sure the pipeline is running normally. + bool IsPipelineOk(); + + // Helper method to tell whether we are stopped or in error. + bool IsPipelineStopped(); + + // Helper method to tell whether we are in transition to stop state. + bool IsPipelineTearingDown(); + + // We could also be delayed by a transition during seek is performed. + bool IsPipelineStopPending(); + + // Helper method to tell whether we are in transition to seek state. + bool IsPipelineSeeking(); + + // Helper method to execute callback from Start() and reset + // |filter_collection_|. Called when initialization completes + // normally or when pipeline is stopped or error occurs during + // initialization. + void FinishInitialization(); + + // Returns true if the given state is one that transitions to a new state + // after iterating through each filter. + static bool TransientState(State state); + + // Given the current state, returns the next state. + State FindNextState(State current); + + // DataSourceHost (by way of DemuxerHost) implementation. + virtual void SetTotalBytes(int64 total_bytes) OVERRIDE; + virtual void SetBufferedBytes(int64 buffered_bytes) OVERRIDE; + virtual void SetNetworkActivity(bool is_downloading_data) OVERRIDE; + + // DemuxerHost implementaion. + virtual void SetDuration(base::TimeDelta duration) OVERRIDE; + virtual void SetBufferedTime(base::TimeDelta buffered_time) OVERRIDE; + virtual void SetCurrentReadPosition(int64 offset) OVERRIDE; + virtual void OnDemuxerError(PipelineStatus error) OVERRIDE; + + // FilterHost implementation. + virtual void SetError(PipelineStatus error) OVERRIDE; + virtual base::TimeDelta GetTime() const OVERRIDE; + virtual base::TimeDelta GetDuration() const OVERRIDE; + virtual void SetTime(base::TimeDelta time) OVERRIDE; + virtual void SetNaturalVideoSize(const gfx::Size& size) OVERRIDE; + virtual void NotifyEnded() OVERRIDE; + virtual void DisableAudioRenderer() OVERRIDE; + + // Callbacks executed by filters upon completing initialization. + void OnFilterInitialize(PipelineStatus status); + + // Callback executed by filters upon completing Play(), Pause(), or Stop(). + void OnFilterStateTransition(); + + // Callback executed by filters upon completing Seek(). + void OnFilterStateTransitionWithStatus(PipelineStatus status); + + // Callback executed by filters when completing teardown operations. + void OnTeardownStateTransition(); + + // Callback executed by filters to update statistics. + void OnUpdateStatistics(const PipelineStatistics& stats); + + // The following "task" methods correspond to the public methods, but these + // methods are run as the result of posting a task to the PipelineInternal's + // message loop. + void StartTask(scoped_ptr filter_collection, + const std::string& url, + const PipelineStatusCB& start_callback); + + // InitializeTask() performs initialization in multiple passes. It is executed + // as a result of calling Start() or InitializationComplete() that advances + // initialization to the next state. It works as a hub of state transition for + // initialization. One stage communicates its status to the next through + // |last_stage_status|. + void InitializeTask(PipelineStatus last_stage_status); + + // Stops and destroys all filters, placing the pipeline in the kStopped state. + void StopTask(const PipelineStatusCB& stop_callback); + + // Carries out stopping and destroying all filters, placing the pipeline in + // the kError state. + void ErrorChangedTask(PipelineStatus error); + + // Carries out notifying filters that the playback rate has changed. + void PlaybackRateChangedTask(float playback_rate); + + // Carries out notifying filters that the volume has changed. + void VolumeChangedTask(float volume); + + // Returns media preload value. + virtual Preload GetPreload() const; + + // Carries out notifying filters that the preload value has changed. + void PreloadChangedTask(Preload preload); + + // Carries out notifying filters that we are seeking to a new timestamp. + void SeekTask(base::TimeDelta time, const PipelineStatusCB& seek_callback); + + // Carries out handling a notification from a filter that it has ended. + void NotifyEndedTask(); + + // Carries out handling a notification of network event. + void NotifyNetworkEventTask(NetworkEvent type); + + // Carries out disabling the audio renderer. + void DisableAudioRendererTask(); + + // Carries out advancing to the next filter during Play()/Pause()/Seek(). + void FilterStateTransitionTask(); + + // Carries out advancing to the next teardown operation. + void TeardownStateTransitionTask(); + + // Carries out stopping filter threads, deleting filters, running + // appropriate callbacks, and setting the appropriate pipeline state + // depending on whether we performing Stop() or SetError(). + // Called after all filters have been stopped. + void FinishDestroyingFiltersTask(); + + // Internal methods used in the implementation of the pipeline thread. All + // of these methods are only called on the pipeline thread. + + // PrepareFilter() creates the filter's thread and injects a FilterHost and + // MessageLoop. + bool PrepareFilter(scoped_refptr filter); + + // The following initialize methods are used to select a specific type of + // Filter object from FilterCollection and initialize it asynchronously. + void InitializeDemuxer(); + void OnDemuxerBuilt(PipelineStatus status, Demuxer* demuxer); + + // Returns true if the asynchronous action of creating decoder has started. + // Returns false if this method did nothing because the corresponding + // audio/video stream does not exist. + bool InitializeAudioDecoder(const scoped_refptr& demuxer); + bool InitializeVideoDecoder(const scoped_refptr& demuxer); + + // Initializes a renderer and connects it with decoder. Returns true if the + // asynchronous action of creating renderer has started. Returns + // false if this method did nothing because the corresponding audio/video + // stream does not exist. + bool InitializeAudioRenderer(const scoped_refptr& decoder); + bool InitializeVideoRenderer(const scoped_refptr& decoder); + + // Kicks off destroying filters. Called by StopTask() and ErrorChangedTask(). + // When we start to tear down the pipeline, we will consider two cases: + // 1. when pipeline has not been initialized, we will transit to stopping + // state first. + // 2. when pipeline has been initialized, we will first transit to pausing + // => flushing => stopping => stopped state. + // This will remove the race condition during stop between filters. + void TearDownPipeline(); + + // Compute the current time. Assumes that the lock has been acquired by the + // caller. + base::TimeDelta GetCurrentTime_Locked() const; + + // Initiates a Stop() on |demuxer_| & |pipeline_filter_|. |callback| + // is called once both objects have been stopped. + void DoStop(const base::Closure& callback); + + // Called when |demuxer_| has stopped. This method calls Stop() + // on |pipeline_filter_|. + void OnDemuxerStopDone(const base::Closure& callback); + + // Initiates a Seek() on the |demuxer_| & |pipeline_filter_|. + void DoSeek(base::TimeDelta seek_timestamp); + + // Called when |demuxer_| finishes seeking. If seeking was successful, + // then Seek() is called on |pipeline_filter_|. + void OnDemuxerSeekDone(base::TimeDelta seek_timestamp, + PipelineStatus status); + + void OnAudioUnderflow(); + + // Called when |download_rate_monitor_| believes that the media can + // be played through without needing to pause to buffer. + void OnCanPlayThrough(); + + // Carries out the notification that the media can be played through without + // needing to pause to buffer. + void NotifyCanPlayThrough(); + + void StartClockIfWaitingForTimeUpdate_Locked(); + + // Message loop used to execute pipeline tasks. + MessageLoop* message_loop_; + + // MediaLog to which to log events. + scoped_refptr media_log_; + + // Lock used to serialize access for the following data members. + mutable base::Lock lock_; + + // Whether or not the pipeline is running. + bool running_; + + // Whether or not the pipeline is in transition for a seek operation. + bool seek_pending_; + + // Whether or not the pipeline is pending a stop operation. + bool stop_pending_; + + // Whether or not the pipeline is perform a stop operation. + bool tearing_down_; + + // Whether or not an error triggered the teardown. + bool error_caused_teardown_; + + // Whether or not a playback rate change should be done once seeking is done. + bool playback_rate_change_pending_; + + // Duration of the media in microseconds. Set by filters. + base::TimeDelta duration_; + + // Amount of available buffered data in microseconds. Set by filters. + base::TimeDelta buffered_time_; + + // Amount of available buffered data. Set by filters. + int64 buffered_bytes_; + + // Total size of the media. Set by filters. + int64 total_bytes_; + + // Video's natural width and height. Set by filters. + gfx::Size natural_size_; + + // Set by the demuxer to indicate whether the data source is a streaming + // source. + bool streaming_; + + // Indicates whether the data source is local, such as a local media file + // from disk or a local webcam stream. + bool local_source_; + + // Current volume level (from 0.0f to 1.0f). This value is set immediately + // via SetVolume() and a task is dispatched on the message loop to notify the + // filters. + float volume_; + + // Current value of preload attribute. This value is set immediately via + // SetPreload() and a task is dispatched on the message loop to notify the + // filters. + Preload preload_; + + // Current playback rate (>= 0.0f). This value is set immediately via + // SetPlaybackRate() and a task is dispatched on the message loop to notify + // the filters. + float playback_rate_; + + // Playback rate to set when the current seek has finished. + float pending_playback_rate_; + + // Reference clock. Keeps track of current playback time. Uses system + // clock and linear interpolation, but can have its time manually set + // by filters. + scoped_ptr clock_; + + // If this value is set to true, then |clock_| is paused and we are waiting + // for an update of the clock greater than or equal to the elapsed time to + // start the clock. + bool waiting_for_clock_update_; + + // Status of the pipeline. Initialized to PIPELINE_OK which indicates that + // the pipeline is operating correctly. Any other value indicates that the + // pipeline is stopped or is stopping. Clients can call the Stop() method to + // reset the pipeline state, and restore this to PIPELINE_OK. + PipelineStatus status_; + + // Whether the media contains rendered audio and video streams. + bool has_audio_; + bool has_video_; + + // The following data members are only accessed by tasks posted to + // |message_loop_|. + + // Member that tracks the current state. + State state_; + + // For kSeeking we need to remember where we're seeking between filter + // replies. + base::TimeDelta seek_timestamp_; + + // For GetCurrentBytes()/SetCurrentBytes() we need to know what byte we are + // currently reading. + int64 current_bytes_; + + // Set to true in DisableAudioRendererTask(). + bool audio_disabled_; + + // Keep track of the maximum buffered position so the buffering appears + // smooth. + // TODO(vrk): This is a hack. + base::TimeDelta max_buffered_time_; + + // Filter collection as passed in by Start(). + scoped_ptr filter_collection_; + + // URL for the data source as passed in by Start(). + std::string url_; + + // Callbacks for various pipeline operations. + PipelineStatusCB seek_callback_; + PipelineStatusCB stop_callback_; + PipelineStatusCB ended_callback_; + PipelineStatusCB error_callback_; + NetworkEventCB network_callback_; + + // Reference to the filter(s) that constitute the pipeline. + scoped_refptr pipeline_filter_; + + // Decoder reference used for signalling imminent shutdown. + // This is a HACK necessary because WebMediaPlayerImpl::Destroy() holds the + // renderer thread loop hostage for until PipelineImpl::Stop() calls its + // callback. http://crbug.com/110228 tracks removing this hack. + scoped_refptr video_decoder_; + + // Renderer references used for setting the volume and determining + // when playback has finished. + scoped_refptr audio_renderer_; + scoped_refptr video_renderer_; + + // Demuxer reference used for setting the preload value. + scoped_refptr demuxer_; + + // Helper class that stores filter references during pipeline + // initialization. + class PipelineInitState; + scoped_ptr pipeline_init_state_; + + // Statistics. + PipelineStatistics statistics_; + // Time of pipeline creation; is non-zero only until the pipeline first + // reaches "kStarted", at which point it is used & zeroed out. + base::Time creation_time_; + + // Approximates the rate at which the media is being downloaded. + DownloadRateMonitor download_rate_monitor_; + + // True if the pipeline is actively downloading bytes, false otherwise. + bool is_downloading_data_; + + DISALLOW_COPY_AND_ASSIGN(Pipeline); }; } // namespace media diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc deleted file mode 100644 index 307976e..0000000 --- a/media/base/pipeline_impl.cc +++ /dev/null @@ -1,1448 +0,0 @@ -// Copyright (c) 2012 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. -// -// TODO(scherkus): clean up PipelineImpl... too many crazy function names, -// potential deadlocks, etc... - -#include "media/base/pipeline_impl.h" - -#include - -#include "base/bind.h" -#include "base/callback.h" -#include "base/compiler_specific.h" -#include "base/metrics/histogram.h" -#include "base/stl_util.h" -#include "base/string_util.h" -#include "base/synchronization/condition_variable.h" -#include "media/base/clock.h" -#include "media/base/filter_collection.h" -#include "media/base/media_log.h" - -namespace media { - -PipelineStatusNotification::PipelineStatusNotification() - : cv_(&lock_), status_(PIPELINE_OK), notified_(false) { -} - -PipelineStatusNotification::~PipelineStatusNotification() { - DCHECK(notified_); -} - -PipelineStatusCB PipelineStatusNotification::Callback() { - return base::Bind(&PipelineStatusNotification::Notify, - base::Unretained(this)); -} - -void PipelineStatusNotification::Notify(media::PipelineStatus status) { - base::AutoLock auto_lock(lock_); - DCHECK(!notified_); - notified_ = true; - status_ = status; - cv_.Signal(); -} - -void PipelineStatusNotification::Wait() { - base::AutoLock auto_lock(lock_); - while (!notified_) - cv_.Wait(); -} - -media::PipelineStatus PipelineStatusNotification::status() { - base::AutoLock auto_lock(lock_); - DCHECK(notified_); - return status_; -} - -class PipelineImpl::PipelineInitState { - public: - scoped_refptr audio_decoder_; - scoped_refptr video_decoder_; - scoped_refptr composite_; -}; - -PipelineImpl::PipelineImpl(MessageLoop* message_loop, MediaLog* media_log) - : message_loop_(message_loop), - media_log_(media_log), - clock_(new Clock(&base::Time::Now)), - waiting_for_clock_update_(false), - state_(kCreated), - current_bytes_(0), - creation_time_(base::Time::Now()), - is_downloading_data_(false) { - media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); - ResetState(); - media_log_->AddEvent( - media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED)); -} - -PipelineImpl::~PipelineImpl() { - base::AutoLock auto_lock(lock_); - DCHECK(!running_) << "Stop() must complete before destroying object"; - DCHECK(!stop_pending_); - DCHECK(!seek_pending_); - - media_log_->AddEvent( - media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED)); -} - -void PipelineImpl::Init(const PipelineStatusCB& ended_callback, - const PipelineStatusCB& error_callback, - const NetworkEventCB& network_callback) { - DCHECK(!IsRunning()) - << "Init() should be called before the pipeline has started"; - ended_callback_ = ended_callback; - error_callback_ = error_callback; - network_callback_ = network_callback; -} - -// Creates the PipelineInternal and calls it's start method. -bool PipelineImpl::Start(scoped_ptr collection, - const std::string& url, - const PipelineStatusCB& start_callback) { - base::AutoLock auto_lock(lock_); - - if (running_) { - VLOG(1) << "Media pipeline is already running"; - return false; - } - - if (collection->IsEmpty()) { - return false; - } - - // Kick off initialization! - running_ = true; - message_loop_->PostTask( - FROM_HERE, - base::Bind(&PipelineImpl::StartTask, this, - base::Passed(&collection), - url, - start_callback)); - return true; -} - -void PipelineImpl::Stop(const PipelineStatusCB& stop_callback) { - base::AutoLock auto_lock(lock_); - if (!running_) { - VLOG(1) << "Media pipeline has already stopped"; - return; - } - - if (video_decoder_) { - video_decoder_->PrepareForShutdownHack(); - video_decoder_ = NULL; - } - - // Stop the pipeline, which will set |running_| to false on our behalf. - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::StopTask, this, stop_callback)); -} - -void PipelineImpl::Seek(base::TimeDelta time, - const PipelineStatusCB& seek_callback) { - base::AutoLock auto_lock(lock_); - if (!running_) { - VLOG(1) << "Media pipeline must be running"; - return; - } - - download_rate_monitor_.Stop(); - - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::SeekTask, this, time, seek_callback)); -} - -bool PipelineImpl::IsRunning() const { - base::AutoLock auto_lock(lock_); - return running_; -} - -bool PipelineImpl::IsInitialized() const { - // TODO(scherkus): perhaps replace this with a bool that is set/get under the - // lock, because this is breaching the contract that |state_| is only accessed - // on |message_loop_|. - base::AutoLock auto_lock(lock_); - switch (state_) { - case kPausing: - case kFlushing: - case kSeeking: - case kStarting: - case kStarted: - case kEnded: - return true; - default: - return false; - } -} - -bool PipelineImpl::HasAudio() const { - base::AutoLock auto_lock(lock_); - return has_audio_; -} - -bool PipelineImpl::HasVideo() const { - base::AutoLock auto_lock(lock_); - return has_video_; -} - -float PipelineImpl::GetPlaybackRate() const { - base::AutoLock auto_lock(lock_); - return playback_rate_; -} - -void PipelineImpl::SetPlaybackRate(float playback_rate) { - if (playback_rate < 0.0f) - return; - - base::AutoLock auto_lock(lock_); - playback_rate_ = playback_rate; - if (running_ && !tearing_down_) { - message_loop_->PostTask(FROM_HERE, base::Bind( - &PipelineImpl::PlaybackRateChangedTask, this, playback_rate)); - } -} - -float PipelineImpl::GetVolume() const { - base::AutoLock auto_lock(lock_); - return volume_; -} - -void PipelineImpl::SetVolume(float volume) { - if (volume < 0.0f || volume > 1.0f) - return; - - base::AutoLock auto_lock(lock_); - volume_ = volume; - if (running_ && !tearing_down_) { - message_loop_->PostTask(FROM_HERE, base::Bind( - &PipelineImpl::VolumeChangedTask, this, volume)); - } -} - -Preload PipelineImpl::GetPreload() const { - base::AutoLock auto_lock(lock_); - return preload_; -} - -void PipelineImpl::SetPreload(Preload preload) { - base::AutoLock auto_lock(lock_); - preload_ = preload; - if (running_ && !tearing_down_) { - message_loop_->PostTask(FROM_HERE, base::Bind( - &PipelineImpl::PreloadChangedTask, this, preload)); - } -} - -base::TimeDelta PipelineImpl::GetCurrentTime() const { - base::AutoLock auto_lock(lock_); - return GetCurrentTime_Locked(); -} - -base::TimeDelta PipelineImpl::GetCurrentTime_Locked() const { - lock_.AssertAcquired(); - base::TimeDelta elapsed = clock_->Elapsed(); - if (elapsed > duration_) - return duration_; - - return elapsed; -} - -base::TimeDelta PipelineImpl::GetBufferedTime() { - base::AutoLock auto_lock(lock_); - - // If media is fully loaded, then return duration. - if (local_source_ || total_bytes_ == buffered_bytes_) { - max_buffered_time_ = duration_; - return duration_; - } - - base::TimeDelta current_time = GetCurrentTime_Locked(); - - // If buffered time was set, we report that value directly. - if (buffered_time_.ToInternalValue() > 0) - return std::max(buffered_time_, current_time); - - if (total_bytes_ == 0) - return base::TimeDelta(); - - // If buffered time was not set, we use current time, current bytes, and - // buffered bytes to estimate the buffered time. - double estimated_rate = duration_.InMillisecondsF() / total_bytes_; - double estimated_current_time = estimated_rate * current_bytes_; - DCHECK_GE(buffered_bytes_, current_bytes_); - base::TimeDelta buffered_time = base::TimeDelta::FromMilliseconds( - static_cast(estimated_rate * (buffered_bytes_ - current_bytes_) + - estimated_current_time)); - - // Cap approximated buffered time at the length of the video. - buffered_time = std::min(buffered_time, duration_); - - // Make sure buffered_time is at least the current time - buffered_time = std::max(buffered_time, current_time); - - // Only print the max buffered time for smooth buffering. - max_buffered_time_ = std::max(buffered_time, max_buffered_time_); - - return max_buffered_time_; -} - -base::TimeDelta PipelineImpl::GetMediaDuration() const { - base::AutoLock auto_lock(lock_); - return duration_; -} - -int64 PipelineImpl::GetBufferedBytes() const { - base::AutoLock auto_lock(lock_); - return buffered_bytes_; -} - -int64 PipelineImpl::GetTotalBytes() const { - base::AutoLock auto_lock(lock_); - return total_bytes_; -} - -void PipelineImpl::GetNaturalVideoSize(gfx::Size* out_size) const { - CHECK(out_size); - base::AutoLock auto_lock(lock_); - *out_size = natural_size_; -} - -bool PipelineImpl::IsStreaming() const { - base::AutoLock auto_lock(lock_); - return streaming_; -} - -bool PipelineImpl::IsLocalSource() const { - base::AutoLock auto_lock(lock_); - return local_source_; -} - -PipelineStatistics PipelineImpl::GetStatistics() const { - base::AutoLock auto_lock(lock_); - return statistics_; -} - -void PipelineImpl::SetClockForTesting(Clock* clock) { - clock_.reset(clock); -} - -void PipelineImpl::SetCurrentReadPosition(int64 offset) { - base::AutoLock auto_lock(lock_); - - // The current read position should never be ahead of the buffered byte - // position but threading issues between BufferedDataSource::DoneRead_Locked() - // and BufferedDataSource::NetworkEventCallback() can cause them to be - // temporarily out of sync. The easiest fix for this is to cap both - // buffered_bytes_ and current_bytes_ to always be legal values in - // SetCurrentReadPosition() and in SetBufferedBytes(). - if (offset > buffered_bytes_) - buffered_bytes_ = offset; - current_bytes_ = offset; -} - -void PipelineImpl::ResetState() { - base::AutoLock auto_lock(lock_); - const base::TimeDelta kZero; - running_ = false; - stop_pending_ = false; - seek_pending_ = false; - tearing_down_ = false; - error_caused_teardown_ = false; - playback_rate_change_pending_ = false; - duration_ = kZero; - buffered_time_ = kZero; - buffered_bytes_ = 0; - streaming_ = false; - local_source_ = false; - total_bytes_ = 0; - natural_size_.SetSize(0, 0); - volume_ = 1.0f; - preload_ = AUTO; - playback_rate_ = 0.0f; - pending_playback_rate_ = 0.0f; - status_ = PIPELINE_OK; - has_audio_ = false; - has_video_ = false; - waiting_for_clock_update_ = false; - audio_disabled_ = false; - clock_->SetTime(kZero); - download_rate_monitor_.Reset(); -} - -void PipelineImpl::SetState(State next_state) { - if (state_ != kStarted && next_state == kStarted && - !creation_time_.is_null()) { - UMA_HISTOGRAM_TIMES( - "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_); - creation_time_ = base::Time(); - } - state_ = next_state; - media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); -} - -bool PipelineImpl::IsPipelineOk() { - base::AutoLock auto_lock(lock_); - return status_ == PIPELINE_OK; -} - -bool PipelineImpl::IsPipelineStopped() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - return state_ == kStopped || state_ == kError; -} - -bool PipelineImpl::IsPipelineTearingDown() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - return tearing_down_; -} - -bool PipelineImpl::IsPipelineStopPending() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - return stop_pending_; -} - -bool PipelineImpl::IsPipelineSeeking() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - if (!seek_pending_) - return false; - DCHECK(kSeeking == state_ || kPausing == state_ || - kFlushing == state_ || kStarting == state_) - << "Current state : " << state_; - return true; -} - -void PipelineImpl::FinishInitialization() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - // Execute the seek callback, if present. Note that this might be the - // initial callback passed into Start(). - if (!seek_callback_.is_null()) { - seek_callback_.Run(status_); - seek_callback_.Reset(); - } -} - -// static -bool PipelineImpl::TransientState(State state) { - return state == kPausing || - state == kFlushing || - state == kSeeking || - state == kStarting || - state == kStopping; -} - -// static -PipelineImpl::State PipelineImpl::FindNextState(State current) { - // TODO(scherkus): refactor InitializeTask() to make use of this function. - if (current == kPausing) { - return kFlushing; - } else if (current == kFlushing) { - // We will always honor Seek() before Stop(). This is based on the - // assumption that we never accept Seek() after Stop(). - DCHECK(IsPipelineSeeking() || - IsPipelineStopPending() || - IsPipelineTearingDown()); - return IsPipelineSeeking() ? kSeeking : kStopping; - } else if (current == kSeeking) { - return kStarting; - } else if (current == kStarting) { - return kStarted; - } else if (current == kStopping) { - return error_caused_teardown_ ? kError : kStopped; - } else { - return current; - } -} - -void PipelineImpl::OnDemuxerError(PipelineStatus error) { - SetError(error); -} - -void PipelineImpl::SetError(PipelineStatus error) { - DCHECK(IsRunning()); - DCHECK_NE(PIPELINE_OK, error); - VLOG(1) << "Media pipeline error: " << error; - - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::ErrorChangedTask, this, error)); - - media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error)); -} - -base::TimeDelta PipelineImpl::GetTime() const { - DCHECK(IsRunning()); - return GetCurrentTime(); -} - -base::TimeDelta PipelineImpl::GetDuration() const { - DCHECK(IsRunning()); - return GetMediaDuration(); -} - -void PipelineImpl::SetTime(base::TimeDelta time) { - DCHECK(IsRunning()); - base::AutoLock auto_lock(lock_); - - // If we were waiting for a valid timestamp and such timestamp arrives, we - // need to clear the flag for waiting and start the clock. - if (waiting_for_clock_update_) { - if (time < clock_->Elapsed()) - return; - clock_->SetTime(time); - StartClockIfWaitingForTimeUpdate_Locked(); - return; - } - clock_->SetTime(time); -} - -void PipelineImpl::SetDuration(base::TimeDelta duration) { - DCHECK(IsRunning()); - media_log_->AddEvent( - media_log_->CreateTimeEvent( - MediaLogEvent::DURATION_SET, "duration", duration)); - UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration); - - base::AutoLock auto_lock(lock_); - duration_ = duration; -} - -void PipelineImpl::SetBufferedTime(base::TimeDelta buffered_time) { - DCHECK(IsRunning()); - base::AutoLock auto_lock(lock_); - buffered_time_ = buffered_time; -} - -void PipelineImpl::SetTotalBytes(int64 total_bytes) { - DCHECK(IsRunning()); - media_log_->AddEvent( - media_log_->CreateIntegerEvent( - MediaLogEvent::TOTAL_BYTES_SET, "total_bytes", total_bytes)); - int64 total_mbytes = total_bytes >> 20; - if (total_mbytes > kint32max) - total_mbytes = kint32max; - UMA_HISTOGRAM_CUSTOM_COUNTS( - "Media.TotalMBytes", static_cast(total_mbytes), 1, kint32max, 50); - - base::AutoLock auto_lock(lock_); - total_bytes_ = total_bytes; - download_rate_monitor_.set_total_bytes(total_bytes_); -} - -void PipelineImpl::SetBufferedBytes(int64 buffered_bytes) { - DCHECK(IsRunning()); - base::AutoLock auto_lock(lock_); - // See comments in SetCurrentReadPosition() about capping. - if (buffered_bytes < current_bytes_) - current_bytes_ = buffered_bytes; - buffered_bytes_ = buffered_bytes; - download_rate_monitor_.SetBufferedBytes(buffered_bytes, base::Time::Now()); -} - -void PipelineImpl::SetNaturalVideoSize(const gfx::Size& size) { - DCHECK(IsRunning()); - media_log_->AddEvent(media_log_->CreateVideoSizeSetEvent( - size.width(), size.height())); - - base::AutoLock auto_lock(lock_); - natural_size_ = size; -} - -void PipelineImpl::NotifyEnded() { - DCHECK(IsRunning()); - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::NotifyEndedTask, this)); - media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED)); -} - -void PipelineImpl::SetNetworkActivity(bool is_downloading_data) { - DCHECK(IsRunning()); - - NetworkEvent type = DOWNLOAD_PAUSED; - if (is_downloading_data) - type = DOWNLOAD_CONTINUED; - - { - base::AutoLock auto_lock(lock_); - download_rate_monitor_.SetNetworkActivity(is_downloading_data); - } - - message_loop_->PostTask(FROM_HERE, - base::Bind( - &PipelineImpl::NotifyNetworkEventTask, this, type)); - media_log_->AddEvent( - media_log_->CreateBooleanEvent( - MediaLogEvent::NETWORK_ACTIVITY_SET, - "is_downloading_data", is_downloading_data)); -} - -void PipelineImpl::DisableAudioRenderer() { - DCHECK(IsRunning()); - - // Disable renderer on the message loop. - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::DisableAudioRendererTask, this)); - media_log_->AddEvent( - media_log_->CreateEvent(MediaLogEvent::AUDIO_RENDERER_DISABLED)); -} - -// Called from any thread. -void PipelineImpl::OnFilterInitialize(PipelineStatus status) { - // Continue the initialize task by proceeding to the next stage. - message_loop_->PostTask( - FROM_HERE, base::Bind(&PipelineImpl::InitializeTask, this, status)); -} - -// Called from any thread. -void PipelineImpl::OnFilterStateTransition() { - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::FilterStateTransitionTask, this)); -} - -// Called from any thread. -// This method makes the FilterStatusCB behave like a Closure. It -// makes it look like a host()->SetError() call followed by a call to -// OnFilterStateTransition() when errors occur. -// -// TODO: Revisit this code when SetError() is removed from FilterHost and -// all the Closures are converted to FilterStatusCB. -void PipelineImpl::OnFilterStateTransitionWithStatus(PipelineStatus status) { - if (status != PIPELINE_OK) - SetError(status); - OnFilterStateTransition(); -} - -void PipelineImpl::OnTeardownStateTransition() { - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::TeardownStateTransitionTask, this)); -} - -// Called from any thread. -void PipelineImpl::OnUpdateStatistics(const PipelineStatistics& stats) { - base::AutoLock auto_lock(lock_); - statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; - statistics_.video_bytes_decoded += stats.video_bytes_decoded; - statistics_.video_frames_decoded += stats.video_frames_decoded; - statistics_.video_frames_dropped += stats.video_frames_dropped; - media_log_->QueueStatisticsUpdatedEvent(statistics_); -} - -void PipelineImpl::StartTask(scoped_ptr filter_collection, - const std::string& url, - const PipelineStatusCB& start_callback) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK_EQ(kCreated, state_); - filter_collection_ = filter_collection.Pass(); - url_ = url; - seek_callback_ = start_callback; - - // Kick off initialization. - pipeline_init_state_.reset(new PipelineInitState()); - pipeline_init_state_->composite_ = new CompositeFilter(message_loop_); - pipeline_init_state_->composite_->set_host(this); - - SetState(kInitDemuxer); - InitializeDemuxer(); -} - -// Main initialization method called on the pipeline thread. This code attempts -// to use the specified filter factory to build a pipeline. -// Initialization step performed in this method depends on current state of this -// object, indicated by |state_|. After each step of initialization, this -// object transits to the next stage. It starts by creating a Demuxer, and then -// connects the Demuxer's audio stream to an AudioDecoder which is then -// connected to an AudioRenderer. If the media has video, then it connects a -// VideoDecoder to the Demuxer's video stream, and then connects the -// VideoDecoder to a VideoRenderer. -// -// When all required filters have been created and have called their -// FilterHost's InitializationComplete() method, the pipeline will update its -// state to kStarted and |init_callback_|, will be executed. -// -// TODO(hclam): InitializeTask() is now starting the pipeline asynchronously. It -// works like a big state change table. If we no longer need to start filters -// in order, we need to get rid of all the state change. -void PipelineImpl::InitializeTask(PipelineStatus last_stage_status) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - - if (last_stage_status != PIPELINE_OK) { - // Currently only VideoDecoders have a recoverable error code. - if (state_ == kInitVideoDecoder && - last_stage_status == DECODER_ERROR_NOT_SUPPORTED) { - pipeline_init_state_->composite_->RemoveFilter( - pipeline_init_state_->video_decoder_.get()); - state_ = kInitAudioRenderer; - } else { - SetError(last_stage_status); - } - } - - // If we have received the stop or error signal, return immediately. - if (IsPipelineStopPending() || IsPipelineStopped() || !IsPipelineOk()) - return; - - DCHECK(state_ == kInitDemuxer || - state_ == kInitAudioDecoder || - state_ == kInitAudioRenderer || - state_ == kInitVideoDecoder || - state_ == kInitVideoRenderer); - - // Demuxer created, create audio decoder. - if (state_ == kInitDemuxer) { - SetState(kInitAudioDecoder); - // If this method returns false, then there's no audio stream. - if (InitializeAudioDecoder(demuxer_)) - return; - } - - // Assuming audio decoder was created, create audio renderer. - if (state_ == kInitAudioDecoder) { - SetState(kInitAudioRenderer); - - // Returns false if there's no audio stream. - if (InitializeAudioRenderer(pipeline_init_state_->audio_decoder_)) { - base::AutoLock auto_lock(lock_); - has_audio_ = true; - return; - } - } - - // Assuming audio renderer was created, create video decoder. - if (state_ == kInitAudioRenderer) { - // Then perform the stage of initialization, i.e. initialize video decoder. - SetState(kInitVideoDecoder); - if (InitializeVideoDecoder(demuxer_)) - return; - } - - // Assuming video decoder was created, create video renderer. - if (state_ == kInitVideoDecoder) { - SetState(kInitVideoRenderer); - if (InitializeVideoRenderer(pipeline_init_state_->video_decoder_)) { - base::AutoLock auto_lock(lock_); - has_video_ = true; - return; - } - } - - if (state_ == kInitVideoRenderer) { - if (!IsPipelineOk() || !(HasAudio() || HasVideo())) { - SetError(PIPELINE_ERROR_COULD_NOT_RENDER); - return; - } - - // Clear the collection of filters. - filter_collection_->Clear(); - - pipeline_filter_ = pipeline_init_state_->composite_; - - // Clear init state since we're done initializing. - pipeline_init_state_.reset(); - - if (audio_disabled_) { - // Audio was disabled at some point during initialization. Notify - // the pipeline filter now that it has been initialized. - demuxer_->OnAudioRendererDisabled(); - pipeline_filter_->OnAudioRendererDisabled(); - } - - // Initialization was successful, we are now considered paused, so it's safe - // to set the initial playback rate and volume. - PreloadChangedTask(GetPreload()); - PlaybackRateChangedTask(GetPlaybackRate()); - VolumeChangedTask(GetVolume()); - - // Fire the seek request to get the filters to preroll. - seek_pending_ = true; - SetState(kSeeking); - seek_timestamp_ = demuxer_->GetStartTime(); - DoSeek(seek_timestamp_); - } -} - -// This method is called as a result of the client calling Pipeline::Stop() or -// as the result of an error condition. -// We stop the filters in the reverse order. -// -// TODO(scherkus): beware! this can get posted multiple times since we post -// Stop() tasks even if we've already stopped. Perhaps this should no-op for -// additional calls, however most of this logic will be changing. -void PipelineImpl::StopTask(const PipelineStatusCB& stop_callback) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK(!IsPipelineStopPending()); - DCHECK_NE(state_, kStopped); - - if (state_ == kStopped) { - // Already stopped so just run callback. - stop_callback.Run(status_); - return; - } - - if (IsPipelineTearingDown() && error_caused_teardown_) { - // If we are stopping due to SetError(), stop normally instead of - // going to error state and calling |error_callback_|. This converts - // the teardown in progress from an error teardown into one that acts - // like the error never occurred. - base::AutoLock auto_lock(lock_); - status_ = PIPELINE_OK; - error_caused_teardown_ = false; - } - - stop_callback_ = stop_callback; - - stop_pending_ = true; - if (!IsPipelineSeeking() && !IsPipelineTearingDown()) { - // We will tear down pipeline immediately when there is no seek operation - // pending and no teardown in progress. This should include the case where - // we are partially initialized. - TearDownPipeline(); - } -} - -void PipelineImpl::ErrorChangedTask(PipelineStatus error) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; - - // Suppress executing additional error logic. Note that if we are currently - // performing a normal stop, then we return immediately and continue the - // normal stop. - if (IsPipelineStopped() || IsPipelineTearingDown()) { - return; - } - - base::AutoLock auto_lock(lock_); - status_ = error; - - error_caused_teardown_ = true; - - // Posting TearDownPipeline() to message loop so that we can make sure - // it runs after any pending callbacks that are already queued. - // |tearing_down_| is set early here to make sure that pending callbacks - // don't modify the state before TeadDownPipeline() can run. - tearing_down_ = true; - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::TearDownPipeline, this)); -} - -void PipelineImpl::PlaybackRateChangedTask(float playback_rate) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - - if (!running_ || tearing_down_) - return; - - // Suppress rate change until after seeking. - if (IsPipelineSeeking()) { - pending_playback_rate_ = playback_rate; - playback_rate_change_pending_ = true; - return; - } - - { - base::AutoLock auto_lock(lock_); - clock_->SetPlaybackRate(playback_rate); - } - - // Notify |pipeline_filter_| if it has been initialized. If initialization - // hasn't completed yet, the playback rate will be set when initialization - // completes. - if (pipeline_filter_) { - DCHECK(demuxer_); - demuxer_->SetPlaybackRate(playback_rate); - pipeline_filter_->SetPlaybackRate(playback_rate); - } -} - -void PipelineImpl::VolumeChangedTask(float volume) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - if (!running_ || tearing_down_) - return; - - if (audio_renderer_) - audio_renderer_->SetVolume(volume); -} - -void PipelineImpl::PreloadChangedTask(Preload preload) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - if (!running_ || tearing_down_) - return; - - if (demuxer_) - demuxer_->SetPreload(preload); -} - -void PipelineImpl::SeekTask(base::TimeDelta time, - const PipelineStatusCB& seek_callback) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK(!IsPipelineStopPending()); - - // Suppress seeking if we're not fully started. - if (state_ != kStarted && state_ != kEnded) { - // TODO(scherkus): should we run the callback? I'm tempted to say the API - // will only execute the first Seek() request. - VLOG(1) << "Media pipeline has not started, ignoring seek to " - << time.InMicroseconds(); - return; - } - - DCHECK(!seek_pending_); - seek_pending_ = true; - - // We'll need to pause every filter before seeking. The state transition - // is as follows: - // kStarted/kEnded - // kPausing (for each filter) - // kSeeking (for each filter) - // kStarting (for each filter) - // kStarted - SetState(kPausing); - seek_timestamp_ = time; - seek_callback_ = seek_callback; - - // Kick off seeking! - { - base::AutoLock auto_lock(lock_); - if (clock_->IsPlaying()) - clock_->Pause(); - } - pipeline_filter_->Pause( - base::Bind(&PipelineImpl::OnFilterStateTransition, this)); -} - -void PipelineImpl::NotifyEndedTask() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - - // We can only end if we were actually playing. - if (state_ != kStarted) { - return; - } - - DCHECK(audio_renderer_ || video_renderer_); - - // Make sure every extant renderer has ended. - if (audio_renderer_ && !audio_disabled_) { - if (!audio_renderer_->HasEnded()) { - return; - } - - // Start clock since there is no more audio to - // trigger clock updates. - base::AutoLock auto_lock(lock_); - StartClockIfWaitingForTimeUpdate_Locked(); - } - - if (video_renderer_ && !video_renderer_->HasEnded()) { - return; - } - - // Transition to ended, executing the callback if present. - SetState(kEnded); - { - base::AutoLock auto_lock(lock_); - clock_->Pause(); - clock_->SetTime(duration_); - } - - if (!ended_callback_.is_null()) { - ended_callback_.Run(status_); - } -} - -void PipelineImpl::NotifyNetworkEventTask(NetworkEvent type) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - if (!network_callback_.is_null()) - network_callback_.Run(type); -} - -void PipelineImpl::DisableAudioRendererTask() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - - base::AutoLock auto_lock(lock_); - has_audio_ = false; - audio_disabled_ = true; - - // Notify all filters of disabled audio renderer. If the filter isn't - // initialized yet, OnAudioRendererDisabled() will be called when - // initialization is complete. - if (pipeline_filter_) { - DCHECK(demuxer_); - demuxer_->OnAudioRendererDisabled(); - pipeline_filter_->OnAudioRendererDisabled(); - } - - // Start clock since there is no more audio to - // trigger clock updates. - StartClockIfWaitingForTimeUpdate_Locked(); -} - -void PipelineImpl::FilterStateTransitionTask() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - - // No reason transitioning if we've errored or have stopped. - if (IsPipelineStopped()) { - return; - } - - // If we are tearing down, don't allow any state changes. Teardown - // state changes will come in via TeardownStateTransitionTask(). - if (IsPipelineTearingDown()) { - return; - } - - if (!TransientState(state_)) { - NOTREACHED() << "Invalid current state: " << state_; - SetError(PIPELINE_ERROR_ABORT); - return; - } - - // Decrement the number of remaining transitions, making sure to transition - // to the next state if needed. - SetState(FindNextState(state_)); - if (state_ == kSeeking) { - base::AutoLock auto_lock(lock_); - clock_->SetTime(seek_timestamp_); - } - - // Carry out the action for the current state. - if (TransientState(state_)) { - if (state_ == kPausing) { - pipeline_filter_->Pause( - base::Bind(&PipelineImpl::OnFilterStateTransition, this)); - } else if (state_ == kFlushing) { - pipeline_filter_->Flush( - base::Bind(&PipelineImpl::OnFilterStateTransition, this)); - } else if (state_ == kSeeking) { - DoSeek(seek_timestamp_); - } else if (state_ == kStarting) { - pipeline_filter_->Play( - base::Bind(&PipelineImpl::OnFilterStateTransition, this)); - } else if (state_ == kStopping) { - DoStop(base::Bind(&PipelineImpl::OnFilterStateTransition, this)); - } else { - NOTREACHED() << "Unexpected state: " << state_; - } - } else if (state_ == kStarted) { - FinishInitialization(); - - // Finally, reset our seeking timestamp back to zero. - seek_timestamp_ = base::TimeDelta(); - seek_pending_ = false; - - // If a playback rate change was requested during a seek, do it now that - // the seek has compelted. - if (playback_rate_change_pending_) { - playback_rate_change_pending_ = false; - PlaybackRateChangedTask(pending_playback_rate_); - } - - 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; - if (!has_audio_) - StartClockIfWaitingForTimeUpdate_Locked(); - - // Start monitoring rate of downloading. - int bitrate = 0; - if (demuxer_.get()) { - bitrate = demuxer_->GetBitrate(); - local_source_ = demuxer_->IsLocalSource(); - streaming_ = !demuxer_->IsSeekable(); - } - // Needs to be locked because most other calls to |download_rate_monitor_| - // occur on the renderer thread. - download_rate_monitor_.Start( - base::Bind(&PipelineImpl::OnCanPlayThrough, this), - bitrate, streaming_, local_source_); - download_rate_monitor_.SetBufferedBytes(buffered_bytes_, base::Time::Now()); - - if (IsPipelineStopPending()) { - // We had a pending stop request need to be honored right now. - TearDownPipeline(); - } - } else { - NOTREACHED() << "Unexpected state: " << state_; - } -} - -void PipelineImpl::TeardownStateTransitionTask() { - DCHECK(IsPipelineTearingDown()); - switch (state_) { - case kStopping: - SetState(error_caused_teardown_ ? kError : kStopped); - FinishDestroyingFiltersTask(); - break; - case kPausing: - SetState(kFlushing); - pipeline_filter_->Flush( - base::Bind(&PipelineImpl::OnTeardownStateTransition, this)); - break; - case kFlushing: - SetState(kStopping); - DoStop(base::Bind(&PipelineImpl::OnTeardownStateTransition, this)); - break; - - case kCreated: - case kError: - case kInitDemuxer: - case kInitAudioDecoder: - case kInitAudioRenderer: - case kInitVideoDecoder: - case kInitVideoRenderer: - case kSeeking: - case kStarting: - case kStopped: - case kStarted: - case kEnded: - NOTREACHED() << "Unexpected state for teardown: " << state_; - break; - // default: intentionally left out to force new states to cause compiler - // errors. - }; -} - -void PipelineImpl::FinishDestroyingFiltersTask() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK(IsPipelineStopped()); - - // Clear filter references. - audio_renderer_ = NULL; - video_renderer_ = NULL; - demuxer_ = NULL; - - pipeline_filter_ = NULL; - - if (error_caused_teardown_ && !IsPipelineOk() && !error_callback_.is_null()) - error_callback_.Run(status_); - - if (stop_pending_) { - stop_pending_ = false; - ResetState(); - PipelineStatusCB stop_cb; - std::swap(stop_cb, stop_callback_); - // Notify the client that stopping has finished. - if (!stop_cb.is_null()) { - stop_cb.Run(status_); - } - } - - tearing_down_ = false; - error_caused_teardown_ = false; -} - -bool PipelineImpl::PrepareFilter(scoped_refptr filter) { - bool ret = pipeline_init_state_->composite_->AddFilter(filter.get()); - if (!ret) - SetError(PIPELINE_ERROR_INITIALIZATION_FAILED); - return ret; -} - -void PipelineImpl::InitializeDemuxer() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK(IsPipelineOk()); - - filter_collection_->GetDemuxerFactory()->Build( - url_, base::Bind(&PipelineImpl::OnDemuxerBuilt, this)); -} - -void PipelineImpl::OnDemuxerBuilt(PipelineStatus status, Demuxer* demuxer) { - if (MessageLoop::current() != message_loop_) { - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::OnDemuxerBuilt, this, - status, - make_scoped_refptr(demuxer))); - return; - } - - if (status != PIPELINE_OK) { - SetError(status); - return; - } - - if (!demuxer) { - SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); - return; - } - - demuxer_ = demuxer; - demuxer_->set_host(this); - - { - base::AutoLock auto_lock(lock_); - // We do not want to start the clock running. We only want to set the base - // media time so our timestamp calculations will be correct. - clock_->SetTime(demuxer_->GetStartTime()); - } - - OnFilterInitialize(PIPELINE_OK); -} - -bool PipelineImpl::InitializeAudioDecoder( - const scoped_refptr& demuxer) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK(IsPipelineOk()); - - scoped_refptr stream = - demuxer->GetStream(DemuxerStream::AUDIO); - - if (!stream) - return false; - - scoped_refptr audio_decoder; - filter_collection_->SelectAudioDecoder(&audio_decoder); - - if (!audio_decoder) { - SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); - return false; - } - - if (!PrepareFilter(audio_decoder)) - return false; - - pipeline_init_state_->audio_decoder_ = audio_decoder; - audio_decoder->Initialize( - stream, - base::Bind(&PipelineImpl::OnFilterInitialize, this, PIPELINE_OK), - base::Bind(&PipelineImpl::OnUpdateStatistics, this)); - return true; -} - -bool PipelineImpl::InitializeVideoDecoder( - const scoped_refptr& demuxer) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK(IsPipelineOk()); - - scoped_refptr stream; - - if (demuxer) { - stream = demuxer->GetStream(DemuxerStream::VIDEO); - - if (!stream) - return false; - } - - filter_collection_->SelectVideoDecoder(&video_decoder_); - - if (!video_decoder_) { - SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); - return false; - } - - if (!PrepareFilter(video_decoder_)) - return false; - - pipeline_init_state_->video_decoder_ = video_decoder_; - video_decoder_->Initialize( - stream, - base::Bind(&PipelineImpl::OnFilterInitialize, this), - base::Bind(&PipelineImpl::OnUpdateStatistics, this)); - return true; -} - -bool PipelineImpl::InitializeAudioRenderer( - const scoped_refptr& decoder) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK(IsPipelineOk()); - - if (!decoder) - return false; - - filter_collection_->SelectAudioRenderer(&audio_renderer_); - if (!audio_renderer_) { - SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); - return false; - } - - if (!PrepareFilter(audio_renderer_)) - return false; - - audio_renderer_->Initialize( - decoder, - base::Bind(&PipelineImpl::OnFilterInitialize, this, PIPELINE_OK), - base::Bind(&PipelineImpl::OnAudioUnderflow, this)); - return true; -} - -bool PipelineImpl::InitializeVideoRenderer( - const scoped_refptr& decoder) { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK(IsPipelineOk()); - - if (!decoder) - return false; - - filter_collection_->SelectVideoRenderer(&video_renderer_); - if (!video_renderer_) { - SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); - return false; - } - - if (!PrepareFilter(video_renderer_)) - return false; - - video_renderer_->Initialize( - decoder, - base::Bind(&PipelineImpl::OnFilterInitialize, this, PIPELINE_OK), - base::Bind(&PipelineImpl::OnUpdateStatistics, this)); - return true; -} - -void PipelineImpl::TearDownPipeline() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - DCHECK_NE(kStopped, state_); - - DCHECK(!tearing_down_ || // Teardown on Stop(). - (tearing_down_ && error_caused_teardown_) || // Teardown on error. - (tearing_down_ && stop_pending_)); // Stop during teardown by error. - - // Mark that we already start tearing down operation. - tearing_down_ = true; - - switch (state_) { - case kCreated: - case kError: - SetState(kStopped); - // Need to put this in the message loop to make sure that it comes - // after any pending callback tasks that are already queued. - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::FinishDestroyingFiltersTask, this)); - break; - - case kInitDemuxer: - case kInitAudioDecoder: - case kInitAudioRenderer: - case kInitVideoDecoder: - case kInitVideoRenderer: - // Make it look like initialization was successful. - pipeline_filter_ = pipeline_init_state_->composite_; - pipeline_init_state_.reset(); - filter_collection_.reset(); - - SetState(kStopping); - DoStop(base::Bind(&PipelineImpl::OnTeardownStateTransition, this)); - - FinishInitialization(); - break; - - case kPausing: - case kSeeking: - case kFlushing: - case kStarting: - SetState(kStopping); - DoStop(base::Bind(&PipelineImpl::OnTeardownStateTransition, this)); - - if (seek_pending_) { - seek_pending_ = false; - FinishInitialization(); - } - - break; - - case kStarted: - case kEnded: - SetState(kPausing); - pipeline_filter_->Pause( - base::Bind(&PipelineImpl::OnTeardownStateTransition, this)); - break; - - case kStopping: - case kStopped: - NOTREACHED() << "Unexpected state for teardown: " << state_; - break; - // default: intentionally left out to force new states to cause compiler - // errors. - }; -} - -void PipelineImpl::DoStop(const base::Closure& callback) { - if (demuxer_) { - demuxer_->Stop(base::Bind( - &PipelineImpl::OnDemuxerStopDone, this, callback)); - return; - } - - OnDemuxerStopDone(callback); -} - -void PipelineImpl::OnDemuxerStopDone(const base::Closure& callback) { - if (MessageLoop::current() != message_loop_) { - message_loop_->PostTask(FROM_HERE, base::Bind( - &PipelineImpl::OnDemuxerStopDone, this, callback)); - return; - } - - if (pipeline_filter_) { - pipeline_filter_->Stop(callback); - return; - } - - callback.Run(); - -} - -void PipelineImpl::DoSeek(base::TimeDelta seek_timestamp) { - // TODO(acolwell) : We might be able to convert this if (demuxer_) into a - // DCHECK(). Further investigation is needed to make sure this won't introduce - // a bug. - if (demuxer_) { - demuxer_->Seek(seek_timestamp, - base::Bind(&PipelineImpl::OnDemuxerSeekDone, - this, - seek_timestamp)); - return; - } - - OnDemuxerSeekDone(seek_timestamp, PIPELINE_OK); -} - -void PipelineImpl::OnDemuxerSeekDone(base::TimeDelta seek_timestamp, - PipelineStatus status) { - if (MessageLoop::current() != message_loop_) { - message_loop_->PostTask(FROM_HERE, base::Bind( - &PipelineImpl::OnDemuxerSeekDone, this, seek_timestamp, status)); - return; - } - - PipelineStatusCB done_cb = - base::Bind(&PipelineImpl::OnFilterStateTransitionWithStatus, this); - - if (status == PIPELINE_OK && pipeline_filter_) { - pipeline_filter_->Seek(seek_timestamp, done_cb); - return; - } - - done_cb.Run(status); -} - -void PipelineImpl::OnAudioUnderflow() { - if (MessageLoop::current() != message_loop_) { - message_loop_->PostTask(FROM_HERE, base::Bind( - &PipelineImpl::OnAudioUnderflow, this)); - return; - } - - if (state_ != kStarted) - return; - - if (audio_renderer_) - audio_renderer_->ResumeAfterUnderflow(true); -} - -void PipelineImpl::OnCanPlayThrough() { - message_loop_->PostTask(FROM_HERE, - base::Bind(&PipelineImpl::NotifyCanPlayThrough, this)); -} - -void PipelineImpl::NotifyCanPlayThrough() { - DCHECK_EQ(MessageLoop::current(), message_loop_); - NotifyNetworkEventTask(CAN_PLAY_THROUGH); -} - -void PipelineImpl::StartClockIfWaitingForTimeUpdate_Locked() { - lock_.AssertAcquired(); - if (!waiting_for_clock_update_) - return; - - waiting_for_clock_update_ = false; - clock_->Play(); -} - -} // namespace media diff --git a/media/base/pipeline_impl.h b/media/base/pipeline_impl.h deleted file mode 100644 index 5b9ee4e..0000000 --- a/media/base/pipeline_impl.h +++ /dev/null @@ -1,518 +0,0 @@ -// Copyright (c) 2012 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. - -// Implementation of Pipeline & PipelineStatusNotification (an async-to-sync -// callback adapter). - -#ifndef MEDIA_BASE_PIPELINE_IMPL_H_ -#define MEDIA_BASE_PIPELINE_IMPL_H_ - -#include -#include -#include - -#include "base/gtest_prod_util.h" -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/message_loop.h" -#include "base/synchronization/condition_variable.h" -#include "base/synchronization/lock.h" -#include "base/threading/thread.h" -#include "base/time.h" -#include "media/base/clock.h" -#include "media/base/composite_filter.h" -#include "media/base/demuxer.h" -#include "media/base/download_rate_monitor.h" -#include "media/base/filter_host.h" -#include "media/base/pipeline.h" -#include "ui/gfx/size.h" - -namespace media { - -class MediaLog; - -// Adapter for using asynchronous Pipeline methods in code that wants to run -// synchronously. To use, construct an instance of this class and pass the -// |Callback()| to the Pipeline method requiring a callback. Then Wait() for -// the callback to get fired and call status() to see what the callback's -// argument was. This object is for one-time use; call |Callback()| exactly -// once. -class MEDIA_EXPORT PipelineStatusNotification { - public: - PipelineStatusNotification(); - ~PipelineStatusNotification(); - - // See class-level comment for usage. - PipelineStatusCB Callback(); - void Wait(); - PipelineStatus status(); - - private: - void Notify(media::PipelineStatus status); - - base::Lock lock_; - base::ConditionVariable cv_; - media::PipelineStatus status_; - bool notified_; - - DISALLOW_COPY_AND_ASSIGN(PipelineStatusNotification); -}; - -// PipelineImpl runs the media pipeline. Filters are created and called on the -// message loop injected into this object. PipelineImpl works like a state -// machine to perform asynchronous initialization, pausing, seeking and playing. -// -// Here's a state diagram that describes the lifetime of this object. -// -// [ *Created ] [ Stopped ] -// | Start() ^ -// V SetError() | -// [ InitXXX (for each filter) ] -------->[ Stopping (for each filter) ] -// | ^ -// V | if Stop -// [ Seeking (for each filter) ] <--------[ Flushing (for each filter) ] -// | if Seek ^ -// V | -// [ Starting (for each filter) ] | -// | | -// V Seek()/Stop() | -// [ Started ] -------------------------> [ Pausing (for each filter) ] -// | ^ -// | NotifyEnded() Seek()/Stop() | -// `-------------> [ Ended ] ---------------------' -// ^ SetError() -// | -// [ Any State Other Than InitXXX ] - -// -// Initialization is a series of state transitions from "Created" through each -// filter initialization state. When all filter initialization states have -// completed, we are implicitly in a "Paused" state. At that point we simulate -// a Seek() to the beginning of the media to give filters a chance to preroll. -// From then on the normal Seek() transitions are carried out and we start -// playing the media. -// -// If any error ever happens, this object will transition to the "Error" state -// from any state. If Stop() is ever called, this object will transition to -// "Stopped" state. -class MEDIA_EXPORT PipelineImpl - : public Pipeline, - public FilterHost, - public DemuxerHost { - public: - explicit PipelineImpl(MessageLoop* message_loop, MediaLog* media_log); - - // Pipeline implementation. - virtual void Init(const PipelineStatusCB& ended_callback, - const PipelineStatusCB& error_callback, - const NetworkEventCB& network_callback) OVERRIDE; - virtual bool Start(scoped_ptr filter_collection, - const std::string& uri, - const PipelineStatusCB& start_callback) OVERRIDE; - virtual void Stop(const PipelineStatusCB& stop_callback) OVERRIDE; - virtual void Seek(base::TimeDelta time, - const PipelineStatusCB& seek_callback) OVERRIDE; - virtual bool IsRunning() const OVERRIDE; - virtual bool IsInitialized() const OVERRIDE; - virtual bool HasAudio() const OVERRIDE; - virtual bool HasVideo() const OVERRIDE; - virtual float GetPlaybackRate() const OVERRIDE; - virtual void SetPlaybackRate(float playback_rate) OVERRIDE; - virtual float GetVolume() const OVERRIDE; - virtual void SetVolume(float volume) OVERRIDE; - virtual void SetPreload(Preload preload) OVERRIDE; - virtual base::TimeDelta GetCurrentTime() const OVERRIDE; - virtual base::TimeDelta GetBufferedTime() OVERRIDE; - virtual base::TimeDelta GetMediaDuration() const OVERRIDE; - virtual int64 GetBufferedBytes() const OVERRIDE; - virtual int64 GetTotalBytes() const OVERRIDE; - virtual void GetNaturalVideoSize(gfx::Size* out_size) const OVERRIDE; - virtual bool IsStreaming() const OVERRIDE; - virtual bool IsLocalSource() const OVERRIDE; - virtual PipelineStatistics GetStatistics() const OVERRIDE; - - void SetClockForTesting(Clock* clock); - - private: - friend class MediaLog; - - // Pipeline states, as described above. - enum State { - kCreated, - kInitDemuxer, - kInitAudioDecoder, - kInitAudioRenderer, - kInitVideoDecoder, - kInitVideoRenderer, - kPausing, - kSeeking, - kFlushing, - kStarting, - kStarted, - kEnded, - kStopping, - kStopped, - kError, - }; - - virtual ~PipelineImpl(); - - // Reset the state of the pipeline object to the initial state. This method - // is used by the constructor, and the Stop() method. - void ResetState(); - - // Updates |state_|. All state transitions should use this call. - void SetState(State next_state); - - // Simple method used to make sure the pipeline is running normally. - bool IsPipelineOk(); - - // Helper method to tell whether we are stopped or in error. - bool IsPipelineStopped(); - - // Helper method to tell whether we are in transition to stop state. - bool IsPipelineTearingDown(); - - // We could also be delayed by a transition during seek is performed. - bool IsPipelineStopPending(); - - // Helper method to tell whether we are in transition to seek state. - bool IsPipelineSeeking(); - - // Helper method to execute callback from Start() and reset - // |filter_collection_|. Called when initialization completes - // normally or when pipeline is stopped or error occurs during - // initialization. - void FinishInitialization(); - - // Returns true if the given state is one that transitions to a new state - // after iterating through each filter. - static bool TransientState(State state); - - // Given the current state, returns the next state. - State FindNextState(State current); - - // DataSourceHost (by way of DemuxerHost) implementation. - virtual void SetTotalBytes(int64 total_bytes) OVERRIDE; - virtual void SetBufferedBytes(int64 buffered_bytes) OVERRIDE; - virtual void SetNetworkActivity(bool is_downloading_data) OVERRIDE; - - // DemuxerHost implementaion. - virtual void SetDuration(base::TimeDelta duration) OVERRIDE; - virtual void SetBufferedTime(base::TimeDelta buffered_time) OVERRIDE; - virtual void SetCurrentReadPosition(int64 offset) OVERRIDE; - virtual void OnDemuxerError(PipelineStatus error) OVERRIDE; - - // FilterHost implementation. - virtual void SetError(PipelineStatus error) OVERRIDE; - virtual base::TimeDelta GetTime() const OVERRIDE; - virtual base::TimeDelta GetDuration() const OVERRIDE; - virtual void SetTime(base::TimeDelta time) OVERRIDE; - virtual void SetNaturalVideoSize(const gfx::Size& size) OVERRIDE; - virtual void NotifyEnded() OVERRIDE; - virtual void DisableAudioRenderer() OVERRIDE; - - // Callbacks executed by filters upon completing initialization. - void OnFilterInitialize(PipelineStatus status); - - // Callback executed by filters upon completing Play(), Pause(), or Stop(). - void OnFilterStateTransition(); - - // Callback executed by filters upon completing Seek(). - void OnFilterStateTransitionWithStatus(PipelineStatus status); - - // Callback executed by filters when completing teardown operations. - void OnTeardownStateTransition(); - - // Callback executed by filters to update statistics. - void OnUpdateStatistics(const PipelineStatistics& stats); - - // The following "task" methods correspond to the public methods, but these - // methods are run as the result of posting a task to the PipelineInternal's - // message loop. - void StartTask(scoped_ptr filter_collection, - const std::string& url, - const PipelineStatusCB& start_callback); - - // InitializeTask() performs initialization in multiple passes. It is executed - // as a result of calling Start() or InitializationComplete() that advances - // initialization to the next state. It works as a hub of state transition for - // initialization. One stage communicates its status to the next through - // |last_stage_status|. - void InitializeTask(PipelineStatus last_stage_status); - - // Stops and destroys all filters, placing the pipeline in the kStopped state. - void StopTask(const PipelineStatusCB& stop_callback); - - // Carries out stopping and destroying all filters, placing the pipeline in - // the kError state. - void ErrorChangedTask(PipelineStatus error); - - // Carries out notifying filters that the playback rate has changed. - void PlaybackRateChangedTask(float playback_rate); - - // Carries out notifying filters that the volume has changed. - void VolumeChangedTask(float volume); - - // Returns media preload value. - virtual Preload GetPreload() const; - - // Carries out notifying filters that the preload value has changed. - void PreloadChangedTask(Preload preload); - - // Carries out notifying filters that we are seeking to a new timestamp. - void SeekTask(base::TimeDelta time, const PipelineStatusCB& seek_callback); - - // Carries out handling a notification from a filter that it has ended. - void NotifyEndedTask(); - - // Carries out handling a notification of network event. - void NotifyNetworkEventTask(NetworkEvent type); - - // Carries out disabling the audio renderer. - void DisableAudioRendererTask(); - - // Carries out advancing to the next filter during Play()/Pause()/Seek(). - void FilterStateTransitionTask(); - - // Carries out advancing to the next teardown operation. - void TeardownStateTransitionTask(); - - // Carries out stopping filter threads, deleting filters, running - // appropriate callbacks, and setting the appropriate pipeline state - // depending on whether we performing Stop() or SetError(). - // Called after all filters have been stopped. - void FinishDestroyingFiltersTask(); - - // Internal methods used in the implementation of the pipeline thread. All - // of these methods are only called on the pipeline thread. - - // PrepareFilter() creates the filter's thread and injects a FilterHost and - // MessageLoop. - bool PrepareFilter(scoped_refptr filter); - - // The following initialize methods are used to select a specific type of - // Filter object from FilterCollection and initialize it asynchronously. - void InitializeDemuxer(); - void OnDemuxerBuilt(PipelineStatus status, Demuxer* demuxer); - - // Returns true if the asynchronous action of creating decoder has started. - // Returns false if this method did nothing because the corresponding - // audio/video stream does not exist. - bool InitializeAudioDecoder(const scoped_refptr& demuxer); - bool InitializeVideoDecoder(const scoped_refptr& demuxer); - - // Initializes a renderer and connects it with decoder. Returns true if the - // asynchronous action of creating renderer has started. Returns - // false if this method did nothing because the corresponding audio/video - // stream does not exist. - bool InitializeAudioRenderer(const scoped_refptr& decoder); - bool InitializeVideoRenderer(const scoped_refptr& decoder); - - // Kicks off destroying filters. Called by StopTask() and ErrorChangedTask(). - // When we start to tear down the pipeline, we will consider two cases: - // 1. when pipeline has not been initialized, we will transit to stopping - // state first. - // 2. when pipeline has been initialized, we will first transit to pausing - // => flushing => stopping => stopped state. - // This will remove the race condition during stop between filters. - void TearDownPipeline(); - - // Compute the current time. Assumes that the lock has been acquired by the - // caller. - base::TimeDelta GetCurrentTime_Locked() const; - - // Initiates a Stop() on |demuxer_| & |pipeline_filter_|. |callback| - // is called once both objects have been stopped. - void DoStop(const base::Closure& callback); - - // Called when |demuxer_| has stopped. This method calls Stop() - // on |pipeline_filter_|. - void OnDemuxerStopDone(const base::Closure& callback); - - // Initiates a Seek() on the |demuxer_| & |pipeline_filter_|. - void DoSeek(base::TimeDelta seek_timestamp); - - // Called when |demuxer_| finishes seeking. If seeking was successful, - // then Seek() is called on |pipeline_filter_|. - void OnDemuxerSeekDone(base::TimeDelta seek_timestamp, - PipelineStatus status); - - void OnAudioUnderflow(); - - // Called when |download_rate_monitor_| believes that the media can - // be played through without needing to pause to buffer. - void OnCanPlayThrough(); - - // Carries out the notification that the media can be played through without - // needing to pause to buffer. - void NotifyCanPlayThrough(); - - void StartClockIfWaitingForTimeUpdate_Locked(); - - // Message loop used to execute pipeline tasks. - MessageLoop* message_loop_; - - // MediaLog to which to log events. - scoped_refptr media_log_; - - // Lock used to serialize access for the following data members. - mutable base::Lock lock_; - - // Whether or not the pipeline is running. - bool running_; - - // Whether or not the pipeline is in transition for a seek operation. - bool seek_pending_; - - // Whether or not the pipeline is pending a stop operation. - bool stop_pending_; - - // Whether or not the pipeline is perform a stop operation. - bool tearing_down_; - - // Whether or not an error triggered the teardown. - bool error_caused_teardown_; - - // Whether or not a playback rate change should be done once seeking is done. - bool playback_rate_change_pending_; - - // Duration of the media in microseconds. Set by filters. - base::TimeDelta duration_; - - // Amount of available buffered data in microseconds. Set by filters. - base::TimeDelta buffered_time_; - - // Amount of available buffered data. Set by filters. - int64 buffered_bytes_; - - // Total size of the media. Set by filters. - int64 total_bytes_; - - // Video's natural width and height. Set by filters. - gfx::Size natural_size_; - - // Set by the demuxer to indicate whether the data source is a streaming - // source. - bool streaming_; - - // Indicates whether the data source is local, such as a local media file - // from disk or a local webcam stream. - bool local_source_; - - // Current volume level (from 0.0f to 1.0f). This value is set immediately - // via SetVolume() and a task is dispatched on the message loop to notify the - // filters. - float volume_; - - // Current value of preload attribute. This value is set immediately via - // SetPreload() and a task is dispatched on the message loop to notify the - // filters. - Preload preload_; - - // Current playback rate (>= 0.0f). This value is set immediately via - // SetPlaybackRate() and a task is dispatched on the message loop to notify - // the filters. - float playback_rate_; - - // Playback rate to set when the current seek has finished. - float pending_playback_rate_; - - // Reference clock. Keeps track of current playback time. Uses system - // clock and linear interpolation, but can have its time manually set - // by filters. - scoped_ptr clock_; - - // If this value is set to true, then |clock_| is paused and we are waiting - // for an update of the clock greater than or equal to the elapsed time to - // start the clock. - bool waiting_for_clock_update_; - - // Status of the pipeline. Initialized to PIPELINE_OK which indicates that - // the pipeline is operating correctly. Any other value indicates that the - // pipeline is stopped or is stopping. Clients can call the Stop() method to - // reset the pipeline state, and restore this to PIPELINE_OK. - PipelineStatus status_; - - // Whether the media contains rendered audio and video streams. - bool has_audio_; - bool has_video_; - - // The following data members are only accessed by tasks posted to - // |message_loop_|. - - // Member that tracks the current state. - State state_; - - // For kSeeking we need to remember where we're seeking between filter - // replies. - base::TimeDelta seek_timestamp_; - - // For GetCurrentBytes()/SetCurrentBytes() we need to know what byte we are - // currently reading. - int64 current_bytes_; - - // Set to true in DisableAudioRendererTask(). - bool audio_disabled_; - - // Keep track of the maximum buffered position so the buffering appears - // smooth. - // TODO(vrk): This is a hack. - base::TimeDelta max_buffered_time_; - - // Filter collection as passed in by Start(). - scoped_ptr filter_collection_; - - // URL for the data source as passed in by Start(). - std::string url_; - - // Callbacks for various pipeline operations. - PipelineStatusCB seek_callback_; - PipelineStatusCB stop_callback_; - PipelineStatusCB ended_callback_; - PipelineStatusCB error_callback_; - NetworkEventCB network_callback_; - - // Reference to the filter(s) that constitute the pipeline. - scoped_refptr pipeline_filter_; - - // Decoder reference used for signalling imminent shutdown. - // This is a HACK necessary because WebMediaPlayerImpl::Destroy() holds the - // renderer thread loop hostage for until PipelineImpl::Stop() calls its - // callback. http://crbug.com/110228 tracks removing this hack. - scoped_refptr video_decoder_; - - // Renderer references used for setting the volume and determining - // when playback has finished. - scoped_refptr audio_renderer_; - scoped_refptr video_renderer_; - - // Demuxer reference used for setting the preload value. - scoped_refptr demuxer_; - - // Helper class that stores filter references during pipeline - // initialization. - class PipelineInitState; - scoped_ptr pipeline_init_state_; - - // Statistics. - PipelineStatistics statistics_; - // Time of pipeline creation; is non-zero only until the pipeline first - // reaches "kStarted", at which point it is used & zeroed out. - base::Time creation_time_; - - // Approximates the rate at which the media is being downloaded. - DownloadRateMonitor download_rate_monitor_; - - // True if the pipeline is actively downloading bytes, false otherwise. - bool is_downloading_data_; - - FRIEND_TEST_ALL_PREFIXES(PipelineImplTest, GetBufferedTime); - - DISALLOW_COPY_AND_ASSIGN(PipelineImpl); -}; - -} // namespace media - -#endif // MEDIA_BASE_PIPELINE_IMPL_H_ diff --git a/media/base/pipeline_impl_unittest.cc b/media/base/pipeline_impl_unittest.cc deleted file mode 100644 index 3ded0e7..0000000 --- a/media/base/pipeline_impl_unittest.cc +++ /dev/null @@ -1,927 +0,0 @@ -// Copyright (c) 2012 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 - -#include "base/bind.h" -#include "base/callback.h" -#include "base/stl_util.h" -#include "base/threading/simple_thread.h" -#include "media/base/filter_host.h" -#include "media/base/filters.h" -#include "media/base/media_log.h" -#include "media/base/pipeline_impl.h" -#include "media/base/mock_callback.h" -#include "media/base/mock_filters.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "ui/gfx/size.h" - -using ::testing::_; -using ::testing::DeleteArg; -using ::testing::InSequence; -using ::testing::Invoke; -using ::testing::InvokeArgument; -using ::testing::Mock; -using ::testing::NotNull; -using ::testing::Return; -using ::testing::ReturnRef; -using ::testing::StrictMock; -using ::testing::WithArg; - -namespace media { - -// Total bytes of the data source. -static const int kTotalBytes = 1024; - -// Buffered bytes of the data source. -static const int kBufferedBytes = 1024; - -// Used for setting expectations on pipeline callbacks. Using a StrictMock -// also lets us test for missing callbacks. -class CallbackHelper { - public: - CallbackHelper() {} - virtual ~CallbackHelper() {} - - MOCK_METHOD1(OnStart, void(PipelineStatus)); - MOCK_METHOD1(OnSeek, void(PipelineStatus)); - MOCK_METHOD1(OnStop, void(PipelineStatus)); - MOCK_METHOD1(OnEnded, void(PipelineStatus)); - MOCK_METHOD1(OnError, void(PipelineStatus)); - - private: - DISALLOW_COPY_AND_ASSIGN(CallbackHelper); -}; - -// Run |cb| w/ OK status. -static void RunPipelineStatusOKCB(const PipelineStatusCB& cb) { - cb.Run(PIPELINE_OK); -} - -// TODO(scherkus): even though some filters are initialized on separate -// threads these test aren't flaky... why? It's because filters' Initialize() -// is executed on |message_loop_| and the mock filters instantly call -// InitializationComplete(), which keeps the pipeline humming along. If -// either filters don't call InitializationComplete() immediately or filter -// initialization is moved to a separate thread this test will become flaky. -class PipelineImplTest : public ::testing::Test { - public: - PipelineImplTest() - : pipeline_(new PipelineImpl(&message_loop_, new MediaLog())) { - pipeline_->Init( - base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)), - base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)), - Pipeline::NetworkEventCB()); - mocks_.reset(new MockFilterCollection()); - - // InitializeDemuxer adds overriding expectations for expected non-NULL - // streams. - DemuxerStream* null_pointer = NULL; - EXPECT_CALL(*mocks_->demuxer(), GetStream(_)) - .WillRepeatedly(Return(null_pointer)); - - EXPECT_CALL(*mocks_->demuxer(), GetStartTime()) - .WillRepeatedly(Return(base::TimeDelta())); - } - - virtual ~PipelineImplTest() { - if (!pipeline_->IsRunning()) { - return; - } - - // Expect a stop callback if we were started. - EXPECT_CALL(callbacks_, OnStop(PIPELINE_OK)); - pipeline_->Stop(base::Bind(&CallbackHelper::OnStop, - base::Unretained(&callbacks_))); - message_loop_.RunAllPending(); - - mocks_.reset(); - } - - protected: - // Sets up expectations to allow the demuxer to initialize. - typedef std::vector MockDemuxerStreamVector; - void InitializeDemuxer(MockDemuxerStreamVector* streams, - const base::TimeDelta& duration) { - mocks_->demuxer()->SetTotalAndBufferedBytesAndDuration( - kTotalBytes, kBufferedBytes, duration); - EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(0.0f)); - EXPECT_CALL(*mocks_->demuxer(), SetPreload(AUTO)); - EXPECT_CALL(*mocks_->demuxer(), Seek(mocks_->demuxer()->GetStartTime(), _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - EXPECT_CALL(*mocks_->demuxer(), Stop(_)) - .WillOnce(Invoke(&RunStopFilterCallback)); - - // Configure the demuxer to return the streams. - for (size_t i = 0; i < streams->size(); ++i) { - scoped_refptr stream((*streams)[i]); - EXPECT_CALL(*mocks_->demuxer(), GetStream(stream->type())) - .WillRepeatedly(Return(stream)); - } - } - - StrictMock* CreateStream(DemuxerStream::Type type) { - StrictMock* stream = - new StrictMock(); - EXPECT_CALL(*stream, type()) - .WillRepeatedly(Return(type)); - return stream; - } - - // Sets up expectations to allow the video decoder to initialize. - void InitializeVideoDecoder(MockDemuxerStream* stream) { - EXPECT_CALL(*mocks_->video_decoder(), - Initialize(stream, _, _)) - .WillOnce(WithArg<1>(Invoke(&RunPipelineStatusOKCB))); - EXPECT_CALL(*mocks_->video_decoder(), SetPlaybackRate(0.0f)); - EXPECT_CALL(*mocks_->video_decoder(), - Seek(mocks_->demuxer()->GetStartTime(), _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - EXPECT_CALL(*mocks_->video_decoder(), Stop(_)) - .WillOnce(Invoke(&RunStopFilterCallback)); - } - - // Sets up expectations to allow the audio decoder to initialize. - void InitializeAudioDecoder(MockDemuxerStream* stream) { - EXPECT_CALL(*mocks_->audio_decoder(), Initialize(stream, _, _)) - .WillOnce(Invoke(&RunFilterCallback3)); - EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(0.0f)); - EXPECT_CALL(*mocks_->audio_decoder(), Seek(base::TimeDelta(), _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - EXPECT_CALL(*mocks_->audio_decoder(), Stop(_)) - .WillOnce(Invoke(&RunStopFilterCallback)); - } - - // Sets up expectations to allow the video renderer to initialize. - void InitializeVideoRenderer() { - EXPECT_CALL(*mocks_->video_renderer(), - Initialize(mocks_->video_decoder(), _, _)) - .WillOnce(Invoke(&RunFilterCallback3)); - EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(0.0f)); - EXPECT_CALL(*mocks_->video_renderer(), - Seek(mocks_->demuxer()->GetStartTime(), _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - EXPECT_CALL(*mocks_->video_renderer(), Stop(_)) - .WillOnce(Invoke(&RunStopFilterCallback)); - } - - // Sets up expectations to allow the audio renderer to initialize. - void InitializeAudioRenderer(bool disable_after_init_callback = false) { - if (disable_after_init_callback) { - EXPECT_CALL(*mocks_->audio_renderer(), - Initialize(mocks_->audio_decoder(), _, _)) - .WillOnce(DoAll(Invoke(&RunFilterCallback3), - DisableAudioRenderer(mocks_->audio_renderer()))); - } else { - EXPECT_CALL(*mocks_->audio_renderer(), - Initialize(mocks_->audio_decoder(), _, _)) - .WillOnce(Invoke(&RunFilterCallback3)); - } - EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f)); - EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f)); - EXPECT_CALL(*mocks_->audio_renderer(), Seek(base::TimeDelta(), _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - EXPECT_CALL(*mocks_->audio_renderer(), Stop(_)) - .WillOnce(Invoke(&RunStopFilterCallback)); - } - - // Sets up expectations on the callback and initializes the pipeline. Called - // after tests have set expectations any filters they wish to use. - void InitializePipeline() { - InitializePipeline(PIPELINE_OK); - } - // Most tests can expect the |filter_collection|'s |build_status| to get - // reflected in |Start()|'s argument. - void InitializePipeline(PipelineStatus start_status) { - InitializePipeline(start_status, start_status); - } - // But some tests require different statuses in build & Start. - void InitializePipeline(PipelineStatus build_status, - PipelineStatus start_status) { - // Expect an initialization callback. - EXPECT_CALL(callbacks_, OnStart(start_status)); - - pipeline_->Start(mocks_->filter_collection(true, - true, - true, - build_status).Pass(), - "", - base::Bind(&CallbackHelper::OnStart, - base::Unretained(&callbacks_))); - - message_loop_.RunAllPending(); - } - - void CreateAudioStream() { - audio_stream_ = CreateStream(DemuxerStream::AUDIO); - } - - void CreateVideoStream() { - video_stream_ = CreateStream(DemuxerStream::VIDEO); - } - - MockDemuxerStream* audio_stream() { - return audio_stream_; - } - - MockDemuxerStream* video_stream() { - return video_stream_; - } - - void ExpectSeek(const base::TimeDelta& seek_time) { - // Every filter should receive a call to Seek(). - EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - - if (audio_stream_) { - EXPECT_CALL(*mocks_->audio_decoder(), Seek(seek_time, _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - EXPECT_CALL(*mocks_->audio_renderer(), Seek(seek_time, _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - } - - if (video_stream_) { - EXPECT_CALL(*mocks_->video_decoder(), Seek(seek_time, _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - EXPECT_CALL(*mocks_->video_renderer(), Seek(seek_time, _)) - .WillOnce(Invoke(&RunFilterStatusCB)); - } - - // We expect a successful seek callback. - EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK)); - } - - void DoSeek(const base::TimeDelta& seek_time) { - pipeline_->Seek(seek_time, - base::Bind(&CallbackHelper::OnSeek, - base::Unretained(&callbacks_))); - - // We expect the time to be updated only after the seek has completed. - EXPECT_NE(seek_time, pipeline_->GetCurrentTime()); - message_loop_.RunAllPending(); - EXPECT_EQ(seek_time, pipeline_->GetCurrentTime()); - } - - // Fixture members. - StrictMock callbacks_; - MessageLoop message_loop_; - scoped_refptr pipeline_; - scoped_ptr mocks_; - scoped_refptr > audio_stream_; - scoped_refptr > video_stream_; - - private: - DISALLOW_COPY_AND_ASSIGN(PipelineImplTest); -}; - -// Test that playback controls methods no-op when the pipeline hasn't been -// started. -TEST_F(PipelineImplTest, NotStarted) { - const base::TimeDelta kZero; - - // StrictMock<> will ensure these never get called, and valgrind will - // make sure the callbacks are instantly deleted. - pipeline_->Stop(base::Bind(&CallbackHelper::OnStop, - base::Unretained(&callbacks_))); - pipeline_->Seek(kZero, - base::Bind(&CallbackHelper::OnSeek, - base::Unretained(&callbacks_))); - - EXPECT_FALSE(pipeline_->IsRunning()); - EXPECT_FALSE(pipeline_->IsInitialized()); - EXPECT_FALSE(pipeline_->HasAudio()); - EXPECT_FALSE(pipeline_->HasVideo()); - - // Setting should still work. - EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); - pipeline_->SetPlaybackRate(-1.0f); - EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); - pipeline_->SetPlaybackRate(1.0f); - EXPECT_EQ(1.0f, pipeline_->GetPlaybackRate()); - - // Setting should still work. - EXPECT_EQ(1.0f, pipeline_->GetVolume()); - pipeline_->SetVolume(-1.0f); - EXPECT_EQ(1.0f, pipeline_->GetVolume()); - pipeline_->SetVolume(0.0f); - EXPECT_EQ(0.0f, pipeline_->GetVolume()); - - EXPECT_TRUE(kZero == pipeline_->GetCurrentTime()); - EXPECT_TRUE(kZero == pipeline_->GetBufferedTime()); - EXPECT_TRUE(kZero == pipeline_->GetMediaDuration()); - - EXPECT_EQ(0, pipeline_->GetBufferedBytes()); - EXPECT_EQ(0, pipeline_->GetTotalBytes()); - - // Should always get set to zero. - gfx::Size size(1, 1); - pipeline_->GetNaturalVideoSize(&size); - EXPECT_EQ(0, size.width()); - EXPECT_EQ(0, size.height()); -} - -TEST_F(PipelineImplTest, NeverInitializes) { - // This test hangs during initialization by never calling - // InitializationComplete(). StrictMock<> will ensure that the callback is - // never executed. - pipeline_->Start(mocks_->filter_collection(false, - false, - true, - PIPELINE_OK).Pass(), - "", - base::Bind(&CallbackHelper::OnStart, - base::Unretained(&callbacks_))); - message_loop_.RunAllPending(); - - EXPECT_FALSE(pipeline_->IsInitialized()); - - // Because our callback will get executed when the test tears down, we'll - // verify that nothing has been called, then set our expectation for the call - // made during tear down. - Mock::VerifyAndClear(&callbacks_); - EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK)); -} - -TEST_F(PipelineImplTest, RequiredFilterMissing) { - EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING)); - - // Sets up expectations on the callback and initializes the pipeline. Called - // after tests have set expectations any filters they wish to use. - // Expect an initialization callback. - EXPECT_CALL(callbacks_, OnStart(PIPELINE_ERROR_REQUIRED_FILTER_MISSING)); - - // Create a filter collection with missing filter. - scoped_ptr collection(mocks_->filter_collection( - false, true, true, PIPELINE_ERROR_REQUIRED_FILTER_MISSING)); - pipeline_->Start(collection.Pass(), "", - base::Bind(&CallbackHelper::OnStart, - base::Unretained(&callbacks_))); - message_loop_.RunAllPending(); - - EXPECT_FALSE(pipeline_->IsInitialized()); -} - -TEST_F(PipelineImplTest, URLNotFound) { - // TODO(acolwell,fischman): Since OnStart() is getting called with an error - // code already, OnError() doesn't also need to get called. Fix the pipeline - // (and it's consumers!) so that OnError doesn't need to be called after - // another callback has already reported the error. Same applies to NoStreams - // below. - EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_URL_NOT_FOUND)); - InitializePipeline(PIPELINE_ERROR_URL_NOT_FOUND); - EXPECT_FALSE(pipeline_->IsInitialized()); -} - -TEST_F(PipelineImplTest, NoStreams) { - // Manually set these expectations because SetPlaybackRate() is not called if - // we cannot fully initialize the pipeline. - EXPECT_CALL(*mocks_->demuxer(), Stop(_)) - .WillOnce(Invoke(&RunStopFilterCallback)); - // TODO(acolwell,fischman): see TODO in URLNotFound above. - EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_COULD_NOT_RENDER)); - - InitializePipeline(PIPELINE_OK, PIPELINE_ERROR_COULD_NOT_RENDER); - EXPECT_FALSE(pipeline_->IsInitialized()); -} - -TEST_F(PipelineImplTest, AudioStream) { - CreateAudioStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - - InitializeDemuxer(&streams, base::TimeDelta()); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(); - - InitializePipeline(PIPELINE_OK); - EXPECT_TRUE(pipeline_->IsInitialized()); - EXPECT_TRUE(pipeline_->HasAudio()); - EXPECT_FALSE(pipeline_->HasVideo()); -} - -TEST_F(PipelineImplTest, VideoStream) { - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(video_stream()); - - InitializeDemuxer(&streams, base::TimeDelta()); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - - InitializePipeline(PIPELINE_OK); - EXPECT_TRUE(pipeline_->IsInitialized()); - EXPECT_FALSE(pipeline_->HasAudio()); - EXPECT_TRUE(pipeline_->HasVideo()); -} - -TEST_F(PipelineImplTest, AudioVideoStream) { - CreateAudioStream(); - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - streams.push_back(video_stream()); - - InitializeDemuxer(&streams, base::TimeDelta()); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - - InitializePipeline(PIPELINE_OK); - EXPECT_TRUE(pipeline_->IsInitialized()); - EXPECT_TRUE(pipeline_->HasAudio()); - EXPECT_TRUE(pipeline_->HasVideo()); -} - -TEST_F(PipelineImplTest, Seek) { - CreateAudioStream(); - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - streams.push_back(video_stream()); - - InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000)); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - - // Every filter should receive a call to Seek(). - base::TimeDelta expected = base::TimeDelta::FromSeconds(2000); - ExpectSeek(expected); - - // Initialize then seek! - InitializePipeline(PIPELINE_OK); - DoSeek(expected); -} - -TEST_F(PipelineImplTest, SetVolume) { - CreateAudioStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - - InitializeDemuxer(&streams, base::TimeDelta()); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(); - - // The audio renderer should receive a call to SetVolume(). - float expected = 0.5f; - EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(expected)); - - // Initialize then set volume! - InitializePipeline(PIPELINE_OK); - pipeline_->SetVolume(expected); -} - -TEST_F(PipelineImplTest, Properties) { - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(video_stream()); - - const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); - InitializeDemuxer(&streams, kDuration); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - - InitializePipeline(PIPELINE_OK); - EXPECT_TRUE(pipeline_->IsInitialized()); - EXPECT_EQ(kDuration.ToInternalValue(), - pipeline_->GetMediaDuration().ToInternalValue()); - EXPECT_EQ(kTotalBytes, pipeline_->GetTotalBytes()); - EXPECT_EQ(kBufferedBytes, pipeline_->GetBufferedBytes()); - - // Because kTotalBytes and kBufferedBytes are equal to each other, - // the entire video should be buffered. - EXPECT_EQ(kDuration.ToInternalValue(), - pipeline_->GetBufferedTime().ToInternalValue()); -} - -TEST_F(PipelineImplTest, GetBufferedTime) { - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(video_stream()); - - const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); - InitializeDemuxer(&streams, kDuration); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - - InitializePipeline(PIPELINE_OK); - EXPECT_TRUE(pipeline_->IsInitialized()); - - // TODO(vrk): The following mini-test cases are order-dependent, and should - // probably be separated into independent test cases. - - // Buffered time is 0 if no bytes are buffered. - pipeline_->SetBufferedBytes(0); - EXPECT_EQ(0, pipeline_->GetBufferedTime().ToInternalValue()); - - // We should return buffered_time_ if it is set, valid and less than - // the current time. - const base::TimeDelta buffered = base::TimeDelta::FromSeconds(10); - pipeline_->SetBufferedTime(buffered); - EXPECT_EQ(buffered.ToInternalValue(), - pipeline_->GetBufferedTime().ToInternalValue()); - - // Test the case where the current time is beyond the buffered time. - base::TimeDelta kSeekTime = buffered + base::TimeDelta::FromSeconds(5); - ExpectSeek(kSeekTime); - DoSeek(kSeekTime); - - // Verify that buffered time is equal to the current time. - EXPECT_EQ(kSeekTime, pipeline_->GetCurrentTime()); - EXPECT_EQ(kSeekTime, pipeline_->GetBufferedTime()); - - // Clear buffered time. - pipeline_->SetBufferedTime(base::TimeDelta()); - - double time_percent = - static_cast(pipeline_->GetCurrentTime().ToInternalValue()) / - kDuration.ToInternalValue(); - - int estimated_bytes = static_cast(time_percent * kTotalBytes); - - // Test VBR case where bytes have been consumed slower than the average rate. - pipeline_->SetBufferedBytes(estimated_bytes - 10); - EXPECT_EQ(pipeline_->GetCurrentTime(), pipeline_->GetBufferedTime()); - - // Test VBR case where the bytes have been consumed faster than the average - // rate. - pipeline_->SetBufferedBytes(estimated_bytes + 10); - EXPECT_LT(pipeline_->GetCurrentTime(), pipeline_->GetBufferedTime()); - - // If media has been fully received, we should return the duration - // of the media. - pipeline_->SetBufferedBytes(kTotalBytes); - EXPECT_EQ(kDuration.ToInternalValue(), - pipeline_->GetBufferedTime().ToInternalValue()); -} - -TEST_F(PipelineImplTest, DisableAudioRenderer) { - CreateAudioStream(); - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - streams.push_back(video_stream()); - - InitializeDemuxer(&streams, base::TimeDelta()); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - - InitializePipeline(PIPELINE_OK); - EXPECT_TRUE(pipeline_->IsInitialized()); - EXPECT_TRUE(pipeline_->HasAudio()); - EXPECT_TRUE(pipeline_->HasVideo()); - - EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(1.0f)) - .WillOnce(DisableAudioRenderer(mocks_->audio_renderer())); - EXPECT_CALL(*mocks_->demuxer(), - OnAudioRendererDisabled()); - EXPECT_CALL(*mocks_->audio_decoder(), - OnAudioRendererDisabled()); - EXPECT_CALL(*mocks_->audio_renderer(), - OnAudioRendererDisabled()); - EXPECT_CALL(*mocks_->video_decoder(), - OnAudioRendererDisabled()); - EXPECT_CALL(*mocks_->video_renderer(), - OnAudioRendererDisabled()); - - mocks_->audio_renderer()->SetPlaybackRate(1.0f); - - // Verify that ended event is fired when video ends. - EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) - .WillOnce(Return(true)); - EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); - FilterHost* host = pipeline_; - host->NotifyEnded(); -} - -TEST_F(PipelineImplTest, DisableAudioRendererDuringInit) { - CreateAudioStream(); - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - streams.push_back(video_stream()); - - InitializeDemuxer(&streams, base::TimeDelta()); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(true); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - - EXPECT_CALL(*mocks_->demuxer(), - OnAudioRendererDisabled()); - EXPECT_CALL(*mocks_->audio_decoder(), - OnAudioRendererDisabled()); - EXPECT_CALL(*mocks_->audio_renderer(), - OnAudioRendererDisabled()); - EXPECT_CALL(*mocks_->video_decoder(), - OnAudioRendererDisabled()); - EXPECT_CALL(*mocks_->video_renderer(), - OnAudioRendererDisabled()); - - InitializePipeline(PIPELINE_OK); - EXPECT_TRUE(pipeline_->IsInitialized()); - EXPECT_FALSE(pipeline_->HasAudio()); - EXPECT_TRUE(pipeline_->HasVideo()); - - // Verify that ended event is fired when video ends. - EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) - .WillOnce(Return(true)); - EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); - FilterHost* host = pipeline_; - host->NotifyEnded(); -} - -TEST_F(PipelineImplTest, EndedCallback) { - CreateAudioStream(); - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - streams.push_back(video_stream()); - - InitializeDemuxer(&streams, base::TimeDelta()); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - InitializePipeline(PIPELINE_OK); - - // For convenience to simulate filters calling the methods. - FilterHost* host = pipeline_; - - // Due to short circuit evaluation we only need to test a subset of cases. - InSequence s; - EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) - .WillOnce(Return(false)); - host->NotifyEnded(); - - EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) - .WillOnce(Return(true)); - EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) - .WillOnce(Return(false)); - host->NotifyEnded(); - - EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) - .WillOnce(Return(true)); - EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) - .WillOnce(Return(true)); - EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); - host->NotifyEnded(); -} - -// Static function & time variable used to simulate changes in wallclock time. -static int64 g_static_clock_time; -static base::Time StaticClockFunction() { - return base::Time::FromInternalValue(g_static_clock_time); -} - -TEST_F(PipelineImplTest, AudioStreamShorterThanVideo) { - base::TimeDelta duration = base::TimeDelta::FromSeconds(10); - - CreateAudioStream(); - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - streams.push_back(video_stream()); - - InitializeDemuxer(&streams, duration); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - InitializePipeline(PIPELINE_OK); - - // For convenience to simulate filters calling the methods. - FilterHost* host = pipeline_; - - // Replace the clock so we can simulate wallclock time advancing w/o using - // Sleep(). - pipeline_->SetClockForTesting(new Clock(&StaticClockFunction)); - - EXPECT_EQ(0, host->GetTime().ToInternalValue()); - - float playback_rate = 1.0f; - EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate)); - EXPECT_CALL(*mocks_->video_decoder(), SetPlaybackRate(playback_rate)); - EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(playback_rate)); - EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(playback_rate)); - EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate)); - pipeline_->SetPlaybackRate(playback_rate); - message_loop_.RunAllPending(); - - InSequence s; - - // Verify that the clock doesn't advance since it hasn't been started by - // a time update from the audio stream. - int64 start_time = host->GetTime().ToInternalValue(); - g_static_clock_time += - base::TimeDelta::FromMilliseconds(100).ToInternalValue(); - EXPECT_EQ(host->GetTime().ToInternalValue(), start_time); - - // Signal end of audio stream. - EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) - .WillOnce(Return(true)); - EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) - .WillOnce(Return(false)); - host->NotifyEnded(); - message_loop_.RunAllPending(); - - // Verify that the clock advances. - start_time = host->GetTime().ToInternalValue(); - g_static_clock_time += - base::TimeDelta::FromMilliseconds(100).ToInternalValue(); - EXPECT_GT(host->GetTime().ToInternalValue(), start_time); - - // Signal end of video stream and make sure OnEnded() callback occurs. - EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) - .WillOnce(Return(true)); - EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) - .WillOnce(Return(true)); - EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); - host->NotifyEnded(); -} - -void SendReadErrorToCB(::testing::Unused, const FilterStatusCB& cb) { - cb.Run(PIPELINE_ERROR_READ); -} - -TEST_F(PipelineImplTest, ErrorDuringSeek) { - CreateAudioStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - - InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(10)); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(); - InitializePipeline(PIPELINE_OK); - - float playback_rate = 1.0f; - EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate)); - EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(playback_rate)); - EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate)); - pipeline_->SetPlaybackRate(playback_rate); - message_loop_.RunAllPending(); - - InSequence s; - - base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); - - EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _)) - .WillOnce(Invoke(&SendReadErrorToCB)); - - pipeline_->Seek(seek_time,base::Bind(&CallbackHelper::OnSeek, - base::Unretained(&callbacks_))); - EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)); - EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); - message_loop_.RunAllPending(); -} - -// Invoked function OnError. This asserts that the pipeline does not enqueue -// non-teardown related tasks while tearing down. -static void TestNoCallsAfterError( - PipelineImpl* pipeline, MessageLoop* message_loop, - PipelineStatus /* status */) { - CHECK(pipeline); - CHECK(message_loop); - - // When we get to this stage, the message loop should be empty. - message_loop->AssertIdle(); - - // Make calls on pipeline after error has occurred. - pipeline->SetPlaybackRate(0.5f); - pipeline->SetVolume(0.5f); - pipeline->SetPreload(AUTO); - - // No additional tasks should be queued as a result of these calls. - message_loop->AssertIdle(); -} - -TEST_F(PipelineImplTest, NoMessageDuringTearDownFromError) { - CreateAudioStream(); - MockDemuxerStreamVector streams; - streams.push_back(audio_stream()); - - InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(10)); - InitializeAudioDecoder(audio_stream()); - InitializeAudioRenderer(); - InitializePipeline(PIPELINE_OK); - - // Trigger additional requests on the pipeline during tear down from error. - base::Callback cb = base::Bind( - &TestNoCallsAfterError, pipeline_, &message_loop_); - ON_CALL(callbacks_, OnError(_)) - .WillByDefault(Invoke(&cb, &base::Callback::Run)); - - InSequence s; - - base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); - - EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _)) - .WillOnce(Invoke(&SendReadErrorToCB)); - - pipeline_->Seek(seek_time,base::Bind(&CallbackHelper::OnSeek, - base::Unretained(&callbacks_))); - EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)); - EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); - message_loop_.RunAllPending(); -} - -TEST_F(PipelineImplTest, StartTimeIsZero) { - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(video_stream()); - - const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); - InitializeDemuxer(&streams, kDuration); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - - InitializePipeline(PIPELINE_OK); - EXPECT_TRUE(pipeline_->IsInitialized()); - EXPECT_FALSE(pipeline_->HasAudio()); - EXPECT_TRUE(pipeline_->HasVideo()); - - EXPECT_EQ(base::TimeDelta(), pipeline_->GetCurrentTime()); -} - -TEST_F(PipelineImplTest, StartTimeIsNonZero) { - const base::TimeDelta kStartTime = base::TimeDelta::FromSeconds(4); - const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); - - EXPECT_CALL(*mocks_->demuxer(), GetStartTime()) - .WillRepeatedly(Return(kStartTime)); - - CreateVideoStream(); - MockDemuxerStreamVector streams; - streams.push_back(video_stream()); - - InitializeDemuxer(&streams, kDuration); - InitializeVideoDecoder(video_stream()); - InitializeVideoRenderer(); - - InitializePipeline(PIPELINE_OK); - EXPECT_TRUE(pipeline_->IsInitialized()); - EXPECT_FALSE(pipeline_->HasAudio()); - EXPECT_TRUE(pipeline_->HasVideo()); - - EXPECT_EQ(kStartTime, pipeline_->GetCurrentTime()); -} - -class FlexibleCallbackRunner : public base::DelegateSimpleThread::Delegate { - public: - FlexibleCallbackRunner(base::TimeDelta delay, PipelineStatus status, - const PipelineStatusCB& callback) - : delay_(delay), - status_(status), - callback_(callback) { - if (delay_ < base::TimeDelta()) { - callback_.Run(status_); - return; - } - } - virtual void Run() { - if (delay_ < base::TimeDelta()) return; - base::PlatformThread::Sleep(delay_); - callback_.Run(status_); - } - - private: - base::TimeDelta delay_; - PipelineStatus status_; - PipelineStatusCB callback_; -}; - -void TestPipelineStatusNotification(base::TimeDelta delay) { - PipelineStatusNotification note; - // Arbitrary error value we expect to fish out of the notification after the - // callback is fired. - const PipelineStatus expected_error = PIPELINE_ERROR_URL_NOT_FOUND; - FlexibleCallbackRunner runner(delay, expected_error, note.Callback()); - base::DelegateSimpleThread thread(&runner, "FlexibleCallbackRunner"); - thread.Start(); - note.Wait(); - EXPECT_EQ(note.status(), expected_error); - thread.Join(); -} - -// Test that in-line callback (same thread, no yield) works correctly. -TEST(PipelineStatusNotificationTest, InlineCallback) { - TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(-1)); -} - -// Test that different-thread, no-delay callback works correctly. -TEST(PipelineStatusNotificationTest, ImmediateCallback) { - TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(0)); -} - -// Test that different-thread, some-delay callback (the expected common case) -// works correctly. -TEST(PipelineStatusNotificationTest, DelayedCallback) { - TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(20)); -} - -} // namespace media diff --git a/media/base/pipeline_unittest.cc b/media/base/pipeline_unittest.cc new file mode 100644 index 0000000..58f028f --- /dev/null +++ b/media/base/pipeline_unittest.cc @@ -0,0 +1,928 @@ +// Copyright (c) 2012 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 + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/stl_util.h" +#include "base/threading/simple_thread.h" +#include "media/base/clock.h" +#include "media/base/filter_host.h" +#include "media/base/filters.h" +#include "media/base/media_log.h" +#include "media/base/pipeline.h" +#include "media/base/mock_callback.h" +#include "media/base/mock_filters.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/size.h" + +using ::testing::_; +using ::testing::DeleteArg; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeArgument; +using ::testing::Mock; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::StrictMock; +using ::testing::WithArg; + +namespace media { + +// Total bytes of the data source. +static const int kTotalBytes = 1024; + +// Buffered bytes of the data source. +static const int kBufferedBytes = 1024; + +// Used for setting expectations on pipeline callbacks. Using a StrictMock +// also lets us test for missing callbacks. +class CallbackHelper { + public: + CallbackHelper() {} + virtual ~CallbackHelper() {} + + MOCK_METHOD1(OnStart, void(PipelineStatus)); + MOCK_METHOD1(OnSeek, void(PipelineStatus)); + MOCK_METHOD1(OnStop, void(PipelineStatus)); + MOCK_METHOD1(OnEnded, void(PipelineStatus)); + MOCK_METHOD1(OnError, void(PipelineStatus)); + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackHelper); +}; + +// Run |cb| w/ OK status. +static void RunPipelineStatusOKCB(const PipelineStatusCB& cb) { + cb.Run(PIPELINE_OK); +} + +// TODO(scherkus): even though some filters are initialized on separate +// threads these test aren't flaky... why? It's because filters' Initialize() +// is executed on |message_loop_| and the mock filters instantly call +// InitializationComplete(), which keeps the pipeline humming along. If +// either filters don't call InitializationComplete() immediately or filter +// initialization is moved to a separate thread this test will become flaky. +class PipelineTest : public ::testing::Test { + public: + PipelineTest() + : pipeline_(new Pipeline(&message_loop_, new MediaLog())) { + pipeline_->Init( + base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)), + base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)), + Pipeline::NetworkEventCB()); + mocks_.reset(new MockFilterCollection()); + + // InitializeDemuxer adds overriding expectations for expected non-NULL + // streams. + DemuxerStream* null_pointer = NULL; + EXPECT_CALL(*mocks_->demuxer(), GetStream(_)) + .WillRepeatedly(Return(null_pointer)); + + EXPECT_CALL(*mocks_->demuxer(), GetStartTime()) + .WillRepeatedly(Return(base::TimeDelta())); + } + + virtual ~PipelineTest() { + if (!pipeline_->IsRunning()) { + return; + } + + // Expect a stop callback if we were started. + EXPECT_CALL(callbacks_, OnStop(PIPELINE_OK)); + pipeline_->Stop(base::Bind(&CallbackHelper::OnStop, + base::Unretained(&callbacks_))); + message_loop_.RunAllPending(); + + mocks_.reset(); + } + + protected: + // Sets up expectations to allow the demuxer to initialize. + typedef std::vector MockDemuxerStreamVector; + void InitializeDemuxer(MockDemuxerStreamVector* streams, + const base::TimeDelta& duration) { + mocks_->demuxer()->SetTotalAndBufferedBytesAndDuration( + kTotalBytes, kBufferedBytes, duration); + EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->demuxer(), SetPreload(AUTO)); + EXPECT_CALL(*mocks_->demuxer(), Seek(mocks_->demuxer()->GetStartTime(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->demuxer(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + + // Configure the demuxer to return the streams. + for (size_t i = 0; i < streams->size(); ++i) { + scoped_refptr stream((*streams)[i]); + EXPECT_CALL(*mocks_->demuxer(), GetStream(stream->type())) + .WillRepeatedly(Return(stream)); + } + } + + StrictMock* CreateStream(DemuxerStream::Type type) { + StrictMock* stream = + new StrictMock(); + EXPECT_CALL(*stream, type()) + .WillRepeatedly(Return(type)); + return stream; + } + + // Sets up expectations to allow the video decoder to initialize. + void InitializeVideoDecoder(MockDemuxerStream* stream) { + EXPECT_CALL(*mocks_->video_decoder(), + Initialize(stream, _, _)) + .WillOnce(WithArg<1>(Invoke(&RunPipelineStatusOKCB))); + EXPECT_CALL(*mocks_->video_decoder(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->video_decoder(), + Seek(mocks_->demuxer()->GetStartTime(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->video_decoder(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + } + + // Sets up expectations to allow the audio decoder to initialize. + void InitializeAudioDecoder(MockDemuxerStream* stream) { + EXPECT_CALL(*mocks_->audio_decoder(), Initialize(stream, _, _)) + .WillOnce(Invoke(&RunFilterCallback3)); + EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->audio_decoder(), Seek(base::TimeDelta(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->audio_decoder(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + } + + // Sets up expectations to allow the video renderer to initialize. + void InitializeVideoRenderer() { + EXPECT_CALL(*mocks_->video_renderer(), + Initialize(mocks_->video_decoder(), _, _)) + .WillOnce(Invoke(&RunFilterCallback3)); + EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->video_renderer(), + Seek(mocks_->demuxer()->GetStartTime(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->video_renderer(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + } + + // Sets up expectations to allow the audio renderer to initialize. + void InitializeAudioRenderer(bool disable_after_init_callback = false) { + if (disable_after_init_callback) { + EXPECT_CALL(*mocks_->audio_renderer(), + Initialize(mocks_->audio_decoder(), _, _)) + .WillOnce(DoAll(Invoke(&RunFilterCallback3), + DisableAudioRenderer(mocks_->audio_renderer()))); + } else { + EXPECT_CALL(*mocks_->audio_renderer(), + Initialize(mocks_->audio_decoder(), _, _)) + .WillOnce(Invoke(&RunFilterCallback3)); + } + EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f)); + EXPECT_CALL(*mocks_->audio_renderer(), Seek(base::TimeDelta(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->audio_renderer(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + } + + // Sets up expectations on the callback and initializes the pipeline. Called + // after tests have set expectations any filters they wish to use. + void InitializePipeline() { + InitializePipeline(PIPELINE_OK); + } + // Most tests can expect the |filter_collection|'s |build_status| to get + // reflected in |Start()|'s argument. + void InitializePipeline(PipelineStatus start_status) { + InitializePipeline(start_status, start_status); + } + // But some tests require different statuses in build & Start. + void InitializePipeline(PipelineStatus build_status, + PipelineStatus start_status) { + // Expect an initialization callback. + EXPECT_CALL(callbacks_, OnStart(start_status)); + + pipeline_->Start(mocks_->filter_collection(true, + true, + true, + build_status).Pass(), + "", + base::Bind(&CallbackHelper::OnStart, + base::Unretained(&callbacks_))); + + message_loop_.RunAllPending(); + } + + void CreateAudioStream() { + audio_stream_ = CreateStream(DemuxerStream::AUDIO); + } + + void CreateVideoStream() { + video_stream_ = CreateStream(DemuxerStream::VIDEO); + } + + MockDemuxerStream* audio_stream() { + return audio_stream_; + } + + MockDemuxerStream* video_stream() { + return video_stream_; + } + + void ExpectSeek(const base::TimeDelta& seek_time) { + // Every filter should receive a call to Seek(). + EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + + if (audio_stream_) { + EXPECT_CALL(*mocks_->audio_decoder(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->audio_renderer(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + } + + if (video_stream_) { + EXPECT_CALL(*mocks_->video_decoder(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->video_renderer(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + } + + // We expect a successful seek callback. + EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK)); + } + + void DoSeek(const base::TimeDelta& seek_time) { + pipeline_->Seek(seek_time, + base::Bind(&CallbackHelper::OnSeek, + base::Unretained(&callbacks_))); + + // We expect the time to be updated only after the seek has completed. + EXPECT_NE(seek_time, pipeline_->GetCurrentTime()); + message_loop_.RunAllPending(); + EXPECT_EQ(seek_time, pipeline_->GetCurrentTime()); + } + + // Fixture members. + StrictMock callbacks_; + MessageLoop message_loop_; + scoped_refptr pipeline_; + scoped_ptr mocks_; + scoped_refptr > audio_stream_; + scoped_refptr > video_stream_; + + private: + DISALLOW_COPY_AND_ASSIGN(PipelineTest); +}; + +// Test that playback controls methods no-op when the pipeline hasn't been +// started. +TEST_F(PipelineTest, NotStarted) { + const base::TimeDelta kZero; + + // StrictMock<> will ensure these never get called, and valgrind will + // make sure the callbacks are instantly deleted. + pipeline_->Stop(base::Bind(&CallbackHelper::OnStop, + base::Unretained(&callbacks_))); + pipeline_->Seek(kZero, + base::Bind(&CallbackHelper::OnSeek, + base::Unretained(&callbacks_))); + + EXPECT_FALSE(pipeline_->IsRunning()); + EXPECT_FALSE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_FALSE(pipeline_->HasVideo()); + + // Setting should still work. + EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); + pipeline_->SetPlaybackRate(-1.0f); + EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); + pipeline_->SetPlaybackRate(1.0f); + EXPECT_EQ(1.0f, pipeline_->GetPlaybackRate()); + + // Setting should still work. + EXPECT_EQ(1.0f, pipeline_->GetVolume()); + pipeline_->SetVolume(-1.0f); + EXPECT_EQ(1.0f, pipeline_->GetVolume()); + pipeline_->SetVolume(0.0f); + EXPECT_EQ(0.0f, pipeline_->GetVolume()); + + EXPECT_TRUE(kZero == pipeline_->GetCurrentTime()); + EXPECT_TRUE(kZero == pipeline_->GetBufferedTime()); + EXPECT_TRUE(kZero == pipeline_->GetMediaDuration()); + + EXPECT_EQ(0, pipeline_->GetBufferedBytes()); + EXPECT_EQ(0, pipeline_->GetTotalBytes()); + + // Should always get set to zero. + gfx::Size size(1, 1); + pipeline_->GetNaturalVideoSize(&size); + EXPECT_EQ(0, size.width()); + EXPECT_EQ(0, size.height()); +} + +TEST_F(PipelineTest, NeverInitializes) { + // This test hangs during initialization by never calling + // InitializationComplete(). StrictMock<> will ensure that the callback is + // never executed. + pipeline_->Start(mocks_->filter_collection(false, + false, + true, + PIPELINE_OK).Pass(), + "", + base::Bind(&CallbackHelper::OnStart, + base::Unretained(&callbacks_))); + message_loop_.RunAllPending(); + + EXPECT_FALSE(pipeline_->IsInitialized()); + + // Because our callback will get executed when the test tears down, we'll + // verify that nothing has been called, then set our expectation for the call + // made during tear down. + Mock::VerifyAndClear(&callbacks_); + EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK)); +} + +TEST_F(PipelineTest, RequiredFilterMissing) { + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING)); + + // Sets up expectations on the callback and initializes the pipeline. Called + // after tests have set expectations any filters they wish to use. + // Expect an initialization callback. + EXPECT_CALL(callbacks_, OnStart(PIPELINE_ERROR_REQUIRED_FILTER_MISSING)); + + // Create a filter collection with missing filter. + scoped_ptr collection(mocks_->filter_collection( + false, true, true, PIPELINE_ERROR_REQUIRED_FILTER_MISSING)); + pipeline_->Start(collection.Pass(), "", + base::Bind(&CallbackHelper::OnStart, + base::Unretained(&callbacks_))); + message_loop_.RunAllPending(); + + EXPECT_FALSE(pipeline_->IsInitialized()); +} + +TEST_F(PipelineTest, URLNotFound) { + // TODO(acolwell,fischman): Since OnStart() is getting called with an error + // code already, OnError() doesn't also need to get called. Fix the pipeline + // (and it's consumers!) so that OnError doesn't need to be called after + // another callback has already reported the error. Same applies to NoStreams + // below. + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_URL_NOT_FOUND)); + InitializePipeline(PIPELINE_ERROR_URL_NOT_FOUND); + EXPECT_FALSE(pipeline_->IsInitialized()); +} + +TEST_F(PipelineTest, NoStreams) { + // Manually set these expectations because SetPlaybackRate() is not called if + // we cannot fully initialize the pipeline. + EXPECT_CALL(*mocks_->demuxer(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + // TODO(acolwell,fischman): see TODO in URLNotFound above. + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_COULD_NOT_RENDER)); + + InitializePipeline(PIPELINE_OK, PIPELINE_ERROR_COULD_NOT_RENDER); + EXPECT_FALSE(pipeline_->IsInitialized()); +} + +TEST_F(PipelineTest, AudioStream) { + CreateAudioStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_TRUE(pipeline_->HasAudio()); + EXPECT_FALSE(pipeline_->HasVideo()); +} + +TEST_F(PipelineTest, VideoStream) { + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); +} + +TEST_F(PipelineTest, AudioVideoStream) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_TRUE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); +} + +TEST_F(PipelineTest, Seek) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000)); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + // Every filter should receive a call to Seek(). + base::TimeDelta expected = base::TimeDelta::FromSeconds(2000); + ExpectSeek(expected); + + // Initialize then seek! + InitializePipeline(PIPELINE_OK); + DoSeek(expected); +} + +TEST_F(PipelineTest, SetVolume) { + CreateAudioStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + + // The audio renderer should receive a call to SetVolume(). + float expected = 0.5f; + EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(expected)); + + // Initialize then set volume! + InitializePipeline(PIPELINE_OK); + pipeline_->SetVolume(expected); +} + +TEST_F(PipelineTest, Properties) { + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); + InitializeDemuxer(&streams, kDuration); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_EQ(kDuration.ToInternalValue(), + pipeline_->GetMediaDuration().ToInternalValue()); + EXPECT_EQ(kTotalBytes, pipeline_->GetTotalBytes()); + EXPECT_EQ(kBufferedBytes, pipeline_->GetBufferedBytes()); + + // Because kTotalBytes and kBufferedBytes are equal to each other, + // the entire video should be buffered. + EXPECT_EQ(kDuration.ToInternalValue(), + pipeline_->GetBufferedTime().ToInternalValue()); +} + +TEST_F(PipelineTest, GetBufferedTime) { + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); + InitializeDemuxer(&streams, kDuration); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + + // TODO(vrk): The following mini-test cases are order-dependent, and should + // probably be separated into independent test cases. + + // Buffered time is 0 if no bytes are buffered. + pipeline_->SetBufferedBytes(0); + EXPECT_EQ(0, pipeline_->GetBufferedTime().ToInternalValue()); + + // We should return buffered_time_ if it is set, valid and less than + // the current time. + const base::TimeDelta buffered = base::TimeDelta::FromSeconds(10); + pipeline_->SetBufferedTime(buffered); + EXPECT_EQ(buffered.ToInternalValue(), + pipeline_->GetBufferedTime().ToInternalValue()); + + // Test the case where the current time is beyond the buffered time. + base::TimeDelta kSeekTime = buffered + base::TimeDelta::FromSeconds(5); + ExpectSeek(kSeekTime); + DoSeek(kSeekTime); + + // Verify that buffered time is equal to the current time. + EXPECT_EQ(kSeekTime, pipeline_->GetCurrentTime()); + EXPECT_EQ(kSeekTime, pipeline_->GetBufferedTime()); + + // Clear buffered time. + pipeline_->SetBufferedTime(base::TimeDelta()); + + double time_percent = + static_cast(pipeline_->GetCurrentTime().ToInternalValue()) / + kDuration.ToInternalValue(); + + int estimated_bytes = static_cast(time_percent * kTotalBytes); + + // Test VBR case where bytes have been consumed slower than the average rate. + pipeline_->SetBufferedBytes(estimated_bytes - 10); + EXPECT_EQ(pipeline_->GetCurrentTime(), pipeline_->GetBufferedTime()); + + // Test VBR case where the bytes have been consumed faster than the average + // rate. + pipeline_->SetBufferedBytes(estimated_bytes + 10); + EXPECT_LT(pipeline_->GetCurrentTime(), pipeline_->GetBufferedTime()); + + // If media has been fully received, we should return the duration + // of the media. + pipeline_->SetBufferedBytes(kTotalBytes); + EXPECT_EQ(kDuration.ToInternalValue(), + pipeline_->GetBufferedTime().ToInternalValue()); +} + +TEST_F(PipelineTest, DisableAudioRenderer) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_TRUE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); + + EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(1.0f)) + .WillOnce(DisableAudioRenderer(mocks_->audio_renderer())); + EXPECT_CALL(*mocks_->demuxer(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->audio_decoder(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->audio_renderer(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->video_decoder(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->video_renderer(), + OnAudioRendererDisabled()); + + mocks_->audio_renderer()->SetPlaybackRate(1.0f); + + // Verify that ended event is fired when video ends. + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); + FilterHost* host = pipeline_; + host->NotifyEnded(); +} + +TEST_F(PipelineTest, DisableAudioRendererDuringInit) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(true); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + EXPECT_CALL(*mocks_->demuxer(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->audio_decoder(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->audio_renderer(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->video_decoder(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->video_renderer(), + OnAudioRendererDisabled()); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); + + // Verify that ended event is fired when video ends. + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); + FilterHost* host = pipeline_; + host->NotifyEnded(); +} + +TEST_F(PipelineTest, EndedCallback) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + InitializePipeline(PIPELINE_OK); + + // For convenience to simulate filters calling the methods. + FilterHost* host = pipeline_; + + // Due to short circuit evaluation we only need to test a subset of cases. + InSequence s; + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(false)); + host->NotifyEnded(); + + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(false)); + host->NotifyEnded(); + + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); + host->NotifyEnded(); +} + +// Static function & time variable used to simulate changes in wallclock time. +static int64 g_static_clock_time; +static base::Time StaticClockFunction() { + return base::Time::FromInternalValue(g_static_clock_time); +} + +TEST_F(PipelineTest, AudioStreamShorterThanVideo) { + base::TimeDelta duration = base::TimeDelta::FromSeconds(10); + + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, duration); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + InitializePipeline(PIPELINE_OK); + + // For convenience to simulate filters calling the methods. + FilterHost* host = pipeline_; + + // Replace the clock so we can simulate wallclock time advancing w/o using + // Sleep(). + pipeline_->SetClockForTesting(new Clock(&StaticClockFunction)); + + EXPECT_EQ(0, host->GetTime().ToInternalValue()); + + float playback_rate = 1.0f; + EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->video_decoder(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate)); + pipeline_->SetPlaybackRate(playback_rate); + message_loop_.RunAllPending(); + + InSequence s; + + // Verify that the clock doesn't advance since it hasn't been started by + // a time update from the audio stream. + int64 start_time = host->GetTime().ToInternalValue(); + g_static_clock_time += + base::TimeDelta::FromMilliseconds(100).ToInternalValue(); + EXPECT_EQ(host->GetTime().ToInternalValue(), start_time); + + // Signal end of audio stream. + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(false)); + host->NotifyEnded(); + message_loop_.RunAllPending(); + + // Verify that the clock advances. + start_time = host->GetTime().ToInternalValue(); + g_static_clock_time += + base::TimeDelta::FromMilliseconds(100).ToInternalValue(); + EXPECT_GT(host->GetTime().ToInternalValue(), start_time); + + // Signal end of video stream and make sure OnEnded() callback occurs. + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); + host->NotifyEnded(); +} + +void SendReadErrorToCB(::testing::Unused, const FilterStatusCB& cb) { + cb.Run(PIPELINE_ERROR_READ); +} + +TEST_F(PipelineTest, ErrorDuringSeek) { + CreateAudioStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + + InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(10)); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializePipeline(PIPELINE_OK); + + float playback_rate = 1.0f; + EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate)); + pipeline_->SetPlaybackRate(playback_rate); + message_loop_.RunAllPending(); + + InSequence s; + + base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); + + EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _)) + .WillOnce(Invoke(&SendReadErrorToCB)); + + pipeline_->Seek(seek_time,base::Bind(&CallbackHelper::OnSeek, + base::Unretained(&callbacks_))); + EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)); + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); + message_loop_.RunAllPending(); +} + +// Invoked function OnError. This asserts that the pipeline does not enqueue +// non-teardown related tasks while tearing down. +static void TestNoCallsAfterError( + Pipeline* pipeline, MessageLoop* message_loop, + PipelineStatus /* status */) { + CHECK(pipeline); + CHECK(message_loop); + + // When we get to this stage, the message loop should be empty. + message_loop->AssertIdle(); + + // Make calls on pipeline after error has occurred. + pipeline->SetPlaybackRate(0.5f); + pipeline->SetVolume(0.5f); + pipeline->SetPreload(AUTO); + + // No additional tasks should be queued as a result of these calls. + message_loop->AssertIdle(); +} + +TEST_F(PipelineTest, NoMessageDuringTearDownFromError) { + CreateAudioStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + + InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(10)); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializePipeline(PIPELINE_OK); + + // Trigger additional requests on the pipeline during tear down from error. + base::Callback cb = base::Bind( + &TestNoCallsAfterError, pipeline_, &message_loop_); + ON_CALL(callbacks_, OnError(_)) + .WillByDefault(Invoke(&cb, &base::Callback::Run)); + + InSequence s; + + base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); + + EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _)) + .WillOnce(Invoke(&SendReadErrorToCB)); + + pipeline_->Seek(seek_time,base::Bind(&CallbackHelper::OnSeek, + base::Unretained(&callbacks_))); + EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)); + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); + message_loop_.RunAllPending(); +} + +TEST_F(PipelineTest, StartTimeIsZero) { + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); + InitializeDemuxer(&streams, kDuration); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); + + EXPECT_EQ(base::TimeDelta(), pipeline_->GetCurrentTime()); +} + +TEST_F(PipelineTest, StartTimeIsNonZero) { + const base::TimeDelta kStartTime = base::TimeDelta::FromSeconds(4); + const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); + + EXPECT_CALL(*mocks_->demuxer(), GetStartTime()) + .WillRepeatedly(Return(kStartTime)); + + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, kDuration); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); + + EXPECT_EQ(kStartTime, pipeline_->GetCurrentTime()); +} + +class FlexibleCallbackRunner : public base::DelegateSimpleThread::Delegate { + public: + FlexibleCallbackRunner(base::TimeDelta delay, PipelineStatus status, + const PipelineStatusCB& callback) + : delay_(delay), + status_(status), + callback_(callback) { + if (delay_ < base::TimeDelta()) { + callback_.Run(status_); + return; + } + } + virtual void Run() { + if (delay_ < base::TimeDelta()) return; + base::PlatformThread::Sleep(delay_); + callback_.Run(status_); + } + + private: + base::TimeDelta delay_; + PipelineStatus status_; + PipelineStatusCB callback_; +}; + +void TestPipelineStatusNotification(base::TimeDelta delay) { + PipelineStatusNotification note; + // Arbitrary error value we expect to fish out of the notification after the + // callback is fired. + const PipelineStatus expected_error = PIPELINE_ERROR_URL_NOT_FOUND; + FlexibleCallbackRunner runner(delay, expected_error, note.Callback()); + base::DelegateSimpleThread thread(&runner, "FlexibleCallbackRunner"); + thread.Start(); + note.Wait(); + EXPECT_EQ(note.status(), expected_error); + thread.Join(); +} + +// Test that in-line callback (same thread, no yield) works correctly. +TEST(PipelineStatusNotificationTest, InlineCallback) { + TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(-1)); +} + +// Test that different-thread, no-delay callback works correctly. +TEST(PipelineStatusNotificationTest, ImmediateCallback) { + TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(0)); +} + +// Test that different-thread, some-delay callback (the expected common case) +// works correctly. +TEST(PipelineStatusNotificationTest, DelayedCallback) { + TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(20)); +} + +} // namespace media diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc index 1e8cc60..71b32e5 100644 --- a/media/filters/ffmpeg_audio_decoder.cc +++ b/media/filters/ffmpeg_audio_decoder.cc @@ -9,6 +9,7 @@ #include "media/base/data_buffer.h" #include "media/base/demuxer.h" #include "media/base/filter_host.h" +#include "media/base/pipeline.h" #include "media/ffmpeg/ffmpeg_common.h" namespace media { @@ -113,7 +114,7 @@ void FFmpegAudioDecoder::DoInitialize( const AudioDecoderConfig& config = stream->audio_decoder_config(); stats_callback_ = stats_callback; - // TODO(scherkus): this check should go in PipelineImpl prior to creating + // TODO(scherkus): this check should go in Pipeline prior to creating // decoder objects. if (!config.IsValidConfig()) { DLOG(ERROR) << "Invalid audio stream -" diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc index 213c09e..fc1236c 100644 --- a/media/filters/ffmpeg_video_decoder.cc +++ b/media/filters/ffmpeg_video_decoder.cc @@ -12,6 +12,7 @@ #include "media/base/filter_host.h" #include "media/base/limits.h" #include "media/base/media_switches.h" +#include "media/base/pipeline.h" #include "media/base/video_decoder_config.h" #include "media/base/video_frame.h" #include "media/base/video_util.h" @@ -85,7 +86,7 @@ void FFmpegVideoDecoder::Initialize(DemuxerStream* demuxer_stream, const VideoDecoderConfig& config = demuxer_stream->video_decoder_config(); - // TODO(scherkus): this check should go in PipelineImpl prior to creating + // TODO(scherkus): this check should go in Pipeline prior to creating // decoder objects. if (!config.IsValidConfig()) { DLOG(ERROR) << "Invalid video stream - " << config.AsHumanReadableString(); diff --git a/media/filters/gpu_video_decoder.cc b/media/filters/gpu_video_decoder.cc index 3b7ea08..193ccd8 100644 --- a/media/filters/gpu_video_decoder.cc +++ b/media/filters/gpu_video_decoder.cc @@ -9,6 +9,7 @@ #include "base/stl_util.h" #include "media/base/demuxer_stream.h" #include "media/base/filter_host.h" +#include "media/base/pipeline.h" #include "media/base/video_decoder_config.h" #include "media/ffmpeg/ffmpeg_common.h" @@ -155,7 +156,7 @@ void GpuVideoDecoder::Initialize(DemuxerStream* demuxer_stream, } const VideoDecoderConfig& config = demuxer_stream->video_decoder_config(); - // TODO(scherkus): this check should go in PipelineImpl prior to creating + // TODO(scherkus): this check should go in Pipeline prior to creating // decoder objects. if (!config.IsValidConfig()) { DLOG(ERROR) << "Invalid video stream - " << config.AsHumanReadableString(); diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc index decadd4..f95fd34 100644 --- a/media/filters/pipeline_integration_test.cc +++ b/media/filters/pipeline_integration_test.cc @@ -3,10 +3,11 @@ // found in the LICENSE file. #include "base/bind.h" +#include "base/message_loop.h" #include "media/base/filter_collection.h" #include "media/base/media_log.h" #include "media/base/message_loop_factory_impl.h" -#include "media/base/pipeline_impl.h" +#include "media/base/pipeline.h" #include "media/base/test_data_util.h" #include "media/filters/ffmpeg_audio_decoder.h" #include "media/filters/ffmpeg_demuxer_factory.h" @@ -21,7 +22,7 @@ using ::testing::AnyNumber; namespace media { -// Integration tests for PipelineImpl. Real demuxers, real decoders, and +// Integration tests for Pipeline. Real demuxers, real decoders, and // base renderer implementations are used to verify pipeline functionality. The // renderers used in these tests rely heavily on the AudioRendererBase & // VideoRendererBase implementations which contain a majority of the code used @@ -34,7 +35,7 @@ class PipelineIntegrationTest : public testing::Test { public: PipelineIntegrationTest() : message_loop_factory_(new MessageLoopFactoryImpl()), - pipeline_(new PipelineImpl(&message_loop_, new MediaLog())), + pipeline_(new Pipeline(&message_loop_, new MediaLog())), ended_(false) { pipeline_->Init( base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)), diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc index 8248f88..df7350c 100644 --- a/media/filters/video_renderer_base.cc +++ b/media/filters/video_renderer_base.cc @@ -8,6 +8,7 @@ #include "media/base/buffers.h" #include "media/base/filter_host.h" #include "media/base/limits.h" +#include "media/base/pipeline.h" #include "media/base/video_frame.h" #include "media/filters/video_renderer_base.h" diff --git a/media/media.gyp b/media/media.gyp index 8bfe118..4d550df 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -148,9 +148,8 @@ 'base/message_loop_factory.h', 'base/message_loop_factory_impl.cc', 'base/message_loop_factory_impl.h', + 'base/pipeline.cc', 'base/pipeline.h', - 'base/pipeline_impl.cc', - 'base/pipeline_impl.h', 'base/pipeline_status.h', 'base/preload.h', 'base/pts_heap.cc', @@ -605,7 +604,7 @@ 'base/filter_collection_unittest.cc', 'base/h264_bitstream_converter_unittest.cc', 'base/mock_reader.h', - 'base/pipeline_impl_unittest.cc', + 'base/pipeline_unittest.cc', 'base/pts_heap_unittest.cc', 'base/pts_stream_unittest.cc', 'base/run_all_unittests.cc', diff --git a/media/tools/player_wtl/movie.cc b/media/tools/player_wtl/movie.cc index 44b20b2..f97f88a 100644 --- a/media/tools/player_wtl/movie.cc +++ b/media/tools/player_wtl/movie.cc @@ -11,7 +11,7 @@ #include "media/base/filter_collection.h" #include "media/base/media_log.h" #include "media/base/message_loop_factory_impl.h" -#include "media/base/pipeline_impl.h" +#include "media/base/pipeline.h" #include "media/filters/ffmpeg_audio_decoder.h" #include "media/filters/ffmpeg_demuxer_factory.h" #include "media/filters/ffmpeg_video_decoder.h" @@ -25,7 +25,7 @@ using media::FFmpegDemuxerFactory; using media::FFmpegVideoDecoder; using media::FileDataSourceFactory; using media::FilterCollection; -using media::PipelineImpl; +using media::Pipeline; using media::ReferenceAudioRenderer; namespace media { @@ -68,7 +68,7 @@ bool Movie::Open(const wchar_t* url, VideoRendererBase* video_renderer) { MessageLoop* pipeline_loop = message_loop_factory_->GetMessageLoop("PipelineThread"); - pipeline_ = new PipelineImpl(pipeline_loop, new media::MediaLog()); + pipeline_ = new Pipeline(pipeline_loop, new media::MediaLog()); // Create filter collection. scoped_ptr collection(new FilterCollection()); diff --git a/media/tools/player_wtl/movie.h b/media/tools/player_wtl/movie.h index 0b407bf..f6a3c44 100644 --- a/media/tools/player_wtl/movie.h +++ b/media/tools/player_wtl/movie.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -18,7 +18,7 @@ template struct DefaultSingletonTraits; namespace media { -class PipelineImpl; +class Pipeline; class VideoRendererBase; class Movie { @@ -83,7 +83,7 @@ class Movie { Movie(); virtual ~Movie(); - scoped_refptr pipeline_; + scoped_refptr pipeline_; scoped_ptr message_loop_factory_; scoped_refptr audio_manager_; diff --git a/media/tools/player_wtl/player_wtl.cc b/media/tools/player_wtl/player_wtl.cc index 7ae160a..7756042 100644 --- a/media/tools/player_wtl/player_wtl.cc +++ b/media/tools/player_wtl/player_wtl.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -8,7 +8,7 @@ #include "base/at_exit.h" #include "base/command_line.h" -#include "media/base/pipeline_impl.h" +#include "media/base/pipeline.h" #include "media/filters/ffmpeg_audio_decoder.h" #include "media/filters/ffmpeg_demuxer.h" #include "media/filters/ffmpeg_video_decoder.h" diff --git a/media/tools/player_x11/player_x11.cc b/media/tools/player_x11/player_x11.cc index 11188db..f4b9fcc 100644 --- a/media/tools/player_x11/player_x11.cc +++ b/media/tools/player_x11/player_x11.cc @@ -21,7 +21,7 @@ #include "media/base/media_log.h" #include "media/base/media_switches.h" #include "media/base/message_loop_factory_impl.h" -#include "media/base/pipeline_impl.h" +#include "media/base/pipeline.h" #include "media/base/video_frame.h" #include "media/filters/ffmpeg_audio_decoder.h" #include "media/filters/ffmpeg_demuxer_factory.h" @@ -100,7 +100,7 @@ bool InitPipeline(MessageLoop* message_loop, const char* filename, const PaintCB& paint_cb, bool enable_audio, - scoped_refptr* pipeline, + scoped_refptr* pipeline, MessageLoop* paint_message_loop, media::MessageLoopFactory* message_loop_factory) { // Load media libraries. @@ -135,7 +135,7 @@ bool InitPipeline(MessageLoop* message_loop, } // Create the pipeline and start it. - *pipeline = new media::PipelineImpl(message_loop, new media::MediaLog()); + *pipeline = new media::Pipeline(message_loop, new media::MediaLog()); media::PipelineStatusNotification note; (*pipeline)->Start(collection.Pass(), filename, note.Callback()); @@ -157,7 +157,7 @@ void TerminateHandler(int signal) { } void PeriodicalUpdate( - media::PipelineImpl* pipeline, + media::Pipeline* pipeline, MessageLoop* message_loop, bool audio_only) { if (!g_running) { @@ -267,7 +267,7 @@ int main(int argc, char** argv) { scoped_ptr message_loop_factory( new media::MessageLoopFactoryImpl()); scoped_ptr thread; - scoped_refptr pipeline; + scoped_refptr pipeline; MessageLoop message_loop; thread.reset(new base::Thread("PipelineThread")); thread->Start(); -- cgit v1.1