Index: media/filters/ffmpeg_audio_decoder.cc |
diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc |
index fb7b4a7609f704d9af3356da9d1c7e18505ae3c0..f8cc1d57cefba5f558001e7d0442852c46b6d0da 100644 |
--- a/media/filters/ffmpeg_audio_decoder.cc |
+++ b/media/filters/ffmpeg_audio_decoder.cc |
@@ -16,16 +16,6 @@ |
namespace media { |
-// Returns true if the decode result was a timestamp packet and not actual audio |
-// data. |
-static inline 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 inline bool IsEndOfStream(int result, int decoded_size, Buffer* input) { |
// Three conditions to meet to declare end of stream for this decoder: |
@@ -35,7 +25,6 @@ static inline bool IsEndOfStream(int result, int decoded_size, Buffer* input) { |
return result == 0 && decoded_size == 0 && input->IsEndOfStream(); |
} |
- |
FFmpegAudioDecoder::FFmpegAudioDecoder( |
const base::Callback<MessageLoop*()>& message_loop_cb) |
: message_loop_factory_cb_(message_loop_cb), |
@@ -44,6 +33,11 @@ FFmpegAudioDecoder::FFmpegAudioDecoder( |
bits_per_channel_(0), |
channel_layout_(CHANNEL_LAYOUT_NONE), |
samples_per_second_(0), |
+ bytes_per_frame_(0), |
+ output_timestamp_base_(kNoTimestamp()), |
+ total_frames_decoded_(0), |
+ last_input_timestamp_(kNoTimestamp()), |
+ output_bytes_to_drop_(0), |
av_frame_(NULL) { |
} |
@@ -146,13 +140,16 @@ void FFmpegAudioDecoder::DoInitialize( |
bits_per_channel_ = config.bits_per_channel(); |
channel_layout_ = config.channel_layout(); |
samples_per_second_ = config.samples_per_second(); |
- |
+ bytes_per_frame_ = codec_context_->channels * bits_per_channel_ / 8; |
status_cb.Run(PIPELINE_OK); |
} |
void FFmpegAudioDecoder::DoReset(const base::Closure& closure) { |
avcodec_flush_buffers(codec_context_); |
- estimated_next_timestamp_ = kNoTimestamp(); |
+ output_timestamp_base_ = kNoTimestamp(); |
+ total_frames_decoded_ = 0; |
+ last_input_timestamp_ = kNoTimestamp(); |
+ output_bytes_to_drop_ = 0; |
closure.Run(); |
} |
@@ -181,14 +178,32 @@ void FFmpegAudioDecoder::DoDecodeBuffer( |
return; |
} |
- // 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; |
+ // Make sure we are notified if http://crbug.com/49709 returns. |
+ CHECK(input->GetTimestamp() != kNoTimestamp() || |
+ output_timestamp_base_ != kNoTimestamp() || |
+ input->IsEndOfStream()) |
+ << "First buffers received don't have timestamps!"; |
+ |
+ bool is_vorbis = codec_context_->codec_id == CODEC_ID_VORBIS; |
+ if (!input->IsEndOfStream()) { |
+ if (last_input_timestamp_ == kNoTimestamp()) { |
+ if (is_vorbis && (input->GetTimestamp() < 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 |
+ int frames_to_drop = floor( |
+ 0.5 + -input->GetTimestamp().InSecondsF() * samples_per_second_); |
+ output_bytes_to_drop_ = bytes_per_frame_ * frames_to_drop; |
+ } else { |
+ last_input_timestamp_ = input->GetTimestamp(); |
+ } |
+ } else if (input->GetTimestamp() < last_input_timestamp_) { |
+ base::TimeDelta diff = input->GetTimestamp() - last_input_timestamp_; |
+ DVLOG(1) << "Input timestamps are not monotonically increasing! " |
+ << " ts " << input->GetTimestamp().InMicroseconds() << " us" |
+ << " diff " << diff.InMicroseconds() << " us"; |
+ base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); |
+ return; |
+ } |
} |
AVPacket packet; |
@@ -221,6 +236,22 @@ void FFmpegAudioDecoder::DoDecodeBuffer( |
return; |
} |
+ if (result > 0) |
+ DCHECK_EQ(result, input->GetDataSize()); |
+ |
+ if (output_timestamp_base_ == kNoTimestamp() && !input->IsEndOfStream()) { |
+ DCHECK(input->GetTimestamp() != kNoTimestamp()); |
+ if (output_bytes_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(is_vorbis); |
+ output_timestamp_base_ = base::TimeDelta(); |
+ } else { |
+ output_timestamp_base_ = input->GetTimestamp(); |
+ } |
+ } |
+ |
+ const uint8* decoded_audio_data = NULL; |
int decoded_audio_size = 0; |
if (frame_decoded) { |
int output_sample_rate = av_frame_->sample_rate; |
@@ -231,6 +262,7 @@ void FFmpegAudioDecoder::DoDecodeBuffer( |
return; |
} |
+ decoded_audio_data = av_frame_->data[0]; |
decoded_audio_size = av_samples_get_buffer_size( |
NULL, codec_context_->channels, av_frame_->nb_samples, |
codec_context_->sample_fmt, 1); |
@@ -238,30 +270,41 @@ void FFmpegAudioDecoder::DoDecodeBuffer( |
scoped_refptr<DataBuffer> output; |
+ if (decoded_audio_size > 0 && output_bytes_to_drop_ > 0) { |
+ int dropped_size = std::min(decoded_audio_size, output_bytes_to_drop_); |
+ decoded_audio_data += dropped_size; |
+ decoded_audio_size -= dropped_size; |
+ output_bytes_to_drop_ -= dropped_size; |
+ } |
+ |
if (decoded_audio_size > 0) { |
+ DCHECK_EQ(decoded_audio_size % bytes_per_frame_, 0) |
+ << "Decoder didn't output full frames"; |
+ |
// 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, av_frame_->data[0], decoded_audio_size); |
+ memcpy(data, decoded_audio_data, 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(); |
+ base::TimeDelta timestamp = GetNextOutputTimestamp(); |
+ total_frames_decoded_ += decoded_audio_size / bytes_per_frame_; |
+ |
+ output->SetTimestamp(timestamp); |
+ output->SetDuration(GetNextOutputTimestamp() - timestamp); |
} 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. |
statistics_cb_.Run(statistics); |
- if (output) |
- base::ResetAndReturn(&read_cb_).Run(kOk, output); |
- else |
+ |
+ if (!output) { |
ReadFromDemuxerStream(); |
+ return; |
+ } |
+ base::ResetAndReturn(&read_cb_).Run(kOk, output); |
} |
void FFmpegAudioDecoder::ReadFromDemuxerStream() { |
@@ -281,34 +324,10 @@ void FFmpegAudioDecoder::DecodeBuffer( |
&FFmpegAudioDecoder::DoDecodeBuffer, this, status, 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::GetNextOutputTimestamp() const { |
+ DCHECK(output_timestamp_base_ != kNoTimestamp()); |
+ double decoded_us = (total_frames_decoded_ / samples_per_second_) * |
+ base::Time::kMicrosecondsPerSecond; |
+ return output_timestamp_base_ + base::TimeDelta::FromMicroseconds(decoded_us); |
} |
- |
-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 |