diff --git a/docs/gst/gstreamer-sections.txt b/docs/gst/gstreamer-sections.txt index c30e6c9d15..e86c0f3c02 100644 --- a/docs/gst/gstreamer-sections.txt +++ b/docs/gst/gstreamer-sections.txt @@ -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 gst_element_link diff --git a/gst/gstelement.c b/gst/gstelement.c index 4026009257..80ecf4447c 100644 --- a/gst/gstelement.c +++ b/gst/gstelement.c @@ -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. diff --git a/gst/gstelement.h b/gst/gstelement.h index 781a21b23f..fab64a040c 100644 --- a/gst/gstelement.h +++ b/gst/gstelement.h @@ -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 diff --git a/tests/check/gst/gstelement.c b/tests/check/gst/gstelement.c index d22471f207..fa63ee6da1 100644 --- a/tests/check/gst/gstelement.c +++ b/tests/check/gst/gstelement.c @@ -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; } diff --git a/win32/common/libgstreamer.def b/win32/common/libgstreamer.def index 40ae649116..8fedefa224 100644 --- a/win32/common/libgstreamer.def +++ b/win32/common/libgstreamer.def @@ -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