/* * Copyright 2022 Michael Goffioul * * 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 #include #include #include "C2FFMPEGAudioDecodeComponent.h" #include #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& intf) : SimpleC2Component(std::make_shared>(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(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 &work, const std::shared_ptr& 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> 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 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 buffer = createLinearBuffer(std::move(block), 0, len); if (mCtx->codec->capabilities & AV_CODEC_CAP_SUBFRAMES) { auto fillWork = [buffer, &work, this](const std::unique_ptr& 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& /* 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