gstreamer/ext/x265/gstx265enc.c
Seungha Yang d6680b35b4 x265enc: Specify colorimetry related VUI parameters
Set the colorimetry config for the information to be embedded in encodec bitstream.
2019-07-17 22:46:58 +09:00

1396 lines
40 KiB
C

/* 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
* @title: 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,
PROP_KEY_INT_MAX
};
#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 */
#define PROP_KEY_INT_MAX_DEFAULT 0 /* x265 lib default */
#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;
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 x265 options (overridden by element properties)"
" in the format \"key1=value1:key2=value2\".",
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));
/**
* GstX265Enc::key-int-max:
*
* Controls maximum number of frames since the last keyframe
*
* Since: 1.16
*/
g_object_class_install_property (gobject_class, PROP_KEY_INT_MAX,
g_param_spec_int ("key-int-max", "Max key frame",
"Maximal distance between two key-frames (0 = x265 default / 250)",
0, G_MAXINT32, PROP_KEY_INT_MAX_DEFAULT, G_PARAM_READWRITE));
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;
encoder->keyintmax = PROP_KEY_INT_MAX_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)
{
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_string_free (encoder->option_string_prop, TRUE);
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;
}
encoder->x265param.vui.bEnableVideoSignalTypePresentFlag = 1;
/* Unspecified video format (5) */
encoder->x265param.vui.videoFormat = 5;
if (info->colorimetry.range == GST_VIDEO_COLOR_RANGE_0_255) {
encoder->x265param.vui.bEnableVideoFullRangeFlag = 1;
} else {
encoder->x265param.vui.bEnableVideoFullRangeFlag = 0;
}
encoder->x265param.vui.bEnableColorDescriptionPresentFlag = 1;
encoder->x265param.vui.matrixCoeffs =
gst_video_color_matrix_to_iso (info->colorimetry.matrix);
encoder->x265param.vui.colorPrimaries =
gst_video_color_primaries_to_iso (info->colorimetry.primaries);
encoder->x265param.vui.transferCharacteristics =
gst_video_color_transfer_to_iso (info->colorimetry.transfer);
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;
}
if (encoder->keyintmax > 0) {
encoder->x265param.keyframeMax = encoder->keyintmax;
}
#if (X265_BUILD >= 79)
{
GstVideoMasteringDisplayInfo minfo;
GstVideoContentLightLevel cll;
if (gst_video_mastering_display_info_from_caps (&minfo,
encoder->input_state->caps)) {
guint16 displayPrimaryX[3];
guint16 displayPrimaryY[3];
guint16 whitePointX, whitePointY;
guint32 maxDisplayMasteringLuminance;
guint32 minDisplayMasteringLuminance;
const guint chroma_scale = 50000;
const guint luma_scale = 10000;
GST_DEBUG_OBJECT (encoder, "Apply mastering display info");
displayPrimaryX[0] =
(guint16) gst_util_uint64_scale_round (minfo.Gx_n, chroma_scale,
minfo.Gx_d);
displayPrimaryX[1] =
(guint16) gst_util_uint64_scale_round (minfo.Bx_n, chroma_scale,
minfo.Bx_d);
displayPrimaryX[2] =
(guint16) gst_util_uint64_scale_round (minfo.Rx_n, chroma_scale,
minfo.Rx_d);
displayPrimaryY[0] =
(guint16) gst_util_uint64_scale_round (minfo.Gy_n, chroma_scale,
minfo.Gy_d);
displayPrimaryY[1] =
(guint16) gst_util_uint64_scale_round (minfo.By_n, chroma_scale,
minfo.By_d);
displayPrimaryY[2] =
(guint16) gst_util_uint64_scale_round (minfo.Ry_n, chroma_scale,
minfo.Ry_d);
whitePointX =
(guint16) gst_util_uint64_scale_round (minfo.Wx_n, chroma_scale,
minfo.Wx_d);
whitePointY =
(guint16) gst_util_uint64_scale_round (minfo.Wy_n, chroma_scale,
minfo.Wy_d);
maxDisplayMasteringLuminance =
(guint32) gst_util_uint64_scale_round (minfo.max_luma_n, luma_scale,
minfo.max_luma_d);
minDisplayMasteringLuminance =
(guint32) gst_util_uint64_scale_round (minfo.min_luma_n, luma_scale,
minfo.min_luma_d);
encoder->x265param.masteringDisplayColorVolume =
g_strdup_printf ("G(%hu,%hu)B(%hu,%hu)R(%hu,%hu)WP(%hu,%hu)L(%u,%u)",
displayPrimaryX[0], displayPrimaryY[0],
displayPrimaryX[1], displayPrimaryY[1],
displayPrimaryX[2], displayPrimaryY[2],
whitePointX, whitePointY,
maxDisplayMasteringLuminance, minDisplayMasteringLuminance);
}
if (gst_video_content_light_level_from_caps (&cll,
encoder->input_state->caps)) {
gdouble val;
GST_DEBUG_OBJECT (encoder, "Apply content light level");
gst_util_fraction_to_double (cll.maxCLL_n, cll.maxCLL_d, &val);
encoder->x265param.maxCLL = (guint16) val;
gst_util_fraction_to_double (cll.maxFALL_n, cll.maxFALL_d, &val);
encoder->x265param.maxFALL = (guint16) val;
}
}
#endif
/* 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;
gsize header_size = 0;
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 == NAL_UNIT_VPS) {
vps_idx = i;
header_size += nal[i].sizeBytes;
} else if (nal[i].type == NAL_UNIT_SPS) {
sps_idx = i;
header_size += nal[i].sizeBytes;
} else if (nal[i].type == NAL_UNIT_PPS) {
pps_idx = i;
header_size += nal[i].sizeBytes;
} else if (nal[i].type == NAL_UNIT_PREFIX_SEI) {
header_size += nal[i].sizeBytes;
}
}
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, header_size, 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);
offset += nal[pps_idx].sizeBytes;
for (i = 0; i < i_nal; i++) {
if (nal[i].type == NAL_UNIT_PREFIX_SEI) {
gst_buffer_fill (buf, offset, nal[i].payload, nal[i].sizeBytes);
offset += nal[i].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;
}
if (pic_out.sliceType == X265_TYPE_IDR || pic_out.sliceType == X265_TYPE_I) {
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
}
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;
case PROP_KEY_INT_MAX:
encoder->keyintmax = g_value_get_int (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;
case PROP_KEY_INT_MAX:
g_value_set_int (value, encoder->keyintmax);
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)