/*
 *  gstvaapiencoder_h265.c - H.265 encoder
 *
 *  Copyright (C) 2015 Intel Corporation
 *    Author: Sreerenj Balachandran <sreerenj.balachandran@intel.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301 USA
 */

#include "sysdeps.h"
#include <math.h>
#include <gst/base/gstbitwriter.h>
#include <gst/codecparsers/gsth265parser.h>
#include "gstvaapicompat.h"
#include "gstvaapiencoder_priv.h"
#include "gstvaapiencoder_h265.h"
#include "gstvaapiutils_h265.h"
#include "gstvaapiutils_h265_priv.h"
#include "gstvaapiutils_h26x_priv.h"
#include "gstvaapicodedbufferproxy_priv.h"
#include "gstvaapisurface.h"

#define DEBUG 1
#include "gstvaapidebug.h"

/* Supported set of VA rate controls, within this implementation */
#define SUPPORTED_RATECONTROLS                          \
  (GST_VAAPI_RATECONTROL_MASK (CQP) |                   \
   GST_VAAPI_RATECONTROL_MASK (CBR) |                   \
   GST_VAAPI_RATECONTROL_MASK (VBR))

/* Supported set of tuning options, within this implementation */
#define SUPPORTED_TUNE_OPTIONS                          \
  (GST_VAAPI_ENCODER_TUNE_MASK (NONE))

/* Supported set of VA packed headers, within this implementation */
#define SUPPORTED_PACKED_HEADERS                \
  (VA_ENC_PACKED_HEADER_SEQUENCE |              \
   VA_ENC_PACKED_HEADER_PICTURE  |              \
   VA_ENC_PACKED_HEADER_SLICE)

typedef struct
{
  GstVaapiSurfaceProxy *pic;
  guint poc;
} GstVaapiEncoderH265Ref;

typedef enum
{
  GST_VAAPI_ENC_H265_REORD_NONE = 0,
  GST_VAAPI_ENC_H265_REORD_DUMP_FRAMES = 1,
  GST_VAAPI_ENC_H265_REORD_WAIT_FRAMES = 2
} GstVaapiEncH265ReorderState;

typedef struct _GstVaapiH265RefPool
{
  GQueue ref_list;
  guint max_ref_frames;
  guint max_reflist0_count;
  guint max_reflist1_count;
} GstVaapiH265RefPool;

typedef struct _GstVaapiH265ReorderPool
{
  GQueue reorder_frame_list;
  guint reorder_state;
  guint frame_index;
  guint cur_present_index;
} GstVaapiH265ReorderPool;

/* ------------------------------------------------------------------------- */
/* --- H.265 Encoder                                                     --- */
/* ------------------------------------------------------------------------- */

struct _GstVaapiEncoderH265
{
  GstVaapiEncoder parent_instance;

  GstVaapiProfile profile;
  GstVaapiTierH265 tier;
  GstVaapiLevelH265 level;
  guint8 profile_idc;
  guint8 max_profile_idc;
  guint8 hw_max_profile_idc;
  guint8 level_idc;
  guint32 idr_period;
  guint32 init_qp;
  guint32 min_qp;
  guint32 qp_i;
  guint32 qp_ip;
  guint32 qp_ib;
  guint32 num_slices;
  guint32 num_bframes;
  guint32 ctu_width;            /* CTU == Coding Tree Unit */
  guint32 ctu_height;
  guint32 luma_width;
  guint32 luma_height;
  GstClockTime cts_offset;
  gboolean config_changed;

  /* maximum required size of the decoded picture buffer */
  guint32 max_dec_pic_buffering;
  /* maximum allowed number of pictures that can precede any picture in
   * the CVS in decoding order and follow that picture in output order */
  guint32 max_num_reorder_pics;

  /* frame, poc */
  guint32 max_pic_order_cnt;
  guint32 log2_max_pic_order_cnt;
  guint32 idr_num;
  guint num_ref_frames;

  GstBuffer *vps_data;
  GstBuffer *sps_data;
  GstBuffer *pps_data;

  guint bitrate_bits;           // bitrate (bits)
  guint cpb_length;             // length of CPB buffer (ms)
  guint cpb_length_bits;        // length of CPB buffer (bits)
  GstVaapiEncoderMbbrc mbbrc;   // macroblock bitrate control

  /* Crop rectangle */
  guint conformance_window_flag:1;
  guint32 conf_win_left_offset;
  guint32 conf_win_right_offset;
  guint32 conf_win_top_offset;
  guint32 conf_win_bottom_offset;

  GstVaapiH265RefPool ref_pool;
  GstVaapiH265ReorderPool reorder_pool;
  guint first_slice_segment_in_pic_flag:1;
  guint sps_temporal_mvp_enabled_flag:1;
  guint sample_adaptive_offset_enabled_flag:1;
};

static inline gboolean
_poc_greater_than (guint poc1, guint poc2, guint max_poc)
{
  return (((poc1 - poc2) & (max_poc - 1)) < max_poc / 2);
}

/* Get slice_type value for H.265 specification */
static guint8
h265_get_slice_type (GstVaapiPictureType type)
{
  switch (type) {
    case GST_VAAPI_PICTURE_TYPE_I:
      return GST_H265_I_SLICE;
    case GST_VAAPI_PICTURE_TYPE_P:
      return GST_H265_P_SLICE;
    case GST_VAAPI_PICTURE_TYPE_B:
      return GST_H265_B_SLICE;
    default:
      break;
  }
  return -1;
}

/* Get log2_max_pic_order_cnt value for H.265 specification */
static guint
h265_get_log2_max_pic_order_cnt (guint num)
{
  guint ret = 0;

  while (num) {
    ++ret;
    num >>= 1;
  }
  if (ret <= 4)
    ret = 4;
  else if (ret > 10)
    ret = 10;
  /* must be greater than 4 */
  return ret;
}

/* Write the NAL unit header */
static gboolean
bs_write_nal_header (GstBitWriter * bs, guint32 nal_unit_type)
{
  guint8 nuh_layer_id = 0;
  guint8 nuh_temporal_id_plus1 = 1;

  WRITE_UINT32 (bs, 0, 1);
  WRITE_UINT32 (bs, nal_unit_type, 6);
  WRITE_UINT32 (bs, nuh_layer_id, 6);
  WRITE_UINT32 (bs, nuh_temporal_id_plus1, 3);

  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write NAL unit header");
    return FALSE;
  }
}

/* Write the NAL unit trailing bits */
static gboolean
bs_write_trailing_bits (GstBitWriter * bs)
{
  if (!gst_bit_writer_put_bits_uint32 (bs, 1, 1))
    goto bs_error;
  gst_bit_writer_align_bytes_unchecked (bs, 0);
  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write NAL unit trailing bits");
    return FALSE;
  }
}

/* Write profile_tier_level()  */
static gboolean
bs_write_profile_tier_level (GstBitWriter * bs,
    const VAEncSequenceParameterBufferHEVC * seq_param)
{
  guint i;
  /* general_profile_space */
  WRITE_UINT32 (bs, 0, 2);
  /* general_tier_flag */
  WRITE_UINT32 (bs, seq_param->general_tier_flag, 1);
  /* general_profile_idc */
  WRITE_UINT32 (bs, seq_param->general_profile_idc, 5);
  /* general_profile_compatibility_flag[32] */
  for (i = 0; i < 32; i++) {
    if (i == 1 || i == 2)
      WRITE_UINT32 (bs, 1, 1);
    else
      WRITE_UINT32 (bs, 0, 1);
  }
  /* general_progressive_source_flag */
  WRITE_UINT32 (bs, 1, 1);
  /* general_interlaced_source_flag */
  WRITE_UINT32 (bs, 0, 1);
  /* general_non_packed_constraint_flag */
  WRITE_UINT32 (bs, 0, 1);
  /* general_frame_only_constraint_flag */
  WRITE_UINT32 (bs, 1, 1);
  /* general_reserved_zero_44bits */
  for (i = 0; i < 44; i++)
    WRITE_UINT32 (bs, 0, 1);
  /* general_level_idc */
  WRITE_UINT32 (bs, seq_param->general_level_idc, 8);

  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write Profile Tier Level");
    return FALSE;
  }
}

/* Write an VPS NAL unit */
static gboolean
bs_write_vps_data (GstBitWriter * bs, GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture,
    const VAEncSequenceParameterBufferHEVC * seq_param, GstVaapiProfile profile)
{
  guint32 video_parameter_set_id = 0;
  guint32 vps_max_layers_minus1 = 0;
  guint32 vps_max_sub_layers_minus1 = 0;
  guint32 vps_temporal_id_nesting_flag = 1;
  guint32 vps_sub_layer_ordering_info_present_flag = 0;
  guint32 vps_max_latency_increase_plus1 = 0;
  guint32 vps_max_layer_id = 0;
  guint32 vps_num_layer_sets_minus1 = 0;
  guint32 vps_timing_info_present_flag = 0;
  guint32 vps_extension_flag = 0;

  /* video_parameter_set_id */
  WRITE_UINT32 (bs, video_parameter_set_id, 4);
  /* vps_reserved_three_2bits */
  WRITE_UINT32 (bs, 3, 2);
  /* vps_max_layers_minus1 */
  WRITE_UINT32 (bs, vps_max_layers_minus1, 6);
  /* vps_max_sub_layers_minus1 */
  WRITE_UINT32 (bs, vps_max_sub_layers_minus1, 3);
  /* vps_temporal_id_nesting_flag */
  WRITE_UINT32 (bs, vps_temporal_id_nesting_flag, 1);
  /* vps_reserved_0xffff_16bits */
  WRITE_UINT32 (bs, 0xffff, 16);

  /* profile_tier_level */
  bs_write_profile_tier_level (bs, seq_param);

  /* vps_sub_layer_ordering_info_present_flag */
  WRITE_UINT32 (bs, vps_sub_layer_ordering_info_present_flag, 1);
  /* vps_max_dec_pic_buffering_minus1 */
  WRITE_UE (bs, encoder->max_dec_pic_buffering - 1);
  /* vps_max_num_reorder_pics */
  WRITE_UE (bs, encoder->max_num_reorder_pics);
  /* vps_max_latency_increase_plus1 */
  WRITE_UE (bs, vps_max_latency_increase_plus1);
  /* vps_max_layer_id */
  WRITE_UINT32 (bs, vps_max_layer_id, 6);
  /* vps_num_layer_sets_minus1 */
  WRITE_UE (bs, vps_num_layer_sets_minus1);
  /* vps_timing_info_present_flag */
  WRITE_UINT32 (bs, vps_timing_info_present_flag, 1);
  /* vps_extension_flag */
  WRITE_UINT32 (bs, vps_extension_flag, 1);

  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write VPS NAL unit");
    return FALSE;
  }
}

static gboolean
bs_write_vps (GstBitWriter * bs, GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture,
    const VAEncSequenceParameterBufferHEVC * seq_param, GstVaapiProfile profile)
{
  if (!bs_write_vps_data (bs, encoder, picture, seq_param, profile))
    return FALSE;

  /* rbsp_trailing_bits */
  bs_write_trailing_bits (bs);

  return FALSE;
}

/* Write an SPS NAL unit */
static gboolean
bs_write_sps_data (GstBitWriter * bs, GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture,
    const VAEncSequenceParameterBufferHEVC * seq_param, GstVaapiProfile profile,
    const VAEncMiscParameterHRD * hrd_params)
{
  guint32 video_parameter_set_id = 0;
  guint32 max_sub_layers_minus1 = 0;
  guint32 temporal_id_nesting_flag = 1;
  guint32 seq_parameter_set_id = 0;
  guint32 sps_sub_layer_ordering_info_present_flag = 0;
  guint32 sps_max_latency_increase_plus1 = 0;
  guint32 num_short_term_ref_pic_sets = 0;
  guint32 long_term_ref_pics_present_flag = 0;
  guint32 sps_extension_flag = 0;
  guint32 nal_hrd_parameters_present_flag = 0;
  guint maxNumSubLayers = 1, i;

  /* video_parameter_set_id */
  WRITE_UINT32 (bs, video_parameter_set_id, 4);
  /* max_sub_layers_minus1 */
  WRITE_UINT32 (bs, max_sub_layers_minus1, 3);
  /* temporal_id_nesting_flag */
  WRITE_UINT32 (bs, temporal_id_nesting_flag, 1);

  /* profile_tier_level */
  bs_write_profile_tier_level (bs, seq_param);

  /* seq_parameter_set_id */
  WRITE_UE (bs, seq_parameter_set_id);
  /* chroma_format_idc  = 1, 4:2:0 */
  WRITE_UE (bs, seq_param->seq_fields.bits.chroma_format_idc);
  /* pic_width_in_luma_samples */
  WRITE_UE (bs, seq_param->pic_width_in_luma_samples);
  /* pic_height_in_luma_samples */
  WRITE_UE (bs, seq_param->pic_height_in_luma_samples);

  /* conformance_window_flag */
  WRITE_UINT32 (bs, encoder->conformance_window_flag, 1);
  if (encoder->conformance_window_flag) {
    WRITE_UE (bs, encoder->conf_win_left_offset);
    WRITE_UE (bs, encoder->conf_win_right_offset);
    WRITE_UE (bs, encoder->conf_win_top_offset);
    WRITE_UE (bs, encoder->conf_win_bottom_offset);
  }

  /* bit_depth_luma_minus8 */
  WRITE_UE (bs, seq_param->seq_fields.bits.bit_depth_luma_minus8);
  /* bit_depth_chroma_minus8 */
  WRITE_UE (bs, seq_param->seq_fields.bits.bit_depth_chroma_minus8);
  /* log2_max_pic_order_cnt_lsb_minus4  */
  WRITE_UE (bs, encoder->log2_max_pic_order_cnt - 4);

  /* sps_sub_layer_ordering_info_present_flag */
  WRITE_UINT32 (bs, sps_sub_layer_ordering_info_present_flag, 1);
  /* sps_max_dec_pic_buffering_minus1 */
  WRITE_UE (bs, encoder->max_dec_pic_buffering - 1);
  /* sps_max_num_reorder_pics */
  WRITE_UE (bs, encoder->max_num_reorder_pics);
  /* sps_max_latency_increase_plus1 */
  WRITE_UE (bs, sps_max_latency_increase_plus1);

  /* log2_min_luma_coding_block_size_minus3 */
  WRITE_UE (bs, seq_param->log2_min_luma_coding_block_size_minus3);
  /* log2_diff_max_min_luma_coding_block_size */
  WRITE_UE (bs, seq_param->log2_diff_max_min_luma_coding_block_size);
  /* log2_min_transform_block_size_minus2 */
  WRITE_UE (bs, seq_param->log2_min_transform_block_size_minus2);
  /* log2_diff_max_min_transform_block_size */
  WRITE_UE (bs, seq_param->log2_diff_max_min_transform_block_size);
  /* max_transform_hierarchy_depth_inter */
  WRITE_UE (bs, seq_param->max_transform_hierarchy_depth_inter);
  /*max_transform_hierarchy_depth_intra */
  WRITE_UE (bs, seq_param->max_transform_hierarchy_depth_intra);

  /* scaling_list_enabled_flag */
  WRITE_UINT32 (bs, seq_param->seq_fields.bits.scaling_list_enabled_flag, 1);
  /* amp_enabled_flag */
  WRITE_UINT32 (bs, seq_param->seq_fields.bits.amp_enabled_flag, 1);
  /* sample_adaptive_offset_enabled_flag */
  WRITE_UINT32 (bs,
      seq_param->seq_fields.bits.sample_adaptive_offset_enabled_flag, 1);
  /* pcm_enabled_flag */
  WRITE_UINT32 (bs, seq_param->seq_fields.bits.pcm_enabled_flag, 1);

  /* num_short_term_ref_pic_sets  */
  WRITE_UE (bs, num_short_term_ref_pic_sets);

  /* long_term_ref_pics_present_flag */
  WRITE_UINT32 (bs, long_term_ref_pics_present_flag, 1);

  /* sps_temporal_mvp_enabled_flag */
  WRITE_UINT32 (bs, seq_param->seq_fields.bits.sps_temporal_mvp_enabled_flag,
      1);
  /* strong_intra_smoothing_enabled_flag */
  WRITE_UINT32 (bs,
      seq_param->seq_fields.bits.strong_intra_smoothing_enabled_flag, 1);

  /* vui_parameters_present_flag */
  WRITE_UINT32 (bs, seq_param->vui_parameters_present_flag, 1);

  /*--------------- Write VUI Parameters--------------- */
  if (seq_param->vui_parameters_present_flag) {
    gboolean vui_hrd_parameters_present_flag;
    /* aspect_ratio_info_present_flag */
    WRITE_UINT32 (bs,
        seq_param->vui_fields.bits.aspect_ratio_info_present_flag, 1);
    if (seq_param->vui_fields.bits.aspect_ratio_info_present_flag) {
      WRITE_UINT32 (bs, seq_param->aspect_ratio_idc, 8);
      if (seq_param->aspect_ratio_idc == 0xFF) {
        WRITE_UINT32 (bs, seq_param->sar_width, 16);
        WRITE_UINT32 (bs, seq_param->sar_height, 16);
      }
    }
    /* overscan_info_present_flag */
    WRITE_UINT32 (bs, 0, 1);
    /* video_signal_type_present_flag */
    WRITE_UINT32 (bs, 0, 1);
    /* chroma_loc_info_present_flag */
    WRITE_UINT32 (bs, 0, 1);
    /* neutral_chroma_indication_flag */
    WRITE_UINT32 (bs, seq_param->vui_fields.bits.neutral_chroma_indication_flag,
        1);
    /* field_seq_flag */
    WRITE_UINT32 (bs, seq_param->vui_fields.bits.field_seq_flag, 1);
    /* frame_field_info_present_flag */
    WRITE_UINT32 (bs, 0, 1);
    /* default_display_window_flag */
    WRITE_UINT32 (bs, 0, 1);

    /* timing_info_present_flag */
    WRITE_UINT32 (bs, seq_param->vui_fields.bits.vui_timing_info_present_flag,
        1);
    if (seq_param->vui_fields.bits.vui_timing_info_present_flag) {
      /* vui_num_units_in_tick */
      WRITE_UINT32 (bs, seq_param->vui_num_units_in_tick, 32);
      /* vui_time_scale */
      WRITE_UINT32 (bs, seq_param->vui_time_scale, 32);
      /* vui_poc_proportional_to_timing_flag */
      WRITE_UINT32 (bs, 0, 1);

      /* vui_hrd_parameters_present_flag */
      vui_hrd_parameters_present_flag = seq_param->bits_per_second > 0;
      WRITE_UINT32 (bs, vui_hrd_parameters_present_flag, 1);

      if (vui_hrd_parameters_present_flag) {
        nal_hrd_parameters_present_flag = 1;
        /* nal_hrd_parameters_present_flag */
        WRITE_UINT32 (bs, nal_hrd_parameters_present_flag, 1);
        /* vcl_hrd_parameters_present_flag */
        WRITE_UINT32 (bs, 0, 1);

        if (nal_hrd_parameters_present_flag) {
          /* sub_pic_hrd_params_present_flag */
          WRITE_UINT32 (bs, 0, 1);
          /* bit_rate_scale */
          WRITE_UINT32 (bs, SX_BITRATE - 6, 4);
          /* cpb_size_scale */
          WRITE_UINT32 (bs, SX_CPB_SIZE - 4, 4);
          /* initial_cpb_removal_delay_length_minus1 */
          WRITE_UINT32 (bs, 23, 5);
          /* au_cpb_removal_delay_length_minus1 */
          WRITE_UINT32 (bs, 23, 5);
          /* dpb_output_delay_length_minus1 */
          WRITE_UINT32 (bs, 23, 5);

          for (i = 0; i < maxNumSubLayers; i++) {
            /* fixed_pic_rate_general_flag */
            WRITE_UINT32 (bs, 0, 1);
            /* fixed_pic_rate_within_cvs_flag */
            WRITE_UINT32 (bs, 0, 1);
            /* low_delay_hrd_flag */
            WRITE_UINT32 (bs, 1, 1);
            /* bit_rate_value_minus1 */
            WRITE_UE (bs, (seq_param->bits_per_second >> SX_BITRATE) - 1);
            /* cpb_size_value_minus1 */
            WRITE_UE (bs, (hrd_params->buffer_size >> SX_CPB_SIZE) - 1);
            /* cbr_flag */
            WRITE_UINT32 (bs, 1, 1);
          }
        }
      }
    }
    /* bitstream_restriction_flag */
    WRITE_UINT32 (bs, seq_param->vui_fields.bits.bitstream_restriction_flag, 1);
  }
  /* sps_extension_flag */
  WRITE_UINT32 (bs, sps_extension_flag, 1);

  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write SPS NAL unit");
    return FALSE;
  }
}

static gboolean
bs_write_sps (GstBitWriter * bs, GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture,
    const VAEncSequenceParameterBufferHEVC * seq_param, GstVaapiProfile profile,
    const VAEncMiscParameterHRD * hrd_params)
{
  if (!bs_write_sps_data (bs, encoder, picture, seq_param, profile, hrd_params))
    return FALSE;

  /* rbsp_trailing_bits */
  bs_write_trailing_bits (bs);

  return FALSE;
}

/* Write a PPS NAL unit */
static gboolean
bs_write_pps (GstBitWriter * bs,
    const VAEncPictureParameterBufferHEVC * pic_param)
{
  guint32 pic_parameter_set_id = 0;
  guint32 seq_parameter_set_id = 0;
  guint32 output_flag_present_flag = 0;
  guint32 num_extra_slice_header_bits = 0;
  guint32 cabac_init_present_flag = 0;
  guint32 pps_slice_chroma_qp_offsets_present_flag = 0;
  guint32 deblocking_filter_control_present_flag = 0;
  guint32 lists_modification_present_flag = 0;
  guint32 slice_segment_header_extension_present_flag = 0;
  guint32 pps_extension_flag = 0;

  /* pic_parameter_set_id */
  WRITE_UE (bs, pic_parameter_set_id);
  /* seq_parameter_set_id */
  WRITE_UE (bs, seq_parameter_set_id);
  /* dependent_slice_segments_enabled_flag */
  WRITE_UINT32 (bs,
      pic_param->pic_fields.bits.dependent_slice_segments_enabled_flag, 1);
  /* output_flag_present_flag */
  WRITE_UINT32 (bs, output_flag_present_flag, 1);
  /* num_extra_slice_header_bits */
  WRITE_UINT32 (bs, num_extra_slice_header_bits, 3);
  /* sign_data_hiding_enabled_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.sign_data_hiding_enabled_flag,
      1);
  /* cabac_init_present_flag */
  WRITE_UINT32 (bs, cabac_init_present_flag, 1);
  /* num_ref_idx_l0_default_active_minus1 */
  WRITE_UE (bs, pic_param->num_ref_idx_l0_default_active_minus1);
  /* num_ref_idx_l1_default_active_minus1 */
  WRITE_UE (bs, pic_param->num_ref_idx_l1_default_active_minus1);
  /* pic_init_qp_minus26 */
  WRITE_SE (bs, pic_param->pic_init_qp - 26);
  /* constrained_intra_pred_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.constrained_intra_pred_flag, 1);
  /* transform_skip_enabled_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.transform_skip_enabled_flag, 1);
  /* cu_qp_delta_enabled_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.cu_qp_delta_enabled_flag, 1);
  /* diff_cu_qp_delta_depth */
  if (pic_param->pic_fields.bits.cu_qp_delta_enabled_flag)
    WRITE_UE (bs, pic_param->diff_cu_qp_delta_depth);

  /* pps_cb_qp_offset */
  WRITE_SE (bs, pic_param->pps_cb_qp_offset);
  /* pps_cr_qp_offset */
  WRITE_SE (bs, pic_param->pps_cr_qp_offset);
  /* pps_slice_chroma_qp_offsets_present_flag */
  WRITE_UINT32 (bs, pps_slice_chroma_qp_offsets_present_flag, 1);
  /* weighted_pred_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.weighted_pred_flag, 1);
  /* weighted_bipred_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.weighted_bipred_flag, 1);
  /* transquant_bypass_enabled_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.transquant_bypass_enabled_flag,
      1);
  /* tiles_enabled_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.tiles_enabled_flag, 1);
  /* entropy_coding_sync_enabled_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.entropy_coding_sync_enabled_flag,
      1);
  /* pps_loop_filter_across_slices_enabled_flag */
  WRITE_UINT32 (bs,
      pic_param->pic_fields.bits.pps_loop_filter_across_slices_enabled_flag, 1);
  /* deblocking_filter_control_present_flag */
  WRITE_UINT32 (bs, deblocking_filter_control_present_flag, 1);
  /* pps_scaling_list_data_present_flag */
  WRITE_UINT32 (bs, pic_param->pic_fields.bits.scaling_list_data_present_flag,
      1);
  /* lists_modification_present_flag */
  WRITE_UINT32 (bs, lists_modification_present_flag, 1);
  /* log2_parallel_merge_level_minus2 */
  WRITE_UE (bs, pic_param->log2_parallel_merge_level_minus2);
  /* slice_segment_header_extension_present_flag */
  WRITE_UINT32 (bs, slice_segment_header_extension_present_flag, 1);
  /* pps_extension_flag */
  WRITE_UINT32 (bs, pps_extension_flag, 1);

  /* rbsp_trailing_bits */
  bs_write_trailing_bits (bs);

  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write PPS NAL unit");
    return FALSE;
  }
}

/* Write a Slice NAL unit */
static gboolean
bs_write_slice (GstBitWriter * bs,
    const VAEncSliceParameterBufferHEVC * slice_param,
    GstVaapiEncoderH265 * encoder, GstVaapiEncPicture * picture,
    guint8 nal_unit_type)
{
  const VAEncPictureParameterBufferHEVC *const pic_param = picture->param;

  guint8 no_output_of_prior_pics_flag = 0;
  guint8 dependent_slice_segment_flag = 0;
  guint8 short_term_ref_pic_set_sps_flag = 0;
  guint8 slice_deblocking_filter_disabled_flag = 0;
  guint8 num_ref_idx_active_override_flag =
      slice_param->slice_fields.bits.num_ref_idx_active_override_flag;

  /* first_slice_segment_in_pic_flag */
  WRITE_UINT32 (bs, encoder->first_slice_segment_in_pic_flag, 1);

  /* FIXME: For all IRAP pics */
  /* no_output_of_prior_pics_flag */
  if (GST_VAAPI_ENC_PICTURE_IS_IDR (picture))
    WRITE_UINT32 (bs, no_output_of_prior_pics_flag, 1);

  /* slice_pic_parameter_set_id */
  WRITE_UE (bs, slice_param->slice_pic_parameter_set_id);

  /* slice_segment_address , bits_size = Ceil(Log2(PicSizeInCtbsY)) */
  if (!encoder->first_slice_segment_in_pic_flag) {
    guint pic_size_ctb = encoder->ctu_width * encoder->ctu_height;
    guint bits_size = (guint) ceil ((log2 (pic_size_ctb)));
    WRITE_UINT32 (bs, slice_param->slice_segment_address, bits_size);
  }

  if (!dependent_slice_segment_flag) {
    /* slice_type */
    WRITE_UE (bs, slice_param->slice_type);

    if (!pic_param->pic_fields.bits.idr_pic_flag) {
      /* slice_pic_order_cnt_lsb */
      WRITE_UINT32 (bs, picture->poc, encoder->log2_max_pic_order_cnt);
      /* short_term_ref_pic_set_sps_flag */
      WRITE_UINT32 (bs, short_term_ref_pic_set_sps_flag, 1);

    /*---------- Write short_term_ref_pic_set(0) ----------- */
      {
        guint num_positive_pics = 0, num_negative_pics = 0;
        guint delta_poc_s0_minus1 = 0, delta_poc_s1_minus1 = 0;
        guint used_by_curr_pic_s0_flag = 0, used_by_curr_pic_s1_flag = 0;
        guint reflist_0_count = 0, reflist_1_count = 0;
        gint i;

        /* Get count of ref_pic_list */
        if (picture->type == GST_VAAPI_PICTURE_TYPE_P
            || picture->type == GST_VAAPI_PICTURE_TYPE_B) {
          for (i = 0; i < G_N_ELEMENTS (slice_param->ref_pic_list0); ++i) {
            if (slice_param->ref_pic_list0[i].picture_id == VA_INVALID_SURFACE)
              break;
          }
          reflist_0_count = i;

          if (picture->type == GST_VAAPI_PICTURE_TYPE_B) {
            for (i = 0; i < G_N_ELEMENTS (slice_param->ref_pic_list1); ++i) {
              if (slice_param->ref_pic_list1[i].picture_id ==
                  VA_INVALID_SURFACE)
                break;
            }
            reflist_1_count = i;
          }
        }

        if (picture->type == GST_VAAPI_PICTURE_TYPE_P) {
          delta_poc_s0_minus1 =
              picture->poc - slice_param->ref_pic_list0[0].pic_order_cnt - 1;
          used_by_curr_pic_s0_flag = 1;
          delta_poc_s1_minus1 = 0;
          used_by_curr_pic_s1_flag = 0;
        }
        if (picture->type == GST_VAAPI_PICTURE_TYPE_B) {
          delta_poc_s0_minus1 =
              picture->poc - slice_param->ref_pic_list0[0].pic_order_cnt - 1;
          used_by_curr_pic_s0_flag = 1;
          delta_poc_s1_minus1 =
              slice_param->ref_pic_list1[0].pic_order_cnt - picture->poc - 1;
          used_by_curr_pic_s1_flag = 1;
        }

        num_negative_pics = reflist_0_count;
        num_positive_pics = reflist_1_count;

        /* num_negative_pics */
        WRITE_UE (bs, num_negative_pics);
        /* num_positive_pics */
        WRITE_UE (bs, num_positive_pics);

        for (i = 0; i < num_negative_pics; i++) {
          /* delta_poc_s0_minus1 */
          if (i == 0) {
            WRITE_UE (bs, delta_poc_s0_minus1);
          } else {
            WRITE_UE (bs,
                slice_param->ref_pic_list0[i - 1].pic_order_cnt -
                slice_param->ref_pic_list0[i].pic_order_cnt - 1);
          }
          /* used_by_curr_pic_s0_flag */
          WRITE_UINT32 (bs, used_by_curr_pic_s0_flag, 1);
        }
        for (i = 0; i < num_positive_pics; i++) {
          /* delta_poc_s1_minus1 */
          if (i == 0) {
            WRITE_UE (bs, delta_poc_s1_minus1);
          } else {
            WRITE_UE (bs,
                slice_param->ref_pic_list1[i - 1].pic_order_cnt -
                slice_param->ref_pic_list1[i].pic_order_cnt - 1);
          }
          /* used_by_curr_pic_s1_flag */
          WRITE_UINT32 (bs, used_by_curr_pic_s1_flag, 1);
        }
      }

      /* slice_temporal_mvp_enabled_flag */
      if (encoder->sps_temporal_mvp_enabled_flag)
        WRITE_UINT32 (bs,
            slice_param->slice_fields.bits.slice_temporal_mvp_enabled_flag, 1);
    }

    if (encoder->sample_adaptive_offset_enabled_flag) {
      WRITE_UINT32 (bs, slice_param->slice_fields.bits.slice_sao_luma_flag, 1);
      WRITE_UINT32 (bs, slice_param->slice_fields.bits.slice_sao_chroma_flag,
          1);
    }

    if (slice_param->slice_type == GST_H265_P_SLICE ||
        slice_param->slice_type == GST_H265_B_SLICE) {
      /* num_ref_idx_active_override_flag */
      WRITE_UINT32 (bs, num_ref_idx_active_override_flag, 1);
      if (num_ref_idx_active_override_flag) {
        WRITE_UE (bs, slice_param->num_ref_idx_l0_active_minus1);
        if (slice_param->slice_type == GST_H265_B_SLICE)
          WRITE_UE (bs, slice_param->num_ref_idx_l1_active_minus1);
      }

      /* mvd_l1_zero_flag */
      if (slice_param->slice_type == GST_H265_B_SLICE)
        WRITE_UINT32 (bs, slice_param->slice_fields.bits.mvd_l1_zero_flag, 1);

      /* cabac_init_present_flag == FALSE */
      /* cabac_init_flag  = FALSE */

      /* collocated_from_l0_flag */
      if (slice_param->slice_fields.bits.slice_temporal_mvp_enabled_flag) {
        if (slice_param->slice_type == GST_H265_B_SLICE)
          WRITE_UINT32 (bs,
              slice_param->slice_fields.bits.collocated_from_l0_flag, 1);
      }
      /* five_minus_max_num_merge_cand */
      WRITE_UE (bs, 5 - slice_param->max_num_merge_cand);
    }

    /* slice_qp_delta */
    WRITE_SE (bs, slice_param->slice_qp_delta);
    if (pic_param->pic_fields.bits.pps_loop_filter_across_slices_enabled_flag &&
        (slice_param->slice_fields.bits.slice_sao_luma_flag
            || slice_param->slice_fields.bits.slice_sao_chroma_flag
            || !slice_deblocking_filter_disabled_flag))
      WRITE_UINT32 (bs,
          slice_param->slice_fields.bits.
          slice_loop_filter_across_slices_enabled_flag, 1);

  }

  /* byte_alignment() */
  {
    /* alignment_bit_equal_to_one */
    WRITE_UINT32 (bs, 1, 1);
    while (GST_BIT_WRITER_BIT_SIZE (bs) % 8 != 0) {
      /* alignment_bit_equal_to_zero */
      WRITE_UINT32 (bs, 0, 1);
    }
  }

  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write Slice NAL unit");
    return FALSE;
  }
}

static inline void
_check_vps_sps_pps_status (GstVaapiEncoderH265 * encoder,
    const guint8 * nal, guint32 size)
{
  guint8 nal_type;
  G_GNUC_UNUSED gsize ret;      /* FIXME */
  g_assert (size);

  if (encoder->vps_data && encoder->sps_data && encoder->pps_data)
    return;

  nal_type = (nal[0] & 0x7E) >> 1;
  switch (nal_type) {
    case GST_H265_NAL_VPS:
      encoder->vps_data = gst_buffer_new_allocate (NULL, size, NULL);
      ret = gst_buffer_fill (encoder->vps_data, 0, nal, size);
      g_assert (ret == size);
      break;
    case GST_H265_NAL_SPS:
      encoder->sps_data = gst_buffer_new_allocate (NULL, size, NULL);
      ret = gst_buffer_fill (encoder->sps_data, 0, nal, size);
      g_assert (ret == size);
      break;
    case GST_H265_NAL_PPS:
      encoder->pps_data = gst_buffer_new_allocate (NULL, size, NULL);
      ret = gst_buffer_fill (encoder->pps_data, 0, nal, size);
      g_assert (ret == size);
      break;
    default:
      break;
  }
}

/* Determines the largest supported profile by the underlying hardware */
static gboolean
ensure_hw_profile_limits (GstVaapiEncoderH265 * encoder)
{
  GstVaapiDisplay *const display = GST_VAAPI_ENCODER_DISPLAY (encoder);
  GArray *profiles;
  guint i, profile_idc, max_profile_idc;

  if (encoder->hw_max_profile_idc)
    return TRUE;

  profiles = gst_vaapi_display_get_encode_profiles (display);
  if (!profiles)
    return FALSE;

  max_profile_idc = 0;
  for (i = 0; i < profiles->len; i++) {
    const GstVaapiProfile profile =
        g_array_index (profiles, GstVaapiProfile, i);
    profile_idc = gst_vaapi_utils_h265_get_profile_idc (profile);
    if (!profile_idc)
      continue;
    if (max_profile_idc < profile_idc)
      max_profile_idc = profile_idc;
  }
  g_array_unref (profiles);

  encoder->hw_max_profile_idc = max_profile_idc;
  return TRUE;
}

/* Derives the profile supported by the underlying hardware */
static gboolean
ensure_hw_profile (GstVaapiEncoderH265 * encoder)
{
  GstVaapiDisplay *const display = GST_VAAPI_ENCODER_DISPLAY (encoder);
  GstVaapiEntrypoint entrypoint = GST_VAAPI_ENTRYPOINT_SLICE_ENCODE;
  GstVaapiProfile profile, profiles[4];
  guint i, num_profiles = 0;

  profiles[num_profiles++] = encoder->profile;
  switch (encoder->profile) {
    case GST_VAAPI_PROFILE_H265_MAIN_STILL_PICTURE:
      profiles[num_profiles++] = GST_VAAPI_PROFILE_H265_MAIN;
      // fall-through
    case GST_VAAPI_PROFILE_H265_MAIN:
      profiles[num_profiles++] = GST_VAAPI_PROFILE_H265_MAIN10;
      break;
    default:
      break;
  }

  profile = GST_VAAPI_PROFILE_UNKNOWN;
  for (i = 0; i < num_profiles; i++) {
    if (gst_vaapi_display_has_encoder (display, profiles[i], entrypoint)) {
      profile = profiles[i];
      break;
    }
  }
  if (profile == GST_VAAPI_PROFILE_UNKNOWN)
    goto error_unsupported_profile;

  GST_VAAPI_ENCODER_CAST (encoder)->profile = profile;
  return TRUE;

  /* ERRORS */
error_unsupported_profile:
  {
    GST_ERROR ("unsupported HW profile %s",
        gst_vaapi_profile_get_name (encoder->profile));
    return FALSE;
  }
}

/* Check target decoder constraints */
static gboolean
ensure_profile_limits (GstVaapiEncoderH265 * encoder)
{

  if (!encoder->max_profile_idc
      || encoder->profile_idc <= encoder->max_profile_idc)
    return TRUE;

  GST_WARNING
      ("Needs to lower coding tools to meet target decoder constraints");
  GST_WARNING ("Only supporting Main profile, reset profile to Main");

  encoder->profile = GST_VAAPI_PROFILE_H265_MAIN;
  encoder->profile_idc =
      gst_vaapi_utils_h265_get_profile_idc (encoder->profile);

  return TRUE;
}

/* Derives the minimum profile from the active coding tools */
static gboolean
ensure_profile (GstVaapiEncoderH265 * encoder)
{
  GstVaapiProfile profile;
  const GstVideoFormat format =
      GST_VIDEO_INFO_FORMAT (GST_VAAPI_ENCODER_VIDEO_INFO (encoder));

  /* Always start from "Main" profile for maximum
     compatibility */
  profile = GST_VAAPI_PROFILE_H265_MAIN;

  if (format == GST_VIDEO_FORMAT_P010_10LE)
    profile = GST_VAAPI_PROFILE_H265_MAIN10;

  encoder->profile = profile;
  encoder->profile_idc = gst_vaapi_utils_h265_get_profile_idc (profile);
  return TRUE;
}

/* Derives the minimum tier from the active coding tools */
static gboolean
ensure_tier (GstVaapiEncoderH265 * encoder)
{

  encoder->tier = GST_VAAPI_TIER_H265_MAIN;
  /* FIXME: Derive proper tier based on upstream caps or limits, coding
   * tools etc */

  return TRUE;
}

/* Derives the level from the currently set limits */
static gboolean
ensure_level (GstVaapiEncoderH265 * encoder)
{
  const GstVaapiH265LevelLimits *limits_table;
  guint i, num_limits, PicSizeInSamplesY;

  PicSizeInSamplesY = encoder->luma_width * encoder->luma_height;

  limits_table = gst_vaapi_utils_h265_get_level_limits_table (&num_limits);
  for (i = 0; i < num_limits; i++) {
    const GstVaapiH265LevelLimits *const limits = &limits_table[i];
    if (PicSizeInSamplesY <= limits->MaxLumaPs)
      break;
    /* FIXME: Add more constraint checking:tier (extracted from caps),
     * cpb size, bitrate, num_tile_columns and num_tile_rows */
  }
  if (i == num_limits)
    goto error_unsupported_level;

  encoder->level = limits_table[i].level;
  encoder->level_idc = limits_table[i].level_idc;
  return TRUE;

  /* ERRORS */
error_unsupported_level:
  {
    GST_ERROR ("failed to find a suitable level matching codec config");
    return FALSE;
  }
}

/* Enable "high-compression" tuning options */
static gboolean
ensure_tuning_high_compression (GstVaapiEncoderH265 * encoder)
{
  guint8 profile_idc;

  if (!ensure_hw_profile_limits (encoder))
    return FALSE;

  profile_idc = encoder->hw_max_profile_idc;
  if (encoder->max_profile_idc && encoder->max_profile_idc < profile_idc)
    profile_idc = encoder->max_profile_idc;

  /* Tuning options */
  if (!encoder->num_bframes)
    encoder->num_bframes = 3;

  return TRUE;
}

/* Ensure tuning options */
static gboolean
ensure_tuning (GstVaapiEncoderH265 * encoder)
{
  gboolean success;

  switch (GST_VAAPI_ENCODER_TUNE (encoder)) {
    case GST_VAAPI_ENCODER_TUNE_HIGH_COMPRESSION:
      success = ensure_tuning_high_compression (encoder);
      break;
    default:
      success = TRUE;
      break;
  }
  return success;
}

/* Handle new GOP starts */
static void
reset_gop_start (GstVaapiEncoderH265 * encoder)
{
  GstVaapiH265ReorderPool *const reorder_pool = &encoder->reorder_pool;

  reorder_pool->frame_index = 1;
  reorder_pool->cur_present_index = 0;
  ++encoder->idr_num;
}

/* Marks the supplied picture as a B-frame */
static void
set_b_frame (GstVaapiEncPicture * pic, GstVaapiEncoderH265 * encoder)
{
  g_assert (pic && encoder);
  g_return_if_fail (pic->type == GST_VAAPI_PICTURE_TYPE_NONE);
  pic->type = GST_VAAPI_PICTURE_TYPE_B;
}

/* Marks the supplied picture as a P-frame */
static void
set_p_frame (GstVaapiEncPicture * pic, GstVaapiEncoderH265 * encoder)
{
  g_return_if_fail (pic->type == GST_VAAPI_PICTURE_TYPE_NONE);
  pic->type = GST_VAAPI_PICTURE_TYPE_P;
}

/* Marks the supplied picture as an I-frame */
static void
set_i_frame (GstVaapiEncPicture * pic, GstVaapiEncoderH265 * encoder)
{
  g_return_if_fail (pic->type == GST_VAAPI_PICTURE_TYPE_NONE);
  pic->type = GST_VAAPI_PICTURE_TYPE_I;

  g_assert (pic->frame);
  GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (pic->frame);
}

/* Marks the supplied picture as an IDR frame */
static void
set_idr_frame (GstVaapiEncPicture * pic, GstVaapiEncoderH265 * encoder)
{
  g_return_if_fail (pic->type == GST_VAAPI_PICTURE_TYPE_NONE);
  pic->type = GST_VAAPI_PICTURE_TYPE_I;
  pic->poc = 0;
  GST_VAAPI_ENC_PICTURE_FLAG_SET (pic, GST_VAAPI_ENC_PICTURE_FLAG_IDR);

  g_assert (pic->frame);
  GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (pic->frame);
}

/* Marks the supplied picture a a key-frame */
static void
set_key_frame (GstVaapiEncPicture * picture,
    GstVaapiEncoderH265 * encoder, gboolean is_idr)
{
  if (is_idr) {
    reset_gop_start (encoder);
    set_idr_frame (picture, encoder);
  } else
    set_i_frame (picture, encoder);
}

/* Fills in VA HRD parameters */
static void
fill_hrd_params (GstVaapiEncoderH265 * encoder, VAEncMiscParameterHRD * hrd)
{
  if (encoder->bitrate_bits > 0) {
    hrd->buffer_size = encoder->cpb_length_bits;
    hrd->initial_buffer_fullness = hrd->buffer_size / 2;
  } else {
    hrd->buffer_size = 0;
    hrd->initial_buffer_fullness = 0;
  }
}

/* Adds the supplied video parameter set header (VPS) to the list of packed
   headers to pass down as-is to the encoder */
static gboolean
add_packed_vps_header (GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture, GstVaapiEncSequence * sequence)
{
  GstVaapiEncPackedHeader *packed_vps;
  GstBitWriter bs;
  VAEncPackedHeaderParameterBuffer packed_vps_param = { 0 };
  const VAEncSequenceParameterBufferHEVC *const seq_param = sequence->param;
  GstVaapiProfile profile = encoder->profile;

  guint32 data_bit_size;
  guint8 *data;

  gst_bit_writer_init (&bs, 128 * 8);
  WRITE_UINT32 (&bs, 0x00000001, 32);   /* start code */
  bs_write_nal_header (&bs, GST_H265_NAL_VPS);

  bs_write_vps (&bs, encoder, picture, seq_param, profile);

  g_assert (GST_BIT_WRITER_BIT_SIZE (&bs) % 8 == 0);
  data_bit_size = GST_BIT_WRITER_BIT_SIZE (&bs);
  data = GST_BIT_WRITER_DATA (&bs);

  packed_vps_param.type = VAEncPackedHeaderSequence;
  packed_vps_param.bit_length = data_bit_size;
  packed_vps_param.has_emulation_bytes = 0;

  packed_vps = gst_vaapi_enc_packed_header_new (GST_VAAPI_ENCODER (encoder),
      &packed_vps_param, sizeof (packed_vps_param),
      data, (data_bit_size + 7) / 8);
  g_assert (packed_vps);

  gst_vaapi_enc_picture_add_packed_header (picture, packed_vps);
  gst_vaapi_codec_object_replace (&packed_vps, NULL);

  /* store vps data */
  _check_vps_sps_pps_status (encoder, data + 4, data_bit_size / 8 - 4);
  gst_bit_writer_clear (&bs, TRUE);
  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write VPS NAL unit");
    gst_bit_writer_clear (&bs, TRUE);
    return FALSE;
  }
}

/* Adds the supplied sequence header (SPS) to the list of packed
   headers to pass down as-is to the encoder */
static gboolean
add_packed_sequence_header (GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture, GstVaapiEncSequence * sequence)
{
  GstVaapiEncPackedHeader *packed_seq;
  GstBitWriter bs;
  VAEncPackedHeaderParameterBuffer packed_seq_param = { 0 };
  const VAEncSequenceParameterBufferHEVC *const seq_param = sequence->param;
  GstVaapiProfile profile = encoder->profile;

  VAEncMiscParameterHRD hrd_params;
  guint32 data_bit_size;
  guint8 *data;

  fill_hrd_params (encoder, &hrd_params);

  gst_bit_writer_init (&bs, 128 * 8);
  WRITE_UINT32 (&bs, 0x00000001, 32);   /* start code */
  bs_write_nal_header (&bs, GST_H265_NAL_SPS);

  bs_write_sps (&bs, encoder, picture, seq_param, profile, &hrd_params);

  g_assert (GST_BIT_WRITER_BIT_SIZE (&bs) % 8 == 0);
  data_bit_size = GST_BIT_WRITER_BIT_SIZE (&bs);
  data = GST_BIT_WRITER_DATA (&bs);

  packed_seq_param.type = VAEncPackedHeaderSequence;
  packed_seq_param.bit_length = data_bit_size;
  packed_seq_param.has_emulation_bytes = 0;

  packed_seq = gst_vaapi_enc_packed_header_new (GST_VAAPI_ENCODER (encoder),
      &packed_seq_param, sizeof (packed_seq_param),
      data, (data_bit_size + 7) / 8);
  g_assert (packed_seq);

  gst_vaapi_enc_picture_add_packed_header (picture, packed_seq);
  gst_vaapi_codec_object_replace (&packed_seq, NULL);

  /* store sps data */
  _check_vps_sps_pps_status (encoder, data + 4, data_bit_size / 8 - 4);
  gst_bit_writer_clear (&bs, TRUE);
  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write SPS NAL unit");
    gst_bit_writer_clear (&bs, TRUE);
    return FALSE;
  }
}

/* Adds the supplied picture header (PPS) to the list of packed
   headers to pass down as-is to the encoder */
static gboolean
add_packed_picture_header (GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture)
{
  GstVaapiEncPackedHeader *packed_pic;
  GstBitWriter bs;
  VAEncPackedHeaderParameterBuffer packed_pic_param = { 0 };
  const VAEncPictureParameterBufferHEVC *const pic_param = picture->param;
  guint32 data_bit_size;
  guint8 *data;

  gst_bit_writer_init (&bs, 128 * 8);
  WRITE_UINT32 (&bs, 0x00000001, 32);   /* start code */
  bs_write_nal_header (&bs, GST_H265_NAL_PPS);
  bs_write_pps (&bs, pic_param);
  g_assert (GST_BIT_WRITER_BIT_SIZE (&bs) % 8 == 0);
  data_bit_size = GST_BIT_WRITER_BIT_SIZE (&bs);
  data = GST_BIT_WRITER_DATA (&bs);

  packed_pic_param.type = VAEncPackedHeaderPicture;
  packed_pic_param.bit_length = data_bit_size;
  packed_pic_param.has_emulation_bytes = 0;

  packed_pic = gst_vaapi_enc_packed_header_new (GST_VAAPI_ENCODER (encoder),
      &packed_pic_param, sizeof (packed_pic_param),
      data, (data_bit_size + 7) / 8);
  g_assert (packed_pic);

  gst_vaapi_enc_picture_add_packed_header (picture, packed_pic);
  gst_vaapi_codec_object_replace (&packed_pic, NULL);

  /* store pps data */
  _check_vps_sps_pps_status (encoder, data + 4, data_bit_size / 8 - 4);
  gst_bit_writer_clear (&bs, TRUE);
  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write PPS NAL unit");
    gst_bit_writer_clear (&bs, TRUE);
    return FALSE;
  }
}

static gboolean
get_nal_unit_type (GstVaapiEncPicture * picture, guint8 * nal_unit_type)
{
  switch (picture->type) {
    case GST_VAAPI_PICTURE_TYPE_I:
      if (GST_VAAPI_ENC_PICTURE_IS_IDR (picture))
        *nal_unit_type = GST_H265_NAL_SLICE_IDR_W_RADL;
      else
        *nal_unit_type = GST_H265_NAL_SLICE_TRAIL_R;
      break;
    case GST_VAAPI_PICTURE_TYPE_P:
      *nal_unit_type = GST_H265_NAL_SLICE_TRAIL_R;
      break;
    case GST_VAAPI_PICTURE_TYPE_B:
      *nal_unit_type = GST_H265_NAL_SLICE_TRAIL_N;
      break;
    default:
      return FALSE;
  }
  return TRUE;
}

/* Adds the supplied slice header to the list of packed
   headers to pass down as-is to the encoder */
static gboolean
add_packed_slice_header (GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture, GstVaapiEncSlice * slice)
{
  GstVaapiEncPackedHeader *packed_slice;
  GstBitWriter bs;
  VAEncPackedHeaderParameterBuffer packed_slice_param = { 0 };
  const VAEncSliceParameterBufferHEVC *const slice_param = slice->param;
  guint32 data_bit_size;
  guint8 *data;
  guint8 nal_unit_type;

  gst_bit_writer_init (&bs, 128 * 8);
  WRITE_UINT32 (&bs, 0x00000001, 32);   /* start code */

  if (!get_nal_unit_type (picture, &nal_unit_type))
    goto bs_error;
  bs_write_nal_header (&bs, nal_unit_type);

  bs_write_slice (&bs, slice_param, encoder, picture, nal_unit_type);
  data_bit_size = GST_BIT_WRITER_BIT_SIZE (&bs);
  data = GST_BIT_WRITER_DATA (&bs);

  packed_slice_param.type = VAEncPackedHeaderSlice;
  packed_slice_param.bit_length = data_bit_size;
  packed_slice_param.has_emulation_bytes = 0;

  packed_slice = gst_vaapi_enc_packed_header_new (GST_VAAPI_ENCODER (encoder),
      &packed_slice_param, sizeof (packed_slice_param),
      data, (data_bit_size + 7) / 8);
  g_assert (packed_slice);

  gst_vaapi_enc_slice_add_packed_header (slice, packed_slice);
  gst_vaapi_codec_object_replace (&packed_slice, NULL);

  gst_bit_writer_clear (&bs, TRUE);
  return TRUE;

  /* ERRORS */
bs_error:
  {
    GST_WARNING ("failed to write Slice NAL unit header");
    gst_bit_writer_clear (&bs, TRUE);
    return FALSE;
  }
}

/* Reference picture management */
static void
reference_pic_free (GstVaapiEncoderH265 * encoder, GstVaapiEncoderH265Ref * ref)
{
  if (!ref)
    return;
  if (ref->pic)
    gst_vaapi_encoder_release_surface (GST_VAAPI_ENCODER (encoder), ref->pic);
  g_slice_free (GstVaapiEncoderH265Ref, ref);
}

static inline GstVaapiEncoderH265Ref *
reference_pic_create (GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture, GstVaapiSurfaceProxy * surface)
{
  GstVaapiEncoderH265Ref *const ref = g_slice_new0 (GstVaapiEncoderH265Ref);

  ref->pic = surface;
  ref->poc = picture->poc;
  return ref;
}

static gboolean
reference_list_update (GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture, GstVaapiSurfaceProxy * surface)
{
  GstVaapiEncoderH265Ref *ref;
  GstVaapiH265RefPool *const ref_pool = &encoder->ref_pool;

  if (GST_VAAPI_PICTURE_TYPE_B == picture->type) {
    gst_vaapi_encoder_release_surface (GST_VAAPI_ENCODER (encoder), surface);
    return TRUE;
  }

  if (GST_VAAPI_ENC_PICTURE_IS_IDR (picture)) {
    while (!g_queue_is_empty (&ref_pool->ref_list))
      reference_pic_free (encoder, g_queue_pop_head (&ref_pool->ref_list));
  } else if (g_queue_get_length (&ref_pool->ref_list) >=
      ref_pool->max_ref_frames) {
    reference_pic_free (encoder, g_queue_pop_head (&ref_pool->ref_list));
  }
  ref = reference_pic_create (encoder, picture, surface);
  g_queue_push_tail (&ref_pool->ref_list, ref);
  g_assert (g_queue_get_length (&ref_pool->ref_list) <=
      ref_pool->max_ref_frames);
  return TRUE;
}

static gboolean
reference_list_init (GstVaapiEncoderH265 * encoder,
    GstVaapiEncPicture * picture,
    GstVaapiEncoderH265Ref ** reflist_0,
    guint * reflist_0_count,
    GstVaapiEncoderH265Ref ** reflist_1, guint * reflist_1_count)
{
  GstVaapiEncoderH265Ref *tmp;
  GstVaapiH265RefPool *const ref_pool = &encoder->ref_pool;
  GList *iter, *list_0_start = NULL, *list_1_start = NULL;
  guint count;

  *reflist_0_count = 0;
  *reflist_1_count = 0;
  if (picture->type == GST_VAAPI_PICTURE_TYPE_I)
    return TRUE;

  iter = g_queue_peek_tail_link (&ref_pool->ref_list);
  for (; iter; iter = g_list_previous (iter)) {
    tmp = (GstVaapiEncoderH265Ref *) iter->data;
    g_assert (tmp && tmp->poc != picture->poc);
    if (_poc_greater_than (picture->poc, tmp->poc, encoder->max_pic_order_cnt)) {
      list_0_start = iter;
      list_1_start = g_list_next (iter);
      break;
    }
  }

  /* order reflist_0 */
  g_assert (list_0_start);
  iter = list_0_start;
  count = 0;
  for (; iter; iter = g_list_previous (iter)) {
    reflist_0[count] = (GstVaapiEncoderH265Ref *) iter->data;
    ++count;
  }
  *reflist_0_count = count;

  if (picture->type != GST_VAAPI_PICTURE_TYPE_B)
    return TRUE;

  /* order reflist_1 */
  count = 0;
  iter = list_1_start;
  for (; iter; iter = g_list_next (iter)) {
    reflist_1[count] = (GstVaapiEncoderH265Ref *) iter->data;
    ++count;
  }
  *reflist_1_count = count;
  return TRUE;
}

/* Fills in VA sequence parameter buffer */
static gboolean
fill_sequence (GstVaapiEncoderH265 * encoder, GstVaapiEncSequence * sequence)
{
  VAEncSequenceParameterBufferHEVC *const seq_param = sequence->param;
  const GstVideoFormat format =
      GST_VIDEO_INFO_FORMAT (GST_VAAPI_ENCODER_VIDEO_INFO (encoder));
  guint bits_depth_luma_minus8 =
      GST_VIDEO_FORMAT_INFO_DEPTH (gst_video_format_get_info (format), 0);
  if (bits_depth_luma_minus8 < 8)
    return FALSE;
  bits_depth_luma_minus8 -= 8;

  memset (seq_param, 0, sizeof (VAEncSequenceParameterBufferHEVC));

  seq_param->general_profile_idc = encoder->profile_idc;
  seq_param->general_level_idc = encoder->level_idc;
  seq_param->general_tier_flag = 0;     /* FIXME: use the tier flag
                                         * extracted from upstream
                                         * caps or calcuted one */

  seq_param->intra_period = GST_VAAPI_ENCODER_KEYFRAME_PERIOD (encoder);
  seq_param->intra_idr_period = encoder->idr_period;
  seq_param->ip_period = seq_param->intra_period > 1 ?
      (1 + encoder->num_bframes) : 0;
  seq_param->bits_per_second = encoder->bitrate_bits;

  seq_param->pic_width_in_luma_samples = encoder->luma_width;
  seq_param->pic_height_in_luma_samples = encoder->luma_height;

  /*sequence field values */
  seq_param->seq_fields.value = 0;
  seq_param->seq_fields.bits.chroma_format_idc = 1;
  seq_param->seq_fields.bits.separate_colour_plane_flag = 0;
  seq_param->seq_fields.bits.bit_depth_luma_minus8 = bits_depth_luma_minus8;
  seq_param->seq_fields.bits.bit_depth_chroma_minus8 = bits_depth_luma_minus8;
  seq_param->seq_fields.bits.scaling_list_enabled_flag = FALSE;
  seq_param->seq_fields.bits.strong_intra_smoothing_enabled_flag = TRUE;
  seq_param->seq_fields.bits.amp_enabled_flag = TRUE;
  seq_param->seq_fields.bits.sample_adaptive_offset_enabled_flag =
      encoder->sample_adaptive_offset_enabled_flag = FALSE;
  seq_param->seq_fields.bits.pcm_enabled_flag = FALSE;
  seq_param->seq_fields.bits.pcm_loop_filter_disabled_flag = FALSE;
  seq_param->seq_fields.bits.sps_temporal_mvp_enabled_flag =
      encoder->sps_temporal_mvp_enabled_flag = TRUE;

  /* Based on 32x32 CTU */
  seq_param->log2_min_luma_coding_block_size_minus3 = 0;
  seq_param->log2_diff_max_min_luma_coding_block_size = 2;
  seq_param->log2_min_transform_block_size_minus2 = 0;
  seq_param->log2_diff_max_min_transform_block_size = 3;
  seq_param->max_transform_hierarchy_depth_inter = 3;
  seq_param->max_transform_hierarchy_depth_intra = 3;

  seq_param->pcm_sample_bit_depth_luma_minus1 = 0;
  seq_param->pcm_sample_bit_depth_chroma_minus1 = 0;
  seq_param->log2_min_pcm_luma_coding_block_size_minus3 = 0;
  seq_param->log2_max_pcm_luma_coding_block_size_minus3 = 0;

  /* VUI parameters are always set, at least for timing_info (framerate) */
  seq_param->vui_parameters_present_flag = TRUE;
  if (seq_param->vui_parameters_present_flag) {
    seq_param->vui_fields.bits.aspect_ratio_info_present_flag = TRUE;
    if (seq_param->vui_fields.bits.aspect_ratio_info_present_flag) {
      const GstVideoInfo *const vip = GST_VAAPI_ENCODER_VIDEO_INFO (encoder);
      seq_param->aspect_ratio_idc = 0xff;
      seq_param->sar_width = GST_VIDEO_INFO_PAR_N (vip);
      seq_param->sar_height = GST_VIDEO_INFO_PAR_D (vip);
    }
    seq_param->vui_fields.bits.bitstream_restriction_flag = FALSE;
    seq_param->vui_fields.bits.vui_timing_info_present_flag = TRUE;
    if (seq_param->vui_fields.bits.vui_timing_info_present_flag) {
      seq_param->vui_num_units_in_tick = GST_VAAPI_ENCODER_FPS_D (encoder);
      seq_param->vui_time_scale = GST_VAAPI_ENCODER_FPS_N (encoder);
    }
  }
  return TRUE;
}

/* Fills in VA picture parameter buffer */
static gboolean
fill_picture (GstVaapiEncoderH265 * encoder, GstVaapiEncPicture * picture,
    GstVaapiCodedBuffer * codedbuf, GstVaapiSurfaceProxy * surface)
{
  VAEncPictureParameterBufferHEVC *const pic_param = picture->param;
  GstVaapiH265RefPool *const ref_pool = &encoder->ref_pool;
  GstVaapiEncoderH265Ref *ref_pic;
  GList *reflist;
  guint i;
  guint8 nal_unit_type, no_output_of_prior_pics_flag = 0;

  memset (pic_param, 0, sizeof (VAEncPictureParameterBufferHEVC));

  pic_param->decoded_curr_pic.picture_id =
      GST_VAAPI_SURFACE_PROXY_SURFACE_ID (surface);
  pic_param->decoded_curr_pic.pic_order_cnt = picture->poc;
  pic_param->decoded_curr_pic.flags = 0;

  i = 0;
  if (picture->type != GST_VAAPI_PICTURE_TYPE_I) {
    for (reflist = g_queue_peek_head_link (&ref_pool->ref_list);
        reflist; reflist = g_list_next (reflist)) {
      ref_pic = reflist->data;
      g_assert (ref_pic && ref_pic->pic &&
          GST_VAAPI_SURFACE_PROXY_SURFACE_ID (ref_pic->pic) != VA_INVALID_ID);

      pic_param->reference_frames[i].picture_id =
          GST_VAAPI_SURFACE_PROXY_SURFACE_ID (ref_pic->pic);
      pic_param->reference_frames[i].pic_order_cnt = ref_pic->poc;
      ++i;
    }
    g_assert (i <= 15 && i <= ref_pool->max_ref_frames);
  }
  for (; i < 15; ++i) {
    pic_param->reference_frames[i].picture_id = VA_INVALID_SURFACE;
    pic_param->reference_frames[i].flags = 0;
  }
  pic_param->coded_buf = GST_VAAPI_OBJECT_ID (codedbuf);

  /* slice_temporal_mvp_enable_flag == FALSE */
  pic_param->collocated_ref_pic_index = 0xFF;

  pic_param->last_picture = 0;
  pic_param->pic_init_qp = encoder->qp_i;
  pic_param->num_ref_idx_l0_default_active_minus1 =
      (ref_pool->max_reflist0_count ? (ref_pool->max_reflist0_count - 1) : 0);
  pic_param->num_ref_idx_l1_default_active_minus1 =
      (ref_pool->max_reflist1_count ? (ref_pool->max_reflist1_count - 1) : 0);

  if (!get_nal_unit_type (picture, &nal_unit_type))
    return FALSE;
  pic_param->nal_unit_type = nal_unit_type;

  /* set picture fields */
  pic_param->pic_fields.value = 0;
  pic_param->pic_fields.bits.idr_pic_flag =
      GST_VAAPI_ENC_PICTURE_IS_IDR (picture);
  pic_param->pic_fields.bits.coding_type = picture->type;
  if (picture->type != GST_VAAPI_PICTURE_TYPE_B)
    pic_param->pic_fields.bits.reference_pic_flag = TRUE;
  pic_param->pic_fields.bits.sign_data_hiding_enabled_flag = FALSE;
  pic_param->pic_fields.bits.transform_skip_enabled_flag = TRUE;
  /* it seems driver requires enablement of cu_qp_delta_enabled_flag
   * to modifiy QP values in CBR mode encoding */
  pic_param->pic_fields.bits.cu_qp_delta_enabled_flag =
      GST_VAAPI_ENCODER_RATE_CONTROL (encoder) != GST_VAAPI_RATECONTROL_CQP;

  pic_param->pic_fields.bits.pps_loop_filter_across_slices_enabled_flag = TRUE;

  if (GST_VAAPI_ENC_PICTURE_IS_IDR (picture))
    no_output_of_prior_pics_flag = 1;
  pic_param->pic_fields.bits.no_output_of_prior_pics_flag =
      no_output_of_prior_pics_flag;

  return TRUE;
}

/* Adds slice headers to picture */
static gboolean
add_slice_headers (GstVaapiEncoderH265 * encoder, GstVaapiEncPicture * picture,
    GstVaapiEncoderH265Ref ** reflist_0, guint reflist_0_count,
    GstVaapiEncoderH265Ref ** reflist_1, guint reflist_1_count)
{
  VAEncSliceParameterBufferHEVC *slice_param;
  GstVaapiEncSlice *slice;
  guint slice_of_ctus, slice_mod_ctus, cur_slice_ctus;
  guint ctu_size;
  guint ctu_width_round_factor;
  guint last_ctu_index;
  guint i_slice, i_ref;

  g_assert (picture);

  ctu_size = encoder->ctu_width * encoder->ctu_height;

  g_assert (encoder->num_slices && encoder->num_slices < ctu_size);
  slice_of_ctus = ctu_size / encoder->num_slices;
  slice_mod_ctus = ctu_size % encoder->num_slices;
  last_ctu_index = 0;

  for (i_slice = 0;
      i_slice < encoder->num_slices && (last_ctu_index < ctu_size); ++i_slice) {
    cur_slice_ctus = slice_of_ctus;
    if (slice_mod_ctus) {
      ++cur_slice_ctus;
      --slice_mod_ctus;
    }

    /* Work-around for satisfying the VA-Intel driver.
     * The driver only support multi slice begin from row start address */
    ctu_width_round_factor =
        encoder->ctu_width - (cur_slice_ctus % encoder->ctu_width);
    cur_slice_ctus += ctu_width_round_factor;
    if ((last_ctu_index + cur_slice_ctus) > ctu_size)
      cur_slice_ctus = ctu_size - last_ctu_index;

    slice = GST_VAAPI_ENC_SLICE_NEW (HEVC, encoder);
    g_assert (slice && slice->param_id != VA_INVALID_ID);
    slice_param = slice->param;

    memset (slice_param, 0, sizeof (VAEncSliceParameterBufferHEVC));
    if (i_slice == 0) {
      encoder->first_slice_segment_in_pic_flag = TRUE;
      slice_param->slice_segment_address = 0;
    } else {
      encoder->first_slice_segment_in_pic_flag = FALSE;
      slice_param->slice_segment_address = last_ctu_index;
    }
    slice_param->num_ctu_in_slice = cur_slice_ctus;
    slice_param->slice_type = h265_get_slice_type (picture->type);
    slice_param->slice_pic_parameter_set_id = 0;

    slice_param->slice_fields.bits.num_ref_idx_active_override_flag =
        reflist_0_count || reflist_1_count;
    if (picture->type != GST_VAAPI_PICTURE_TYPE_I && reflist_0_count > 0)
      slice_param->num_ref_idx_l0_active_minus1 = reflist_0_count - 1;
    else
      slice_param->num_ref_idx_l0_active_minus1 = 0;
    if (picture->type == GST_VAAPI_PICTURE_TYPE_B && reflist_1_count > 0)
      slice_param->num_ref_idx_l1_active_minus1 = reflist_1_count - 1;
    else
      slice_param->num_ref_idx_l1_active_minus1 = 0;

    i_ref = 0;
    if (picture->type != GST_VAAPI_PICTURE_TYPE_I) {
      for (; i_ref < reflist_0_count; ++i_ref) {
        slice_param->ref_pic_list0[i_ref].picture_id =
            GST_VAAPI_SURFACE_PROXY_SURFACE_ID (reflist_0[i_ref]->pic);
        slice_param->ref_pic_list0[i_ref].pic_order_cnt = reflist_0[i_ref]->poc;
      }
    }
    for (; i_ref < G_N_ELEMENTS (slice_param->ref_pic_list0); ++i_ref) {
      slice_param->ref_pic_list0[i_ref].picture_id = VA_INVALID_SURFACE;
      slice_param->ref_pic_list0[i_ref].flags = 0;
    }

    i_ref = 0;
    if (picture->type == GST_VAAPI_PICTURE_TYPE_B) {
      for (; i_ref < reflist_1_count; ++i_ref) {
        slice_param->ref_pic_list1[i_ref].picture_id =
            GST_VAAPI_SURFACE_PROXY_SURFACE_ID (reflist_1[i_ref]->pic);
        slice_param->ref_pic_list1[i_ref].pic_order_cnt = reflist_1[i_ref]->poc;
      }
    }
    for (; i_ref < G_N_ELEMENTS (slice_param->ref_pic_list1); ++i_ref) {
      slice_param->ref_pic_list1[i_ref].picture_id = VA_INVALID_SURFACE;
      slice_param->ref_pic_list1[i_ref].flags = 0;
    }

    slice_param->max_num_merge_cand = 5;        /* MaxNumMergeCand  */
    slice_param->slice_qp_delta = encoder->qp_i - encoder->init_qp;
    if (GST_VAAPI_ENCODER_RATE_CONTROL (encoder) == GST_VAAPI_RATECONTROL_CQP) {
      if (picture->type == GST_VAAPI_PICTURE_TYPE_P) {
        slice_param->slice_qp_delta += encoder->qp_ip;
      } else if (picture->type == GST_VAAPI_PICTURE_TYPE_B) {
        slice_param->slice_qp_delta += encoder->qp_ib;
      }
      if ((gint) encoder->init_qp + slice_param->slice_qp_delta <
          (gint) encoder->min_qp) {
        slice_param->slice_qp_delta = encoder->min_qp - encoder->init_qp;
      }
      /* TODO: max_qp could be provided as a property in the future */
      if ((gint) encoder->init_qp + slice_param->slice_qp_delta > 51) {
        slice_param->slice_qp_delta = 51 - encoder->init_qp;
      }
    }

    slice_param->slice_fields.bits.
        slice_loop_filter_across_slices_enabled_flag = TRUE;

    /* set calculation for next slice */
    last_ctu_index += cur_slice_ctus;

    if ((i_slice == encoder->num_slices - 1) || (last_ctu_index == ctu_size))
      slice_param->slice_fields.bits.last_slice_of_pic_flag = 1;

    if ((GST_VAAPI_ENCODER_PACKED_HEADERS (encoder) &
            VA_ENC_PACKED_HEADER_SLICE)
        && !add_packed_slice_header (encoder, picture, slice))
      goto error_create_packed_slice_hdr;

    gst_vaapi_enc_picture_add_slice (picture, slice);
    gst_vaapi_codec_object_replace (&slice, NULL);
  }
  if (i_slice < encoder->num_slices)
    GST_WARNING
        ("Using less number of slices than requested, Number of slices per pictures is %d",
        i_slice);
  g_assert (last_ctu_index == ctu_size);

  return TRUE;

error_create_packed_slice_hdr:
  {
    GST_ERROR ("failed to create packed slice header buffer");
    gst_vaapi_codec_object_replace (&slice, NULL);
    return FALSE;
  }
}

/* Generates and submits SPS header accordingly into the bitstream */
static gboolean
ensure_sequence (GstVaapiEncoderH265 * encoder, GstVaapiEncPicture * picture)
{
  GstVaapiEncSequence *sequence = NULL;

  /* submit an SPS header before every new I-frame, if codec config changed */
  if (!encoder->config_changed || picture->type != GST_VAAPI_PICTURE_TYPE_I)
    return TRUE;

  sequence = GST_VAAPI_ENC_SEQUENCE_NEW (HEVC, encoder);
  if (!sequence || !fill_sequence (encoder, sequence))
    goto error_create_seq_param;

  /* add packed vps and sps headers */
  if ((GST_VAAPI_ENCODER_PACKED_HEADERS (encoder) &
          VA_ENC_PACKED_HEADER_SEQUENCE)
      && !(add_packed_vps_header (encoder, picture, sequence)
          && add_packed_sequence_header (encoder, picture, sequence))) {
    goto error_create_packed_seq_hdr;
  }

  if (sequence) {
    gst_vaapi_enc_picture_set_sequence (picture, sequence);
    gst_vaapi_codec_object_replace (&sequence, NULL);
  }

  encoder->config_changed = FALSE;
  return TRUE;

  /* ERRORS */
error_create_seq_param:
  {
    GST_ERROR ("failed to create sequence parameter buffer (SPS)");
    gst_vaapi_codec_object_replace (&sequence, NULL);
    return FALSE;
  }
error_create_packed_seq_hdr:
  {
    GST_ERROR ("failed to create packed sequence header buffer");
    gst_vaapi_codec_object_replace (&sequence, NULL);
    return FALSE;
  }
}

static gboolean
ensure_control_rate_params (GstVaapiEncoderH265 * encoder)
{
  if (GST_VAAPI_ENCODER_RATE_CONTROL (encoder) == GST_VAAPI_RATECONTROL_CQP)
    return TRUE;

  /* RateControl params */
  GST_VAAPI_ENCODER_VA_RATE_CONTROL (encoder).bits_per_second =
      encoder->bitrate_bits;
  /* CPB (Coded picture buffer) length in milliseconds, which could be
   * provided as a property */
  GST_VAAPI_ENCODER_VA_RATE_CONTROL (encoder).window_size = encoder->cpb_length;
  GST_VAAPI_ENCODER_VA_RATE_CONTROL (encoder).initial_qp = encoder->init_qp;
  GST_VAAPI_ENCODER_VA_RATE_CONTROL (encoder).min_qp = encoder->min_qp;

#if VA_CHECK_VERSION(1,0,0)
  GST_VAAPI_ENCODER_VA_RATE_CONTROL (encoder).rc_flags.bits.mb_rate_control =
      (guint) encoder->mbbrc;
#endif

  /* HRD params */
  fill_hrd_params (encoder, &GST_VAAPI_ENCODER_VA_HRD (encoder));

  return TRUE;
}

static gboolean
ensure_misc_params (GstVaapiEncoderH265 * encoder, GstVaapiEncPicture * picture)
{
  GstVaapiEncoder *const base_encoder = GST_VAAPI_ENCODER_CAST (encoder);

  if (!gst_vaapi_encoder_ensure_param_control_rate (base_encoder, picture))
    return FALSE;
  if (!gst_vaapi_encoder_ensure_param_roi_regions (base_encoder, picture))
    return FALSE;
  if (!gst_vaapi_encoder_ensure_param_quality_level (base_encoder, picture))
    return FALSE;
  return TRUE;
}

/* Generates and submits PPS header accordingly into the bitstream */
static gboolean
ensure_picture (GstVaapiEncoderH265 * encoder, GstVaapiEncPicture * picture,
    GstVaapiCodedBufferProxy * codedbuf_proxy, GstVaapiSurfaceProxy * surface)
{
  GstVaapiCodedBuffer *const codedbuf =
      GST_VAAPI_CODED_BUFFER_PROXY_BUFFER (codedbuf_proxy);
  gboolean res = FALSE;

  res = fill_picture (encoder, picture, codedbuf, surface);

  if (!res)
    return FALSE;

  if (picture->type == GST_VAAPI_PICTURE_TYPE_I &&
      (GST_VAAPI_ENCODER_PACKED_HEADERS (encoder) &
          VA_ENC_PACKED_HEADER_PICTURE)
      && !add_packed_picture_header (encoder, picture)) {
    GST_ERROR ("set picture packed header failed");
    return FALSE;
  }
  return TRUE;
}


/* Generates slice headers */
static gboolean
ensure_slices (GstVaapiEncoderH265 * encoder, GstVaapiEncPicture * picture)
{
  GstVaapiEncoderH265Ref *reflist_0[15];
  GstVaapiEncoderH265Ref *reflist_1[15];
  GstVaapiH265RefPool *const ref_pool = &encoder->ref_pool;
  guint reflist_0_count = 0, reflist_1_count = 0;

  g_assert (picture);

  if (picture->type != GST_VAAPI_PICTURE_TYPE_I &&
      !reference_list_init (encoder, picture,
          reflist_0, &reflist_0_count, reflist_1, &reflist_1_count)) {
    GST_ERROR ("reference list reorder failed");
    return FALSE;
  }

  g_assert (reflist_0_count + reflist_1_count <= ref_pool->max_ref_frames);
  if (reflist_0_count > ref_pool->max_reflist0_count)
    reflist_0_count = ref_pool->max_reflist0_count;
  if (reflist_1_count > ref_pool->max_reflist1_count)
    reflist_1_count = ref_pool->max_reflist1_count;

  if (!add_slice_headers (encoder, picture,
          reflist_0, reflist_0_count, reflist_1, reflist_1_count))
    return FALSE;

  return TRUE;
}

/* Normalizes bitrate (and CPB size) for HRD conformance */
static void
ensure_bitrate_hrd (GstVaapiEncoderH265 * encoder)
{
  GstVaapiEncoder *const base_encoder = GST_VAAPI_ENCODER_CAST (encoder);
  guint bitrate, cpb_size;

  if (!base_encoder->bitrate) {
    encoder->bitrate_bits = 0;
    return;
  }

  /* Round down bitrate. This is a hard limit mandated by the user */
  g_assert (SX_BITRATE >= 6);
  bitrate = (base_encoder->bitrate * 1000) & ~((1U << SX_BITRATE) - 1);
  if (bitrate != encoder->bitrate_bits) {
    GST_DEBUG ("HRD bitrate: %u bits/sec", bitrate);
    encoder->bitrate_bits = bitrate;
    encoder->config_changed = TRUE;
  }

  /* Round up CPB size. This is an HRD compliance detail */
  g_assert (SX_CPB_SIZE >= 4);
  cpb_size = gst_util_uint64_scale (bitrate, encoder->cpb_length, 1000) &
      ~((1U << SX_CPB_SIZE) - 1);
  if (cpb_size != encoder->cpb_length_bits) {
    GST_DEBUG ("HRD CPB size: %u bits", cpb_size);
    encoder->cpb_length_bits = cpb_size;
    encoder->config_changed = TRUE;
  }
}

/* Estimates a good enough bitrate if none was supplied */
static void
ensure_bitrate (GstVaapiEncoderH265 * encoder)
{
  GstVaapiEncoder *const base_encoder = GST_VAAPI_ENCODER_CAST (encoder);

  switch (GST_VAAPI_ENCODER_RATE_CONTROL (encoder)) {
    case GST_VAAPI_RATECONTROL_CBR:
    case GST_VAAPI_RATECONTROL_VBR:
      if (!base_encoder->bitrate) {
        /* FIXME: Provide better estimation */
        /* Using a 1/6 compression ratio */
        /* 12 bits per pixel for YUV420 */
        guint64 factor;

        factor = (guint64) encoder->luma_width * encoder->luma_height * 12 / 6;
        base_encoder->bitrate =
            gst_util_uint64_scale (factor, GST_VAAPI_ENCODER_FPS_N (encoder),
            GST_VAAPI_ENCODER_FPS_D (encoder)) / 1000;
        GST_INFO ("target bitrate computed to %u kbps", base_encoder->bitrate);
      }
      break;
    default:
      base_encoder->bitrate = 0;
      break;
  }
  ensure_bitrate_hrd (encoder);
}

/* Constructs profile, tier and level information based on user-defined limits */
static GstVaapiEncoderStatus
ensure_profile_tier_level (GstVaapiEncoderH265 * encoder)
{
  const GstVaapiProfile profile = encoder->profile;
  const GstVaapiTierH265 tier = encoder->tier;
  const GstVaapiLevelH265 level = encoder->level;

  ensure_tuning (encoder);

  if (!ensure_profile (encoder) || !ensure_profile_limits (encoder))
    return GST_VAAPI_ENCODER_STATUS_ERROR_UNSUPPORTED_PROFILE;

  /* Check HW constraints */
  if (!ensure_hw_profile_limits (encoder))
    return GST_VAAPI_ENCODER_STATUS_ERROR_UNSUPPORTED_PROFILE;
  if (encoder->profile_idc > encoder->hw_max_profile_idc)
    return GST_VAAPI_ENCODER_STATUS_ERROR_UNSUPPORTED_PROFILE;

  /* ensure tier */
  if (!ensure_tier (encoder))
    return GST_VAAPI_ENCODER_STATUS_ERROR_OPERATION_FAILED;

  /* Ensure bitrate if not set already and derive the right level to use */
  ensure_bitrate (encoder);

  if (!ensure_level (encoder))
    return GST_VAAPI_ENCODER_STATUS_ERROR_OPERATION_FAILED;

  if (encoder->profile != profile || encoder->level != level
      || encoder->tier != tier) {
    GST_DEBUG ("selected %s profile at tier %s and level %s",
        gst_vaapi_utils_h265_get_profile_string (encoder->profile),
        gst_vaapi_utils_h265_get_tier_string (encoder->tier),
        gst_vaapi_utils_h265_get_level_string (encoder->level));
    encoder->config_changed = TRUE;
  }
  return GST_VAAPI_ENCODER_STATUS_SUCCESS;
}

static void
reset_properties (GstVaapiEncoderH265 * encoder)
{
  GstVaapiEncoder *const base_encoder = GST_VAAPI_ENCODER_CAST (encoder);
  GstVaapiH265ReorderPool *reorder_pool;
  GstVaapiH265RefPool *ref_pool;
  guint ctu_size;

  if (encoder->idr_period < base_encoder->keyframe_period)
    encoder->idr_period = base_encoder->keyframe_period;

  if (encoder->min_qp > encoder->init_qp)
    encoder->min_qp = encoder->init_qp;
  encoder->qp_i = encoder->init_qp;

  ctu_size = encoder->ctu_width * encoder->ctu_height;
  g_assert (gst_vaapi_encoder_ensure_num_slices (base_encoder, encoder->profile,
          GST_VAAPI_ENTRYPOINT_SLICE_ENCODE, (ctu_size + 1) / 2,
          &encoder->num_slices));

  gst_vaapi_encoder_ensure_max_num_ref_frames (base_encoder, encoder->profile,
      GST_VAAPI_ENTRYPOINT_SLICE_ENCODE);

  if (base_encoder->max_num_ref_frames_1 < 1 && encoder->num_bframes > 0) {
    GST_WARNING ("Disabling b-frame since the driver doesn't support it");
    encoder->num_bframes = 0;
  }

  if (encoder->num_ref_frames > base_encoder->max_num_ref_frames_0) {
    GST_INFO ("Lowering the number of reference frames to %d",
        base_encoder->max_num_ref_frames_0);
    encoder->num_ref_frames = base_encoder->max_num_ref_frames_0;
  }

  if (encoder->num_bframes > (base_encoder->keyframe_period + 1) / 2)
    encoder->num_bframes = (base_encoder->keyframe_period + 1) / 2;

  if (encoder->num_bframes > 0 && GST_VAAPI_ENCODER_FPS_N (encoder) > 0)
    encoder->cts_offset = gst_util_uint64_scale (GST_SECOND,
        GST_VAAPI_ENCODER_FPS_D (encoder), GST_VAAPI_ENCODER_FPS_N (encoder));
  else
    encoder->cts_offset = 0;

  /* init max_poc */
  encoder->log2_max_pic_order_cnt =
      h265_get_log2_max_pic_order_cnt (encoder->idr_period);
  g_assert (encoder->log2_max_pic_order_cnt >= 4);
  encoder->max_pic_order_cnt = (1 << encoder->log2_max_pic_order_cnt);
  encoder->idr_num = 0;

  /* Only Supporting a maximum of two reference frames */
  if (encoder->num_bframes) {
    encoder->max_dec_pic_buffering = encoder->num_ref_frames + 2;
    encoder->max_num_reorder_pics = 1;
  } else {
    encoder->max_dec_pic_buffering = encoder->num_ref_frames + 1;
    encoder->max_num_reorder_pics = 0;
  }

  ref_pool = &encoder->ref_pool;
  ref_pool->max_reflist0_count = encoder->num_ref_frames;
  ref_pool->max_reflist1_count = encoder->num_bframes > 0;
  ref_pool->max_ref_frames = ref_pool->max_reflist0_count
      + ref_pool->max_reflist1_count;

  reorder_pool = &encoder->reorder_pool;
  reorder_pool->frame_index = 0;
}

static GstVaapiEncoderStatus
gst_vaapi_encoder_h265_encode (GstVaapiEncoder * base_encoder,
    GstVaapiEncPicture * picture, GstVaapiCodedBufferProxy * codedbuf)
{
  GstVaapiEncoderH265 *const encoder = GST_VAAPI_ENCODER_H265 (base_encoder);
  GstVaapiEncoderStatus ret = GST_VAAPI_ENCODER_STATUS_ERROR_UNKNOWN;
  GstVaapiSurfaceProxy *reconstruct = NULL;

  reconstruct = gst_vaapi_encoder_create_surface (base_encoder);

  g_assert (GST_VAAPI_SURFACE_PROXY_SURFACE (reconstruct));

  if (!ensure_sequence (encoder, picture))
    goto error;
  if (!ensure_misc_params (encoder, picture))
    goto error;
  if (!ensure_picture (encoder, picture, codedbuf, reconstruct))
    goto error;
  if (!ensure_slices (encoder, picture))
    goto error;
  if (!gst_vaapi_enc_picture_encode (picture))
    goto error;

  if (!reference_list_update (encoder, picture, reconstruct))
    goto error;

  return GST_VAAPI_ENCODER_STATUS_SUCCESS;

  /* ERRORS */
error:
  {
    if (reconstruct)
      gst_vaapi_encoder_release_surface (GST_VAAPI_ENCODER (encoder),
          reconstruct);
    return ret;
  }
}

static GstVaapiEncoderStatus
gst_vaapi_encoder_h265_flush (GstVaapiEncoder * base_encoder)
{
  GstVaapiEncoderH265 *const encoder = GST_VAAPI_ENCODER_H265 (base_encoder);
  GstVaapiH265ReorderPool *reorder_pool;
  GstVaapiEncPicture *pic;

  reorder_pool = &encoder->reorder_pool;
  reorder_pool->frame_index = 0;
  reorder_pool->cur_present_index = 0;

  while (!g_queue_is_empty (&reorder_pool->reorder_frame_list)) {
    pic = (GstVaapiEncPicture *)
        g_queue_pop_head (&reorder_pool->reorder_frame_list);
    gst_vaapi_enc_picture_unref (pic);
  }
  g_queue_clear (&reorder_pool->reorder_frame_list);

  return GST_VAAPI_ENCODER_STATUS_SUCCESS;
}

/* Generate "codec-data" buffer */
static GstVaapiEncoderStatus
gst_vaapi_encoder_h265_get_codec_data (GstVaapiEncoder * base_encoder,
    GstBuffer ** out_buffer_ptr)
{
  GstVaapiEncoderH265 *const encoder = GST_VAAPI_ENCODER_H265 (base_encoder);
  const guint32 configuration_version = 0x01;
  const guint32 nal_length_size = 4;
  GstMapInfo vps_info, sps_info, pps_info;
  GstBitWriter bs;
  GstBuffer *buffer;
  guint min_spatial_segmentation_idc = 0;
  guint num_arrays = 3;

  if (!encoder->vps_data || !encoder->sps_data || !encoder->pps_data)
    return GST_VAAPI_ENCODER_STATUS_ERROR_INVALID_HEADER;
  if (gst_buffer_get_size (encoder->sps_data) < 4)
    return GST_VAAPI_ENCODER_STATUS_ERROR_INVALID_HEADER;

  if (!gst_buffer_map (encoder->vps_data, &vps_info, GST_MAP_READ))
    goto error_map_vps_buffer;

  if (!gst_buffer_map (encoder->sps_data, &sps_info, GST_MAP_READ))
    goto error_map_sps_buffer;

  if (!gst_buffer_map (encoder->pps_data, &pps_info, GST_MAP_READ))
    goto error_map_pps_buffer;

  /* Header */
  gst_bit_writer_init (&bs,
      (vps_info.size + sps_info.size + pps_info.size + 64) * 8);
  WRITE_UINT32 (&bs, configuration_version, 8);
  WRITE_UINT32 (&bs, sps_info.data[4], 8);      /* profile_space | tier_flag | profile_idc */
  WRITE_UINT32 (&bs, sps_info.data[5], 32);     /* profile_compatibility_flag [0-31] */
  /* progressive_source_flag | interlaced_source_flag | non_packed_constraint_flag |
   * frame_only_constraint_flag | reserved_zero_bits[0-27] */
  WRITE_UINT32 (&bs, sps_info.data[9], 32);
  WRITE_UINT32 (&bs, sps_info.data[13], 16);    /* reserved_zero_bits [28-43] */
  WRITE_UINT32 (&bs, sps_info.data[15], 8);     /* level_idc */
  WRITE_UINT32 (&bs, 0x0f, 4);  /* 1111 */
  WRITE_UINT32 (&bs, min_spatial_segmentation_idc, 12); /* min_spatial_segmentation_idc */
  WRITE_UINT32 (&bs, 0x3f, 6);  /* 111111 */
  WRITE_UINT32 (&bs, 0x00, 2);  /* parallelismType */
  WRITE_UINT32 (&bs, 0x3f, 6);  /* 111111 */
  WRITE_UINT32 (&bs, 0x01, 2);  /* chroma_format_idc */
  WRITE_UINT32 (&bs, 0x1f, 5);  /* 11111 */
  WRITE_UINT32 (&bs, 0x01, 3);  /* bit_depth_luma_minus8 */
  WRITE_UINT32 (&bs, 0x1f, 5);  /* 11111 */
  WRITE_UINT32 (&bs, 0x01, 3);  /* bit_depth_chroma_minus8 */
  WRITE_UINT32 (&bs, 0x00, 16); /* avgFramerate */
  WRITE_UINT32 (&bs, 0x00, 2);  /* constatnFramerate */
  WRITE_UINT32 (&bs, 0x00, 3);  /* numTemporalLayers */
  WRITE_UINT32 (&bs, 0x00, 1);  /* temporalIdNested */
  WRITE_UINT32 (&bs, nal_length_size - 1, 2);   /* lengthSizeMinusOne */
  WRITE_UINT32 (&bs, 0x00, 8);  /* numOfArrays */

  WRITE_UINT32 (&bs, num_arrays, 8);    /* numOfArrays */

  /* Write VPS */
  WRITE_UINT32 (&bs, 0x00, 1);  /* array_completeness */
  WRITE_UINT32 (&bs, 0x00, 1);  /* reserved zero */
  WRITE_UINT32 (&bs, GST_H265_NAL_VPS, 6);      /* Nal_unit_type */
  WRITE_UINT32 (&bs, 0x01, 16); /* numNalus, VPS count = 1 */
  g_assert (GST_BIT_WRITER_BIT_SIZE (&bs) % 8 == 0);
  /* Write Nal unit length and data of VPS */
  if (!gst_vaapi_utils_h26x_write_nal_unit (&bs, vps_info.data, vps_info.size))
    goto nal_to_byte_stream_error;

  /* Write SPS */
  WRITE_UINT32 (&bs, 0x00, 1);  /* array_completeness */
  WRITE_UINT32 (&bs, 0x00, 1);  /* reserved zero */
  WRITE_UINT32 (&bs, GST_H265_NAL_SPS, 6);      /* Nal_unit_type */
  WRITE_UINT32 (&bs, 0x01, 16); /* numNalus, SPS count = 1 */
  g_assert (GST_BIT_WRITER_BIT_SIZE (&bs) % 8 == 0);
  /* Write Nal unit length and data of SPS */
  if (!gst_vaapi_utils_h26x_write_nal_unit (&bs, sps_info.data, sps_info.size))
    goto nal_to_byte_stream_error;

  /* Write PPS */
  WRITE_UINT32 (&bs, 0x00, 1);  /* array_completeness */
  WRITE_UINT32 (&bs, 0x00, 1);  /* reserved zero */
  WRITE_UINT32 (&bs, GST_H265_NAL_PPS, 6);      /* Nal_unit_type */
  WRITE_UINT32 (&bs, 0x01, 16); /* numNalus, PPS count = 1 */
  /* Write Nal unit length and data of PPS */
  if (!gst_vaapi_utils_h26x_write_nal_unit (&bs, pps_info.data, pps_info.size))
    goto nal_to_byte_stream_error;

  gst_buffer_unmap (encoder->pps_data, &pps_info);
  gst_buffer_unmap (encoder->sps_data, &sps_info);
  gst_buffer_unmap (encoder->vps_data, &vps_info);

  buffer = gst_buffer_new_wrapped (GST_BIT_WRITER_DATA (&bs),
      GST_BIT_WRITER_BIT_SIZE (&bs) / 8);
  if (!buffer)
    goto error_alloc_buffer;
  *out_buffer_ptr = buffer;

  gst_bit_writer_clear (&bs, FALSE);
  return GST_VAAPI_ENCODER_STATUS_SUCCESS;

  /* ERRORS */
bs_error:
  {
    GST_ERROR ("failed to write codec-data");
    gst_buffer_unmap (encoder->vps_data, &vps_info);
    gst_buffer_unmap (encoder->sps_data, &sps_info);
    gst_buffer_unmap (encoder->pps_data, &pps_info);
    gst_bit_writer_clear (&bs, TRUE);
    return GST_VAAPI_ENCODER_STATUS_ERROR_OPERATION_FAILED;
  }
nal_to_byte_stream_error:
  {
    GST_ERROR ("failed to write nal unit");
    gst_buffer_unmap (encoder->vps_data, &vps_info);
    gst_buffer_unmap (encoder->sps_data, &sps_info);
    gst_buffer_unmap (encoder->pps_data, &pps_info);
    gst_bit_writer_clear (&bs, TRUE);
    return GST_VAAPI_ENCODER_STATUS_ERROR_OPERATION_FAILED;
  }
error_map_vps_buffer:
  {
    GST_ERROR ("failed to map VPS packed header");
    return GST_VAAPI_ENCODER_STATUS_ERROR_ALLOCATION_FAILED;
  }
error_map_sps_buffer:
  {
    GST_ERROR ("failed to map SPS packed header");
    return GST_VAAPI_ENCODER_STATUS_ERROR_ALLOCATION_FAILED;
  }
error_map_pps_buffer:
  {
    GST_ERROR ("failed to map PPS packed header");
    gst_buffer_unmap (encoder->sps_data, &sps_info);
    return GST_VAAPI_ENCODER_STATUS_ERROR_ALLOCATION_FAILED;
  }
error_alloc_buffer:
  {
    GST_ERROR ("failed to allocate codec-data buffer");
    gst_bit_writer_clear (&bs, TRUE);
    return GST_VAAPI_ENCODER_STATUS_ERROR_ALLOCATION_FAILED;
  }
}

/* TODO */
/* The re-ordering algorithm is similar to what we implemented for
 * h264 encoder. But We could have a better algorithm for hevc encoder
 * by having B-frames as reference pictures */
static GstVaapiEncoderStatus
gst_vaapi_encoder_h265_reordering (GstVaapiEncoder * base_encoder,
    GstVideoCodecFrame * frame, GstVaapiEncPicture ** output)
{
  GstVaapiEncoderH265 *const encoder = GST_VAAPI_ENCODER_H265 (base_encoder);
  GstVaapiH265ReorderPool *reorder_pool = NULL;
  GstVaapiEncPicture *picture;
  gboolean is_idr = FALSE;

  *output = NULL;

  reorder_pool = &encoder->reorder_pool;

  if (!frame) {
    if (reorder_pool->reorder_state != GST_VAAPI_ENC_H265_REORD_DUMP_FRAMES)
      return GST_VAAPI_ENCODER_STATUS_NO_SURFACE;

    /* reorder_state = GST_VAAPI_ENC_H265_REORD_DUMP_FRAMES
       dump B frames from queue, sometime, there may also have P frame or I frame */
    g_assert (encoder->num_bframes > 0);
    g_return_val_if_fail (!g_queue_is_empty (&reorder_pool->reorder_frame_list),
        GST_VAAPI_ENCODER_STATUS_ERROR_UNKNOWN);
    picture = g_queue_pop_head (&reorder_pool->reorder_frame_list);
    g_assert (picture);
    if (g_queue_is_empty (&reorder_pool->reorder_frame_list)) {
      reorder_pool->reorder_state = GST_VAAPI_ENC_H265_REORD_WAIT_FRAMES;
    }
    goto end;
  }

  /* new frame coming */
  picture = GST_VAAPI_ENC_PICTURE_NEW (HEVC, encoder, frame);
  if (!picture) {
    GST_WARNING ("create H265 picture failed, frame timestamp:%"
        GST_TIME_FORMAT, GST_TIME_ARGS (frame->pts));
    return GST_VAAPI_ENCODER_STATUS_ERROR_ALLOCATION_FAILED;
  }
  ++reorder_pool->cur_present_index;
  picture->poc = ((reorder_pool->cur_present_index * 1) %
      encoder->max_pic_order_cnt);

  is_idr = (reorder_pool->frame_index == 0 ||
      reorder_pool->frame_index >= encoder->idr_period);

  /* check key frames */
  if (is_idr || GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame) ||
      (reorder_pool->frame_index %
          GST_VAAPI_ENCODER_KEYFRAME_PERIOD (encoder)) == 0) {
    ++reorder_pool->frame_index;

    /* b frame enabled,  check queue of reorder_frame_list */
    if (encoder->num_bframes
        && !g_queue_is_empty (&reorder_pool->reorder_frame_list)) {
      GstVaapiEncPicture *p_pic;

      p_pic = g_queue_pop_tail (&reorder_pool->reorder_frame_list);
      set_p_frame (p_pic, encoder);
      g_queue_foreach (&reorder_pool->reorder_frame_list,
          (GFunc) set_b_frame, encoder);
      set_key_frame (picture, encoder, is_idr);
      g_queue_push_tail (&reorder_pool->reorder_frame_list, picture);
      picture = p_pic;
      reorder_pool->reorder_state = GST_VAAPI_ENC_H265_REORD_DUMP_FRAMES;
    } else {                    /* no b frames in queue */
      set_key_frame (picture, encoder, is_idr);
      g_assert (g_queue_is_empty (&reorder_pool->reorder_frame_list));
      if (encoder->num_bframes)
        reorder_pool->reorder_state = GST_VAAPI_ENC_H265_REORD_WAIT_FRAMES;
    }
    goto end;
  }

  /* new p/b frames coming */
  ++reorder_pool->frame_index;
  if (reorder_pool->reorder_state == GST_VAAPI_ENC_H265_REORD_WAIT_FRAMES &&
      g_queue_get_length (&reorder_pool->reorder_frame_list) <
      encoder->num_bframes) {
    g_queue_push_tail (&reorder_pool->reorder_frame_list, picture);
    return GST_VAAPI_ENCODER_STATUS_NO_SURFACE;
  }

  set_p_frame (picture, encoder);

  if (reorder_pool->reorder_state == GST_VAAPI_ENC_H265_REORD_WAIT_FRAMES) {
    g_queue_foreach (&reorder_pool->reorder_frame_list, (GFunc) set_b_frame,
        encoder);
    reorder_pool->reorder_state = GST_VAAPI_ENC_H265_REORD_DUMP_FRAMES;
    g_assert (!g_queue_is_empty (&reorder_pool->reorder_frame_list));
  }

end:
  g_assert (picture);
  frame = picture->frame;
  if (GST_CLOCK_TIME_IS_VALID (frame->pts))
    frame->pts += encoder->cts_offset;
  *output = picture;

  return GST_VAAPI_ENCODER_STATUS_SUCCESS;
}

static GstVaapiEncoderStatus
set_context_info (GstVaapiEncoder * base_encoder)
{
  GstVaapiEncoderH265 *const encoder = GST_VAAPI_ENCODER_H265 (base_encoder);
  GstVideoInfo *const vip = GST_VAAPI_ENCODER_VIDEO_INFO (encoder);
  const guint DEFAULT_SURFACES_COUNT = 3;

  /* FIXME: Using only a rough approximation for bitstream headers.
   * Not taken into account: ScalingList, RefPicListModification,
   * PredWeightTable */
  /* Maximum sizes for common headers (in bits) */
  enum
  {
    MAX_PROFILE_TIER_LEVEL_SIZE = 684,
    MAX_VPS_HDR_SIZE = 13781,
    MAX_SPS_HDR_SIZE = 615,
    MAX_SHORT_TERM_REFPICSET_SIZE = 55,
    MAX_VUI_PARAMS_SIZE = 267,
    MAX_HRD_PARAMS_SIZE = 8196,
    MAX_PPS_HDR_SIZE = 274,
    MAX_SLICE_HDR_SIZE = 33660
  };

  /* Account for VPS header */
  base_encoder->codedbuf_size += 4 + GST_ROUND_UP_8 (MAX_VPS_HDR_SIZE +
      MAX_PROFILE_TIER_LEVEL_SIZE + MAX_HRD_PARAMS_SIZE) / 8;

  /* Account for SPS header */
  base_encoder->codedbuf_size += 4 + GST_ROUND_UP_8 (MAX_SPS_HDR_SIZE +
      MAX_PROFILE_TIER_LEVEL_SIZE + 64 * MAX_SHORT_TERM_REFPICSET_SIZE +
      MAX_VUI_PARAMS_SIZE + MAX_HRD_PARAMS_SIZE) / 8;

  /* Account for PPS header */
  base_encoder->codedbuf_size += 4 + GST_ROUND_UP_8 (MAX_PPS_HDR_SIZE) / 8;

  /* Account for slice header */
  base_encoder->codedbuf_size += encoder->num_slices * (4 +
      GST_ROUND_UP_8 (MAX_SLICE_HDR_SIZE + MAX_SHORT_TERM_REFPICSET_SIZE) / 8);

  if (!ensure_hw_profile (encoder))
    return GST_VAAPI_ENCODER_STATUS_ERROR_UNSUPPORTED_PROFILE;

  base_encoder->num_ref_frames = (encoder->num_ref_frames
      + (encoder->num_bframes > 0 ? 1 : 0) + DEFAULT_SURFACES_COUNT);

  /* Only YUV 4:2:0 formats are supported for now. */
  base_encoder->codedbuf_size += GST_ROUND_UP_32 (vip->width) *
      GST_ROUND_UP_32 (vip->height) * 3 / 2;

  return GST_VAAPI_ENCODER_STATUS_SUCCESS;
}

static GstVaapiEncoderStatus
gst_vaapi_encoder_h265_reconfigure (GstVaapiEncoder * base_encoder)
{
  GstVaapiEncoderH265 *const encoder = GST_VAAPI_ENCODER_H265 (base_encoder);
  GstVaapiEncoderStatus status;
  guint luma_width, luma_height;

  luma_width = GST_VAAPI_ENCODER_WIDTH (encoder);
  luma_height = GST_VAAPI_ENCODER_HEIGHT (encoder);

  if (luma_width != encoder->luma_width || luma_height != encoder->luma_height) {
    GST_DEBUG ("resolution: %d %d", GST_VAAPI_ENCODER_WIDTH (encoder),
        GST_VAAPI_ENCODER_HEIGHT (encoder));
    encoder->luma_width = GST_ROUND_UP_32 (luma_width);
    encoder->luma_height = GST_ROUND_UP_32 (luma_height);
    encoder->ctu_width = (encoder->luma_width + 31) / 32;
    encoder->ctu_height = (encoder->luma_height + 31) / 32;
    encoder->config_changed = TRUE;

    /* Frame Cropping */
    if ((GST_VAAPI_ENCODER_WIDTH (encoder) & 31) ||
        (GST_VAAPI_ENCODER_HEIGHT (encoder) & 31)) {
      static const guint SubWidthC[] = { 1, 2, 2, 1 };
      static const guint SubHeightC[] = { 1, 2, 1, 1 };
      encoder->conformance_window_flag = 1;
      encoder->conf_win_left_offset = 0;
      encoder->conf_win_right_offset =
          (encoder->luma_width -
          GST_VAAPI_ENCODER_WIDTH (encoder)) / SubWidthC[1];
      encoder->conf_win_top_offset = 0;
      encoder->conf_win_bottom_offset =
          (encoder->luma_height -
          GST_VAAPI_ENCODER_HEIGHT (encoder)) / SubHeightC[1];
    }
  }

  status = ensure_profile_tier_level (encoder);
  if (status != GST_VAAPI_ENCODER_STATUS_SUCCESS)
    return status;

  reset_properties (encoder);
  ensure_control_rate_params (encoder);
  return set_context_info (base_encoder);
}

static gboolean
gst_vaapi_encoder_h265_init (GstVaapiEncoder * base_encoder)
{
  GstVaapiEncoderH265 *const encoder = GST_VAAPI_ENCODER_H265 (base_encoder);
  GstVaapiH265ReorderPool *reorder_pool;
  GstVaapiH265RefPool *ref_pool;

  encoder->conformance_window_flag = 0;
  encoder->num_slices = 1;

  /* re-ordering  list initialize */
  reorder_pool = &encoder->reorder_pool;
  g_queue_init (&reorder_pool->reorder_frame_list);
  reorder_pool->reorder_state = GST_VAAPI_ENC_H265_REORD_NONE;
  reorder_pool->frame_index = 0;
  reorder_pool->cur_present_index = 0;

  /* reference list info initialize */
  ref_pool = &encoder->ref_pool;
  g_queue_init (&ref_pool->ref_list);
  ref_pool->max_ref_frames = 0;
  ref_pool->max_reflist0_count = 1;
  ref_pool->max_reflist1_count = 1;

  return TRUE;
}

static void
gst_vaapi_encoder_h265_finalize (GstVaapiEncoder * base_encoder)
{
  /*free private buffers */
  GstVaapiEncoderH265 *const encoder = GST_VAAPI_ENCODER_H265 (base_encoder);
  GstVaapiEncPicture *pic;
  GstVaapiEncoderH265Ref *ref;
  GstVaapiH265RefPool *ref_pool;
  GstVaapiH265ReorderPool *reorder_pool;

  gst_buffer_replace (&encoder->vps_data, NULL);
  gst_buffer_replace (&encoder->sps_data, NULL);
  gst_buffer_replace (&encoder->pps_data, NULL);

  /* reference list info de-init */
  ref_pool = &encoder->ref_pool;
  while (!g_queue_is_empty (&ref_pool->ref_list)) {
    ref = (GstVaapiEncoderH265Ref *) g_queue_pop_head (&ref_pool->ref_list);
    reference_pic_free (encoder, ref);
  }
  g_queue_clear (&ref_pool->ref_list);

  /* re-ordering  list initialize */
  reorder_pool = &encoder->reorder_pool;
  while (!g_queue_is_empty (&reorder_pool->reorder_frame_list)) {
    pic = (GstVaapiEncPicture *)
        g_queue_pop_head (&reorder_pool->reorder_frame_list);
    gst_vaapi_enc_picture_unref (pic);
  }
  g_queue_clear (&reorder_pool->reorder_frame_list);
}

static GstVaapiEncoderStatus
gst_vaapi_encoder_h265_set_property (GstVaapiEncoder * base_encoder,
    gint prop_id, const GValue * value)
{
  GstVaapiEncoderH265 *const encoder = GST_VAAPI_ENCODER_H265 (base_encoder);

  switch (prop_id) {
    case GST_VAAPI_ENCODER_H265_PROP_MAX_BFRAMES:
      encoder->num_bframes = g_value_get_uint (value);
      break;
    case GST_VAAPI_ENCODER_H265_PROP_INIT_QP:
      encoder->init_qp = g_value_get_uint (value);
      break;
    case GST_VAAPI_ENCODER_H265_PROP_MIN_QP:
      encoder->min_qp = g_value_get_uint (value);
      break;
    case GST_VAAPI_ENCODER_H265_PROP_QP_IP:
      encoder->qp_ip = g_value_get_int (value);
      break;
    case GST_VAAPI_ENCODER_H265_PROP_QP_IB:
      encoder->qp_ib = g_value_get_int (value);
      break;
    case GST_VAAPI_ENCODER_H265_PROP_NUM_SLICES:
      encoder->num_slices = g_value_get_uint (value);
      break;
    case GST_VAAPI_ENCODER_H265_PROP_CPB_LENGTH:
      encoder->cpb_length = g_value_get_uint (value);
      break;
    case GST_VAAPI_ENCODER_H265_PROP_NUM_REF_FRAMES:
      encoder->num_ref_frames = g_value_get_uint (value);
      break;
    case GST_VAAPI_ENCODER_H265_PROP_MBBRC:
      encoder->mbbrc = g_value_get_enum (value);
      break;
    default:
      return GST_VAAPI_ENCODER_STATUS_ERROR_INVALID_PARAMETER;
  }
  return GST_VAAPI_ENCODER_STATUS_SUCCESS;
}

GST_VAAPI_ENCODER_DEFINE_CLASS_DATA (H265);

static inline const GstVaapiEncoderClass *
gst_vaapi_encoder_h265_class (void)
{
  static const GstVaapiEncoderClass GstVaapiEncoderH265Class = {
    GST_VAAPI_ENCODER_CLASS_INIT (H265, h265),
    .set_property = gst_vaapi_encoder_h265_set_property,
    .get_codec_data = gst_vaapi_encoder_h265_get_codec_data
  };
  return &GstVaapiEncoderH265Class;
}

/**
 * gst_vaapi_encoder_h265_new:
 * @display: a #GstVaapiDisplay
 *
 * Creates a new #GstVaapiEncoder for H.265 encoding. Note that the
 * only supported output stream format is "byte-stream" format.
 *
 * Return value: the newly allocated #GstVaapiEncoder object
 */
GstVaapiEncoder *
gst_vaapi_encoder_h265_new (GstVaapiDisplay * display)
{
  return gst_vaapi_encoder_new (gst_vaapi_encoder_h265_class (), display);
}

/**
 * gst_vaapi_encoder_h265_get_default_properties:
 *
 * Determines the set of common and H.265 specific encoder properties.
 * The caller owns an extra reference to the resulting array of
 * #GstVaapiEncoderPropInfo elements, so it shall be released with
 * g_ptr_array_unref() after usage.
 *
 * Return value: the set of encoder properties for #GstVaapiEncoderH265,
 *   or %NULL if an error occurred.
 */
GPtrArray *
gst_vaapi_encoder_h265_get_default_properties (void)
{
  const GstVaapiEncoderClass *const klass = gst_vaapi_encoder_h265_class ();
  GPtrArray *props;

  props = gst_vaapi_encoder_properties_get_default (klass);
  if (!props)
    return NULL;

  /**
   * GstVaapiEncoderH265:max-bframes:
   *
   * The number of B-frames between I and P.
   */
  GST_VAAPI_ENCODER_PROPERTIES_APPEND (props,
      GST_VAAPI_ENCODER_H265_PROP_MAX_BFRAMES,
      g_param_spec_uint ("max-bframes",
          "Max B-Frames", "Number of B-frames between I and P", 0, 10, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiEncoderH265:refs:
   *
   * The number of reference frames.
   * If B frame is encoded, it will add 1 reference frame more.
   */
  GST_VAAPI_ENCODER_PROPERTIES_APPEND (props,
      GST_VAAPI_ENCODER_H265_PROP_NUM_REF_FRAMES,
      g_param_spec_uint ("refs",
          "Number of Reference Frames", "Number of reference frames", 1, 3, 1,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiEncoderH265:init-qp:
   *
   * The initial quantizer value.
   */
  GST_VAAPI_ENCODER_PROPERTIES_APPEND (props,
      GST_VAAPI_ENCODER_H265_PROP_INIT_QP,
      g_param_spec_uint ("init-qp",
          "Initial QP", "Initial quantizer value", 1, 51, 26,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiEncoderH265:min-qp:
   *
   * The minimum quantizer value.
   */
  GST_VAAPI_ENCODER_PROPERTIES_APPEND (props,
      GST_VAAPI_ENCODER_H265_PROP_MIN_QP,
      g_param_spec_uint ("min-qp",
          "Minimum QP", "Minimum quantizer value", 1, 51, 1,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiEncoderH265:qp-ip:
   *
   * The difference of QP between I and P Frame.
   * This is available only on CQP mode.
   */
  GST_VAAPI_ENCODER_PROPERTIES_APPEND (props,
      GST_VAAPI_ENCODER_H265_PROP_QP_IP,
      g_param_spec_int ("qp-ip",
          "Difference of QP between I and P frame",
          "Difference of QP between I and P frame (available only on CQP)",
          -51, 51, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiEncoderH265:qp-ib:
   *
   * The difference of QP between I and B Frame.
   * This is available only on CQP mode.
   */
  GST_VAAPI_ENCODER_PROPERTIES_APPEND (props,
      GST_VAAPI_ENCODER_H265_PROP_QP_IB,
      g_param_spec_int ("qp-ib",
          "Difference of QP between I and B frame",
          "Difference of QP between I and B frame (available only on CQP)",
          -51, 51, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /* FIXME: there seems to be issues with multi-slice encoding */
  /**
   * GstVaapiEncoderH265:num-slices:
   *
   * The number of slices per frame.
   */
  GST_VAAPI_ENCODER_PROPERTIES_APPEND (props,
      GST_VAAPI_ENCODER_H265_PROP_NUM_SLICES,
      g_param_spec_uint ("num-slices",
          "Number of Slices",
          "Number of slices per frame",
          1, 200, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiEncoderH265:cpb-length:
   *
   * The size of the CPB buffer in milliseconds.
   */
  GST_VAAPI_ENCODER_PROPERTIES_APPEND (props,
      GST_VAAPI_ENCODER_H265_PROP_CPB_LENGTH,
      g_param_spec_uint ("cpb-length",
          "CPB Length", "Length of the CPB buffer in milliseconds",
          1, 10000, DEFAULT_CPB_LENGTH,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiEncoderH265:mbbrc:
   *
   * Macroblock level bitrate control.
   * This is not compatible with Constant QP rate control.
   */
  GST_VAAPI_ENCODER_PROPERTIES_APPEND (props,
      GST_VAAPI_ENCODER_H265_PROP_MBBRC,
      g_param_spec_enum ("mbbrc",
          "Macroblock level Bitrate Control",
          "Macroblock level Bitrate Control",
          GST_VAAPI_TYPE_ENCODER_MBBRC, GST_VAAPI_ENCODER_MBBRC_AUTO,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  return props;
}

/**
 * gst_vaapi_encoder_h265_set_max_profile:
 * @encoder: a #GstVaapiEncoderH265
 * @profile: an H.265 #GstVaapiProfile
 *
 * Notifies the @encoder to use coding tools from the supplied
 * @profile at most.
 *
 * This means that if the minimal profile derived to
 * support the specified coding tools is greater than this @profile,
 * then an error is returned when the @encoder is configured.
 *
 * Return value: %TRUE on success
 */
gboolean
gst_vaapi_encoder_h265_set_max_profile (GstVaapiEncoderH265 * encoder,
    GstVaapiProfile profile)
{
  guint8 profile_idc;

  g_return_val_if_fail (encoder != NULL, FALSE);
  g_return_val_if_fail (profile != GST_VAAPI_PROFILE_UNKNOWN, FALSE);

  if (gst_vaapi_profile_get_codec (profile) != GST_VAAPI_CODEC_H265)
    return FALSE;

  profile_idc = gst_vaapi_utils_h265_get_profile_idc (profile);
  if (!profile_idc)
    return FALSE;

  encoder->max_profile_idc = profile_idc;
  return TRUE;
}

/**
 * gst_vaapi_encoder_h265_get_profile_tier_level:
 * @encoder: a #GstVaapiEncoderH265
 * @out_profile_ptr: return location for the #GstVaapiProfile
 * @out_level_ptr: return location for the #GstVaapiLevelH265
 * @out_tier_ptr: return location for the #GstVaapiTierH265
 *
 * Queries the H.265 @encoder for the active profile and level. That
 * information is only constructed and valid after the encoder is
 * configured, i.e. after the gst_vaapi_encoder_set_codec_state()
 * function is called.
 *
 * Return value: %TRUE on success
 */
gboolean
gst_vaapi_encoder_h265_get_profile_tier_level (GstVaapiEncoderH265 * encoder,
    GstVaapiProfile * out_profile_ptr, GstVaapiTierH265 * out_tier_ptr,
    GstVaapiLevelH265 * out_level_ptr)
{
  g_return_val_if_fail (encoder != NULL, FALSE);

  if (!encoder->profile || !encoder->tier || !encoder->level)
    return FALSE;

  if (out_profile_ptr)
    *out_profile_ptr = encoder->profile;
  if (out_level_ptr)
    *out_level_ptr = encoder->level;
  if (out_tier_ptr)
    *out_tier_ptr = encoder->tier;

  return TRUE;
}
