Thursday, 19 February 2015

First blog post! Let's get straight into it.

While developing some cool new features for the GStreamer GL stack, I found the need to have GstBin contain the properties of a containing GstElement. Extra points if we can change the containing element and the GstBin properties react accordingly.

Option 1 - manual forwarding

The simplest and most boring solution, create subclasses with the necessary properties and forward to the relevant containing element. We should be able to do better than this.

Option 2 - GParamSpecOverride

The next option is to use g_param_spec_override which points the property to a property within another object. This comes with the advantage and pitfall that it's kinda supported by GLib directly but not thoroughly tested.

/* should only called from an object's class_init function */
void
install_properties_into_class_from_gtype (GObjectClass * self, GType type)
{
  GObjectClass *class;
  GParamSpec ** props;
  GParamSpec ** filtered_props;
  guint i, j, n;

  class = g_type_class_ref (type);
  if (!class)
    g_assert_not_reached ();

  props = g_object_class_list_properties (class, &n);
  filtered_props = g_new0 (GParamSpec *, n + 1);
  filtered_props[0] = NULL;

  for (i = 0, j = 1; i < n; i++) {
    /* don't forward element properties */
    if (!g_type_is_a (GST_TYPE_ELEMENT, props[i]->owner_type)) {
      filtered_props[j] = g_param_spec_override (name, props[i]);
      j++;
    }
  }
  g_object_class_install_properties (self, j, filtered_props);

  g_free (props);
  g_free (filtered_props);

  g_type_class_unref (class);
}

Then you also need to manually forward the set/get_property() to the appropriate object. Technically the documentation hints that the overridden property and the property to be overridden should be in the same class hierarchy, i.e. one be a subclass of the other. In practice, this seems to mostly works across different classes. You can provide an override for a property for a completely different object. However, I have noticed that g_object_class_list_properties seems to skip override properties altogether. Presumably because they are meant to be provided by another class lower down the hierarchy. This causes the properties to not show up in the gst-inspect output.

Option 3 - GBinding

Instead of overriding properties, bind them to each other. To do that we need equivalent properties on both classes. This is done by this hacky copy code for GParamSpec's

for (i = 0, j = 1; i < n; i++) {
  /* don't forward element properties */
  if (!g_type_is_a (GST_TYPE_ELEMENT, props[i]->owner_type)) {
    const gchar *name = g_param_spec_get_name (props[i]);
    const gchar *nick = g_param_spec_get_nick (props[i]);
    const gchar *blurb = g_param_spec_get_blurb (props[i]);
    GTypeQuery t_info;

    g_type_query (G_PARAM_SPEC_TYPE (props[i]), &t_info);

    printf ("prop %u size %u name %s nick %s blurb %s\n", i,
        t_info.instance_size, name, nick, blurb);

    /* the other option is to use g_param_spec_override() however the causes
     * the properties to not display in gst-inspect */
    filtered_props[j] = g_param_spec_internal (G_PARAM_SPEC_TYPE (props[i]),
        name, nick, blurb, props[i]->flags);
    j++;
  }
}
Then you need to call g_object_bind_property for each property after _class_init() (like _init()) like so.

GParamSpec **props;
guint i, n;

props = g_object_class_list_properties (G_OBJECT_CLASS (klass), &n);

for (i = 0; i < n; i++) {
  /* don't forward element properties */
  if (!g_type_is_a (GST_TYPE_ELEMENT, props[i]->owner_type)) {
    /* only bind properties that are in both objects */
    if (g_object_class_find_property (G_OBJECT_GET_CLASS (self->filter),
          props[i]->name)) {
      /* property binding only works for writable properties */
      if ((props[i]->flags & G_PARAM_WRITABLE) == G_PARAM_WRITABLE) {
        g_object_bind_property (self->filter, props[i]->name, self,
            props[i]->name, G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
      }
    }
  }
}

g_free (props);

As hinted by the code, this only seems to work for writable properties. Readable or construct-only properties will fail to bind correctly. Also, the ranges, defaults and some types don't seem to be copied correctly from the original property. If you try and set an invalid value however, it still warns appropriately from trying to set the value on the original property.

Option 4 - GstChildProxy

As the documentation says, GstChildProxy allows to set child properties from the containing element. The drawback is that it comes with a completely different API and any dependent code needs to be updated.

For this case, I think I'm going to start with GstChildProxy for the generic stuff and implement manual overrides for cases where we need actual properties on the containing elements like sinks with the last-sample or sync/async properties.

No comments:

Post a Comment