From f241294662f3b48c10e527e9949aa2449e51739a Mon Sep 17 00:00:00 2001 From: Thijs Vermeir Date: Wed, 5 Mar 2014 16:39:30 +0100 Subject: [PATCH] x265enc: add x265 encoder element --- configure.ac | 8 + ext/Makefile.am | 12 +- ext/x265/Makefile.am | 32 ++ ext/x265/gstx265enc.c | 1236 +++++++++++++++++++++++++++++++++++++++++++++++++ ext/x265/gstx265enc.h | 87 ++++ 5 files changed, 1373 insertions(+), 2 deletions(-) create mode 100644 ext/x265/Makefile.am create mode 100644 ext/x265/gstx265enc.c create mode 100644 ext/x265/gstx265enc.h diff --git a/configure.ac b/configure.ac index 3afa850..7e0d2c3 100644 --- a/configure.ac +++ b/configure.ac @@ -3026,6 +3026,12 @@ AG_GST_CHECK_FEATURE(HLS, [http live streaming plugin], hls, [ ]) ]) +dnl *** x265 (H.265/HEVC encoder) *** +translit(dnm, m, l) AM_CONDITIONAL(USE_X265, true) +AG_GST_CHECK_FEATURE(X265, [x265 plug-in], x265, [ + AG_GST_PKG_CHECK_MODULES(X265, x265) +]) + else dnl not building plugins with external dependencies, @@ -3108,6 +3114,7 @@ AM_CONDITIONAL(USE_SNDIO, false) AM_CONDITIONAL(USE_UVCH264, false) AM_CONDITIONAL(USE_WEBP, false) AM_CONDITIONAL(USE_OPENH264, false) +AM_CONDITIONAL(USE_X265, false) fi dnl of EXT plugins @@ -3401,6 +3408,7 @@ ext/gsettings/org.freedesktop.gstreamer.default-elements.gschema.xml ext/spc/Makefile ext/timidity/Makefile ext/webp/Makefile +ext/x265/Makefile ext/xvid/Makefile ext/zbar/Makefile po/Makefile.in diff --git a/ext/Makefile.am b/ext/Makefile.am index 37eece0..3859cf7 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -406,6 +406,12 @@ else WEBP_DIR= endif +if USE_X265 +X265_DIR=x265 +else +X265_DIR= +endif + SUBDIRS=\ $(VOAACENC_DIR) \ $(ASSRENDER_DIR) \ @@ -473,7 +479,8 @@ SUBDIRS=\ $(ZBAR_DIR) \ $(RTMP_DIR) \ $(HLS_DIR) \ - $(WEBP_DIR) + $(WEBP_DIR) \ + $(X265_DIR) DIST_SUBDIRS = \ assrender \ @@ -538,6 +545,7 @@ DIST_SUBDIRS = \ xvid \ zbar \ rtmp \ - webp + webp \ + x265 include $(top_srcdir)/common/parallel-subdirs.mak diff --git a/ext/x265/Makefile.am b/ext/x265/Makefile.am new file mode 100644 index 0000000..d2a8102 --- /dev/null +++ b/ext/x265/Makefile.am @@ -0,0 +1,32 @@ +plugin_LTLIBRARIES = libgstx265.la + +libgstx265_la_SOURCES = gstx265enc.c +libgstx265_la_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(X265_CFLAGS) +libgstx265_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstvideo-$(GST_API_VERSION) \ + -lgstpbutils-$(GST_API_VERSION) \ + $(GST_LIBS) \ + $(X265_LIBS) +libgstx265_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstx265_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +noinst_HEADERS = gstx265enc.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstx265 -:SHARED libgstx265 \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstx265_la_SOURCES) \ + -:CPPFLAGS $(CPPFLAGS) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstx265_la_CFLAGS) -I'$$(GSTREAMER_AGGREGATE_TOP)/x265' \ + -:LDFLAGS $(libgstx265_la_LDFLAGS) \ + $(libgstx265_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ diff --git a/ext/x265/gstx265enc.c b/ext/x265/gstx265enc.c new file mode 100644 index 0000000..6629794 --- /dev/null +++ b/ext/x265/gstx265enc.c @@ -0,0 +1,1236 @@ +/* GStreamer H265 encoder plugin + * Copyright (C) 2005 Michal Benes + * Copyright (C) 2005 Josef Zlomek + * Copyright (C) 2008 Mark Nauwelaerts + * Copyright (C) 2014 Thijs Vermeir + * + * 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-x265enc + * + * This element encodes raw video into H265 compressed data. + * + **/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gstx265enc.h" + +#include +#include +#include +#include + +#include +#include + +GST_DEBUG_CATEGORY_STATIC (x265_enc_debug); +#define GST_CAT_DEFAULT x265_enc_debug + +enum +{ + PROP_0, + PROP_BITRATE, + PROP_OPTION_STRING, + PROP_X265_LOG_LEVEL, + PROP_SPEED_PRESET, + PROP_TUNE +}; + +static GString *x265enc_defaults; + +#define PROP_BITRATE_DEFAULT (2 * 1024) +#define PROP_OPTION_STRING_DEFAULT "" +#define PROP_LOG_LEVEL_DEFAULT -1 // None +#define PROP_SPEED_PRESET_DEFAULT 6 // Medium +#define PROP_TUNE_DEFAULT 2 // SSIM + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +#define FORMATS "I420, Y444" +#else +#define FORMATS "I420, Y444" +#endif + +#define GST_X265_ENC_LOG_LEVEL_TYPE (gst_x265_enc_log_level_get_type()) +static GType +gst_x265_enc_log_level_get_type (void) +{ + static GType log_level = 0; + + static const GEnumValue log_levels[] = { + {X265_LOG_NONE, "No logging", "none"}, + {X265_LOG_ERROR, "Error", "error"}, + {X265_LOG_WARNING, "Warning", "warning"}, + {X265_LOG_INFO, "Info", "info"}, + {X265_LOG_DEBUG, "Debug", "debug"}, + {X265_LOG_FULL, "Full", "full"}, + {0, NULL, NULL} + }; + + if (!log_level) { + log_level = g_enum_register_static ("GstX265LogLevel", log_levels); + } + return log_level; +} + +#define GST_X265_ENC_SPEED_PRESET_TYPE (gst_x265_enc_speed_preset_get_type()) +static GType +gst_x265_enc_speed_preset_get_type (void) +{ + static GType speed_preset = 0; + static GEnumValue *speed_presets; + int n, i; + + if (speed_preset != 0) + return speed_preset; + + n = 0; + while (x265_preset_names[n] != NULL) + n++; + + speed_presets = g_new0 (GEnumValue, n + 2); + + speed_presets[0].value = 0; + speed_presets[0].value_name = "No preset"; + speed_presets[0].value_nick = "No preset"; + + for (i = 0; i < n; i++) { + speed_presets[i + 1].value = i + 1; + speed_presets[i + 1].value_name = x265_preset_names[i]; + speed_presets[i + 1].value_nick = x265_preset_names[i]; + } + + speed_preset = g_enum_register_static ("GstX265SpeedPreset", speed_presets); + + return speed_preset; +} + +#define GST_X265_ENC_TUNE_TYPE (gst_x265_enc_tune_get_type()) +static GType +gst_x265_enc_tune_get_type (void) +{ + static GType tune = 0; + static GEnumValue *tune_values; + int n, i; + + if (tune != 0) + return tune; + + n = 0; + while (x265_tune_names[n] != NULL) + n++; + + tune_values = g_new0 (GEnumValue, n + 2); + + tune_values[0].value = 0; + tune_values[0].value_name = "No tunning"; + tune_values[0].value_nick = "No tunning"; + + for (i = 0; i < n; i++) { + tune_values[i + 1].value = i + 1; + tune_values[i + 1].value_name = x265_tune_names[i]; + tune_values[i + 1].value_nick = x265_tune_names[i]; + } + + tune = g_enum_register_static ("GstX265Tune", tune_values); + + return tune; +} + +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) [ 4, MAX ], " "height = (int) [ 4, MAX ]") + ); + +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) [ 4, MAX ], " "height = (int) [ 4, MAX ], " + "stream-format = (string) { avc, byte-stream }, " + "alignment = (string) au, " "profile = (string) { main }") + ); + +static void gst_x265_enc_finalize (GObject * object); +static gboolean gst_x265_enc_start (GstVideoEncoder * encoder); +static gboolean gst_x265_enc_stop (GstVideoEncoder * encoder); +static gboolean gst_x265_enc_flush (GstVideoEncoder * encoder); + +static gboolean gst_x265_enc_init_encoder (GstX265Enc * encoder); +static void gst_x265_enc_close_encoder (GstX265Enc * encoder); + +static GstFlowReturn gst_x265_enc_finish (GstVideoEncoder * encoder); +static GstFlowReturn gst_x265_enc_handle_frame (GstVideoEncoder * encoder, + GstVideoCodecFrame * frame); +static void gst_x265_enc_flush_frames (GstX265Enc * encoder, gboolean send); +static GstFlowReturn gst_x265_enc_encode_frame (GstX265Enc * encoder, + x265_picture * pic_in, GstVideoCodecFrame * input_frame, guint32 * i_nal, + gboolean send); +static gboolean gst_x265_enc_set_format (GstVideoEncoder * video_enc, + GstVideoCodecState * state); +static gboolean gst_x265_enc_propose_allocation (GstVideoEncoder * encoder, + GstQuery * query); + +static void gst_x265_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_x265_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +#define gst_x265_enc_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstX265Enc, gst_x265_enc, GST_TYPE_VIDEO_ENCODER, + G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, NULL)); + +static void +set_value (GValue * val, gint count, ...) +{ + const gchar *fmt = NULL; + GValue sval = G_VALUE_INIT; + va_list ap; + gint i; + + g_value_init (&sval, G_TYPE_STRING); + + if (count > 1) + g_value_init (val, GST_TYPE_LIST); + + va_start (ap, count); + for (i = 0; i < count; i++) { + fmt = va_arg (ap, const gchar *); + g_value_set_string (&sval, fmt); + if (count > 1) { + gst_value_list_append_value (val, &sval); + } + } + va_end (ap); + + if (count == 1) + *val = sval; + else + g_value_unset (&sval); +} + +static void +gst_x265_enc_add_x265_chroma_format (GstStructure * s, + int x265_chroma_format_local) +{ + GValue fmt = G_VALUE_INIT; + + GST_INFO ("This x265 build supports 8-bit depth"); + if (x265_chroma_format_local == 0) { + set_value (&fmt, 2, "I420", "Y444"); + } else if (x265_chroma_format_local == X265_CSP_I444) { + set_value (&fmt, 1, "Y444"); + } else if (x265_chroma_format_local == X265_CSP_I420) { + set_value (&fmt, 1, "I420"); + } else { + GST_ERROR ("Unsupported chroma format %d", x265_chroma_format_local); + } + + if (G_VALUE_TYPE (&fmt) != G_TYPE_INVALID) + gst_structure_take_value (s, "format", &fmt); +} + +static GstCaps * +gst_x265_enc_get_supported_input_caps (void) +{ + GstCaps *caps; + int x265_chroma_format = 0; + + caps = gst_caps_new_simple ("video/x-raw", + "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, + "width", GST_TYPE_INT_RANGE, 4, G_MAXINT, + "height", GST_TYPE_INT_RANGE, 4, G_MAXINT, NULL); + + gst_x265_enc_add_x265_chroma_format (gst_caps_get_structure (caps, 0), + x265_chroma_format); + + GST_DEBUG ("returning %" GST_PTR_FORMAT, caps); + return caps; +} + +static GstCaps * +gst_x265_enc_sink_getcaps (GstVideoEncoder * enc, GstCaps * filter) +{ + GstCaps *supported_incaps; + GstCaps *allowed; + GstCaps *filter_caps, *fcaps; + gint i, j; + + supported_incaps = gst_x265_enc_get_supported_input_caps (); + + if (!supported_incaps) + supported_incaps = gst_pad_get_pad_template_caps (enc->sinkpad); + allowed = gst_pad_get_allowed_caps (enc->srcpad); + + if (!allowed || gst_caps_is_empty (allowed) || gst_caps_is_any (allowed)) { + 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); + + 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); j++) { + const GstStructure *allowed_s = gst_caps_get_structure (allowed, 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, "framerate"))) + gst_structure_set_value (s, "framerate", val); + if ((val = gst_structure_get_value (allowed_s, "pixel-aspect-ratio"))) + gst_structure_set_value (s, "pixel-aspect-ratio", val); + + 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: + gst_caps_replace (&allowed, NULL); + + GST_LOG_OBJECT (enc, "proxy caps %" GST_PTR_FORMAT, fcaps); + + return fcaps; +} + +static void +gst_x265_enc_class_init (GstX265EncClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + GstVideoEncoderClass *gstencoder_class; + + x265enc_defaults = g_string_new (""); + + gobject_class = G_OBJECT_CLASS (klass); + element_class = GST_ELEMENT_CLASS (klass); + gstencoder_class = GST_VIDEO_ENCODER_CLASS (klass); + + gobject_class->set_property = gst_x265_enc_set_property; + gobject_class->get_property = gst_x265_enc_get_property; + gobject_class->finalize = gst_x265_enc_finalize; + + gstencoder_class->set_format = GST_DEBUG_FUNCPTR (gst_x265_enc_set_format); + gstencoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_x265_enc_handle_frame); + gstencoder_class->start = GST_DEBUG_FUNCPTR (gst_x265_enc_start); + gstencoder_class->stop = GST_DEBUG_FUNCPTR (gst_x265_enc_stop); + gstencoder_class->flush = GST_DEBUG_FUNCPTR (gst_x265_enc_flush); + gstencoder_class->finish = GST_DEBUG_FUNCPTR (gst_x265_enc_finish); + gstencoder_class->getcaps = GST_DEBUG_FUNCPTR (gst_x265_enc_sink_getcaps); + gstencoder_class->propose_allocation = + GST_DEBUG_FUNCPTR (gst_x265_enc_propose_allocation); + + g_object_class_install_property (gobject_class, PROP_BITRATE, + g_param_spec_uint ("bitrate", "Bitrate", "Bitrate in kbit/sec", 1, + 100 * 1024, PROP_BITRATE_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_PLAYING)); + + g_object_class_install_property (gobject_class, PROP_OPTION_STRING, + g_param_spec_string ("option-string", "Option string", + "String of x264 options (overridden by element properties)", + PROP_OPTION_STRING_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_X265_LOG_LEVEL, + g_param_spec_enum ("log-level", "(internal) x265 log level", + "x265 log level", GST_X265_ENC_LOG_LEVEL_TYPE, + PROP_LOG_LEVEL_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SPEED_PRESET, + g_param_spec_enum ("speed-preset", "Speed preset", + "Preset name for speed/quality tradeoff options", + GST_X265_ENC_SPEED_PRESET_TYPE, PROP_SPEED_PRESET_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_TUNE, + g_param_spec_enum ("tune", "Tune options", + "Preset name for tuning options", GST_X265_ENC_TUNE_TYPE, + PROP_TUNE_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_set_static_metadata (element_class, + "x265enc", "Codec/Encoder/Video", "H265 Encoder", + "Thijs Vermeir "); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); +} + +/* initialize the new element + * instantiate pads and add them to element + * set functions + * initialize structure + */ +static void +gst_x265_enc_init (GstX265Enc * encoder) +{ + x265_param_default (&encoder->x265param); + + encoder->push_header = TRUE; + + encoder->bitrate = PROP_BITRATE_DEFAULT; + encoder->option_string_prop = g_string_new (PROP_OPTION_STRING_DEFAULT); + encoder->log_level = PROP_LOG_LEVEL_DEFAULT; + encoder->speed_preset = PROP_SPEED_PRESET_DEFAULT; + encoder->tune = PROP_TUNE_DEFAULT; +} + +typedef struct +{ + GstVideoCodecFrame *frame; + GstVideoFrame vframe; +} FrameData; + +static FrameData * +gst_x265_enc_queue_frame (GstX265Enc * enc, GstVideoCodecFrame * frame, + GstVideoInfo * info) +{ + GstVideoFrame vframe; + FrameData *fdata; + + if (!gst_video_frame_map (&vframe, info, frame->input_buffer, GST_MAP_READ)) + return NULL; + + fdata = g_slice_new (FrameData); + fdata->frame = gst_video_codec_frame_ref (frame); + fdata->vframe = vframe; + + enc->pending_frames = g_list_prepend (enc->pending_frames, fdata); + + return fdata; +} + +static void +gst_x265_enc_dequeue_frame (GstX265Enc * enc, GstVideoCodecFrame * frame) +{ + GList *l; + + for (l = enc->pending_frames; l; l = l->next) { + FrameData *fdata = l->data; + + if (fdata->frame != frame) + continue; + + gst_video_frame_unmap (&fdata->vframe); + gst_video_codec_frame_unref (fdata->frame); + g_slice_free (FrameData, fdata); + + enc->pending_frames = g_list_delete_link (enc->pending_frames, l); + return; + } +} + +static void +gst_x265_enc_dequeue_all_frames (GstX265Enc * enc) +{ + GList *l; + + for (l = enc->pending_frames; l; l = l->next) { + FrameData *fdata = l->data; + + gst_video_frame_unmap (&fdata->vframe); + gst_video_codec_frame_unref (fdata->frame); + g_slice_free (FrameData, fdata); + } + g_list_free (enc->pending_frames); + enc->pending_frames = NULL; +} + +static gboolean +gst_x265_enc_start (GstVideoEncoder * encoder) +{ + //GstX265Enc *x265enc = GST_X265_ENC (encoder); + + return TRUE; +} + +static gboolean +gst_x265_enc_stop (GstVideoEncoder * encoder) +{ + GstX265Enc *x265enc = GST_X265_ENC (encoder); + + GST_DEBUG_OBJECT (encoder, "stop encoder"); + + gst_x265_enc_flush_frames (x265enc, FALSE); + gst_x265_enc_close_encoder (x265enc); + gst_x265_enc_dequeue_all_frames (x265enc); + + if (x265enc->input_state) + gst_video_codec_state_unref (x265enc->input_state); + x265enc->input_state = NULL; + + return TRUE; +} + + +static gboolean +gst_x265_enc_flush (GstVideoEncoder * encoder) +{ + GstX265Enc *x265enc = GST_X265_ENC (encoder); + + GST_DEBUG_OBJECT (encoder, "flushing encoder"); + + gst_x265_enc_flush_frames (x265enc, FALSE); + gst_x265_enc_close_encoder (x265enc); + gst_x265_enc_dequeue_all_frames (x265enc); + + gst_x265_enc_init_encoder (x265enc); + + return TRUE; +} + +static void +gst_x265_enc_finalize (GObject * object) +{ + GstX265Enc *encoder = GST_X265_ENC (object); + + if (encoder->input_state) + gst_video_codec_state_unref (encoder->input_state); + encoder->input_state = NULL; + + gst_x265_enc_close_encoder (encoder); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gint +gst_x265_enc_gst_to_x265_video_format (GstVideoFormat format, gint * nplanes) +{ + switch (format) { + case GST_VIDEO_FORMAT_I420: + case GST_VIDEO_FORMAT_YV12: + if (nplanes) + *nplanes = 3; + return X265_CSP_I420; + case GST_VIDEO_FORMAT_Y444: + if (nplanes) + *nplanes = 3; + return X265_CSP_I444; + default: + g_return_val_if_reached (GST_VIDEO_FORMAT_UNKNOWN); + } +} + +/* + * gst_x265_enc_parse_options + * @encoder: Encoder to which options are assigned + * @str: Option string + * + * Parse option string and assign to x265 parameters + * + */ +static gboolean +gst_x265_enc_parse_options (GstX265Enc * encoder, const gchar * str) +{ + GStrv kvpairs; + guint npairs, i; + gint parse_result = 0, ret = 0; + gchar *options = (gchar *) str; + + while (*options == ':') + options++; + + kvpairs = g_strsplit (options, ":", 0); + npairs = g_strv_length (kvpairs); + + for (i = 0; i < npairs; i++) { + GStrv key_val = g_strsplit (kvpairs[i], "=", 2); + + parse_result = + x265_param_parse (&encoder->x265param, key_val[0], key_val[1]); + + if (parse_result == X265_PARAM_BAD_NAME) { + GST_ERROR_OBJECT (encoder, "Bad name for option %s=%s", + key_val[0] ? key_val[0] : "", key_val[1] ? key_val[1] : ""); + } + if (parse_result == X265_PARAM_BAD_VALUE) { + GST_ERROR_OBJECT (encoder, + "Bad value for option %s=%s (Note: a NULL value for a non-boolean triggers this)", + key_val[0] ? key_val[0] : "", key_val[1] ? key_val[1] : ""); + } + + g_strfreev (key_val); + + if (parse_result) + ret++; + } + + g_strfreev (kvpairs); + return !ret; +} + +/* + * gst_x265_enc_init_encoder + * @encoder: Encoder which should be initialized. + * + * Initialize x265 encoder. + * + */ +static gboolean +gst_x265_enc_init_encoder (GstX265Enc * encoder) +{ + GstVideoInfo *info; + + if (!encoder->input_state) { + GST_DEBUG_OBJECT (encoder, "Have no input state yet"); + return FALSE; + } + + info = &encoder->input_state->info; + + /* make sure that the encoder is closed */ + gst_x265_enc_close_encoder (encoder); + + GST_OBJECT_LOCK (encoder); + + if (x265_param_default_preset (&encoder->x265param, + x265_preset_names[encoder->speed_preset - 1], + x265_tune_names[encoder->tune]) < 0) { + GST_DEBUG_OBJECT (encoder, "preset or tune unrecognized"); + GST_OBJECT_UNLOCK (encoder); + return FALSE; + } + + /* set up encoder parameters */ + encoder->x265param.logLevel = encoder->log_level; + encoder->x265param.internalCsp = + gst_x265_enc_gst_to_x265_video_format (info->finfo->format, NULL); + if (info->fps_d == 0 || info->fps_n == 0) { + } else { + encoder->x265param.fpsNum = info->fps_n; + encoder->x265param.fpsDenom = info->fps_d; + } + encoder->x265param.sourceWidth = info->width; + encoder->x265param.sourceHeight = info->height; + if (info->par_d > 0) { + encoder->x265param.vui.sarWidth = info->par_n; + encoder->x265param.vui.sarHeight = info->par_d; + } + encoder->x265param.rc.bitrate = encoder->bitrate; + encoder->x265param.rc.rateControlMode = X265_RC_ABR; + + /* apply option-string property */ + if (encoder->option_string_prop && encoder->option_string_prop->len) { + GST_DEBUG_OBJECT (encoder, "Applying option-string: %s", + encoder->option_string_prop->str); + if (gst_x265_enc_parse_options (encoder, + encoder->option_string_prop->str) == FALSE) { + GST_DEBUG_OBJECT (encoder, "Your option-string contains errors."); + GST_OBJECT_UNLOCK (encoder); + return FALSE; + } + } + + encoder->reconfig = FALSE; + + /* good start, will be corrected if needed */ + encoder->dts_offset = 0; + + GST_OBJECT_UNLOCK (encoder); + + encoder->x265enc = x265_encoder_open (&encoder->x265param); + if (!encoder->x265enc) { + GST_ELEMENT_ERROR (encoder, STREAM, ENCODE, + ("Can not initialize x265 encoder."), (NULL)); + return FALSE; + } + + encoder->push_header = TRUE; + + return TRUE; +} + +/* gst_x265_enc_close_encoder + * @encoder: Encoder which should close. + * + * Close x265 encoder. + */ +static void +gst_x265_enc_close_encoder (GstX265Enc * encoder) +{ + if (encoder->x265enc != NULL) { + x265_encoder_close (encoder->x265enc); + encoder->x265enc = NULL; + } +} + +static x265_nal * +gst_x265_enc_bytestream_to_nal (x265_nal * input) +{ + x265_nal *output; + int i, j, zeros; + + output = g_malloc (sizeof (x265_nal)); + output->payload = g_malloc (input->sizeBytes - 4); + output->sizeBytes = input->sizeBytes - 4; + output->type = input->type; + + zeros = 0; + for (i = 4, j = 0; i < input->sizeBytes; (i++, j++)) { + if (input->payload[i] == 0x00) { + zeros++; + } else if (input->payload[i] == 0x03 && zeros == 2) { + zeros = 0; + j--; + output->sizeBytes--; + continue; + } else { + zeros = 0; + } + output->payload[j] = input->payload[i]; + } + + return output; +} + +static void +x265_nal_free (x265_nal * nal) +{ + g_free (nal->payload); + g_free (nal); +} + +static gboolean +gst_x265_enc_set_level_tier_and_profile (GstX265Enc * encoder, GstCaps * caps) +{ + x265_nal *nal, *vps_nal; + guint32 i_nal; + int header_return; + + GST_DEBUG_OBJECT (encoder, "set profile, level and tier"); + + header_return = x265_encoder_headers (encoder->x265enc, &nal, &i_nal); + if (header_return < 0) { + GST_ELEMENT_ERROR (encoder, STREAM, ENCODE, ("Encode x265 header failed."), + ("x265_encoder_headers return code=%d", header_return)); + return FALSE; + } + + GST_DEBUG_OBJECT (encoder, "%d nal units in header", i_nal); + + g_assert (nal[0].type == NAL_UNIT_VPS); + vps_nal = gst_x265_enc_bytestream_to_nal (&nal[0]); + + GST_MEMDUMP ("VPS", vps_nal->payload, vps_nal->sizeBytes); + + if (!gst_codec_utils_h265_caps_set_level_tier_and_profile (caps, + vps_nal->payload + 6, vps_nal->sizeBytes - 6)) { + GST_ELEMENT_ERROR (encoder, STREAM, ENCODE, ("Encode x265 failed."), + ("Failed to find correct level, tier or profile in VPS")); + return FALSE; + } + + x265_nal_free (vps_nal); + + return TRUE; +} + +static GstBuffer * +gst_x265_enc_get_header_buffer (GstX265Enc * encoder) +{ + x265_nal *nal; + guint32 i_nal, i, offset, vps_idx; + int header_return; + GstBuffer *buf; + + header_return = x265_encoder_headers (encoder->x265enc, &nal, &i_nal); + if (header_return < 0) { + GST_ELEMENT_ERROR (encoder, STREAM, ENCODE, ("Encode x265 header failed."), + ("x265_encoder_headers return code=%d", header_return)); + return FALSE; + } + + GST_DEBUG_OBJECT (encoder, "%d nal units in header", i_nal); + + /* x265 returns also non header nal units with the call x265_encoder_headers. + * The usefull headers are sequential (VPS, SPS and PPS), so we look for this + * nal units and only copy these tree nal units as the header */ + + for (vps_idx = 0; vps_idx < i_nal; vps_idx++) { + if (nal[vps_idx].type == 32) { + break; + } + } + + if (vps_idx >= i_nal - 3) { + GST_ELEMENT_ERROR (encoder, STREAM, ENCODE, ("Encode x265 header failed."), + ("x265_encoder_headers did not return VPS, SPS and PPS")); + return FALSE; + } + + offset = 0; + buf = + gst_buffer_new_allocate (NULL, + nal[vps_idx].sizeBytes + nal[vps_idx + 1].sizeBytes + nal[vps_idx + + 2].sizeBytes, NULL); + for (i = 0; i < i_nal; i++) { + gst_buffer_fill (buf, offset, nal[i + vps_idx].payload, + nal[i + vps_idx].sizeBytes); + offset += nal[i + vps_idx].sizeBytes; + } + + return buf; +} + +/* gst_x265_enc_set_src_caps + * Returns: TRUE on success. + */ +static gboolean +gst_x265_enc_set_src_caps (GstX265Enc * 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_x265_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_DEBUG_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, "x265", + GST_TAG_ENCODER_VERSION, x265_version_str, 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_x265_enc_set_latency (GstX265Enc * encoder) +{ + GstVideoInfo *info = &encoder->input_state->info; + + if (info->fps_n) { + GstClockTime latency; + gint max_delayed_frames; + + // FIXME get a real value from the encoder, this is currently not exposed + max_delayed_frames = 5; + latency = gst_util_uint64_scale_ceil (GST_SECOND * info->fps_d, + max_delayed_frames, info->fps_n); + + 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); + } else { + /* We can't do live as we don't know our latency */ + gst_video_encoder_set_latency (GST_VIDEO_ENCODER (encoder), + 0, GST_CLOCK_TIME_NONE); + } +} + +static gboolean +gst_x265_enc_set_format (GstVideoEncoder * video_enc, + GstVideoCodecState * state) +{ + GstX265Enc *encoder = GST_X265_ENC (video_enc); + GstVideoInfo *info = &state->info; + gboolean level_ok = TRUE; + + /* If the encoder is initialized, do not reinitialize it again if not + * necessary */ + if (encoder->x265enc) { + 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_x265_enc_flush_frames (encoder, TRUE); + } + + if (encoder->input_state) + gst_video_codec_state_unref (encoder->input_state); + encoder->input_state = gst_video_codec_state_ref (state); + + if (!level_ok) + return FALSE; + + if (!gst_x265_enc_init_encoder (encoder)) + return FALSE; + + if (!gst_x265_enc_set_src_caps (encoder, state->caps)) { + gst_x265_enc_close_encoder (encoder); + return FALSE; + } + + gst_x265_enc_set_latency (encoder); + + return TRUE; +} + +static GstFlowReturn +gst_x265_enc_finish (GstVideoEncoder * encoder) +{ + GST_DEBUG_OBJECT (encoder, "finish encoder"); + + gst_x265_enc_flush_frames (GST_X265_ENC (encoder), TRUE); + gst_x265_enc_flush_frames (GST_X265_ENC (encoder), TRUE); + return GST_FLOW_OK; +} + +static gboolean +gst_x265_enc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query) +{ + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); + + return GST_VIDEO_ENCODER_CLASS (parent_class)->propose_allocation (encoder, + query); +} + +/* chain function + * this function does the actual processing + */ +static GstFlowReturn +gst_x265_enc_handle_frame (GstVideoEncoder * video_enc, + GstVideoCodecFrame * frame) +{ + GstX265Enc *encoder = GST_X265_ENC (video_enc); + GstVideoInfo *info = &encoder->input_state->info; + GstFlowReturn ret; + x265_picture pic_in; + guint32 i_nal, i; + FrameData *fdata; + gint nplanes = 0; + + if (G_UNLIKELY (encoder->x265enc == NULL)) + goto not_inited; + + /* set up input picture */ + x265_picture_init (&encoder->x265param, &pic_in); + + fdata = gst_x265_enc_queue_frame (encoder, frame, info); + if (!fdata) + goto invalid_frame; + + pic_in.colorSpace = + gst_x265_enc_gst_to_x265_video_format (info->finfo->format, &nplanes); + for (i = 0; i < nplanes; i++) { + pic_in.planes[i] = GST_VIDEO_FRAME_PLANE_DATA (&fdata->vframe, i); + pic_in.stride[i] = GST_VIDEO_FRAME_COMP_STRIDE (&fdata->vframe, i); + } + + pic_in.sliceType = X265_TYPE_AUTO; + pic_in.pts = frame->pts; + pic_in.dts = frame->dts; + pic_in.bitDepth = 8; + pic_in.userData = GINT_TO_POINTER (frame->system_frame_number); + + ret = gst_x265_enc_encode_frame (encoder, &pic_in, frame, &i_nal, TRUE); + + /* input buffer is released later on */ + return ret; + +/* ERRORS */ +not_inited: + { + GST_WARNING_OBJECT (encoder, "Got buffer before set_caps was called"); + return GST_FLOW_NOT_NEGOTIATED; + } +invalid_frame: + { + GST_ERROR_OBJECT (encoder, "Failed to map frame"); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_x265_enc_encode_frame (GstX265Enc * encoder, x265_picture * pic_in, + GstVideoCodecFrame * input_frame, guint32 * i_nal, gboolean send) +{ + GstVideoCodecFrame *frame = NULL; + GstBuffer *out_buf = NULL; + x265_picture pic_out; + x265_nal *nal; + int i_size, i, offset; + int encoder_return; + GstFlowReturn ret = GST_FLOW_OK; + gboolean update_latency = FALSE; + + if (G_UNLIKELY (encoder->x265enc == NULL)) { + if (input_frame) + gst_video_codec_frame_unref (input_frame); + return GST_FLOW_NOT_NEGOTIATED; + } + + GST_OBJECT_LOCK (encoder); + if (encoder->reconfig) { + // x265_encoder_reconfig is not yet implemented thus we shut down and re-create encoder + gst_x265_enc_init_encoder (encoder); + update_latency = TRUE; + } + + if (pic_in && input_frame) { + if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (input_frame)) { + GST_INFO_OBJECT (encoder, "Forcing key frame"); + pic_in->sliceType = X265_TYPE_IDR; + } + } + GST_OBJECT_UNLOCK (encoder); + + if (G_UNLIKELY (update_latency)) + gst_x265_enc_set_latency (encoder); + + encoder_return = x265_encoder_encode (encoder->x265enc, + &nal, i_nal, pic_in, &pic_out); + + GST_DEBUG_OBJECT (encoder, "encoder result (%d) with %u nal units", + encoder_return, *i_nal); + + if (encoder_return < 0) { + GST_ELEMENT_ERROR (encoder, STREAM, ENCODE, ("Encode x265 frame failed."), + ("x265_encoder_encode return code=%d", encoder_return)); + ret = GST_FLOW_ERROR; + /* Make sure we finish this frame */ + frame = input_frame; + goto out; + } + + /* Input frame is now queued */ + if (input_frame) + gst_video_codec_frame_unref (input_frame); + + if (!*i_nal) { + ret = GST_FLOW_OK; + GST_LOG_OBJECT (encoder, "no output yet"); + goto out; + } + + frame = gst_video_encoder_get_frame (GST_VIDEO_ENCODER (encoder), + GPOINTER_TO_INT (pic_out.userData)); + g_assert (frame || !send); + + GST_DEBUG_OBJECT (encoder, + "output picture ready POC=%d system=%d frame found %d", pic_out.poc, + GPOINTER_TO_INT (pic_out.userData), frame != NULL); + + if (!send || !frame) { + GST_LOG_OBJECT (encoder, "not sending (%d) or frame not found (%d)", send, + frame != NULL); + ret = GST_FLOW_OK; + goto out; + } + + i_size = 0; + offset = 0; + for (i = 0; i < *i_nal; i++) + i_size += nal[i].sizeBytes; + out_buf = gst_buffer_new_allocate (NULL, i_size, NULL); + for (i = 0; i < *i_nal; i++) { + gst_buffer_fill (out_buf, offset, nal[i].payload, nal[i].sizeBytes); + offset += nal[i].sizeBytes; + } + + frame->output_buffer = out_buf; + + if (encoder->push_header) { + GstBuffer *header; + + header = gst_x265_enc_get_header_buffer (encoder); + frame->output_buffer = gst_buffer_append (header, frame->output_buffer); + encoder->push_header = FALSE; + } + + GST_LOG_OBJECT (encoder, + "output: dts %" G_GINT64_FORMAT " pts %" G_GINT64_FORMAT, + (gint64) pic_out.dts, (gint64) pic_out.pts); + + frame->dts = pic_out.dts + encoder->dts_offset; + +out: + if (frame) { + gst_x265_enc_dequeue_frame (encoder, frame); + ret = gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (encoder), frame); + } + + return ret; +} + +static void +gst_x265_enc_flush_frames (GstX265Enc * encoder, gboolean send) +{ + GstFlowReturn flow_ret; + guint32 i_nal; + + /* first send the remaining frames */ + if (encoder->x265enc) + do { + flow_ret = gst_x265_enc_encode_frame (encoder, NULL, NULL, &i_nal, send); + } while (flow_ret == GST_FLOW_OK && i_nal > 0); +} + +static void +gst_x265_enc_reconfig (GstX265Enc * encoder) +{ + encoder->x265param.rc.bitrate = encoder->bitrate; + encoder->reconfig = TRUE; +} + +static void +gst_x265_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstX265Enc *encoder; + GstState state; + + encoder = GST_X265_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_BITRATE: + encoder->bitrate = g_value_get_uint (value); + break; + case PROP_OPTION_STRING: + g_string_assign (encoder->option_string_prop, g_value_get_string (value)); + break; + case PROP_X265_LOG_LEVEL: + encoder->log_level = g_value_get_enum (value); + break; + case PROP_SPEED_PRESET: + encoder->speed_preset = g_value_get_enum (value); + break; + case PROP_TUNE: + encoder->tune = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + gst_x265_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_x265_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstX265Enc *encoder; + + encoder = GST_X265_ENC (object); + + GST_OBJECT_LOCK (encoder); + switch (prop_id) { + case PROP_BITRATE: + g_value_set_uint (value, encoder->bitrate); + break; + case PROP_OPTION_STRING: + g_value_set_string (value, encoder->option_string_prop->str); + break; + case PROP_X265_LOG_LEVEL: + g_value_set_enum (value, encoder->log_level); + break; + case PROP_SPEED_PRESET: + g_value_set_enum (value, encoder->speed_preset); + break; + case PROP_TUNE: + g_value_set_enum (value, encoder->tune); + 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 (x265_enc_debug, "x265enc", 0, + "h265 encoding element"); + + GST_INFO ("x265 build: %u", X265_BUILD); + + return gst_element_register (plugin, "x265enc", + GST_RANK_PRIMARY, GST_TYPE_X265_ENC); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + x265, + "x265-based H265 plugins", + plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/x265/gstx265enc.h b/ext/x265/gstx265enc.h new file mode 100644 index 0000000..a94784e --- /dev/null +++ b/ext/x265/gstx265enc.h @@ -0,0 +1,87 @@ +/* GStreamer H265 encoder plugin + * Copyright (C) 2005 Michal Benes + * Copyright (C) 2005 Josef Zlomek + * Copyright (C) 2014 Thijs Vermeir + * + * 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_X265_ENC_H__ +#define __GST_X265_ENC_H__ + +#include +#include +#include +#include "_stdint.h" +#include + +G_BEGIN_DECLS +#define GST_TYPE_X265_ENC \ + (gst_x265_enc_get_type()) +#define GST_X265_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_X265_ENC,GstX265Enc)) +#define GST_X265_ENC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_X265_ENC,GstX265EncClass)) +#define GST_IS_X265_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_X265_ENC)) +#define GST_IS_X265_ENC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_X265_ENC)) +typedef struct _GstX265Enc GstX265Enc; +typedef struct _GstX265EncClass GstX265EncClass; + +struct _GstX265Enc +{ + GstVideoEncoder element; + + /*< private > */ + x265_encoder *x265enc; + x265_param x265param; + GstClockTime dts_offset; + gboolean push_header; + + /* List of frame/buffer mapping structs for + * pending frames */ + GList *pending_frames; + + /* properties */ + guint bitrate; + gint log_level; + gint tune; + gint speed_preset; + GString *option_string_prop; /* option-string property */ + /*GString *option_string; *//* used by set prop */ + + /* input description */ + GstVideoCodecState *input_state; + + /* configuration changed while playing */ + gboolean reconfig; + + /* from the downstream caps */ + const gchar *peer_profile; + gboolean peer_intra_profile; + /*const x265_level_t *peer_level; */ +}; + +struct _GstX265EncClass +{ + GstVideoEncoderClass parent_class; +}; + +GType gst_x265_enc_get_type (void); + +G_END_DECLS +#endif /* __GST_X265_ENC_H__ */ -- 2.7.4