/*
* Copyright(c) 2019 Intel Corporation
*     Authors: Jun Tian <jun.tian@intel.com> Xavier Hallade <xavier.hallade@intel.com>
* SPDX - License - Identifier: LGPL-2.1-or-later
*/

/**
 * SECTION:element-gstsvtav1enc
 *
 * The svtav1enc element does AV1 encoding using Scalable
 * Video Technology for AV1 Encoder (SVT-AV1 Encoder).
 *
 * <refsect2>
 * <title>Example launch line</title>
 * |[
 * gst-launch-1.0 -e videotestsrc ! video/x-raw ! svtav1enc ! matroskamux ! filesink location=out.mkv
 * ]|
 * Encodes test input into AV1 compressed data which is then packaged in out.mkv
 * </refsect2>
 */

#include "config.h"

#include "gstsvtav1enc.h"
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/video/gstvideoencoder.h>

#if !SVT_AV1_CHECK_VERSION(1,2,1)
#define SVT_AV1_RC_MODE_CQP_OR_CRF 0
#define SVT_AV1_RC_MODE_VBR 1
#define SVT_AV1_RC_MODE_CBR 2
#endif

GST_DEBUG_CATEGORY_STATIC (gst_svtav1enc_debug_category);
#define GST_CAT_DEFAULT gst_svtav1enc_debug_category

#define GST_SVTAV1ENC_TYPE_INTRA_REFRESH_TYPE (gst_svtav1enc_intra_refresh_type_get_type())
static GType
gst_svtav1enc_intra_refresh_type_get_type (void)
{
  static GType intra_refresh_type = 0;
  static const GEnumValue intra_refresh[] = {
    {SVT_AV1_FWDKF_REFRESH, "Open GOP", "CRA"},
    {SVT_AV1_KF_REFRESH, "Closed GOP", "IDR"},
    {0, NULL, NULL},
  };

  if (!intra_refresh_type) {
    intra_refresh_type =
        g_enum_register_static ("GstSvtAv1EncIntraRefreshType", intra_refresh);
  }
  return intra_refresh_type;
}

typedef struct _GstSvtAv1Enc
{
  GstVideoEncoder video_encoder;

  /* SVT-AV1 Encoder Handle */
  EbComponentType *svt_encoder;

  /* GStreamer Codec state */
  GstVideoCodecState *state;

  /* SVT-AV1 configuration */
  EbSvtAv1EncConfiguration *svt_config;
  /* Property values */
  guint preset;
  guint target_bitrate;
  guint max_bitrate;
  guint max_qp_allowed;
  guint min_qp_allowed;
  gint cqp, crf;
  guint maximum_buffer_size;
  gint intra_period_length;
  gint intra_refresh_type;
  gint logical_processors;
  gint target_socket;
  gchar *parameters_string;

  EbBufferHeaderType *input_buf;
} GstSvtAv1Enc;


/* prototypes */
static void gst_svtav1enc_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec);
static void gst_svtav1enc_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec);
static void gst_svtav1enc_finalize (GObject * object);

static void gst_svtav1enc_allocate_svt_buffers (GstSvtAv1Enc * svtav1enc);
static void gst_svtav1enc_deallocate_svt_buffers (GstSvtAv1Enc * svtav1enc);
static gboolean gst_svtav1enc_configure_svt (GstSvtAv1Enc * svtav1enc);
static GstFlowReturn gst_svtav1enc_encode (GstSvtAv1Enc * svtav1enc,
    GstVideoCodecFrame * frame);
static gboolean gst_svtav1enc_send_eos (GstSvtAv1Enc * svtav1enc);
static GstFlowReturn gst_svtav1enc_dequeue_encoded_frames (GstSvtAv1Enc *
    svtav1enc, gboolean closing_encoder, gboolean output_frames);

static gboolean gst_svtav1enc_open (GstVideoEncoder * encoder);
static gboolean gst_svtav1enc_close (GstVideoEncoder * encoder);
static gboolean gst_svtav1enc_start (GstVideoEncoder * encoder);
static gboolean gst_svtav1enc_stop (GstVideoEncoder * encoder);
static gboolean gst_svtav1enc_set_format (GstVideoEncoder * encoder,
    GstVideoCodecState * state);
static GstFlowReturn gst_svtav1enc_handle_frame (GstVideoEncoder * encoder,
    GstVideoCodecFrame * frame);
static GstFlowReturn gst_svtav1enc_finish (GstVideoEncoder * encoder);
static gboolean gst_svtav1enc_propose_allocation (GstVideoEncoder * encoder,
    GstQuery * query);
static gboolean gst_svtav1enc_flush (GstVideoEncoder * encoder);

static void gst_svtav1enc_parse_parameters_string (GstSvtAv1Enc * svtav1enc);

enum
{
  PROP_0,
  PROP_PRESET,
  PROP_TARGET_BITRATE,
  PROP_MAX_BITRATE,
  PROP_MAX_QP_ALLOWED,
  PROP_MIN_QP_ALLOWED,
  PROP_CQP,
  PROP_CRF,
  PROP_MAXIMUM_BUFFER_SIZE,
  PROP_INTRA_PERIOD_LENGTH,
  PROP_INTRA_REFRESH_TYPE,
  PROP_LOGICAL_PROCESSORS,
  PROP_TARGET_SOCKET,
  PROP_PARAMETERS_STRING,
};

#define PROP_PRESET_DEFAULT 10
#define PROP_TARGET_BITRATE_DEFAULT 0
#define PROP_MAX_BITRATE_DEFAULT 0
#define PROP_QP_MAX_QP_ALLOWED_DEFAULT 63
#define PROP_QP_MIN_QP_ALLOWED_DEFAULT 1
#define PROP_CQP_DEFAULT -1
#define PROP_CRF_DEFAULT 35
#define PROP_MAXIMUM_BUFFER_SIZE_DEFAULT 1000
#define PROP_INTRA_PERIOD_LENGTH_DEFAULT -2
#define PROP_INTRA_REFRESH_TYPE_DEFAULT SVT_AV1_KF_REFRESH
#define PROP_LOGICAL_PROCESSORS_DEFAULT 0
#define PROP_TARGET_SOCKET_DEFAULT -1
#define PROP_PARAMETERS_STRING_DEFAULT NULL

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define FORMAT_I420_10 "I420_10LE"
#else
#define FORMAT_I420_10 "I420_10BE"
#endif

/* pad templates */
static GstStaticPadTemplate gst_svtav1enc_sink_pad_template =
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-raw, " "format = (string) {I420, " FORMAT_I420_10
        "}, " "width = (int) [64, 16384], " "height = (int) [64, 8704], "
        "framerate = (fraction) [0, MAX]"));

static GstStaticPadTemplate gst_svtav1enc_src_pad_template =
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-av1, " "stream-format = (string) obu-stream, "
        "alignment = (string) tu, " "width = (int) [64, 16384], "
        "height = (int) [64, 8704], " "framerate = (fraction) [0, MAX]"));

/* class initialization */
G_DEFINE_TYPE_WITH_CODE (GstSvtAv1Enc, gst_svtav1enc, GST_TYPE_VIDEO_ENCODER,
    GST_DEBUG_CATEGORY_INIT (gst_svtav1enc_debug_category, "svtav1enc", 0,
        "SVT-AV1 encoder element"));

GST_ELEMENT_REGISTER_DEFINE (svtav1enc, "svtav1enc", GST_RANK_SECONDARY,
    gst_svtav1enc_get_type ());

/* this mutex is required to avoid race conditions in SVT-AV1 memory allocations, which aren't thread-safe */
G_LOCK_DEFINE_STATIC (init_mutex);

static void
gst_svtav1enc_class_init (GstSvtAv1EncClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstVideoEncoderClass *video_encoder_class = GST_VIDEO_ENCODER_CLASS (klass);

  gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
      &gst_svtav1enc_src_pad_template);

  gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
      &gst_svtav1enc_sink_pad_template);

  gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass),
      "SvtAv1Enc",
      "Codec/Encoder/Video",
      "Scalable Video Technology for AV1 Encoder (SVT-AV1 Encoder)",
      "Jun Tian <jun.tian@intel.com> Xavier Hallade <xavier.hallade@intel.com>");

  gobject_class->set_property = gst_svtav1enc_set_property;
  gobject_class->get_property = gst_svtav1enc_get_property;
  gobject_class->finalize = gst_svtav1enc_finalize;
  video_encoder_class->open = GST_DEBUG_FUNCPTR (gst_svtav1enc_open);
  video_encoder_class->close = GST_DEBUG_FUNCPTR (gst_svtav1enc_close);
  video_encoder_class->start = GST_DEBUG_FUNCPTR (gst_svtav1enc_start);
  video_encoder_class->stop = GST_DEBUG_FUNCPTR (gst_svtav1enc_stop);
  video_encoder_class->set_format =
      GST_DEBUG_FUNCPTR (gst_svtav1enc_set_format);
  video_encoder_class->handle_frame =
      GST_DEBUG_FUNCPTR (gst_svtav1enc_handle_frame);
  video_encoder_class->finish = GST_DEBUG_FUNCPTR (gst_svtav1enc_finish);
  video_encoder_class->propose_allocation =
      GST_DEBUG_FUNCPTR (gst_svtav1enc_propose_allocation);
  video_encoder_class->flush = GST_DEBUG_FUNCPTR (gst_svtav1enc_flush);

  g_object_class_install_property (gobject_class,
      PROP_PRESET,
      g_param_spec_uint ("preset",
          "Preset",
          "Quality vs density tradeoff point"
          " that the encoding is to be performed at"
          " (0 is the highest quality, 13 is the highest speed) ",
          0,
          13, PROP_PRESET_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_TARGET_BITRATE,
      g_param_spec_uint ("target-bitrate",
          "Target bitrate",
          "Target bitrate in kbits/sec. Enables CBR or VBR mode",
          0,
          100000,
          PROP_TARGET_BITRATE_DEFAULT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_MAX_BITRATE,
      g_param_spec_uint ("max-bitrate",
          "Maximum bitrate",
          "Maximum bitrate in kbits/sec. Enables VBR mode if a different "
          "target-bitrate is provided",
          0,
          100000,
          PROP_MAX_BITRATE_DEFAULT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
          GST_PARAM_MUTABLE_PLAYING));

  g_object_class_install_property (gobject_class,
      PROP_MAX_QP_ALLOWED,
      g_param_spec_uint ("max-qp-allowed",
          "Max Quantization parameter",
          "Maximum QP value allowed for rate control use"
          " Only used in CBR and VBR mode.",
          0,
          63, PROP_MAX_QP_ALLOWED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_MIN_QP_ALLOWED,
      g_param_spec_uint ("min-qp-allowed",
          "Min Quantization parameter",
          "Minimum QP value allowed for rate control use"
          " Only used in CBR and VBR mode.",
          0,
          63, PROP_MIN_QP_ALLOWED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_CQP,
      g_param_spec_int ("cqp",
          "Quantization parameter",
          "Quantization parameter used in CQP mode (-1 is disabled)",
          -1,
          63, PROP_CQP_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_CRF,
      g_param_spec_int ("crf",
          "Constant Rate Factor",
          "Quantization parameter used in CRF mode (-1 is disabled)",
          -1,
          63, PROP_CRF_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_MAXIMUM_BUFFER_SIZE,
      g_param_spec_uint ("maximum-buffer-size",
          "Maximum Buffer Size",
          "Maximum buffer size in milliseconds."
          " Only used in CBR mode.",
          20,
          10000,
          PROP_MAXIMUM_BUFFER_SIZE_DEFAULT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_INTRA_PERIOD_LENGTH,
      g_param_spec_int ("intra-period-length",
          "Intra Period Length",
          "Period of Intra Frames insertion (-2 is auto, -1 no updates)",
          -2,
          G_MAXINT,
          PROP_INTRA_PERIOD_LENGTH_DEFAULT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_INTRA_REFRESH_TYPE,
      g_param_spec_enum ("intra-refresh-type",
          "Intra refresh type",
          "CRA (open GOP)"
          "or IDR frames (closed GOP)",
          GST_SVTAV1ENC_TYPE_INTRA_REFRESH_TYPE,
          PROP_INTRA_REFRESH_TYPE_DEFAULT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_LOGICAL_PROCESSORS,
      g_param_spec_uint ("logical-processors",
          "Logical Processors",
          "Number of logical CPU cores to be used. 0: auto",
          0,
          G_MAXUINT,
          PROP_LOGICAL_PROCESSORS_DEFAULT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_TARGET_SOCKET,
      g_param_spec_int ("target-socket",
          "Target socket",
          "Target CPU socket to run on. -1: all available",
          -1,
          15,
          PROP_TARGET_SOCKET_DEFAULT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_PARAMETERS_STRING,
      g_param_spec_string ("parameters-string",
          "Parameters String",
          "Colon-delimited list of key=value pairs of additional parameters to set",
          PROP_PARAMETERS_STRING_DEFAULT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static void
gst_svtav1enc_init (GstSvtAv1Enc * svtav1enc)
{
  svtav1enc->svt_config = g_new0 (EbSvtAv1EncConfiguration, 1);
  svtav1enc->preset = PROP_PRESET_DEFAULT;
  svtav1enc->target_bitrate = PROP_TARGET_BITRATE_DEFAULT;
  svtav1enc->max_bitrate = PROP_MAX_BITRATE_DEFAULT;
  svtav1enc->max_qp_allowed = PROP_QP_MAX_QP_ALLOWED_DEFAULT;
  svtav1enc->min_qp_allowed = PROP_QP_MIN_QP_ALLOWED_DEFAULT;
  svtav1enc->cqp = PROP_CQP_DEFAULT;
  svtav1enc->crf = PROP_CRF_DEFAULT;
  svtav1enc->maximum_buffer_size = PROP_MAXIMUM_BUFFER_SIZE_DEFAULT;
  svtav1enc->intra_period_length = PROP_INTRA_PERIOD_LENGTH_DEFAULT;
  svtav1enc->intra_refresh_type = PROP_INTRA_REFRESH_TYPE_DEFAULT;
  svtav1enc->logical_processors = PROP_LOGICAL_PROCESSORS_DEFAULT;
  svtav1enc->target_socket = PROP_TARGET_SOCKET_DEFAULT;
  svtav1enc->parameters_string = PROP_PARAMETERS_STRING_DEFAULT;
}

static void
gst_svtav1enc_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (object);

  /* TODO: support reconfiguring on the fly when possible */
  if (svtav1enc->state) {
    GST_ERROR_OBJECT (svtav1enc,
        "encoder state has been set before properties, this isn't supported yet.");
    return;
  }

  GST_LOG_OBJECT (svtav1enc, "setting property %u", property_id);

  switch (property_id) {
    case PROP_PRESET:
      svtav1enc->preset = g_value_get_uint (value);
      break;
    case PROP_TARGET_BITRATE:
      svtav1enc->target_bitrate = g_value_get_uint (value) * 1000;
      break;
    case PROP_MAX_BITRATE:
      svtav1enc->max_bitrate = g_value_get_uint (value) * 1000;
      break;
    case PROP_MAX_QP_ALLOWED:
      svtav1enc->max_qp_allowed = g_value_get_uint (value);
      break;
    case PROP_MIN_QP_ALLOWED:
      svtav1enc->min_qp_allowed = g_value_get_uint (value);
      break;
    case PROP_CQP:
      svtav1enc->cqp = g_value_get_int (value);
      break;
    case PROP_CRF:
      svtav1enc->crf = g_value_get_int (value);
      break;
    case PROP_MAXIMUM_BUFFER_SIZE:
      svtav1enc->maximum_buffer_size = g_value_get_uint (value);
      break;
    case PROP_INTRA_PERIOD_LENGTH:
      svtav1enc->intra_period_length = g_value_get_int (value);
      break;
    case PROP_INTRA_REFRESH_TYPE:
      svtav1enc->intra_refresh_type = g_value_get_enum (value);
      break;
    case PROP_LOGICAL_PROCESSORS:
      svtav1enc->logical_processors = g_value_get_uint (value);
      break;
    case PROP_TARGET_SOCKET:
      svtav1enc->target_socket = g_value_get_int (value);
      break;
    case PROP_PARAMETERS_STRING:{
      g_free (svtav1enc->parameters_string);
      svtav1enc->parameters_string = g_value_dup_string (value);
      break;
    }
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
gst_svtav1enc_get_property (GObject * object, guint property_id, GValue * value,
    GParamSpec * pspec)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (object);

  GST_LOG_OBJECT (svtav1enc, "getting property %u", property_id);

  switch (property_id) {
    case PROP_PRESET:
      g_value_set_uint (value, svtav1enc->preset);
      break;
    case PROP_TARGET_BITRATE:
      g_value_set_uint (value, svtav1enc->target_bitrate / 1000);
      break;
    case PROP_MAX_BITRATE:
      g_value_set_uint (value, svtav1enc->max_bitrate / 1000);
      break;
    case PROP_MAX_QP_ALLOWED:
      g_value_set_uint (value, svtav1enc->max_qp_allowed);
      break;
    case PROP_MIN_QP_ALLOWED:
      g_value_set_uint (value, svtav1enc->min_qp_allowed);
      break;
    case PROP_CQP:
      g_value_set_int (value, svtav1enc->cqp);
      break;
    case PROP_CRF:
      g_value_set_int (value, svtav1enc->crf);
      break;
    case PROP_MAXIMUM_BUFFER_SIZE:
      g_value_set_uint (value, svtav1enc->maximum_buffer_size);
      break;
    case PROP_INTRA_PERIOD_LENGTH:
      g_value_set_int (value, svtav1enc->intra_period_length);
      break;
    case PROP_INTRA_REFRESH_TYPE:
      g_value_set_enum (value, svtav1enc->intra_refresh_type);
      break;
    case PROP_LOGICAL_PROCESSORS:
      g_value_set_uint (value, svtav1enc->logical_processors);
      break;
    case PROP_TARGET_SOCKET:
      g_value_set_int (value, svtav1enc->target_socket);
      break;
    case PROP_PARAMETERS_STRING:
      g_value_set_string (value, svtav1enc->parameters_string);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
gst_svtav1enc_finalize (GObject * object)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (object);

  GST_DEBUG_OBJECT (svtav1enc, "finalizing svtav1enc");

  g_free (svtav1enc->svt_config);
  g_free (svtav1enc->parameters_string);

  G_OBJECT_CLASS (gst_svtav1enc_parent_class)->finalize (object);
}

static void
gst_svtav1enc_allocate_svt_buffers (GstSvtAv1Enc * svtav1enc)
{
  svtav1enc->input_buf = g_new0 (EbBufferHeaderType, 1);
  svtav1enc->input_buf->p_buffer = (guint8 *) g_new0 (EbSvtIOFormat, 1);
  svtav1enc->input_buf->size = sizeof (EbBufferHeaderType);
  svtav1enc->input_buf->p_app_private = NULL;
  svtav1enc->input_buf->pic_type = EB_AV1_INVALID_PICTURE;
  svtav1enc->input_buf->metadata = NULL;
}

static void
gst_svtav1enc_deallocate_svt_buffers (GstSvtAv1Enc * svtav1enc)
{
  if (svtav1enc->input_buf) {
    g_free (svtav1enc->input_buf->p_buffer);
    svtav1enc->input_buf->p_buffer = NULL;
    g_free (svtav1enc->input_buf);
    svtav1enc->input_buf = NULL;
  }
}

static gboolean
gst_svtav1enc_configure_svt (GstSvtAv1Enc * svtav1enc)
{
  if (!svtav1enc->state) {
    GST_WARNING_OBJECT (svtav1enc, "no state, can't configure encoder yet");
    return FALSE;
  }

  /* set object properties */
  svtav1enc->svt_config->enc_mode = svtav1enc->preset;
  if (svtav1enc->target_bitrate != 0) {
    svtav1enc->svt_config->target_bit_rate = svtav1enc->target_bitrate;
    if (svtav1enc->target_bitrate != svtav1enc->max_bitrate) {
      GST_DEBUG_OBJECT (svtav1enc,
          "Enabling VBR mode (br %u max-br %u max-qp %u min-qp %u)",
          svtav1enc->target_bitrate,
          svtav1enc->max_bitrate,
          svtav1enc->max_qp_allowed, svtav1enc->min_qp_allowed);
      svtav1enc->svt_config->max_bit_rate = svtav1enc->max_bitrate;
      svtav1enc->svt_config->rate_control_mode = SVT_AV1_RC_MODE_VBR;
    } else {
      GST_DEBUG_OBJECT (svtav1enc,
          "Enabling CBR mode (br %u max-bs %u)",
          svtav1enc->target_bitrate, svtav1enc->maximum_buffer_size);
      svtav1enc->svt_config->rate_control_mode = SVT_AV1_RC_MODE_CBR;
      svtav1enc->svt_config->maximum_buffer_size_ms =
          svtav1enc->maximum_buffer_size;
    }
    svtav1enc->svt_config->max_qp_allowed = svtav1enc->max_qp_allowed;
    svtav1enc->svt_config->min_qp_allowed = svtav1enc->min_qp_allowed;
    svtav1enc->svt_config->force_key_frames = FALSE;
  } else if (svtav1enc->crf > 0) {
    GST_DEBUG_OBJECT (svtav1enc, "Enabling CRF mode (qp %u)", svtav1enc->crf);
    svtav1enc->svt_config->qp = svtav1enc->crf;
    svtav1enc->svt_config->rate_control_mode = SVT_AV1_RC_MODE_CQP_OR_CRF;
    svtav1enc->svt_config->force_key_frames = TRUE;
  } else if (svtav1enc->cqp > 0) {
    GST_DEBUG_OBJECT (svtav1enc, "Enabling CQP mode (qp %u)", svtav1enc->cqp);
    svtav1enc->svt_config->qp = svtav1enc->cqp;
    svtav1enc->svt_config->rate_control_mode = SVT_AV1_RC_MODE_CQP_OR_CRF;
    svtav1enc->svt_config->enable_adaptive_quantization = FALSE;
    svtav1enc->svt_config->force_key_frames = TRUE;
  } else {
    GST_DEBUG_OBJECT (svtav1enc, "Using default rate control settings");
  }
  svtav1enc->svt_config->intra_period_length = svtav1enc->intra_period_length;
  svtav1enc->svt_config->intra_refresh_type = svtav1enc->intra_refresh_type;
  svtav1enc->svt_config->logical_processors = svtav1enc->logical_processors;
  svtav1enc->svt_config->target_socket = svtav1enc->target_socket;
  gst_svtav1enc_parse_parameters_string (svtav1enc);

  /* set properties out of GstVideoInfo */
  const GstVideoInfo *info = &svtav1enc->state->info;
  svtav1enc->svt_config->encoder_bit_depth =
      GST_VIDEO_INFO_COMP_DEPTH (info, 0);
  svtav1enc->svt_config->source_width = GST_VIDEO_INFO_WIDTH (info);
  svtav1enc->svt_config->source_height = GST_VIDEO_INFO_HEIGHT (info);
  svtav1enc->svt_config->frame_rate_numerator = GST_VIDEO_INFO_FPS_N (info) > 0
      ? GST_VIDEO_INFO_FPS_N (info)
      : 1;
  svtav1enc->svt_config->frame_rate_denominator =
      GST_VIDEO_INFO_FPS_D (info) > 0 ? GST_VIDEO_INFO_FPS_D (info)
      : 1;
  GST_LOG_OBJECT (svtav1enc,
      "width %d, height %d, framerate %d/%d",
      svtav1enc->svt_config->source_width,
      svtav1enc->svt_config->source_height,
      svtav1enc->svt_config->frame_rate_numerator,
      svtav1enc->svt_config->frame_rate_denominator);

  switch (GST_VIDEO_INFO_COLORIMETRY (info).primaries) {
    case GST_VIDEO_COLOR_PRIMARIES_BT709:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_BT_709;
      break;
    case GST_VIDEO_COLOR_PRIMARIES_BT470M:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_BT_470_M;
      break;
    case GST_VIDEO_COLOR_PRIMARIES_BT470BG:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_BT_470_B_G;
      break;
    case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_BT_601;
      break;
    case GST_VIDEO_COLOR_PRIMARIES_SMPTE240M:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_SMPTE_240;
      break;
    case GST_VIDEO_COLOR_PRIMARIES_FILM:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_GENERIC_FILM;
      break;
    case GST_VIDEO_COLOR_PRIMARIES_BT2020:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_BT_2020;
      break;
    case GST_VIDEO_COLOR_PRIMARIES_SMPTERP431:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_SMPTE_431;
      break;
    case GST_VIDEO_COLOR_PRIMARIES_SMPTEEG432:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_SMPTE_432;
      break;
    case GST_VIDEO_COLOR_PRIMARIES_EBU3213:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_EBU_3213;
      break;
    default:
      svtav1enc->svt_config->color_primaries = EB_CICP_CP_UNSPECIFIED;
      break;
  }

  switch (GST_VIDEO_INFO_COLORIMETRY (info).transfer) {
    case GST_VIDEO_TRANSFER_BT709:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_BT_709;
      break;
    case GST_VIDEO_TRANSFER_GAMMA28:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_BT_470_B_G;
      break;
    case GST_VIDEO_TRANSFER_BT601:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_BT_601;
      break;
    case GST_VIDEO_TRANSFER_SMPTE240M:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_SMPTE_240;
      break;
    case GST_VIDEO_TRANSFER_GAMMA10:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_LINEAR;
      break;
    case GST_VIDEO_TRANSFER_LOG100:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_LOG_100;
      break;
    case GST_VIDEO_TRANSFER_LOG316:
      svtav1enc->svt_config->transfer_characteristics =
          EB_CICP_TC_LOG_100_SQRT10;
      break;
    case GST_VIDEO_TRANSFER_SRGB:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_SRGB;
      break;
    case GST_VIDEO_TRANSFER_BT2020_10:
      svtav1enc->svt_config->transfer_characteristics =
          EB_CICP_TC_BT_2020_10_BIT;
      break;
    case GST_VIDEO_TRANSFER_BT2020_12:
      svtav1enc->svt_config->transfer_characteristics =
          EB_CICP_TC_BT_2020_12_BIT;
      break;
    case GST_VIDEO_TRANSFER_SMPTE2084:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_SMPTE_2084;
      break;
    case GST_VIDEO_TRANSFER_ARIB_STD_B67:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_HLG;
      break;
    default:
      svtav1enc->svt_config->transfer_characteristics = EB_CICP_TC_UNSPECIFIED;
      break;
  }

  switch (GST_VIDEO_INFO_COLORIMETRY (info).matrix) {
    case GST_VIDEO_COLOR_MATRIX_RGB:
      svtav1enc->svt_config->matrix_coefficients = EB_CICP_MC_IDENTITY;
      break;
    case GST_VIDEO_COLOR_MATRIX_BT709:
      svtav1enc->svt_config->matrix_coefficients = EB_CICP_MC_BT_709;
      break;
    case GST_VIDEO_COLOR_MATRIX_FCC:
      svtav1enc->svt_config->matrix_coefficients = EB_CICP_MC_FCC;
      break;
    case GST_VIDEO_COLOR_MATRIX_BT601:
      svtav1enc->svt_config->matrix_coefficients = EB_CICP_MC_BT_601;
      break;
    case GST_VIDEO_COLOR_MATRIX_SMPTE240M:
      svtav1enc->svt_config->matrix_coefficients = EB_CICP_MC_SMPTE_240;
      break;
    case GST_VIDEO_COLOR_MATRIX_BT2020:
      svtav1enc->svt_config->matrix_coefficients = EB_CICP_MC_BT_2020_NCL;
      break;

    default:
      svtav1enc->svt_config->matrix_coefficients = EB_CICP_MC_UNSPECIFIED;
      break;
  }

  if (GST_VIDEO_INFO_COLORIMETRY (info).range == GST_VIDEO_COLOR_RANGE_0_255) {
    svtav1enc->svt_config->color_range = EB_CR_FULL_RANGE;
  } else {
    svtav1enc->svt_config->color_range = EB_CR_STUDIO_RANGE;
  }

  switch (GST_VIDEO_INFO_CHROMA_SITE (info)) {
    case GST_VIDEO_CHROMA_SITE_V_COSITED:
      svtav1enc->svt_config->chroma_sample_position = EB_CSP_VERTICAL;
      break;
    case GST_VIDEO_CHROMA_SITE_COSITED:
      svtav1enc->svt_config->chroma_sample_position = EB_CSP_COLOCATED;
      break;
    default:
      svtav1enc->svt_config->chroma_sample_position = EB_CSP_UNKNOWN;
  }

  GstVideoMasteringDisplayInfo master_display_info;
  if (gst_video_mastering_display_info_from_caps (&master_display_info,
          svtav1enc->state->caps)) {
    svtav1enc->svt_config->mastering_display.r.x =
        master_display_info.display_primaries[0].x;
    svtav1enc->svt_config->mastering_display.r.y =
        master_display_info.display_primaries[0].y;
    svtav1enc->svt_config->mastering_display.g.x =
        master_display_info.display_primaries[1].x;
    svtav1enc->svt_config->mastering_display.g.y =
        master_display_info.display_primaries[1].y;
    svtav1enc->svt_config->mastering_display.b.x =
        master_display_info.display_primaries[2].x;
    svtav1enc->svt_config->mastering_display.b.y =
        master_display_info.display_primaries[2].y;
    svtav1enc->svt_config->mastering_display.white_point.x =
        master_display_info.white_point.x;
    svtav1enc->svt_config->mastering_display.white_point.y =
        master_display_info.white_point.y;
    svtav1enc->svt_config->mastering_display.max_luma =
        master_display_info.max_display_mastering_luminance;
    svtav1enc->svt_config->mastering_display.min_luma =
        master_display_info.min_display_mastering_luminance;
    svtav1enc->svt_config->high_dynamic_range_input = TRUE;
  } else {
    memset (&svtav1enc->svt_config->mastering_display,
        0, sizeof (svtav1enc->svt_config->mastering_display));
    svtav1enc->svt_config->high_dynamic_range_input = FALSE;
  }

  GstVideoContentLightLevel content_light_level;
  if (gst_video_content_light_level_from_caps (&content_light_level,
          svtav1enc->state->caps)) {
    svtav1enc->svt_config->content_light_level.max_cll =
        content_light_level.max_content_light_level;
    svtav1enc->svt_config->content_light_level.max_fall =
        content_light_level.max_frame_average_light_level;
  } else {
    memset (&svtav1enc->svt_config->content_light_level,
        0, sizeof (svtav1enc->svt_config->content_light_level));
  }

  EbErrorType res =
      svt_av1_enc_set_parameter (svtav1enc->svt_encoder, svtav1enc->svt_config);
  if (res != EB_ErrorNone) {
    GST_ELEMENT_ERROR (svtav1enc,
        LIBRARY,
        INIT, (NULL), ("svt_av1_enc_set_parameter failed with error %d", res));
    return FALSE;
  }
  return TRUE;
}

static gboolean
gst_svtav1enc_start_svt (GstSvtAv1Enc * svtav1enc)
{
  G_LOCK (init_mutex);
  EbErrorType res = svt_av1_enc_init (svtav1enc->svt_encoder);
  G_UNLOCK (init_mutex);

  if (res != EB_ErrorNone) {
    GST_ELEMENT_ERROR (svtav1enc, LIBRARY, INIT, (NULL),
        ("svt_av1_enc_init failed with error %d", res));
    return FALSE;
  }
  return TRUE;
}

static GstFlowReturn
gst_svtav1enc_encode (GstSvtAv1Enc * svtav1enc, GstVideoCodecFrame * frame)
{
  GstFlowReturn ret = GST_FLOW_OK;
  EbErrorType res = EB_ErrorNone;
  EbBufferHeaderType *input_buffer = svtav1enc->input_buf;
  EbSvtIOFormat *input_picture_buffer =
      (EbSvtIOFormat *) svtav1enc->input_buf->p_buffer;
  GstVideoFrame video_frame;

  if (!gst_video_frame_map (&video_frame, &svtav1enc->state->info,
          frame->input_buffer, GST_MAP_READ)) {
    GST_ELEMENT_ERROR (svtav1enc, LIBRARY, ENCODE, (NULL),
        ("couldn't map input frame"));
    return GST_FLOW_ERROR;
  }

  input_picture_buffer->y_stride =
      GST_VIDEO_FRAME_COMP_STRIDE (&video_frame,
      0) / GST_VIDEO_FRAME_COMP_PSTRIDE (&video_frame, 0);
  input_picture_buffer->cb_stride =
      GST_VIDEO_FRAME_COMP_STRIDE (&video_frame,
      1) / GST_VIDEO_FRAME_COMP_PSTRIDE (&video_frame, 1);
  input_picture_buffer->cr_stride =
      GST_VIDEO_FRAME_COMP_STRIDE (&video_frame,
      2) / GST_VIDEO_FRAME_COMP_PSTRIDE (&video_frame, 2);

  input_picture_buffer->luma = GST_VIDEO_FRAME_PLANE_DATA (&video_frame, 0);
  input_picture_buffer->cb = GST_VIDEO_FRAME_PLANE_DATA (&video_frame, 1);
  input_picture_buffer->cr = GST_VIDEO_FRAME_PLANE_DATA (&video_frame, 2);

  input_buffer->n_filled_len = GST_VIDEO_FRAME_SIZE (&video_frame);

  /* Fill in Buffers Header control data */
  input_buffer->flags = 0;
  input_buffer->p_app_private = NULL;
  input_buffer->pts = frame->pts;
  input_buffer->pic_type = EB_AV1_INVALID_PICTURE;

  if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame)) {
    input_buffer->pic_type = EB_AV1_KEY_PICTURE;
  }

  input_buffer->metadata = NULL;

  res = svt_av1_enc_send_picture (svtav1enc->svt_encoder, input_buffer);
  if (res != EB_ErrorNone) {
    GST_ELEMENT_ERROR (svtav1enc, LIBRARY, ENCODE, (NULL),
        ("error in sending picture to encoder"));
    ret = GST_FLOW_ERROR;
  }
  gst_video_frame_unmap (&video_frame);

  return ret;
}

static gboolean
gst_svtav1enc_send_eos (GstSvtAv1Enc * svtav1enc)
{
  EbErrorType ret = EB_ErrorNone;

  EbBufferHeaderType input_buffer;
  input_buffer.n_alloc_len = 0;
  input_buffer.n_filled_len = 0;
  input_buffer.n_tick_count = 0;
  input_buffer.p_app_private = NULL;
  input_buffer.flags = EB_BUFFERFLAG_EOS;
  input_buffer.p_buffer = NULL;
  input_buffer.metadata = NULL;

  GST_DEBUG_OBJECT (svtav1enc, "send eos");

  ret = svt_av1_enc_send_picture (svtav1enc->svt_encoder, &input_buffer);

  if (ret != EB_ErrorNone) {
    GST_ELEMENT_ERROR (svtav1enc, LIBRARY, ENCODE, (NULL),
        ("couldn't send EOS frame."));
    return FALSE;
  }

  return (ret == EB_ErrorNone);
}

static gboolean
gst_svtav1enc_flush (GstVideoEncoder * encoder)
{
  GstFlowReturn ret =
      gst_svtav1enc_dequeue_encoded_frames (GST_SVTAV1ENC (encoder), TRUE,
      FALSE);

  return (ret != GST_FLOW_ERROR);
}

static GstFlowReturn
gst_svtav1enc_dequeue_encoded_frames (GstSvtAv1Enc * svtav1enc,
    gboolean done_sending_pics, gboolean output_frames)
{
  GstFlowReturn ret = GST_FLOW_OK;
  EbErrorType res = EB_ErrorNone;
  gboolean encode_at_eos = FALSE;

  GST_DEBUG_OBJECT (svtav1enc, "dequeue encoded frames");

  do {
    GstVideoCodecFrame *frame = NULL;
    EbBufferHeaderType *output_buf = NULL;

    res =
        svt_av1_enc_get_packet (svtav1enc->svt_encoder, &output_buf,
        done_sending_pics);

    if (output_buf != NULL)
      encode_at_eos =
          ((output_buf->flags & EB_BUFFERFLAG_EOS) == EB_BUFFERFLAG_EOS);

    if (res == EB_ErrorMax) {
      GST_ELEMENT_ERROR (svtav1enc, LIBRARY, ENCODE, (NULL), ("encode failed"));
      return GST_FLOW_ERROR;
    } else if (res != EB_NoErrorEmptyQueue && output_frames && output_buf) {
      // AV1 has no frame re-ordering so always get the oldest frame
      frame =
          gst_video_encoder_get_oldest_frame (GST_VIDEO_ENCODER (svtav1enc));
      if (output_buf->pic_type == EB_AV1_KEY_PICTURE
          || output_buf->pic_type == EB_AV1_INTRA_ONLY_PICTURE) {
        GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
      }

      if ((ret =
              gst_video_encoder_allocate_output_frame (GST_VIDEO_ENCODER
                  (svtav1enc), frame,
                  output_buf->n_filled_len)) != GST_FLOW_OK) {
        svt_av1_enc_release_out_buffer (&output_buf);
        gst_video_codec_frame_unref (frame);
        return ret;
      }
      gst_buffer_fill (frame->output_buffer, 0, output_buf->p_buffer,
          output_buf->n_filled_len);

      frame->pts = frame->output_buffer->pts = output_buf->pts;

      GST_LOG_OBJECT (svtav1enc,
          "#frame:%u pts:%" G_GINT64_FORMAT " SliceType:%d\n",
          frame->system_frame_number, (frame->pts), output_buf->pic_type);

      svt_av1_enc_release_out_buffer (&output_buf);
      output_buf = NULL;

      ret =
          gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (svtav1enc), frame);
    }

  } while (res == EB_ErrorNone && !encode_at_eos && ret == GST_FLOW_OK);

  return ret;
}

static gboolean
gst_svtav1enc_open (GstVideoEncoder * encoder)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (encoder);

  GST_DEBUG_OBJECT (svtav1enc, "open");

  EbErrorType res = svt_av1_enc_init_handle (&svtav1enc->svt_encoder, NULL,
      svtav1enc->svt_config);
  if (res != EB_ErrorNone) {
    GST_ELEMENT_ERROR (svtav1enc,
        LIBRARY,
        INIT, (NULL), ("svt_av1_enc_init_handle failed with error %d", res));
    return FALSE;
  }

  return TRUE;
}

static gboolean
gst_svtav1enc_close (GstVideoEncoder * encoder)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (encoder);

  GST_DEBUG_OBJECT (svtav1enc, "close");

  svt_av1_enc_deinit_handle (svtav1enc->svt_encoder);
  svtav1enc->svt_encoder = NULL;
  return TRUE;
}

static gboolean
gst_svtav1enc_start (GstVideoEncoder * encoder)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (encoder);

  GST_DEBUG_OBJECT (svtav1enc, "start");

  gst_svtav1enc_allocate_svt_buffers (svtav1enc);
  return TRUE;
}

static gboolean
gst_svtav1enc_stop (GstVideoEncoder * encoder)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (encoder);

  GST_DEBUG_OBJECT (svtav1enc, "stop");

  if (svtav1enc->state)
    gst_video_codec_state_unref (svtav1enc->state);
  svtav1enc->state = NULL;

  svt_av1_enc_deinit (svtav1enc->svt_encoder);
  gst_svtav1enc_deallocate_svt_buffers (svtav1enc);

  return TRUE;
}

static gboolean
gst_svtav1enc_set_format (GstVideoEncoder * encoder, GstVideoCodecState * state)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (encoder);
  GstClockTime min_latency_frames = 0;
  GstCaps *src_caps = NULL;
  GstVideoCodecState *output_state;
  GST_DEBUG_OBJECT (svtav1enc, "set_format");

  if (svtav1enc->state
      && !gst_video_info_is_equal (&svtav1enc->state->info, &state->info)) {
    gst_svtav1enc_finish (encoder);
    gst_svtav1enc_stop (encoder);
    gst_svtav1enc_close (encoder);
    gst_svtav1enc_open (encoder);
    gst_svtav1enc_start (encoder);
  }
  svtav1enc->state = gst_video_codec_state_ref (state);

  if (!gst_svtav1enc_configure_svt (svtav1enc))
    return FALSE;
  if (!gst_svtav1enc_start_svt (svtav1enc))
    return FALSE;

  guint32 fps = svtav1enc->svt_config->frame_rate_numerator /
      svtav1enc->svt_config->frame_rate_denominator;
  fps = fps > 120 ? 120 : fps;
  fps = fps < 24 ? 24 : fps;

  min_latency_frames = ((fps * 5) >> 2);

  gst_video_encoder_set_latency (encoder,
      min_latency_frames * GST_SECOND /
      (svtav1enc->svt_config->frame_rate_numerator /
          svtav1enc->svt_config->frame_rate_denominator), -1);

  src_caps = gst_static_pad_template_get_caps (&gst_svtav1enc_src_pad_template);
  output_state =
      gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (encoder), src_caps,
      svtav1enc->state);
  gst_video_codec_state_unref (output_state);

  GST_DEBUG_OBJECT (svtav1enc, "output caps: %" GST_PTR_FORMAT,
      svtav1enc->state->caps);

  return gst_video_encoder_negotiate (encoder);
}

static GstFlowReturn
gst_svtav1enc_handle_frame (GstVideoEncoder * encoder,
    GstVideoCodecFrame * frame)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (encoder);
  GstFlowReturn ret = GST_FLOW_OK;

  GST_DEBUG_OBJECT (svtav1enc, "handle_frame");

  ret = gst_svtav1enc_encode (svtav1enc, frame);
  gst_video_codec_frame_unref (frame);
  if (ret != GST_FLOW_OK) {
    GST_DEBUG_OBJECT (svtav1enc, "gst_svtav1enc_encode returned %d", ret);
    return ret;
  }

  return gst_svtav1enc_dequeue_encoded_frames (svtav1enc, FALSE, TRUE);
}

static GstFlowReturn
gst_svtav1enc_finish (GstVideoEncoder * encoder)
{
  GstFlowReturn ret = GST_FLOW_OK;
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (encoder);

  GST_DEBUG_OBJECT (svtav1enc, "finish");

  if (svtav1enc->state) {
    gst_svtav1enc_send_eos (svtav1enc);
    ret = gst_svtav1enc_dequeue_encoded_frames (svtav1enc, TRUE, TRUE);
  }

  return ret;
}

static gboolean
gst_svtav1enc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
{
  GstSvtAv1Enc *svtav1enc = GST_SVTAV1ENC (encoder);

  GST_DEBUG_OBJECT (svtav1enc, "propose_allocation");

  gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);

  return
      GST_VIDEO_ENCODER_CLASS (gst_svtav1enc_parent_class)->propose_allocation
      (encoder, query);
}

static void
gst_svtav1enc_parse_parameters_string (GstSvtAv1Enc * svtav1enc)
{
  gchar **key_values, **p;

  if (!svtav1enc->parameters_string)
    return;

  p = key_values = g_strsplit (svtav1enc->parameters_string, ":", -1);
  while (p && *p) {
    gchar *equals;
    EbErrorType res;

    equals = strchr (*p, '=');
    if (!equals) {
      p++;
      continue;
    }

    *equals = '\0';
    equals++;

    GST_DEBUG_OBJECT (svtav1enc, "Setting parameter %s=%s", *p, equals);

    res = svt_av1_enc_parse_parameter (svtav1enc->svt_config, *p, equals);
    if (res != EB_ErrorNone) {
      GST_WARNING_OBJECT (svtav1enc, "Failed to set parameter %s=%s: %d", *p,
          equals, res);
    }

    p++;
  }

  g_strfreev (key_values);
}

static gboolean
plugin_init (GstPlugin * plugin)
{
  return GST_ELEMENT_REGISTER (svtav1enc, plugin);
}

GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, svtav1,
    "Scalable Video Technology for AV1 (SVT-AV1)", plugin_init,
    VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN)
