diff options
author | Olivier CrĂȘte <olivier.crete@collabora.com> | 2012-03-15 14:12:21 -0400 |
---|---|---|
committer | Olivier CrĂȘte <olivier.crete@collabora.com> | 2013-01-23 21:13:03 -0500 |
commit | d1023646f9ef0437d134ae0ba006855e1b9982d6 (patch) | |
tree | d2ab938ecb4d168d65929ca3d76dc347dd29ec78 | |
parent | 07a51b16eb92e998a831e65a4a54466da29ea469 (diff) |
insertbin: Add bin to dynamically insert elements in a running pipeline
This element automatically links in any element added using it's
action signals. These elements must have a single source pad and a single
sink pad.
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Android.mk | 2 | ||||
-rw-r--r-- | configure.ac | 6 | ||||
-rw-r--r-- | docs/libs/Makefile.am | 1 | ||||
-rw-r--r-- | docs/libs/gst-plugins-bad-libs-docs.sgml | 5 | ||||
-rw-r--r-- | docs/libs/gst-plugins-bad-libs-sections.txt | 24 | ||||
-rw-r--r-- | docs/libs/gst-plugins-bad-libs.types | 3 | ||||
-rw-r--r-- | gst-libs/gst/Makefile.am | 6 | ||||
-rw-r--r-- | gst-libs/gst/insertbin/Makefile.am | 90 | ||||
-rw-r--r-- | gst-libs/gst/insertbin/gstinsertbin.c | 1029 | ||||
-rw-r--r-- | gst-libs/gst/insertbin/gstinsertbin.h | 101 | ||||
-rw-r--r-- | gst-plugins-bad.spec.in | 4 | ||||
-rw-r--r-- | pkgconfig/Makefile.am | 3 | ||||
-rw-r--r-- | pkgconfig/gstreamer-insertbin-uninstalled.pc.in | 12 | ||||
-rw-r--r-- | pkgconfig/gstreamer-insertbin.pc.in | 12 | ||||
-rw-r--r-- | tests/check/Makefile.am | 8 | ||||
-rw-r--r-- | tests/check/libs/.gitignore | 1 | ||||
-rw-r--r-- | tests/check/libs/insertbin.c | 354 |
18 files changed, 1661 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore index 141f5cb81..d98a98596 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,8 @@ gst-plugins-bad-*.tar.* /m4 Makefile.in Makefile +*.gir +*.typelib tmp-orc.c gst*orc.h diff --git a/Android.mk b/Android.mk index b8b9fb662..b53166d3d 100644 --- a/Android.mk +++ b/Android.mk @@ -9,6 +9,7 @@ GST_PLUGINS_BAD_BUILT_SOURCES := \ gst-libs/gst/baseparse/Android.mk \ gst-libs/gst/basecamerabinsrc/Android.mk \ gst-libs/gst/codecparsers/Android.mk \ + gst-libs/gst/insertbin/Android.mk gst-libs/gst/interfaces/Android.mk \ gst/h264parse/Android.mk \ gst/videoparsers/Android.mk \ @@ -98,6 +99,7 @@ CONFIGURE_TARGETS += gst-plugins-bad-configure -include $(GST_PLUGINS_BAD_TOP)/gst-libs/gst/baseparse/Android.mk -include $(GST_PLUGINS_BAD_TOP)/gst-libs/gst/basecamerabinsrc/Android.mk -include $(GST_PLUGINS_BAD_TOP)/gst-libs/gst/codecparsers/Android.mk +-include $(GST_PLUGINS_BAD_TOP)/gst-libs/gst/insertbin/Android.mk -include $(GST_PLUGINS_BAD_TOP)/gst-libs/gst/interfaces/Android.mk -include $(GST_PLUGINS_BAD_TOP)/gst/h264parse/Android.mk -include $(GST_PLUGINS_BAD_TOP)/gst/audiobuffer/Android.mk diff --git a/configure.ac b/configure.ac index ffc86cbd4..94d3a851b 100644 --- a/configure.ac +++ b/configure.ac @@ -123,6 +123,9 @@ AG_GST_CHECK_DOWHILE_MACROS AC_PATH_PROG(VALGRIND_PATH, valgrind, no) AM_CONDITIONAL(HAVE_VALGRIND, test ! "x$VALGRIND_PATH" = "xno") +dnl check for gobject-introspection +GOBJECT_INTROSPECTION_CHECK([1.31.1]) + dnl check for documentation tools GTK_DOC_CHECK([1.12]) AG_GST_PLUGIN_DOCS([1.12]) @@ -2257,6 +2260,7 @@ gst/y4m/Makefile gst-libs/Makefile gst-libs/gst/Makefile gst-libs/gst/basecamerabinsrc/Makefile +gst-libs/gst/insertbin/Makefile gst-libs/gst/interfaces/Makefile gst-libs/gst/signalprocessor/Makefile gst-libs/gst/codecparsers/Makefile @@ -2371,6 +2375,8 @@ pkgconfig/gstreamer-plugins-bad.pc pkgconfig/gstreamer-plugins-bad-uninstalled.pc pkgconfig/gstreamer-codecparsers.pc pkgconfig/gstreamer-codecparsers-uninstalled.pc +pkgconfig/gstreamer-insertbin.pc +pkgconfig/gstreamer-insertbin-uninstalled.pc pkgconfig/gstreamer-basevideo.pc pkgconfig/gstreamer-basevideo-uninstalled.pc tools/Makefile diff --git a/docs/libs/Makefile.am b/docs/libs/Makefile.am index 7e9b966a8..3ffe796bb 100644 --- a/docs/libs/Makefile.am +++ b/docs/libs/Makefile.am @@ -56,6 +56,7 @@ GTKDOC_LIBS = \ $(top_builddir)/gst-libs/gst/interfaces/libgstphotography-@GST_API_VERSION@.la \ $(top_builddir)/gst-libs/gst/signalprocessor/libgstsignalprocessor-@GST_API_VERSION@.la \ $(top_builddir)/gst-libs/gst/video/libgstbasevideo-@GST_API_VERSION@.la \ + $(top_builddir)/gst-libs/gst/insertbin/libgstinsertbin-@GST_API_VERSION@.la \ $(GST_BASE_LIBS) GTKDOC_CC=$(LIBTOOL) --tag=CC --mode=compile $(CC) diff --git a/docs/libs/gst-plugins-bad-libs-docs.sgml b/docs/libs/gst-plugins-bad-libs-docs.sgml index 3384e9fd4..d4e0060ca 100644 --- a/docs/libs/gst-plugins-bad-libs-docs.sgml +++ b/docs/libs/gst-plugins-bad-libs-docs.sgml @@ -44,6 +44,11 @@ <xi:include href="xml/gstsurfacebuffer.xml" /> <xi:include href="xml/gstsurfaceconverter.xml" /> </chapter> + + <chapter id="tools"> + <title>Useful elements</title> + <xi:include href="xml/gstinsertbin.xml" /> + </chapter> </part> <part id="gstreamer-libs-hierarchy"> diff --git a/docs/libs/gst-plugins-bad-libs-sections.txt b/docs/libs/gst-plugins-bad-libs-sections.txt index 59439f9bb..433c4b962 100644 --- a/docs/libs/gst-plugins-bad-libs-sections.txt +++ b/docs/libs/gst-plugins-bad-libs-sections.txt @@ -486,3 +486,27 @@ GstSurfaceConverterInterface gst_surface_converter_get_type gst_surface_converter_upload </SECTION> + +<SECTION> +<FILE>gstinsertbin</FILE> +<TITLE>GstInsertbin</TITLE> +GstInsertBin +GstInsertBinClass +GstInsertBinCallback +gst_insert_bin_new +gst_insert_bin_append +gst_insert_bin_prepend +gst_insert_bin_insert_after +gst_insert_bin_insert_before +gst_insert_bin_remove +<SUBSECTION Standard> +GST_INSERT_BIN +GST_INSERT_BIN_CLASS +GST_INSERT_BIN_GET_CLASS +GST_IS_INSERT_BIN +GST_IS_INSERT_BIN_CLASS +GST_TYPE_INSERT_BIN +gst_insert_bin_get_type +<SUBSECTION Private> +GstInsertBinPrivate +</SECTION> diff --git a/docs/libs/gst-plugins-bad-libs.types b/docs/libs/gst-plugins-bad-libs.types index 493215718..caa3918aa 100644 --- a/docs/libs/gst-plugins-bad-libs.types +++ b/docs/libs/gst-plugins-bad-libs.types @@ -2,3 +2,6 @@ #include <gst/codecparsers/gsth264parser.h> #include <gst/codecparsers/gstmpegvideoparser.h> +#include <gst/insertbin/gstinsertbin.h> + +gst_insert_bin_get_type
\ No newline at end of file diff --git a/gst-libs/gst/Makefile.am b/gst-libs/gst/Makefile.am index ab1c26e84..2868042aa 100644 --- a/gst-libs/gst/Makefile.am +++ b/gst-libs/gst/Makefile.am @@ -1,6 +1,8 @@ -SUBDIRS = interfaces signalprocessor video basecamerabinsrc codecparsers +SUBDIRS = interfaces signalprocessor video basecamerabinsrc codecparsers \ + insertbin noinst_HEADERS = gst-i18n-plugin.h gettext.h glib-compat-private.h -DIST_SUBDIRS = interfaces signalprocessor video basecamerabinsrc codecparsers +DIST_SUBDIRS = interfaces signalprocessor video basecamerabinsrc codecparsers \ + insertbin diff --git a/gst-libs/gst/insertbin/Makefile.am b/gst-libs/gst/insertbin/Makefile.am new file mode 100644 index 000000000..8c4cfba0d --- /dev/null +++ b/gst-libs/gst/insertbin/Makefile.am @@ -0,0 +1,90 @@ +lib_LTLIBRARIES = libgstinsertbin-@GST_API_VERSION@.la + +libgstinsertbin_@GST_API_VERSION@_la_SOURCES = gstinsertbin.c + +libgstinsertbin_@GST_API_VERSION@includedir = \ + $(includedir)/gstreamer-@GST_API_VERSION@/gst/insertbin + +libgstinsertbin_@GST_API_VERSION@include_HEADERS = gstinsertbin.h + +libgstinsertbin_@GST_API_VERSION@_la_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) \ + $(GST_CFLAGS) + +libgstinsertbin_@GST_API_VERSION@_la_LIBADD = \ + $(GST_LIBS) + +libgstinsertbin_@GST_API_VERSION@_la_LDFLAGS = \ + $(GST_LIB_LDFLAGS) \ + $(GST_ALL_LDFLAGS) \ + $(GST_LT_LDFLAGS) + + +if HAVE_INTROSPECTION +BUILT_GIRSOURCES = GstInsertBin-@GST_API_VERSION@.gir + +gir_headers=$(patsubst %,$(srcdir)/%, $(libgstinsertbin_@GST_API_VERSION@include_HEADERS)) +gir_headers+=$(patsubst %,$(builddir)/%, $(built_headers)) +gir_sources=$(patsubst %,$(srcdir)/%, $(libgstinsertbin_@GST_API_VERSION@_la_SOURCES)) +gir_sources+=$(patsubst %,$(builddir)/%, $(built_sources)) + +GstInsertBin-@GST_API_VERSION@.gir: $(INTROSPECTION_SCANNER) libgstinsertbin-@GST_API_VERSION@.la + $(AM_V_GEN)PKG_CONFIG_PATH="$(GST_PKG_CONFIG_PATH)" \ + GST_PLUGIN_SYSTEM_PATH_1_0="" GST_PLUGIN_PATH_1_0="" GST_REGISTRY_UPDATE=no \ + $(INTROSPECTION_SCANNER) -v --namespace GstInsertBin \ + --nsversion=@GST_API_VERSION@ \ + --strip-prefix=Gst \ + --warn-all \ + --c-include "gst/insertbin/gstinsertbin.h" \ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + --add-include-path=`PKG_CONFIG_PATH="$(GST_PKG_CONFIG_PATH)" $(PKG_CONFIG) --variable=girdir gstreamer-@GST_API_VERSION@` \ + --add-include-path=`PKG_CONFIG_PATH="$(GST_PKG_CONFIG_PATH)" $(PKG_CONFIG) --variable=girdir gstreamer-base-@GST_API_VERSION@` \ + --library=libgstinsertbin-@GST_API_VERSION@.la \ + --include=Gst-@GST_API_VERSION@ \ + --include=GstBase-@GST_API_VERSION@ \ + --libtool="$(top_builddir)/libtool" \ + --pkg gstreamer-@GST_API_VERSION@ \ + --pkg gstreamer-base-@GST_API_VERSION@ \ + --pkg-export gstreamer-insertbin-@GST_API_VERSION@ \ + --add-init-section="gst_init(NULL,NULL);" \ + --output $@ \ + $(gir_headers) \ + $(gir_sources) + +# INTROSPECTION_GIRDIR/INTROSPECTION_TYPELIBDIR aren't the right place to +# install anything - we need to install inside our prefix. +girdir = $(datadir)/gir-1.0 +gir_DATA = $(BUILT_GIRSOURCES) + +typelibsdir = $(libdir)/girepository-1.0/ + +typelibs_DATA = $(BUILT_GIRSOURCES:.gir=.typelib) + +%.typelib: %.gir $(INTROSPECTION_COMPILER) + $(AM_V_GEN)PKG_CONFIG_PATH="$(GST_PKG_CONFIG_PATH)" \ + $(INTROSPECTION_COMPILER) \ + --includedir=$(srcdir) \ + --includedir=$(builddir) \ + --includedir=`PKG_CONFIG_PATH="$(GST_PKG_CONFIG_PATH)" $(PKG_CONFIG) --variable=girdir gstreamer-@GST_API_VERSION@` \ + --includedir=`PKG_CONFIG_PATH="$(GST_PKG_CONFIG_PATH)" $(PKG_CONFIG) --variable=girdir gstreamer-base-@GST_API_VERSION@` \ + $(INTROSPECTION_COMPILER_OPTS) $< -o $(@F) + +CLEANFILES = $(BUILT_GIRSOURCES) $(typelibs_DATA) +endif + +Android.mk: $(BUILT_SOURCES) Makefile.am + androgenizer -:PROJECT libgstinsertbin -:STATIC libgstinsertbin-@GST_API_VERSION@ \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstinsertbin_@GST_API_VERSION@_la_SOURCES) \ + $(built_sources) \ + -:CFLAGS $(DEFS) $(libgstinsertbin_@GST_API_VERSION@_la_CFLAGS) \ + -:LDFLAGS $(libgstinsertbin_@GST_API_VERSION@_la_LDFLAGS) \ + $(libgstinsertbin@GST_API_VERSION@_la_LIBADD) \ + -ldl \ + -:HEADER_TARGET gstreamer-@GST_API_VERSION@/gst/insertbin \ + -:HEADERS $(libgstinsertbininclude_HEADERS) \ + $(built_headers) \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + > $@ diff --git a/gst-libs/gst/insertbin/gstinsertbin.c b/gst-libs/gst/insertbin/gstinsertbin.c new file mode 100644 index 000000000..295d2bc1b --- /dev/null +++ b/gst-libs/gst/insertbin/gstinsertbin.c @@ -0,0 +1,1029 @@ +/* + * GStreamer + * + * Copyright 2013 Collabora Ltd + * @author: Olivier Crete <olivier.crete@collabora.com> + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +/** + * SECTION:gstinsertbin + * @short_description: A #GstBin to insertally link filter-like elements. + * + * This element is a #GstBin that has a single source and sink pad. It allows + * the user (the application) to easily add and remove filter-like element + * (that has a single source and sink pad), to the pipeline while it is running. + * It features a fully asynchronous API inspired by GLib's GAsyncResult based + * APIs. + * + * Each operation (addition or removal) can take a callback, this callback + * is guaranteed to be called. Unlike GIO, there is no guarantee about where + * this callback will be called from, it could be called before the action + * returns or it could be called later from another thread. The signature of + * this callback GstInsertBinCallback(). + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstinsertbin.h" + + +GST_DEBUG_CATEGORY_STATIC (insert_bin_debug); +#define GST_CAT_DEFAULT (insert_bin_debug) + + +static GstStaticPadTemplate gst_insert_bin_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate gst_insert_bin_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +enum +{ + SIG_PREPEND, + SIG_APPEND, + SIG_INSERT_BEFORE, + SIG_INSERT_AFTER, + SIG_REMOVE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +enum +{ + PROP_0, +}; + +struct _GstInsertBinPrivate +{ + GstPad *srcpad; + GstPad *sinkpad; + + GQueue change_queue; +}; + +typedef enum +{ + GST_INSERT_BIN_ACTION_ADD, + GST_INSERT_BIN_ACTION_REMOVE +} GstInsertBinAction; + + +typedef enum +{ + DIRECTION_NONE, + DIRECTION_AFTER, + DIRECTION_BEFORE +} GstInsertBinDirection; + +struct ChangeData +{ + GstElement *element; + GstInsertBinAction action; + GstElement *sibling; + GstInsertBinDirection direction; + + GstInsertBinCallback callback; + gpointer user_data; +}; + +static void gst_insert_bin_dispose (GObject * object); + + +static void gst_insert_bin_do_change (GstInsertBin * self, GstPad * pad); +static GstPadProbeReturn pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data); + +G_DEFINE_TYPE (GstInsertBin, gst_insert_bin, GST_TYPE_BIN); + +static void +gst_insert_bin_class_init (GstInsertBinClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = (GstElementClass *) klass; + + GST_DEBUG_CATEGORY_INIT (insert_bin_debug, "insertbin", 0, "Insert Bin"); + + g_type_class_add_private (klass, sizeof (GstInsertBinPrivate)); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_insert_bin_src_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_insert_bin_sink_template)); + gst_element_class_set_static_metadata (gstelement_class, "Insert Bin", + "Generic/Bin/Filter", + "Auto-links filter style elements insertally", + "Olivier Crete <olivier.crete@collabora.com>"); + + gobject_class->dispose = gst_insert_bin_dispose; + + /** + * GstInsertBin::prepend: + * @element: the #GstElement to add + * @callback: the callback to call when the element has been added or not, or + * %NULL + * @user_data: The data to pass to the callback + * + * This action signal adds the filter like element before any other element + * in the bin. + * + * Same as gst_insert_bin_prepend() + */ + signals[SIG_PREPEND] = g_signal_new_class_handler ("prepend", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_insert_bin_prepend), + NULL, NULL, NULL, + G_TYPE_NONE, 3, GST_TYPE_ELEMENT, G_TYPE_POINTER, G_TYPE_POINTER); + + /** + * GstInsertBin::append: + * @element: the #GstElement to add + * @callback: the callback to call when the element has been added or not, or + * %NULL + * @user_data: The data to pass to the callback + * + * This action signal adds the filter like element after any other element + * in the bin. + * + * Same as gst_insert_bin_append() + */ + signals[SIG_APPEND] = g_signal_new_class_handler ("append", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_insert_bin_append), + NULL, NULL, NULL, + G_TYPE_NONE, 3, GST_TYPE_ELEMENT, G_TYPE_POINTER, G_TYPE_POINTER); + + /** + * GstInsertBin::insert-before: + * @element: the #GstElement to add + * @sibling: the #GstElement to add @element before + * @callback: the callback to call when the element has been added or not, or + * %NULL + * @user_data: The data to pass to the callback + * + * This action signal adds the filter like element before the @sibling + * element in the bin. + * + * Same as gst_insert_bin_insert_before() + */ + signals[SIG_INSERT_BEFORE] = g_signal_new_class_handler ("insert-before", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_insert_bin_insert_before), + NULL, NULL, NULL, + G_TYPE_NONE, 4, GST_TYPE_ELEMENT, GST_TYPE_ELEMENT, + G_TYPE_POINTER, G_TYPE_POINTER); + + /** + * GstInsertBin::insert-after: + * @element: the #GstElement to add + * @sibling: the #GstElement to add @element after + * @callback: the callback to call when the element has been added or not, or + * %NULL + * @user_data: The data to pass to the callback + * + * This action signal adds the filter like element after the @sibling + * element in the bin. + * element in the bin. + * + * Same as gst_insert_bin_insert_after() + */ + signals[SIG_INSERT_AFTER] = g_signal_new_class_handler ("insert-after", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_insert_bin_insert_after), + NULL, NULL, NULL, + G_TYPE_NONE, 4, GST_TYPE_ELEMENT, GST_TYPE_ELEMENT, + G_TYPE_POINTER, G_TYPE_POINTER); + + + /** + * GstInsertBin::remove: + * @element: the #GstElement to remove + * @callback: the callback to call when the element has been removed or not, + * or %NULL + * @user_data: The data to pass to the callback + * + * This action signal removed the filter like element from the bin. + * + * Same as gst_insert_bin_remove() + */ + signals[SIG_REMOVE] = g_signal_new_class_handler ("remove", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_insert_bin_remove), + NULL, NULL, NULL, + G_TYPE_NONE, 3, GST_TYPE_ELEMENT, G_TYPE_POINTER, G_TYPE_POINTER); +} + +static void +change_data_free (struct ChangeData *data) +{ + gst_object_unref (data->element); + if (data->sibling) + gst_object_unref (data->sibling); + g_slice_free (struct ChangeData, data); +} + + +static void +gst_insert_bin_change_data_complete (GstInsertBin * self, + struct ChangeData *data, gboolean success) +{ + if (data->callback) + data->callback (self, data->element, success, data->user_data); + + change_data_free (data); +} + +static void +gst_insert_bin_init (GstInsertBin * self) +{ + GstProxyPad *internal; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GST_TYPE_INSERT_BIN, + GstInsertBinPrivate); + + g_queue_init (&self->priv->change_queue); + + self->priv->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", + gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), + "sink")); + gst_element_add_pad (GST_ELEMENT (self), self->priv->sinkpad); + + internal = gst_proxy_pad_get_internal (GST_PROXY_PAD (self->priv->sinkpad)); + self->priv->srcpad = gst_ghost_pad_new_from_template ("src", + GST_PAD (internal), + gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "src")); + gst_object_unref (internal); + gst_element_add_pad (GST_ELEMENT (self), self->priv->srcpad); +} + +static void +gst_insert_bin_dispose (GObject * object) +{ + GstInsertBin *self = GST_INSERT_BIN (object); + struct ChangeData *data; + + while ((data = g_queue_pop_head (&self->priv->change_queue))) + gst_insert_bin_change_data_complete (self, data, FALSE); + + gst_ghost_pad_set_target (GST_GHOST_PAD (self->priv->srcpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD (self->priv->sinkpad), NULL); + + G_OBJECT_CLASS (gst_insert_bin_parent_class)->dispose (object); +} + +static gboolean +validate_element (GstInsertBin * self, GstElement * element) +{ + gboolean valid = TRUE;; + + GST_OBJECT_LOCK (element); + if (element->numsrcpads != 1 || element->numsinkpads != 1) { + GST_WARNING_OBJECT (self, + "Element does not have a single src and sink pad"); + valid = FALSE; + } + + if (GST_OBJECT_PARENT (element) != NULL) { + GST_WARNING_OBJECT (self, "Element already has a parent"); + valid = FALSE; + } + GST_OBJECT_UNLOCK (element); + + return valid; +} + +static GstPad * +get_single_pad (GstElement * element, GstPadDirection direction) +{ + GstPad *pad = NULL; + + GST_OBJECT_LOCK (element); + if (direction == GST_PAD_SRC) { + if (element->numsrcpads == 1) + pad = element->srcpads->data; + } else { + if (element->numsinkpads == 1) + pad = element->sinkpads->data; + } + + if (pad) + gst_object_ref (pad); + GST_OBJECT_UNLOCK (element); + + return pad; +} + +static gboolean +is_right_direction_for_block (GstPad * pad) +{ + gboolean right_direction; + + GST_OBJECT_LOCK (pad); + switch (pad->mode) { + case GST_PAD_MODE_NONE: + right_direction = TRUE; + break; + case GST_PAD_MODE_PUSH: + right_direction = (pad->direction == GST_PAD_SRC); + break; + case GST_PAD_MODE_PULL: + right_direction = (pad->direction == GST_PAD_SINK); + break; + default: + right_direction = FALSE; + g_assert_not_reached (); + } + GST_OBJECT_UNLOCK (pad); + + return right_direction; +} + +static void +gst_insert_bin_block_pad_unlock (GstInsertBin * self) +{ + struct ChangeData *data; + GstPad *pad; + GstPadProbeType probetype; + +again: + + data = g_queue_peek_head (&self->priv->change_queue); + if (!data) { + GST_OBJECT_UNLOCK (self); + return; + } + + if (data->action == GST_INSERT_BIN_ACTION_ADD && + !validate_element (self, data->element)) + goto error; + + if (data->action == GST_INSERT_BIN_ACTION_ADD) { + if (data->sibling) { + if (data->direction == DIRECTION_BEFORE) + pad = get_single_pad (data->sibling, GST_PAD_SINK); + else + pad = get_single_pad (data->sibling, GST_PAD_SRC); + } else { + if (data->direction == DIRECTION_BEFORE) + pad = (GstPad *) + gst_proxy_pad_get_internal (GST_PROXY_PAD (self->priv->srcpad)); + else + pad = (GstPad *) + gst_proxy_pad_get_internal (GST_PROXY_PAD (self->priv->sinkpad)); + } + + if (!pad) { + GST_WARNING_OBJECT (self, "Can not obtain a sibling pad to block" + " before adding"); + goto error; + } + + if (!is_right_direction_for_block (pad)) { + GstPad *peer = gst_pad_get_peer (pad); + + if (peer) { + gst_object_unref (pad); + pad = peer; + } + } + } else { + GstPad *element_pad; + + element_pad = get_single_pad (data->element, GST_PAD_SINK); + if (!element_pad) { + GST_WARNING_OBJECT (self, "Can not obtain the element's sink pad"); + goto error; + } + + if (!is_right_direction_for_block (element_pad)) { + pad = gst_pad_get_peer (element_pad); + } else { + gst_object_unref (element_pad); + + element_pad = get_single_pad (data->element, GST_PAD_SRC); + if (!element_pad) { + GST_WARNING_OBJECT (self, "Can not obtain the element's src pad"); + goto error; + } + + pad = gst_pad_get_peer (element_pad); + } + gst_object_unref (element_pad); + + if (!pad) { + GST_WARNING_OBJECT (self, "Can not obtain a sibling pad for removing"); + goto error; + } + } + + if (GST_PAD_IS_SRC (pad)) + probetype = GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM; + else + probetype = GST_PAD_PROBE_TYPE_BLOCK_UPSTREAM; + + GST_OBJECT_UNLOCK (self); + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_IDLE | probetype, pad_blocked_cb, + self, NULL); + gst_object_unref (pad); + return; + +error: + g_queue_pop_head (&self->priv->change_queue); + gst_insert_bin_change_data_complete (self, data, FALSE); + goto again; +} + +static GstPadProbeReturn +wait_and_drop_eos_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstPad *peer; + + if (GST_EVENT_TYPE (GST_PAD_PROBE_INFO_EVENT (info)) != GST_EVENT_EOS) + return GST_PAD_PROBE_PASS; + + peer = gst_pad_get_peer (pad); + if (peer) { + gst_pad_unlink (pad, peer); + gst_object_unref (peer); + } + + return GST_PAD_PROBE_DROP; +} + +static void +gst_insert_bin_do_change (GstInsertBin * self, GstPad * pad) +{ + struct ChangeData *data; + + GST_OBJECT_LOCK (self); + + if (!is_right_direction_for_block (pad)) { + GST_WARNING_OBJECT (self, "Block pad does not have the expected direction"); + goto next; + } + + while ((data = g_queue_pop_head (&self->priv->change_queue)) != NULL) { + GstPad *peer = NULL; + GstPad *other_peer = NULL; + + GST_OBJECT_UNLOCK (self); + + + if (data->action == GST_INSERT_BIN_ACTION_ADD && + !validate_element (self, data->element)) + goto error; + + peer = gst_pad_get_peer (pad); + + if (peer == NULL) { + GST_WARNING_OBJECT (self, "Blocked pad has no peer"); + goto error; + } + + if (data->action == GST_INSERT_BIN_ACTION_ADD) { + GstPad *srcpad = NULL, *sinkpad = NULL; + GstPad *peersrcpad, *peersinkpad; + + /* First let's make sure we have the right pad */ + if (data->sibling) { + GstElement *parent = NULL; + GstPad *siblingpad; + + if ((gst_pad_get_direction (pad) == GST_PAD_SRC && + data->direction == DIRECTION_BEFORE) || + (gst_pad_get_direction (pad) == GST_PAD_SINK && + data->direction == DIRECTION_AFTER)) + siblingpad = peer; + else + siblingpad = pad; + + parent = gst_pad_get_parent_element (siblingpad); + if (parent != NULL) + gst_object_unref (parent); + + if (parent != data->sibling) + goto retry; + } else { + GstObject *parent; + GstPad *ghost; + GstPad *proxypad; + + if (data->direction == DIRECTION_BEFORE) { + ghost = self->priv->srcpad; + if (gst_pad_get_direction (pad) == GST_PAD_SINK) + proxypad = pad; + else + proxypad = peer; + } else { + ghost = self->priv->sinkpad; + if (gst_pad_get_direction (pad) == GST_PAD_SINK) + proxypad = peer; + else + proxypad = pad; + } + + if (!GST_IS_PROXY_PAD (proxypad)) + goto retry; + parent = gst_pad_get_parent (proxypad); + if (!parent) + goto retry; + gst_object_unref (parent); + if (GST_PAD_CAST (parent) != ghost) + goto retry; + } + + if (gst_pad_get_direction (pad) == GST_PAD_SRC) { + peersrcpad = pad; + peersinkpad = peer; + } else { + peersrcpad = peer; + peersinkpad = pad; + } + + if (GST_IS_PROXY_PAD (peersrcpad)) { + GstObject *parent = gst_pad_get_parent (peersrcpad); + + if (GST_PAD_CAST (parent) == self->priv->sinkpad) + peersrcpad = NULL; + + if (parent) + gst_object_unref (parent); + } + + if (GST_IS_PROXY_PAD (peersinkpad)) { + GstObject *parent = gst_pad_get_parent (peersinkpad); + + if (GST_PAD_CAST (parent) == self->priv->srcpad) + peersinkpad = NULL; + + if (parent) + gst_object_unref (parent); + } + + if (peersinkpad && peersrcpad) { + gst_pad_unlink (peersrcpad, peersinkpad); + } else { + if (!peersinkpad) + gst_ghost_pad_set_target (GST_GHOST_PAD (self->priv->srcpad), NULL); + if (!peersrcpad) + gst_ghost_pad_set_target (GST_GHOST_PAD (self->priv->sinkpad), NULL); + } + + srcpad = get_single_pad (data->element, GST_PAD_SRC); + sinkpad = get_single_pad (data->element, GST_PAD_SINK); + + if (srcpad == NULL || sinkpad == NULL) { + GST_WARNING_OBJECT (self, "Can not get element src or sink pad"); + goto error; + } + + if (!gst_bin_add (GST_BIN (self), data->element)) { + GST_WARNING_OBJECT (self, "Can not add element to bin"); + goto error; + } + + if (peersrcpad) { + if (GST_PAD_LINK_FAILED (gst_pad_link (peersrcpad, sinkpad))) { + GST_WARNING_OBJECT (self, "Can not link sibling's %s:%s pad" + " to element's %s:%s pad", GST_DEBUG_PAD_NAME (peersrcpad), + GST_DEBUG_PAD_NAME (sinkpad)); + goto error; + } + } else { + if (!gst_ghost_pad_set_target (GST_GHOST_PAD (self->priv->sinkpad), + sinkpad)) { + GST_WARNING_OBJECT (self, "Can not set %s:%s as target for %s", + GST_DEBUG_PAD_NAME (sinkpad), + GST_DEBUG_PAD_NAME (self->priv->sinkpad)); + goto error; + } + } + + if (peersinkpad) { + if (GST_PAD_LINK_FAILED (gst_pad_link (srcpad, peersinkpad))) { + GST_WARNING_OBJECT (self, "Can not link element's %s:%s pad" + " to sibling's %s:%s pad", GST_DEBUG_PAD_NAME (srcpad), + GST_DEBUG_PAD_NAME (peersinkpad)); + goto error; + } + } else { + if (!gst_ghost_pad_set_target (GST_GHOST_PAD (self->priv->srcpad), + srcpad)) { + GST_WARNING_OBJECT (self, "Can not set %s:%s as target for %s", + GST_DEBUG_PAD_NAME (srcpad), + GST_DEBUG_PAD_NAME (self->priv->srcpad)); + goto error; + } + } + + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + if (!gst_element_sync_state_with_parent (data->element)) { + GST_WARNING_OBJECT (self, "Can not sync element's state with parent"); + goto error; + } + } else { + GstElement *parent = NULL; + GstPad *other_pad; + GstCaps *caps = NULL, *peercaps = NULL; + gboolean can_intersect; + gboolean success; + + parent = gst_pad_get_parent_element (peer); + if (parent != NULL) + gst_object_unref (parent); + + if (parent != data->element) + goto retry; + + if (gst_pad_get_direction (peer) == GST_PAD_SRC) + other_pad = get_single_pad (data->element, GST_PAD_SINK); + else + other_pad = get_single_pad (data->element, GST_PAD_SRC); + + if (!other_pad) { + GST_WARNING_OBJECT (self, "Can not get element's other pad"); + goto error; + } + + other_peer = gst_pad_get_peer (other_pad); + gst_object_unref (other_pad); + + if (!other_peer) { + GST_WARNING_OBJECT (self, "Can not get element's other peer"); + goto error; + } + + /* Get the negotiated caps for the source pad peer, + * because renegotiation while the pipeline is playing doesn't work + * that fast. + */ + if (gst_pad_get_direction (pad) == GST_PAD_SRC) + caps = gst_pad_get_current_caps (pad); + else + peercaps = gst_pad_get_current_caps (other_peer); + if (!caps) + caps = gst_pad_query_caps (pad, NULL); + if (!peercaps) + peercaps = gst_pad_query_caps (other_peer, NULL); + can_intersect = gst_caps_can_intersect (caps, peercaps); + gst_caps_unref (caps); + gst_caps_unref (peercaps); + + if (!can_intersect) { + GST_WARNING_OBJECT (self, "Pads are incompatible without the element"); + goto error; + } + + if (gst_pad_get_direction (other_peer) == GST_PAD_SRC && + gst_pad_is_active (other_peer)) { + gulong probe_id; + + probe_id = gst_pad_add_probe (other_peer, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + wait_and_drop_eos_cb, NULL, NULL); + gst_pad_send_event (peer, gst_event_new_eos ()); + gst_pad_remove_probe (other_peer, probe_id); + } + + gst_element_set_locked_state (data->element, TRUE); + gst_element_set_state (data->element, GST_STATE_NULL); + if (!gst_bin_remove (GST_BIN (self), data->element)) { + GST_WARNING_OBJECT (self, "Element removal rejected"); + goto error; + } + gst_element_set_locked_state (data->element, FALSE); + + if (gst_pad_get_direction (pad) == GST_PAD_SRC) + success = GST_PAD_LINK_SUCCESSFUL (gst_pad_link_full (pad, other_peer, + GST_PAD_LINK_CHECK_HIERARCHY | + GST_PAD_LINK_CHECK_TEMPLATE_CAPS)); + else + success = GST_PAD_LINK_SUCCESSFUL (gst_pad_link_full (other_peer, pad, + GST_PAD_LINK_CHECK_HIERARCHY | + GST_PAD_LINK_CHECK_TEMPLATE_CAPS)); + gst_object_unref (other_peer); + other_peer = NULL; + + if (!success) { + GST_ERROR_OBJECT (self, "Could not re-link after the element's" + " removal"); + goto error; + } + } + + + gst_insert_bin_change_data_complete (self, data, TRUE); + gst_object_unref (peer); + + GST_OBJECT_LOCK (self); + continue; + done: + if (other_peer != NULL) + gst_object_unref (other_peer); + + if (peer != NULL) + gst_object_unref (peer); + break; + retry: + GST_OBJECT_LOCK (self); + g_queue_push_head (&self->priv->change_queue, data); + goto done; + error: + /* Handle error */ + gst_insert_bin_change_data_complete (self, data, FALSE); + GST_OBJECT_LOCK (self); + goto done; + } + +next: + gst_insert_bin_block_pad_unlock (self); +} + + + +static GstPadProbeReturn +pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstInsertBin *self = GST_INSERT_BIN (user_data); + + g_assert (GST_PAD_PROBE_INFO_TYPE (info) & + (GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_IDLE)); + + gst_insert_bin_do_change (self, pad); + + return GST_PAD_PROBE_REMOVE; +} + +static void +gst_insert_bin_add_operation (GstInsertBin * self, + GstElement * element, GstInsertBinAction action, GstElement * sibling, + GstInsertBinDirection direction, GstInsertBinCallback callback, + gpointer user_data) +{ + struct ChangeData *data = g_slice_new (struct ChangeData); + gboolean block_pad; + + data->element = element; + data->action = action; + data->sibling = sibling; + if (data->sibling) + gst_object_ref (data->sibling); + data->direction = direction; + data->callback = callback; + data->user_data = user_data; + + GST_OBJECT_LOCK (self); + block_pad = g_queue_is_empty (&self->priv->change_queue); + g_queue_push_tail (&self->priv->change_queue, data); + + if (block_pad) + gst_insert_bin_block_pad_unlock (self); + else + GST_OBJECT_UNLOCK (self); +} + +static void +gst_insert_bin_add (GstInsertBin * self, GstElement * element, + GstElement * sibling, GstInsertBinDirection direction, + GstInsertBinCallback callback, gpointer user_data) +{ + gst_object_ref_sink (element); + + if (!validate_element (self, element)) + goto reject; + + if (sibling) { + gboolean is_parent; + + GST_OBJECT_LOCK (sibling); + is_parent = (GST_OBJECT_PARENT (sibling) == GST_OBJECT_CAST (self)); + GST_OBJECT_UNLOCK (sibling); + + if (!is_parent) + goto reject; + } + + + gst_insert_bin_add_operation (self, element, GST_INSERT_BIN_ACTION_ADD, + sibling, direction, callback, user_data); + return; + +reject: + if (callback) + callback (self, element, FALSE, user_data); + gst_object_unref (element); +} + + +/** + * gst_insert_bin_prepend: + * @element: the #GstElement to add + * @callback: the callback to call when the element has been added or not, or + * %NULL + * @user_data: The data to pass to the callback + * + * This action signal adds the filter like element before any other element + * in the bin. + * + * Same as the #GstInsertBin::prepend signal. + */ + +void +gst_insert_bin_prepend (GstInsertBin * self, GstElement * element, + GstInsertBinCallback callback, gpointer user_data) +{ + g_return_if_fail (GST_IS_ELEMENT (element)); + + gst_insert_bin_add (self, element, NULL, DIRECTION_AFTER, + callback, user_data); +} + +/** + * gst_insert_bin_append: + * @element: the #GstElement to add + * @callback: the callback to call when the element has been added or not, or + * %NULL + * @user_data: The data to pass to the callback + * + * This action signal adds the filter like element after any other element + * in the bin. + * + * Same as the #GstInsertBin::append signal. + */ + +void +gst_insert_bin_append (GstInsertBin * self, GstElement * element, + GstInsertBinCallback callback, gpointer user_data) +{ + g_return_if_fail (GST_IS_ELEMENT (element)); + + gst_insert_bin_add (self, element, NULL, DIRECTION_BEFORE, + callback, user_data); +} + +/** + * gst_insert_bin_insert_before + * @element: the #GstElement to add + * @sibling: the #GstElement to add @element before + * @callback: the callback to call when the element has been added or not, or + * %NULL + * @user_data: The data to pass to the callback + * + * This action signal adds the filter like element before the @sibling + * element in the bin. + * + * Same as the #GstInsertBin::insert-before signal. + */ +void +gst_insert_bin_insert_before (GstInsertBin * self, GstElement * element, + GstElement * sibling, GstInsertBinCallback callback, gpointer user_data) +{ + g_return_if_fail (GST_IS_ELEMENT (element)); + g_return_if_fail (GST_IS_ELEMENT (sibling)); + + gst_insert_bin_add (self, element, sibling, DIRECTION_BEFORE, + callback, user_data); +} + +/** + * gst_insert_bin_insert_after: + * @element: the #GstElement to add + * @sibling: the #GstElement to add @element after + * @callback: the callback to call when the element has been added or not, or + * %NULL + * @user_data: The data to pass to the callback + * + * This action signal adds the filter like element after the @sibling + * element in the bin. + * + * Same as the #GstInsertBin::insert-after signal. + */ +void +gst_insert_bin_insert_after (GstInsertBin * self, GstElement * element, + GstElement * sibling, GstInsertBinCallback callback, gpointer user_data) +{ + g_return_if_fail (GST_IS_ELEMENT (element)); + g_return_if_fail (GST_IS_ELEMENT (sibling)); + + gst_insert_bin_add (self, element, sibling, DIRECTION_AFTER, + callback, user_data); +} + +/** + * gst_insert_bin_remove: + * @element: the #GstElement to remove + * @callback: the callback to call when the element has been removed or not, + * or %NULL + * @user_data: The data to pass to the callback + * + * This action signal removed the filter like element from the bin. + * + * Same as the #GstInsertBin::remove signal. + */ + +void +gst_insert_bin_remove (GstInsertBin * self, GstElement * element, + GstInsertBinCallback callback, gpointer user_data) +{ + GstObject *parent; + + g_return_if_fail (GST_IS_ELEMENT (element)); + + parent = gst_element_get_parent (element); + + if (parent) { + gboolean is_parent; + + is_parent = (parent == GST_OBJECT_CAST (self)); + gst_object_unref (parent); + + if (!is_parent) { + if (callback) + callback (self, element, FALSE, user_data); + return; + } + } else { + GList *item; + struct ChangeData *data = NULL; + + GST_OBJECT_LOCK (self); + for (item = self->priv->change_queue.head; item; item = item->next) { + data = item->data; + + if (data->element == element) { + if (data->action == GST_INSERT_BIN_ACTION_ADD) + g_queue_delete_link (&self->priv->change_queue, item); + break; + } + data = NULL; + } + GST_OBJECT_UNLOCK (self); + + if (data) { + gst_object_ref (element); + gst_insert_bin_change_data_complete (self, data, TRUE); + if (callback) + callback (self, element, TRUE, user_data); + gst_object_unref (element); + } else { + if (callback) + callback (self, element, FALSE, user_data); + } + return; + } + + gst_object_ref (element); + + gst_insert_bin_add_operation (self, element, GST_INSERT_BIN_ACTION_REMOVE, + NULL, FALSE, callback, user_data); +} + +/** + * gst_insert_bin_new: + * @name: (allow-none): The name of the new #GstInsertBin element (or %NULL) + * + * Creates a new #GstInsertBin + * + * Returns: The new #GstInsertBin + */ + +GstElement * +gst_insert_bin_new (const gchar * name) +{ + if (name) + return g_object_new (GST_TYPE_INSERT_BIN, "name", name, NULL); + else + return g_object_new (GST_TYPE_INSERT_BIN, NULL); +} diff --git a/gst-libs/gst/insertbin/gstinsertbin.h b/gst-libs/gst/insertbin/gstinsertbin.h new file mode 100644 index 000000000..21e5d5430 --- /dev/null +++ b/gst-libs/gst/insertbin/gstinsertbin.h @@ -0,0 +1,101 @@ +/* + * GStreamer + * + * Copyright 2013 Collabora Ltd + * @author: Olivier Crete <olivier.crete@collabora.com> + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + + + +#ifndef __GST_INSERT_BIN_H__ +#define __GST_INSERT_BIN_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS +#define GST_TYPE_INSERT_BIN (gst_insert_bin_get_type()) +#define GST_INSERT_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_INSERT_BIN,GstInsertBin)) +#define GST_IS_INSERT_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_INSERT_BIN)) +#define GST_INSERT_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_INSERT_BIN,GstInsertBinClass)) +#define GST_IS_INSERT_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_INSERT_BIN)) +#define GST_INSERT_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_INSERT_BIN,GstInsertBinClass)) +typedef struct _GstInsertBin GstInsertBin; +typedef struct _GstInsertBinClass GstInsertBinClass; +typedef struct _GstInsertBinPrivate GstInsertBinPrivate; + +/** + * GstInsertBinCallback: + * @insertbin: A #GstInsertBin + * @element: The #GstElement on which the operation was performed + * @success: %TRUE if the operation was successful + * @user_data: The user data passed + * + * This is the prototype of callbacks to be called when the operation completes. + * It could be called at any time, including as a re-entrant call while the + * operation is requested. + */ + +typedef void (*GstInsertBinCallback) (GstInsertBin *insertbin, + GstElement *element, + gboolean success, + gpointer user_data); + +/** + * GstInsertBin: + * + * The object structure. + */ +struct _GstInsertBin +{ + GstBin parent; + + /*< private >*/ + GstInsertBinPrivate *priv; +}; + +/** + * GstInsertBinClass: + * + * The object class structure. + */ +struct _GstInsertBinClass +{ + GstBinClass parent_class; +}; + +GType gst_insert_bin_get_type (void); + +GstElement *gst_insert_bin_new (const gchar * name); + +void gst_insert_bin_prepend (GstInsertBin * self, GstElement * element, + GstInsertBinCallback callback, gpointer user_data); +void gst_insert_bin_append (GstInsertBin * self, GstElement * element, + GstInsertBinCallback callback, gpointer user_data); +void gst_insert_bin_insert_before (GstInsertBin * self, + GstElement * element, GstElement * sibling, + GstInsertBinCallback callback, gpointer user_data); +void gst_insert_bin_insert_after (GstInsertBin * self, + GstElement * element, GstElement * sibling, + GstInsertBinCallback callback, gpointer user_data); +void gst_insert_bin_remove (GstInsertBin * self, GstElement * element, + GstInsertBinCallback callback, gpointer user_data); + + +G_END_DECLS +#endif /* __GST_INSERT_BIN_H__ */ diff --git a/gst-plugins-bad.spec.in b/gst-plugins-bad.spec.in index e20fd5341..08d690716 100644 --- a/gst-plugins-bad.spec.in +++ b/gst-plugins-bad.spec.in @@ -201,6 +201,7 @@ make ERROR_CFLAGS='' ERROR_CXXFLAGS='' %{_libdir}/libgstphotography-%{majorminor}.so.* %{_libdir}/libgstsignalprocessor-%{majorminor}.so.* %{_libdir}/libgstcodecparsers-%{majorminor}.so.* +%{_libdir}/libgstinsertbin-%{majorminor}.so.* # Plugins without external dependencies %{_libdir}/gstreamer-%{majorminor}/libgstadpcmdec.so %{_libdir}/gstreamer-%{majorminor}/libgstadpcmenc.so @@ -353,10 +354,12 @@ make ERROR_CFLAGS='' ERROR_CXXFLAGS='' %{_libdir}/libgstbasevideo-%{majorminor}.so %{_libdir}/libgstphotography-%{majorminor}.so %{_libdir}/libgstsignalprocessor-%{majorminor}.so +%{_libdir}/libgstinsertbin-%{majorminor}.so %{_libdir}/libgstcodecparsers-%{majorminor}.so %{_libdir}/libgstbasecamerabinsrc-%{majorminor}.so %{_includedir}/gstreamer-%{majorminor}/gst/interfaces/photography* %{_includedir}/gstreamer-%{majorminor}/gst/codecparsers +%{_includedir}/gstreamer-%{majorminor}/gst/insertbin %{_includedir}/gstreamer-%{majorminor}/gst/signalprocessor %{_includedir}/gstreamer-%{majorminor}/gst/video %{_includedir}/gstreamer-%{majorminor}/gst/basecamerabinsrc/gstbasecamerasrc.h @@ -367,6 +370,7 @@ make ERROR_CFLAGS='' ERROR_CXXFLAGS='' # pkg-config files %{_libdir}/pkgconfig/gstreamer-plugins-bad-%{majorminor}.pc %{_libdir}/pkgconfig/gstreamer-codecparsers-%{majorminor}.pc +%{_libdir}/pkgconfig/gstreamer-insertbin-%{majorminor}.pc %{_libdir}/pkgconfig/gstreamer-basevideo-%{majorminor}.pc %files devel-docs diff --git a/pkgconfig/Makefile.am b/pkgconfig/Makefile.am index 6ab9bfd72..93f7de34c 100644 --- a/pkgconfig/Makefile.am +++ b/pkgconfig/Makefile.am @@ -2,11 +2,13 @@ pcverfiles = \ gstreamer-plugins-bad-@GST_API_VERSION@.pc \ gstreamer-codecparsers-@GST_API_VERSION@.pc \ + gstreamer-insertbin-@GST_API_VERSION@.pc \ gstreamer-basevideo-@GST_API_VERSION@.pc pcverfiles_uninstalled = \ gstreamer-plugins-bad-@GST_API_VERSION@-uninstalled.pc \ gstreamer-codecparsers-@GST_API_VERSION@-uninstalled.pc \ + gstreamer-insertbin-@GST_API_VERSION@-uninstalled.pc \ gstreamer-basevideo-@GST_API_VERSION@-uninstalled.pc all-local: $(pcverfiles) $(pcverfiles_uninstalled) @@ -28,6 +30,7 @@ CLEANFILES = $(pcverfiles) $(pcverfiles_uninstalled) pcinfiles = \ gstreamer-plugins-bad.pc.in gstreamer-plugins-bad-uninstalled.pc.in \ gstreamer-codecparsers.pc.in gstreamer-codecparsers-uninstalled.pc.in \ + gstreamer-insertbin.pc.in gstreamer-insertbin-uninstalled.pc.in \ gstreamer-basevideo.pc.in gstreamer-basevideo-uninstalled.pc.in DISTCLEANFILES = $(pcinfiles:.in=) diff --git a/pkgconfig/gstreamer-insertbin-uninstalled.pc.in b/pkgconfig/gstreamer-insertbin-uninstalled.pc.in new file mode 100644 index 000000000..0a01e7996 --- /dev/null +++ b/pkgconfig/gstreamer-insertbin-uninstalled.pc.in @@ -0,0 +1,12 @@ +prefix= +exec_prefix= +libdir=${pcfiledir}/../gst-libs/gst/insertbin +includedir=${pcfiledir}/../gst-libs + +Name: GStreamer Insert Bin, Uninstalled +Description: Bin to automatically and insertally link elements, uninstalled +Requires: gstreamer-@GST_API_VERSION@ +Version: @VERSION@ +Libs: -L${libdir} ${libdir}/libgstinsertbin-@GST_API_VERSION@.la +Cflags: -I${includedir} + diff --git a/pkgconfig/gstreamer-insertbin.pc.in b/pkgconfig/gstreamer-insertbin.pc.in new file mode 100644 index 000000000..cbf76d327 --- /dev/null +++ b/pkgconfig/gstreamer-insertbin.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@/gstreamer-@GST_API_VERSION@ + +Name: GStreamer Insert Bin +Description: Bin to automatically and insertally link elements +Requires: gstreamer-@GST_API_VERSION@ +Version: @VERSION@ +Libs: -L${libdir} -lgstinsertbin-@GST_API_VERSION@ +Cflags: -I${includedir} + diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 51d560eca..66a72dac5 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -232,6 +232,7 @@ check_PROGRAMS = \ elements/viewfinderbin \ $(check_zbar) \ $(check_orc) \ + libs/insertbin \ $(EXPERIMENTAL_CHECKS) noinst_HEADERS = elements/mxfdemux.h @@ -365,6 +366,13 @@ elements_uvch264demux_CFLAGS = -DUVCH264DEMUX_DATADIR="$(srcdir)/elements/uvch26 pipelines_streamheader_CFLAGS = $(GIO_CFLAGS) $(AM_CFLAGS) pipelines_streamheader_LDADD = $(GIO_LIBS) $(LDADD) +libs_insertbin_LDADD = \ + $(GST_PLUGINS_BAD_LIBS) $(GST_BASE_LIBS) $(GST_LIBS) $(LDADD) \ + $(top_builddir)/gst-libs/gst/insertbin/libgstinsertbin-@GST_API_VERSION@.la +libs_insertbin_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(AM_CFLAGS) + + EXTRA_DIST = gst-plugins-bad.supp $(uvch264_dist_data) orc_bayer_CFLAGS = $(ORC_CFLAGS) diff --git a/tests/check/libs/.gitignore b/tests/check/libs/.gitignore index 238cb3d11..728f9e9c2 100644 --- a/tests/check/libs/.gitignore +++ b/tests/check/libs/.gitignore @@ -2,3 +2,4 @@ h264parser mpegvideoparser vc1parser +insertbin diff --git a/tests/check/libs/insertbin.c b/tests/check/libs/insertbin.c new file mode 100644 index 000000000..b98c94a54 --- /dev/null +++ b/tests/check/libs/insertbin.c @@ -0,0 +1,354 @@ +/* GStreamer + * + * unit test for autoconvert element + * Copyright (C) 2009 Jan Schmidt <thaytan@noraisin.net> + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <gst/check/gstcheck.h> +#include <gst/insertbin/gstinsertbin.h> + +GstStaticPadTemplate sinkpad_template = GST_STATIC_PAD_TEMPLATE ("sink", // the name of the pad + GST_PAD_SINK, // the direction of the pad + GST_PAD_ALWAYS, // when this pad will be present + GST_STATIC_CAPS ( // the capabilities of the padtemplate + "video/test") + ); + +GstStaticPadTemplate srcpad_template = GST_STATIC_PAD_TEMPLATE ("src", // the name of the pad + GST_PAD_SRC, // the direction of the pad + GST_PAD_ALWAYS, // when this pad will be present + GST_STATIC_CAPS ( // the capabilities of the padtemplate + "video/test") + ); + +gint cb_count = 0; + +GMutex mutex; +GCond cond; + +GThread *push_thread = NULL; +gulong block_probe_id = 0; +gboolean is_blocked = FALSE; + +static void +success_cb (GstInsertBin * insertbin, GstElement * element, gboolean success, + gpointer user_data) +{ + fail_unless (g_thread_self () != push_thread); + fail_unless (success == TRUE); + fail_unless (GST_IS_ELEMENT (insertbin)); + fail_unless (GST_IS_ELEMENT (element)); + cb_count++; +} + +static void +fail_cb (GstInsertBin * insertbin, GstElement * element, gboolean success, + gpointer user_data) +{ + fail_unless (GST_IS_ELEMENT (insertbin)); + fail_unless (GST_IS_ELEMENT (element)); + fail_unless (success == FALSE); + cb_count++; +} + +/* + * This is a macro so the line number of any error is more useful + */ +#define push_buffer(srcpad, count) \ + { \ + fail_unless (cb_count == 0); \ + gst_pad_push (srcpad, gst_buffer_new ()); \ + fail_unless (g_list_length (buffers) == 1); \ + gst_check_drop_buffers (); \ + fail_unless (cb_count == (count)); \ + cb_count = 0; \ + } + +#define check_reset_cb_count(count) \ + { \ + fail_unless (cb_count == (count)); \ + cb_count = 0; \ + } + +static gpointer +thread_push_buffer (gpointer data) +{ + GstPad *pad = data; + + gst_pad_push (pad, gst_buffer_new ()); + return NULL; +} + +static GstPadProbeReturn +got_buffer_block (GstPad * pad, GstPadProbeInfo * info, gpointer data) +{ + g_mutex_lock (&mutex); + is_blocked = TRUE; + g_cond_broadcast (&cond); + g_mutex_unlock (&mutex); + + return GST_PAD_PROBE_OK; +} + +#define block_thread() \ +{ \ + fail_unless (cb_count == 0); \ + fail_unless (block_probe_id == 0); \ + fail_unless (is_blocked == FALSE); \ + fail_unless (push_thread == NULL); \ + block_probe_id = gst_pad_add_probe (sinkpad, \ + GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER, \ + got_buffer_block, NULL, NULL); \ + push_thread = g_thread_new ("push block", thread_push_buffer, srcpad); \ + fail_unless (push_thread != NULL); \ + g_mutex_lock (&mutex); \ + while (is_blocked == FALSE) \ + g_cond_wait (&cond, &mutex); \ + g_mutex_unlock (&mutex); \ +} + +#define unblock_thread() \ +{ \ + fail_unless (cb_count == 0); \ + fail_unless (push_thread != NULL); \ + fail_unless (is_blocked == TRUE); \ + fail_unless (block_probe_id != 0); \ + gst_pad_remove_probe (sinkpad, block_probe_id); \ + g_thread_join (push_thread); \ + fail_unless (g_list_length (buffers) == 1); \ + gst_check_drop_buffers (); \ + block_probe_id = 0; \ + push_thread = NULL; \ + is_blocked = FALSE; \ +} + +GST_START_TEST (test_insertbin_simple) +{ + GstElement *insertbin; + GstElement *elem; + GstElement *elem2; + GstElement *elem3; + GstElement *elem4; + GstPad *srcpad; + GstPad *sinkpad; + + g_mutex_init (&mutex); + g_cond_init (&cond); + + insertbin = gst_insert_bin_new (NULL); + fail_unless (insertbin != NULL); + ASSERT_OBJECT_REFCOUNT (insertbin, insertbin, 1); + srcpad = gst_check_setup_src_pad (insertbin, &srcpad_template); + sinkpad = gst_check_setup_sink_pad (insertbin, &sinkpad_template); + + g_assert (srcpad && sinkpad); + + ASSERT_CRITICAL (gst_insert_bin_append (GST_INSERT_BIN (insertbin), NULL, + NULL, NULL)); + ASSERT_CRITICAL (gst_insert_bin_append (GST_INSERT_BIN (insertbin), NULL, + fail_cb, NULL)); + fail_unless (cb_count == 0); + + + elem = gst_element_factory_make ("identity", NULL); + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem, success_cb, NULL); + check_reset_cb_count (1); + + gst_insert_bin_remove (GST_INSERT_BIN (insertbin), elem, success_cb, NULL); + check_reset_cb_count (1); + + fail_unless (gst_pad_set_active (srcpad, TRUE)); + fail_unless (gst_pad_set_active (sinkpad, TRUE)); + fail_unless (gst_element_set_state (insertbin, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + + fail_unless (cb_count == 0); + fail_unless (buffers == NULL); + + push_buffer (srcpad, 0); + + block_thread (); + elem = gst_element_factory_make ("identity", NULL); + gst_insert_bin_prepend (GST_INSERT_BIN (insertbin), elem, success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 1); + push_buffer (srcpad, 0); + + block_thread (); + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem, fail_cb, NULL); + check_reset_cb_count (1); + unblock_thread (); + push_buffer (srcpad, 0); + + block_thread (); + gst_insert_bin_remove (GST_INSERT_BIN (insertbin), elem, success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 1); + push_buffer (srcpad, 0); + + block_thread (); + elem = gst_element_factory_make ("identity", NULL); + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem, success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 1); + push_buffer (srcpad, 0); + + block_thread (); + elem2 = gst_element_factory_make ("identity", NULL); + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem2, success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 1); + push_buffer (srcpad, 0); + + block_thread (); + elem3 = gst_element_factory_make ("identity", NULL); + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem3, success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 1); + push_buffer (srcpad, 0); + + block_thread (); + elem4 = gst_element_factory_make ("identity", NULL); + gst_insert_bin_prepend (GST_INSERT_BIN (insertbin), elem4, success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 1); + push_buffer (srcpad, 0); + + block_thread (); + gst_insert_bin_remove (GST_INSERT_BIN (insertbin), elem3, success_cb, NULL); + gst_insert_bin_remove (GST_INSERT_BIN (insertbin), elem2, success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 1); + push_buffer (srcpad, 1); + push_buffer (srcpad, 0); + + block_thread (); + elem2 = gst_element_factory_make ("identity", NULL); + elem3 = gst_element_factory_make ("identity", NULL); + gst_insert_bin_insert_after (GST_INSERT_BIN (insertbin), elem2, elem, + success_cb, NULL); + gst_insert_bin_insert_before (GST_INSERT_BIN (insertbin), elem3, elem4, + success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 1); + push_buffer (srcpad, 1); + push_buffer (srcpad, 0); + + block_thread (); + gst_insert_bin_remove (GST_INSERT_BIN (insertbin), elem3, success_cb, NULL); + gst_insert_bin_remove (GST_INSERT_BIN (insertbin), elem2, success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 2); + push_buffer (srcpad, 0); + + block_thread (); + elem2 = gst_element_factory_make ("identity", NULL); + elem3 = gst_element_factory_make ("identity", NULL); + gst_insert_bin_insert_before (GST_INSERT_BIN (insertbin), elem3, elem4, + success_cb, NULL); + gst_insert_bin_insert_after (GST_INSERT_BIN (insertbin), elem2, elem, + success_cb, NULL); + unblock_thread (); + push_buffer (srcpad, 2); + push_buffer (srcpad, 0); + + block_thread (); + elem = gst_bin_new (NULL); + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem, fail_cb, NULL); + check_reset_cb_count (1); + unblock_thread (); + + block_thread (); + elem = gst_bin_new (NULL); + elem2 = gst_element_factory_make ("identity", NULL); + gst_bin_add (GST_BIN (elem), elem2); + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem2, fail_cb, NULL); + check_reset_cb_count (1); + gst_insert_bin_remove (GST_INSERT_BIN (insertbin), elem2, fail_cb, NULL); + check_reset_cb_count (1); + unblock_thread (); + gst_object_unref (elem); + + push_buffer (srcpad, 0); + + block_thread (); + elem = gst_element_factory_make ("identity", NULL); + elem2 = gst_element_factory_make ("identity", NULL); + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem, success_cb, NULL); + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem2, success_cb, NULL); + gst_insert_bin_remove (GST_INSERT_BIN (insertbin), elem2, success_cb, NULL); + check_reset_cb_count (2); + unblock_thread (); + push_buffer (srcpad, 1); + push_buffer (srcpad, 0); + + block_thread (); + elem = gst_element_factory_make ("identity", NULL); + elem2 = gst_element_factory_make ("identity", NULL); + gst_insert_bin_insert_before (GST_INSERT_BIN (insertbin), elem, elem2, + fail_cb, NULL); + check_reset_cb_count (1); + unblock_thread (); + push_buffer (srcpad, 0); + gst_object_unref (elem2); + + fail_unless (gst_element_set_state (insertbin, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + gst_pad_set_active (srcpad, FALSE); + gst_pad_set_active (sinkpad, FALSE); + + + elem = gst_element_factory_make ("identity", NULL); + gst_insert_bin_remove (GST_INSERT_BIN (insertbin), elem, fail_cb, NULL); + check_reset_cb_count (1); + + gst_insert_bin_append (GST_INSERT_BIN (insertbin), elem, success_cb, NULL); + check_reset_cb_count (1); + + gst_check_teardown_sink_pad (insertbin); + gst_check_teardown_src_pad (insertbin); + gst_check_teardown_element (insertbin); + + fail_unless (cb_count == 0); + + g_mutex_clear (&mutex); + g_cond_clear (&cond); +} + +GST_END_TEST; + + +static Suite * +insert_bin_suite (void) +{ + Suite *s = suite_create ("insertbin"); + TCase *tc_basic = tcase_create ("general"); + + suite_add_tcase (s, tc_basic); + tcase_add_test (tc_basic, test_insertbin_simple); + + return s; +} + + +GST_CHECK_MAIN (insert_bin); |