summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-06 20:27:51 +0000
committerralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-06 20:27:51 +0000
commitea8395df79a1585f765946a002676cf2bf1c65c5 (patch)
tree3bd886fe009ab4f9591aef43a4982504a39d6c1f /media
parentf6ea4733ac52a29238825efcfe07d9b7a33a63d0 (diff)
downloadchromium_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.h3
-rw-r--r--media/base/filter_host_impl.h12
-rw-r--r--media/base/mock_media_filters.h67
-rw-r--r--media/base/mock_pipeline.h4
-rw-r--r--media/base/pipeline.h17
-rw-r--r--media/base/pipeline_impl.cc213
-rw-r--r--media/base/pipeline_impl.h86
-rw-r--r--media/base/pipeline_impl_unittest.cc82
-rw-r--r--media/filters/file_data_source.cc2
-rw-r--r--media/filters/file_data_source_unittest.cc2
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();