/*
 * Copyright (C) 2013 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.
 */

#include <inttypes.h>

//#define LOG_NDEBUG 0
#define LOG_TAG "SoftVideoDecoderOMXComponent"
#include <utils/Log.h>

#include <media/stagefright/omx/SoftVideoDecoderOMXComponent.h>

#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/foundation/MediaDefs.h>
#include <media/hardware/HardwareAPI.h>

namespace android {

template<class T>
static void InitOMXParams(T *params) {
    params->nSize = sizeof(T);
    params->nVersion.s.nVersionMajor = 1;
    params->nVersion.s.nVersionMinor = 0;
    params->nVersion.s.nRevision = 0;
    params->nVersion.s.nStep = 0;
}

SoftVideoDecoderOMXComponent::SoftVideoDecoderOMXComponent(
        const char *name,
        const char *componentRole,
        OMX_VIDEO_CODINGTYPE codingType,
        const CodecProfileLevel *profileLevels,
        size_t numProfileLevels,
        int32_t width,
        int32_t height,
        const OMX_CALLBACKTYPE *callbacks,
        OMX_PTR appData,
        OMX_COMPONENTTYPE **component)
        : SimpleSoftOMXComponent(name, callbacks, appData, component),
        mIsAdaptive(false),
        mAdaptiveMaxWidth(0),
        mAdaptiveMaxHeight(0),
        mWidth(width),
        mHeight(height),
        mCropLeft(0),
        mCropTop(0),
        mCropWidth(width),
        mCropHeight(height),
        mOutputFormat(OMX_COLOR_FormatYUV420Planar),
        mOutputPortSettingsChange(NONE),
        mUpdateColorAspects(false),
        mMinInputBufferSize(384), // arbitrary, using one uncompressed macroblock
        mMinCompressionRatio(1),  // max input size is normally the output size
        mComponentRole(componentRole),
        mCodingType(codingType),
        mProfileLevels(profileLevels),
        mNumProfileLevels(numProfileLevels) {

    // init all the color aspects to be Unspecified.
    memset(&mDefaultColorAspects, 0, sizeof(ColorAspects));
    memset(&mBitstreamColorAspects, 0, sizeof(ColorAspects));
    memset(&mFinalColorAspects, 0, sizeof(ColorAspects));
    memset(&mHdrStaticInfo, 0, sizeof(HDRStaticInfo));
}

void SoftVideoDecoderOMXComponent::initPorts(
        OMX_U32 numInputBuffers,
        OMX_U32 inputBufferSize,
        OMX_U32 numOutputBuffers,
        const char *mimeType,
        OMX_U32 minCompressionRatio) {
    initPorts(numInputBuffers, numInputBuffers, inputBufferSize,
            numOutputBuffers, numOutputBuffers, mimeType, minCompressionRatio);
}

void SoftVideoDecoderOMXComponent::initPorts(
        OMX_U32 numMinInputBuffers,
        OMX_U32 numInputBuffers,
        OMX_U32 inputBufferSize,
        OMX_U32 numMinOutputBuffers,
        OMX_U32 numOutputBuffers,
        const char *mimeType,
        OMX_U32 minCompressionRatio) {
    mMinInputBufferSize = inputBufferSize;
    mMinCompressionRatio = minCompressionRatio;

    OMX_PARAM_PORTDEFINITIONTYPE def;
    InitOMXParams(&def);

    def.nPortIndex = kInputPortIndex;
    def.eDir = OMX_DirInput;
    def.nBufferCountMin = numMinInputBuffers;
    def.nBufferCountActual = numInputBuffers;
    def.nBufferSize = inputBufferSize;
    def.bEnabled = OMX_TRUE;
    def.bPopulated = OMX_FALSE;
    def.eDomain = OMX_PortDomainVideo;
    def.bBuffersContiguous = OMX_FALSE;
    def.nBufferAlignment = 1;

    def.format.video.cMIMEType = const_cast<char *>(mimeType);
    def.format.video.pNativeRender = NULL;
    /* size is initialized in updatePortDefinitions() */
    def.format.video.nBitrate = 0;
    def.format.video.xFramerate = 0;
    def.format.video.bFlagErrorConcealment = OMX_FALSE;
    def.format.video.eCompressionFormat = mCodingType;
    def.format.video.eColorFormat = OMX_COLOR_FormatUnused;
    def.format.video.pNativeWindow = NULL;

    addPort(def);

    def.nPortIndex = kOutputPortIndex;
    def.eDir = OMX_DirOutput;
    def.nBufferCountMin = numMinOutputBuffers;
    def.nBufferCountActual = numOutputBuffers;
    def.bEnabled = OMX_TRUE;
    def.bPopulated = OMX_FALSE;
    def.eDomain = OMX_PortDomainVideo;
    def.bBuffersContiguous = OMX_FALSE;
    def.nBufferAlignment = 2;

    def.format.video.cMIMEType = const_cast<char *>("video/raw");
    def.format.video.pNativeRender = NULL;
    /* size is initialized in updatePortDefinitions() */
    def.format.video.nBitrate = 0;
    def.format.video.xFramerate = 0;
    def.format.video.bFlagErrorConcealment = OMX_FALSE;
    def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused;
    def.format.video.pNativeWindow = NULL;

    addPort(def);

    updatePortDefinitions(true /* updateCrop */, true /* updateInputSize */);
}

void SoftVideoDecoderOMXComponent::updatePortDefinitions(bool updateCrop, bool updateInputSize) {
    OMX_PARAM_PORTDEFINITIONTYPE *outDef = &editPortInfo(kOutputPortIndex)->mDef;
    outDef->format.video.nFrameWidth = outputBufferWidth();
    outDef->format.video.nFrameHeight = outputBufferHeight();
    outDef->format.video.eColorFormat = mOutputFormat;
    outDef->format.video.nSliceHeight = outDef->format.video.nFrameHeight;

    int32_t bpp = (mOutputFormat == OMX_COLOR_FormatYUV420Planar16) ? 2 : 1;
    outDef->format.video.nStride = outDef->format.video.nFrameWidth * bpp;
    outDef->nBufferSize =
            (outDef->format.video.nStride * outDef->format.video.nSliceHeight * 3) / 2;

    OMX_PARAM_PORTDEFINITIONTYPE *inDef = &editPortInfo(kInputPortIndex)->mDef;
    inDef->format.video.nFrameWidth = mWidth;
    inDef->format.video.nFrameHeight = mHeight;
    // input port is compressed, hence it has no stride
    inDef->format.video.nStride = 0;
    inDef->format.video.nSliceHeight = 0;

    // when output format changes, input buffer size does not actually change
    if (updateInputSize) {
        inDef->nBufferSize = max(
                outDef->nBufferSize / mMinCompressionRatio,
                max(mMinInputBufferSize, inDef->nBufferSize));
    }

    if (updateCrop) {
        mCropLeft = 0;
        mCropTop = 0;
        mCropWidth = mWidth;
        mCropHeight = mHeight;
    }
}


uint32_t SoftVideoDecoderOMXComponent::outputBufferWidth() {
    return max(mIsAdaptive ? mAdaptiveMaxWidth : 0, mWidth);
}

uint32_t SoftVideoDecoderOMXComponent::outputBufferHeight() {
    return max(mIsAdaptive ? mAdaptiveMaxHeight : 0, mHeight);
}

void SoftVideoDecoderOMXComponent::handlePortSettingsChange(
        bool *portWillReset, uint32_t width, uint32_t height,
        OMX_COLOR_FORMATTYPE outputFormat,
        CropSettingsMode cropSettingsMode, bool fakeStride) {
    *portWillReset = false;
    bool sizeChanged = (width != mWidth || height != mHeight);
    bool formatChanged = (outputFormat != mOutputFormat);
    bool updateCrop = (cropSettingsMode == kCropUnSet);
    bool cropChanged = (cropSettingsMode == kCropChanged);
    bool strideChanged = false;
    if (fakeStride) {
        OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kOutputPortIndex)->mDef;
        if (def->format.video.nStride != (OMX_S32)width
                || def->format.video.nSliceHeight != (OMX_U32)height) {
            strideChanged = true;
        }
    }

    if (formatChanged || sizeChanged || cropChanged || strideChanged) {
        if (formatChanged) {
            ALOGD("formatChanged: 0x%08x -> 0x%08x", mOutputFormat, outputFormat);
        }
        mOutputFormat = outputFormat;
        mWidth = width;
        mHeight = height;

        if ((sizeChanged && !mIsAdaptive)
            || width > mAdaptiveMaxWidth
            || height > mAdaptiveMaxHeight
            || formatChanged) {
            if (mIsAdaptive) {
                if (width > mAdaptiveMaxWidth) {
                    mAdaptiveMaxWidth = width;
                }
                if (height > mAdaptiveMaxHeight) {
                    mAdaptiveMaxHeight = height;
                }
            }
            updatePortDefinitions(updateCrop);
            notify(OMX_EventPortSettingsChanged, kOutputPortIndex, 0, NULL);
            mOutputPortSettingsChange = AWAITING_DISABLED;
            *portWillReset = true;
        } else {
            updatePortDefinitions(updateCrop);

            if (fakeStride) {
                // MAJOR HACK that is not pretty, it's just to fool the renderer to read the correct
                // data.
                // Some software decoders (e.g. SoftMPEG4) fill decoded frame directly to output
                // buffer without considering the output buffer stride and slice height. So this is
                // used to signal how the buffer is arranged.  The alternative is to re-arrange the
                // output buffer in SoftMPEG4, but that results in memcopies.
                OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kOutputPortIndex)->mDef;
                def->format.video.nStride = mWidth;
                def->format.video.nSliceHeight = mHeight;
            }

            notify(OMX_EventPortSettingsChanged, kOutputPortIndex,
                   OMX_IndexConfigCommonOutputCrop, NULL);
        }
    } else if (mUpdateColorAspects) {
        notify(OMX_EventPortSettingsChanged, kOutputPortIndex,
                kDescribeColorAspectsIndex, NULL);
        mUpdateColorAspects = false;
    }
}

void SoftVideoDecoderOMXComponent::dumpColorAspects(const ColorAspects &colorAspects) {
    ALOGD("dumpColorAspects: (R:%d(%s), P:%d(%s), M:%d(%s), T:%d(%s)) ",
            colorAspects.mRange, asString(colorAspects.mRange),
            colorAspects.mPrimaries, asString(colorAspects.mPrimaries),
            colorAspects.mMatrixCoeffs, asString(colorAspects.mMatrixCoeffs),
            colorAspects.mTransfer, asString(colorAspects.mTransfer));
}

bool SoftVideoDecoderOMXComponent::colorAspectsDiffer(
        const ColorAspects &a, const ColorAspects &b) {
    if (a.mRange != b.mRange
        || a.mPrimaries != b.mPrimaries
        || a.mTransfer != b.mTransfer
        || a.mMatrixCoeffs != b.mMatrixCoeffs) {
        return true;
    }
    return false;
}

void SoftVideoDecoderOMXComponent::updateFinalColorAspects(
        const ColorAspects &otherAspects, const ColorAspects &preferredAspects) {
    Mutex::Autolock autoLock(mColorAspectsLock);
    ColorAspects newAspects;
    newAspects.mRange = preferredAspects.mRange != ColorAspects::RangeUnspecified ?
        preferredAspects.mRange : otherAspects.mRange;
    newAspects.mPrimaries = preferredAspects.mPrimaries != ColorAspects::PrimariesUnspecified ?
        preferredAspects.mPrimaries : otherAspects.mPrimaries;
    newAspects.mTransfer = preferredAspects.mTransfer != ColorAspects::TransferUnspecified ?
        preferredAspects.mTransfer : otherAspects.mTransfer;
    newAspects.mMatrixCoeffs = preferredAspects.mMatrixCoeffs != ColorAspects::MatrixUnspecified ?
        preferredAspects.mMatrixCoeffs : otherAspects.mMatrixCoeffs;

    // Check to see if need update mFinalColorAspects.
    if (colorAspectsDiffer(mFinalColorAspects, newAspects)) {
        mFinalColorAspects = newAspects;
        mUpdateColorAspects = true;
    }
}

status_t SoftVideoDecoderOMXComponent::handleColorAspectsChange() {
    int perference = getColorAspectPreference();
    ALOGD("Color Aspects preference: %d ", perference);

    if (perference == kPreferBitstream) {
        updateFinalColorAspects(mDefaultColorAspects, mBitstreamColorAspects);
    } else if (perference == kPreferContainer) {
        updateFinalColorAspects(mBitstreamColorAspects, mDefaultColorAspects);
    } else {
        return OMX_ErrorUnsupportedSetting;
    }
    return OK;
}

void SoftVideoDecoderOMXComponent::copyYV12FrameToOutputBuffer(
        uint8_t *dst, const uint8_t *srcY, const uint8_t *srcU, const uint8_t *srcV,
        size_t srcYStride, size_t srcUStride, size_t srcVStride) {
    OMX_PARAM_PORTDEFINITIONTYPE *outDef = &editPortInfo(kOutputPortIndex)->mDef;
    int32_t bpp = (outDef->format.video.eColorFormat == OMX_COLOR_FormatYUV420Planar16) ? 2 : 1;

    size_t dstYStride = outputBufferWidth() * bpp;
    size_t dstUVStride = dstYStride / 2;
    size_t dstHeight = outputBufferHeight();
    uint8_t *dstStart = dst;

    for (size_t i = 0; i < mHeight; ++i) {
         memcpy(dst, srcY, mWidth * bpp);
         srcY += srcYStride;
         dst += dstYStride;
    }

    dst = dstStart + dstYStride * dstHeight;
    for (size_t i = 0; i < mHeight / 2; ++i) {
         memcpy(dst, srcU, mWidth / 2 * bpp);
         srcU += srcUStride;
         dst += dstUVStride;
    }

    dst = dstStart + (5 * dstYStride * dstHeight) / 4;
    for (size_t i = 0; i < mHeight / 2; ++i) {
         memcpy(dst, srcV, mWidth / 2 * bpp);
         srcV += srcVStride;
         dst += dstUVStride;
    }
}

OMX_ERRORTYPE SoftVideoDecoderOMXComponent::internalGetParameter(
        OMX_INDEXTYPE index, OMX_PTR params) {
    switch (index) {
        case OMX_IndexParamVideoPortFormat:
        {
            OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
                (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;

            if (!isValidOMXParam(formatParams)) {
                return OMX_ErrorBadParameter;
            }

            if (formatParams->nPortIndex > kMaxPortIndex) {
                return OMX_ErrorBadPortIndex;
            }

            if (formatParams->nIndex != 0) {
                return OMX_ErrorNoMore;
            }

            if (formatParams->nPortIndex == kInputPortIndex) {
                formatParams->eCompressionFormat = mCodingType;
                formatParams->eColorFormat = OMX_COLOR_FormatUnused;
                formatParams->xFramerate = 0;
            } else {
                CHECK_EQ(formatParams->nPortIndex, 1u);

                formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused;
                formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar;
                formatParams->xFramerate = 0;
            }

            return OMX_ErrorNone;
        }

        case OMX_IndexParamVideoProfileLevelQuerySupported:
        {
            OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel =
                  (OMX_VIDEO_PARAM_PROFILELEVELTYPE *) params;

            if (!isValidOMXParam(profileLevel)) {
                return OMX_ErrorBadParameter;
            }

            if (profileLevel->nPortIndex != kInputPortIndex) {
                ALOGE("Invalid port index: %" PRIu32, profileLevel->nPortIndex);
                return OMX_ErrorUnsupportedIndex;
            }

            if (profileLevel->nProfileIndex >= mNumProfileLevels) {
                return OMX_ErrorNoMore;
            }

            profileLevel->eProfile = mProfileLevels[profileLevel->nProfileIndex].mProfile;
            profileLevel->eLevel   = mProfileLevels[profileLevel->nProfileIndex].mLevel;
            return OMX_ErrorNone;
        }

        default:
            return SimpleSoftOMXComponent::internalGetParameter(index, params);
    }
}

OMX_ERRORTYPE SoftVideoDecoderOMXComponent::internalSetParameter(
        OMX_INDEXTYPE index, const OMX_PTR params) {
    // Include extension index OMX_INDEXEXTTYPE.
    const int32_t indexFull = index;

    switch (indexFull) {
        case OMX_IndexParamStandardComponentRole:
        {
            const OMX_PARAM_COMPONENTROLETYPE *roleParams =
                (const OMX_PARAM_COMPONENTROLETYPE *)params;

            if (!isValidOMXParam(roleParams)) {
                return OMX_ErrorBadParameter;
            }

            if (strncmp((const char *)roleParams->cRole,
                        mComponentRole,
                        OMX_MAX_STRINGNAME_SIZE - 1)) {
                return OMX_ErrorUndefined;
            }

            return OMX_ErrorNone;
        }

        case OMX_IndexParamVideoPortFormat:
        {
            OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
                (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;

            if (!isValidOMXParam(formatParams)) {
                return OMX_ErrorBadParameter;
            }

            if (formatParams->nPortIndex > kMaxPortIndex) {
                return OMX_ErrorBadPortIndex;
            }

            if (formatParams->nPortIndex == kInputPortIndex) {
                if (formatParams->eCompressionFormat != mCodingType
                        || formatParams->eColorFormat != OMX_COLOR_FormatUnused) {
                    return OMX_ErrorUnsupportedSetting;
                }
            } else {
                if (formatParams->eCompressionFormat != OMX_VIDEO_CodingUnused
                        || formatParams->eColorFormat != OMX_COLOR_FormatYUV420Planar) {
                    return OMX_ErrorUnsupportedSetting;
                }
            }

            return OMX_ErrorNone;
        }

        case kPrepareForAdaptivePlaybackIndex:
        {
            const PrepareForAdaptivePlaybackParams* adaptivePlaybackParams =
                    (const PrepareForAdaptivePlaybackParams *)params;

            if (!isValidOMXParam(adaptivePlaybackParams)) {
                return OMX_ErrorBadParameter;
            }

            mIsAdaptive = adaptivePlaybackParams->bEnable;
            if (mIsAdaptive) {
                mAdaptiveMaxWidth = adaptivePlaybackParams->nMaxFrameWidth;
                mAdaptiveMaxHeight = adaptivePlaybackParams->nMaxFrameHeight;
                mWidth = mAdaptiveMaxWidth;
                mHeight = mAdaptiveMaxHeight;
            } else {
                mAdaptiveMaxWidth = 0;
                mAdaptiveMaxHeight = 0;
            }
            updatePortDefinitions(true /* updateCrop */, true /* updateInputSize */);
            return OMX_ErrorNone;
        }

        case OMX_IndexParamPortDefinition:
        {
            OMX_PARAM_PORTDEFINITIONTYPE *newParams =
                (OMX_PARAM_PORTDEFINITIONTYPE *)params;

            if (!isValidOMXParam(newParams)) {
                return OMX_ErrorBadParameter;
            }

            OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &newParams->format.video;
            OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(newParams->nPortIndex)->mDef;

            uint32_t oldWidth = def->format.video.nFrameWidth;
            uint32_t oldHeight = def->format.video.nFrameHeight;
            uint32_t newWidth = video_def->nFrameWidth;
            uint32_t newHeight = video_def->nFrameHeight;
            // We need width, height, stride and slice-height to be non-zero and sensible.
            // These values were chosen to prevent integer overflows further down the line, and do
            // not indicate support for 32kx32k video.
            if (newWidth > 32768 || newHeight > 32768
                    || video_def->nStride > 32768 || video_def->nStride < -32768
                    || video_def->nSliceHeight > 32768) {
                ALOGE("b/22885421");
                return OMX_ErrorBadParameter;
            }
            if (newWidth != oldWidth || newHeight != oldHeight) {
                bool outputPort = (newParams->nPortIndex == kOutputPortIndex);
                if (outputPort) {
                    // only update (essentially crop) if size changes
                    mWidth = newWidth;
                    mHeight = newHeight;

                    updatePortDefinitions(true /* updateCrop */, true /* updateInputSize */);
                    // reset buffer size based on frame size
                    newParams->nBufferSize = def->nBufferSize;
                } else {
                    // For input port, we only set nFrameWidth and nFrameHeight. Buffer size
                    // is updated when configuring the output port using the max-frame-size,
                    // though client can still request a larger size.
                    def->format.video.nFrameWidth = newWidth;
                    def->format.video.nFrameHeight = newHeight;
                }
            }
            return SimpleSoftOMXComponent::internalSetParameter(index, params);
        }

        default:
            return SimpleSoftOMXComponent::internalSetParameter(index, params);
    }
}

OMX_ERRORTYPE SoftVideoDecoderOMXComponent::getConfig(
        OMX_INDEXTYPE index, OMX_PTR params) {
    switch ((int)index) {
        case OMX_IndexConfigCommonOutputCrop:
        {
            OMX_CONFIG_RECTTYPE *rectParams = (OMX_CONFIG_RECTTYPE *)params;

            if (!isValidOMXParam(rectParams)) {
                return OMX_ErrorBadParameter;
            }

            if (rectParams->nPortIndex != kOutputPortIndex) {
                return OMX_ErrorUndefined;
            }

            rectParams->nLeft = mCropLeft;
            rectParams->nTop = mCropTop;
            rectParams->nWidth = mCropWidth;
            rectParams->nHeight = mCropHeight;

            return OMX_ErrorNone;
        }
        case kDescribeColorAspectsIndex:
        {
            if (!supportsDescribeColorAspects()) {
                return OMX_ErrorUnsupportedIndex;
            }

            DescribeColorAspectsParams* colorAspectsParams =
                    (DescribeColorAspectsParams *)params;

            if (!isValidOMXParam(colorAspectsParams)) {
                return OMX_ErrorBadParameter;
            }

            if (colorAspectsParams->nPortIndex != kOutputPortIndex) {
                return OMX_ErrorBadParameter;
            }

            colorAspectsParams->sAspects = mFinalColorAspects;
            if (colorAspectsParams->bRequestingDataSpace || colorAspectsParams->bDataSpaceChanged) {
                return OMX_ErrorUnsupportedSetting;
            }

            return OMX_ErrorNone;
        }

        case kDescribeHdrStaticInfoIndex:
        {
            if (!supportDescribeHdrStaticInfo()) {
                return OMX_ErrorUnsupportedIndex;
            }

            DescribeHDRStaticInfoParams* hdrStaticInfoParams =
                    (DescribeHDRStaticInfoParams *)params;

            if (!isValidOMXParam(hdrStaticInfoParams)) {
                return OMX_ErrorBadParameter;
            }

            if (hdrStaticInfoParams->nPortIndex != kOutputPortIndex) {
                return OMX_ErrorBadPortIndex;
            }

            hdrStaticInfoParams->sInfo = mHdrStaticInfo;

            return OMX_ErrorNone;
        }

        case kDescribeHdr10PlusInfoIndex:
        {
            if (!supportDescribeHdr10PlusInfo()) {
                return OMX_ErrorUnsupportedIndex;
            }

            if (mHdr10PlusOutputs.size() > 0) {
                auto it = mHdr10PlusOutputs.begin();

                auto info = (*it).get();

                DescribeHDR10PlusInfoParams* outParams =
                        (DescribeHDR10PlusInfoParams *)params;

                if (!isValidOMXParam(outParams)) {
                    return OMX_ErrorBadParameter;
                }
                if (offsetof(DescribeHDR10PlusInfoParams, nValue) + outParams->nParamSize >
                    outParams->nSize) {
                    ALOGE("b/329641908: too large param size; nParamSize=%u nSize=%u",
                          outParams->nParamSize, outParams->nSize);
                    android_errorWriteLog(0x534e4554, "329641908");
                    return OMX_ErrorBadParameter;
                }

                outParams->nParamSizeUsed = info->size();

                // If the buffer provided by the client does not have enough
                // storage, return the size only and do not remove the param yet.
                if (outParams->nParamSize >= info->size()) {
                    memcpy(outParams->nValue, info->data(), info->size());
                    mHdr10PlusOutputs.erase(it);
                }
                return OMX_ErrorNone;
            }
            return OMX_ErrorUnderflow;
        }

        default:
            return OMX_ErrorUnsupportedIndex;
    }
}

OMX_ERRORTYPE SoftVideoDecoderOMXComponent::internalSetConfig(
        OMX_INDEXTYPE index, const OMX_PTR params, bool *frameConfig){
    switch ((int)index) {
        case kDescribeColorAspectsIndex:
        {
            if (!supportsDescribeColorAspects()) {
                return OMX_ErrorUnsupportedIndex;
            }
            const DescribeColorAspectsParams* colorAspectsParams =
                    (const DescribeColorAspectsParams *)params;

            if (!isValidOMXParam(colorAspectsParams)) {
                return OMX_ErrorBadParameter;
            }

            if (colorAspectsParams->nPortIndex != kOutputPortIndex) {
                return OMX_ErrorBadParameter;
            }

            // Update color aspects if necessary.
            if (colorAspectsDiffer(colorAspectsParams->sAspects, mDefaultColorAspects)) {
                mDefaultColorAspects = colorAspectsParams->sAspects;
                status_t err = handleColorAspectsChange();
                CHECK(err == OK);
            }
            return OMX_ErrorNone;
        }

        case kDescribeHdrStaticInfoIndex:
        {
            if (!supportDescribeHdrStaticInfo()) {
                return OMX_ErrorUnsupportedIndex;
            }

            const DescribeHDRStaticInfoParams* hdrStaticInfoParams =
                    (DescribeHDRStaticInfoParams *)params;

            if (!isValidOMXParam(hdrStaticInfoParams)) {
                return OMX_ErrorBadParameter;
            }

            if (hdrStaticInfoParams->nPortIndex != kOutputPortIndex) {
                return OMX_ErrorBadPortIndex;
            }

            mHdrStaticInfo = hdrStaticInfoParams->sInfo;
            updatePortDefinitions(false);

            return OMX_ErrorNone;
        }

        case kDescribeHdr10PlusInfoIndex:
        {
            if (!supportDescribeHdr10PlusInfo()) {
                return OMX_ErrorUnsupportedIndex;
            }

            const DescribeHDR10PlusInfoParams* inParams =
                    (DescribeHDR10PlusInfoParams *)params;

            if (!isValidOMXParam(inParams)) {
                return OMX_ErrorBadParameter;
            }

            if (*frameConfig) {
                // This is a request to append to the current frame config set.
                // For now, we only support kDescribeHdr10PlusInfoIndex, which
                // we simply replace with the last set value.
                if (mHdr10PlusInputs.size() > 0) {
                    *(--mHdr10PlusInputs.end()) = ABuffer::CreateAsCopy(
                            inParams->nValue, inParams->nParamSizeUsed);
                } else {
                    ALOGW("Ignoring kDescribeHdr10PlusInfoIndex: append to "
                            "frame config while no frame config is present");
                }
            } else {
                // This is a frame config, setting *frameConfig to true so that
                // the client marks the next queued input frame to apply it.
                *frameConfig = true;
                mHdr10PlusInputs.push_back(ABuffer::CreateAsCopy(
                        inParams->nValue, inParams->nParamSizeUsed));
            }
            return OMX_ErrorNone;
        }

        default:
            return OMX_ErrorUnsupportedIndex;
    }
}

sp<ABuffer> SoftVideoDecoderOMXComponent::dequeueInputFrameConfig() {
    auto it = mHdr10PlusInputs.begin();
    sp<ABuffer> info = *it;
    mHdr10PlusInputs.erase(it);
    return info;
}

void SoftVideoDecoderOMXComponent::queueOutputFrameConfig(const sp<ABuffer> &info) {
    mHdr10PlusOutputs.push_back(info);
    notify(OMX_EventConfigUpdate,
           kOutputPortIndex,
           kDescribeHdr10PlusInfoIndex,
           NULL);
}

OMX_ERRORTYPE SoftVideoDecoderOMXComponent::getExtensionIndex(
        const char *name, OMX_INDEXTYPE *index) {
    if (!strcmp(name, "OMX.google.android.index.prepareForAdaptivePlayback")) {
        *(int32_t*)index = kPrepareForAdaptivePlaybackIndex;
        return OMX_ErrorNone;
    } else if (!strcmp(name, "OMX.google.android.index.describeColorAspects")
                && supportsDescribeColorAspects()) {
        *(int32_t*)index = kDescribeColorAspectsIndex;
        return OMX_ErrorNone;
    } else if (!strcmp(name, "OMX.google.android.index.describeHDRStaticInfo")
            && supportDescribeHdrStaticInfo()) {
        *(int32_t*)index = kDescribeHdrStaticInfoIndex;
        return OMX_ErrorNone;
    } else if (!strcmp(name, "OMX.google.android.index.describeHDR10PlusInfo")
            && supportDescribeHdr10PlusInfo()) {
        *(int32_t*)index = kDescribeHdr10PlusInfoIndex;
        return OMX_ErrorNone;
    }

    return SimpleSoftOMXComponent::getExtensionIndex(name, index);
}

bool SoftVideoDecoderOMXComponent::supportsDescribeColorAspects() {
    return getColorAspectPreference() != kNotSupported;
}

int SoftVideoDecoderOMXComponent::getColorAspectPreference() {
    return kNotSupported;
}

bool SoftVideoDecoderOMXComponent::supportDescribeHdrStaticInfo() {
    return false;
}

bool SoftVideoDecoderOMXComponent::supportDescribeHdr10PlusInfo() {
    return false;
}

void SoftVideoDecoderOMXComponent::onReset() {
    mOutputPortSettingsChange = NONE;
}

void SoftVideoDecoderOMXComponent::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
    if (portIndex != kOutputPortIndex) {
        return;
    }

    switch (mOutputPortSettingsChange) {
        case NONE:
            break;

        case AWAITING_DISABLED:
        {
            CHECK(!enabled);
            mOutputPortSettingsChange = AWAITING_ENABLED;
            break;
        }

        default:
        {
            CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED);
            CHECK(enabled);
            mOutputPortSettingsChange = NONE;
            break;
        }
    }
}

}  // namespace android
