| // Copyright 2013 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/cdm/library_cdm/clear_key_cdm/ffmpeg_cdm_audio_decoder.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/logging.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/audio_timestamp_helper.h" |
| #include "media/base/data_buffer.h" |
| #include "media/base/limits.h" |
| #include "media/cdm/library_cdm/cdm_host_proxy.h" |
| #include "media/ffmpeg/ffmpeg_common.h" |
| #include "media/ffmpeg/ffmpeg_decoding_loop.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Maximum number of channels with defined layout in src/media. |
| static const int kMaxChannels = 8; |
| |
| bool IsValidConfig(const cdm::AudioDecoderConfig_2& config) { |
| // No need to check |encryption_scheme| as the buffers will always be |
| // decrypted before calling DecodeBuffer(). |
| return config.codec != cdm::kUnknownAudioCodec && config.channel_count > 0 && |
| config.channel_count <= kMaxChannels && config.bits_per_channel > 0 && |
| config.bits_per_channel <= limits::kMaxBitsPerSample && |
| config.samples_per_second > 0 && |
| config.samples_per_second <= limits::kMaxSampleRate; |
| } |
| |
| AVCodecID CdmAudioCodecToCodecID(cdm::AudioCodec audio_codec) { |
| switch (audio_codec) { |
| case cdm::kCodecVorbis: |
| return AV_CODEC_ID_VORBIS; |
| case cdm::kCodecAac: |
| return AV_CODEC_ID_AAC; |
| case cdm::kUnknownAudioCodec: |
| default: |
| NOTREACHED() << "Unsupported cdm::AudioCodec: " << audio_codec; |
| return AV_CODEC_ID_NONE; |
| } |
| } |
| |
| void CdmAudioDecoderConfigToAVCodecContext( |
| const cdm::AudioDecoderConfig_2& 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 + AV_INPUT_BUFFER_PADDING_SIZE)); |
| memcpy(codec_context->extradata, config.extra_data, config.extra_data_size); |
| memset(codec_context->extradata + config.extra_data_size, '\0', |
| AV_INPUT_BUFFER_PADDING_SIZE); |
| } else { |
| codec_context->extradata = NULL; |
| codec_context->extradata_size = 0; |
| } |
| } |
| |
| cdm::AudioFormat AVSampleFormatToCdmAudioFormat(AVSampleFormat sample_format) { |
| switch (sample_format) { |
| case AV_SAMPLE_FMT_U8: |
| return cdm::kAudioFormatU8; |
| case AV_SAMPLE_FMT_S16: |
| return cdm::kAudioFormatS16; |
| case AV_SAMPLE_FMT_S32: |
| return cdm::kAudioFormatS32; |
| case AV_SAMPLE_FMT_FLT: |
| return cdm::kAudioFormatF32; |
| case AV_SAMPLE_FMT_S16P: |
| return cdm::kAudioFormatPlanarS16; |
| case AV_SAMPLE_FMT_FLTP: |
| return cdm::kAudioFormatPlanarF32; |
| default: |
| DVLOG(1) << "Unknown AVSampleFormat: " << sample_format; |
| } |
| return cdm::kUnknownAudioFormat; |
| } |
| |
| void CopySamples(cdm::AudioFormat cdm_format, |
| int decoded_audio_size, |
| const AVFrame& av_frame, |
| uint8_t* output_buffer) { |
| switch (cdm_format) { |
| case cdm::kAudioFormatU8: |
| case cdm::kAudioFormatS16: |
| case cdm::kAudioFormatS32: |
| case cdm::kAudioFormatF32: |
| memcpy(output_buffer, av_frame.data[0], decoded_audio_size); |
| break; |
| case cdm::kAudioFormatPlanarS16: |
| case cdm::kAudioFormatPlanarF32: { |
| const int decoded_size_per_channel = |
| decoded_audio_size / av_frame.channels; |
| for (int i = 0; i < av_frame.channels; ++i) { |
| memcpy(output_buffer, av_frame.extended_data[i], |
| decoded_size_per_channel); |
| output_buffer += decoded_size_per_channel; |
| } |
| break; |
| } |
| default: |
| NOTREACHED() << "Unsupported CDM Audio Format!"; |
| memset(output_buffer, 0, decoded_audio_size); |
| } |
| } |
| |
| } // namespace |
| |
| FFmpegCdmAudioDecoder::FFmpegCdmAudioDecoder(CdmHostProxy* cdm_host_proxy) |
| : cdm_host_proxy_(cdm_host_proxy) {} |
| |
| FFmpegCdmAudioDecoder::~FFmpegCdmAudioDecoder() { |
| ReleaseFFmpegResources(); |
| } |
| |
| bool FFmpegCdmAudioDecoder::Initialize( |
| const cdm::AudioDecoderConfig_2& 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_.reset(avcodec_alloc_context3(NULL)); |
| CdmAudioDecoderConfigToAVCodecContext(config, codec_context_.get()); |
| |
| // MP3 decodes to S16P which we don't support, tell it to use S16 instead. |
| if (codec_context_->sample_fmt == AV_SAMPLE_FMT_S16P) |
| codec_context_->request_sample_fmt = AV_SAMPLE_FMT_S16; |
| |
| const 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; |
| } |
| |
| // Ensure avcodec_open2() respected our format request. |
| if (codec_context_->sample_fmt == AV_SAMPLE_FMT_S16P) { |
| DLOG(ERROR) << "Unable to configure a supported sample format: " |
| << codec_context_->sample_fmt; |
| return false; |
| } |
| |
| // Success! |
| decoding_loop_ = std::make_unique<FFmpegDecodingLoop>(codec_context_.get()); |
| samples_per_second_ = config.samples_per_second; |
| bytes_per_frame_ = codec_context_->channels * config.bits_per_channel / 8; |
| output_timestamp_helper_ = |
| std::make_unique<AudioTimestampHelper>(config.samples_per_second); |
| is_initialized_ = true; |
| |
| // Store initial values to guard against midstream configuration changes. |
| channels_ = codec_context_->channels; |
| av_sample_format_ = codec_context_->sample_fmt; |
| |
| return true; |
| } |
| |
| void FFmpegCdmAudioDecoder::Deinitialize() { |
| DVLOG(1) << "Deinitialize()"; |
| ReleaseFFmpegResources(); |
| is_initialized_ = false; |
| ResetTimestampState(); |
| } |
| |
| void FFmpegCdmAudioDecoder::Reset() { |
| DVLOG(1) << "Reset()"; |
| avcodec_flush_buffers(codec_context_.get()); |
| ResetTimestampState(); |
| } |
| |
| 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; |
| base::TimeDelta timestamp = base::Microseconds(input_timestamp); |
| |
| if (!is_end_of_stream && timestamp != kNoTimestamp) { |
| if (last_input_timestamp_ != kNoTimestamp && |
| 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; |
| } |
| |
| size_t total_size = 0u; |
| std::vector<std::unique_ptr<AVFrame, ScopedPtrAVFreeFrame>> audio_frames; |
| |
| AVPacket* packet = av_packet_alloc(); |
| packet->data = const_cast<uint8_t*>(compressed_buffer); |
| packet->size = compressed_buffer_size; |
| |
| FFmpegDecodingLoop::DecodeStatus decode_status = decoding_loop_->DecodePacket( |
| packet, |
| base::BindRepeating(&FFmpegCdmAudioDecoder::OnNewFrame, |
| base::Unretained(this), &total_size, &audio_frames)); |
| av_packet_free(&packet); |
| switch (decode_status) { |
| case FFmpegDecodingLoop::DecodeStatus::kSendPacketFailed: |
| return cdm::kDecodeError; |
| case FFmpegDecodingLoop::DecodeStatus::kFrameProcessingFailed: |
| NOTREACHED(); |
| FALLTHROUGH; |
| case FFmpegDecodingLoop::DecodeStatus::kDecodeFrameFailed: |
| DLOG(WARNING) << " failed to decode an audio buffer: " |
| << timestamp.InMicroseconds(); |
| break; |
| case FFmpegDecodingLoop::DecodeStatus::kOkay: |
| break; |
| } |
| |
| if (output_timestamp_helper_->base_timestamp() == kNoTimestamp && |
| !is_end_of_stream) { |
| DCHECK(timestamp != kNoTimestamp); |
| output_timestamp_helper_->SetBaseTimestamp(timestamp); |
| } |
| |
| if (audio_frames.empty()) |
| return cdm::kNeedMoreData; |
| |
| const size_t allocation_size = total_size + 2 * sizeof(int64_t); |
| decoded_frames->SetFrameBuffer(cdm_host_proxy_->Allocate(allocation_size)); |
| if (!decoded_frames->FrameBuffer()) { |
| LOG(ERROR) << "DecodeBuffer() ClearKeyCdmHost::Allocate failed."; |
| return cdm::kDecodeError; |
| } |
| decoded_frames->FrameBuffer()->SetSize(allocation_size); |
| |
| // Tell the CDM what AudioFormat we're using. |
| const cdm::AudioFormat cdm_format = AVSampleFormatToCdmAudioFormat( |
| static_cast<AVSampleFormat>(av_sample_format_)); |
| DCHECK_NE(cdm_format, cdm::kUnknownAudioFormat); |
| decoded_frames->SetFormat(cdm_format); |
| |
| uint8_t* output_buffer = decoded_frames->FrameBuffer()->Data(); |
| SerializeInt64(output_timestamp_helper_->GetTimestamp().InMicroseconds(), |
| output_buffer); |
| output_buffer += sizeof(int64_t); |
| SerializeInt64(total_size, output_buffer); |
| output_buffer += sizeof(int64_t); |
| output_timestamp_helper_->AddFrames(total_size / bytes_per_frame_); |
| |
| for (auto& frame : audio_frames) { |
| int decoded_audio_size = 0; |
| if (frame->sample_rate != samples_per_second_ || |
| frame->channels != channels_ || frame->format != av_sample_format_) { |
| DLOG(ERROR) << "Unsupported midstream configuration change!" |
| << " Sample Rate: " << frame->sample_rate << " vs " |
| << samples_per_second_ << ", Channels: " << frame->channels |
| << " vs " << channels_ << ", Sample Format: " << frame->format |
| << " vs " << av_sample_format_; |
| return cdm::kDecodeError; |
| } |
| |
| decoded_audio_size = av_samples_get_buffer_size( |
| nullptr, codec_context_->channels, frame->nb_samples, |
| codec_context_->sample_fmt, 1); |
| if (!decoded_audio_size) |
| continue; |
| |
| DCHECK_EQ(decoded_audio_size % bytes_per_frame_, 0) |
| << "Decoder didn't output full frames"; |
| |
| CopySamples(cdm_format, decoded_audio_size, *frame, output_buffer); |
| output_buffer += decoded_audio_size; |
| } |
| |
| return cdm::kSuccess; |
| } |
| |
| bool FFmpegCdmAudioDecoder::OnNewFrame( |
| size_t* total_size, |
| std::vector<std::unique_ptr<AVFrame, ScopedPtrAVFreeFrame>>* audio_frames, |
| AVFrame* frame) { |
| *total_size += av_samples_get_buffer_size(nullptr, codec_context_->channels, |
| frame->nb_samples, |
| codec_context_->sample_fmt, 1); |
| audio_frames->emplace_back(av_frame_clone(frame)); |
| return true; |
| } |
| |
| void FFmpegCdmAudioDecoder::ResetTimestampState() { |
| output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp); |
| last_input_timestamp_ = kNoTimestamp; |
| } |
| |
| void FFmpegCdmAudioDecoder::ReleaseFFmpegResources() { |
| DVLOG(1) << "ReleaseFFmpegResources()"; |
| |
| decoding_loop_.reset(); |
| codec_context_.reset(); |
| } |
| |
| void FFmpegCdmAudioDecoder::SerializeInt64(int64_t value, uint8_t* dest) { |
| memcpy(dest, &value, sizeof(value)); |
| } |
| |
| } // namespace media |