[go: nahoru, domu]

blob: 0b2618212c16b84ea7a919979c847ffa8caaf3e5 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/android/ndk_audio_encoder.h"
#include <aaudio/AAudio.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaError.h>
#include <media/NdkMediaFormat.h>
#include <memory>
#include <optional>
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/sequence_checker.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "media/base/android/media_codec_util.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_encoder.h"
#include "media/base/audio_sample_types.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/converting_audio_fifo.h"
#include "media/base/encoder_status.h"
#include "media/base/media_util.h"
#include "media/base/sample_format.h"
#include "media/base/timestamp_constants.h"
#include "media/gpu/android/ndk_media_codec_wrapper.h"
#pragma clang attribute push DEFAULT_REQUIRES_ANDROID_API( \
NDK_MEDIA_CODEC_MIN_API)
namespace media {
struct AMediaFormatDeleter {
inline void operator()(AMediaFormat* ptr) const {
if (ptr) {
AMediaFormat_delete(ptr);
}
}
};
// AAC uses a frame size of 1024 samples.
constexpr int kAacFramesPerBuffer = 1024;
// Chosen since this offers high quality audio, while still saving some space.
// Apps might set a lower value for voice-only RTC applications, and a higher
// value for encoding music.
constexpr int kDefaultAacBitrate = 192000;
using MediaFormatPtr = std::unique_ptr<AMediaFormat, AMediaFormatDeleter>;
AudioEncoder::AacOutputFormat GetOutputFormat(
const AudioEncoder::Options options) {
return options.aac.value_or(AudioEncoder::AacOptions()).format;
}
MediaFormatPtr CreateAudioParams(const AudioEncoder::Options& options,
std::string_view mime_type) {
MediaFormatPtr result(AMediaFormat_new());
AMediaFormat_setString(result.get(), AMEDIAFORMAT_KEY_MIME, mime_type.data());
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_CHANNEL_COUNT,
options.channels);
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_SAMPLE_RATE,
options.sample_rate);
// AMediaCodec uses signed 16 bits input by default.
const int input_size =
sizeof(int16_t) * kAacFramesPerBuffer * options.channels;
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_MAX_INPUT_SIZE,
input_size);
// TODO(crbug.com/1421301) Consider adding HE-AAC profile support.
if (options.bitrate_mode) {
constexpr int32_t BITRATE_MODE_VBR = 1;
constexpr int32_t BITRATE_MODE_CBR = 2;
switch (*options.bitrate_mode) {
case media::AudioEncoder::BitrateMode::kConstant:
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_BITRATE_MODE,
BITRATE_MODE_CBR);
break;
case media::AudioEncoder::BitrateMode::kVariable:
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_BITRATE_MODE,
BITRATE_MODE_VBR);
break;
}
}
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_BIT_RATE,
options.bitrate.value_or(kDefaultAacBitrate));
auto format = GetOutputFormat(options);
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_IS_ADTS,
format == AudioEncoder::AacOutputFormat::ADTS ? 1 : 0);
return result;
}
NdkAudioEncoder::NdkAudioEncoder(
scoped_refptr<base::SequencedTaskRunner> runner)
: task_runner_(std::move(runner)) {}
NdkAudioEncoder::~NdkAudioEncoder() {
ClearMediaCodec();
}
bool NdkAudioEncoder::CreateAndStartMediaCodec() {
auto mime_type =
MediaCodecUtil::CodecToAndroidMimeType(options_.codec, kSampleFormatS16);
media_codec_ =
NdkMediaCodecWrapper::CreateByMimeType(mime_type, this, task_runner_);
if (!media_codec_) {
LogError({EncoderStatus::Codes::kEncoderInitializationError,
"Could not create AMediaCodec"});
return false;
}
auto aac_format = CreateAudioParams(options_, mime_type);
media_status_t status =
AMediaCodec_configure(media_codec_->codec(), aac_format.get(), nullptr,
nullptr, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
if (status != AMEDIA_OK) {
LogError({EncoderStatus::Codes::kEncoderInitializationError,
base::StringPrintf("Could not create AMediaCodec. Status: %d",
status)});
return false;
}
status = media_codec_->Start();
if (status != AMEDIA_OK) {
LogError({EncoderStatus::Codes::kEncoderInitializationError,
base::StringPrintf("Could not start AMediaCodec. Status: %d",
status)});
return false;
}
return true;
}
void NdkAudioEncoder::Initialize(const Options& options,
OutputCB output_callback,
EncoderStatusCB done_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
done_cb = BindCallbackToCurrentLoopIfNeeded(std::move(done_cb));
// Check for `fifo_` instead of `media_codec_`, as `media_codec_` is reset
// during a flush.
if (fifo_) {
LogAndReportError({EncoderStatus::Codes::kEncoderInitializeTwice,
"Encoder initialized twice"},
std::move(done_cb));
return;
}
if (options.codec != AudioCodec::kAAC) {
LogAndReportError({EncoderStatus::Codes::kEncoderInitializationError,
"NdkAudioEncoder only supports AAC"},
std::move(done_cb));
return;
}
options_ = options;
if (!CreateAndStartMediaCodec()) {
ReportPendingError(std::move(done_cb));
return;
}
output_cb_ = BindCallbackToCurrentLoopIfNeeded(std::move(output_callback));
output_params_.Reset(
AudioParameters::Format::AUDIO_PCM_LINEAR,
ChannelLayoutConfig(GuessChannelLayout(options_.channels),
options_.channels),
options_.sample_rate, kAacFramesPerBuffer);
// `fifo_` will upmix/downmix and repacketize inputs to make sure there are
// the correct number of channels and samples per buffer, without resampling.
fifo_ = std::make_unique<ConvertingAudioFifo>(output_params_, output_params_);
input_timestamp_tracker_ =
std::make_unique<AudioTimestampHelper>(options_.sample_rate);
output_timestamp_tracker_ =
std::make_unique<AudioTimestampHelper>(options_.sample_rate);
ReportOk(std::move(done_cb));
}
void NdkAudioEncoder::Encode(std::unique_ptr<AudioBus> audio_bus,
base::TimeTicks capture_time,
EncoderStatusCB done_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
done_cb = BindCallbackToCurrentLoopIfNeeded(std::move(done_cb));
if (error_occurred_) {
ReportPendingError(std::move(done_cb));
return;
}
if (flush_state_ == FlushState::kNeedsMediaCodec) {
CHECK(!media_codec_);
if (!CreateAndStartMediaCodec()) {
ReportPendingError(std::move(done_cb));
return;
}
flush_state_ = FlushState::kNone;
}
if (!media_codec_) {
LogAndReportError(EncoderStatus::Codes::kEncoderInitializeNeverCompleted,
std::move(done_cb));
return;
}
if (flush_state_ != FlushState::kNone) {
CHECK(pending_flush_cb_);
LogAndReportError({EncoderStatus::Codes::kEncoderFailedFlush,
"Received Encode() before Flush() completed."},
std::move(pending_flush_cb_));
ReportPendingError(std::move(done_cb));
return;
}
if (input_timestamp_tracker_->base_timestamp() == kNoTimestamp) {
input_timestamp_tracker_->SetBaseTimestamp(capture_time -
base::TimeTicks());
output_timestamp_tracker_->SetBaseTimestamp(capture_time -
base::TimeTicks());
}
fifo_->Push(std::move(audio_bus));
FeedAllInputs();
if (error_occurred_) {
ReportPendingError(std::move(done_cb));
} else {
ReportOk(std::move(done_cb));
}
}
void NdkAudioEncoder::Flush(EncoderStatusCB done_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
done_cb = BindCallbackToCurrentLoopIfNeeded(std::move(done_cb));
if (error_occurred_) {
ReportPendingError(std::move(done_cb));
return;
}
// We should have been initialized already.
if (!fifo_) {
LogAndReportError({EncoderStatus::Codes::kEncoderInitializeNeverCompleted,
"Cannot flush uninitialized encoder."},
std::move(done_cb));
return;
}
if (flush_state_ != FlushState::kNone) {
LogAndReportError({EncoderStatus::Codes::kEncoderIllegalState,
"Cannot start new Flush() before first one completes."},
std::move(done_cb));
return;
}
// Nothing to flush if we never fed input to the encoder.
if (input_timestamp_tracker_->base_timestamp() == kNoTimestamp) {
ReportOk(std::move(done_cb));
return;
}
CHECK(!pending_flush_cb_);
pending_flush_cb_ = std::move(done_cb);
flush_state_ = FlushState::kFlushingInputs;
fifo_->Flush();
FeedAllInputs();
}
void NdkAudioEncoder::FeedAllInputs() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
while (InputReady()) {
FeedInput(fifo_->PeekOutput());
fifo_->PopOutput();
}
if (error_occurred_ && pending_flush_cb_) {
flush_state_ = FlushState::kNone;
ReportPendingError(std::move(pending_flush_cb_));
return;
}
// When we have fed all inputs, send an EOS to `media_codec_`.
if (flush_state_ == FlushState::kFlushingInputs) {
MaybeFeedEos();
}
}
void NdkAudioEncoder::MaybeFeedEos() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_EQ(flush_state_, FlushState::kFlushingInputs);
CHECK(!error_occurred_);
CHECK(pending_flush_cb_);
// Don't send EOS until all inputs have been fed.
if (fifo_->HasOutput()) {
return;
}
// We don't have a buffer to send an EOS yet.
if (!media_codec_->HasInput()) {
return;
}
FeedEos();
}
void NdkAudioEncoder::FeedEos() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_EQ(flush_state_, FlushState::kFlushingInputs);
size_t capacity = 0;
const size_t buffer_idx = media_codec_->TakeInput();
uint8_t* buffer_ptr =
AMediaCodec_getInputBuffer(media_codec_->codec(), buffer_idx, &capacity);
if (!buffer_ptr) {
LogAndReportError({EncoderStatus::Codes::kEncoderFailedFlush,
"Unable to get input buffer during flush"},
std::move(pending_flush_cb_));
return;
}
const auto timestamp_us =
input_timestamp_tracker_->GetTimestamp().InMicroseconds();
media_status_t status = AMediaCodec_queueInputBuffer(
media_codec_->codec(), buffer_idx, /*offset=*/0, /*size=*/0, timestamp_us,
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
flush_state_ = FlushState::kPendingEOS;
if (status != AMEDIA_OK) {
LogAndReportError(
{EncoderStatus::Codes::kEncoderFailedFlush,
base::StringPrintf("Error queueing EOS input buffer: status=%d",
status)},
std::move(pending_flush_cb_));
}
}
bool NdkAudioEncoder::InputReady() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !error_occurred_ && media_codec_->HasInput() && fifo_->HasOutput();
}
void NdkAudioEncoder::FeedInput(const AudioBus* audio_bus) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(InputReady());
CHECK(!error_occurred_);
const size_t buffer_idx = media_codec_->TakeInput();
size_t capacity = 0;
uint8_t* buffer_ptr =
AMediaCodec_getInputBuffer(media_codec_->codec(), buffer_idx, &capacity);
if (!buffer_ptr) {
LogError({EncoderStatus::Codes::kEncoderFailedEncode,
"Unable to get input buffer"});
return;
}
const size_t bytes_per_frame =
audio_bus->channels() * SampleFormatToBytesPerChannel(kSampleFormatS16);
const size_t total_bytes = bytes_per_frame * audio_bus->frames();
if (capacity < total_bytes) {
LogError({EncoderStatus::Codes::kEncoderFailedEncode,
base::StringPrintf(
"Input capacity too small: needed=%zu, capacity=%zu",
total_bytes, capacity)});
return;
}
// MediaCodec uses signed 16bit PCM encoding by default.
// Configuring the encoder to use float PCM did not work in tests.
audio_bus->ToInterleaved<SignedInt16SampleTypeTraits>(
audio_bus->frames(), reinterpret_cast<int16_t*>(buffer_ptr));
CHECK_EQ(audio_bus->frames(), kAacFramesPerBuffer);
const auto timestamp_us =
input_timestamp_tracker_->GetTimestamp().InMicroseconds();
input_timestamp_tracker_->AddFrames(audio_bus->frames());
media_status_t status =
AMediaCodec_queueInputBuffer(media_codec_->codec(), buffer_idx,
/*offset=*/0, total_bytes, timestamp_us,
/*flags=*/0);
if (status != AMEDIA_OK) {
LogError(
{EncoderStatus::Codes::kEncoderFailedEncode,
base::StringPrintf("Error queueing input buffer: status=%d", status)});
}
}
void NdkAudioEncoder::CompleteFlush() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_EQ(flush_state_, FlushState::kPendingEOS);
input_timestamp_tracker_->SetBaseTimestamp(kNoTimestamp);
output_timestamp_tracker_->SetBaseTimestamp(kNoTimestamp);
ClearMediaCodec();
flush_state_ = FlushState::kNeedsMediaCodec;
ReportOk(std::move(pending_flush_cb_));
}
void NdkAudioEncoder::ClearMediaCodec() {
if (!media_codec_) {
return;
}
media_codec_->Stop();
media_codec_.reset();
}
bool NdkAudioEncoder::DrainConfig() {
CHECK(media_codec_->HasOutput());
NdkMediaCodecWrapper::OutputInfo output_buffer = media_codec_->PeekOutput();
AMediaCodecBufferInfo& mc_buffer_info = output_buffer.info;
// Check whether the first buffer in the queue contains config data.
if ((mc_buffer_info.flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) {
return false;
}
// We already have the info we need from `output_buffer`
std::ignore = media_codec_->TakeOutput();
size_t capacity = 0;
uint8_t* buf_data = AMediaCodec_getOutputBuffer(
media_codec_->codec(), output_buffer.buffer_index, &capacity);
if (!buf_data) {
LogError({EncoderStatus::Codes::kEncoderFailedEncode,
"Can't obtain config output buffer from media codec"});
return false;
}
const size_t mc_buffer_size = base::checked_cast<size_t>(mc_buffer_info.size);
if (mc_buffer_info.offset + mc_buffer_size > capacity) {
LogError(
{EncoderStatus::Codes::kEncoderFailedEncode,
base::StringPrintf("Invalid config output buffer layout."
"offset: %d size: %zu capacity: %zu",
mc_buffer_info.offset, mc_buffer_size, capacity)});
return false;
}
const uint8_t* data_start = buf_data + mc_buffer_info.offset;
if (GetOutputFormat(options_) == AudioEncoder::AacOutputFormat::ADTS) {
NullMediaLog null_log;
if (!aac_config_parser_.Parse(base::make_span(data_start, mc_buffer_size),
&null_log)) {
LogError({EncoderStatus::Codes::kInvalidOutputBuffer,
"Could not parse output config"});
return false;
}
} else {
// Output format is AudioEncoder::AacOutputFormat::AAC
codec_desc_.assign(data_start, data_start + mc_buffer_size);
}
AMediaCodec_releaseOutputBuffer(media_codec_->codec(),
output_buffer.buffer_index, false);
return true;
}
void NdkAudioEncoder::DrainOutput() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (error_occurred_) {
return;
}
if (!media_codec_->HasOutput()) {
return;
}
if (DrainConfig()) {
return;
}
NdkMediaCodecWrapper::OutputInfo output_buffer = media_codec_->TakeOutput();
AMediaCodecBufferInfo& mc_buffer_info = output_buffer.info;
// The current output buffer should be data, and not a config.
CHECK_EQ(mc_buffer_info.flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG, 0u);
if ((mc_buffer_info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
CompleteFlush();
return;
}
size_t capacity = 0;
uint8_t* buf_data = AMediaCodec_getOutputBuffer(
media_codec_->codec(), output_buffer.buffer_index, &capacity);
if (!buf_data) {
LogError({EncoderStatus::Codes::kEncoderFailedEncode,
"Unable to get output buffer"});
return;
}
const size_t mc_buffer_size = base::checked_cast<size_t>(mc_buffer_info.size);
const int32_t mc_buffer_offset = mc_buffer_info.offset;
if (mc_buffer_size + mc_buffer_offset > capacity) {
LogError(
{EncoderStatus::Codes::kEncoderFailedEncode,
base::StringPrintf("Invalid output buffer layout."
"offset: %d size: %zu capacity: %zu",
mc_buffer_info.offset, mc_buffer_size, capacity)});
return;
}
auto output_format = GetOutputFormat(options_);
int output_data_size;
auto mc_data = base::make_span(buf_data + mc_buffer_offset, mc_buffer_size);
std::unique_ptr<uint8_t[]> output_data;
if (output_format == AudioEncoder::AacOutputFormat::ADTS) {
int adts_header_size = 0;
output_data =
aac_config_parser_.CreateAdtsFromEsds(mc_data, &adts_header_size);
output_data_size = mc_data.size() + adts_header_size;
if (!output_data) {
AMediaCodec_releaseOutputBuffer(media_codec_->codec(),
output_buffer.buffer_index, false);
LogError({EncoderStatus::Codes::kFormatConversionError,
"Unable to convert to ADTS"});
return;
}
} else {
output_data = std::make_unique<uint8_t[]>(mc_data.size());
memcpy(output_data.get(), mc_data.data(), mc_data.size_bytes());
output_data_size = mc_data.size();
}
AMediaCodec_releaseOutputBuffer(media_codec_->codec(),
output_buffer.buffer_index, false);
const auto timestamp =
output_timestamp_tracker_->GetTimestamp() + base::TimeTicks();
output_timestamp_tracker_->AddFrames(kAacFramesPerBuffer);
std::optional<CodecDescription> desc;
if (!codec_desc_.empty()) {
desc = codec_desc_;
codec_desc_.clear();
}
output_cb_.Run(
EncodedAudioBuffer(
output_params_, std::move(output_data), output_data_size, timestamp,
output_timestamp_tracker_->GetFrameDuration(kAacFramesPerBuffer)),
desc);
}
void NdkAudioEncoder::OnInputAvailable() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
FeedAllInputs();
}
void NdkAudioEncoder::OnOutputAvailable() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DrainOutput();
}
void NdkAudioEncoder::OnError(media_status_t error) {
LogError({EncoderStatus::Codes::kEncoderFailedEncode,
base::StringPrintf("MediaCodec async error:%d", error)});
}
void NdkAudioEncoder::LogAndReportError(EncoderStatus status,
EncoderStatusCB done_cb) {
LogError(status);
ReportPendingError(std::move(done_cb));
}
void NdkAudioEncoder::LogError(EncoderStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!status.is_ok());
LOG(ERROR) << "ReportError(): code=" << static_cast<int>(status.code())
<< ", message=" << status.message();
if (!error_occurred_) {
error_occurred_ = true;
pending_error_status_ = status;
}
}
void NdkAudioEncoder::ReportPendingError(EncoderStatusCB done_cb) {
CHECK(error_occurred_);
// Already reported error.
if (!pending_error_status_) {
std::move(done_cb).Run({EncoderStatus::Codes::kEncoderIllegalState,
"Encoder already reported error"});
return;
}
std::move(done_cb).Run(*pending_error_status_);
pending_error_status_ = std::nullopt;
}
void NdkAudioEncoder::ReportOk(EncoderStatusCB done_cb) {
CHECK(!error_occurred_);
CHECK(!pending_error_status_);
std::move(done_cb).Run(EncoderStatus::Codes::kOk);
}
} // namespace media
#pragma clang attribute pop