diff --git a/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoder.c b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoder.c new file mode 100644 index 0000000000..6407c114ce --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoder.c @@ -0,0 +1,866 @@ +/* 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 + +#include + +#include + +#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; +} diff --git a/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoder.h b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoder.h new file mode 100644 index 0000000000..053222e0f6 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoder.h @@ -0,0 +1,42 @@ +/* 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. + */ + +#ifndef __GST_LCEVC_ENCODER_H__ +#define __GST_LCEVC_ENCODER_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_LCEVC_ENCODER (gst_lcevc_encoder_get_type()) +G_DECLARE_DERIVABLE_TYPE (GstLcevcEncoder, + gst_lcevc_encoder, GST, LCEVC_ENCODER, GstVideoEncoder); + +struct _GstLcevcEncoderClass +{ + GstVideoEncoderClass parent_class; + + const gchar *(*get_eil_plugin_name) (GstLcevcEncoder *enc); + GstCaps *(*get_output_caps) (GstLcevcEncoder *enc); +}; + +G_END_DECLS + +#endif /* __GST_LCEVC_ENCODER_H__ */ diff --git a/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoderutils.c b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoderutils.c new file mode 100644 index 0000000000..9c08cfc6f1 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoderutils.c @@ -0,0 +1,91 @@ +/* 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. + */ + +#include "gstlcevcencoderutils.h" + +EILColourFormat +gst_lcevc_encoder_utils_get_color_format (GstVideoFormat format) +{ + switch (format) { + case GST_VIDEO_FORMAT_I420: + return EIL_YUV_420P; + case GST_VIDEO_FORMAT_I420_10LE: + return EIL_YUV_420P10; + case GST_VIDEO_FORMAT_Y42B: + return EIL_YUV_422P; + case GST_VIDEO_FORMAT_I422_10LE: + return EIL_YUV_422P10; + case GST_VIDEO_FORMAT_Y444: + return EIL_YUV_444P; + case GST_VIDEO_FORMAT_Y444_10LE: + return EIL_YUV_444P10; + case GST_VIDEO_FORMAT_RGB: + return EIL_RGB_24; + case GST_VIDEO_FORMAT_BGR: + return EIL_BGR_24; + case GST_VIDEO_FORMAT_RGBA: + return EIL_RGBA_32; + case GST_VIDEO_FORMAT_BGRA: + return EIL_BGRA_32; + case GST_VIDEO_FORMAT_ARGB: + return EIL_ARGB_32; + case GST_VIDEO_FORMAT_ABGR: + return EIL_ABGR_32; + default: + break; + } + + return -1; +} + +gboolean +gst_lcevc_encoder_utils_init_eil_picture (EILFrameType frame_type, + GstVideoFrame * frame, GstClockTime pts, EILPicture * picture) +{ + picture->memory_type = EIL_MT_Host; + picture->num_planes = GST_VIDEO_FRAME_N_PLANES (frame); + + if (picture->num_planes > EIL_MaxPlanes) + return FALSE; + + for (guint i = 0; i < picture->num_planes; i++) { + picture->plane[i] = GST_VIDEO_FRAME_PLANE_DATA (frame, i); + picture->stride[i] = GST_VIDEO_FRAME_PLANE_STRIDE (frame, i); + picture->offset[i] = GST_VIDEO_FRAME_PLANE_OFFSET (frame, i); + } + + picture->base_type = EIL_BT_Unknown; + picture->frame_type = frame_type; + + switch (frame->info.ABI.abi.field_order) { + case GST_VIDEO_FIELD_ORDER_TOP_FIELD_FIRST: + picture->field_type = EIL_FieldType_Top; + break; + case GST_VIDEO_FIELD_ORDER_BOTTOM_FIELD_FIRST: + picture->field_type = EIL_FieldType_Bottom; + break; + default: + picture->field_type = EIL_FieldType_None; + break; + } + + picture->pts = pts; + + return TRUE; +} diff --git a/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoderutils.h b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoderutils.h new file mode 100644 index 0000000000..bf3f53fdf6 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevcencoderutils.h @@ -0,0 +1,41 @@ +/* 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. + */ + +#ifndef __GST_LCEVC_ENCODER_UTILS_H__ +#define __GST_LCEVC_ENCODER_UTILS_H__ + +#include +#include + +#include + +G_BEGIN_DECLS + +#define GST_LCEVC_ENCODER_UTILS_SUPPORTED_FORMATS \ + "{ I420, I420_10LE, Y42B, I422_10LE, Y444, Y444_10LE, RGB, BGR, RGBA, BGRA, ARGB, ABGR }" + +EILColourFormat gst_lcevc_encoder_utils_get_color_format ( + GstVideoFormat format); + +gboolean gst_lcevc_encoder_utils_init_eil_picture (EILFrameType frame_type, + GstVideoFrame *frame, GstClockTime pts, EILPicture *picture); + +G_END_DECLS + +#endif /* __GST_LCEVC_ENCODER_UTILS_H__ */ diff --git a/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevch264enc.c b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevch264enc.c new file mode 100644 index 0000000000..861ad14a30 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevch264enc.c @@ -0,0 +1,76 @@ +/* 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 "gstlcevch264enc.h" + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-h264, lcevc = (boolean) true") + ); + +struct _GstLcevcH264Enc +{ + GstLcevcEncoder parent; +}; + +#define gst_lcevc_h264_enc_parent_class parent_class +G_DEFINE_TYPE (GstLcevcH264Enc, gst_lcevc_h264_enc, GST_TYPE_LCEVC_ENCODER); + +GST_ELEMENT_REGISTER_DEFINE (lcevch264enc, "lcevch264enc", + GST_RANK_PRIMARY, GST_TYPE_LCEVC_H264_ENC); + +static const gchar * +gst_lecevc_h264_enc_get_eil_plugin_name (GstLcevcEncoder * enc) +{ + return "x264"; +} + +static GstCaps * +gst_lecevc_h264_enc_get_output_caps (GstLcevcEncoder * enc) +{ + return gst_caps_new_simple ("video/x-h264", + "lcevc", G_TYPE_BOOLEAN, TRUE, NULL); +} + +static void +gst_lcevc_h264_enc_class_init (GstLcevcH264EncClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstLcevcEncoderClass *le_class = GST_LCEVC_ENCODER_CLASS (klass); + + gst_element_class_add_static_pad_template (element_class, &src_template); + + gst_element_class_set_static_metadata (element_class, + "H.264 LCEVC Encoder", "Codec/Encoder/Video", + "Encoder that internally uses EIL plugins to encode LCEVC H.264 video", + "Julian Bouzas "); + + le_class->get_eil_plugin_name = gst_lecevc_h264_enc_get_eil_plugin_name; + le_class->get_output_caps = gst_lecevc_h264_enc_get_output_caps; +} + +static void +gst_lcevc_h264_enc_init (GstLcevcH264Enc * self) +{ +} diff --git a/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevch264enc.h b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevch264enc.h new file mode 100644 index 0000000000..1dce5896b9 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lcevcencoder/gstlcevch264enc.h @@ -0,0 +1,34 @@ +/* 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. + */ + +#ifndef __GST_LCEVC_H264_ENC_H__ +#define __GST_LCEVC_H264_ENC_H__ + +#include "gstlcevcencoder.h" + +G_BEGIN_DECLS + +#define GST_TYPE_LCEVC_H264_ENC (gst_lcevc_h264_enc_get_type()) +G_DECLARE_FINAL_TYPE (GstLcevcH264Enc, gst_lcevc_h264_enc, + GST, LCEVC_H264_ENC, GstLcevcEncoder); + +GST_ELEMENT_REGISTER_DECLARE (lcevch264enc); + +G_END_DECLS +#endif diff --git a/subprojects/gst-plugins-bad/ext/lcevcencoder/meson.build b/subprojects/gst-plugins-bad/ext/lcevcencoder/meson.build new file mode 100644 index 0000000000..81ceffb85b --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lcevcencoder/meson.build @@ -0,0 +1,24 @@ +lcevcencoder_sources = [ + 'plugin.c', + 'gstlcevcencoderutils.c', + 'gstlcevcencoder.c', + 'gstlcevch264enc.c', +] + +lcevc_eil_dep = dependency ('lcevc_eil', required: get_option('lcevcencoder')) + +if lcevc_eil_dep.found() + gstlcevcencoder = library('gstlcevcencoder', + lcevcencoder_sources, + c_args : gst_plugins_bad_args, + include_directories : [configinc], + dependencies : [ + gstvideo_dep, + gstcodecparsers_dep, + lcevc_eil_dep, + ], + install : true, + install_dir : plugins_install_dir, + ) + plugins += [gstlcevcencoder] +endif diff --git a/subprojects/gst-plugins-bad/ext/lcevcencoder/plugin.c b/subprojects/gst-plugins-bad/ext/lcevcencoder/plugin.c new file mode 100644 index 0000000000..9c549edfc9 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lcevcencoder/plugin.c @@ -0,0 +1,42 @@ +/* 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 + +#include "gstlcevch264enc.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean ret = FALSE; + + ret |= GST_ELEMENT_REGISTER (lcevch264enc, plugin); + + return ret; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + lcevcencoder, + "LCEVC encoder", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/subprojects/gst-plugins-bad/ext/meson.build b/subprojects/gst-plugins-bad/ext/meson.build index 2c5b212090..eee193e099 100644 --- a/subprojects/gst-plugins-bad/ext/meson.build +++ b/subprojects/gst-plugins-bad/ext/meson.build @@ -30,6 +30,7 @@ subdir('isac') subdir('ladspa') subdir('lc3') subdir('lcevcdecoder') +subdir('lcevcencoder') subdir('ldac') subdir('libde265') subdir('lv2') diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt index 20999d29e2..5ffbcc2a53 100644 --- a/subprojects/gst-plugins-bad/meson_options.txt +++ b/subprojects/gst-plugins-bad/meson_options.txt @@ -41,6 +41,7 @@ option('ivtc', type : 'feature', value : 'auto') option('jp2kdecimator', type : 'feature', value : 'auto') option('jpegformat', type : 'feature', value : 'auto') option('lcevcdecoder', type : 'feature', value : 'auto') +option('lcevcencoder', type : 'feature', value : 'auto') option('librfb', type : 'feature', value : 'auto') option('midi', type : 'feature', value : 'auto') option('mpegdemux', type : 'feature', value : 'auto')