blob: 8a78682b70a0ac90f1c735594ea6c7d872ac8ff7 [file] [log] [blame]
// 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