/*
 * Copyright 2018 The Android Open Source Project
 *
 * 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_NDEBUG 0
#ifdef MPEG4
  #define LOG_TAG "C2SoftMpeg4Enc"
#else
  #define LOG_TAG "C2SoftH263Enc"
#endif
#include <log/log.h>

#include <inttypes.h>

#include <media/hardware/VideoAPI.h>
#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/MediaDefs.h>
#include <utils/misc.h>

#include <C2Debug.h>
#include <C2PlatformSupport.h>
#include <SimpleC2Interface.h>
#include <util/C2InterfaceHelper.h>

#include "C2SoftMpeg4Enc.h"
#include "mp4enc_api.h"

namespace android {

namespace {

#ifdef MPEG4
constexpr char COMPONENT_NAME[] = "c2.android.mpeg4.encoder";
const char *MEDIA_MIMETYPE_VIDEO = MEDIA_MIMETYPE_VIDEO_MPEG4;
#else
constexpr char COMPONENT_NAME[] = "c2.android.h263.encoder";
const char *MEDIA_MIMETYPE_VIDEO = MEDIA_MIMETYPE_VIDEO_H263;
#endif

constexpr float VBV_DELAY = 5.0f;

} // namepsace

class C2SoftMpeg4Enc::IntfImpl : public SimpleInterface<void>::BaseParams {
   public:
    explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper)
        : SimpleInterface<void>::BaseParams(
                helper,
                COMPONENT_NAME,
                C2Component::KIND_ENCODER,
                C2Component::DOMAIN_VIDEO,
                MEDIA_MIMETYPE_VIDEO) {
        noPrivateBuffers(); // TODO: account for our buffers here
        noInputReferences();
        noOutputReferences();
        noInputLatency();
        noTimeStretch();
        setDerivedInstance(this);

        addParameter(
                DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES)
                .withConstValue(new C2ComponentAttributesSetting(
                    C2Component::ATTRIB_IS_TEMPORAL))
                .build());

        addParameter(
                DefineParam(mUsage, C2_PARAMKEY_INPUT_STREAM_USAGE)
                .withConstValue(new C2StreamUsageTuning::input(
                        0u, (uint64_t)C2MemoryUsage::CPU_READ))
                .build());

        addParameter(
            DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE)
                .withDefault(new C2StreamPictureSizeInfo::input(0u, 176, 144))
                .withFields({
#ifdef MPEG4
                    C2F(mSize, width).inRange(16, 176, 16),
                    C2F(mSize, height).inRange(16, 144, 16),
#else
                    C2F(mSize, width).oneOf({176, 352}),
                    C2F(mSize, height).oneOf({144, 288}),
#endif
                })
                .withSetter(SizeSetter)
                .build());

        addParameter(
            DefineParam(mFrameRate, C2_PARAMKEY_FRAME_RATE)
                .withDefault(new C2StreamFrameRateInfo::output(0u, 17.))
                // TODO: More restriction?
                .withFields({C2F(mFrameRate, value).greaterThan(0.)})
                .withSetter(
                    Setter<decltype(*mFrameRate)>::StrictValueWithNoDeps)
                .build());

        addParameter(
            DefineParam(mBitrate, C2_PARAMKEY_BITRATE)
                .withDefault(new C2StreamBitrateInfo::output(0u, 64000))
                .withFields({C2F(mBitrate, value).inRange(4096, 12000000)})
                .withSetter(BitrateSetter)
                .build());

        addParameter(
                DefineParam(mSyncFramePeriod, C2_PARAMKEY_SYNC_FRAME_INTERVAL)
                .withDefault(new C2StreamSyncFrameIntervalTuning::output(0u, 1000000))
                .withFields({C2F(mSyncFramePeriod, value).any()})
                .withSetter(Setter<decltype(*mSyncFramePeriod)>::StrictValueWithNoDeps)
                .build());

#ifdef MPEG4
        addParameter(
                DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL)
                .withDefault(new C2StreamProfileLevelInfo::output(
                        0u, PROFILE_MP4V_SIMPLE, LEVEL_MP4V_2))
                .withFields({
                    C2F(mProfileLevel, profile).equalTo(
                            PROFILE_MP4V_SIMPLE),
                    C2F(mProfileLevel, level).oneOf({
                            C2Config::LEVEL_MP4V_0,
                            C2Config::LEVEL_MP4V_0B,
                            C2Config::LEVEL_MP4V_1,
                            C2Config::LEVEL_MP4V_2})
                })
                .withSetter(ProfileLevelSetter, mSize, mFrameRate, mBitrate)
                .build());
#else
        addParameter(
                DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL)
                .withDefault(new C2StreamProfileLevelInfo::output(
                        0u, PROFILE_H263_BASELINE, LEVEL_H263_45))
                .withFields({
                    C2F(mProfileLevel, profile).equalTo(
                            PROFILE_H263_BASELINE),
                    C2F(mProfileLevel, level).oneOf({
                            C2Config::LEVEL_H263_10,
                            C2Config::LEVEL_H263_20,
                            C2Config::LEVEL_H263_30,
                            C2Config::LEVEL_H263_40,
                            C2Config::LEVEL_H263_45})
                })
                .withSetter(ProfileLevelSetter, mSize, mFrameRate, mBitrate)
                .build());
#endif
    }

    static C2R BitrateSetter(bool mayBlock, C2P<C2StreamBitrateInfo::output> &me) {
        (void)mayBlock;
        C2R res = C2R::Ok();
        if (me.v.value <= 4096) {
            me.set().value = 4096;
        }
        return res;
    }

    static C2R SizeSetter(bool mayBlock, const C2P<C2StreamPictureSizeInfo::input> &oldMe,
                          C2P<C2StreamPictureSizeInfo::input> &me) {
        (void)mayBlock;
        C2R res = C2R::Ok();
        if (!me.F(me.v.width).supportsAtAll(me.v.width)) {
            res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width)));
            me.set().width = oldMe.v.width;
        }
        if (!me.F(me.v.height).supportsAtAll(me.v.height)) {
            res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height)));
            me.set().height = oldMe.v.height;
        }
        return res;
    }

    static C2R ProfileLevelSetter(
            bool mayBlock,
            C2P<C2StreamProfileLevelInfo::output> &me,
            const C2P<C2StreamPictureSizeInfo::input> &size,
            const C2P<C2StreamFrameRateInfo::output> &frameRate,
            const C2P<C2StreamBitrateInfo::output> &bitrate) {
        (void)mayBlock;
        if (!me.F(me.v.profile).supportsAtAll(me.v.profile)) {
#ifdef MPEG4
            me.set().profile = PROFILE_MP4V_SIMPLE;
#else
            me.set().profile = PROFILE_H263_BASELINE;
#endif
        }

        struct LevelLimits {
            C2Config::level_t level;
            uint32_t sampleRate;
            uint32_t width;
            uint32_t height;
            uint32_t frameRate;
            uint32_t bitrate;
            uint32_t vbvSize;
        };

        constexpr LevelLimits kLimits[] = {
#ifdef MPEG4
            { LEVEL_MP4V_0, 380160, 176, 144, 15, 64000, 163840 },
            // { LEVEL_MP4V_0B, 380160, 176, 144, 15, 128000, 163840 },
            { LEVEL_MP4V_1, 380160, 176, 144, 30, 64000, 163840 },
            { LEVEL_MP4V_2, 1520640, 352, 288, 30, 128000, 655360 },
#else
            // HRD Buffer Size = (B + BPPmaxKb * 1024 bits)
            // where, (BPPmaxKb * 1024) is maximum number of bits per picture
            // that has been negotiated for use in the bitstream Sec 3.6 of T-Rec-H.263
            // and B = 4 * Rmax / PCF. Rmax is max bit rate and PCF is picture
            // clock frequency
            { LEVEL_H263_10, 380160, 176, 144, 15, 64000, 74077 },
            { LEVEL_H263_45, 380160, 176, 144, 15, 128000, 82619 },
            { LEVEL_H263_20, 1520640, 352, 288, 30, 128000, 279227 },
            { LEVEL_H263_30, 3041280, 352, 288, 30, 384000, 313395 },
            { LEVEL_H263_40, 3041280, 352, 288, 30, 2048000, 535483 },
            // { LEVEL_H263_50, 5068800, 352, 288, 60, 4096000, 808823 },
#endif
        };

        auto mbs = ((size.v.width + 15) / 16) * ((size.v.height + 15) / 16);
        auto sampleRate = mbs * frameRate.v.value * 16 * 16;
        auto vbvSize = bitrate.v.value * VBV_DELAY;

        // Check if the supplied level meets the MB / bitrate requirements. If
        // not, update the level with the lowest level meeting the requirements.
        bool found = false;

        // By default needsUpdate = false in case the supplied level does meet
        // the requirements.
        bool needsUpdate = false;
#ifdef MPEG4
        // For Level 0b, we want to update the level anyway, as library does not
        // seem to accept this value.
        if (me.v.level == LEVEL_MP4V_0B) {
            needsUpdate = true;
        }
#endif
        if (!me.F(me.v.level).supportsAtAll(me.v.level)) {
            needsUpdate = true;
        }
        for (const LevelLimits &limit : kLimits) {
            if (sampleRate <= limit.sampleRate && size.v.width <= limit.width &&
                    vbvSize <= limit.vbvSize && size.v.height <= limit.height &&
                    bitrate.v.value <= limit.bitrate && frameRate.v.value <= limit.frameRate) {
                // This is the lowest level that meets the requirements, and if
                // we haven't seen the supplied level yet, that means we don't
                // need the update.
                if (needsUpdate) {
                    ALOGD("Given level %x does not cover current configuration: "
                          "adjusting to %x", me.v.level, limit.level);
                    me.set().level = limit.level;
                }
                found = true;
                break;
            }
            if (me.v.level == limit.level) {
                // We break out of the loop when the lowest feasible level is
                // found. The fact that we're here means that our level doesn't
                // meet the requirement and needs to be updated.
                needsUpdate = true;
            }
        }
        // If not found or exceeds max level, set to the highest supported level.
#ifdef MPEG4
        if (!found || me.v.level > LEVEL_MP4V_2) {
            me.set().level = LEVEL_MP4V_2;
        }
#else
        if (!found || (me.v.level != LEVEL_H263_45 && me.v.level > LEVEL_H263_40)) {
            me.set().level = LEVEL_H263_40;
        }
#endif
        return C2R::Ok();
    }

    // unsafe getters
    std::shared_ptr<C2StreamPictureSizeInfo::input> getSize_l() const { return mSize; }
    std::shared_ptr<C2StreamFrameRateInfo::output> getFrameRate_l() const { return mFrameRate; }
    std::shared_ptr<C2StreamBitrateInfo::output> getBitrate_l() const { return mBitrate; }
    uint32_t getSyncFramePeriod() const {
        if (mSyncFramePeriod->value < 0 || mSyncFramePeriod->value == INT64_MAX) {
            return 0;
        }
        double period = mSyncFramePeriod->value / 1e6 * mFrameRate->value;
        return (uint32_t)c2_max(c2_min(period + 0.5, double(UINT32_MAX)), 1.);
    }

   private:
    std::shared_ptr<C2StreamUsageTuning::input> mUsage;
    std::shared_ptr<C2StreamPictureSizeInfo::input> mSize;
    std::shared_ptr<C2StreamFrameRateInfo::output> mFrameRate;
    std::shared_ptr<C2StreamBitrateInfo::output> mBitrate;
    std::shared_ptr<C2StreamProfileLevelInfo::output> mProfileLevel;
    std::shared_ptr<C2StreamSyncFrameIntervalTuning::output> mSyncFramePeriod;
};

C2SoftMpeg4Enc::C2SoftMpeg4Enc(const char* name, c2_node_id_t id,
                               const std::shared_ptr<IntfImpl>& intfImpl)
    : SimpleC2Component(
          std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)),
      mIntf(intfImpl),
      mHandle(nullptr),
      mEncParams(nullptr),
      mStarted(false),
      mOutBufferSize(524288) {
}

C2SoftMpeg4Enc::~C2SoftMpeg4Enc() {
    onRelease();
}

c2_status_t C2SoftMpeg4Enc::onInit() {
#ifdef MPEG4
    mEncodeMode = COMBINE_MODE_WITH_ERR_RES;
#else
    mEncodeMode = H263_MODE;
#endif
    if (!mHandle) {
        mHandle = new tagvideoEncControls;
    }

    if (!mEncParams) {
        mEncParams = new tagvideoEncOptions;
    }

    if (!(mEncParams && mHandle)) return C2_NO_MEMORY;

    mSignalledOutputEos = false;
    mSignalledError = false;

    return initEncoder();
}

c2_status_t C2SoftMpeg4Enc::onStop() {
    if (!mStarted) {
        return C2_OK;
    }
    if (mHandle) {
        (void)PVCleanUpVideoEncoder(mHandle);
    }
    mStarted = false;
    mSignalledOutputEos = false;
    mSignalledError = false;
    return C2_OK;
}

void C2SoftMpeg4Enc::onReset() {
    onStop();
    initEncoder();
}

void C2SoftMpeg4Enc::onRelease() {
    onStop();
    if (mEncParams) {
        delete mEncParams;
        mEncParams = nullptr;
    }
    if (mHandle) {
        delete mHandle;
        mHandle = nullptr;
    }
}

c2_status_t C2SoftMpeg4Enc::onFlush_sm() {
    return C2_OK;
}

static void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
    uint32_t flags = 0;
    if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
        flags |= C2FrameData::FLAG_END_OF_STREAM;
        ALOGV("signalling eos");
    }
    work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
    work->worklets.front()->output.buffers.clear();
    work->worklets.front()->output.ordinal = work->input.ordinal;
    work->workletsProcessed = 1u;
}

c2_status_t C2SoftMpeg4Enc::initEncParams() {
    if (mHandle) {
        memset(mHandle, 0, sizeof(tagvideoEncControls));
    } else return C2_CORRUPTED;
    if (mEncParams) {
        memset(mEncParams, 0, sizeof(tagvideoEncOptions));
    } else return C2_CORRUPTED;

    if (!PVGetDefaultEncOption(mEncParams, 0)) {
        ALOGE("Failed to get default encoding parameters");
        return C2_CORRUPTED;
    }

    if (mFrameRate->value == 0) {
        ALOGE("Framerate should not be 0");
        return C2_BAD_VALUE;
    }

    mEncParams->encMode = mEncodeMode;
    mEncParams->encWidth[0] = mSize->width;
    mEncParams->encHeight[0] = mSize->height;
    mEncParams->encFrameRate[0] = mFrameRate->value + 0.5;
    mEncParams->rcType = VBR_1;
    mEncParams->vbvDelay = VBV_DELAY;
    mEncParams->profile_level = CORE_PROFILE_LEVEL2;
    mEncParams->packetSize = 32;
    mEncParams->rvlcEnable = PV_OFF;
    mEncParams->numLayers = 1;
    mEncParams->timeIncRes = 1000;
    mEncParams->tickPerSrc = mEncParams->timeIncRes / (mFrameRate->value + 0.5);
    mEncParams->bitRate[0] = mBitrate->value;
    mEncParams->iQuant[0] = 15;
    mEncParams->pQuant[0] = 12;
    mEncParams->quantType[0] = 0;
    mEncParams->noFrameSkipped = PV_OFF;

    // PV's MPEG4 encoder requires the video dimension of multiple
    if (mSize->width % 16 != 0 || mSize->height % 16 != 0) {
        ALOGE("Video frame size %dx%d must be a multiple of 16",
              mSize->width, mSize->height);
        return C2_BAD_VALUE;
    }

    // Set IDR frame refresh interval
    mEncParams->intraPeriod = mIntf->getSyncFramePeriod();
    mEncParams->numIntraMB = 0;
    mEncParams->sceneDetect = PV_ON;
    mEncParams->searchRange = 16;
    mEncParams->mv8x8Enable = PV_OFF;
    mEncParams->gobHeaderInterval = 0;
    mEncParams->useACPred = PV_ON;
    mEncParams->intraDCVlcTh = 0;

    return C2_OK;
}

c2_status_t C2SoftMpeg4Enc::initEncoder() {
    if (mStarted) {
        return C2_OK;
    }
    {
        IntfImpl::Lock lock = mIntf->lock();
        mSize = mIntf->getSize_l();
        mBitrate = mIntf->getBitrate_l();
        mFrameRate = mIntf->getFrameRate_l();
    }
    c2_status_t err = initEncParams();
    if (C2_OK != err) {
        ALOGE("Failed to initialized encoder params");
        mSignalledError = true;
        return err;
    }
    if (!PVInitVideoEncoder(mHandle, mEncParams)) {
        ALOGE("Failed to initialize the encoder");
        mSignalledError = true;
        return C2_CORRUPTED;
    }

    // 1st buffer for codec specific data
    mNumInputFrames = -1;
    mStarted = true;
    return C2_OK;
}

void C2SoftMpeg4Enc::process(
        const std::unique_ptr<C2Work> &work,
        const std::shared_ptr<C2BlockPool> &pool) {
    // Initialize output work
    work->result = C2_OK;
    work->workletsProcessed = 1u;
    work->worklets.front()->output.flags = work->input.flags;
    if (mSignalledError || mSignalledOutputEos) {
        work->result = C2_BAD_VALUE;
        return;
    }

    // Initialize encoder if not already initialized
    if (!mStarted && C2_OK != initEncoder()) {
        ALOGE("Failed to initialize encoder");
        mSignalledError = true;
        work->result = C2_CORRUPTED;
        return;
    }

    std::shared_ptr<C2LinearBlock> block;
    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
    c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block);
    if (err != C2_OK) {
        ALOGE("fetchLinearBlock for Output failed with status %d", err);
        work->result = C2_NO_MEMORY;
        return;
    }

    C2WriteView wView = block->map().get();
    if (wView.error()) {
        ALOGE("write view map failed %d", wView.error());
        work->result = wView.error();
        return;
    }

    uint8_t *outPtr = (uint8_t *)wView.data();
    if (mNumInputFrames < 0) {
        // The very first thing we want to output is the codec specific data.
        int32_t outputSize = mOutBufferSize;
        if (!PVGetVolHeader(mHandle, outPtr, &outputSize, 0)) {
            ALOGE("Failed to get VOL header");
            mSignalledError = true;
            work->result = C2_CORRUPTED;
            return;
        } else {
            ALOGV("Bytes Generated in header %d\n", outputSize);
        }

        ++mNumInputFrames;
        if (outputSize) {
            std::unique_ptr<C2StreamInitDataInfo::output> csd =
                C2StreamInitDataInfo::output::AllocUnique(outputSize, 0u);
            if (!csd) {
                ALOGE("CSD allocation failed");
                mSignalledError = true;
                work->result = C2_NO_MEMORY;
                return;
            }
            memcpy(csd->m.value, outPtr, outputSize);
            work->worklets.front()->output.configUpdate.push_back(std::move(csd));
        }
    }

    // handle dynamic bitrate change
    {
        IntfImpl::Lock lock = mIntf->lock();
        std::shared_ptr<C2StreamBitrateInfo::output> bitrate = mIntf->getBitrate_l();
        lock.unlock();

        if (bitrate != mBitrate) {
            mBitrate = bitrate;
            int layerBitrate[2] = {static_cast<int>(mBitrate->value), 0};
            ALOGV("Calling PVUpdateBitRate %d", layerBitrate[0]);
            PVUpdateBitRate(mHandle, layerBitrate);
        }
    }

    std::shared_ptr<C2GraphicView> rView;
    std::shared_ptr<C2Buffer> inputBuffer;
    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
    if (!work->input.buffers.empty()) {
        inputBuffer = work->input.buffers[0];
        rView = std::make_shared<C2GraphicView>(
                inputBuffer->data().graphicBlocks().front().map().get());
        if (rView->error() != C2_OK) {
            ALOGE("graphic view map err = %d", rView->error());
            work->result = rView->error();
            return;
        }
        //(b/232396154)
        //workaround for incorrect crop size in view when using surface mode
        rView->setCrop_be(C2Rect(mSize->width, mSize->height));
    } else {
        fillEmptyWork(work);
        if (eos) {
            mSignalledOutputEos = true;
            ALOGV("signalled EOS");
        }
        return;
    }

    uint64_t inputTimeStamp = work->input.ordinal.timestamp.peekull();
    const C2ConstGraphicBlock inBuffer = inputBuffer->data().graphicBlocks().front();
    if (inBuffer.width() < mSize->width ||
        inBuffer.height() < mSize->height) {
        /* Expect width height to be configured */
        ALOGW("unexpected Capacity Aspect %d(%d) x %d(%d)", inBuffer.width(),
              mSize->width, inBuffer.height(), mSize->height);
        work->result = C2_BAD_VALUE;
        return;
    }

    const C2PlanarLayout &layout = rView->layout();
    uint8_t *yPlane = const_cast<uint8_t *>(rView->data()[C2PlanarLayout::PLANE_Y]);
    uint8_t *uPlane = const_cast<uint8_t *>(rView->data()[C2PlanarLayout::PLANE_U]);
    uint8_t *vPlane = const_cast<uint8_t *>(rView->data()[C2PlanarLayout::PLANE_V]);
    int32_t yStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc;
    int32_t uStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc;
    int32_t vStride = layout.planes[C2PlanarLayout::PLANE_V].rowInc;
    uint32_t width = mSize->width;
    uint32_t height = mSize->height;
    // width and height are always even (as block size is 16x16)
    CHECK_EQ((width & 1u), 0u);
    CHECK_EQ((height & 1u), 0u);
    size_t yPlaneSize = width * height;
    switch (layout.type) {
        case C2PlanarLayout::TYPE_RGB:
            [[fallthrough]];
        case C2PlanarLayout::TYPE_RGBA: {
            MemoryBlock conversionBuffer = mConversionBuffers.fetch(yPlaneSize * 3 / 2);
            mConversionBuffersInUse.emplace(conversionBuffer.data(), conversionBuffer);
            yPlane = conversionBuffer.data();
            uPlane = yPlane + yPlaneSize;
            vPlane = uPlane + yPlaneSize / 4;
            yStride = width;
            uStride = vStride = width / 2;
            ConvertRGBToPlanarYUV(yPlane, yStride, height, conversionBuffer.size(), *rView.get());
            break;
        }
        case C2PlanarLayout::TYPE_YUV: {
            if (!IsYUV420(*rView)) {
                ALOGE("input is not YUV420");
                work->result = C2_BAD_VALUE;
                break;
            }

            if (layout.planes[layout.PLANE_Y].colInc == 1
                    && layout.planes[layout.PLANE_U].colInc == 1
                    && layout.planes[layout.PLANE_V].colInc == 1
                    && yStride == align(width, 16)
                    && uStride == vStride
                    && yStride == 2 * vStride) {
                // I420 compatible with yStride being equal to aligned width
                // planes are already set up above
                break;
            }

            // copy to I420
            MemoryBlock conversionBuffer = mConversionBuffers.fetch(yPlaneSize * 3 / 2);
            mConversionBuffersInUse.emplace(conversionBuffer.data(), conversionBuffer);
            MediaImage2 img = CreateYUV420PlanarMediaImage2(width, height, width, height);
            status_t err = ImageCopy(conversionBuffer.data(), &img, *rView);
            if (err != OK) {
                ALOGE("Buffer conversion failed: %d", err);
                work->result = C2_BAD_VALUE;
                return;
            }
            yPlane = conversionBuffer.data();
            uPlane = yPlane + yPlaneSize;
            vPlane = uPlane + yPlaneSize / 4;
            yStride = width;
            uStride = vStride = width / 2;
            break;
        }

        case C2PlanarLayout::TYPE_YUVA:
            ALOGE("YUVA plane type is not supported");
            work->result = C2_BAD_VALUE;
            return;

        default:
            ALOGE("Unrecognized plane type: %d", layout.type);
            work->result = C2_BAD_VALUE;
            return;
    }

    CHECK(NULL != yPlane);
    /* Encode frames */
    VideoEncFrameIO vin, vout;
    memset(&vin, 0, sizeof(vin));
    memset(&vout, 0, sizeof(vout));
    vin.yChan = yPlane;
    vin.uChan = uPlane;
    vin.vChan = vPlane;
    vin.timestamp = (inputTimeStamp + 500) / 1000;  // in ms
    vin.height = align(height, 16);
    vin.pitch = align(width, 16);

    uint32_t modTimeMs = 0;
    int32_t nLayer = 0;
    MP4HintTrack hintTrack;
    int32_t outputSize = mOutBufferSize;
    if (!PVEncodeVideoFrame(mHandle, &vin, &vout, &modTimeMs, outPtr, &outputSize, &nLayer) ||
        !PVGetHintTrack(mHandle, &hintTrack)) {
        ALOGE("Failed to encode frame or get hint track at frame %" PRId64, mNumInputFrames);
        mSignalledError = true;
        work->result = C2_CORRUPTED;
        return;
    }
    ALOGV("outputSize filled : %d", outputSize);
    ++mNumInputFrames;
    CHECK(NULL == PVGetOverrunBuffer(mHandle));

    fillEmptyWork(work);
    if (outputSize) {
        std::shared_ptr<C2Buffer> buffer = createLinearBuffer(block, 0, outputSize);
        work->worklets.front()->output.ordinal.timestamp = inputTimeStamp;
        if (hintTrack.CodeType == 0) {
            buffer->setInfo(std::make_shared<C2StreamPictureTypeMaskInfo::output>(
                    0u /* stream id */, C2Config::SYNC_FRAME));
        }
        work->worklets.front()->output.buffers.push_back(buffer);
    }
    if (eos) {
        mSignalledOutputEos = true;
    }

    mConversionBuffersInUse.erase(yPlane);
}

c2_status_t C2SoftMpeg4Enc::drain(
        uint32_t drainMode,
        const std::shared_ptr<C2BlockPool> &pool) {
    (void)pool;
    if (drainMode == NO_DRAIN) {
        ALOGW("drain with NO_DRAIN: no-op");
        return C2_OK;
    }
    if (drainMode == DRAIN_CHAIN) {
        ALOGW("DRAIN_CHAIN not supported");
        return C2_OMITTED;
    }

    return C2_OK;
}

class C2SoftMpeg4EncFactory : public C2ComponentFactory {
public:
    C2SoftMpeg4EncFactory()
        : mHelper(std::static_pointer_cast<C2ReflectorHelper>(
              GetCodec2PlatformComponentStore()->getParamReflector())) {}

    virtual c2_status_t createComponent(
            c2_node_id_t id,
            std::shared_ptr<C2Component>* const component,
            std::function<void(C2Component*)> deleter) override {
        *component = std::shared_ptr<C2Component>(
            new C2SoftMpeg4Enc(
                COMPONENT_NAME, id,
                std::make_shared<C2SoftMpeg4Enc::IntfImpl>(mHelper)),
            deleter);
        return C2_OK;
    }

    virtual c2_status_t createInterface(
            c2_node_id_t id,
            std::shared_ptr<C2ComponentInterface>* const interface,
            std::function<void(C2ComponentInterface*)> deleter) override {
        *interface = std::shared_ptr<C2ComponentInterface>(
            new SimpleInterface<C2SoftMpeg4Enc::IntfImpl>(
                COMPONENT_NAME, id,
                std::make_shared<C2SoftMpeg4Enc::IntfImpl>(mHelper)),
            deleter);
        return C2_OK;
    }

    virtual ~C2SoftMpeg4EncFactory() override = default;

private:
    std::shared_ptr<C2ReflectorHelper> mHelper;
};

}  // namespace android

__attribute__((cfi_canonical_jump_table))
extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
    ALOGV("in %s", __func__);
    return new ::android::C2SoftMpeg4EncFactory();
}

__attribute__((cfi_canonical_jump_table))
extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
    ALOGV("in %s", __func__);
    delete factory;
}
