blob: dca4bfab31bf3c4cda2d1acf2f2633d870ae88c4 [file] [log] [blame]
// 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/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/ffmpeg/ffmpeg_common.h"
namespace media {
// 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)
: message_loop_(message_loop),
codec_context_(NULL),
bits_per_channel_(0),
channel_layout_(CHANNEL_LAYOUT_NONE),
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) {
}
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(const base::Closure& callback) {
message_loop_->PostTask(
FROM_HERE,
base::Bind(&FFmpegAudioDecoder::DoFlush, this, callback));
}
void FFmpegAudioDecoder::Initialize(
DemuxerStream* stream,
const base::Closure& callback,
const 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,
base::Bind(&FFmpegAudioDecoder::DoInitialize, this,
ref_stream, callback, stats_callback));
}
void FFmpegAudioDecoder::ProduceAudioSamples(scoped_refptr<Buffer> buffer) {
message_loop_->PostTask(
FROM_HERE,
base::Bind(&FFmpegAudioDecoder::DoProduceAudioSamples, this,
buffer));
}
int FFmpegAudioDecoder::bits_per_channel() {
return bits_per_channel_;
}
ChannelLayout FFmpegAudioDecoder::channel_layout() {
return channel_layout_;
}
int FFmpegAudioDecoder::samples_per_second() {
return samples_per_second_;
}
void FFmpegAudioDecoder::DoInitialize(
const scoped_refptr<DemuxerStream>& stream,
const base::Closure& callback,
const StatisticsCallback& stats_callback) {
demuxer_stream_ = stream;
const AudioDecoderConfig& config = stream->audio_decoder_config();
stats_callback_ = stats_callback;
// TODO(scherkus): this check should go in PipelineImpl prior to creating
// decoder objects.
if (!config.IsValidConfig()) {
DLOG(ERROR) << "Invalid audio stream -"
<< " codec: " << config.codec()
<< " channel layout: " << config.channel_layout()
<< " bits per channel: " << config.bits_per_channel()
<< " samples per second: " << config.samples_per_second();
host()->SetError(DECODER_ERROR_NOT_SUPPORTED);
callback.Run();
return;
}
// 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(DECODER_ERROR_NOT_SUPPORTED);
callback.Run();
return;
}
// Success!
bits_per_channel_ = config.bits_per_channel();
channel_layout_ = config.channel_layout();
samples_per_second_ = config.samples_per_second();
callback.Run();
}
void FFmpegAudioDecoder::DoFlush(const base::Closure& callback) {
avcodec_flush_buffers(codec_context_);
estimated_next_timestamp_ = kNoTimestamp;
callback.Run();
}
void FFmpegAudioDecoder::DoProduceAudioSamples(
const scoped_refptr<Buffer>& output) {
output_buffers_.push_back(output);
ReadFromDemuxerStream();
}
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
if (input->GetTimestamp() == kNoTimestamp &&
estimated_next_timestamp_ == kNoTimestamp &&
!input->IsEndOfStream()) {
ReadFromDemuxerStream();
return;
}
AVPacket packet;
av_init_packet(&packet);
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();
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 (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.";
DLOG(ERROR) << "Error decoding an audio frame with timestamp: "
<< input->GetTimestamp().InMicroseconds() << " us, duration: "
<< input->GetDuration().InMicroseconds() << " us, packet size: "
<< input->GetDataSize() << " bytes";
ReadFromDemuxerStream();
return;
}
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();
} 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,
base::Bind(&FFmpegAudioDecoder::DoDecodeBuffer, this, 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;
}
// 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;
}
}
base::TimeDelta FFmpegAudioDecoder::CalculateDuration(int size) {
int64 denominator = ChannelLayoutToChannelCount(channel_layout_) *
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));
}
} // namespace media