/* SPDX-License-Identifier: Apache-2.0 OR MIT */
/*
 * Copyright (c) 2015 Rockchip Electronics Co., Ltd.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <unistd.h>

#include "vpu_api.h"

#define FOR_TEST_ENCODE 1

#define BSWAP32(x) \
    ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) | \
     (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))

#define DEMO_ERR_RET(err) do { ret = err; goto DEMO_OUT; } while (0)
#define DECODE_ERR_RET(err) do { ret = err; goto DECODE_OUT; } while (0)
#define ENCODE_ERR_RET(err) do { ret = err; goto ENCODE_OUT; } while (0)

typedef enum VPU_API_DEMO_RET {
    VPU_DEMO_OK             = 0,
    VPU_DEMO_PARSE_HELP_OK  = 1,

    VPU_DEMO_ERROR_BASE     = -100,
    ERROR_INVALID_PARAM     = VPU_DEMO_ERROR_BASE - 1,
    ERROR_INVALID_STREAM    = VPU_DEMO_ERROR_BASE - 2,
    ERROR_IO                = VPU_DEMO_ERROR_BASE - 3,
    ERROR_MEMORY            = VPU_DEMO_ERROR_BASE - 4,
    ERROR_INIT_VPU          = VPU_DEMO_ERROR_BASE - 5,

    ERROR_VPU_DECODE        = VPU_DEMO_ERROR_BASE - 90,
} VPU_API_DEMO_RET;

typedef struct VpuApiDemoCmdContext {
    RK_U32  width;
    RK_U32  height;
    CODEC_TYPE  codec_type;
    OMX_RK_VIDEO_CODINGTYPE coding;
    char    input_file[200];
    char    output_file[200];
    RK_U8   have_input;
    RK_U8   have_output;
    RK_U32  record_frames;
    RK_S64  record_start_ms;
} VpuApiDemoCmdContext_t;

typedef struct VpuApiEncInput {
    EncInputStream_t stream;
    RK_U32 capability;
} VpuApiEncInput;

typedef struct VpuApiOptionInfo_t {
    const char*     name;
    const char*     argname;
    const char*     help;
} VpuApiOptionInfo;

static VpuApiOptionInfo vpuApiCmd[] = {
    { "i",       "input_file",  "input bitstream file" },
    { "o",       "output_file", "output bitstream file, " },
    { "w",       "width",       "the width of input bitstream" },
    { "h",       "height",      "the height of input bitstream" },
    { "t",       "codec_type",  "the codec type, dec: deoder, enc: encoder, default: decoder" },
    { "coding",  "coding_type", "encoding type of the bitstream" },
    { "vframes", "number",      "set the number of video frames to record" },
    { "ss",      "time_off",    "set the start time offset, use Ms as the unit." },
};

static void *vpuapi_hdl = NULL;
static VpuCodecContext_t *ctx;
RK_S32 (*vpuapi_open_ctx)(VpuCodecContext_t **ctx);
RK_S32 (*vpuapi_close_ctx)(VpuCodecContext_t **ctx);
RK_S32 (*vpuapi_mem_link)(VPUMemLinear_t *p);
RK_S32 (*vpuapi_mem_free)(VPUMemLinear_t *p);

static void show_usage()
{
    printf("usage: vpu_apiDemo [options] input_file, \n\n");

    printf("Getting help:\n");
    printf("-help  --print options of vpu api demo\n");
}

static RK_S32 show_help()
{
    RK_S32 i;
    RK_S32 count = sizeof(vpuApiCmd) / sizeof(vpuApiCmd[0]);
    VpuApiOptionInfo *opt = vpuApiCmd;

    printf("usage: vpu_apiDemo [opt] input_file, \n\n");
    for (i = 0; i < count; i++)
        printf("-%s  %-16s\t%s\n", opt[i].name, opt[i].argname, opt[i].help);

    return 0;
}

static RK_S32 parse_options(int argc, char **argv, VpuApiDemoCmdContext_t *cmdCxt)
{
    char *opt;
    RK_S32 optindex, handleoptions = 1, ret = 0;

    if ((argc < 2) || (cmdCxt == NULL)) {
        printf("vpu api demo, input parameter invalid\n");
        show_usage();
        return ERROR_INVALID_PARAM;
    }

    /* parse options */
    optindex = 1;
    while (optindex < argc) {
        opt = argv[optindex++];

        if (handleoptions && opt[0] == '-' && opt[1] != '\0') {
            if (opt[1] == '-') {
                if (opt[2] != '\0') {
                    opt++;
                } else {
                    handleoptions = 0;
                    continue;
                }
            }

            opt++;

            switch (*opt) {
            case 'i':
                if (argv[optindex]) {
                    memcpy(cmdCxt->input_file, argv[optindex], strlen(argv[optindex]));
                    cmdCxt->input_file[strlen(argv[optindex])] = '\0';
                    cmdCxt->have_input = 1;
                } else {
                    printf("input file is invalid\n");
                    ret = -1;
                    goto PARSE_OPINIONS_OUT;
                }
                break;
            case 'o':
                if (argv[optindex]) {
                    memcpy(cmdCxt->output_file, argv[optindex], strlen(argv[optindex]));
                    cmdCxt->output_file[strlen(argv[optindex])] = '\0';
                    cmdCxt->have_output = 1;
                    break;
                } else {
                    printf("out file is invalid\n");
                    ret = -1;
                    goto PARSE_OPINIONS_OUT;
                }
            case 'w':
                if (argv[optindex]) {
                    cmdCxt->width = atoi(argv[optindex]);
                    break;
                } else {
                    printf("input width is invalid\n");
                    ret = -1;
                    goto PARSE_OPINIONS_OUT;
                }
            case 'h':
                if ((*(opt + 1) != '\0') && !strncmp(opt, "help", 4)) {
                    show_help();
                    ret = VPU_DEMO_PARSE_HELP_OK;
                    goto PARSE_OPINIONS_OUT;
                } else if (argv[optindex]) {
                    cmdCxt->height = atoi(argv[optindex]);
                } else {
                    printf("input height is invalid\n");
                    ret = -1;
                    goto PARSE_OPINIONS_OUT;
                }
                break;
            case 't':
                if (argv[optindex]) {
                    cmdCxt->codec_type = atoi(argv[optindex]);
                    break;
                } else {
                    printf("input codec_type is invalid\n");
                    ret = -1;
                    goto PARSE_OPINIONS_OUT;
                }

            default:
                if ((*(opt + 1) != '\0') && argv[optindex]) {
                    if (!strncmp(opt, "coding", 6)) {
                        printf("coding, argv[optindex]: %s",
                               argv[optindex]);
                        cmdCxt->coding = atoi(argv[optindex]);
                    } else if (!strncmp(opt, "vframes", 7)) {
                        cmdCxt->record_frames = atoi(argv[optindex]);
                    } else if (!strncmp(opt, "ss", 2)) {
                        cmdCxt->record_start_ms = atoi(argv[optindex]);
                    } else {
                        ret = -1;
                        goto PARSE_OPINIONS_OUT;
                    }
                } else {
                    ret = -1;
                    goto PARSE_OPINIONS_OUT;
                }
                break;
            }

            optindex += ret;
        }
    }

PARSE_OPINIONS_OUT:
    if (ret < 0) {
        printf("vpu api demo, input parameter invalid\n");
        show_usage();
        return ERROR_INVALID_PARAM;
    }
    return ret;
}

static RK_S32 readBytesFromFile(RK_U8 *buf, RK_S32 aBytes, FILE *file)
{
    RK_S32 ret = 0;

    if ((NULL == buf) || (NULL == file) || (0 == aBytes)) {
        return -1;
    }

    ret = (RK_S32)fread(buf, 1, aBytes, file);
    if (ret != aBytes) {
        printf("read %d bytes from file fail\n", aBytes);
        return -1;
    }

    return 0;
}

static RK_S32 vpu_encode_demo(VpuApiDemoCmdContext_t *cmd)
{
    FILE *pInFile = NULL;
    FILE *pOutFile = NULL;
    RK_S32 nal = 0x00000001;
    RK_S32 fileSize;
    RK_S32 ret = 0;
    RK_S32 size;
    RK_U32 readOneFrameSize = 0;
    EncoderOut_t    enc_out_yuv;
    EncoderOut_t *enc_out = NULL;
    VpuApiEncInput enc_in_strm;
    VpuApiEncInput *api_enc_in = &enc_in_strm;
    EncInputStream_t *enc_in = NULL;
    EncParameter_t *enc_param = NULL;
    RK_S64 fakeTimeUs = 0;
    RK_U32 w_align = 0;
    RK_U32 h_align = 0;

    int Format = ENC_INPUT_YUV420_PLANAR;

    if (cmd == NULL) {
        return -1;
    }

    if ((cmd->have_input == 0) || (cmd->width <= 0) || (cmd->height <= 0)
        || (cmd->coding <= OMX_RK_VIDEO_CodingAutoDetect)) {
        printf("Warning: missing needed parameters for vpu api demo\n");
    }

    if (cmd->have_input) {
        printf("input bitstream w: %d, h: %d, coding: %d(%s), path: %s\n",
               cmd->width, cmd->height, cmd->coding,
               cmd->codec_type == CODEC_DECODER ? "decode" : "encode",
               cmd->input_file);

        pInFile = fopen(cmd->input_file, "rb");
        if (pInFile == NULL) {
            printf("input file not exsist\n");
            ENCODE_ERR_RET(ERROR_INVALID_PARAM);
        }
    } else {
        printf("please set input bitstream file\n");
        ENCODE_ERR_RET(ERROR_INVALID_PARAM);
    }

    if (cmd->have_output) {
        printf("vpu api demo output file: %s\n",
               cmd->output_file);
        pOutFile = fopen(cmd->output_file, "wb");
        if (pOutFile == NULL) {
            printf("can not write output file\n");
            ENCODE_ERR_RET(ERROR_INVALID_PARAM);
        }
    }

#ifdef FOR_TEST_ENCODE
    ctx = (struct VpuCodecContext *)malloc(sizeof(struct VpuCodecContext));
    if (!ctx) {
        printf("Input context has not been properly allocated");
        return -1;
    }
    memset(ctx, 0, sizeof(struct VpuCodecContext));

    ctx->videoCoding = OMX_RK_VIDEO_CodingAVC;
    ctx->codecType = CODEC_ENCODER;
    ctx->width  = cmd->width;
    ctx->height = cmd->height;
#endif

    fseek(pInFile, 0L, SEEK_END);
    fileSize = ftell(pInFile);
    fseek(pInFile, 0L, SEEK_SET);

    memset(&enc_in_strm, 0, sizeof(VpuApiEncInput));
    enc_in = &enc_in_strm.stream;
    enc_in->buf = NULL;

    memset(&enc_out_yuv, 0, sizeof(EncoderOut_t));
    enc_out = &enc_out_yuv;
    enc_out->data = (RK_U8 *)malloc(cmd->width * cmd->height);
    if (enc_out->data == NULL) {
        ENCODE_ERR_RET(ERROR_MEMORY);
    }

    ret = vpuapi_open_ctx(&ctx);
    if (ret || (ctx == NULL)) {
        ENCODE_ERR_RET(ERROR_MEMORY);
    }

    /*
     ** now init vpu api context. codecType, codingType, width ,height
     ** are all needed before init.
    */
    ctx->codecType = cmd->codec_type;
    ctx->videoCoding = cmd->coding;
    ctx->width = cmd->width;
    ctx->height = cmd->height;
    ctx->no_thread = 1;

    ctx->private_data = malloc(sizeof(EncParameter_t));
    memset(ctx->private_data, 0, sizeof(EncParameter_t));

    enc_param = (EncParameter_t *)ctx->private_data;
    enc_param->width        = cmd->width;
    enc_param->height       = cmd->height;
    enc_param->format       = ENC_INPUT_YUV420_PLANAR;
    enc_param->rc_mode      = 0;
    enc_param->bitRate      = 4000000;
    enc_param->framerate    = 25;
    enc_param->enableCabac  = 1;
    enc_param->cabacInitIdc = 0;
    enc_param->intraPicRate = 30;
    enc_param->profileIdc   = 100;
    enc_param->levelIdc     = 40;

    if ((ret = ctx->init(ctx, NULL, 0)) != 0) {
        printf("init vpu api context fail, ret: 0x%X\n", ret);
        ENCODE_ERR_RET(ERROR_INIT_VPU);
    }

    /*
     ** init of VpuCodecContext while running encode, it returns
     ** sps and pps of encoder output, you need to save sps and pps
     ** after init.
    */
    printf("encode init ok, sps len: %d\n", ctx->extradata_size);
    if (pOutFile && (ctx->extradata_size > 0)) {
        printf("dump %d bytes enc output stream to file\n",
               ctx->extradata_size);

        /* save sps and pps */
        fwrite(ctx->extradata, 1, ctx->extradata_size, pOutFile);
        fflush(pOutFile);
    }

    ret = ctx->control(ctx, VPU_API_ENC_SETFORMAT, &Format);
    if (ret)
        printf("VPU_API_ENC_SETFORMAT ret %d\n", ret);

    ret = ctx->control(ctx, VPU_API_ENC_GETCFG, enc_param);
    if (ret)
        printf("VPU_API_ENC_GETCFG ret %d\n", ret);

    enc_param->rc_mode = 1;

    ret = ctx->control(ctx, VPU_API_ENC_SETCFG, enc_param);
    if (ret)
        printf("VPU_API_ENC_SETCFG ret %d\n", ret);

    /*
     ** vpu api encode process.
    */
    printf("init vpu api context ok, input yuv stream file size: %d\n", fileSize);
    w_align = ((ctx->width + 15) & (~15));
    h_align = ((ctx->height + 15) & (~15));
    size = w_align * h_align * 3 / 2;
    readOneFrameSize = ctx->width * ctx->height * 3 / 2;
    printf("%d %d %d %d %d", ctx->width, ctx->height, w_align, h_align, size);
    nal = BSWAP32(nal);

    do {
        if (ftell(pInFile) >= fileSize) {
            printf("read end of file, complete\n");
            break;
        }

        if (enc_in && (enc_in->size == 0)) {
            if (enc_in->buf == NULL) {
                enc_in->buf = (RK_U8 *)(malloc)(size);
                if (enc_in->buf == NULL) {
                    ENCODE_ERR_RET(ERROR_MEMORY);
                }
                api_enc_in->capability = size;
            }

            if (api_enc_in->capability < ((RK_U32)size)) {
                enc_in->buf = (RK_U8 *)(realloc)((void *)(enc_in->buf), size);
                if (enc_in->buf == NULL) {
                    ENCODE_ERR_RET(ERROR_MEMORY);
                }
                api_enc_in->capability = size;
            }

            if (readBytesFromFile(enc_in->buf, readOneFrameSize, pInFile)) {
                break;
            } else {
                enc_in->size = size;
                enc_in->timeUs = fakeTimeUs;
                fakeTimeUs += 40000;
            }

            printf("read one frame, size: %d, timeUs: %lld, filePos: %ld\n",
                   enc_in->size, enc_in->timeUs, ftell(pInFile));
        }

        if ((ret = ctx->encode(ctx, enc_in, enc_out)) < 0) {
            ENCODE_ERR_RET(ERROR_VPU_DECODE);
        } else {
            enc_in->size = 0;  // TODO encode completely, and set enc_in->size to 0
            printf("vpu encode one frame, out len: %d, left size: %d\n",
                   enc_out->size, enc_in->size);

            /*
             ** encoder output stream is raw bitstream, you need to add nal
             ** head by yourself.
            */
            if ((enc_out->size) && (enc_out->data)) {
                if (pOutFile) {
                    printf("dump %d bytes enc output stream to file\n",
                           enc_out->size);
                    //fwrite((RK_U8*)&nal, 1, 4, pOutFile);  // because output stream have start code, so here mask this code
                    fwrite(enc_out->data, 1, enc_out->size, pOutFile);
                    fflush(pOutFile);
                }

                enc_out->size = 0;
            }
        }

        usleep(3000);
    } while (1);

ENCODE_OUT:
    if (enc_in && enc_in->buf) {
        free(enc_in->buf);
        enc_in->buf = NULL;
    }
    if (enc_out && (enc_out->data)) {
        free(enc_out->data);
        enc_out->data = NULL;
    }
    if (ctx) {
        if (ctx->private_data) {
            free(ctx->private_data);
            ctx->private_data = NULL;
        }
        vpuapi_close_ctx(&ctx);
        ctx = NULL;
    }
    if (pInFile) {
        fclose(pInFile);
        pInFile = NULL;
    }
    if (pOutFile) {
        fclose(pOutFile);
        pOutFile = NULL;
    }

    if (ret) {
        printf("encode demo fail, err: %d\n", ret);
    } else {
        printf("encode demo complete OK.\n");
    }
    return ret;

}

static RK_S32 vpu_decode_demo(VpuApiDemoCmdContext_t *cmd)
{
    FILE *pInFile = NULL;
    FILE *pOutFile = NULL;
    RK_S32 fileSize = 0, pkt_size = 0;
    RK_S32 ret = 0;
    RK_U32 frame_count = 0;
    DecoderOut_t    decOut;
    VideoPacket_t demoPkt;
    VideoPacket_t *pkt = NULL;
    DecoderOut_t *pOut = NULL;
    VPU_FRAME *frame = NULL;
    RK_S64 fakeTimeUs = 0;
    RK_U8 *pExtra = NULL;
    RK_U32 extraSize = 0;
    RK_U32 wAlign16  = 0;
    RK_U32 hAlign16  = 0;
    RK_U32 frameSize = 0;

    if (cmd == NULL) {
        return -1;
    }

    if ((cmd->have_input == 0) || (cmd->width <= 0) || (cmd->height <= 0)
        || (cmd->coding <= OMX_RK_VIDEO_CodingAutoDetect)) {
        printf("Warning: missing needed parameters for vpu api demo\n");
    }

    if (cmd->have_input) {
        printf("input bitstream w: %d, h: %d, coding: %d(%s), path: %s\n",
               cmd->width, cmd->height, cmd->coding,
               cmd->codec_type == CODEC_DECODER ? "decode" : "encode",
               cmd->input_file);

        pInFile = fopen(cmd->input_file, "rb");
        if (pInFile == NULL) {
            printf("input file not exsist\n");
            DECODE_ERR_RET(ERROR_INVALID_PARAM);
        }
    } else {
        printf("please set input bitstream file\n");
        DECODE_ERR_RET(ERROR_INVALID_PARAM);
    }

    if (cmd->have_output) {
        printf("vpu api demo output file: %s\n",
               cmd->output_file);
        pOutFile = fopen(cmd->output_file, "wb");
        if (pOutFile == NULL) {
            printf("can not write output file\n");
            DECODE_ERR_RET(ERROR_INVALID_PARAM);
        }
        if (cmd->record_frames == 0)
            cmd->record_frames = 5;
    }

    fseek(pInFile, 0L, SEEK_END);
    fileSize = ftell(pInFile);
    fseek(pInFile, 0L, SEEK_SET);

    memset(&demoPkt, 0, sizeof(VideoPacket_t));
    pkt = &demoPkt;
    pkt->data = NULL;
    pkt->pts = VPU_API_NOPTS_VALUE;
    pkt->dts = VPU_API_NOPTS_VALUE;

    memset(&decOut, 0, sizeof(DecoderOut_t));
    pOut = &decOut;

    ret = vpuapi_open_ctx(&ctx);
    if (ret || (ctx == NULL)) {
        DECODE_ERR_RET(ERROR_MEMORY);
    }

    /*
     ** read codec extra data from input stream file.
    */
    if (readBytesFromFile((RK_U8 *)(&extraSize), 4, pInFile)) {
        DECODE_ERR_RET(ERROR_IO);
    }

    printf("codec extra data size: %d\n", extraSize);

    pExtra = (RK_U8 *)(malloc)(extraSize);
    if (pExtra == NULL) {
        DECODE_ERR_RET(ERROR_MEMORY);
    }
    memset(pExtra, 0, extraSize);

    if (readBytesFromFile(pExtra, extraSize, pInFile)) {
        DECODE_ERR_RET(ERROR_IO);
    }

    /*
     ** now init vpu api context. codecType, codingType, width ,height
     ** are all needed before init.
    */
    ctx->codecType = cmd->codec_type;
    ctx->videoCoding = cmd->coding;
    ctx->width = cmd->width;
    ctx->height = cmd->height;
    ctx->no_thread = 1;

    if ((ret = ctx->init(ctx, pExtra, extraSize)) != 0) {
        printf("init vpu api context fail, ret: 0x%X\n", ret);
        DECODE_ERR_RET(ERROR_INIT_VPU);
    }

    /*
     ** vpu api decoder process.
    */
    printf("init vpu api context ok, fileSize: %d\n", fileSize);

    do {
        if (ftell(pInFile) >= fileSize) {
            printf("read end of file, complete\n");
            break;
        }

        if (pkt && (pkt->size == 0)) {
            if (readBytesFromFile((RK_U8 *)(&pkt_size), 4, pInFile)) {
                break;
            }

            if (pkt->data == NULL) {
                pkt->data = (RK_U8 *)(malloc)(pkt_size);
                if (pkt->data == NULL) {
                    DECODE_ERR_RET(ERROR_MEMORY);
                }
                pkt->capability = pkt_size;
            }

            if (pkt->capability < ((RK_U32)pkt_size)) {
                pkt->data = (RK_U8 *)(realloc)((void *)(pkt->data), pkt_size);
                if (pkt->data == NULL) {
                    DECODE_ERR_RET(ERROR_MEMORY);
                }
                pkt->capability = pkt_size;
            }

            if (readBytesFromFile(pkt->data, pkt_size, pInFile)) {
                break;
            } else {
                pkt->size = pkt_size;
                pkt->pts = fakeTimeUs;
                fakeTimeUs += 40000;
            }

            printf("read one packet, size: %d, pts: %lld, filePos: %ld\n",
                   pkt->size, pkt->pts, ftell(pInFile));
        }

        /* note: must set out put size to 0 before do decoder. */
        pOut->size = 0;

        if (ctx->decode_sendstream(ctx, pkt) != 0) {
            printf("send packet failed");
            DECODE_ERR_RET(ERROR_VPU_DECODE);
        }


        if ((ret = ctx->decode_getframe(ctx, pOut)) != 0) {
            printf("get decoded data failed\n");
            DECODE_ERR_RET(ERROR_VPU_DECODE);
        } else {
            printf("vpu decode one frame, out len: %d, left size: %d\n",
                   pOut->size, pkt->size);

            /*
             ** both virtual and physical address of the decoded frame are contained
             ** in structure named VPU_FRAME, if you want to use virtual address, make
             ** sure you have done VPUMemLink before.
            */
            if ((pOut->size) && (pOut->data)) {
                frame = (VPU_FRAME *)(pOut->data);
                vpuapi_mem_link(&frame->vpumem);
                wAlign16 = ((frame->DisplayWidth + 15) & (~15));
                hAlign16 = ((frame->DisplayHeight + 15) & (~15));
                frameSize = wAlign16 * hAlign16 * 3 / 2;

                if (pOutFile && (frame_count++ < cmd->record_frames)) {
                    printf("write %d frame(yuv420sp) data, %d bytes to file\n",
                           frame_count, frameSize);

                    fwrite((RK_U8 *)(frame->vpumem.vir_addr), 1, frameSize, pOutFile);
                    fflush(pOutFile);
                }

                /*
                 ** remember use VPUFreeLinear to free, other wise memory leak will
                 ** give you a surprise.
                */
                vpuapi_mem_free(&frame->vpumem);
                // NOTE: pOut->data is malloc from vpu_api we need to free it.
                free(pOut->data);
                pOut->data = NULL;
                pOut->size = 0;
            }
        }

        usleep(3000);
    } while (!(ctx->decoder_err));

DECODE_OUT:
    if (pkt && pkt->data) {
        free(pkt->data);
        pkt->data = NULL;
    }
    if (pOut && (pOut->data)) {
        free(pOut->data);
        pOut->data = NULL;
    }
    if (pExtra) {
        free(pExtra);
        pExtra = NULL;
    }
    if (ctx) {
        vpuapi_close_ctx(&ctx);
        ctx = NULL;
    }
    if (pInFile) {
        fclose(pInFile);
        pInFile = NULL;
    }
    if (pOutFile) {
        fclose(pOutFile);
        pOutFile = NULL;
    }

    if (ret) {
        printf("decode demo fail, err: %d\n", ret);
    } else {
        printf("encode demo complete OK.\n");
    }
    return ret;
}

/* vpu_api is only on Android platform */
int main(int argc, char **argv)
{
    RK_S32 ret = 0;
    VpuApiDemoCmdContext_t demoCmdCtx;
    VpuApiDemoCmdContext_t *cmd = NULL;

    printf("/*******  vpu api demo in *******/\n");
    if (argc == 1) {
        show_usage();
        return 0;
    }

    /* open library for access */
    vpuapi_hdl = dlopen("libvpu.so", RTLD_LAZY | RTLD_GLOBAL);
    if (NULL == vpuapi_hdl) {
        printf("failed to open libvpu.so\n");
        ret = -1;
        goto DEMO_OUT;
    }

    vpuapi_open_ctx = (RK_S32 (*)(VpuCodecContext_t **ctx))dlsym(vpuapi_hdl, "vpu_open_context");
    vpuapi_close_ctx = (RK_S32 (*)(VpuCodecContext_t **ctx))dlsym(vpuapi_hdl, "vpu_close_context");
    vpuapi_mem_link = (RK_S32 (*)(VPUMemLinear_t * p))dlsym(vpuapi_hdl, "VPUMemLink");
    vpuapi_mem_free = (RK_S32 (*)(VPUMemLinear_t * p))dlsym(vpuapi_hdl, "VPUFreeLinear");

    if (NULL == vpuapi_open_ctx || NULL == vpuapi_close_ctx ||
        NULL == vpuapi_mem_link || NULL == vpuapi_mem_free) {
        printf("failed to open vpu_open_context %p vpu_close_context %p\n",
               vpuapi_open_ctx, vpuapi_close_ctx);
        printf("failed to open VPUMemLink %p VPUFreeLinear %p\n",
               vpuapi_mem_link, vpuapi_mem_free);
        ret = -1;
        goto DEMO_OUT;
    }

    printf("get vpuapi handle %p vpu_open_context %p vpu_close_context %p\n",
           vpuapi_hdl, vpuapi_open_ctx, vpuapi_close_ctx);

    cmd = &demoCmdCtx;
    memset(cmd, 0, sizeof(VpuApiDemoCmdContext_t));
    cmd->codec_type = CODEC_DECODER;
    if ((ret = parse_options(argc, argv, cmd)) != 0) {
        if (ret == VPU_DEMO_PARSE_HELP_OK) {
            return 0;
        }

        printf("parse_options fail\n\n");
        show_usage();
        DEMO_ERR_RET(ERROR_INVALID_PARAM);
    }

    switch (cmd->codec_type) {
    case CODEC_DECODER : {
        ret = vpu_decode_demo(cmd);
    } break;
    case CODEC_ENCODER : {
        ret = vpu_encode_demo(cmd);
    } break;
    default : {
        ret = ERROR_INVALID_PARAM;
    } break;
    }

DEMO_OUT:
    if (ret) {
        printf("vpu api demo fail, err: %d\n", ret);
        if (vpuapi_hdl) {
            dlclose(vpuapi_hdl);
            vpuapi_hdl = NULL;
        }
    } else {
        printf("vpu api demo complete OK.\n");
    }
    return ret;
}
