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

Unified Diff: media/filters/ffmpeg_audio_decoder.cc

Issue 6901135: Rewriting FFmpegAudioDecoder and eliminating DecoderBase. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src
Patch Set: all good Created 9 years, 3 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_video_decoder.h » ('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 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
« no previous file with comments | « media/filters/ffmpeg_audio_decoder.h ('k') | media/filters/ffmpeg_video_decoder.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698