element: add gst_element_foreach_*pad()

Add convenience API that iterates over all pads, sink pads or
source pads and makes sure that the foreach function is called
exactly once for each pad.

This is a KISS implementation. It doesn't use GstIterator and
doesn't try to do clever things like resync if pads are added
or removed while the function is executing. We can still do that
in future if we think it's needed, but in practice it will
likely make absolutely no difference whatsoever, since these
things will have to be handled properly elsewhere by the element
anyway if they're important.

After all, it's always possible that a pad is added or removed
just after the iterator finishes iterating, but before the
function returns.

This is also a replacement for gst_aggregator_iterate_sink_pads().

https://bugzilla.gnome.org/show_bug.cgi?id=785679
This commit is contained in:
Tim-Philipp Müller 2017-08-01 11:06:32 +01:00
parent f911fe4314
commit d106390adc
5 changed files with 269 additions and 0 deletions

View file

@ -886,6 +886,9 @@ gst_element_remove_pad
gst_element_iterate_pads
gst_element_iterate_sink_pads
gst_element_iterate_src_pads
gst_element_foreach_pad
gst_element_foreach_sink_pad
gst_element_foreach_src_pad
<SUBSECTION element-linking>
gst_element_link

View file

@ -1247,6 +1247,121 @@ gst_element_iterate_sink_pads (GstElement * element)
return gst_element_iterate_pad_list (element, &element->sinkpads);
}
static gboolean
gst_element_do_foreach_pad (GstElement * element,
GstElementForeachPadFunc func, gpointer user_data,
GList ** p_pads, guint16 * p_npads)
{
gboolean ret = TRUE;
GstPad **pads;
guint n_pads, i;
GList *l;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (func != NULL, FALSE);
GST_OBJECT_LOCK (element);
n_pads = *p_npads;
pads = g_newa (GstPad *, n_pads + 1);
for (l = *p_pads, i = 0; l != NULL; l = l->next) {
g_assert (i < n_pads);
pads[i++] = gst_object_ref (l->data);
}
GST_OBJECT_UNLOCK (element);
if (n_pads == 0)
return FALSE;
for (i = 0; i < n_pads; ++i) {
ret = func (element, pads[i], user_data);
if (!ret)
break;
}
for (i = 0; i < n_pads; ++i)
gst_object_unref (pads[i]);
return ret;
}
/**
* gst_element_foreach_sink_pad:
* @element: a #GstElement to iterate sink pads of
* @func: (scope call): function to call for each sink pad
* @user_data: (closure): user data passed to @func
*
* Call @func with @user_data for each of @element's sink pads. @func will be
* called exactly once for each sink pad that exists at the time of this call,
* unless one of the calls to @func returns %FALSE in which case we will stop
* iterating pads and return early. If new sink pads are added or sink pads
* are removed while the sink pads are being iterated, this will not be taken
* into account until next time this function is used.
*
* Returns: %FALSE if @element had no sink pads or if one of the calls to @func
* returned %FALSE.
*
* Since: 1.14
*/
gboolean
gst_element_foreach_sink_pad (GstElement * element,
GstElementForeachPadFunc func, gpointer user_data)
{
return gst_element_do_foreach_pad (element, func, user_data,
&element->sinkpads, &element->numsinkpads);
}
/**
* gst_element_foreach_src_pad:
* @element: a #GstElement to iterate source pads of
* @func: (scope call): function to call for each source pad
* @user_data: (closure): user data passed to @func
*
* Call @func with @user_data for each of @element's source pads. @func will be
* called exactly once for each source pad that exists at the time of this call,
* unless one of the calls to @func returns %FALSE in which case we will stop
* iterating pads and return early. If new source pads are added or source pads
* are removed while the source pads are being iterated, this will not be taken
* into account until next time this function is used.
*
* Returns: %FALSE if @element had no source pads or if one of the calls
* to @func returned %FALSE.
*
* Since: 1.14
*/
gboolean
gst_element_foreach_src_pad (GstElement * element,
GstElementForeachPadFunc func, gpointer user_data)
{
return gst_element_do_foreach_pad (element, func, user_data,
&element->srcpads, &element->numsrcpads);
}
/**
* gst_element_foreach_pad:
* @element: a #GstElement to iterate pads of
* @func: (scope call): function to call for each pad
* @user_data: (closure): user data passed to @func
*
* Call @func with @user_data for each of @element's pads. @func will be called
* exactly once for each pad that exists at the time of this call, unless
* one of the calls to @func returns %FALSE in which case we will stop
* iterating pads and return early. If new pads are added or pads are removed
* while pads are being iterated, this will not be taken into account until
* next time this function is used.
*
* Returns: %FALSE if @element had no pads or if one of the calls to @func
* returned %FALSE.
*
* Since: 1.14
*/
gboolean
gst_element_foreach_pad (GstElement * element, GstElementForeachPadFunc func,
gpointer user_data)
{
return gst_element_do_foreach_pad (element, func, user_data,
&element->pads, &element->numpads);
}
/**
* gst_element_class_add_pad_template:
* @klass: the #GstElementClass to add the pad template to.

View file

@ -909,6 +909,35 @@ GstIterator * gst_element_iterate_src_pads (GstElement * element);
GST_EXPORT
GstIterator * gst_element_iterate_sink_pads (GstElement * element);
/**
* GstElementForeachPadFunc:
* @element: the #GstElement
* @pad: a #GstPad
* @user_data: user data passed to the foreach function
*
* Function called for each pad when using gst_element_foreach_sink_pad(),
* gst_element_foreach_src_pad(), or gst_element_foreach_pad().
*
* Returns: %FALSE to stop iterating pads, %TRUE to continue
*
* Since: 1.14
*/
typedef gboolean (*GstElementForeachPadFunc) (GstElement * element,
GstPad * pad,
gpointer user_data);
GST_EXPORT
gboolean gst_element_foreach_sink_pad (GstElement * element,
GstElementForeachPadFunc func,
gpointer user_data);
GST_EXPORT
gboolean gst_element_foreach_src_pad (GstElement * element,
GstElementForeachPadFunc func,
gpointer user_data);
GST_EXPORT
gboolean gst_element_foreach_pad (GstElement * element,
GstElementForeachPadFunc func,
gpointer user_data);
/* event/query/format stuff */
GST_EXPORT

View file

@ -789,6 +789,124 @@ GST_START_TEST (test_request_pad_templates)
GST_END_TEST;
static gboolean run_foreach_thread;
/* thread function that just adds/removes pads while main thread iterates pads */
static gpointer
thread_add_remove_pads (GstElement * e)
{
GPtrArray *pads;
guint n, c = 0;
pads = g_ptr_array_new ();
THREAD_START ();
while (g_atomic_int_get (&run_foreach_thread)) {
GstPad *p;
gchar name[16];
/* add a new pad */
g_snprintf (name, 16, "pad_%u", c++);
p = gst_pad_new (name, g_random_boolean ()? GST_PAD_SRC : GST_PAD_SINK);
g_ptr_array_add (pads, p);
gst_element_add_pad (e, p);
THREAD_SWITCH ();
/* and remove a random pad */
if (g_random_boolean () || pads->len > 100) {
n = g_random_int_range (0, pads->len);
p = g_ptr_array_remove_index (pads, n);
gst_element_remove_pad (e, p);
}
THREAD_SWITCH ();
}
g_ptr_array_free (pads, TRUE);
return NULL;
}
typedef struct
{
GQuark q;
GstPadDirection dir; /* GST_PAD_UNKNOWN = both are allowed */
gboolean func_called;
} PadChecks;
static gboolean
pad_foreach_func (GstElement * e, GstPad * pad, gpointer user_data)
{
PadChecks *checks = user_data;
/* check we haven't visited this pad already */
fail_if (g_object_get_qdata (G_OBJECT (pad), checks->q) != NULL);
g_object_set_qdata (G_OBJECT (pad), checks->q, GINT_TO_POINTER (1));
if (checks->dir != GST_PAD_UNKNOWN) {
fail_unless_equals_int (checks->dir, GST_PAD_DIRECTION (pad));
}
checks->func_called = TRUE;
return TRUE;
}
GST_START_TEST (test_foreach_pad)
{
PadChecks checks = { 0, GST_PAD_UNKNOWN, FALSE };
GstElement *e;
gint i;
e = gst_bin_new ("testbin");
/* function should not be called if there are no pads! */
gst_element_foreach_pad (e, pad_foreach_func, &checks);
fail_if (checks.func_called);
g_atomic_int_set (&run_foreach_thread, TRUE);
MAIN_INIT ();
MAIN_START_THREAD_FUNCTION (0, thread_add_remove_pads, e);
MAIN_SYNCHRONIZE ();
for (i = 0; i < 10000; ++i) {
gchar num[32];
g_snprintf (num, 32, "foreach-test-%u", i);
checks.q = g_quark_from_string (num);
checks.func_called = FALSE;
if (g_random_boolean ()) {
checks.dir = GST_PAD_UNKNOWN;
gst_element_foreach_pad (e, pad_foreach_func, &checks);
} else if (g_random_boolean ()) {
checks.dir = GST_PAD_SRC;
gst_element_foreach_src_pad (e, pad_foreach_func, &checks);
} else {
checks.dir = GST_PAD_SINK;
gst_element_foreach_sink_pad (e, pad_foreach_func, &checks);
}
THREAD_SWITCH ();
}
g_atomic_int_set (&run_foreach_thread, FALSE);
MAIN_STOP_THREADS ();
/* function should be called if there are pads */
checks.q = g_quark_from_string ("fini");
checks.dir = GST_PAD_UNKNOWN;
checks.func_called = FALSE;
gst_element_foreach_pad (e, pad_foreach_func, &checks);
fail_if (e->numpads > 0 && !checks.func_called);
gst_object_unref (e);
}
GST_END_TEST;
static Suite *
gst_element_suite (void)
{
@ -804,6 +922,7 @@ gst_element_suite (void)
tcase_add_test (tc_chain, test_pad_templates);
tcase_add_test (tc_chain, test_property_notify_message);
tcase_add_test (tc_chain, test_request_pad_templates);
tcase_add_test (tc_chain, test_foreach_pad);
return s;
}

View file

@ -531,6 +531,9 @@ EXPORTS
gst_element_factory_list_is_type
gst_element_factory_make
gst_element_flags_get_type
gst_element_foreach_pad
gst_element_foreach_sink_pad
gst_element_foreach_src_pad
gst_element_get_base_time
gst_element_get_bus
gst_element_get_clock