| /* GStreamer H265 encoder plugin |
| * Copyright (C) 2005 Michal Benes <michal.benes@itonis.tv> |
| * Copyright (C) 2005 Josef Zlomek <josef.zlomek@itonis.tv> |
| * Copyright (C) 2008 Mark Nauwelaerts <mnauw@users.sf.net> |
| * Copyright (C) 2014 Thijs Vermeir <thijs.vermeir@barco.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-x265enc |
| * |
| * This element encodes raw video into H265 compressed data. |
| * |
| **/ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include "gstx265enc.h" |
| |
| #include <gst/pbutils/pbutils.h> |
| #include <gst/video/video.h> |
| #include <gst/video/gstvideometa.h> |
| #include <gst/video/gstvideopool.h> |
| |
| #include <string.h> |
| #include <stdlib.h> |
| |
| GST_DEBUG_CATEGORY_STATIC (x265_enc_debug); |
| #define GST_CAT_DEFAULT x265_enc_debug |
| |
| enum |
| { |
| PROP_0, |
| PROP_BITRATE, |
| PROP_QP, |
| PROP_OPTION_STRING, |
| PROP_X265_LOG_LEVEL, |
| PROP_SPEED_PRESET, |
| PROP_TUNE |
| }; |
| |
| static GString *x265enc_defaults; |
| |
| #define PROP_BITRATE_DEFAULT (2 * 1024) |
| #define PROP_QP_DEFAULT -1 |
| #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, I420_10LE, Y444_10LE" |
| #else |
| #define FORMATS "I420, Y444, I420_10BE, Y444_10BE" |
| #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) 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; |
| |
| if (x265_max_bit_depth >= 10) { |
| GST_INFO ("This x265 build supports %d-bit depth", x265_max_bit_depth); |
| if (x265_chroma_format_local == 0) { |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN |
| set_value (&fmt, 4, "I420", "Y444", "I420_10LE", "Y444_10LE"); |
| #else |
| set_value (&fmt, 4, "I420", "Y444", "I420_10BE", "Y444_10BE"); |
| #endif |
| } else if (x265_chroma_format_local == X265_CSP_I444) { |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN |
| set_value (&fmt, 2, "Y444", "Y444_10LE"); |
| #else |
| set_value (&fmt, 2, "Y444", "Y444_10BE"); |
| #endif |
| } else if (x265_chroma_format_local == X265_CSP_I420) { |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN |
| set_value (&fmt, 2, "I420", "I420_10LE"); |
| #else |
| set_value (&fmt, 2, "I420", "I420_10BE"); |
| #endif |
| } else { |
| GST_ERROR ("Unsupported chroma format %d", x265_chroma_format_local); |
| } |
| } else if (x265_max_bit_depth == 8) { |
| 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 gboolean |
| gst_x265_enc_sink_query (GstVideoEncoder * enc, GstQuery * query) |
| { |
| gboolean res; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_ACCEPT_CAPS:{ |
| GstCaps *acceptable, *caps; |
| |
| acceptable = gst_x265_enc_get_supported_input_caps (); |
| gst_query_parse_accept_caps (query, &caps); |
| |
| gst_query_set_accept_caps_result (query, |
| gst_caps_is_subset (caps, acceptable)); |
| gst_caps_unref (acceptable); |
| res = TRUE; |
| } |
| break; |
| default: |
| res = GST_VIDEO_ENCODER_CLASS (parent_class)->sink_query (enc, query); |
| break; |
| } |
| |
| return res; |
| } |
| |
| static GstCaps * |
| gst_x265_enc_sink_getcaps (GstVideoEncoder * enc, GstCaps * filter) |
| { |
| GstCaps *supported_incaps; |
| GstCaps *ret; |
| |
| supported_incaps = gst_x265_enc_get_supported_input_caps (); |
| |
| ret = gst_video_encoder_proxy_getcaps (enc, supported_incaps, filter); |
| if (supported_incaps) |
| gst_caps_unref (supported_incaps); |
| return ret; |
| } |
| |
| 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->sink_query = GST_DEBUG_FUNCPTR (gst_x265_enc_sink_query); |
| 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_QP, |
| g_param_spec_int ("qp", "Quantization parameter", |
| "QP for P slices in (implied) CQP mode (-1 = disabled)", -1, |
| 51, PROP_QP_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| 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 <thijs.vermeir@barco.com>"); |
| |
| gst_element_class_add_static_pad_template (element_class, &sink_factory); |
| gst_element_class_add_static_pad_template (element_class, &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->qp = PROP_QP_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: |
| case GST_VIDEO_FORMAT_I420_10LE: |
| case GST_VIDEO_FORMAT_I420_10BE: |
| if (nplanes) |
| *nplanes = 3; |
| return X265_CSP_I420; |
| case GST_VIDEO_FORMAT_Y444: |
| case GST_VIDEO_FORMAT_Y444_10LE: |
| case GST_VIDEO_FORMAT_Y444_10BE: |
| 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 - 1]) < 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.aspectRatioIdc = X265_EXTENDED_SAR; |
| encoder->x265param.vui.sarWidth = info->par_n; |
| encoder->x265param.vui.sarHeight = info->par_d; |
| } |
| |
| if (encoder->qp != -1) { |
| /* CQP */ |
| encoder->x265param.rc.qp = encoder->qp; |
| encoder->x265param.rc.rateControlMode = X265_RC_CQP; |
| } else { |
| /* ABR */ |
| 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; |
| gboolean ret = TRUE; |
| |
| 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")); |
| ret = FALSE; |
| } |
| |
| x265_nal_free (vps_nal); |
| |
| return ret; |
| } |
| |
| static GstBuffer * |
| gst_x265_enc_get_header_buffer (GstX265Enc * encoder) |
| { |
| x265_nal *nal; |
| guint32 i_nal, i, offset; |
| gint32 vps_idx, sps_idx, pps_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 */ |
| |
| vps_idx = sps_idx = pps_idx = -1; |
| for (i = 0; i < i_nal; i++) { |
| if (nal[i].type == 32) { |
| vps_idx = i; |
| } else if (nal[i].type == 33) { |
| sps_idx = i; |
| } else if (nal[i].type == 34) { |
| pps_idx = i; |
| } |
| } |
| |
| if (vps_idx == -1 || sps_idx == -1 || pps_idx == -1) { |
| 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[sps_idx].sizeBytes + nal[pps_idx].sizeBytes, |
| NULL); |
| gst_buffer_fill (buf, offset, nal[vps_idx].payload, nal[vps_idx].sizeBytes); |
| offset += nal[vps_idx].sizeBytes; |
| gst_buffer_fill (buf, offset, nal[sps_idx].payload, nal[sps_idx].sizeBytes); |
| offset += nal[sps_idx].sizeBytes; |
| gst_buffer_fill (buf, offset, nal[pps_idx].payload, nal[pps_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; |
| gint max_delayed_frames; |
| GstClockTime latency; |
| |
| /* FIXME get a real value from the encoder, this is currently not exposed */ |
| if (encoder->tune > 0 && encoder->tune <= G_N_ELEMENTS (x265_tune_names) && |
| strcmp (x265_tune_names[encoder->tune - 1], "zerolatency") == 0) |
| max_delayed_frames = 0; |
| else |
| max_delayed_frames = 5; |
| |
| 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 gboolean |
| gst_x265_enc_set_format (GstVideoEncoder * video_enc, |
| GstVideoCodecState * state) |
| { |
| GstX265Enc *encoder = GST_X265_ENC (video_enc); |
| GstVideoInfo *info = &state->info; |
| |
| /* 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 (!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 = info->finfo->depth[0]; |
| 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_QP: |
| encoder->qp = g_value_get_int (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_QP: |
| g_value_set_int (value, encoder->qp); |
| 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) |