mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 18:50:48 +00:00
8d728abd8c
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.
1245 lines
34 KiB
C
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)
|