/* GStreamer Intel MSDK plugin
 * Copyright (c) 2016, Oblong Industries, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* TODO:
 *  - Add support for interlaced content
 *  - Add support for MVC AVC
 *  - Wrap more configuration options and maybe move properties to derived
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#ifdef _WIN32
#  include <malloc.h>
#endif

#include <stdlib.h>

#include "gstmsdkenc.h"
#include "gstmsdkbufferpool.h"
#include "gstmsdkvideomemory.h"
#include "gstmsdksystemmemory.h"
#include "gstmsdkcontextutil.h"
#include "mfxjpeg.h"

#ifndef _WIN32
#include "gstmsdkallocator_libva.h"
#include <gst/va/gstvaallocator.h>
#else
#include <gst/d3d11/gstd3d11.h>
#endif

static inline void *
_aligned_alloc (size_t alignment, size_t size)
{
#ifdef _WIN32
  return _aligned_malloc (size, alignment);
#else
  void *out;
  if (posix_memalign (&out, alignment, size) != 0)
    out = NULL;
  return out;
#endif
}

#ifndef _WIN32
#define _aligned_free free
#endif

static void gst_msdkenc_close_encoder (GstMsdkEnc * thiz);

GST_DEBUG_CATEGORY_EXTERN (gst_msdkenc_debug);
#define GST_CAT_DEFAULT gst_msdkenc_debug

#ifdef _WIN32
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_MSDK_CAPS_STR
        ("{ NV12, I420, YV12, YUY2, UYVY, BGRA }", "NV12") "; "
        GST_MSDK_CAPS_MAKE_WITH_D3D11_FEATURE ("NV12"))
    );
#else
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_MSDK_CAPS_STR
        ("{ NV12, I420, YV12, YUY2, UYVY, BGRA }", "{ NV12, BGRx }") "; "
        GST_MSDK_CAPS_MAKE_WITH_VA_FEATURE ("NV12"))
    );
#endif

#define PROP_HARDWARE_DEFAULT            TRUE
#define PROP_ASYNC_DEPTH_DEFAULT         4
#define PROP_TARGET_USAGE_DEFAULT        (MFX_TARGETUSAGE_BALANCED)
#define PROP_RATE_CONTROL_DEFAULT        (MFX_RATECONTROL_VBR)
#define PROP_BITRATE_DEFAULT             (2 * 1024)
#define PROP_QPI_DEFAULT                 0
#define PROP_QPP_DEFAULT                 0
#define PROP_QPB_DEFAULT                 0
#define PROP_GOP_SIZE_DEFAULT            0
#define PROP_REF_FRAMES_DEFAULT          0
#define PROP_I_FRAMES_DEFAULT            0
#define PROP_B_FRAMES_DEFAULT            -1
#define PROP_NUM_SLICES_DEFAULT          0
#define PROP_AVBR_ACCURACY_DEFAULT       0
#define PROP_AVBR_CONVERGENCE_DEFAULT    0
#define PROP_RC_LOOKAHEAD_DEPTH_DEFAULT  10
#define PROP_MAX_VBV_BITRATE_DEFAULT     0
#define PROP_MAX_FRAME_SIZE_DEFAULT      0
#define PROP_MAX_FRAME_SIZE_I_DEFAULT    0
#define PROP_MAX_FRAME_SIZE_P_DEFAULT    0
#define PROP_MBBRC_DEFAULT               MFX_CODINGOPTION_OFF
#define PROP_LOWDELAY_BRC_DEFAULT        MFX_CODINGOPTION_OFF
#define PROP_ADAPTIVE_I_DEFAULT          MFX_CODINGOPTION_UNKNOWN
#define PROP_ADAPTIVE_B_DEFAULT          MFX_CODINGOPTION_UNKNOWN

/* External coding properties */
#define EC_PROPS_STRUCT_NAME             "props"
#define EC_PROPS_EXTBRC                  "extbrc"

#define gst_msdkenc_parent_class parent_class
G_DEFINE_TYPE (GstMsdkEnc, gst_msdkenc, GST_TYPE_VIDEO_ENCODER);

void
gst_msdkenc_add_extra_param (GstMsdkEnc * thiz, mfxExtBuffer * param)
{
  if (thiz->num_extra_params < MAX_EXTRA_PARAMS) {
    thiz->extra_params[thiz->num_extra_params] = param;
    thiz->num_extra_params++;
  }
}

static void
gst_msdkenc_set_context (GstElement * element, GstContext * context)
{
  GstMsdkContext *msdk_context = NULL;
  GstMsdkEnc *thiz = GST_MSDKENC (element);

  if (gst_msdk_context_get_context (context, &msdk_context)) {
    gst_object_replace ((GstObject **) & thiz->context,
        (GstObject *) msdk_context);
    gst_object_unref (msdk_context);
  } else
#ifndef _WIN32
    if (gst_msdk_context_from_external_va_display (context,
          thiz->hardware, 0 /* GST_MSDK_JOB_ENCODER will be set later */ ,
          &msdk_context)) {
    gst_object_replace ((GstObject **) & thiz->context,
        (GstObject *) msdk_context);
    gst_object_unref (msdk_context);
  }
#else
    if (gst_msdk_context_from_external_d3d11_device (context,
          thiz->hardware, 0 /* GST_MSDK_JOB_ENCODER will be set later */ ,
          &msdk_context)) {
    gst_object_replace ((GstObject **) & thiz->context,
        (GstObject *) msdk_context);
    gst_object_unref (msdk_context);
  }
#endif

  GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
}

static void
ensure_bitrate_control (GstMsdkEnc * thiz)
{
  mfxInfoMFX *mfx = &thiz->param.mfx;
  mfxExtCodingOption2 *option2 = &thiz->option2;
  mfxExtCodingOption3 *option3 = &thiz->option3;

  GST_DEBUG_OBJECT (thiz, "set target bitrate: %u kbit/sec", thiz->bitrate);

  mfx->RateControlMethod = thiz->rate_control;
  /* No effect in CQP variant algorithms */
  if ((mfx->RateControlMethod != MFX_RATECONTROL_CQP) &&
      (thiz->bitrate > G_MAXUINT16 || thiz->max_vbv_bitrate > G_MAXUINT16)) {
    mfxU32 max_val = MAX (thiz->max_vbv_bitrate, thiz->bitrate);

    mfx->BRCParamMultiplier = (mfxU16) ((max_val + 0x10000) / 0x10000);
    mfx->TargetKbps = (mfxU16) (thiz->bitrate / mfx->BRCParamMultiplier);
    mfx->MaxKbps = (mfxU16) (thiz->max_vbv_bitrate / mfx->BRCParamMultiplier);
    mfx->BufferSizeInKB =
        (mfxU16) (mfx->BufferSizeInKB / mfx->BRCParamMultiplier);
    /* Currently InitialDelayInKB is not used in this plugin */
    mfx->InitialDelayInKB =
        (mfxU16) (mfx->InitialDelayInKB / mfx->BRCParamMultiplier);
  } else {
    mfx->TargetKbps = thiz->bitrate;
    mfx->MaxKbps = thiz->max_vbv_bitrate;
    mfx->BRCParamMultiplier = 1;
  }

  switch (mfx->RateControlMethod) {
    case MFX_RATECONTROL_CQP:
      mfx->QPI = thiz->qpi;
      mfx->QPP = thiz->qpp;
      mfx->QPB = thiz->qpb;
      break;

    case MFX_RATECONTROL_LA_ICQ:
      option2->LookAheadDepth = thiz->lookahead_depth;
    case MFX_RATECONTROL_ICQ:
      mfx->ICQQuality = CLAMP (thiz->qpi, 1, 51);
      break;

    case MFX_RATECONTROL_LA:   /* VBR with LA. Only supported in H264?? */
    case MFX_RATECONTROL_LA_HRD:       /* VBR with LA, HRD compliant */
      option2->LookAheadDepth = thiz->lookahead_depth;
      break;

    case MFX_RATECONTROL_QVBR:
      option3->QVBRQuality = CLAMP (thiz->qpi, 1, 51);
      thiz->enable_extopt3 = TRUE;
      break;

    case MFX_RATECONTROL_AVBR:
      mfx->Accuracy = thiz->accuracy;
      mfx->Convergence = thiz->convergence;
      break;

    case MFX_RATECONTROL_VBR:
      thiz->enable_extopt3 = TRUE;
      option2->MaxFrameSize = thiz->max_frame_size * 1000;
      if (thiz->max_frame_size_i > 0)
        option3->MaxFrameSizeI = thiz->max_frame_size_i * 1000;
      if (thiz->max_frame_size_p > 0)
        option3->MaxFrameSizeP = thiz->max_frame_size_p * 1000;
      if (thiz->lowdelay_brc != MFX_CODINGOPTION_UNKNOWN) {
        option3->LowDelayBRC = thiz->lowdelay_brc;
      }
      break;

    case MFX_RATECONTROL_VCM:
      /*Non HRD compliant mode with no B-frame and interlaced support */
      thiz->param.mfx.GopRefDist = 0;
      break;

    case MFX_RATECONTROL_CBR:
      break;

    default:
      GST_ERROR ("Unsupported RateControl!");
      break;
  }
}

static gint16
coding_option_get_value (const gchar * key, const gchar * nickname)
{
  if (!g_strcmp0 (nickname, "on")) {
    return MFX_CODINGOPTION_ON;
  } else if (!g_strcmp0 (nickname, "off")) {
    return MFX_CODINGOPTION_OFF;
  } else if (!g_strcmp0 (nickname, "auto")) {
    return MFX_CODINGOPTION_UNKNOWN;
  }

  GST_ERROR ("\"%s\" illegal option \"%s\", set to \"off\"", key, nickname);

  return MFX_CODINGOPTION_OFF;
}

static gboolean
structure_transform (const GstStructure * src, GstStructure * dst)
{
  guint len;
  GValue dst_value = G_VALUE_INIT;
  gboolean ret = TRUE;

  g_return_val_if_fail (src != NULL, FALSE);
  g_return_val_if_fail (dst != NULL, FALSE);

  len = gst_structure_n_fields (src);

  for (guint i = 0; i < len; i++) {
    const gchar *key = gst_structure_nth_field_name (src, i);
    const GValue *src_value = gst_structure_get_value (src, key);

    if (!gst_structure_has_field (dst, key)) {
      GST_ERROR ("structure \"%s\" does not support \"%s\"",
          gst_structure_get_name (dst), key);
      ret = FALSE;
      continue;
    }

    g_value_init (&dst_value, gst_structure_get_field_type (dst, key));

    if (g_value_transform (src_value, &dst_value)) {
      gst_structure_set_value (dst, key, &dst_value);
    } else {
      GST_ERROR ("\"%s\" transform %s to %s failed", key,
          G_VALUE_TYPE_NAME (src_value), G_VALUE_TYPE_NAME (&dst_value));
      ret = FALSE;
    }

    g_value_unset (&dst_value);
  }

  return ret;
}

/* Supported types: gchar*, gboolean, gint, guint, gfloat, gdouble */
static gboolean
structure_get_value (const GstStructure * s, const gchar * key, gpointer value)
{
  const GValue *gvalue = gst_structure_get_value (s, key);
  if (!gvalue) {
    GST_ERROR ("structure \"%s\" does not support \"%s\"",
        gst_structure_get_name (s), key);
    return FALSE;
  }

  switch (G_VALUE_TYPE (gvalue)) {
    case G_TYPE_STRING:{
      const gchar **val = (const gchar **) value;
      *val = g_value_get_string (gvalue);
      break;
    }
    case G_TYPE_BOOLEAN:{
      gboolean *val = (gboolean *) value;
      *val = g_value_get_boolean (gvalue);
      break;
    }
    case G_TYPE_INT:{
      gint *val = (gint *) value;
      *val = g_value_get_int (gvalue);
      break;
    }
    case G_TYPE_UINT:{
      guint *val = (guint *) value;
      *val = g_value_get_uint (gvalue);
      break;
    }
    case G_TYPE_FLOAT:{
      gfloat *val = (gfloat *) value;
      *val = g_value_get_float (gvalue);
      break;
    }
    case G_TYPE_DOUBLE:{
      gdouble *val = (gdouble *) value;
      *val = g_value_get_double (gvalue);
      break;
    }
    default:
      GST_ERROR ("\"%s\" unsupported type %s", key, G_VALUE_TYPE_NAME (gvalue));
      return FALSE;
  }

  return TRUE;
}

static gboolean
ext_coding_props_get_value (GstMsdkEnc * thiz,
    const gchar * key, gpointer value)
{
  gboolean ret;
  if (!(ret = structure_get_value (thiz->ext_coding_props, key, value))) {
    GST_ERROR_OBJECT (thiz, "structure \"%s\" failed to get value for \"%s\"",
        gst_structure_get_name (thiz->ext_coding_props), key);
  }

  return ret;
}

void
gst_msdkenc_ensure_extended_coding_options (GstMsdkEnc * thiz)
{
  mfxExtCodingOption2 *option2 = &thiz->option2;
  mfxExtCodingOption3 *option3 = &thiz->option3;

  gchar *extbrc;
  ext_coding_props_get_value (thiz, EC_PROPS_EXTBRC, &extbrc);

  /* Fill ExtendedCodingOption2, set non-zero defaults too */
  option2->Header.BufferId = MFX_EXTBUFF_CODING_OPTION2;
  option2->Header.BufferSz = sizeof (thiz->option2);
  option2->MBBRC = thiz->mbbrc;
  option2->ExtBRC = coding_option_get_value (EC_PROPS_EXTBRC, extbrc);
  option2->AdaptiveI = thiz->adaptive_i;
  option2->AdaptiveB = thiz->adaptive_b;
  option2->BitrateLimit = MFX_CODINGOPTION_OFF;
  option2->EnableMAD = MFX_CODINGOPTION_OFF;
  option2->UseRawRef = MFX_CODINGOPTION_OFF;
  gst_msdkenc_add_extra_param (thiz, (mfxExtBuffer *) option2);

  if (thiz->enable_extopt3) {
    option3->Header.BufferId = MFX_EXTBUFF_CODING_OPTION3;
    option3->Header.BufferSz = sizeof (thiz->option3);
    gst_msdkenc_add_extra_param (thiz, (mfxExtBuffer *) option3);
  }
}

/* Return TRUE if ROI is changed and update ROI parameters in encoder_roi */
gboolean
gst_msdkenc_get_roi_params (GstMsdkEnc * thiz,
    GstVideoCodecFrame * frame, mfxExtEncoderROI * encoder_roi)
{
  GstBuffer *input;
  guint num_roi, i, num_valid_roi = 0;
  gushort roi_mode = G_MAXUINT16;
  gpointer state = NULL;
  mfxExtEncoderROI *curr_roi = encoder_roi;
  mfxExtEncoderROI *prev_roi = encoder_roi + 1;

  if (!frame || !frame->input_buffer)
    return FALSE;

  memset (curr_roi, 0, sizeof (mfxExtEncoderROI));
  input = frame->input_buffer;

  num_roi =
      gst_buffer_get_n_meta (input, GST_VIDEO_REGION_OF_INTEREST_META_API_TYPE);

  if (num_roi == 0)
    goto end;

  curr_roi->Header.BufferId = MFX_EXTBUFF_ENCODER_ROI;
  curr_roi->Header.BufferSz = sizeof (mfxExtEncoderROI);

  for (i = 0; i < num_roi && num_valid_roi < 256; i++) {
    GstVideoRegionOfInterestMeta *roi;
    GstStructure *s;

    roi = (GstVideoRegionOfInterestMeta *)
        gst_buffer_iterate_meta_filtered (input, &state,
        GST_VIDEO_REGION_OF_INTEREST_META_API_TYPE);

    if (!roi)
      continue;

    /* ignore roi if overflow */
    if ((roi->x > G_MAXINT16) || (roi->y > G_MAXINT16)
        || (roi->w > G_MAXUINT16) || (roi->h > G_MAXUINT16)) {
      GST_DEBUG_OBJECT (thiz, "Ignoring ROI... ROI overflow");
      continue;
    }

    GST_LOG ("Input buffer ROI: type=%s id=%d (%d, %d) %dx%d",
        g_quark_to_string (roi->roi_type), roi->id, roi->x, roi->y, roi->w,
        roi->h);

    curr_roi->ROI[num_valid_roi].Left = roi->x;
    curr_roi->ROI[num_valid_roi].Top = roi->y;
    curr_roi->ROI[num_valid_roi].Right = roi->x + roi->w;
    curr_roi->ROI[num_valid_roi].Bottom = roi->y + roi->h;

    s = gst_video_region_of_interest_meta_get_param (roi, "roi/msdk");

    if (s) {
      int value = 0;

      if (roi_mode == G_MAXUINT16) {
        if (gst_structure_get_int (s, "delta-qp", &value)) {
#if (MFX_VERSION >= 1022)
          roi_mode = MFX_ROI_MODE_QP_DELTA;
          curr_roi->ROI[num_valid_roi].DeltaQP = CLAMP (value, -51, 51);
          GST_LOG ("Use delta-qp %d", value);
#else
          GST_WARNING
              ("Ignore delta QP because the MFX doesn't support delta QP mode");
#endif
        } else if (gst_structure_get_int (s, "priority", &value)) {
          roi_mode = MFX_ROI_MODE_PRIORITY;
          curr_roi->ROI[num_valid_roi].Priority = CLAMP (value, -3, 3);
          GST_LOG ("Use priority %d", value);
        } else
          continue;
#if (MFX_VERSION >= 1022)
      } else if (roi_mode == MFX_ROI_MODE_QP_DELTA &&
          gst_structure_get_int (s, "delta-qp", &value)) {
        curr_roi->ROI[num_valid_roi].DeltaQP = CLAMP (value, -51, 51);
#endif
      } else if (roi_mode == MFX_ROI_MODE_PRIORITY &&
          gst_structure_get_int (s, "priority", &value)) {
        curr_roi->ROI[num_valid_roi].Priority = CLAMP (value, -3, 3);
      } else
        continue;

      num_valid_roi++;
    }
  }

#if (MFX_VERSION >= 1022)
  curr_roi->ROIMode = roi_mode;
#endif

  curr_roi->NumROI = num_valid_roi;

end:
  if (curr_roi->NumROI == 0 && prev_roi->NumROI == 0)
    return FALSE;

  if (curr_roi->NumROI != prev_roi->NumROI ||
      memcmp (curr_roi, prev_roi, sizeof (mfxExtEncoderROI)) != 0) {
    *prev_roi = *curr_roi;
    return TRUE;
  }

  return FALSE;
}

static gboolean
gst_msdkenc_init_encoder (GstMsdkEnc * thiz)
{
  GstMsdkEncClass *klass = GST_MSDKENC_GET_CLASS (thiz);
  GstVideoInfo *info;
  mfxSession session;
  mfxStatus status;
  mfxFrameAllocRequest request[2];
  guint i;
  gboolean need_vpp = TRUE;
  GstVideoFormat encoder_input_fmt;
  mfxExtVideoSignalInfo ext_vsi;

  if (thiz->initialized) {
    GST_DEBUG_OBJECT (thiz, "Already initialized");
    return TRUE;
  }

  if (!thiz->context) {
    GST_WARNING_OBJECT (thiz, "No MSDK Context");
    return FALSE;
  }

  if (!thiz->input_state) {
    GST_DEBUG_OBJECT (thiz, "Have no input state yet");
    return FALSE;
  }
  info = &thiz->input_state->info;

  GST_OBJECT_LOCK (thiz);
  session = gst_msdk_context_get_session (thiz->context);
  thiz->codename = msdk_get_platform_codename (session);

  thiz->has_vpp = FALSE;
  if (thiz->use_video_memory)
    gst_msdk_set_frame_allocator (thiz->context);

  encoder_input_fmt = GST_VIDEO_INFO_FORMAT (info);
  need_vpp = klass->need_conversion (thiz, info, &encoder_input_fmt);

  if (need_vpp) {
    switch (GST_VIDEO_INFO_FORMAT (info)) {
      case GST_VIDEO_FORMAT_YV12:
      case GST_VIDEO_FORMAT_I420:
        thiz->vpp_param.vpp.In.FourCC = MFX_FOURCC_YV12;
        thiz->vpp_param.vpp.In.ChromaFormat = MFX_CHROMAFORMAT_YUV420;
        break;
      case GST_VIDEO_FORMAT_YUY2:
        thiz->vpp_param.vpp.In.FourCC = MFX_FOURCC_YUY2;
        thiz->vpp_param.vpp.In.ChromaFormat = MFX_CHROMAFORMAT_YUV422;
        break;
      case GST_VIDEO_FORMAT_UYVY:
        thiz->vpp_param.vpp.In.FourCC = MFX_FOURCC_UYVY;
        thiz->vpp_param.vpp.In.ChromaFormat = MFX_CHROMAFORMAT_YUV422;
        break;
      case GST_VIDEO_FORMAT_BGRA:
        thiz->vpp_param.vpp.In.FourCC = MFX_FOURCC_RGB4;
        thiz->vpp_param.vpp.In.ChromaFormat = MFX_CHROMAFORMAT_YUV444;
        break;
      default:
        g_assert_not_reached ();
        break;
    }

    if (thiz->use_video_memory)
      thiz->vpp_param.IOPattern =
          MFX_IOPATTERN_IN_VIDEO_MEMORY | MFX_IOPATTERN_OUT_VIDEO_MEMORY;
    else
      thiz->vpp_param.IOPattern =
          MFX_IOPATTERN_IN_SYSTEM_MEMORY | MFX_IOPATTERN_OUT_SYSTEM_MEMORY;

    thiz->vpp_param.vpp.In.Width = GST_ROUND_UP_16 (info->width);
    thiz->vpp_param.vpp.In.Height = GST_ROUND_UP_32 (info->height);
    thiz->vpp_param.vpp.In.CropW = info->width;
    thiz->vpp_param.vpp.In.CropH = info->height;
    thiz->vpp_param.vpp.In.FrameRateExtN = info->fps_n;
    thiz->vpp_param.vpp.In.FrameRateExtD = info->fps_d;
    thiz->vpp_param.vpp.In.AspectRatioW = info->par_n;
    thiz->vpp_param.vpp.In.AspectRatioH = info->par_d;
    thiz->vpp_param.vpp.In.PicStruct = MFX_PICSTRUCT_PROGRESSIVE;

    /* work-around to avoid zero fps in msdk structure */
    if (0 == thiz->vpp_param.vpp.In.FrameRateExtN)
      thiz->vpp_param.vpp.In.FrameRateExtN = 30;

    thiz->vpp_param.vpp.Out = thiz->vpp_param.vpp.In;

    switch (encoder_input_fmt) {
      case GST_VIDEO_FORMAT_P010_10LE:
        thiz->vpp_param.vpp.Out.FourCC = MFX_FOURCC_P010;
        thiz->vpp_param.vpp.Out.ChromaFormat = MFX_CHROMAFORMAT_YUV420;
        break;

      case GST_VIDEO_FORMAT_YUY2:
        thiz->vpp_param.vpp.Out.FourCC = MFX_FOURCC_YUY2;
        thiz->vpp_param.vpp.Out.ChromaFormat = MFX_CHROMAFORMAT_YUV422;
        break;

      default:
        thiz->vpp_param.vpp.Out.FourCC = MFX_FOURCC_NV12;
        thiz->vpp_param.vpp.Out.ChromaFormat = MFX_CHROMAFORMAT_YUV420;
        break;
    }

    /* validate parameters and allow MFX to make adjustments */
    status = MFXVideoVPP_Query (session, &thiz->vpp_param, &thiz->vpp_param);
    if (status < MFX_ERR_NONE) {
      GST_ERROR_OBJECT (thiz, "Video VPP Query failed (%s)",
          msdk_status_to_string (status));
      goto failed;
    } else if (status > MFX_ERR_NONE) {
      GST_WARNING_OBJECT (thiz, "Video VPP Query returned: %s",
          msdk_status_to_string (status));
    }

    status = MFXVideoVPP_QueryIOSurf (session, &thiz->vpp_param, request);
    if (status < MFX_ERR_NONE) {
      GST_ERROR_OBJECT (thiz, "VPP Query IO surfaces failed (%s)",
          msdk_status_to_string (status));
      goto failed;
    } else if (status > MFX_ERR_NONE) {
      GST_WARNING_OBJECT (thiz, "VPP Query IO surfaces returned: %s",
          msdk_status_to_string (status));
    }

    if (thiz->use_video_memory)
      request[0].NumFrameSuggested +=
          gst_msdk_context_get_shared_async_depth (thiz->context);
    thiz->num_vpp_surfaces = request[0].NumFrameSuggested;

    status = MFXVideoVPP_Init (session, &thiz->vpp_param);
    if (status < MFX_ERR_NONE) {
      GST_ERROR_OBJECT (thiz, "Init failed (%s)",
          msdk_status_to_string (status));
    } else if (status > MFX_ERR_NONE) {
      GST_WARNING_OBJECT (thiz, "Init returned: %s",
          msdk_status_to_string (status));
    }

    status = MFXVideoVPP_GetVideoParam (session, &thiz->vpp_param);
    if (status < MFX_ERR_NONE) {
      mfxStatus status1;
      GST_ERROR_OBJECT (thiz, "Get VPP Parameters failed (%s)",
          msdk_status_to_string (status));
      status1 = MFXVideoVPP_Close (session);
      if (status1 != MFX_ERR_NONE && status1 != MFX_ERR_NOT_INITIALIZED)
        GST_WARNING_OBJECT (thiz, "VPP close failed (%s)",
            msdk_status_to_string (status1));
    } else if (status > MFX_ERR_NONE) {
      GST_WARNING_OBJECT (thiz, "Get VPP Parameters returned: %s",
          msdk_status_to_string (status));
    }

    thiz->has_vpp = TRUE;
  }

  thiz->param.AsyncDepth = thiz->async_depth;
  if (thiz->use_video_memory)
    thiz->param.IOPattern = MFX_IOPATTERN_IN_VIDEO_MEMORY;
  else
    thiz->param.IOPattern = MFX_IOPATTERN_IN_SYSTEM_MEMORY;

  thiz->param.mfx.TargetUsage = thiz->target_usage;
  thiz->param.mfx.GopPicSize = thiz->gop_size;
  thiz->param.mfx.GopRefDist = thiz->b_frames + 1;
  thiz->param.mfx.IdrInterval = thiz->i_frames;
  thiz->param.mfx.NumSlice = thiz->num_slices;
  thiz->param.mfx.NumRefFrame = thiz->ref_frames;
  thiz->param.mfx.EncodedOrder = 0;     /* Take input frames in display order */

  thiz->param.mfx.FrameInfo.Width = GST_ROUND_UP_16 (info->width);
  thiz->param.mfx.FrameInfo.Height = GST_ROUND_UP_32 (info->height);
  thiz->param.mfx.FrameInfo.CropW = info->width;
  thiz->param.mfx.FrameInfo.CropH = info->height;
  thiz->param.mfx.FrameInfo.FrameRateExtN = info->fps_n;
  thiz->param.mfx.FrameInfo.FrameRateExtD = info->fps_d;
  thiz->param.mfx.FrameInfo.AspectRatioW = info->par_n;
  thiz->param.mfx.FrameInfo.AspectRatioH = info->par_d;
  thiz->param.mfx.FrameInfo.PicStruct = MFX_PICSTRUCT_PROGRESSIVE;
  thiz->param.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV420;

  /* work-around to avoid zero fps in msdk structure */
  if (0 == thiz->param.mfx.FrameInfo.FrameRateExtN)
    thiz->param.mfx.FrameInfo.FrameRateExtN = 30;

  thiz->frame_duration =
      gst_util_uint64_scale (GST_SECOND,
      thiz->param.mfx.FrameInfo.FrameRateExtD,
      thiz->param.mfx.FrameInfo.FrameRateExtN);

  switch (encoder_input_fmt) {
    case GST_VIDEO_FORMAT_P010_10LE:
      thiz->param.mfx.FrameInfo.FourCC = MFX_FOURCC_P010;
      thiz->param.mfx.FrameInfo.BitDepthLuma = 10;
      thiz->param.mfx.FrameInfo.BitDepthChroma = 10;
      thiz->param.mfx.FrameInfo.Shift = 1;
      break;
    case GST_VIDEO_FORMAT_VUYA:
      thiz->param.mfx.FrameInfo.FourCC = MFX_FOURCC_AYUV;
      thiz->param.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV444;
      thiz->param.mfx.FrameInfo.BitDepthLuma = 8;
      thiz->param.mfx.FrameInfo.BitDepthChroma = 8;
      break;
#if (MFX_VERSION >= 1027)
    case GST_VIDEO_FORMAT_Y410:
      thiz->param.mfx.FrameInfo.FourCC = MFX_FOURCC_Y410;
      thiz->param.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV444;
      thiz->param.mfx.FrameInfo.BitDepthLuma = 10;
      thiz->param.mfx.FrameInfo.BitDepthChroma = 10;
      break;
    case GST_VIDEO_FORMAT_Y210:
      thiz->param.mfx.FrameInfo.FourCC = MFX_FOURCC_Y210;
      thiz->param.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV422;
      thiz->param.mfx.FrameInfo.BitDepthLuma = 10;
      thiz->param.mfx.FrameInfo.BitDepthChroma = 10;
      thiz->param.mfx.FrameInfo.Shift = 1;
      break;
#endif
    case GST_VIDEO_FORMAT_BGRA:
      thiz->param.mfx.FrameInfo.FourCC = MFX_FOURCC_RGB4;
      thiz->param.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV444;
      thiz->param.mfx.FrameInfo.BitDepthLuma = 8;
      thiz->param.mfx.FrameInfo.BitDepthChroma = 8;
      break;
    case GST_VIDEO_FORMAT_BGR10A2_LE:
      thiz->param.mfx.FrameInfo.FourCC = MFX_FOURCC_A2RGB10;
      thiz->param.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV444;
      thiz->param.mfx.FrameInfo.BitDepthLuma = 10;
      thiz->param.mfx.FrameInfo.BitDepthChroma = 10;
      break;
    case GST_VIDEO_FORMAT_YUY2:
      thiz->param.mfx.FrameInfo.FourCC = MFX_FOURCC_YUY2;
      thiz->param.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV422;
      thiz->param.mfx.FrameInfo.BitDepthLuma = 8;
      thiz->param.mfx.FrameInfo.BitDepthChroma = 8;
      break;
#if (MFX_VERSION >= 1031)
    case GST_VIDEO_FORMAT_P012_LE:
      thiz->param.mfx.FrameInfo.FourCC = MFX_FOURCC_P016;
      thiz->param.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV420;
      thiz->param.mfx.FrameInfo.BitDepthLuma = 12;
      thiz->param.mfx.FrameInfo.BitDepthChroma = 12;
      thiz->param.mfx.FrameInfo.Shift = 1;
      break;
#endif
    default:
      thiz->param.mfx.FrameInfo.FourCC = MFX_FOURCC_NV12;
      thiz->param.mfx.FrameInfo.BitDepthLuma = 8;
      thiz->param.mfx.FrameInfo.BitDepthChroma = 8;
  }

  /* work-around to avoid zero fps in msdk structure */
  if (0 == thiz->param.mfx.FrameInfo.FrameRateExtN)
    thiz->param.mfx.FrameInfo.FrameRateExtN = 30;

  /* ensure bitrate control parameters */
  ensure_bitrate_control (thiz);

  /* allow subclass configure further */
  if (klass->configure) {
    if (!klass->configure (thiz))
      goto failed;
  }

  /* If color properties are available from upstream, set it and pass to MediaSDK here.
   * MJPEG and VP9 are excluded as MediaSDK does not support to handle video param
   * extbuff with buffer id equals to MFX_EXTBUFF_VIDEO_SIGNAL_INFO.
   */
  if (thiz->param.mfx.CodecId != MFX_CODEC_JPEG &&
      thiz->param.mfx.CodecId != MFX_CODEC_VP9 &&
      (info->colorimetry.primaries || info->colorimetry.transfer
          || info->colorimetry.matrix)) {
    memset (&ext_vsi, 0, sizeof (ext_vsi));
    ext_vsi.Header.BufferId = MFX_EXTBUFF_VIDEO_SIGNAL_INFO;
    ext_vsi.Header.BufferSz = sizeof (ext_vsi);
    ext_vsi.ColourDescriptionPresent = 1;
    ext_vsi.ColourPrimaries =
        gst_video_color_primaries_to_iso (info->colorimetry.primaries);
    ext_vsi.TransferCharacteristics =
        gst_video_transfer_function_to_iso (info->colorimetry.transfer);
    ext_vsi.MatrixCoefficients =
        gst_video_color_matrix_to_iso (info->colorimetry.matrix);
    gst_msdkenc_add_extra_param (thiz, (mfxExtBuffer *) & ext_vsi);
  }

  if (thiz->num_extra_params) {
    thiz->param.NumExtParam = thiz->num_extra_params;
    thiz->param.ExtParam = thiz->extra_params;
  }

  /* validate parameters and allow MFX to make adjustments */
  status = MFXVideoENCODE_Query (session, &thiz->param, &thiz->param);
  if (status < MFX_ERR_NONE) {
    GST_ERROR_OBJECT (thiz, "Video Encode Query failed (%s)",
        msdk_status_to_string (status));
    goto failed;
  } else if (status > MFX_ERR_NONE) {
    GST_WARNING_OBJECT (thiz, "Video Encode Query returned: %s",
        msdk_status_to_string (status));
  }

  status = MFXVideoENCODE_QueryIOSurf (session, &thiz->param, request);
  if (status < MFX_ERR_NONE) {
    GST_ERROR_OBJECT (thiz, "Encode Query IO surfaces failed (%s)",
        msdk_status_to_string (status));
    goto failed;
  } else if (status > MFX_ERR_NONE) {
    GST_WARNING_OBJECT (thiz, "Encode Query IO surfaces returned: %s",
        msdk_status_to_string (status));
  }

  request[0].NumFrameSuggested += thiz->num_extra_frames;

  if (thiz->has_vpp)
    request[0].NumFrameSuggested += thiz->num_vpp_surfaces + 1 - 4;

  /* Maximum of VPP output and encoder input, if using VPP */
  if (thiz->has_vpp)
    request[0].NumFrameSuggested =
        MAX (request[0].NumFrameSuggested, request[1].NumFrameSuggested);
  if (request[0].NumFrameSuggested < thiz->param.AsyncDepth) {
    GST_ERROR_OBJECT (thiz, "Required %d surfaces (%d suggested), async %d",
        request[0].NumFrameMin, request[0].NumFrameSuggested,
        thiz->param.AsyncDepth);
    goto failed;
  }

  /* This is VPP output (if any) and encoder input */
  thiz->num_surfaces = request[0].NumFrameSuggested;

  GST_DEBUG_OBJECT (thiz, "Required %d surfaces (%d suggested), allocated %d",
      request[0].NumFrameMin, request[0].NumFrameSuggested, thiz->num_surfaces);

  status = MFXVideoENCODE_Init (session, &thiz->param);
  if (status < MFX_ERR_NONE) {
    GST_ERROR_OBJECT (thiz, "Init failed (%s)", msdk_status_to_string (status));
    goto failed;
  } else if (status > MFX_ERR_NONE) {
    GST_WARNING_OBJECT (thiz, "Init returned: %s",
        msdk_status_to_string (status));
  }

  status = MFXVideoENCODE_GetVideoParam (session, &thiz->param);
  if (status < MFX_ERR_NONE) {
    GST_ERROR_OBJECT (thiz, "Get Video Parameters failed (%s)",
        msdk_status_to_string (status));
    goto failed;
  } else if (status > MFX_ERR_NONE) {
    GST_WARNING_OBJECT (thiz, "Get Video Parameters returned: %s",
        msdk_status_to_string (status));
  }

  thiz->num_tasks = thiz->param.AsyncDepth;
  thiz->tasks = g_new0 (MsdkEncTask, thiz->num_tasks);
  for (i = 0; i < thiz->num_tasks; i++) {
    thiz->tasks[i].output_bitstream.Data = _aligned_alloc (32,
        thiz->param.mfx.BufferSizeInKB * thiz->param.mfx.BRCParamMultiplier *
        1024);
    if (!thiz->tasks[i].output_bitstream.Data) {
      GST_ERROR_OBJECT (thiz, "Memory allocation failed");
      goto failed;
    }
    thiz->tasks[i].output_bitstream.MaxLength =
        thiz->param.mfx.BufferSizeInKB * thiz->param.mfx.BRCParamMultiplier *
        1024;
  }
  thiz->next_task = 0;

  thiz->reconfig = FALSE;
  thiz->initialized = TRUE;

  GST_OBJECT_UNLOCK (thiz);

  return TRUE;

failed:
  GST_OBJECT_UNLOCK (thiz);
  return FALSE;
}

static void
gst_msdkenc_close_encoder (GstMsdkEnc * thiz)
{
  guint i;
  mfxStatus status;

  if (!thiz->context || !thiz->initialized)
    return;

  GST_DEBUG_OBJECT (thiz, "Closing encoder with context %" GST_PTR_FORMAT,
      thiz->context);

  gst_clear_object (&thiz->msdk_pool);
  gst_clear_object (&thiz->msdk_converted_pool);

  status = MFXVideoENCODE_Close (gst_msdk_context_get_session (thiz->context));
  if (status != MFX_ERR_NONE && status != MFX_ERR_NOT_INITIALIZED) {
    GST_WARNING_OBJECT (thiz, "Encoder close failed (%s)",
        msdk_status_to_string (status));
  }

  if (thiz->tasks) {
    for (i = 0; i < thiz->num_tasks; i++) {
      MsdkEncTask *task = &thiz->tasks[i];
      if (task->output_bitstream.Data) {
        _aligned_free (task->output_bitstream.Data);
      }
    }
  }
  g_free (thiz->tasks);
  thiz->tasks = NULL;

  /* Close VPP before freeing the surfaces. They are shared between encoder
   * and VPP */
  if (thiz->has_vpp) {
    status = MFXVideoVPP_Close (gst_msdk_context_get_session (thiz->context));
    if (status != MFX_ERR_NONE && status != MFX_ERR_NOT_INITIALIZED) {
      GST_WARNING_OBJECT (thiz, "VPP close failed (%s)",
          msdk_status_to_string (status));
    }
  }

  memset (&thiz->param, 0, sizeof (thiz->param));
  thiz->num_extra_params = 0;
  thiz->initialized = FALSE;
}

typedef struct
{
  GstVideoCodecFrame *frame;
  GstMsdkSurface *frame_surface;
  GstMsdkSurface *converted_surface;
} FrameData;

static FrameData *
gst_msdkenc_queue_frame (GstMsdkEnc * thiz, GstVideoCodecFrame * frame,
    GstVideoInfo * info)
{
  FrameData *fdata;

  fdata = g_slice_new (FrameData);
  fdata->frame = gst_video_codec_frame_ref (frame);

  thiz->pending_frames = g_list_prepend (thiz->pending_frames, fdata);

  return fdata;
}

static void
gst_msdkenc_free_surface (GstMsdkSurface * surface)
{
  if (surface->buf)
    gst_buffer_unref (surface->buf);

  g_slice_free (GstMsdkSurface, surface);
}

static void
gst_msdkenc_free_frame_data (GstMsdkEnc * thiz, FrameData * fdata)
{
  if (fdata->frame_surface)
    gst_msdkenc_free_surface (fdata->frame_surface);
  if (thiz->has_vpp)
    gst_msdkenc_free_surface (fdata->converted_surface);

  gst_video_codec_frame_unref (fdata->frame);
  g_slice_free (FrameData, fdata);
}

static void
gst_msdkenc_dequeue_frame (GstMsdkEnc * thiz, GstVideoCodecFrame * frame)
{
  GList *l;

  for (l = thiz->pending_frames; l;) {
    FrameData *fdata = l->data;
    GList *l1 = l;

    l = l->next;

    if (fdata->frame != frame)
      continue;

    gst_msdkenc_free_frame_data (thiz, fdata);

    thiz->pending_frames = g_list_delete_link (thiz->pending_frames, l1);
    return;
  }
}

static void
gst_msdkenc_dequeue_all_frames (GstMsdkEnc * thiz)
{
  GList *l;

  for (l = thiz->pending_frames; l; l = l->next) {
    FrameData *fdata = l->data;

    gst_msdkenc_free_frame_data (thiz, fdata);
  }
  g_list_free (thiz->pending_frames);
  thiz->pending_frames = NULL;
}

static MsdkEncTask *
gst_msdkenc_get_free_task (GstMsdkEnc * thiz)
{
  MsdkEncTask *tasks = thiz->tasks;
  guint size = thiz->num_tasks;
  guint start = thiz->next_task;
  guint i;

  if (tasks) {
    for (i = 0; i < size; i++) {
      guint t = (start + i) % size;
      if (tasks[t].sync_point == NULL)
        return &tasks[t];
    }
  }
  return NULL;
}

static void
gst_msdkenc_reset_task (MsdkEncTask * task)
{
  task->output_bitstream.DataLength = 0;
  task->sync_point = NULL;
}

static GstVideoCodecFrame *
gst_msdkenc_find_best_frame (GstMsdkEnc * thiz, GList * frames,
    mfxBitstream * bitstream)
{
  GList *iter;
  GstVideoCodecFrame *ret = NULL;
  GstClockTime pts;
  GstClockTimeDiff best_diff = GST_CLOCK_STIME_NONE;

  if (!bitstream)
    return NULL;

  if (bitstream->TimeStamp == MFX_TIMESTAMP_UNKNOWN) {
    pts = GST_CLOCK_TIME_NONE;
  } else {
    pts = gst_util_uint64_scale (bitstream->TimeStamp, GST_SECOND, 90000);
  }

  for (iter = frames; iter; iter = g_list_next (iter)) {
    GstVideoCodecFrame *frame = (GstVideoCodecFrame *) iter->data;

    /* if we don't know the time stamp, find the first frame which
     * has unknown timestamp */
    if (!GST_CLOCK_TIME_IS_VALID (pts)) {
      if (!GST_CLOCK_TIME_IS_VALID (frame->pts)) {
        ret = frame;
        break;
      }
    } else {
      GstClockTimeDiff abs_diff = ABS (GST_CLOCK_DIFF (frame->pts, pts));
      if (abs_diff == 0) {
        ret = frame;
        break;
      }

      if (!GST_CLOCK_STIME_IS_VALID (best_diff) || abs_diff < best_diff) {
        ret = frame;
        best_diff = abs_diff;
      }
    }
  }

  if (ret)
    gst_video_codec_frame_ref (ret);

  return ret;
}

static GstFlowReturn
gst_msdkenc_finish_frame (GstMsdkEnc * thiz, MsdkEncTask * task,
    gboolean discard)
{
  GstVideoCodecFrame *frame;
  GList *list;

  if (!task->sync_point)
    return GST_FLOW_OK;

  list = gst_video_encoder_get_frames (GST_VIDEO_ENCODER (thiz));

  if (!list) {
    GST_ERROR_OBJECT (thiz, "failed to get list of frame");
    return GST_FLOW_ERROR;
  }

  /* Wait for encoding operation to complete, the magic number 300000 below
   * is used in MSDK samples
   * #define MSDK_ENC_WAIT_INTERVAL 300000
   */
  if (MFXVideoCORE_SyncOperation (gst_msdk_context_get_session (thiz->context),
          task->sync_point, 300000) != MFX_ERR_NONE)
    GST_WARNING_OBJECT (thiz, "failed to do sync operation");

  if (!discard && task->output_bitstream.DataLength) {
    GstBuffer *out_buf = NULL;
    guint8 *data =
        task->output_bitstream.Data + task->output_bitstream.DataOffset;
    gsize size = task->output_bitstream.DataLength;

    frame = gst_msdkenc_find_best_frame (thiz, list, &task->output_bitstream);
    if (!frame) {
      /* just pick the oldest one */
      frame = gst_video_encoder_get_oldest_frame (GST_VIDEO_ENCODER (thiz));
    }

    out_buf = gst_buffer_new_allocate (NULL, size, NULL);
    gst_buffer_fill (out_buf, 0, data, size);
    frame->output_buffer = out_buf;
    frame->pts =
        gst_util_uint64_scale (task->output_bitstream.TimeStamp, GST_SECOND,
        90000);
    frame->dts =
        gst_util_uint64_scale (task->output_bitstream.DecodeTimeStamp,
        GST_SECOND, 90000);
    if ((task->output_bitstream.FrameType & MFX_FRAMETYPE_IDR) != 0 ||
        (task->output_bitstream.FrameType & MFX_FRAMETYPE_xIDR) != 0) {
      GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
    }

    /* Mark task as available */
    gst_msdkenc_reset_task (task);
  } else {
    frame = gst_video_encoder_get_oldest_frame (GST_VIDEO_ENCODER (thiz));
  }

  g_list_free_full (list, (GDestroyNotify) gst_video_codec_frame_unref);

  gst_video_codec_frame_unref (frame);
  gst_msdkenc_dequeue_frame (thiz, frame);

  return gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (thiz), frame);
}

static GstFlowReturn
gst_msdkenc_encode_frame (GstMsdkEnc * thiz, mfxFrameSurface1 * surface,
    GstVideoCodecFrame * input_frame)
{
  mfxSession session;
  MsdkEncTask *task;
  mfxStatus status;

  if (G_UNLIKELY (thiz->context == NULL)) {
    gst_msdkenc_dequeue_frame (thiz, input_frame);
    gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (thiz), input_frame);
    return GST_FLOW_NOT_NEGOTIATED;
  }
  session = gst_msdk_context_get_session (thiz->context);

  task = gst_msdkenc_get_free_task (thiz);

  for (;;) {
    /* Force key-frame if needed */
    if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (input_frame))
      thiz->enc_cntrl.FrameType =
          MFX_FRAMETYPE_I | MFX_FRAMETYPE_IDR | MFX_FRAMETYPE_REF;
    else
      thiz->enc_cntrl.FrameType = MFX_FRAMETYPE_UNKNOWN;

    status =
        MFXVideoENCODE_EncodeFrameAsync (session, &thiz->enc_cntrl, surface,
        &task->output_bitstream, &task->sync_point);

    if (status != MFX_WRN_DEVICE_BUSY)
      break;
    /* If device is busy, wait 1ms and retry, as per MSDK's recomendation */
    g_usleep (1000);
  };

  if (status != MFX_ERR_NONE && status != MFX_ERR_MORE_DATA) {
    GST_ELEMENT_ERROR (thiz, STREAM, ENCODE, ("Encode frame failed."),
        ("MSDK encode error (%s)", msdk_status_to_string (status)));
    gst_msdkenc_dequeue_frame (thiz, input_frame);
    gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (thiz), input_frame);
    return GST_FLOW_ERROR;
  }

  if (task->sync_point) {
    thiz->next_task = ((task - thiz->tasks) + 1) % thiz->num_tasks;
  } else if (status == MFX_ERR_MORE_DATA) {
    gst_msdkenc_dequeue_frame (thiz, input_frame);
  }

  /* Ensure that next task is available */
  task = thiz->tasks + thiz->next_task;
  return gst_msdkenc_finish_frame (thiz, task, FALSE);
}

static guint
gst_msdkenc_maximum_delayed_frames (GstMsdkEnc * thiz)
{
  return thiz->num_tasks;
}

static void
gst_msdkenc_set_latency (GstMsdkEnc * thiz)
{
  GstVideoInfo *info = &thiz->input_state->info;
  gint max_delayed_frames;
  GstClockTime latency;

  max_delayed_frames = gst_msdkenc_maximum_delayed_frames (thiz);

  if (info->fps_n) {
    latency = gst_util_uint64_scale_ceil (GST_SECOND * info->fps_d,
        max_delayed_frames, info->fps_n);
  } else {
    /* FIXME: Assume 25fps. This is better than reporting no latency at
     * all and then later failing in live pipelines
     */
    latency = gst_util_uint64_scale_ceil (GST_SECOND * 1,
        max_delayed_frames, 25);
  }

  GST_INFO_OBJECT (thiz,
      "Updating latency to %" GST_TIME_FORMAT " (%d frames)",
      GST_TIME_ARGS (latency), max_delayed_frames);

  gst_video_encoder_set_latency (GST_VIDEO_ENCODER (thiz), latency, latency);
}

static void
gst_msdkenc_flush_frames (GstMsdkEnc * thiz, gboolean discard)
{
  mfxStatus status;
  mfxSession session;
  MsdkEncTask *task;
  guint i, t;

  if (!thiz->tasks)
    return;

  GST_DEBUG_OBJECT (thiz, "flush frames");

  session = gst_msdk_context_get_session (thiz->context);

  for (;;) {
    task = thiz->tasks + thiz->next_task;
    gst_msdkenc_finish_frame (thiz, task, FALSE);

    status = MFXVideoENCODE_EncodeFrameAsync (session, NULL, NULL,
        &task->output_bitstream, &task->sync_point);

    if (status != MFX_ERR_NONE && status != MFX_ERR_MORE_DATA) {
      GST_ELEMENT_ERROR (thiz, STREAM, ENCODE, ("Encode frame failed."),
          ("MSDK encode error (%s)", msdk_status_to_string (status)));
      break;
    }

    if (task->sync_point) {
      thiz->next_task = ((task - thiz->tasks) + 1) % thiz->num_tasks;
    } else if (status == MFX_ERR_MORE_DATA) {
      break;
    }
  };

  t = thiz->next_task;
  for (i = 0; i < thiz->num_tasks; i++) {
    gst_msdkenc_finish_frame (thiz, &thiz->tasks[t], discard);
    t = (t + 1) % thiz->num_tasks;
  }
}

static gboolean
gst_msdkenc_set_src_caps (GstMsdkEnc * thiz)
{
  GstMsdkEncClass *klass = GST_MSDKENC_GET_CLASS (thiz);
  GstCaps *outcaps = NULL;
  GstVideoCodecState *state;
  GstTagList *tags;

  if (klass->set_src_caps)
    outcaps = klass->set_src_caps (thiz);

  if (!outcaps)
    return FALSE;

  state = gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (thiz),
      outcaps, thiz->input_state);
  GST_DEBUG_OBJECT (thiz, "output caps: %" GST_PTR_FORMAT, state->caps);

  gst_video_codec_state_unref (state);

  tags = gst_tag_list_new_empty ();
  gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER, "msdkenc",
      GST_TAG_MAXIMUM_BITRATE, thiz->bitrate * 1024,
      GST_TAG_NOMINAL_BITRATE, thiz->bitrate * 1024, NULL);
  gst_video_encoder_merge_tags (GST_VIDEO_ENCODER (thiz), tags,
      GST_TAG_MERGE_REPLACE);
  gst_tag_list_unref (tags);

  return TRUE;
}

#ifndef _WIN32
static GstBufferPool *
gst_msdk_create_va_pool (GstMsdkEnc * thiz, GstCaps * caps, guint num_buffers)
{
  GstBufferPool *pool = NULL;
  GstAllocator *allocator;
  GArray *formats = NULL;
  GstAllocationParams alloc_params = { 0, 31, 0, 0 };
  GstVaDisplay *display = NULL;
  GstVideoInfo info = thiz->input_state->info;

  display = (GstVaDisplay *) gst_msdk_context_get_va_display (thiz->context);

  if (thiz->use_dmabuf) {
    allocator = gst_va_dmabuf_allocator_new (display);
  } else {
    formats = g_array_new (FALSE, FALSE, sizeof (GstVideoFormat));
    g_array_append_val (formats, GST_VIDEO_INFO_FORMAT (&info));
    allocator = gst_va_allocator_new (display, formats);
  }

  if (!allocator) {
    GST_ERROR_OBJECT (thiz, "failed to create allocator");
    if (formats)
      g_array_unref (formats);
    return NULL;
  }

  pool =
      gst_va_pool_new_with_config (caps, GST_VIDEO_INFO_SIZE (&info),
      num_buffers, 0, VA_SURFACE_ATTRIB_USAGE_HINT_GENERIC, GST_VA_FEATURE_AUTO,
      allocator, &alloc_params);

  gst_object_unref (allocator);

  GST_LOG_OBJECT (thiz, "Creating va pool");
  return pool;
}
#else
static GstBufferPool *
gst_msdk_create_d3d11_pool (GstMsdkEnc * thiz, guint num_buffers)
{
  GstBufferPool *pool = NULL;
  GstD3D11Device *device;
  GstStructure *config;
  GstD3D11AllocationParams *params;
  GstD3D11Format device_format;
  guint bind_flags = 0;
  GstCaps *aligned_caps = NULL;
  GstVideoInfo *info = &thiz->input_state->info;
  GstVideoInfo aligned_info;
  gint aligned_width;
  gint aligned_height;

  device = gst_msdk_context_get_d3d11_device (thiz->context);

  aligned_width = GST_ROUND_UP_16 (info->width);
  if (GST_VIDEO_INFO_IS_INTERLACED (info)) {
    aligned_height = GST_ROUND_UP_32 (info->height);
  } else {
    aligned_height = GST_ROUND_UP_16 (info->height);
  }

  gst_video_info_set_interlaced_format (&aligned_info,
      GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_INTERLACE_MODE (info),
      aligned_width, aligned_height);

  gst_d3d11_device_get_format (device, GST_VIDEO_INFO_FORMAT (&aligned_info),
      &device_format);
  if ((device_format.format_support[0] & D3D11_FORMAT_SUPPORT_RENDER_TARGET) ==
      D3D11_FORMAT_SUPPORT_RENDER_TARGET) {
    bind_flags = D3D11_BIND_RENDER_TARGET;
  }

  aligned_caps = gst_video_info_to_caps (&aligned_info);

  pool = gst_d3d11_buffer_pool_new (device);
  config = gst_buffer_pool_get_config (pool);
  params = gst_d3d11_allocation_params_new (device, &aligned_info,
      GST_D3D11_ALLOCATION_FLAG_DEFAULT, bind_flags,
      D3D11_RESOURCE_MISC_SHARED);

  gst_buffer_pool_config_set_d3d11_allocation_params (config, params);
  gst_d3d11_allocation_params_free (params);
  gst_buffer_pool_config_set_params (config, aligned_caps,
      GST_VIDEO_INFO_SIZE (&aligned_info), num_buffers, 0);
  gst_buffer_pool_set_config (pool, config);

  gst_caps_unref (aligned_caps);
  GST_LOG_OBJECT (thiz, "Creating d3d11 pool");

  return pool;
}
#endif

static GstBufferPool *
gst_msdkenc_create_buffer_pool (GstMsdkEnc * thiz, GstCaps * caps,
    guint num_buffers, gboolean set_align)
{
  GstBufferPool *pool = NULL;
  GstStructure *config;
  GstVideoInfo info;
  GstVideoAlignment align;

  if (!gst_video_info_from_caps (&info, caps)) {
    GST_INFO_OBJECT (thiz, "failed to get video info");
    return FALSE;
  }

  gst_msdk_set_video_alignment (&info, 0, 0, &align);
  gst_video_info_align (&info, &align);
#ifndef _WIN32
  pool = gst_msdk_create_va_pool (thiz, caps, num_buffers);
#else
  pool = gst_msdk_create_d3d11_pool (thiz, num_buffers);
#endif
  if (!thiz->use_video_memory)
    pool = gst_video_buffer_pool_new ();
  if (!pool)
    goto error_no_pool;

  config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pool));
  gst_buffer_pool_config_set_params (config, caps,
      GST_VIDEO_INFO_SIZE (&info), num_buffers, 0);
  gst_buffer_pool_config_set_video_alignment (config, &align);

  if (thiz->use_video_memory) {
    gst_buffer_pool_config_add_option (config,
        GST_BUFFER_POOL_OPTION_MSDK_USE_VIDEO_MEMORY);
    if (thiz->use_dmabuf)
      gst_buffer_pool_config_add_option (config,
          GST_BUFFER_POOL_OPTION_MSDK_USE_DMABUF);
  }
  if (!gst_buffer_pool_set_config (pool, config))
    goto error_pool_config;

  if (set_align)
    thiz->aligned_info = info;

  return pool;

error_no_pool:
  {
    GST_INFO_OBJECT (thiz, "failed to create bufferpool");
    return NULL;
  }
error_pool_config:
  {
    GST_INFO_OBJECT (thiz, "failed to set config");
    gst_object_unref (pool);
    return NULL;
  }
}

/* Fixme: Common routine used by all msdk elements, should be
 * moved to a common util file */
static gboolean
_gst_caps_has_feature (const GstCaps * caps, const gchar * feature)
{
  guint i;

  for (i = 0; i < gst_caps_get_size (caps); i++) {
    GstCapsFeatures *const features = gst_caps_get_features (caps, i);
    /* Skip ANY features, we need an exact match for correct evaluation */
    if (gst_caps_features_is_any (features))
      continue;
    if (gst_caps_features_contains (features, feature))
      return TRUE;
  }
  return FALSE;
}

static gboolean
sinkpad_can_dmabuf (GstMsdkEnc * thiz)
{
  gboolean ret = FALSE;
  GstCaps *caps, *allowed_caps;
  GstPad *sinkpad;

  sinkpad = GST_VIDEO_ENCODER_SINK_PAD (thiz);
  caps = gst_pad_get_pad_template_caps (sinkpad);

  allowed_caps = gst_pad_peer_query_caps (sinkpad, caps);
  if (!allowed_caps)
    goto done;
  if (gst_caps_is_any (allowed_caps) || gst_caps_is_empty (allowed_caps)
      || allowed_caps == caps)
    goto done;

  if (_gst_caps_has_feature (allowed_caps, GST_CAPS_FEATURE_MEMORY_DMABUF))
    ret = TRUE;

done:
  if (caps)
    gst_caps_unref (caps);
  if (allowed_caps)
    gst_caps_unref (allowed_caps);
  return ret;
}

#ifndef _WIN32
static gboolean
sinkpad_is_va (GstMsdkEnc * thiz)
{
  GstCapsFeatures *features =
      gst_caps_get_features (thiz->input_state->caps, 0);
  if (gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_VA))
    return TRUE;

  return FALSE;
}
#else
static gboolean
sinkpad_is_d3d11 (GstMsdkEnc * thiz)
{
  GstCapsFeatures *features =
      gst_caps_get_features (thiz->input_state->caps, 0);
  if (gst_caps_features_contains (features,
          GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY))
    return TRUE;

  return FALSE;
}
#endif

static gboolean
gst_msdkenc_set_format (GstVideoEncoder * encoder, GstVideoCodecState * state)
{
  GstMsdkEnc *thiz = GST_MSDKENC (encoder);
  GstMsdkEncClass *klass = GST_MSDKENC_GET_CLASS (thiz);

  if (state) {
    if (thiz->input_state) {
      if (!gst_video_info_is_equal (&thiz->input_state->info, &state->info)) {
        GST_INFO_OBJECT (thiz, "Re-init the encoder as info changed");
        gst_msdkenc_flush_frames (thiz, FALSE);
        gst_msdkenc_close_encoder (thiz);
      }
      gst_video_codec_state_unref (thiz->input_state);
    }
    thiz->input_state = gst_video_codec_state_ref (state);
  }
#ifndef _WIN32
  thiz->use_video_memory = TRUE;
  if (sinkpad_is_va (thiz))
    thiz->use_va = TRUE;
#else
  thiz->use_video_memory = TRUE;
  if (sinkpad_is_d3d11 (thiz))
    thiz->use_d3d11 = TRUE;
#endif

  GST_INFO_OBJECT (encoder, "This MSDK encoder uses %s memory",
      thiz->use_video_memory ? "video" : "system");

  if (klass->set_format) {
    if (!klass->set_format (thiz))
      return FALSE;
  }

  /* If upstream supports DMABufCapsfeatures, then we request for the dmabuf
   * based pipeline usage. Ideally we should have dmabuf support even with
   * raw-caps negotiation, but we don't have dmabuf-import support in msdk
   * plugin yet */
  /* If VA is set, we do not fallback to DMA. */
  if (!thiz->use_va && sinkpad_can_dmabuf (thiz)) {
    thiz->input_state->caps = gst_caps_make_writable (thiz->input_state->caps);
    gst_caps_set_features (thiz->input_state->caps, 0,
        gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_DMABUF, NULL));
    thiz->use_dmabuf = TRUE;
  }

  if (!gst_msdkenc_init_encoder (thiz))
    return FALSE;

  if (!gst_msdkenc_set_src_caps (thiz)) {
    gst_msdkenc_close_encoder (thiz);
    return FALSE;
  }

  if (!thiz->msdk_pool) {
    guint num_buffers = gst_msdkenc_maximum_delayed_frames (thiz) + 1;
    thiz->msdk_pool =
        gst_msdkenc_create_buffer_pool (thiz, thiz->input_state->caps,
        num_buffers, TRUE);
  }

  gst_msdkenc_set_latency (thiz);

  /* Create another bufferpool if VPP requires */
  if (thiz->has_vpp) {
    GstVideoInfo *info = &thiz->input_state->info;
    GstVideoInfo out_info;
    GstVideoFormat out_fmt;
    GstCaps *caps;
    GstBufferPool *pool = NULL;

    gst_video_info_init (&out_info);
    out_fmt =
        gst_msdk_get_video_format_from_mfx_fourcc (thiz->vpp_param.vpp.
        Out.FourCC);
    gst_video_info_set_format (&out_info, out_fmt, info->width, info->height);
    caps = gst_video_info_to_caps (&out_info);

    /* If there's an existing pool try to reuse it when is compatible */
    if (thiz->msdk_converted_pool) {
      GstStructure *config;
      GstCaps *pool_caps;
      gboolean is_pool_compatible = FALSE;

      config = gst_buffer_pool_get_config (thiz->msdk_converted_pool);
      gst_buffer_pool_config_get_params (config, &pool_caps, NULL, NULL, NULL);
      if (caps && pool_caps)
        is_pool_compatible = gst_caps_is_equal (caps, pool_caps);
      gst_structure_free (config);

      /* If caps are the same then we are done */
      if (is_pool_compatible) {
        gst_caps_unref (caps);
        goto done;
      }
      /* Release current pool because we are going to create a new one */
      gst_clear_object (&thiz->msdk_converted_pool);
    }

    /* Otherwise create a new pool */
    pool =
        gst_msdkenc_create_buffer_pool (thiz, caps, thiz->num_surfaces, FALSE);

    thiz->msdk_converted_pool = pool;
    gst_caps_unref (caps);
  }

done:
  return TRUE;
}

/* This function will be removed later */
static GstMsdkSurface *
gst_msdkenc_get_surface_from_pool_old (GstMsdkEnc * thiz, GstBufferPool * pool,
    GstBufferPoolAcquireParams * params)
{
  GstBuffer *new_buffer;
  GstMsdkSurface *msdk_surface = NULL;

  if (!gst_buffer_pool_is_active (pool) &&
      !gst_buffer_pool_set_active (pool, TRUE)) {
    GST_ERROR_OBJECT (pool, "failed to activate buffer pool");
    return NULL;
  }

  if (gst_buffer_pool_acquire_buffer (pool, &new_buffer, params) != GST_FLOW_OK) {
    GST_ERROR_OBJECT (pool, "failed to acquire a buffer from pool");
    return NULL;
  }
#ifndef _WIN32
  msdk_surface = gst_msdk_import_to_msdk_surface (new_buffer, thiz->context,
      &thiz->aligned_info, 0);
#else
  msdk_surface =
      gst_msdk_import_sys_mem_to_msdk_surface (new_buffer, &thiz->aligned_info);
#endif

  if (msdk_surface)
    msdk_surface->buf = new_buffer;

  return msdk_surface;
}

static GstMsdkSurface *
gst_msdkenc_get_surface_from_pool (GstMsdkEnc * thiz,
    GstVideoCodecFrame * frame, GstBuffer * buf)
{
  GstBuffer *upload_buf;
  GstMsdkSurface *msdk_surface = NULL;
  GstVideoFrame src_frame, dst_frame;

  if (!gst_buffer_pool_is_active (thiz->msdk_pool) &&
      !gst_buffer_pool_set_active (thiz->msdk_pool, TRUE)) {
    GST_ERROR_OBJECT (thiz->msdk_pool, "failed to activate buffer pool");
    return NULL;
  }

  if (gst_buffer_pool_acquire_buffer (thiz->msdk_pool, &upload_buf,
          NULL) != GST_FLOW_OK) {
    GST_ERROR_OBJECT (thiz->msdk_pool, "failed to acquire a buffer from pool");
    return NULL;
  }

  if (!gst_video_frame_map (&src_frame, &thiz->input_state->info, buf,
          GST_MAP_READ)) {
    GST_WARNING ("Failed to map src frame");
    gst_buffer_unref (upload_buf);
    return NULL;
  }

  if (!gst_video_frame_map (&dst_frame, &thiz->aligned_info, upload_buf,
          GST_MAP_WRITE)) {
    GST_WARNING ("Failed to map dst frame");
    gst_video_frame_unmap (&src_frame);
    gst_buffer_unref (upload_buf);
    return NULL;
  }

  for (guint i = 0; i < GST_VIDEO_FRAME_N_PLANES (&src_frame); i++) {
    guint src_width_in_bytes, src_height;
    guint dst_width_in_bytes, dst_height;
    guint width_in_bytes, height;
    guint src_stride, dst_stride;
    guint8 *src_data, *dst_data;

    src_width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&src_frame, i) *
        GST_VIDEO_FRAME_COMP_PSTRIDE (&src_frame, i);
    src_height = GST_VIDEO_FRAME_COMP_HEIGHT (&src_frame, i);
    src_stride = GST_VIDEO_FRAME_COMP_STRIDE (&src_frame, i);

    dst_width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&dst_frame, i) *
        GST_VIDEO_FRAME_COMP_PSTRIDE (&src_frame, i);
    dst_height = GST_VIDEO_FRAME_COMP_HEIGHT (&src_frame, i);
    dst_stride = GST_VIDEO_FRAME_COMP_STRIDE (&dst_frame, i);

    width_in_bytes = MIN (src_width_in_bytes, dst_width_in_bytes);
    height = MIN (src_height, dst_height);

    src_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&src_frame, i);
    dst_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&dst_frame, i);

    for (guint j = 0; j < height; j++) {
      memcpy (dst_data, src_data, width_in_bytes);
      dst_data += dst_stride;
      src_data += src_stride;
    }
  }

  gst_video_frame_unmap (&dst_frame);
  gst_video_frame_unmap (&src_frame);

  if (thiz->use_video_memory) {
    msdk_surface = gst_msdk_import_to_msdk_surface (upload_buf, thiz->context,
        &thiz->aligned_info, GST_MAP_READ);
  } else {
    msdk_surface =
        gst_msdk_import_sys_mem_to_msdk_surface (upload_buf,
        &thiz->aligned_info);
  }

  gst_buffer_replace (&frame->input_buffer, upload_buf);
  gst_buffer_unref (upload_buf);

  return msdk_surface;
}

static GstMsdkSurface *
gst_msdkenc_get_surface_from_frame (GstMsdkEnc * thiz,
    GstVideoCodecFrame * frame)
{
  GstMsdkSurface *msdk_surface;
  GstBuffer *inbuf;

  inbuf = frame->input_buffer;
  if (gst_msdk_is_msdk_buffer (inbuf)) {
    msdk_surface = g_slice_new0 (GstMsdkSurface);
    msdk_surface->surface = gst_msdk_get_surface_from_buffer (inbuf);
    return msdk_surface;
  }

  msdk_surface = gst_msdk_import_to_msdk_surface (inbuf, thiz->context,
      &thiz->input_state->info, GST_MAP_READ);
  if (msdk_surface) {
    msdk_surface->buf = gst_buffer_ref (inbuf);
    return msdk_surface;
  }

  /* If upstream hasn't accpeted the proposed msdk bufferpool,
   * just copy frame to msdk buffer and take a surface from it.
   */

  return gst_msdkenc_get_surface_from_pool (thiz, frame, inbuf);
}

static GstFlowReturn
gst_msdkenc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame)
{
  GstMsdkEnc *thiz = GST_MSDKENC (encoder);
  GstMsdkEncClass *klass = GST_MSDKENC_GET_CLASS (thiz);
  GstVideoInfo *info = &thiz->input_state->info;
  FrameData *fdata;
  GstMsdkSurface *surface;

  if (thiz->reconfig || klass->need_reconfig (thiz, frame)) {
    gst_msdkenc_flush_frames (thiz, FALSE);
    gst_msdkenc_close_encoder (thiz);

    klass->set_extra_params (thiz, frame);

    // This will reinitialized the encoder but keep same input format.
    gst_msdkenc_set_format (encoder, NULL);
  }

  if (G_UNLIKELY (thiz->context == NULL))
    goto not_inited;

  if (thiz->has_vpp) {
    GstMsdkSurface *vpp_surface;
    GstVideoFrame vframe;
    mfxSession session;
    mfxSyncPoint vpp_sync_point = NULL;
    mfxStatus status;

    vpp_surface = gst_msdkenc_get_surface_from_frame (thiz, frame);
    if (!vpp_surface)
      goto invalid_surface;
    surface =
        gst_msdkenc_get_surface_from_pool_old (thiz, thiz->msdk_converted_pool,
        NULL);
    if (!surface)
      goto invalid_surface;

    if (!gst_video_frame_map (&vframe, info, frame->input_buffer, GST_MAP_READ))
      goto invalid_frame;

    if (frame->pts != GST_CLOCK_TIME_NONE) {
      vpp_surface->surface->Data.TimeStamp =
          gst_util_uint64_scale (frame->pts, 90000, GST_SECOND);
      surface->surface->Data.TimeStamp =
          gst_util_uint64_scale (frame->pts, 90000, GST_SECOND);
    } else {
      vpp_surface->surface->Data.TimeStamp = MFX_TIMESTAMP_UNKNOWN;
      surface->surface->Data.TimeStamp = MFX_TIMESTAMP_UNKNOWN;
    }

    session = gst_msdk_context_get_session (thiz->context);
    for (;;) {
      status =
          MFXVideoVPP_RunFrameVPPAsync (session, vpp_surface->surface,
          surface->surface, NULL, &vpp_sync_point);
      if (status != MFX_WRN_DEVICE_BUSY)
        break;
      /* If device is busy, wait 1ms and retry, as per MSDK's recomendation */
      g_usleep (1000);
    };

    gst_video_frame_unmap (&vframe);

    if (status != MFX_ERR_NONE && status != MFX_ERR_MORE_DATA) {
      GST_ELEMENT_ERROR (thiz, STREAM, ENCODE, ("Converting frame failed."),
          ("MSDK VPP error (%s)", msdk_status_to_string (status)));
      gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (thiz), frame);
      return GST_FLOW_ERROR;
    }

    fdata = g_slice_new0 (FrameData);
    fdata->frame = gst_video_codec_frame_ref (frame);
    fdata->frame_surface = vpp_surface;
    fdata->converted_surface = surface;

    thiz->pending_frames = g_list_prepend (thiz->pending_frames, fdata);
  } else {
    surface = gst_msdkenc_get_surface_from_frame (thiz, frame);
    if (!surface)
      goto invalid_surface;

    fdata = gst_msdkenc_queue_frame (thiz, frame, info);
    if (!fdata)
      goto invalid_frame;

    fdata->frame_surface = surface;

    /* It is possible to have input frame without any framerate/pts info,
     * we need to set the correct pts here. */
    if (frame->presentation_frame_number == 0)
      thiz->start_pts = frame->pts;

    if (frame->pts != GST_CLOCK_TIME_NONE) {
      frame->pts = thiz->start_pts +
          frame->presentation_frame_number * thiz->frame_duration;
      frame->duration = thiz->frame_duration;
      surface->surface->Data.TimeStamp =
          gst_util_uint64_scale (frame->pts, 90000, GST_SECOND);
    } else {
      surface->surface->Data.TimeStamp = MFX_TIMESTAMP_UNKNOWN;
    }
  }

  return gst_msdkenc_encode_frame (thiz, surface->surface, frame);

/* ERRORS */
not_inited:
  {
    GST_WARNING_OBJECT (encoder, "Got buffer before set_caps was called");
    return GST_FLOW_NOT_NEGOTIATED;
  }
invalid_surface:
  {
    GST_ERROR_OBJECT (encoder, "Surface pool is full");
    return GST_FLOW_ERROR;
  }
invalid_frame:
  {
    GST_WARNING_OBJECT (encoder, "Failed to map frame");
    return GST_FLOW_OK;
  }
}

static gboolean
gst_msdkenc_context_prepare (GstMsdkEnc * thiz)
{
  /* Try to find an existing context from the pipeline. This may (indirectly)
   * invoke gst_msdkenc_set_context, which will set thiz->context. */
  if (!gst_msdk_context_find (GST_ELEMENT_CAST (thiz), &thiz->context))
    return FALSE;

  if (thiz->context == thiz->old_context) {
    GST_INFO_OBJECT (thiz, "Found old context %" GST_PTR_FORMAT
        ", reusing as-is", thiz->context);
    return TRUE;
  }

  GST_INFO_OBJECT (thiz, "Found context %" GST_PTR_FORMAT " from neighbour",
      thiz->context);

  /* Check GST_MSDK_JOB_VPP and GST_MSDK_JOB_ENCODER together to avoid sharing context
   * between VPP and ENCODER
   * Example:
   * gst-launch-1.0 videotestsrc ! video/x-raw,format=I420 ! msdkh264enc ! \
   * msdkh264dec ! msdkvpp ! video/x-raw,format=YUY2 ! fakesink
   */
  if (!(gst_msdk_context_get_job_type (thiz->context) & (GST_MSDK_JOB_VPP |
              GST_MSDK_JOB_ENCODER))) {
    gst_msdk_context_add_job_type (thiz->context, GST_MSDK_JOB_ENCODER);
    return TRUE;
  }

  /* Found an existing context that's already being used as an encoder, clone
   * the MFX session inside it to create a new one */
  {
    GstMsdkContext *parent_context, *msdk_context;

    GST_INFO_OBJECT (thiz, "Creating new context %" GST_PTR_FORMAT " with "
        "joined session", thiz->context);
    parent_context = thiz->context;
    msdk_context = gst_msdk_context_new_with_parent (parent_context);

    if (!msdk_context) {
      GST_ERROR_OBJECT (thiz, "Failed to create a context with parent context "
          "as %" GST_PTR_FORMAT, parent_context);
      return FALSE;
    }

    thiz->context = msdk_context;
    gst_object_unref (parent_context);
  }

  return TRUE;
}

static gboolean
gst_msdkenc_start (GstVideoEncoder * encoder)
{
  GstMsdkEnc *thiz = GST_MSDKENC (encoder);

  if (!gst_msdkenc_context_prepare (thiz)) {
    if (!gst_msdk_ensure_new_context (GST_ELEMENT_CAST (thiz),
            thiz->hardware, GST_MSDK_JOB_ENCODER, &thiz->context))
      return FALSE;
    GST_INFO_OBJECT (thiz, "Creating new context %" GST_PTR_FORMAT,
        thiz->context);
  }

  /* Save the current context in a separate field so that we know whether it
   * has changed between calls to _start() */
  gst_object_replace ((GstObject **) & thiz->old_context,
      (GstObject *) thiz->context);

  gst_msdk_context_add_shared_async_depth (thiz->context, thiz->async_depth);

  /* Set the minimum pts to some huge value (1000 hours). This keeps
     the dts at the start of the stream from needing to be
     negative. */
  gst_video_encoder_set_min_pts (encoder, GST_SECOND * 60 * 60 * 1000);

  return TRUE;
}

static gboolean
gst_msdkenc_stop (GstVideoEncoder * encoder)
{
  GstMsdkEnc *thiz = GST_MSDKENC (encoder);

  gst_msdkenc_flush_frames (thiz, TRUE);
  gst_msdkenc_close_encoder (thiz);
  gst_msdkenc_dequeue_all_frames (thiz);

  if (thiz->input_state)
    gst_video_codec_state_unref (thiz->input_state);
  thiz->input_state = NULL;

  gst_clear_object (&thiz->context);

  return TRUE;
}

static gboolean
gst_msdkenc_flush (GstVideoEncoder * encoder)
{
  GstMsdkEnc *thiz = GST_MSDKENC (encoder);

  GST_DEBUG_OBJECT (encoder, "flush and close encoder");

  gst_msdkenc_flush_frames (thiz, TRUE);
  gst_msdkenc_close_encoder (thiz);
  gst_msdkenc_dequeue_all_frames (thiz);

  gst_msdkenc_init_encoder (thiz);

  return TRUE;
}

static GstFlowReturn
gst_msdkenc_finish (GstVideoEncoder * encoder)
{
  GstMsdkEnc *thiz = GST_MSDKENC (encoder);

  gst_msdkenc_flush_frames (thiz, FALSE);

  return GST_FLOW_OK;
}

#ifndef _WIN32
static gboolean
gst_msdkenc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
{
  GstMsdkEnc *thiz = GST_MSDKENC (encoder);
  GstVideoInfo info;
  GstBufferPool *pool = NULL;
  GstAllocator *allocator = NULL;
  GstCaps *caps;
  guint num_buffers;

  if (!thiz->input_state)
    return FALSE;

  gst_query_parse_allocation (query, &caps, NULL);

  if (!caps) {
    GST_INFO_OBJECT (encoder, "failed to get caps");
    return FALSE;
  }

  if (!gst_video_info_from_caps (&info, caps)) {
    GST_INFO_OBJECT (encoder, "failed to get video info");
    return FALSE;
  }

  /* if upstream allocation query supports dmabuf-capsfeatures,
   *  we do allocate dmabuf backed memory */
  if (_gst_caps_has_feature (caps, GST_CAPS_FEATURE_MEMORY_DMABUF)) {
    GST_INFO_OBJECT (thiz, "MSDK VPP srcpad uses DMABuf memory");
    thiz->use_dmabuf = TRUE;
  }

  num_buffers = gst_msdkenc_maximum_delayed_frames (thiz) + 1;
  pool = gst_msdkenc_create_buffer_pool (thiz, caps, num_buffers, FALSE);

  gst_query_add_allocation_pool (query, pool, GST_VIDEO_INFO_SIZE (&info),
      num_buffers, 0);
  gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);

  if (pool) {
    GstStructure *config;
    GstAllocationParams params = { 0, 31, 0, 0, };

    config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pool));

    if (gst_buffer_pool_config_get_allocator (config, &allocator, NULL))
      gst_query_add_allocation_param (query, allocator, &params);
    gst_structure_free (config);
  }

  gst_object_unref (pool);

  return GST_VIDEO_ENCODER_CLASS (parent_class)->propose_allocation (encoder,
      query);
}
#else
static gboolean
gst_msdkenc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
{
  GstMsdkEnc *thiz = GST_MSDKENC (encoder);
  GstVideoInfo info;
  GstBufferPool *pool = NULL;
  GstD3D11Device *device;
  GstCaps *caps;
  guint size;
  GstCapsFeatures *features;
  guint num_buffers;
  GstStructure *config;
  gboolean is_d3d11 = FALSE;

  if (!thiz->input_state)
    return FALSE;

  gst_query_parse_allocation (query, &caps, NULL);

  if (!caps) {
    GST_INFO_OBJECT (encoder, "failed to get caps");
    return FALSE;
  }

  if (!gst_video_info_from_caps (&info, caps)) {
    GST_INFO_OBJECT (encoder, "failed to get video info");
    return FALSE;
  }

  features = gst_caps_get_features (caps, 0);
  if (features && gst_caps_features_contains (features,
          GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) {
    GST_DEBUG_OBJECT (thiz, "upstream support d3d11 memory");
    device = gst_msdk_context_get_d3d11_device (thiz->context);
    pool = gst_d3d11_buffer_pool_new (device);
    is_d3d11 = TRUE;
  } else {
    pool = gst_video_buffer_pool_new ();
  }

  config = gst_buffer_pool_get_config (pool);
  gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);

  if (is_d3d11) {
    GstD3D11AllocationParams *d3d11_params;
    GstVideoAlignment align;

    /* d3d11 buffer pool doesn't support generic video alignment
     * because memory layout of CPU accessible staging texture is uncontrollable.
     * Do D3D11 specific handling */
    gst_msdk_set_video_alignment (&info, 0, 0, &align);

    d3d11_params = gst_d3d11_allocation_params_new (device, &info,
        GST_D3D11_ALLOCATION_FLAG_DEFAULT, 0, 0);

    gst_d3d11_allocation_params_alignment (d3d11_params, &align);
    gst_buffer_pool_config_set_d3d11_allocation_params (config, d3d11_params);
    gst_d3d11_allocation_params_free (d3d11_params);
  } else {
    gst_buffer_pool_config_add_option (config,
        GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
  }

  num_buffers = gst_msdkenc_maximum_delayed_frames (thiz) + 1;
  gst_buffer_pool_config_set_params (config,
      caps, GST_VIDEO_INFO_SIZE (&info), num_buffers, 0);
  gst_buffer_pool_set_config (pool, config);

  /* d3d11 buffer pool will update actual CPU accessible buffer size based on
   * allocated staging texture per gst_buffer_pool_set_config() call,
   * need query again to get the size */
  config = gst_buffer_pool_get_config (pool);
  gst_buffer_pool_config_get_params (config, NULL, &size, NULL, NULL);
  gst_structure_free (config);

  gst_query_add_allocation_pool (query, pool, size, num_buffers, 0);
  gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
  gst_object_unref (pool);

  return TRUE;
}
#endif

static gboolean
gst_msdkenc_query (GstVideoEncoder * encoder, GstQuery * query,
    GstPadDirection dir)
{
  GstMsdkEnc *thiz = GST_MSDKENC (encoder);
  gboolean ret = FALSE;

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_CONTEXT:{
      GstMsdkContext *msdk_context = NULL;

      gst_object_replace ((GstObject **) & msdk_context,
          (GstObject *) thiz->context);
      ret = gst_msdk_handle_context_query (GST_ELEMENT_CAST (encoder),
          query, msdk_context);
      gst_clear_object (&msdk_context);
      break;
    }
    default:
      if (dir == GST_PAD_SRC) {
        ret =
            GST_VIDEO_ENCODER_CLASS (parent_class)->src_query (encoder, query);
      } else {
        ret =
            GST_VIDEO_ENCODER_CLASS (parent_class)->sink_query (encoder, query);
      }
      break;
  }

  return ret;
}

static gboolean
gst_msdkenc_src_query (GstVideoEncoder * encoder, GstQuery * query)
{
  return gst_msdkenc_query (encoder, query, GST_PAD_SRC);
}

static gboolean
gst_msdkenc_sink_query (GstVideoEncoder * encoder, GstQuery * query)
{
  return gst_msdkenc_query (encoder, query, GST_PAD_SINK);
}

static void
gst_msdkenc_dispose (GObject * object)
{
  GstMsdkEnc *thiz = GST_MSDKENC (object);

  if (thiz->input_state)
    gst_video_codec_state_unref (thiz->input_state);
  thiz->input_state = NULL;

  gst_clear_object (&thiz->msdk_pool);
  gst_clear_object (&thiz->msdk_converted_pool);
  gst_clear_object (&thiz->old_context);

  gst_clear_structure (&thiz->ext_coding_props);

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static gboolean
gst_msdkenc_need_conversion (GstMsdkEnc * encoder, GstVideoInfo * info,
    GstVideoFormat * out_format)
{
  switch (GST_VIDEO_INFO_FORMAT (info)) {
    case GST_VIDEO_FORMAT_NV12:
    case GST_VIDEO_FORMAT_P010_10LE:
    case GST_VIDEO_FORMAT_VUYA:
    case GST_VIDEO_FORMAT_BGRx:
#if (MFX_VERSION >= 1027)
    case GST_VIDEO_FORMAT_Y410:
    case GST_VIDEO_FORMAT_Y210:
#endif
      return FALSE;

    default:
      if (GST_VIDEO_INFO_COMP_DEPTH (info, 0) == 10)
        *out_format = GST_VIDEO_FORMAT_P010_10LE;
      else
        *out_format = GST_VIDEO_FORMAT_NV12;
      return TRUE;
  }
}

static gboolean
gst_msdkenc_need_reconfig (GstMsdkEnc * encoder, GstVideoCodecFrame * frame)
{
  return FALSE;
}

static void
gst_msdkenc_set_extra_params (GstMsdkEnc * encoder, GstVideoCodecFrame * frame)
{
  /* Do nothing */
}

static void
gst_msdkenc_class_init (GstMsdkEncClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *element_class;
  GstVideoEncoderClass *gstencoder_class;

  gobject_class = G_OBJECT_CLASS (klass);
  element_class = GST_ELEMENT_CLASS (klass);
  gstencoder_class = GST_VIDEO_ENCODER_CLASS (klass);

  klass->need_conversion = gst_msdkenc_need_conversion;
  klass->need_reconfig = gst_msdkenc_need_reconfig;
  klass->set_extra_params = gst_msdkenc_set_extra_params;
  klass->qp_max = 51;
  klass->qp_min = 0;

  gobject_class->dispose = gst_msdkenc_dispose;

  element_class->set_context = gst_msdkenc_set_context;

  gstencoder_class->set_format = GST_DEBUG_FUNCPTR (gst_msdkenc_set_format);
  gstencoder_class->handle_frame = GST_DEBUG_FUNCPTR (gst_msdkenc_handle_frame);
  gstencoder_class->start = GST_DEBUG_FUNCPTR (gst_msdkenc_start);
  gstencoder_class->stop = GST_DEBUG_FUNCPTR (gst_msdkenc_stop);
  gstencoder_class->flush = GST_DEBUG_FUNCPTR (gst_msdkenc_flush);
  gstencoder_class->finish = GST_DEBUG_FUNCPTR (gst_msdkenc_finish);
  gstencoder_class->propose_allocation =
      GST_DEBUG_FUNCPTR (gst_msdkenc_propose_allocation);
  gstencoder_class->src_query = GST_DEBUG_FUNCPTR (gst_msdkenc_src_query);
  gstencoder_class->sink_query = GST_DEBUG_FUNCPTR (gst_msdkenc_sink_query);

  gst_element_class_add_static_pad_template (element_class, &sink_factory);
}

static void
gst_msdkenc_init (GstMsdkEnc * thiz)
{
  thiz->hardware = PROP_HARDWARE_DEFAULT;
  thiz->async_depth = PROP_ASYNC_DEPTH_DEFAULT;
  thiz->target_usage = PROP_TARGET_USAGE_DEFAULT;
  thiz->rate_control = PROP_RATE_CONTROL_DEFAULT;
  thiz->bitrate = PROP_BITRATE_DEFAULT;
  thiz->max_frame_size = PROP_MAX_FRAME_SIZE_DEFAULT;
  thiz->max_frame_size_i = PROP_MAX_FRAME_SIZE_I_DEFAULT;
  thiz->max_frame_size_p = PROP_MAX_FRAME_SIZE_P_DEFAULT;
  thiz->max_vbv_bitrate = PROP_MAX_VBV_BITRATE_DEFAULT;
  thiz->accuracy = PROP_AVBR_ACCURACY_DEFAULT;
  thiz->convergence = PROP_AVBR_ACCURACY_DEFAULT;
  thiz->lookahead_depth = PROP_RC_LOOKAHEAD_DEPTH_DEFAULT;
  thiz->qpi = PROP_QPI_DEFAULT;
  thiz->qpp = PROP_QPP_DEFAULT;
  thiz->qpb = PROP_QPB_DEFAULT;
  thiz->gop_size = PROP_GOP_SIZE_DEFAULT;
  thiz->ref_frames = PROP_REF_FRAMES_DEFAULT;
  thiz->i_frames = PROP_I_FRAMES_DEFAULT;
  thiz->b_frames = PROP_B_FRAMES_DEFAULT;
  thiz->num_slices = PROP_NUM_SLICES_DEFAULT;
  thiz->mbbrc = PROP_MBBRC_DEFAULT;
  thiz->lowdelay_brc = PROP_LOWDELAY_BRC_DEFAULT;
  thiz->adaptive_i = PROP_ADAPTIVE_I_DEFAULT;
  thiz->adaptive_b = PROP_ADAPTIVE_B_DEFAULT;

  thiz->ext_coding_props = gst_structure_new (EC_PROPS_STRUCT_NAME,
      EC_PROPS_EXTBRC, G_TYPE_STRING, "off", NULL);
}

/* gst_msdkenc_set_common_property:
 *
 * This is a helper function to set the common property
 * of base encoder from subclass implementation.
 */
gboolean
gst_msdkenc_set_common_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstMsdkEnc *thiz = GST_MSDKENC (object);
  GstState state;
  gboolean ret = TRUE;

  GST_OBJECT_LOCK (thiz);

  state = GST_STATE (thiz);
  if ((state != GST_STATE_READY && state != GST_STATE_NULL) &&
      !(pspec->flags & GST_PARAM_MUTABLE_PLAYING)) {
    ret = FALSE;
    goto wrong_state;
  }

  switch (prop_id) {
    case GST_MSDKENC_PROP_HARDWARE:
      thiz->hardware = g_value_get_boolean (value);
      break;
    case GST_MSDKENC_PROP_ASYNC_DEPTH:
      thiz->async_depth = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_TARGET_USAGE:
      thiz->target_usage = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_RATE_CONTROL:
      thiz->rate_control = g_value_get_enum (value);
      break;
    case GST_MSDKENC_PROP_BITRATE:
    {
      guint bitrate = g_value_get_uint (value);
      /* Ensure that bitrate changed before triggering a reconfig */
      if (bitrate != thiz->bitrate) {
        thiz->bitrate = bitrate;
        thiz->reconfig = TRUE;
        GST_DEBUG_OBJECT (thiz, "changed bitrate to %u", bitrate);
      }
      break;
    }
    case GST_MSDKENC_PROP_MAX_FRAME_SIZE:
      thiz->max_frame_size = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_MAX_FRAME_SIZE_I:
      thiz->max_frame_size_i = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_MAX_FRAME_SIZE_P:
      thiz->max_frame_size_p = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_MAX_VBV_BITRATE:
      thiz->max_vbv_bitrate = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_AVBR_ACCURACY:
      thiz->accuracy = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_AVBR_CONVERGENCE:
      thiz->convergence = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_RC_LOOKAHEAD_DEPTH:
      thiz->lookahead_depth = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_QPI:
      thiz->qpi = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_QPP:
      thiz->qpp = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_QPB:
      thiz->qpb = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_GOP_SIZE:
      thiz->gop_size = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_REF_FRAMES:
      thiz->ref_frames = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_I_FRAMES:
      thiz->i_frames = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_B_FRAMES:
      thiz->b_frames = g_value_get_int (value);
      break;
    case GST_MSDKENC_PROP_NUM_SLICES:
      thiz->num_slices = g_value_get_uint (value);
      break;
    case GST_MSDKENC_PROP_MBBRC:
      thiz->mbbrc = g_value_get_enum (value);
      break;
    case GST_MSDKENC_PROP_LOWDELAY_BRC:
      thiz->lowdelay_brc = g_value_get_enum (value);
      break;
    case GST_MSDKENC_PROP_ADAPTIVE_I:
      thiz->adaptive_i = g_value_get_enum (value);
      break;
    case GST_MSDKENC_PROP_ADAPTIVE_B:
      thiz->adaptive_b = g_value_get_enum (value);
      break;
    case GST_MSDKENC_PROP_EXT_CODING_PROPS:
    {
      const GstStructure *s = gst_value_get_structure (value);
      const gchar *name = gst_structure_get_name (s);
      gst_structure_set_name (thiz->ext_coding_props, name);
      if (!structure_transform (s, thiz->ext_coding_props)) {
        GST_ERROR_OBJECT (thiz, "failed to transform structure");
      }
      break;
    }
    default:
      ret = FALSE;
      break;
  }
  GST_OBJECT_UNLOCK (thiz);
  return ret;

  /* ERROR */
wrong_state:
  {
    GST_WARNING_OBJECT (thiz, "setting property in wrong state");
    GST_OBJECT_UNLOCK (thiz);
    return ret;
  }
}

/* gst_msdkenc_get_common_property:
 *
 * This is a helper function to get the common property
 * of base encoder from subclass implementation.
 */
gboolean
gst_msdkenc_get_common_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstMsdkEnc *thiz = GST_MSDKENC (object);
  gboolean ret = TRUE;

  GST_OBJECT_LOCK (thiz);
  switch (prop_id) {
    case GST_MSDKENC_PROP_HARDWARE:
      g_value_set_boolean (value, thiz->hardware);
      break;
    case GST_MSDKENC_PROP_ASYNC_DEPTH:
      g_value_set_uint (value, thiz->async_depth);
      break;
    case GST_MSDKENC_PROP_TARGET_USAGE:
      g_value_set_uint (value, thiz->target_usage);
      break;
    case GST_MSDKENC_PROP_RATE_CONTROL:
      g_value_set_enum (value, thiz->rate_control);
      break;
    case GST_MSDKENC_PROP_BITRATE:
      g_value_set_uint (value, thiz->bitrate);
      break;
    case GST_MSDKENC_PROP_MAX_FRAME_SIZE:
      g_value_set_uint (value, thiz->max_frame_size);
      break;
    case GST_MSDKENC_PROP_MAX_FRAME_SIZE_I:
      g_value_set_uint (value, thiz->max_frame_size_i);
      break;
    case GST_MSDKENC_PROP_MAX_FRAME_SIZE_P:
      g_value_set_uint (value, thiz->max_frame_size_p);
      break;
    case GST_MSDKENC_PROP_MAX_VBV_BITRATE:
      g_value_set_uint (value, thiz->max_vbv_bitrate);
      break;
    case GST_MSDKENC_PROP_AVBR_ACCURACY:
      g_value_set_uint (value, thiz->accuracy);
      break;
    case GST_MSDKENC_PROP_AVBR_CONVERGENCE:
      g_value_set_uint (value, thiz->convergence);
      break;
    case GST_MSDKENC_PROP_RC_LOOKAHEAD_DEPTH:
      g_value_set_uint (value, thiz->lookahead_depth);
      break;
    case GST_MSDKENC_PROP_QPI:
      g_value_set_uint (value, thiz->qpi);
      break;
    case GST_MSDKENC_PROP_QPP:
      g_value_set_uint (value, thiz->qpp);
      break;
    case GST_MSDKENC_PROP_QPB:
      g_value_set_uint (value, thiz->qpb);
      break;
    case GST_MSDKENC_PROP_GOP_SIZE:
      g_value_set_uint (value, thiz->gop_size);
      break;
    case GST_MSDKENC_PROP_REF_FRAMES:
      g_value_set_uint (value, thiz->ref_frames);
      break;
    case GST_MSDKENC_PROP_I_FRAMES:
      g_value_set_uint (value, thiz->i_frames);
      break;
    case GST_MSDKENC_PROP_B_FRAMES:
      g_value_set_int (value, thiz->b_frames);
      break;
    case GST_MSDKENC_PROP_NUM_SLICES:
      g_value_set_uint (value, thiz->num_slices);
      break;
    case GST_MSDKENC_PROP_MBBRC:
      g_value_set_enum (value, thiz->mbbrc);
      break;
    case GST_MSDKENC_PROP_LOWDELAY_BRC:
      g_value_set_enum (value, thiz->lowdelay_brc);
      break;
    case GST_MSDKENC_PROP_ADAPTIVE_I:
      g_value_set_enum (value, thiz->adaptive_i);
      break;
    case GST_MSDKENC_PROP_ADAPTIVE_B:
      g_value_set_enum (value, thiz->adaptive_b);
      break;
    case GST_MSDKENC_PROP_EXT_CODING_PROPS:
      gst_value_set_structure (value, thiz->ext_coding_props);
      break;
    default:
      ret = FALSE;
      break;
  }
  GST_OBJECT_UNLOCK (thiz);
  return ret;
}

/* gst_msdkenc_install_common_properties:
 * @thiz: a #GstMsdkEnc
 *
 * This is a helper function to install common properties
 * of base encoder from subclass implementation.
 * Encoders like jpeg do't require all the common properties
 * and they can avoid installing it into base gobject.
 */
void
gst_msdkenc_install_common_properties (GstMsdkEncClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GParamSpec *obj_properties[GST_MSDKENC_PROP_MAX] = { NULL, };
  guint qp_range_max = klass->qp_max;
  guint qp_range_min = klass->qp_min;

  obj_properties[GST_MSDKENC_PROP_HARDWARE] =
      g_param_spec_boolean ("hardware", "Hardware", "Enable hardware encoders",
      PROP_HARDWARE_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_ASYNC_DEPTH] =
      g_param_spec_uint ("async-depth", "Async Depth",
      "Depth of asynchronous pipeline",
      1, 20, PROP_ASYNC_DEPTH_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_TARGET_USAGE] =
      g_param_spec_uint ("target-usage", "Target Usage",
      "1: Best quality, 4: Balanced, 7: Best speed",
      1, 7, PROP_TARGET_USAGE_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_RATE_CONTROL] =
      g_param_spec_enum ("rate-control", "Rate Control",
      "Rate control method", gst_msdkenc_rate_control_get_type (),
      PROP_RATE_CONTROL_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_BITRATE] =
      g_param_spec_uint ("bitrate", "Bitrate", "Bitrate in kbit/sec", 1,
      2000 * 1024, PROP_BITRATE_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_PLAYING);

  obj_properties[GST_MSDKENC_PROP_MAX_FRAME_SIZE] =
      g_param_spec_uint ("max-frame-size", "Max Frame Size",
      "Maximum possible size (in kbyte) of any compressed frames (0: auto-calculate)",
      0, G_MAXUINT16, PROP_MAX_FRAME_SIZE_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_MAX_FRAME_SIZE_I] =
      g_param_spec_uint ("max-frame-size-i", "Max Frame Size for I frame",
      "Maximum possible size (in kbyte) of I frames (0: auto-calculate)",
      0, G_MAXUINT16, PROP_MAX_FRAME_SIZE_I_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_MAX_FRAME_SIZE_P] =
      g_param_spec_uint ("max-frame-size-p", "Max Frame Size for P frame",
      "Maximum possible size (in kbyte) of P frames (0: auto-calculate)",
      0, G_MAXUINT16, PROP_MAX_FRAME_SIZE_P_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /* Set the same upper bound with bitrate */
  obj_properties[GST_MSDKENC_PROP_MAX_VBV_BITRATE] =
      g_param_spec_uint ("max-vbv-bitrate", "Max VBV Bitrate",
      "Maximum bitrate(kbit/sec) at which data enters Video Buffering Verifier (0: auto-calculate)",
      0, 2000 * 1024, PROP_MAX_VBV_BITRATE_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_AVBR_ACCURACY] =
      g_param_spec_uint ("accuracy", "Accuracy", "The AVBR Accuracy in "
      "the unit of tenth of percent", 0, G_MAXUINT16,
      PROP_AVBR_ACCURACY_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_AVBR_CONVERGENCE] =
      g_param_spec_uint ("convergence", "Convergence",
      "The AVBR Convergence in the unit of 100 frames", 0, G_MAXUINT16,
      PROP_AVBR_CONVERGENCE_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_RC_LOOKAHEAD_DEPTH] =
      g_param_spec_uint ("rc-lookahead", "Look-ahead depth",
      "Number of frames to look ahead for Rate control", 10, 100,
      PROP_RC_LOOKAHEAD_DEPTH_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_QPI] =
      g_param_spec_uint ("qpi", "QPI",
      "Constant quantizer for I frames (0 unlimited). Also used as "
      "ICQQuality or QVBRQuality for different RateControl methods",
      qp_range_min, qp_range_max, PROP_QPI_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_QPP] =
      g_param_spec_uint ("qpp", "QPP",
      "Constant quantizer for P frames (0 unlimited)",
      qp_range_min, qp_range_max, PROP_QPP_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_QPB] =
      g_param_spec_uint ("qpb", "QPB",
      "Constant quantizer for B frames (0 unlimited)",
      qp_range_min, qp_range_max, PROP_QPB_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_GOP_SIZE] =
      g_param_spec_uint ("gop-size", "GOP Size", "GOP Size", 0,
      G_MAXINT, PROP_GOP_SIZE_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_REF_FRAMES] =
      g_param_spec_uint ("ref-frames", "Reference Frames",
      "Number of reference frames",
      0, G_MAXINT, PROP_REF_FRAMES_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_I_FRAMES] =
      g_param_spec_uint ("i-frames", "I Frames",
      "Number of I frames between IDR frames",
      0, G_MAXINT, PROP_I_FRAMES_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_B_FRAMES] =
      g_param_spec_int ("b-frames", "B Frames",
      "Number of B frames between I and P frames",
      -1, G_MAXINT, PROP_B_FRAMES_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_NUM_SLICES] =
      g_param_spec_uint ("num-slices", "Number of Slices",
      "Number of slices per frame, Zero tells the encoder to "
      "choose any slice partitioning allowed by the codec standard",
      0, G_MAXINT, PROP_NUM_SLICES_DEFAULT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_MBBRC] =
      g_param_spec_enum ("mbbrc", "MB level bitrate control",
      "Macroblock level bitrate control",
      gst_msdkenc_mbbrc_get_type (),
      PROP_MBBRC_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_LOWDELAY_BRC] =
      g_param_spec_enum ("lowdelay-brc", "Low delay bitrate control",
      "Bitrate control for low-delay user scenarios",
      gst_msdkenc_lowdelay_brc_get_type (),
      PROP_LOWDELAY_BRC_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_ADAPTIVE_I] =
      g_param_spec_enum ("i-adapt", "Adaptive I-Frame Insertion",
      "Adaptive I-Frame Insertion control",
      gst_msdkenc_adaptive_i_get_type (),
      PROP_ADAPTIVE_I_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  obj_properties[GST_MSDKENC_PROP_ADAPTIVE_B] =
      g_param_spec_enum ("b-adapt", "Adaptive B-Frame Insertion",
      "Adaptive B-Frame Insertion control",
      gst_msdkenc_adaptive_b_get_type (),
      PROP_ADAPTIVE_B_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * GstMsdkEnc:ext-coding-props
   *
   * The properties for the external coding.
   *
   * Supported properties:
   * ```
   * extbrc         : External bitrate control
   *                  String. Range: { auto, on, off } Default: off
   * ```
   *
   * Example:
   * ```
   * ext-coding-props="props,extbrc=on"
   * ```
   *
   * Since: 1.20
   *
   */
  obj_properties[GST_MSDKENC_PROP_EXT_CODING_PROPS] =
      g_param_spec_boxed ("ext-coding-props", "External coding properties",
      "The properties for the external coding, refer to the hotdoc for the "
      "supported properties",
      GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (gobject_class,
      GST_MSDKENC_PROP_MAX, obj_properties);
}