diff --git a/gst-libs/gst/pbutils/encoding-profile.c b/gst-libs/gst/pbutils/encoding-profile.c index 9c9c47c14a..65d6277ca6 100644 --- a/gst-libs/gst/pbutils/encoding-profile.c +++ b/gst-libs/gst/pbutils/encoding-profile.c @@ -30,8 +30,8 @@ * * Functions to create and handle encoding profiles. * - * Encoding profiles describe the media types and settings one wishes to use - * for an encoding process. The top-level profiles are commonly + * Encoding profiles describe the media types and settings one wishes to use for + * an encoding process. The top-level profiles are commonly * #GstEncodingContainerProfile(s) (which contains a user-readable name and * description along with which container format to use). These, in turn, * reference one or more #GstEncodingProfile(s) which indicate which encoding @@ -101,18 +101,21 @@ * * ### Setting properties on muxers or on the encoding profile itself * - * Moreover, you can set extra properties `presence`, `single-segment` and - * `variable-framerate` * of an * encoding profile using the `|presence=` syntax - * as in: + * Moreover, you can set the extra properties: + * + * * `|element-properties,property1=true` (See + * #gst_encoding_profile_set_element_properties) + * * `|presence=true` (See See #gst_encoding_profile_get_presence) + * * `|single-segment=true` (See #gst_encoding_profile_set_single_segment) + * * `|single-segment=true` (See + * #gst_encoding_video_profile_set_variableframerate) + * + * for example: * * ``` - * video/webm:video/x-vp8|presence=1,variable-framerate=true|single-segment=true:audio/x-vorbis + * video/webm:video/x-vp8|presence=1|element-properties,target-bitrate=500000: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 - * mandatory stream and can be used as many times as necessary. - * * ### Enforcing properties to the stream itself (video size, number of audio channels, etc..) * * You can also use the `restriction_caps->encoded_format_caps` syntax to @@ -297,6 +300,8 @@ #include /* GstEncodingProfile API */ +#define PROFILE_LOCK(profile) (g_mutex_lock(&((GstEncodingProfile*)profile)->lock)) +#define PROFILE_UNLOCK(profile) (g_mutex_unlock(&((GstEncodingProfile*)profile)->lock)) struct _GstEncodingProfile { @@ -309,10 +314,14 @@ struct _GstEncodingProfile gchar *preset; gchar *preset_name; guint presence; - GstCaps *restriction; gboolean allow_dynamic_output; gboolean enabled; gboolean single_segment; + + GMutex lock; // { + GstCaps *restriction; + GstStructure *element_properties; + // } }; struct _GstEncodingProfileClass @@ -326,6 +335,7 @@ enum { FIRST_PROPERTY, PROP_RESTRICTION_CAPS, + PROP_ELEMENT_PROPERTIES, LAST_PROPERTY }; @@ -391,6 +401,11 @@ _encoding_profile_get_property (GObject * object, guint prop_id, case PROP_RESTRICTION_CAPS: gst_value_set_caps (value, prof->restriction); break; + case PROP_ELEMENT_PROPERTIES: + PROFILE_LOCK (prof); + gst_value_set_structure (value, prof->element_properties); + PROFILE_UNLOCK (prof); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -408,6 +423,14 @@ _encoding_profile_set_property (GObject * object, guint prop_id, gst_encoding_profile_set_restriction (prof, gst_caps_copy (gst_value_get_caps (value))); break; + case PROP_ELEMENT_PROPERTIES: + { + const GstStructure *structure = gst_value_get_structure (value); + + gst_encoding_profile_set_element_properties (prof, + structure ? gst_structure_copy (structure) : NULL); + break; + } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -441,11 +464,30 @@ gst_encoding_profile_class_init (GstEncodingProfileClass * klass) _properties[PROP_RESTRICTION_CAPS] = g_param_spec_boxed ("restriction-caps", "Restriction caps", "The restriction caps to use", GST_TYPE_CAPS, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); - g_object_class_install_property (gobject_class, - PROP_RESTRICTION_CAPS, _properties[PROP_RESTRICTION_CAPS]); + /** + * GstEncodingProfile:element-properties: + * + * A #GstStructure defining the properties to be set to the element + * the profile represents. + * + * For example for `av1enc`: + * + * ``` + * element-properties,row-mt=true, end-usage=vbr + * ``` + * + * Since: 1.20 + */ + _properties[PROP_ELEMENT_PROPERTIES] = + g_param_spec_boxed ("element-properties", "Element properties", + "The element properties to use. " + "Example: {properties,boolean-prop=true,string-prop=\"hi\"}.", + GST_TYPE_STRUCTURE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, LAST_PROPERTY, _properties); } /** @@ -791,6 +833,84 @@ gst_encoding_profile_set_restriction (GstEncodingProfile * profile, _properties[PROP_RESTRICTION_CAPS]); } +/** + * gst_encoding_profile_set_element_properties: + * @self: a #GstEncodingProfile + * @element_properties: (transfer full): A #GstStructure defining the properties + * to be set to the element the profile represents. + * + * This allows setting the muxing/encoding element properties. + * + * **Set properties generically** + * + * ``` properties + * [element-properties, boolean-prop=true, string-prop="hi"] + * ``` + * + * **Mapping properties with well known element factories** + * + * ``` properties + * element-properties-map, map = { + * [openh264enc, gop-size=32, ], + * [x264enc, key-int-max=32, tune=zerolatency], + * } + * ``` + * + * Since: 1.20 + */ +void +gst_encoding_profile_set_element_properties (GstEncodingProfile * self, + GstStructure * element_properties) +{ + g_return_if_fail (GST_IS_ENCODING_PROFILE (self)); + g_return_if_fail (!element_properties + || GST_IS_STRUCTURE (element_properties)); + +#ifndef G_DISABLE_CHECKS + if (element_properties && + (gst_structure_has_name (element_properties, "element-properties-map") + || gst_structure_has_name (element_properties, "properties-map") + || gst_structure_has_name (element_properties, "map"))) + g_return_if_fail (gst_structure_has_field_typed (element_properties, "map", + GST_TYPE_LIST)); +#endif + + PROFILE_LOCK (self); + if (self->element_properties) + gst_structure_free (self->element_properties); + if (element_properties) + self->element_properties = element_properties; + else + self->element_properties = NULL; + PROFILE_UNLOCK (self); + + g_object_notify_by_pspec (G_OBJECT (self), + _properties[PROP_ELEMENT_PROPERTIES]); +} + +/** + * gst_encoding_profile_get_element_properties: + * @self: a #GstEncodingProfile + * + * Returns: (transfer full) (nullable): The properties that are going to be set on the underlying element + * + * Since: 1.20 + */ +GstStructure * +gst_encoding_profile_get_element_properties (GstEncodingProfile * self) +{ + GstStructure *res = NULL; + + g_return_val_if_fail (GST_IS_ENCODING_PROFILE (self), NULL); + + PROFILE_LOCK (self); + if (self->element_properties) + res = gst_structure_copy (self->element_properties); + PROFILE_UNLOCK (self); + + return res; +} + /* Container profiles */ struct _GstEncodingContainerProfile @@ -1648,6 +1768,30 @@ done: return profile; } +static gboolean +gst_structure_validate_name (const gchar * name) +{ + const gchar *s; + + g_return_val_if_fail (name != NULL, FALSE); + + if (G_UNLIKELY (!g_ascii_isalpha (*name))) + return FALSE; + + /* FIXME: test name string more */ + s = &name[1]; + while (*s && (g_ascii_isalnum (*s) || strchr ("/-_.:+", *s) != NULL)) + s++; + + if (*s == ',') + return TRUE; + + if (G_UNLIKELY (*s != '\0')) + return FALSE; + + return TRUE; +} + static GstEncodingProfile * create_encoding_stream_profile (gchar * serialized_profile, GList * muxers_and_encoders, GstCaps * raw_audio_caps, @@ -1659,6 +1803,7 @@ create_encoding_stream_profile (gchar * serialized_profile, gchar *strcaps, *strpresence, **strprops_v, **restriction_format, **preset_v, *preset_name = NULL, *factory_name = NULL, *variable_framerate = NULL; + GstStructure *element_properties = NULL; GstCaps *restrictioncaps = NULL; GstEncodingProfile *profile = NULL; @@ -1694,12 +1839,26 @@ create_encoding_stream_profile (gchar * serialized_profile, } for (propi = 1; strprops_v[propi]; propi++) { - gchar **propv = g_strsplit (strprops_v[propi], "=", -1); + gchar **propv; gchar *presence_str = NULL; + gchar *prop = strprops_v[propi]; + GstStructure *tmpstruct = NULL; + if (gst_structure_validate_name (prop)) + tmpstruct = gst_structure_new_from_string (prop); + if (tmpstruct) { + if (element_properties) + gst_structure_free (element_properties); + + element_properties = tmpstruct; + + continue; + } + + propv = g_strsplit (prop, "=", -1); if (propv[1] && propv[2]) { g_warning ("Wrong format for property: %s, only 1 `=` is expected", - strprops_v[propi]); + prop); return NULL; } @@ -1723,6 +1882,9 @@ create_encoding_stream_profile (gchar * serialized_profile, single_segment = g_value_get_boolean (&v); g_value_reset (&v); + } else { + g_warning ("Unsupported property: %s", propv[0]); + return NULL; } if (presence_str) { @@ -1806,6 +1968,9 @@ create_encoding_stream_profile (gchar * serialized_profile, return NULL; } + if (element_properties) + gst_encoding_profile_set_element_properties (profile, element_properties); + return profile; } diff --git a/gst-libs/gst/pbutils/encoding-profile.h b/gst-libs/gst/pbutils/encoding-profile.h index bb781a325a..ad97d01f0b 100644 --- a/gst-libs/gst/pbutils/encoding-profile.h +++ b/gst-libs/gst/pbutils/encoding-profile.h @@ -264,7 +264,14 @@ GST_PBUTILS_API GstEncodingProfile * gst_encoding_profile_from_discoverer (GstDiscovererInfo *info); GST_PBUTILS_API -GstEncodingProfile * gst_encoding_profile_copy (GstEncodingProfile *self); +GstEncodingProfile * gst_encoding_profile_copy (GstEncodingProfile *self); + +GST_PBUTILS_API +void gst_encoding_profile_set_element_properties (GstEncodingProfile *self, + GstStructure *element_properties); + +GST_PBUTILS_API +GstStructure *gst_encoding_profile_get_element_properties (GstEncodingProfile *self); G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstEncodingAudioProfile, gst_object_unref) diff --git a/gst/encoding/gstencodebasebin.c b/gst/encoding/gstencodebasebin.c index 0a686279d8..03c28b774e 100644 --- a/gst/encoding/gstencodebasebin.c +++ b/gst/encoding/gstencodebasebin.c @@ -870,12 +870,84 @@ beach: return parser; } +static gboolean +_set_properties (GQuark property_id, const GValue * value, GObject * element) +{ + GST_DEBUG_OBJECT (element, "Setting %s", g_quark_to_string (property_id)); + g_object_set_property (element, g_quark_to_string (property_id), value); + + return TRUE; +} + +static void +set_element_properties_from_encoding_profile (GstEncodingProfile * profile, + GParamSpec * arg G_GNUC_UNUSED, GstElement * element) +{ + gint i; + const GValue *v; + GstElementFactory *factory; + GstStructure *properties = + gst_encoding_profile_get_element_properties (profile); + + if (!properties) + return; + + if (!gst_structure_has_name (properties, "element-properties-map")) { + gst_structure_foreach (properties, + (GstStructureForeachFunc) _set_properties, element); + goto done; + } + + factory = gst_element_get_factory (element); + if (!factory) { + GST_INFO_OBJECT (profile, "No factory for underlying element, " + "not setting properties"); + return; + } + + v = gst_structure_get_value (properties, "map"); + for (i = 0; i < gst_value_list_get_size (v); i++) { + const GValue *map_value = gst_value_list_get_value (v, i); + const GstStructure *tmp_properties; + + if (!GST_VALUE_HOLDS_STRUCTURE (map_value)) { + g_warning ("Invalid value type %s in the property map " + "(expected GstStructure)", G_VALUE_TYPE_NAME (map_value)); + continue; + } + + tmp_properties = gst_value_get_structure (map_value); + if (!gst_structure_has_name (tmp_properties, GST_OBJECT_NAME (factory))) { + GST_INFO_OBJECT (GST_OBJECT_PARENT (element), + "Ignoring values for %" GST_PTR_FORMAT, tmp_properties); + continue; + } + + GST_DEBUG_OBJECT (GST_OBJECT_PARENT (element), + "Setting %" GST_PTR_FORMAT " on %" GST_PTR_FORMAT, tmp_properties, + element); + gst_structure_foreach (tmp_properties, + (GstStructureForeachFunc) _set_properties, element); + goto done; + } + + GST_ERROR_OBJECT (GST_OBJECT_PARENT (element), "Unknown factory: %s", + GST_OBJECT_NAME (factory)); + +done: + gst_structure_free (properties); +} + static GstElement * _create_element_and_set_preset (GstElementFactory * factory, - const gchar * preset, const gchar * name, const gchar * preset_name) + GstEncodingProfile * profile, const gchar * name) { GstElement *res = NULL; + const gchar *preset; + const gchar *preset_name; + preset_name = gst_encoding_profile_get_preset_name (profile); + preset = gst_encoding_profile_get_preset (profile); GST_DEBUG ("Creating element from factory %s (preset factory name: %s" " preset name: %s)", GST_OBJECT_NAME (factory), preset_name, preset); @@ -902,6 +974,12 @@ _create_element_and_set_preset (GstElementFactory * factory, } } /* Else we keep it */ + if (res) { + set_element_properties_from_encoding_profile (profile, NULL, res); + + g_signal_connect (profile, "notify::element-properties", + G_CALLBACK (set_element_properties_from_encoding_profile), res); + } return res; } @@ -914,11 +992,8 @@ _get_encoder (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof) GstElement *encoder = NULL; GstElementFactory *encoderfact = NULL; GstCaps *format; - const gchar *preset, *preset_name; format = gst_encoding_profile_get_format (sprof); - preset = gst_encoding_profile_get_preset (sprof); - preset_name = gst_encoding_profile_get_preset_name (sprof); GST_DEBUG ("Getting list of encoders for format %" GST_PTR_FORMAT, format); @@ -948,8 +1023,7 @@ _get_encoder (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof) for (tmp = encoders; tmp; tmp = tmp->next) { encoderfact = (GstElementFactory *) tmp->data; - if ((encoder = _create_element_and_set_preset (encoderfact, preset, - NULL, preset_name))) + if ((encoder = _create_element_and_set_preset (encoderfact, sprof, NULL))) break; } @@ -1458,7 +1532,7 @@ _create_stream_group (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof, tosync = g_list_append (tosync, sgroup->identity); } else { GST_INFO_OBJECT (ebin, "Single segment is not supported when avoiding" - " to reencode!"); + " to re-encode!"); } } @@ -1900,11 +1974,7 @@ _get_formatter (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof) GstElement *formatter = NULL; GstElementFactory *formatterfact = NULL; GstCaps *format; - const gchar *preset, *preset_name; - format = gst_encoding_profile_get_format (sprof); - preset = gst_encoding_profile_get_preset (sprof); - preset_name = gst_encoding_profile_get_preset_name (sprof); GST_DEBUG ("Getting list of formatters for format %" GST_PTR_FORMAT, format); @@ -1923,8 +1993,7 @@ _get_formatter (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof) GST_OBJECT_NAME (formatterfact)); if ((formatter = - _create_element_and_set_preset (formatterfact, preset, - NULL, preset_name))) + _create_element_and_set_preset (formatterfact, sprof, NULL))) break; } @@ -1969,10 +2038,9 @@ _get_muxer (GstEncodeBaseBin * ebin) GstElementFactory *muxerfact = NULL; const GList *tmp; GstCaps *format; - const gchar *preset, *preset_name; + const gchar *preset_name; format = gst_encoding_profile_get_format (ebin->profile); - preset = gst_encoding_profile_get_preset (ebin->profile); preset_name = gst_encoding_profile_get_preset_name (ebin->profile); GST_DEBUG_OBJECT (ebin, "Getting list of muxers for format %" GST_PTR_FORMAT, @@ -2037,8 +2105,7 @@ _get_muxer (GstEncodeBaseBin * ebin) /* Only use a muxer than can use all streams and than can accept the * preset (which may be present or not) */ if (cansinkstreams && (muxer = - _create_element_and_set_preset (muxerfact, preset, "muxer", - preset_name))) + _create_element_and_set_preset (muxerfact, ebin->profile, "muxer"))) break; } @@ -2259,8 +2326,11 @@ stream_group_free (GstEncodeBaseBin * ebin, StreamGroup * sgroup) if (sgroup->inqueue) gst_element_set_state (sgroup->inqueue, GST_STATE_NULL); - if (sgroup->encoder) + if (sgroup->encoder) { gst_element_set_state (sgroup->encoder, GST_STATE_NULL); + g_signal_handlers_disconnect_by_func (sgroup->profile, + set_element_properties_from_encoding_profile, sgroup->encoder); + } if (sgroup->fakesink) gst_element_set_state (sgroup->fakesink, GST_STATE_NULL); if (sgroup->outfilter) { @@ -2372,6 +2442,8 @@ gst_encode_base_bin_tear_down_profile (GstEncodeBaseBin * ebin) /* Remove muxer if present */ if (ebin->muxer) { + g_signal_handlers_disconnect_by_func (ebin->profile, + set_element_properties_from_encoding_profile, ebin->muxer); gst_element_set_state (ebin->muxer, GST_STATE_NULL); gst_bin_remove (GST_BIN (ebin), ebin->muxer); ebin->muxer = NULL; diff --git a/tests/validate/encodebin/set-encoder-properties.validatetest b/tests/validate/encodebin/set-encoder-properties.validatetest new file mode 100644 index 0000000000..b6dfbac500 --- /dev/null +++ b/tests/validate/encodebin/set-encoder-properties.validatetest @@ -0,0 +1,20 @@ +meta, + seek=false, + handles-states=true, + args = { + "audiotestsrc num-buffers=4 ! encodebin name=ebin profile=\"vorbisenc|element-properties,managed=true,name=audioencoder\" ! fakesink", + } + +pause +check-properties, audioencoder::managed=true + +set-properties, ebin::profile::element-properties=[ + element-properties-map, map = { + [vorbisenc, managed=false], + [somethingelse, whatever=false], + }, +] + +check-properties, audioencoder::managed=false + +stop \ No newline at end of file diff --git a/tests/validate/meson.build b/tests/validate/meson.build index 882a560663..d1ecbaaf99 100644 --- a/tests/validate/meson.build +++ b/tests/validate/meson.build @@ -19,6 +19,7 @@ tests = [ 'videorate/rate_2_0_with_decoder', 'compositor/renogotiate_failing_unsupported_src_format', 'giosrc/read-growing-file', + 'encodebin/set-encoder-properties', ] env = environment()