diff options
-rw-r--r-- | chrome/renderer/webmediaplayer_impl.cc | 22 | ||||
-rw-r--r-- | chrome/renderer/webmediaplayer_impl.h | 8 | ||||
-rw-r--r-- | media/base/mock_media_filters.h | 184 | ||||
-rw-r--r-- | media/base/mock_pipeline.h | 5 | ||||
-rw-r--r-- | media/base/pipeline.h | 45 | ||||
-rw-r--r-- | media/base/pipeline_impl.cc | 81 | ||||
-rw-r--r-- | media/base/pipeline_impl.h | 29 | ||||
-rw-r--r-- | media/base/pipeline_impl_unittest.cc | 282 | ||||
-rw-r--r-- | media/filters/file_data_source_unittest.cc | 5 | ||||
-rw-r--r-- | media/filters/video_decoder_unittest.cc | 8 |
10 files changed, 416 insertions, 253 deletions
diff --git a/chrome/renderer/webmediaplayer_impl.cc b/chrome/renderer/webmediaplayer_impl.cc index 9cff367..781c49b 100644 --- a/chrome/renderer/webmediaplayer_impl.cc +++ b/chrome/renderer/webmediaplayer_impl.cc @@ -113,7 +113,7 @@ void WebMediaPlayerImpl::load(const WebKit::WebURL& url) { // Initialize the pipeline pipeline_.Start(filter_factory_.get(), url.spec(), - NewCallback(this, &WebMediaPlayerImpl::DidInitializePipeline)); + NewCallback(this, &WebMediaPlayerImpl::OnPipelineInitialize)); } void WebMediaPlayerImpl::cancelLoad() { @@ -146,14 +146,11 @@ void WebMediaPlayerImpl::stop() { void WebMediaPlayerImpl::seek(float seconds) { DCHECK(main_loop_ && MessageLoop::current() == main_loop_); - pipeline_.Seek(base::TimeDelta::FromSeconds(static_cast<int64>(seconds))); - - // Even though the seek might be in progress, WebKit's HTMLMediaElement - // thinks we're seeking unless we notify that the time has changed. - // - // TODO(scherkus): add a seek completion callback to the pipeline. - PostTask(kTimeChangedTaskIndex, - &WebKit::WebMediaPlayerClient::timeChanged); + // Try to preserve as much accuracy as possible. + float microseconds = seconds * base::Time::kMicrosecondsPerSecond; + pipeline_.Seek( + base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds)), + NewCallback(this, &WebMediaPlayerImpl::OnPipelineSeek)); } void WebMediaPlayerImpl::setEndTime(float seconds) { @@ -297,7 +294,7 @@ void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() { pipeline_.Stop(); } -void WebMediaPlayerImpl::DidInitializePipeline(bool successful) { +void WebMediaPlayerImpl::OnPipelineInitialize(bool successful) { if (successful) { // Since we have initialized the pipeline, say we have everything. // TODO(hclam): change this to report the correct status. @@ -316,6 +313,11 @@ void WebMediaPlayerImpl::DidInitializePipeline(bool successful) { &WebKit::WebMediaPlayerClient::readyStateChanged); } +void WebMediaPlayerImpl::OnPipelineSeek(bool successful) { + PostTask(kTimeChangedTaskIndex, + &WebKit::WebMediaPlayerClient::timeChanged); +} + void WebMediaPlayerImpl::SetVideoRenderer(VideoRendererImpl* video_renderer) { video_renderer_ = video_renderer; } diff --git a/chrome/renderer/webmediaplayer_impl.h b/chrome/renderer/webmediaplayer_impl.h index bce6205..f2202bf 100644 --- a/chrome/renderer/webmediaplayer_impl.h +++ b/chrome/renderer/webmediaplayer_impl.h @@ -132,9 +132,11 @@ class WebMediaPlayerImpl : public WebKit::WebMediaPlayer, // to it. virtual void WillDestroyCurrentMessageLoop(); - // Notification callback for initialization from |pipeline_|. |successful| is - // true if the pipeline initialization is successful otherwise false. - void DidInitializePipeline(bool successful); + // Notification from |pipeline_| when initialization has finished. + void OnPipelineInitialize(bool successful); + + // Notification from |pipeline_| when a seek has finished. + void OnPipelineSeek(bool successful); // Called from tasks posted to |main_loop_| from this object to remove // reference of them. diff --git a/media/base/mock_media_filters.h b/media/base/mock_media_filters.h index d043999..f2f57e1 100644 --- a/media/base/mock_media_filters.h +++ b/media/base/mock_media_filters.h @@ -37,7 +37,8 @@ enum MockDataSourceBehavior { // is typically allocated on the stack. struct MockFilterConfig { MockFilterConfig() - : data_source_behavior(MOCK_DATA_SOURCE_NORMAL_INIT), + : create_filter(true), + data_source_behavior(MOCK_DATA_SOURCE_NORMAL_INIT), data_source_value('!'), has_video(true), video_width(1280u), @@ -53,6 +54,7 @@ struct MockFilterConfig { media_total_bytes(media_duration.InMilliseconds() * 250) { } + bool create_filter; MockDataSourceBehavior data_source_behavior; char data_source_value; bool has_video; @@ -72,11 +74,6 @@ struct MockFilterConfig { class MockDataSource : public DataSource { public: - static FilterFactory* CreateFactory(const MockFilterConfig* config) { - return new FilterFactoryImpl1<MockDataSource, - const MockFilterConfig*>(config); - } - explicit MockDataSource(const MockFilterConfig* config) : config_(config), position_(0), @@ -94,6 +91,10 @@ class MockDataSource : public DataSource { // Implementation of MediaFilter. virtual void Stop() {} + virtual void Seek(base::TimeDelta time) { + seek_time_ = time; + } + // Implementation of DataSource. virtual bool Initialize(const std::string& url) { media_format_.SetAsString(MediaFormat::kMimeType, @@ -162,8 +163,9 @@ class MockDataSource : public DataSource { return true; } - // Simple position getter for unit testing. + // Mock accessors. int64 position() const { return position_; } + const base::TimeDelta& seek_time() const { return seek_time_; } private: virtual ~MockDataSource() { @@ -189,6 +191,7 @@ class MockDataSource : public DataSource { const MockFilterConfig* config_; int64 position_; MediaFormat media_format_; + base::TimeDelta seek_time_; // Set to true inside the destructor. Used in FFmpegGlue unit tests for // testing proper reference counting. @@ -197,7 +200,6 @@ class MockDataSource : public DataSource { DISALLOW_COPY_AND_ASSIGN(MockDataSource); }; -//------------------------------------------------------------------------------ class MockDemuxerStream : public DemuxerStream { public: @@ -230,15 +232,9 @@ class MockDemuxerStream : public DemuxerStream { DISALLOW_COPY_AND_ASSIGN(MockDemuxerStream); }; -//------------------------------------------------------------------------------ class MockDemuxer : public Demuxer { public: - static FilterFactory* CreateFactory(const MockFilterConfig* config) { - return new FilterFactoryImpl1<MockDemuxer, - const MockFilterConfig*>(config); - } - explicit MockDemuxer(const MockFilterConfig* config) : config_(config), mock_audio_stream_(new MockDemuxerStream(config, true)), @@ -248,6 +244,10 @@ class MockDemuxer : public Demuxer { // Implementation of MediaFilter. virtual void Stop() {} + virtual void Seek(base::TimeDelta time) { + seek_time_ = time; + } + // Implementation of Demuxer. virtual bool Initialize(DataSource* data_source) { host_->InitializationComplete(); @@ -284,29 +284,23 @@ class MockDemuxer : public Demuxer { return NULL; } + // Mock accessors. + const base::TimeDelta& seek_time() const { return seek_time_; } + private: virtual ~MockDemuxer() {} const MockFilterConfig* config_; scoped_refptr<DemuxerStream> mock_audio_stream_; scoped_refptr<DemuxerStream> mock_video_stream_; + base::TimeDelta seek_time_; DISALLOW_COPY_AND_ASSIGN(MockDemuxer); }; -//------------------------------------------------------------------------------ class MockAudioDecoder : public AudioDecoder { public: - static FilterFactory* CreateFactory(const MockFilterConfig* config) { - return new FilterFactoryImpl1<MockAudioDecoder, - const MockFilterConfig*>(config); - } - - static bool IsMediaFormatSupported(const MediaFormat& media_format) { - return true; // TODO(ralphl): check for a supported format. - } - explicit MockAudioDecoder(const MockFilterConfig* config) { media_format_.SetAsString(MediaFormat::kMimeType, config->uncompressed_audio_mime_type); @@ -315,6 +309,10 @@ class MockAudioDecoder : public AudioDecoder { // Implementation of MediaFilter. virtual void Stop() {} + virtual void Seek(base::TimeDelta time) { + seek_time_ = time; + } + // Implementation of AudioDecoder. virtual bool Initialize(DemuxerStream* stream) { host_->InitializationComplete(); @@ -330,32 +328,30 @@ class MockAudioDecoder : public AudioDecoder { NOTREACHED(); } + // Mock accessors. + const base::TimeDelta& seek_time() const { return seek_time_; } + private: virtual ~MockAudioDecoder() {} MediaFormat media_format_; + base::TimeDelta seek_time_; DISALLOW_COPY_AND_ASSIGN(MockAudioDecoder); }; -//------------------------------------------------------------------------------ class MockAudioRenderer : public AudioRenderer { public: - static FilterFactory* CreateFactory(const MockFilterConfig* config) { - return new FilterFactoryImpl1<MockAudioRenderer, - const MockFilterConfig*>(config); - } - - static bool IsMediaFormatSupported(const MediaFormat& media_format) { - return true; // TODO(ralphl): check for a supported format - } - explicit MockAudioRenderer(const MockFilterConfig* config) {} // Implementation of MediaFilter. virtual void Stop() {} + virtual void Seek(base::TimeDelta time) { + seek_time_ = time; + } + // Implementation of AudioRenderer. virtual bool Initialize(AudioDecoder* decoder) { host_->InitializationComplete(); @@ -364,25 +360,20 @@ class MockAudioRenderer : public AudioRenderer { virtual void SetVolume(float volume) {} + // Mock accessors. + const base::TimeDelta& seek_time() const { return seek_time_; } + private: virtual ~MockAudioRenderer() {} + base::TimeDelta seek_time_; + DISALLOW_COPY_AND_ASSIGN(MockAudioRenderer); }; -//------------------------------------------------------------------------------ class MockVideoDecoder : public VideoDecoder { public: - static FilterFactory* CreateFactory(const MockFilterConfig* config) { - return new FilterFactoryImpl1<MockVideoDecoder, - const MockFilterConfig*>(config); - } - - static bool IsMediaFormatSupported(const MediaFormat& media_format) { - return true; // TODO(ralphl): check for a supported format. - } - // Helper function that initializes a YV12 frame with white and black scan // lines based on the |white_to_black| parameter. If 0, then the entire // frame will be black, if 1 then the entire frame will be white. @@ -423,6 +414,10 @@ class MockVideoDecoder : public VideoDecoder { // Implementation of MediaFilter. virtual void Stop() {} + virtual void Seek(base::TimeDelta time) { + seek_time_ = time; + } + // Implementation of VideoDecoder. virtual bool Initialize(DemuxerStream* stream) { host_->InitializationComplete(); @@ -438,6 +433,9 @@ class MockVideoDecoder : public VideoDecoder { this, &MockVideoDecoder::DoRead, callback)); } + // Mock accessors. + const base::TimeDelta& seek_time() const { return seek_time_; } + private: virtual ~MockVideoDecoder() {} @@ -471,24 +469,15 @@ class MockVideoDecoder : public VideoDecoder { MediaFormat media_format_; base::TimeDelta mock_frame_time_; + base::TimeDelta seek_time_; const MockFilterConfig* config_; DISALLOW_COPY_AND_ASSIGN(MockVideoDecoder); }; -//------------------------------------------------------------------------------ class MockVideoRenderer : public VideoRenderer { public: - static FilterFactory* CreateFactory(const MockFilterConfig* config) { - return new FilterFactoryImpl1<MockVideoRenderer, - const MockFilterConfig*>(config); - } - - static bool IsMediaFormatSupported(const MediaFormat& media_format) { - return true; // TODO(ralphl): check for a supported format - } - explicit MockVideoRenderer(const MockFilterConfig* config) : config_(config) { } @@ -496,6 +485,10 @@ class MockVideoRenderer : public VideoRenderer { // Implementation of MediaFilter. virtual void Stop() {} + virtual void Seek(base::TimeDelta time) { + seek_time_ = time; + } + // Implementation of VideoRenderer. virtual bool Initialize(VideoDecoder* decoder) { host_->SetVideoSize(config_->video_width, config_->video_height); @@ -503,19 +496,94 @@ class MockVideoRenderer : public VideoRenderer { return true; } + // Mock accessors. + const base::TimeDelta& seek_time() const { return seek_time_; } + private: virtual ~MockVideoRenderer() {} + base::TimeDelta seek_time_; const MockFilterConfig* config_; DISALLOW_COPY_AND_ASSIGN(MockVideoRenderer); }; -//------------------------------------------------------------------------------ +// FilterFactory capable of creating each mock filter type. Only one instance +// of each filter type can exist at any time. Filters can be inspected for +// expectations using the accessors, which may return NULL if the filter was +// never created (i.e., streams containing no video). +class MockFilterFactory : public FilterFactory { + public: + explicit MockFilterFactory(const MockFilterConfig* config) + : config_(config) { + } + + // Mock accessors. + MockDataSource* data_source() const { return data_source_; } + MockDemuxer* demuxer() const { return demuxer_; } + MockAudioDecoder* audio_decoder() const { return audio_decoder_; } + MockVideoDecoder* video_decoder() const { return video_decoder_; } + MockAudioRenderer* audio_renderer() const { return audio_renderer_; } + MockVideoRenderer* video_renderer() const { return video_renderer_; } + + protected: + MediaFilter* Create(FilterType filter_type, const MediaFormat& media_format) { + if (!config_->create_filter) + return NULL; + + switch (filter_type) { + case FILTER_DATA_SOURCE: + DCHECK(!data_source_); + data_source_ = new MockDataSource(config_); + return data_source_; + + case FILTER_DEMUXER: + DCHECK(!demuxer_); + demuxer_ = new MockDemuxer(config_); + return demuxer_; + + case FILTER_AUDIO_DECODER: + DCHECK(!audio_decoder_); + audio_decoder_ = new MockAudioDecoder(config_); + return audio_decoder_; + + case FILTER_VIDEO_DECODER: + DCHECK(!video_decoder_); + video_decoder_ = new MockVideoDecoder(config_); + return video_decoder_; + + case FILTER_AUDIO_RENDERER: + DCHECK(!audio_renderer_); + audio_renderer_ = new MockAudioRenderer(config_); + return audio_renderer_; + + case FILTER_VIDEO_RENDERER: + DCHECK(!video_renderer_); + video_renderer_ = new MockVideoRenderer(config_); + return video_renderer_; + + default: + NOTREACHED(); + } + return NULL; + } + + private: + const MockFilterConfig* config_; + scoped_refptr<MockDataSource> data_source_; + scoped_refptr<MockDemuxer> demuxer_; + scoped_refptr<MockAudioDecoder> audio_decoder_; + scoped_refptr<MockVideoDecoder> video_decoder_; + scoped_refptr<MockAudioRenderer> audio_renderer_; + scoped_refptr<MockVideoRenderer> video_renderer_; + + DISALLOW_COPY_AND_ASSIGN(MockFilterFactory); +}; + // 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 +// either a pre-determined amount of time has passed or the pipeline calls the // InitCallback() callback. A typical use would be: // Pipeline p; // FilterFactoryCollection f; @@ -531,6 +599,8 @@ class MockVideoRenderer : public VideoRenderer { // 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); +// +// TODO(scherkus): Keep refactoring tests until we can remove this entirely. class InitializationHelper { public: InitializationHelper() @@ -543,7 +613,7 @@ class InitializationHelper { bool callback_success_status() { return callback_success_status_; } // Returns true if Start has been called, but the pipeline has not yet - // called the intialization complete callback. + // called the initialization complete callback. bool waiting_for_callback() { return waiting_for_callback_; } // Starts the pipeline, providing an initialization callback that points diff --git a/media/base/mock_pipeline.h b/media/base/mock_pipeline.h index e317a5d..dd31ca6 100644 --- a/media/base/mock_pipeline.h +++ b/media/base/mock_pipeline.h @@ -82,7 +82,7 @@ class MockPipeline : public media::Pipeline { // Implementation of Pipeline interface. virtual bool Start(FilterFactory* filter_factory, const std::string& url, - Callback1<bool>::Type* init_complete_callback) { + PipelineCallback* init_complete_callback) { EXPECT_FALSE(initialized_); initialized_ = true; if (init_complete_callback) { @@ -101,7 +101,8 @@ class MockPipeline : public media::Pipeline { playback_rate_ = playback_rate; } - virtual void Seek(base::TimeDelta time) { + virtual void Seek(base::TimeDelta time, + PipelineCallback* seek_complete_callback) { time_ = time; } diff --git a/media/base/pipeline.h b/media/base/pipeline.h index 3b18b81..0120863 100644 --- a/media/base/pipeline.h +++ b/media/base/pipeline.h @@ -116,6 +116,11 @@ class PipelineStatus { virtual ~PipelineStatus() {} }; +// Client-provided callbacks for various pipeline operations. +// +// TODO(scherkus): consider returning a PipelineError instead of a bool, or +// perhaps a client callback interface. +typedef Callback1<bool>::Type PipelineCallback; class Pipeline : public PipelineStatus { public: @@ -123,28 +128,28 @@ class Pipeline : public PipelineStatus { // construct a filter chain. Returns true if successful, false otherwise // (i.e., pipeline already started). Note that a return value of true // only indicates that the initialization process has started successfully. - // Pipeline initializaion is an inherently asynchronous process. Clients - // should not call SetPlaybackRate, Seek, or SetVolume until initialization - // is complete. Clients can either poll the IsInitialized() method (which is - // discouraged) or use the init_complete_callback as described below. + // Pipeline initialization is an inherently asynchronous process. Clients + // should not call SetPlaybackRate(), Seek(), or SetVolume() until + // initialization is complete. Clients can either poll the IsInitialized() + // method (which is discouraged) or use the |start_callback| as described + // below. // // This method is asynchronous and can execute a callback when completed. - // If the caller provides an init_complete_callback, it will be - // called when the pipeline initiailization completes. If successful, the - // callback's bool parameter will be true. If the callback is called with - // false, then the client can use the GetError method to obtain more - // information about the reason initialization failed. The prototype for - // the client callback is: + // If the caller provides a |start_callback|, it will be called when the + // pipeline initialization completes. If successful, the callback's bool + // parameter will be true. If the callback is called with false, then the + // client can use the GetError() method to obtain more information about the + // reason initialization failed. The prototype for the client callback is: // void Client::PipelineInitComplete(bool init_was_successful); // // Note that clients must not call the Stop method from within the - // init_complete_callback. Other methods, including SetPlaybackRate, - // Seek, and SetVolume may be called. The client will be called on a - // thread owned by the pipeline class, not on the thread that originally - // called the Start method. + // |start_callback|. Other methods, including SetPlaybackRate(), Seek(), and + // SetVolume() may be called. The client will be called on a thread owned by + // the pipeline class, not on the thread that originally called the Start() + // method. virtual bool Start(FilterFactory* filter_factory, const std::string& uri, - Callback1<bool>::Type* init_complete_callback) = 0; + PipelineCallback* start_callback) = 0; // Stops the pipeline and resets to an uninitialized state. This method // will block the calling thread until the pipeline has been completely @@ -158,15 +163,19 @@ class Pipeline : public PipelineStatus { // 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(ralphl) What about maximum rate? Does HTML5 specify a max? + // TODO(ralphl): What about maximum rate? Does HTML5 specify a max? // // This method must be called only after initialization has completed. virtual void SetPlaybackRate(float playback_rate) = 0; - // Attempt to seek to the position in microseconds. + // 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. + // The callback will return true if the seek was carried out, false otherwise + // (i.e., streaming media). // // This method must be called only after initialization has completed. - virtual void Seek(base::TimeDelta time) = 0; + virtual void Seek(base::TimeDelta time, + PipelineCallback* seek_callback) = 0; // 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 diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc index a4c85d5..88c3537 100644 --- a/media/base/pipeline_impl.cc +++ b/media/base/pipeline_impl.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "base/compiler_specific.h" +#include "base/stl_util-inl.h" #include "media/base/filter_host_impl.h" #include "media/base/media_format.h" #include "media/base/pipeline_impl.h" @@ -124,10 +125,11 @@ bool PipelineImpl::InternalSetError(PipelineError error) { // Creates the PipelineThread and calls it's start method. bool PipelineImpl::Start(FilterFactory* factory, const std::string& url, - Callback1<bool>::Type* init_complete_callback) { + PipelineCallback* init_complete_callback) { DCHECK(!pipeline_thread_); DCHECK(factory); DCHECK(!initialized_); + DCHECK(!IsPipelineThread()); if (!pipeline_thread_ && factory) { pipeline_thread_ = new PipelineThread(this); if (pipeline_thread_) { @@ -146,16 +148,18 @@ bool PipelineImpl::Start(FilterFactory* factory, // Stop the PipelineThread and return to a state identical to that of a newly // created PipelineImpl object. void PipelineImpl::Stop() { + DCHECK(!IsPipelineThread()); + if (pipeline_thread_) { pipeline_thread_->Stop(); } ResetState(); } - - void PipelineImpl::SetPlaybackRate(float rate) { - if (OkToCallThread() && rate >= 0.0f) { + DCHECK(!IsPipelineThread()); + + if (IsPipelineOk() && rate >= 0.0f) { pipeline_thread_->SetPlaybackRate(rate); } else { // It's OK for a client to call SetPlaybackRate(0.0f) if we're stopped. @@ -163,16 +167,21 @@ void PipelineImpl::SetPlaybackRate(float rate) { } } -void PipelineImpl::Seek(base::TimeDelta time) { - if (OkToCallThread()) { - pipeline_thread_->Seek(time); +void PipelineImpl::Seek(base::TimeDelta time, + PipelineCallback* seek_callback) { + DCHECK(!IsPipelineThread()); + + if (IsPipelineOk()) { + pipeline_thread_->Seek(time, seek_callback); } else { NOTREACHED(); } } void PipelineImpl::SetVolume(float volume) { - if (OkToCallThread() && volume >= 0.0f && volume <= 1.0f) { + DCHECK(!IsPipelineThread()); + + if (IsPipelineOk() && volume >= 0.0f && volume <= 1.0f) { pipeline_thread_->SetVolume(volume); } else { NOTREACHED(); @@ -197,6 +206,15 @@ void PipelineImpl::ResetState() { rendered_mime_types_.clear(); } +bool PipelineImpl::IsPipelineOk() const { + return pipeline_thread_ && initialized_ && PIPELINE_OK == error_; +} + +bool PipelineImpl::IsPipelineThread() const { + return pipeline_thread_ && + PlatformThread::CurrentId() == pipeline_thread_->thread_id(); +} + void PipelineImpl::SetDuration(base::TimeDelta duration) { AutoLock auto_lock(lock_); duration_ = duration; @@ -247,7 +265,7 @@ PipelineThread::~PipelineThread() { // thread. bool PipelineThread::Start(FilterFactory* filter_factory, const std::string& url, - Callback1<bool>::Type* init_complete_callback) { + PipelineCallback* init_complete_callback) { if (thread_.Start()) { filter_factory->AddRef(); PostTask(NewRunnableMethod(this, @@ -284,8 +302,10 @@ void PipelineThread::SetPlaybackRate(float rate) { } // Called on client's thread. -void PipelineThread::Seek(base::TimeDelta time) { - PostTask(NewRunnableMethod(this, &PipelineThread::SeekTask, time)); +void PipelineThread::Seek(base::TimeDelta time, + PipelineCallback* seek_callback) { + PostTask(NewRunnableMethod(this, &PipelineThread::SeekTask, time, + seek_callback)); } // Called on client's thread. @@ -355,7 +375,7 @@ void PipelineThread::PostTask(Task* task) { // handled by the CreateFilter method. void PipelineThread::StartTask(FilterFactory* filter_factory, const std::string& url, - Callback1<bool>::Type* init_complete_callback) { + PipelineCallback* init_complete_callback) { // 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 @@ -402,10 +422,11 @@ void PipelineThread::StopTask() { if (PipelineOk()) { pipeline_->error_ = PIPELINE_STOPPING; } - FilterHostVector::iterator iter = filter_hosts_.begin(); - while (iter != filter_hosts_.end()) { + + for (FilterHostVector::iterator iter = filter_hosts_.begin(); + iter != filter_hosts_.end(); + ++iter) { (*iter)->Stop(); - ++iter; } if (host_initializing_) { host_initializing_ = NULL; @@ -453,18 +474,23 @@ void PipelineThread::InitializationCompleteTask(FilterHostImpl* host) { void PipelineThread::SetPlaybackRateTask(float rate) { pipeline_->InternalSetPlaybackRate(rate); - FilterHostVector::iterator iter = filter_hosts_.begin(); - while (iter != filter_hosts_.end()) { + for (FilterHostVector::iterator iter = filter_hosts_.begin(); + iter != filter_hosts_.end(); + ++iter) { (*iter)->media_filter()->SetPlaybackRate(rate); - ++iter; } } -void PipelineThread::SeekTask(base::TimeDelta time) { - FilterHostVector::iterator iter = filter_hosts_.begin(); - while (iter != filter_hosts_.end()) { +void PipelineThread::SeekTask(base::TimeDelta time, + PipelineCallback* seek_callback) { + for (FilterHostVector::iterator iter = filter_hosts_.begin(); + iter != filter_hosts_.end(); + ++iter) { (*iter)->media_filter()->Seek(time); - ++iter; + } + if (seek_callback) { + seek_callback->Run(true); + delete seek_callback; } } @@ -479,10 +505,10 @@ void PipelineThread::SetVolumeTask(float volume) { void PipelineThread::SetTimeTask() { time_update_callback_scheduled_ = false; - FilterHostVector::iterator iter = filter_hosts_.begin(); - while (iter != filter_hosts_.end()) { + for (FilterHostVector::iterator iter = filter_hosts_.begin(); + iter != filter_hosts_.end(); + ++iter) { (*iter)->RunTimeUpdateCallback(pipeline_->time_); - ++iter; } } @@ -554,10 +580,7 @@ scoped_refptr<DataSource> PipelineThread::CreateDataSource( // Called as a result of destruction of the thread. void PipelineThread::WillDestroyCurrentMessageLoop() { - while (!filter_hosts_.empty()) { - delete filter_hosts_.back(); - filter_hosts_.pop_back(); - } + STLDeleteElements(&filter_hosts_); } } // namespace media diff --git a/media/base/pipeline_impl.h b/media/base/pipeline_impl.h index c4c4393..ad57444 100644 --- a/media/base/pipeline_impl.h +++ b/media/base/pipeline_impl.h @@ -46,29 +46,32 @@ class PipelineImpl : public Pipeline { virtual PipelineError GetError() const; virtual bool IsRendered(const std::string& major_mime_type) const; - // Impementation of Pipeline methods. + // Implementation of Pipeline methods. virtual bool Start(FilterFactory* filter_factory, const std::string& url, - Callback1<bool>::Type* init_complete_callback); + PipelineCallback* start_callback); virtual void Stop(); virtual void SetPlaybackRate(float rate); - virtual void Seek(base::TimeDelta time); + virtual void Seek(base::TimeDelta time, + PipelineCallback* seek_callback); virtual void SetVolume(float volume); private: friend class FilterHostImpl; friend class PipelineThread; - // Reset the state of the pipline object to the initial state. This method + // Reset the state of the pipeline object to the initial state. This method // is used by the constructor, and the Stop method. void ResetState(); // Used internally to make sure that the thread is in a state that is // acceptable to post a task to. It must exist, be initialized, and there // must not be an error. - bool OkToCallThread() const { - return (pipeline_thread_ && initialized_ && PIPELINE_OK == error_); - } + bool IsPipelineOk() const; + + // Returns true if we're currently executing on the pipeline thread. Mostly + // used in DCHECKs. + bool IsPipelineThread() const; // Methods called by FilterHostImpl to update pipeline state. void SetDuration(base::TimeDelta duration); @@ -170,10 +173,10 @@ class PipelineThread : public base::RefCountedThreadSafe<PipelineThread>, bool Start(FilterFactory* filter_factory, const std::string& url_media_source, - Callback1<bool>::Type* init_complete_callback); + PipelineCallback* init_complete_callback); void Stop(); void SetPlaybackRate(float rate); - void Seek(base::TimeDelta time); + void Seek(base::TimeDelta time, PipelineCallback* seek_callback); void SetVolume(float volume); // Methods called by a FilterHostImpl object. These methods may be called @@ -203,6 +206,10 @@ class PipelineThread : public base::RefCountedThreadSafe<PipelineThread>, // Accessor used to post messages to thread's message loop. MessageLoop* message_loop() const { return thread_.message_loop(); } + // Accessor used by PipelineImpl to check if we're executing on the pipeline + // thread. + PlatformThreadId thread_id() const { return thread_.thread_id(); } + private: // Implementation of MessageLoop::DestructionObserver. StartTask registers // this class as a destruction observer on the thread's message loop. @@ -223,10 +230,10 @@ class PipelineThread : public base::RefCountedThreadSafe<PipelineThread>, // StartTask message on the pipeline thread. void StartTask(FilterFactory* filter_factory, const std::string& url, - Callback1<bool>::Type* init_complete_callback); + PipelineCallback* init_complete_callback); void StopTask(); void SetPlaybackRateTask(float rate); - void SeekTask(base::TimeDelta time); + void SeekTask(base::TimeDelta time, PipelineCallback* seek_callback); void SetVolumeTask(float volume); void SetTimeTask(); void InitializationCompleteTask(FilterHostImpl* FilterHost); diff --git a/media/base/pipeline_impl_unittest.cc b/media/base/pipeline_impl_unittest.cc index 8e300a1..adfc3a6 100644 --- a/media/base/pipeline_impl_unittest.cc +++ b/media/base/pipeline_impl_unittest.cc @@ -13,140 +13,194 @@ #include "media/base/mock_media_filters.h" #include "testing/gtest/include/gtest/gtest.h" -using media::FilterFactory; -using media::FilterFactoryCollection; -using media::FilterHost; -using media::InitializationHelper; -using media::MediaFormat; -using media::MockAudioDecoder; -using media::MockAudioRenderer; -using media::MockDataSource; -using media::MockDemuxer; -using media::MockFilterConfig; -using media::MockVideoDecoder; -using media::MockVideoRenderer; -using media::PipelineImpl; - namespace { -void AddAllMockFilters(FilterFactoryCollection* factories, - const MockFilterConfig* config) { - factories->AddFactory(MockDataSource::CreateFactory(config)); - factories->AddFactory(MockDemuxer::CreateFactory(config)); - factories->AddFactory(MockAudioDecoder::CreateFactory(config)); - factories->AddFactory(MockAudioRenderer::CreateFactory(config)); - factories->AddFactory(MockVideoDecoder::CreateFactory(config)); - factories->AddFactory(MockVideoRenderer::CreateFactory(config)); +class PipelineImplTest : public testing::Test { + protected: + PipelineImplTest() + : initialize_result_(false), + seek_result_(false), + initialize_event_(false, false), + seek_event_(false, false) { + } + + virtual ~PipelineImplTest() {} + + virtual void TearDown() { + // Force the pipeline to shut down its thread. + pipeline_.Stop(); + } + + // Called by tests after they have finished setting up MockFilterConfig. + // Initializes the pipeline and returns true if the initialization callback + // was executed, false otherwise. + bool InitializeAndWait() { + DCHECK(!filters_); + filters_ = new media::MockFilterFactory(&config_); + pipeline_.Start(filters_, "", + NewCallback(this, &PipelineImplTest::OnInitialize)); + return initialize_event_.TimedWait(base::TimeDelta::FromMilliseconds(500)); + } + + // Issues a seek on the pipeline and returns true if the seek callback was + // executed, false otherwise. + bool SeekAndWait(const base::TimeDelta& time) { + pipeline_.Seek(time, NewCallback(this, &PipelineImplTest::OnSeek)); + return seek_event_.TimedWait(base::TimeDelta::FromMilliseconds(500)); + } + + // Fixture members. + media::PipelineImpl pipeline_; + scoped_refptr<media::MockFilterFactory> filters_; + media::MockFilterConfig config_; + bool initialize_result_; + bool seek_result_; + + private: + void OnInitialize(bool result) { + initialize_result_ = result; + initialize_event_.Signal(); + } + + void OnSeek(bool result) { + seek_result_ = result; + seek_event_.Signal(); + } + + // Used to wait for callbacks. + base::WaitableEvent initialize_event_; + base::WaitableEvent seek_event_; + + DISALLOW_COPY_AND_ASSIGN(PipelineImplTest); +}; + +TEST_F(PipelineImplTest, NeverInitializes) { + config_.data_source_behavior = media::MOCK_DATA_SOURCE_NEVER_INIT; + + // This test hangs during initialization by never calling + // InitializationComplete(). Make sure we tear down the pipeline properly. + ASSERT_FALSE(InitializeAndWait()); + EXPECT_FALSE(initialize_result_); + EXPECT_FALSE(pipeline_.IsInitialized()); + EXPECT_EQ(media::PIPELINE_OK, pipeline_.GetError()); } -} // namespace +TEST_F(PipelineImplTest, RequiredFilterMissing) { + config_.create_filter = false; + + ASSERT_TRUE(InitializeAndWait()); + EXPECT_FALSE(initialize_result_); + EXPECT_FALSE(pipeline_.IsInitialized()); + EXPECT_EQ(media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING, + pipeline_.GetError()); +} + +TEST_F(PipelineImplTest, URLNotFound) { + config_.data_source_behavior = media::MOCK_DATA_SOURCE_URL_ERROR_IN_INIT; -// 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, - 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, - media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING); - p.Stop(); - - // 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(); + ASSERT_TRUE(InitializeAndWait()); + EXPECT_FALSE(initialize_result_); + EXPECT_FALSE(pipeline_.IsInitialized()); + EXPECT_EQ(media::PIPELINE_ERROR_URL_NOT_FOUND, pipeline_.GetError()); } -TEST(PipelineImplTest, MockAudioPipeline) { - std::string url(""); - PipelineImpl p; - MockFilterConfig config; - config.has_video = false; - scoped_refptr<FilterFactoryCollection> c = new FilterFactoryCollection(); - AddAllMockFilters(c, &config); - InitializationHelper h; - h.Start(&p, c, url); +TEST_F(PipelineImplTest, NoStreams) { + config_.has_audio = false; + config_.has_video = false; + + ASSERT_TRUE(InitializeAndWait()); + EXPECT_FALSE(initialize_result_); + EXPECT_FALSE(pipeline_.IsInitialized()); + EXPECT_EQ(media::PIPELINE_ERROR_COULD_NOT_RENDER, pipeline_.GetError()); + + EXPECT_FALSE(filters_->audio_decoder()); + EXPECT_FALSE(filters_->audio_renderer()); + EXPECT_FALSE(filters_->video_decoder()); + EXPECT_FALSE(filters_->video_renderer()); +} + +TEST_F(PipelineImplTest, AudioStream) { + config_.has_video = false; + + ASSERT_TRUE(InitializeAndWait()); + EXPECT_TRUE(initialize_result_); + EXPECT_TRUE(pipeline_.IsInitialized()); + EXPECT_EQ(media::PIPELINE_OK, pipeline_.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)); + pipeline_.GetVideoSize(&width, &height); EXPECT_EQ(0u, width); EXPECT_EQ(0u, height); - p.SetPlaybackRate(1.0f); - p.SetVolume(0.5f); - p.Stop(); - EXPECT_FALSE(p.IsInitialized()); + EXPECT_TRUE(pipeline_.IsRendered(media::mime_type::kMajorTypeAudio)); + EXPECT_FALSE(pipeline_.IsRendered(media::mime_type::kMajorTypeVideo)); + + EXPECT_TRUE(filters_->audio_decoder()); + EXPECT_TRUE(filters_->audio_renderer()); + EXPECT_FALSE(filters_->video_decoder()); + EXPECT_FALSE(filters_->video_renderer()); } -TEST(PipelineImplTest, MockVideoPipeline) { - std::string url(""); - PipelineImpl p; - scoped_refptr<FilterFactoryCollection> c = new FilterFactoryCollection(); - MockFilterConfig config; - AddAllMockFilters(c, &config); - InitializationHelper h; - h.Start(&p, c, url); +TEST_F(PipelineImplTest, VideoStream) { + config_.has_audio = false; + + ASSERT_TRUE(InitializeAndWait()); + EXPECT_TRUE(initialize_result_); + EXPECT_TRUE(pipeline_.IsInitialized()); + EXPECT_EQ(media::PIPELINE_OK, pipeline_.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()); + pipeline_.GetVideoSize(&width, &height); + EXPECT_EQ(config_.video_width, width); + EXPECT_EQ(config_.video_height, height); + EXPECT_FALSE(pipeline_.IsRendered(media::mime_type::kMajorTypeAudio)); + EXPECT_TRUE(pipeline_.IsRendered(media::mime_type::kMajorTypeVideo)); + + EXPECT_FALSE(filters_->audio_decoder()); + EXPECT_FALSE(filters_->audio_renderer()); + EXPECT_TRUE(filters_->video_decoder()); + EXPECT_TRUE(filters_->video_renderer()); } -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); +TEST_F(PipelineImplTest, AudioVideoStream) { + ASSERT_TRUE(InitializeAndWait()); + EXPECT_TRUE(initialize_result_); + EXPECT_TRUE(pipeline_.IsInitialized()); + EXPECT_EQ(media::PIPELINE_OK, pipeline_.GetError()); + 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()); + pipeline_.GetVideoSize(&width, &height); + EXPECT_EQ(config_.video_width, width); + EXPECT_EQ(config_.video_height, height); + EXPECT_TRUE(pipeline_.IsRendered(media::mime_type::kMajorTypeAudio)); + EXPECT_TRUE(pipeline_.IsRendered(media::mime_type::kMajorTypeVideo)); + + EXPECT_TRUE(filters_->audio_decoder()); + EXPECT_TRUE(filters_->audio_renderer()); + EXPECT_TRUE(filters_->video_decoder()); + EXPECT_TRUE(filters_->video_renderer()); } -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(); +TEST_F(PipelineImplTest, Seek) { + ASSERT_TRUE(InitializeAndWait()); + + // Seek and verify callback returned true. + base::TimeDelta expected = base::TimeDelta::FromSeconds(2000); + EXPECT_TRUE(SeekAndWait(expected)); + EXPECT_TRUE(seek_result_); + + // Verify every filter received the seek. + // TODO(scherkus): implement whatever it takes so I can use EXPECT_EQ with + // base::TimeDelta. + EXPECT_TRUE(expected == filters_->data_source()->seek_time()); + EXPECT_TRUE(expected == filters_->demuxer()->seek_time()); + EXPECT_TRUE(expected == filters_->audio_decoder()->seek_time()); + EXPECT_TRUE(expected == filters_->audio_renderer()->seek_time()); + EXPECT_TRUE(expected == filters_->video_decoder()->seek_time()); + EXPECT_TRUE(expected == filters_->video_renderer()->seek_time()); } // 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. + +} // namespace diff --git a/media/filters/file_data_source_unittest.cc b/media/filters/file_data_source_unittest.cc index 8512300..1d2f054 100644 --- a/media/filters/file_data_source_unittest.cc +++ b/media/filters/file_data_source_unittest.cc @@ -31,6 +31,7 @@ using media::MockDemuxer; using media::MockAudioDecoder; using media::MockAudioRenderer; using media::MockFilterConfig; +using media::MockFilterFactory; using media::MockFilterHost; using media::MockPipeline; using media::PipelineImpl; @@ -66,9 +67,7 @@ TEST(FileDataSourceTest, OpenFile) { config.has_video = false; scoped_refptr<FilterFactoryCollection> c = new FilterFactoryCollection(); c->AddFactory(FileDataSource::CreateFactory()); - c->AddFactory(MockDemuxer::CreateFactory(&config)); - c->AddFactory(MockAudioDecoder::CreateFactory(&config)); - c->AddFactory(MockAudioRenderer::CreateFactory(&config)); + c->AddFactory(new MockFilterFactory(&config)); InitializationHelper h; h.Start(&pipeline, c, TestFileURL()); EXPECT_EQ(pipeline.GetTotalBytes(), 10); diff --git a/media/filters/video_decoder_unittest.cc b/media/filters/video_decoder_unittest.cc index d6bb9dc..d400d2f 100644 --- a/media/filters/video_decoder_unittest.cc +++ b/media/filters/video_decoder_unittest.cc @@ -20,6 +20,7 @@ using media::MockAudioRenderer; using media::MockDataSource; using media::MockDemuxer; using media::MockFilterConfig; +using media::MockFilterFactory; using media::MockVideoRenderer; using media::PipelineImpl; using media::TestVideoDecoder; @@ -31,15 +32,10 @@ TEST(VideoDecoder, CreateTestDecoder) { scoped_refptr<TestVideoDecoder> test_decoder = new TestVideoDecoder(); MockFilterConfig config; scoped_refptr<FilterFactoryCollection> c = new FilterFactoryCollection(); - c->AddFactory(MockDataSource::CreateFactory(&config)); - c->AddFactory(MockDemuxer::CreateFactory(&config)); c->AddFactory(new InstanceFilterFactory<TestVideoDecoder>(test_decoder)); - c->AddFactory(MockAudioDecoder::CreateFactory(&config)); - c->AddFactory(MockAudioRenderer::CreateFactory(&config)); - c->AddFactory(MockVideoRenderer::CreateFactory(&config)); + c->AddFactory(new MockFilterFactory(&config)); media::InitializationHelper h; h.Start(&p, c, url); p.SetPlaybackRate(1.0f); p.Stop(); } - |