diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-16 01:26:40 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-16 01:26:40 +0000 |
commit | cef6492128ea70329523cbcf5d4c204660090857 (patch) | |
tree | 7386081bed04bb489d9e19fa64f8090ff4bfb6a4 /media | |
parent | b02c01713a168403f65d5193ad6934d761c7fbaa (diff) | |
download | chromium_src-cef6492128ea70329523cbcf5d4c204660090857.zip chromium_src-cef6492128ea70329523cbcf5d4c204660090857.tar.gz chromium_src-cef6492128ea70329523cbcf5d4c204660090857.tar.bz2 |
Adding callback support to media filter Initialize() and Seek().
Also includes unit tests for AudioRendererBase and VideoRendererBase.
I had to rollback my first attempt at this change. Original review: http://codereview.chromium.org/155469
BUG=16014,16031
TEST=media_unittests, layout tests
Review URL: http://codereview.chromium.org/155608
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20836 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
24 files changed, 696 insertions, 159 deletions
diff --git a/media/base/filter_host.h b/media/base/filter_host.h index 4907df2a..982b3e5 100644 --- a/media/base/filter_host.h +++ b/media/base/filter_host.h @@ -24,11 +24,6 @@ namespace media { class FilterHost { public: - // Filters must call this method to indicate that their initialization is - // complete. They may call this from within their Initialize() method or may - // choose call it after processing some data. - virtual void InitializationComplete() = 0; - // 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; diff --git a/media/base/filter_host_impl.cc b/media/base/filter_host_impl.cc index 09f1791..4192afd 100644 --- a/media/base/filter_host_impl.cc +++ b/media/base/filter_host_impl.cc @@ -6,10 +6,6 @@ namespace media { -void FilterHostImpl::InitializationComplete() { - pipeline_internal_->InitializationComplete(this); -} - void FilterHostImpl::Error(PipelineError error) { pipeline_internal_->Error(error); } diff --git a/media/base/filter_host_impl.h b/media/base/filter_host_impl.h index 71e2417..cb33a48 100644 --- a/media/base/filter_host_impl.h +++ b/media/base/filter_host_impl.h @@ -16,7 +16,6 @@ namespace media { class FilterHostImpl : public FilterHost { public: // FilterHost interface. - virtual void InitializationComplete(); virtual void Error(PipelineError error); virtual base::TimeDelta GetTime() const; virtual void SetTime(base::TimeDelta time); diff --git a/media/base/filters.h b/media/base/filters.h index b892961..84df0966 100644 --- a/media/base/filters.h +++ b/media/base/filters.h @@ -53,6 +53,8 @@ enum FilterType { FILTER_VIDEO_RENDERER }; +// Used for completing asynchronous methods. +typedef Callback0::Type FilterCallback; class MediaFilter : public base::RefCountedThreadSafe<MediaFilter> { public: @@ -94,9 +96,14 @@ class MediaFilter : public base::RefCountedThreadSafe<MediaFilter> { // method if they need to respond to this call. virtual void SetPlaybackRate(float playback_rate) {} - // The pipeline is seeking to the specified time. Filters may implement - // this method if they need to respond to this call. - virtual void Seek(base::TimeDelta time) {} + // Carry out any actions required to seek to the given time, executing the + // callback upon completion. + virtual void Seek(base::TimeDelta time, FilterCallback* callback) { + scoped_ptr<FilterCallback> seek_callback(callback); + if (seek_callback.get()) { + seek_callback->Run(); + } + } protected: // Only allow scoped_refptr<> to delete filters. @@ -128,8 +135,9 @@ class DataSource : public MediaFilter { static const size_t kReadError = static_cast<size_t>(-1); - // Initializes this filter, returns true if successful, false otherwise. - virtual bool Initialize(const std::string& url) = 0; + // Initialize a DataSource for the given URL, executing the callback upon + // completion. + virtual void Initialize(const std::string& url, FilterCallback* callback) = 0; // Returns the MediaFormat for this filter. virtual const MediaFormat& media_format() = 0; @@ -166,8 +174,10 @@ class Demuxer : public MediaFilter { mime_type == mime_type::kApplicationOctetStream); } - // Initializes this filter, returns true if successful, false otherwise. - virtual bool Initialize(DataSource* data_source) = 0; + // Initialize a Demuxer with the given DataSource, executing the callback upon + // completion. + virtual void Initialize(DataSource* data_source, + FilterCallback* callback) = 0; // Returns the number of streams available virtual size_t GetNumberOfStreams() = 0; @@ -223,8 +233,9 @@ class VideoDecoder : public MediaFilter { return mime_type::kMajorTypeVideo; } - // Initializes this filter, returns true if successful, false otherwise. - virtual bool Initialize(DemuxerStream* demuxer_stream) = 0; + // Initialize a VideoDecoder with the given DemuxerStream, executing the + // callback upon completion. + virtual void Initialize(DemuxerStream* stream, FilterCallback* callback) = 0; // Returns the MediaFormat for this filter. virtual const MediaFormat& media_format() = 0; @@ -246,8 +257,9 @@ class AudioDecoder : public MediaFilter { return mime_type::kMajorTypeAudio; } - // Initializes this filter, returns true if successful, false otherwise. - virtual bool Initialize(DemuxerStream* demuxer_stream) = 0; + // Initialize a AudioDecoder with the given DemuxerStream, executing the + // callback upon completion. + virtual void Initialize(DemuxerStream* stream, FilterCallback* callback) = 0; // Returns the MediaFormat for this filter. virtual const MediaFormat& media_format() = 0; @@ -269,8 +281,9 @@ class VideoRenderer : public MediaFilter { return mime_type::kMajorTypeVideo; } - // Initializes this filter, returns true if successful, false otherwise. - virtual bool Initialize(VideoDecoder* decoder) = 0; + // Initialize a VideoRenderer with the given VideoDecoder, executing the + // callback upon completion. + virtual void Initialize(VideoDecoder* decoder, FilterCallback* callback) = 0; }; @@ -284,8 +297,9 @@ class AudioRenderer : public MediaFilter { return mime_type::kMajorTypeAudio; } - // Initializes this filter, returns true if successful, false otherwise. - virtual bool Initialize(AudioDecoder* decoder) = 0; + // Initialize a AudioRenderer with the given AudioDecoder, executing the + // callback upon completion. + virtual void Initialize(AudioDecoder* decoder, FilterCallback* callback) = 0; // Sets the output volume. virtual void SetVolume(float volume) = 0; diff --git a/media/base/mock_filters.cc b/media/base/mock_filters.cc new file mode 100644 index 0000000..7b854fc --- /dev/null +++ b/media/base/mock_filters.cc @@ -0,0 +1,18 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this +// source code is governed by a BSD-style license that can be found in the +// LICENSE file. + +#include "media/base/mock_filters.h" + +namespace media { + +void RunFilterCallback(::testing::Unused, FilterCallback* callback) { + callback->Run(); + delete callback; +} + +void DestroyFilterCallback(::testing::Unused, FilterCallback* callback) { + delete callback; +} + +} // namespace media diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h index bffba6f..6d8c8a9 100644 --- a/media/base/mock_filters.h +++ b/media/base/mock_filters.h @@ -40,6 +40,52 @@ class Destroyable : public MockClass { DISALLOW_COPY_AND_ASSIGN(Destroyable); }; +// Helper class used to test that callbacks are executed. It is recommend you +// combine this class with StrictMock<> to verify that the callback is executed. +// You can reuse the same instance of a MockFilterCallback many times since +// gmock will track the number of times the methods are executed. +class MockFilterCallback { + public: + MockFilterCallback() {} + virtual ~MockFilterCallback() {} + + MOCK_METHOD0(OnCallbackDestroyed, void()); + MOCK_METHOD0(OnFilterCallback, void()); + + // Helper method to create a new callback for this mock. The callback will + // call OnFilterCallback() when executed and OnCallbackDestroyed() when + // destroyed. Clients should use NiceMock<> or StrictMock<> depending on the + // test. + FilterCallback* NewCallback() { + return new CallbackImpl(this); + } + + private: + // Private implementation of CallbackRunner used to trigger expectations on + // MockFilterCallback. + class CallbackImpl : public CallbackRunner<Tuple0> { + public: + CallbackImpl(MockFilterCallback* mock_callback) + : mock_callback_(mock_callback) { + } + + virtual ~CallbackImpl() { + mock_callback_->OnCallbackDestroyed(); + } + + virtual void RunWithParams(const Tuple0& params) { + mock_callback_->OnFilterCallback(); + } + + private: + MockFilterCallback* mock_callback_; + + DISALLOW_COPY_AND_ASSIGN(CallbackImpl); + }; + + DISALLOW_COPY_AND_ASSIGN(MockFilterCallback); +}; + class MockDataSource : public DataSource { public: MockDataSource() {} @@ -47,10 +93,11 @@ class MockDataSource : public DataSource { // MediaFilter implementation. MOCK_METHOD0(Stop, void()); MOCK_METHOD1(SetPlaybackRate, void(float playback_rate)); - MOCK_METHOD1(Seek, void(base::TimeDelta time)); + MOCK_METHOD2(Seek, void(base::TimeDelta time, FilterCallback* callback)); // DataSource implementation. - MOCK_METHOD1(Initialize, bool(const std::string& url)); + MOCK_METHOD2(Initialize, void(const std::string& url, + FilterCallback* callback)); const MediaFormat& media_format() { return media_format_; } MOCK_METHOD2(Read, size_t(uint8* data, size_t size)); MOCK_METHOD1(GetPosition, bool(int64* position_out)); @@ -74,10 +121,11 @@ class MockDemuxer : public Demuxer { // MediaFilter implementation. MOCK_METHOD0(Stop, void()); MOCK_METHOD1(SetPlaybackRate, void(float playback_rate)); - MOCK_METHOD1(Seek, void(base::TimeDelta time)); + MOCK_METHOD2(Seek, void(base::TimeDelta time, FilterCallback* callback)); // Demuxer implementation. - MOCK_METHOD1(Initialize, bool(DataSource* data_source)); + MOCK_METHOD2(Initialize, void(DataSource* data_source, + FilterCallback* callback)); MOCK_METHOD0(GetNumberOfStreams, size_t()); MOCK_METHOD1(GetStream, scoped_refptr<DemuxerStream>(int stream_id)); @@ -116,13 +164,21 @@ class MockVideoDecoder : public VideoDecoder { public: MockVideoDecoder() {} + // Sets the essential media format keys for this decoder. + MockVideoDecoder(const std::string& mime_type, int width, int height) { + media_format_.SetAsString(MediaFormat::kMimeType, mime_type); + media_format_.SetAsInteger(MediaFormat::kWidth, width); + media_format_.SetAsInteger(MediaFormat::kHeight, height); + } + // MediaFilter implementation. MOCK_METHOD0(Stop, void()); MOCK_METHOD1(SetPlaybackRate, void(float playback_rate)); - MOCK_METHOD1(Seek, void(base::TimeDelta time)); + MOCK_METHOD2(Seek, void(base::TimeDelta time, FilterCallback* callback)); // VideoDecoder implementation. - MOCK_METHOD1(Initialize, bool(DemuxerStream* demuxer_stream)); + MOCK_METHOD2(Initialize, void(DemuxerStream* stream, + FilterCallback* callback)); const MediaFormat& media_format() { return media_format_; } MOCK_METHOD1(Read, void(Callback1<VideoFrame*>::Type* read_callback)); @@ -142,10 +198,11 @@ class MockAudioDecoder : public AudioDecoder { // MediaFilter implementation. MOCK_METHOD0(Stop, void()); MOCK_METHOD1(SetPlaybackRate, void(float playback_rate)); - MOCK_METHOD1(Seek, void(base::TimeDelta time)); + MOCK_METHOD2(Seek, void(base::TimeDelta time, FilterCallback* callback)); // AudioDecoder implementation. - MOCK_METHOD1(Initialize, bool(DemuxerStream* demuxer_stream)); + MOCK_METHOD2(Initialize, void(DemuxerStream* stream, + FilterCallback* callback)); const MediaFormat& media_format() { return media_format_; } MOCK_METHOD1(Read, void(Callback1<Buffer*>::Type* read_callback)); @@ -165,10 +222,11 @@ class MockVideoRenderer : public VideoRenderer { // MediaFilter implementation. MOCK_METHOD0(Stop, void()); MOCK_METHOD1(SetPlaybackRate, void(float playback_rate)); - MOCK_METHOD1(Seek, void(base::TimeDelta time)); + MOCK_METHOD2(Seek, void(base::TimeDelta time, FilterCallback* callback)); // VideoRenderer implementation. - MOCK_METHOD1(Initialize, bool(VideoDecoder* decoder)); + MOCK_METHOD2(Initialize, void(VideoDecoder* decoder, + FilterCallback* callback)); protected: virtual ~MockVideoRenderer() {} @@ -184,10 +242,11 @@ class MockAudioRenderer : public AudioRenderer { // MediaFilter implementation. MOCK_METHOD0(Stop, void()); MOCK_METHOD1(SetPlaybackRate, void(float playback_rate)); - MOCK_METHOD1(Seek, void(base::TimeDelta time)); + MOCK_METHOD2(Seek, void(base::TimeDelta time, FilterCallback* callback)); // AudioRenderer implementation. - MOCK_METHOD1(Initialize, bool(AudioDecoder* decoder)); + MOCK_METHOD2(Initialize, void(AudioDecoder* decoder, + FilterCallback* callback)); MOCK_METHOD1(SetVolume, void(float volume)); protected: @@ -263,11 +322,15 @@ class MockFilterFactory : public FilterFactory { DISALLOW_COPY_AND_ASSIGN(MockFilterFactory); }; -// Helper gmock action that calls InitializationComplete() on behalf of the -// provided filter. -ACTION_P(InitializationComplete, filter) { - filter->host()->InitializationComplete(); -} +// Helper gmock function that immediately executes and destroys the +// FilterCallback on behalf of the provided filter. Can be used when mocking +// the Initialize() and Seek() methods. +void RunFilterCallback(::testing::Unused, FilterCallback* callback); + +// Helper gmock function that immediately destroys the FilterCallback on behalf +// of the provided filter. Can be used when mocking the Initialize() and Seek() +// methods. +void DestroyFilterCallback(::testing::Unused, FilterCallback* callback); // Helper gmock action that calls Error() on behalf of the provided filter. ACTION_P2(Error, filter, error) { diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc index f021ad3..7ef051d 100644 --- a/media/base/pipeline_impl.cc +++ b/media/base/pipeline_impl.cc @@ -328,15 +328,6 @@ void PipelineInternal::VolumeChanged(float volume) { NewRunnableMethod(this, &PipelineInternal::VolumeChangedTask, volume)); } -// Called from any thread. -void PipelineInternal::InitializationComplete(FilterHostImpl* host) { - if (IsPipelineOk()) { - // Continue the initialize task by proceeding to the next stage. - message_loop_->PostTask(FROM_HERE, - NewRunnableMethod(this, &PipelineInternal::InitializeTask)); - } -} - // Called from any thread. Updates the pipeline time. void PipelineInternal::SetTime(base::TimeDelta time) { // TODO(scherkus): why not post a task? @@ -350,6 +341,19 @@ void PipelineInternal::Error(PipelineError error) { NewRunnableMethod(this, &PipelineInternal::ErrorTask, error)); } +// Called from any thread. +void PipelineInternal::OnFilterInitialize() { + // Continue the initialize task by proceeding to the next stage. + message_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &PipelineInternal::InitializeTask)); +} + +// Called from any thread. +void PipelineInternal::OnFilterSeek() { + // TODO(scherkus): have PipelineInternal wait to receive replies from every + // filter before calling the client's |seek_callback_|. +} + void PipelineInternal::StartTask(FilterFactory* filter_factory, const std::string& url, PipelineCallback* start_callback) { @@ -383,8 +387,8 @@ void PipelineInternal::StartTask(FilterFactory* filter_factory, void PipelineInternal::InitializeTask() { DCHECK_EQ(MessageLoop::current(), message_loop_); - // If we have received the stop signal, return immediately. - if (state_ == kStopped) + // If we have received the stop or error signal, return immediately. + if (state_ == kStopped || state_ == kError) return; DCHECK(state_ == kCreated || IsPipelineInitializing()); @@ -551,7 +555,8 @@ void PipelineInternal::SeekTask(base::TimeDelta time, for (FilterHostVector::iterator iter = filter_hosts_.begin(); iter != filter_hosts_.end(); ++iter) { - (*iter)->media_filter()->Seek(time); + (*iter)->media_filter()->Seek(time, + NewCallback(this, &PipelineInternal::OnFilterSeek)); } // TODO(hclam): we should set the time when the above seek operations were all @@ -602,9 +607,8 @@ void PipelineInternal::CreateFilter(FilterFactory* filter_factory, filter_hosts_.push_back(host.release()); // Now initialize the filter. - if (!filter->Initialize(source)) { - Error(PIPELINE_ERROR_INITIALIZATION_FAILED); - } + filter->Initialize(source, + NewCallback(this, &PipelineInternal::OnFilterInitialize)); } void PipelineInternal::CreateDataSource() { diff --git a/media/base/pipeline_impl.h b/media/base/pipeline_impl.h index 911d253..e9f44d5 100644 --- a/media/base/pipeline_impl.h +++ b/media/base/pipeline_impl.h @@ -191,11 +191,6 @@ class PipelineInternal : public base::RefCountedThreadSafe<PipelineInternal> { // Methods called by a FilterHostImpl object. These methods may be called // on any thread, either the pipeline's thread or any other. - // When a filter calls it's FilterHost, the filter host calls back to the - // pipeline thread. If the pipeline thread is running a nested message loop - // then it will be exited. - void InitializationComplete(FilterHostImpl* host); - // Sets the pipeline time and schedules a task to call back to any filters // that have registered a time update callback. void SetTime(base::TimeDelta time); @@ -245,6 +240,10 @@ class PipelineInternal : public base::RefCountedThreadSafe<PipelineInternal> { state_ == kInitVideoRenderer; } + // Callback executed by filters upon completing initialization and seeking. + void OnFilterInitialize(); + void OnFilterSeek(); + // The following "task" methods correspond to the public methods, but these // methods are run as the result of posting a task to the PipelineInternal's // message loop. diff --git a/media/base/pipeline_impl_unittest.cc b/media/base/pipeline_impl_unittest.cc index b673cfb..d6bd285 100644 --- a/media/base/pipeline_impl_unittest.cc +++ b/media/base/pipeline_impl_unittest.cc @@ -14,7 +14,9 @@ #include "testing/gtest/include/gtest/gtest.h" using ::testing::DoAll; +using ::testing::Invoke; using ::testing::Mock; +using ::testing::NotNull; using ::testing::Return; using ::testing::StrictMock; @@ -63,9 +65,8 @@ class PipelineImplTest : public ::testing::Test { protected: // Sets up expectations to allow the data source to initialize. void InitializeDataSource() { - EXPECT_CALL(*mocks_->data_source(), Initialize("")) - .WillOnce(DoAll(InitializationComplete(mocks_->data_source()), - Return(true))); + EXPECT_CALL(*mocks_->data_source(), Initialize("", NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); EXPECT_CALL(*mocks_->data_source(), SetPlaybackRate(0.0f)); EXPECT_CALL(*mocks_->data_source(), Stop()); } @@ -73,9 +74,9 @@ class PipelineImplTest : public ::testing::Test { // Sets up expectations to allow the demuxer to initialize. typedef std::vector<MockDemuxerStream*> MockDemuxerStreamVector; void InitializeDemuxer(MockDemuxerStreamVector* streams) { - EXPECT_CALL(*mocks_->demuxer(), Initialize(mocks_->data_source())) - .WillOnce(DoAll(InitializationComplete(mocks_->demuxer()), - Return(true))); + EXPECT_CALL(*mocks_->demuxer(), + Initialize(mocks_->data_source(), NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); EXPECT_CALL(*mocks_->demuxer(), GetNumberOfStreams()) .WillRepeatedly(Return(streams->size())); EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(0.0f)); @@ -91,36 +92,34 @@ class PipelineImplTest : public ::testing::Test { // Sets up expectations to allow the video decoder to initialize. void InitializeVideoDecoder(MockDemuxerStream* stream) { - EXPECT_CALL(*mocks_->video_decoder(), Initialize(stream)) - .WillOnce(DoAll(InitializationComplete(mocks_->video_decoder()), - Return(true))); + EXPECT_CALL(*mocks_->video_decoder(), Initialize(stream, NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); EXPECT_CALL(*mocks_->video_decoder(), SetPlaybackRate(0.0f)); EXPECT_CALL(*mocks_->video_decoder(), Stop()); } // Sets up expectations to allow the audio decoder to initialize. void InitializeAudioDecoder(MockDemuxerStream* stream) { - EXPECT_CALL(*mocks_->audio_decoder(), Initialize(stream)) - .WillOnce(DoAll(InitializationComplete(mocks_->audio_decoder()), - Return(true))); + EXPECT_CALL(*mocks_->audio_decoder(), Initialize(stream, NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(0.0f)); EXPECT_CALL(*mocks_->audio_decoder(), Stop()); } // Sets up expectations to allow the video renderer to initialize. void InitializeVideoRenderer() { - EXPECT_CALL(*mocks_->video_renderer(), Initialize(mocks_->video_decoder())) - .WillOnce(DoAll(InitializationComplete(mocks_->video_renderer()), - Return(true))); + EXPECT_CALL(*mocks_->video_renderer(), + Initialize(mocks_->video_decoder(), NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(0.0f)); EXPECT_CALL(*mocks_->video_renderer(), Stop()); } // Sets up expectations to allow the audio renderer to initialize. void InitializeAudioRenderer() { - EXPECT_CALL(*mocks_->audio_renderer(), Initialize(mocks_->audio_decoder())) - .WillOnce(DoAll(InitializationComplete(mocks_->audio_renderer()), - Return(true))); + EXPECT_CALL(*mocks_->audio_renderer(), + Initialize(mocks_->audio_decoder(), NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f)); EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f)); EXPECT_CALL(*mocks_->audio_renderer(), Stop()); @@ -201,8 +200,8 @@ TEST_F(PipelineImplTest, NotStarted) { } TEST_F(PipelineImplTest, NeverInitializes) { - EXPECT_CALL(*mocks_->data_source(), Initialize("")) - .WillOnce(Return(true)); + EXPECT_CALL(*mocks_->data_source(), Initialize("", NotNull())) + .WillOnce(Invoke(&DestroyFilterCallback)); EXPECT_CALL(*mocks_->data_source(), Stop()); // This test hangs during initialization by never calling @@ -233,10 +232,10 @@ TEST_F(PipelineImplTest, RequiredFilterMissing) { } TEST_F(PipelineImplTest, URLNotFound) { - EXPECT_CALL(*mocks_->data_source(), Initialize("")) + EXPECT_CALL(*mocks_->data_source(), Initialize("", NotNull())) .WillOnce(DoAll(Error(mocks_->data_source(), PIPELINE_ERROR_URL_NOT_FOUND), - Return(false))); + Invoke(&RunFilterCallback))); EXPECT_CALL(*mocks_->data_source(), Stop()); InitializePipeline(); @@ -247,14 +246,12 @@ TEST_F(PipelineImplTest, URLNotFound) { TEST_F(PipelineImplTest, NoStreams) { // Manually set these expecations because SetPlaybackRate() is not called if // we cannot fully initialize the pipeline. - EXPECT_CALL(*mocks_->data_source(), Initialize("")) - .WillOnce(DoAll(InitializationComplete(mocks_->data_source()), - Return(true))); + EXPECT_CALL(*mocks_->data_source(), Initialize("", NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); EXPECT_CALL(*mocks_->data_source(), Stop()); - EXPECT_CALL(*mocks_->demuxer(), Initialize(mocks_->data_source())) - .WillOnce(DoAll(InitializationComplete(mocks_->demuxer()), - Return(true))); + EXPECT_CALL(*mocks_->demuxer(), Initialize(mocks_->data_source(), NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); EXPECT_CALL(*mocks_->demuxer(), GetNumberOfStreams()) .WillRepeatedly(Return(0)); EXPECT_CALL(*mocks_->demuxer(), Stop()); @@ -341,12 +338,18 @@ TEST_F(PipelineImplTest, Seek) { // Every filter should receive a call to Seek(). base::TimeDelta expected = base::TimeDelta::FromSeconds(2000); - EXPECT_CALL(*mocks_->data_source(), Seek(expected)); - EXPECT_CALL(*mocks_->demuxer(), Seek(expected)); - EXPECT_CALL(*mocks_->audio_decoder(), Seek(expected)); - EXPECT_CALL(*mocks_->audio_renderer(), Seek(expected)); - EXPECT_CALL(*mocks_->video_decoder(), Seek(expected)); - EXPECT_CALL(*mocks_->video_renderer(), Seek(expected)); + EXPECT_CALL(*mocks_->data_source(), Seek(expected, NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); + EXPECT_CALL(*mocks_->demuxer(), Seek(expected, NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); + EXPECT_CALL(*mocks_->audio_decoder(), Seek(expected, NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); + EXPECT_CALL(*mocks_->audio_renderer(), Seek(expected, NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); + EXPECT_CALL(*mocks_->video_decoder(), Seek(expected, NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); + EXPECT_CALL(*mocks_->video_renderer(), Seek(expected, NotNull())) + .WillOnce(Invoke(&RunFilterCallback)); // We expect a successful seek callback. EXPECT_CALL(callbacks_, OnSeek()); diff --git a/media/filters/audio_renderer_base.cc b/media/filters/audio_renderer_base.cc index ba52fe0..d0bad99 100644 --- a/media/filters/audio_renderer_base.cc +++ b/media/filters/audio_renderer_base.cc @@ -38,7 +38,7 @@ void AudioRendererBase::Stop() { stopped_ = true; } -void AudioRendererBase::Seek(base::TimeDelta time) { +void AudioRendererBase::Seek(base::TimeDelta time, FilterCallback* callback) { AutoLock auto_lock(lock_); last_fill_buffer_time_ = base::TimeDelta(); @@ -53,9 +53,12 @@ void AudioRendererBase::Seek(base::TimeDelta time) { } } -bool AudioRendererBase::Initialize(AudioDecoder* decoder) { +void AudioRendererBase::Initialize(AudioDecoder* decoder, + FilterCallback* callback) { DCHECK(decoder); + DCHECK(callback); decoder_ = decoder; + initialize_callback_.reset(callback); // Schedule our initial reads. for (size_t i = 0; i < max_queue_size_; ++i) { @@ -63,7 +66,11 @@ bool AudioRendererBase::Initialize(AudioDecoder* decoder) { } // Defer initialization until all scheduled reads have completed. - return OnInitialize(decoder_->media_format()); + if (!OnInitialize(decoder_->media_format())) { + host()->Error(PIPELINE_ERROR_INITIALIZATION_FAILED); + initialize_callback_->Run(); + initialize_callback_.reset(); + } } void AudioRendererBase::OnReadComplete(Buffer* buffer_in) { @@ -92,8 +99,9 @@ void AudioRendererBase::OnReadComplete(Buffer* buffer_in) { host()->Error(PIPELINE_ERROR_NO_DATA); } else { initialized_ = true; - host()->InitializationComplete(); } + initialize_callback_->Run(); + initialize_callback_.reset(); } } diff --git a/media/filters/audio_renderer_base.h b/media/filters/audio_renderer_base.h index e3ec686..3d31474 100644 --- a/media/filters/audio_renderer_base.h +++ b/media/filters/audio_renderer_base.h @@ -32,10 +32,10 @@ class AudioRendererBase : public AudioRenderer { // MediaFilter implementation. virtual void Stop(); - virtual void Seek(base::TimeDelta time); + virtual void Seek(base::TimeDelta time, FilterCallback* callback); // AudioRenderer implementation. - virtual bool Initialize(AudioDecoder* decoder); + virtual void Initialize(AudioDecoder* decoder, FilterCallback* callback); protected: // The default maximum size of the queue. @@ -116,6 +116,9 @@ class AudioRendererBase : public AudioRenderer { // TODO(ralphl): Update this value after seeking. base::TimeDelta last_fill_buffer_time_; + // Filter callbacks. + scoped_ptr<FilterCallback> initialize_callback_; + DISALLOW_COPY_AND_ASSIGN(AudioRendererBase); }; diff --git a/media/filters/audio_renderer_base_unittest.cc b/media/filters/audio_renderer_base_unittest.cc new file mode 100644 index 0000000..ed0d138 --- /dev/null +++ b/media/filters/audio_renderer_base_unittest.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/stl_util-inl.h" +#include "media/base/data_buffer.h" +#include "media/base/mock_filter_host.h" +#include "media/base/mock_filters.h" +#include "media/filters/audio_renderer_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::StrictMock; + +namespace media { + +// Mocked subclass of AudioRendererBase for testing purposes. +class MockAudioRendererBase : public AudioRendererBase { + public: + MockAudioRendererBase(size_t max_queue_size) + : AudioRendererBase(max_queue_size) {} + virtual ~MockAudioRendererBase() {} + + // AudioRenderer implementation. + MOCK_METHOD1(SetVolume, void(float volume)); + + // AudioRendererBase implementation. + MOCK_METHOD1(OnInitialize, bool(const MediaFormat& media_format)); + MOCK_METHOD0(OnStop, void()); + + // Used for verifying check points during tests. + MOCK_METHOD1(CheckPoint, void(int id)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAudioRendererBase); +}; + +class AudioRendererBaseTest : public ::testing::Test { + public: + AudioRendererBaseTest() + : renderer_(new MockAudioRendererBase(kMaxQueueSize)), + decoder_(new MockAudioDecoder()) { + renderer_->set_host(&host_); + + // Queue all reads from the decoder. + EXPECT_CALL(*decoder_, Read(NotNull())) + .WillRepeatedly(Invoke(this, &AudioRendererBaseTest::EnqueueCallback)); + } + + virtual ~AudioRendererBaseTest() { + STLDeleteElements(&read_queue_); + + // Expect a call into the subclass. + EXPECT_CALL(*renderer_, OnStop()); + renderer_->Stop(); + } + + protected: + static const size_t kMaxQueueSize; + + // Fixture members. + scoped_refptr<MockAudioRendererBase> renderer_; + scoped_refptr<MockAudioDecoder> decoder_; + StrictMock<MockFilterHost> host_; + StrictMock<MockFilterCallback> callback_; + + // Receives asynchronous read requests sent to |decoder_|. + std::deque<Callback1<Buffer*>::Type*> read_queue_; + + private: + void EnqueueCallback(Callback1<Buffer*>::Type* callback) { + read_queue_.push_back(callback); + } + + DISALLOW_COPY_AND_ASSIGN(AudioRendererBaseTest); +}; + +const size_t AudioRendererBaseTest::kMaxQueueSize = 16u; + +TEST_F(AudioRendererBaseTest, Initialize_Failed) { + InSequence s; + + // Our subclass will fail when asked to initialize. + EXPECT_CALL(*renderer_, OnInitialize(_)) + .WillOnce(Return(false)); + + // We expect to receive an error. + EXPECT_CALL(host_, Error(PIPELINE_ERROR_INITIALIZATION_FAILED)); + + // We expect our callback to be executed. + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + + // Initialize, we expect to get a bunch of read requests. + renderer_->Initialize(decoder_, callback_.NewCallback()); + EXPECT_EQ(kMaxQueueSize, read_queue_.size()); +} + +TEST_F(AudioRendererBaseTest, Initialize_Successful) { + InSequence s; + + // Then our subclass will be asked to initialize. + EXPECT_CALL(*renderer_, OnInitialize(_)) + .WillOnce(Return(true)); + + // Set up a check point to verify that the callback hasn't been executed yet. + EXPECT_CALL(*renderer_, CheckPoint(0)); + + // After finishing preroll, we expect our callback to be executed. + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + + // Initialize, we expect to get a bunch of read requests. + renderer_->Initialize(decoder_, callback_.NewCallback()); + EXPECT_EQ(kMaxQueueSize, read_queue_.size()); + + // Verify our callback hasn't been executed yet. + renderer_->CheckPoint(0); + + // Now satisfy the read requests. Our callback should be executed after + // exiting this loop. + while (!read_queue_.empty()) { + scoped_refptr<DataBuffer> buffer = new DataBuffer(1); + read_queue_.front()->Run(buffer); + delete read_queue_.front(); + read_queue_.pop_front(); + } +} + +} // namespace media diff --git a/media/filters/decoder_base.h b/media/filters/decoder_base.h index 775d3f9..3e1ba52c 100644 --- a/media/filters/decoder_base.h +++ b/media/filters/decoder_base.h @@ -33,16 +33,18 @@ class DecoderBase : public Decoder { NewRunnableMethod(this, &DecoderBase::StopTask)); } - virtual void Seek(base::TimeDelta time) { + virtual void Seek(base::TimeDelta time, + FilterCallback* callback) { this->message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(this, &DecoderBase::SeekTask, time)); + NewRunnableMethod(this, &DecoderBase::SeekTask, time, callback)); } // Decoder implementation. - virtual bool Initialize(DemuxerStream* demuxer_stream) { + virtual void Initialize(DemuxerStream* demuxer_stream, + FilterCallback* callback) { this->message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(this, &DecoderBase::InitializeTask, demuxer_stream)); - return true; + NewRunnableMethod(this, &DecoderBase::InitializeTask, demuxer_stream, + callback)); } virtual const MediaFormat& media_format() { return media_format_; } @@ -127,8 +129,9 @@ class DecoderBase : public Decoder { state_ = STOPPED; } - void SeekTask(base::TimeDelta time) { + void SeekTask(base::TimeDelta time, FilterCallback* callback) { DCHECK_EQ(MessageLoop::current(), this->message_loop()); + scoped_ptr<FilterCallback> c(callback); // Delegate to the subclass first. OnSeek(time); @@ -139,24 +142,30 @@ class DecoderBase : public Decoder { // Turn on the seeking flag so that we can discard buffers until a // discontinuous buffer is received. seeking_ = true; + + // For now, signal that we're done seeking. + // TODO(scherkus): implement asynchronous seeking for decoder_base.h + callback->Run(); } - void InitializeTask(DemuxerStream* demuxer_stream) { + void InitializeTask(DemuxerStream* demuxer_stream, FilterCallback* callback) { DCHECK_EQ(MessageLoop::current(), this->message_loop()); DCHECK(state_ == UNINITIALIZED); DCHECK(!demuxer_stream_); + scoped_ptr<FilterCallback> c(callback); demuxer_stream_ = demuxer_stream; // Delegate to subclass first. if (!OnInitialize(demuxer_stream_)) { this->host()->Error(PIPELINE_ERROR_DECODE); + callback->Run(); return; } // TODO(scherkus): subclass shouldn't mutate superclass media format. DCHECK(!media_format_.empty()) << "Subclass did not set media_format_"; state_ = INITIALIZED; - this->host()->InitializationComplete(); + callback->Run(); } void ReadTask(ReadCallback* read_callback) { diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc index aa7ff27..8526b4e 100644 --- a/media/filters/ffmpeg_demuxer.cc +++ b/media/filters/ffmpeg_demuxer.cc @@ -261,19 +261,20 @@ void FFmpegDemuxer::Stop() { NewRunnableMethod(this, &FFmpegDemuxer::StopTask)); } -void FFmpegDemuxer::Seek(base::TimeDelta time) { +void FFmpegDemuxer::Seek(base::TimeDelta time, FilterCallback* callback) { // TODO(hclam): by returning from this method, it is assumed that the seek // operation is completed and filters behind the demuxer is good to issue // more reads, but we are posting a task here, which makes the seek operation // asynchronous, should change how seek works to make it fully asynchronous. message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(this, &FFmpegDemuxer::SeekTask, time)); + NewRunnableMethod(this, &FFmpegDemuxer::SeekTask, time, callback)); } -bool FFmpegDemuxer::Initialize(DataSource* data_source) { +void FFmpegDemuxer::Initialize(DataSource* data_source, + FilterCallback* callback) { message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(this, &FFmpegDemuxer::InititalizeTask, data_source)); - return true; + NewRunnableMethod(this, &FFmpegDemuxer::InititalizeTask, data_source, + callback)); } size_t FFmpegDemuxer::GetNumberOfStreams() { @@ -286,8 +287,10 @@ scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream(int stream) { return streams_[stream].get(); } -void FFmpegDemuxer::InititalizeTask(DataSource* data_source) { +void FFmpegDemuxer::InititalizeTask(DataSource* data_source, + FilterCallback* callback) { DCHECK_EQ(MessageLoop::current(), message_loop()); + scoped_ptr<FilterCallback> c(callback); // In order to get FFmpeg to use |data_source| for file IO we must transfer // ownership via FFmpegGlue. We'll add |data_source| to FFmpegGlue and pass @@ -311,6 +314,7 @@ void FFmpegDemuxer::InititalizeTask(DataSource* data_source) { if (result < 0) { host()->Error(DEMUXER_ERROR_COULD_NOT_OPEN); + callback->Run(); return; } @@ -325,6 +329,7 @@ void FFmpegDemuxer::InititalizeTask(DataSource* data_source) { result = av_find_stream_info(format_context_); if (result < 0) { host()->Error(DEMUXER_ERROR_COULD_NOT_PARSE); + callback->Run(); return; } } @@ -347,16 +352,18 @@ void FFmpegDemuxer::InititalizeTask(DataSource* data_source) { } if (streams_.empty()) { host()->Error(DEMUXER_ERROR_NO_SUPPORTED_STREAMS); + callback->Run(); return; } // Good to go: set the duration and notify we're done initializing. host()->SetDuration(max_duration); - host()->InitializationComplete(); + callback->Run(); } -void FFmpegDemuxer::SeekTask(base::TimeDelta time) { +void FFmpegDemuxer::SeekTask(base::TimeDelta time, FilterCallback* callback) { DCHECK_EQ(MessageLoop::current(), message_loop()); + scoped_ptr<FilterCallback> c(callback); // Tell streams to flush buffers due to seeking. StreamVector::iterator iter; @@ -375,6 +382,9 @@ void FFmpegDemuxer::SeekTask(base::TimeDelta time) { // TODO(scherkus): signal error. NOTIMPLEMENTED(); } + + // Notify we're finished seeking. + callback->Run(); } void FFmpegDemuxer::DemuxTask() { diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h index 9d68e35..20b77e4 100644 --- a/media/filters/ffmpeg_demuxer.h +++ b/media/filters/ffmpeg_demuxer.h @@ -120,10 +120,10 @@ class FFmpegDemuxer : public Demuxer { // MediaFilter implementation. virtual void Stop(); - virtual void Seek(base::TimeDelta time); + virtual void Seek(base::TimeDelta time, FilterCallback* callback); // Demuxer implementation. - virtual bool Initialize(DataSource* data_source); + virtual void Initialize(DataSource* data_source, FilterCallback* callback); virtual size_t GetNumberOfStreams(); virtual scoped_refptr<DemuxerStream> GetStream(int stream_id); @@ -134,10 +134,10 @@ class FFmpegDemuxer : public Demuxer { virtual ~FFmpegDemuxer(); // Carries out initialization on the demuxer thread. - void InititalizeTask(DataSource* data_source); + void InititalizeTask(DataSource* data_source, FilterCallback* callback); // Carries out a seek on the demuxer thread. - void SeekTask(base::TimeDelta time); + void SeekTask(base::TimeDelta time, FilterCallback* callback); // Carries out demuxing and satisfying stream reads on the demuxer thread. void DemuxTask(); diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc index 43b14c7..18cec27 100644 --- a/media/filters/ffmpeg_demuxer_unittest.cc +++ b/media/filters/ffmpeg_demuxer_unittest.cc @@ -133,7 +133,8 @@ class FFmpegDemuxerTest : public testing::Test { InitializeDemuxerMocks(); // We expect a successful initialization. - EXPECT_CALL(host_, InitializationComplete()); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); // Since we ignore data streams, the duration should be equal to the longest // supported stream's duration (audio, in this case). @@ -141,7 +142,7 @@ class FFmpegDemuxerTest : public testing::Test { base::TimeDelta::FromMicroseconds(kDurations[AV_STREAM_AUDIO]); EXPECT_CALL(host_, SetDuration(expected_duration)); - EXPECT_TRUE(demuxer_->Initialize(data_source_.get())); + demuxer_->Initialize(data_source_.get(), callback_.NewCallback()); message_loop_.RunAllPending(); } @@ -150,6 +151,7 @@ class FFmpegDemuxerTest : public testing::Test { scoped_refptr<FFmpegDemuxer> demuxer_; scoped_refptr<StrictMock<MockDataSource> > data_source_; StrictMock<MockFilterHost> host_; + StrictMock<MockFilterCallback> callback_; MessageLoop message_loop_; // FFmpeg fixtures. @@ -196,8 +198,10 @@ TEST_F(FFmpegDemuxerTest, Initialize_OpenFails) { EXPECT_CALL(*MockFFmpeg::get(), AVOpenInputFile(_, _, NULL, 0, NULL)) .WillOnce(Return(-1)); EXPECT_CALL(host_, Error(DEMUXER_ERROR_COULD_NOT_OPEN)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); - EXPECT_TRUE(demuxer_->Initialize(data_source_.get())); + demuxer_->Initialize(data_source_.get(), callback_.NewCallback()); message_loop_.RunAllPending(); } @@ -209,8 +213,10 @@ TEST_F(FFmpegDemuxerTest, Initialize_ParseFails) { .WillOnce(Return(AVERROR_IO)); EXPECT_CALL(*MockFFmpeg::get(), AVCloseInputFile(&format_context_)); EXPECT_CALL(host_, Error(DEMUXER_ERROR_COULD_NOT_PARSE)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); - EXPECT_TRUE(demuxer_->Initialize(data_source_.get())); + demuxer_->Initialize(data_source_.get(), callback_.NewCallback()); message_loop_.RunAllPending(); } @@ -221,9 +227,11 @@ TEST_F(FFmpegDemuxerTest, Initialize_NoStreams) { InitializeDemuxerMocks(); } EXPECT_CALL(host_, Error(DEMUXER_ERROR_NO_SUPPORTED_STREAMS)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); format_context_.nb_streams = 0; - EXPECT_TRUE(demuxer_->Initialize(data_source_.get())); + demuxer_->Initialize(data_source_.get(), callback_.NewCallback()); message_loop_.RunAllPending(); } @@ -234,10 +242,12 @@ TEST_F(FFmpegDemuxerTest, Initialize_DataStreamOnly) { InitializeDemuxerMocks(); } EXPECT_CALL(host_, Error(DEMUXER_ERROR_NO_SUPPORTED_STREAMS)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); EXPECT_EQ(format_context_.streams[0], &streams_[AV_STREAM_DATA]); format_context_.nb_streams = 1; - EXPECT_TRUE(demuxer_->Initialize(data_source_.get())); + demuxer_->Initialize(data_source_.get(), callback_.NewCallback()); message_loop_.RunAllPending(); } @@ -446,6 +456,11 @@ TEST_F(FFmpegDemuxerTest, Seek) { EXPECT_CALL(*MockFFmpeg::get(), AVSeekFrame(&format_context_, -1, kExpectedTimestamp, kExpectedFlags)) .WillOnce(Return(0)); + + // ...then our callback will be executed... + StrictMock<MockFilterCallback> seek_callback; + EXPECT_CALL(seek_callback, OnFilterCallback()); + EXPECT_CALL(seek_callback, OnCallbackDestroyed()); EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(2)); // ...followed by two audio packet reads we'll trigger... @@ -483,7 +498,8 @@ TEST_F(FFmpegDemuxerTest, Seek) { MockFFmpeg::get()->CheckPoint(1); // Now issue a simple forward seek, which should discard queued packets. - demuxer_->Seek(base::TimeDelta::FromMicroseconds(kExpectedTimestamp)); + demuxer_->Seek(base::TimeDelta::FromMicroseconds(kExpectedTimestamp), + seek_callback.NewCallback()); message_loop_.RunAllPending(); MockFFmpeg::get()->CheckPoint(2); diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc index 3178725..cfba730 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -117,6 +117,7 @@ class FFmpegVideoDecoderTest : public testing::Test { scoped_refptr<DataBuffer> buffer_; scoped_refptr<DataBuffer> end_of_stream_buffer_; StrictMock<MockFilterHost> host_; + StrictMock<MockFilterCallback> callback_; MessageLoop message_loop_; // FFmpeg fixtures. @@ -161,8 +162,10 @@ TEST_F(FFmpegVideoDecoderTest, Initialize_QueryInterfaceFails) { EXPECT_CALL(*demuxer_, QueryInterface(AVStreamProvider::interface_id())) .WillOnce(ReturnNull()); EXPECT_CALL(host_, Error(PIPELINE_ERROR_DECODE)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); - EXPECT_TRUE(decoder_->Initialize(demuxer_)); + decoder_->Initialize(demuxer_, callback_.NewCallback()); message_loop_.RunAllPending(); } @@ -176,8 +179,10 @@ TEST_F(FFmpegVideoDecoderTest, Initialize_FindDecoderFails) { EXPECT_CALL(*MockFFmpeg::get(), AVCodecFindDecoder(CODEC_ID_NONE)) .WillOnce(ReturnNull()); EXPECT_CALL(host_, Error(PIPELINE_ERROR_DECODE)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); - EXPECT_TRUE(decoder_->Initialize(demuxer_)); + decoder_->Initialize(demuxer_, callback_.NewCallback()); message_loop_.RunAllPending(); } @@ -193,8 +198,10 @@ TEST_F(FFmpegVideoDecoderTest, Initialize_InitThreadFails) { EXPECT_CALL(*MockFFmpeg::get(), AVCodecThreadInit(&codec_context_, 2)) .WillOnce(Return(-1)); EXPECT_CALL(host_, Error(PIPELINE_ERROR_DECODE)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); - EXPECT_TRUE(decoder_->Initialize(demuxer_)); + decoder_->Initialize(demuxer_, callback_.NewCallback()); message_loop_.RunAllPending(); } @@ -212,8 +219,10 @@ TEST_F(FFmpegVideoDecoderTest, Initialize_OpenDecoderFails) { EXPECT_CALL(*MockFFmpeg::get(), AVCodecOpen(&codec_context_, &codec_)) .WillOnce(Return(-1)); EXPECT_CALL(host_, Error(PIPELINE_ERROR_DECODE)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); - EXPECT_TRUE(decoder_->Initialize(demuxer_)); + decoder_->Initialize(demuxer_, callback_.NewCallback()); message_loop_.RunAllPending(); } @@ -230,9 +239,10 @@ TEST_F(FFmpegVideoDecoderTest, Initialize_Successful) { .WillOnce(Return(0)); EXPECT_CALL(*MockFFmpeg::get(), AVCodecOpen(&codec_context_, &codec_)) .WillOnce(Return(0)); - EXPECT_CALL(host_, InitializationComplete()); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); - EXPECT_TRUE(decoder_->Initialize(demuxer_)); + decoder_->Initialize(demuxer_, callback_.NewCallback()); message_loop_.RunAllPending(); // Test that the output media format is an uncompressed video surface that diff --git a/media/filters/file_data_source.cc b/media/filters/file_data_source.cc index 2e13a59..8ac4499 100644 --- a/media/filters/file_data_source.cc +++ b/media/filters/file_data_source.cc @@ -20,8 +20,10 @@ FileDataSource::~FileDataSource() { Stop(); } -bool FileDataSource::Initialize(const std::string& url) { +void FileDataSource::Initialize(const std::string& url, + FilterCallback* callback) { DCHECK(!file_); + scoped_ptr<FilterCallback> c(callback); #if defined(OS_WIN) FilePath file_path(UTF8ToWide(url)); #else @@ -33,15 +35,15 @@ bool FileDataSource::Initialize(const std::string& url) { if (!file_) { file_size_ = 0; host()->Error(PIPELINE_ERROR_URL_NOT_FOUND); - return false; + callback->Run(); + return; } media_format_.SetAsString(MediaFormat::kMimeType, mime_type::kApplicationOctetStream); media_format_.SetAsString(MediaFormat::kURL, url); host()->SetTotalBytes(file_size_); host()->SetBufferedBytes(file_size_); - host()->InitializationComplete(); - return true; + callback->Run(); } void FileDataSource::Stop() { diff --git a/media/filters/file_data_source.h b/media/filters/file_data_source.h index e58e4aa..5c91ce1 100644 --- a/media/filters/file_data_source.h +++ b/media/filters/file_data_source.h @@ -26,7 +26,7 @@ class FileDataSource : public DataSource { virtual void Stop(); // Implementation of DataSource. - virtual bool Initialize(const std::string& url); + virtual void Initialize(const std::string& url, FilterCallback* callback); virtual const MediaFormat& media_format(); virtual size_t Read(uint8* data, size_t size); virtual bool GetPosition(int64* position_out); @@ -41,6 +41,7 @@ class FileDataSource : public DataSource { // of my tests!!! FRIEND_TEST(FileDataSourceTest, OpenFile); FRIEND_TEST(FileDataSourceTest, ReadData); + FRIEND_TEST(FileDataSourceTest, Seek); friend class FilterFactoryImpl0<FileDataSource>; FileDataSource(); virtual ~FileDataSource(); diff --git a/media/filters/file_data_source_unittest.cc b/media/filters/file_data_source_unittest.cc index dd41e88..9c4b9ee8 100644 --- a/media/filters/file_data_source_unittest.cc +++ b/media/filters/file_data_source_unittest.cc @@ -8,6 +8,7 @@ #include "base/file_path.h" #include "base/string_util.h" #include "media/base/mock_filter_host.h" +#include "media/base/mock_filters.h" #include "media/filters/file_data_source.h" using ::testing::NiceMock; @@ -38,13 +39,15 @@ std::string TestFileURL() { // Test that FileDataSource call the appropriate methods on its filter host. TEST(FileDataSourceTest, OpenFile) { StrictMock<MockFilterHost> host; + StrictMock<MockFilterCallback> callback; EXPECT_CALL(host, SetTotalBytes(10)); EXPECT_CALL(host, SetBufferedBytes(10)); - EXPECT_CALL(host, InitializationComplete()); + EXPECT_CALL(callback, OnFilterCallback()); + EXPECT_CALL(callback, OnCallbackDestroyed()); scoped_refptr<FileDataSource> filter = new FileDataSource(); filter->set_host(&host); - EXPECT_TRUE(filter->Initialize(TestFileURL())); + filter->Initialize(TestFileURL(), callback.NewCallback()); } // Use the mock filter host to directly call the Read and GetPosition methods. @@ -55,9 +58,10 @@ TEST(FileDataSourceTest, ReadData) { // Create our mock filter host and initialize the data source. NiceMock<MockFilterHost> host; + NiceMock<MockFilterCallback> callback; scoped_refptr<FileDataSource> filter = new FileDataSource(); filter->set_host(&host); - EXPECT_TRUE(filter->Initialize(TestFileURL())); + filter->Initialize(TestFileURL(), callback.NewCallback()); EXPECT_TRUE(filter->GetSize(&size)); EXPECT_EQ(10, size); @@ -80,4 +84,15 @@ TEST(FileDataSourceTest, ReadData) { EXPECT_EQ(10, position); } +// Test that FileDataSource does nothing on Seek(). +TEST(FileDataSourceTest, Seek) { + StrictMock<MockFilterCallback> callback; + EXPECT_CALL(callback, OnFilterCallback()); + EXPECT_CALL(callback, OnCallbackDestroyed()); + const base::TimeDelta kZero; + + scoped_refptr<FileDataSource> filter = new FileDataSource(); + filter->Seek(kZero, callback.NewCallback()); +} + } // namespace media diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc index 7eace6f..3ca45dc 100644 --- a/media/filters/video_renderer_base.cc +++ b/media/filters/video_renderer_base.cc @@ -81,7 +81,7 @@ void VideoRendererBase::SetPlaybackRate(float playback_rate) { playback_rate_ = playback_rate; } -void VideoRendererBase::Seek(base::TimeDelta time) { +void VideoRendererBase::Seek(base::TimeDelta time, FilterCallback* callback) { AutoLock auto_lock(lock_); // We need the first frame in |frames_| to run the VideoRendererBase main // loop, but we don't need decoded frames after the first frame since we are @@ -93,29 +93,44 @@ void VideoRendererBase::Seek(base::TimeDelta time) { } } -bool VideoRendererBase::Initialize(VideoDecoder* decoder) { +void VideoRendererBase::Initialize(VideoDecoder* decoder, + FilterCallback* callback) { AutoLock auto_lock(lock_); + DCHECK(decoder); + DCHECK(callback); DCHECK_EQ(state_, UNINITIALIZED); state_ = INITIALIZING; decoder_ = decoder; + initialize_callback_.reset(callback); // Notify the pipeline of the video dimensions. int width = 0; int height = 0; - if (!ParseMediaFormat(decoder->media_format(), &width, &height)) - return false; + if (!ParseMediaFormat(decoder->media_format(), &width, &height)) { + host()->Error(PIPELINE_ERROR_INITIALIZATION_FAILED); + initialize_callback_->Run(); + initialize_callback_.reset(); + return; + } host()->SetVideoSize(width, height); // Initialize the subclass. // TODO(scherkus): do we trust subclasses not to do something silly while // we're holding the lock? - if (!OnInitialize(decoder)) - return false; + if (!OnInitialize(decoder)) { + host()->Error(PIPELINE_ERROR_INITIALIZATION_FAILED); + initialize_callback_->Run(); + initialize_callback_.reset(); + return; + } // Create our video thread. if (!PlatformThread::Create(0, this, &thread_)) { NOTREACHED() << "Video thread creation failed"; - return false; + host()->Error(PIPELINE_ERROR_INITIALIZATION_FAILED); + initialize_callback_->Run(); + initialize_callback_.reset(); + return; } #if defined(OS_WIN) @@ -128,8 +143,6 @@ bool VideoRendererBase::Initialize(VideoDecoder* decoder) { for (size_t i = 0; i < kMaxFrames; ++i) { ScheduleRead(); } - - return true; } // PlatformThread::Delegate implementation. @@ -248,11 +261,15 @@ void VideoRendererBase::OnReadComplete(VideoFrame* frame) { if (frames_.empty()) { // We should have initialized but there's no decoded frames in the queue. // Raise an error. + state_ = ERRORED; host()->Error(PIPELINE_ERROR_NO_DATA); + initialize_callback_->Run(); + initialize_callback_.reset(); } else { state_ = INITIALIZED; current_frame_ = frames_.front(); - host()->InitializationComplete(); + initialize_callback_->Run(); + initialize_callback_.reset(); } } } @@ -266,12 +283,11 @@ bool VideoRendererBase::WaitForInitialized() { // initialized so we can call OnFrameAvailable() to provide subclasses with // the first frame. AutoLock auto_lock(lock_); - DCHECK_EQ(state_, INITIALIZING); while (state_ == INITIALIZING) { frame_available_.Wait(); - if (state_ == STOPPED) { - return false; - } + } + if (state_ == STOPPED || state_ == ERRORED) { + return false; } DCHECK_EQ(state_, INITIALIZED); DCHECK(current_frame_); diff --git a/media/filters/video_renderer_base.h b/media/filters/video_renderer_base.h index e71befa..b1b46ab 100644 --- a/media/filters/video_renderer_base.h +++ b/media/filters/video_renderer_base.h @@ -39,10 +39,10 @@ class VideoRendererBase : public VideoRenderer, // MediaFilter implementation. virtual void Stop(); virtual void SetPlaybackRate(float playback_rate); - virtual void Seek(base::TimeDelta time); + virtual void Seek(base::TimeDelta time, FilterCallback* callback); // VideoRenderer implementation. - virtual bool Initialize(VideoDecoder* decoder); + virtual void Initialize(VideoDecoder* decoder, FilterCallback* callback); // PlatformThread::Delegate implementation. virtual void ThreadMain(); @@ -106,6 +106,7 @@ class VideoRendererBase : public VideoRenderer, INITIALIZING, INITIALIZED, STOPPED, + ERRORED, }; State state_; @@ -117,6 +118,9 @@ class VideoRendererBase : public VideoRenderer, float playback_rate_; + // Filter callbacks. + scoped_ptr<FilterCallback> initialize_callback_; + DISALLOW_COPY_AND_ASSIGN(VideoRendererBase); }; diff --git a/media/filters/video_renderer_base_unittest.cc b/media/filters/video_renderer_base_unittest.cc new file mode 100644 index 0000000..691815d --- /dev/null +++ b/media/filters/video_renderer_base_unittest.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/stl_util-inl.h" +#include "media/base/data_buffer.h" +#include "media/base/mock_filter_host.h" +#include "media/base/mock_filters.h" +#include "media/base/video_frame_impl.h" +#include "media/filters/video_renderer_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::StrictMock; + +namespace media { + +// Mocked subclass of VideoRendererBase for testing purposes. +class MockVideoRendererBase : public VideoRendererBase { + public: + MockVideoRendererBase() {} + virtual ~MockVideoRendererBase() {} + + // VideoRendererBase implementation. + MOCK_METHOD1(OnInitialize, bool (VideoDecoder* decoder)); + MOCK_METHOD0(OnStop, void()); + MOCK_METHOD0(OnFrameAvailable, void()); + + // Used for verifying check points during tests. + MOCK_METHOD1(CheckPoint, void(int id)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockVideoRendererBase); +}; + +class VideoRendererBaseTest : public ::testing::Test { + public: + VideoRendererBaseTest() + : renderer_(new MockVideoRendererBase()), + decoder_(new MockVideoDecoder(mime_type::kUncompressedVideo, kWidth, + kHeight)) { + renderer_->set_host(&host_); + + // Queue all reads from the decoder. + EXPECT_CALL(*decoder_, Read(NotNull())) + .WillRepeatedly(Invoke(this, &VideoRendererBaseTest::EnqueueCallback)); + } + + virtual ~VideoRendererBaseTest() { + STLDeleteElements(&read_queue_); + + // Expect a call into the subclass. + EXPECT_CALL(*renderer_, OnStop()); + renderer_->Stop(); + } + + protected: + static const size_t kWidth; + static const size_t kHeight; + + // Fixture members. + scoped_refptr<MockVideoRendererBase> renderer_; + scoped_refptr<MockVideoDecoder> decoder_; + StrictMock<MockFilterHost> host_; + StrictMock<MockFilterCallback> callback_; + + // Receives asynchronous read requests sent to |decoder_|. + std::deque<Callback1<VideoFrame*>::Type*> read_queue_; + + private: + void EnqueueCallback(Callback1<VideoFrame*>::Type* callback) { + read_queue_.push_back(callback); + } + + DISALLOW_COPY_AND_ASSIGN(VideoRendererBaseTest); +}; + +const size_t VideoRendererBaseTest::kWidth = 16u; +const size_t VideoRendererBaseTest::kHeight = 16u; + +// Test initialization where the decoder's media format is malformed. +TEST_F(VideoRendererBaseTest, Initialize_BadMediaFormat) { + InSequence s; + + // Don't set a media format. + scoped_refptr<MockVideoDecoder> bad_decoder = new MockVideoDecoder(); + + // We expect to receive an error. + EXPECT_CALL(host_, Error(PIPELINE_ERROR_INITIALIZATION_FAILED)); + + // We expect our callback to be executed. + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + + // Initialize, we expect to have no reads. + renderer_->Initialize(bad_decoder, callback_.NewCallback()); + EXPECT_EQ(0u, read_queue_.size()); +} + +// Test initialization where the subclass failed for some reason. +TEST_F(VideoRendererBaseTest, Initialize_Failed) { + InSequence s; + + // We expect the video size to be set. + EXPECT_CALL(host_, SetVideoSize(kWidth, kHeight)); + + // Our subclass will fail when asked to initialize. + EXPECT_CALL(*renderer_, OnInitialize(_)) + .WillOnce(Return(false)); + + // We expect to receive an error. + EXPECT_CALL(host_, Error(PIPELINE_ERROR_INITIALIZATION_FAILED)); + + // We expect our callback to be executed. + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + + // Initialize, we expect to have no reads. + renderer_->Initialize(decoder_, callback_.NewCallback()); + EXPECT_EQ(0u, read_queue_.size()); +} + +// Tests successful initialization, but when we immediately return an end of +// stream frame. +TEST_F(VideoRendererBaseTest, Initialize_NoData) { + InSequence s; + + // We expect the video size to be set. + EXPECT_CALL(host_, SetVideoSize(kWidth, kHeight)); + + // Then our subclass will be asked to initialize. + EXPECT_CALL(*renderer_, OnInitialize(_)) + .WillOnce(Return(true)); + + // Set up a check point to verify that the callback hasn't been executed yet. + EXPECT_CALL(*renderer_, CheckPoint(0)); + + // We'll provide end-of-stream immediately, which results in an error. + EXPECT_CALL(host_, Error(PIPELINE_ERROR_NO_DATA)); + + // Then we expect our callback to be executed. + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + + // Since the callbacks are on a separate thread, expect any number of calls. + EXPECT_CALL(*renderer_, OnFrameAvailable()) + .Times(AnyNumber()); + + // Initialize, we should expect to get a bunch of read requests. + renderer_->Initialize(decoder_, callback_.NewCallback()); + EXPECT_EQ(3u, read_queue_.size()); + + // Verify our callback hasn't been executed yet. + renderer_->CheckPoint(0); + + // Now satisfy the read requests. Our callback should be executed after + // exiting this loop. + while (!read_queue_.empty()) { + const base::TimeDelta kZero; + scoped_refptr<VideoFrame> frame; + VideoFrameImpl::CreateEmptyFrame(&frame); + read_queue_.front()->Run(frame); + delete read_queue_.front(); + read_queue_.pop_front(); + } +} + +// Test successful initialization and preroll. +TEST_F(VideoRendererBaseTest, Initialize_Successful) { + InSequence s; + + // We expect the video size to be set. + EXPECT_CALL(host_, SetVideoSize(kWidth, kHeight)); + + // Then our subclass will be asked to initialize. + EXPECT_CALL(*renderer_, OnInitialize(_)) + .WillOnce(Return(true)); + + // Set up a check point to verify that the callback hasn't been executed yet. + EXPECT_CALL(*renderer_, CheckPoint(0)); + + // After finishing preroll, we expect our callback to be executed. + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + + // Since the callbacks are on a separate thread, expect any number of calls. + EXPECT_CALL(*renderer_, OnFrameAvailable()) + .Times(AnyNumber()); + + // Initialize, we should expect to get a bunch of read requests. + renderer_->Initialize(decoder_, callback_.NewCallback()); + EXPECT_EQ(3u, read_queue_.size()); + + // Verify our callback hasn't been executed yet. + renderer_->CheckPoint(0); + + // Now satisfy the read requests. Our callback should be executed after + // exiting this loop. + while (!read_queue_.empty()) { + const base::TimeDelta kZero; + scoped_refptr<VideoFrame> frame; + VideoFrameImpl::CreateFrame(VideoSurface::RGB32, kWidth, kHeight, kZero, + kZero, &frame); + read_queue_.front()->Run(frame); + delete read_queue_.front(); + read_queue_.pop_front(); + } +} + +} // namespace media diff --git a/media/media.gyp b/media/media.gyp index 3b15e80..5b4b96a 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -155,6 +155,7 @@ 'base/mock_ffmpeg.cc', 'base/mock_ffmpeg.h', 'base/mock_filter_host.h', + 'base/mock_filters.cc', 'base/mock_filters.h', 'base/mock_reader.h', 'base/pipeline_impl_unittest.cc', @@ -162,10 +163,12 @@ 'base/seekable_buffer_unittest.cc', 'base/video_frame_impl_unittest.cc', 'base/yuv_convert_unittest.cc', + 'filters/audio_renderer_base_unittest.cc', 'filters/ffmpeg_demuxer_unittest.cc', 'filters/ffmpeg_glue_unittest.cc', 'filters/ffmpeg_video_decoder_unittest.cc', 'filters/file_data_source_unittest.cc', + 'filters/video_renderer_base_unittest.cc', ], 'conditions': [ ['OS=="linux"', { |