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

Unified Diff: media/filters/opus_audio_decoder.cc

Issue 11416367: Add Opus decode wrapper to media. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase on updates to webkit/media/filter_helpers.cc Created 8 years 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/opus_audio_decoder.h ('k') | media/media.gyp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/filters/opus_audio_decoder.cc
diff --git a/media/filters/opus_audio_decoder.cc b/media/filters/opus_audio_decoder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8837d2c8d935918e6cc3b81c9c1e5883ca965d9c
--- /dev/null
+++ b/media/filters/opus_audio_decoder.cc
@@ -0,0 +1,561 @@
+// Copyright (c) 2012 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/opus_audio_decoder.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/message_loop_proxy.h"
+#include "base/sys_byteorder.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/data_buffer.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/demuxer.h"
+#include "media/base/pipeline.h"
+#include "third_party/opus/src/include/opus.h"
+#include "third_party/opus/src/include/opus_multistream.h"
+
+namespace media {
+
+static uint16 ReadLE16(const uint8* data, size_t data_size, int read_offset) {
+ DCHECK(data);
+ uint16 value = 0;
+ DCHECK_LE(read_offset + sizeof(value), data_size);
+ memcpy(&value, data + read_offset, sizeof(value));
+ return base::ByteSwapToLE16(value);
+}
+
+// Returns true if the decode result was end of stream.
+static inline bool IsEndOfStream(int decoded_size, Buffer* input) {
+ // Two conditions to meet to declare end of stream for this decoder:
+ // 1. Opus didn't output anything.
+ // 2. An end of stream buffer is received.
+ return decoded_size == 0 && input->IsEndOfStream();
+}
+
+// The Opus specification is part of IETF RFC 6716:
+// http://tools.ietf.org/html/rfc6716
+
+// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
+// mappings for up to 8 channels. See section 4.3.9 of the vorbis
+// specification:
+// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
+static const int kMaxVorbisChannels = 8;
+
+// Opus allows for decode of S16 or float samples. OpusAudioDecoder always uses
+// S16 samples.
+static const int kBitsPerChannel = 16;
+static const int kBytesPerChannel = kBitsPerChannel / 8;
+
+// Maximum packet size used in Xiph's opusdec and FFmpeg's libopusdec.
+static const int kMaxOpusOutputPacketSizeSamples = 960 * 6;
+static const int kMaxOpusOutputPacketSizeBytes =
+ kMaxOpusOutputPacketSizeSamples * kBytesPerChannel;
+
+static void RemapOpusChannelLayout(const uint8* opus_mapping,
+ int num_channels,
+ uint8* channel_layout) {
+ DCHECK_LE(num_channels, kMaxVorbisChannels);
+
+ // Opus uses Vorbis channel layout.
+ const int32 num_layouts = kMaxVorbisChannels;
+ const int32 num_layout_values = kMaxVorbisChannels;
+ const uint8 kVorbisChannelLayouts[num_layouts][num_layout_values] = {
+ { 0 },
+ { 0, 1 },
+ { 0, 2, 1 },
+ { 0, 1, 2, 3 },
+ { 0, 2, 1, 3, 4 },
+ { 0, 2, 1, 5, 3, 4 },
+ { 0, 2, 1, 6, 5, 3, 4 },
+ { 0, 2, 1, 7, 5, 6, 3, 4 },
+ };
+
+ // Reorder the channels to produce the same ordering as FFmpeg, which is
+ // what the pipeline expects.
+ const uint8* vorbis_layout_offset = kVorbisChannelLayouts[num_channels - 1];
+ for (int channel = 0; channel < num_channels; ++channel)
+ channel_layout[channel] = opus_mapping[vorbis_layout_offset[channel]];
+}
+
+// Opus Header contents:
+// - "OpusHead" (64 bits)
+// - version number (8 bits)
+// - Channels C (8 bits)
+// - Pre-skip (16 bits)
+// - Sampling rate (32 bits)
+// - Gain in dB (16 bits, S7.8)
+// - Mapping (8 bits, 0=single stream (mono/stereo) 1=Vorbis mapping,
+// 2..254: reserved, 255: multistream with no mapping)
+//
+// - if (mapping != 0)
+// - N = totel number of streams (8 bits)
+// - M = number of paired streams (8 bits)
+// - C times channel origin
+// - if (C<2*M)
+// - stream = byte/2
+// - if (byte&0x1 == 0)
+// - left
+// else
+// - right
+// - else
+// - stream = byte-M
+
+// Default audio output channel layout. Used to initialize |stream_map| in
+// OpusHeader, and passed to opus_multistream_decoder_create() when the header
+// does not contain mapping information. The values are valid only for mono and
+// stereo output: Opus streams with more than 2 channels require a stream map.
+static const int kMaxChannelsWithDefaultLayout = 2;
+static const uint8 kDefaultOpusChannelLayout[kMaxChannelsWithDefaultLayout] = {
+ 0, 1 };
+
+// Size of the Opus header excluding optional mapping information.
+static const int kOpusHeaderSize = 19;
+
+// Offset to the channel count byte in the Opus header.
+static const int kOpusHeaderChannelsOffset = 9;
+
+// Offset to the pre-skip value in the Opus header.
+static const int kOpusHeaderSkipSamplesOffset = 10;
+
+// Offset to the channel mapping byte in the Opus header.
+static const int kOpusHeaderChannelMappingOffset = 18;
+
+// Header contains a stream map. The mapping values are in extra data beyond
+// the always present |kOpusHeaderSize| bytes of data. The mapping data
+// contains stream count, coupling information, and per channel mapping values:
+// - Byte 0: Number of streams.
+// - Byte 1: Number coupled.
+// - Byte 2: Starting at byte 2 are |header->channels| uint8 mapping values.
+static const int kOpusHeaderNumStreamsOffset = kOpusHeaderSize;
+static const int kOpusHeaderNumCoupledOffset = kOpusHeaderNumStreamsOffset + 1;
+static const int kOpusHeaderStreamMapOffset = kOpusHeaderNumStreamsOffset + 2;
+
+struct OpusHeader {
+ OpusHeader()
+ : channels(0),
+ skip_samples(0),
+ channel_mapping(0),
+ num_streams(0),
+ num_coupled(0) {
+ memcpy(stream_map,
+ kDefaultOpusChannelLayout,
+ kMaxChannelsWithDefaultLayout);
+ }
+ int channels;
+ int skip_samples;
+ int channel_mapping;
+ int num_streams;
+ int num_coupled;
+ uint8 stream_map[kMaxVorbisChannels];
+};
+
+// Returns true when able to successfully parse and store Opus header data in
+// data parsed in |header|. Based on opus header parsing code in libopusdec
+// from FFmpeg, and opus_header from Xiph's opus-tools project.
+static void ParseOpusHeader(const uint8* data, int data_size,
+ const AudioDecoderConfig& config,
+ OpusHeader* header) {
+ CHECK_GE(data_size, kOpusHeaderSize);
+
+ header->channels = *(data + kOpusHeaderChannelsOffset);
+
+ CHECK(header->channels > 0 && header->channels <= kMaxVorbisChannels)
+ << "invalid channel count in header: " << header->channels;
+
+ header->skip_samples =
+ ReadLE16(data, data_size, kOpusHeaderSkipSamplesOffset);
+
+ header->channel_mapping = *(data + kOpusHeaderChannelMappingOffset);
+
+ if (!header->channel_mapping) {
+ CHECK_LE(header->channels, kMaxChannelsWithDefaultLayout)
+ << "Invalid header, missing stream map.";
+
+ header->num_streams = 1;
+ header->num_coupled =
+ (ChannelLayoutToChannelCount(config.channel_layout()) > 1) ? 1 : 0;
+ return;
+ }
+
+ CHECK_GE(data_size, kOpusHeaderStreamMapOffset + header->channels)
+ << "Invalid stream map.";
+
+ header->num_streams = *(data + kOpusHeaderNumStreamsOffset);
+ header->num_coupled = *(data + kOpusHeaderNumCoupledOffset);
+
+ if (header->num_streams + header->num_coupled != header->channels)
+ LOG(WARNING) << "Inconsistent channel mapping.";
+
+ for (int i = 0; i < kMaxVorbisChannels; ++i)
+ header->stream_map[i] = *(data + kOpusHeaderStreamMapOffset + i);
+}
+
+OpusAudioDecoder::OpusAudioDecoder(
+ const scoped_refptr<base::MessageLoopProxy>& message_loop)
+ : message_loop_(message_loop),
+ opus_decoder_(NULL),
+ bits_per_channel_(0),
+ channel_layout_(CHANNEL_LAYOUT_NONE),
+ samples_per_second_(0),
+ last_input_timestamp_(kNoTimestamp()),
+ output_bytes_to_drop_(0),
+ skip_samples_(0) {
+}
+
+void OpusAudioDecoder::Initialize(
+ const scoped_refptr<DemuxerStream>& stream,
+ const PipelineStatusCB& status_cb,
+ const StatisticsCB& statistics_cb) {
+ if (!message_loop_->BelongsToCurrentThread()) {
+ message_loop_->PostTask(FROM_HERE, base::Bind(
+ &OpusAudioDecoder::DoInitialize, this,
+ stream, status_cb, statistics_cb));
+ return;
+ }
+ DoInitialize(stream, status_cb, statistics_cb);
+}
+
+void OpusAudioDecoder::Read(const ReadCB& read_cb) {
+ // Complete operation asynchronously on different stack of execution as per
+ // the API contract of AudioDecoder::Read()
+ message_loop_->PostTask(FROM_HERE, base::Bind(
+ &OpusAudioDecoder::DoRead, this, read_cb));
+}
+
+int OpusAudioDecoder::bits_per_channel() {
+ return bits_per_channel_;
+}
+
+ChannelLayout OpusAudioDecoder::channel_layout() {
+ return channel_layout_;
+}
+
+int OpusAudioDecoder::samples_per_second() {
+ return samples_per_second_;
+}
+
+void OpusAudioDecoder::Reset(const base::Closure& closure) {
+ message_loop_->PostTask(FROM_HERE, base::Bind(
+ &OpusAudioDecoder::DoReset, this, closure));
+}
+
+OpusAudioDecoder::~OpusAudioDecoder() {
+ // TODO(scherkus): should we require Stop() to be called? this might end up
+ // getting called on a random thread due to refcounting.
+ CloseDecoder();
+}
+
+void OpusAudioDecoder::DoInitialize(
+ const scoped_refptr<DemuxerStream>& stream,
+ const PipelineStatusCB& status_cb,
+ const StatisticsCB& statistics_cb) {
+ if (demuxer_stream_) {
+ // TODO(scherkus): initialization currently happens more than once in
+ // PipelineIntegrationTest.BasicPlayback.
+ LOG(ERROR) << "Initialize has already been called.";
+ CHECK(false);
+ }
+
+ demuxer_stream_ = stream;
+
+ if (!ConfigureDecoder()) {
+ status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
+ return;
+ }
+
+ statistics_cb_ = statistics_cb;
+ status_cb.Run(PIPELINE_OK);
+}
+
+void OpusAudioDecoder::DoReset(const base::Closure& closure) {
+ opus_multistream_decoder_ctl(opus_decoder_, OPUS_RESET_STATE);
+ ResetTimestampState();
+ closure.Run();
+}
+
+void OpusAudioDecoder::DoRead(const ReadCB& read_cb) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ DCHECK(!read_cb.is_null());
+ CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
+
+ read_cb_ = read_cb;
+ ReadFromDemuxerStream();
+}
+
+void OpusAudioDecoder::DoDecodeBuffer(
+ DemuxerStream::Status status,
+ const scoped_refptr<DecoderBuffer>& input) {
+ if (!message_loop_->BelongsToCurrentThread()) {
+ message_loop_->PostTask(FROM_HERE, base::Bind(
+ &OpusAudioDecoder::DoDecodeBuffer, this, status, input));
+ return;
+ }
+
+ DCHECK(!read_cb_.is_null());
+ DCHECK_EQ(status != DemuxerStream::kOk, !input) << status;
+
+ if (status == DemuxerStream::kAborted) {
+ DCHECK(!input);
+ base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
+ return;
+ }
+
+ if (status == DemuxerStream::kConfigChanged) {
+ DCHECK(!input);
+
+ scoped_refptr<DataBuffer> output_buffer;
+
+ // Send a "end of stream" buffer to the decode loop
+ // to output any remaining data still in the decoder.
+ if (!Decode(DecoderBuffer::CreateEOSBuffer(), true, &output_buffer)) {
+ base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
+ return;
+ }
+
+ DVLOG(1) << "Config changed.";
+
+ if (!ConfigureDecoder()) {
+ base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
+ return;
+ }
+
+ ResetTimestampState();
+
+ if (output_buffer) {
+ // Execute callback to return the decoded audio.
+ base::ResetAndReturn(&read_cb_).Run(kOk, output_buffer);
+ } else {
+ // We exhausted the input data, but it wasn't enough for a frame. Ask for
+ // more data in order to fulfill this read.
+ ReadFromDemuxerStream();
+ }
+
+ return;
+ }
+
+ DCHECK_EQ(status, DemuxerStream::kOk);
+ DCHECK(input);
+
+ // Make sure we are notified if http://crbug.com/49709 returns. Issue also
+ // occurs with some damaged files.
+ if (!input->IsEndOfStream() && input->GetTimestamp() == kNoTimestamp() &&
+ output_timestamp_helper_->base_timestamp() == kNoTimestamp()) {
+ DVLOG(1) << "Received a buffer without timestamps!";
+ base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
+ return;
+ }
+
+ if (!input->IsEndOfStream()) {
+ if (last_input_timestamp_ != kNoTimestamp() &&
+ input->GetTimestamp() != kNoTimestamp() &&
+ 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);
+ }
+
+ last_input_timestamp_ = input->GetTimestamp();
+ }
+
+ scoped_refptr<DataBuffer> output_buffer;
+
+ if (!Decode(input, false, &output_buffer)) {
+ base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
+ return;
+ }
+
+ if (output_buffer) {
+ // Execute callback to return the decoded audio.
+ base::ResetAndReturn(&read_cb_).Run(kOk, output_buffer);
+ } else {
+ // We exhausted the input data, but it wasn't enough for a frame. Ask for
+ // more data in order to fulfill this read.
+ ReadFromDemuxerStream();
+ }
+}
+
+void OpusAudioDecoder::ReadFromDemuxerStream() {
+ DCHECK(!read_cb_.is_null());
+
+ demuxer_stream_->Read(base::Bind(&OpusAudioDecoder::DoDecodeBuffer, this));
+}
+
+bool OpusAudioDecoder::ConfigureDecoder() {
+ const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config();
+
+ if (config.codec() != kCodecOpus) {
+ DLOG(ERROR) << "ConfigureDecoder(): codec must be kCodecOpus.";
+ return false;
+ }
+
+ const int channel_count =
+ ChannelLayoutToChannelCount(config.channel_layout());
+ if (!config.IsValidConfig() || channel_count > kMaxVorbisChannels) {
+ DLOG(ERROR) << "ConfigureDecoder(): Invalid or unsupported audio stream -"
+ << " codec: " << config.codec()
+ << " channel count: " << channel_count
+ << " channel layout: " << config.channel_layout()
+ << " bits per channel: " << config.bits_per_channel()
+ << " samples per second: " << config.samples_per_second();
+ return false;
+ }
+
+ if (config.bits_per_channel() != kBitsPerChannel) {
+ DLOG(ERROR) << "ConfigureDecoder(): 16 bit samples required.";
+ return false;
+ }
+
+ if (config.is_encrypted()) {
+ DLOG(ERROR) << "ConfigureDecoder(): Encrypted audio stream not supported.";
+ return false;
+ }
+
+ if (opus_decoder_ &&
+ (bits_per_channel_ != config.bits_per_channel() ||
+ channel_layout_ != config.channel_layout() ||
+ samples_per_second_ != config.samples_per_second())) {
+ DVLOG(1) << "Unsupported config change :";
+ DVLOG(1) << "\tbits_per_channel : " << bits_per_channel_
+ << " -> " << config.bits_per_channel();
+ DVLOG(1) << "\tchannel_layout : " << channel_layout_
+ << " -> " << config.channel_layout();
+ DVLOG(1) << "\tsample_rate : " << samples_per_second_
+ << " -> " << config.samples_per_second();
+ return false;
+ }
+
+ // Clean up existing decoder if necessary.
+ CloseDecoder();
+
+ // Allocate the output buffer if necessary.
+ if (!output_buffer_)
+ output_buffer_.reset(new int16[kMaxOpusOutputPacketSizeSamples]);
+
+ // Parse the Opus header.
+ OpusHeader opus_header;
+ ParseOpusHeader(config.extra_data(), config.extra_data_size(),
+ config,
+ &opus_header);
+
+ skip_samples_ = opus_header.skip_samples;
+
+ if (skip_samples_ > 0)
+ output_bytes_to_drop_ = skip_samples_ * config.bytes_per_frame();
+
+ uint8 channel_mapping[kMaxVorbisChannels];
+ memcpy(&channel_mapping,
+ kDefaultOpusChannelLayout,
+ kMaxChannelsWithDefaultLayout);
+
+ if (channel_count > kMaxChannelsWithDefaultLayout) {
+ RemapOpusChannelLayout(opus_header.stream_map,
+ channel_count,
+ channel_mapping);
+ }
+
+ // Init Opus.
+ int status = OPUS_INVALID_STATE;
+ opus_decoder_ = opus_multistream_decoder_create(config.samples_per_second(),
+ channel_count,
+ opus_header.num_streams,
+ opus_header.num_coupled,
+ channel_mapping,
+ &status);
+ if (!opus_decoder_ || status != OPUS_OK) {
+ LOG(ERROR) << "ConfigureDecoder(): opus_multistream_decoder_create failed"
+ << " status=" << opus_strerror(status);
+ return false;
+ }
+
+ // TODO(tomfinegan): Handle audio delay once the matroska spec is updated
+ // to represent the value.
+
+ bits_per_channel_ = config.bits_per_channel();
+ channel_layout_ = config.channel_layout();
+ samples_per_second_ = config.samples_per_second();
+ output_timestamp_helper_.reset(new AudioTimestampHelper(
+ config.bytes_per_frame(), config.samples_per_second()));
+ return true;
+}
+
+void OpusAudioDecoder::CloseDecoder() {
+ if (opus_decoder_) {
+ opus_multistream_decoder_destroy(opus_decoder_);
+ opus_decoder_ = NULL;
+ }
+}
+
+void OpusAudioDecoder::ResetTimestampState() {
+ output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp());
+ last_input_timestamp_ = kNoTimestamp();
+ output_bytes_to_drop_ = 0;
+}
+
+bool OpusAudioDecoder::Decode(const scoped_refptr<DecoderBuffer>& input,
+ bool skip_eos_append,
+ scoped_refptr<DataBuffer>* output_buffer) {
+ int samples_decoded =
+ opus_multistream_decode(opus_decoder_,
+ input->GetData(), input->GetDataSize(),
+ &output_buffer_[0],
+ kMaxOpusOutputPacketSizeSamples,
+ 0);
+ if (samples_decoded < 0) {
+ DCHECK(!input->IsEndOfStream())
+ << "Decode(): End of stream buffer produced an error!";
+
+ LOG(ERROR) << "ConfigureDecoder(): opus_multistream_decode failed for"
+ << " timestamp: " << input->GetTimestamp().InMicroseconds()
+ << " us, duration: " << input->GetDuration().InMicroseconds()
+ << " us, packet size: " << input->GetDataSize() << " bytes with"
+ << " status: " << opus_strerror(samples_decoded);
+ return false;
+ }
+
+ uint8* decoded_audio_data = reinterpret_cast<uint8*>(&output_buffer_[0]);
+ int decoded_audio_size = samples_decoded *
+ demuxer_stream_->audio_decoder_config().bytes_per_frame();
+ DCHECK_LE(decoded_audio_size, kMaxOpusOutputPacketSizeBytes);
+
+ if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() &&
+ !input->IsEndOfStream()) {
+ DCHECK(input->GetTimestamp() != kNoTimestamp());
+ output_timestamp_helper_->SetBaseTimestamp(input->GetTimestamp());
+ }
+
+ if (decoded_audio_size > 0 && output_bytes_to_drop_ > 0) {
+ int dropped_size = std::min(decoded_audio_size, output_bytes_to_drop_);
+ DCHECK_EQ(dropped_size % kBytesPerChannel, 0);
+ decoded_audio_data += dropped_size;
+ decoded_audio_size -= dropped_size;
+ output_bytes_to_drop_ -= dropped_size;
+ }
+
+ if (decoded_audio_size > 0) {
+ // Copy the audio samples into an output buffer.
+ *output_buffer = new DataBuffer(decoded_audio_data, decoded_audio_size);
+ (*output_buffer)->SetTimestamp(output_timestamp_helper_->GetTimestamp());
+ (*output_buffer)->SetDuration(
+ output_timestamp_helper_->GetDuration(decoded_audio_size));
+ output_timestamp_helper_->AddBytes(decoded_audio_size);
+ } else if (IsEndOfStream(decoded_audio_size, input) && !skip_eos_append) {
+ DCHECK_EQ(input->GetDataSize(), 0);
+ // Create an end of stream output buffer.
+ *output_buffer = new DataBuffer(0);
+ }
+
+ // Decoding finished successfully, update statistics.
+ PipelineStatistics statistics;
+ statistics.audio_bytes_decoded = decoded_audio_size;
+ statistics_cb_.Run(statistics);
+
+ return true;
+}
+
+} // namespace media
« no previous file with comments | « media/filters/opus_audio_decoder.h ('k') | media/media.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698