gstreamer/ext/x265/gstx265enc.c
Thijs Vermeir 8d728abd8c x265enc: Only copy VPS/SPS/PPS NAL units in header buffer
Previously the VPS unit was detected and all next packets where copied
into the header buffer assuming only SPS and PPS would follow.  This is
not always true, also other types of NAL units follow the VPS unit and
where copied to the header buffer. Now the VPS/SPS/PPS are explicitely
detected and copied in the header buffer.
2015-05-20 13:16:01 +02:00

1245 lines
34 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
*
* 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) { 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;
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 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->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_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->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.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;
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;
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;
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 = 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)