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

Unified Diff: webkit/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.cc

Issue 11260007: Add FFmpeg audio decoder for the clear key CDM. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Add TODO. Created 8 years, 2 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
Index: webkit/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.cc
diff --git a/webkit/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.cc b/webkit/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6880dc44a1a0c550b7a9241224d61923fc9a562f
--- /dev/null
+++ b/webkit/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.cc
@@ -0,0 +1,340 @@
+// 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 "webkit/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "media/base/buffers.h"
+#include "media/base/limits.h"
+#include "webkit/media/crypto/ppapi/content_decryption_module.h"
+
+// Include FFmpeg header files.
+extern "C" {
+// Temporarily disable possible loss of data warning.
+MSVC_PUSH_DISABLE_WARNING(4244);
+#include <libavcodec/avcodec.h>
+MSVC_POP_WARNING();
+} // extern "C"
+
+namespace webkit_media {
+
+// Maximum number of channels with defined order in the Vorbis specification.
+// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
+static const int kMaxVorbisChannels = 8;
+
+static CodecID CdmAudioCodecToCodecID(
+ cdm::AudioDecoderConfig::AudioCodec audio_codec) {
+ switch (audio_codec) {
+ case cdm::AudioDecoderConfig::kCodecVorbis:
+ return CODEC_ID_VORBIS;
+ default:
+ NOTREACHED() << "Unsupported cdm::AudioCodec: " << audio_codec;
+ }
+
+ return CODEC_ID_NONE;
+}
+
+static void CdmAudioDecoderConfigToAVCodecContext(
+ const cdm::AudioDecoderConfig& config,
+ AVCodecContext* codec_context) {
+ codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
+ codec_context->codec_id = CdmAudioCodecToCodecID(config.codec);
+
+ switch (config.bits_per_channel) {
+ case 8:
+ codec_context->sample_fmt = AV_SAMPLE_FMT_U8;
+ break;
+ case 16:
+ codec_context->sample_fmt = AV_SAMPLE_FMT_S16;
+ break;
+ case 32:
+ codec_context->sample_fmt = AV_SAMPLE_FMT_S32;
+ break;
+ default:
+ DVLOG(1) << "CdmAudioDecoderConfigToAVCodecContext() Unsupported bits "
+ "per channel: " << config.bits_per_channel;
+ codec_context->sample_fmt = AV_SAMPLE_FMT_NONE;
+ }
+
+ codec_context->channels = config.channel_count;
+ codec_context->sample_rate = config.samples_per_second;
+
+ if (config.extra_data) {
+ codec_context->extradata_size = config.extra_data_size;
+ codec_context->extradata = reinterpret_cast<uint8_t*>(
+ av_malloc(config.extra_data_size + FF_INPUT_BUFFER_PADDING_SIZE));
+ memcpy(codec_context->extradata, config.extra_data,
+ config.extra_data_size);
+ memset(codec_context->extradata + config.extra_data_size, '\0',
+ FF_INPUT_BUFFER_PADDING_SIZE);
+ } else {
+ codec_context->extradata = NULL;
+ codec_context->extradata_size = 0;
+ }
+}
+
+FFmpegCdmAudioDecoder::FFmpegCdmAudioDecoder(cdm::Allocator* allocator)
+ : is_initialized_(false),
+ allocator_(allocator),
+ codec_context_(NULL),
+ av_frame_(NULL),
+ bits_per_channel_(0),
+ samples_per_second_(0),
+ bytes_per_frame_(0),
+ output_timestamp_base_(media::kNoTimestamp()),
+ total_frames_decoded_(0),
+ last_input_timestamp_(media::kNoTimestamp()),
+ output_bytes_to_drop_(0) {
+}
+
+FFmpegCdmAudioDecoder::~FFmpegCdmAudioDecoder() {
+ ReleaseFFmpegResources();
+}
+
+bool FFmpegCdmAudioDecoder::Initialize(const cdm::AudioDecoderConfig& config) {
+ DVLOG(1) << "Initialize()";
+
+ if (!IsValidConfig(config)) {
+ LOG(ERROR) << "Initialize(): invalid audio decoder configuration.";
+ return false;
+ }
+
+ if (is_initialized_) {
+ LOG(ERROR) << "Initialize(): Already initialized.";
+ return false;
+ }
+
+ // Initialize AVCodecContext structure.
+ codec_context_ = avcodec_alloc_context3(NULL);
+ CdmAudioDecoderConfigToAVCodecContext(config, codec_context_);
+ DCHECK_EQ(CODEC_ID_VORBIS, codec_context_->codec_id);
+
+ AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
+ if (!codec) {
+ LOG(ERROR) << "Initialize(): avcodec_find_decoder failed.";
+ return false;
+ }
+
+ int status;
+ if ((status = avcodec_open2(codec_context_, codec, NULL)) < 0) {
+ LOG(ERROR) << "Initialize(): avcodec_open2 failed: " << status;
+ return false;
+ }
+
+ av_frame_ = avcodec_alloc_frame();
+ bits_per_channel_ = config.bits_per_channel;
+ samples_per_second_ = config.samples_per_second;
+ bytes_per_frame_ = codec_context_->channels * bits_per_channel_ / 8;
+ serialized_audio_frames_.reserve(bytes_per_frame_ * samples_per_second_);
+ is_initialized_ = true;
+
+ return true;
+}
+
+void FFmpegCdmAudioDecoder::Deinitialize() {
+ DVLOG(1) << "Deinitialize()";
+ ReleaseFFmpegResources();
+ is_initialized_ = false;
+ ResetAudioTimingData();
+}
+
+void FFmpegCdmAudioDecoder::Reset() {
+ DVLOG(1) << "Reset()";
+ avcodec_flush_buffers(codec_context_);
+ ResetAudioTimingData();
+}
+
+// static
+bool FFmpegCdmAudioDecoder::IsValidConfig(
+ const cdm::AudioDecoderConfig& config) {
+ return config.codec == cdm::AudioDecoderConfig::kCodecVorbis &&
+ config.channel_count > 0 &&
+ config.channel_count <= kMaxVorbisChannels &&
+ config.bits_per_channel > 0 &&
+ config.bits_per_channel <= media::limits::kMaxBitsPerSample &&
+ config.samples_per_second > 0 &&
+ config.samples_per_second <= media::limits::kMaxSampleRate;
+}
+
+cdm::Status FFmpegCdmAudioDecoder::DecodeBuffer(
+ const uint8_t* compressed_buffer,
+ int32_t compressed_buffer_size,
+ int64_t input_timestamp,
+ cdm::AudioFrames* decoded_frames) {
+ DVLOG(1) << "DecodeBuffer()";
+ const bool is_end_of_stream = compressed_buffer_size == 0;
+ base::TimeDelta timestamp =
+ base::TimeDelta::FromMicroseconds(input_timestamp);
+ if (!is_end_of_stream) {
+ if (last_input_timestamp_ == media::kNoTimestamp()) {
+ if (codec_context_->codec_id == CODEC_ID_VORBIS &&
+ 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
+ int frames_to_drop = floor(
+ 0.5 + -timestamp.InSecondsF() * samples_per_second_);
+ output_bytes_to_drop_ = bytes_per_frame_ * frames_to_drop;
+ } else {
+ last_input_timestamp_ = timestamp;
+ }
+ } else if (timestamp != media::kNoTimestamp()) {
+ if (timestamp < last_input_timestamp_) {
+ base::TimeDelta diff = timestamp - last_input_timestamp_;
+ DVLOG(1) << "Input timestamps are not monotonically increasing! "
+ << " ts " << timestamp.InMicroseconds() << " us"
+ << " diff " << diff.InMicroseconds() << " us";
+ return cdm::kDecodeError;
+ }
+
+ last_input_timestamp_ = timestamp;
+ }
+ }
+
+ AVPacket packet;
+ av_init_packet(&packet);
+ packet.data = const_cast<uint8_t*>(compressed_buffer);
+ packet.size = compressed_buffer_size;
+
+ // Each audio packet may contain several frames, so we must call the decoder
+ // until we've exhausted the packet. Regardless of the packet size we always
+ // want to hand it to the decoder at least once, otherwise we would end up
+ // skipping end of stream packets since they have a size of zero.
+ do {
+ // Reset frame to default values.
+ avcodec_get_frame_defaults(av_frame_);
+
+ int frame_decoded = 0;
+ int result = avcodec_decode_audio4(
+ codec_context_, av_frame_, &frame_decoded, &packet);
+
+ if (result < 0) {
+ DCHECK(!is_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(ERROR)
+ << "Error decoding an audio frame with timestamp: "
+ << timestamp.InMicroseconds() << " us, duration: "
+ << timestamp.InMicroseconds() << " us, packet size: "
+ << compressed_buffer_size << " bytes";
+
+ return cdm::kDecodeError;
+ }
+
+ // Update packet size and data pointer in case we need to call the decoder
+ // with the remaining bytes from this packet.
+ packet.size -= result;
+ packet.data += result;
+
+ if (output_timestamp_base_ == media::kNoTimestamp() && !is_end_of_stream) {
+ DCHECK(timestamp != media::kNoTimestamp());
+ if (output_bytes_to_drop_ > 0) {
+ // If we have to drop samples it always means the timeline starts at 0.
+ output_timestamp_base_ = base::TimeDelta();
+ } else {
+ output_timestamp_base_ = timestamp;
+ }
+ }
+
+ const uint8_t* decoded_audio_data = NULL;
+ int decoded_audio_size = 0;
+ if (frame_decoded) {
+ int output_sample_rate = av_frame_->sample_rate;
+ if (output_sample_rate != samples_per_second_) {
+ DLOG(ERROR) << "Output sample rate (" << output_sample_rate
+ << ") doesn't match expected rate " << samples_per_second_;
+ return cdm::kDecodeError;
+ }
+
+ 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);
+ }
+
+ 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";
+
+ base::TimeDelta output_timestamp = GetNextOutputTimestamp();
+ total_frames_decoded_ += decoded_audio_size / bytes_per_frame_;
+
+ // Serialize the audio samples into |serialized_audio_frames_|.
+ SerializeInt64(output_timestamp.InMicroseconds());
+ SerializeInt64(decoded_audio_size);
+ serialized_audio_frames_.insert(serialized_audio_frames_.end(),
+ decoded_audio_data,
+ decoded_audio_data + decoded_audio_size);
+ }
+ } while (packet.size > 0);
+
+ if (!serialized_audio_frames_.empty()) {
+ decoded_frames->set_buffer(
+ allocator_->Allocate(serialized_audio_frames_.size()));
+ if (!decoded_frames->buffer()) {
+ LOG(ERROR) << "DecodeBuffer() cdm::Allocator::Allocate failed.";
+ return cdm::kDecodeError;
+ }
+ memcpy(decoded_frames->buffer()->data(),
+ &serialized_audio_frames_[0],
+ serialized_audio_frames_.size());
+ serialized_audio_frames_.clear();
+
+ return cdm::kSuccess;
+ }
+
+ return cdm::kNeedMoreData;
+}
+
+void FFmpegCdmAudioDecoder::ResetAudioTimingData() {
+ output_timestamp_base_ = media::kNoTimestamp();
+ total_frames_decoded_ = 0;
+ last_input_timestamp_ = media::kNoTimestamp();
+ output_bytes_to_drop_ = 0;
+}
+
+void FFmpegCdmAudioDecoder::ReleaseFFmpegResources() {
+ DVLOG(1) << "ReleaseFFmpegResources()";
+
+ if (codec_context_) {
+ av_free(codec_context_->extradata);
+ avcodec_close(codec_context_);
+ av_free(codec_context_);
+ codec_context_ = NULL;
+ }
+ if (av_frame_) {
+ av_free(av_frame_);
+ av_frame_ = NULL;
+ }
+}
+
+base::TimeDelta FFmpegCdmAudioDecoder::GetNextOutputTimestamp() const {
+ DCHECK(output_timestamp_base_ != media::kNoTimestamp());
+ const double total_frames_decoded = total_frames_decoded_;
+ const double decoded_us = (total_frames_decoded / samples_per_second_) *
+ base::Time::kMicrosecondsPerSecond;
+ return output_timestamp_base_ +
+ base::TimeDelta::FromMicroseconds(decoded_us);
+}
+
+void FFmpegCdmAudioDecoder::SerializeInt64(int64 value) {
+ const uint8_t* ptr = reinterpret_cast<uint8_t*>(&value);
+ serialized_audio_frames_.insert(serialized_audio_frames_.end(),
+ ptr, ptr + sizeof(value));
+}
+
+} // namespace webkit_media
« no previous file with comments | « webkit/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.h ('k') | webkit/media/crypto/ppapi/ffmpeg_cdm_video_decoder.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698