/*
 * Copyright (C) 2012 Intel Corporation.  All rights reserved.
 *
 * 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 <cutils/properties.h>
#include <system/graphics.h>
#include <nativebase/nativebase.h>

#include "isv_worker.h"
#ifndef TARGET_VPP_USE_GEN
#include <hal_public.h>
#else
#include <ufo/graphics.h>
#endif

//#define LOG_NDEBUG 0
#undef LOG_TAG
#define LOG_TAG "isv-omxil"

#define CHECK_VASTATUS(str) \
    do { \
        if (vaStatus != VA_STATUS_SUCCESS) { \
                ALOGE("%s failed\n", str); \
                return STATUS_ERROR;}   \
        }while(0);

enum STRENGTH {
    STRENGTH_LOW = 0,
    STRENGTH_MEDIUM,
    STRENGTH_HIGH
};

#define DENOISE_DEBLOCK_STRENGTH STRENGTH_MEDIUM
#define COLOR_STRENGTH STRENGTH_MEDIUM
#ifdef TARGET_VPP_USE_GEN
#define COLOR_NUM 4
#else
#define COLOR_NUM 2
#endif

#define MAX_FRC_OUTPUT 4 /*for frcx4*/

using namespace android;

ISVWorker::ISVWorker()
    :mNumForwardReferences(0),
    mVAContext(VA_INVALID_ID),
    mWidth(0), mHeight(0),
    mDisplay(NULL), mVADisplay(NULL),
    mVAConfig(VA_INVALID_ID),
    mForwardReferences(NULL),
    mPrevInput(0), mPrevOutput(0),
    mNumFilterBuffers(0),
    mFilterFrc(VA_INVALID_ID), mFilters(0),
    mInputIndex(0), mOutputIndex(0),
    mOutputCount(0) {
    memset(&mFilterBuffers, VA_INVALID_ID, VAProcFilterCount * sizeof(VABufferID));
    memset(&mFilterParam, 0, sizeof(mFilterParam));
}

bool ISVWorker::isSupport() const {
    bool support = false;

    int num_entrypoints = vaMaxNumEntrypoints(mVADisplay);
    VAEntrypoint * entrypoints = (VAEntrypoint *)malloc(num_entrypoints * sizeof(VAEntrypoint));
    if (entrypoints == NULL) {
        ALOGE("failed to malloc entrypoints array\n");
        return false;
    }

    // check if it contains VPP entry point VAEntrypointVideoProc
    VAStatus vaStatus = vaQueryConfigEntrypoints(mVADisplay, VAProfileNone, entrypoints, &num_entrypoints);
    if (vaStatus != VA_STATUS_SUCCESS) {
        ALOGE("vaQueryConfigEntrypoints failed");
        return false;
    }
    for (int i = 0; !support && i < num_entrypoints; i++) {
        support = entrypoints[i] == VAEntrypointVideoProc;
    }
    free(entrypoints);
    entrypoints = NULL;

    return support;
}

uint32_t ISVWorker::getProcBufCount() {
    return getOutputBufCount(mInputIndex);
}

uint32_t ISVWorker::getFillBufCount() {
        return getOutputBufCount(mOutputIndex);
}

uint32_t ISVWorker::getOutputBufCount(uint32_t index) {
    uint32_t bufCount = 1;
    if (((mFilters & FilterFrameRateConversion) != 0)
            && index > 0)
            bufCount = mFilterParam.frcRate - (((mFilterParam.frcRate == FRC_RATE_2_5X) ? (index & 1): 0));
    return bufCount;
}


status_t ISVWorker::init(uint32_t width, uint32_t height) {
    ALOGV("init");

    if (mDisplay != NULL) {
        ALOGE("VA is particially started");
        return STATUS_ERROR;
    }
    mDisplay = new Display;
    *mDisplay = ANDROID_DISPLAY_HANDLE;

    mVADisplay = vaGetDisplay(mDisplay);
    if (mVADisplay == NULL) {
        ALOGE("vaGetDisplay failed");
        return STATUS_ERROR;
    }

    int majorVersion, minorVersion;
    VAStatus vaStatus = vaInitialize(mVADisplay, &majorVersion, &minorVersion);
    CHECK_VASTATUS("vaInitialize");

    // Check if VPP entry point is supported
    if (!isSupport()) {
        ALOGE("VPP is not supported on current platform");
        return STATUS_NOT_SUPPORT;
    }

    // Find out the format for the target
    VAConfigAttrib attrib;
    attrib.type = VAConfigAttribRTFormat;
    vaStatus = vaGetConfigAttributes(mVADisplay, VAProfileNone, VAEntrypointVideoProc, &attrib, 1);
    CHECK_VASTATUS("vaGetConfigAttributes");

    if ((attrib.value & VA_RT_FORMAT_YUV420) == 0) {
        ALOGE("attribute is %x vs wanted %x", attrib.value, VA_RT_FORMAT_YUV420);
        return STATUS_NOT_SUPPORT;
    }

    ALOGV("ready to create config");
    // Create the configuration
    vaStatus = vaCreateConfig(mVADisplay, VAProfileNone, VAEntrypointVideoProc, &attrib, 1, &mVAConfig);
    CHECK_VASTATUS("vaCreateConfig");


    // Create Context
    ALOGV("ready to create context");
    mWidth = width;
    mHeight = height;
    vaStatus = vaCreateContext(mVADisplay, mVAConfig, mWidth, mHeight, 0, NULL, 0, &mVAContext);
    CHECK_VASTATUS("vaCreateContext");

    ALOGV("VA has been successfully started");
    return STATUS_OK;
}

status_t ISVWorker::deinit() {
    {
        Mutex::Autolock autoLock(mPipelineBufferLock);
        while (!mPipelineBuffers.isEmpty()) {
            VABufferID pipelineBuffer = mPipelineBuffers.itemAt(0);
            if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, pipelineBuffer))
                ALOGW("%s: failed to destroy va buffer id %d", __func__, pipelineBuffer);
            mPipelineBuffers.removeAt(0);
        }
    }

    if (mNumFilterBuffers != 0) {
        for (uint32_t i = 0; i < mNumFilterBuffers; i++) {
            if(VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, mFilterBuffers[i]))
                ALOGW("%s: failed to destroy va buffer id %d", __func__, mFilterBuffers[i]);
        }
        mNumFilterBuffers = 0;
        memset(&mFilterBuffers, VA_INVALID_ID, VAProcFilterCount * sizeof(VABufferID));
        mFilterFrc = VA_INVALID_ID;
    }

    if (mForwardReferences != NULL) {
        free(mForwardReferences);
        mForwardReferences = NULL;
        mNumForwardReferences = 0;
    }

    if (mVAContext != VA_INVALID_ID) {
         vaDestroyContext(mVADisplay, mVAContext);
         mVAContext = VA_INVALID_ID;
    }

    if (mVAConfig != VA_INVALID_ID) {
        vaDestroyConfig(mVADisplay, mVAConfig);
        mVAConfig = VA_INVALID_ID;
    }

    if (mVADisplay) {
        vaTerminate(mVADisplay);
        mVADisplay = NULL;
    }

    if (mDisplay) {
        delete mDisplay;
        mDisplay = NULL;
    }

    return STATUS_OK;
}

status_t ISVWorker::allocSurface(uint32_t* width, uint32_t* height,
        uint32_t stride, uint32_t format, unsigned long handle, int32_t* surfaceId)
{
    if (mWidth == 0 || mHeight == 0) {
        ALOGE("%s: isv worker has not been initialized.", __func__);
        return STATUS_ERROR;
    }

#ifndef TARGET_VPP_USE_GEN
    *width = mWidth;
    *height = mHeight;
#endif
    // Create VASurfaces
    VASurfaceAttrib attribs[3];
    VASurfaceAttribExternalBuffers vaExtBuf;

    memset(&vaExtBuf, 0, sizeof(VASurfaceAttribExternalBuffers));
    switch(format) {
        case HAL_PIXEL_FORMAT_YV12:
            vaExtBuf.pixel_format = VA_FOURCC_YV12;
            vaExtBuf.num_planes = 3;
            vaExtBuf.pitches[0] = stride;
            vaExtBuf.pitches[1] = stride / 2;
            vaExtBuf.pitches[2] = stride / 2;
            vaExtBuf.pitches[3] = 0;
            vaExtBuf.offsets[0] = 0;
            vaExtBuf.offsets[1] = stride * *height;
            vaExtBuf.offsets[2] = vaExtBuf.offsets[1] + (stride / 2) * (*height / 2);
            vaExtBuf.offsets[3] = 0;
            break;
        case HAL_PIXEL_FORMAT_INTEL_YV12:
            vaExtBuf.pixel_format = VA_FOURCC_YV12;
            vaExtBuf.num_planes = 3;
            vaExtBuf.pitches[0] = stride;
            vaExtBuf.pitches[1] = stride / 2;
            vaExtBuf.pitches[2] = stride / 2;
            vaExtBuf.pitches[3] = 0;
            vaExtBuf.offsets[0] = 0;
            // The height of HAL_PIXEL_FORMAT_INTEL_YV12 gralloc buffer has been aligned with 32 pixels.
            vaExtBuf.offsets[1] = stride * ((*height + 31) & ~31);
            vaExtBuf.offsets[2] = vaExtBuf.offsets[1] + (stride / 2) * (((*height + 31) & ~31) / 2);
            vaExtBuf.offsets[3] = 0;
            break;
#ifdef TARGET_VPP_USE_GEN
        case HAL_PIXEL_FORMAT_NV12_Y_TILED_INTEL:
        case HAL_PIXEL_FORMAT_NV12_X_TILED_INTEL:
        //it will be removed in future, it indicate the same format with HAL_PIXEL_FORMAT_NV12_Y_TILED_INTEL
        case HAL_PIXEL_FORMAT_YUV420PackedSemiPlanar_Tiled_INTEL:
#else
        case HAL_PIXEL_FORMAT_NV12_VED:
        case HAL_PIXEL_FORMAT_NV12_VEDT:
#endif
            vaExtBuf.pixel_format = VA_FOURCC_NV12;
            vaExtBuf.num_planes = 2;
            vaExtBuf.pitches[0] = stride;
            vaExtBuf.pitches[1] = stride;
            vaExtBuf.pitches[2] = 0;
            vaExtBuf.pitches[3] = 0;
            vaExtBuf.offsets[0] = 0;
            vaExtBuf.offsets[1] = stride * *height;
            vaExtBuf.offsets[2] = 0;
            vaExtBuf.offsets[3] = 0;
            break;
        default:
            ALOGE("%s: can't support this format 0x%08x", __func__, format);
            return STATUS_ERROR;
    }
    vaExtBuf.width = *width;
    vaExtBuf.height = *height;
    vaExtBuf.data_size = stride * *height * 1.5;
    vaExtBuf.num_buffers = 1;
#ifndef TARGET_VPP_USE_GEN
    if (format == HAL_PIXEL_FORMAT_NV12_VEDT) {
        ALOGV("set TILING flag");
        vaExtBuf.flags |= VA_SURFACE_EXTBUF_DESC_ENABLE_TILING;
    }
#endif
    vaExtBuf.flags |= VA_SURFACE_ATTRIB_MEM_TYPE_ANDROID_GRALLOC;
    vaExtBuf.buffers = (long unsigned int*)&handle;

    attribs[0].type = (VASurfaceAttribType)VASurfaceAttribMemoryType;
    attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
    attribs[0].value.type = VAGenericValueTypeInteger;
    attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_ANDROID_GRALLOC;

    attribs[1].type = (VASurfaceAttribType)VASurfaceAttribExternalBufferDescriptor;
    attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
    attribs[1].value.type = VAGenericValueTypePointer;
    attribs[1].value.value.p = &vaExtBuf;

    attribs[2].type = (VASurfaceAttribType)VASurfaceAttribUsageHint;
    attribs[2].flags = VA_SURFACE_ATTRIB_SETTABLE;
    attribs[2].value.type = VAGenericValueTypeInteger;
    attribs[2].value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_VPP_READ;

    ALOGV("%s: Ext buffer: width %d, height %d, data_size %d, pitch %d", __func__,
            vaExtBuf.width, vaExtBuf.height, vaExtBuf.data_size, vaExtBuf.pitches[0]);
    VAStatus vaStatus = vaCreateSurfaces(mVADisplay, VA_RT_FORMAT_YUV420, vaExtBuf.width,
                                 vaExtBuf.height, (VASurfaceID*)surfaceId, 1, attribs, 3);
    CHECK_VASTATUS("vaCreateSurfaces");

    return (vaStatus == VA_STATUS_SUCCESS) ? STATUS_OK : STATUS_ERROR;
}

status_t ISVWorker::freeSurface(int32_t* surfaceId)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS;
    vaDestroySurfaces(mVADisplay, (VASurfaceID*)surfaceId, 1);
    CHECK_VASTATUS("vaDestroySurfaces");

    return (vaStatus == VA_STATUS_SUCCESS) ? STATUS_OK : STATUS_ERROR;
}

status_t ISVWorker::configFilters(uint32_t filters,
                                  const FilterParam* filterParam)
{
    status_t ret = STATUS_OK;

    if (!filterParam) {
        ALOGE("%s: invalid filterParam", __func__);
        return STATUS_ERROR;
    }

    if (filters != 0) {
        mFilterParam.srcWidth = filterParam->srcWidth;
        mFilterParam.srcHeight = filterParam->srcHeight;
        mFilterParam.dstWidth = filterParam->dstWidth;
        mFilterParam.dstHeight = filterParam->dstHeight;
        mFilterParam.frameRate = filterParam->frameRate;
        mFilterParam.frcRate = filterParam->frcRate;
    }

    if (mFilters != filters) {
        mFilters = filters;
        ALOGI("%s: mFilters 0x%x, fps %d, frc rate %d", __func__, mFilters, mFilterParam.frameRate, mFilterParam.frcRate);
        ret = setupFilters();
    }

    return ret;
}

bool ISVWorker::isFpsSupport(int32_t fps, int32_t *fpsSet, int32_t fpsSetCnt) {
    bool ret = false;
    for (int32_t i = 0; i < fpsSetCnt; i++) {
        if (fps == fpsSet[i]) {
            ret = true;
            break;
        }
    }

    return ret;
}

status_t ISVWorker::setupFilters() {
    ALOGV("setupFilters");
    VAProcFilterParameterBuffer deblock, denoise, sharpen, stde;
    VAProcFilterParameterBufferDeinterlacing deint;
    VAProcFilterParameterBufferColorBalance color[COLOR_NUM];
    VAProcFilterParameterBufferFrameRateConversion frc;
    VABufferID deblockId, denoiseId, deintId, sharpenId, colorId, frcId, stdeId;
    uint32_t numCaps;
    VAProcFilterCap deblockCaps, denoiseCaps, sharpenCaps, frcCaps, stdeCaps;
    VAProcFilterCapDeinterlacing deinterlacingCaps[VAProcDeinterlacingCount];
    VAProcFilterCapColorBalance colorCaps[COLOR_NUM];
    VAStatus vaStatus;
    uint32_t numSupportedFilters = VAProcFilterCount;
    VAProcFilterType supportedFilters[VAProcFilterCount];

    if (mNumFilterBuffers != 0) {
        for (uint32_t i = 0; i < mNumFilterBuffers; i++) {
            if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, mFilterBuffers[i]))
                ALOGW("%s: failed to destroy va buffer %d", __func__, mFilterBuffers[i]);
                //return STATUS_ERROR;
        }
        memset(&mFilterBuffers, VA_INVALID_ID, VAProcFilterCount * sizeof(VABufferID));
        mFilterFrc = VA_INVALID_ID;
        mNumFilterBuffers = 0;
    }

    // query supported filters
    vaStatus = vaQueryVideoProcFilters(mVADisplay, mVAContext, supportedFilters, &numSupportedFilters);
    CHECK_VASTATUS("vaQueryVideoProcFilters");

    // create filter buffer for each filter
    for (uint32_t i = 0; i < numSupportedFilters; i++) {
        switch (supportedFilters[i]) {
            case VAProcFilterDeblocking:
                if ((mFilters & FilterDeblocking) != 0) {
                    // check filter caps
                    numCaps = 1;
                    vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext,
                            VAProcFilterDeblocking,
                            &deblockCaps,
                            &numCaps);
                    CHECK_VASTATUS("vaQueryVideoProcFilterCaps for deblocking");
                    // create parameter buffer
                    deblock.type = VAProcFilterDeblocking;
                    deblock.value = deblockCaps.range.min_value + DENOISE_DEBLOCK_STRENGTH * deblockCaps.range.step;
                    vaStatus = vaCreateBuffer(mVADisplay, mVAContext,
                        VAProcFilterParameterBufferType, sizeof(deblock), 1,
                        &deblock, &deblockId);
                    CHECK_VASTATUS("vaCreateBuffer for deblocking");
                    mFilterBuffers[mNumFilterBuffers] = deblockId;
                    mNumFilterBuffers++;
                }
                break;
            case VAProcFilterNoiseReduction:
                if((mFilters & FilterNoiseReduction) != 0) {
                    // check filter caps
                    numCaps = 1;
                    vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext,
                            VAProcFilterNoiseReduction,
                            &denoiseCaps,
                            &numCaps);
                    CHECK_VASTATUS("vaQueryVideoProcFilterCaps for denoising");
                    // create parameter buffer
                    denoise.type = VAProcFilterNoiseReduction;
#ifdef TARGET_VPP_USE_GEN
                    char propValueString[PROPERTY_VALUE_MAX];

                    // placeholder for vpg driver: can't support denoise factor auto adjust, so leave config to user.
                    property_get("vpp.filter.denoise.factor", propValueString, "64.0");
                    denoise.value = atof(propValueString);
                    denoise.value = (denoise.value < 0.0f) ? 0.0f : denoise.value;
                    denoise.value = (denoise.value > 64.0f) ? 64.0f : denoise.value;
#else
                    denoise.value = denoiseCaps.range.min_value + DENOISE_DEBLOCK_STRENGTH * denoiseCaps.range.step;
#endif
                    vaStatus = vaCreateBuffer(mVADisplay, mVAContext,
                        VAProcFilterParameterBufferType, sizeof(denoise), 1,
                        &denoise, &denoiseId);
                    CHECK_VASTATUS("vaCreateBuffer for denoising");
                    mFilterBuffers[mNumFilterBuffers] = denoiseId;
                    mNumFilterBuffers++;
                }
                break;
            case VAProcFilterDeinterlacing:
                if ((mFilters & FilterDeinterlacing) != 0) {
                    numCaps = VAProcDeinterlacingCount;
                    vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext,
                            VAProcFilterDeinterlacing,
                            &deinterlacingCaps[0],
                            &numCaps);
                    CHECK_VASTATUS("vaQueryVideoProcFilterCaps for deinterlacing");
                    for (uint32_t i = 0; i < numCaps; i++)
                    {
                        VAProcFilterCapDeinterlacing * const cap = &deinterlacingCaps[i];
                        if (cap->type != VAProcDeinterlacingBob) // desired Deinterlacing Type
                            continue;

                        deint.type = VAProcFilterDeinterlacing;
                        deint.algorithm = VAProcDeinterlacingBob;
                        vaStatus = vaCreateBuffer(mVADisplay,
                                mVAContext,
                                VAProcFilterParameterBufferType,
                                sizeof(deint), 1,
                                &deint, &deintId);
                        CHECK_VASTATUS("vaCreateBuffer for deinterlacing");
                        mFilterBuffers[mNumFilterBuffers] = deintId;
                        mNumFilterBuffers++;
                    }
                }
                break;
            case VAProcFilterSharpening:
                if((mFilters & FilterSharpening) != 0) {
                    // check filter caps
                    numCaps = 1;
                    vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext,
                            VAProcFilterSharpening,
                            &sharpenCaps,
                            &numCaps);
                    CHECK_VASTATUS("vaQueryVideoProcFilterCaps for sharpening");
                    // create parameter buffer
                    sharpen.type = VAProcFilterSharpening;
#ifdef TARGET_VPP_USE_GEN
                    char propValueString[PROPERTY_VALUE_MAX];

                    // placeholder for vpg driver: can't support sharpness factor auto adjust, so leave config to user.
                    property_get("vpp.filter.sharpen.factor", propValueString, "8.0");
                    sharpen.value = atof(propValueString);
                    sharpen.value = (sharpen.value < 0.0f) ? 0.0f : sharpen.value;
                    sharpen.value = (sharpen.value > 64.0f) ? 64.0f : sharpen.value;
#else
                    sharpen.value = sharpenCaps.range.default_value;
#endif
                    vaStatus = vaCreateBuffer(mVADisplay, mVAContext,
                        VAProcFilterParameterBufferType, sizeof(sharpen), 1,
                        &sharpen, &sharpenId);
                    CHECK_VASTATUS("vaCreateBuffer for sharpening");
                    mFilterBuffers[mNumFilterBuffers] = sharpenId;
                    mNumFilterBuffers++;
                }
                break;
            case VAProcFilterColorBalance:
                if((mFilters & FilterColorBalance) != 0) {
                    uint32_t featureCount = 0;
                    // check filter caps
                    // FIXME: it's not used at all!
                    numCaps = COLOR_NUM;
                    vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext,
                            VAProcFilterColorBalance,
                            colorCaps,
                            &numCaps);
                    CHECK_VASTATUS("vaQueryVideoProcFilterCaps for color balance");
                    // create parameter buffer
                    for (uint32_t i = 0; i < numCaps; i++) {
                        if (colorCaps[i].type == VAProcColorBalanceAutoSaturation) {
                            color[i].type = VAProcFilterColorBalance;
                            color[i].attrib = VAProcColorBalanceAutoSaturation;
                            color[i].value = colorCaps[i].range.min_value + COLOR_STRENGTH * colorCaps[i].range.step;
                            featureCount++;
                        }
                        else if (colorCaps[i].type == VAProcColorBalanceAutoBrightness) {
                            color[i].type = VAProcFilterColorBalance;
                            color[i].attrib = VAProcColorBalanceAutoBrightness;
                            color[i].value = colorCaps[i].range.min_value + COLOR_STRENGTH * colorCaps[i].range.step;
                            featureCount++;
                        }
                    }
#ifdef TARGET_VPP_USE_GEN
                    //TODO: VPG need to support check input value by colorCaps.
                    enum {kHue = 0, kSaturation, kBrightness, kContrast};
                    char propValueString[PROPERTY_VALUE_MAX];
                    color[kHue].type = VAProcFilterColorBalance;
                    color[kHue].attrib = VAProcColorBalanceHue;

                    // placeholder for vpg driver: can't support auto color balance, so leave config to user.
                    property_get("vpp.filter.procamp.hue", propValueString, "179.0");
                    color[kHue].value = atof(propValueString);
                    color[kHue].value = (color[kHue].value < -180.0f) ? -180.0f : color[kHue].value;
                    color[kHue].value = (color[kHue].value > 180.0f) ? 180.0f : color[kHue].value;
                    featureCount++;

                    color[kSaturation].type   = VAProcFilterColorBalance;
                    color[kSaturation].attrib = VAProcColorBalanceSaturation;
                    property_get("vpp.filter.procamp.saturation", propValueString, "1.0");
                    color[kSaturation].value = atof(propValueString);
                    color[kSaturation].value = (color[kSaturation].value < 0.0f) ? 0.0f : color[kSaturation].value;
                    color[kSaturation].value = (color[kSaturation].value > 10.0f) ? 10.0f : color[kSaturation].value;
                    featureCount++;

                    color[kBrightness].type   = VAProcFilterColorBalance;
                    color[kBrightness].attrib = VAProcColorBalanceBrightness;
                    property_get("vpp.filter.procamp.brightness", propValueString, "0.0");
                    color[kBrightness].value = atof(propValueString);
                    color[kBrightness].value = (color[kBrightness].value < -100.0f) ? -100.0f : color[kBrightness].value;
                    color[kBrightness].value = (color[kBrightness].value > 100.0f) ? 100.0f : color[kBrightness].value;
                    featureCount++;

                    color[kContrast].type   = VAProcFilterColorBalance;
                    color[kContrast].attrib = VAProcColorBalanceContrast;
                    property_get("vpp.filter.procamp.contrast", propValueString, "1.0");
                    color[kContrast].value = atof(propValueString);
                    color[kContrast].value = (color[kContrast].value < 0.0f) ? 0.0f : color[kContrast].value;
                    color[kContrast].value = (color[kContrast].value > 10.0f) ? 10.0f : color[kContrast].value;
                    featureCount++;
#endif
                    vaStatus = vaCreateBuffer(mVADisplay, mVAContext,
                        VAProcFilterParameterBufferType, sizeof(*color), featureCount,
                        color, &colorId);
                    CHECK_VASTATUS("vaCreateBuffer for color balance");
                    mFilterBuffers[mNumFilterBuffers] = colorId;
                    mNumFilterBuffers++;
                }
                break;
            case VAProcFilterFrameRateConversion:
                if((mFilters & FilterFrameRateConversion) != 0) {
                    frc.type = VAProcFilterFrameRateConversion;
                    frc.input_fps = mFilterParam.frameRate;
                    switch (mFilterParam.frcRate){
                        case FRC_RATE_1X:
                            frc.output_fps = frc.input_fps;
                            break;
                        case FRC_RATE_2X:
                            frc.output_fps = frc.input_fps * 2;
                            break;
                        case FRC_RATE_2_5X:
                            frc.output_fps = frc.input_fps * 5/2;
                            break;
                        case FRC_RATE_4X:
                            frc.output_fps = frc.input_fps * 4;
                            break;
                    }
                    vaStatus = vaCreateBuffer(mVADisplay, mVAContext,
                        VAProcFilterParameterBufferType, sizeof(frc), 1,
                        &frc, &frcId);
                    CHECK_VASTATUS("vaCreateBuffer for frc");
                    mFilterBuffers[mNumFilterBuffers] = frcId;
                    mNumFilterBuffers++;
                    mFilterFrc = frcId;
                }
                break;
            case VAProcFilterSkinToneEnhancement:
                if((mFilters & FilterSkinToneEnhancement) != 0) {
                    // check filter caps
                    numCaps = 1;
                    vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext,
                            VAProcFilterSkinToneEnhancement,
                            &stdeCaps,
                            &numCaps);
                    CHECK_VASTATUS("vaQueryVideoProcFilterCaps for skintone");
                    // create parameter buffer
                    stde.type = VAProcFilterSkinToneEnhancement;
#ifdef TARGET_VPP_USE_GEN
                    char propValueString[PROPERTY_VALUE_MAX];

                    // placeholder for vpg driver: can't support skintone factor auto adjust, so leave config to user.
                    property_get("vpp.filter.skintone.factor", propValueString, "8.0");
                    stde.value = atof(propValueString);
                    stde.value = (stde.value < 0.0f) ? 0.0f : stde.value;
                    stde.value = (stde.value > 8.0f) ? 8.0f : stde.value;
#else
                    stde.value = stdeCaps.range.default_value;
#endif
                    vaStatus = vaCreateBuffer(mVADisplay, mVAContext,
                        VAProcFilterParameterBufferType, sizeof(stde), 1,
                        &stde, &stdeId);
                    CHECK_VASTATUS("vaCreateBuffer for skintone");
                    mFilterBuffers[mNumFilterBuffers] = stdeId;
                    mNumFilterBuffers++;
                }
                break;
            default:
                ALOGW("%s: Not supported filter 0x%08x", __func__, supportedFilters[i]);
                break;
        }
    }

    return setupPipelineCaps();
}

status_t ISVWorker::setupPipelineCaps() {
    ALOGV("setupPipelineCaps");
    //TODO color standards
    VAProcPipelineCaps pipelineCaps;
    VAStatus vaStatus;
    pipelineCaps.input_color_standards = in_color_standards;
    pipelineCaps.num_input_color_standards = VAProcColorStandardCount;
    pipelineCaps.output_color_standards = out_color_standards;
    pipelineCaps.num_output_color_standards = VAProcColorStandardCount;

    vaStatus = vaQueryVideoProcPipelineCaps(mVADisplay, mVAContext,
        mFilterBuffers, mNumFilterBuffers,
        &pipelineCaps);
    CHECK_VASTATUS("vaQueryVideoProcPipelineCaps");

    if (mForwardReferences != NULL) {
        free(mForwardReferences);
        mForwardReferences = NULL;
        mNumForwardReferences = 0;
    }

    mNumForwardReferences = pipelineCaps.num_forward_references;
    if (mNumForwardReferences > 0) {
        mForwardReferences = (VASurfaceID*)malloc(mNumForwardReferences * sizeof(VASurfaceID));
        if (mForwardReferences == NULL)
            return STATUS_ALLOCATION_ERROR;
        memset(mForwardReferences, 0, mNumForwardReferences * sizeof(VASurfaceID));
    }
    return STATUS_OK;
}

status_t ISVWorker::process(ISVBuffer* inputBuffer, Vector<ISVBuffer*> outputBuffer,
                             uint32_t outputCount, bool isEOS, uint32_t flags) {
    ALOGV("process: outputCount=%d, mInputIndex=%d", outputCount, mInputIndex);
    VASurfaceID input;
    VASurfaceID output[MAX_FRC_OUTPUT];
    VABufferID pipelineId;
    VAProcPipelineParameterBuffer *pipeline;
    VAProcFilterParameterBufferFrameRateConversion *frc;
    VAStatus vaStatus = STATUS_OK;
    uint32_t i = 0;

    if (isEOS) {
        if (mInputIndex == 0) {
            ALOGV("%s: don't need to flush VSP", __func__);
            return STATUS_OK;
        }
        input = VA_INVALID_SURFACE;
        outputCount = 1;
        output[0] = mPrevOutput;
    } else {
        if (!inputBuffer || outputBuffer.size() != outputCount) {
            ALOGE("%s: invalid input/output buffer", __func__);
            return STATUS_ERROR;
        }

        if (outputCount < 1 || outputCount > 4) {
            ALOGE("%s: invalid outputCount", __func__);
            return STATUS_ERROR;
        }

        input = inputBuffer->getSurface();
        for (i = 0; i < outputCount; i++) {
            output[i] = outputBuffer[i]->getSurface();
            if (output[i] == VA_INVALID_SURFACE) {
                ALOGE("invalid output buffer");
                return STATUS_ERROR;
            }
        }
    }

    // reference frames setting
    if (mNumForwardReferences > 0) {
        /* add previous frame into reference array*/
        for (i = 1; i < mNumForwardReferences; i++) {
            mForwardReferences[i - 1] = mForwardReferences[i];
        }

        //make last reference to input
        mForwardReferences[mNumForwardReferences - 1] = mPrevInput;
    }

    mPrevInput = input;

    // create pipeline parameter buffer
    vaStatus = vaCreateBuffer(mVADisplay,
            mVAContext,
            VAProcPipelineParameterBufferType,
            sizeof(VAProcPipelineParameterBuffer),
            1,
            NULL,
            &pipelineId);
    CHECK_VASTATUS("vaCreateBuffer for VAProcPipelineParameterBufferType");

    ALOGV("before vaBeginPicture");
    vaStatus = vaBeginPicture(mVADisplay, mVAContext, output[0]);
    CHECK_VASTATUS("vaBeginPicture");

    // map pipeline paramter buffer
    vaStatus = vaMapBuffer(mVADisplay, pipelineId, (void**)&pipeline);
    CHECK_VASTATUS("vaMapBuffer for pipeline parameter buffer");

    // frc pamameter setting
    if ((mFilters & FilterFrameRateConversion) != 0) {
        vaStatus = vaMapBuffer(mVADisplay, mFilterFrc, (void **)&frc);
        CHECK_VASTATUS("vaMapBuffer for frc parameter buffer");
        if (isEOS)
            frc->num_output_frames = 0;
        else
            frc->num_output_frames = outputCount - 1;
        frc->output_frames = output + 1;
    }

    // pipeline parameter setting
    VARectangle dst_region;
    dst_region.x = 0;
    dst_region.y = 0;
    dst_region.width = mFilterParam.dstWidth;
    dst_region.height = mFilterParam.dstHeight;

    VARectangle src_region;
    src_region.x = 0;
    src_region.y = 0;
    src_region.width = mFilterParam.srcWidth;
    src_region.height = mFilterParam.srcHeight;

    if (isEOS) {
        pipeline->surface = 0;
        pipeline->pipeline_flags = VA_PIPELINE_FLAG_END;
    }
    else {
        pipeline->surface = input;
        pipeline->pipeline_flags = 0;
    }
#ifdef TARGET_VPP_USE_GEN
    pipeline->surface_region = &src_region;
    pipeline->output_region = &dst_region;
    pipeline->surface_color_standard = VAProcColorStandardBT601;
    pipeline->output_color_standard = VAProcColorStandardBT601;
#else
    pipeline->surface_region = NULL;
    pipeline->output_region = NULL;//&output_region;
    pipeline->surface_color_standard = VAProcColorStandardNone;
    pipeline->output_color_standard = VAProcColorStandardNone;
    /* real rotate state will be decided in psb video */
    pipeline->rotation_state = 0;
#endif
    /* FIXME: set more meaningful background color */
    pipeline->output_background_color = 0;
    pipeline->filters = mFilterBuffers;
    pipeline->num_filters = mNumFilterBuffers;
    pipeline->forward_references = mForwardReferences;
    pipeline->num_forward_references = mNumForwardReferences;
    pipeline->backward_references = NULL;
    pipeline->num_backward_references = 0;

    //currently, we only transfer TOP field to frame, no frame rate change.
    if (flags & (OMX_BUFFERFLAG_TFF | OMX_BUFFERFLAG_BFF)) {
        pipeline->filter_flags = VA_TOP_FIELD;
    } else {
        pipeline->filter_flags = VA_FRAME_PICTURE;
    }

    if ((mFilters & FilterFrameRateConversion) != 0) {
        vaStatus = vaUnmapBuffer(mVADisplay, mFilterFrc);
        CHECK_VASTATUS("vaUnmapBuffer for frc parameter buffer");
    }

    vaStatus = vaUnmapBuffer(mVADisplay, pipelineId);
    CHECK_VASTATUS("vaUnmapBuffer for pipeline parameter buffer");

    ALOGV("before vaRenderPicture");
    // Send parameter to driver
    vaStatus = vaRenderPicture(mVADisplay, mVAContext, &pipelineId, 1);
    CHECK_VASTATUS("vaRenderPicture");

    ALOGV("before vaEndPicture");
    vaStatus = vaEndPicture(mVADisplay, mVAContext);
    CHECK_VASTATUS("vaEndPicture");
    
    if (isEOS) {
        vaStatus = vaSyncSurface(mVADisplay, mPrevOutput);
        CHECK_VASTATUS("vaSyncSurface");
        if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, pipelineId)) {
            ALOGE("%s: failed to destroy va buffer %d", __func__, pipelineId);
            return STATUS_ERROR;
        }
        return STATUS_OK;
    }

    mPrevOutput = output[0];
    mInputIndex++;

    Mutex::Autolock autoLock(mPipelineBufferLock);
    mPipelineBuffers.push_back(pipelineId);

    ALOGV("process, exit");
    return STATUS_OK;
}

status_t ISVWorker::fill(Vector<ISVBuffer*> outputBuffer, uint32_t outputCount) {
    ALOGV("fill, outputCount=%d, mOutputIndex=%d",outputCount, mOutputIndex);
    // get output surface
    VASurfaceID output[MAX_FRC_OUTPUT];
    VAStatus vaStatus;
    VASurfaceStatus surStatus;

    if (outputCount < 1)
        return STATUS_ERROR;
    // map GraphicBuffer to VASurface
    for (uint32_t i = 0; i < outputCount; i++) {

        output[i] = outputBuffer[i]->getSurface();
        if (output[i] == VA_INVALID_SURFACE) {
            ALOGE("invalid output buffer");
            return STATUS_ERROR;
        }
        //FIXME: only enable sync mode
#if 0
        vaStatus = vaQuerySurfaceStatus(mVADisplay, output[i],&surStatus);
        CHECK_VASTATUS("vaQuerySurfaceStatus");
        if (surStatus == VASurfaceRendering) {
            ALOGV("Rendering %d", i);
            /* The behavior of driver is: all output of one process task are return in one interruption.
               The whole outputs of one FRC task are all ready or none of them is ready.
               If the behavior changed, it hurts the performance.
            */
            if (0 != i) {
                ALOGW("*****Driver behavior changed. The performance is hurt.");
                ALOGW("Please check driver behavior: all output of one task return in one interruption.");
            }
            vaStatus = STATUS_DATA_RENDERING;
            break;
        }

        if ((surStatus != VASurfaceRendering) && (surStatus != VASurfaceReady)) {
            ALOGE("surface statu Error %d", surStatus);
            vaStatus = STATUS_ERROR;
        }
#endif
        vaStatus = vaSyncSurface(mVADisplay, output[i]);
        CHECK_VASTATUS("vaSyncSurface");
        vaStatus = STATUS_OK;
        mOutputCount++;
        //dumpYUVFrameData(output[i]);
    }

    {
        Mutex::Autolock autoLock(mPipelineBufferLock);
        if (vaStatus == STATUS_OK) {
            VABufferID pipelineBuffer = mPipelineBuffers.itemAt(0);
            if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, pipelineBuffer)) {
                ALOGE("%s: failed to destroy va buffer %d", __func__, pipelineBuffer);
                return STATUS_ERROR;
            }
            mPipelineBuffers.removeAt(0);
            mOutputIndex++;
        }
    }

    ALOGV("fill, exit");
    return vaStatus;
}

// Debug only
#define FRAME_OUTPUT_FILE_NV12 "/storage/sdcard0/vpp_output.nv12"
status_t ISVWorker::dumpYUVFrameData(VASurfaceID surfaceID) {
    status_t ret;
    if (surfaceID == VA_INVALID_SURFACE)
        return STATUS_ERROR;

    VAStatus vaStatus;
    VAImage image;
    unsigned char *data_ptr;

    vaStatus = vaDeriveImage(mVADisplay,
            surfaceID,
            &image);
    CHECK_VASTATUS("vaDeriveImage");

    vaStatus = vaMapBuffer(mVADisplay, image.buf, (void **)&data_ptr);
    CHECK_VASTATUS("vaMapBuffer");

    ret = writeNV12(mFilterParam.srcWidth, mFilterParam.srcHeight, data_ptr, image.pitches[0], image.pitches[1]);
    if (ret != STATUS_OK) {
        ALOGV("writeNV12 error");
        return STATUS_ERROR;
    }

    vaStatus = vaUnmapBuffer(mVADisplay, image.buf);
    CHECK_VASTATUS("vaUnMapBuffer");

    vaStatus = vaDestroyImage(mVADisplay,image.image_id);
    CHECK_VASTATUS("vaDestroyImage");

    return STATUS_OK;
}

status_t ISVWorker::reset() {
    status_t ret;
    ALOGV("reset");
    if (mOutputCount > 0) {
        ALOGI("======mVPPInputCount=%d, mVPPRenderCount=%d======",
                mInputIndex, mOutputCount);
    }
    mInputIndex = 0;
    mOutputIndex = 0;
    mOutputCount = 0;

    {
        Mutex::Autolock autoLock(mPipelineBufferLock);
        while (!mPipelineBuffers.isEmpty()) {
            VABufferID pipelineBuffer = mPipelineBuffers.itemAt(0);
            if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, pipelineBuffer)) {
                ALOGE("%s: failed to destroy va buffer %d", __func__, pipelineBuffer);
                return STATUS_ERROR;
            }
            mPipelineBuffers.removeAt(0);
        }
    }

    if (mNumFilterBuffers != 0) {
        for (uint32_t i = 0; i < mNumFilterBuffers; i++) {
            if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, mFilterBuffers[i]))
                ALOGW("%s: failed to destroy va buffer %d", __func__, mFilterBuffers[i]);
                //return STATUS_ERROR;
        }
        mNumFilterBuffers = 0;
        memset(&mFilterBuffers, VA_INVALID_ID, VAProcFilterCount * sizeof(VABufferID));
        mFilterFrc = VA_INVALID_ID;
    }

    // we need to clear the cache for reference surfaces
    if (mForwardReferences != NULL) {
        free(mForwardReferences);
        mForwardReferences = NULL;
        mNumForwardReferences = 0;
    }

    if (mVAContext != VA_INVALID_ID) {
         vaDestroyContext(mVADisplay, mVAContext);
         mVAContext = VA_INVALID_ID;
    }
    VAStatus vaStatus = vaCreateContext(mVADisplay, mVAConfig, mWidth, mHeight, 0, NULL, 0, &mVAContext);
    CHECK_VASTATUS("vaCreateContext");

    return setupFilters();
}

uint32_t ISVWorker::getVppOutputFps() {
    uint32_t outputFps;
    //mFilterParam.frcRate is 1 if FRC is disabled or input FPS is not changed by VPP.
    if (FRC_RATE_2_5X == mFilterParam.frcRate) {
        outputFps = mFilterParam.frameRate * 5 / 2;
    } else {
        outputFps = mFilterParam.frameRate * mFilterParam.frcRate;
    }

    ALOGV("vpp is on in settings %d %d %d", outputFps,  mFilterParam.frameRate, mFilterParam.frcRate);
    return outputFps;
}


status_t ISVWorker::writeNV12(int width, int height, unsigned char *out_buf, int y_pitch, int uv_pitch) {
    size_t result;
    int frame_size;
    unsigned char *y_start, *uv_start;
    int h;

    FILE *ofile = fopen(FRAME_OUTPUT_FILE_NV12, "ab");
    if(ofile == NULL) {
        ALOGE("Open %s failed!", FRAME_OUTPUT_FILE_NV12);
        return STATUS_ERROR;
    }

    if (out_buf == NULL)
    {
        fclose(ofile);
        return STATUS_ERROR;
    }
    if ((width % 2) || (height % 2))
    {
        fclose(ofile);
        return STATUS_ERROR;
    }
    // Set frame size
    frame_size = height * width * 3/2;

    /* write y */
    y_start = out_buf;
    for (h = 0; h < height; ++h) {
        result = fwrite(y_start, sizeof(unsigned char), width, ofile);
        y_start += y_pitch;
    }

    /* write uv */
    uv_start = out_buf + uv_pitch * height;
    for (h = 0; h < height / 2; ++h) {
        result = fwrite(uv_start, sizeof(unsigned char), width, ofile);
        uv_start += uv_pitch;
    }
    // Close file
    fclose(ofile);
    return STATUS_OK;
}