mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-29 13:11:06 +00:00
4c82416798
This new LCEVC encoder plugin is meant to implement all LCEVC encoder elements. For now, it only implements the LCEVC H264 encoder (lcevch264enc) element. This element essentially encodes raw video frames using a specific EIL plugin, and outputs H264 frames with LCEVC data. Depending on the encoder properties, the LCEVC data can be either part of the video stream as SEI NAL Units, or attached to buffers as GstMeta. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7330>
866 lines
25 KiB
C
866 lines
25 KiB
C
/* GStreamer
|
|
* Copyright (C) <2024> V-Nova International Limited
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <math.h>
|
|
|
|
#include <gst/codecparsers/gstlcevcmeta.h>
|
|
|
|
#include <lcevc_eil.h>
|
|
|
|
#include "gstlcevcencoder.h"
|
|
#include "gstlcevcencoderutils.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (lcevcencoder_debug);
|
|
#define GST_CAT_DEFAULT (lcevcencoder_debug)
|
|
|
|
#define DEFAULT_MIN_BITRATE 0
|
|
#define DEFAULT_MAX_BITRATE 2048000
|
|
#define DEFAULT_BITRATE 0
|
|
#define DEFAULT_SEI_LCEVC TRUE
|
|
#define DEFAULT_MIN_GOP_LENGTH -2
|
|
#define DEFAULT_GOP_LENGTH -2
|
|
#define DEFAULT_DEBUG FALSE
|
|
|
|
/* The max number of frames the encoder can receive without encoding a frame */
|
|
#define MAX_DELAYED_FRAMES 65
|
|
|
|
struct GstEILContext_
|
|
{
|
|
grefcount ref;
|
|
EILContext context;
|
|
};
|
|
typedef struct GstEILContext_ GstEILContext;
|
|
|
|
static void
|
|
log_callback (void *userdata, int32_t level, const char *msg)
|
|
{
|
|
GstLcevcEncoder *eil = GST_LCEVC_ENCODER (userdata);
|
|
gchar *new_msg = NULL;
|
|
|
|
if (strlen (msg) <= 1)
|
|
return;
|
|
|
|
/* Remove \n from msg */
|
|
new_msg = g_strdup (msg);
|
|
new_msg[strlen (msg) - 1] = '\0';
|
|
|
|
switch (level) {
|
|
case EIL_LL_Error:
|
|
GST_ERROR_OBJECT (eil, "EIL: %s", new_msg);
|
|
break;
|
|
case EIL_LL_Warning:
|
|
GST_WARNING_OBJECT (eil, "EIL: %s", new_msg);
|
|
break;
|
|
case EIL_LL_Info:
|
|
GST_INFO_OBJECT (eil, "EIL: %s", new_msg);
|
|
break;
|
|
case EIL_LL_Debug:
|
|
GST_DEBUG_OBJECT (eil, "EIL: %s", new_msg);
|
|
break;
|
|
case EIL_LL_Verbose:
|
|
GST_TRACE_OBJECT (eil, "EIL: %s", new_msg);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
g_clear_pointer (&new_msg, g_free);
|
|
}
|
|
|
|
static GstEILContext *
|
|
gst_eil_context_new (GstLcevcEncoder * eil, const gchar * plugin_name,
|
|
gboolean debug)
|
|
{
|
|
GstEILContext *ctx = g_new0 (GstEILContext, 1);
|
|
EILOpenSettings settings;
|
|
EILReturnCode rc;
|
|
|
|
g_return_val_if_fail (plugin_name, NULL);
|
|
|
|
/* Initialize settings to default values */
|
|
rc = EIL_OpenSettingsDefault (&settings);
|
|
if (rc != EIL_RC_Success)
|
|
return NULL;
|
|
|
|
/* Set settings */
|
|
settings.base_encoder = plugin_name;
|
|
if (debug) {
|
|
settings.log_callback = log_callback;
|
|
settings.log_userdata = eil;
|
|
}
|
|
|
|
/* Open EIL context */
|
|
if ((rc = EIL_Open (&settings, &ctx->context)) != EIL_RC_Success)
|
|
return NULL;
|
|
|
|
/* Init refcount */
|
|
g_ref_count_init (&ctx->ref);
|
|
return ctx;
|
|
}
|
|
|
|
static void
|
|
gst_eil_context_unref (GstEILContext * ctx)
|
|
{
|
|
if (g_ref_count_dec (&ctx->ref)) {
|
|
EIL_Close (ctx->context);
|
|
g_free (ctx);
|
|
}
|
|
}
|
|
|
|
static GstEILContext *
|
|
gst_eil_context_ref (GstEILContext * ctx)
|
|
{
|
|
g_ref_count_inc (&ctx->ref);
|
|
return ctx;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GstEILContext *ctx;
|
|
EILOutput *output;
|
|
} OutputData;
|
|
|
|
static OutputData *
|
|
output_data_new (GstEILContext * ctx, EILOutput * output)
|
|
{
|
|
OutputData *data = g_new0 (OutputData, 1);
|
|
data->ctx = gst_eil_context_ref (ctx);
|
|
data->output = output;
|
|
return data;
|
|
}
|
|
|
|
static void
|
|
output_data_free (gpointer p)
|
|
{
|
|
OutputData *data = p;
|
|
EIL_ReleaseOutput (data->ctx->context, data->output);
|
|
g_clear_pointer (&data->ctx, gst_eil_context_unref);
|
|
g_free (data);
|
|
}
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_PLUGIN_NAME,
|
|
PROP_PLUGIN_PROPS,
|
|
PROP_BITRATE,
|
|
PROP_SEI_LCEVC,
|
|
PROP_GOP_LENGTH,
|
|
PROP_DEBUG,
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
/* Props */
|
|
gchar *plugin_name;
|
|
gchar *plugin_props;
|
|
guint bitrate;
|
|
gboolean sei_lcevc;
|
|
gint gop_length;
|
|
gboolean debug;
|
|
|
|
/* EIL */
|
|
GstEILContext *ctx;
|
|
GHashTable *plugin_props_spec;
|
|
|
|
/* Input info */
|
|
GstVideoInfo in_info;
|
|
EILColourFormat in_format;
|
|
EILFrameType in_frame_type;
|
|
|
|
GstClockTime out_ts_offset;
|
|
} GstLcevcEncoderPrivate;
|
|
|
|
#define gst_lcevc_encoder_parent_class parent_class
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstLcevcEncoder, gst_lcevc_encoder,
|
|
GST_TYPE_VIDEO_ENCODER,
|
|
G_ADD_PRIVATE (GstLcevcEncoder);
|
|
GST_DEBUG_CATEGORY_INIT (lcevcencoder_debug, "lcevcencoder", 0,
|
|
"lcevcencoder"));
|
|
|
|
static GstStaticPadTemplate gst_lcevc_encoder_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
|
|
(GST_LCEVC_ENCODER_UTILS_SUPPORTED_FORMATS))
|
|
);
|
|
|
|
static void
|
|
gst_lcevc_encoder_finalize (GObject * obj)
|
|
{
|
|
GstLcevcEncoder *eil = GST_LCEVC_ENCODER (obj);
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
|
|
g_clear_pointer (&priv->plugin_name, g_free);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
gst_lcevc_encoder_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstLcevcEncoder *self = GST_LCEVC_ENCODER (object);
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (self);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PLUGIN_NAME:
|
|
g_clear_pointer (&priv->plugin_name, g_free);
|
|
priv->plugin_name = g_value_dup_string (value);
|
|
break;
|
|
case PROP_PLUGIN_PROPS:
|
|
g_clear_pointer (&priv->plugin_props, g_free);
|
|
priv->plugin_props = g_value_dup_string (value);
|
|
break;
|
|
case PROP_BITRATE:
|
|
priv->bitrate = g_value_get_uint (value);
|
|
break;
|
|
case PROP_SEI_LCEVC:
|
|
priv->sei_lcevc = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_GOP_LENGTH:
|
|
priv->gop_length = g_value_get_int (value);
|
|
break;
|
|
case PROP_DEBUG:
|
|
priv->debug = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_lcevc_encoder_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstLcevcEncoder *self = GST_LCEVC_ENCODER (object);
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (self);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PLUGIN_NAME:
|
|
g_value_set_string (value, priv->plugin_name);
|
|
break;
|
|
case PROP_PLUGIN_PROPS:
|
|
g_value_set_string (value, priv->plugin_props);
|
|
break;
|
|
case PROP_BITRATE:
|
|
g_value_set_uint (value, priv->bitrate);
|
|
break;
|
|
case PROP_SEI_LCEVC:
|
|
g_value_set_boolean (value, priv->sei_lcevc);
|
|
break;
|
|
case PROP_GOP_LENGTH:
|
|
g_value_set_int (value, priv->gop_length);
|
|
break;
|
|
case PROP_DEBUG:
|
|
g_value_set_boolean (value, priv->debug);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GHashTable *
|
|
get_plugin_props_spec (GstEILContext * ctx, const gchar * plugin_name)
|
|
{
|
|
EILReturnCode rc;
|
|
GHashTable *res;
|
|
EILPropertyGroups groups;
|
|
|
|
rc = EIL_QueryPropertyGroups (ctx->context, &groups);
|
|
if (rc != EIL_RC_Success)
|
|
return NULL;
|
|
|
|
res = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
|
|
for (guint32 i = 0; i < groups.group_count; i++) {
|
|
EILPropertyGroup *group = &groups.group[i];
|
|
|
|
for (guint32 j = 0; j < group->property_count; j++) {
|
|
EILProperty *property = &group->properties[j];
|
|
g_hash_table_insert (res, g_strdup (property->name),
|
|
GINT_TO_POINTER (property->type));
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
open_eil_context (GstLcevcEncoder * eil)
|
|
{
|
|
GstLcevcEncoderClass *klass = GST_LCEVC_ENCODER_GET_CLASS (eil);
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
const gchar *plugin_name;
|
|
|
|
g_return_val_if_fail (!priv->ctx, FALSE);
|
|
|
|
/* Get the plugin name */
|
|
if (priv->plugin_name)
|
|
plugin_name = priv->plugin_name;
|
|
else if (klass->get_eil_plugin_name)
|
|
plugin_name = klass->get_eil_plugin_name (eil);
|
|
else
|
|
return FALSE;
|
|
|
|
/* Create the EIL context */
|
|
priv->ctx = gst_eil_context_new (eil, plugin_name, priv->debug);
|
|
if (!priv->ctx)
|
|
return FALSE;
|
|
|
|
/* Get the plugin properties spec */
|
|
priv->plugin_props_spec = get_plugin_props_spec (priv->ctx, plugin_name);
|
|
if (!priv->plugin_props_spec) {
|
|
g_clear_pointer (&priv->ctx, gst_eil_context_unref);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
close_eil_context (GstLcevcEncoder * eil)
|
|
{
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
|
|
/* Flush */
|
|
EIL_Encode (priv->ctx->context, NULL);
|
|
|
|
/* Clear properties spec */
|
|
g_clear_pointer (&priv->plugin_props_spec, g_hash_table_unref);
|
|
|
|
/* Clear context */
|
|
g_clear_pointer (&priv->ctx, gst_eil_context_unref);
|
|
}
|
|
|
|
static gboolean
|
|
gst_lcevc_encoder_start (GstVideoEncoder * encoder)
|
|
{
|
|
GstLcevcEncoder *eil = GST_LCEVC_ENCODER (encoder);
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
|
|
/* Open EIL context */
|
|
if (!open_eil_context (eil)) {
|
|
GST_ELEMENT_ERROR (encoder, LIBRARY, INIT, (NULL),
|
|
("Couldn't initialize EIL context"));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Reset out TS offset */
|
|
priv->out_ts_offset = 0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_lcevc_encoder_stop (GstVideoEncoder * encoder)
|
|
{
|
|
GstLcevcEncoder *eil = GST_LCEVC_ENCODER (encoder);
|
|
|
|
/* Close EIL context */
|
|
close_eil_context (eil);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
try_parse_number (const char *value, double *parsed)
|
|
{
|
|
char *endptr;
|
|
|
|
/* Skip leading spaces */
|
|
while (g_ascii_isspace (*value))
|
|
value++;
|
|
|
|
/* Parse number */
|
|
*parsed = g_strtod (value, &endptr);
|
|
|
|
/* Allow trailing spaces */
|
|
while (g_ascii_isspace (*endptr))
|
|
value++;
|
|
|
|
/* Ceck no extra characters after number and spaces */
|
|
if (*endptr != '\0')
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GString *
|
|
build_json_props (GstLcevcEncoder * eil)
|
|
{
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
gchar *key_value;
|
|
GString *res = g_string_new ("{");
|
|
|
|
/* I/O props */
|
|
if (priv->sei_lcevc) {
|
|
/* separate_output=false */
|
|
g_string_append_printf (res, "\"%s\"", "separate_output");
|
|
g_string_append (res, ":");
|
|
g_string_append (res, "false");
|
|
} else {
|
|
/* separate_output=true */
|
|
g_string_append_printf (res, "\"%s\"", "separate_output");
|
|
g_string_append (res, ": ");
|
|
g_string_append (res, "true");
|
|
g_string_append (res, ", ");
|
|
/* output_format=raw */
|
|
g_string_append_printf (res, "\"%s\"", "output_format");
|
|
g_string_append (res, ": ");
|
|
g_string_append_printf (res, "\"%s\"", "raw");
|
|
}
|
|
|
|
if (!priv->plugin_props)
|
|
goto done;
|
|
|
|
/* Plugin props */
|
|
key_value = strtok (priv->plugin_props, ";");
|
|
while (key_value != NULL) {
|
|
const gchar *val_str = strchr (key_value, '=');
|
|
if (val_str) {
|
|
gsize key_size = val_str - key_value;
|
|
if (key_size > 0) {
|
|
gchar *key = g_strndup (key_value, key_size);
|
|
gpointer p = g_hash_table_lookup (priv->plugin_props_spec, key);
|
|
|
|
/* Add key */
|
|
g_string_append (res, ", ");
|
|
g_string_append_printf (res, "\"%s\"", key);
|
|
g_string_append (res, ": ");
|
|
|
|
/* Convert value to type defined by spec and add it, otherwise add the
|
|
* value as it is */
|
|
if (p) {
|
|
EILPropertyType spec = GPOINTER_TO_INT (p);
|
|
|
|
switch (spec) {
|
|
case EIL_PT_Int8:
|
|
case EIL_PT_Int16:
|
|
case EIL_PT_Int32:
|
|
case EIL_PT_Int64:{
|
|
gint64 val = g_ascii_strtoll (val_str + 1, NULL, 10);
|
|
g_string_append_printf (res, "%ld", val);
|
|
break;
|
|
}
|
|
case EIL_PT_Uint8:
|
|
case EIL_PT_Uint16:
|
|
case EIL_PT_Uint32:
|
|
case EIL_PT_Uint64:{
|
|
guint64 val = g_ascii_strtoull (val_str + 1, NULL, 10);
|
|
g_string_append_printf (res, "%lu", val);
|
|
break;
|
|
}
|
|
case EIL_PT_Float:
|
|
case EIL_PT_Double:{
|
|
double val = g_ascii_strtod (val_str + 1, NULL);
|
|
g_string_append_printf (res, "%f", val);
|
|
break;
|
|
}
|
|
case EIL_PT_Boolean:{
|
|
if (g_str_equal (val_str + 1, "TRUE") ||
|
|
g_str_equal (val_str + 1, "True") ||
|
|
g_str_equal (val_str + 1, "true") ||
|
|
g_str_equal (val_str + 1, "1"))
|
|
g_string_append (res, "true");
|
|
else
|
|
g_string_append (res, "false");
|
|
break;
|
|
}
|
|
case EIL_PT_String:
|
|
default:
|
|
g_string_append_printf (res, "\"%s\"", val_str + 1);
|
|
break;
|
|
}
|
|
} else {
|
|
double val;
|
|
if (try_parse_number (val_str + 1, &val)) {
|
|
if (val == ceil (val))
|
|
g_string_append_printf (res, "%d", (gint) val);
|
|
else
|
|
g_string_append_printf (res, "%f", val);
|
|
} else {
|
|
g_string_append_printf (res, "\"%s\"", val_str + 1);
|
|
}
|
|
}
|
|
|
|
g_free (key);
|
|
} else {
|
|
GST_WARNING_OBJECT (eil, "Key value pair %s does not have key",
|
|
key_value);
|
|
goto error;
|
|
}
|
|
} else {
|
|
GST_WARNING_OBJECT (eil, "Key value pair %s does not have '=' char",
|
|
key_value);
|
|
goto error;
|
|
}
|
|
|
|
key_value = strtok (NULL, ";");
|
|
}
|
|
|
|
done:
|
|
res = g_string_append (res, "}");
|
|
return res;
|
|
|
|
error:
|
|
g_string_free (res, TRUE);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
on_encoded_output (void *data, EILOutput * output)
|
|
{
|
|
GstLcevcEncoder *eil = GST_LCEVC_ENCODER (data);
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
GstVideoCodecFrame *frame;
|
|
GstClockTime pts;
|
|
|
|
if (!output) {
|
|
GST_INFO_OBJECT (eil, "All EIL Pictures processed");
|
|
return;
|
|
}
|
|
|
|
frame = output->user_data;
|
|
pts = frame->input_buffer->pts;
|
|
|
|
GST_INFO_OBJECT (eil, "Received output frame %ld with lcevc size %d", pts,
|
|
output->lcevc_length);
|
|
|
|
/* The EIL DTS can be negative, we need to do the conversion so it can be
|
|
* stored in a GstClockTime (guint64). The EIL PTS can never be negative
|
|
* because it is set using the input buffer PTS, which is a GstClockTime. */
|
|
if (output->dts < 0 && priv->out_ts_offset == 0) {
|
|
priv->out_ts_offset = -1 * output->dts;
|
|
GST_INFO_OBJECT (eil, "Output DTS offset set to %ld", priv->out_ts_offset);
|
|
}
|
|
|
|
/* Created output buffer with output data */
|
|
frame->output_buffer = gst_buffer_new_wrapped_full (0,
|
|
(gpointer) output->data, output->data_length, 0, output->data_length,
|
|
output_data_new (priv->ctx, output), output_data_free);
|
|
frame->pts = priv->out_ts_offset + output->pts;
|
|
frame->dts = priv->out_ts_offset + output->dts;
|
|
|
|
/* Add LCEVC metadata to output buffer if present */
|
|
if (output->lcevc_length > 0) {
|
|
GstBuffer *lcevc_data = gst_buffer_new_memdup ((gpointer) output->lcevc,
|
|
output->lcevc_length);
|
|
gst_buffer_add_lcevc_meta (frame->output_buffer, lcevc_data);
|
|
gst_buffer_unref (lcevc_data);
|
|
}
|
|
|
|
/* Set Delta unit flag if this is not a key frame */
|
|
if (!output->keyframe)
|
|
GST_BUFFER_FLAG_SET (frame->output_buffer, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
else
|
|
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
|
|
|
|
gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (eil), frame);
|
|
}
|
|
|
|
static void
|
|
gst_lcevc_encoder_set_latency (GstLcevcEncoder * eil, GstVideoInfo * info)
|
|
{
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
gint delayed_frames;
|
|
GstClockTime latency;
|
|
|
|
/* The GOP affects the number of delayed frames */
|
|
if (priv->gop_length == -2 || priv->gop_length == -1)
|
|
delayed_frames = MAX_DELAYED_FRAMES;
|
|
else
|
|
delayed_frames = MIN (5 + priv->gop_length, MAX_DELAYED_FRAMES);
|
|
|
|
latency =
|
|
gst_util_uint64_scale_ceil (GST_SECOND * GST_VIDEO_INFO_FPS_D (info),
|
|
delayed_frames, GST_VIDEO_INFO_FPS_N (info));
|
|
gst_video_encoder_set_latency (GST_VIDEO_ENCODER (eil), latency, latency);
|
|
|
|
GST_INFO_OBJECT (eil, "Updated latency to %" GST_TIME_FORMAT " (%d frames)",
|
|
GST_TIME_ARGS (latency), delayed_frames);
|
|
}
|
|
|
|
static gboolean
|
|
gst_lcevc_encoder_set_format (GstVideoEncoder * encoder,
|
|
GstVideoCodecState * state)
|
|
{
|
|
GstLcevcEncoder *eil = GST_LCEVC_ENCODER (encoder);
|
|
GstLcevcEncoderClass *klass = GST_LCEVC_ENCODER_GET_CLASS (eil);
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
EILInitSettings settings;
|
|
GString *properties_json;
|
|
GstVideoInterlaceMode interlace_mode;
|
|
EILReturnCode rc;
|
|
GstCaps *outcaps;
|
|
GstVideoCodecState *s;
|
|
gint width = GST_VIDEO_INFO_WIDTH (&state->info);
|
|
gint height = GST_VIDEO_INFO_HEIGHT (&state->info);
|
|
|
|
/* Set input info, format and frame type */
|
|
priv->in_info = state->info;
|
|
priv->in_format =
|
|
gst_lcevc_encoder_utils_get_color_format (GST_VIDEO_INFO_FORMAT
|
|
(&state->info));
|
|
interlace_mode = GST_VIDEO_INFO_INTERLACE_MODE (&state->info);
|
|
switch (interlace_mode) {
|
|
case GST_VIDEO_INTERLACE_MODE_PROGRESSIVE:
|
|
priv->in_frame_type = EIL_FrameType_Progressive;
|
|
break;
|
|
case GST_VIDEO_INTERLACE_MODE_INTERLEAVED:
|
|
priv->in_frame_type = EIL_FrameType_Interlaced;
|
|
break;
|
|
case GST_VIDEO_INTERLACE_MODE_FIELDS:
|
|
priv->in_frame_type = EIL_FrameType_Field;
|
|
break;
|
|
default:
|
|
GST_ELEMENT_ERROR (eil, STREAM, FORMAT, (NULL),
|
|
("Interlace mode %s not supported",
|
|
gst_video_interlace_mode_to_string (interlace_mode)));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Init EIL Settings to default values */
|
|
rc = EIL_InitSettingsDefault (&settings);
|
|
if (rc != EIL_RC_Success) {
|
|
GST_ELEMENT_ERROR (eil, LIBRARY, INIT, (NULL),
|
|
("Unabled to initialize EIL Settings"));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Set basic EIL settings */
|
|
settings.color_format = priv->in_format;
|
|
settings.memory_type = EIL_MT_Host;
|
|
settings.width = width;
|
|
settings.height = height;
|
|
settings.fps_num = GST_VIDEO_INFO_FPS_N (&state->info);
|
|
settings.fps_denom = GST_VIDEO_INFO_FPS_D (&state->info);
|
|
settings.bitrate = priv->bitrate;
|
|
settings.gop_length = priv->gop_length;
|
|
settings.external_input = 1;
|
|
|
|
/* Set properties JSON EIL setting */
|
|
properties_json = build_json_props (eil);
|
|
if (!properties_json) {
|
|
GST_ELEMENT_ERROR (eil, RESOURCE, SETTINGS, (NULL),
|
|
("Could not parse plugin properties to JSON"));
|
|
return FALSE;
|
|
}
|
|
settings.properties_json = properties_json->str;
|
|
GST_INFO_OBJECT (eil, "Properties JSON: %s", properties_json->str);
|
|
|
|
/* Initialize EIL */
|
|
rc = EIL_Initialise (priv->ctx->context, &settings);
|
|
g_string_free (properties_json, TRUE);
|
|
if (rc != EIL_RC_Success)
|
|
return FALSE;
|
|
|
|
/* Get output caps */
|
|
g_assert (klass->get_output_caps);
|
|
outcaps = klass->get_output_caps (eil);
|
|
if (!outcaps) {
|
|
GST_ELEMENT_ERROR (eil, RESOURCE, NOT_FOUND, (NULL),
|
|
("Could not get output caps"));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Set width, height and pixel aspect ration.
|
|
* The values from settings are updated to base width and height after
|
|
* initialization.
|
|
*/
|
|
if (width != settings.width || height != settings.height) {
|
|
GST_VIDEO_INFO_WIDTH (&state->info) = settings.width;
|
|
GST_VIDEO_INFO_HEIGHT (&state->info) = settings.height;
|
|
/* If changed, the new width and height values are always half of what they
|
|
* used to be, so update the pixel aspect ratio accordingly */
|
|
GST_VIDEO_INFO_PAR_N (&state->info) = width > settings.width ? 2 : 1;
|
|
GST_VIDEO_INFO_PAR_D (&state->info) = height > settings.height ? 2 : 1;
|
|
GST_INFO_OBJECT (eil,
|
|
"Base resolution changed: w=%d h=%d -> w=%d h=%d (par_n=%d, par_d=%d)",
|
|
width, height, settings.width, settings.height,
|
|
GST_VIDEO_INFO_PAR_N (&state->info),
|
|
GST_VIDEO_INFO_PAR_D (&state->info));
|
|
}
|
|
|
|
/* Set output state */
|
|
s = gst_video_encoder_set_output_state (encoder, outcaps, state);
|
|
if (!s) {
|
|
GST_ELEMENT_ERROR (eil, STREAM, FORMAT, (NULL),
|
|
("Could not set output state"));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Set output callback */
|
|
EIL_SetOnEncodedCallback (priv->ctx->context, eil, on_encoded_output);
|
|
|
|
/* Update latency */
|
|
gst_lcevc_encoder_set_latency (eil, &s->info);
|
|
|
|
gst_video_codec_state_unref (s);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_lcevc_encoder_sink_event (GstVideoEncoder * encoder, GstEvent * event)
|
|
{
|
|
GstLcevcEncoder *eil = GST_LCEVC_ENCODER (encoder);
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_EOS:
|
|
/* Flush on EOS */
|
|
GST_INFO_OBJECT (eil, "EOS received, flushing encoder");
|
|
EIL_Encode (priv->ctx->context, NULL);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_VIDEO_ENCODER_CLASS (parent_class)->sink_event (encoder, event);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_lcevc_encoder_handle_frame (GstVideoEncoder * encoder,
|
|
GstVideoCodecFrame * frame)
|
|
{
|
|
GstLcevcEncoder *eil = GST_LCEVC_ENCODER (encoder);
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
GstClockTime pts = frame->input_buffer->pts;
|
|
GstVideoFrame video_frame = { 0, };
|
|
EILPicture picture;
|
|
|
|
/* Map the input buffer */
|
|
if (!gst_video_frame_map (&video_frame, &priv->in_info, frame->input_buffer,
|
|
GST_MAP_READ)) {
|
|
GST_ELEMENT_ERROR (eil, STREAM, ENCODE, (NULL),
|
|
("Could not map input buffer %ld", pts));
|
|
goto error;
|
|
}
|
|
|
|
/* Initialize EIL picture */
|
|
if (EIL_InitPictureDefault (&picture) != EIL_RC_Success) {
|
|
GST_ELEMENT_ERROR (eil, STREAM, ENCODE, (NULL),
|
|
("Could not initialize EIL picture %ld", pts));
|
|
goto error;
|
|
}
|
|
|
|
/* Set frame values on EIL picture */
|
|
if (!gst_lcevc_encoder_utils_init_eil_picture (priv->in_frame_type,
|
|
&video_frame, pts, &picture)) {
|
|
GST_ELEMENT_ERROR (eil, STREAM, ENCODE, (NULL),
|
|
("Could not set frame values on EIL picture %ld", pts));
|
|
goto error;
|
|
}
|
|
|
|
/* Set input frame as user data. This will be set in the encoded output as
|
|
* user data, which will help us getting the associated frame */
|
|
picture.user_data = frame;
|
|
|
|
/* Encode frame */
|
|
if (EIL_Encode (priv->ctx->context, &picture) != EIL_RC_Success) {
|
|
GST_ELEMENT_ERROR (eil, STREAM, ENCODE, (NULL),
|
|
("Could not encode input frame %ld", pts));
|
|
goto error;
|
|
}
|
|
|
|
GST_INFO_OBJECT (eil, "Sent input frame %ld", pts);
|
|
|
|
gst_video_frame_unmap (&video_frame);
|
|
return GST_FLOW_OK;
|
|
|
|
error:
|
|
gst_video_frame_unmap (&video_frame);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
static void
|
|
gst_lcevc_encoder_class_init (GstLcevcEncoderClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
GstVideoEncoderClass *video_encoder_class = GST_VIDEO_ENCODER_CLASS (klass);
|
|
|
|
gst_element_class_add_static_pad_template (element_class,
|
|
&gst_lcevc_encoder_sink_template);
|
|
|
|
gst_type_mark_as_plugin_api (GST_TYPE_LCEVC_ENCODER, 0);
|
|
|
|
gobject_class->finalize = gst_lcevc_encoder_finalize;
|
|
gobject_class->set_property = gst_lcevc_encoder_set_property;
|
|
gobject_class->get_property = gst_lcevc_encoder_get_property;
|
|
|
|
video_encoder_class->start = gst_lcevc_encoder_start;
|
|
video_encoder_class->stop = gst_lcevc_encoder_stop;
|
|
video_encoder_class->set_format = gst_lcevc_encoder_set_format;
|
|
video_encoder_class->sink_event = gst_lcevc_encoder_sink_event;
|
|
video_encoder_class->handle_frame = gst_lcevc_encoder_handle_frame;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_PLUGIN_NAME,
|
|
g_param_spec_string ("plugin-name", "Plugin Name",
|
|
"The name of the EIL plugin to use (NULL = auto)",
|
|
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_PLUGIN_PROPS,
|
|
g_param_spec_string ("plugin-props", "Plugin Props",
|
|
"A semi-colon list of key value pair properties for the EIL plugin",
|
|
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_BITRATE,
|
|
g_param_spec_uint ("bitrate", "Bitrate",
|
|
"Bitrate in kbit/sec (0 = auto)",
|
|
DEFAULT_MIN_BITRATE, DEFAULT_MAX_BITRATE, DEFAULT_BITRATE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SEI_LCEVC,
|
|
g_param_spec_boolean ("sei-lcevc", "SEI LCEVC",
|
|
"Whether to have LCEVC data as SEI (in the video stream) or not (attached to buffers as GstMeta)",
|
|
DEFAULT_SEI_LCEVC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_GOP_LENGTH,
|
|
g_param_spec_int ("gop-length", "GOP Length",
|
|
"The group of pictures length (-2 = auto, -1 = infinite, 0 = intra-only)",
|
|
DEFAULT_MIN_GOP_LENGTH, INT_MAX, DEFAULT_GOP_LENGTH,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DEBUG,
|
|
g_param_spec_boolean ("debug", "Debug",
|
|
"Whether to show EIL SDK logs or not",
|
|
DEFAULT_DEBUG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static void
|
|
gst_lcevc_encoder_init (GstLcevcEncoder * eil)
|
|
{
|
|
GstLcevcEncoderPrivate *priv = gst_lcevc_encoder_get_instance_private (eil);
|
|
|
|
/* Props */
|
|
priv->plugin_name = NULL;
|
|
priv->plugin_props = NULL;
|
|
priv->bitrate = DEFAULT_BITRATE;
|
|
priv->sei_lcevc = DEFAULT_SEI_LCEVC;
|
|
priv->gop_length = DEFAULT_GOP_LENGTH;
|
|
}
|