diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-21 22:44:04 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-21 22:44:04 +0000 |
commit | 1ab1c24bda1b30b57e040b3b077a001d095fcab3 (patch) | |
tree | 59e01bd62cc44596a5d8df6c7e63e6414846b4a8 /media | |
parent | 735586ec444f701f94219fbb59ff01f11b064071 (diff) | |
download | chromium_src-1ab1c24bda1b30b57e040b3b077a001d095fcab3.zip chromium_src-1ab1c24bda1b30b57e040b3b077a001d095fcab3.tar.gz chromium_src-1ab1c24bda1b30b57e040b3b077a001d095fcab3.tar.bz2 |
Introduce AudioDecoderConfig to migrate away from GetAVStream().
Instead add DemuxerStream::audio_decoder_config() to break FFmpegAudioDecoder's dependency on the AVCodecContext object maintained by FFmpegDemuxer.
Review URL: http://codereview.chromium.org/7867051
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@102183 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
26 files changed, 426 insertions, 85 deletions
diff --git a/media/base/audio_decoder_config.cc b/media/base/audio_decoder_config.cc new file mode 100644 index 0000000..80b23e2 --- /dev/null +++ b/media/base/audio_decoder_config.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2011 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/audio_decoder_config.h" + +#include "base/logging.h" +#include "media/base/limits.h" + +namespace media { + +AudioDecoderConfig::AudioDecoderConfig() + : codec_(kUnknownAudioCodec), + bits_per_channel_(0), + channel_layout_(CHANNEL_LAYOUT_UNSUPPORTED), + samples_per_second_(0), + extra_data_size_(0) { +} + +AudioDecoderConfig::AudioDecoderConfig(AudioCodec codec, + int bits_per_channel, + ChannelLayout channel_layout, + int samples_per_second, + const uint8* extra_data, + size_t extra_data_size) { + Initialize(codec, bits_per_channel, channel_layout, samples_per_second, + extra_data, extra_data_size); +} + +void AudioDecoderConfig::Initialize(AudioCodec codec, + int bits_per_channel, + ChannelLayout channel_layout, + int samples_per_second, + const uint8* extra_data, + size_t extra_data_size) { + CHECK((extra_data_size != 0) == (extra_data != NULL)); + + codec_ = codec; + bits_per_channel_ = bits_per_channel; + channel_layout_ = channel_layout; + samples_per_second_ = samples_per_second; + extra_data_size_ = extra_data_size; + + if (extra_data_size_ > 0) { + extra_data_.reset(new uint8[extra_data_size_]); + memcpy(extra_data_.get(), extra_data, extra_data_size_); + } else { + extra_data_.reset(); + } +} + +AudioDecoderConfig::~AudioDecoderConfig() {} + +bool AudioDecoderConfig::IsValidConfig() const { + return codec_ != kUnknownAudioCodec && + channel_layout_ != CHANNEL_LAYOUT_UNSUPPORTED && + bits_per_channel_ > 0 && + bits_per_channel_ <= Limits::kMaxBitsPerSample && + samples_per_second_ > 0 && + samples_per_second_ <= Limits::kMaxSampleRate; +} + +AudioCodec AudioDecoderConfig::codec() const { + return codec_; +} + +int AudioDecoderConfig::bits_per_channel() const { + return bits_per_channel_; +} + +ChannelLayout AudioDecoderConfig::channel_layout() const { + return channel_layout_; +} + +int AudioDecoderConfig::samples_per_second() const { + return samples_per_second_; +} + +uint8* AudioDecoderConfig::extra_data() const { + return extra_data_.get(); +} + +size_t AudioDecoderConfig::extra_data_size() const { + return extra_data_size_; +} + +} // namespace media diff --git a/media/base/audio_decoder_config.h b/media/base/audio_decoder_config.h new file mode 100644 index 0000000..0245f42 --- /dev/null +++ b/media/base/audio_decoder_config.h @@ -0,0 +1,75 @@ +// Copyright (c) 2011 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. + +#ifndef MEDIA_BASE_AUDIO_DECODER_CONFIG_H_ +#define MEDIA_BASE_AUDIO_DECODER_CONFIG_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/channel_layout.h" +#include "media/base/media_export.h" + +namespace media { + +enum AudioCodec { + kUnknownAudioCodec, + kCodecAAC, + kCodecMP3, + kCodecPCM, + kCodecVorbis, + + // DO NOT ADD RANDOM AUDIO CODECS! + // + // The only acceptable time to add a new codec is if there is production code + // that uses said codec in the same CL. +}; + +class MEDIA_EXPORT AudioDecoderConfig { + public: + // Constructs an uninitialized object. Clients should call Initialize() with + // appropriate values before using. + AudioDecoderConfig(); + + // Constructs an initialized object. It is acceptable to pass in NULL for + // |extra_data|, otherwise the memory is copied. + AudioDecoderConfig(AudioCodec codec, int bits_per_channel, + ChannelLayout channel_layout, int samples_per_second, + const uint8* extra_data, size_t extra_data_size); + + ~AudioDecoderConfig(); + + // Resets the internal state of this object. + void Initialize(AudioCodec codec, int bits_per_channel, + ChannelLayout channel_layout, int samples_per_second, + const uint8* extra_data, size_t extra_data_size); + + // Returns true if this object has appropriate configuration values, false + // otherwise. + bool IsValidConfig() const; + + AudioCodec codec() const; + int bits_per_channel() const; + ChannelLayout channel_layout() const; + int samples_per_second() const; + + // Optional byte data required to initialize audio decoders such as Vorbis + // codebooks. + uint8* extra_data() const; + size_t extra_data_size() const; + + private: + AudioCodec codec_; + int bits_per_channel_; + ChannelLayout channel_layout_; + int samples_per_second_; + + scoped_array<uint8> extra_data_; + size_t extra_data_size_; + + DISALLOW_COPY_AND_ASSIGN(AudioDecoderConfig); +}; + +} // namespace media + +#endif // MEDIA_BASE_AUDIO_DECODER_CONFIG_H_ diff --git a/media/base/demuxer_stream.h b/media/base/demuxer_stream.h index 5256115..e81423e 100644 --- a/media/base/demuxer_stream.h +++ b/media/base/demuxer_stream.h @@ -13,6 +13,7 @@ struct AVStream; namespace media { +class AudioDecoderConfig; class Buffer; class MEDIA_EXPORT DemuxerStream @@ -34,6 +35,10 @@ class MEDIA_EXPORT DemuxerStream // Returns an |AVStream*| if supported, or NULL. virtual AVStream* GetAVStream(); + // Returns the audio decoder configuration. It is an error to call this method + // if type() != AUDIO. + virtual const AudioDecoderConfig& audio_decoder_config() = 0; + // Returns the type of stream. virtual Type type() = 0; diff --git a/media/base/filters.h b/media/base/filters.h index a379a58..c11c943 100644 --- a/media/base/filters.h +++ b/media/base/filters.h @@ -231,7 +231,7 @@ class MEDIA_EXPORT AudioDecoder : public Filter { // Returns various information about the decoded audio format. virtual int bits_per_channel() = 0; virtual ChannelLayout channel_layout() = 0; - virtual int sample_rate() = 0; + virtual int samples_per_second() = 0; protected: AudioDecoder(); diff --git a/media/base/media_log.cc b/media/base/media_log.cc index 62f509e..af205dd 100644 --- a/media/base/media_log.cc +++ b/media/base/media_log.cc @@ -137,6 +137,8 @@ const char* MediaLog::PipelineStatusToString(PipelineStatus status) { return "demuxer: no supported streams"; case DEMUXER_ERROR_COULD_NOT_CREATE_THREAD: return "demuxer: could not create thread"; + case DECODER_ERROR_NOT_SUPPORTED: + return "decoder: not supported"; case DATASOURCE_ERROR_URL_NOT_SUPPORTED: return "data source: url not supported"; } diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h index a836f0b..9b16ff5 100644 --- a/media/base/mock_filters.h +++ b/media/base/mock_filters.h @@ -15,6 +15,7 @@ #include <string> +#include "media/base/audio_decoder_config.h" #include "media/base/demuxer.h" #include "media/base/filters.h" #include "media/base/filter_collection.h" @@ -160,6 +161,7 @@ class MockDemuxerStream : public DemuxerStream { MOCK_METHOD0(type, Type()); MOCK_METHOD1(Read, void(const ReadCallback& read_callback)); MOCK_METHOD0(GetAVStream, AVStream*()); + MOCK_METHOD0(audio_decoder_config, const AudioDecoderConfig&()); MOCK_METHOD0(EnableBitstreamConverter, void()); protected: @@ -214,7 +216,7 @@ class MockAudioDecoder : public AudioDecoder { MOCK_METHOD1(ProduceAudioSamples, void(scoped_refptr<Buffer>)); MOCK_METHOD0(bits_per_channel, int(void)); MOCK_METHOD0(channel_layout, ChannelLayout(void)); - MOCK_METHOD0(sample_rate, int(void)); + MOCK_METHOD0(samples_per_second, int(void)); void ConsumeAudioSamplesForTest(scoped_refptr<Buffer> buffer) { AudioDecoder::ConsumeAudioSamples(buffer); diff --git a/media/base/pipeline_status.h b/media/base/pipeline_status.h index dcbda09..508d2d6 100644 --- a/media/base/pipeline_status.h +++ b/media/base/pipeline_status.h @@ -30,6 +30,8 @@ enum PipelineStatus { DEMUXER_ERROR_COULD_NOT_PARSE, DEMUXER_ERROR_NO_SUPPORTED_STREAMS, DEMUXER_ERROR_COULD_NOT_CREATE_THREAD, + // Decoder related errors. + DECODER_ERROR_NOT_SUPPORTED, // DataSourceFactory errors. DATASOURCE_ERROR_URL_NOT_SUPPORTED, }; diff --git a/media/base/video_decoder_config.h b/media/base/video_decoder_config.h index fcfd3e7..2c6bd45 100644 --- a/media/base/video_decoder_config.h +++ b/media/base/video_decoder_config.h @@ -14,7 +14,7 @@ namespace media { enum VideoCodec { - kUnknown, + kUnknownVideoCodec, kCodecH264, kCodecVC1, kCodecMPEG2, diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc index e0e40f3..72323c7 100644 --- a/media/ffmpeg/ffmpeg_common.cc +++ b/media/ffmpeg/ffmpeg_common.cc @@ -21,6 +21,113 @@ int64 ConvertToTimeBase(const AVRational& time_base, return av_rescale_q(timestamp.InMicroseconds(), kMicrosBase, time_base); } +static AudioCodec CodecIDToAudioCodec(CodecID codec_id) { + switch (codec_id) { + case CODEC_ID_AAC: + return kCodecAAC; + case CODEC_ID_MP3: + return kCodecMP3; + case CODEC_ID_VORBIS: + return kCodecVorbis; + case CODEC_ID_PCM_U8: + case CODEC_ID_PCM_S16LE: + case CODEC_ID_PCM_S32LE: + return kCodecPCM; + default: + NOTREACHED(); + } + return kUnknownAudioCodec; +} + +static CodecID AudioCodecToCodecID(AudioCodec audio_codec, + int bits_per_channel) { + switch (audio_codec) { + case kUnknownAudioCodec: + return CODEC_ID_NONE; + case kCodecAAC: + return CODEC_ID_AAC; + case kCodecMP3: + return CODEC_ID_MP3; + case kCodecPCM: + switch (bits_per_channel) { + case 8: + return CODEC_ID_PCM_U8; + case 16: + return CODEC_ID_PCM_S16LE; + case 32: + return CODEC_ID_PCM_S32LE; + default: + NOTREACHED() << "Unsupported bits_per_channel: " << bits_per_channel; + } + case kCodecVorbis: + return CODEC_ID_VORBIS; + default: + NOTREACHED(); + } + return CODEC_ID_NONE; +} + +void AVCodecContextToAudioDecoderConfig( + const AVCodecContext* codec_context, + AudioDecoderConfig* config) { + DCHECK_EQ(codec_context->codec_type, AVMEDIA_TYPE_AUDIO); + + AudioCodec codec = CodecIDToAudioCodec(codec_context->codec_id); + int bits_per_channel = av_get_bits_per_sample_fmt(codec_context->sample_fmt); + ChannelLayout channel_layout = + ChannelLayoutToChromeChannelLayout(codec_context->channel_layout, + codec_context->channels); + int samples_per_second = codec_context->sample_rate; + + config->Initialize(codec, + bits_per_channel, + channel_layout, + samples_per_second, + codec_context->extradata, + codec_context->extradata_size); +} + +void AudioDecoderConfigToAVCodecContext(const AudioDecoderConfig& config, + AVCodecContext* codec_context) { + codec_context->codec_type = AVMEDIA_TYPE_AUDIO; + codec_context->codec_id = AudioCodecToCodecID(config.codec(), + config.bits_per_channel()); + + switch (config.bits_per_channel()) { + case 8: + codec_context->sample_fmt = AV_SAMPLE_FMT_U8; + break; + case 16: + codec_context->sample_fmt = AV_SAMPLE_FMT_S16; + break; + case 32: + codec_context->sample_fmt = AV_SAMPLE_FMT_S32; + break; + default: + NOTIMPLEMENTED() << "TODO(scherkus): DO SOMETHING BETTER HERE?"; + codec_context->sample_fmt = AV_SAMPLE_FMT_NONE; + } + + // TODO(scherkus): should we set |channel_layout|? I'm not sure if FFmpeg uses + // said information to decode. + codec_context->channels = + ChannelLayoutToChannelCount(config.channel_layout()); + codec_context->sample_rate = config.samples_per_second(); + + if (config.extra_data()) { + codec_context->extradata_size = config.extra_data_size(); + codec_context->extradata = reinterpret_cast<uint8_t*>( + av_malloc(config.extra_data_size() + FF_INPUT_BUFFER_PADDING_SIZE)); + memcpy(codec_context->extradata, config.extra_data(), + config.extra_data_size()); + memset(codec_context->extradata + config.extra_data_size(), '\0', + FF_INPUT_BUFFER_PADDING_SIZE); + } else { + codec_context->extradata = NULL; + codec_context->extradata_size = 0; + } +} + VideoCodec CodecIDToVideoCodec(CodecID codec_id) { switch (codec_id) { case CODEC_ID_VC1: @@ -38,12 +145,12 @@ VideoCodec CodecIDToVideoCodec(CodecID codec_id) { default: NOTREACHED(); } - return kUnknown; + return kUnknownVideoCodec; } CodecID VideoCodecToCodecID(VideoCodec video_codec) { switch (video_codec) { - case kUnknown: + case kUnknownVideoCodec: return CODEC_ID_NONE; case kCodecVC1: return CODEC_ID_VC1; diff --git a/media/ffmpeg/ffmpeg_common.h b/media/ffmpeg/ffmpeg_common.h index 6f04e07..dbdbc6e 100644 --- a/media/ffmpeg/ffmpeg_common.h +++ b/media/ffmpeg/ffmpeg_common.h @@ -11,6 +11,7 @@ #include "base/compiler_specific.h" #include "base/memory/singleton.h" #include "base/time.h" +#include "media/base/audio_decoder_config.h" #include "media/base/channel_layout.h" #include "media/base/media_export.h" #include "media/video/video_decode_engine.h" @@ -65,6 +66,13 @@ MEDIA_EXPORT base::TimeDelta ConvertFromTimeBase(const AVRational& time_base, MEDIA_EXPORT int64 ConvertToTimeBase(const AVRational& time_base, const base::TimeDelta& timestamp); +void AVCodecContextToAudioDecoderConfig( + const AVCodecContext* codec_context, + AudioDecoderConfig* config); +void AudioDecoderConfigToAVCodecContext( + const AudioDecoderConfig& config, + AVCodecContext* codec_context); + VideoCodec CodecIDToVideoCodec(CodecID codec_id); CodecID VideoCodecToCodecID(VideoCodec video_codec); diff --git a/media/filters/adaptive_demuxer.cc b/media/filters/adaptive_demuxer.cc index 84a84c8..de980f9 100644 --- a/media/filters/adaptive_demuxer.cc +++ b/media/filters/adaptive_demuxer.cc @@ -217,6 +217,10 @@ AVStream* AdaptiveDemuxerStream::GetAVStream() { return current_stream()->GetAVStream(); } +const AudioDecoderConfig& AdaptiveDemuxerStream::audio_decoder_config() { + return current_stream()->audio_decoder_config(); +} + DemuxerStream::Type AdaptiveDemuxerStream::type() { return current_stream()->type(); } diff --git a/media/filters/adaptive_demuxer.h b/media/filters/adaptive_demuxer.h index 1bc83f6..4fd7e27 100644 --- a/media/filters/adaptive_demuxer.h +++ b/media/filters/adaptive_demuxer.h @@ -70,6 +70,7 @@ class AdaptiveDemuxerStream : public DemuxerStream { virtual Type type(); virtual void EnableBitstreamConverter(); virtual AVStream* GetAVStream(); + virtual const AudioDecoderConfig& audio_decoder_config(); private: // Returns a pointer to the current stream. diff --git a/media/filters/audio_renderer_base.cc b/media/filters/audio_renderer_base.cc index f64f13f..10ad5ea 100644 --- a/media/filters/audio_renderer_base.cc +++ b/media/filters/audio_renderer_base.cc @@ -107,7 +107,7 @@ void AudioRendererBase::Initialize(AudioDecoder* decoder, ChannelLayout channel_layout = decoder_->channel_layout(); int channels = ChannelLayoutToChannelCount(channel_layout); int bits_per_channel = decoder_->bits_per_channel(); - int sample_rate = decoder_->sample_rate(); + int sample_rate = decoder_->samples_per_second(); algorithm_->Initialize(channels, sample_rate, bits_per_channel, 0.0f, cb); // Give the subclass an opportunity to initialize itself. diff --git a/media/filters/audio_renderer_base_unittest.cc b/media/filters/audio_renderer_base_unittest.cc index 87faab3..7c1e6c9 100644 --- a/media/filters/audio_renderer_base_unittest.cc +++ b/media/filters/audio_renderer_base_unittest.cc @@ -61,7 +61,7 @@ class AudioRendererBaseTest : public ::testing::Test { .WillByDefault(Return(16)); ON_CALL(*decoder_, channel_layout()) .WillByDefault(Return(CHANNEL_LAYOUT_MONO)); - ON_CALL(*decoder_, sample_rate()) + ON_CALL(*decoder_, samples_per_second()) .WillByDefault(Return(44100)); } diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc index 0a90e5c..ef4f792 100644 --- a/media/filters/chunk_demuxer.cc +++ b/media/filters/chunk_demuxer.cc @@ -80,12 +80,14 @@ class ChunkDemuxerStream : public DemuxerStream { virtual Type type(); virtual void EnableBitstreamConverter(); virtual AVStream* GetAVStream(); + virtual const AudioDecoderConfig& audio_decoder_config(); private: static void RunCallback(ReadCallback cb, scoped_refptr<Buffer> buffer); Type type_; AVStream* av_stream_; + AudioDecoderConfig audio_config_; mutable base::Lock lock_; ReadCBQueue read_cbs_; @@ -107,6 +109,9 @@ ChunkDemuxerStream::ChunkDemuxerStream(Type type, AVStream* stream) shutdown_called_(false), received_end_of_stream_(false), last_buffer_timestamp_(kNoTimestamp) { + if (type_ == AUDIO) { + AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_); + } } ChunkDemuxerStream::~ChunkDemuxerStream() {} @@ -281,6 +286,11 @@ void ChunkDemuxerStream::EnableBitstreamConverter() {} AVStream* ChunkDemuxerStream::GetAVStream() { return av_stream_; } +const AudioDecoderConfig& ChunkDemuxerStream::audio_decoder_config() { + CHECK_EQ(type_, AUDIO); + return audio_config_; +} + ChunkDemuxer::ChunkDemuxer(ChunkDemuxerClient* client) : state_(WAITING_FOR_INIT), client_(client), diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc index 8217514..6b0ee3b 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "base/bind.h" +#include "media/base/audio_decoder_config.h" #include "media/base/mock_callback.h" #include "media/base/mock_filter_host.h" #include "media/base/test_data_util.h" @@ -185,16 +186,37 @@ TEST_F(ChunkDemuxerTest, TestInit) { client_.reset(new MockChunkDemuxerClient()); demuxer_ = new ChunkDemuxer(client_.get()); InitDemuxer(has_audio, has_video); - EXPECT_EQ(demuxer_->GetStream(DemuxerStream::AUDIO).get() != NULL, - has_audio); - EXPECT_EQ(demuxer_->GetStream(DemuxerStream::VIDEO).get() != NULL, - has_video); + + scoped_refptr<DemuxerStream> audio_stream = + demuxer_->GetStream(DemuxerStream::AUDIO); + if (has_audio) { + EXPECT_TRUE(audio_stream); + + const AudioDecoderConfig& config = audio_stream->audio_decoder_config(); + EXPECT_EQ(kCodecVorbis, config.codec()); + EXPECT_EQ(16, config.bits_per_channel()); + EXPECT_EQ(CHANNEL_LAYOUT_STEREO, config.channel_layout()); + EXPECT_EQ(44100, config.samples_per_second()); + EXPECT_TRUE(config.extra_data()); + EXPECT_GT(config.extra_data_size(), 0u); + } else { + EXPECT_FALSE(audio_stream); + } + + scoped_refptr<DemuxerStream> video_stream = + demuxer_->GetStream(DemuxerStream::VIDEO); + if (has_video) { + EXPECT_TRUE(video_stream); + } else { + EXPECT_FALSE(video_stream); + } + ShutdownDemuxer(); demuxer_ = NULL; } } -// Makes sure that Seek() reports an error if Shutdown() +// Makes sure that Seek() reports an error if Shutdown() // is called before the first cluster is passed to the demuxer. TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstSeekCompletes) { InitDemuxer(true, true); diff --git a/media/filters/dummy_demuxer.cc b/media/filters/dummy_demuxer.cc index 91aa40d..a9afbfc 100644 --- a/media/filters/dummy_demuxer.cc +++ b/media/filters/dummy_demuxer.cc @@ -18,6 +18,11 @@ DemuxerStream::Type DummyDemuxerStream::type() { return type_; } +const AudioDecoderConfig& DummyDemuxerStream::audio_decoder_config() { + CHECK_EQ(type_, AUDIO); + return audio_config_; +} + void DummyDemuxerStream::Read(const ReadCallback& read_callback) {} void DummyDemuxerStream::EnableBitstreamConverter() {} diff --git a/media/filters/dummy_demuxer.h b/media/filters/dummy_demuxer.h index 272d1e4..1b74d01 100644 --- a/media/filters/dummy_demuxer.h +++ b/media/filters/dummy_demuxer.h @@ -11,6 +11,7 @@ #include <vector> +#include "media/base/audio_decoder_config.h" #include "media/base/demuxer.h" namespace media { @@ -22,12 +23,14 @@ class DummyDemuxerStream : public DemuxerStream { // DemuxerStream implementation. virtual void Read(const ReadCallback& read_callback) OVERRIDE; virtual Type type() OVERRIDE; + virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE; virtual void EnableBitstreamConverter() OVERRIDE; private: virtual ~DummyDemuxerStream(); Type type_; + AudioDecoderConfig audio_config_; DISALLOW_COPY_AND_ASSIGN(DummyDemuxerStream); }; diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc index cb3f846..d2086a8 100644 --- a/media/filters/ffmpeg_audio_decoder.cc +++ b/media/filters/ffmpeg_audio_decoder.cc @@ -8,7 +8,6 @@ #include "media/base/data_buffer.h" #include "media/base/demuxer.h" #include "media/base/filter_host.h" -#include "media/base/limits.h" #include "media/ffmpeg/ffmpeg_common.h" namespace media { @@ -50,7 +49,7 @@ FFmpegAudioDecoder::FFmpegAudioDecoder(MessageLoop* message_loop) codec_context_(NULL), bits_per_channel_(0), channel_layout_(CHANNEL_LAYOUT_NONE), - sample_rate_(0), + samples_per_second_(0), decoded_audio_size_(AVCODEC_MAX_AUDIO_FRAME_SIZE), decoded_audio_(static_cast<uint8*>(av_malloc(decoded_audio_size_))), pending_reads_(0) { @@ -58,6 +57,14 @@ FFmpegAudioDecoder::FFmpegAudioDecoder(MessageLoop* message_loop) FFmpegAudioDecoder::~FFmpegAudioDecoder() { av_free(decoded_audio_); + + // XXX: should we require Stop() to be called? this might end up getting + // called on a random thread due to refcounting. + if (codec_context_) { + av_free(codec_context_->extradata); + avcodec_close(codec_context_); + av_free(codec_context_); + } } void FFmpegAudioDecoder::Flush(FilterCallback* callback) { @@ -94,8 +101,8 @@ ChannelLayout FFmpegAudioDecoder::channel_layout() { return channel_layout_; } -int FFmpegAudioDecoder::sample_rate() { - return sample_rate_; +int FFmpegAudioDecoder::samples_per_second() { + return samples_per_second_; } void FFmpegAudioDecoder::DoInitialize( @@ -105,48 +112,41 @@ void FFmpegAudioDecoder::DoInitialize( scoped_ptr<FilterCallback> c(callback); demuxer_stream_ = stream; - AVStream* av_stream = demuxer_stream_->GetAVStream(); - CHECK(av_stream); - + const AudioDecoderConfig& config = stream->audio_decoder_config(); stats_callback_.reset(stats_callback); - // Grab the AVStream's codec context and make sure we have sensible values. - codec_context_ = av_stream->codec; - int bps = av_get_bits_per_sample_fmt(codec_context_->sample_fmt); - if (codec_context_->channels <= 0 || - codec_context_->channels > Limits::kMaxChannels || - (codec_context_->channel_layout == 0 && codec_context_->channels > 2) || - bps <= 0 || bps > Limits::kMaxBitsPerSample || - codec_context_->sample_rate <= 0 || - codec_context_->sample_rate > Limits::kMaxSampleRate) { + // TODO(scherkus): this check should go in PipelineImpl prior to creating + // decoder objects. + if (!config.IsValidConfig()) { DLOG(ERROR) << "Invalid audio stream -" - << " channels: " << codec_context_->channels - << " channel layout:" << codec_context_->channel_layout - << " bps: " << bps - << " sample rate: " << codec_context_->sample_rate; + << " codec: " << config.codec() + << " channel layout: " << config.channel_layout() + << " bits per channel: " << config.bits_per_channel() + << " samples per second: " << config.samples_per_second(); - host()->SetError(PIPELINE_ERROR_DECODE); + host()->SetError(DECODER_ERROR_NOT_SUPPORTED); callback->Run(); return; } - // Serialize calls to avcodec_open(). + // Initialize AVCodecContext structure. + codec_context_ = avcodec_alloc_context(); + AudioDecoderConfigToAVCodecContext(config, codec_context_); + AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); if (!codec || avcodec_open(codec_context_, codec) < 0) { DLOG(ERROR) << "Could not initialize audio decoder: " << codec_context_->codec_id; - host()->SetError(PIPELINE_ERROR_DECODE); + host()->SetError(DECODER_ERROR_NOT_SUPPORTED); callback->Run(); return; } // Success! - bits_per_channel_ = av_get_bits_per_sample_fmt(codec_context_->sample_fmt); - channel_layout_ = - ChannelLayoutToChromeChannelLayout(codec_context_->channel_layout, - codec_context_->channels); - sample_rate_ = codec_context_->sample_rate; + bits_per_channel_ = config.bits_per_channel(); + channel_layout_ = config.channel_layout(); + samples_per_second_ = config.samples_per_second(); callback->Run(); } @@ -286,7 +286,7 @@ void FFmpegAudioDecoder::UpdateDurationAndTimestamp( base::TimeDelta FFmpegAudioDecoder::CalculateDuration(int size) { int64 denominator = ChannelLayoutToChannelCount(channel_layout_) * - bits_per_channel_ / 8 * sample_rate_; + bits_per_channel_ / 8 * samples_per_second_; double microseconds = size / (denominator / static_cast<double>(base::Time::kMicrosecondsPerSecond)); return base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds)); diff --git a/media/filters/ffmpeg_audio_decoder.h b/media/filters/ffmpeg_audio_decoder.h index 999e37c..65f499b 100644 --- a/media/filters/ffmpeg_audio_decoder.h +++ b/media/filters/ffmpeg_audio_decoder.h @@ -30,7 +30,7 @@ class MEDIA_EXPORT FFmpegAudioDecoder : public AudioDecoder { virtual void ProduceAudioSamples(scoped_refptr<Buffer> output) OVERRIDE; virtual int bits_per_channel() OVERRIDE; virtual ChannelLayout channel_layout() OVERRIDE; - virtual int sample_rate() OVERRIDE; + virtual int samples_per_second() OVERRIDE; private: // Methods running on decoder thread. @@ -62,7 +62,7 @@ class MEDIA_EXPORT FFmpegAudioDecoder : public AudioDecoder { // Decoded audio format. int bits_per_channel_; ChannelLayout channel_layout_; - int sample_rate_; + int samples_per_second_; base::TimeDelta estimated_next_timestamp_; diff --git a/media/filters/ffmpeg_audio_decoder_unittest.cc b/media/filters/ffmpeg_audio_decoder_unittest.cc index 9edc85a..d9919427 100644 --- a/media/filters/ffmpeg_audio_decoder_unittest.cc +++ b/media/filters/ffmpeg_audio_decoder_unittest.cc @@ -16,7 +16,8 @@ #include "testing/gtest/include/gtest/gtest.h" using ::testing::_; -using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::StrictMock; namespace media { @@ -28,8 +29,7 @@ class FFmpegAudioDecoderTest : public testing::Test { public: FFmpegAudioDecoderTest() : decoder_(new FFmpegAudioDecoder(&message_loop_)), - demuxer_(new MockDemuxerStream()), - codec_context_(avcodec_alloc_context()) { + demuxer_(new StrictMock<MockDemuxerStream>()) { CHECK(FFmpegGlue::GetInstance()); ReadTestDataFile("vorbis-extradata", @@ -58,31 +58,19 @@ class FFmpegAudioDecoderTest : public testing::Test { base::Bind(&FFmpegAudioDecoderTest::DecodeFinished, base::Unretained(this))); - memset(&stream_, 0, sizeof(stream_)); - - stream_.codec = codec_context_; - codec_context_->codec_id = CODEC_ID_VORBIS; - codec_context_->codec_type = AVMEDIA_TYPE_AUDIO; - codec_context_->channels = 2; - codec_context_->channel_layout = AV_CH_LAYOUT_STEREO; - codec_context_->sample_fmt = AV_SAMPLE_FMT_S16; - codec_context_->sample_rate = 44100; - codec_context_->extradata = vorbis_extradata_.get(); - codec_context_->extradata_size = vorbis_extradata_size_; + config_.Initialize(kCodecVorbis, + 16, + CHANNEL_LAYOUT_STEREO, + 44100, + vorbis_extradata_.get(), + vorbis_extradata_size_); } - virtual ~FFmpegAudioDecoderTest() { - // TODO(scherkus): currently FFmpegAudioDecoder assumes FFmpegDemuxer will - // call avcodec_close(). - if (codec_context_->codec) { - avcodec_close(codec_context_); - } - av_free(codec_context_); - } + virtual ~FFmpegAudioDecoderTest() {} void Initialize() { - EXPECT_CALL(*demuxer_, GetAVStream()) - .WillOnce(Return(&stream_)); + EXPECT_CALL(*demuxer_, audio_decoder_config()) + .WillOnce(ReturnRef(config_)); decoder_->Initialize(demuxer_, NewExpectedCallback(), @@ -125,7 +113,7 @@ class FFmpegAudioDecoderTest : public testing::Test { MessageLoop message_loop_; scoped_refptr<FFmpegAudioDecoder> decoder_; - scoped_refptr<MockDemuxerStream> demuxer_; + scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_; MockStatisticsCallback statistics_callback_; scoped_array<uint8> vorbis_extradata_; @@ -134,8 +122,7 @@ class FFmpegAudioDecoderTest : public testing::Test { std::deque<scoped_refptr<Buffer> > encoded_audio_; std::deque<scoped_refptr<Buffer> > decoded_audio_; - AVStream stream_; - AVCodecContext* codec_context_; // Allocated via avcodec_alloc_context(). + AudioDecoderConfig config_; }; TEST_F(FFmpegAudioDecoderTest, Initialize) { @@ -143,7 +130,7 @@ TEST_F(FFmpegAudioDecoderTest, Initialize) { EXPECT_EQ(16, decoder_->bits_per_channel()); EXPECT_EQ(CHANNEL_LAYOUT_STEREO, decoder_->channel_layout()); - EXPECT_EQ(44100, decoder_->sample_rate()); + EXPECT_EQ(44100, decoder_->samples_per_second()); Stop(); } diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc index e4e21af..3d7bb12 100644 --- a/media/filters/ffmpeg_demuxer.cc +++ b/media/filters/ffmpeg_demuxer.cc @@ -67,6 +67,7 @@ FFmpegDemuxerStream::FFmpegDemuxerStream(FFmpegDemuxer* demuxer, switch (stream->codec->codec_type) { case AVMEDIA_TYPE_AUDIO: type_ = AUDIO; + AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_); break; case AVMEDIA_TYPE_VIDEO: type_ = VIDEO; @@ -241,6 +242,11 @@ AVStream* FFmpegDemuxerStream::GetAVStream() { return stream_; } +const AudioDecoderConfig& FFmpegDemuxerStream::audio_decoder_config() { + CHECK_EQ(type_, AUDIO); + return audio_config_; +} + // static base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp( const AVRational& time_base, int64 timestamp) { diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h index ed59c87..e9bd127 100644 --- a/media/filters/ffmpeg_demuxer.h +++ b/media/filters/ffmpeg_demuxer.h @@ -28,6 +28,7 @@ #include "base/callback.h" #include "base/gtest_prod_util.h" #include "base/synchronization/waitable_event.h" +#include "media/base/audio_decoder_config.h" #include "media/base/buffers.h" #include "media/base/demuxer.h" #include "media/base/pipeline.h" @@ -55,22 +56,22 @@ class FFmpegDemuxerStream : public DemuxerStream { // Returns true is this stream has pending reads, false otherwise. // // Safe to call on any thread. - virtual bool HasPendingReads(); + bool HasPendingReads(); // Enqueues and takes ownership over the given AVPacket. - virtual void EnqueuePacket(AVPacket* packet); + void EnqueuePacket(AVPacket* packet); // Signals to empty the buffer queue and mark next packet as discontinuous. - virtual void FlushBuffers(); + void FlushBuffers(); // Empties the queues and ignores any additional calls to Read(). - virtual void Stop(); + void Stop(); // Returns the duration of this stream. - virtual base::TimeDelta duration(); + base::TimeDelta duration(); // DemuxerStream implementation. - virtual Type type(); + virtual Type type() OVERRIDE; // If |buffer_queue_| is not empty will execute on caller's thread, otherwise // will post ReadTask to execute on demuxer's thread. Read will acquire @@ -78,10 +79,10 @@ class FFmpegDemuxerStream : public DemuxerStream { // not make calls into FFmpegDemuxerStream directly or that may cause a // deadlock. |read_callback| should execute as quickly as possible because // |lock_| is held throughout the life of the callback. - virtual void Read(const ReadCallback& read_callback); - // Bitstream converter to convert input packet. - virtual void EnableBitstreamConverter(); - virtual AVStream* GetAVStream(); + virtual void Read(const ReadCallback& read_callback) OVERRIDE; + virtual void EnableBitstreamConverter() OVERRIDE; + virtual AVStream* GetAVStream() OVERRIDE; + virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE; private: virtual ~FFmpegDemuxerStream(); @@ -100,6 +101,7 @@ class FFmpegDemuxerStream : public DemuxerStream { FFmpegDemuxer* demuxer_; AVStream* stream_; + AudioDecoderConfig audio_config_; Type type_; base::TimeDelta duration_; bool discontinuous_; diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc index e636849..d5c76ae 100644 --- a/media/filters/ffmpeg_demuxer_unittest.cc +++ b/media/filters/ffmpeg_demuxer_unittest.cc @@ -170,18 +170,27 @@ TEST_F(FFmpegDemuxerTest, Initialize_NoStreams) { TEST_F(FFmpegDemuxerTest, Initialize_Successful) { InitializeDemuxer(CreateDataSource("bear-320x240.webm")); - // First stream should be video and support the FFmpegDemuxerStream interface. + // Video stream should be present. scoped_refptr<DemuxerStream> stream = demuxer_->GetStream(DemuxerStream::VIDEO); ASSERT_TRUE(stream); EXPECT_EQ(DemuxerStream::VIDEO, stream->type()); ASSERT_TRUE(stream->GetAVStream()); - // Other stream should be audio and support the FFmpegDemuxerStream interface. + // Audio stream should be present. stream = demuxer_->GetStream(DemuxerStream::AUDIO); ASSERT_TRUE(stream); EXPECT_EQ(DemuxerStream::AUDIO, stream->type()); ASSERT_TRUE(stream->GetAVStream()); + + // FFmpegDemuxer's audio streams support AudioDecoderConfig structs. + const AudioDecoderConfig& config = stream->audio_decoder_config(); + EXPECT_EQ(kCodecVorbis, config.codec()); + EXPECT_EQ(16, config.bits_per_channel()); + EXPECT_EQ(CHANNEL_LAYOUT_STEREO, config.channel_layout()); + EXPECT_EQ(44100, config.samples_per_second()); + EXPECT_TRUE(config.extra_data()); + EXPECT_GT(config.extra_data_size(), 0u); } TEST_F(FFmpegDemuxerTest, Read_Audio) { diff --git a/media/media.gyp b/media/media.gyp index 603f5a0..cdc1495 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -84,6 +84,8 @@ 'audio/win/waveout_output_win.h', 'base/async_filter_factory_base.cc', 'base/async_filter_factory_base.h', + 'base/audio_decoder_config.cc', + 'base/audio_decoder_config.h', 'base/bitstream_buffer.h', 'base/buffers.cc', 'base/buffers.h', diff --git a/media/video/ffmpeg_video_decode_engine_unittest.cc b/media/video/ffmpeg_video_decode_engine_unittest.cc index e8238ff..8e171e1 100644 --- a/media/video/ffmpeg_video_decode_engine_unittest.cc +++ b/media/video/ffmpeg_video_decode_engine_unittest.cc @@ -149,8 +149,10 @@ TEST_F(FFmpegVideoDecodeEngineTest, Initialize_Normal) { } TEST_F(FFmpegVideoDecodeEngineTest, Initialize_FindDecoderFails) { - VideoDecoderConfig config(kUnknown, kCodedSize, kVisibleRect, kNaturalSize, - kFrameRate.num, kFrameRate.den, NULL, 0); + VideoDecoderConfig config(kUnknownVideoCodec, kCodedSize, kVisibleRect, + kNaturalSize, kFrameRate.num, kFrameRate.den, + NULL, 0); + // Test avcodec_find_decoder() returning NULL. VideoCodecInfo info; EXPECT_CALL(*this, OnInitializeComplete(_)) |