blob: 693c13313a3c72e4ab6238da1794bea8656ad733 [file] [log] [blame]
// Copyright 2022 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/libfdkaac/fdk_aac_audio_decoder.h"
#include "starboard/common/log.h"
#include "starboard/common/string.h"
#include "starboard/shared/libfdkaac/libfdkaac_library_loader.h"
namespace starboard {
namespace shared {
namespace libfdkaac {
FdkAacAudioDecoder::FdkAacAudioDecoder() {
static_assert(sizeof(INT_PCM) == sizeof(int16_t),
"sizeof(INT_PCM) has to be the same as sizeof(int16_t).");
InitializeCodec();
}
FdkAacAudioDecoder::~FdkAacAudioDecoder() {
TeardownCodec();
}
void FdkAacAudioDecoder::Initialize(const OutputCB& output_cb,
const ErrorCB& error_cb) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(output_cb);
SB_DCHECK(!output_cb_);
SB_DCHECK(error_cb);
SB_DCHECK(!error_cb_);
output_cb_ = output_cb;
error_cb_ = error_cb;
}
void FdkAacAudioDecoder::Decode(const InputBuffers& input_buffers,
const ConsumedCB& consumed_cb) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(input_buffers.size() == 1);
SB_DCHECK(input_buffers[0]);
SB_DCHECK(output_cb_);
SB_DCHECK(decoder_ != NULL);
if (stream_ended_) {
SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called.";
return;
}
const auto& input_buffer = input_buffers[0];
if (!WriteToFdkDecoder(input_buffer)) {
return;
}
decoding_input_buffers_.push(input_buffer);
Schedule(consumed_cb);
ReadFromFdkDecoder(kDecodeModeDoNotFlush);
}
scoped_refptr<FdkAacAudioDecoder::DecodedAudio> FdkAacAudioDecoder::Read(
int* samples_per_second) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(output_cb_);
SB_DCHECK(!decoded_audios_.empty());
scoped_refptr<DecodedAudio> result;
if (!decoded_audios_.empty()) {
result = decoded_audios_.front();
decoded_audios_.pop();
}
*samples_per_second = samples_per_second_;
return result;
}
void FdkAacAudioDecoder::Reset() {
SB_DCHECK(decoder_ != NULL);
SB_DCHECK(BelongsToCurrentThread());
TeardownCodec();
InitializeCodec();
stream_ended_ = false;
decoded_audios_ = std::queue<scoped_refptr<DecodedAudio>>(); // clear
partially_decoded_audio_ = nullptr;
partially_decoded_audio_data_in_bytes_ = 0;
decoding_input_buffers_ = decltype(decoding_input_buffers_)(); // clear
// Clean up stream information and deduced results.
has_stream_info_ = false;
num_channels_ = 0;
samples_per_second_ = 0;
decoded_audio_size_in_bytes_ = 0;
audio_data_to_discard_in_bytes_ = 0;
CancelPendingJobs();
}
void FdkAacAudioDecoder::WriteEndOfStream() {
SB_DCHECK(decoder_ != NULL);
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(output_cb_);
while (!decoding_input_buffers_.empty()) {
if (!ReadFromFdkDecoder(kDecodeModeFlush)) {
return;
}
}
stream_ended_ = true;
// Put EOS into the queue.
decoded_audios_.push(new DecodedAudio);
Schedule(output_cb_);
}
void FdkAacAudioDecoder::InitializeCodec() {
SB_DCHECK(decoder_ == NULL);
decoder_ = aacDecoder_Open(TT_MP4_ADTS, 1);
SB_DCHECK(decoder_ != NULL);
// Set AAC_PCM_MAX_OUTPUT_CHANNELS to 0 to disable downmixing feature.
// It makes the decoder output contain the same number of channels as the
// encoded bitstream.
AAC_DECODER_ERROR error =
aacDecoder_SetParam(decoder_, AAC_PCM_MAX_OUTPUT_CHANNELS, 0);
SB_DCHECK(error == AAC_DEC_OK);
}
void FdkAacAudioDecoder::TeardownCodec() {
if (decoder_) {
aacDecoder_Close(decoder_);
decoder_ = nullptr;
}
}
bool FdkAacAudioDecoder::WriteToFdkDecoder(
const scoped_refptr<InputBuffer>& input_buffer) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(input_buffer);
unsigned char* data = const_cast<unsigned char*>(input_buffer->data());
const unsigned int data_size_in_bytes = input_buffer->size();
unsigned int left_to_decode_in_bytes = input_buffer->size();
AAC_DECODER_ERROR error = aacDecoder_Fill(
decoder_, &data, &data_size_in_bytes, &left_to_decode_in_bytes);
if (error != AAC_DEC_OK) {
SB_LOG(ERROR) << "aacDecoder_Fill() failed with result : " << error;
error_cb_(kSbPlayerErrorDecode,
FormatString("aacDecoder_Fill() failed with result %d.", error));
return false;
}
// Returned |left_to_decode_in_bytes| should always be 0 as DecodeFrame() will
// be called immediately on the same thread.
SB_DCHECK(left_to_decode_in_bytes == 0);
return true;
}
bool FdkAacAudioDecoder::ReadFromFdkDecoder(DecodeMode mode) {
SB_DCHECK(mode == kDecodeModeFlush || mode == kDecodeModeDoNotFlush);
int flags = mode == kDecodeModeFlush ? AACDEC_FLUSH : 0;
AAC_DECODER_ERROR error = aacDecoder_DecodeFrame(
decoder_, reinterpret_cast<INT_PCM*>(output_buffer_),
kMaxOutputBufferSizeInBytes / sizeof(INT_PCM), flags);
if (error != AAC_DEC_OK) {
error_cb_(
kSbPlayerErrorDecode,
FormatString("aacDecoder_DecodeFrame() failed with result %d.", error));
return false;
}
TryToUpdateStreamInfo();
SB_DCHECK(has_stream_info_);
if (audio_data_to_discard_in_bytes_ >= decoded_audio_size_in_bytes_) {
// Discard all decoded data in |output_buffer_|.
audio_data_to_discard_in_bytes_ -= decoded_audio_size_in_bytes_;
return true;
}
// Discard the initial |audio_data_to_discard_in_bytes_| in |output_buffer_|.
int offset_in_bytes = audio_data_to_discard_in_bytes_;
audio_data_to_discard_in_bytes_ = 0;
TryToOutputDecodedAudio(output_buffer_ + offset_in_bytes,
decoded_audio_size_in_bytes_ - offset_in_bytes);
return true;
}
void FdkAacAudioDecoder::TryToUpdateStreamInfo() {
if (has_stream_info_) {
return;
}
CStreamInfo* stream_info = aacDecoder_GetStreamInfo(decoder_);
SB_DCHECK(stream_info);
num_channels_ = stream_info->numChannels;
samples_per_second_ = stream_info->sampleRate;
decoded_audio_size_in_bytes_ =
sizeof(int16_t) * stream_info->frameSize * num_channels_;
audio_data_to_discard_in_bytes_ =
sizeof(int16_t) * stream_info->outputDelay * num_channels_;
has_stream_info_ = true;
}
void FdkAacAudioDecoder::TryToOutputDecodedAudio(const uint8_t* data,
int size_in_bytes) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(has_stream_info_);
while (size_in_bytes > 0 && !decoding_input_buffers_.empty()) {
if (!partially_decoded_audio_) {
SB_DCHECK(partially_decoded_audio_data_in_bytes_ == 0);
partially_decoded_audio_ = new DecodedAudio(
num_channels_, kSbMediaAudioSampleTypeInt16Deprecated,
kSbMediaAudioFrameStorageTypeInterleaved,
decoding_input_buffers_.front()->timestamp(),
decoded_audio_size_in_bytes_);
}
int freespace =
static_cast<int>(partially_decoded_audio_->size_in_bytes()) -
partially_decoded_audio_data_in_bytes_;
if (size_in_bytes >= freespace) {
memcpy(partially_decoded_audio_->data() +
partially_decoded_audio_data_in_bytes_,
data, freespace);
data += freespace;
size_in_bytes -= freespace;
SB_DCHECK(decoding_input_buffers_.front()->timestamp() ==
partially_decoded_audio_->timestamp());
const auto& sample_info =
decoding_input_buffers_.front()->audio_sample_info();
partially_decoded_audio_->AdjustForDiscardedDurations(
samples_per_second_, sample_info.discarded_duration_from_front,
sample_info.discarded_duration_from_back);
decoding_input_buffers_.pop();
decoded_audios_.push(partially_decoded_audio_);
Schedule(output_cb_);
partially_decoded_audio_ = nullptr;
partially_decoded_audio_data_in_bytes_ = 0;
continue;
}
memcpy(partially_decoded_audio_->data() +
partially_decoded_audio_data_in_bytes_,
data, size_in_bytes);
partially_decoded_audio_data_in_bytes_ += size_in_bytes;
return;
}
}
} // namespace libfdkaac
} // namespace shared
} // namespace starboard