encodebin: Add APIs to set element properties on encoding profiles
authorThibault Saunier <tsaunier@igalia.com>
Fri, 20 Nov 2020 21:35:49 +0000 (18:35 -0300)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Wed, 10 Feb 2021 15:56:26 +0000 (15:56 +0000)
User often want to set encoder properties on encoding profiles,
this introduces a way to easily 'preset' properties when defining the
profile. This uses GstStructure to define those properties the same
way it is done in `splitmux` for example as it makes simple to handle.

This also defines a more complex structure type where we can map a set
of properties to set depending on the muxer/encoder factory that has
been picked by EncodeBin so it is quite flexible.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/1002>

gst-libs/gst/pbutils/encoding-profile.c
gst-libs/gst/pbutils/encoding-profile.h
gst/encoding/gstencodebasebin.c
tests/validate/encodebin/set-encoder-properties.validatetest [new file with mode: 0644]
tests/validate/meson.build

index 9c9c47c..65d6277 100644 (file)
@@ -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
  *
  * ### 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
 #include <string.h>
 
 /* 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_object_class_install_property (gobject_class,
-      PROP_RESTRICTION_CAPS, _properties[PROP_RESTRICTION_CAPS]);
-
+      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * 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;
 }
 
index bb781a3..ad97d01 100644 (file)
@@ -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)
 
index 0a68627..03c28b7 100644 (file)
@@ -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 (file)
index 0000000..b6dfbac
--- /dev/null
@@ -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
index 882a560..d1ecbaa 100644 (file)
@@ -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()