element: add API to get property change notifications via messages
authorTim-Philipp Müller <tim@centricular.com>
Sat, 5 Mar 2016 14:12:36 +0000 (14:12 +0000)
committerTim-Philipp Müller <tim@centricular.com>
Fri, 8 Apr 2016 12:27:59 +0000 (13:27 +0100)
Be notified in the application thread via bus messages about
notify::* and deep-notify::* property changes, instead of
having to deal with it in a non-application thread.

API: gst_element_add_property_notify_watch()
API: gst_element_add_property_deep_notify_watch()
API: gst_element_remove_property_notify_watch()
API: gst_message_new_property_notify()
API: gst_message_parse_property_notify()
API: GST_MESSAGE_PROPERTY_NOTIFY

https://bugzilla.gnome.org/show_bug.cgi?id=763142

docs/gst/gstreamer-sections.txt
gst/gstelement.c
gst/gstelement.h
gst/gstmessage.c
gst/gstmessage.h
gst/gstquark.c
gst/gstquark.h
tests/check/gst/gstelement.c
win32/common/libgstreamer.def

index d5451e1..24d6aca 100644 (file)
@@ -912,6 +912,11 @@ gst_element_send_event
 gst_element_seek_simple
 gst_element_seek
 
+<SUBSECTION element-property-notifications>
+gst_element_add_property_notify_watch
+gst_element_add_property_deep_notify_watch
+gst_element_remove_property_notify_watch
+
 <SUBSECTION Standard>
 GST_ELEMENT
 GST_IS_ELEMENT
@@ -1637,6 +1642,9 @@ gst_message_new_device_added
 gst_message_new_device_removed
 gst_message_parse_device_added
 gst_message_parse_device_removed
+
+gst_message_new_property_notify
+gst_message_parse_property_notify
 <SUBSECTION Standard>
 GstMessageClass
 GST_MESSAGE
index c97ad1b..75ca7b2 100644 (file)
@@ -3245,3 +3245,122 @@ gst_element_get_context (GstElement * element, const gchar * context_type)
 
   return ret;
 }
+
+static void
+gst_element_property_post_notify_msg (GstElement * element, GObject * obj,
+    GParamSpec * pspec, gboolean include_value)
+{
+  GValue val = G_VALUE_INIT;
+  GValue *v;
+
+  GST_LOG_OBJECT (element, "property '%s' of object %" GST_PTR_FORMAT " has "
+      "changed, posting message with%s value", pspec->name, obj,
+      include_value ? "" : "out");
+
+  if (include_value && (pspec->flags & G_PARAM_READABLE) != 0) {
+    g_value_init (&val, pspec->value_type);
+    g_object_get_property (obj, pspec->name, &val);
+    v = &val;
+  } else {
+    v = NULL;
+  }
+  gst_element_post_message (element,
+      gst_message_new_property_notify (GST_OBJECT_CAST (obj), pspec->name, v));
+}
+
+static void
+gst_element_property_deep_notify_cb (GstElement * element, GObject * prop_obj,
+    GParamSpec * pspec, gpointer user_data)
+{
+  gboolean include_value = GPOINTER_TO_INT (user_data);
+
+  gst_element_property_post_notify_msg (element, prop_obj, pspec,
+      include_value);
+}
+
+static void
+gst_element_property_notify_cb (GObject * obj, GParamSpec * pspec,
+    gpointer user_data)
+{
+  gboolean include_value = GPOINTER_TO_INT (user_data);
+
+  gst_element_property_post_notify_msg (GST_ELEMENT_CAST (obj), obj, pspec,
+      include_value);
+}
+
+/**
+ * gst_element_add_property_notify_watch:
+ * @element: a #GstElement to watch for property changes
+ * @property_name: (allow-none): name of property to watch for changes, or
+ *     NULL to watch all properties
+ * @include_value: whether to include the new property value in the message
+ *
+ * Returns: a watch id, which can be used in connection with
+ *     gst_element_remove_property_notify_watch() to remove the watch again.
+ *
+ * Since: 1.10
+ */
+gulong
+gst_element_add_property_notify_watch (GstElement * element,
+    const gchar * property_name, gboolean include_value)
+{
+  const gchar *sep;
+  gchar *signal_name;
+  gulong id;
+
+  g_return_val_if_fail (GST_IS_ELEMENT (element), 0);
+
+  sep = (property_name != NULL) ? "::" : NULL;
+  signal_name = g_strconcat ("notify", sep, property_name, NULL);
+  id = g_signal_connect (element, signal_name,
+      G_CALLBACK (gst_element_property_notify_cb),
+      GINT_TO_POINTER (include_value));
+  g_free (signal_name);
+
+  return id;
+}
+
+/**
+ * gst_element_add_property_deep_notify_watch:
+ * @element: a #GstElement to watch (recursively) for property changes
+ * @property_name: (allow-none): name of property to watch for changes, or
+ *     NULL to watch all properties
+ * @include_value: whether to include the new property value in the message
+ *
+ * Returns: a watch id, which can be used in connection with
+ *     gst_element_remove_property_notify_watch() to remove the watch again.
+ *
+ * Since: 1.10
+ */
+gulong
+gst_element_add_property_deep_notify_watch (GstElement * element,
+    const gchar * property_name, gboolean include_value)
+{
+  const gchar *sep;
+  gchar *signal_name;
+  gulong id;
+
+  g_return_val_if_fail (GST_IS_ELEMENT (element), 0);
+
+  sep = (property_name != NULL) ? "::" : NULL;
+  signal_name = g_strconcat ("deep-notify", sep, property_name, NULL);
+  id = g_signal_connect (element, signal_name,
+      G_CALLBACK (gst_element_property_deep_notify_cb),
+      GINT_TO_POINTER (include_value));
+  g_free (signal_name);
+
+  return id;
+}
+
+/**
+ * gst_element_remove_property_notify_watch:
+ * @element: a #GstElement being watched for property changes
+ * @watch_id: watch id to remove
+ *
+ * Since: 1.10
+ */
+void
+gst_element_remove_property_notify_watch (GstElement * element, gulong watch_id)
+{
+  g_signal_handler_disconnect (element, watch_id);
+}
index f4bed31..39aa8cf 100644 (file)
@@ -819,6 +819,18 @@ void                    gst_element_lost_state          (GstElement * element);
 /* factory management */
 GstElementFactory*      gst_element_get_factory         (GstElement *element);
 
+/* utility functions */
+gulong                  gst_element_add_property_notify_watch (GstElement  * element,
+                                                               const gchar * property_name,
+                                                               gboolean      include_value);
+
+gulong                  gst_element_add_property_deep_notify_watch (GstElement  * element,
+                                                                    const gchar * property_name,
+                                                                    gboolean      include_value);
+
+void                    gst_element_remove_property_notify_watch (GstElement * element,
+                                                                  gulong       watch_id);
+
 #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstElement, gst_object_unref)
 #endif
index 299ecfb..673df03 100644 (file)
@@ -105,6 +105,7 @@ static GstMessageQuarks message_quarks[] = {
   {GST_MESSAGE_HAVE_CONTEXT, "have-context", 0},
   {GST_MESSAGE_DEVICE_ADDED, "device-added", 0},
   {GST_MESSAGE_DEVICE_REMOVED, "device-removed", 0},
+  {GST_MESSAGE_PROPERTY_NOTIFY, "property-notify", 0},
   {0, NULL, 0}
 };
 
@@ -2450,3 +2451,74 @@ gst_message_parse_device_removed (GstMessage * message, GstDevice ** device)
     gst_structure_id_get (GST_MESSAGE_STRUCTURE (message),
         GST_QUARK (DEVICE), GST_TYPE_DEVICE, device, NULL);
 }
+
+/**
+ * gst_message_new_property_notify:
+ * @src: The #GstObject whose property changed (may or may not be a #GstElement)
+ * @property_name: name of the property that changed
+ * @val: (allow-none) (transfer full): new property value, or %NULL
+ *
+ * Returns: a newly allocated #GstMessage
+ *
+ * Since: 1.10
+ */
+GstMessage *
+gst_message_new_property_notify (GstObject * src, const gchar * property_name,
+    GValue * val)
+{
+  GstStructure *structure;
+  GValue name_val = G_VALUE_INIT;
+
+  g_return_val_if_fail (property_name != NULL, NULL);
+
+  structure = gst_structure_new_id_empty (GST_QUARK (MESSAGE_PROPERTY_NOTIFY));
+  g_value_init (&name_val, G_TYPE_STRING);
+  /* should already be interned, but let's make sure */
+  g_value_set_static_string (&name_val, g_intern_string (property_name));
+  gst_structure_id_take_value (structure, GST_QUARK (PROPERTY_NAME), &name_val);
+  if (val != NULL)
+    gst_structure_id_take_value (structure, GST_QUARK (PROPERTY_VALUE), val);
+
+  return gst_message_new_custom (GST_MESSAGE_PROPERTY_NOTIFY, src, structure);
+}
+
+/**
+ * gst_message_parse_property_notify:
+ * @message: a #GstMessage of type %GST_MESSAGE_PROPERTY_NOTIFY
+ * @object: (out) (allow-none) (transfer none): location where to store a
+ *     pointer to the object whose property got changed, or %NULL
+ * @property_name: (out) (allow-none): return location for the name of the
+ *     property that got changed, or %NULL
+ * @property_value: (out) (allow-none): return location for the new value of
+ *     the property that got changed, or %NULL. This will only be set if the
+ *     property notify watch was told to include the value when it was set up
+ *
+ * Parses a property-notify message. These will be posted on the bus only
+ * when set up with gst_element_add_property_notify_watch() or
+ * gst_element_add_property_deep_notify_watch().
+ *
+ * Since: 1.10
+ */
+void
+gst_message_parse_property_notify (GstMessage * message, GstObject ** object,
+    const gchar ** property_name, const GValue ** property_value)
+{
+  const GstStructure *s = GST_MESSAGE_STRUCTURE (message);
+
+  g_return_if_fail (GST_IS_MESSAGE (message));
+  g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_PROPERTY_NOTIFY);
+
+  if (object)
+    *object = GST_MESSAGE_SRC (message);
+
+  if (property_name) {
+    const GValue *name_value;
+
+    name_value = gst_structure_id_get_value (s, GST_QUARK (PROPERTY_NAME));
+    *property_name = g_value_get_string (name_value);
+  }
+
+  if (property_value)
+    *property_value =
+        gst_structure_id_get_value (s, GST_QUARK (PROPERTY_VALUE));
+}
index 1ea4085..68449d0 100644 (file)
@@ -108,6 +108,8 @@ typedef struct _GstMessage GstMessage;
  *     a #GstDeviceProvider (Since 1.4)
  * @GST_MESSAGE_DEVICE_REMOVED: Message indicating a #GstDevice was removed
  *     from a #GstDeviceProvider (Since 1.4)
+ * @GST_MESSAGE_PROPERTY_NOTIFY: Message indicating a #GObject property has
+ *     changed (Since 1.10)
  * @GST_MESSAGE_ANY: mask for all of the above messages.
  *
  * The different message types that are available.
@@ -156,6 +158,7 @@ typedef enum
   GST_MESSAGE_EXTENDED          = (1 << 31),
   GST_MESSAGE_DEVICE_ADDED      = GST_MESSAGE_EXTENDED + 1,
   GST_MESSAGE_DEVICE_REMOVED    = GST_MESSAGE_EXTENDED + 2,
+  GST_MESSAGE_PROPERTY_NOTIFY   = GST_MESSAGE_EXTENDED + 3,
   GST_MESSAGE_ANY               = (gint) (0xffffffff)
 } GstMessageType;
 
@@ -592,6 +595,9 @@ void            gst_message_parse_device_added  (GstMessage * message, GstDevice
 GstMessage *    gst_message_new_device_removed    (GstObject * src, GstDevice * device) G_GNUC_MALLOC;
 void            gst_message_parse_device_removed  (GstMessage * message, GstDevice ** device);
 
+/* PROPERTY_NOTIFY */
+GstMessage *    gst_message_new_property_notify   (GstObject * src, const gchar * property_name, GValue * val) G_GNUC_MALLOC;
+void            gst_message_parse_property_notify (GstMessage * message, GstObject ** object, const gchar ** property_name, const GValue ** property_value);
 
 #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstMessage, gst_message_unref)
index cf5c766..bfa7933 100644 (file)
@@ -70,7 +70,8 @@ static const gchar *_quark_strings[] = {
   "GstMessageNeedContext", "GstMessageHaveContext", "context", "context-type",
   "GstMessageStreamStart", "group-id", "uri-redirection",
   "GstMessageDeviceAdded", "GstMessageDeviceRemoved", "device",
-  "uri-redirection-permanent"
+  "uri-redirection-permanent", "GstMessagePropertyNotify", "property-name",
+  "property-value"
 };
 
 GQuark _priv_gst_quark_table[GST_QUARK_MAX];
index b8daeb0..5365a7e 100644 (file)
@@ -202,7 +202,10 @@ typedef enum _GstQuarkId
   GST_QUARK_MESSAGE_DEVICE_REMOVED = 171,
   GST_QUARK_DEVICE = 172,
   GST_QUARK_URI_REDIRECTION_PERMANENT = 173,
-  GST_QUARK_MAX = 174
+  GST_QUARK_MESSAGE_PROPERTY_NOTIFY = 174,
+  GST_QUARK_PROPERTY_NAME = 175,
+  GST_QUARK_PROPERTY_VALUE = 176,
+  GST_QUARK_MAX = 177
 } GstQuarkId;
 
 extern GQuark _priv_gst_quark_table[GST_QUARK_MAX];
index 8732611..e2505bb 100644 (file)
@@ -347,6 +347,141 @@ GST_START_TEST (test_pad_templates)
 
 GST_END_TEST;
 
+/* need to return the message here because object, property name and value
+ * are only valid as long as we keep the message alive */
+static GstMessage *
+bus_wait_for_notify_message (GstBus * bus, GstElement ** obj,
+    const gchar ** prop_name, const GValue ** val)
+{
+  GstMessage *msg;
+
+  do {
+    msg = gst_bus_timed_pop_filtered (bus, -1, GST_MESSAGE_ANY);
+    if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_PROPERTY_NOTIFY)
+      break;
+    gst_message_unref (msg);
+  } while (TRUE);
+
+  gst_message_parse_property_notify (msg, (GstObject **) obj, prop_name, val);
+  return msg;
+}
+
+GST_START_TEST (test_property_notify_message)
+{
+  GstElement *pipeline, *identity;
+  gulong watch_id0, watch_id1, watch_id2, deep_watch_id1, deep_watch_id2;
+  GstBus *bus;
+
+  pipeline = gst_pipeline_new (NULL);
+  identity = gst_element_factory_make ("identity", NULL);
+  gst_bin_add (GST_BIN (pipeline), identity);
+
+  bus = GST_ELEMENT_BUS (pipeline);
+
+  /* need to set state to READY, otherwise bus will be flushing and discard
+   * our messages */
+  gst_element_set_state (pipeline, GST_STATE_READY);
+
+  watch_id0 = gst_element_add_property_notify_watch (identity, NULL, FALSE);
+
+  watch_id1 = gst_element_add_property_notify_watch (identity, "sync", FALSE);
+
+  watch_id2 = gst_element_add_property_notify_watch (identity, "silent", TRUE);
+
+  deep_watch_id1 =
+      gst_element_add_property_deep_notify_watch (pipeline, NULL, TRUE);
+
+  deep_watch_id2 =
+      gst_element_add_property_deep_notify_watch (pipeline, "silent", FALSE);
+
+  /* Now test property changes and if we get the messages we expect. We rely
+   * on the signals being fired in the order that they were set up here. */
+  {
+    const GValue *val;
+    const gchar *name;
+    GstMessage *msg;
+    GstElement *obj;
+
+    /* A - This should be picked up by... */
+    g_object_set (identity, "dump", TRUE, NULL);
+    /* 1) the catch-all notify on the element (no value) */
+    msg = bus_wait_for_notify_message (bus, &obj, &name, &val);
+    fail_unless (obj == identity);
+    fail_unless_equals_string (name, "dump");
+    fail_unless (val == NULL);
+    gst_message_unref (msg);
+    /* 2) the catch-all deep-notify on the pipeline (with value) */
+    msg = bus_wait_for_notify_message (bus, &obj, &name, &val);
+    fail_unless_equals_string (name, "dump");
+    fail_unless (obj == identity);
+    fail_unless (G_VALUE_HOLDS_BOOLEAN (val));
+    fail_unless_equals_int (g_value_get_boolean (val), TRUE);
+    gst_message_unref (msg);
+
+    /* B - This should be picked up by... */
+    g_object_set (identity, "sync", TRUE, NULL);
+    /* 1) the catch-all notify on the element (no value) */
+    msg = bus_wait_for_notify_message (bus, &obj, &name, &val);
+    fail_unless (obj == identity);
+    fail_unless_equals_string (name, "sync");
+    fail_unless (val == NULL);
+    gst_message_unref (msg);
+    /* 2) the "sync" notify on the element (no value) */
+    msg = bus_wait_for_notify_message (bus, &obj, &name, &val);
+    fail_unless (obj == identity);
+    fail_unless_equals_string (name, "sync");
+    fail_unless (val == NULL);
+    gst_message_unref (msg);
+    /* 3) the catch-all deep-notify on the pipeline (with value) */
+    msg = bus_wait_for_notify_message (bus, &obj, &name, &val);
+    fail_unless_equals_string (name, "sync");
+    fail_unless (obj == identity);
+    fail_unless (G_VALUE_HOLDS_BOOLEAN (val));
+    fail_unless_equals_int (g_value_get_boolean (val), TRUE);
+    gst_message_unref (msg);
+
+    /* C - This should be picked up by... */
+    g_object_set (identity, "silent", FALSE, NULL);
+    /* 1) the catch-all notify on the element (no value) */
+    msg = bus_wait_for_notify_message (bus, &obj, &name, &val);
+    fail_unless (obj == identity);
+    fail_unless_equals_string (name, "silent");
+    fail_unless (val == NULL);
+    gst_message_unref (msg);
+    /* 2) the "silent" notify on the element (with value) */
+    msg = bus_wait_for_notify_message (bus, &obj, &name, &val);
+    fail_unless (obj == identity);
+    fail_unless_equals_string (name, "silent");
+    fail_unless (val != NULL);
+    fail_unless (G_VALUE_HOLDS_BOOLEAN (val));
+    fail_unless_equals_int (g_value_get_boolean (val), FALSE);
+    gst_message_unref (msg);
+    /* 3) the catch-all deep-notify on the pipeline (with value) */
+    msg = bus_wait_for_notify_message (bus, &obj, &name, &val);
+    fail_unless_equals_string (name, "silent");
+    fail_unless (obj == identity);
+    fail_unless (G_VALUE_HOLDS_BOOLEAN (val));
+    fail_unless_equals_int (g_value_get_boolean (val), FALSE);
+    gst_message_unref (msg);
+    /* 4) the "silent" deep-notify on the pipeline (without value) */
+    msg = bus_wait_for_notify_message (bus, &obj, &name, &val);
+    fail_unless_equals_string (name, "silent");
+    fail_unless (obj == identity);
+    fail_unless (val == NULL);
+    gst_message_unref (msg);
+  }
+
+  gst_element_remove_property_notify_watch (identity, watch_id0);
+  gst_element_remove_property_notify_watch (identity, watch_id1);
+  gst_element_remove_property_notify_watch (identity, watch_id2);
+  gst_element_remove_property_notify_watch (pipeline, deep_watch_id1);
+  gst_element_remove_property_notify_watch (pipeline, deep_watch_id2);
+  gst_element_set_state (pipeline, GST_STATE_NULL);
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
 static Suite *
 gst_element_suite (void)
 {
@@ -360,6 +495,7 @@ gst_element_suite (void)
   tcase_add_test (tc_chain, test_link);
   tcase_add_test (tc_chain, test_link_no_pads);
   tcase_add_test (tc_chain, test_pad_templates);
+  tcase_add_test (tc_chain, test_property_notify_message);
 
   return s;
 }
index c41c86a..0b54e8d 100644 (file)
@@ -476,6 +476,8 @@ EXPORTS
        gst_double_range_get_type
        gst_element_abort_state
        gst_element_add_pad
+       gst_element_add_property_deep_notify_watch
+       gst_element_add_property_notify_watch
        gst_element_change_state
        gst_element_class_add_metadata
        gst_element_class_add_pad_template
@@ -545,6 +547,7 @@ EXPORTS
        gst_element_register
        gst_element_release_request_pad
        gst_element_remove_pad
+       gst_element_remove_property_notify_watch
        gst_element_request_pad
        gst_element_seek
        gst_element_seek_simple
@@ -712,6 +715,7 @@ EXPORTS
        gst_message_new_need_context
        gst_message_new_new_clock
        gst_message_new_progress
+       gst_message_new_property_notify
        gst_message_new_qos
        gst_message_new_request_state
        gst_message_new_reset_time
@@ -741,6 +745,7 @@ EXPORTS
        gst_message_parse_info
        gst_message_parse_new_clock
        gst_message_parse_progress
+       gst_message_parse_property_notify
        gst_message_parse_qos
        gst_message_parse_qos_stats
        gst_message_parse_qos_values