mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 03:00:35 +00:00
867 lines
25 KiB
C
867 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;
|
||
|
}
|