diff options
author | ralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-06 20:27:51 +0000 |
---|---|---|
committer | ralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-06 20:27:51 +0000 |
commit | ea8395df79a1585f765946a002676cf2bf1c65c5 (patch) | |
tree | 3bd886fe009ab4f9591aef43a4982504a39d6c1f /media | |
parent | f6ea4733ac52a29238825efcfe07d9b7a33a63d0 (diff) | |
download | chromium_src-ea8395df79a1585f765946a002676cf2bf1c65c5.zip chromium_src-ea8395df79a1585f765946a002676cf2bf1c65c5.tar.gz chromium_src-ea8395df79a1585f765946a002676cf2bf1c65c5.tar.bz2 |
Pipeline_Impl was modified to properly render a stream that has video but no audio. A unit test accompanies the change.
Note that one minor other change was snuck in with this change. The data_source_impl.cc had a TODO to set a more specific error
when a read failed. Because I was already updating the pipeline error enum, I added the error code, changed the call to
host_->Error(), and removed the TODO.
Review URL: http://codereview.chromium.org/39170
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@11148 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/base/filter_host.h | 3 | ||||
-rw-r--r-- | media/base/filter_host_impl.h | 12 | ||||
-rw-r--r-- | media/base/mock_media_filters.h | 67 | ||||
-rw-r--r-- | media/base/mock_pipeline.h | 4 | ||||
-rw-r--r-- | media/base/pipeline.h | 17 | ||||
-rw-r--r-- | media/base/pipeline_impl.cc | 213 | ||||
-rw-r--r-- | media/base/pipeline_impl.h | 86 | ||||
-rw-r--r-- | media/base/pipeline_impl_unittest.cc | 82 | ||||
-rw-r--r-- | media/filters/file_data_source.cc | 2 | ||||
-rw-r--r-- | media/filters/file_data_source_unittest.cc | 2 |
10 files changed, 276 insertions, 212 deletions
diff --git a/media/base/filter_host.h b/media/base/filter_host.h index d04d4d4..28e56d1 100644 --- a/media/base/filter_host.h +++ b/media/base/filter_host.h @@ -62,7 +62,8 @@ class FilterHost { // Posts a task to be executed asynchronously on the pipeline's thread. virtual void PostTask(Task* task) = 0; - // Stops execution of the pipeline due to a fatal error. + // Stops execution of the pipeline due to a fatal error. Do not call this + // method with PIPELINE_OK or PIPELINE_STOPPING (used internally by pipeline). virtual void Error(PipelineError error) = 0; // Sets the current time. Any filters that have registered a callback through diff --git a/media/base/filter_host_impl.h b/media/base/filter_host_impl.h index df48592..af1c9e3 100644 --- a/media/base/filter_host_impl.h +++ b/media/base/filter_host_impl.h @@ -47,14 +47,12 @@ class FilterHostImpl : public FilterHost { ~FilterHostImpl() {} // If this FilterHost contains a filter of the specifed Filter class, then - // this method returns a pointer to the interface, otherwise it returns NULL. + // this method returns a pointer to the interface, otherwise it returns NULL + // in |*filter_out|. template <class Filter> - Filter* GetFilter() const { - Filter* filter = NULL; - if (Filter::filter_type() == filter_type_) { - filter = reinterpret_cast<Filter*>(media_filter()); - } - return filter; + void GetFilter(scoped_refptr<Filter>* filter_out) const { + *filter_out = (Filter::filter_type() == filter_type_) ? + reinterpret_cast<Filter*>(media_filter()) : NULL; } // Call the filter if it has registered a time update callback if the filter diff --git a/media/base/mock_media_filters.h b/media/base/mock_media_filters.h index ec324ed..dd6dd3d 100644 --- a/media/base/mock_media_filters.h +++ b/media/base/mock_media_filters.h @@ -23,7 +23,7 @@ enum MockDataSourceBehavior { MOCK_DATA_SOURCE_NORMAL_INIT, MOCK_DATA_SOURCE_NEVER_INIT, MOCK_DATA_SOURCE_TASK_INIT, - MOCK_DATA_SOURCE_ERROR_IN_INIT, + MOCK_DATA_SOURCE_URL_ERROR_IN_INIT, MOCK_DATA_SOURCE_INIT_RETURN_FALSE, MOCK_DATA_SOURCE_TASK_ERROR_PRE_INIT, MOCK_DATA_SOURCE_TASK_ERROR_POST_INIT @@ -96,8 +96,8 @@ class MockDataSource : public DataSource { case MOCK_DATA_SOURCE_TASK_INIT: host_->PostTask(NewRunnableMethod(this, &MockDataSource::TaskBehavior)); return true; - case MOCK_DATA_SOURCE_ERROR_IN_INIT: - host_->Error(PIPELINE_ERROR_NETWORK); + case MOCK_DATA_SOURCE_URL_ERROR_IN_INIT: + host_->Error(PIPELINE_ERROR_URL_NOT_FOUND); return false; case MOCK_DATA_SOURCE_INIT_RETURN_FALSE: return false; @@ -529,9 +529,10 @@ class MockVideoRenderer : public VideoRenderer { //------------------------------------------------------------------------------ -// Simple class that derives from the WaitableEvent class. The event remains -// in the reset state until the initialization complete callback is called from -// a media pipeline. The normal use of this object looks like: +// A simple class that waits for a pipeline to be started and checks some +// basic initialization values. The Start() method will not return until +// either a pre-dermined amount of time has passed or the pipeline calls the +// InitCallback() callback. A typical use would be: // Pipeline p; // FilterFactoryCollection f; // f->AddFactory(a); @@ -539,12 +540,17 @@ class MockVideoRenderer : public VideoRenderer { // ... // InitializationHelper h; // h.Start(&p, f, uri); -// h.Wait(); -// (when the Wait() returns, the pipeline is initialized or in error state) -class InitializationHelper : public base::WaitableEvent { +// +// If the test is expecting to produce an error use would be: +// h.Start(&p, f, uri, PIPELINE_ERROR_REQUIRED_FILTER_MISSING) +// +// If the test expects the pipeline to hang during initialization (a filter +// never calls FilterHost::InitializationComplete()) then the use would be: +// h.Start(&p, f, uri, PIPELINE_OK, true); +class InitializationHelper { public: InitializationHelper() - : WaitableEvent(true, false), + : event_(true, false), callback_success_status_(false), waiting_for_callback_(false) {} @@ -560,32 +566,45 @@ class InitializationHelper : public base::WaitableEvent { // to this object. void Start(Pipeline* pipeline, FilterFactory* filter_factory, - const std::string& uri) { - Reset(); + const std::string& uri, + PipelineError expect_error = PIPELINE_OK, + bool expect_hang = false) { + // For tests that we expect to hang in initialization, we want to + // wait a short time. If a hang is not expected, then wait long enough + // to make sure that the filters have time to initalize. 1/2 second if + // we expect to hang, and 3 seconds if we expect success. + base::TimeDelta max_wait = base::TimeDelta::FromMilliseconds(expect_hang ? + 500 : 3000); + EXPECT_FALSE(waiting_for_callback_); waiting_for_callback_ = true; + callback_success_status_ = false; + event_.Reset(); pipeline->Start(filter_factory, uri, NewCallback(this, &InitializationHelper::InitCallback)); - } - - // Resets the state. This method should not be called if waiting for - // a callback from a previous call to Start. Note that the Start method - // resets the state, so callers are not required to call this method prior - // to calling the start method. - void Reset() { - EXPECT_FALSE(waiting_for_callback_); - base::WaitableEvent::Reset(); - callback_success_status_ = false; + bool signaled = event_.TimedWait(max_wait); + if (expect_hang) { + EXPECT_FALSE(signaled); + EXPECT_FALSE(pipeline->IsInitialized()); + EXPECT_TRUE(waiting_for_callback_); + } else { + EXPECT_TRUE(signaled); + EXPECT_FALSE(waiting_for_callback_); + EXPECT_EQ(pipeline->GetError(), expect_error); + EXPECT_EQ(callback_success_status_, (expect_error == PIPELINE_OK)); + EXPECT_EQ(pipeline->IsInitialized(), (expect_error == PIPELINE_OK)); + } } private: void InitCallback(bool success) { EXPECT_TRUE(waiting_for_callback_); - EXPECT_FALSE(IsSignaled()); + EXPECT_FALSE(event_.IsSignaled()); waiting_for_callback_ = false; callback_success_status_ = success; - Signal(); + event_.Signal(); } + base::WaitableEvent event_; bool callback_success_status_; bool waiting_for_callback_; diff --git a/media/base/mock_pipeline.h b/media/base/mock_pipeline.h index 8741cc2..d320301 100644 --- a/media/base/mock_pipeline.h +++ b/media/base/mock_pipeline.h @@ -74,6 +74,10 @@ class MockPipeline : public media::Pipeline { return error_; } + virtual bool IsRendered(const std::string&) const { + return true; + } + // Implementation of Pipeline interface. virtual bool Start(FilterFactory* filter_factory, const std::string& url, diff --git a/media/base/pipeline.h b/media/base/pipeline.h index f6ef541..da3dfc4 100644 --- a/media/base/pipeline.h +++ b/media/base/pipeline.h @@ -17,14 +17,21 @@ namespace media { -// Error definitions for pipeline. +// Error definitions for pipeline. All codes indicate an error except: +// PIPELINE_OK indicates the pipeline is running normally. +// PIPELINE_STOPPING is used internally when Pipeline::Stop() is called. enum PipelineError { PIPELINE_OK, + PIPELINE_STOPPING, + PIPELINE_ERROR_URL_NOT_FOUND, PIPELINE_ERROR_NETWORK, PIPELINE_ERROR_DECODE, PIPELINE_ERROR_ABORT, PIPELINE_ERROR_INITIALIZATION_FAILED, - PIPELINE_STOPPING + PIPELINE_ERROR_REQUIRED_FILTER_MISSING, + PIPELINE_ERROR_OUT_OF_MEMORY, + PIPELINE_ERROR_COULD_NOT_RENDER, + PIPELINE_ERROR_READ }; // Base class for Pipeline class which allows for read-only access to members. @@ -90,6 +97,12 @@ class PipelineStatus { // operating correctly, this will return OK. virtual PipelineError GetError() const = 0; + // If the |major_mime_type| exists in the pipeline and is being rendered, this + // method will return true. Types are defined in media/base/media_foramt.h. + // For example, to determine if a pipeline contains video: + // bool has_video = pipeline->IsRendered(mime_type::kMajorTypeVideo); + virtual bool IsRendered(const std::string& major_mime_type) const = 0; + protected: virtual ~PipelineStatus() {} }; diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc index c815a26..67f4b9c 100644 --- a/media/base/pipeline_impl.cc +++ b/media/base/pipeline_impl.cc @@ -95,16 +95,25 @@ void PipelineImpl::InternalSetPlaybackRate(float rate) { playback_rate_ = rate; } - PipelineError PipelineImpl::GetError() const { AutoLock auto_lock(const_cast<Lock&>(lock_)); return error_; } +bool PipelineImpl::IsRendered(const std::string& major_mime_type) const { + AutoLock auto_lock(const_cast<Lock&>(lock_)); + bool is_rendered = (rendered_mime_types_.find(major_mime_type) != + rendered_mime_types_.end()); + return is_rendered; +} + + bool PipelineImpl::InternalSetError(PipelineError error) { + // Don't want callers to set an error of "OK". STOPPING is a special value + // that should only be used internally by the StopTask() method. + DCHECK(PIPELINE_OK != error && PIPELINE_STOPPING != error); AutoLock auto_lock(lock_); bool changed_error = false; - DCHECK(PIPELINE_OK != error); if (PIPELINE_OK == error_) { error_ = error; changed_error = true; @@ -184,6 +193,7 @@ void PipelineImpl::ResetState() { error_ = PIPELINE_OK; time_ = base::TimeDelta(); ticks_at_last_set_time_ = base::TimeTicks::Now(); + rendered_mime_types_.clear(); } void PipelineImpl::SetDuration(base::TimeDelta duration) { @@ -212,6 +222,12 @@ void PipelineImpl::SetVideoSize(size_t width, size_t height) { video_height_ = height; } +void PipelineImpl::InsertRenderedMimeType(const std::string& major_mime_type) { + AutoLock auto_lock(lock_); + rendered_mime_types_.insert(major_mime_type); +} + + //----------------------------------------------------------------------------- PipelineThread::PipelineThread(PipelineImpl* pipeline) @@ -339,8 +355,6 @@ void PipelineThread::PostTask(Task* task) { void PipelineThread::StartTask(FilterFactory* filter_factory, const std::string& url, Callback1<bool>::Type* init_complete_callback) { - bool success = true; - // During the entire StartTask we hold the initialization_lock_ so that // if the client calls the Pipeline::Stop method while we are running a // nested message loop, we can correctly unwind out of it before calling @@ -351,33 +365,30 @@ void PipelineThread::StartTask(FilterFactory* filter_factory, // we can delete filters at an appropriate time (when all tasks have been // processed and the thread is about to be destroyed). message_loop()->AddDestructionObserver(this); - success = CreateDataSource(filter_factory, url); - if (success) { - success = CreateAndConnect<Demuxer, DataSource>(filter_factory); - } - if (success) { - success = CreateDecoder<AudioDecoder>(filter_factory); - } - if (success) { - success = CreateAndConnect<AudioRenderer, AudioDecoder>(filter_factory); - } - if (success && HasVideo()) { - success = CreateDecoder<VideoDecoder>(filter_factory); - if (success) { - success = CreateAndConnect<VideoRenderer, VideoDecoder>(filter_factory); + + scoped_refptr<DataSource> data_source = CreateDataSource(filter_factory, url); + if (PipelineOk()) { + scoped_refptr<Demuxer> demuxer = + CreateFilter<Demuxer, DataSource>(filter_factory, data_source); + if (PipelineOk()) { + Render<AudioDecoder, AudioRenderer>(filter_factory, demuxer); + } + if (PipelineOk()) { + Render<VideoDecoder, VideoRenderer>(filter_factory, demuxer); } } - if (success) { - pipeline_->initialized_ = true; - } else { - Error(PIPELINE_ERROR_INITIALIZATION_FAILED); + + if (PipelineOk() && pipeline_->rendered_mime_types_.empty()) { + Error(PIPELINE_ERROR_COULD_NOT_RENDER); } + pipeline_->initialized_ = PipelineOk(); + // No matter what, we're done with the filter factory, and // client callback so get rid of them. filter_factory->Release(); if (init_complete_callback) { - init_complete_callback->Run(success); + init_complete_callback->Run(pipeline_->initialized_); delete init_complete_callback; } } @@ -387,7 +398,7 @@ void PipelineThread::StartTask(FilterFactory* filter_factory, // pipeline's error_ member to PIPELINE_STOPPING. We stop the filters in the // reverse order. void PipelineThread::StopTask() { - if (PIPELINE_OK == pipeline_->error_) { + if (PipelineOk()) { pipeline_->error_ = PIPELINE_STOPPING; } FilterHostVector::reverse_iterator riter = filter_hosts_.rbegin(); @@ -401,6 +412,32 @@ void PipelineThread::StopTask() { } } +template <class Decoder, class Renderer> +void PipelineThread::Render(FilterFactory* filter_factory, Demuxer* demuxer) { + DCHECK(PipelineOk()); + const std::string major_mime_type = Decoder::major_mime_type(); + const int num_outputs = demuxer->GetNumberOfStreams(); + for (int i = 0; i < num_outputs; ++i) { + DemuxerStream* demuxer_stream = demuxer->GetStream(i); + const MediaFormat* stream_format = demuxer_stream->GetMediaFormat(); + std::string value; + if (stream_format->GetAsString(MediaFormat::kMimeType, &value) && + 0 == value.compare(0, major_mime_type.length(), major_mime_type)) { + scoped_refptr<Decoder> decoder = + CreateFilter<Decoder, DemuxerStream>(filter_factory, demuxer_stream); + if (PipelineOk()) { + DCHECK(decoder); + CreateFilter<Renderer, Decoder>(filter_factory, decoder); + } + if (PipelineOk()) { + pipeline_->InsertRenderedMimeType(major_mime_type); + } + break; + } + } +} + + // Task runs as a result of a filter calling InitializationComplete. If for // some reason StopTask has been executed prior to this, the host_initializing_ // member will be NULL, and the message loop will have been quit already, so @@ -433,7 +470,8 @@ void PipelineThread::SeekTask(base::TimeDelta time) { void PipelineThread::SetVolumeTask(float volume) { pipeline_->volume_ = volume; - AudioRenderer* audio_renderer = GetFilter<AudioRenderer>(); + scoped_refptr<AudioRenderer> audio_renderer; + GetFilter(&audio_renderer); if (audio_renderer) { audio_renderer->SetVolume(volume); } @@ -449,37 +487,38 @@ void PipelineThread::SetTimeTask() { } template <class Filter> -Filter* PipelineThread::GetFilter() const { - Filter* filter = NULL; - FilterHostVector::const_iterator iter = filter_hosts_.begin(); - while (iter != filter_hosts_.end() && NULL == filter) { - filter = (*iter)->GetFilter<Filter>(); - ++iter; +void PipelineThread::GetFilter(scoped_refptr<Filter>* filter_out) const { + *filter_out = NULL; + for (FilterHostVector::const_iterator iter = filter_hosts_.begin(); + iter != filter_hosts_.end() && NULL == *filter_out; + iter++) { + (*iter)->GetFilter(filter_out); } - return filter; } template <class Filter, class Source> -bool PipelineThread::CreateFilter(FilterFactory* filter_factory, - Source source, - const MediaFormat* media_format) { +scoped_refptr<Filter> PipelineThread::CreateFilter( + FilterFactory* filter_factory, + Source source, + const MediaFormat* media_format) { + DCHECK(PipelineOk()); scoped_refptr<Filter> filter = filter_factory->Create<Filter>(media_format); - bool success = (NULL != filter); - if (success) { + if (!filter) { + Error(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); + } else { DCHECK(!host_initializing_); host_initializing_ = new FilterHostImpl(this, filter.get()); - success = (NULL != host_initializing_); - } - if (success) { - filter_hosts_.push_back(host_initializing_); - filter->SetFilterHost(host_initializing_); - - // The filter must return true from initialize and there must still not - // be an error or it's not successful. - success = (filter->Initialize(source) && - PIPELINE_OK == pipeline_->error_); + if (NULL == host_initializing_) { + Error(PIPELINE_ERROR_OUT_OF_MEMORY); + } else { + filter_hosts_.push_back(host_initializing_); + filter->SetFilterHost(host_initializing_); + if (!filter->Initialize(source)) { + Error(PIPELINE_ERROR_INITIALIZATION_FAILED); + } + } } - if (success) { + if (PipelineOk()) { // Now we run the thread's message loop recursively. We want all // pending tasks to be processed, so we set nestable tasks to be allowed // and then run the loop. The only way we exit the loop is as the result @@ -493,84 +532,26 @@ bool PipelineThread::CreateFilter(FilterFactory* filter_factory, message_loop()->Run(); message_loop()->SetNestableTasksAllowed(false); DCHECK(!host_initializing_); - - // If an error occurred while we were in the nested Run state, then - // not successful. When stopping, the |error_| member is set to a value of - // PIPELINE_STOPPING so we will exit in that case also with false. - success = (PIPELINE_OK == pipeline_->error_); + } else { + // This could still be set if we never ran the message loop (for example, + // if the fiter returned false from it's Initialize() method), so make sure + // to reset it. + host_initializing_ = NULL; } - - // This could still be set if we never ran the message loop (for example, - // if the fiter returned false from it's Initialize method), so make sure - // to reset it. - host_initializing_ = NULL; - - // If this method fails, but no error set, then indicate a general - // initialization failure. - if (!success) { - Error(PIPELINE_ERROR_INITIALIZATION_FAILED); + if (!PipelineOk()) { + filter = NULL; } - return success; + return filter; } -bool PipelineThread::CreateDataSource(FilterFactory* filter_factory, - const std::string& url) { +scoped_refptr<DataSource> PipelineThread::CreateDataSource( + FilterFactory* filter_factory, const std::string& url) { MediaFormat url_format; url_format.SetAsString(MediaFormat::kMimeType, mime_type::kURL); url_format.SetAsString(MediaFormat::kURL, url); return CreateFilter<DataSource>(filter_factory, url, &url_format); } -template <class Decoder> -bool PipelineThread::CreateDecoder(FilterFactory* filter_factory) { - Demuxer* demuxer = GetFilter<Demuxer>(); - if (demuxer) { - int num_outputs = demuxer->GetNumberOfStreams(); - for (int i = 0; i < num_outputs; ++i) { - DemuxerStream* stream = demuxer->GetStream(i); - const MediaFormat* stream_format = stream->GetMediaFormat(); - if (IsMajorMimeType(stream_format, Decoder::major_mime_type())) { - return CreateFilter<Decoder>(filter_factory, stream, stream_format); - } - } - } - return false; -} - -template <class NewFilter, class SourceFilter> -bool PipelineThread::CreateAndConnect(FilterFactory* filter_factory) { - SourceFilter* source_filter = GetFilter<SourceFilter>(); - bool success = (source_filter && - CreateFilter<NewFilter>(filter_factory, - source_filter, - source_filter->GetMediaFormat())); - return success; -} - -// TODO(ralphl): Consider making this part of the demuxer interface. -bool PipelineThread::HasVideo() const { - Demuxer* demuxer = GetFilter<Demuxer>(); - if (demuxer) { - int num_outputs = demuxer->GetNumberOfStreams(); - for (int i = 0; i < num_outputs; ++i) { - if (IsMajorMimeType(demuxer->GetStream(i)->GetMediaFormat(), - mime_type::kMajorTypeVideo)) { - return true; - } - } - } - return false; -} - -bool PipelineThread::IsMajorMimeType(const MediaFormat* media_format, - const std::string& major_mime_type) const { - std::string value; - if (media_format->GetAsString(MediaFormat::kMimeType, &value)) { - return (0 == value.compare(0, major_mime_type.length(), major_mime_type)); - } - return false; -} - // Called as a result of destruction of the thread. void PipelineThread::WillDestroyCurrentMessageLoop() { while (!filter_hosts_.empty()) { diff --git a/media/base/pipeline_impl.h b/media/base/pipeline_impl.h index 362b54e..a5b8c0e 100644 --- a/media/base/pipeline_impl.h +++ b/media/base/pipeline_impl.h @@ -9,6 +9,7 @@ #include <string> #include <vector> +#include <set> #include "base/message_loop.h" #include "base/ref_counted.h" @@ -43,6 +44,7 @@ class PipelineImpl : public Pipeline { virtual base::TimeDelta GetTime() const; virtual base::TimeDelta GetInterpolatedTime() const; virtual PipelineError GetError() const; + virtual bool IsRendered(const std::string& major_mime_type) const; // Impementation of Pipeline methods. virtual bool Start(FilterFactory* filter_factory, @@ -82,6 +84,10 @@ class PipelineImpl : public Pipeline { // alone, and returns false. bool InternalSetError(PipelineError error); + // Method called by the |pipeline_thread_| to insert a mime type into + // the |rendered_mime_types_| set. + void InsertRenderedMimeType(const std::string& major_mime_type); + // Holds a ref counted reference to the PipelineThread object associated // with this pipeline. Prior to the call to the Start method, this member // will be NULL, since no thread is running. @@ -143,6 +149,10 @@ class PipelineImpl : public Pipeline { // reset the pipeline state, and restore this to PIPELINE_OK. PipelineError error_; + // Vector of major mime types that have been rendered by this pipeline. + typedef std::set<std::string> RenderedMimeTypesSet; + RenderedMimeTypesSet rendered_mime_types_; + DISALLOW_COPY_AND_ASSIGN(PipelineImpl); }; @@ -178,7 +188,7 @@ class PipelineThread : public base::RefCountedThreadSafe<PipelineThread>, // that have registered a time update callback. void SetTime(base::TimeDelta time); - // Called by a FilterHostImpl on behalf of a filter calling FilerHost::Error. + // Called by a FilterHostImpl on behalf of a filter calling FilterHost::Error. // If the pipeline is running a nested message loop, it will be exited. void Error(PipelineError error); @@ -204,6 +214,9 @@ class PipelineThread : public base::RefCountedThreadSafe<PipelineThread>, friend class base::RefCountedThreadSafe<PipelineThread>; virtual ~PipelineThread(); + // Simple method used to make sure the pipeline is running normally. + bool PipelineOk() { return PIPELINE_OK == pipeline_->error_; } + // The following "task" methods correspond to the public methods, but these // methods are run as the result of posting a task to the PipelineThread's // message loop. For example, the Start method posts a task to call the @@ -224,18 +237,14 @@ class PipelineThread : public base::RefCountedThreadSafe<PipelineThread>, // Calls the Stop method on every filter in the pipeline void StopFilters(); - // Examines the demuxer filter output streams. If one contains video then - // returns true. - bool HasVideo() const; - // The following template funcions make use of the fact that media filter // derived interfaces are self-describing in the sense that they all contain // the static method filter_type() which returns a FilterType enum that - // uniquely identifies the filer's interface. In addition, filters that are + // uniquely identifies the filter's interface. In addition, filters that are // specific to audio or video also support a static method major_mime_type() // which returns a string of "audio/" or "video/". - // Uses the FilterFactory to create a new filter of the NewFilter class, and + // Uses the FilterFactory to create a new filter of the Filter class, and // initiaializes it using the Source object. The source may be another filter // or it could be a string in the case of a DataSource. // @@ -248,36 +257,51 @@ class PipelineThread : public base::RefCountedThreadSafe<PipelineThread>, // 1. The filter calls FilterHost::InitializationComplete() // 2. A filter calls FilterHost::Error() // 3. The client calls Pipeline::Stop() - template <class NewFilter, class Source> - bool CreateFilter(FilterFactory* filter_factory, - Source source, - const MediaFormat* source_media_format); + // + // Callers can optionally use the returned Filter for further processing, + // but since the call already placed the filter in the list of filter hosts, + // callers can ignore the return value. In any case, if this function can + // not create and initailze the speified Filter, then this method will return + // with |pipeline_->error_| != PIPELINE_OK. + template <class Filter, class Source> + scoped_refptr<Filter> CreateFilter(FilterFactory* filter_factory, + Source source, + const MediaFormat* source_media_format); + + // Creates a Filter and initilizes it with the given |source|. If a Filter + // could not be created or an error occurred, this metod returns NULL and the + // pipeline's |error_| member will contain a specific error code. Note that + // the Source could be a filter or a DemuxerStream, but it must support the + // GetMediaFormat() method. + template <class Filter, class Source> + scoped_refptr<Filter> CreateFilter(FilterFactory* filter_factory, + Source* source) { + return CreateFilter<Filter, Source*>(filter_factory, + source, + source->GetMediaFormat()); + } // Creates a DataSource (the first filter in a pipeline), and initializes it // with the specified URL. - bool CreateDataSource(FilterFactory* filter_factory, - const std::string& url); - - // Examines the list of existing filters to find a Source, then creates a - // NewFilter, and initializes it with the Source filter. - template <class NewFilter, class Source> - bool CreateAndConnect(FilterFactory* filter_factory); - - // Creates and initiaializes a decoder. - template <class Decoder> - bool CreateDecoder(FilterFactory* filter_factory); + scoped_refptr<DataSource> CreateDataSource(FilterFactory* filter_factory, + const std::string& url); + + // If the |demuxer| contains a stream that matches Decoder::major_media_type() + // this method creates and initializes the specified Decoder and Renderer. + // Callers should examine the |pipeline_->error_| member to see if there was + // an error duing the call. The lack of the specified stream does not + // constitute an error, and no Decoder or Renderer will be created if the + // data stream does not exist in the |demuxer|. If a stream is rendered, then + // this method will call |pipeline_|->InsertRenderedMimeType() to add the + // mime type to the set of rendered major mime types for the pipeline. + template <class Decoder, class Renderer> + void Render(FilterFactory* filter_factory, Demuxer* demuxer); // Examine the list of existing filters to find one that supports the - // specified Filter interface. If one exists, the specified Filter interface - // is returned otherwise the method retuns NULL. + // specified Filter interface. If one exists, the |filter_out| will contain + // the filter, |*filter_out| will be NULL. template <class Filter> - Filter* GetFilter() const; - - // Simple function that returns true if the specified MediaFormat object - // has a mime type that matches the major_mime_type. Examples of major mime - // types are "audio/" and "video/" - bool IsMajorMimeType(const MediaFormat* media_format, - const std::string& major_mime_type) const; + void GetFilter(scoped_refptr<Filter>* filter_out) const; // Pointer to the pipeline that owns this PipelineThread. PipelineImpl* const pipeline_; diff --git a/media/base/pipeline_impl_unittest.cc b/media/base/pipeline_impl_unittest.cc index dbaedcc..6685896 100644 --- a/media/base/pipeline_impl_unittest.cc +++ b/media/base/pipeline_impl_unittest.cc @@ -46,36 +46,32 @@ void AddAllMockFilters(FilterFactoryCollection* factories, // TODO(ralphl): Get rid of single character variable names in these tests. TEST(PipelineImplTest, Initialization) { std::string u(""); + + // This test hangs during initialization of the data source (it never + // calls InitializationComplete). Make sure we tear down the pipeline + // propertly. PipelineImpl p; InitializationHelper h; MockFilterConfig config; config.data_source_behavior = media::MOCK_DATA_SOURCE_NEVER_INIT; - h.Start(&p, MockDataSource::CreateFactory(&config), u); - h.TimedWait(base::TimeDelta::FromMilliseconds(300)); - EXPECT_TRUE(h.waiting_for_callback()); - EXPECT_FALSE(p.IsInitialized()); - EXPECT_TRUE(media::PIPELINE_OK == p.GetError()); + h.Start(&p, MockDataSource::CreateFactory(&config), u, + media::PIPELINE_OK, true); p.Stop(); EXPECT_FALSE(h.waiting_for_callback()); EXPECT_FALSE(h.callback_success_status()); EXPECT_TRUE(media::PIPELINE_OK == p.GetError()); + // This test should not hang. Should return an error indicating that we are + // missing a requried filter. config.data_source_behavior = media::MOCK_DATA_SOURCE_TASK_INIT; - h.Start(&p, MockDataSource::CreateFactory(&config), u); - h.TimedWait(base::TimeDelta::FromSeconds(5)); - EXPECT_FALSE(h.waiting_for_callback()); - EXPECT_FALSE(h.callback_success_status()); - EXPECT_FALSE(p.IsInitialized()); - EXPECT_FALSE(media::PIPELINE_OK == p.GetError()); + h.Start(&p, MockDataSource::CreateFactory(&config), u, + media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING); p.Stop(); - config.data_source_behavior = media::MOCK_DATA_SOURCE_ERROR_IN_INIT; - h.Start(&p, MockDataSource::CreateFactory(&config), u); - h.TimedWait(base::TimeDelta::FromSeconds(5)); - EXPECT_FALSE(h.waiting_for_callback()); - EXPECT_FALSE(h.callback_success_status()); - EXPECT_FALSE(p.IsInitialized()); - EXPECT_FALSE(media::PIPELINE_OK == p.GetError()); + // This test should return a specific error from the mock data source. + config.data_source_behavior = media::MOCK_DATA_SOURCE_URL_ERROR_IN_INIT; + h.Start(&p, MockDataSource::CreateFactory(&config), u, + media::PIPELINE_ERROR_URL_NOT_FOUND); p.Stop(); } @@ -88,13 +84,10 @@ TEST(PipelineImplTest, MockAudioPipeline) { AddAllMockFilters(c, &config); InitializationHelper h; h.Start(&p, c, url); - h.TimedWait(base::TimeDelta::FromSeconds(5)); - EXPECT_FALSE(h.waiting_for_callback()); - EXPECT_TRUE(h.callback_success_status()); - EXPECT_TRUE(p.IsInitialized()); - EXPECT_TRUE(media::PIPELINE_OK == p.GetError()); size_t width, height; p.GetVideoSize(&width, &height); + EXPECT_TRUE(p.IsRendered(media::mime_type::kMajorTypeAudio)); + EXPECT_FALSE(p.IsRendered(media::mime_type::kMajorTypeVideo)); EXPECT_EQ(0u, width); EXPECT_EQ(0u, height); p.SetPlaybackRate(1.0f); @@ -111,18 +104,51 @@ TEST(PipelineImplTest, MockVideoPipeline) { AddAllMockFilters(c, &config); InitializationHelper h; h.Start(&p, c, url); - h.TimedWait(base::TimeDelta::FromSeconds(5)); - EXPECT_FALSE(h.waiting_for_callback()); - EXPECT_TRUE(h.callback_success_status()); - EXPECT_TRUE(p.IsInitialized()); - EXPECT_TRUE(media::PIPELINE_OK == p.GetError()); size_t width, height; p.GetVideoSize(&width, &height); EXPECT_EQ(config.video_width, width); EXPECT_EQ(config.video_height, height); + EXPECT_TRUE(p.IsRendered(media::mime_type::kMajorTypeAudio)); + EXPECT_TRUE(p.IsRendered(media::mime_type::kMajorTypeVideo)); p.SetPlaybackRate(1.0f); p.SetVolume(0.5f); p.Stop(); EXPECT_FALSE(p.IsInitialized()); } +TEST(PipelineImplTest, MockVideoOnlyPipeline) { + std::string url(""); + PipelineImpl p; + scoped_refptr<FilterFactoryCollection> c = new FilterFactoryCollection(); + MockFilterConfig config; + config.has_audio = false; + AddAllMockFilters(c, &config); + InitializationHelper h; + h.Start(&p, c, url); + size_t width, height; + p.GetVideoSize(&width, &height); + EXPECT_EQ(config.video_width, width); + EXPECT_EQ(config.video_height, height); + EXPECT_FALSE(p.IsRendered(media::mime_type::kMajorTypeAudio)); + EXPECT_TRUE(p.IsRendered(media::mime_type::kMajorTypeVideo)); + p.SetPlaybackRate(1.0f); + p.Stop(); + EXPECT_FALSE(p.IsInitialized()); +} + +TEST(PipelineImplTest, MockNothingToRenderPipeline) { + std::string url(""); + PipelineImpl p; + scoped_refptr<FilterFactoryCollection> c = new FilterFactoryCollection(); + MockFilterConfig config; + config.has_audio = false; + config.has_video = false; + AddAllMockFilters(c, &config); + InitializationHelper h; + h.Start(&p, c, url, media::PIPELINE_ERROR_COULD_NOT_RENDER); + p.Stop(); +} + +// TODO(ralphl): Add a unit test that makes sure that the mock audio filter +// is actually called on a SetVolume() call to the pipeline. I almost checked +// in code that broke this, but all unit tests were passing. diff --git a/media/filters/file_data_source.cc b/media/filters/file_data_source.cc index 9e8f54b..854fea2 100644 --- a/media/filters/file_data_source.cc +++ b/media/filters/file_data_source.cc @@ -32,7 +32,7 @@ bool FileDataSource::Initialize(const std::string& url) { } if (!file_) { file_size_ = 0; - host_->Error(PIPELINE_ERROR_NETWORK); + host_->Error(PIPELINE_ERROR_URL_NOT_FOUND); return false; } media_format_.SetAsString(MediaFormat::kMimeType, diff --git a/media/filters/file_data_source_unittest.cc b/media/filters/file_data_source_unittest.cc index 3bce493..2545e7c 100644 --- a/media/filters/file_data_source_unittest.cc +++ b/media/filters/file_data_source_unittest.cc @@ -71,8 +71,6 @@ TEST(FileDataSourceTest, OpenFile) { c->AddFactory(MockAudioRenderer::CreateFactory(&config)); InitializationHelper h; h.Start(&pipeline, c, TestFileURL()); - h.Wait(); - EXPECT_EQ(pipeline.GetError(), media::PIPELINE_OK); EXPECT_EQ(pipeline.GetTotalBytes(), 10); EXPECT_EQ(pipeline.GetBufferedBytes(), 10); pipeline.Stop(); |