Chromium Code Reviews| Index: media/filters/ffmpeg_audio_decoder.cc |
| diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc |
| index 22c568574395cbe344d5f11b7061ac0966a644e4..58eb1b28d1f0839533c1a280fe0c59e9a587d507 100644 |
| --- a/media/filters/ffmpeg_audio_decoder.cc |
| +++ b/media/filters/ffmpeg_audio_decoder.cc |
| @@ -4,48 +4,102 @@ |
| #include "media/filters/ffmpeg_audio_decoder.h" |
| +#include "base/bind.h" |
| #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" |
| -#include "media/filters/ffmpeg_demuxer.h" |
| - |
| -#if !defined(USE_SSE) |
| -#if defined(__SSE__) || defined(ARCH_CPU_X86_64) || _M_IX86_FP==1 |
| -#define USE_SSE 1 |
| -#else |
| -#define USE_SSE 0 |
| -#endif |
| -#endif |
| -#if USE_SSE |
| -#include <xmmintrin.h> |
| -#endif |
| namespace media { |
| -// Size of the decoded audio buffer. |
| -const size_t FFmpegAudioDecoder::kOutputBufferSize = |
| - AVCODEC_MAX_AUDIO_FRAME_SIZE; |
| +// Returns true if the decode result was an error. |
| +static bool IsErrorResult(int result, int decoded_size) { |
| + return result < 0 || |
| + decoded_size < 0 || |
| + decoded_size > AVCODEC_MAX_AUDIO_FRAME_SIZE; |
| +} |
| + |
| +// Returns true if the decode result produced audio samples. |
| +static bool ProducedAudioSamples(int decoded_size) { |
| + return decoded_size > 0; |
| +} |
| + |
| +// Returns true if the decode result was a timestamp packet and not actual audio |
| +// data. |
| +static bool IsTimestampMarkerPacket(int result, Buffer* input) { |
| + // We can get a positive result but no decoded data. This is ok because this |
| + // this can be a marker packet that only contains timestamp. |
| + return result > 0 && !input->IsEndOfStream() && |
| + input->GetTimestamp() != kNoTimestamp && |
| + input->GetDuration() != kNoTimestamp; |
| +} |
| + |
| +// Returns true if the decode result was end of stream. |
| +static bool IsEndOfStream(int result, int decoded_size, Buffer* input) { |
| + // Three conditions to meet to declare end of stream for this decoder: |
| + // 1. FFmpeg didn't read anything. |
| + // 2. FFmpeg didn't output anything. |
| + // 3. An end of stream buffer is received. |
| + return result == 0 && decoded_size == 0 && input->IsEndOfStream(); |
| +} |
| + |
| FFmpegAudioDecoder::FFmpegAudioDecoder(MessageLoop* message_loop) |
| - : DecoderBase<AudioDecoder, Buffer>(message_loop), |
| + : message_loop_(message_loop), |
| codec_context_(NULL), |
| config_(0, CHANNEL_LAYOUT_NONE, 0), |
| - estimated_next_timestamp_(kNoTimestamp) { |
| + estimated_next_timestamp_(kNoTimestamp), |
| + decoded_audio_size_(AVCODEC_MAX_AUDIO_FRAME_SIZE), |
| + decoded_audio_(static_cast<uint8*>(av_malloc(decoded_audio_size_))), |
| + pending_reads_(0) { |
| } |
| FFmpegAudioDecoder::~FFmpegAudioDecoder() { |
| + av_free(decoded_audio_); |
| } |
| -void FFmpegAudioDecoder::DoInitialize(DemuxerStream* demuxer_stream, |
| - bool* success, |
| - Task* done_cb) { |
| - base::ScopedTaskRunner done_runner(done_cb); |
| - *success = false; |
| +void FFmpegAudioDecoder::Flush(FilterCallback* callback) { |
| + message_loop_->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(this, &FFmpegAudioDecoder::DoFlush, callback)); |
| +} |
| - AVStream* av_stream = demuxer_stream->GetAVStream(); |
| - if (!av_stream) { |
| - return; |
| - } |
| +void FFmpegAudioDecoder::Initialize( |
| + DemuxerStream* stream, |
| + FilterCallback* callback, |
| + StatisticsCallback* stats_callback) { |
| + // TODO(scherkus): change Initialize() signature to pass |stream| as a |
| + // scoped_refptr<>. |
|
acolwell GONE FROM CHROMIUM
2011/09/16 19:04:37
const scoped_refptr<>&
|
| + scoped_refptr<DemuxerStream> ref_stream(stream); |
| + message_loop_->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(this, &FFmpegAudioDecoder::DoInitialize, |
| + ref_stream, callback, stats_callback)); |
| +} |
| + |
| +AudioDecoderConfig FFmpegAudioDecoder::config() { |
| + return config_; |
| +} |
| + |
| +void FFmpegAudioDecoder::ProduceAudioSamples(scoped_refptr<Buffer> buffer) { |
| + message_loop_->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(this, &FFmpegAudioDecoder::DoProduceAudioSamples, |
| + buffer)); |
| +} |
| + |
| +void FFmpegAudioDecoder::DoInitialize( |
| + scoped_refptr<DemuxerStream> stream, |
|
acolwell GONE FROM CHROMIUM
2011/09/16 19:04:37
const&
|
| + FilterCallback* callback, |
| + StatisticsCallback* stats_callback) { |
| + scoped_ptr<FilterCallback> c(callback); |
| + |
| + demuxer_stream_ = stream; |
| + AVStream* av_stream = demuxer_stream_->GetAVStream(); |
| + CHECK(av_stream); |
| + |
| + stats_callback_.reset(stats_callback); |
| // Grab the AVStream's codec context and make sure we have sensible values. |
| codec_context_ = av_stream->codec; |
| @@ -56,20 +110,29 @@ void FFmpegAudioDecoder::DoInitialize(DemuxerStream* demuxer_stream, |
| bps <= 0 || bps > Limits::kMaxBitsPerSample || |
| codec_context_->sample_rate <= 0 || |
| codec_context_->sample_rate > Limits::kMaxSampleRate) { |
| - DLOG(WARNING) << "Invalid audio stream -" |
| - << " channels: " << codec_context_->channels |
| - << " channel layout:" << codec_context_->channel_layout |
| - << " bps: " << bps |
| - << " sample rate: " << codec_context_->sample_rate; |
| + DLOG(ERROR) << "Invalid audio stream -" |
| + << " channels: " << codec_context_->channels |
| + << " channel layout:" << codec_context_->channel_layout |
| + << " bps: " << bps |
| + << " sample rate: " << codec_context_->sample_rate; |
| + |
| + host()->SetError(PIPELINE_ERROR_DECODE); |
| + callback->Run(); |
| return; |
| } |
| // Serialize calls to avcodec_open(). |
| 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); |
| + callback->Run(); |
| return; |
| } |
| + // Success! |
| config_.bits_per_channel = |
| av_get_bits_per_sample_fmt(codec_context_->sample_fmt); |
| config_.channel_layout = |
| @@ -77,185 +140,147 @@ void FFmpegAudioDecoder::DoInitialize(DemuxerStream* demuxer_stream, |
| codec_context_->channels); |
| config_.sample_rate = codec_context_->sample_rate; |
| - // Prepare the output buffer. |
| - output_buffer_.reset(static_cast<uint8*>(av_malloc(kOutputBufferSize))); |
| - if (!output_buffer_.get()) { |
| - host()->SetError(PIPELINE_ERROR_OUT_OF_MEMORY); |
| - return; |
| - } |
| - *success = true; |
| -} |
| - |
| -AudioDecoderConfig FFmpegAudioDecoder::config() { |
| - return config_; |
| + callback->Run(); |
| } |
| -void FFmpegAudioDecoder::ProduceAudioSamples(scoped_refptr<Buffer> output) { |
| - DecoderBase<AudioDecoder, Buffer>::PostReadTaskHack(output); |
| -} |
| - |
| -void FFmpegAudioDecoder::DoSeek(base::TimeDelta time, Task* done_cb) { |
| +void FFmpegAudioDecoder::DoFlush(FilterCallback* callback) { |
| avcodec_flush_buffers(codec_context_); |
| estimated_next_timestamp_ = kNoTimestamp; |
| - done_cb->Run(); |
| - delete done_cb; |
| -} |
| -// ConvertAudioF32ToS32() converts float audio (F32) to int (S32) in place. |
| -// This is a temporary solution. |
| -// The purpose of this short term fix is to enable WMApro, which decodes to |
| -// float. |
| -// The audio driver has been tested by passing the float audio thru. |
| -// FFmpeg for ChromeOS only exposes U8, S16 and F32. |
| -// To properly expose new audio sample types at the audio driver layer, a enum |
| -// should be created to represent all suppported types, including types |
| -// for Pepper. FFmpeg should be queried for type and passed along. |
| - |
| -// TODO(fbarchard): Remove this function. Expose all FFmpeg types to driver. |
| -// TODO(fbarchard): If this function is kept, move it to audio_util.cc |
| - |
| -#if USE_SSE |
| -const __m128 kFloatScaler = _mm_set1_ps( 2147483648.0f ); |
| -static void FloatToIntSaturate(float* p) { |
| - __m128 a = _mm_set1_ps(*p); |
| - a = _mm_mul_ss(a, kFloatScaler); |
| - *reinterpret_cast<int32*>(p) = _mm_cvtss_si32(a); |
| -} |
| -#else |
| -const float kFloatScaler = 2147483648.0f; |
| -const int kMinSample = std::numeric_limits<int32>::min(); |
| -const int kMaxSample = std::numeric_limits<int32>::max(); |
| -const float kMinSampleFloat = |
| - static_cast<float>(std::numeric_limits<int32>::min()); |
| -const float kMaxSampleFloat = |
| - static_cast<float>(std::numeric_limits<int32>::max()); |
| -static void FloatToIntSaturate(float* p) { |
| - float f = *p * kFloatScaler + 0.5f; |
| - int sample; |
| - if (f <= kMinSampleFloat) { |
| - sample = kMinSample; |
| - } else if (f >= kMaxSampleFloat) { |
| - sample = kMaxSample; |
| - } else { |
| - sample = static_cast<int32>(f); |
| - } |
| - *reinterpret_cast<int32*>(p) = sample; |
| + callback->Run(); |
| + delete callback; |
| } |
| -#endif |
| -static void ConvertAudioF32ToS32(void* buffer, int buffer_size) { |
| - for (int i = 0; i < buffer_size / 4; ++i) { |
| - FloatToIntSaturate(reinterpret_cast<float*>(buffer) + i); |
| - } |
| + |
| +void FFmpegAudioDecoder::DoProduceAudioSamples(scoped_refptr<Buffer> output) { |
|
acolwell GONE FROM CHROMIUM
2011/09/16 19:04:37
const&
scherkus (not reviewing)
2011/09/16 20:39:40
Done.
|
| + output_buffers_.push_back(output); |
| + ReadFromDemuxerStream(); |
| } |
| -void FFmpegAudioDecoder::DoDecode(Buffer* input) { |
| - PipelineStatistics statistics; |
| +void FFmpegAudioDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& input) { |
| + DCHECK(!output_buffers_.empty()); |
| + DCHECK_GT(pending_reads_, 0); |
| + pending_reads_--; |
| // FFmpeg tends to seek Ogg audio streams in the middle of nowhere, giving us |
| // a whole bunch of AV_NOPTS_VALUE packets. Discard them until we find |
| // something valid. Refer to http://crbug.com/49709 |
| - // TODO(hclam): remove this once fixing the issue in FFmpeg. |
| if (input->GetTimestamp() == kNoTimestamp && |
| estimated_next_timestamp_ == kNoTimestamp && |
| !input->IsEndOfStream()) { |
| - DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); |
| + ReadFromDemuxerStream(); |
| return; |
| } |
| - // Due to FFmpeg API changes we no longer have const read-only pointers. |
| AVPacket packet; |
| av_init_packet(&packet); |
| - packet.data = const_cast<uint8*>(input->GetData()); |
| - packet.size = input->GetDataSize(); |
| + if (input->IsEndOfStream()) { |
| + packet.data = NULL; |
| + packet.size = 0; |
| + } else { |
| + packet.data = const_cast<uint8*>(input->GetData()); |
| + packet.size = input->GetDataSize(); |
| + } |
| + PipelineStatistics statistics; |
| statistics.audio_bytes_decoded = input->GetDataSize(); |
| - int16_t* output_buffer = reinterpret_cast<int16_t*>(output_buffer_.get()); |
| - int output_buffer_size = kOutputBufferSize; |
| - int result = avcodec_decode_audio3(codec_context_, |
| - output_buffer, |
| - &output_buffer_size, |
| - &packet); |
| + int decoded_audio_size = decoded_audio_size_; |
| + int result = avcodec_decode_audio3( |
| + codec_context_, reinterpret_cast<int16_t*>(decoded_audio_), |
| + &decoded_audio_size, &packet); |
| - if (codec_context_->sample_fmt == SAMPLE_FMT_FLT) { |
| - ConvertAudioF32ToS32(output_buffer, output_buffer_size); |
| - } |
| + if (IsErrorResult(result, decoded_audio_size)) { |
| + DCHECK(!input->IsEndOfStream()) |
| + << "End of stream buffer produced an error! " |
| + << "This is quite possibly a bug in the audio decoder not handling " |
| + << "end of stream AVPackets correctly."; |
| - // TODO(ajwong): Consider if kOutputBufferSize should just be an int instead |
| - // of a size_t. |
| - if (result < 0 || |
| - output_buffer_size < 0 || |
| - static_cast<size_t>(output_buffer_size) > kOutputBufferSize) { |
| - VLOG(1) << "Error decoding an audio frame with timestamp: " |
| - << input->GetTimestamp().InMicroseconds() << " us, duration: " |
| - << input->GetDuration().InMicroseconds() << " us, packet size: " |
| - << input->GetDataSize() << " bytes"; |
| - DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); |
| - return; |
| - } |
| + DLOG(ERROR) << "Error decoding an audio frame with timestamp: " |
| + << input->GetTimestamp().InMicroseconds() << " us, duration: " |
| + << input->GetDuration().InMicroseconds() << " us, packet size: " |
| + << input->GetDataSize() << " bytes"; |
| - // If we have decoded something, enqueue the result. |
| - if (output_buffer_size) { |
| - DataBuffer* result_buffer = new DataBuffer(output_buffer_size); |
| - result_buffer->SetDataSize(output_buffer_size); |
| - uint8* data = result_buffer->GetWritableData(); |
| - memcpy(data, output_buffer, output_buffer_size); |
| - |
| - // We don't trust the demuxer, so always calculate the duration based on |
| - // the actual number of samples decoded. |
| - base::TimeDelta duration = CalculateDuration(output_buffer_size); |
| - result_buffer->SetDuration(duration); |
| - |
| - // Use an estimated timestamp unless the incoming buffer has a valid one. |
| - if (input->GetTimestamp() == kNoTimestamp) { |
| - result_buffer->SetTimestamp(estimated_next_timestamp_); |
| - |
| - // Keep the estimated timestamp invalid until we get an incoming buffer |
| - // with a valid timestamp. This can happen during seeks, etc... |
| - if (estimated_next_timestamp_ != kNoTimestamp) { |
| - estimated_next_timestamp_ += duration; |
| - } |
| - } else { |
| - result_buffer->SetTimestamp(input->GetTimestamp()); |
| - estimated_next_timestamp_ = input->GetTimestamp() + duration; |
| - } |
| - |
| - EnqueueResult(result_buffer); |
| - DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); |
| + ReadFromDemuxerStream(); |
| return; |
| } |
| - // We can get a positive result but no decoded data. This is ok because this |
| - // this can be a marker packet that only contains timestamp. In this case we |
| - // save the timestamp for later use. |
| - if (result && !input->IsEndOfStream() && |
| - input->GetTimestamp() != kNoTimestamp && |
| - input->GetDuration() != kNoTimestamp) { |
| + scoped_refptr<DataBuffer> output; |
| + |
| + if (ProducedAudioSamples(decoded_audio_size)) { |
| + // Copy the audio samples into an output buffer. |
| + output = new DataBuffer(decoded_audio_size); |
| + output->SetDataSize(decoded_audio_size); |
| + uint8* data = output->GetWritableData(); |
| + memcpy(data, decoded_audio_, decoded_audio_size); |
| + |
| + UpdateDurationAndTimestamp(input, output); |
| + } else if (IsTimestampMarkerPacket(result, input)) { |
| + // Nothing else to do here but update our estimation. |
| estimated_next_timestamp_ = input->GetTimestamp() + input->GetDuration(); |
| - DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); |
| + } else if (IsEndOfStream(result, decoded_audio_size, input)) { |
| + // Create an end of stream output buffer. |
| + output = new DataBuffer(0); |
| + output->SetTimestamp(input->GetTimestamp()); |
| + output->SetDuration(input->GetDuration()); |
| + } |
| + |
| + // Decoding finished successfully, update stats and execute callback. |
| + stats_callback_->Run(statistics); |
| + if (output) { |
| + DCHECK_GT(output_buffers_.size(), 0u); |
| + output_buffers_.pop_front(); |
| + |
| + ConsumeAudioSamples(output); |
| + } else { |
| + ReadFromDemuxerStream(); |
| + } |
| +} |
| + |
| +void FFmpegAudioDecoder::ReadFromDemuxerStream() { |
| + DCHECK(!output_buffers_.empty()) |
| + << "Reads should only occur if there are output buffers."; |
| + |
| + pending_reads_++; |
| + demuxer_stream_->Read(base::Bind(&FFmpegAudioDecoder::DecodeBuffer, this)); |
| +} |
| + |
| +void FFmpegAudioDecoder::DecodeBuffer(Buffer* buffer) { |
| + // TODO(scherkus): change DemuxerStream::Read() to use scoped_refptr<> for |
| + // callback. |
| + scoped_refptr<Buffer> ref_buffer(buffer); |
| + message_loop_->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(this, &FFmpegAudioDecoder::DoDecodeBuffer, ref_buffer)); |
| +} |
| + |
| +void FFmpegAudioDecoder::UpdateDurationAndTimestamp( |
| + const Buffer* input, |
| + DataBuffer* output) { |
| + // Always calculate duration based on the actual number of samples decoded. |
| + base::TimeDelta duration = CalculateDuration(output->GetDataSize()); |
| + output->SetDuration(duration); |
| + |
| + // Use the incoming timestamp if it's valid. |
| + if (input->GetTimestamp() != kNoTimestamp) { |
| + output->SetTimestamp(input->GetTimestamp()); |
| + estimated_next_timestamp_ = input->GetTimestamp() + duration; |
| return; |
| } |
| - // Three conditions to meet to declare end of stream for this decoder: |
| - // 1. FFmpeg didn't read anything. |
| - // 2. FFmpeg didn't output anything. |
| - // 3. An end of stream buffer is received. |
| - if (result == 0 && output_buffer_size == 0 && input->IsEndOfStream()) { |
| - DataBuffer* result_buffer = new DataBuffer(0); |
| - result_buffer->SetTimestamp(input->GetTimestamp()); |
| - result_buffer->SetDuration(input->GetDuration()); |
| - EnqueueResult(result_buffer); |
| + // Otherwise use an estimated timestamp and attempt to update the estimation |
| + // as long as it's valid. |
| + output->SetTimestamp(estimated_next_timestamp_); |
| + if (estimated_next_timestamp_ != kNoTimestamp) { |
| + estimated_next_timestamp_ += duration; |
| } |
| - DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); |
| } |
| -base::TimeDelta FFmpegAudioDecoder::CalculateDuration(size_t size) { |
| - int64 denominator = codec_context_->channels * |
| - av_get_bits_per_sample_fmt(codec_context_->sample_fmt) / 8 * |
| - codec_context_->sample_rate; |
| +base::TimeDelta FFmpegAudioDecoder::CalculateDuration(int size) { |
| + int64 denominator = ChannelLayoutToChannelCount(config_.channel_layout) * |
| + config_.bits_per_channel / 8 * config_.sample_rate; |
| double microseconds = size / |
| (denominator / static_cast<double>(base::Time::kMicrosecondsPerSecond)); |
| return base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds)); |
| } |
| -} // namespace |
| +} // namespace media |