#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
//#include <stropts.h>

#include <drm.h>
#include <xf86drm.h>
#include <va/va_drm.h>
#include <va/va_drmcommon.h>

#include "vaapi_h264.h"
#include "../kylinlogger/kylinlogger_C/kylinlogger_c.h"

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

enum {
    FRAME_P = 0,
    FRAME_B = 1,
    FRAME_I = 2,
    FRAME_IDR = 3,
};

struct _VAAPIContext {
    int drm_fd;
    VADisplay va_dpy;

    /* encoding profile bitmask */
    int support_hwenc;
#define MAX_ENC_ATTR 4
    /* 0: enc RT Format
     * 1: enc Rate Control
     * 2: enc Packed Header
     * 3: enc MaxRef Frames
     */
    VAConfigAttrib support_attrib[MAX_ENC_ATTR];
};

typedef struct {
    int gem_name;
    /* the prime buffer fd from name */
    int prime_fd;
    int width;
    int height;
    VASurfaceID rgb_surf;
    /* if changed, need refresh */
    int need_refresh;
} RGBPicture;

struct _YUV_Surf {
    VASurfaceID id;
    int width;
    int height;
    uint8_t *buf;
};

struct _PProcContext {
    struct _VAAPIContext *va_ctx;

    VAContextID vpp_context;
    VAConfigID vpp_config;
    /* vpp params for re-used */
    VABufferID vpp_param;

/* max support buffers */
#define MAX_SWAP_BUFFERS 3
    RGBPicture rgb_pic[MAX_SWAP_BUFFERS];
    int current;
    /* rgb surface export */
    uint8_t *buf;
};

struct _HWEncContext {
    struct _VAAPIContext *va_ctx;

    /* profile support:
     * VAProfileH264ConstrainedBaseline
     * VAProfileH264Main
     * VAProfileH264High
     * VAProfileHEVCMain
     */
    VAProfile profile;

    /* used in enc config
     * 0: enc RT Format
     * 1: enc Rate Control
     * 2: enc Packed Header
     */
    VAConfigAttrib enc_attrib[3];
    /* amd gpu support 1 */
    uint32_t num_ref_frames;
    /* param buffers for re-used
     * 0. VAEncMiscParameterRateControl
     * 1. VAEncSequenceParameterBufferH264/HEVC
     * 2. VAEncPictureParameterBufferH264/HEVC
     * 3. VAEncSliceParameterBufferH264/HEVC
     */
    VABufferID enc_param[4];

    VAContextID enc_context;
    VAConfigID enc_config;
    int32_t valid_ids;

    /* current size */
    int32_t width;
    int32_t height;

    /* encode reference frame */
    VASurfaceID ref_surf;
    VAPictureH264 current_pic;
    VAPictureHEVC current_pic_hevc;
    /* input nv12 surface, always changed */
    VASurfaceID yuv_surf;

    /* coded buffer */
    VABufferID coded_buf;
    uint32_t buf_size;
    uint8_t *export_buf;

    int32_t i_period;
    int32_t idr_period;
    /* if no B frames, assign to 1 */
    int32_t ip_period;
    int32_t bitrate;

    /* current frame info */
    uint32_t encoding;
    int32_t frame_type;

    /* force reset encoder status to idr */
    int32_t enc_reset;
};

static int vaapi_open_display(VAAPIContext *ctx, int card)
{
    VADisplay va_dpy = NULL;
    int32_t i, drm_fd = 0;
    static const char *drm_device_paths[] = { "/dev/dri/renderD128",
                                              "/dev/dri/card0",
                                              "/dev/dri/renderD129",
                                              "/dev/dri/card1"};

    if (card < 0 || card > 1) {
        logger_log(LOG_FATAL, "Cannot open card %d\n", card);
        return -1;
    }

    for (i = card * 2; i < card * 2 + 2; i++) {
        drm_fd = open(drm_device_paths[i], O_RDWR);
        if (drm_fd < 0)
            continue;

        va_dpy = vaGetDisplayDRM(drm_fd);
        if (va_dpy)
            break;

        close(drm_fd);
        drm_fd = -1;
    }

    if (!va_dpy)
        return -1;

    logger_log(LOG_INFO, "Init VA context in %s\n", drm_device_paths[i]);

    ctx->va_dpy = va_dpy;
    ctx->drm_fd = drm_fd;

    return 0;
}

void vaapi_context_destroy(VAAPIContext *ctx)
{
    if (!ctx)
        return;

    if (ctx->va_dpy)
        vaTerminate(ctx->va_dpy);

    if (ctx->drm_fd > 0)
        close(ctx->drm_fd);

    free(ctx);
}

static void vaapi_device_log_error(void *context, const char *message)
{
    VAAPIContext *ctx = context;
    logger_log(LOG_ERROR, "libva(%d): %s", ctx->drm_fd, message);
}

static void vaapi_device_log_info(void *context, const char *message)
{
    VAAPIContext *ctx = context;
    logger_log(LOG_INFO, "libva(%d): %s", ctx->drm_fd, message);
}

int vaapi_get_drmfd(VAAPIContext *ctx)
{
    return ctx->drm_fd;
}

VAAPIContext *vaapi_context_create(int card)
{
    int32_t major_ver, minor_ver;
    int32_t num_entry, slice_entry;
    VAEntrypoint *entrypoints = NULL;
    VAProfile profile_list[] = { VAProfileNone,
                                 VAProfileH264ConstrainedBaseline,
                                 VAProfileH264Main,
                                 VAProfileH264High,
                                 VAProfileHEVCMain };
    uint32_t i, support_profile = 0;
    VAConfigAttrib pp_attrib = { VAConfigAttribRTFormat, 0 };
    VAStatus vas;

    VAAPIContext *ctx = calloc(1, sizeof(*ctx));
    if (!ctx)
        return NULL;

    /* VA driver initialization */
    vas = vaapi_open_display(ctx, card);
    if (vas) {
        logger_log(LOG_FATAL, "open VA display failed\n");
        goto fail;
    }

    /* add logger to vaapi */
    //vaSetErrorCallback(ctx->va_dpy, &vaapi_device_log_error, ctx);
    //vaSetInfoCallback(ctx->va_dpy, &vaapi_device_log_info, ctx);

    vas = vaInitialize(ctx->va_dpy, &major_ver, &minor_ver);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_WARN, "VA initialization failed\n");
        goto fail;
    }

    /* Check supported profile and entrypoints by driver
     * Profile:
     *          VAProfileNone (VPP)
     *          VAProfileH264ConstrainedBaseline
     *          VAProfileH264Main
     *          VAProfileH264High
     *          VAProfileHEVCMain
     * Entrypoint:
     *          VAEntrypointEncSlice (ENC)
     *          VAEntrypointVideoProc (VPP)
     */
    /* get all entrypoints */
    num_entry = vaMaxNumEntrypoints(ctx->va_dpy);
    entrypoints = malloc(num_entry * sizeof(*entrypoints));
    if (!entrypoints) {
        logger_log(LOG_FATAL, "Initialize VA entrypoints array failed\n");
        goto fail;
    }
    for (i = 0; i < ARRAY_SIZE(profile_list); i++) {
        vas = vaQueryConfigEntrypoints(ctx->va_dpy, profile_list[i],
                                       entrypoints, &num_entry);
        if (vas != VA_STATUS_SUCCESS) {
            logger_log(LOG_FATAL, "vaQueryConfigEntrypoints failed\n");
            goto fail;
        }
        for (slice_entry = 0; slice_entry < num_entry; slice_entry++) {
            if (entrypoints[slice_entry] == VAEntrypointEncSlice ||
                entrypoints[slice_entry] == VAEntrypointVideoProc) {
                support_profile |= 1 << i;
                break;
            }
        }
    }
    /* check post-process support */
    if (!(support_profile & 0x1)) {
        logger_log(LOG_FATAL, "VA not support PostProcess\n");
        goto fail;
    }
    /* now check vpp attributes */
    vas = vaGetConfigAttributes(ctx->va_dpy, VAProfileNone,
                                VAEntrypointVideoProc, &pp_attrib, 1);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_FATAL, "VPP vaGetConfigAttributes failed\n");
        goto fail;
    }
    if ((pp_attrib.value & VA_RT_FORMAT_YUV420) == 0) {
        logger_log(LOG_FATAL, "Not find desired YUV420 RT format\n");
        goto fail;
    }

    /* check encode support if hwenc */
    ctx->support_attrib[0].type = VAConfigAttribRTFormat;
    ctx->support_attrib[1].type = VAConfigAttribRateControl;
    ctx->support_attrib[2].type = VAConfigAttribEncPackedHeaders;
    ctx->support_attrib[3].type = VAConfigAttribEncMaxRefFrames;

    for (int i = 1; i < ARRAY_SIZE(profile_list); i++) {
        if ((support_profile >> i) & 0x1) {
            /* now check encoder attributes */
            vas = vaGetConfigAttributes(ctx->va_dpy, profile_list[i],
                                        VAEntrypointEncSlice,
                                        &ctx->support_attrib[0], MAX_ENC_ATTR);
            if (vas == VA_STATUS_SUCCESS) {
                logger_log(LOG_INFO, "Hardware encoding %d is support\n", i);
                ctx->support_hwenc |= 1 << (i - 1);
            }
        }
    }

    free(entrypoints);
    return ctx;

fail:
    if (entrypoints)
        free(entrypoints);
    vaapi_context_destroy(ctx);
    return NULL;
}

void pproc_context_destroy(PProcContext *ctx)
{
    VADisplay va_dpy;

    if (!ctx)
        return;

    va_dpy = ctx->va_ctx->va_dpy;

    if (ctx->buf)
        free(ctx->buf);

    for (int i = 0; i < MAX_SWAP_BUFFERS; i++) {
        if (ctx->rgb_pic[i].rgb_surf != 0)
            vaDestroySurfaces(va_dpy, &ctx->rgb_pic[i].rgb_surf, 1);
        if (ctx->rgb_pic[i].prime_fd != 0)
            close(ctx->rgb_pic[i].prime_fd);
    }

    vaDestroyBuffer(va_dpy, ctx->vpp_param);
    vaDestroyContext(va_dpy, ctx->vpp_context);
    vaDestroyConfig(va_dpy, ctx->vpp_config);

    free(ctx);
}

PProcContext *pproc_context_create(VAAPIContext *ctx)
{
    PProcContext *pp_ctx = NULL;
    VAStatus vas;

    if (!ctx)
        return NULL;

    pp_ctx = calloc(1, sizeof(*pp_ctx));
    if (!pp_ctx)
        return NULL;

    /* create vpp configure and context */
    vas = vaCreateConfig(ctx->va_dpy, VAProfileNone, VAEntrypointVideoProc,
                         NULL, 0, &pp_ctx->vpp_config);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_FATAL, "VPP vaCreateConfig failed\n");
        free(pp_ctx);
        return NULL;
    }
    vas = vaCreateContext(ctx->va_dpy, pp_ctx->vpp_config, 0,
                          0, // is ok in gallium va
                          0, // is ok when vpp
                          NULL, 0, // is ok in gallium va
                          &pp_ctx->vpp_context);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_FATAL, "VPP vaCreateContext failed\n");
        vaDestroyConfig(ctx->va_dpy, pp_ctx->vpp_config);
        free(pp_ctx);
        return NULL;
    }
    /* vpp param buffer */
    vas = vaCreateBuffer(ctx->va_dpy, pp_ctx->vpp_context,
                         VAProcPipelineParameterBufferType,
                         sizeof(VAProcPipelineParameterBuffer), 1, NULL,
                         &pp_ctx->vpp_param);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_FATAL, "VPP param vaCreateBuffer failed\n");
        vaDestroyContext(ctx->va_dpy, pp_ctx->vpp_context);
        vaDestroyConfig(ctx->va_dpy, pp_ctx->vpp_config);
        free(pp_ctx);
        return NULL;
    }

    pp_ctx->va_ctx = ctx;
    return pp_ctx;
}

int pproc_import_rgb(PProcContext *ctx, int name, int width, int height,
                     int stride)
{
    VASurfaceAttribExternalBuffers buffer_desc;
    VASurfaceAttrib surface_attrib[2] = {
        {
            .type = VASurfaceAttribMemoryType,
            .flags = VA_SURFACE_ATTRIB_SETTABLE,
            .value.type = VAGenericValueTypeInteger,
            .value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME,
        },
        {
            .type = VASurfaceAttribExternalBufferDescriptor,
            .flags = VA_SURFACE_ATTRIB_SETTABLE,
            .value.type = VAGenericValueTypePointer,
            .value.value.p = &buffer_desc,
        }
    };
    VADisplay va_dpy;
    RGBPicture *picture;
    VAStatus vas;
    int idx = 0;

    if (!ctx || name <= 0 || width <= 0 || height <= 0) {
        logger_log(LOG_ERROR, "VPP picture format error\n");
        return -1;
    }

    va_dpy = ctx->va_ctx->va_dpy;

    /* assume that buffers swap */
    for (idx = 0; idx < MAX_SWAP_BUFFERS; idx++) {
        /* already created picture */
        if (name == ctx->rgb_pic[idx].gem_name) {
            ctx->current = idx;
            break;
        }
        if (ctx->rgb_pic[idx].gem_name == 0) {
            ctx->current = idx;
            ctx->rgb_pic[idx].need_refresh = 1;
            break;
        }
    }
    /* cannot found, reset all */
    if (idx == MAX_SWAP_BUFFERS) {
        logger_log(LOG_WARN, "VPP picture cannot found name\n");
        /* reset rgb_pic for new buffers */
        for (idx = 0; idx < MAX_SWAP_BUFFERS; idx++) {
            if (ctx->rgb_pic[idx].rgb_surf != 0)
                vaDestroySurfaces(va_dpy, &ctx->rgb_pic[idx].rgb_surf, 1);
            if (ctx->rgb_pic[idx].prime_fd != 0)
                close(ctx->rgb_pic[idx].prime_fd);
        }
        memset(ctx->rgb_pic, 0, sizeof(RGBPicture) * MAX_SWAP_BUFFERS);
        ctx->current = 0;
        ctx->rgb_pic[ctx->current].need_refresh = 1;
    }

    picture = &ctx->rgb_pic[ctx->current];

    /* update info */
    if (picture->width != width || picture->height != height)
        picture->need_refresh = 1;

    if (picture->need_refresh) {
        int prime_fd;
#if 0
        struct drm_gem_open open_arg;

        /* gem name to drm handle */
        memset(&open_arg, 0, sizeof(open_arg));
        open_arg.name = name;
        if (ioctl(drm_fd, DRM_IOCTL_GEM_OPEN, &open_arg))
            return -1;

        /* handle to dma fd */
        if (drmPrimeHandleToFD(drm_fd, open_arg.handle, DRM_CLOEXEC, &prime_fd))
            return -1;
#else
       prime_fd = name;
#endif

        /* is ok if destroy error */
        if (picture->rgb_surf != 0)
            vaDestroySurfaces(va_dpy, &picture->rgb_surf, 1);

        if (picture->prime_fd != 0)
            close(picture->prime_fd);

        picture->prime_fd = prime_fd;
        picture->gem_name = name;
        picture->width = width;
        picture->height = height;

        buffer_desc.pixel_format = VA_FOURCC_BGRX;
        buffer_desc.width = width;
        buffer_desc.height = height;
        buffer_desc.data_size = stride * height;
        buffer_desc.buffers = (uintptr_t *)&prime_fd;
        buffer_desc.num_buffers = 1;
        buffer_desc.flags = 0;

        /* nplanes == 1 always */
        buffer_desc.num_planes = 1;
        buffer_desc.offsets[0] = 0;
        buffer_desc.pitches[0] = stride;

        vas = vaCreateSurfaces(va_dpy, VA_RT_FORMAT_RGB32, width, height,
                               &picture->rgb_surf, 1, surface_attrib, 2);
        if (vas != VA_STATUS_SUCCESS) {
            logger_log(LOG_ERROR, "VPP picture vaCreateSurfaces failed\n");
            return -1;
        }

        picture->need_refresh = 0;
    }

    return 0;
}

int pproc_export_rgb(PProcContext *ctx, uint8_t **buf)
{
    VAStatus vas;
    VAImageFormat format;
    VAImage image;
    void *surface_p = NULL;
    unsigned char *src, *dst;
    VADisplay va_dpy;
    RGBPicture *picture;

    if (!ctx || !buf)
        return -1;

    va_dpy = ctx->va_ctx->va_dpy;
    picture = &ctx->rgb_pic[ctx->current];

    /* BUG: vaDeriveImage + vaMapBuffer
     * mesa bug: VlVaMapBuffer transfer_map write not feedback data
     * image.pitches[0] just width * 4, not hw align
     */
    //vas = vaDeriveImage(va_dpy, picture->rgb_surf, &image);
    //if (vas != VA_STATUS_SUCCESS) {
        format.fourcc = VA_FOURCC_BGRX;
        vas =
            vaCreateImage(va_dpy, &format, picture->width, picture->height, &image);
        if (vas != VA_STATUS_SUCCESS) {
            logger_log(LOG_ERROR, "vaCreateImage failed\n");
            return -1;
        }
        vas = vaGetImage(va_dpy, picture->rgb_surf, 0, 0, picture->width,
                         picture->height, image.image_id);
        if (vas != VA_STATUS_SUCCESS) {
            logger_log(LOG_ERROR, "vaGetImage failed\n");
            vaDestroyImage(va_dpy, image.image_id);
            return -1;
        }
    //}

    vas = vaMapBuffer(va_dpy, image.buf, &surface_p);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "vaMapBuffer failed\n");
        vaDestroyImage(va_dpy, image.image_id);
        return -1;
    }

    if (!ctx->buf)
        ctx->buf = malloc(image.width * image.height * 4);

    dst = ctx->buf;
    src = (unsigned char *)surface_p + image.offsets[0];

    for (int i  = 0; i < image.height; i++) {
        memcpy(dst, src, image.width * 4);
        src += image.pitches[0];
        dst += image.width * 4;
    }

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

    *buf = ctx->buf;

    return image.width * image.height * 4;
}

/* return a nv12/yv12 surface */
YUV_Surf *vaapi_create_yuv(VAAPIContext *ctx, int width, int height)
{
    VASurfaceAttrib surface_attrib;
    YUV_Surf *yuv;
    VAStatus vas;

    if (!ctx || width <= 0 || height <= 0)
        return NULL;

    yuv = calloc(1, sizeof(*yuv));
    if (!yuv)
        return NULL;

    surface_attrib.type = VASurfaceAttribPixelFormat;
    surface_attrib.flags = VA_SURFACE_ATTRIB_SETTABLE;
    surface_attrib.value.type = VAGenericValueTypeInteger;
    surface_attrib.value.value.i = VA_FOURCC_NV12;
    vas = vaCreateSurfaces(ctx->va_dpy, VA_RT_FORMAT_YUV420, width, height,
                           &yuv->id, 1, &surface_attrib, 1);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "VPP output vaCreateSurfaces failed\n");
        free(yuv);
        return NULL;
    }

    yuv->width = width;
    yuv->height = height;

    return yuv;
}

void vaapi_destroy_yuv(VAAPIContext *ctx, YUV_Surf *surf)
{
    if (!ctx || !surf)
        return;

    if (surf->buf)
        free(surf->buf);

    vaDestroySurfaces(ctx->va_dpy, &surf->id, 1);
    free(surf);
}

int vaapi_yuv_width(YUV_Surf *surf)
{
    return surf->width;
}

int vaapi_yuv_height(YUV_Surf *surf)
{
    return surf->height;
}

int vaapi_export_yuv(VAAPIContext *ctx, YUV_Surf *surf, uint8_t **buf)
{
    VAStatus vas;
    VAImageFormat format;
    VAImage image;
    void *surface_p = NULL;
    unsigned char *y_src, *v_src;
    unsigned char *y_dst, *v_dst;
    uint32_t y_size;

    if (!ctx || !surf || !buf)
        return -1;

    format.fourcc = VA_FOURCC_NV12;
    vas = vaCreateImage(ctx->va_dpy, &format, surf->width, surf->height,
                        &image);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "vaCreateImage failed\n");
        return -1;
    }
    vas = vaGetImage(ctx->va_dpy, surf->id, 0, 0, surf->width, surf->height,
                     image.image_id);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "vaGetImage failed\n");
        vaDestroyImage(ctx->va_dpy, image.image_id);
        return -1;
    }

    vas = vaMapBuffer(ctx->va_dpy, image.buf, &surface_p);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "vaMapBuffer failed\n");
        vaDestroyImage(ctx->va_dpy, image.image_id);
        return -1;
    }

    y_size = image.width * image.height;

    if (!surf->buf)
        surf->buf = malloc(y_size * 3 / 2);

    y_dst = surf->buf;
    v_dst = surf->buf + y_size;

    y_src = (unsigned char *)((unsigned char *)surface_p + image.offsets[0]);
    v_src = (unsigned char *)surface_p + image.offsets[1];

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

    /* UV plane copy */
    for (int i = 0; i < image.height / 2; i++) {
        memcpy(v_dst, v_src, image.width);
        v_dst += image.width;
        v_src += image.pitches[1];
    }

    vaUnmapBuffer(ctx->va_dpy, image.buf);
    vaDestroyImage(ctx->va_dpy, image.image_id);

    *buf = surf->buf;

    return y_size * 3 / 2;
}

int hwenc_upload_surface(VAAPIContext *ctx, YUV_Surf *surf, unsigned char *buf)
{
    VAStatus vas;
    VAImageFormat format;
    VAImage image;
    void *surface_p = NULL;
    unsigned char *y_src, *v_src;
    unsigned char *y_dst, *v_dst;
    uint32_t y_size;

    format.fourcc = VA_FOURCC_NV12;
    vas = vaCreateImage(ctx->va_dpy, &format, surf->width, surf->height,
                        &image);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "vaCreateImage failed\n");
        return -1;
    }

    vas = vaMapBuffer(ctx->va_dpy, image.buf, &surface_p);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "vaMapBuffer failed\n");
        vaDestroyImage(ctx->va_dpy, image.image_id);
        return -1;
    }

    y_size = image.width * image.height;

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

    y_src = buf;
    v_src = buf + y_size;

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

    /* UV plane copy */
    for (int i = 0; i < image.height / 2; i++) {
        memcpy(v_dst, v_src, image.width);
        v_dst += image.width;
        v_src += image.pitches[1];
    }

    vaUnmapBuffer(ctx->va_dpy, image.buf);
    vas = vaPutImage(ctx->va_dpy, surf->id, image.image_id,
                     0, 0, surf->width, surf->height,
                     0, 0, surf->width, surf->height);
    vaDestroyImage(ctx->va_dpy, image.image_id);

    return 1;
}

int pproc_do_convert(PProcContext *ctx, YUV_Surf *surf)
{
    VAProcPipelineParameterBuffer *vparms;
    VARectangle input_region, output_region;
    VADisplay va_dpy;
    RGBPicture *picture;
    VAStatus vas;

    if (!ctx || !surf)
        return -1;

    va_dpy = ctx->va_ctx->va_dpy;
    picture = &ctx->rgb_pic[ctx->current];

    vas = vaMapBuffer(va_dpy, ctx->vpp_param, (void **)&vparms);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "VPP vaMapBuffer failed!\n");
        return -1;
    }

    input_region = (VARectangle){
        .x = 0,
        .y = 0,
        .width = picture->width,
        .height = picture->height,
    };
    /* used to scale output */
    output_region = (VARectangle){
        .x = 0,
        .y = 0,
        .width = surf->width,
        .height = surf->height,
    };

    vparms->surface = picture->rgb_surf;
    vparms->surface_region = &input_region;
    vparms->surface_color_standard = VAProcColorStandardBT709;

    vparms->output_region = &output_region;
    vparms->output_background_color = 0xff000000;
    vparms->output_color_standard = vparms->surface_color_standard;

    vparms->pipeline_flags = 0;
    vparms->filter_flags = VA_FILTER_SCALING_HQ;
    vparms->num_filters = 0;

    vas = vaUnmapBuffer(va_dpy, ctx->vpp_param);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "VPP vaUnmapBuffer failed\n");
        return -1;
    }

    vas = vaBeginPicture(va_dpy, ctx->vpp_context, surf->id);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "VPP vaBeginPicture failed\n");
        return -1;
    }

    vas = vaRenderPicture(va_dpy, ctx->vpp_context, &ctx->vpp_param, 1);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "VPP vaRenderPicture failed\n");
        return -1;
    }

    vas = vaEndPicture(va_dpy, ctx->vpp_context);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "VPP vaEndPicture failed\n");
        return -1;
    }

    return 0;
}

static int hwenc_attrib_check(HWEncContext *ctx)
{
    VAConfigAttrib *support_attr = ctx->va_ctx->support_attrib;
    VAConfigAttrib *enc_attr = ctx->enc_attrib;
    uint32_t tmp;

    ctx->enc_attrib[0].type = VAConfigAttribRTFormat;
    ctx->enc_attrib[1].type = VAConfigAttribRateControl;
    ctx->enc_attrib[2].type = VAConfigAttribEncPackedHeaders;

    /* encoder RT Format */
    if (!(support_attr[0].value & VA_RT_FORMAT_YUV420)) {
        logger_log(LOG_FATAL, "Not find desired YUV420 RT format\n");
        return -1;
    }
    enc_attr[0].value = VA_RT_FORMAT_YUV420;

    /* encoder RC Mode */
    if (support_attr[1].value == VA_ATTRIB_NOT_SUPPORTED) {
        logger_log(LOG_FATAL, "No Support RateControl mode found\n");
        return -1;
    }
    /* check support rate control mode */
    tmp = support_attr[1].value;
    if (tmp & VA_RC_VBR) {
        logger_log(LOG_INFO, "Force to VBR\n");
        enc_attr[1].value = VA_RC_VBR;
    } else
        enc_attr[1].value = VA_RC_CBR;

    /* encoder Packed Headers */
    if (support_attr[2].value != VA_ATTRIB_NOT_SUPPORTED) {
        tmp = support_attr[2].value;

        if (tmp == VA_ENC_PACKED_HEADER_NONE) {
            logger_log(LOG_INFO, "Not support PackedHeaders\n");
            enc_attr[2].value = VA_ENC_PACKED_HEADER_NONE;
        } else {
            /* force disable PackedHeaders */
            enc_attr[2].value = VA_ENC_PACKED_HEADER_NONE;
            logger_log(LOG_INFO, "Force no packed header suppoert\n");
        }
    } else
        enc_attr[2].value = VA_ENC_PACKED_HEADER_NONE;

    /* encoder MaxRef Frames */
    if (support_attr[3].value == VA_ATTRIB_NOT_SUPPORTED) {
        logger_log(LOG_FATAL, "No MaxRef Frames support\n");
        return -1;
    }
    tmp = support_attr[3].value; /* maxref == 1 */
    ctx->num_ref_frames = tmp;
    logger_log(LOG_INFO, "Support %d RefPicList0 and %d RefPicList1\n",
               tmp & 0xffff, (tmp >> 16) & 0xffff);

    return 0;
}

static inline VAProfile hwenc_get_profile(int profile)
{
    switch (profile) {
    case 0:
        return VAProfileH264ConstrainedBaseline;
    case 1:
        return VAProfileH264Main;
    case 2:
        return VAProfileH264High;
    case 3:
        return VAProfileHEVCMain;
    default:
        return -1;
    }
}

HWEncContext *hwenc_context_create(VAAPIContext *ctx, int profile)
{
    HWEncContext *enc_ctx = NULL;
    VAStatus vas;

    if (!ctx)
        return NULL;

    if (profile < 0 || profile > 4 ||
            !((ctx->support_hwenc >> profile) & 0x1))
        return NULL;

    enc_ctx = calloc(1, sizeof(*enc_ctx));
    if (!enc_ctx)
        return NULL;

    enc_ctx->va_ctx = ctx;
    enc_ctx->profile = hwenc_get_profile(profile);

    /* support_attrib -> enc_attrib */
    if (hwenc_attrib_check(enc_ctx) < 0)
        goto fail;

    /* create 4 param buffers for re-used, map write unmap
     * in gallium vlVaCreateBuffer, context is useless,
     * so we create buffers here with un-init enc_context
     */
    vas = vaCreateBuffer(ctx->va_dpy, enc_ctx->enc_context,
                         VAEncMiscParameterBufferType,
                         sizeof(VAEncMiscParameterBuffer) +
                             sizeof(VAEncMiscParameterRateControl),
                         1, NULL, &enc_ctx->enc_param[0]);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_FATAL, "ENC rc-mode vaCreateBuffer failed!\n");
        goto fail;
    }
    vas = vaCreateBuffer(ctx->va_dpy, enc_ctx->enc_context,
                         VAEncSequenceParameterBufferType,
                         enc_ctx->profile == VAProfileHEVCMain ?
                         sizeof(VAEncSequenceParameterBufferHEVC) :
                         sizeof(VAEncSequenceParameterBufferH264), 1, NULL,
                         &enc_ctx->enc_param[1]);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_FATAL, "ENC sequence vaCreateBuffer failed!\n");
        goto fail;
    }
    vas = vaCreateBuffer(ctx->va_dpy, enc_ctx->enc_context,
                         VAEncPictureParameterBufferType,
                         enc_ctx->profile == VAProfileHEVCMain ?
                         sizeof(VAEncPictureParameterBufferHEVC) :
                         sizeof(VAEncPictureParameterBufferH264), 1, NULL,
                         &enc_ctx->enc_param[2]);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_FATAL, "ENC picture vaCreateBuffer failed!\n");
        goto fail;
    }
    vas = vaCreateBuffer(ctx->va_dpy, enc_ctx->enc_context,
                         VAEncSliceParameterBufferType,
                         enc_ctx->profile == VAProfileHEVCMain ?
                         sizeof(VAEncSliceParameterBufferHEVC) :
                         sizeof(VAEncSliceParameterBufferH264), 1, NULL,
                         &enc_ctx->enc_param[3]);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_FATAL, "ENC slice vaCreateBuffer failed!\n");
        goto fail;
    }

    return enc_ctx;

fail:
    free(enc_ctx);
    return NULL;
}

static void hwenc_destroy_ids(HWEncContext *ctx)
{
    VADisplay va_dpy = ctx->va_ctx->va_dpy;

    if (ctx->valid_ids) {
        if (ctx->export_buf) {
            free(ctx->export_buf);
            ctx->export_buf = NULL;
        }

        vaDestroyBuffer(va_dpy, ctx->coded_buf);
        vaDestroySurfaces(va_dpy, &ctx->ref_surf, 1);

        vaDestroyContext(va_dpy, ctx->enc_context);
        vaDestroyConfig(va_dpy, ctx->enc_config);

        ctx->valid_ids = 0;
    }
}

void hwenc_context_destroy(HWEncContext *ctx)
{
    VADisplay va_dpy;

    if (!ctx)
        return;

    va_dpy = ctx->va_ctx->va_dpy;

    hwenc_destroy_ids(ctx);

    vaDestroyBuffer(va_dpy, ctx->enc_param[0]);
    vaDestroyBuffer(va_dpy, ctx->enc_param[1]);
    vaDestroyBuffer(va_dpy, ctx->enc_param[2]);
    vaDestroyBuffer(va_dpy, ctx->enc_param[3]);

    free(ctx);
}

static int hwenc_reset_context(HWEncContext *ctx)
{
    VADisplay va_dpy = ctx->va_ctx->va_dpy;
    VAStatus vas;

    /* destroy old stuff */
    hwenc_destroy_ids(ctx);

    /* Main and high available in Mesa-18.3 due to profile fixed */
    vas = vaCreateConfig(va_dpy, ctx->profile,
                         VAEntrypointEncSlice, ctx->enc_attrib, 3,
                         &ctx->enc_config);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC vaCreateConfig failed\n");
        return -1;
    }

    /* Create a context for this encode pipe */
    vas = vaCreateContext(va_dpy, ctx->enc_config, ctx->width,
                          ctx->height, VA_PROGRESSIVE, NULL,
                          0, // is ok in gallium va
                          &ctx->enc_context);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC vaCreateContext failed\n");
        vaDestroyConfig(va_dpy, ctx->enc_config);
        return -1;
    }

    /* create ref surface */
    vas = vaCreateSurfaces(va_dpy, VA_RT_FORMAT_YUV420, ctx->width,
                           ctx->height, &ctx->ref_surf, 1, NULL, 0);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC ref vaCreateSurfaces failed!\n");
        vaDestroyContext(va_dpy, ctx->enc_context);
        vaDestroyConfig(va_dpy, ctx->enc_config);
        return -1;
    }

    /* create coded buffer */
    if(ctx->width > 1920)
        ctx->buf_size = (ctx->width * ctx->height * 800) / (16 * 16); //4K
    else
        ctx->buf_size = (ctx->width * ctx->height * 400) / (16 * 16);
    vas = vaCreateBuffer(va_dpy, ctx->enc_context, VAEncCodedBufferType,
                         ctx->buf_size, 1, NULL, &ctx->coded_buf);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC coded vaCreateBuffer failed!\n");
        vaDestroySurfaces(va_dpy, &ctx->ref_surf, 1);
        vaDestroyContext(va_dpy, ctx->enc_context);
        vaDestroyConfig(va_dpy, ctx->enc_config);
        return -1;
    }

    ctx->valid_ids = 1;

    return 0;
}

int hwenc_import_yuv(HWEncContext *ctx, YUV_Surf *surf)
{
    /* only support nv12 */
    if (!ctx || !surf)
        return -1;

    /* align to macro block is done in mesa */

    if (ctx->width != surf->width ||
        ctx->height != surf->height) {
        ctx->width = surf->width;
        ctx->height = surf->height;
        /* size changed, need reset encoder */
        ctx->enc_reset = 1;
    }

    ctx->yuv_surf = surf->id;

    return 0;
}

typedef struct {
    int i_peroid;
    int idr_period;
    int bitrate;
} enc_level;

int hwenc_params(HWEncContext *ctx, int i_period, int idr_period, int bitrate)
{
    if (!ctx || (i_period != 0 && idr_period % i_period != 0)) {
        logger_log(LOG_ERROR, "idr_period must be a multiplier of i_period\n");
        return -1;
    }

    ctx->ip_period = 1;

    if (i_period == 0)
        idr_period = 0;

    if (ctx->i_period != i_period ||
            ctx->idr_period != idr_period) {
        ctx->i_period = i_period;
        ctx->idr_period = idr_period;
        ctx->encoding = 0;
    }

    /* need mesa patched */
    if (ctx->bitrate != bitrate) {
        ctx->bitrate = bitrate;
        ctx->encoding = 0;
        /* HEVC need reset context */
        if (ctx->profile == VAProfileHEVCMain)
            ctx->enc_reset = 1;
    }

    return 0;
}

int hwenc_params_level(HWEncContext *ctx, uint8_t lv)
{
    static const enc_level levels[] = {
        {
            /* high level */
            .i_peroid = 8,
            .idr_period = 16,
            .bitrate = 4000000,
        },
        {
            /* medium level */
            .i_peroid = 9,
            .idr_period = 18,
            .bitrate = 3000000,
        },
        {
            /* low level */
            .i_peroid = 10,
            .idr_period = 20,
            .bitrate = 1500000,
        },
    };

    if (lv >= ARRAY_SIZE(levels)) {
        logger_log(LOG_ERROR, "encode level error\n");
        return -1;
    }

    return hwenc_params(ctx, levels[lv].i_peroid, levels[lv].idr_period,
                        levels[lv].bitrate);
}

static void calc_display_and_type(HWEncContext *ctx)
{
    int encoding_gop = 0;

    if (ctx->i_period == 1) { /* all are I/IDR frames */
        if (ctx->idr_period == 0)
            ctx->frame_type = (ctx->encoding == 0) ? FRAME_IDR : FRAME_I;
        else
            ctx->frame_type =
                (ctx->encoding % ctx->idr_period == 0) ? FRAME_IDR : FRAME_I;
        return;
    }

    if (ctx->i_period == 0) /* ignore idr_period */
        ctx->idr_period = 0;

    /* new sequence like
     * IDR PPPPP IPPPPP
     * IDR (PBB)(PBB)(IBB)(PBB)
     */
    encoding_gop = (ctx->idr_period == 0) ?
                       ctx->encoding :
                       (ctx->encoding %
                        (ctx->idr_period + ((ctx->ip_period == 1) ? 0 : 1)));

    if (encoding_gop == 0) { /* the first frame */
        ctx->frame_type = FRAME_IDR;

    } else if (((encoding_gop - 1) % ctx->ip_period) != 0) { /* B frames */
        ctx->frame_type = FRAME_B;
        /* never go here */
    } else if ((ctx->i_period != 0) && /* have I frames */
               (encoding_gop >= 2) && /* for IDR PPPPP IPPPP */
               ((ctx->ip_period == 1 && encoding_gop % ctx->i_period == 0) ||
                (ctx->ip_period >= 2 && /* for IDR (PBB)(PBB)(IBB) */
                 ((encoding_gop - 1) / ctx->ip_period %
                  (ctx->i_period / ctx->ip_period)) == 0))) {
        ctx->frame_type = FRAME_I;
    } else
        ctx->frame_type = FRAME_P;
}

static int vaapi_fill_sequence(HWEncContext *ctx)
{
    VAStatus vas;
    void *vseq_p;
    VAEncMiscParameterBuffer *vmisc;
    VAEncMiscParameterRateControl *vrc;
    VADisplay va_dpy = ctx->va_ctx->va_dpy;

    /* rate control params,
     * gallium va support two Misc type,
     * VAEncMiscParameterTypeRateControl
     * VAEncMiscParameterTypeFrameRate
     */
    vas = vaMapBuffer(va_dpy, ctx->enc_param[0], (void **)&vmisc);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC rcmode vaMapBuffer failed\n");
        return -1;
    }
    vmisc->type = VAEncMiscParameterTypeRateControl;
    vrc = (VAEncMiscParameterRateControl *)vmisc->data;

    vrc->bits_per_second = ctx->bitrate;
    vrc->target_percentage = 66;

    vas = vaUnmapBuffer(va_dpy, ctx->enc_param[0]);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC rc-mode vaUnmapBuffer failed\n");
        return -1;
    }

    /* sequence params */
    vas = vaMapBuffer(va_dpy, ctx->enc_param[1], (void **)&vseq_p);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC sequence vaCreateBuffer failed\n");
        return -1;
    }

    /* according to gallium va, not all params used */
    if (ctx->profile == VAProfileHEVCMain) {
        VAEncSequenceParameterBufferHEVC *vseq = vseq_p;
        vseq->general_profile_idc = 1; /* VAProfileHEVCMain */
        vseq->general_level_idc = 93;
        vseq->general_tier_flag = 0;
        vseq->intra_period = ctx->i_period;
        vseq->intra_idr_period = ctx->idr_period;
        vseq->pic_width_in_luma_samples = ctx->width;
        vseq->pic_height_in_luma_samples = ctx->height;
        vseq->seq_fields.bits.chroma_format_idc = 1; /* 4:2:0 */
        vseq->seq_fields.bits.bit_depth_luma_minus8 = 0;
        vseq->seq_fields.bits.bit_depth_chroma_minus8 = 0;
        vseq->seq_fields.bits.scaling_list_enabled_flag = 0;
        vseq->seq_fields.bits.strong_intra_smoothing_enabled_flag = 0;
        vseq->seq_fields.bits.amp_enabled_flag = 1;
        vseq->seq_fields.bits.sample_adaptive_offset_enabled_flag = 0;
        vseq->seq_fields.bits.pcm_enabled_flag = 0;
        vseq->seq_fields.bits.sps_temporal_mvp_enabled_flag = 0;
        vseq->log2_min_luma_coding_block_size_minus3 = 0;
        vseq->log2_diff_max_min_luma_coding_block_size = 2;
        vseq->log2_min_transform_block_size_minus2 = 0;
        vseq->log2_diff_max_min_transform_block_size = 3;
        vseq->max_transform_hierarchy_depth_inter = 3;
        vseq->max_transform_hierarchy_depth_intra = 3;
        vseq->vui_time_scale = 30;
        vseq->vui_num_units_in_tick = 1;
    } else {
        VAEncSequenceParameterBufferH264 *vseq = vseq_p;
        vseq->max_num_ref_frames = ctx->num_ref_frames;
        if(ctx->width > 1920)
            vseq->level_idc = 51; // 51 support 4K
        else
            vseq->level_idc = 31; // 31 support 720p
        vseq->intra_idr_period = ctx->idr_period; // Period between IDR frames
        /* time_scale / num_units_in_tick
         * smaller: higher cpu usage and better quality
         * due to h264 data size
         */
        vseq->time_scale = 45; /* 60 for 60fps */
        vseq->num_units_in_tick = 1;
        vseq->seq_fields.bits.pic_order_cnt_type = 0;
    }

    vas = vaUnmapBuffer(va_dpy, ctx->enc_param[1]);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC sequence vaUnmapBuffer failed\n");
        return -1;
    }

    return 0;
}

static int vaapi_fill_picture(HWEncContext *ctx)
{
    VAStatus vas;
    void *vpic_p;
    VADisplay va_dpy = ctx->va_ctx->va_dpy;

    /* picture params */
    vas = vaMapBuffer(va_dpy, ctx->enc_param[2], (void **)&vpic_p);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC picture vaCreateBuffer failed\n");
        return -1;
    }

    if (ctx->profile == VAProfileHEVCMain) {
        VAEncPictureParameterBufferHEVC *vpic = vpic_p;
        vpic->reference_frames[0] = (VAPictureHEVC) {
            .picture_id    = ctx->ref_surf,
            .pic_order_cnt = ctx->encoding,
            .flags = 0
        };
        for (int i = 1; i < ARRAY_SIZE(vpic->reference_frames); i++) {
            vpic->reference_frames[i].picture_id = VA_INVALID_SURFACE;
            vpic->reference_frames[i].flags = VA_PICTURE_HEVC_INVALID;
        }

        vpic->decoded_curr_pic.picture_id = ctx->ref_surf;
        vpic->decoded_curr_pic.pic_order_cnt = ctx->encoding;
        vpic->decoded_curr_pic.flags = 0;
        vpic->coded_buf = ctx->coded_buf;
        vpic->log2_parallel_merge_level_minus2 = 0;
        /* used in CQP, higher value, less quality */
        vpic->pic_init_qp = 28;
        switch (ctx->frame_type) {
        case FRAME_IDR:
            vpic->nal_unit_type = 19;
            vpic->pic_fields.bits.coding_type  = 1;
            break;
        case FRAME_I:
            vpic->nal_unit_type = 21;
            vpic->pic_fields.bits.coding_type  = 1;
            break;
        case FRAME_P:
            vpic->nal_unit_type = 1;
            vpic->pic_fields.bits.coding_type  = 2;
            break;
        case FRAME_B:
            vpic->nal_unit_type = 0;
            vpic->pic_fields.bits.coding_type  = 3;
            break;
        default:
            break;
        }
        vpic->pic_fields.bits.idr_pic_flag = (ctx->frame_type == FRAME_IDR);
        vpic->pic_fields.bits.constrained_intra_pred_flag = 0;
        vpic->pic_fields.bits.scaling_list_data_present_flag = 0;

        /* udpate current picture for reference */
        ctx->current_pic_hevc = vpic->decoded_curr_pic;
    } else {
        VAEncPictureParameterBufferH264 *vpic = vpic_p;
        vpic->frame_num = ctx->encoding; /* if idr, 0 */
        vpic->CurrPic.picture_id = ctx->ref_surf; //ref surface
        vpic->CurrPic.TopFieldOrderCnt = ctx->encoding;

        vpic->CurrPic.flags = 0;
        vpic->CurrPic.frame_idx = ctx->encoding;
        vpic->CurrPic.BottomFieldOrderCnt = ctx->encoding;
        for (int i = 0; i < ARRAY_SIZE(vpic->ReferenceFrames); i++) {
            vpic->ReferenceFrames[i] = (VAPictureH264) {
                .picture_id = VA_INVALID_ID,
                .flags      = VA_PICTURE_H264_INVALID,
            };
        }

        vpic->coded_buf = ctx->coded_buf;
        vpic->pic_fields.bits.idr_pic_flag = (ctx->frame_type == FRAME_IDR);
        /* only used in CQP */
        vpic->pic_init_qp = 28;

        /* udpate current picture for reference */
        ctx->current_pic = vpic->CurrPic;
    }

    vas = vaUnmapBuffer(va_dpy, ctx->enc_param[2]);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC picture vaUnmapBuffer failed\n");
        return -1;
    }

    return 0;
}

static int vaapi_fill_slice(HWEncContext *ctx)
{
    VAStatus vas;
    void *vslice_p;
    VADisplay va_dpy = ctx->va_ctx->va_dpy;

    /* slice params */
    vas = vaMapBuffer(va_dpy, ctx->enc_param[3], (void **)&vslice_p);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC slice vaCreateBuffer failed\n");
        return -1;
    }

    if (ctx->profile == VAProfileHEVCMain) {
        VAEncSliceParameterBufferHEVC *vslice = vslice_p;
        /* one frame, one slice */
        vslice->slice_type =
            (ctx->frame_type == FRAME_IDR) ? FRAME_I : ctx->frame_type;

        for (int i = 0; i < ARRAY_SIZE(vslice->ref_pic_list0); i++) {
            vslice->ref_pic_list0[i].picture_id = VA_INVALID_SURFACE;
            vslice->ref_pic_list0[i].flags      = VA_PICTURE_HEVC_INVALID;
            vslice->ref_pic_list1[i].picture_id = VA_INVALID_SURFACE;
            vslice->ref_pic_list1[i].flags      = VA_PICTURE_HEVC_INVALID;
        }

        /* IDR/I no need ref pictrue */
        if (ctx->frame_type == FRAME_P) {
            /* AMD card only support 1 reference */
            memcpy(vslice->ref_pic_list0, &ctx->current_pic_hevc,
                   sizeof(VAPictureHEVC));
        }

        vslice->max_num_merge_cand = 5;
        vslice->slice_cb_qp_offset = 0;
        vslice->slice_cr_qp_offset = 0;
        vslice->slice_beta_offset_div2 = 0;
        vslice->slice_tc_offset_div2 = 0;
        vslice->slice_fields.bits.cabac_init_flag = 0;
        vslice->slice_fields.bits.slice_deblocking_filter_disabled_flag = 0;
        vslice->slice_fields.bits.slice_loop_filter_across_slices_enabled_flag = 0;
    } else {
        VAEncSliceParameterBufferH264 *vslice = vslice_p;
        /* one frame, one slice */
        vslice->slice_type =
            (ctx->frame_type == FRAME_IDR) ? FRAME_I : ctx->frame_type;

        for (int i = 0; i < ARRAY_SIZE(vslice->RefPicList0); i++) {
            vslice->RefPicList0[i].picture_id = VA_INVALID_SURFACE;
            vslice->RefPicList0[i].flags      = VA_PICTURE_H264_INVALID;
            vslice->RefPicList1[i].picture_id = VA_INVALID_SURFACE;
            vslice->RefPicList1[i].flags      = VA_PICTURE_H264_INVALID;
        }

        /* IDR/I no need ref pictrue */
        if (ctx->frame_type == FRAME_P) {
            /* AMD card only support 1 reference */
            memcpy(vslice->RefPicList0, &ctx->current_pic,
                   sizeof(VAPictureH264));
        }
    }

    vas = vaUnmapBuffer(va_dpy, ctx->enc_param[3]);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC slice vaUnmapBuffer failed\n");
        return -1;
    }

    return 0;
}

void hwenc_force_idr(HWEncContext *ctx)
{
    /* reset encoder seq to idr */
    ctx->encoding = 0;
}

int hwenc_do_encode(HWEncContext *ctx)
{
    VAStatus vas;
    VADisplay va_dpy = ctx->va_ctx->va_dpy;

    if (!ctx->valid_ids || ctx->enc_reset) {

        if (hwenc_reset_context(ctx) < 0)
            return -1;

        ctx->encoding = 0;
        ctx->enc_reset = 0;
    }

    /* calc current display order and frame type */
    calc_display_and_type(ctx);
    /* if IDR, we need render sequence */
    if (ctx->frame_type == FRAME_IDR) {
        ctx->encoding = 0;
        vaapi_fill_sequence(ctx);
    }

    vaapi_fill_picture(ctx);
    vaapi_fill_slice(ctx);

    /* start encoding */
    vas = vaBeginPicture(va_dpy, ctx->enc_context, ctx->yuv_surf);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC vaBeginPicture failed\n");
        return -1;
    }

    /* render pircture */
    vas = vaRenderPicture(va_dpy, ctx->enc_context,
                          (ctx->frame_type == FRAME_IDR) ? ctx->enc_param :
                                                           &ctx->enc_param[2],
                          (ctx->frame_type == FRAME_IDR) ? 4 : 2);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC vaRenderPicture failed\n");
        return -1;
    }

    vas = vaEndPicture(va_dpy, ctx->enc_context);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC vaEndPicture failed\n");
        return -1;
    }

    vas = vaSyncSurface(va_dpy, ctx->yuv_surf);
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC vaSyncSurface failed\n");
        return -1;
    }

    ctx->encoding++;

    return 0;
}

int hwenc_export_h264(HWEncContext *ctx, uint8_t **buf)
{
    VAStatus vas;
    uint32_t len = 0;
    uint8_t *buffer;

    VACodedBufferSegment *buf_list = NULL;
    vas =
        vaMapBuffer(ctx->va_ctx->va_dpy, ctx->coded_buf, (void **)(&buf_list));
    if (vas != VA_STATUS_SUCCESS) {
        logger_log(LOG_ERROR, "ENC export vaMapBuffer failed\n");
        return -1;
    }

    if (!ctx->export_buf) {
        /* buf_size is big enough, realloc if still small */
        ctx->export_buf = malloc(ctx->buf_size);
        if (!ctx->export_buf) {
            logger_log(LOG_ERROR, "malloc export buffer failed\n");
            vaUnmapBuffer(ctx->va_ctx->va_dpy, ctx->coded_buf);
            return -1;
        }
    }

    buffer = ctx->export_buf;

    while (buf_list != NULL) {
        memcpy(buffer + len, buf_list->buf, buf_list->size);
        len += buf_list->size;
        buf_list = (VACodedBufferSegment *)buf_list->next;
    }
    vaUnmapBuffer(ctx->va_ctx->va_dpy, ctx->coded_buf);

    *buf = buffer;

    return len;
}
