element: add API to get property change notifications via messages

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
This commit is contained in:
Tim-Philipp Müller 2016-03-05 14:12:36 +00:00
parent c78ff47a87
commit 6e3fb7af52
9 changed files with 364 additions and 2 deletions

View file

@ -912,6 +912,11 @@ gst_element_send_event
gst_element_seek_simple gst_element_seek_simple
gst_element_seek 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> <SUBSECTION Standard>
GST_ELEMENT GST_ELEMENT
GST_IS_ELEMENT GST_IS_ELEMENT
@ -1637,6 +1642,9 @@ gst_message_new_device_added
gst_message_new_device_removed gst_message_new_device_removed
gst_message_parse_device_added gst_message_parse_device_added
gst_message_parse_device_removed gst_message_parse_device_removed
gst_message_new_property_notify
gst_message_parse_property_notify
<SUBSECTION Standard> <SUBSECTION Standard>
GstMessageClass GstMessageClass
GST_MESSAGE GST_MESSAGE

View file

@ -3245,3 +3245,122 @@ gst_element_get_context (GstElement * element, const gchar * context_type)
return ret; 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);
}

View file

@ -819,6 +819,18 @@ void gst_element_lost_state (GstElement * element);
/* factory management */ /* factory management */
GstElementFactory* gst_element_get_factory (GstElement *element); 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 #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstElement, gst_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstElement, gst_object_unref)
#endif #endif

View file

@ -105,6 +105,7 @@ static GstMessageQuarks message_quarks[] = {
{GST_MESSAGE_HAVE_CONTEXT, "have-context", 0}, {GST_MESSAGE_HAVE_CONTEXT, "have-context", 0},
{GST_MESSAGE_DEVICE_ADDED, "device-added", 0}, {GST_MESSAGE_DEVICE_ADDED, "device-added", 0},
{GST_MESSAGE_DEVICE_REMOVED, "device-removed", 0}, {GST_MESSAGE_DEVICE_REMOVED, "device-removed", 0},
{GST_MESSAGE_PROPERTY_NOTIFY, "property-notify", 0},
{0, NULL, 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_structure_id_get (GST_MESSAGE_STRUCTURE (message),
GST_QUARK (DEVICE), GST_TYPE_DEVICE, device, NULL); 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));
}

View file

@ -108,6 +108,8 @@ typedef struct _GstMessage GstMessage;
* a #GstDeviceProvider (Since 1.4) * a #GstDeviceProvider (Since 1.4)
* @GST_MESSAGE_DEVICE_REMOVED: Message indicating a #GstDevice was removed * @GST_MESSAGE_DEVICE_REMOVED: Message indicating a #GstDevice was removed
* from a #GstDeviceProvider (Since 1.4) * 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. * @GST_MESSAGE_ANY: mask for all of the above messages.
* *
* The different message types that are available. * The different message types that are available.
@ -156,6 +158,7 @@ typedef enum
GST_MESSAGE_EXTENDED = (1 << 31), GST_MESSAGE_EXTENDED = (1 << 31),
GST_MESSAGE_DEVICE_ADDED = GST_MESSAGE_EXTENDED + 1, GST_MESSAGE_DEVICE_ADDED = GST_MESSAGE_EXTENDED + 1,
GST_MESSAGE_DEVICE_REMOVED = GST_MESSAGE_EXTENDED + 2, GST_MESSAGE_DEVICE_REMOVED = GST_MESSAGE_EXTENDED + 2,
GST_MESSAGE_PROPERTY_NOTIFY = GST_MESSAGE_EXTENDED + 3,
GST_MESSAGE_ANY = (gint) (0xffffffff) GST_MESSAGE_ANY = (gint) (0xffffffff)
} GstMessageType; } 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; GstMessage * gst_message_new_device_removed (GstObject * src, GstDevice * device) G_GNUC_MALLOC;
void gst_message_parse_device_removed (GstMessage * message, GstDevice ** device); 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 #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstMessage, gst_message_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstMessage, gst_message_unref)

View file

@ -70,7 +70,8 @@ static const gchar *_quark_strings[] = {
"GstMessageNeedContext", "GstMessageHaveContext", "context", "context-type", "GstMessageNeedContext", "GstMessageHaveContext", "context", "context-type",
"GstMessageStreamStart", "group-id", "uri-redirection", "GstMessageStreamStart", "group-id", "uri-redirection",
"GstMessageDeviceAdded", "GstMessageDeviceRemoved", "device", "GstMessageDeviceAdded", "GstMessageDeviceRemoved", "device",
"uri-redirection-permanent" "uri-redirection-permanent", "GstMessagePropertyNotify", "property-name",
"property-value"
}; };
GQuark _priv_gst_quark_table[GST_QUARK_MAX]; GQuark _priv_gst_quark_table[GST_QUARK_MAX];

View file

@ -202,7 +202,10 @@ typedef enum _GstQuarkId
GST_QUARK_MESSAGE_DEVICE_REMOVED = 171, GST_QUARK_MESSAGE_DEVICE_REMOVED = 171,
GST_QUARK_DEVICE = 172, GST_QUARK_DEVICE = 172,
GST_QUARK_URI_REDIRECTION_PERMANENT = 173, 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; } GstQuarkId;
extern GQuark _priv_gst_quark_table[GST_QUARK_MAX]; extern GQuark _priv_gst_quark_table[GST_QUARK_MAX];

View file

@ -347,6 +347,141 @@ GST_START_TEST (test_pad_templates)
GST_END_TEST; 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 * static Suite *
gst_element_suite (void) 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);
tcase_add_test (tc_chain, test_link_no_pads); tcase_add_test (tc_chain, test_link_no_pads);
tcase_add_test (tc_chain, test_pad_templates); tcase_add_test (tc_chain, test_pad_templates);
tcase_add_test (tc_chain, test_property_notify_message);
return s; return s;
} }

View file

@ -476,6 +476,8 @@ EXPORTS
gst_double_range_get_type gst_double_range_get_type
gst_element_abort_state gst_element_abort_state
gst_element_add_pad gst_element_add_pad
gst_element_add_property_deep_notify_watch
gst_element_add_property_notify_watch
gst_element_change_state gst_element_change_state
gst_element_class_add_metadata gst_element_class_add_metadata
gst_element_class_add_pad_template gst_element_class_add_pad_template
@ -545,6 +547,7 @@ EXPORTS
gst_element_register gst_element_register
gst_element_release_request_pad gst_element_release_request_pad
gst_element_remove_pad gst_element_remove_pad
gst_element_remove_property_notify_watch
gst_element_request_pad gst_element_request_pad
gst_element_seek gst_element_seek
gst_element_seek_simple gst_element_seek_simple
@ -712,6 +715,7 @@ EXPORTS
gst_message_new_need_context gst_message_new_need_context
gst_message_new_new_clock gst_message_new_new_clock
gst_message_new_progress gst_message_new_progress
gst_message_new_property_notify
gst_message_new_qos gst_message_new_qos
gst_message_new_request_state gst_message_new_request_state
gst_message_new_reset_time gst_message_new_reset_time
@ -741,6 +745,7 @@ EXPORTS
gst_message_parse_info gst_message_parse_info
gst_message_parse_new_clock gst_message_parse_new_clock
gst_message_parse_progress gst_message_parse_progress
gst_message_parse_property_notify
gst_message_parse_qos gst_message_parse_qos
gst_message_parse_qos_stats gst_message_parse_qos_stats
gst_message_parse_qos_values gst_message_parse_qos_values