svthevcenc: Add new SVT-HEVC encoder element
authorYeongjin Jeong <yeongjin.jeong@navercorp.com>
Sat, 23 Mar 2019 12:01:51 +0000 (21:01 +0900)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Fri, 20 Dec 2019 15:43:55 +0000 (15:43 +0000)
The SVT-HEVC (Scalable Video Technology[0] for HEVC) Encoder is an
open source video coding technology[1] that is highly optimized for
Intel Xeon Scalable processors and Intel Xeon D processors.

[0] https://01.org/svt
[1] https://github.com/OpenVisualCloud/SVT-HEVC

ext/meson.build
ext/svthevcenc/gstsvthevcenc.c [new file with mode: 0644]
ext/svthevcenc/gstsvthevcenc.h [new file with mode: 0644]
ext/svthevcenc/meson.build [new file with mode: 0644]
meson_options.txt
tests/check/elements/svthevcenc.c [new file with mode: 0644]
tests/check/meson.build

index cd7c657..ab8ec43 100644 (file)
@@ -52,6 +52,7 @@ subdir('soundtouch')
 subdir('spandsp')
 subdir('srt')
 subdir('srtp')
+subdir('svthevcenc')
 subdir('teletextdec')
 subdir('ttml')
 subdir('voaacenc')
diff --git a/ext/svthevcenc/gstsvthevcenc.c b/ext/svthevcenc/gstsvthevcenc.c
new file mode 100644 (file)
index 0000000..72ac308
--- /dev/null
@@ -0,0 +1,2273 @@
+/* GStreamer H265 encoder plugin
+ * Copyright (C) 2019 Yeongjin Jeong <yeongjin.jeong@navercorp.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-svthevcenc
+ * @title: svthevcenc
+ *
+ * This element encodes raw video into H265 compressed data.
+ *
+ **/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "gstsvthevcenc.h"
+
+#include <gst/pbutils/pbutils.h>
+#include <gst/video/video.h>
+#include <gst/video/gstvideometa.h>
+#include <gst/video/gstvideopool.h>
+#include <gst/base/gstbitreader.h>
+#include <gst/codecparsers/gsth265parser.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+GST_DEBUG_CATEGORY_STATIC (svthevc_enc_debug);
+#define GST_CAT_DEFAULT svthevc_enc_debug
+
+enum
+{
+  PROP_0,
+  PROP_INSERT_VUI,
+  PROP_AUD,
+  PROP_HIERARCHICAL_LEVEL,
+  PROP_LOOKAHEAD_DISTANCE,
+  PROP_ENCODER_MODE,
+  PROP_RC_MODE,
+  PROP_QP_I,
+  PROP_QP_MAX,
+  PROP_QP_MIN,
+  PROP_SCENE_CHANGE_DETECTION,
+  PROP_TUNE,
+  PROP_BASE_LAYER_SWITCH_MODE,
+  PROP_BITRATE,
+  PROP_KEY_INT_MAX,
+  PROP_ENABLE_OPEN_GOP,
+  PROP_CONFIG_INTERVAL,
+  PROP_CORES,
+  PROP_SOCKET,
+  PROP_TILE_ROW,
+  PROP_TILE_COL,
+  PROP_PRED_STRUCTURE,
+  PROP_VBV_MAX_RATE,
+  PROP_VBV_BUFFER_SIZE,
+};
+
+#define PROP_INSERT_VUI_DEFAULT             FALSE
+#define PROP_AUD_DEFAULT                    FALSE
+#define PROP_HIERARCHICAL_LEVEL_DEFAULT     GST_SVTHEVC_ENC_B_PYRAMID_4LEVEL_HIERARCHY
+#define PROP_LOOKAHEAD_DISTANCE_DEFAULT     40
+#define PROP_ENCODER_MODE_DEFAULT           7
+#define PROP_RC_MODE_DEFAULT                GST_SVTHEVC_ENC_RC_CQP
+#define PROP_QP_I_DEFAULT                   25
+#define PROP_QP_MAX_DEFAULT                 48
+#define PROP_QP_MIN_DEFAULT                 10
+#define PROP_SCENE_CHANGE_DETECTION_DEFAULT TRUE
+#define PROP_TUNE_DEFAULT                   GST_SVTHEVC_ENC_TUNE_OQ
+#define PROP_BASE_LAYER_SWITCH_MODE_DEFAULT GST_SVTHEVC_ENC_BASE_LAYER_MODE_BFRAME
+#define PROP_BITRATE_DEFAULT                (7 * 1000)
+#define PROP_KEY_INT_MAX_DEFAULT            -2
+#define PROP_ENABLE_OPEN_GOP_DEFAULT        TRUE
+#define PROP_CONFIG_INTERVAL_DEFAULT        0
+#define PROP_CORES_DEFAULT                  0
+#define PROP_SOCKET_DEFAULT                 -1
+#define PROP_TILE_ROW_DEFAULT               1
+#define PROP_TILE_COL_DEFAULT               1
+#define PROP_PRED_STRUCTURE_DEFAULT         GST_SVTHEVC_ENC_PRED_STRUCT_RANDOM_ACCESS
+#define PROP_VBV_MAX_RATE_DEFAULT           0
+#define PROP_VBV_BUFFER_SIZE_DEFAULT        0
+
+#define PROFILE_DEFAULT                     2
+#define LEVEL_DEFAULT                       0
+#define TIER_DEFAULT                        0
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+#define FORMATS "I420, Y42B, Y444, I420_10LE, I422_10LE, Y444_10LE"
+#else
+#define FORMATS "I420, Y42B, Y444, I420_10BE, I422_10BE, Y444_10BE"
+#endif
+
+#define GST_SVTHEVC_ENC_B_PYRAMID_TYPE (gst_svthevc_enc_b_pyramid_get_type())
+static GType
+gst_svthevc_enc_b_pyramid_get_type (void)
+{
+  static GType b_pyramid_type = 0;
+
+  static const GEnumValue b_pyramid_types[] = {
+    {GST_SVTHEVC_ENC_B_PYRAMID_FLAT, "Flat", "flat"},
+    {GST_SVTHEVC_ENC_B_PYRAMID_2LEVEL_HIERARCHY, "2-Level Hierarchy",
+        "2-level-hierarchy"},
+    {GST_SVTHEVC_ENC_B_PYRAMID_3LEVEL_HIERARCHY, "3-Level Hierarchy",
+        "3-level-hierarchy"},
+    {GST_SVTHEVC_ENC_B_PYRAMID_4LEVEL_HIERARCHY, "4-Level Hierarchy",
+        "4-level-hierarchy"},
+    {0, NULL, NULL}
+  };
+
+  if (!b_pyramid_type) {
+    b_pyramid_type =
+        g_enum_register_static ("GstSvtHevcEncBPyramid", b_pyramid_types);
+  }
+  return b_pyramid_type;
+}
+
+#define GST_SVTHEVC_ENC_BASE_LAYER_MODE_TYPE (gst_svthevc_enc_base_layer_mode_get_type())
+static GType
+gst_svthevc_enc_base_layer_mode_get_type (void)
+{
+  static GType base_layer_mode_type = 0;
+
+  static const GEnumValue base_layer_mode_types[] = {
+    {GST_SVTHEVC_ENC_BASE_LAYER_MODE_BFRAME,
+          "Use B-frames in the base layer pointing to the same past picture",
+        "B-frame"},
+    {GST_SVTHEVC_ENC_BASE_LAYER_MODE_PFRAME, "Use P-frames in the base layer",
+        "P-frame"},
+    {0, NULL, NULL}
+  };
+
+  if (!base_layer_mode_type) {
+    base_layer_mode_type =
+        g_enum_register_static ("GstSvtHevcEncBaseLayerMode",
+        base_layer_mode_types);
+  }
+  return base_layer_mode_type;
+}
+
+#define GST_SVTHEVC_ENC_RC_TYPE (gst_svthevc_enc_rc_get_type())
+static GType
+gst_svthevc_enc_rc_get_type (void)
+{
+  static GType rc_type = 0;
+
+  static const GEnumValue rc_types[] = {
+    {GST_SVTHEVC_ENC_RC_CQP, "Constant QP Control", "cqp"},
+    {GST_SVTHEVC_ENC_RC_VBR, "Variable Bitrate Contorol", "vbr"},
+    {0, NULL, NULL}
+  };
+
+  if (!rc_type) {
+    rc_type = g_enum_register_static ("GstSvtHevcEncRC", rc_types);
+  }
+  return rc_type;
+}
+
+#define GST_SVTHEVC_ENC_TUNE_TYPE (gst_svthevc_enc_tune_get_type())
+static GType
+gst_svthevc_enc_tune_get_type (void)
+{
+  static GType tune_type = 0;
+
+  static const GEnumValue tune_types[] = {
+    {GST_SVTHEVC_ENC_TUNE_SQ, "Visually Optimized Mode", "sq"},
+    {GST_SVTHEVC_ENC_TUNE_OQ, "PSNR/SSIM Optimized Mode", "oq"},
+    {GST_SVTHEVC_ENC_TUNE_VMAF, "VMAF Optimized Mode", "vmaf"},
+    {0, NULL, NULL}
+  };
+
+  if (!tune_type) {
+    tune_type = g_enum_register_static ("GstSvtHevcEncTune", tune_types);
+  }
+  return tune_type;
+}
+
+#define GST_SVTHEVC_ENC_PRED_STRUCT_TYPE (gst_svthevc_enc_pred_struct_get_type())
+static GType
+gst_svthevc_enc_pred_struct_get_type (void)
+{
+  static GType pred_struct_type = 0;
+
+  static const GEnumValue pred_struct_types[] = {
+    {GST_SVTHEVC_ENC_PRED_STRUCT_LOW_DELAY_P,
+        "Low Delay Prediction Structure with P/p pictures", "low-delay-P"},
+    {GST_SVTHEVC_ENC_PRED_STRUCT_LOW_DELAY_B,
+        "Low Delay Prediction Structure with B/b pictures", "low-delay-B"},
+    {GST_SVTHEVC_ENC_PRED_STRUCT_RANDOM_ACCESS,
+        "Random Access Prediction Structure", "random-access"},
+    {0, NULL, NULL}
+  };
+
+  if (!pred_struct_type) {
+    pred_struct_type =
+        g_enum_register_static ("GstSvtHevcEncPredStruct", pred_struct_types);
+  }
+  return pred_struct_type;
+}
+
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-raw, "
+        "format = (string) { " FORMATS " }, "
+        "framerate = (fraction) [0, MAX], "
+        "width = (int) [ 64, 8192 ], " "height = (int) [ 64, 4320 ]")
+    );
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-h265, "
+        "framerate = (fraction) [0/1, MAX], "
+        "width = (int) [ 64, 8192 ], " "height = (int) [ 64, 4320 ], "
+        "stream-format = (string) byte-stream, "
+        "alignment = (string) au, "
+        "profile = (string) { main, main-10, main-422-10, main-444, main-444-10 }")
+    );
+
+static void gst_svthevc_enc_finalize (GObject * object);
+static gboolean gst_svthevc_enc_start (GstVideoEncoder * encoder);
+static gboolean gst_svthevc_enc_stop (GstVideoEncoder * encoder);
+static gboolean gst_svthevc_enc_flush (GstVideoEncoder * encoder);
+
+static gboolean gst_svthevc_enc_init_encoder (GstSvtHevcEnc * encoder);
+static void gst_svthevc_enc_close_encoder (GstSvtHevcEnc * encoder);
+
+static GstFlowReturn gst_svthevc_enc_finish (GstVideoEncoder * encoder);
+static GstFlowReturn gst_svthevc_enc_handle_frame (GstVideoEncoder * encoder,
+    GstVideoCodecFrame * frame);
+static GstFlowReturn gst_svthevc_enc_drain_encoder (GstSvtHevcEnc * encoder,
+    gboolean send);
+static GstFlowReturn gst_svthevc_enc_send_frame (GstSvtHevcEnc * encoder,
+    GstVideoCodecFrame * frame);
+static GstFlowReturn gst_svthevc_enc_receive_frame (GstSvtHevcEnc * encoder,
+    gboolean * got_packet, gboolean send);
+static gboolean gst_svthevc_enc_set_format (GstVideoEncoder * video_enc,
+    GstVideoCodecState * state);
+static gboolean gst_svthevc_enc_propose_allocation (GstVideoEncoder * encoder,
+    GstQuery * query);
+
+static void gst_svthevc_enc_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_svthevc_enc_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+#define gst_svthevc_enc_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstSvtHevcEnc, gst_svthevc_enc, GST_TYPE_VIDEO_ENCODER,
+    G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, NULL));
+
+#define MAX_FORMAT_COUNT 6
+typedef struct
+{
+  const GstH265Profile gst_profile;
+  const guint svt_profile;
+  const GstVideoFormat formats[MAX_FORMAT_COUNT];
+} GstSvtHevcEncProfileTable;
+
+static const GstSvtHevcEncProfileTable profile_table[] = {
+  {GST_H265_PROFILE_MAIN, 1, {GST_VIDEO_FORMAT_I420,}},
+  {GST_H265_PROFILE_MAIN_444, 4, {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_Y42B,
+          GST_VIDEO_FORMAT_Y444,}},
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+  {GST_H265_PROFILE_MAIN_10, 2, {GST_VIDEO_FORMAT_I420,
+          GST_VIDEO_FORMAT_I420_10LE,}},
+  {GST_H265_PROFILE_MAIN_422_10, 4, {GST_VIDEO_FORMAT_I420,
+              GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_I420_10LE,
+          GST_VIDEO_FORMAT_I422_10LE,}},
+  {GST_H265_PROFILE_MAIN_444_10, 4, {GST_VIDEO_FORMAT_I420,
+              GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_Y444,
+              GST_VIDEO_FORMAT_I420_10LE, GST_VIDEO_FORMAT_I422_10LE,
+          GST_VIDEO_FORMAT_Y444_10LE}}
+#else
+  {GST_H265_PROFILE_MAIN_10, 2, {GST_VIDEO_FORMAT_I420,
+          GST_VIDEO_FORMAT_I420_10BE,}},
+  {GST_H265_PROFILE_MAIN_422_10, 4, {GST_VIDEO_FORMAT_I420,
+              GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_I420_10BE,
+          GST_VIDEO_FORMAT_I422_10BE,}},
+  {GST_H265_PROFILE_MAIN_444_10, 4, {GST_VIDEO_FORMAT_I420,
+              GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_Y444,
+              GST_VIDEO_FORMAT_I420_10BE, GST_VIDEO_FORMAT_I422_10BE,
+          GST_VIDEO_FORMAT_Y444_10BE}}
+#endif
+};
+
+static void
+set_array_val (GArray * arr, guint index, guint val)
+{
+  if (!arr)
+    return;
+
+  if (index >= arr->len)
+    g_array_set_size (arr, index + 1);
+  arr->data[index] = val;
+}
+
+static void
+get_support_format_from_profile (GArray * formats, const gchar * profile_str)
+{
+  GstH265Profile profile = gst_h265_profile_from_string (profile_str);
+  guint i, j;
+
+  if (!formats)
+    return;
+
+  for (i = 0; i < G_N_ELEMENTS (profile_table); i++) {
+    if (profile_table[i].gst_profile == profile) {
+      for (j = 0; j < MAX_FORMAT_COUNT; j++) {
+        if (profile_table[i].formats[j] > GST_VIDEO_FORMAT_UNKNOWN)
+          set_array_val (formats, profile_table[i].formats[j], 1);
+      }
+      break;
+    }
+  }
+}
+
+static void
+get_compatible_profile_from_format (GArray * profiles,
+    const GstVideoFormat format)
+{
+  guint i, j;
+
+  if (!profiles)
+    return;
+
+  for (i = 0; i < G_N_ELEMENTS (profile_table); i++) {
+    for (j = 0; j < MAX_FORMAT_COUNT; j++) {
+      if (profile_table[i].formats[j] == format) {
+        set_array_val (profiles, profile_table[i].gst_profile, 1);
+      }
+    }
+  }
+}
+
+static GstCaps *
+gst_svthevc_enc_sink_getcaps (GstVideoEncoder * enc, GstCaps * filter)
+{
+  GstCaps *supported_incaps;
+  GstCaps *allowed_caps;
+  GstCaps *filter_caps, *fcaps;
+  gint i, j, k;
+
+  supported_incaps = gst_static_pad_template_get_caps (&sink_factory);
+
+  allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (enc));
+
+  if (!allowed_caps || gst_caps_is_empty (allowed_caps)
+      || gst_caps_is_any (allowed_caps)) {
+    fcaps = supported_incaps;
+    goto done;
+  }
+
+  GST_LOG_OBJECT (enc, "template caps %" GST_PTR_FORMAT, supported_incaps);
+  GST_LOG_OBJECT (enc, "allowed caps %" GST_PTR_FORMAT, allowed_caps);
+
+  filter_caps = gst_caps_new_empty ();
+
+  for (i = 0; i < gst_caps_get_size (supported_incaps); i++) {
+    GQuark q_name =
+        gst_structure_get_name_id (gst_caps_get_structure (supported_incaps,
+            i));
+
+    for (j = 0; j < gst_caps_get_size (allowed_caps); j++) {
+      const GstStructure *allowed_s = gst_caps_get_structure (allowed_caps, j);
+      const GValue *val;
+      GstStructure *s;
+
+      s = gst_structure_new_id_empty (q_name);
+      if ((val = gst_structure_get_value (allowed_s, "width")))
+        gst_structure_set_value (s, "width", val);
+      if ((val = gst_structure_get_value (allowed_s, "height")))
+        gst_structure_set_value (s, "height", val);
+
+      if ((val = gst_structure_get_value (allowed_s, "profile"))) {
+        GArray *formats = g_array_new (FALSE, TRUE, sizeof (guint));
+        GValue fmts = G_VALUE_INIT;
+        GValue fmt = G_VALUE_INIT;
+        guint i;
+
+        g_value_init (&fmts, GST_TYPE_LIST);
+        g_value_init (&fmt, G_TYPE_STRING);
+
+        if (G_VALUE_HOLDS_STRING (val)) {
+          get_support_format_from_profile (formats, g_value_get_string (val));
+        } else if (GST_VALUE_HOLDS_LIST (val)) {
+          for (k = 0; k < gst_value_list_get_size (val); k++) {
+            const GValue *vlist = gst_value_list_get_value (val, k);
+
+            if (G_VALUE_HOLDS_STRING (vlist))
+              get_support_format_from_profile (formats,
+                  g_value_get_string (vlist));
+          }
+        }
+
+        for (i = 0; i < formats->len; i++) {
+          if (formats->data[i]) {
+            g_value_set_string (&fmt,
+                gst_video_format_to_string ((GstVideoFormat) i));
+            gst_value_list_append_value (&fmts, &fmt);
+          }
+        }
+
+        g_array_free (formats, TRUE);
+
+        if (gst_value_list_get_size (&fmts) != 0)
+          gst_structure_take_value (s, "format", &fmts);
+        else
+          g_value_unset (&fmts);
+
+        g_value_unset (&fmt);
+      }
+
+      filter_caps = gst_caps_merge_structure (filter_caps, s);
+    }
+  }
+
+  fcaps = gst_caps_intersect (filter_caps, supported_incaps);
+  gst_caps_unref (filter_caps);
+  gst_caps_unref (supported_incaps);
+
+  if (filter) {
+    GST_LOG_OBJECT (enc, "intersecting with %" GST_PTR_FORMAT, filter);
+    filter_caps = gst_caps_intersect (fcaps, filter);
+    gst_caps_unref (fcaps);
+    fcaps = filter_caps;
+  }
+
+done:
+  if (allowed_caps)
+    gst_caps_unref (allowed_caps);
+
+  GST_LOG_OBJECT (enc, "proxy caps %" GST_PTR_FORMAT, fcaps);
+
+  return fcaps;
+}
+
+static void
+gst_svthevc_enc_class_init (GstSvtHevcEncClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *element_class;
+  GstVideoEncoderClass *gstencoder_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  element_class = GST_ELEMENT_CLASS (klass);
+  gstencoder_class = GST_VIDEO_ENCODER_CLASS (klass);
+
+  gobject_class->set_property = gst_svthevc_enc_set_property;
+  gobject_class->get_property = gst_svthevc_enc_get_property;
+  gobject_class->finalize = gst_svthevc_enc_finalize;
+
+  gstencoder_class->set_format = GST_DEBUG_FUNCPTR (gst_svthevc_enc_set_format);
+  gstencoder_class->handle_frame =
+      GST_DEBUG_FUNCPTR (gst_svthevc_enc_handle_frame);
+  gstencoder_class->start = GST_DEBUG_FUNCPTR (gst_svthevc_enc_start);
+  gstencoder_class->stop = GST_DEBUG_FUNCPTR (gst_svthevc_enc_stop);
+  gstencoder_class->flush = GST_DEBUG_FUNCPTR (gst_svthevc_enc_flush);
+  gstencoder_class->finish = GST_DEBUG_FUNCPTR (gst_svthevc_enc_finish);
+  gstencoder_class->getcaps = GST_DEBUG_FUNCPTR (gst_svthevc_enc_sink_getcaps);
+  gstencoder_class->propose_allocation =
+      GST_DEBUG_FUNCPTR (gst_svthevc_enc_propose_allocation);
+
+  g_object_class_install_property (gobject_class, PROP_INSERT_VUI,
+      g_param_spec_boolean ("insert-vui", "Insert VUI",
+          "Insert VUI NAL in stream",
+          PROP_INSERT_VUI_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_AUD,
+      g_param_spec_boolean ("aud", "AUD",
+          "Use AU (Access Unit) delimiter", PROP_AUD_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_HIERARCHICAL_LEVEL,
+      g_param_spec_enum ("b-pyramid", "B Pyramid (Hierarchical Levels)",
+          "Number of hierarchical layers used to construct GOP",
+          GST_SVTHEVC_ENC_B_PYRAMID_TYPE, PROP_HIERARCHICAL_LEVEL_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_LOOKAHEAD_DISTANCE,
+      g_param_spec_uint ("lookahead", "Lookahead Depth",
+          "Look ahead distance",
+          0, 250, PROP_LOOKAHEAD_DISTANCE_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_ENCODER_MODE,
+      g_param_spec_uint ("speed", "speed (Encoder Mode)",
+          "Encoding preset [0, 11] (e.g. 0 is the highest quality mode, 11 is the highest), [0, 11] (for >= 4k resolution), [0, 10] (for >= 1080p resolution), [0, 9] (for all resolution)",
+          0, 11, PROP_ENCODER_MODE_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_RC_MODE,
+      g_param_spec_enum ("rc", "Ratecontrol Mode",
+          "Bitrate control mode",
+          GST_SVTHEVC_ENC_RC_TYPE, PROP_RC_MODE_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_QP_I,
+      g_param_spec_uint ("qp-i", "QP I",
+          "QP value for intra frames in CQP mode",
+          0, 51, PROP_QP_I_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_QP_MAX,
+      g_param_spec_uint ("qp-max", "QP Max",
+          "Maximum QP value allowed for rate control use",
+          0, 51, PROP_QP_MAX_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_QP_MIN,
+      g_param_spec_uint ("qp-min", "QP Min",
+          "Minimum QP value allowed for rate control use",
+          0, 50, PROP_QP_MIN_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_SCENE_CHANGE_DETECTION,
+      g_param_spec_boolean ("enable-scd", "Scene Change Detection",
+          "Use the scene change detection algorithm",
+          PROP_SCENE_CHANGE_DETECTION_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_TUNE,
+      g_param_spec_enum ("tune", "Tune",
+          "Quality tuning mode",
+          GST_SVTHEVC_ENC_TUNE_TYPE, PROP_TUNE_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED));
+
+  g_object_class_install_property (gobject_class, PROP_BASE_LAYER_SWITCH_MODE,
+      g_param_spec_enum ("baselayer-mode", "Base Layer Switch Mode",
+          "Random Access Prediction Structure type setting",
+          GST_SVTHEVC_ENC_BASE_LAYER_MODE_TYPE,
+          PROP_BASE_LAYER_SWITCH_MODE_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_BITRATE,
+      g_param_spec_uint ("bitrate", "Bitrate",
+          "Bitrate in kbit/sec",
+          1, G_MAXINT, PROP_BITRATE_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_KEY_INT_MAX,
+      g_param_spec_int ("key-int-max", "Key-frame maximal interval",
+          "Distance Between Intra Frame inserted: -1=no intra update. -2=auto",
+          -2, 255, PROP_KEY_INT_MAX_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_ENABLE_OPEN_GOP,
+      g_param_spec_boolean ("enable-open-gop", "Enable Open GOP",
+          "Allow intra-refresh using the CRA, not IDR",
+          PROP_ENABLE_OPEN_GOP_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_CONFIG_INTERVAL,
+      g_param_spec_uint ("config-interval", "VPS SPS PPS Send Interval",
+          "Send VPS, SPS and PPS Insertion Interval per every few IDR. 0: disabled",
+          0, UINT_MAX, PROP_CONFIG_INTERVAL_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_CORES,
+      g_param_spec_uint ("cores", "Number of logical cores",
+          "Number of logical cores to be used. 0: auto",
+          0, UINT_MAX, PROP_CORES_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_SOCKET,
+      g_param_spec_int ("socket", "Target socket",
+          "Target socket to run on. -1: all available",
+          -1, 1, PROP_SOCKET_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_TILE_ROW,
+      g_param_spec_uint ("tile-row", "Tile Row Count",
+          "Tile count in the Row",
+          1, 16, PROP_TILE_ROW_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_TILE_COL,
+      g_param_spec_uint ("tile-col", "Tile Column Count",
+          "Tile count in the Column",
+          1, 16, PROP_TILE_COL_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_PRED_STRUCTURE,
+      g_param_spec_enum ("pred-struct", "Prediction Structure",
+          "Prediction Structure used to construct GOP",
+          GST_SVTHEVC_ENC_PRED_STRUCT_TYPE, PROP_PRED_STRUCTURE_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_VBV_MAX_RATE,
+      g_param_spec_uint ("vbv-max-rate", "VBV Maxrate",
+          "VBV maxrate in kbit/sec for VBR mode",
+          0, G_MAXINT, PROP_VBV_MAX_RATE_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_VBV_BUFFER_SIZE,
+      g_param_spec_uint ("vbv-buffer-size", "VBV Buffer Size",
+          "VBV buffer size in kbits for VBR mode",
+          0, G_MAXINT, PROP_VBV_BUFFER_SIZE_DEFAULT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+
+  gst_element_class_set_static_metadata (element_class,
+      "svthevcenc", "Codec/Encoder/Video",
+      "Scalable Video Technology for HEVC Encoder (SVT-HEVC Encoder)",
+      "Yeongjin Jeong <yeongjin.jeong@navercorp.com>");
+
+  gst_element_class_add_static_pad_template (element_class, &sink_factory);
+  gst_element_class_add_static_pad_template (element_class, &src_factory);
+}
+
+static void
+gst_svthevc_enc_init (GstSvtHevcEnc * encoder)
+{
+  EB_H265_ENC_INPUT *in_data;
+
+  encoder->in_buf = g_slice_new0 (EB_BUFFERHEADERTYPE);
+  in_data = g_slice_new0 (EB_H265_ENC_INPUT);
+  encoder->in_buf->pBuffer = (unsigned char *) in_data;
+  encoder->in_buf->nSize = sizeof (*encoder->in_buf);
+  encoder->in_buf->pAppPrivate = NULL;
+
+  encoder->insert_vui = PROP_INSERT_VUI_DEFAULT;
+  encoder->aud = PROP_AUD_DEFAULT;
+  encoder->hierarchical_level = PROP_HIERARCHICAL_LEVEL_DEFAULT;
+  encoder->la_depth = PROP_LOOKAHEAD_DISTANCE_DEFAULT;
+  encoder->enc_mode = PROP_ENCODER_MODE_DEFAULT;
+  encoder->rc_mode = PROP_RC_MODE_DEFAULT;
+  encoder->qp_i = PROP_QP_I_DEFAULT;
+  encoder->qp_max = PROP_QP_MAX_DEFAULT;
+  encoder->qp_min = PROP_QP_MIN_DEFAULT;
+  encoder->scene_change_detection = PROP_SCENE_CHANGE_DETECTION_DEFAULT;
+  encoder->tune = PROP_TUNE_DEFAULT;
+  encoder->base_layer_switch_mode = PROP_BASE_LAYER_SWITCH_MODE_DEFAULT;
+  encoder->bitrate = PROP_BITRATE_DEFAULT;
+  encoder->keyintmax = PROP_KEY_INT_MAX_DEFAULT;
+  encoder->enable_open_gop = PROP_ENABLE_OPEN_GOP_DEFAULT;
+  encoder->config_interval = PROP_CONFIG_INTERVAL_DEFAULT;
+  encoder->cores = PROP_CORES_DEFAULT;
+  encoder->socket = PROP_SOCKET_DEFAULT;
+  encoder->tile_row = PROP_TILE_ROW_DEFAULT;
+  encoder->tile_col = PROP_TILE_COL_DEFAULT;
+  encoder->pred_structure = PROP_PRED_STRUCTURE_DEFAULT;
+  encoder->vbv_maxrate = PROP_VBV_MAX_RATE_DEFAULT;
+  encoder->vbv_bufsize = PROP_VBV_BUFFER_SIZE_DEFAULT;
+
+  encoder->profile = PROFILE_DEFAULT;
+  encoder->tier = TIER_DEFAULT;
+  encoder->level = LEVEL_DEFAULT;
+
+  encoder->svthevc_version =
+      g_strdup_printf ("%d.%d.%d", SVT_VERSION_MAJOR, SVT_VERSION_MINOR,
+      SVT_VERSION_PATCHLEVEL);
+  encoder->push_header = TRUE;
+  encoder->first_buffer = TRUE;
+  encoder->update_latency = TRUE;
+
+  encoder->internal_pool = NULL;
+  encoder->aligned_info = NULL;
+
+  GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_ENCODER_SINK_PAD (encoder));
+}
+
+static gboolean
+gst_svthevc_enc_start (GstVideoEncoder * encoder)
+{
+  GstSvtHevcEnc *svthevcenc = GST_SVTHEVC_ENC (encoder);
+
+  GST_INFO_OBJECT (svthevcenc, "start encoder");
+
+  /* make sure that we have enough time for first DTS,
+     this is probably overkill for most streams */
+  gst_video_encoder_set_min_pts (encoder, GST_SECOND * 60 * 60 * 1000);
+
+  return TRUE;
+}
+
+static gboolean
+gst_svthevc_enc_stop (GstVideoEncoder * encoder)
+{
+  GstSvtHevcEnc *svthevcenc = GST_SVTHEVC_ENC (encoder);
+
+  GST_INFO_OBJECT (encoder, "stop encoder");
+
+  /* Always drain SVT-HEVC encoder before releasing SVT-HEVC.
+   * Otherwise, randomly block happens when releasing SVT-HEVC. */
+  gst_svthevc_enc_drain_encoder (svthevcenc, FALSE);
+  gst_svthevc_enc_close_encoder (svthevcenc);
+
+  if (svthevcenc->input_state)
+    gst_video_codec_state_unref (svthevcenc->input_state);
+  svthevcenc->input_state = NULL;
+
+  if (svthevcenc->internal_pool)
+    gst_object_unref (svthevcenc->internal_pool);
+  svthevcenc->internal_pool = NULL;
+
+  if (svthevcenc->aligned_info)
+    gst_video_info_free (svthevcenc->aligned_info);
+  svthevcenc->aligned_info = NULL;
+
+  return TRUE;
+}
+
+
+static gboolean
+gst_svthevc_enc_flush (GstVideoEncoder * encoder)
+{
+  GstSvtHevcEnc *svthevcenc = GST_SVTHEVC_ENC (encoder);
+
+  GST_INFO_OBJECT (encoder, "flushing encoder");
+
+  /* Always drain SVT-HEVC encoder before releasing SVT-HEVC.
+   * Otherwise, randomly block happens when releasing SVT-HEVC. */
+  gst_svthevc_enc_drain_encoder (svthevcenc, FALSE);
+  gst_svthevc_enc_close_encoder (svthevcenc);
+
+  GST_OBJECT_LOCK (encoder);
+  if (!gst_svthevc_enc_init_encoder (svthevcenc)) {
+    GST_OBJECT_UNLOCK (encoder);
+    return FALSE;
+  }
+  GST_OBJECT_UNLOCK (encoder);
+
+  return TRUE;
+}
+
+static void
+gst_svthevc_enc_finalize (GObject * object)
+{
+  GstSvtHevcEnc *encoder = GST_SVTHEVC_ENC (object);
+
+  if (encoder->in_buf) {
+    EB_H265_ENC_INPUT *in_data = (EB_H265_ENC_INPUT *) encoder->in_buf->pBuffer;
+    if (in_data)
+      g_slice_free (EB_H265_ENC_INPUT, in_data);
+    g_slice_free (EB_BUFFERHEADERTYPE, encoder->in_buf);
+  }
+
+  g_free ((gpointer) encoder->svthevc_version);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint
+gst_svthevc_enc_gst_to_svthevc_video_format (GstVideoFormat format,
+    gint * nplanes)
+{
+  switch (format) {
+    case GST_VIDEO_FORMAT_I420:
+    case GST_VIDEO_FORMAT_YV12:
+    case GST_VIDEO_FORMAT_I420_10LE:
+    case GST_VIDEO_FORMAT_I420_10BE:
+      if (nplanes)
+        *nplanes = 3;
+      return EB_YUV420;
+    case GST_VIDEO_FORMAT_Y42B:
+    case GST_VIDEO_FORMAT_I422_10LE:
+    case GST_VIDEO_FORMAT_I422_10BE:
+      if (nplanes)
+        *nplanes = 3;
+      return EB_YUV422;
+    case GST_VIDEO_FORMAT_Y444:
+    case GST_VIDEO_FORMAT_Y444_10LE:
+    case GST_VIDEO_FORMAT_Y444_10BE:
+      if (nplanes)
+        *nplanes = 3;
+      return EB_YUV444;
+    default:
+      g_return_val_if_reached (GST_VIDEO_FORMAT_UNKNOWN);
+  }
+}
+
+static void
+config_enc_params (GstSvtHevcEnc * encoder, EB_H265_ENC_CONFIGURATION * param)
+{
+  GstVideoInfo *info;
+
+  info = &encoder->input_state->info;
+
+  param->sourceWidth = info->width;
+  param->sourceHeight = info->height;
+
+  if (GST_VIDEO_INFO_COMP_DEPTH (info, 0) == 10) {
+    GST_DEBUG_OBJECT (encoder, "Encoder 10 bits depth input");
+    /* Disable Compressed 10-bit format default.
+     * SVT-HEVC support a compressed 10-bit format allowing the
+     * software to achieve a higher speed and channel density levels.
+     * The conversion between the 10-bit I420 and the compressed
+     * 10-bit format is a lossless operation.
+     */
+    param->compressedTenBitFormat = 0;
+    param->encoderBitDepth = 10;
+  }
+  /* Update param from options */
+  param->hierarchicalLevels = encoder->hierarchical_level;
+  param->encMode = encoder->enc_mode;
+  param->profile = encoder->profile;
+  param->tier = encoder->tier;
+  param->level = encoder->level;
+  param->rateControlMode = encoder->rc_mode;
+  param->sceneChangeDetection = encoder->scene_change_detection;
+  param->tune = encoder->tune;
+  param->latencyMode = 0;
+  param->baseLayerSwitchMode = encoder->base_layer_switch_mode;
+  param->qp = encoder->qp_i;
+  param->accessUnitDelimiter = encoder->aud;
+
+  param->targetBitRate = encoder->bitrate * 1000;
+  param->intraPeriodLength =
+      encoder->keyintmax > 0 ? encoder->keyintmax - 1 : encoder->keyintmax;
+
+  if (info->fps_d == 0 || info->fps_n == 0) {
+    param->frameRateNumerator = 0;
+    param->frameRateDenominator = 1;
+  } else {
+    param->frameRateNumerator = info->fps_n;
+    param->frameRateDenominator = info->fps_d;
+  }
+
+  if (param->rateControlMode) {
+    param->maxQpAllowed = encoder->qp_max;
+    param->minQpAllowed = encoder->qp_min;
+  }
+
+  if (encoder->enable_open_gop)
+    param->intraRefreshType = -1;
+  else
+    param->intraRefreshType = encoder->config_interval;
+
+  param->logicalProcessors = encoder->cores;
+  param->targetSocket = encoder->socket;
+
+  param->tileRowCount = encoder->tile_row;
+  param->tileColumnCount = encoder->tile_col;
+
+  param->predStructure = encoder->pred_structure;
+
+  if (encoder->vbv_maxrate)
+    param->vbvMaxrate = encoder->vbv_maxrate * 1000;
+
+  if (encoder->vbv_bufsize)
+    param->vbvBufsize = encoder->vbv_bufsize * 1000;
+
+  /*
+   * NOTE: codeVpsSpsPps flag allows the VPS, SPS and PPS Insertion and
+   * sending in first IDR frame. But in the SVT-HEVC specific version,
+   * If codeVpsSpsPps enabled and using the EbH265EncStreamHeader API
+   * before receiving encoded packets, It cause bug which encoded packets
+   * are not output.
+   */
+  if (SVT_CHECK_VERSION (1, 4, 1))
+    param->codeVpsSpsPps = 1;
+  else
+    param->codeVpsSpsPps = 0;
+
+  param->codeEosNal = 1;
+
+  if (encoder->insert_vui)
+    param->videoUsabilityInfo = encoder->insert_vui;
+
+  if (encoder->la_depth != -1)
+    param->lookAheadDistance = encoder->la_depth;
+
+  param->encoderColorFormat =
+      gst_svthevc_enc_gst_to_svthevc_video_format (info->finfo->format, NULL);
+}
+
+static void
+read_in_data (EB_H265_ENC_CONFIGURATION * config,
+    GstVideoFrame * vframe, EB_BUFFERHEADERTYPE * headerPtr)
+{
+  EB_H265_ENC_INPUT *in_data = (EB_H265_ENC_INPUT *) headerPtr->pBuffer;
+
+  in_data->luma = GST_VIDEO_FRAME_PLANE_DATA (vframe, 0);
+  in_data->cb = GST_VIDEO_FRAME_PLANE_DATA (vframe, 1);
+  in_data->cr = GST_VIDEO_FRAME_PLANE_DATA (vframe, 2);
+
+  in_data->yStride =
+      GST_VIDEO_FRAME_COMP_STRIDE (vframe,
+      0) / GST_VIDEO_FRAME_COMP_PSTRIDE (vframe, 0);
+  in_data->cbStride =
+      GST_VIDEO_FRAME_COMP_STRIDE (vframe,
+      1) / GST_VIDEO_FRAME_COMP_PSTRIDE (vframe, 1);
+  in_data->crStride =
+      GST_VIDEO_FRAME_COMP_STRIDE (vframe,
+      2) / GST_VIDEO_FRAME_COMP_PSTRIDE (vframe, 2);
+
+  headerPtr->nAllocLen = headerPtr->nFilledLen = GST_VIDEO_FRAME_SIZE (vframe);
+}
+
+/*
+ * gst_svthevc_enc_init_encoder
+ * @encoder:  Encoder which should be initialized.
+ *
+ * Initialize svthevc encoder.
+ *
+ */
+static gboolean
+gst_svthevc_enc_init_encoder (GstSvtHevcEnc * encoder)
+{
+  EB_ERRORTYPE svt_ret;
+
+  if (!encoder->input_state) {
+    GST_DEBUG_OBJECT (encoder, "Have no input state yet");
+    return FALSE;
+  }
+
+  /* make sure that the encoder is closed */
+  gst_svthevc_enc_close_encoder (encoder);
+
+  encoder->svt_eos_flag = EOS_NOT_REACHED;
+
+  /* set up encoder parameters */
+  svt_ret = EbInitHandle (&encoder->svt_handle, encoder, &encoder->enc_params);
+  if (svt_ret != EB_ErrorNone) {
+    GST_DEBUG_OBJECT (encoder, "Error init encoder handle");
+    goto failed;
+  }
+
+  config_enc_params (encoder, &encoder->enc_params);
+
+  svt_ret = EbH265EncSetParameter (encoder->svt_handle, &encoder->enc_params);
+  if (svt_ret != EB_ErrorNone) {
+    GST_DEBUG_OBJECT (encoder, "Error setting encoder parameters");
+    goto failed_init_handle;
+  }
+
+  svt_ret = EbInitEncoder (encoder->svt_handle);
+  if (svt_ret != EB_ErrorNone) {
+    GST_DEBUG_OBJECT (encoder, "Error init encoder");
+    goto failed_init_handle;
+  }
+
+  encoder->push_header = TRUE;
+  encoder->first_buffer = TRUE;
+  encoder->update_latency = TRUE;
+  encoder->reconfig = FALSE;
+
+  /* good start, will be corrected if needed */
+  encoder->dts_offset = 0;
+  encoder->first_frame = NULL;
+
+  return TRUE;
+
+failed_init_handle:
+  EbDeinitHandle (encoder->svt_handle);
+failed:
+  encoder->svt_handle = NULL;
+
+  return FALSE;
+}
+
+/* gst_svthevc_enc_close_encoder
+ * @encoder:  Encoder which should close.
+ *
+ * Close svthevc encoder.
+ */
+static void
+gst_svthevc_enc_close_encoder (GstSvtHevcEnc * encoder)
+{
+  if (encoder->svt_handle != NULL) {
+    EbDeinitEncoder (encoder->svt_handle);
+    EbDeinitHandle (encoder->svt_handle);
+    encoder->svt_handle = NULL;
+  }
+}
+
+static EB_BUFFERHEADERTYPE *
+gst_svthevc_enc_bytestream_to_nal (GstSvtHevcEnc * encoder,
+    EB_BUFFERHEADERTYPE * input)
+{
+  EB_BUFFERHEADERTYPE *output;
+  int i, j, zeros;
+  int offset = 4;
+
+  output = g_malloc (sizeof (EB_BUFFERHEADERTYPE));
+
+  /* skip access unit delimiter */
+  if (encoder->aud)
+    offset += 7;
+
+  output->pBuffer = g_malloc (input->nFilledLen - offset);
+  output->nFilledLen = input->nFilledLen - offset;
+
+  zeros = 0;
+  for (i = offset, j = 0; i < input->nFilledLen; (i++, j++)) {
+    if (input->pBuffer[i] == 0x00) {
+      zeros++;
+    } else if (input->pBuffer[i] == 0x03 && zeros == 2) {
+      zeros = 0;
+      j--;
+      output->nFilledLen--;
+      continue;
+    } else {
+      zeros = 0;
+    }
+    output->pBuffer[j] = input->pBuffer[i];
+  }
+
+  return output;
+}
+
+static void
+svthevc_nal_free (EB_BUFFERHEADERTYPE * nal)
+{
+  g_free (nal->pBuffer);
+  g_free (nal);
+}
+
+static gboolean
+gst_svthevc_enc_set_level_tier_and_profile (GstSvtHevcEnc * encoder,
+    GstCaps * caps)
+{
+  EB_BUFFERHEADERTYPE *headerPtr = NULL, *nal = NULL;
+  EB_ERRORTYPE svt_ret;
+  const gchar *level, *tier, *profile;
+  GstStructure *s;
+  GstCaps *allowed_caps;
+  GstStructure *s2;
+  const gchar *allowed_profile;
+
+  GST_DEBUG_OBJECT (encoder, "set profile, level and tier");
+
+  svt_ret = EbH265EncStreamHeader (encoder->svt_handle, &headerPtr);
+  if (svt_ret != EB_ErrorNone) {
+    GST_ELEMENT_ERROR (encoder, STREAM, ENCODE,
+        ("Encode svthevc header failed."),
+        ("svthevc_encoder_headers return code=%d", svt_ret));
+    return FALSE;
+  }
+
+  GST_MEMDUMP ("ENCODER_HEADER", headerPtr->pBuffer, headerPtr->nFilledLen);
+
+  nal = gst_svthevc_enc_bytestream_to_nal (encoder, headerPtr);
+
+  gst_codec_utils_h265_caps_set_level_tier_and_profile (caps,
+      nal->pBuffer + 6, nal->nFilledLen - 6);
+
+  svthevc_nal_free (nal);
+
+  s = gst_caps_get_structure (caps, 0);
+  profile = gst_structure_get_string (s, "profile");
+  tier = gst_structure_get_string (s, "tier");
+  level = gst_structure_get_string (s, "level");
+
+  GST_DEBUG_OBJECT (encoder, "profile : %s", (profile) ? profile : "---");
+  GST_DEBUG_OBJECT (encoder, "tier    : %s", (tier) ? tier : "---");
+  GST_DEBUG_OBJECT (encoder, "level   : %s", (level) ? level : "---");
+
+  /* Relaxing the profile condition since libSvtHevcEnc can generate
+   * wrong bitstream indication for conformance to profile than requested one.
+   * See : https://github.com/OpenVisualCloud/SVT-HEVC/pull/320
+   */
+  allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
+
+  if (allowed_caps == NULL)
+    goto no_peer;
+
+  if (!gst_caps_can_intersect (allowed_caps, caps)) {
+    GArray *peer_formats = g_array_new (FALSE, TRUE, sizeof (guint));
+    GArray *enc_formats = g_array_new (FALSE, TRUE, sizeof (guint));
+    gboolean is_subset = TRUE;
+    guint i, j;
+
+    allowed_caps = gst_caps_make_writable (allowed_caps);
+    allowed_caps = gst_caps_truncate (allowed_caps);
+    s2 = gst_caps_get_structure (allowed_caps, 0);
+    gst_structure_fixate_field_string (s2, "profile", profile);
+    allowed_profile = gst_structure_get_string (s2, "profile");
+
+    get_support_format_from_profile (peer_formats, allowed_profile);
+    get_support_format_from_profile (enc_formats, profile);
+
+    for (i = 0; i < enc_formats->len; i++) {
+      if (enc_formats->data[i]) {
+        gboolean is_support = FALSE;
+        for (j = 0; j < peer_formats->len; j++) {
+          if (peer_formats->data[j] && (i == j))
+            is_support = TRUE;
+        }
+        if (!is_support) {
+          is_subset = FALSE;
+          break;
+        }
+      }
+    }
+
+    GST_INFO_OBJECT (encoder, "downstream requested %s profile but "
+        "encoder will now output %s profile (which is a %s), so relaxing the "
+        "profile condition for negotiation",
+        allowed_profile, profile, is_subset ? "subset" : "not subset");
+
+    gst_structure_set (s, "profile", G_TYPE_STRING, allowed_profile, NULL);
+
+    g_array_free (peer_formats, TRUE);
+    g_array_free (enc_formats, TRUE);
+  }
+  gst_caps_unref (allowed_caps);
+
+no_peer:
+  return TRUE;
+}
+
+static GstBuffer *
+gst_svthevc_enc_get_header_buffer (GstSvtHevcEnc * encoder)
+{
+  EB_BUFFERHEADERTYPE *headerPtr = NULL;
+  EB_ERRORTYPE svt_ret;
+  GstBuffer *buf;
+
+  svt_ret = EbH265EncStreamHeader (encoder->svt_handle, &headerPtr);
+  if (svt_ret != EB_ErrorNone) {
+    GST_ELEMENT_ERROR (encoder, STREAM, ENCODE,
+        ("Encode svthevc header failed."),
+        ("svthevc_encoder_headers return code=%d", svt_ret));
+    return FALSE;
+  }
+
+  buf = gst_buffer_new_allocate (NULL, headerPtr->nFilledLen, NULL);
+  gst_buffer_fill (buf, 0, headerPtr->pBuffer, headerPtr->nFilledLen);
+
+  return buf;
+}
+
+/* gst_svthevc_enc_set_src_caps
+ * Returns: TRUE on success.
+ */
+static gboolean
+gst_svthevc_enc_set_src_caps (GstSvtHevcEnc * encoder, GstCaps * caps)
+{
+  GstCaps *outcaps;
+  GstStructure *structure;
+  GstVideoCodecState *state;
+  GstTagList *tags;
+
+  outcaps = gst_caps_new_empty_simple ("video/x-h265");
+  structure = gst_caps_get_structure (outcaps, 0);
+
+  gst_structure_set (structure, "stream-format", G_TYPE_STRING, "byte-stream",
+      NULL);
+  gst_structure_set (structure, "alignment", G_TYPE_STRING, "au", NULL);
+
+  if (!gst_svthevc_enc_set_level_tier_and_profile (encoder, outcaps)) {
+    gst_caps_unref (outcaps);
+    return FALSE;
+  }
+
+  state = gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (encoder),
+      outcaps, encoder->input_state);
+  GST_LOG_OBJECT (encoder, "output caps: %" GST_PTR_FORMAT, state->caps);
+  gst_video_codec_state_unref (state);
+
+  tags = gst_tag_list_new_empty ();
+  gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER, "svthevc",
+      GST_TAG_ENCODER_VERSION, encoder->svthevc_version, NULL);
+  gst_video_encoder_merge_tags (GST_VIDEO_ENCODER (encoder), tags,
+      GST_TAG_MERGE_REPLACE);
+  gst_tag_list_unref (tags);
+
+  return TRUE;
+}
+
+static void
+gst_svthevc_enc_set_latency (GstSvtHevcEnc * encoder)
+{
+  GstVideoInfo *info = &encoder->input_state->info;
+  guint max_delayed_frames;
+  GstClockTime latency;
+
+  if (encoder->first_buffer) {
+    /* FIXME get a real value from the encoder, this is currently not exposed */
+    max_delayed_frames = 5;
+  } else {
+    GList *frames = gst_video_encoder_get_frames (GST_VIDEO_ENCODER (encoder));
+    max_delayed_frames = g_list_length (frames);
+    g_list_free_full (frames, (GDestroyNotify) gst_video_codec_frame_unref);
+  }
+
+  if (info->fps_n) {
+    latency = gst_util_uint64_scale_ceil (GST_SECOND * info->fps_d,
+        max_delayed_frames, info->fps_n);
+  } else {
+    /* FIXME: Assume 25fps. This is better than reporting no latency at
+     * all and then later failing in live pipelines
+     */
+    latency = gst_util_uint64_scale_ceil (GST_SECOND * 1,
+        max_delayed_frames, 25);
+  }
+
+  GST_INFO_OBJECT (encoder,
+      "Updating latency to %" GST_TIME_FORMAT " (%d frames)",
+      GST_TIME_ARGS (latency), max_delayed_frames);
+
+  gst_video_encoder_set_latency (GST_VIDEO_ENCODER (encoder), latency, latency);
+}
+
+static const guint
+gst_svthevc_enc_profile_from_gst (const GstH265Profile profile)
+{
+  gint i;
+
+  for (i = 0; i < G_N_ELEMENTS (profile_table); i++) {
+    if (profile == profile_table[i].gst_profile)
+      return profile_table[i].svt_profile;
+  }
+
+  GST_WARNING ("Unsupported profile string '%s'",
+      gst_h265_profile_to_string (profile));
+  return 0;
+}
+
+static guint
+gst_svthevc_enc_level_from_gst (const gchar * level)
+{
+  if (g_str_equal (level, "1"))
+    return 10;
+  else if (g_str_equal (level, "2"))
+    return 20;
+  else if (g_str_equal (level, "2.1"))
+    return 21;
+  else if (g_str_equal (level, "3"))
+    return 30;
+  else if (g_str_equal (level, "3.1"))
+    return 31;
+  else if (g_str_equal (level, "4"))
+    return 40;
+  else if (g_str_equal (level, "4.1"))
+    return 41;
+  else if (g_str_equal (level, "5"))
+    return 50;
+  else if (g_str_equal (level, "5.1"))
+    return 51;
+  else if (g_str_equal (level, "5.2"))
+    return 52;
+  else if (g_str_equal (level, "6"))
+    return 60;
+  else if (g_str_equal (level, "6.1"))
+    return 61;
+  else if (g_str_equal (level, "6.2"))
+    return 62;
+
+  GST_WARNING ("Unsupported level string '%s'", level);
+  return LEVEL_DEFAULT;
+}
+
+static guint
+gst_svthevc_enc_tier_from_gst (const gchar * level)
+{
+  if (g_str_equal (level, "main"))
+    return 0;
+  else if (g_str_equal (level, "high"))
+    return 1;
+
+  GST_WARNING ("Unsupported tier string '%s'", level);
+  return TIER_DEFAULT;
+}
+
+static gboolean
+gst_svthevc_enc_set_format (GstVideoEncoder * video_enc,
+    GstVideoCodecState * state)
+{
+  GstSvtHevcEnc *encoder = GST_SVTHEVC_ENC (video_enc);
+  GstVideoInfo *info = &state->info;
+  GstCaps *template_caps;
+  GstCaps *allowed_caps;
+
+  /* If the encoder is initialized, do not reinitialize it again if not
+   * necessary */
+  if (encoder->svt_handle) {
+    GstVideoInfo *old = &encoder->input_state->info;
+
+    if (info->finfo->format == old->finfo->format
+        && info->width == old->width && info->height == old->height
+        && info->fps_n == old->fps_n && info->fps_d == old->fps_d
+        && info->par_n == old->par_n && info->par_d == old->par_d) {
+      gst_video_codec_state_unref (encoder->input_state);
+      encoder->input_state = gst_video_codec_state_ref (state);
+      return TRUE;
+    }
+
+    /* clear out pending frames */
+    gst_svthevc_enc_drain_encoder (encoder, TRUE);
+  }
+
+  if (encoder->input_state)
+    gst_video_codec_state_unref (encoder->input_state);
+  encoder->input_state = gst_video_codec_state_ref (state);
+
+  template_caps = gst_static_pad_template_get_caps (&src_factory);
+  allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
+
+  if (allowed_caps == template_caps) {
+    GST_INFO_OBJECT (encoder, "downstream has ANY caps");
+
+    /* SVT-HEVC encoder does not yet support auto profile selecting.
+     * So we should be set the profile from input format */
+    encoder->profile = GST_VIDEO_INFO_COMP_DEPTH (info, 0) == 8 ? 1 : 2;
+    switch (GST_VIDEO_INFO_FORMAT (info)) {
+      case GST_VIDEO_FORMAT_Y42B:
+      case GST_VIDEO_FORMAT_I422_10LE:
+      case GST_VIDEO_FORMAT_I422_10BE:
+      case GST_VIDEO_FORMAT_Y444:
+      case GST_VIDEO_FORMAT_Y444_10LE:
+      case GST_VIDEO_FORMAT_Y444_10BE:
+        encoder->profile = 4;
+      default:
+        break;
+    }
+
+    gst_caps_unref (allowed_caps);
+  } else if (allowed_caps) {
+    GstStructure *s;
+    const gchar *profile;
+    const gchar *level;
+    const gchar *tier;
+
+    GST_LOG_OBJECT (encoder, "allowed caps %" GST_PTR_FORMAT, allowed_caps);
+
+    if (gst_caps_is_empty (allowed_caps)) {
+      gst_caps_unref (template_caps);
+      gst_caps_unref (allowed_caps);
+      return FALSE;
+    }
+
+    s = gst_caps_get_structure (allowed_caps, 0);
+
+    if (gst_structure_has_field (s, "profile")) {
+      const GValue *v = gst_structure_get_value (s, "profile");
+      GArray *profiles = g_array_new (FALSE, TRUE, sizeof (guint));
+      GstH265Profile gst_profile;
+      guint svt_profile = 0;
+
+      get_compatible_profile_from_format (profiles,
+          GST_VIDEO_INFO_FORMAT (info));
+
+      if (GST_VALUE_HOLDS_LIST (v)) {
+        const gint list_size = gst_value_list_get_size (v);
+        gint i, j;
+
+        for (i = 0; i < list_size; i++) {
+          const GValue *list_val = gst_value_list_get_value (v, i);
+          profile = g_value_get_string (list_val);
+
+          if (profile) {
+            gst_profile =
+                gst_h265_profile_from_string (g_value_get_string (list_val));
+
+            for (j = 0; j < profiles->len; j++) {
+              if (profiles->data[j] && (j == gst_profile)) {
+                svt_profile = gst_svthevc_enc_profile_from_gst (j);
+                break;
+              }
+            }
+          }
+
+          if (svt_profile != 0)
+            break;
+        }
+      } else if (G_VALUE_HOLDS_STRING (v)) {
+        gint i;
+        profile = g_value_get_string (v);
+
+        if (profile) {
+          gst_profile = gst_h265_profile_from_string (g_value_get_string (v));
+
+          for (i = 0; i < profiles->len; i++) {
+            if (profiles->data[i] && (i == gst_profile)) {
+              svt_profile = gst_svthevc_enc_profile_from_gst (i);
+              break;
+            }
+          }
+        }
+      }
+
+      g_array_free (profiles, TRUE);
+
+      if (svt_profile == 0) {
+        GST_ERROR_OBJECT (encoder, "Could't apply peer profile");
+        gst_caps_unref (template_caps);
+        gst_caps_unref (allowed_caps);
+        return FALSE;
+      }
+
+      encoder->profile = svt_profile;
+    }
+
+    level = gst_structure_get_string (s, "level");
+    if (level)
+      encoder->level = gst_svthevc_enc_level_from_gst (level);
+
+    tier = gst_structure_get_string (s, "tier");
+    if (tier)
+      encoder->tier = gst_svthevc_enc_tier_from_gst (tier);
+
+    gst_caps_unref (allowed_caps);
+  }
+  gst_caps_unref (template_caps);
+
+  GST_INFO_OBJECT (encoder, "Using profile %d, tier %d, level %d",
+      encoder->profile, encoder->tier, encoder->level);
+
+  GST_OBJECT_LOCK (encoder);
+  if (!gst_svthevc_enc_init_encoder (encoder)) {
+    GST_OBJECT_UNLOCK (encoder);
+    return FALSE;
+  }
+  GST_OBJECT_UNLOCK (encoder);
+
+  if (!gst_svthevc_enc_set_src_caps (encoder, state->caps)) {
+    gst_svthevc_enc_close_encoder (encoder);
+    return FALSE;
+  }
+
+  {
+    /* The SVT-HEVC uses stride in pixel, not in bytes, while upstream can
+     * provide aligned stride in bytes. So there is no guaranty
+     * that a stride is multiple of PSTRIDE, we should ensure internal pool
+     * to use when converting frames. */
+    GstVideoAlignment video_align;
+    GstAllocationParams params = { 0, 15, 0, 0 };
+    GstCaps *caps;
+    GstBufferPool *pool;
+    GstStructure *config;
+    guint i, size;
+
+    if (encoder->internal_pool)
+      gst_object_unref (encoder->internal_pool);
+    encoder->internal_pool = NULL;
+
+    if (encoder->aligned_info)
+      gst_video_info_free (encoder->aligned_info);
+    encoder->aligned_info = gst_video_info_copy (info);
+
+    caps = gst_video_info_to_caps (info);
+    pool = gst_video_buffer_pool_new ();
+
+    size = GST_VIDEO_INFO_SIZE (info);
+    GST_INFO_OBJECT (encoder,
+        "create internal buffer pool size %u, caps %" GST_PTR_FORMAT, size,
+        caps);
+
+    config = gst_buffer_pool_get_config (pool);
+    gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
+    gst_buffer_pool_config_set_allocator (config, NULL, &params);
+
+    gst_caps_unref (caps);
+
+    /* set stride align */
+    gst_video_alignment_reset (&video_align);
+    for (i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++)
+      video_align.stride_align[i] = GST_VIDEO_INFO_COMP_PSTRIDE (info, i) - 1;
+    gst_video_info_align (encoder->aligned_info, &video_align);
+
+    gst_buffer_pool_config_add_option (config,
+        GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
+    gst_buffer_pool_config_set_video_alignment (config, &video_align);
+
+    if (!gst_buffer_pool_set_config (pool, config)) {
+      if (pool)
+        gst_object_unref (pool);
+      pool = NULL;
+    }
+    gst_buffer_pool_set_active (pool, TRUE);
+
+    encoder->internal_pool = pool;
+  }
+
+  gst_svthevc_enc_set_latency (encoder);
+
+  return TRUE;
+}
+
+static GstFlowReturn
+gst_svthevc_enc_finish (GstVideoEncoder * encoder)
+{
+  GST_INFO_OBJECT (encoder, "finish encoder");
+
+  gst_svthevc_enc_drain_encoder (GST_SVTHEVC_ENC (encoder), TRUE);
+  return GST_FLOW_OK;
+}
+
+static gboolean
+gst_svthevc_enc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
+{
+  GstSvtHevcEnc *svthevcenc = GST_SVTHEVC_ENC (encoder);
+  GstCaps *caps;
+  GstVideoInfo info;
+  GstVideoAlignment video_align;
+  GstBufferPool *pool;
+  GstStructure *config;
+  guint i, size, min, max;
+
+  GST_INFO_OBJECT (svthevcenc, "propose allocation");
+
+  gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
+
+  gst_query_parse_allocation (query, &caps, NULL);
+
+  if (caps == NULL)
+    goto done;
+
+  if (!gst_video_info_from_caps (&info, caps))
+    goto done;
+
+  /* We should propose to specify required stride alignments. */
+  gst_video_alignment_reset (&video_align);
+  for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&info); i++)
+    video_align.stride_align[i] = GST_VIDEO_INFO_COMP_PSTRIDE (&info, i) - 1;
+  gst_video_info_align (&info, &video_align);
+
+  if (gst_query_get_n_allocation_pools (query) > 0) {
+    gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
+    config = gst_buffer_pool_get_config (pool);
+
+    /* set stride align */
+    gst_buffer_pool_config_add_option (config,
+        GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
+    gst_buffer_pool_config_set_video_alignment (config, &video_align);
+
+    gst_buffer_pool_set_config (pool, config);
+    gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
+  } else {
+    GstAllocator *allocator = NULL;
+    GstAllocationParams params = { 0, 15, 0, 0 };
+
+    size = GST_VIDEO_INFO_SIZE (&info);
+    GST_INFO_OBJECT (svthevcenc,
+        "create buffer pool size %u, caps %" GST_PTR_FORMAT, size, caps);
+
+    if (gst_query_get_n_allocation_params (query) > 0)
+      gst_query_parse_nth_allocation_param (query, 0, &allocator, &params);
+    else
+      gst_query_add_allocation_param (query, allocator, &params);
+
+    pool = gst_video_buffer_pool_new ();
+
+    config = gst_buffer_pool_get_config (pool);
+    gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
+    gst_buffer_pool_config_set_allocator (config, allocator, &params);
+
+    /* set stride align */
+    gst_buffer_pool_config_add_option (config,
+        GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
+    gst_buffer_pool_config_set_video_alignment (config, &video_align);
+
+    if (allocator)
+      gst_object_unref (allocator);
+
+    if (!gst_buffer_pool_set_config (pool, config))
+      goto done;
+
+    gst_query_add_allocation_pool (query, pool, size, 0, 0);
+  }
+
+done:
+  if (pool)
+    gst_object_unref (pool);
+
+  return GST_VIDEO_ENCODER_CLASS (parent_class)->propose_allocation (encoder,
+      query);
+}
+
+/* chain function
+ * this function does the actual processing
+ */
+static GstFlowReturn
+gst_svthevc_enc_handle_frame (GstVideoEncoder * video_enc,
+    GstVideoCodecFrame * frame)
+{
+  GstSvtHevcEnc *encoder = GST_SVTHEVC_ENC (video_enc);
+  GstFlowReturn ret = GST_FLOW_OK;
+  gboolean got_packet;
+
+  if (G_UNLIKELY (encoder->svt_handle == NULL))
+    goto not_inited;
+
+  ret = gst_svthevc_enc_send_frame (encoder, frame);
+
+  if (ret != GST_FLOW_OK)
+    goto encode_fail;
+
+  do {
+    ret = gst_svthevc_enc_receive_frame (encoder, &got_packet, TRUE);
+    GST_LOG_OBJECT (encoder, "ret %d, got_packet %d", ret, got_packet);
+    if (ret != GST_FLOW_OK)
+      break;
+  } while (got_packet);
+
+done:
+  return ret;
+
+/* ERRORS */
+not_inited:
+  {
+    GST_WARNING_OBJECT (encoder, "Got buffer before set_caps was called");
+    return GST_FLOW_NOT_NEGOTIATED;
+  }
+encode_fail:
+  {
+    /* avoid frame (and ts etc) piling up */
+    if (frame)
+      ret = gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (encoder), frame);
+    goto done;
+  }
+}
+
+static gboolean
+gst_svthevc_enc_convert_frame (GstSvtHevcEnc * encoder,
+    GstVideoCodecFrame * frame)
+{
+  GstVideoInfo *info = &encoder->input_state->info;
+  GstVideoFrame src_frame, aligned_frame;
+  GstBuffer *aligned_buffer;
+
+  if (encoder->internal_pool == NULL)
+    return FALSE;
+
+  if (gst_buffer_pool_acquire_buffer (encoder->internal_pool, &aligned_buffer,
+          NULL) != GST_FLOW_OK) {
+    GST_ERROR_OBJECT (encoder, "Failed to acquire a buffer from pool");
+    return FALSE;
+  }
+
+  if (!gst_video_frame_map (&src_frame, info, frame->input_buffer,
+          GST_MAP_READ)) {
+    GST_ERROR_OBJECT (encoder, "Failed to map the frame for aligned buffer");
+    goto error;
+  }
+
+  /* FIXME: need to adjust video info align?? */
+  if (!gst_video_frame_map (&aligned_frame, encoder->aligned_info,
+          aligned_buffer, GST_MAP_WRITE)) {
+    GST_ERROR_OBJECT (encoder, "Failed to map the frame for aligned buffer");
+    gst_video_frame_unmap (&src_frame);
+    goto error;
+  }
+
+  if (!gst_video_frame_copy (&aligned_frame, &src_frame)) {
+    GST_ERROR_OBJECT (encoder, "Failed to copy frame");
+    gst_video_frame_unmap (&src_frame);
+    gst_video_frame_unmap (&aligned_frame);
+    goto error;
+  }
+
+  gst_video_frame_unmap (&src_frame);
+  gst_video_frame_unmap (&aligned_frame);
+
+  gst_buffer_replace (&frame->input_buffer, aligned_buffer);
+  gst_buffer_unref (aligned_buffer);
+
+  return TRUE;
+
+error:
+  if (aligned_buffer)
+    gst_buffer_unref (aligned_buffer);
+  return FALSE;
+}
+
+static GstFlowReturn
+gst_svthevc_enc_send_frame (GstSvtHevcEnc * encoder, GstVideoCodecFrame * frame)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  EB_BUFFERHEADERTYPE *headerPtr = NULL;
+  GstVideoInfo *info = &encoder->input_state->info;
+  GstVideoFrame vframe;
+  EB_ERRORTYPE svt_ret;
+  guint i;
+
+  if (encoder->svt_eos_flag == EOS_REACHED) {
+    if (frame)
+      gst_video_codec_frame_unref (frame);
+    return GST_FLOW_OK;
+  }
+
+  if (encoder->svt_eos_flag == EOS_TOTRIGGER) {
+    if (frame)
+      gst_video_codec_frame_unref (frame);
+    return GST_FLOW_EOS;
+  }
+
+  if (!frame)
+    goto out;
+
+  headerPtr = encoder->in_buf;
+
+  /* Check that stride is a multiple of pstride, otherwise convert to
+   * desired stride from SVT-HEVC.*/
+  for (i = 0; i < 3; i++) {
+    if (GST_VIDEO_INFO_COMP_STRIDE (info,
+            i) % GST_VIDEO_INFO_COMP_PSTRIDE (info, i)) {
+      GST_LOG_OBJECT (encoder, "need to convert frame");
+      if (!gst_svthevc_enc_convert_frame (encoder, frame)) {
+        if (frame)
+          gst_video_codec_frame_unref (frame);
+      }
+      break;
+    }
+  }
+
+  if (!gst_video_frame_map (&vframe, info, frame->input_buffer, GST_MAP_READ)) {
+    GST_ERROR_OBJECT (encoder, "Failed to map frame");
+    if (frame)
+      gst_video_codec_frame_unref (frame);
+    return GST_FLOW_ERROR;
+  }
+
+  read_in_data (&encoder->enc_params, &vframe, headerPtr);
+
+  headerPtr->nFlags = 0;
+  headerPtr->sliceType = EB_INVALID_PICTURE;
+  headerPtr->pAppPrivate = NULL;
+  headerPtr->pts = frame->pts;
+
+  if (encoder->reconfig && frame) {
+    /* svthevc_encoder_reconfig is not yet implemented thus we shut down and re-create encoder */
+    GST_INFO_OBJECT (encoder, "reconfigure encoder");
+    gst_svthevc_enc_drain_encoder (encoder, TRUE);
+    GST_OBJECT_LOCK (encoder);
+    if (!gst_svthevc_enc_init_encoder (encoder)) {
+      GST_OBJECT_UNLOCK (encoder);
+      return GST_FLOW_ERROR;
+    }
+    GST_OBJECT_UNLOCK (encoder);
+  }
+
+  if (headerPtr && frame) {
+    if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame)) {
+      GST_INFO_OBJECT (encoder, "Forcing key frame");
+      headerPtr->sliceType = EB_IDR_PICTURE;
+    }
+  }
+
+out:
+  if (!headerPtr) {
+    EB_BUFFERHEADERTYPE headerPtrLast;
+
+    if (encoder->first_buffer) {
+      GST_DEBUG_OBJECT (encoder, "No need to send eos buffer");
+      encoder->svt_eos_flag = EOS_TOTRIGGER;
+      return GST_FLOW_OK;
+    }
+
+    headerPtrLast.nAllocLen = 0;
+    headerPtrLast.nFilledLen = 0;
+    headerPtrLast.nTickCount = 0;
+    headerPtrLast.pAppPrivate = NULL;
+    headerPtrLast.pBuffer = NULL;
+    headerPtrLast.nFlags = EB_BUFFERFLAG_EOS;
+
+    GST_DEBUG_OBJECT (encoder, "drain frame");
+    svt_ret = EbH265EncSendPicture (encoder->svt_handle, &headerPtrLast);
+    encoder->svt_eos_flag = EOS_REACHED;
+  } else {
+    GST_LOG_OBJECT (encoder, "encode frame");
+    svt_ret = EbH265EncSendPicture (encoder->svt_handle, headerPtr);
+    encoder->first_buffer = FALSE;
+  }
+
+  GST_LOG_OBJECT (encoder, "encoder result (%d)", svt_ret);
+
+  if (svt_ret != EB_ErrorNone) {
+    GST_ELEMENT_ERROR (encoder, STREAM, ENCODE,
+        ("Encode svthevc frame failed."),
+        ("svthevc_encoder_encode return code=%d", svt_ret));
+    ret = GST_FLOW_ERROR;
+  }
+
+  /* Input frame is now queued */
+  if (frame) {
+    gst_video_frame_unmap (&vframe);
+    gst_video_codec_frame_unref (frame);
+  }
+
+  return ret;
+}
+
+static GstVideoCodecFrame *
+gst_svthevc_encoder_get_frame (GstVideoEncoder * encoder, GstClockTime ts)
+{
+  GList *g;
+  GList *frames;
+  GstVideoCodecFrame *frame = NULL;
+
+  GST_LOG_OBJECT (encoder, "timestamp : %" GST_TIME_FORMAT, GST_TIME_ARGS (ts));
+
+  frames = gst_video_encoder_get_frames (GST_VIDEO_ENCODER (encoder));
+
+  for (g = frames; g; g = g->next) {
+    GstVideoCodecFrame *tmp = g->data;
+
+    if (tmp->pts == ts) {
+      frame = gst_video_codec_frame_ref (tmp);
+      break;
+    }
+  }
+
+  g_list_free_full (frames, (GDestroyNotify) gst_video_codec_frame_unref);
+
+  return frame;
+}
+
+static GstClockTime
+gst_svthevc_encoder_get_oldest_pts (GstVideoEncoder * encoder)
+{
+  GList *g;
+  GList *frames;
+  GstClockTime min_ts = GST_CLOCK_TIME_NONE;
+  gboolean seen_none = FALSE;
+
+  frames = gst_video_encoder_get_frames (GST_VIDEO_ENCODER (encoder));
+
+  /* find the lowest unsent PTS */
+  for (g = frames; g; g = g->next) {
+    GstVideoCodecFrame *tmp = g->data;
+
+    if (!GST_CLOCK_TIME_IS_VALID (tmp->abidata.ABI.ts)) {
+      seen_none = TRUE;
+      continue;
+    }
+
+    if (!GST_CLOCK_TIME_IS_VALID (min_ts) || tmp->abidata.ABI.ts < min_ts) {
+      if (!seen_none)
+        min_ts = tmp->abidata.ABI.ts;
+    }
+  }
+
+  g_list_free_full (frames, (GDestroyNotify) gst_video_codec_frame_unref);
+
+  return min_ts;
+}
+
+static GstFlowReturn
+gst_svthevc_enc_receive_frame (GstSvtHevcEnc * encoder,
+    gboolean * got_packet, gboolean send)
+{
+  GstVideoCodecFrame *frame = NULL;
+  GstBuffer *out_buf = NULL;
+  GstFlowReturn ret = GST_FLOW_OK;
+  EB_BUFFERHEADERTYPE *output_buffer = NULL;
+  EB_ERRORTYPE svt_ret;
+
+  *got_packet = FALSE;
+
+  if (encoder->svt_eos_flag == EOS_TOTRIGGER)
+    return GST_FLOW_EOS;
+
+  svt_ret =
+      EbH265GetPacket (encoder->svt_handle, &output_buffer,
+      encoder->svt_eos_flag);
+
+  if (svt_ret == EB_NoErrorEmptyQueue) {
+    GST_DEBUG_OBJECT (encoder, "no output yet");
+    return GST_FLOW_OK;
+  }
+
+  if (svt_ret != EB_ErrorNone || !output_buffer) {
+    GST_ELEMENT_ERROR (encoder, STREAM, ENCODE,
+        ("Encode svthevc frame failed."),
+        ("EbH265GetPacket return code=%d", svt_ret));
+    return GST_FLOW_ERROR;
+  }
+
+  GST_LOG_OBJECT (encoder, "got %d from svt", output_buffer->nFlags);
+
+  *got_packet = TRUE;
+
+  frame =
+      gst_svthevc_encoder_get_frame (GST_VIDEO_ENCODER (encoder),
+      output_buffer->pts);
+
+  if (!frame && send) {
+    GST_ELEMENT_ERROR (encoder, STREAM, ENCODE,
+        ("Encode svthevc frame failed."), ("Frame not found."));
+    ret = GST_FLOW_ERROR;
+    goto out;
+  }
+
+  if (!send || !frame) {
+    GST_DEBUG_OBJECT (encoder, "not sending (%d) or frame not found (%d)", send,
+        frame != NULL);
+    ret = GST_FLOW_OK;
+    goto out;
+  }
+
+  GST_LOG_OBJECT (encoder,
+      "output picture ready system=%d frame found %d",
+      frame->system_frame_number, frame != NULL);
+
+  if (encoder->update_latency) {
+    gst_svthevc_enc_set_latency (encoder);
+    encoder->update_latency = FALSE;
+  }
+
+  out_buf = gst_buffer_new_allocate (NULL, output_buffer->nFilledLen, NULL);
+  gst_buffer_fill (out_buf, 0, output_buffer->pBuffer,
+      output_buffer->nFilledLen);
+
+  frame->output_buffer = out_buf;
+
+  if (output_buffer->sliceType == EB_IDR_PICTURE)
+    GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
+  else
+    GST_VIDEO_CODEC_FRAME_UNSET_SYNC_POINT (frame);
+
+  if (encoder->push_header) {
+    GstBuffer *header;
+
+    header = gst_svthevc_enc_get_header_buffer (encoder);
+    frame->output_buffer = gst_buffer_append (header, frame->output_buffer);
+    encoder->push_header = FALSE;
+  }
+
+  frame->pts = output_buffer->pts;
+
+  if (encoder->pred_structure) {
+    /* Since the SVT-HEVC does not support adjust dts when bframe was enabled,
+     * output pts can be smaller than dts. The maximum difference between DTS and PTS can be calculated
+     * using the PTS difference between the first frame and the second frame.
+     */
+    if (encoder->dts_offset == 0) {
+      if (encoder->first_frame) {
+        if (frame->pts > encoder->first_frame->pts) {
+          encoder->dts_offset = frame->pts - encoder->first_frame->pts;
+        } else {
+          GstVideoInfo *info = &encoder->input_state->info;
+          GstClockTime duration;
+          gdouble framerate;
+
+          GST_WARNING_OBJECT (encoder, "Could not calculate DTS offset");
+
+          /* No way to get maximum bframe count since SVT-HEVC does not support it,
+           * so using keyframe interval instead.
+           */
+          if (info->fps_d == 0 || info->fps_n == 0) {
+            /* No way to get duration, assume 60fps. */
+            duration = gst_util_uint64_scale (1, GST_SECOND, 60);
+            framerate = 60;
+          } else {
+            duration =
+                gst_util_uint64_scale (info->fps_d, GST_SECOND, info->fps_n);
+            gst_util_fraction_to_double (info->fps_n, info->fps_d, &framerate);
+          }
+
+          if (encoder->keyintmax > 0) {
+            encoder->dts_offset = duration * encoder->keyintmax;
+          } else {
+            /* The SVT-HEVC sets the default gop-size the closest possible to
+             * 1 second without breaking the minigop.
+             */
+            gint mini_gop = (1 << (encoder->hierarchical_level));
+            gint keyintmin = ((int) ((framerate) / mini_gop) * (mini_gop));
+            gint keyintmax =
+                ((int) ((framerate + mini_gop) / mini_gop) * (mini_gop));
+            gint keyint =
+                (ABS ((framerate - keyintmax)) >
+                ABS ((framerate - keyintmin))) ? keyintmin : keyintmax;
+
+            if (encoder->enable_open_gop)
+              keyint -= 1;
+
+            encoder->dts_offset = duration * keyint;
+          }
+        }
+
+        GST_INFO_OBJECT (encoder, "Calculated DTS offset %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (encoder->dts_offset));
+
+        encoder->first_frame->dts =
+            gst_svthevc_encoder_get_oldest_pts (GST_VIDEO_ENCODER (encoder));
+        if (GST_CLOCK_TIME_IS_VALID (encoder->first_frame->dts))
+          encoder->first_frame->dts -= encoder->dts_offset;
+
+        GST_LOG_OBJECT (encoder,
+            "output: frame dts %" GST_TIME_FORMAT " pts %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (encoder->first_frame->dts),
+            GST_TIME_ARGS (encoder->first_frame->pts));
+
+        ret =
+            gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (encoder),
+            encoder->first_frame);
+        encoder->first_frame = NULL;
+      } else {
+        encoder->first_frame = frame;
+        frame = NULL;
+        goto out;
+      }
+    }
+
+    frame->dts =
+        gst_svthevc_encoder_get_oldest_pts (GST_VIDEO_ENCODER (encoder));
+    if (GST_CLOCK_TIME_IS_VALID (frame->dts))
+      frame->dts -= encoder->dts_offset;
+  }
+
+  GST_LOG_OBJECT (encoder,
+      "output: frame dts %" GST_TIME_FORMAT " pts %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (frame->dts), GST_TIME_ARGS (frame->pts));
+
+out:
+  if (output_buffer->nFlags == EB_BUFFERFLAG_EOS)
+    encoder->svt_eos_flag = EOS_TOTRIGGER;
+
+  if (output_buffer)
+    EbH265ReleaseOutBuffer (&output_buffer);
+
+  if (frame)
+    ret = gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (encoder), frame);
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_svthevc_enc_drain_encoder (GstSvtHevcEnc * encoder, gboolean send)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  gboolean got_packet;
+
+  /* first send the remaining frames */
+
+  if (G_UNLIKELY (encoder->svt_handle == NULL) ||
+      G_UNLIKELY (encoder->svt_eos_flag == EOS_TOTRIGGER))
+    goto done;
+
+  ret = gst_svthevc_enc_send_frame (encoder, NULL);
+
+  if (ret != GST_FLOW_OK)
+    goto done;
+
+  do {
+    ret = gst_svthevc_enc_receive_frame (encoder, &got_packet, send);
+    GST_LOG_OBJECT (encoder, "ret %d, got_packet %d", ret, got_packet);
+    if (ret != GST_FLOW_OK)
+      break;
+  } while (got_packet);
+
+done:
+  if (encoder->first_frame) {
+    GST_LOG_OBJECT (encoder,
+        "output: frame dts %" GST_TIME_FORMAT " pts %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (encoder->first_frame->dts),
+        GST_TIME_ARGS (encoder->first_frame->pts));
+    gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (encoder),
+        encoder->first_frame);
+    encoder->first_frame = NULL;
+  }
+
+  return ret;
+}
+
+static void
+gst_svthevc_enc_reconfig (GstSvtHevcEnc * encoder)
+{
+  encoder->reconfig = TRUE;
+}
+
+static void
+gst_svthevc_enc_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstSvtHevcEnc *encoder;
+  GstState state;
+
+  encoder = GST_SVTHEVC_ENC (object);
+
+  GST_OBJECT_LOCK (encoder);
+
+  state = GST_STATE (encoder);
+  if ((state != GST_STATE_READY && state != GST_STATE_NULL) &&
+      !(pspec->flags & GST_PARAM_MUTABLE_PLAYING))
+    goto wrong_state;
+
+  switch (prop_id) {
+    case PROP_INSERT_VUI:
+      encoder->insert_vui = g_value_get_boolean (value);
+      break;
+    case PROP_AUD:
+      encoder->aud = g_value_get_boolean (value);
+      break;
+    case PROP_HIERARCHICAL_LEVEL:
+      encoder->hierarchical_level = g_value_get_enum (value);
+      break;
+    case PROP_LOOKAHEAD_DISTANCE:
+      encoder->la_depth = g_value_get_uint (value);
+      break;
+    case PROP_ENCODER_MODE:
+      encoder->enc_mode = g_value_get_uint (value);
+      break;
+    case PROP_RC_MODE:
+      encoder->rc_mode = g_value_get_enum (value);
+      break;
+    case PROP_QP_I:
+      encoder->qp_i = g_value_get_uint (value);
+      break;
+    case PROP_QP_MAX:
+      encoder->qp_max = g_value_get_uint (value);
+      break;
+    case PROP_QP_MIN:
+      encoder->qp_min = g_value_get_uint (value);
+      break;
+    case PROP_SCENE_CHANGE_DETECTION:
+      encoder->scene_change_detection = g_value_get_boolean (value);
+      break;
+    case PROP_TUNE:
+      encoder->tune = g_value_get_enum (value);
+      break;
+    case PROP_BASE_LAYER_SWITCH_MODE:
+      encoder->base_layer_switch_mode = g_value_get_enum (value);
+      break;
+    case PROP_BITRATE:
+      encoder->bitrate = g_value_get_uint (value);
+      break;
+    case PROP_KEY_INT_MAX:
+      encoder->keyintmax = g_value_get_int (value);
+      break;
+    case PROP_ENABLE_OPEN_GOP:
+      encoder->enable_open_gop = g_value_get_boolean (value);
+      break;
+    case PROP_CONFIG_INTERVAL:
+      encoder->config_interval = g_value_get_uint (value);
+      break;
+    case PROP_CORES:
+      encoder->cores = g_value_get_uint (value);
+      break;
+    case PROP_SOCKET:
+      encoder->socket = g_value_get_int (value);
+      break;
+    case PROP_TILE_ROW:
+      encoder->tile_row = g_value_get_uint (value);
+      break;
+    case PROP_TILE_COL:
+      encoder->tile_col = g_value_get_uint (value);
+      break;
+    case PROP_PRED_STRUCTURE:
+      encoder->pred_structure = g_value_get_enum (value);
+      break;
+    case PROP_VBV_MAX_RATE:
+      encoder->vbv_maxrate = g_value_get_uint (value);
+      break;
+    case PROP_VBV_BUFFER_SIZE:
+      encoder->vbv_bufsize = g_value_get_uint (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+
+  gst_svthevc_enc_reconfig (encoder);
+  GST_OBJECT_UNLOCK (encoder);
+  return;
+
+wrong_state:
+  {
+    GST_WARNING_OBJECT (encoder, "setting property in wrong state");
+    GST_OBJECT_UNLOCK (encoder);
+  }
+}
+
+static void
+gst_svthevc_enc_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstSvtHevcEnc *encoder;
+
+  encoder = GST_SVTHEVC_ENC (object);
+
+  GST_OBJECT_LOCK (encoder);
+  switch (prop_id) {
+    case PROP_INSERT_VUI:
+      g_value_set_boolean (value, encoder->insert_vui);
+      break;
+    case PROP_AUD:
+      g_value_set_boolean (value, encoder->aud);
+      break;
+    case PROP_HIERARCHICAL_LEVEL:
+      g_value_set_enum (value, encoder->hierarchical_level);
+      break;
+    case PROP_LOOKAHEAD_DISTANCE:
+      g_value_set_uint (value, encoder->la_depth);
+      break;
+    case PROP_ENCODER_MODE:
+      g_value_set_uint (value, encoder->enc_mode);
+      break;
+    case PROP_RC_MODE:
+      g_value_set_enum (value, encoder->rc_mode);
+      break;
+    case PROP_QP_I:
+      g_value_set_uint (value, encoder->qp_i);
+      break;
+    case PROP_QP_MAX:
+      g_value_set_uint (value, encoder->qp_max);
+      break;
+    case PROP_QP_MIN:
+      g_value_set_uint (value, encoder->qp_min);
+      break;
+    case PROP_SCENE_CHANGE_DETECTION:
+      g_value_set_boolean (value, encoder->scene_change_detection);
+      break;
+    case PROP_TUNE:
+      g_value_set_enum (value, encoder->tune);
+      break;
+    case PROP_BASE_LAYER_SWITCH_MODE:
+      g_value_set_enum (value, encoder->base_layer_switch_mode);
+      break;
+    case PROP_BITRATE:
+      g_value_set_uint (value, encoder->bitrate);
+      break;
+    case PROP_KEY_INT_MAX:
+      g_value_set_int (value, encoder->keyintmax);
+      break;
+    case PROP_ENABLE_OPEN_GOP:
+      g_value_set_boolean (value, encoder->enable_open_gop);
+      break;
+    case PROP_CONFIG_INTERVAL:
+      g_value_set_uint (value, encoder->config_interval);
+      break;
+    case PROP_CORES:
+      g_value_set_uint (value, encoder->cores);
+      break;
+    case PROP_SOCKET:
+      g_value_set_int (value, encoder->socket);
+      break;
+    case PROP_TILE_ROW:
+      g_value_set_uint (value, encoder->tile_row);
+      break;
+    case PROP_TILE_COL:
+      g_value_set_uint (value, encoder->tile_col);
+      break;
+    case PROP_PRED_STRUCTURE:
+      g_value_set_enum (value, encoder->pred_structure);
+      break;
+    case PROP_VBV_MAX_RATE:
+      g_value_set_uint (value, encoder->vbv_maxrate);
+      break;
+    case PROP_VBV_BUFFER_SIZE:
+      g_value_set_uint (value, encoder->vbv_bufsize);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_OBJECT_UNLOCK (encoder);
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  GST_DEBUG_CATEGORY_INIT (svthevc_enc_debug, "svthevcenc", 0,
+      "h265 encoding element");
+
+  return gst_element_register (plugin, "svthevcenc",
+      GST_RANK_PRIMARY, GST_TYPE_SVTHEVC_ENC);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    svthevcenc,
+    "svt-hevc encoder based H265 plugins",
+    plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/ext/svthevcenc/gstsvthevcenc.h b/ext/svthevcenc/gstsvthevcenc.h
new file mode 100644 (file)
index 0000000..f8f6eaf
--- /dev/null
@@ -0,0 +1,139 @@
+/* GStreamer H265 encoder plugin
+ * Copyright (C) 2019 Yeongjin Jeong <yeongjin.jeong@navercorp.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_SVTHEVC_ENC_H__
+#define __GST_SVTHEVC_ENC_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/video/gstvideoencoder.h>
+
+#include <EbApi.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_SVTHEVC_ENC \
+  (gst_svthevc_enc_get_type())
+G_DECLARE_FINAL_TYPE (GstSvtHevcEnc, gst_svthevc_enc, GST, SVTHEVC_ENC, GstVideoEncoder)
+#define GST_SVTHEVC_ENC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SVTHEVC_ENC,GstSvtHevcEncClass))
+#define GST_IS_SVTHEVC_ENC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SVTHEVC_ENC))
+
+typedef enum svt_eos_status
+{
+  EOS_NOT_REACHED = 0,
+  EOS_REACHED,
+  EOS_TOTRIGGER
+} SVT_EOS_STATUS;
+
+typedef enum
+{
+  GST_SVTHEVC_ENC_B_PYRAMID_FLAT,
+  GST_SVTHEVC_ENC_B_PYRAMID_2LEVEL_HIERARCHY,
+  GST_SVTHEVC_ENC_B_PYRAMID_3LEVEL_HIERARCHY,
+  GST_SVTHEVC_ENC_B_PYRAMID_4LEVEL_HIERARCHY,
+} GstSvtHevcEncBPyramid;
+
+typedef enum
+{
+  GST_SVTHEVC_ENC_BASE_LAYER_MODE_BFRAME,
+  GST_SVTHEVC_ENC_BASE_LAYER_MODE_PFRAME,
+} GstSvtHevcEncBaseLayerMode;
+
+typedef enum
+{
+  GST_SVTHEVC_ENC_RC_CQP,
+  GST_SVTHEVC_ENC_RC_VBR,
+} GstSvtHevcEncRC;
+
+typedef enum
+{
+  GST_SVTHEVC_ENC_TUNE_SQ,
+  GST_SVTHEVC_ENC_TUNE_OQ,
+  GST_SVTHEVC_ENC_TUNE_VMAF,
+} GstSvtHevcEncTune;
+
+typedef enum
+{
+  GST_SVTHEVC_ENC_PRED_STRUCT_LOW_DELAY_P,
+  GST_SVTHEVC_ENC_PRED_STRUCT_LOW_DELAY_B,
+  GST_SVTHEVC_ENC_PRED_STRUCT_RANDOM_ACCESS,
+} GstSvtHevcEncPredStruct;
+
+struct _GstSvtHevcEnc
+{
+  GstVideoEncoder element;
+
+  /*< private > */
+  const gchar *svthevc_version;
+  EB_H265_ENC_CONFIGURATION enc_params;
+  EB_COMPONENTTYPE *svt_handle;
+
+  EB_BUFFERHEADERTYPE *in_buf;
+
+  SVT_EOS_STATUS svt_eos_flag;
+
+  GstClockTime dts_offset;
+  GstVideoCodecFrame *first_frame;
+  gboolean push_header;
+  gboolean first_buffer;
+  gboolean update_latency;
+
+  /* Internally used for convert stride to multiple of pstride */
+  GstBufferPool *internal_pool;
+  GstVideoInfo *aligned_info;
+
+  /* properties */
+  gboolean insert_vui;
+  gboolean aud;
+  GstSvtHevcEncBPyramid hierarchical_level;
+  guint la_depth;
+  guint enc_mode;
+  GstSvtHevcEncRC rc_mode;
+  guint qp_i;
+  guint qp_max;
+  guint qp_min;
+  gboolean scene_change_detection;
+  GstSvtHevcEncTune tune;
+  GstSvtHevcEncBaseLayerMode base_layer_switch_mode;
+  guint bitrate;
+  gint keyintmax;
+  gboolean enable_open_gop;
+  guint config_interval;
+  guint cores;
+  gint socket;
+  guint tile_row;
+  guint tile_col;
+  GstSvtHevcEncPredStruct pred_structure;
+  guint vbv_maxrate;
+  guint vbv_bufsize;
+
+  guint profile;
+  guint tier;
+  guint level;
+
+  /* input description */
+  GstVideoCodecState *input_state;
+
+  /* configuration changed  while playing */
+  gboolean reconfig;
+};
+
+G_END_DECLS
+#endif /* __GST_SVTHEVC_ENC_H__ */
diff --git a/ext/svthevcenc/meson.build b/ext/svthevcenc/meson.build
new file mode 100644 (file)
index 0000000..9efe54b
--- /dev/null
@@ -0,0 +1,18 @@
+svthevcenc_sources = [
+  'gstsvthevcenc.c',
+]
+
+svthevcenc_dep = dependency('SvtHevcEnc', version : '>= 1.4.1', required: get_option('svthevcenc'))
+
+if svthevcenc_dep.found()
+  gstsvthevcenc = library('gstsvthevcenc',
+    svthevcenc_sources,
+    c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'],
+    include_directories : [configinc],
+    dependencies : [gstbase_dep, gstpbutils_dep, gstvideo_dep, gstcodecparsers_dep, svthevcenc_dep],
+    install : true,
+    install_dir : plugins_install_dir,
+  )
+  pkgconfig.generate(gstsvthevcenc, install_dir : plugins_pkgconfig_install_dir)
+  plugins += [gstsvthevcenc]
+endif
index 0bcb5c0..65574fb 100644 (file)
@@ -144,6 +144,7 @@ option('soundtouch', type : 'feature', value : 'auto', description : 'Audio pitc
 option('spandsp', type : 'feature', value : 'auto', description : 'Packet loss concealment audio plugin')
 option('srt', type : 'feature', value : 'auto', description : 'Secure, Reliable, Transport client/server network source/sink plugin')
 option('srtp', type : 'feature', value : 'auto', description : 'Secure RTP codec plugin')
+option('svthevcenc', type : 'feature', value : 'auto', description : 'Scalable Video Technology for HEVC encoder plugin')
 option('teletext', type : 'feature', value : 'auto', description : 'Teletext plugin')
 option('tinyalsa', type : 'feature', value : 'auto', description : 'TinyALSA plugin')
 option('transcode', type : 'feature', value : 'auto', description : 'Transcode plugin')
diff --git a/tests/check/elements/svthevcenc.c b/tests/check/elements/svthevcenc.c
new file mode 100644 (file)
index 0000000..d1234fc
--- /dev/null
@@ -0,0 +1,280 @@
+/* GStreamer
+ * Copyright (C) 2019 Yeongjin Jeong <yeongjin.jeong@navercorp.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <gst/check/gstcheck.h>
+#include <gst/check/gstharness.h>
+
+GST_START_TEST (test_encode_simple)
+{
+  GstHarness *h;
+  GstCaps *outcaps, *caps;
+  gint i = 0;
+
+  h = gst_harness_new_parse ("svthevcenc speed=9 bitrate=1000 ! h265parse");
+
+  gst_harness_add_src_parse (h, "videotestsrc is-live=true ! "
+      "capsfilter caps=\"video/x-raw,width=320,height=240,framerate=25/1\"",
+      TRUE);
+
+  /* Push 25 buffers into the encoder */
+  fail_unless_equals_int (GST_FLOW_OK,
+      gst_harness_src_crank_and_push_many (h, 25, 25));
+
+  /* EOS will cause the remaining buffers to be drained */
+  fail_unless (gst_harness_push_event (h, gst_event_new_eos ()));
+  fail_unless_equals_int (gst_harness_buffers_received (h), 25);
+
+  outcaps =
+      gst_caps_from_string
+      ("video/x-h265,width=(int)320,height=(int)240,framerate=(fraction)25/1");
+
+  caps = gst_pad_get_current_caps (h->sinkpad);
+  fail_unless (caps != NULL);
+  fail_unless (gst_caps_can_intersect (caps, outcaps));
+
+  for (i = 0; i < 25; i++) {
+    GstBuffer *buffer = gst_harness_pull (h);
+
+    fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer),
+        gst_util_uint64_scale (1, GST_SECOND, 25));
+
+    gst_buffer_unref (buffer);
+  }
+  gst_caps_unref (outcaps);
+  gst_caps_unref (caps);
+
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_reuse)
+{
+  GstHarness *h;
+  GstCaps *srccaps, *outcaps, *caps;
+  GstBuffer *in_buf = NULL;
+  GstFlowReturn ret;
+  GstSegment seg;
+  gint loop, i;
+
+  h = gst_harness_new_parse ("svthevcenc speed=9 bitrate=1000");
+
+  srccaps =
+      gst_caps_from_string
+      ("video/x-raw,format=I420,width=(int)320,height=(int)240,framerate=(fraction)25/1");
+  outcaps =
+      gst_caps_from_string
+      ("video/x-h265,width=(int)320,height=(int)240,framerate=(fraction)25/1");
+
+  in_buf = gst_buffer_new_and_alloc ((320 * 240) * 3 / 2);
+  gst_buffer_memset (in_buf, 0, 0, -1);
+
+  GST_BUFFER_DURATION (in_buf) = gst_util_uint64_scale (1, GST_SECOND, 25);
+  GST_BUFFER_DTS (in_buf) = GST_CLOCK_TIME_NONE;
+
+  gst_segment_init (&seg, GST_FORMAT_TIME);
+
+  for (loop = 0; loop < 2; loop++) {
+    gst_harness_play (h);
+
+    fail_unless (gst_harness_push_event (h,
+            gst_event_new_stream_start ("test")));
+    fail_unless (gst_harness_push_event (h, gst_event_new_caps (srccaps)));
+    fail_unless (gst_harness_push_event (h, gst_event_new_segment (&seg)));
+
+    for (i = 0; i < 25; i++) {
+      GST_BUFFER_PTS (in_buf) = gst_util_uint64_scale (i, GST_SECOND, 25);
+
+      ret = gst_harness_push (h, gst_buffer_ref (in_buf));
+      fail_unless (ret == GST_FLOW_OK, "GstFlowReturn was %s",
+          gst_flow_get_name (ret));
+    }
+
+    /* EOS will cause the remaining buffers to be drained */
+    fail_unless (gst_harness_push_event (h, gst_event_new_eos ()));
+    fail_unless_equals_int (gst_harness_buffers_received (h), (loop + 1) * 25);
+
+    caps = gst_pad_get_current_caps (h->sinkpad);
+    fail_unless (caps != NULL);
+    fail_unless (gst_caps_can_intersect (caps, outcaps));
+
+    for (i = 0; i < 25; i++) {
+      GstBuffer *buffer = gst_harness_pull (h);
+
+      fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer),
+          gst_util_uint64_scale (1, GST_SECOND, 25));
+
+      gst_buffer_unref (buffer);
+    }
+    gst_caps_unref (caps);
+
+    ASSERT_SET_STATE (h->element, GST_STATE_READY, GST_STATE_CHANGE_SUCCESS);
+  }
+  gst_caps_unref (srccaps);
+  gst_caps_unref (outcaps);
+  gst_buffer_unref (in_buf);
+
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_no_encoding)
+{
+  GstHarness *h;
+  GstCaps *caps;
+  GstEvent *event;
+
+  h = gst_harness_new_parse ("svthevcenc");
+  fail_unless (h != NULL);
+
+  gst_harness_play (h);
+
+  caps = gst_caps_from_string
+      ("video/x-raw,format=I420,width=(int)320,height=(int)240,framerate=(fraction)25/1");
+  gst_harness_set_src_caps (h, caps);
+
+  /* Check if draining are performed well without a buffer push */
+  fail_unless (gst_harness_push_event (h, gst_event_new_eos ()));
+
+  do {
+    gboolean term = FALSE;
+    event = gst_harness_pull_event (h);
+    /* wait until get EOS event */
+    if (event) {
+      if (GST_EVENT_TYPE (event) == GST_EVENT_EOS)
+        term = TRUE;
+
+      gst_event_unref (event);
+
+      if (term)
+        break;
+    }
+  } while (TRUE);
+
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+#define MAX_PUSH_BUFFER 300
+
+GST_START_TEST (test_reconfigure)
+{
+  GstHarness *h;
+  GstElement *svthevcenc;
+  GstCaps *caps;
+  GstEvent *event;
+  GstBuffer *in_buf, *out_buf = NULL;
+  GstFlowReturn ret;
+  gint i = 0;
+
+  h = gst_harness_new_parse ("svthevcenc ! h265parse");
+  fail_unless (h != NULL);
+
+  svthevcenc = gst_harness_find_element (h, "svthevcenc");
+  g_object_set (svthevcenc, "speed", 9, NULL);
+
+  gst_harness_play (h);
+
+  caps = gst_caps_from_string
+      ("video/x-raw,format=I420,width=(int)320,height=(int)240,framerate=(fraction)25/1");
+  gst_harness_set_src_caps (h, caps);
+
+  in_buf = gst_buffer_new_and_alloc ((320 * 240) * 3 / 2);
+  gst_buffer_memset (in_buf, 0, 0, -1);
+
+  GST_BUFFER_DURATION (in_buf) = GST_SECOND;
+  GST_BUFFER_DTS (in_buf) = GST_CLOCK_TIME_NONE;
+
+  /* Push bufffers until get encoder output */
+  do {
+    fail_if (i > MAX_PUSH_BUFFER);
+
+    GST_BUFFER_PTS (in_buf) = i * GST_SECOND;
+
+    ret = gst_harness_push (h, gst_buffer_ref (in_buf));
+    fail_unless (ret == GST_FLOW_OK, "GstFlowReturn was %s",
+        gst_flow_get_name (ret));
+
+    out_buf = gst_harness_try_pull (h);
+    i++;
+  } while (out_buf == NULL);
+  gst_buffer_unref (out_buf);
+
+  /* Change property for reconfig */
+  g_object_set (svthevcenc, "speed", 8, NULL);
+
+  /* Push bufffers until get encoder output */
+  do {
+    fail_if (i > 2 * MAX_PUSH_BUFFER);
+
+    GST_BUFFER_PTS (in_buf) = i * GST_SECOND;
+
+    ret = gst_harness_push (h, gst_buffer_ref (in_buf));
+    fail_unless (ret == GST_FLOW_OK, "GstFlowReturn was %s",
+        gst_flow_get_name (ret));
+
+    out_buf = gst_harness_try_pull (h);
+    i++;
+  } while (out_buf == NULL);
+  gst_buffer_unref (out_buf);
+  gst_buffer_unref (in_buf);
+
+  /* push EOS to drain all buffers */
+  fail_unless (gst_harness_push_event (h, gst_event_new_eos ()));
+
+  do {
+    gboolean term = FALSE;
+    event = gst_harness_pull_event (h);
+    /* wait until get EOS event */
+    if (event) {
+      if (GST_EVENT_TYPE (event) == GST_EVENT_EOS)
+        term = TRUE;
+
+      gst_event_unref (event);
+
+      if (term)
+        break;
+    }
+  } while (out_buf == NULL);
+
+  gst_object_unref (svthevcenc);
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+static Suite *
+svthevcenc_suite (void)
+{
+  Suite *s = suite_create ("svthevcenc");
+  TCase *tc_chain = tcase_create ("general");
+
+  suite_add_tcase (s, tc_chain);
+
+  tcase_add_test (tc_chain, test_encode_simple);
+  tcase_add_test (tc_chain, test_reuse);
+  tcase_add_test (tc_chain, test_no_encoding);
+  tcase_add_test (tc_chain, test_reconfigure);
+
+  return s;
+}
+
+GST_CHECK_MAIN (svthevcenc);
index 52acd79..911c174 100644 (file)
@@ -37,6 +37,7 @@ base_tests = [
   [['elements/mxfmux.c']],
   [['elements/nvenc.c'], false, [gmodule_dep, gstgl_dep]],
   [['elements/nvdec.c'], not gstgl_dep.found(), [gmodule_dep, gstgl_dep]],
+  [['elements/svthevcenc.c'], not svthevcenc_dep.found(), [svthevcenc_dep]],
   [['elements/pcapparse.c'], false, [libparser_dep]],
   [['elements/pnm.c']],
   [['elements/rtponvifparse.c']],