| Index: media/filters/ffmpeg_audio_decoder.cc
|
| diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc
|
| index 50335d3829b77adf5d1cf852bb76fedbbae9d0e5..cb3f846078329514071f4791569be5aa59b7b128 100644
|
| --- a/media/filters/ffmpeg_audio_decoder.cc
|
| +++ b/media/filters/ffmpeg_audio_decoder.cc
|
| @@ -4,50 +4,111 @@
|
|
|
| #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),
|
| bits_per_channel_(0),
|
| channel_layout_(CHANNEL_LAYOUT_NONE),
|
| sample_rate_(0),
|
| - 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<>.
|
| + scoped_refptr<DemuxerStream> ref_stream(stream);
|
| + message_loop_->PostTask(
|
| + FROM_HERE,
|
| + NewRunnableMethod(this, &FFmpegAudioDecoder::DoInitialize,
|
| + ref_stream, callback, stats_callback));
|
| +}
|
| +
|
| +void FFmpegAudioDecoder::ProduceAudioSamples(scoped_refptr<Buffer> buffer) {
|
| + message_loop_->PostTask(
|
| + FROM_HERE,
|
| + NewRunnableMethod(this, &FFmpegAudioDecoder::DoProduceAudioSamples,
|
| + buffer));
|
| +}
|
| +
|
| +int FFmpegAudioDecoder::bits_per_channel() {
|
| + return bits_per_channel_;
|
| +}
|
| +
|
| +ChannelLayout FFmpegAudioDecoder::channel_layout() {
|
| + return channel_layout_;
|
| +}
|
| +
|
| +int FFmpegAudioDecoder::sample_rate() {
|
| + return sample_rate_;
|
| +}
|
| +
|
| +void FFmpegAudioDecoder::DoInitialize(
|
| + const scoped_refptr<DemuxerStream>& stream,
|
| + 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;
|
| @@ -58,213 +119,177 @@ 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!
|
| 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;
|
|
|
| - // 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;
|
| -}
|
| -
|
| -void FFmpegAudioDecoder::ProduceAudioSamples(scoped_refptr<Buffer> output) {
|
| - DecoderBase<AudioDecoder, Buffer>::PostReadTaskHack(output);
|
| + callback->Run();
|
| }
|
|
|
| -int FFmpegAudioDecoder::bits_per_channel() {
|
| - return bits_per_channel_;
|
| -}
|
| -
|
| -ChannelLayout FFmpegAudioDecoder::channel_layout() {
|
| - return channel_layout_;
|
| -}
|
| -
|
| -int FFmpegAudioDecoder::sample_rate() {
|
| - return sample_rate_;
|
| -}
|
| -
|
| -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);
|
| + callback->Run();
|
| + delete callback;
|
| }
|
| -#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;
|
| -}
|
| -#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(
|
| + const scoped_refptr<Buffer>& output) {
|
| + 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(channel_layout_) *
|
| + bits_per_channel_ / 8 * sample_rate_;
|
| double microseconds = size /
|
| (denominator / static_cast<double>(base::Time::kMicrosecondsPerSecond));
|
| return base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds));
|
| }
|
|
|
| -} // namespace
|
| +} // namespace media
|
|
|