/*
 * Copyright (c) 2014 Intel Corporation. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * IN NO EVENT SHALL INTEL AND/OR ITS SUPPLIERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/*
 * Video process test case based on LibVA.
 * This test covers deinterlace, denoise, color balance, sharpening,
 * blending, scaling and several surface format conversion.
 * Usage: vavpp process.cfg
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/time.h>
#include <assert.h>
#include <va/va.h>
#include <va/va_vpp.h>
#include "va_display.h"

#define BLEND_ON        0

#ifndef VA_FOURCC_I420
#define VA_FOURCC_I420 0x30323449
#endif

#define MAX_LEN   1024

#define CHECK_VASTATUS(va_status,func)                                      \
  if (va_status != VA_STATUS_SUCCESS) {                                     \
      fprintf(stderr,"%s:%s (%d) failed,exit\n", __func__, func, __LINE__); \
      exit(1);                                                              \
  }

static VADisplay va_dpy = NULL;
static VAContextID context_id = 0;
static VAConfigID  config_id = 0;
static VAProcFilterType g_filter_type = VAProcFilterNone;
static VASurfaceID g_in_surface_id = VA_INVALID_ID;
static VASurfaceID g_out_surface_id = VA_INVALID_ID;

static FILE* g_config_file_fd = NULL;
static FILE* g_src_file_fd = NULL;
static FILE* g_dst_file_fd = NULL;

static char g_config_file_name[MAX_LEN];
static char g_src_file_name[MAX_LEN];
static char g_dst_file_name[MAX_LEN];
static char g_filter_type_name[MAX_LEN];

static uint32_t g_in_pic_width = 352;
static uint32_t g_in_pic_height = 288;
static uint32_t g_out_pic_width = 352;
static uint32_t g_out_pic_height = 288;

static uint32_t g_in_fourcc  = VA_FOURCC('N', 'V', '1', '2');
static uint32_t g_in_format  = VA_RT_FORMAT_YUV420;
static uint32_t g_out_fourcc = VA_FOURCC('N', 'V', '1', '2');
static uint32_t g_out_format = VA_RT_FORMAT_YUV420;
static uint32_t g_src_file_fourcc = VA_FOURCC('I', '4', '2', '0');
static uint32_t g_dst_file_fourcc = VA_FOURCC('Y', 'V', '1', '2');

static uint8_t g_blending_enabled = 0;
static uint8_t g_blending_min_luma = 1;
static uint8_t g_blending_max_luma = 254;

static uint32_t g_frame_count = 0;

static int8_t
read_value_string(FILE *fp, const char* field_name, char* value)
{
    char strLine[MAX_LEN];
    char* field;
    char* str;
    uint16_t i;

    if (!fp || !field_name || !value)  {
        printf("Invalid fuction parameters\n");
        return -1;
    }

    rewind(fp);

    while (!feof(fp)) {
        if (!fgets(strLine, MAX_LEN, fp))
            continue;

        for (i = 0; i < MAX_LEN && strLine[i]; i++)
            if (strLine[i] != ' ') break;

        if (i == MAX_LEN || strLine[i] == '#' || strLine[i] == '\n')
            continue;

        field = strtok(&strLine[i], ":");
        if (strncmp(field, field_name, strlen(field_name)))
            continue;

        if (!(str = strtok(NULL, ":")))
            continue;

        /* skip blank space in string */
        while (*str == ' ')
            str++;

        *(str + strlen(str) - 1) = '\0';
        strcpy(value, str);

        return 0;
    }

    return -1;
}

static int8_t
read_value_uint8(FILE* fp, const char* field_name, uint8_t* value)
{
    char str[MAX_LEN];

    if (read_value_string(fp, field_name, str)) {
        printf("Failed to find integer field: %s", field_name);
        return -1;
    }

    *value = (uint8_t)atoi(str);
    return 0;
}

static int8_t
read_value_uint32(FILE* fp, const char* field_name, uint32_t* value)
{
    char str[MAX_LEN];

    if (read_value_string(fp, field_name, str)) {
        printf("Failed to find integer field: %s", field_name);
        return -1;
    }

    *value = (uint32_t)atoi(str);
    return 0;
}

static int8_t
read_value_float(FILE *fp, const char* field_name, float* value)
{
    char str[MAX_LEN];
    if (read_value_string(fp, field_name, str)) {
        printf("Failed to find float field: %s \n", field_name);
        return -1;
    }

    *value = atof(str);
    return 0;
}

static float
adjust_to_range(VAProcFilterValueRange *range, float value)
{
    if (value < range->min_value || value > range->max_value) {
        printf("Value: %f exceed range: (%f ~ %f), force to use default: %f \n",
               value, range->min_value, range->max_value, range->default_value);
        return range->default_value;
    }

    return value;
}

static VAStatus
create_surface(VASurfaceID * p_surface_id,
               uint32_t width, uint32_t height,
               uint32_t fourCC, uint32_t format)
{
    VAStatus va_status;
    VASurfaceAttrib    surface_attrib;
    surface_attrib.type =  VASurfaceAttribPixelFormat;
    surface_attrib.flags = VA_SURFACE_ATTRIB_SETTABLE;
    surface_attrib.value.type = VAGenericValueTypeInteger;
    surface_attrib.value.value.i = fourCC;

    va_status = vaCreateSurfaces(va_dpy,
                                 format,
                                 width,
                                 height,
                                 p_surface_id,
                                 1,
                                 &surface_attrib,
                                 1);
    return va_status;
}

static VAStatus
construct_nv12_mask_surface(VASurfaceID surface_id,
                            uint8_t min_luma,
                            uint8_t max_luma)
{
    VAStatus va_status;
    VAImage surface_image;
    void *surface_p = NULL;
    unsigned char *y_dst, *u_dst;
    uint32_t row, col;

    va_status = vaDeriveImage(va_dpy, surface_id, &surface_image);
    CHECK_VASTATUS(va_status, "vaDeriveImage");

    va_status = vaMapBuffer(va_dpy, surface_image.buf, &surface_p);
    CHECK_VASTATUS(va_status, "vaMapBuffer");

    y_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);
    u_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);

    /* fill Y plane, the luma values of some pixels is in the range of min_luma~max_luma,
     * and others are out side of it, in luma key blending case, the pixels with Y value
     * exceeding the range will be hided*/
    for (row = 0; row < surface_image.height; row++) {
        if (row < surface_image.height / 4 || row > surface_image.height * 3 / 4)
            memset(y_dst, max_luma + 1, surface_image.pitches[0]);
        else
            memset(y_dst, (min_luma + max_luma) / 2, surface_image.pitches[0]);

        y_dst += surface_image.pitches[0];
    }

    /* fill UV plane */
    for (row = 0; row < surface_image.height / 2; row++) {
        for (col = 0; col < surface_image.width / 2; col++) {
            u_dst[col * 2] = 128;
            u_dst[col * 2 + 1] = 128;
        }
        u_dst += surface_image.pitches[1];
    }

    vaUnmapBuffer(va_dpy, surface_image.buf);
    vaDestroyImage(va_dpy, surface_image.image_id);

    return VA_STATUS_SUCCESS;
}

/* Load yuv frame to NV12/YV12/I420 surface*/
static VAStatus
upload_yuv_frame_to_yuv_surface(FILE *fp,
                                VASurfaceID surface_id)
{
    VAStatus va_status;
    VAImage surface_image;
    unsigned char *y_src, *u_src, *v_src;
    unsigned char *y_dst, *u_dst, *v_dst;
    void *surface_p = NULL;
    uint32_t frame_size, row, col;
    size_t n_items;
    unsigned char * newImageBuffer = NULL;

    va_status = vaDeriveImage(va_dpy, surface_id, &surface_image);
    CHECK_VASTATUS(va_status, "vaDeriveImage");

    va_status = vaMapBuffer(va_dpy, surface_image.buf, &surface_p);
    CHECK_VASTATUS(va_status, "vaMapBuffer");

    if (surface_image.format.fourcc == VA_FOURCC_YV12 ||
        surface_image.format.fourcc == VA_FOURCC_I420 ||
        surface_image.format.fourcc == VA_FOURCC_NV12) {

        frame_size = surface_image.width * surface_image.height * 3 / 2;
        newImageBuffer = (unsigned char*)malloc(frame_size);
        assert(newImageBuffer);

        do {
            n_items = fread(newImageBuffer, frame_size, 1, fp);
        } while (n_items != 1);

        y_src = newImageBuffer;
        if (g_src_file_fourcc == VA_FOURCC_I420) {
            u_src = newImageBuffer + surface_image.width * surface_image.height;
            v_src = newImageBuffer + surface_image.width * surface_image.height * 5 / 4;
        } else if (g_src_file_fourcc == VA_FOURCC_YV12) {
            v_src = newImageBuffer + surface_image.width * surface_image.height;
            u_src = newImageBuffer + surface_image.width * surface_image.height * 5 / 4;
        } else if (g_src_file_fourcc == VA_FOURCC_NV12) {
            u_src = newImageBuffer + surface_image.width * surface_image.height;
            v_src = u_src;
        } else {
            printf("Not supported YUV fourcc for input file !!!\n");
            free(newImageBuffer);
            return VA_STATUS_ERROR_INVALID_SURFACE;
        }

        y_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);

        if (surface_image.format.fourcc == VA_FOURCC_YV12) {
            v_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            u_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);
        } else if (surface_image.format.fourcc == VA_FOURCC_I420) {
            u_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);
        } else {
            u_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_dst = u_dst;
        }

        /* Y plane, directly copy */
        for (row = 0; row < surface_image.height; row++) {
            memcpy(y_dst, y_src, surface_image.width);
            y_dst += surface_image.pitches[0];
            y_src += surface_image.width;
        }

        /* UV plane */
        if (surface_image.format.fourcc == VA_FOURCC_YV12 ||
            surface_image.format.fourcc == VA_FOURCC_I420) {
            for (row = 0; row < surface_image.height / 2; row ++) {
                if (g_src_file_fourcc == VA_FOURCC_I420 ||
                    g_src_file_fourcc == VA_FOURCC_YV12) {
                    memcpy(v_dst, v_src, surface_image.width / 2);
                    memcpy(u_dst, u_src, surface_image.width / 2);

                    v_src += surface_image.width / 2;
                    u_src += surface_image.width / 2;
                } else {
                    for (col = 0; col < surface_image.width / 2; col++) {
                        u_dst[col] = u_src[col * 2];
                        v_dst[col] = u_src[col * 2 + 1];
                    }

                    u_src += surface_image.width;
                    v_src = u_src;
                }

                if (surface_image.format.fourcc == VA_FOURCC_YV12) {
                    v_dst += surface_image.pitches[1];
                    u_dst += surface_image.pitches[2];
                } else {
                    v_dst += surface_image.pitches[2];
                    u_dst += surface_image.pitches[1];
                }
            }
        } else if (surface_image.format.fourcc == VA_FOURCC_NV12) {
            for (row = 0; row < surface_image.height / 2; row++) {
                if (g_src_file_fourcc == VA_FOURCC_I420 ||
                    g_src_file_fourcc == VA_FOURCC_YV12) {
                    for (col = 0; col < surface_image.width / 2; col++) {
                        u_dst[col * 2] = u_src[col];
                        u_dst[col * 2 + 1] = v_src[col];
                    }

                    u_src += (surface_image.width / 2);
                    v_src += (surface_image.width / 2);
                } else {
                    memcpy(u_dst, u_src, surface_image.width);
                    u_src += surface_image.width;
                    v_src = u_src;
                }

                u_dst += surface_image.pitches[1];
            }
        }
    } else if ((surface_image.format.fourcc == VA_FOURCC_YUY2 &&
                g_src_file_fourcc == VA_FOURCC_YUY2) ||
               (surface_image.format.fourcc == VA_FOURCC_UYVY &&
                g_src_file_fourcc == VA_FOURCC_UYVY) ||
               (surface_image.format.fourcc == VA_FOURCC_AYUV &&
                g_src_file_fourcc == VA_FOURCC_AYUV)) {
        uint32_t byte_per_pixel = (g_src_file_fourcc == VA_FOURCC_AYUV) ? 4 : 2;

        frame_size = surface_image.width * surface_image.height * byte_per_pixel;
        newImageBuffer = (unsigned char*)malloc(frame_size);
        assert(newImageBuffer);

        do {
            n_items = fread(newImageBuffer, frame_size, 1, fp);
        } while (n_items != 1);

        y_src = newImageBuffer;
        y_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);

        /* plane 0, directly copy */
        for (row = 0; row < surface_image.height; row++) {
            memcpy(y_dst, y_src, surface_image.width * byte_per_pixel);
            y_src += surface_image.width * byte_per_pixel;
            y_dst += surface_image.pitches[0];
        }
    } else if ((surface_image.format.fourcc == VA_FOURCC_P010 &&
                g_src_file_fourcc == VA_FOURCC_P010) ||
               (surface_image.format.fourcc == VA_FOURCC_I010 &&
                g_src_file_fourcc == VA_FOURCC_I010)) {
        frame_size = surface_image.width * surface_image.height * 3;
        newImageBuffer = (unsigned char*)malloc(frame_size);
        assert(newImageBuffer);

        do {
            n_items = fread(newImageBuffer, frame_size, 1, fp);
        } while (n_items != 1);

        y_src = newImageBuffer;
        y_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);

        /* plane 0, directly copy */
        for (row = 0; row < surface_image.height; row++) {
            memcpy(y_dst, y_src, surface_image.width * 2);
            y_src += surface_image.width * 2;
            y_dst += surface_image.pitches[0];
        }

        /* UV plane */
        if (surface_image.format.fourcc == VA_FOURCC_I010) {
            assert(g_src_file_fourcc == VA_FOURCC_I010);

            u_src = newImageBuffer + surface_image.width * surface_image.height * 2;
            v_src = newImageBuffer + surface_image.width * surface_image.height * 5 / 2;

            u_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);

            for (row = 0; row < surface_image.height / 2; row++) {
                memcpy(u_dst, u_src, surface_image.width);
                memcpy(v_dst, v_src, surface_image.width);

                u_src += surface_image.width;
                v_src += surface_image.width;

                u_dst += surface_image.pitches[1];
                v_dst += surface_image.pitches[2];
            }
        } else if (surface_image.format.fourcc == VA_FOURCC_P010) {
            assert(g_src_file_fourcc == VA_FOURCC_P010);

            u_src = newImageBuffer + surface_image.width * surface_image.height * 2;
            v_src = u_src;

            u_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_dst = u_dst;

            for (row = 0; row < surface_image.height / 2; row++) {
                memcpy(u_dst, u_src, surface_image.width * 2);

                u_src += surface_image.width * 2;
                v_src = u_src;

                u_dst += surface_image.pitches[1];
                v_dst = u_dst;
            }
        }
    }  else if ((surface_image.format.fourcc == VA_FOURCC_RGBA &&
                 g_src_file_fourcc == VA_FOURCC_RGBA) ||
                (surface_image.format.fourcc == VA_FOURCC_RGBX &&
                 g_src_file_fourcc == VA_FOURCC_RGBX) ||
                (surface_image.format.fourcc == VA_FOURCC_BGRA &&
                 g_src_file_fourcc == VA_FOURCC_BGRA) ||
                (surface_image.format.fourcc == VA_FOURCC_BGRX &&
                 g_src_file_fourcc == VA_FOURCC_BGRX)) {
        frame_size = surface_image.width * surface_image.height * 4;
        newImageBuffer = (unsigned char*)malloc(frame_size);
        assert(newImageBuffer);

        do {
            n_items = fread(newImageBuffer, frame_size, 1, fp);
        } while (n_items != 1);

        y_src = newImageBuffer;
        y_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);

        /* plane 0, directly copy */
        for (row = 0; row < surface_image.height; row++) {
            memcpy(y_dst, y_src, surface_image.width * 4);
            y_src += surface_image.width * 4;
            y_dst += surface_image.pitches[0];
        }
    } else if (surface_image.format.fourcc == VA_FOURCC_RGBP &&
               g_src_file_fourcc == VA_FOURCC_RGBP) {
        int i;

        frame_size = surface_image.width * surface_image.height * 3;
        newImageBuffer = (unsigned char*)malloc(frame_size);
        assert(newImageBuffer);

        do {
            n_items = fread(newImageBuffer, frame_size, 1, fp);
        } while (n_items != 1);

        y_src = newImageBuffer;

        for (i = 0; i < 3; i++) {
            y_dst = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[i]);

            for (row = 0; row < surface_image.height; row++) {
                memcpy(y_dst, y_src, surface_image.width);
                y_src += surface_image.width;
                y_dst += surface_image.pitches[i];
            }
        }
    } else {
        printf("Not supported YUV surface fourcc !!! \n");
        return VA_STATUS_ERROR_INVALID_SURFACE;
    }

    if (newImageBuffer) {
        free(newImageBuffer);
        newImageBuffer = NULL;
    }

    vaUnmapBuffer(va_dpy, surface_image.buf);
    vaDestroyImage(va_dpy, surface_image.image_id);

    return VA_STATUS_SUCCESS;
}

/* Store NV12/YV12/I420 surface to yv12 file */
static VAStatus
store_yuv_surface_to_yv12_file(FILE *fp,
                               VASurfaceID surface_id)
{
    VAStatus va_status;
    VAImage surface_image;
    void *surface_p = NULL;
    unsigned char *y_src, *u_src, *v_src;
    unsigned char *y_dst, *u_dst, *v_dst;
    uint32_t row, col;
    int32_t n_items;
    unsigned char * newImageBuffer = NULL;

    va_status = vaDeriveImage(va_dpy, surface_id, &surface_image);
    CHECK_VASTATUS(va_status, "vaDeriveImage");

    va_status = vaMapBuffer(va_dpy, surface_image.buf, &surface_p);
    CHECK_VASTATUS(va_status, "vaMapBuffer");

    /* store the surface to one YV12 file or one bmp file*/
    if (surface_image.format.fourcc == VA_FOURCC_YV12 ||
        surface_image.format.fourcc == VA_FOURCC_I420 ||
        surface_image.format.fourcc == VA_FOURCC_NV12) {

        uint32_t y_size = surface_image.width * surface_image.height;
        uint32_t u_size = y_size / 4;

        newImageBuffer = (unsigned char*)malloc(y_size * 3 / 2);
        assert(newImageBuffer);

        /* stored as YV12 format */
        y_dst = newImageBuffer;
        v_dst = newImageBuffer + y_size;
        u_dst = newImageBuffer + y_size + u_size;

        y_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);
        if (surface_image.format.fourcc == VA_FOURCC_YV12) {
            v_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);
        } else if (surface_image.format.fourcc == VA_FOURCC_I420) {
            u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);
        } else if (surface_image.format.fourcc == VA_FOURCC_NV12) {
            u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_src = u_src;
        }

        /* Y plane copy */
        for (row = 0; row < surface_image.height; row++) {
            memcpy(y_dst, y_src, surface_image.width);
            y_src += surface_image.pitches[0];
            y_dst += surface_image.width;
        }

        /* UV plane copy */
        if (surface_image.format.fourcc == VA_FOURCC_YV12 ||
            surface_image.format.fourcc == VA_FOURCC_I420) {
            for (row = 0; row < surface_image.height / 2; row ++) {
                memcpy(v_dst, v_src, surface_image.width / 2);
                memcpy(u_dst, u_src, surface_image.width / 2);

                v_dst += surface_image.width / 2;
                u_dst += surface_image.width / 2;

                if (surface_image.format.fourcc == VA_FOURCC_YV12) {
                    v_src += surface_image.pitches[1];
                    u_src += surface_image.pitches[2];
                } else {
                    v_src += surface_image.pitches[2];
                    u_src += surface_image.pitches[1];
                }
            }
        } else if (surface_image.format.fourcc == VA_FOURCC_NV12) {
            for (row = 0; row < surface_image.height / 2; row++) {
                for (col = 0; col < surface_image.width / 2; col++) {
                    u_dst[col] = u_src[col * 2];
                    v_dst[col] = u_src[col * 2 + 1];
                }

                u_src += surface_image.pitches[1];
                u_dst += (surface_image.width / 2);
                v_dst += (surface_image.width / 2);
            }
        }

        /* write frame to file */
        do {
            n_items = fwrite(newImageBuffer, y_size * 3 / 2, 1, fp);
        } while (n_items != 1);

    } else {
        printf("Not supported YUV surface fourcc !!! \n");
        return VA_STATUS_ERROR_INVALID_SURFACE;
    }

    if (newImageBuffer) {
        free(newImageBuffer);
        newImageBuffer = NULL;
    }

    vaUnmapBuffer(va_dpy, surface_image.buf);
    vaDestroyImage(va_dpy, surface_image.image_id);

    return VA_STATUS_SUCCESS;
}

static VAStatus
store_yuv_surface_to_i420_file(FILE *fp,
                               VASurfaceID surface_id)
{
    VAStatus va_status;
    VAImage surface_image;
    void *surface_p = NULL;
    unsigned char *y_src, *u_src, *v_src;
    unsigned char *y_dst, *u_dst, *v_dst;
    uint32_t row, col;
    int32_t n_items;
    unsigned char * newImageBuffer = NULL;

    va_status = vaDeriveImage(va_dpy, surface_id, &surface_image);
    CHECK_VASTATUS(va_status, "vaDeriveImage");

    va_status = vaMapBuffer(va_dpy, surface_image.buf, &surface_p);
    CHECK_VASTATUS(va_status, "vaMapBuffer");

    /* store the surface to one i420 file */
    if (surface_image.format.fourcc == VA_FOURCC_YV12 ||
        surface_image.format.fourcc == VA_FOURCC_I420 ||
        surface_image.format.fourcc == VA_FOURCC_NV12) {

        uint32_t y_size = surface_image.width * surface_image.height;
        uint32_t u_size = y_size / 4;

        newImageBuffer = (unsigned char*)malloc(y_size * 3 / 2);
        assert(newImageBuffer);

        /* stored as YV12 format */
        y_dst = newImageBuffer;
        u_dst = newImageBuffer + y_size;
        v_dst = newImageBuffer + y_size + u_size;

        y_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);
        if (surface_image.format.fourcc == VA_FOURCC_YV12) {
            v_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);
        } else if (surface_image.format.fourcc == VA_FOURCC_I420) {
            u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);
        } else if (surface_image.format.fourcc == VA_FOURCC_NV12) {
            u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_src = u_src;
        }

        /* Y plane copy */
        for (row = 0; row < surface_image.height; row++) {
            memcpy(y_dst, y_src, surface_image.width);
            y_src += surface_image.pitches[0];
            y_dst += surface_image.width;
        }

        /* UV plane copy */
        if (surface_image.format.fourcc == VA_FOURCC_YV12 ||
            surface_image.format.fourcc == VA_FOURCC_I420) {
            for (row = 0; row < surface_image.height / 2; row ++) {
                memcpy(v_dst, v_src, surface_image.width / 2);
                memcpy(u_dst, u_src, surface_image.width / 2);

                v_dst += surface_image.width / 2;
                u_dst += surface_image.width / 2;

                if (surface_image.format.fourcc == VA_FOURCC_YV12) {
                    v_src += surface_image.pitches[1];
                    u_src += surface_image.pitches[2];
                } else {
                    v_src += surface_image.pitches[2];
                    u_src += surface_image.pitches[1];
                }
            }
        } else if (surface_image.format.fourcc == VA_FOURCC_NV12) {
            for (row = 0; row < surface_image.height / 2; row++) {
                for (col = 0; col < surface_image.width / 2; col++) {
                    u_dst[col] = u_src[col * 2];
                    v_dst[col] = u_src[col * 2 + 1];
                }

                u_src += surface_image.pitches[1];
                u_dst += (surface_image.width / 2);
                v_dst += (surface_image.width / 2);
            }
        }

        /* write frame to file */
        do {
            n_items = fwrite(newImageBuffer, y_size * 3 / 2, 1, fp);
        } while (n_items != 1);

    } else {
        printf("Not supported YUV surface fourcc !!! \n");
        return VA_STATUS_ERROR_INVALID_SURFACE;
    }

    if (newImageBuffer) {
        free(newImageBuffer);
        newImageBuffer = NULL;
    }

    vaUnmapBuffer(va_dpy, surface_image.buf);
    vaDestroyImage(va_dpy, surface_image.image_id);

    return VA_STATUS_SUCCESS;
}

static VAStatus
store_yuv_surface_to_nv12_file(FILE *fp,
                               VASurfaceID surface_id)
{
    VAStatus va_status;
    VAImage surface_image;
    void *surface_p = NULL;
    unsigned char *y_src, *u_src, *v_src;
    unsigned char *y_dst, *u_dst, *v_dst;
    uint32_t row, col;
    int32_t n_items;
    unsigned char * newImageBuffer = NULL;

    va_status = vaDeriveImage(va_dpy, surface_id, &surface_image);
    CHECK_VASTATUS(va_status, "vaDeriveImage");

    va_status = vaMapBuffer(va_dpy, surface_image.buf, &surface_p);
    CHECK_VASTATUS(va_status, "vaMapBuffer");

    /* store the surface to one nv12 file */
    if (surface_image.format.fourcc == VA_FOURCC_YV12 ||
        surface_image.format.fourcc == VA_FOURCC_I420 ||
        surface_image.format.fourcc == VA_FOURCC_NV12) {

        uint32_t y_size = surface_image.width * surface_image.height;

        newImageBuffer = (unsigned char*)malloc(y_size * 3 / 2);
        assert(newImageBuffer);

        /* stored as YV12 format */
        y_dst = newImageBuffer;
        u_dst = v_dst = newImageBuffer + y_size;

        y_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);

        if (surface_image.format.fourcc == VA_FOURCC_YV12) {
            v_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);
        } else if (surface_image.format.fourcc == VA_FOURCC_I420) {
            u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);
        } else if (surface_image.format.fourcc == VA_FOURCC_NV12) {
            u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
            v_src = u_src;
        }

        /* Y plane copy */
        for (row = 0; row < surface_image.height; row++) {
            memcpy(y_dst, y_src, surface_image.width);
            y_src += surface_image.pitches[0];
            y_dst += surface_image.width;
        }

        /* UV plane copy */
        if (surface_image.format.fourcc == VA_FOURCC_YV12 ||
            surface_image.format.fourcc == VA_FOURCC_I420) {
            for (row = 0; row < surface_image.height / 2; row ++) {
                for (col = 0; col < surface_image.width / 2; col++) {
                    u_dst[col * 2] = u_src[col];
                    u_dst[col * 2 + 1] = v_src[col];
                }

                u_dst += surface_image.width;

                if (surface_image.format.fourcc == VA_FOURCC_YV12) {
                    v_src += surface_image.pitches[1];
                    u_src += surface_image.pitches[2];
                } else {
                    v_src += surface_image.pitches[2];
                    u_src += surface_image.pitches[1];
                }
            }
        } else if (surface_image.format.fourcc == VA_FOURCC_NV12) {
            for (row = 0; row < surface_image.height / 2; row++) {
                memcpy(u_dst, u_src, surface_image.width);
                u_dst += surface_image.width;
                u_src += surface_image.pitches[1];
            }
        }

        /* write frame to file */
        do {
            n_items = fwrite(newImageBuffer, y_size * 3 / 2, 1, fp);
        } while (n_items != 1);

    } else {
        printf("Not supported YUV surface fourcc !!! \n");
        return VA_STATUS_ERROR_INVALID_SURFACE;
    }

    if (newImageBuffer) {
        free(newImageBuffer);
        newImageBuffer = NULL;
    }

    vaUnmapBuffer(va_dpy, surface_image.buf);
    vaDestroyImage(va_dpy, surface_image.image_id);

    return VA_STATUS_SUCCESS;
}

static VAStatus
store_packed_yuv_surface_to_packed_file(FILE *fp,
                                        VASurfaceID surface_id)
{
    VAStatus va_status;
    VAImage surface_image;
    void *surface_p = NULL;
    unsigned char *y_src;
    unsigned char *y_dst;
    uint32_t row;
    int32_t n_items;
    unsigned char * newImageBuffer = NULL;

    va_status = vaDeriveImage(va_dpy, surface_id, &surface_image);
    CHECK_VASTATUS(va_status, "vaDeriveImage");

    va_status = vaMapBuffer(va_dpy, surface_image.buf, &surface_p);
    CHECK_VASTATUS(va_status, "vaMapBuffer");

    /* store the surface to one YUY2 or UYVY file */
    if (surface_image.format.fourcc == VA_FOURCC_YUY2 ||
        surface_image.format.fourcc == VA_FOURCC_UYVY ||
        surface_image.format.fourcc == VA_FOURCC_AYUV) {
        uint32_t byte_per_pixel = (surface_image.format.fourcc == VA_FOURCC_AYUV ? 4 : 2);
        uint32_t frame_size = surface_image.width * surface_image.height * byte_per_pixel;

        newImageBuffer = (unsigned char*)malloc(frame_size);
        assert(newImageBuffer);
        memset(newImageBuffer, 0, frame_size);

        /* stored as YUY2 or UYVY format */
        y_dst = newImageBuffer;

        y_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);

        /* Plane 0 copy */
        for (row = 0; row < surface_image.height; row++) {
            memcpy(y_dst, y_src, surface_image.width * byte_per_pixel);
            y_src += surface_image.pitches[0];
            y_dst += surface_image.width * byte_per_pixel;
        }

        /* write frame to file */
        do {
            n_items = fwrite(newImageBuffer, frame_size, 1, fp);
        } while (n_items != 1);

    } else {
        printf("Not supported YUV surface fourcc !!! \n");
        return VA_STATUS_ERROR_INVALID_SURFACE;
    }

    if (newImageBuffer) {
        free(newImageBuffer);
        newImageBuffer = NULL;
    }

    vaUnmapBuffer(va_dpy, surface_image.buf);
    vaDestroyImage(va_dpy, surface_image.image_id);

    return VA_STATUS_SUCCESS;
}

static VAStatus
store_yuv_surface_to_10bit_file(FILE *fp, VASurfaceID surface_id)
{
    VAStatus va_status;
    VAImage surface_image;
    void *surface_p = NULL;
    unsigned char *y_src, *u_src, *v_src;
    unsigned char *y_dst, *u_dst, *v_dst;
    uint32_t row;
    int32_t n_items;
    unsigned char * newImageBuffer = NULL;

    va_status = vaDeriveImage(va_dpy, surface_id, &surface_image);
    CHECK_VASTATUS(va_status, "vaDeriveImage");

    va_status = vaMapBuffer(va_dpy, surface_image.buf, &surface_p);
    CHECK_VASTATUS(va_status, "vaMapBuffer");

    /* store the surface to one 10bit file */
    uint32_t y_size = surface_image.width * surface_image.height * 2;
    uint32_t u_size = y_size / 4;

    newImageBuffer = (unsigned char*)malloc(y_size * 3);
    assert(newImageBuffer);
    y_dst = newImageBuffer;

    y_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);

    /* Y plane copy */
    for (row = 0; row < surface_image.height; row++) {
        memcpy(y_dst, y_src, surface_image.width * 2);
        y_src += surface_image.pitches[0];
        y_dst += surface_image.width * 2;
    }

    if (surface_image.format.fourcc == VA_FOURCC_I010) {
        u_dst = newImageBuffer + y_size;
        v_dst = newImageBuffer + y_size + u_size;

        u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
        v_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[2]);

        for (row = 0; row < surface_image.height / 2; row++) {
            memcpy(u_dst, u_src, surface_image.width);
            memcpy(v_dst, v_src, surface_image.width);

            u_dst += surface_image.width;
            v_dst += surface_image.width;

            u_src += surface_image.pitches[1];
            v_src += surface_image.pitches[2];
        }
    } else if (surface_image.format.fourcc == VA_FOURCC_P010) {
        u_dst = newImageBuffer + y_size;
        v_dst = u_dst;

        u_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[1]);
        v_src = u_src;

        for (row = 0; row < surface_image.height / 2; row++) {
            memcpy(u_dst, u_src, surface_image.width * 2);
            u_dst += surface_image.width * 2;
            u_src += surface_image.pitches[1];
        }
    } else {
        printf("Not supported YUV surface fourcc !!! \n");
        free(newImageBuffer);
        return VA_STATUS_ERROR_INVALID_SURFACE;
    }

    /* write frame to file */
    do {
        n_items = fwrite(newImageBuffer, y_size * 3 / 2, 1, fp);
    } while (n_items != 1);

    if (newImageBuffer) {
        free(newImageBuffer);
        newImageBuffer = NULL;
    }

    vaUnmapBuffer(va_dpy, surface_image.buf);
    vaDestroyImage(va_dpy, surface_image.image_id);

    return VA_STATUS_SUCCESS;
}

static VAStatus
store_rgb_surface_to_rgb_file(FILE *fp, VASurfaceID surface_id)
{
    VAStatus va_status;
    VAImage surface_image;
    void *surface_p = NULL;
    unsigned char *y_src;
    unsigned char *y_dst;
    uint32_t frame_size, row;
    int32_t n_items;
    unsigned char * newImageBuffer = NULL;

    va_status = vaDeriveImage(va_dpy, surface_id, &surface_image);
    CHECK_VASTATUS(va_status, "vaDeriveImage");

    va_status = vaMapBuffer(va_dpy, surface_image.buf, &surface_p);
    CHECK_VASTATUS(va_status, "vaMapBuffer");

    frame_size = surface_image.width * surface_image.height * 4;
    newImageBuffer = (unsigned char*)malloc(frame_size);
    assert(newImageBuffer);
    y_dst = newImageBuffer;

    y_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[0]);

    for (row = 0; row < surface_image.height; row++) {
        memcpy(y_dst, y_src, surface_image.width * 4);
        y_src += surface_image.pitches[0];
        y_dst += surface_image.width * 4;
    }

    /* write frame to file */
    do {
        n_items = fwrite(newImageBuffer, frame_size, 1, fp);
    } while (n_items != 1);

    if (newImageBuffer) {
        free(newImageBuffer);
        newImageBuffer = NULL;
    }

    vaUnmapBuffer(va_dpy, surface_image.buf);
    vaDestroyImage(va_dpy, surface_image.image_id);

    return VA_STATUS_SUCCESS;
}

static VAStatus
store_rgbp_surface_to_rgbp_file(FILE *fp, VASurfaceID surface_id)
{
    VAStatus va_status;
    VAImage surface_image;
    void *surface_p = NULL;
    unsigned char *y_src;
    unsigned char *y_dst;
    uint32_t frame_size, row;
    int32_t n_items;
    unsigned char * newImageBuffer = NULL;
    int i;

    va_status = vaDeriveImage(va_dpy, surface_id, &surface_image);
    CHECK_VASTATUS(va_status, "vaDeriveImage");

    va_status = vaMapBuffer(va_dpy, surface_image.buf, &surface_p);
    CHECK_VASTATUS(va_status, "vaMapBuffer");

    frame_size = surface_image.width * surface_image.height * 3;
    newImageBuffer = (unsigned char*)malloc(frame_size);
    assert(newImageBuffer);
    y_dst = newImageBuffer;

    for (i = 0; i < 3; i++) {
        y_src = (unsigned char *)((unsigned char*)surface_p + surface_image.offsets[i]);

        for (row = 0; row < surface_image.height; row++) {
            memcpy(y_dst, y_src, surface_image.width);
            y_src += surface_image.pitches[i];
            y_dst += surface_image.width;
        }
    }

    /* write frame to file */
    do {
        n_items = fwrite(newImageBuffer, frame_size, 1, fp);
    } while (n_items != 1);

    if (newImageBuffer) {
        free(newImageBuffer);
        newImageBuffer = NULL;
    }

    vaUnmapBuffer(va_dpy, surface_image.buf);
    vaDestroyImage(va_dpy, surface_image.image_id);

    return VA_STATUS_SUCCESS;
}

static VAStatus
store_yuv_surface_to_file(FILE *fp,
                          VASurfaceID surface_id)
{
    if (g_out_fourcc == VA_FOURCC_YV12 ||
        g_out_fourcc == VA_FOURCC_I420 ||
        g_out_fourcc == VA_FOURCC_NV12) {
        if (g_dst_file_fourcc == VA_FOURCC_YV12)
            return store_yuv_surface_to_yv12_file(fp, surface_id);
        else if (g_dst_file_fourcc == VA_FOURCC_I420)
            return store_yuv_surface_to_i420_file(fp, surface_id);
        else if (g_dst_file_fourcc == VA_FOURCC_NV12)
            return store_yuv_surface_to_nv12_file(fp, surface_id);
        else {
            printf("Not supported YUV fourcc for output !!!\n");
            return VA_STATUS_ERROR_INVALID_SURFACE;
        }
    } else if ((g_out_fourcc == VA_FOURCC_YUY2 &&
                g_dst_file_fourcc == VA_FOURCC_YUY2) ||
               (g_out_fourcc == VA_FOURCC_UYVY &&
                g_dst_file_fourcc == VA_FOURCC_UYVY) ||
               (g_out_fourcc == VA_FOURCC_AYUV &&
                g_dst_file_fourcc == VA_FOURCC_AYUV)) {
        return store_packed_yuv_surface_to_packed_file(fp, surface_id);
    } else if ((g_out_fourcc == VA_FOURCC_I010 &&
                g_dst_file_fourcc == VA_FOURCC_I010) ||
               (g_out_fourcc == VA_FOURCC_P010 &&
                g_dst_file_fourcc == VA_FOURCC_P010)) {
        return store_yuv_surface_to_10bit_file(fp, surface_id);
    } else if ((g_out_fourcc == VA_FOURCC_RGBA &&
                g_dst_file_fourcc == VA_FOURCC_RGBA) ||
               (g_out_fourcc == VA_FOURCC_RGBX &&
                g_dst_file_fourcc == VA_FOURCC_RGBX) ||
               (g_out_fourcc == VA_FOURCC_BGRA &&
                g_dst_file_fourcc == VA_FOURCC_BGRA) ||
               (g_out_fourcc == VA_FOURCC_BGRX &&
                g_dst_file_fourcc == VA_FOURCC_BGRX)) {
        return store_rgb_surface_to_rgb_file(fp, surface_id);
    } else if (g_out_fourcc == VA_FOURCC_RGBP ||
               g_out_fourcc == VA_FOURCC_BGRP) {
        return store_rgbp_surface_to_rgbp_file(fp, surface_id);
    } else {
        printf("Not supported YUV fourcc for output !!!\n");
        return VA_STATUS_ERROR_INVALID_SURFACE;
    }
}

static VAStatus
denoise_filter_init(VABufferID *filter_param_buf_id)
{
    VAStatus va_status = VA_STATUS_SUCCESS;
    VAProcFilterParameterBuffer denoise_param;
    VABufferID denoise_param_buf_id;
    float intensity;

    VAProcFilterCap denoise_caps;
    uint32_t num_denoise_caps = 1;
    va_status = vaQueryVideoProcFilterCaps(va_dpy, context_id,
                                           VAProcFilterNoiseReduction,
                                           &denoise_caps, &num_denoise_caps);
    CHECK_VASTATUS(va_status, "vaQueryVideoProcFilterCaps");

    if (read_value_float(g_config_file_fd, "DENOISE_INTENSITY", &intensity)) {
        printf("Read denoise intensity failed, use default value");
        intensity = denoise_caps.range.default_value;
    }
    intensity = adjust_to_range(&denoise_caps.range, intensity);

    denoise_param.type  = VAProcFilterNoiseReduction;
    denoise_param.value = intensity;

    printf("Denoise intensity: %f\n", intensity);

    va_status = vaCreateBuffer(va_dpy, context_id,
                               VAProcFilterParameterBufferType, sizeof(denoise_param), 1,
                               &denoise_param, &denoise_param_buf_id);
    CHECK_VASTATUS(va_status, "vaCreateBuffer");

    *filter_param_buf_id = denoise_param_buf_id;

    return va_status;
}

/*
 * This is a method to initialize STDE filter.
 * If this filter is called, it is enabled by default.
 */
static VAStatus
skintone_filter_init(VABufferID *filter_param_buf_id)
{
    VAStatus va_status = VA_STATUS_SUCCESS;
    VAProcFilterParameterBuffer stde_param;
    VABufferID stde_param_buf_id;
    uint8_t stde_factor = 0;

    if (read_value_uint8(g_config_file_fd, "STDE_FACTOR", &stde_factor)) {
        printf("Read STDE Factor failed, use default value");
        stde_factor = 0;
    }

    printf("Applying STDE factor: %d\n", stde_factor);

    stde_param.type  = VAProcFilterSkinToneEnhancement;
    stde_param.value = stde_factor;

    va_status = vaCreateBuffer(va_dpy, context_id,
                               VAProcFilterParameterBufferType, sizeof(stde_param), 1,
                               &stde_param, &stde_param_buf_id);
    CHECK_VASTATUS(va_status, "vaCreateBuffer");

    *filter_param_buf_id = stde_param_buf_id;

    return va_status;
}

static VAStatus
deinterlace_filter_init(VABufferID *filter_param_buf_id)
{
    VAStatus va_status = VA_STATUS_SUCCESS;
    VAProcFilterParameterBufferDeinterlacing deinterlacing_param;
    VABufferID deinterlacing_param_buf_id;
    char algorithm_str[MAX_LEN], flags_str[MAX_LEN];
    uint32_t i;

    /* read and check whether configured deinterlace algorithm is supported */
    deinterlacing_param.algorithm  = VAProcDeinterlacingBob;
    if (!read_value_string(g_config_file_fd, "DEINTERLACING_ALGORITHM", algorithm_str)) {
        printf("Deinterlacing algorithm in config: %s \n", algorithm_str);
        if (!strcmp(algorithm_str, "VAProcDeinterlacingBob"))
            deinterlacing_param.algorithm  = VAProcDeinterlacingBob;
        else if (!strcmp(algorithm_str, "VAProcDeinterlacingWeave"))
            deinterlacing_param.algorithm  = VAProcDeinterlacingWeave;
        else if (!strcmp(algorithm_str, "VAProcDeinterlacingMotionAdaptive"))
            deinterlacing_param.algorithm  = VAProcDeinterlacingMotionAdaptive;
        else if (!strcmp(algorithm_str, "VAProcDeinterlacingMotionCompensated"))
            deinterlacing_param.algorithm  = VAProcDeinterlacingMotionCompensated;
    } else {
        printf("Read deinterlace algorithm failed, use default algorithm");
        deinterlacing_param.algorithm  = VAProcDeinterlacingBob;
    }

    VAProcFilterCapDeinterlacing deinterlacing_caps[VAProcDeinterlacingCount];
    uint32_t num_deinterlacing_caps = VAProcDeinterlacingCount;
    va_status = vaQueryVideoProcFilterCaps(va_dpy, context_id,
                                           VAProcFilterDeinterlacing,
                                           &deinterlacing_caps, &num_deinterlacing_caps);
    CHECK_VASTATUS(va_status, "vaQueryVideoProcFilterCaps");

    for (i = 0; i < VAProcDeinterlacingCount; i ++)
        if (deinterlacing_caps[i].type == deinterlacing_param.algorithm)
            break;

    if (i == VAProcDeinterlacingCount) {
        printf("Deinterlacing algorithm: %d is not supported by driver, \
                use defautl algorithm :%d \n",
               deinterlacing_param.algorithm,
               VAProcDeinterlacingBob);
        deinterlacing_param.algorithm = VAProcDeinterlacingBob;
    }

    /* read and check the deinterlace flags */
    deinterlacing_param.flags = 0;
    if (!read_value_string(g_config_file_fd, "DEINTERLACING_FLAG", flags_str)) {
        if (strstr(flags_str, "VA_DEINTERLACING_BOTTOM_FIELD_FIRST"))
            deinterlacing_param.flags |= VA_DEINTERLACING_BOTTOM_FIELD_FIRST;
        if (strstr(flags_str, "VA_DEINTERLACING_BOTTOM_FIELD"))
            deinterlacing_param.flags |= VA_DEINTERLACING_BOTTOM_FIELD;
        if (strstr(flags_str, "VA_DEINTERLACING_ONE_FIELD"))
            deinterlacing_param.flags |= VA_DEINTERLACING_ONE_FIELD;
    }

    deinterlacing_param.type  = VAProcFilterDeinterlacing;

    /* create deinterlace fitler buffer */
    va_status = vaCreateBuffer(va_dpy, context_id,
                               VAProcFilterParameterBufferType, sizeof(deinterlacing_param), 1,
                               &deinterlacing_param, &deinterlacing_param_buf_id);
    CHECK_VASTATUS(va_status, "vaCreateBuffer");

    *filter_param_buf_id = deinterlacing_param_buf_id;

    return va_status;
}

static VAStatus
sharpening_filter_init(VABufferID *filter_param_buf_id)
{
    VAStatus va_status;
    VAProcFilterParameterBuffer sharpening_param;
    VABufferID sharpening_param_buf_id;
    float intensity;

    VAProcFilterCap sharpening_caps;
    uint32_t num_sharpening_caps = 1;
    va_status = vaQueryVideoProcFilterCaps(va_dpy, context_id,
                                           VAProcFilterSharpening,
                                           &sharpening_caps, &num_sharpening_caps);
    CHECK_VASTATUS(va_status, "vaQueryVideoProcFilterCaps");

    if (read_value_float(g_config_file_fd, "SHARPENING_INTENSITY", &intensity)) {
        printf("Read sharpening intensity failed, use default value.");
        intensity = sharpening_caps.range.default_value;
    }

    intensity = adjust_to_range(&sharpening_caps.range, intensity);
    printf("Sharpening intensity: %f\n", intensity);
    sharpening_param.value = intensity;

    sharpening_param.type  = VAProcFilterSharpening;

    /* create sharpening fitler buffer */
    va_status = vaCreateBuffer(va_dpy, context_id,
                               VAProcFilterParameterBufferType, sizeof(sharpening_param), 1,
                               &sharpening_param, &sharpening_param_buf_id);

    *filter_param_buf_id = sharpening_param_buf_id;

    return va_status;
}

static VAStatus
color_balance_filter_init(VABufferID *filter_param_buf_id)
{
    VAStatus va_status;
    VAProcFilterParameterBufferColorBalance color_balance_param[4];
    VABufferID color_balance_param_buf_id;
    float value;
    uint32_t i, count;
    int8_t status;

    VAProcFilterCapColorBalance color_balance_caps[VAProcColorBalanceCount];
    unsigned int num_color_balance_caps = VAProcColorBalanceCount;
    va_status = vaQueryVideoProcFilterCaps(va_dpy, context_id,
                                           VAProcFilterColorBalance,
                                           &color_balance_caps, &num_color_balance_caps);
    CHECK_VASTATUS(va_status, "vaQueryVideoProcFilterCaps");

    count = 0;
    printf("Color balance params: ");
    for (i = 0; i < num_color_balance_caps; i++) {
        if (color_balance_caps[i].type == VAProcColorBalanceHue) {
            color_balance_param[count].attrib  = VAProcColorBalanceHue;
            status = read_value_float(g_config_file_fd, "COLOR_BALANCE_HUE", &value);
            printf("Hue: ");
        } else if (color_balance_caps[i].type == VAProcColorBalanceSaturation) {
            color_balance_param[count].attrib  = VAProcColorBalanceSaturation;
            status = read_value_float(g_config_file_fd, "COLOR_BALANCE_SATURATION", &value);
            printf("Saturation: ");
        } else if (color_balance_caps[i].type == VAProcColorBalanceBrightness) {
            color_balance_param[count].attrib  = VAProcColorBalanceBrightness;
            status = read_value_float(g_config_file_fd, "COLOR_BALANCE_BRIGHTNESS", &value);
            printf("Brightness: ");
        } else if (color_balance_caps[i].type == VAProcColorBalanceContrast) {
            color_balance_param[count].attrib  = VAProcColorBalanceContrast;
            status = read_value_float(g_config_file_fd, "COLOR_BALANCE_CONTRAST", &value);
            printf("Contrast: ");
        } else {
            continue;
        }

        if (status)
            value = color_balance_caps[i].range.default_value;
        else
            value = adjust_to_range(&color_balance_caps[i].range, value);

        color_balance_param[count].value = value;
        color_balance_param[count].type  = VAProcFilterColorBalance;
        count++;

        printf("%4f,  ", value);
    }
    printf("\n");

    va_status = vaCreateBuffer(va_dpy, context_id,
                               VAProcFilterParameterBufferType, sizeof(color_balance_param), 4,
                               color_balance_param, &color_balance_param_buf_id);

    *filter_param_buf_id = color_balance_param_buf_id;

    return va_status;
}

#if BLEND_ON

static VAStatus
blending_state_init(VABlendState *state)
{
    VAStatus va_status = VA_STATUS_SUCCESS;
    char blending_flags_str[MAX_LEN];
    float global_alpha;
    uint32_t min_luma, max_luma;

    /* read and check blend state */
    state->flags = 0;
    if (!read_value_string(g_config_file_fd, "BLENDING_FLAGS", blending_flags_str)) {
        if (strstr(blending_flags_str, "VA_BLEND_GLOBAL_ALPHA")) {
            if (read_value_float(g_config_file_fd, "BLENDING_GLOBAL_ALPHA", &global_alpha)) {
                global_alpha = 1.0  ;
                printf("Use default global alpha : %4f \n", global_alpha);
            }
            state->flags |= VA_BLEND_GLOBAL_ALPHA;
            state->global_alpha = global_alpha;
        }
        if (strstr(blending_flags_str, "VA_BLEND_LUMA_KEY")) {
            if (read_value_uint8(g_config_file_fd, "BLENDING_MIN_LUMA", &g_blending_min_luma)) {
                g_blending_min_luma = 1;
                printf("Use default min luma : %3d \n", g_blending_min_luma);
            }
            if (read_value_uint8(g_config_file_fd, "BLENDING_MAX_LUMA", &g_blending_max_luma)) {
                g_blending_max_luma = 254;
                printf("Use default max luma : %3d \n", g_blending_max_luma);
            }
            state->flags |= VA_BLEND_LUMA_KEY;
            state->min_luma = g_blending_min_luma * 1.0 / 256;
            state->max_luma = g_blending_max_luma * 1.0 / 256;
        }

        printf("Blending type = %s, alpha = %f, min_luma = %3d, max_luma = %3d \n",
               blending_flags_str, global_alpha, min_luma, max_luma);
    }

    VAProcPipelineCaps pipeline_caps;
    va_status = vaQueryVideoProcPipelineCaps(va_dpy, context_id,
                NULL, 0, &pipeline_caps);
    CHECK_VASTATUS(va_status, "vaQueryVideoProcPipelineCaps");

    if (!pipeline_caps.blend_flags) {
        printf("Blending is not supported in driver! \n");
        return VA_STATUS_ERROR_UNIMPLEMENTED;
    }

    if (!(pipeline_caps.blend_flags & state->flags)) {
        printf("Driver do not support current blending flags: %d", state->flags);
        return VA_STATUS_ERROR_UNIMPLEMENTED;
    }

    return va_status;
}

#endif

static VAStatus
video_frame_process(VAProcFilterType filter_type,
                    uint32_t frame_idx,
                    VASurfaceID in_surface_id,
                    VASurfaceID out_surface_id)
{
    VAStatus va_status;
    VAProcPipelineParameterBuffer pipeline_param;
    VARectangle surface_region, output_region;
    VABufferID pipeline_param_buf_id = VA_INVALID_ID;
    VABufferID filter_param_buf_id = VA_INVALID_ID;
#if BLEND_ON
    VABlendState state ;
#endif
    uint32_t filter_count = 1;

    /* create denoise_filter buffer id */
    switch (filter_type) {
    case VAProcFilterNoiseReduction:
        denoise_filter_init(&filter_param_buf_id);
        break;
    case VAProcFilterDeinterlacing:
        deinterlace_filter_init(&filter_param_buf_id);
        break;
    case VAProcFilterSharpening:
        sharpening_filter_init(&filter_param_buf_id);
        break;
    case VAProcFilterColorBalance:
        color_balance_filter_init(&filter_param_buf_id);
        break;
    case VAProcFilterSkinToneEnhancement:
        skintone_filter_init(&filter_param_buf_id);
        break;
    default :
        filter_count = 0;
        break;
    }

    /* Fill pipeline buffer */
    surface_region.x = 0;
    surface_region.y = 0;
    surface_region.width = g_in_pic_width;
    surface_region.height = g_in_pic_height;
    output_region.x = 0;
    output_region.y = 0;
    output_region.width = g_out_pic_width;
    output_region.height = g_out_pic_height;

    memset(&pipeline_param, 0, sizeof(pipeline_param));
    pipeline_param.surface = in_surface_id;
    pipeline_param.surface_region = &surface_region;
    pipeline_param.output_region = &output_region;

    pipeline_param.filter_flags = 0;
    pipeline_param.filters      = &filter_param_buf_id;
    pipeline_param.num_filters  = filter_count;

#if BLEND_ON
    /* Blending related state */
    if (g_blending_enabled) {
        blending_state_init(&state);
        pipeline_param.blend_state = &state;
    }
#endif

    va_status = vaCreateBuffer(va_dpy,
                               context_id,
                               VAProcPipelineParameterBufferType,
                               sizeof(pipeline_param),
                               1,
                               &pipeline_param,
                               &pipeline_param_buf_id);
    CHECK_VASTATUS(va_status, "vaCreateBuffer");

    va_status = vaBeginPicture(va_dpy,
                               context_id,
                               out_surface_id);
    CHECK_VASTATUS(va_status, "vaBeginPicture");

    va_status = vaRenderPicture(va_dpy,
                                context_id,
                                &pipeline_param_buf_id,
                                1);
    CHECK_VASTATUS(va_status, "vaRenderPicture");

    va_status = vaEndPicture(va_dpy, context_id);
    CHECK_VASTATUS(va_status, "vaEndPicture");

    if (filter_param_buf_id != VA_INVALID_ID)
        vaDestroyBuffer(va_dpy, filter_param_buf_id);

    if (pipeline_param_buf_id != VA_INVALID_ID)
        vaDestroyBuffer(va_dpy, pipeline_param_buf_id);

    return va_status;
}

static VAStatus
vpp_context_create()
{
    VAStatus va_status = VA_STATUS_SUCCESS;
    uint32_t i;
    int32_t j;

    /* VA driver initialization */
    va_dpy = va_open_display();
    int32_t major_ver, minor_ver;
    va_status = vaInitialize(va_dpy, &major_ver, &minor_ver);
    assert(va_status == VA_STATUS_SUCCESS);

    /* Check whether VPP is supported by driver */
    VAEntrypoint entrypoints[5];
    int32_t num_entrypoints;
    num_entrypoints = vaMaxNumEntrypoints(va_dpy);
    va_status = vaQueryConfigEntrypoints(va_dpy,
                                         VAProfileNone,
                                         entrypoints,
                                         &num_entrypoints);
    CHECK_VASTATUS(va_status, "vaQueryConfigEntrypoints");

    for (j = 0; j < num_entrypoints; j++) {
        if (entrypoints[j] == VAEntrypointVideoProc)
            break;
    }

    if (j == num_entrypoints) {
        printf("VPP is not supported by driver\n");
        assert(0);
    }

    /* Render target surface format check */
    VAConfigAttrib attrib;
    attrib.type = VAConfigAttribRTFormat;
    va_status = vaGetConfigAttributes(va_dpy,
                                      VAProfileNone,
                                      VAEntrypointVideoProc,
                                      &attrib,
                                      1);
    CHECK_VASTATUS(va_status, "vaGetConfigAttributes");
    if (!(attrib.value & g_out_format)) {
        printf("RT format %d is not supported by VPP !\n", g_out_format);
        assert(0);
    }

    /* Create surface/config/context for VPP pipeline */
    va_status = create_surface(&g_in_surface_id, g_in_pic_width, g_in_pic_height,
                               g_in_fourcc, g_in_format);
    CHECK_VASTATUS(va_status, "vaCreateSurfaces for input");

    va_status = create_surface(&g_out_surface_id, g_out_pic_width, g_out_pic_height,
                               g_out_fourcc, g_out_format);
    CHECK_VASTATUS(va_status, "vaCreateSurfaces for output");

    va_status = vaCreateConfig(va_dpy,
                               VAProfileNone,
                               VAEntrypointVideoProc,
                               &attrib,
                               1,
                               &config_id);
    CHECK_VASTATUS(va_status, "vaCreateConfig");

    /* Source surface format check */
    uint32_t num_surf_attribs = VASurfaceAttribCount;
    VASurfaceAttrib * surf_attribs = (VASurfaceAttrib*)
                                     malloc(sizeof(VASurfaceAttrib) * num_surf_attribs);
    if (!surf_attribs)
        assert(0);

    va_status = vaQuerySurfaceAttributes(va_dpy,
                                         config_id,
                                         surf_attribs,
                                         &num_surf_attribs);

    if (va_status == VA_STATUS_ERROR_MAX_NUM_EXCEEDED) {
        surf_attribs = (VASurfaceAttrib*)realloc(surf_attribs,
                       sizeof(VASurfaceAttrib) * num_surf_attribs);
        if (!surf_attribs)
            assert(0);
        va_status = vaQuerySurfaceAttributes(va_dpy,
                                             config_id,
                                             surf_attribs,
                                             &num_surf_attribs);
    }
    CHECK_VASTATUS(va_status, "vaQuerySurfaceAttributes");

    for (i = 0; i < num_surf_attribs; i++) {
        if (surf_attribs[i].type == VASurfaceAttribPixelFormat &&
            surf_attribs[i].value.value.i == (int)g_in_fourcc)
            break;
    }
    free(surf_attribs);

    if (i == num_surf_attribs) {
        printf("Input fourCC %d  is not supported by VPP !\n", g_in_fourcc);
        assert(0);
    }

    va_status = vaCreateContext(va_dpy,
                                config_id,
                                g_out_pic_width,
                                g_out_pic_height,
                                VA_PROGRESSIVE,
                                &g_out_surface_id,
                                1,
                                &context_id);
    CHECK_VASTATUS(va_status, "vaCreateContext");


    /* Validate  whether currect filter is supported */
    if (g_filter_type != VAProcFilterNone) {
        uint32_t supported_filter_num = VAProcFilterCount;
        VAProcFilterType supported_filter_types[VAProcFilterCount];

        va_status = vaQueryVideoProcFilters(va_dpy,
                                            context_id,
                                            supported_filter_types,
                                            &supported_filter_num);

        CHECK_VASTATUS(va_status, "vaQueryVideoProcFilters");

        for (i = 0; i < supported_filter_num; i++) {
            if (supported_filter_types[i] == g_filter_type)
                break;
        }

        if (i == supported_filter_num) {
            printf("VPP filter type %s is not supported by driver !\n", g_filter_type_name);
            assert(0);
        }
    }

    return va_status;
}

static void
vpp_context_destroy()
{
    /* Release resource */
    vaDestroySurfaces(va_dpy, &g_in_surface_id, 1);
    vaDestroySurfaces(va_dpy, &g_out_surface_id, 1);
    vaDestroyContext(va_dpy, context_id);
    vaDestroyConfig(va_dpy, config_id);

    vaTerminate(va_dpy);
    va_close_display(va_dpy);
}

static int8_t
parse_fourcc_and_format(char *str, uint32_t *fourcc, uint32_t *format)
{
    uint32_t tfourcc = VA_FOURCC('N', 'V', '1', '2');
    uint32_t tformat = VA_RT_FORMAT_YUV420;

    if (!strcmp(str, "YV12")) {
        tfourcc = VA_FOURCC('Y', 'V', '1', '2');
        tformat = VA_RT_FORMAT_YUV420;
    } else if (!strcmp(str, "I420")) {
        tfourcc = VA_FOURCC('I', '4', '2', '0');
        tformat = VA_RT_FORMAT_YUV420;
    } else if (!strcmp(str, "NV12")) {
        tfourcc = VA_FOURCC('N', 'V', '1', '2');
        tformat = VA_RT_FORMAT_YUV420;
    } else if (!strcmp(str, "YUY2") || !strcmp(str, "YUYV")) {
        tfourcc = VA_FOURCC('Y', 'U', 'Y', '2');
        tformat = VA_RT_FORMAT_YUV422;
    } else if (!strcmp(str, "UYVY")) {
        tfourcc = VA_FOURCC('U', 'Y', 'V', 'Y');
        tformat = VA_RT_FORMAT_YUV422;
    } else if (!strcmp(str, "P010")) {
        tfourcc = VA_FOURCC('P', '0', '1', '0');
        tformat = VA_RT_FORMAT_YUV420_10BPP;
    } else if (!strcmp(str, "I010")) {
        tfourcc = VA_FOURCC('I', '0', '1', '0');
        tformat = VA_RT_FORMAT_YUV420_10BPP;
    } else if (!strcmp(str, "RGBA")) {
        tfourcc = VA_FOURCC_RGBA;
        tformat = VA_RT_FORMAT_RGB32;
    } else if (!strcmp(str, "RGBX")) {
        tfourcc = VA_FOURCC_RGBX;
        tformat = VA_RT_FORMAT_RGB32;
    } else if (!strcmp(str, "BGRA")) {
        tfourcc = VA_FOURCC_BGRA;
        tformat = VA_RT_FORMAT_RGB32;
    } else if (!strcmp(str, "BGRX")) {
        tfourcc = VA_FOURCC_BGRX;
        tformat = VA_RT_FORMAT_RGB32;
    } else if (!strcmp(str, "AYUV")) {
        tfourcc = VA_FOURCC_AYUV;
        tformat = VA_RT_FORMAT_YUV444;
    } else if (!strcmp(str, "RGBP")) {
        tfourcc = VA_FOURCC_RGBP;
        tformat = VA_RT_FORMAT_RGBP;
    } else if (!strcmp(str, "BGRP")) {
        tfourcc = VA_FOURCC_BGRP;
        tformat = VA_RT_FORMAT_RGBP;
    } else {
        printf("Not supported format: %s! Currently only support following format: %s\n",
               str, "YV12, I420, NV12, YUY2(YUYV), UYVY, AYUV, P010, I010, RGBA, RGBX, BGRA, "
               "BGRX or RGBP");
        assert(0);
    }

    if (fourcc)
        *fourcc = tfourcc;

    if (format)
        *format = tformat;

    return 0;
}

static int8_t
parse_basic_parameters()
{
    char str[MAX_LEN];

    /* Read src frame file information */
    read_value_string(g_config_file_fd, "SRC_FILE_NAME", g_src_file_name);
    read_value_uint32(g_config_file_fd, "SRC_FRAME_WIDTH", &g_in_pic_width);
    read_value_uint32(g_config_file_fd, "SRC_FRAME_HEIGHT", &g_in_pic_height);
    read_value_string(g_config_file_fd, "SRC_FRAME_FORMAT", str);
    parse_fourcc_and_format(str, &g_in_fourcc, &g_in_format);

    /* Read dst frame file information */
    read_value_string(g_config_file_fd, "DST_FILE_NAME", g_dst_file_name);
    read_value_uint32(g_config_file_fd, "DST_FRAME_WIDTH", &g_out_pic_width);
    read_value_uint32(g_config_file_fd, "DST_FRAME_HEIGHT", &g_out_pic_height);
    read_value_string(g_config_file_fd, "DST_FRAME_FORMAT", str);
    parse_fourcc_and_format(str, &g_out_fourcc, &g_out_format);

    read_value_string(g_config_file_fd, "SRC_FILE_FORMAT", str);
    parse_fourcc_and_format(str, &g_src_file_fourcc, NULL);

    read_value_string(g_config_file_fd, "DST_FILE_FORMAT", str);
    parse_fourcc_and_format(str, &g_dst_file_fourcc, NULL);

    read_value_uint32(g_config_file_fd, "FRAME_SUM", &g_frame_count);

    /* Read filter type */
    if (read_value_string(g_config_file_fd, "FILTER_TYPE", g_filter_type_name)) {
        printf("Read filter type error !\n");
        assert(0);
    }

    if (!strcmp(g_filter_type_name, "VAProcFilterNoiseReduction"))
        g_filter_type = VAProcFilterNoiseReduction;
    else if (!strcmp(g_filter_type_name, "VAProcFilterDeinterlacing"))
        g_filter_type = VAProcFilterDeinterlacing;
    else if (!strcmp(g_filter_type_name, "VAProcFilterSharpening"))
        g_filter_type = VAProcFilterSharpening;
    else if (!strcmp(g_filter_type_name, "VAProcFilterColorBalance"))
        g_filter_type = VAProcFilterColorBalance;
    else if (!strcmp(g_filter_type_name, "VAProcFilterSkinToneEnhancement"))
        g_filter_type = VAProcFilterSkinToneEnhancement;
    else if (!strcmp(g_filter_type_name, "VAProcFilterNone"))
        g_filter_type = VAProcFilterNone;
    else {
        printf("Unsupported filter type :%s \n", g_filter_type_name);
        return -1;
    }

    /* Check whether blending is enabled */
    if (read_value_uint8(g_config_file_fd, "BLENDING_ENABLED", &g_blending_enabled))
        g_blending_enabled = 0;

    if (g_blending_enabled)
        printf("Blending will be done \n");

    if (g_in_pic_width != g_out_pic_width ||
        g_in_pic_height != g_out_pic_height)
        printf("Scaling will be done : from %4d x %4d to %4d x %4d \n",
               g_in_pic_width, g_in_pic_height,
               g_out_pic_width, g_out_pic_height);

    if (g_in_fourcc != g_out_fourcc)
        printf("Format conversion will be done: from %d to %d \n",
               g_in_fourcc, g_out_fourcc);

    return 0;
}

int32_t main(int32_t argc, char *argv[])
{
    VAStatus va_status;
    uint32_t i;

    if (argc != 2) {
        printf("Input error! please specify the configure file \n");
        return -1;
    }

    /* Parse the configure file for video process*/
    strncpy(g_config_file_name, argv[1], MAX_LEN);
    g_config_file_name[MAX_LEN - 1] = '\0';

    if (NULL == (g_config_file_fd = fopen(g_config_file_name, "r"))) {
        printf("Open configure file %s failed!\n", g_config_file_name);
        assert(0);
    }

    /* Parse basic parameters */
    if (parse_basic_parameters()) {
        printf("Parse parameters in configure file error\n");
        assert(0);
    }

    va_status = vpp_context_create();
    if (va_status != VA_STATUS_SUCCESS) {
        printf("vpp context create failed \n");
        assert(0);
    }

    /* Video frame fetch, process and store */
    if (NULL == (g_src_file_fd = fopen(g_src_file_name, "r"))) {
        printf("Open SRC_FILE_NAME: %s failed, please specify it in config file: %s !\n",
               g_src_file_name, g_config_file_name);
        assert(0);
    }

    if (NULL == (g_dst_file_fd = fopen(g_dst_file_name, "w"))) {
        printf("Open DST_FILE_NAME: %s failed, please specify it in config file: %s !\n",
               g_dst_file_name, g_config_file_name);
        assert(0);
    }

    printf("\nStart to process, processing type is %s ...\n", g_filter_type_name);
    struct timeval start_time, end_time;
    gettimeofday(&start_time, NULL);

    for (i = 0; i < g_frame_count; i ++) {
        if (g_blending_enabled) {
            construct_nv12_mask_surface(g_in_surface_id, g_blending_min_luma, g_blending_max_luma);
            upload_yuv_frame_to_yuv_surface(g_src_file_fd, g_out_surface_id);
        } else {
            upload_yuv_frame_to_yuv_surface(g_src_file_fd, g_in_surface_id);
        }

        video_frame_process(g_filter_type, i, g_in_surface_id, g_out_surface_id);
        store_yuv_surface_to_file(g_dst_file_fd, g_out_surface_id);
    }

    gettimeofday(&end_time, NULL);
    float duration = (end_time.tv_sec - start_time.tv_sec) +
                     (end_time.tv_usec - start_time.tv_usec) / 1000000.0;
    printf("Finish processing, performance: \n");
    printf("%d frames processed in: %f s, ave time = %.6fs \n", g_frame_count, duration, duration / g_frame_count);

    if (g_src_file_fd)
        fclose(g_src_file_fd);

    if (g_dst_file_fd)
        fclose(g_dst_file_fd);

    if (g_config_file_fd)
        fclose(g_config_file_fd);

    vpp_context_destroy();

    return 0;
}
