diff options
author | ralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-19 05:04:33 +0000 |
---|---|---|
committer | ralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-19 05:04:33 +0000 |
commit | 082d3cae492040ea1a433b067d82654e98d59bf4 (patch) | |
tree | 63434ee91c91de04448f61e857b2b8c004e8cd39 /media | |
parent | 2e5e02fb69357d0030f27eca29d12df0f779d649 (diff) | |
download | chromium_src-082d3cae492040ea1a433b067d82654e98d59bf4.zip chromium_src-082d3cae492040ea1a433b067d82654e98d59bf4.tar.gz chromium_src-082d3cae492040ea1a433b067d82654e98d59bf4.tar.bz2 |
Fixes a bug in the media pipeline that resulted in the video dimensions never getting set. Includes a new unit test, along with new
mocks for a video decoder and video renderer.
Review URL: http://codereview.chromium.org/20326
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10006 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/base/mock_media_filters.h | 304 | ||||
-rw-r--r-- | media/base/pipeline_impl.cc | 4 | ||||
-rw-r--r-- | media/base/pipeline_impl_unittest.cc | 71 | ||||
-rw-r--r-- | media/filters/file_data_source_unittest.cc | 19 |
4 files changed, 344 insertions, 54 deletions
diff --git a/media/base/mock_media_filters.h b/media/base/mock_media_filters.h index 93665d6..386d685 100644 --- a/media/base/mock_media_filters.h +++ b/media/base/mock_media_filters.h @@ -7,6 +7,8 @@ #include <string> +#include "base/waitable_event.h" +#include "media/base/buffers.h" #include "media/base/factory.h" #include "media/base/filter_host.h" #include "media/base/filters.h" @@ -17,20 +19,53 @@ namespace media { // Behaviors for MockDataSource filter. -enum MockBehavior { - MOCK_FILTER_NORMAL_INIT, - MOCK_FILTER_NEVER_INIT, - MOCK_FILTER_TASK_INIT, - MOCK_FILTER_ERROR_IN_INIT, - MOCK_FILTER_INIT_RETURN_FALSE, - MOCK_FILTER_TASK_ERROR_PRE_INIT, - MOCK_FILTER_TASK_ERROR_POST_INIT +enum MockDataSourceBehavior { + MOCK_DATA_SOURCE_NORMAL_INIT, + MOCK_DATA_SOURCE_NEVER_INIT, + MOCK_DATA_SOURCE_TASK_INIT, + MOCK_DATA_SOURCE_ERROR_IN_INIT, + MOCK_DATA_SOURCE_INIT_RETURN_FALSE, + MOCK_DATA_SOURCE_TASK_ERROR_PRE_INIT, + MOCK_DATA_SOURCE_TASK_ERROR_POST_INIT }; + +// This class is used by all of the mock filters to change the configuration +// of the desired pipeline. The test using this must ensure that the lifetime +// of the object is at least as long as the lifetime of the filters, as this +// is typically allocated on the stack. +struct MockFilterConfig { + MockFilterConfig() + : data_source_behavior(MOCK_DATA_SOURCE_NORMAL_INIT), + has_video(true), + video_width(1280u), + video_height(720u), + video_surface_format(VideoSurface::YV12), + compressed_audio_mime_type(mime_type::kAACAudio), + uncompressed_audio_mime_type(mime_type::kUncompressedAudio), + compressed_video_mime_type(mime_type::kH264AnnexB), + uncompressed_video_mime_type(mime_type::kUncompressedVideo), + frame_duration(base::TimeDelta::FromMicroseconds(33333)) { + } + + MockDataSourceBehavior data_source_behavior; + bool has_video; + size_t video_width; + size_t video_height; + VideoSurface::Format video_surface_format; + std::string compressed_audio_mime_type; + std::string uncompressed_audio_mime_type; + std::string compressed_video_mime_type; + std::string uncompressed_video_mime_type; + base::TimeDelta frame_duration; +}; + + class MockDataSource : public media::DataSource { public: - static FilterFactory* CreateFactory(MockBehavior behavior) { - return new FilterFactoryImpl1<MockDataSource, MockBehavior>(behavior); + static FilterFactory* CreateFactory(const MockFilterConfig* config) { + return new FilterFactoryImpl1<MockDataSource, + const MockFilterConfig*>(config); } // Implementation of MediaFilter. @@ -42,22 +77,22 @@ class MockDataSource : public media::DataSource { mime_type::kApplicationOctetStream); media_format_.SetAsString(MediaFormat::kURL, url); switch (behavior_) { - case MOCK_FILTER_NORMAL_INIT: + case MOCK_DATA_SOURCE_NORMAL_INIT: host_->InitializationComplete(); return true; - case MOCK_FILTER_NEVER_INIT: + case MOCK_DATA_SOURCE_NEVER_INIT: return true; - case MOCK_FILTER_TASK_ERROR_POST_INIT: + case MOCK_DATA_SOURCE_TASK_ERROR_POST_INIT: host_->InitializationComplete(); // Yes, we want to fall through to schedule the task... - case MOCK_FILTER_TASK_ERROR_PRE_INIT: - case MOCK_FILTER_TASK_INIT: + case MOCK_DATA_SOURCE_TASK_ERROR_PRE_INIT: + case MOCK_DATA_SOURCE_TASK_INIT: host_->PostTask(NewRunnableMethod(this, &MockDataSource::TaskBehavior)); return true; - case MOCK_FILTER_ERROR_IN_INIT: + case MOCK_DATA_SOURCE_ERROR_IN_INIT: host_->Error(PIPELINE_ERROR_NETWORK); return false; - case MOCK_FILTER_INIT_RETURN_FALSE: + case MOCK_DATA_SOURCE_INIT_RETURN_FALSE: return false; default: NOTREACHED(); @@ -88,19 +123,22 @@ class MockDataSource : public media::DataSource { } private: - friend class media::FilterFactoryImpl1<MockDataSource, MockBehavior>; + friend class media::FilterFactoryImpl1<MockDataSource, + const MockFilterConfig*>; - explicit MockDataSource(MockBehavior behavior) : behavior_(behavior) {} + explicit MockDataSource(const MockFilterConfig* config) + : behavior_(config->data_source_behavior) { + } virtual ~MockDataSource() {} void TaskBehavior() { switch (behavior_) { - case MOCK_FILTER_TASK_ERROR_POST_INIT: - case MOCK_FILTER_TASK_ERROR_PRE_INIT: + case MOCK_DATA_SOURCE_TASK_ERROR_POST_INIT: + case MOCK_DATA_SOURCE_TASK_ERROR_PRE_INIT: host_->Error(PIPELINE_ERROR_NETWORK); break; - case MOCK_FILTER_TASK_INIT: + case MOCK_DATA_SOURCE_TASK_INIT: host_->InitializationComplete(); break; default: @@ -108,7 +146,7 @@ class MockDataSource : public media::DataSource { } } - MockBehavior behavior_; + MockDataSourceBehavior behavior_; MediaFormat media_format_; DISALLOW_COPY_AND_ASSIGN(MockDataSource); @@ -118,8 +156,9 @@ class MockDataSource : public media::DataSource { class MockDemuxer : public Demuxer { public: - static FilterFactory* CreateFactory() { - return new FilterFactoryImpl0<MockDemuxer>(); + static FilterFactory* CreateFactory(const MockFilterConfig* config) { + return new FilterFactoryImpl1<MockDemuxer, + const MockFilterConfig*>(config); } // Implementation of MediaFilter. @@ -132,27 +171,43 @@ class MockDemuxer : public Demuxer { } virtual size_t GetNumberOfStreams() { + if (config_->has_video) { + return 2; + } return 1; } virtual media::DemuxerStream* GetStream(int stream_id) { - EXPECT_EQ(stream_id, 0); - return &mock_demuxer_stream_; + switch (stream_id) { + case 0: + return &mock_audio_stream_; + case 1: + if (config_->has_video) { + return &mock_video_stream_; + } + // Fall-through is correct if no video. + default: + ADD_FAILURE(); + return NULL; + } } private: - friend class media::FilterFactoryImpl0<MockDemuxer>; + friend class media::FilterFactoryImpl1<MockDemuxer, const MockFilterConfig*>; - MockDemuxer() {} + explicit MockDemuxer(const MockFilterConfig* config) + : config_(config), + mock_audio_stream_(config->compressed_audio_mime_type), + mock_video_stream_(config->compressed_video_mime_type) { + } virtual ~MockDemuxer() {} // Internal class implements DemuxerStream interface. class MockDemuxerStream : public DemuxerStream { public: - MockDemuxerStream() { - media_format_.SetAsString(MediaFormat::kMimeType, - media::mime_type::kUncompressedAudio); + explicit MockDemuxerStream(const std::string& mime_type) { + media_format_.SetAsString(MediaFormat::kMimeType, mime_type); } virtual ~MockDemuxerStream() {} @@ -172,7 +227,9 @@ class MockDemuxer : public Demuxer { DISALLOW_COPY_AND_ASSIGN(MockDemuxerStream); }; - MockDemuxerStream mock_demuxer_stream_; + const MockFilterConfig* config_; + MockDemuxerStream mock_audio_stream_; + MockDemuxerStream mock_video_stream_; DISALLOW_COPY_AND_ASSIGN(MockDemuxer); }; @@ -181,8 +238,9 @@ class MockDemuxer : public Demuxer { class MockAudioDecoder : public AudioDecoder { public: - static FilterFactory* CreateFactory() { - return new FilterFactoryImpl0<MockAudioDecoder>(); + static FilterFactory* CreateFactory(const MockFilterConfig* config) { + return new FilterFactoryImpl1<MockAudioDecoder, + const MockFilterConfig*>(config); } static bool IsMediaFormatSupported(const MediaFormat* media_format) { @@ -208,11 +266,12 @@ class MockAudioDecoder : public AudioDecoder { } private: - friend class media::FilterFactoryImpl0<MockAudioDecoder>; + friend class media::FilterFactoryImpl1<MockAudioDecoder, + const MockFilterConfig*>; - MockAudioDecoder() { + explicit MockAudioDecoder(const MockFilterConfig* config) { media_format_.SetAsString(MediaFormat::kMimeType, - media::mime_type::kUncompressedAudio); + config->uncompressed_audio_mime_type); } virtual ~MockAudioDecoder() {} @@ -226,8 +285,9 @@ class MockAudioDecoder : public AudioDecoder { class MockAudioRenderer : public AudioRenderer { public: - static FilterFactory* CreateFactory() { - return new FilterFactoryImpl0<MockAudioRenderer>(); + static FilterFactory* CreateFactory(const MockFilterConfig* config) { + return new FilterFactoryImpl1<MockAudioRenderer, + const MockFilterConfig*>(config); } static bool IsMediaFormatSupported(const MediaFormat* media_format) { @@ -246,9 +306,10 @@ class MockAudioRenderer : public AudioRenderer { virtual void SetVolume(float volume) {} private: - friend class media::FilterFactoryImpl0<MockAudioRenderer>; + friend class media::FilterFactoryImpl1<MockAudioRenderer, + const MockFilterConfig*>; - MockAudioRenderer() {} + explicit MockAudioRenderer(const MockFilterConfig* config) {} virtual ~MockAudioRenderer() {} @@ -256,6 +317,165 @@ class MockAudioRenderer : public AudioRenderer { }; //------------------------------------------------------------------------------ + +class MockVideoFrame : public VideoFrame { + public: + explicit MockVideoFrame(const MockFilterConfig* config, + base::TimeDelta timestamp) + : config_(config), + surface_locked_(false), + timestamp_(timestamp), + duration_(config->frame_duration) { + } + + virtual ~MockVideoFrame() {} + + virtual base::TimeDelta GetTimestamp() const { + return timestamp_; + } + + virtual base::TimeDelta GetDuration() const { + return duration_; + } + + virtual void SetTimestamp(const base::TimeDelta& timestamp) { + timestamp_ = timestamp; + } + + virtual void SetDuration(const base::TimeDelta& duration) { + duration_ = duration; + } + + virtual bool Lock(VideoSurface* surface) { + EXPECT_FALSE(surface_locked_); + surface_locked_ = true; + surface->format = config_->video_surface_format; + surface->width = config_->video_width; + surface->height = config_->video_height; + // TODO(ralphl): mock the data for video surfaces too. + surface->planes = 3; + surface->data[0] = NULL; + surface->data[1] = NULL; + surface->data[2] = NULL; + surface->strides[0] = 0; + surface->strides[1] = 0; + surface->strides[2] = 0; + return false; + } + + virtual void Unlock() { + EXPECT_TRUE(surface_locked_); + surface_locked_ = false; + } + + private: + const MockFilterConfig* config_; + bool surface_locked_; + base::TimeDelta timestamp_; + base::TimeDelta duration_; + + DISALLOW_COPY_AND_ASSIGN(MockVideoFrame); +}; + +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. + } + + // Implementation of MediaFilter. + virtual void Stop() {} + + // Implementation of VideoDecoder. + virtual bool Initialize(DemuxerStream* stream) { + host_->InitializationComplete(); + return true; + } + + virtual const MediaFormat* GetMediaFormat() { + return &media_format_; + } + + virtual void Read(Assignable<VideoFrame>* buffer) { + buffer->AddRef(); + host_->PostTask(NewRunnableMethod(this, &MockVideoDecoder::DoRead, buffer)); + } + + private: + friend class media::FilterFactoryImpl1<MockVideoDecoder, + const MockFilterConfig*>; + + explicit MockVideoDecoder(const MockFilterConfig* config) + : config_(config) { + media_format_.SetAsString(MediaFormat::kMimeType, + config->uncompressed_video_mime_type); + media_format_.SetAsInteger(MediaFormat::kWidth, config->video_width); + media_format_.SetAsInteger(MediaFormat::kHeight, config->video_height); + } + + void DoRead(Assignable<VideoFrame>* buffer) { + VideoFrame* frame = new MockVideoFrame(config_, mock_frame_time_); + mock_frame_time_ += config_->frame_duration; + buffer->SetBuffer(frame); + buffer->OnAssignment(); + buffer->Release(); + } + + virtual ~MockVideoDecoder() {} + + MediaFormat media_format_; + base::TimeDelta mock_frame_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 + } + + // Implementation of MediaFilter. + virtual void Stop() {} + + // Implementation of VideoRenderer. + virtual bool Initialize(VideoDecoder* decoder) { + host_->SetVideoSize(config_->video_width, config_->video_height); + host_->InitializationComplete(); + return true; + } + + private: + friend class media::FilterFactoryImpl1<MockVideoRenderer, + const MockFilterConfig*>; + + explicit MockVideoRenderer(const MockFilterConfig* config) + : config_(config) { + } + + virtual ~MockVideoRenderer() {} + + const MockFilterConfig* config_; + + DISALLOW_COPY_AND_ASSIGN(MockVideoRenderer); +}; + + + +//------------------------------------------------------------------------------ // Simple class that derives from the WaitableEvent class. The event remains // in the reset state until the initialization complete callback is called from // a media pipeline. The normal use of this object looks like: diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc index 5792972..c815a26 100644 --- a/media/base/pipeline_impl.cc +++ b/media/base/pipeline_impl.cc @@ -208,8 +208,8 @@ void PipelineImpl::SetBufferedBytes(int64 buffered_bytes) { void PipelineImpl::SetVideoSize(size_t width, size_t height) { AutoLock auto_lock(lock_); - width = width; - height = height; + video_width_ = width; + video_height_ = height; } //----------------------------------------------------------------------------- diff --git a/media/base/pipeline_impl_unittest.cc b/media/base/pipeline_impl_unittest.cc index 53c29ba..dbaedcc 100644 --- a/media/base/pipeline_impl_unittest.cc +++ b/media/base/pipeline_impl_unittest.cc @@ -24,13 +24,33 @@ 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)); +} + +} // namespace + +// TODO(ralphl): Get rid of single character variable names in these tests. TEST(PipelineImplTest, Initialization) { std::string u(""); PipelineImpl p; InitializationHelper h; - h.Start(&p, MockDataSource::CreateFactory(media::MOCK_FILTER_NEVER_INIT), u); + MockFilterConfig config; + config.data_source_behavior = media::MOCK_DATA_SOURCE_NEVER_INIT; + h.Start(&p, MockDataSource::CreateFactory(&config), u); h.TimedWait(base::TimeDelta::FromMilliseconds(300)); EXPECT_TRUE(h.waiting_for_callback()); EXPECT_FALSE(p.IsInitialized()); @@ -40,7 +60,17 @@ TEST(PipelineImplTest, Initialization) { EXPECT_FALSE(h.callback_success_status()); EXPECT_TRUE(media::PIPELINE_OK == p.GetError()); - h.Start(&p, MockDataSource::CreateFactory(media::MOCK_FILTER_TASK_INIT), u); + config.data_source_behavior = media::MOCK_DATA_SOURCE_TASK_INIT; + h.Start(&p, MockDataSource::CreateFactory(&config), u); + h.TimedWait(base::TimeDelta::FromSeconds(5)); + EXPECT_FALSE(h.waiting_for_callback()); + EXPECT_FALSE(h.callback_success_status()); + EXPECT_FALSE(p.IsInitialized()); + EXPECT_FALSE(media::PIPELINE_OK == p.GetError()); + p.Stop(); + + config.data_source_behavior = media::MOCK_DATA_SOURCE_ERROR_IN_INIT; + h.Start(&p, MockDataSource::CreateFactory(&config), u); h.TimedWait(base::TimeDelta::FromSeconds(5)); EXPECT_FALSE(h.waiting_for_callback()); EXPECT_FALSE(h.callback_success_status()); @@ -49,14 +79,36 @@ TEST(PipelineImplTest, Initialization) { p.Stop(); } -TEST(PipelineImplTest, FullMockPipeline) { +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); + h.TimedWait(base::TimeDelta::FromSeconds(5)); + EXPECT_FALSE(h.waiting_for_callback()); + EXPECT_TRUE(h.callback_success_status()); + EXPECT_TRUE(p.IsInitialized()); + EXPECT_TRUE(media::PIPELINE_OK == p.GetError()); + size_t width, height; + p.GetVideoSize(&width, &height); + EXPECT_EQ(0u, width); + EXPECT_EQ(0u, height); + p.SetPlaybackRate(1.0f); + p.SetVolume(0.5f); + p.Stop(); + EXPECT_FALSE(p.IsInitialized()); +} + +TEST(PipelineImplTest, MockVideoPipeline) { std::string url(""); PipelineImpl p; scoped_refptr<FilterFactoryCollection> c = new FilterFactoryCollection(); - c->AddFactory(MockDataSource::CreateFactory(media::MOCK_FILTER_NORMAL_INIT)); - c->AddFactory(MockDemuxer::CreateFactory()); - c->AddFactory(MockAudioDecoder::CreateFactory()); - c->AddFactory(MockAudioRenderer::CreateFactory()); + MockFilterConfig config; + AddAllMockFilters(c, &config); InitializationHelper h; h.Start(&p, c, url); h.TimedWait(base::TimeDelta::FromSeconds(5)); @@ -64,8 +116,13 @@ TEST(PipelineImplTest, FullMockPipeline) { EXPECT_TRUE(h.callback_success_status()); EXPECT_TRUE(p.IsInitialized()); EXPECT_TRUE(media::PIPELINE_OK == p.GetError()); + size_t width, height; + p.GetVideoSize(&width, &height); + EXPECT_EQ(config.video_width, width); + EXPECT_EQ(config.video_height, height); p.SetPlaybackRate(1.0f); p.SetVolume(0.5f); p.Stop(); EXPECT_FALSE(p.IsInitialized()); } + diff --git a/media/filters/file_data_source_unittest.cc b/media/filters/file_data_source_unittest.cc index 3006454..311b6cd 100644 --- a/media/filters/file_data_source_unittest.cc +++ b/media/filters/file_data_source_unittest.cc @@ -28,9 +28,18 @@ using media::MediaFormat; using media::MockDemuxer; using media::MockAudioDecoder; using media::MockAudioRenderer; +using media::MockFilterConfig; using media::MockFilterHost; using media::PipelineImpl; +namespace { + +// Returns a path to the test file which contains the string "0123456789" +// without the quotes or any trailing space or null termination. The file lives +// under the media/test/data directory. Under Windows, strings for the +// FilePath class are unicode, and the pipeline wants char strings. Convert +// the string to UTF8 under Windows. For Mac and Linux, file paths are already +// chars so just return the string from the FilePath. std::string TestFileURL() { FilePath data_dir; EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); @@ -45,14 +54,18 @@ std::string TestFileURL() { #endif } +} // namespace + // Use the "real" pipeline to open the file. TEST(FileDataSourceTest, OpenFile) { PipelineImpl pipeline; + MockFilterConfig config; + config.has_video = false; scoped_refptr<FilterFactoryCollection> c = new FilterFactoryCollection(); c->AddFactory(FileDataSource::CreateFactory()); - c->AddFactory(MockDemuxer::CreateFactory()); - c->AddFactory(MockAudioDecoder::CreateFactory()); - c->AddFactory(MockAudioRenderer::CreateFactory()); + c->AddFactory(MockDemuxer::CreateFactory(&config)); + c->AddFactory(MockAudioDecoder::CreateFactory(&config)); + c->AddFactory(MockAudioRenderer::CreateFactory(&config)); InitializationHelper h; h.Start(&pipeline, c, TestFileURL()); h.Wait(); |