diff --git a/docs/plugins/gst-plugins-bad-plugins-docs.sgml b/docs/plugins/gst-plugins-bad-plugins-docs.sgml
index eb42b958d2..9ac8e67076 100644
--- a/docs/plugins/gst-plugins-bad-plugins-docs.sgml
+++ b/docs/plugins/gst-plugins-bad-plugins-docs.sgml
@@ -240,6 +240,7 @@
+
diff --git a/docs/plugins/gst-plugins-bad-plugins-sections.txt b/docs/plugins/gst-plugins-bad-plugins-sections.txt
index a640450a1a..88039634fb 100644
--- a/docs/plugins/gst-plugins-bad-plugins-sections.txt
+++ b/docs/plugins/gst-plugins-bad-plugins-sections.txt
@@ -3602,6 +3602,19 @@ GST_TYPE_TIME_CODE_STAMPER
gst_time_code_stamper_get_type
+
+element-testsrcbin
+testsrcbin
+GstTestSrcBin
+
+GstTestSrcBinClass
+GST_TEST_SRC_BIN
+GST_IS_TEST_SRC_BIN
+GST_TEST_SRC_BIN_CLASS
+
+gst_test_src_bin_get_type
+
+
element-tonegeneratesrc
tonegeneratesrc
diff --git a/gst/debugutils/Makefile.am b/gst/debugutils/Makefile.am
index 1113c5e559..66f782bcf3 100644
--- a/gst/debugutils/Makefile.am
+++ b/gst/debugutils/Makefile.am
@@ -9,7 +9,8 @@ libgstdebugutilsbad_la_SOURCES = \
gstcompare.c \
gstwatchdog.c \
gsterrorignore.c \
- gstfakevideosink.c
+ gstfakevideosink.c \
+ gsttestsrcbin.c
libgstdebugutilsbad_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS)
libgstdebugutilsbad_la_LIBADD = $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) \
diff --git a/gst/debugutils/debugutilsbad.c b/gst/debugutils/debugutilsbad.c
index cc7af306df..b4a9b4c630 100644
--- a/gst/debugutils/debugutilsbad.c
+++ b/gst/debugutils/debugutilsbad.c
@@ -31,6 +31,7 @@ GType gst_debug_spy_get_type (void);
GType gst_error_ignore_get_type (void);
GType gst_watchdog_get_type (void);
GType gst_fake_video_sink_get_type (void);
+GType gst_test_src_bin_get_type (void);
static gboolean
plugin_init (GstPlugin * plugin)
@@ -51,6 +52,8 @@ plugin_init (GstPlugin * plugin)
gst_error_ignore_get_type ());
gst_element_register (plugin, "fakevideosink", GST_RANK_NONE,
gst_fake_video_sink_get_type ());
+ gst_element_register (plugin, "testsrcbin", GST_RANK_NONE,
+ gst_test_src_bin_get_type ());
return TRUE;
}
diff --git a/gst/debugutils/gsttestsrcbin.c b/gst/debugutils/gsttestsrcbin.c
new file mode 100644
index 0000000000..0e1edb1a51
--- /dev/null
+++ b/gst/debugutils/gsttestsrcbin.c
@@ -0,0 +1,421 @@
+/* GStreamer
+ *
+ * Copyright (C) 2018 Igalia S.L. All rights reserved.
+ * @author: Thibault Saunier
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-testsrcbin
+ * @title: testsrc
+ *
+ * This is a simple GstBin source that wraps audiotestsrc/videotestsrc
+ * following specification passed in the URI (it implements the #GstURIHandler interface)
+ * in the form of `testbin://audio+video` or setting the "stream-types" property
+ * with the same format.
+ *
+ * This element also provides GstStream and GstStreamCollection and
+ * thus the element is useful for testing the new playbin3 infrastructure.
+ *
+ * Example pipeline:
+ * ```
+ * gst-launcher-1.0 playbin uri=testbin://audio,volume=0.5+video,pattern=white
+ * ```
+ */
+#include
+#include
+#include
+
+static GstStaticPadTemplate video_src_template =
+GST_STATIC_PAD_TEMPLATE ("video_src_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS ("video/x-raw(ANY)"));
+
+static GstStaticPadTemplate audio_src_template =
+ GST_STATIC_PAD_TEMPLATE ("audio_src_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS ("audio/x-raw(ANY);"));
+
+G_DECLARE_FINAL_TYPE (GstTestSrcBin, gst_test_src_bin, GST, TEST_SRC_BIN,
+ GstBin);
+
+struct _GstTestSrcBin
+{
+ GstBin parent;
+
+ gchar *uri;
+ GstStreamCollection *collection;
+ gint group_id;
+ GstFlowCombiner *flow_combiner;
+};
+
+enum
+{
+ PROP_0,
+ PROP_STREAM_TYPES,
+ PROP_LAST
+};
+
+#define DEFAULT_TYPES GST_STREAM_TYPE_AUDIO & GST_STREAM_TYPE_VIDEO
+
+static GstURIType
+gst_test_src_bin_uri_handler_get_type (GType type)
+{
+ return GST_URI_SRC;
+}
+
+static const gchar *const *
+gst_test_src_bin_uri_handler_get_protocols (GType type)
+{
+ static const gchar *protocols[] = { "testbin", NULL };
+
+ return protocols;
+}
+
+static gchar *
+gst_test_src_bin_uri_handler_get_uri (GstURIHandler * handler)
+{
+ GstTestSrcBin *self = GST_TEST_SRC_BIN (handler);
+ gchar *uri;
+
+ GST_OBJECT_LOCK (self);
+ uri = g_strdup (self->uri);
+ GST_OBJECT_UNLOCK (self);
+
+ return uri;
+}
+
+typedef struct
+{
+ GstEvent *stream_start;
+ GstStreamCollection *collection;
+} ProbeData;
+
+static ProbeData *
+_probe_data_new (GstEvent * stream_start, GstStreamCollection * collection)
+{
+ ProbeData *data = g_malloc0 (sizeof (ProbeData));
+
+ data->stream_start = gst_event_ref (stream_start);
+ data->collection = gst_object_ref (collection);
+
+ return data;
+}
+
+static void
+_probe_data_free (ProbeData * data)
+{
+ gst_event_replace (&data->stream_start, NULL);
+ gst_object_replace ((GstObject **) & data->collection, NULL);
+
+ g_free (data);
+}
+
+static GstPadProbeReturn
+src_pad_probe_cb (GstPad * pad, GstPadProbeInfo * info, ProbeData * data)
+{
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_STREAM_START:{
+ gst_event_unref (event);
+ info->data = gst_event_ref (data->stream_start);
+ return GST_PAD_PROBE_OK;
+ }
+ case GST_EVENT_CAPS:{
+ if (data->collection) {
+ GstStreamCollection *collection = data->collection;
+ /* Make sure the collection is NULL so that when caps get unstickied
+ * we let them pass through. */
+ data->collection = NULL;
+ gst_pad_push_event (pad, gst_event_new_stream_collection (collection));
+ gst_object_unref (collection);
+ }
+ return GST_PAD_PROBE_REMOVE;
+ }
+ default:
+ break;
+ }
+
+ return GST_PAD_PROBE_OK;
+}
+
+static GstFlowReturn
+gst_test_src_bin_chain (GstPad * pad, GstObject * object, GstBuffer * buffer)
+{
+ GstFlowReturn res;
+
+ GstTestSrcBin *self = GST_TEST_SRC_BIN (gst_object_get_parent (object));
+
+ res = gst_flow_combiner_update_pad_flow (self->flow_combiner, pad,
+ gst_proxy_pad_chain_default (pad, GST_OBJECT (self), buffer));
+ gst_object_unref (self);
+
+ return res;
+}
+
+static gboolean
+gst_test_src_bin_set_element_property (GQuark property_id, const GValue * value,
+ GObject * element)
+{
+ if (G_VALUE_HOLDS_STRING (value))
+ gst_util_set_object_arg (element, g_quark_to_string (property_id),
+ g_value_get_string (value));
+ else
+ g_object_set_property (element, g_quark_to_string (property_id), value);
+
+ return TRUE;
+}
+
+static void
+gst_test_src_bin_setup_src (GstTestSrcBin * self, const gchar * srcfactory,
+ GstStaticPadTemplate * template, GstStreamType stype,
+ GstStreamCollection * collection, gint * n_stream, GstStructure * props)
+{
+ GstElement *src = gst_element_factory_make (srcfactory, NULL);
+ GstPad *proxypad, *ghost, *pad = gst_element_get_static_pad (src, "src");
+ gchar *stream_id = g_strdup_printf ("%s_stream_%d", srcfactory, *n_stream);
+ gchar *pad_name = g_strdup_printf (template->name_template, *n_stream);
+ GstStream *stream = gst_stream_new (stream_id, NULL, stype,
+ n_stream ? GST_STREAM_FLAG_UNSELECT : GST_STREAM_FLAG_SELECT);
+ GstEvent *stream_start =
+ gst_event_new_stream_start (gst_stream_get_stream_id (stream));
+
+ gst_structure_foreach (props,
+ (GstStructureForeachFunc) gst_test_src_bin_set_element_property, src);
+
+ gst_event_set_stream (stream_start, stream);
+ gst_event_set_group_id (stream_start, self->group_id);
+
+ gst_pad_add_probe (pad, (GstPadProbeType) GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ (GstPadProbeCallback) src_pad_probe_cb, _probe_data_new (stream_start,
+ collection), (GDestroyNotify) _probe_data_free);
+
+ gst_stream_collection_add_stream (collection, stream);
+ g_free (stream_id);
+
+ gst_bin_add (GST_BIN (self), src);
+
+ ghost =
+ gst_ghost_pad_new_from_template (pad_name, pad,
+ gst_static_pad_template_get (template));
+ proxypad = GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (ghost)));
+ gst_flow_combiner_add_pad (self->flow_combiner, ghost);
+ gst_pad_set_chain_function (proxypad,
+ (GstPadChainFunction) gst_test_src_bin_chain);
+ gst_object_unref (proxypad);
+ gst_element_add_pad (GST_ELEMENT (self), ghost);
+ gst_object_unref (pad);
+ gst_element_sync_state_with_parent (src);
+ *n_stream += 1;
+}
+
+static void
+gst_test_src_bin_remove_child (GValue * val, GstBin * self)
+{
+ GstElement *child = g_value_get_object (val);
+
+ gst_bin_remove (self, child);
+}
+
+static gboolean
+gst_test_src_bin_uri_handler_set_uri (GstURIHandler * handler,
+ const gchar * uri, GError ** error)
+{
+ GstTestSrcBin *self = GST_TEST_SRC_BIN (handler);
+ gchar *tmp, *location = gst_uri_get_location (uri);
+ gint i, n_audio = 0, n_video = 0;
+ GstStreamCollection *collection = gst_stream_collection_new (NULL);
+ GstIterator *it = gst_bin_iterate_elements (GST_BIN (self));
+ GstCaps *streams_defs;
+
+ for (tmp = location; *tmp != '\0'; tmp++)
+ if (*tmp == '+')
+ *tmp = ';';
+
+ streams_defs = gst_caps_from_string (location);
+ g_free (location);
+
+ if (!streams_defs)
+ goto failed;
+
+ /* Clear us up */
+ while (gst_iterator_foreach (it,
+ (GstIteratorForeachFunction) gst_test_src_bin_remove_child,
+ self) == GST_ITERATOR_RESYNC)
+ gst_iterator_resync (it);
+
+ self->group_id = gst_util_group_id_next ();
+ for (i = 0; i < gst_caps_get_size (streams_defs); i++) {
+ GstStructure *stream_def = gst_caps_get_structure (streams_defs, i);
+
+ if (gst_structure_has_name (stream_def, "video"))
+ gst_test_src_bin_setup_src (self, "videotestsrc", &video_src_template,
+ GST_STREAM_TYPE_VIDEO, collection, &n_video, stream_def);
+ else if (gst_structure_has_name (stream_def, "audio"))
+ gst_test_src_bin_setup_src (self, "audiotestsrc", &audio_src_template,
+ GST_STREAM_TYPE_AUDIO, collection, &n_audio, stream_def);
+ else
+ GST_ERROR_OBJECT (self, "Unkown type %s",
+ gst_structure_get_name (stream_def));
+ }
+
+ if (!n_video && !n_audio)
+ goto failed;
+
+ self->uri = g_strdup (uri);
+ gst_element_post_message (GST_ELEMENT (self),
+ gst_message_new_stream_collection (GST_OBJECT (self), collection));
+
+ return TRUE;
+
+failed:
+ if (error)
+ *error =
+ g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
+ "No media type specified in the testbin:// URL.");
+
+ return FALSE;
+}
+
+static void
+gst_test_src_bin_uri_handler_init (gpointer g_iface, gpointer unused)
+{
+ GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
+
+ iface->get_type = gst_test_src_bin_uri_handler_get_type;
+ iface->get_protocols = gst_test_src_bin_uri_handler_get_protocols;
+ iface->get_uri = gst_test_src_bin_uri_handler_get_uri;
+ iface->set_uri = gst_test_src_bin_uri_handler_set_uri;
+}
+
+/* *INDENT-OFF* */
+G_DEFINE_TYPE_WITH_CODE (GstTestSrcBin, gst_test_src_bin, GST_TYPE_BIN,
+ G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_test_src_bin_uri_handler_init))
+/* *INDENT-ON* */
+
+static void
+gst_test_src_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstTestSrcBin *self = GST_TEST_SRC_BIN (object);
+
+ switch (prop_id) {
+ case PROP_STREAM_TYPES:
+ {
+ gchar *uri = g_strdup_printf ("testbin://%s", g_value_get_string (value));
+
+ g_assert (gst_uri_handler_set_uri (GST_URI_HANDLER (self), uri, NULL));
+ g_free (uri);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_test_src_bin_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstTestSrcBin *self = GST_TEST_SRC_BIN (object);
+
+ switch (prop_id) {
+ case PROP_STREAM_TYPES:
+ {
+ gchar *uri = gst_uri_handler_get_uri (GST_URI_HANDLER (self));
+ if (uri) {
+ gchar *types = gst_uri_get_location (uri);
+ g_value_set_string (value, types);
+ g_free (uri);
+ g_free (types);
+ }
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GstStateChangeReturn
+gst_test_src_bin_change_state (GstElement * element, GstStateChange transition)
+{
+ GstTestSrcBin *self = GST_TEST_SRC_BIN (element);
+ GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
+
+ result =
+ GST_ELEMENT_CLASS (gst_test_src_bin_parent_class)->change_state (element,
+ transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PAUSED_TO_READY:{
+ gst_flow_combiner_reset (self->flow_combiner);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return result;
+}
+
+static void
+gst_test_src_bin_finalize (GObject * object)
+{
+ GstTestSrcBin *self = GST_TEST_SRC_BIN (object);
+
+ g_free (self->uri);
+ gst_flow_combiner_free (self->flow_combiner);
+}
+
+static void
+gst_test_src_bin_init (GstTestSrcBin * self)
+{
+ self->flow_combiner = gst_flow_combiner_new ();
+}
+
+static void
+gst_test_src_bin_class_init (GstTestSrcBinClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstElementClass *gstelement_klass = (GstElementClass *) klass;
+
+ gobject_class->finalize = gst_test_src_bin_finalize;
+ gobject_class->get_property = gst_test_src_bin_get_property;
+ gobject_class->set_property = gst_test_src_bin_set_property;
+
+ /**
+ * GstTestSrcBin::stream-types:
+ *
+ * String describing the stream types to expose, eg. "video+audio".
+ */
+ g_object_class_install_property (gobject_class, PROP_STREAM_TYPES,
+ g_param_spec_string ("stream-types", "Stream types",
+ "String describing the stream types to expose, eg. \"video+audio\".",
+ NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+ gstelement_klass->change_state =
+ GST_DEBUG_FUNCPTR (gst_test_src_bin_change_state);
+ gst_element_class_add_pad_template (gstelement_klass,
+ gst_static_pad_template_get (&video_src_template));
+ gst_element_class_add_pad_template (gstelement_klass,
+ gst_static_pad_template_get (&audio_src_template));
+}
diff --git a/gst/debugutils/meson.build b/gst/debugutils/meson.build
index fb236dbb76..1836ebe068 100644
--- a/gst/debugutils/meson.build
+++ b/gst/debugutils/meson.build
@@ -8,6 +8,7 @@ debugutilsbad_sources = [
'gstcompare.c',
'gstfakevideosink.c',
'gstwatchdog.c',
+ 'gsttestsrcbin.c',
]
gstdebugutilsbad = library('gstdebugutilsbad',