Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(248)

Unified Diff: media/filters/ffmpeg_audio_decoder.cc

Issue 141243003: Add AudioBufferStream. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@decoderstream_rebased
Patch Set: Rebase! Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/filters/ffmpeg_audio_decoder.h ('k') | media/filters/ffmpeg_audio_decoder_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/filters/ffmpeg_audio_decoder.cc
diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc
index 3ea70ff02407ac1df9d9b9b9ef5a39ae0cf85c07..5f1c37118b492b6d54c364d715b7864aad933563 100644
--- a/media/filters/ffmpeg_audio_decoder.cc
+++ b/media/filters/ffmpeg_audio_decoder.cc
@@ -23,12 +23,6 @@
namespace media {
-// Helper structure for managing multiple decoded audio frames per packet.
-struct QueuedAudioBuffer {
- AudioDecoder::Status status;
- scoped_refptr<AudioBuffer> buffer;
-};
-
// Returns true if the decode result was end of stream.
static inline bool IsEndOfStream(int result,
int decoded_size,
@@ -50,18 +44,6 @@ static inline int DetermineChannels(AVFrame* frame) {
#endif
}
-// Called by FFmpeg's allocation routine to allocate a buffer. Uses
-// AVCodecContext.opaque to get the object reference in order to call
-// GetAudioBuffer() to do the actual allocation.
-static int GetAudioBufferImpl(struct AVCodecContext* s,
- AVFrame* frame,
- int flags) {
- DCHECK(s->codec->capabilities & CODEC_CAP_DR1);
- DCHECK_EQ(s->codec_type, AVMEDIA_TYPE_AUDIO);
- FFmpegAudioDecoder* decoder = static_cast<FFmpegAudioDecoder*>(s->opaque);
- return decoder->GetAudioBuffer(s, frame, flags);
-}
-
// Called by FFmpeg's allocation routine to free a buffer. |opaque| is the
// AudioBuffer allocated, so unref it.
static void ReleaseAudioBufferImpl(void* opaque, uint8* data) {
@@ -69,119 +51,16 @@ static void ReleaseAudioBufferImpl(void* opaque, uint8* data) {
buffer.swap(reinterpret_cast<AudioBuffer**>(&opaque));
}
-FFmpegAudioDecoder::FFmpegAudioDecoder(
- const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
- : task_runner_(task_runner),
- weak_factory_(this),
- demuxer_stream_(NULL),
- bytes_per_channel_(0),
- channel_layout_(CHANNEL_LAYOUT_NONE),
- channels_(0),
- samples_per_second_(0),
- av_sample_format_(0),
- last_input_timestamp_(kNoTimestamp()),
- output_frames_to_drop_(0) {
-}
-
-void FFmpegAudioDecoder::Initialize(
- DemuxerStream* stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- DCHECK(task_runner_->BelongsToCurrentThread());
- PipelineStatusCB initialize_cb = BindToCurrentLoop(status_cb);
-
- FFmpegGlue::InitializeFFmpeg();
-
- if (demuxer_stream_) {
- // TODO(scherkus): initialization currently happens more than once in
- // PipelineIntegrationTest.BasicPlayback.
- LOG(ERROR) << "Initialize has already been called.";
- CHECK(false);
- }
-
- weak_this_ = weak_factory_.GetWeakPtr();
- demuxer_stream_ = stream;
-
- if (!ConfigureDecoder()) {
- status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- statistics_cb_ = statistics_cb;
- initialize_cb.Run(PIPELINE_OK);
-}
-
-void FFmpegAudioDecoder::Read(const ReadCB& read_cb) {
- DCHECK(task_runner_->BelongsToCurrentThread());
- DCHECK(!read_cb.is_null());
- CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
- DCHECK(reset_cb_.is_null());
- DCHECK(stop_cb_.is_null());
-
- read_cb_ = BindToCurrentLoop(read_cb);
-
- // If we don't have any queued audio from the last packet we decoded, ask for
- // more data from the demuxer to satisfy this read.
- if (queued_audio_.empty()) {
- ReadFromDemuxerStream();
- return;
- }
-
- base::ResetAndReturn(&read_cb_).Run(
- queued_audio_.front().status, queued_audio_.front().buffer);
- queued_audio_.pop_front();
-}
-
-int FFmpegAudioDecoder::bits_per_channel() {
- DCHECK(task_runner_->BelongsToCurrentThread());
- return bytes_per_channel_ * 8;
-}
-
-ChannelLayout FFmpegAudioDecoder::channel_layout() {
- DCHECK(task_runner_->BelongsToCurrentThread());
- return channel_layout_;
-}
-
-int FFmpegAudioDecoder::samples_per_second() {
- DCHECK(task_runner_->BelongsToCurrentThread());
- return samples_per_second_;
-}
-
-void FFmpegAudioDecoder::Reset(const base::Closure& closure) {
- DCHECK(task_runner_->BelongsToCurrentThread());
- reset_cb_ = BindToCurrentLoop(closure);
-
- // A demuxer read is pending, we'll wait until it finishes.
- if (!read_cb_.is_null())
- return;
-
- DoReset();
-}
-
-void FFmpegAudioDecoder::Stop(const base::Closure& closure) {
- DCHECK(task_runner_->BelongsToCurrentThread());
- stop_cb_ = BindToCurrentLoop(closure);
-
- // A demuxer read is pending, we'll wait until it finishes.
- if (!read_cb_.is_null())
- return;
-
- if (!reset_cb_.is_null()) {
- DoReset();
- return;
- }
-
- DoStop();
-}
-
-FFmpegAudioDecoder::~FFmpegAudioDecoder() {}
+// Called by FFmpeg's allocation routine to allocate a buffer. Uses
+// AVCodecContext.opaque to get the object reference in order to call
+// GetAudioBuffer() to do the actual allocation.
+static int GetAudioBuffer(struct AVCodecContext* s, AVFrame* frame, int flags) {
+ DCHECK(s->codec->capabilities & CODEC_CAP_DR1);
+ DCHECK_EQ(s->codec_type, AVMEDIA_TYPE_AUDIO);
-int FFmpegAudioDecoder::GetAudioBuffer(AVCodecContext* codec,
- AVFrame* frame,
- int flags) {
// Since this routine is called by FFmpeg when a buffer is required for audio
// data, use the values supplied by FFmpeg (ignoring the current settings).
- // RunDecodeLoop() gets to determine if the buffer is useable or not.
+ // FFmpegDecode() gets to determine if the buffer is useable or not.
AVSampleFormat format = static_cast<AVSampleFormat>(frame->format);
SampleFormat sample_format = AVSampleFormatToSampleFormat(format);
int channels = DetermineChannels(frame);
@@ -241,241 +120,227 @@ int FFmpegAudioDecoder::GetAudioBuffer(AVCodecContext* codec,
return 0;
}
-void FFmpegAudioDecoder::DoStop() {
- DCHECK(task_runner_->BelongsToCurrentThread());
- DCHECK(!stop_cb_.is_null());
- DCHECK(read_cb_.is_null());
- DCHECK(reset_cb_.is_null());
+FFmpegAudioDecoder::FFmpegAudioDecoder(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
+ : task_runner_(task_runner),
+ weak_factory_(this),
+ state_(kUninitialized),
+ bytes_per_channel_(0),
+ channel_layout_(CHANNEL_LAYOUT_NONE),
+ channels_(0),
+ samples_per_second_(0),
+ av_sample_format_(0),
+ last_input_timestamp_(kNoTimestamp()),
+ output_frames_to_drop_(0) {}
- ResetTimestampState();
- queued_audio_.clear();
- ReleaseFFmpegResources();
- base::ResetAndReturn(&stop_cb_).Run();
+FFmpegAudioDecoder::~FFmpegAudioDecoder() {
+ DCHECK_EQ(state_, kUninitialized);
+ DCHECK(!codec_context_);
+ DCHECK(!av_frame_);
}
-void FFmpegAudioDecoder::DoReset() {
+void FFmpegAudioDecoder::Initialize(const AudioDecoderConfig& config,
+ const PipelineStatusCB& status_cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
- DCHECK(!reset_cb_.is_null());
- DCHECK(read_cb_.is_null());
+ DCHECK(!config.is_encrypted());
- avcodec_flush_buffers(codec_context_.get());
- ResetTimestampState();
- queued_audio_.clear();
- base::ResetAndReturn(&reset_cb_).Run();
+ FFmpegGlue::InitializeFFmpeg();
+ weak_this_ = weak_factory_.GetWeakPtr();
- if (!stop_cb_.is_null())
- DoStop();
-}
+ config_ = config;
+ PipelineStatusCB initialize_cb = BindToCurrentLoop(status_cb);
-void FFmpegAudioDecoder::ReadFromDemuxerStream() {
- DCHECK(!read_cb_.is_null());
- demuxer_stream_->Read(base::Bind(
- &FFmpegAudioDecoder::BufferReady, weak_this_));
+ if (!config.IsValidConfig() || !ConfigureDecoder()) {
+ initialize_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
+ return;
+ }
+
+ // Success!
+ state_ = kNormal;
+ initialize_cb.Run(PIPELINE_OK);
}
-void FFmpegAudioDecoder::BufferReady(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& input) {
+void FFmpegAudioDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
+ const DecodeCB& decode_cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
- DCHECK(!read_cb_.is_null());
- DCHECK(queued_audio_.empty());
- DCHECK_EQ(status != DemuxerStream::kOk, !input.get()) << status;
-
- // Pending Reset: ignore the buffer we just got, send kAborted to |read_cb_|
- // and carry out the Reset().
- // If there happens to also be a pending Stop(), that will be handled at
- // the end of DoReset().
- if (!reset_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- DoReset();
+ DCHECK(!decode_cb.is_null());
+ CHECK_NE(state_, kUninitialized);
+ DecodeCB decode_cb_bound = BindToCurrentLoop(decode_cb);
+
+ if (state_ == kError) {
+ decode_cb_bound.Run(kDecodeError, NULL);
return;
}
- // Pending Stop: ignore the buffer we just got, send kAborted to |read_cb_|
- // and carry out the Stop().
- if (!stop_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- DoStop();
+ // Return empty frames if decoding has finished.
+ if (state_ == kDecodeFinished) {
+ decode_cb_bound.Run(kOk, AudioBuffer::CreateEOSBuffer());
return;
}
- if (status == DemuxerStream::kAborted) {
- DCHECK(!input.get());
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
+ if (!buffer) {
+ decode_cb_bound.Run(kAborted, NULL);
return;
}
- if (status == DemuxerStream::kConfigChanged) {
- DCHECK(!input.get());
+ DecodeBuffer(buffer, decode_cb_bound);
+}
- // Send a "end of stream" buffer to the decode loop
- // to output any remaining data still in the decoder.
- RunDecodeLoop(DecoderBuffer::CreateEOSBuffer(), true);
+scoped_refptr<AudioBuffer> FFmpegAudioDecoder::GetDecodeOutput() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ if (queued_audio_.empty())
+ return NULL;
+ scoped_refptr<AudioBuffer> out = queued_audio_.front();
+ queued_audio_.pop_front();
+ return out;
+}
- DVLOG(1) << "Config changed.";
+int FFmpegAudioDecoder::bits_per_channel() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ return bytes_per_channel_ * 8;
+}
- if (!ConfigureDecoder()) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
+ChannelLayout FFmpegAudioDecoder::channel_layout() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ return channel_layout_;
+}
- ResetTimestampState();
+int FFmpegAudioDecoder::samples_per_second() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ return samples_per_second_;
+}
- if (queued_audio_.empty()) {
- ReadFromDemuxerStream();
- return;
- }
+void FFmpegAudioDecoder::Reset(const base::Closure& closure) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ avcodec_flush_buffers(codec_context_.get());
+ state_ = kNormal;
+ ResetTimestampState();
+ task_runner_->PostTask(FROM_HERE, closure);
+}
- base::ResetAndReturn(&read_cb_).Run(
- queued_audio_.front().status, queued_audio_.front().buffer);
- queued_audio_.pop_front();
+void FFmpegAudioDecoder::Stop(const base::Closure& closure) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ base::ScopedClosureRunner runner(BindToCurrentLoop(closure));
+
+ if (state_ == kUninitialized)
return;
- }
- DCHECK_EQ(status, DemuxerStream::kOk);
- DCHECK(input.get());
+ ReleaseFFmpegResources();
+ ResetTimestampState();
+ state_ = kUninitialized;
+}
+
+void FFmpegAudioDecoder::DecodeBuffer(
+ const scoped_refptr<DecoderBuffer>& buffer,
+ const DecodeCB& decode_cb) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK_NE(state_, kUninitialized);
+ DCHECK_NE(state_, kDecodeFinished);
+ DCHECK_NE(state_, kError);
+
+ DCHECK(buffer);
+
+ // During decode, because reads are issued asynchronously, it is possible to
+ // receive multiple end of stream buffers since each decode is acked. When the
+ // first end of stream buffer is read, FFmpeg may still have frames queued
+ // up in the decoder so we need to go through the decode loop until it stops
+ // giving sensible data. After that, the decoder should output empty
+ // frames. There are three states the decoder can be in:
+ //
+ // kNormal: This is the starting state. Buffers are decoded. Decode errors
+ // are discarded.
+ // kFlushCodec: There isn't any more input data. Call avcodec_decode_audio4
+ // until no more data is returned to flush out remaining
+ // frames. The input buffer is ignored at this point.
+ // kDecodeFinished: All calls return empty frames.
+ // kError: Unexpected error happened.
+ //
+ // These are the possible state transitions.
+ //
+ // kNormal -> kFlushCodec:
+ // When buffer->end_of_stream() is first true.
+ // kNormal -> kError:
+ // A decoding error occurs and decoding needs to stop.
+ // kFlushCodec -> kDecodeFinished:
+ // When avcodec_decode_audio4() returns 0 data.
+ // kFlushCodec -> kError:
+ // When avcodec_decode_audio4() errors out.
+ // (any state) -> kNormal:
+ // Any time Reset() is called.
// Make sure we are notified if http://crbug.com/49709 returns. Issue also
// occurs with some damaged files.
- if (!input->end_of_stream() && input->timestamp() == kNoTimestamp() &&
+ if (!buffer->end_of_stream() && buffer->timestamp() == kNoTimestamp() &&
output_timestamp_helper_->base_timestamp() == kNoTimestamp()) {
DVLOG(1) << "Received a buffer without timestamps!";
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
+ decode_cb.Run(kDecodeError, NULL);
return;
}
- if (!input->end_of_stream()) {
+ if (!buffer->end_of_stream()) {
if (last_input_timestamp_ == kNoTimestamp() &&
codec_context_->codec_id == AV_CODEC_ID_VORBIS &&
- input->timestamp() < base::TimeDelta()) {
+ buffer->timestamp() < base::TimeDelta()) {
// Dropping frames for negative timestamps as outlined in section A.2
// in the Vorbis spec. http://xiph.org/vorbis/doc/Vorbis_I_spec.html
output_frames_to_drop_ = floor(
- 0.5 + -input->timestamp().InSecondsF() * samples_per_second_);
+ 0.5 + -buffer->timestamp().InSecondsF() * samples_per_second_);
} else {
if (last_input_timestamp_ != kNoTimestamp() &&
- input->timestamp() < last_input_timestamp_) {
- const base::TimeDelta diff = input->timestamp() - last_input_timestamp_;
+ buffer->timestamp() < last_input_timestamp_) {
+ const base::TimeDelta diff =
+ buffer->timestamp() - last_input_timestamp_;
DLOG(WARNING)
<< "Input timestamps are not monotonically increasing! "
- << " ts " << input->timestamp().InMicroseconds() << " us"
+ << " ts " << buffer->timestamp().InMicroseconds() << " us"
<< " diff " << diff.InMicroseconds() << " us";
}
- last_input_timestamp_ = input->timestamp();
+ last_input_timestamp_ = buffer->timestamp();
}
}
- RunDecodeLoop(input, false);
-
- // We exhausted the provided packet, but it wasn't enough for a frame. Ask
- // for more data in order to fulfill this read.
- if (queued_audio_.empty()) {
- ReadFromDemuxerStream();
- return;
- }
-
- // Execute callback to return the first frame we decoded.
- base::ResetAndReturn(&read_cb_).Run(
- queued_audio_.front().status, queued_audio_.front().buffer);
- queued_audio_.pop_front();
-}
-
-bool FFmpegAudioDecoder::ConfigureDecoder() {
- const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config();
-
- 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();
- return false;
- }
-
- if (config.is_encrypted()) {
- DLOG(ERROR) << "Encrypted audio stream not supported";
- return false;
- }
-
- if (codec_context_.get() &&
- (bytes_per_channel_ != config.bytes_per_channel() ||
- channel_layout_ != config.channel_layout() ||
- samples_per_second_ != config.samples_per_second())) {
- DVLOG(1) << "Unsupported config change :";
- DVLOG(1) << "\tbytes_per_channel : " << bytes_per_channel_
- << " -> " << config.bytes_per_channel();
- DVLOG(1) << "\tchannel_layout : " << channel_layout_
- << " -> " << config.channel_layout();
- DVLOG(1) << "\tsample_rate : " << samples_per_second_
- << " -> " << config.samples_per_second();
- return false;
+ // Transition to kFlushCodec on the first end of stream buffer.
+ if (state_ == kNormal && buffer->end_of_stream()) {
+ state_ = kFlushCodec;
}
- // Release existing decoder resources if necessary.
- ReleaseFFmpegResources();
-
- // Initialize AVCodecContext structure.
- codec_context_.reset(avcodec_alloc_context3(NULL));
- AudioDecoderConfigToAVCodecContext(config, codec_context_.get());
-
- codec_context_->opaque = this;
- codec_context_->get_buffer2 = GetAudioBufferImpl;
- codec_context_->refcounted_frames = 1;
-
- AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
- if (!codec || avcodec_open2(codec_context_.get(), codec, NULL) < 0) {
- DLOG(ERROR) << "Could not initialize audio decoder: "
- << codec_context_->codec_id;
- return false;
+ if (!FFmpegDecode(buffer)) {
+ state_ = kError;
+ decode_cb.Run(kDecodeError, NULL);
+ return;
}
- // Success!
- av_frame_.reset(av_frame_alloc());
- channel_layout_ = config.channel_layout();
- samples_per_second_ = config.samples_per_second();
- output_timestamp_helper_.reset(
- new AudioTimestampHelper(config.samples_per_second()));
+ if (queued_audio_.empty()) {
+ if (state_ == kFlushCodec) {
+ DCHECK(buffer->end_of_stream());
+ state_ = kDecodeFinished;
+ decode_cb.Run(kOk, AudioBuffer::CreateEOSBuffer());
+ return;
+ }
- // Store initial values to guard against midstream configuration changes.
- channels_ = codec_context_->channels;
- if (channels_ != ChannelLayoutToChannelCount(channel_layout_)) {
- DLOG(ERROR) << "Audio configuration specified "
- << ChannelLayoutToChannelCount(channel_layout_)
- << " channels, but FFmpeg thinks the file contains "
- << channels_ << " channels";
- return false;
+ decode_cb.Run(kNotEnoughData, NULL);
+ return;
}
- av_sample_format_ = codec_context_->sample_fmt;
- sample_format_ = AVSampleFormatToSampleFormat(
- static_cast<AVSampleFormat>(av_sample_format_));
- bytes_per_channel_ = SampleFormatToBytesPerChannel(sample_format_);
- return true;
+ decode_cb.Run(kOk, queued_audio_.front());
+ queued_audio_.pop_front();
}
-void FFmpegAudioDecoder::ReleaseFFmpegResources() {
- codec_context_.reset();
- av_frame_.reset();
-}
+bool FFmpegAudioDecoder::FFmpegDecode(
+ const scoped_refptr<DecoderBuffer>& buffer) {
-void FFmpegAudioDecoder::ResetTimestampState() {
- output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp());
- last_input_timestamp_ = kNoTimestamp();
- output_frames_to_drop_ = 0;
-}
+ DCHECK(queued_audio_.empty());
-void FFmpegAudioDecoder::RunDecodeLoop(
- const scoped_refptr<DecoderBuffer>& input,
- bool skip_eos_append) {
AVPacket packet;
av_init_packet(&packet);
- if (input->end_of_stream()) {
+ if (buffer->end_of_stream()) {
packet.data = NULL;
packet.size = 0;
} else {
- packet.data = const_cast<uint8*>(input->data());
- packet.size = input->data_size();
+ packet.data = const_cast<uint8*>(buffer->data());
+ packet.size = buffer->data_size();
}
// Each audio packet may contain several frames, so we must call the decoder
@@ -488,16 +353,16 @@ void FFmpegAudioDecoder::RunDecodeLoop(
codec_context_.get(), av_frame_.get(), &frame_decoded, &packet);
if (result < 0) {
- DCHECK(!input->end_of_stream())
+ DCHECK(!buffer->end_of_stream())
<< "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(WARNING)
<< "Failed to decode an audio frame with timestamp: "
- << input->timestamp().InMicroseconds() << " us, duration: "
- << input->duration().InMicroseconds() << " us, packet size: "
- << input->data_size() << " bytes";
+ << buffer->timestamp().InMicroseconds() << " us, duration: "
+ << buffer->duration().InMicroseconds() << " us, packet size: "
+ << buffer->data_size() << " bytes";
break;
}
@@ -508,15 +373,15 @@ void FFmpegAudioDecoder::RunDecodeLoop(
packet.data += result;
if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() &&
- !input->end_of_stream()) {
- DCHECK(input->timestamp() != kNoTimestamp());
+ !buffer->end_of_stream()) {
+ DCHECK(buffer->timestamp() != kNoTimestamp());
if (output_frames_to_drop_ > 0) {
// Currently Vorbis is the only codec that causes us to drop samples.
// If we have to drop samples it always means the timeline starts at 0.
DCHECK_EQ(codec_context_->codec_id, AV_CODEC_ID_VORBIS);
output_timestamp_helper_->SetBaseTimestamp(base::TimeDelta());
} else {
- output_timestamp_helper_->SetBaseTimestamp(input->timestamp());
+ output_timestamp_helper_->SetBaseTimestamp(buffer->timestamp());
}
}
@@ -525,7 +390,10 @@ void FFmpegAudioDecoder::RunDecodeLoop(
int original_frames = 0;
int channels = DetermineChannels(av_frame_.get());
if (frame_decoded) {
- if (av_frame_->sample_rate != samples_per_second_ ||
+
+ // TODO(rileya) Remove this check once we properly support midstream audio
+ // config changes.
+ if (av_frame_->sample_rate != config_.samples_per_second() ||
channels != channels_ ||
av_frame_->format != av_sample_format_) {
DLOG(ERROR) << "Unsupported midstream configuration change!"
@@ -537,16 +405,16 @@ void FFmpegAudioDecoder::RunDecodeLoop(
<< av_sample_format_;
// This is an unrecoverable error, so bail out.
- QueuedAudioBuffer queue_entry = { kDecodeError, NULL };
- queued_audio_.push_back(queue_entry);
+ queued_audio_.clear();
av_frame_unref(av_frame_.get());
- break;
+ return false;
}
// Get the AudioBuffer that the data was decoded into. Adjust the number
// of frames, in case fewer than requested were actually decoded.
output = reinterpret_cast<AudioBuffer*>(
av_buffer_get_opaque(av_frame_->buf[0]));
+
DCHECK_EQ(channels_, output->channel_count());
original_frames = av_frame_->nb_samples;
int unread_frames = output->frame_count() - original_frames;
@@ -574,8 +442,7 @@ void FFmpegAudioDecoder::RunDecodeLoop(
output->set_duration(
output_timestamp_helper_->GetFrameDuration(decoded_frames));
output_timestamp_helper_->AddFrames(decoded_frames);
- } else if (IsEndOfStream(result, original_frames, input) &&
- !skip_eos_append) {
+ } else if (IsEndOfStream(result, original_frames, buffer)) {
DCHECK_EQ(packet.size, 0);
output = AudioBuffer::CreateEOSBuffer();
} else {
@@ -583,18 +450,100 @@ void FFmpegAudioDecoder::RunDecodeLoop(
output = NULL;
}
- if (output.get()) {
- QueuedAudioBuffer queue_entry = { kOk, output };
- queued_audio_.push_back(queue_entry);
- }
+ if (output.get())
+ queued_audio_.push_back(output);
- // Decoding finished successfully, update statistics.
- if (result > 0) {
- PipelineStatistics statistics;
- statistics.audio_bytes_decoded = result;
- statistics_cb_.Run(statistics);
- }
} while (packet.size > 0);
+
+ return true;
+}
+
+void FFmpegAudioDecoder::ReleaseFFmpegResources() {
+ codec_context_.reset();
+ av_frame_.reset();
+}
+
+bool FFmpegAudioDecoder::ConfigureDecoder() {
+ 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();
+ return false;
+ }
+
+ if (config_.is_encrypted()) {
+ DLOG(ERROR) << "Encrypted audio stream not supported";
+ return false;
+ }
+
+ // TODO(rileya) Remove this check once we properly support midstream audio
+ // config changes.
+ if (codec_context_.get() &&
+ (bytes_per_channel_ != config_.bytes_per_channel() ||
+ channel_layout_ != config_.channel_layout() ||
+ samples_per_second_ != config_.samples_per_second())) {
+ DVLOG(1) << "Unsupported config change :";
+ DVLOG(1) << "\tbytes_per_channel : " << bytes_per_channel_
+ << " -> " << config_.bytes_per_channel();
+ DVLOG(1) << "\tchannel_layout : " << channel_layout_
+ << " -> " << config_.channel_layout();
+ DVLOG(1) << "\tsample_rate : " << samples_per_second_
+ << " -> " << config_.samples_per_second();
+ return false;
+ }
+
+ // Release existing decoder resources if necessary.
+ ReleaseFFmpegResources();
+
+ // Initialize AVCodecContext structure.
+ codec_context_.reset(avcodec_alloc_context3(NULL));
+ AudioDecoderConfigToAVCodecContext(config_, codec_context_.get());
+
+ codec_context_->opaque = this;
+ codec_context_->get_buffer2 = GetAudioBuffer;
+ codec_context_->refcounted_frames = 1;
+
+ AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
+ if (!codec || avcodec_open2(codec_context_.get(), codec, NULL) < 0) {
+ DLOG(ERROR) << "Could not initialize audio decoder: "
+ << codec_context_->codec_id;
+ ReleaseFFmpegResources();
+ state_ = kUninitialized;
+ return false;
+ }
+
+ // Success!
+ av_frame_.reset(av_frame_alloc());
+ channel_layout_ = config_.channel_layout();
+ samples_per_second_ = config_.samples_per_second();
+ output_timestamp_helper_.reset(
+ new AudioTimestampHelper(config_.samples_per_second()));
+
+ // Store initial values to guard against midstream configuration changes.
+ channels_ = codec_context_->channels;
+ if (channels_ != ChannelLayoutToChannelCount(channel_layout_)) {
+ DLOG(ERROR) << "Audio configuration specified "
+ << ChannelLayoutToChannelCount(channel_layout_)
+ << " channels, but FFmpeg thinks the file contains "
+ << channels_ << " channels";
+ ReleaseFFmpegResources();
+ state_ = kUninitialized;
+ return false;
+ }
+ av_sample_format_ = codec_context_->sample_fmt;
+ sample_format_ = AVSampleFormatToSampleFormat(
+ static_cast<AVSampleFormat>(av_sample_format_));
+ bytes_per_channel_ = SampleFormatToBytesPerChannel(sample_format_);
+
+ return true;
+}
+
+void FFmpegAudioDecoder::ResetTimestampState() {
+ output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp());
+ last_input_timestamp_ = kNoTimestamp();
+ output_frames_to_drop_ = 0;
}
} // namespace media
« no previous file with comments | « media/filters/ffmpeg_audio_decoder.h ('k') | media/filters/ffmpeg_audio_decoder_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698