1
0
Fork 0
android_external_ffmpeg_codec2/C2FFMPEGAudioDecodeComponent.cpp
2025-06-27 00:01:59 -05:00

730 lines
25 KiB
C++

/*
* Copyright 2022 Michael Goffioul <michael.goffioul@gmail.com>
*
* 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.
*/
#define LOG_TAG "C2FFMPEGAudioDecodeComponent"
#include <android-base/stringprintf.h>
#include <log/log.h>
#include <SimpleC2Interface.h>
#include "C2FFMPEGAudioDecodeComponent.h"
#include <libswresample/swresample_internal.h>
#define DEBUG_FRAMES 0
#define DEBUG_EXTRADATA 0
namespace android {
static enum AVSampleFormat convertFormatToFFMPEG(C2Config::pcm_encoding_t encoding) {
switch (encoding) {
case C2Config::PCM_8: return AV_SAMPLE_FMT_U8;
case C2Config::PCM_16: return AV_SAMPLE_FMT_S16;
case C2Config::PCM_32: return AV_SAMPLE_FMT_S32;
case C2Config::PCM_FLOAT: return AV_SAMPLE_FMT_FLT;
default: return AV_SAMPLE_FMT_NONE;
}
}
__unused
static C2Config::pcm_encoding_t convertFormatToC2(enum AVSampleFormat format) {
switch (format) {
case AV_SAMPLE_FMT_U8: return C2Config::PCM_8;
case AV_SAMPLE_FMT_S16: return C2Config::PCM_16;
case AV_SAMPLE_FMT_S32: return C2Config::PCM_32;
case AV_SAMPLE_FMT_FLT: return C2Config::PCM_FLOAT;
default: return C2Config::PCM_16;
}
}
// Helper structures to encapsulate the specific codec behaviors.
// Currently only used to process extradata.
struct CodecHelper {
virtual ~CodecHelper() {}
virtual c2_status_t onCodecConfig(AVCodecContext* mCtx, C2ReadView* inBuffer);
virtual c2_status_t onOpen(AVCodecContext* mCtx);
virtual c2_status_t onOpened(AVCodecContext* mCtx);
};
c2_status_t CodecHelper::onCodecConfig(AVCodecContext* mCtx, C2ReadView* inBuffer) {
int orig_extradata_size = mCtx->extradata_size;
int add_extradata_size = inBuffer->capacity();
#if DEBUG_EXTRADATA
ALOGD("CodecHelper::onCodecConfig: add = %u, current = %d", add_extradata_size, orig_extradata_size);
#endif
mCtx->extradata_size += add_extradata_size;
mCtx->extradata = (uint8_t *) realloc(mCtx->extradata, mCtx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
if (! mCtx->extradata) {
ALOGE("CodecHelper::onCodecConfig: ffmpeg audio decoder failed to alloc extradata memory.");
return C2_NO_MEMORY;
}
memcpy(mCtx->extradata + orig_extradata_size, inBuffer->data(), add_extradata_size);
memset(mCtx->extradata + mCtx->extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
return C2_OK;
}
c2_status_t CodecHelper::onOpen(AVCodecContext* mCtx) {
#if DEBUG_EXTRADATA
ALOGD("CodecHelper::onOpen: extradata = %d", mCtx->extradata_size);
#else
// Silence compilation warning.
(void)mCtx;
#endif
return C2_OK;
}
c2_status_t CodecHelper::onOpened(AVCodecContext* mCtx) {
(void)mCtx;
return C2_OK;
}
struct VorbisCodecHelper : public CodecHelper {
VorbisCodecHelper();
~VorbisCodecHelper();
c2_status_t onCodecConfig(AVCodecContext* mCtx, C2ReadView* rView);
c2_status_t onOpen(AVCodecContext* mCtx);
uint8_t* mHeader[3];
int mHeaderLen[3];
};
VorbisCodecHelper::VorbisCodecHelper()
: CodecHelper(),
mHeader{ NULL, NULL, NULL },
mHeaderLen{ 0, 0, 0 } {
}
VorbisCodecHelper::~VorbisCodecHelper() {
for (int i = 0; i < 3; i++) {
if (mHeader[i]) {
av_free(mHeader[i]);
mHeader[i] = NULL;
}
mHeaderLen[i] = 0;
}
}
c2_status_t VorbisCodecHelper::onCodecConfig(AVCodecContext* mCtx __unused, C2ReadView* inBuffer) {
const uint8_t* data = inBuffer->data();
int len = inBuffer->capacity();
int index = 0;
switch (data[0]) {
case 1: index = 0; break;
case 3: index = 1; break;
case 5: index = 2; break;
default:
ALOGE("VorbisCodecHelper::onCodecConfig: invalid vorbis codec config (%02x)", data[0]);
return C2_BAD_VALUE;
}
if (mHeader[index]) {
ALOGW("VorbisCodecHelper::onCodecConfig: overwriting header[%d]", index);
av_free(mHeader[index]);
}
mHeader[index] = (uint8_t*)av_mallocz(len);
if (! mHeader[index]) {
ALOGE("VorbisCodecHelper::onCodecConfig: oom for vorbis extradata");
return C2_NO_MEMORY;
}
memcpy(mHeader[index], data, len);
mHeaderLen[index] = len;
#if DEBUG_EXTRADATA
ALOGD("VorbisCodecHelper::onCodecConfig: found header[%d] = %d", index, len);
#endif
return C2_OK;
}
c2_status_t VorbisCodecHelper::onOpen(AVCodecContext* mCtx) {
// Don't generate extradata twice
if (! mCtx->extradata) {
if (! setup_vorbis_extradata(&mCtx->extradata,
&mCtx->extradata_size,
(const uint8_t**)mHeader,
mHeaderLen)) {
return C2_NO_MEMORY;
}
#if DEBUG_EXTRADATA
ALOGD("VorbisCodecHelper::onOpen: extradata = %d", mCtx->extradata_size);
#endif
}
return C2_OK;
}
struct Ac3CodecHelper : public CodecHelper {
c2_status_t onOpened(AVCodecContext* mCtx);
};
c2_status_t Ac3CodecHelper::onOpened(AVCodecContext* mCtx) {
int err = av_opt_set_chlayout(mCtx->priv_data, "downmix", &mCtx->ch_layout, 0);
if (err < 0) {
ALOGE("Ac3CodecHelper::onOpened: failed to set downmix = %d: %s (%08x)",
mCtx->ch_layout.nb_channels, av_err2str(err), err);
} else {
ALOGD("Ac3CodecHelper::onOpened: set downmix = %d", mCtx->ch_layout.nb_channels);
}
return C2_OK;
}
CodecHelper* createCodecHelper(enum AVCodecID codec_id) {
switch (codec_id) {
case AV_CODEC_ID_AC3:
case AV_CODEC_ID_EAC3:
return new Ac3CodecHelper();
case AV_CODEC_ID_VORBIS:
return new VorbisCodecHelper();
default:
return new CodecHelper();
}
}
C2FFMPEGAudioDecodeComponent::C2FFMPEGAudioDecodeComponent(
const C2FFMPEGComponentInfo* componentInfo,
const std::shared_ptr<C2FFMPEGAudioDecodeInterface>& intf)
: SimpleC2Component(std::make_shared<SimpleInterface<C2FFMPEGAudioDecodeInterface>>(componentInfo->name, 0, intf)),
mInfo(componentInfo),
mIntf(intf),
mCodecID(componentInfo->codecID),
mCtx(NULL),
mFrame(NULL),
mPacket(NULL),
mFFMPEGInitialized(false),
mCodecAlreadyOpened(false),
mEOSSignalled(false),
mSwrCtx(NULL),
mTargetSampleFormat(AV_SAMPLE_FMT_NONE),
mTargetSampleRate(44100),
mTargetChannels(1) {
ALOGD("C2FFMPEGAudioDecodeComponent: mediaType = %s", componentInfo->mediaType);
}
C2FFMPEGAudioDecodeComponent::~C2FFMPEGAudioDecodeComponent() {
ALOGD("~C2FFMPEGAudioDecodeComponent: mCtx = %p", mCtx);
onRelease();
}
c2_status_t C2FFMPEGAudioDecodeComponent::initDecoder() {
if (! mFFMPEGInitialized) {
if (initFFmpeg() != C2_OK) {
ALOGE("initDecoder: FFMPEG initialization failed.");
return C2_NO_INIT;
}
mFFMPEGInitialized = true;
}
mCtx = avcodec_alloc_context3(NULL);
if (! mCtx) {
ALOGE("initDecoder: avcodec_alloc_context failed.");
return C2_NO_MEMORY;
}
mCtx->codec_type = AVMEDIA_TYPE_AUDIO;
mCtx->codec_id = mCodecID;
updateAudioParameters();
av_channel_layout_default(&mCtx->ch_layout, mTargetChannels);
mCtx->sample_rate = mTargetSampleRate;
mCtx->bit_rate = 0;
mCtx->sample_fmt = mTargetSampleFormat;
// Avoid resampling if possible, ask the codec for the target format.
mCtx->request_sample_fmt = mCtx->sample_fmt;
mCodecHelper = createCodecHelper(mCtx->codec_id);
ALOGD("initDecoder: %p [%s], %s - sr=%d, ch=%d, fmt=%s",
mCtx, avcodec_get_name(mCtx->codec_id), mInfo->mediaType,
mCtx->sample_rate, mCtx->ch_layout.nb_channels, av_get_sample_fmt_name(mCtx->sample_fmt));
return C2_OK;
}
c2_status_t C2FFMPEGAudioDecodeComponent::openDecoder() {
if (mCodecAlreadyOpened) {
return C2_OK;
}
mCodecHelper->onOpen(mCtx);
// Find decoder
mCtx->codec = avcodec_find_decoder(mCtx->codec_id);
if (! mCtx->codec) {
ALOGE("openDecoder: ffmpeg audio decoder failed to find codec %d", mCtx->codec_id);
return C2_NOT_FOUND;
}
// Configure decoder
mCtx->workaround_bugs = 1;
mCtx->idct_algo = 0;
mCtx->skip_frame = AVDISCARD_DEFAULT;
mCtx->skip_idct = AVDISCARD_DEFAULT;
mCtx->skip_loop_filter = AVDISCARD_DEFAULT;
mCtx->error_concealment = 3;
mCtx->flags |= AV_CODEC_FLAG_BITEXACT;
ALOGD("openDecoder: begin to open ffmpeg audio decoder(%s), mCtx sample_rate: %d, channels: %d",
avcodec_get_name(mCtx->codec_id), mCtx->sample_rate, mCtx->ch_layout.nb_channels);
int err = avcodec_open2(mCtx, mCtx->codec, NULL);
if (err < 0) {
ALOGE("openDecoder: ffmpeg audio decoder failed to initialize.(%s)", av_err2str(err));
return C2_NO_INIT;
}
mCodecAlreadyOpened = true;
mCodecHelper->onOpened(mCtx);
ALOGD("openDecoder: open ffmpeg audio decoder(%s) success, mCtx sample_rate: %d, "
"channels: %d, sample_fmt: %s, bits_per_coded_sample: %d, bits_per_raw_sample: %d",
avcodec_get_name(mCtx->codec_id),
mCtx->sample_rate, mCtx->ch_layout.nb_channels,
av_get_sample_fmt_name(mCtx->sample_fmt),
mCtx->bits_per_coded_sample, mCtx->bits_per_raw_sample);
mFrame = av_frame_alloc();
if (! mFrame) {
ALOGE("openDecoder: oom for audio frame");
return C2_NO_MEMORY;
}
return C2_OK;
}
void C2FFMPEGAudioDecodeComponent::deInitDecoder() {
ALOGD("deInitDecoder: %p", mCtx);
if (mCtx) {
if (avcodec_is_open(mCtx)) {
avcodec_flush_buffers(mCtx);
}
avcodec_free_context(&mCtx);
mCodecAlreadyOpened = false;
}
if (mFrame) {
av_frame_free(&mFrame);
mFrame = NULL;
}
if (mPacket) {
av_packet_free(&mPacket);
mPacket = NULL;
}
if (mSwrCtx) {
swr_free(&mSwrCtx);
}
if (mCodecHelper) {
delete mCodecHelper;
mCodecHelper = NULL;
}
mEOSSignalled = false;
}
c2_status_t C2FFMPEGAudioDecodeComponent::processCodecConfig(C2ReadView* inBuffer) {
#if DEBUG_EXTRADATA
ALOGD("processCodecConfig: inBuffer = %d", inBuffer->capacity());
#endif
if (! mCodecAlreadyOpened) {
return mCodecHelper->onCodecConfig(mCtx, inBuffer);
} else {
ALOGW("processCodecConfig: decoder is already opened, ignoring %d bytes", inBuffer->capacity());
}
return C2_OK;
}
c2_status_t C2FFMPEGAudioDecodeComponent::sendInputBuffer(
C2ReadView *inBuffer, int64_t timestamp) {
if (!mPacket) {
mPacket = av_packet_alloc();
if (!mPacket) {
ALOGE("sendInputBuffer: oom for audio packet");
return C2_NO_MEMORY;
}
}
mPacket->data = inBuffer ? const_cast<uint8_t *>(inBuffer->data()) : NULL;
mPacket->size = inBuffer ? inBuffer->capacity() : 0;
mPacket->pts = timestamp;
mPacket->dts = timestamp;
int err = avcodec_send_packet(mCtx, mPacket);
av_packet_unref(mPacket);
if (err < 0) {
ALOGE("sendInputBuffer: failed to send data to decoder err = %d", err);
// Don't report error to client.
}
return C2_OK;
}
c2_status_t C2FFMPEGAudioDecodeComponent::receiveFrame(bool* hasFrame) {
int err = avcodec_receive_frame(mCtx, mFrame);
if (err == 0) {
*hasFrame = true;
} else if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
*hasFrame = false;
} else {
ALOGE("receiveFrame: failed to receive frame from decoder err = %d", err);
// Don't report error to client.
}
return C2_OK;
}
c2_status_t C2FFMPEGAudioDecodeComponent::getOutputBuffer(C2WriteView* outBuffer) {
if (! mSwrCtx ||
mSwrCtx->in_sample_fmt != mFrame->format ||
mSwrCtx->in_sample_rate != mFrame->sample_rate ||
av_channel_layout_compare(&mSwrCtx->in_ch_layout, &mFrame->ch_layout) != 0 ||
mSwrCtx->out_sample_fmt != mTargetSampleFormat ||
mSwrCtx->out_sample_rate != mTargetSampleRate ||
mSwrCtx->out_ch_layout.nb_channels != mTargetChannels) {
if (mSwrCtx) {
swr_free(&mSwrCtx);
}
AVChannelLayout newLayout;
av_channel_layout_default(&newLayout, mTargetChannels);
swr_alloc_set_opts2(&mSwrCtx,
&newLayout, mTargetSampleFormat, mTargetSampleRate,
&mFrame->ch_layout, (enum AVSampleFormat)mFrame->format, mFrame->sample_rate,
0, NULL);
av_channel_layout_uninit(&newLayout);
if (! mSwrCtx || swr_init(mSwrCtx) < 0) {
ALOGE("getOutputBuffer: cannot create audio converter - sr=%d, ch=%d, fmt=%s => sr=%d, ch=%d, fmt=%s",
mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format),
mTargetSampleRate, mTargetChannels, av_get_sample_fmt_name(mTargetSampleFormat));
if (mSwrCtx) {
swr_free(&mSwrCtx);
}
return C2_NO_MEMORY;
}
ALOGD("getOutputBuffer: created audio converter - sr=%d, ch=%d, fmt=%s => sr=%d, ch=%d, fmt=%s",
mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format),
mTargetSampleRate, mTargetChannels, av_get_sample_fmt_name(mTargetSampleFormat));
}
uint8_t* out[1] = { outBuffer->data() };
int ret = swr_convert(mSwrCtx, out, mFrame->nb_samples, (const uint8_t**)mFrame->extended_data, mFrame->nb_samples);
if (ret < 0) {
ALOGE("getOutputBuffer: audio conversion failed");
return C2_CORRUPTED;
} else if (ret != mFrame->nb_samples) {
ALOGW("getOutputBuffer: audio conversion truncated!");
}
#if DEBUG_FRAMES
ALOGD("getOutputBuffer: audio converted - sr=%d, ch=%d, fmt=%s, #=%d => sr=%d, ch=%d, fmt=%s, #=%d(%d)",
mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format), mFrame->nb_samples,
mTargetSampleRate, mTargetChannels, av_get_sample_fmt_name(mTargetSampleFormat), mFrame->nb_samples, outBuffer->capacity());
#endif
return C2_OK;
}
void C2FFMPEGAudioDecodeComponent::updateAudioParameters() {
mTargetSampleFormat = convertFormatToFFMPEG(mIntf->getPcmEncodingInfo());
mTargetSampleRate = mIntf->getSampleRate();
mTargetChannels = mIntf->getChannelCount();
}
c2_status_t C2FFMPEGAudioDecodeComponent::onInit() {
ALOGD("onInit");
return initDecoder();
}
c2_status_t C2FFMPEGAudioDecodeComponent::onStop() {
ALOGD("onStop");
return C2_OK;
}
void C2FFMPEGAudioDecodeComponent::onReset() {
ALOGD("onReset");
deInitDecoder();
initDecoder();
}
void C2FFMPEGAudioDecodeComponent::onRelease() {
ALOGD("onRelease");
deInitDecoder();
if (mFFMPEGInitialized) {
deInitFFmpeg();
mFFMPEGInitialized = false;
}
}
c2_status_t C2FFMPEGAudioDecodeComponent::onFlush_sm() {
ALOGD("onFlush_sm");
if (mCtx && avcodec_is_open(mCtx)) {
// Make sure that the next buffer output does not still
// depend on fragments from the last one decoded.
avcodec_flush_buffers(mCtx);
mEOSSignalled = false;
}
return C2_OK;
}
void C2FFMPEGAudioDecodeComponent::process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool>& pool
) {
size_t inSize = 0u;
bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM);
C2ReadView rView = mDummyReadView;
bool hasInputBuffer = false;
bool hasFrame = false;
if (! work->input.buffers.empty()) {
rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
inSize = rView.capacity();
hasInputBuffer = true;
}
#if DEBUG_FRAMES
ALOGD("process: input flags=%08x ts=%lu idx=%lu #buf=%lu[%lu] #conf=%lu #info=%lu",
work->input.flags, work->input.ordinal.timestamp.peeku(), work->input.ordinal.frameIndex.peeku(),
work->input.buffers.size(), inSize, work->input.configUpdate.size(), work->input.infoBuffers.size());
#endif
if (mEOSSignalled) {
ALOGE("process: ignoring work while EOS reached");
work->workletsProcessed = 0u;
work->result = C2_BAD_VALUE;
return;
}
if (hasInputBuffer && rView.error()) {
ALOGE("process: read view map failed err = %d", rView.error());
work->workletsProcessed = 0u;
work->result = rView.error();
return;
}
// In all cases the work is marked as completed.
// NOTE: This has an impact on the drain operation.
work->result = C2_OK;
work->worklets.front()->output.flags = (C2FrameData::flags_t)0;
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.ordinal = work->input.ordinal;
work->workletsProcessed = 1u;
if (inSize || (eos && mCodecAlreadyOpened)) {
c2_status_t err = C2_OK;
if (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) {
work->result = processCodecConfig(&rView);
return;
}
if (! mCodecAlreadyOpened) {
err = openDecoder();
if (err != C2_OK) {
work->result = err;
return;
}
}
err = sendInputBuffer(&rView, work->input.ordinal.timestamp.peekll());
if (err != C2_OK) {
work->result = err;
return;
}
while (true) {
hasFrame = false;
err = receiveFrame(&hasFrame);
if (err != C2_OK) {
work->result = err;
return;
}
if (! hasFrame) {
break;
}
#if DEBUG_FRAMES
ALOGD("process: got frame pts=%" PRId64 " dts=%" PRId64 " ts=%" PRId64 " - sr=%d, ch=%d, fmt=%s, #=%d",
mFrame->pts, mFrame->pkt_dts, mFrame->best_effort_timestamp,
mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format),
mFrame->nb_samples);
#endif
// Always target the sample format on output port. Even if we can trigger a config update
// for the sample format, Android does not support planar formats, so if the codec uses
// such format (e.g. AC3), conversion is needed. Technically we can limit the conversion to
// planer->packed, but that means Android would also do its own conversion to the wanted
// format on output port. To avoid double conversion, target directly the wanted format.
bool needConfigUpdate = (mFrame->sample_rate != mTargetSampleRate ||
mFrame->ch_layout.nb_channels != mTargetChannels);
bool needResampling = (needConfigUpdate ||
mFrame->format != mTargetSampleFormat ||
// We only support sending audio data to Android in native order.
mFrame->ch_layout.order != AV_CHANNEL_ORDER_NATIVE);
if (needConfigUpdate) {
ALOGD("process: audio params changed - sr=%d, ch=%d, fmt=%s => sr=%d, ch=%d, fmt=%s",
mTargetSampleRate, mTargetChannels, av_get_sample_fmt_name(mTargetSampleFormat),
mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name(mTargetSampleFormat));
if (work->worklets.front()->output.buffers.size() > 0) {
// Not sure if this would ever happen, nor how to handle it...
ALOGW("process: audio params changed with non empty output buffers pending");
}
C2StreamSampleRateInfo::output sampleRate(0u, mFrame->sample_rate);
C2StreamChannelCountInfo::output channelCount(0u, mFrame->ch_layout.nb_channels);
std::vector<std::unique_ptr<C2SettingResult>> failures;
err = mIntf->config({ &sampleRate, &channelCount }, C2_MAY_BLOCK, &failures);
if (err == C2_OK) {
work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(sampleRate));
work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(channelCount));
updateAudioParameters();
} else {
ALOGE("process: config update failed err = %d", err);
work->result = C2_CORRUPTED;
return;
}
}
std::shared_ptr<C2LinearBlock> block;
int len = av_samples_get_buffer_size(NULL, mTargetChannels, mFrame->nb_samples, mTargetSampleFormat, 0);
err = pool->fetchLinearBlock(len, { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }, &block);
if (err != C2_OK) {
ALOGE("process: failed to fetch linear block for #=%d err = %d",
mFrame->nb_samples, err);
work->result = C2_CORRUPTED;
return;
}
C2WriteView wView = block->map().get();
err = wView.error();
if (err != C2_OK) {
ALOGE("process: write view map failed err = %d", err);
work->result = C2_CORRUPTED;
return;
}
if (needResampling) {
err = getOutputBuffer(&wView);
if (err != C2_OK) {
work->result = err;
return;
}
}
else {
#if DEBUG_FRAMES
ALOGD("process: no audio conversion needed");
#endif
memcpy(wView.data(), mFrame->data[0], mFrame->linesize[0]);
}
std::shared_ptr<C2Buffer> buffer = createLinearBuffer(std::move(block), 0, len);
if (mCtx->codec->capabilities & AV_CODEC_CAP_SUBFRAMES) {
auto fillWork = [buffer, &work, this](const std::unique_ptr<C2Work>& clone) {
clone->worklets.front()->output.configUpdate = std::move(work->worklets.front()->output.configUpdate);
clone->worklets.front()->output.buffers.clear();
clone->worklets.front()->output.buffers.push_back(buffer);
clone->worklets.front()->output.ordinal = clone->input.ordinal;
if (mFrame->best_effort_timestamp != AV_NOPTS_VALUE) {
work->worklets.front()->output.ordinal.timestamp = mFrame->best_effort_timestamp;
}
clone->worklets.front()->output.flags = C2FrameData::FLAG_INCOMPLETE;
clone->workletsProcessed = 1u;
clone->result = C2_OK;
};
#if DEBUG_FRAMES
ALOGD("process: send subframe buffer ts=%" PRIu64 " idx=%" PRIu64,
work->input.ordinal.timestamp.peeku(), work->input.ordinal.frameIndex.peeku());
#endif
cloneAndSend(work->input.ordinal.frameIndex.peeku(), work, fillWork);
}
else {
work->worklets.front()->output.buffers.push_back(buffer);
if (mFrame->best_effort_timestamp != AV_NOPTS_VALUE) {
work->worklets.front()->output.ordinal.timestamp = mFrame->best_effort_timestamp;
}
break;
}
}
}
#if DEBUG_FRAMES
else {
ALOGW("process: ignoring empty work");
}
#endif
if (eos) {
mEOSSignalled = true;
work->worklets.front()->output.flags = C2FrameData::FLAG_END_OF_STREAM;
}
}
c2_status_t C2FFMPEGAudioDecodeComponent::drain(
uint32_t drainMode,
const std::shared_ptr<C2BlockPool>& /* pool */
) {
ALOGD("drain: mode = %u", drainMode);
if (drainMode == NO_DRAIN) {
ALOGW("drain: NO_DRAIN is no-op");
return C2_OK;
}
if (drainMode == DRAIN_CHAIN) {
ALOGW("drain: DRAIN_CHAIN not supported");
return C2_OMITTED;
}
if (! mCodecAlreadyOpened) {
ALOGW("drain: codec not opened yet");
return C2_OK;
}
bool hasFrame = false;
c2_status_t err = C2_OK;
while (err == C2_OK) {
hasFrame = false;
err = sendInputBuffer(NULL, 0);
if (err == C2_OK) {
err = receiveFrame(&hasFrame);
if (hasFrame) {
ALOGW("drain: skip frame pts=%" PRId64 " dts=%" PRId64 " ts=%" PRId64 " - sr=%d, ch=%d, fmt=%s, #=%d",
mFrame->pts, mFrame->pkt_dts, mFrame->best_effort_timestamp,
mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format),
mFrame->nb_samples);
} else {
err = C2_NOT_FOUND;
}
}
}
return C2_OK;
}
} // namespace android