From a724f9ddfb5d1cec8f3ef6eb720395f96b78b026 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Fri, 30 Aug 2019 17:16:35 -0400 Subject: [PATCH] encodebin: Ensure that a single segment is pushed into encoders Following the [design document] encodebin needs to handle sources that output multiple streams, for that purpose and to make it simpler, we ensure that a single segment is outputted to the encoders by using an `identity single-segment=true` at the beginning of streams chains. Added API to enable or disable the use of that new feature. Added support for the encoding profile parser for that new property, keeping backward compatibility [design document]: https://gstreamer.freedesktop.org/documentation/additional/design/encoding.html?gi-language=c#rendering-timelines --- gst-libs/gst/pbutils/encoding-profile.c | 177 +++++++++++++++++------- gst-libs/gst/pbutils/encoding-profile.h | 7 + gst/encoding/gstencodebin.c | 29 +++- 3 files changed, 155 insertions(+), 58 deletions(-) diff --git a/gst-libs/gst/pbutils/encoding-profile.c b/gst-libs/gst/pbutils/encoding-profile.c index bde7e82eae..35c45ac240 100644 --- a/gst-libs/gst/pbutils/encoding-profile.c +++ b/gst-libs/gst/pbutils/encoding-profile.c @@ -54,31 +54,31 @@ * * #### Using encoders and muxer element factory name: * - * |[ + * ``` * muxer_factory_name:video_encoder_factory_name:audio_encoder_factory_name - * ]| + * ``` * * For example to encode a stream into a WebM container, with an OGG audio * stream and a VP8 video stream, the serialized #GstEncodingProfile looks * like: * - * |[ + * ``` * webmmux:vp8enc:vorbisenc - * ]| + * ``` * * #### Define the encoding profile in a generic way using caps: * - * |[ + * ``` * muxer_source_caps:video_encoder_source_caps:audio_encoder_source_caps - * ]| + * ``` * * For example to encode a stream into a WebM container, with an OGG audio * stream and a VP8 video stream, the serialized #GstEncodingProfile looks * like: * - * |[ + * ``` * video/webm:video/x-vp8:audio/x-vorbis - * ]| + * ``` * * It is possible to mix caps and element type names so you can specify a specific * video encoder while using caps for other encoders/muxer. @@ -88,16 +88,16 @@ * You can also set the preset name of the encoding profile using the * caps+preset_name syntax as in: * - * |[ + * ``` * video/webm:video/x-vp8+youtube-preset:audio/x-vorbis - * ]| + * ``` * - * Moreover, you can set the `presence` property of an - * encoding profile using the `|presence` syntax as in: + * Moreover, you can set extra properties `presence` and `single-segment` of an + * encoding profile using the `|presence=` syntax as in: * - * |[ - * video/webm:video/x-vp8|1:audio/x-vorbis - * ]| + * ``` + * video/webm:video/x-vp8|presence=1|single-segment=true:audio/x-vorbis + * ``` * * This field allows specifies the maximum number of times a * #GstEncodingProfile can be used inside an encodebin. If 0, it is not a @@ -117,59 +117,59 @@ * as the container format, VP8 as the video codec and Vorbis as the audio * codec), you should use: * - * |[ + * ``` * "video/webm:video/x-raw,width=1920,height=1080->video/x-vp8:audio/x-vorbis" - * ]| + * ``` * * > NOTE: Make sure to enclose into quotes to avoid '>' to be reinterpreted by * > the shell. * * In the case you are using encoder types, the following is also possible: * - * |[ + * ``` * "matroskamux:x264enc,width=1920,height=1080:audio/x-vorbis" - * ]| + * ``` * * ## Some serialized encoding formats examples: * * MP3 audio and H264 in MP4: * - * |[ + * ``` * video/quicktime,variant=iso:video/x-h264:audio/mpeg,mpegversion=1,layer=3 - * ]| + * ``` * * Vorbis and theora in OGG: * - * |[ + * ``` * application/ogg:video/x-theora:audio/x-vorbis - * ]| + * ``` * * AC3 and H264 in MPEG-TS: * - * |[ + * ``` * video/mpegts:video/x-h264:audio/x-ac3 - * ]| + * ``` * * ## Loading a profile from encoding targets * * Anywhere where you have to use a string to define a #GstEncodingProfile, * you can use load it from a #GstEncodingTarget using the following syntaxes: * - * |[ + * ``` * target_name[/profilename/category] - * ]| + * ``` * * or * - * |[ + * ``` * /path/to/target.gep:profilename - * ]| + * ``` * * ## Examples * * ### Creating a profile * - * |[ + * ``` c * #include * ... * GstEncodingProfile * @@ -197,11 +197,11 @@ * return (GstEncodingProfile*) prof; *} * - * ]| + * ``` * * ### Example: Using an encoder preset with a profile * - * |[ + * ``` c * #include * ... * GstEncodingProfile * @@ -239,11 +239,11 @@ * return (GstEncodingProfile*) prof; *} * - * ]| + * ``` * * ### Listing categories, targets and profiles * - * |[ + * ``` c * #include * ... * GstEncodingProfile *prof; @@ -271,7 +271,7 @@ * g_list_free (categories); * * ... - * ]| + * ``` */ #ifdef HAVE_CONFIG_H @@ -299,6 +299,7 @@ struct _GstEncodingProfile GstCaps *restriction; gboolean allow_dynamic_output; gboolean enabled; + gboolean single_segment; }; struct _GstEncodingProfileClass @@ -641,6 +642,40 @@ gst_encoding_profile_set_allow_dynamic_output (GstEncodingProfile * profile, profile->allow_dynamic_output = allow_dynamic_output; } +/** + * gst_encoding_profile_get_single_segment: + * @profile: a #GstEncodingProfile + * + * Returns: #TRUE if the stream represented by @profile should use a single + * segment before the encoder, #FALSE otherwise. This means that buffers will be retimestamped + * and segments will be eat so as to appear as one segment. + */ +gboolean +gst_encoding_profile_get_single_segment (GstEncodingProfile * profile) +{ + g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE); + + return profile->single_segment; +} + +/** + * gst_encoding_profile_set_single_segment: + * @profile: a #GstEncodingProfile + * @single_segment: #TRUE if the stream represented by @profile should use a single + * segment before the encoder #FALSE otherwise. + * + * If using a single segment, buffers will be retimestamped + * and segments will be eat so as to appear as one segment. + */ +void +gst_encoding_profile_set_single_segment (GstEncodingProfile * profile, + gboolean single_segment) +{ + g_return_if_fail (GST_IS_ENCODING_PROFILE (profile)); + + profile->single_segment = single_segment; +} + /** * gst_encoding_profile_set_preset: * @profile: a #GstEncodingProfile @@ -1529,8 +1564,8 @@ done: static GstEncodingProfile * create_encoding_profile_from_caps (GstCaps * caps, gchar * preset_name, - GstCaps * restrictioncaps, gint presence, gchar * factory_name, - GList * muxers_and_encoders, GstCaps * raw_audio_caps, + GstCaps * restrictioncaps, gint presence, gboolean single_segment, + gchar * factory_name, GList * muxers_and_encoders, GstCaps * raw_audio_caps, GstCaps * raw_video_caps) { GstEncodingProfile *profile = NULL; @@ -1571,6 +1606,7 @@ create_encoding_profile_from_caps (GstCaps * caps, gchar * preset_name, if (factory_name && profile) gst_encoding_profile_set_preset_name (profile, factory_name); + gst_encoding_profile_set_single_segment (profile, single_segment); g_free (factory_name); @@ -1584,7 +1620,8 @@ create_encoding_stream_profile (gchar * serialized_profile, { GstCaps *caps; guint presence = 0; - gchar *strcaps, *strpresence, **strpresence_v, **restriction_format, + gboolean single_segment = FALSE; + gchar *strcaps, *strpresence, **strprops_v, **restriction_format, **preset_v, *preset_name = NULL, *factory_name = NULL; GstCaps *restrictioncaps = NULL; GstEncodingProfile *profile = NULL; @@ -1608,22 +1645,56 @@ create_encoding_stream_profile (gchar * serialized_profile, strpresence = preset_v[0]; } - strpresence_v = g_strsplit (strpresence, "|", 0); - if (strpresence_v[1]) { /* We have a presence */ + strprops_v = g_strsplit (strpresence, "|", 0); + if (strprops_v[1]) { /* We have a properties */ gchar *endptr; + guint propi; - if (preset_v[1]) { /* We have preset and presence */ - preset_name = g_strdup (strpresence_v[0]); - } else { /* We have a presence but no preset */ + if (preset_v[1]) { /* We have preset and properties */ + preset_name = g_strdup (strprops_v[0]); + } else { /* We have a properties but no preset */ g_free (strcaps); - strcaps = g_strdup (strpresence_v[0]); + strcaps = g_strdup (strprops_v[0]); } - presence = g_ascii_strtoll (strpresence_v[1], &endptr, 10); - if (endptr == strpresence_v[1]) { - GST_ERROR ("Wrong presence %s", strpresence_v[1]); + for (propi = 1; strprops_v[propi]; propi++) { + gchar **propv = g_strsplit (strprops_v[propi], "=", -1); + gchar *presence_str = NULL; - return NULL; + if (propv[1] && propv[2]) { + g_warning ("Wrong format for property: %s, only 1 `=` is expected", + strprops_v[propi]); + + return NULL; + } + + if (!propv[1]) { + presence_str = propv[0]; + } else if (!g_strcmp0 (propv[0], "presence")) { + presence_str = propv[1]; + } else if (!g_strcmp0 (propv[0], "single-segment")) { + GValue v = G_VALUE_INIT; + + g_value_init (&v, G_TYPE_BOOLEAN); + if (!gst_value_deserialize (&v, propv[1])) { + g_warning ("Invalid value for property 'single-segment': %s", + propv[1]); + + return NULL; + } + + single_segment = g_value_get_boolean (&v); + g_value_reset (&v); + } + + if (presence_str) { + presence = g_ascii_strtoll (presence_str, &endptr, 10); + + if (endptr == strprops_v[1]) { + g_warning ("Wrong presence %s", presence_str); + return NULL; + } + } } } else { /* We have no presence */ if (preset_v[1]) { /* Not presence but preset */ @@ -1632,7 +1703,7 @@ create_encoding_stream_profile (gchar * serialized_profile, strcaps = g_strdup (preset_v[0]); } /* Else we have no presence nor preset */ } - g_strfreev (strpresence_v); + g_strfreev (strprops_v); g_strfreev (preset_v); GST_DEBUG ("Creating preset with restrictions: %" GST_PTR_FORMAT @@ -1642,8 +1713,8 @@ create_encoding_stream_profile (gchar * serialized_profile, caps = gst_caps_from_string (strcaps); if (caps) { profile = create_encoding_profile_from_caps (caps, preset_name, - restrictioncaps, presence, NULL, muxers_and_encoders, raw_audio_caps, - raw_video_caps); + restrictioncaps, presence, single_segment, NULL, muxers_and_encoders, + raw_audio_caps, raw_video_caps); gst_caps_unref (caps); } @@ -1652,8 +1723,8 @@ create_encoding_stream_profile (gchar * serialized_profile, &factory_name, restrictioncaps ? NULL : &restrictioncaps); if (caps) { profile = create_encoding_profile_from_caps (caps, preset_name, - restrictioncaps, presence, factory_name, muxers_and_encoders, - raw_audio_caps, raw_video_caps); + restrictioncaps, presence, single_segment, factory_name, + muxers_and_encoders, raw_audio_caps, raw_video_caps); gst_caps_unref (caps); } } diff --git a/gst-libs/gst/pbutils/encoding-profile.h b/gst-libs/gst/pbutils/encoding-profile.h index 786426095d..bb781a325a 100644 --- a/gst-libs/gst/pbutils/encoding-profile.h +++ b/gst-libs/gst/pbutils/encoding-profile.h @@ -152,6 +152,13 @@ GST_PBUTILS_API void gst_encoding_profile_set_allow_dynamic_output (GstEncodingProfile *profile, gboolean allow_dynamic_output); +GST_PBUTILS_API +gboolean gst_encoding_profile_get_single_segment (GstEncodingProfile *profile); + +GST_PBUTILS_API +void gst_encoding_profile_set_single_segment (GstEncodingProfile *profile, + gboolean single_segment); + GST_PBUTILS_API const gchar * gst_encoding_profile_get_preset (GstEncodingProfile *profile); diff --git a/gst/encoding/gstencodebin.c b/gst/encoding/gstencodebin.c index de1e2bb023..62f7869e09 100644 --- a/gst/encoding/gstencodebin.c +++ b/gst/encoding/gstencodebin.c @@ -198,7 +198,8 @@ struct _StreamGroup GstEncodeBin *ebin; GstEncodingProfile *profile; GstPad *ghostpad; /* Sink ghostpad */ - GstElement *inqueue; /* Queue just after the ghostpad */ + GstElement *identity; /* Identity just after the ghostpad */ + GstElement *inqueue; /* Queue just after the identity */ GstElement *splitter; GList *converters; /* List of conversion GstElement */ GstElement *capsfilter; /* profile->restriction (if non-NULL/ANY) */ @@ -1328,6 +1329,13 @@ _create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, gst_bin_add (GST_BIN (ebin), sgroup->splitter); tosync = g_list_append (tosync, sgroup->splitter); + if (gst_encoding_profile_get_single_segment (sprof)) { + sgroup->identity = gst_element_factory_make ("identity", NULL); + g_object_set (sgroup->identity, "single-segment", TRUE, NULL); + gst_bin_add (GST_BIN (ebin), sgroup->identity); + tosync = g_list_append (tosync, sgroup->identity); + } + /* Input queue * FIXME : figure out what max-size to use for the input queue */ sgroup->inqueue = gst_element_factory_make ("queue", NULL); @@ -1338,11 +1346,11 @@ _create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, gst_bin_add (GST_BIN (ebin), sgroup->inqueue); tosync = g_list_append (tosync, sgroup->inqueue); - if (G_UNLIKELY (!fast_element_link (sgroup->inqueue, sgroup->splitter))) - goto splitter_link_failure; - /* Expose input queue sink pad as ghostpad */ - sinkpad = gst_element_get_static_pad (sgroup->inqueue, "sink"); + /* Expose input queue or identity sink pad as ghostpad */ + sinkpad = + gst_element_get_static_pad (sgroup->identity ? sgroup-> + identity : sgroup->inqueue, "sink"); if (sinkpadname == NULL) { gchar *pname = g_strdup_printf ("%s_%u", gst_encoding_profile_get_type_nick (sprof), @@ -1354,6 +1362,13 @@ _create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, sgroup->ghostpad = gst_ghost_pad_new (sinkpadname, sinkpad); gst_object_unref (sinkpad); + if (sgroup->identity + && G_UNLIKELY (!fast_element_link (sgroup->identity, sgroup->inqueue))) + goto queue_link_failure; + + if (G_UNLIKELY (!fast_element_link (sgroup->inqueue, sgroup->splitter))) + goto splitter_link_failure; + /* Path 1 : Already-encoded data */ sinkpad = @@ -1669,6 +1684,10 @@ splitter_link_failure: GST_ERROR_OBJECT (ebin, "Failure linking to the splitter"); goto cleanup; +queue_link_failure: + GST_ERROR_OBJECT (ebin, "Failure linking to the inqueue"); + goto cleanup; + combiner_link_failure: GST_ERROR_OBJECT (ebin, "Failure linking to the combiner"); goto cleanup;