696 lines
23 KiB
C++
696 lines
23 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 "C2FFMPEGVideoDecodeComponent"
|
|
#include <android-base/properties.h>
|
|
#include <log/log.h>
|
|
#include <algorithm>
|
|
|
|
#include <SimpleC2Interface.h>
|
|
#include "C2FFMPEGVideoDecodeComponent.h"
|
|
#include "ffmpeg_hwaccel.h"
|
|
|
|
#define DEBUG_FRAMES 0
|
|
#define DEBUG_WORKQUEUE 0
|
|
#define DEBUG_EXTRADATA 0
|
|
|
|
namespace android {
|
|
|
|
C2FFMPEGVideoDecodeComponent::C2FFMPEGVideoDecodeComponent(
|
|
const C2FFMPEGComponentInfo* componentInfo,
|
|
const std::shared_ptr<C2FFMPEGVideoDecodeInterface>& intf)
|
|
: SimpleC2Component(std::make_shared<SimpleInterface<C2FFMPEGVideoDecodeInterface>>(componentInfo->name, 0, intf)),
|
|
mInfo(componentInfo),
|
|
mIntf(intf),
|
|
mCodecID(componentInfo->codecID),
|
|
mCtx(NULL),
|
|
mImgConvertCtx(NULL),
|
|
mFrame(NULL),
|
|
mPacket(NULL),
|
|
mFFMPEGInitialized(false),
|
|
mCodecAlreadyOpened(false),
|
|
mExtradataReady(false),
|
|
mEOSSignalled(false) {
|
|
ALOGD("C2FFMPEGVideoDecodeComponent: mediaType = %s", componentInfo->mediaType);
|
|
}
|
|
|
|
C2FFMPEGVideoDecodeComponent::~C2FFMPEGVideoDecodeComponent() {
|
|
ALOGD("~C2FFMPEGVideoDecodeComponent: mCtx = %p", mCtx);
|
|
onRelease();
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::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;
|
|
}
|
|
|
|
C2StreamPictureSizeInfo::output size(0u, 320, 240);
|
|
c2_status_t err = mIntf->query({ &size }, {}, C2_DONT_BLOCK, nullptr);
|
|
if (err != C2_OK) {
|
|
ALOGE("initDecoder: cannot query picture size, err = %d", err);
|
|
}
|
|
|
|
mCtx->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
mCtx->codec_id = mCodecID;
|
|
mCtx->extradata_size = 0;
|
|
mCtx->extradata = NULL;
|
|
mCtx->width = size.width;
|
|
mCtx->height = size.height;
|
|
|
|
ALOGD("initDecoder: %p [%s], %d x %d, %s",
|
|
mCtx, avcodec_get_name(mCtx->codec_id), size.width, size.height, mInfo->mediaType);
|
|
|
|
return C2_OK;
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::openDecoder() {
|
|
if (mCodecAlreadyOpened) {
|
|
return C2_OK;
|
|
}
|
|
|
|
// Can't change extradata after opening the decoder.
|
|
#if DEBUG_EXTRADATA
|
|
ALOGD("openDecoder: extradata_size = %d", mCtx->extradata_size);
|
|
#endif
|
|
mExtradataReady = true;
|
|
|
|
// Find decoder again as codec_id may have changed.
|
|
if (mCtx->codec_id == AV_CODEC_ID_H264 &&
|
|
base::GetBoolProperty("persist.vendor.ffmpeg_codec2.v4l2.h264", false)) {
|
|
mCtx->codec = avcodec_find_decoder_by_name("h264_v4l2m2m");
|
|
} else {
|
|
mCtx->codec = avcodec_find_decoder(mCtx->codec_id);
|
|
}
|
|
|
|
if (! mCtx->codec) {
|
|
ALOGE("openDecoder: ffmpeg video 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->thread_count = base::GetIntProperty("debug.ffmpeg_codec2.threads", 0);
|
|
|
|
if (base::GetBoolProperty("debug.ffmpeg_codec2.fast", false)) {
|
|
mCtx->flags2 |= AV_CODEC_FLAG2_FAST;
|
|
}
|
|
|
|
ffmpeg_hwaccel_init(mCtx);
|
|
|
|
ALOGD("openDecoder: opening ffmpeg decoder(%s): threads = %d, hw = %s",
|
|
avcodec_get_name(mCtx->codec_id), mCtx->thread_count, mCtx->hw_device_ctx ? "yes" : "no");
|
|
|
|
int err = avcodec_open2(mCtx, mCtx->codec, NULL);
|
|
if (err < 0) {
|
|
ALOGE("openDecoder: ffmpeg video decoder failed to initialize. (%s)", av_err2str(err));
|
|
return C2_NO_INIT;
|
|
}
|
|
mCodecAlreadyOpened = true;
|
|
|
|
ALOGD("openDecoder: open ffmpeg video decoder(%s) success, caps = %08x",
|
|
avcodec_get_name(mCtx->codec_id), mCtx->codec->capabilities);
|
|
|
|
mFrame = av_frame_alloc();
|
|
if (! mFrame) {
|
|
ALOGE("openDecoder: oom for video frame");
|
|
return C2_NO_MEMORY;
|
|
}
|
|
|
|
return C2_OK;
|
|
}
|
|
|
|
void C2FFMPEGVideoDecodeComponent::deInitDecoder() {
|
|
ALOGD("%p deInitDecoder: %p", this, mCtx);
|
|
if (mCtx) {
|
|
if (avcodec_is_open(mCtx)) {
|
|
avcodec_flush_buffers(mCtx);
|
|
}
|
|
ffmpeg_hwaccel_deinit(mCtx);
|
|
avcodec_free_context(&mCtx);
|
|
mCodecAlreadyOpened = false;
|
|
}
|
|
if (mFrame) {
|
|
av_frame_free(&mFrame);
|
|
mFrame = NULL;
|
|
}
|
|
if (mPacket) {
|
|
av_packet_free(&mPacket);
|
|
mPacket = NULL;
|
|
}
|
|
if (mImgConvertCtx) {
|
|
sws_freeContext(mImgConvertCtx);
|
|
mImgConvertCtx = NULL;
|
|
}
|
|
mEOSSignalled = false;
|
|
mExtradataReady = false;
|
|
mPendingWorkQueue.clear();
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::processCodecConfig(C2ReadView* inBuffer) {
|
|
int orig_extradata_size = mCtx->extradata_size;
|
|
int add_extradata_size = inBuffer->capacity();
|
|
|
|
#if DEBUG_EXTRADATA
|
|
ALOGD("processCodecConfig: add = %u, current = %d", add_extradata_size, orig_extradata_size);
|
|
#endif
|
|
if (! mExtradataReady) {
|
|
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("processCodecConfig: ffmpeg video 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);
|
|
}
|
|
else {
|
|
ALOGW("processCodecConfig: decoder is already opened, ignoring...");
|
|
}
|
|
|
|
return C2_OK;
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::sendInputBuffer(
|
|
C2ReadView *inBuffer, int64_t timestamp) {
|
|
if (!mPacket) {
|
|
mPacket = av_packet_alloc();
|
|
if (!mPacket) {
|
|
ALOGE("sendInputBuffer: oom for video 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 = AV_NOPTS_VALUE;
|
|
|
|
int err = avcodec_send_packet(mCtx, mPacket);
|
|
av_packet_unref(mPacket);
|
|
|
|
if (err < 0) {
|
|
ALOGE("sendInputBuffer: failed to send data (%d) to decoder: %s (%08x)",
|
|
inBuffer->capacity(), av_err2str(err), err);
|
|
if (err == AVERROR(EAGAIN)) {
|
|
// Frames must be read first, notify main decoding loop.
|
|
ALOGD("sendInputBuffer: returning C2_BAD_STATE");
|
|
return C2_BAD_STATE;
|
|
}
|
|
// Otherwise don't send error to client.
|
|
}
|
|
|
|
return C2_OK;
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::receiveFrame(bool* hasPicture) {
|
|
int err = avcodec_receive_frame(mCtx, mFrame);
|
|
|
|
*hasPicture = false;
|
|
if (err == 0) {
|
|
err = ffmpeg_hwaccel_get_frame(mCtx, mFrame);
|
|
if (err == 0) {
|
|
*hasPicture = true;
|
|
} else {
|
|
ALOGE("receiveFrame: failed to receive frame from HW decoder err = %d", err);
|
|
// Don't send error to client, skip frame!
|
|
}
|
|
} else if (err != AVERROR(EAGAIN) && err != AVERROR_EOF) {
|
|
ALOGE("receiveFrame: failed to receive frame from decoder err = %d", err);
|
|
// Don't report error to client.
|
|
}
|
|
|
|
return C2_OK;
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::getOutputBuffer(C2GraphicView* outBuffer) {
|
|
uint8_t* data[4];
|
|
int linesize[4];
|
|
C2PlanarLayout layout = outBuffer->layout();
|
|
struct SwsContext* currentImgConvertCtx = mImgConvertCtx;
|
|
|
|
data[0] = outBuffer->data()[C2PlanarLayout::PLANE_Y];
|
|
data[1] = outBuffer->data()[C2PlanarLayout::PLANE_U];
|
|
data[2] = outBuffer->data()[C2PlanarLayout::PLANE_V];
|
|
linesize[0] = layout.planes[C2PlanarLayout::PLANE_Y].rowInc;
|
|
linesize[1] = layout.planes[C2PlanarLayout::PLANE_U].rowInc;
|
|
linesize[2] = layout.planes[C2PlanarLayout::PLANE_V].rowInc;
|
|
|
|
mImgConvertCtx = sws_getCachedContext(currentImgConvertCtx,
|
|
mFrame->width, mFrame->height, (AVPixelFormat)mFrame->format,
|
|
mFrame->width, mFrame->height, AV_PIX_FMT_YUV420P,
|
|
SWS_BICUBIC, NULL, NULL, NULL);
|
|
if (mImgConvertCtx && mImgConvertCtx != currentImgConvertCtx) {
|
|
ALOGD("getOutputBuffer: created video converter - %s => %s",
|
|
av_get_pix_fmt_name((AVPixelFormat)mFrame->format), av_get_pix_fmt_name(AV_PIX_FMT_YUV420P));
|
|
|
|
} else if (! mImgConvertCtx) {
|
|
ALOGE("getOutputBuffer: cannot initialize the conversion context");
|
|
return C2_NO_MEMORY;
|
|
}
|
|
|
|
sws_scale(mImgConvertCtx, mFrame->data, mFrame->linesize,
|
|
0, mFrame->height, data, linesize);
|
|
|
|
return C2_OK;
|
|
}
|
|
|
|
static void fillEmptyWork(const std::unique_ptr<C2Work>& work) {
|
|
work->worklets.front()->output.flags =
|
|
(C2FrameData::flags_t)(work->input.flags & C2FrameData::FLAG_END_OF_STREAM);
|
|
work->worklets.front()->output.buffers.clear();
|
|
work->worklets.front()->output.ordinal = work->input.ordinal;
|
|
work->workletsProcessed = 1u;
|
|
work->result = C2_OK;
|
|
#if DEBUG_WORKQUEUE
|
|
ALOGD("WorkQueue: drop idx=%" PRIu64 ", ts=%" PRIu64,
|
|
work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());
|
|
#endif
|
|
}
|
|
|
|
static bool comparePendingWork(const PendingWork& w1, const PendingWork& w2) {
|
|
return w1.second < w2.second;
|
|
}
|
|
|
|
void C2FFMPEGVideoDecodeComponent::pushPendingWork(const std::unique_ptr<C2Work>& work) {
|
|
uint32_t outputDelay = mIntf->getOutputDelay();
|
|
|
|
if (mPendingWorkQueue.size() >= outputDelay) {
|
|
uint32_t newOutputDelay = outputDelay;
|
|
std::vector<std::unique_ptr<C2Param>> configUpdate;
|
|
|
|
switch (mCtx->codec_id) {
|
|
case AV_CODEC_ID_HEVC:
|
|
case AV_CODEC_ID_H264:
|
|
// Increase output delay step-wise.
|
|
if (outputDelay >= 18u) {
|
|
newOutputDelay = 34u;
|
|
} else if (outputDelay >= 8u) {
|
|
newOutputDelay = 18u;
|
|
} else {
|
|
newOutputDelay = 8u;
|
|
}
|
|
break;
|
|
default:
|
|
// Other codecs use constant output delay.
|
|
break;
|
|
}
|
|
|
|
if (newOutputDelay != outputDelay) {
|
|
C2PortActualDelayTuning::output delay(newOutputDelay);
|
|
std::vector<std::unique_ptr<C2SettingResult>> failures;
|
|
int err;
|
|
|
|
err = mIntf->config({ &delay }, C2_MAY_BLOCK, &failures);
|
|
if (err == C2_OK) {
|
|
ALOGD("WorkQueue: queue full, output delay set to %u", newOutputDelay);
|
|
configUpdate.push_back(C2Param::Copy(delay));
|
|
} else {
|
|
ALOGE("WorkQueue: output delay update to %u failed err = %d",
|
|
newOutputDelay, err);
|
|
}
|
|
}
|
|
|
|
auto fillEmptyWorkWithConfigUpdate = [&configUpdate](const std::unique_ptr<C2Work>& work) {
|
|
fillEmptyWork(work);
|
|
work->worklets.front()->output.configUpdate = std::move(configUpdate);
|
|
};
|
|
|
|
finish(mPendingWorkQueue.front().first, fillEmptyWorkWithConfigUpdate);
|
|
mPendingWorkQueue.pop_front();
|
|
}
|
|
#if DEBUG_WORKQUEUE
|
|
ALOGD("WorkQueue: push idx=%" PRIu64 ", ts=%" PRIu64,
|
|
work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());
|
|
#endif
|
|
mPendingWorkQueue.push_back(PendingWork(work->input.ordinal.frameIndex.peeku(),
|
|
work->input.ordinal.timestamp.peeku()));
|
|
std::sort(mPendingWorkQueue.begin(), mPendingWorkQueue.end(), comparePendingWork);
|
|
}
|
|
|
|
void C2FFMPEGVideoDecodeComponent::popPendingWork(const std::unique_ptr<C2Work>& work) {
|
|
uint64_t index = work->input.ordinal.frameIndex.peeku();
|
|
auto it = std::find_if(mPendingWorkQueue.begin(), mPendingWorkQueue.end(),
|
|
[index](const PendingWork& pWork) { return index == pWork.first; });
|
|
|
|
#if DEBUG_WORKQUEUE
|
|
ALOGD("WorkQueue: pop idx=%" PRIu64 ", ts=%" PRIu64,
|
|
work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());
|
|
#endif
|
|
|
|
if (it != mPendingWorkQueue.end()) {
|
|
mPendingWorkQueue.erase(it);
|
|
}
|
|
#if DEBUG_WORKQUEUE
|
|
else {
|
|
ALOGD("WorkQueue: pop work not found idx=%" PRIu64 ", ts=%" PRIu64,
|
|
work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());
|
|
}
|
|
#endif
|
|
prunePendingWorksUntil(work);
|
|
}
|
|
|
|
void C2FFMPEGVideoDecodeComponent::prunePendingWorksUntil(const std::unique_ptr<C2Work>& work) {
|
|
#if DEBUG_WORKQUEUE
|
|
ALOGD("WorkQueue: prune until idx=%" PRIu64 ", ts=%" PRIu64,
|
|
work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());
|
|
#endif
|
|
// Drop all works with a PTS earlier than provided argument.
|
|
while (mPendingWorkQueue.size() > 0 &&
|
|
mPendingWorkQueue.front().second < work->input.ordinal.timestamp.peeku()) {
|
|
finish(mPendingWorkQueue.front().first, fillEmptyWork);
|
|
mPendingWorkQueue.pop_front();
|
|
}
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::onInit() {
|
|
ALOGD("onInit");
|
|
return initDecoder();
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::onStop() {
|
|
ALOGD("onStop");
|
|
return C2_OK;
|
|
}
|
|
|
|
void C2FFMPEGVideoDecodeComponent::onReset() {
|
|
ALOGD("onReset");
|
|
deInitDecoder();
|
|
initDecoder();
|
|
}
|
|
|
|
void C2FFMPEGVideoDecodeComponent::onRelease() {
|
|
ALOGD("onRelease");
|
|
deInitDecoder();
|
|
if (mFFMPEGInitialized) {
|
|
deInitFFmpeg();
|
|
mFFMPEGInitialized = false;
|
|
}
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::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;
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::outputFrame(
|
|
const std::unique_ptr<C2Work>& work,
|
|
const std::shared_ptr<C2BlockPool> &pool
|
|
) {
|
|
c2_status_t err;
|
|
std::vector<std::unique_ptr<C2Param>> configUpdate;
|
|
|
|
#if DEBUG_FRAMES
|
|
ALOGD("outputFrame: pts=%" PRId64 " dts=%" PRId64 " ts=%" PRId64 " - %d x %d (%x)",
|
|
mFrame->pts, mFrame->pkt_dts, mFrame->best_effort_timestamp, mFrame->width, mFrame->height, mFrame->format);
|
|
#endif
|
|
|
|
if (mFrame->width != mIntf->getWidth() || mFrame->height != mIntf->getHeight()) {
|
|
ALOGD("outputFrame: video params changed - %d x %d (%x)", mFrame->width, mFrame->height, mFrame->format);
|
|
|
|
C2StreamPictureSizeInfo::output size(0u, mFrame->width, mFrame->height);
|
|
std::vector<std::unique_ptr<C2SettingResult>> failures;
|
|
|
|
err = mIntf->config({ &size }, C2_MAY_BLOCK, &failures);
|
|
if (err == OK) {
|
|
configUpdate.push_back(C2Param::Copy(size));
|
|
mCtx->width = mFrame->width;
|
|
mCtx->height = mFrame->height;
|
|
} else {
|
|
ALOGE("outputFrame: config update failed err = %d", err);
|
|
return C2_CORRUPTED;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<C2GraphicBlock> block;
|
|
|
|
err = pool->fetchGraphicBlock(mFrame->width, mFrame->height, HAL_PIXEL_FORMAT_YV12,
|
|
{ C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }, &block);
|
|
|
|
if (err != C2_OK) {
|
|
ALOGE("outputFrame: failed to fetch graphic block %d x %d (%x) err = %d",
|
|
mFrame->width, mFrame->height, HAL_PIXEL_FORMAT_YV12, err);
|
|
return C2_CORRUPTED;
|
|
}
|
|
|
|
C2GraphicView wView = block->map().get();
|
|
|
|
err = wView.error();
|
|
if (err != C2_OK) {
|
|
ALOGE("outputFrame: graphic view map failed err = %d", err);
|
|
return C2_CORRUPTED;
|
|
}
|
|
|
|
err = getOutputBuffer(&wView);
|
|
if (err == C2_OK) {
|
|
std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(block), C2Rect(mFrame->width, mFrame->height));
|
|
|
|
buffer->setInfo(mIntf->getPixelFormatInfo());
|
|
|
|
if (work && c2_cntr64_t(mFrame->best_effort_timestamp) == work->input.ordinal.frameIndex) {
|
|
prunePendingWorksUntil(work);
|
|
work->worklets.front()->output.configUpdate = std::move(configUpdate);
|
|
work->worklets.front()->output.buffers.clear();
|
|
work->worklets.front()->output.buffers.push_back(buffer);
|
|
work->worklets.front()->output.ordinal = work->input.ordinal;
|
|
work->workletsProcessed = 1u;
|
|
work->result = C2_OK;
|
|
} else {
|
|
auto fillWork = [buffer, &configUpdate, this](const std::unique_ptr<C2Work>& work) {
|
|
popPendingWork(work);
|
|
work->worklets.front()->output.configUpdate = std::move(configUpdate);
|
|
work->worklets.front()->output.flags = (C2FrameData::flags_t)0;
|
|
work->worklets.front()->output.buffers.clear();
|
|
work->worklets.front()->output.buffers.push_back(buffer);
|
|
work->worklets.front()->output.ordinal = work->input.ordinal;
|
|
work->workletsProcessed = 1u;
|
|
work->result = C2_OK;
|
|
#if DEBUG_FRAMES
|
|
ALOGD("outputFrame: work(finish) idx=%" PRIu64 ", processed=%u, result=%d",
|
|
work->input.ordinal.frameIndex.peeku(), work->workletsProcessed, work->result);
|
|
#endif
|
|
};
|
|
|
|
finish(mFrame->best_effort_timestamp, fillWork);
|
|
}
|
|
} else {
|
|
return err;
|
|
}
|
|
|
|
return C2_OK;
|
|
}
|
|
|
|
void C2FFMPEGVideoDecodeComponent::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;
|
|
|
|
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.
|
|
//
|
|
// There is not always a 1:1 mapping between input and output frames, in particular for
|
|
// interlaced content. Keeping the corresponding worklets in the queue quickly fills it
|
|
// in and stalls the decoder. But there's no obvious mechanism to determine, from
|
|
// FFMPEG API, whether a given packet will produce an output frame and the worklet should
|
|
// be kept around so it can be completed when the frame is produced.
|
|
//
|
|
// NOTE: This has an impact on the drain operation.
|
|
|
|
work->result = C2_OK;
|
|
work->worklets.front()->output.flags = (C2FrameData::flags_t)0;
|
|
work->workletsProcessed = 0u;
|
|
|
|
if (inSize || (eos && mCodecAlreadyOpened)) {
|
|
c2_status_t err = C2_OK;
|
|
|
|
if (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) {
|
|
work->workletsProcessed = 1u;
|
|
work->result = processCodecConfig(&rView);
|
|
return;
|
|
}
|
|
|
|
if (! mCodecAlreadyOpened) {
|
|
err = openDecoder();
|
|
if (err != C2_OK) {
|
|
work->workletsProcessed = 1u;
|
|
work->result = err;
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool inputConsumed = false;
|
|
bool outputAvailable = true;
|
|
bool hasPicture = false;
|
|
#if DEBUG_FRAMES
|
|
int outputFrameCount = 0;
|
|
#endif
|
|
|
|
while (!inputConsumed || outputAvailable) {
|
|
if (!inputConsumed) {
|
|
err = sendInputBuffer(&rView, work->input.ordinal.frameIndex.peekll());
|
|
if (err == C2_OK) {
|
|
inputConsumed = true;
|
|
outputAvailable = true;
|
|
work->input.buffers.clear();
|
|
} else if (err != C2_BAD_STATE) {
|
|
work->workletsProcessed = 1u;
|
|
work->result = err;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (outputAvailable) {
|
|
hasPicture = false;
|
|
err = receiveFrame(&hasPicture);
|
|
if (err != C2_OK) {
|
|
work->workletsProcessed = 1u;
|
|
work->result = err;
|
|
return;
|
|
}
|
|
|
|
if (hasPicture) {
|
|
err = outputFrame(work, pool);
|
|
if (err != C2_OK) {
|
|
work->workletsProcessed = 1u;
|
|
work->result = err;
|
|
return;
|
|
}
|
|
#if DEBUG_FRAMES
|
|
else {
|
|
outputFrameCount++;
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
#if DEBUG_FRAMES
|
|
if (!outputFrameCount) {
|
|
ALOGD("process: no frame");
|
|
}
|
|
#endif
|
|
outputAvailable = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if DEBUG_FRAMES
|
|
else {
|
|
ALOGD("process: empty work");
|
|
}
|
|
#endif
|
|
|
|
if (eos) {
|
|
mEOSSignalled = true;
|
|
work->worklets.front()->output.flags = C2FrameData::FLAG_END_OF_STREAM;
|
|
work->workletsProcessed = 1u;
|
|
}
|
|
|
|
if (work->workletsProcessed == 0u) {
|
|
pushPendingWork(work);
|
|
}
|
|
|
|
#if DEBUG_FRAMES
|
|
ALOGD("process: work(end) idx=%" PRIu64 ", processed=%u, result=%d",
|
|
work->input.ordinal.frameIndex.peeku(), work->workletsProcessed, work->result);
|
|
#endif
|
|
}
|
|
|
|
c2_status_t C2FFMPEGVideoDecodeComponent::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 hasPicture = false;
|
|
c2_status_t err = C2_OK;
|
|
|
|
err = sendInputBuffer(NULL, 0);
|
|
while (err == C2_OK) {
|
|
hasPicture = false;
|
|
err = receiveFrame(&hasPicture);
|
|
if (hasPicture) {
|
|
// Ignore errors at this point, just drain the decoder.
|
|
outputFrame(nullptr, pool);
|
|
} else {
|
|
err = C2_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
return C2_OK;
|
|
}
|
|
|
|
} // namespace android
|