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 |