diff options
author | Nicolas Dechesne <n-dechesne@ti.com> | 2011-09-30 00:54:14 +0200 |
---|---|---|
committer | Nicolas Dechesne <n-dechesne@ti.com> | 2011-09-30 00:54:14 +0200 |
commit | beb43201c942afa12dfc4225218b61fcd6e90923 (patch) | |
tree | a1d85bac4a9ad32ed087d08ecd99ac3c03b9aa0a /gst |
Imported Upstream version 0.11.1upstream/0.11.1
Diffstat (limited to 'gst')
133 files changed, 89256 insertions, 0 deletions
diff --git a/gst/Makefile.am b/gst/Makefile.am new file mode 100644 index 0000000..dcb8c93 --- /dev/null +++ b/gst/Makefile.am @@ -0,0 +1,319 @@ +lib_LTLIBRARIES = libgstreamer-@GST_MAJORMINOR@.la + +if GST_DISABLE_REGISTRY +GST_REGISTRY_SRC = +else +GST_REGISTRY_SRC = gstregistrybinary.c +endif + +if GST_DISABLE_PARSE +SUBDIRS_PARSE = +GST_PARSE_LA = +else +SUBDIRS_PARSE = parse +GST_PARSE_LA = parse/libgstparse.la +endif + +if GST_DISABLE_TRACE +GST_TRACE_SRC = +else +GST_TRACE_SRC = gsttrace.c +endif + +if GST_DISABLE_PLUGIN +GST_PLUGIN_SRC = +else +GST_PLUGIN_SRC = gstplugin.c +endif + +SUBDIRS = $(SUBDIRS_PARSE) + +DIST_SUBDIRS = parse + +# make variables for all generated source and header files to make the +# distinction clear + +built_header_configure = gstconfig.h gstversion.h +built_header_make = gstenumtypes.h gstmarshal.h +built_source_make = gstenumtypes.c gstmarshal.c + +EXTRA_libgstreamer_@GST_MAJORMINOR@_la_SOURCES = \ + gstmarshal.list gsttrace.c \ + gstregistrybinary.c + + +# temporarily not used +# glib-compat.c + +libgstreamer_@GST_MAJORMINOR@_la_SOURCES = \ + gst.c \ + gstobject.c \ + gstbin.c \ + gstbuffer.c \ + gstbufferlist.c \ + gstbufferpool.c \ + gstbus.c \ + gstcaps.c \ + gstchildproxy.c \ + gstclock.c \ + gstdatetime.c \ + gstdebugutils.c \ + gstelement.c \ + gstelementfactory.c \ + gsterror.c \ + gstevent.c \ + gstfilter.c \ + gstformat.c \ + gstghostpad.c \ + gstindex.c \ + gstindexfactory.c \ + gstinfo.c \ + gstiterator.c \ + gstatomicqueue.c \ + gstmessage.c \ + gstmeta.c \ + gstmemory.c \ + gstminiobject.c \ + gstpad.c \ + gstpadtemplate.c \ + gstparamspecs.c \ + gstpipeline.c \ + gstplugin.c \ + gstpluginfeature.c \ + gstpluginloader.c \ + gstpoll.c \ + gstpreset.c \ + gstquark.c \ + gstquery.c \ + gstregistry.c \ + gstregistrychunks.c \ + gstsegment.c \ + gststructure.c \ + gstsystemclock.c \ + gsttaglist.c \ + gsttagsetter.c \ + gsttask.c \ + gsttaskpool.c \ + $(GST_TRACE_SRC) \ + gsttypefind.c \ + gsttypefindfactory.c \ + gsturi.c \ + gstutils.c \ + gstvalue.c \ + gstparse.c \ + $(GST_REGISTRY_SRC) + +# do not put files in the distribution that are generated +nodist_libgstreamer_@GST_MAJORMINOR@_la_SOURCES = $(built_source_make) + +# BUILT_SOURCES are built on make all/check/install before all other targets +BUILT_SOURCES = \ + $(built_header_configure) \ + $(built_header_make) \ + $(built_source_make) +# CLEANFILES is for files generated by make +CLEANFILES = $(built_header_make) $(built_source_make) $(as_dll_cleanfiles) *.gcno *.gcda *.gcov *.gcov.out +# DISTCLEANFILES is for files generated by configure +DISTCLEANFILES = $(built_header_configure) + +libgstreamer_@GST_MAJORMINOR@_la_CFLAGS = \ + -D_GNU_SOURCE \ + -DGST_EXPORTS \ + -DG_LOG_DOMAIN=g_log_domain_gstreamer \ + -DGST_MAJORMINOR=\""$(GST_MAJORMINOR)"\" \ + -DGST_DISABLE_DEPRECATED \ + $(VALGRIND_CFLAGS) \ + $(GST_ALL_CFLAGS) + +libgstreamer_@GST_MAJORMINOR@_la_LIBADD = \ + $(GST_PARSE_LA) \ + $(GST_ALL_LIBS) \ + $(WIN32_LIBS) \ + $(LIBM) + +libgstreamer_@GST_MAJORMINOR@_la_LDFLAGS = \ + $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) + +libgstreamer_@GST_MAJORMINOR@includedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst + +gst_headers = \ + gst.h \ + glib-compat.h \ + gstobject.h \ + gstbin.h \ + gstbuffer.h \ + gstbufferlist.h \ + gstbufferpool.h \ + gstbus.h \ + gstcaps.h \ + gstchildproxy.h \ + gstclock.h \ + gstcompat.h \ + gstdatetime.h \ + gstdebugutils.h \ + gstelement.h \ + gstelementmetadata.h \ + gstelementfactory.h \ + gsterror.h \ + gstevent.h \ + gstfilter.h \ + gstformat.h \ + gstghostpad.h \ + gstindex.h \ + gstindexfactory.h \ + gstinfo.h \ + gstiterator.h \ + gstatomicqueue.h \ + gstmacros.h \ + gstmessage.h \ + gstmeta.h \ + gstmemory.h \ + gstminiobject.h \ + gstpad.h \ + gstpadtemplate.h \ + gstparamspecs.h \ + gstpipeline.h \ + gstplugin.h \ + gstpluginfeature.h \ + gstpoll.h \ + gstpreset.h \ + gstquery.h \ + gstsegment.h \ + gststructure.h \ + gstsystemclock.h \ + gsttaglist.h \ + gsttagsetter.h \ + gsttask.h \ + gsttaskpool.h \ + gsttrace.h \ + gsttypefind.h \ + gsttypefindfactory.h \ + gsturi.h \ + gstutils.h \ + gstvalue.h \ + gstregistry.h \ + gstparse.h + +libgstreamer_@GST_MAJORMINOR@include_HEADERS = $(gst_headers) math-compat.h + +nodist_libgstreamer_@GST_MAJORMINOR@include_HEADERS = \ + $(built_header_configure) $(built_header_make) + +noinst_HEADERS = \ + gettext.h \ + glib-compat-private.h \ + gst-i18n-lib.h \ + gst-i18n-app.h \ + gstelementmetadata.h \ + gstpluginloader.h \ + gstquark.h \ + gstregistrybinary.h \ + gstregistrychunks.h \ + gst_private.h + +gstmarshal.h: gstmarshal.list + $(AM_V_GEN)glib-genmarshal --header --prefix=gst_marshal $(srcdir)/gstmarshal.list > gstmarshal.h.tmp && \ + mv gstmarshal.h.tmp gstmarshal.h + +gstmarshal.c: gstmarshal.list gst_private.h + $(AM_V_GEN)echo "#include \"gst_private.h\"" > gstmarshal.c.tmp && \ + echo "#include \"glib-object.h\"" >> gstmarshal.c.tmp && \ + echo "#include \"gstmarshal.h\"" >> gstmarshal.c.tmp && \ + glib-genmarshal --body --prefix=gst_marshal $(srcdir)/gstmarshal.list >> gstmarshal.c.tmp && \ + mv gstmarshal.c.tmp gstmarshal.c + +gstenumtypes.h: $(gst_headers) + $(AM_V_GEN)glib-mkenums \ + --fhead "#ifndef __GST_ENUM_TYPES_H__\n#define __GST_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \ + --fprod "\n/* enumerations from \"@filename@\" */\n" \ + --vhead "GType @enum_name@_get_type (void);\n#define GST_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \ + --ftail "G_END_DECLS\n\n#endif /* __GST_ENUM_TYPES_H__ */" \ + $^ > gstenumtypes.h + +gstenumtypes.c: $(gst_headers) + $(AM_V_GEN)glib-mkenums \ + --fhead "#include \"gst_private.h\"\n#include <gst/gst.h>\n#define C_ENUM(v) ((gint) v)\n#define C_FLAGS(v) ((guint) v)\n " \ + --fprod "\n/* enumerations from \"@filename@\" */" \ + --vhead "GType\n@enum_name@_get_type (void)\n{\n static gsize id = 0;\n static const G@Type@Value values[] = {" \ + --vprod " { C_@TYPE@(@VALUENAME@), \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n };\n\n if (g_once_init_enter (&id)) {\n GType tmp = g_@type@_register_static (\"@EnumName@\", values);\n g_once_init_leave (&id, tmp);\n }\n\n return (GType) id;\n}" \ + $^ > gstenumtypes.c + +%.c.gcov: .libs/libgstreamer_@GST_MAJORMINOR@_la-%.gcda %.c + $(GCOV) -b -f -o $^ > $@.out + +gcov: $(libgstreamer_@GST_MAJORMINOR@_la_SOURCES:=.gcov) + +Android.mk: Makefile.am + androgenizer -:PROJECT gstreamer -:SHARED libgstreamer-@GST_MAJORMINOR@ \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstreamer_@GST_MAJORMINOR@_la_SOURCES) \ + $(nodist_libgstreamer_@GST_MAJORMINOR@_la_SOURCES) \ + -:CFLAGS $(DEFS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) \ + -:LDFLAGS $(libgstreamer_@GST_MAJORMINOR@_la_LDFLAGS) \ + $(libgstreamer_@GST_MAJORMINOR@_la_LIBADD) \ + -ldl \ + -:SUBDIR gst/parse \ + -:HEADER_TARGET gstreamer-@GST_MAJORMINOR@/gst \ + -:HEADERS $(libgstreamer_@GST_MAJORMINOR@include_HEADERS) \ + -:LIBFILTER_STATIC gstparse \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + > $@ + +if HAVE_INTROSPECTION +BUILT_GIRSOURCES = Gst-@GST_MAJORMINOR@.gir + +gir_headers=$(patsubst %,$(srcdir)/%, $(libgstreamer_@GST_MAJORMINOR@include_HEADERS)) +gir_sources=$(patsubst %,$(srcdir)/%, $(libgstreamer_@GST_MAJORMINOR@_la_SOURCES)) + +Gst-@GST_MAJORMINOR@.gir: $(INTROSPECTION_SCANNER) libgstreamer-@GST_MAJORMINOR@.la + $(AM_V_GEN)GST_PLUGIN_SYSTEM_PATH="" GST_PLUGIN_PATH="" GST_REGISTRY_UPDATE=no \ + $(INTROSPECTION_SCANNER) -v --namespace Gst \ + --nsversion=@GST_MAJORMINOR@ \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + -DIN_GOBJECT_INTROSPECTION=1 \ + -DGST_USE_UNSTABLE_API \ + --c-include='gst/gst.h' \ + --library=libgstreamer-0.11.la \ + --include=GLib-2.0 \ + --include=GObject-2.0 \ + --include=GModule-2.0 \ + --libtool="$(top_builddir)/libtool" \ + --pkg glib-2.0 \ + --pkg gobject-2.0 \ + --pkg gmodule-no-export-2.0 \ + --pkg gthread-2.0 \ + --pkg-export gstreamer-@GST_MAJORMINOR@ \ + --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)$(INTROSPECTION_COMPILER) --includedir=$(srcdir) --includedir=$(builddir) $(INTROSPECTION_COMPILER_OPTS) $< -o $(@F) + +CLEANFILES += $(BUILT_GIRSOURCES) $(typelibs_DATA) +endif + +# try to prevent packaging errors +check-libexecdir-consistency: + @if test "${GST_PLUGIN_SCANNER_INSTALLED}" != "${libexecdir}/gstreamer-$(GST_MAJORMINOR)/gst-plugin-scanner"; then \ + echo "*** Inconsistent libexecdir! Please use ./configure --libexecdir=/foo/bar"; \ + echo "*** to set the libexecdir and not make libexecdir=/foo/bar or the like."; \ + echo "*** The same goes for prefix, libdir etc."; \ + echo "*** ${GST_PLUGIN_SCANNER_INSTALLED} != ${libexecdir}/gstreamer-$(GST_MAJORMINOR)/gst-plugin-scanner"; \ + exit 1; \ + fi + +all-local: check-libexecdir-consistency diff --git a/gst/Makefile.in b/gst/Makefile.in new file mode 100644 index 0000000..aa21f3a --- /dev/null +++ b/gst/Makefile.in @@ -0,0 +1,1879 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +@HAVE_INTROSPECTION_TRUE@am__append_1 = $(BUILT_GIRSOURCES) $(typelibs_DATA) +subdir = gst +DIST_COMMON = $(libgstreamer_@GST_MAJORMINOR@include_HEADERS) \ + $(noinst_HEADERS) $(srcdir)/Makefile.am $(srcdir)/Makefile.in \ + $(srcdir)/gstconfig.h.in $(srcdir)/gstversion.h.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/common/m4/as-ac-expand.m4 \ + $(top_srcdir)/common/m4/as-auto-alt.m4 \ + $(top_srcdir)/common/m4/as-compiler-flag.m4 \ + $(top_srcdir)/common/m4/as-docbook.m4 \ + $(top_srcdir)/common/m4/as-libtool.m4 \ + $(top_srcdir)/common/m4/as-python.m4 \ + $(top_srcdir)/common/m4/as-scrub-include.m4 \ + $(top_srcdir)/common/m4/as-version.m4 \ + $(top_srcdir)/common/m4/ax_create_stdint_h.m4 \ + $(top_srcdir)/common/m4/gst-arch.m4 \ + $(top_srcdir)/common/m4/gst-args.m4 \ + $(top_srcdir)/common/m4/gst-check.m4 \ + $(top_srcdir)/common/m4/gst-doc.m4 \ + $(top_srcdir)/common/m4/gst-error.m4 \ + $(top_srcdir)/common/m4/gst-feature.m4 \ + $(top_srcdir)/common/m4/gst-function.m4 \ + $(top_srcdir)/common/m4/gst-gettext.m4 \ + $(top_srcdir)/common/m4/gst-glib2.m4 \ + $(top_srcdir)/common/m4/gst-package-release-datetime.m4 \ + $(top_srcdir)/common/m4/gst-parser.m4 \ + $(top_srcdir)/common/m4/gst-platform.m4 \ + $(top_srcdir)/common/m4/gst-plugin-docs.m4 \ + $(top_srcdir)/common/m4/gst-plugindir.m4 \ + $(top_srcdir)/common/m4/gst.m4 \ + $(top_srcdir)/common/m4/gtk-doc.m4 \ + $(top_srcdir)/common/m4/introspection.m4 \ + $(top_srcdir)/common/m4/pkg.m4 \ + $(top_srcdir)/m4/check-checks.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = gstconfig.h gstversion.h +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(girdir)" \ + "$(DESTDIR)$(typelibsdir)" \ + "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)" \ + "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +@GST_DISABLE_PARSE_FALSE@am__DEPENDENCIES_1 = parse/libgstparse.la +am__DEPENDENCIES_2 = +libgstreamer_@GST_MAJORMINOR@_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_2) \ + $(am__DEPENDENCIES_2) +am__libgstreamer_@GST_MAJORMINOR@_la_SOURCES_DIST = gst.c gstobject.c \ + gstbin.c gstbuffer.c gstbufferlist.c gstbufferpool.c gstbus.c \ + gstcaps.c gstchildproxy.c gstclock.c gstdatetime.c \ + gstdebugutils.c gstelement.c gstelementfactory.c gsterror.c \ + gstevent.c gstfilter.c gstformat.c gstghostpad.c gstindex.c \ + gstindexfactory.c gstinfo.c gstiterator.c gstatomicqueue.c \ + gstmessage.c gstmeta.c gstmemory.c gstminiobject.c gstpad.c \ + gstpadtemplate.c gstparamspecs.c gstpipeline.c gstplugin.c \ + gstpluginfeature.c gstpluginloader.c gstpoll.c gstpreset.c \ + gstquark.c gstquery.c gstregistry.c gstregistrychunks.c \ + gstsegment.c gststructure.c gstsystemclock.c gsttaglist.c \ + gsttagsetter.c gsttask.c gsttaskpool.c gsttrace.c \ + gsttypefind.c gsttypefindfactory.c gsturi.c gstutils.c \ + gstvalue.c gstparse.c gstregistrybinary.c +@GST_DISABLE_TRACE_FALSE@am__objects_1 = libgstreamer_@GST_MAJORMINOR@_la-gsttrace.lo +@GST_DISABLE_REGISTRY_FALSE@am__objects_2 = libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.lo +am_libgstreamer_@GST_MAJORMINOR@_la_OBJECTS = \ + libgstreamer_@GST_MAJORMINOR@_la-gst.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstobject.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstbin.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstbus.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstcaps.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstclock.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstelement.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gsterror.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstevent.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstfilter.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstformat.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstindex.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstinfo.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstiterator.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstmessage.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstmeta.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstmemory.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstpad.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstplugin.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstpoll.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstpreset.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstquark.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstquery.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstregistry.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstsegment.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gststructure.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gsttask.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.lo \ + $(am__objects_1) \ + libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gsturi.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstutils.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstvalue.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstparse.lo $(am__objects_2) +am__objects_3 = libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.lo \ + libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.lo +nodist_libgstreamer_@GST_MAJORMINOR@_la_OBJECTS = $(am__objects_3) +libgstreamer_@GST_MAJORMINOR@_la_OBJECTS = \ + $(am_libgstreamer_@GST_MAJORMINOR@_la_OBJECTS) \ + $(nodist_libgstreamer_@GST_MAJORMINOR@_la_OBJECTS) +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +libgstreamer_@GST_MAJORMINOR@_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) \ + $(libgstreamer_@GST_MAJORMINOR@_la_LDFLAGS) $(LDFLAGS) -o $@ +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libgstreamer_@GST_MAJORMINOR@_la_SOURCES) \ + $(EXTRA_libgstreamer_@GST_MAJORMINOR@_la_SOURCES) \ + $(nodist_libgstreamer_@GST_MAJORMINOR@_la_SOURCES) +DIST_SOURCES = $(am__libgstreamer_@GST_MAJORMINOR@_la_SOURCES_DIST) \ + $(EXTRA_libgstreamer_@GST_MAJORMINOR@_la_SOURCES) +RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ + html-recursive info-recursive install-data-recursive \ + install-dvi-recursive install-exec-recursive \ + install-html-recursive install-info-recursive \ + install-pdf-recursive install-ps-recursive install-recursive \ + installcheck-recursive installdirs-recursive pdf-recursive \ + ps-recursive uninstall-recursive +DATA = $(gir_DATA) $(typelibs_DATA) +HEADERS = $(libgstreamer_@GST_MAJORMINOR@include_HEADERS) \ + $(nodist_libgstreamer_@GST_MAJORMINOR@include_HEADERS) \ + $(noinst_HEADERS) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \ + $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \ + distdir +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BISON_PATH = @BISON_PATH@ +CAT_ENTRY_END = @CAT_ENTRY_END@ +CAT_ENTRY_START = @CAT_ENTRY_START@ +CC = @CC@ +CCAS = @CCAS@ +CCASDEPMODE = @CCASDEPMODE@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CHECK_MAJOR_VERSION = @CHECK_MAJOR_VERSION@ +CHECK_MICRO_VERSION = @CHECK_MICRO_VERSION@ +CHECK_MINOR_VERSION = @CHECK_MINOR_VERSION@ +CHECK_VERSION = @CHECK_VERSION@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIR = @DATADIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DEPRECATED_CFLAGS = @DEPRECATED_CFLAGS@ +DLLTOOL = @DLLTOOL@ +DOCBOOK_ROOT = @DOCBOOK_ROOT@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLE_SUBUNIT = @ENABLE_SUBUNIT@ +ERROR_CFLAGS = @ERROR_CFLAGS@ +EXEEXT = @EXEEXT@ +FFLAGS = @FFLAGS@ +FGREP = @FGREP@ +FLEX_PATH = @FLEX_PATH@ +GCOV = @GCOV@ +GCOV_CFLAGS = @GCOV_CFLAGS@ +GCOV_LIBS = @GCOV_LIBS@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_ONLY_CFLAGS = @GLIB_ONLY_CFLAGS@ +GLIB_ONLY_LIBS = @GLIB_ONLY_LIBS@ +GLIB_PREFIX = @GLIB_PREFIX@ +GLIB_REQ = @GLIB_REQ@ +GMP_LIBS = @GMP_LIBS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GREP = @GREP@ +GSL_LIBS = @GSL_LIBS@ +GST_AGE = @GST_AGE@ +GST_ALL_CFLAGS = @GST_ALL_CFLAGS@ +GST_ALL_CXXFLAGS = @GST_ALL_CXXFLAGS@ +GST_ALL_LDFLAGS = @GST_ALL_LDFLAGS@ +GST_ALL_LIBS = @GST_ALL_LIBS@ +GST_CURRENT = @GST_CURRENT@ +GST_DISABLE_ALLOC_TRACE_DEFINE = @GST_DISABLE_ALLOC_TRACE_DEFINE@ +GST_DISABLE_GST_DEBUG_DEFINE = @GST_DISABLE_GST_DEBUG_DEFINE@ +GST_DISABLE_NET_DEFINE = @GST_DISABLE_NET_DEFINE@ +GST_DISABLE_OPTION_PARSING_DEFINE = @GST_DISABLE_OPTION_PARSING_DEFINE@ +GST_DISABLE_PARSE_DEFINE = @GST_DISABLE_PARSE_DEFINE@ +GST_DISABLE_PLUGIN_DEFINE = @GST_DISABLE_PLUGIN_DEFINE@ +GST_DISABLE_REGISTRY_DEFINE = @GST_DISABLE_REGISTRY_DEFINE@ +GST_DISABLE_TRACE_DEFINE = @GST_DISABLE_TRACE_DEFINE@ +GST_HAVE_MONOTONIC_CLOCK_DEFINE = @GST_HAVE_MONOTONIC_CLOCK_DEFINE@ +GST_HAVE_POSIX_TIMERS_DEFINE = @GST_HAVE_POSIX_TIMERS_DEFINE@ +GST_HAVE_UNALIGNED_ACCESS_DEFINE = @GST_HAVE_UNALIGNED_ACCESS_DEFINE@ +GST_LEVEL_DEFAULT = @GST_LEVEL_DEFAULT@ +GST_LIBVERSION = @GST_LIBVERSION@ +GST_LIB_LDFLAGS = @GST_LIB_LDFLAGS@ +GST_LICENSE = @GST_LICENSE@ +GST_LT_LDFLAGS = @GST_LT_LDFLAGS@ +GST_MAJORMINOR = @GST_MAJORMINOR@ +GST_OBJ_CFLAGS = @GST_OBJ_CFLAGS@ +GST_OBJ_CXXFLAGS = @GST_OBJ_CXXFLAGS@ +GST_OBJ_LIBS = @GST_OBJ_LIBS@ +GST_OPTION_CFLAGS = @GST_OPTION_CFLAGS@ +GST_OPTION_CXXFLAGS = @GST_OPTION_CXXFLAGS@ +GST_PACKAGE_NAME = @GST_PACKAGE_NAME@ +GST_PACKAGE_ORIGIN = @GST_PACKAGE_ORIGIN@ +GST_PKG_CONFIG_PATH = @GST_PKG_CONFIG_PATH@ +GST_PKG_DEPS = @GST_PKG_DEPS@ +GST_PLUGIN_LDFLAGS = @GST_PLUGIN_LDFLAGS@ +GST_PLUGIN_SCANNER_INSTALLED = @GST_PLUGIN_SCANNER_INSTALLED@ +GST_PRINTF_EXTENSION_POINTER_FORMAT_DEFINE = @GST_PRINTF_EXTENSION_POINTER_FORMAT_DEFINE@ +GST_PRINTF_EXTENSION_SEGMENT_FORMAT_DEFINE = @GST_PRINTF_EXTENSION_SEGMENT_FORMAT_DEFINE@ +GST_REGISTRY_DOC_TYPES = @GST_REGISTRY_DOC_TYPES@ +GST_REVISION = @GST_REVISION@ +GST_USING_PRINTF_EXTENSION_DEFINE = @GST_USING_PRINTF_EXTENSION_DEFINE@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +HAVE_DOCBOOK2HTML = @HAVE_DOCBOOK2HTML@ +HAVE_DOCBOOK2PS = @HAVE_DOCBOOK2PS@ +HAVE_DVIPS = @HAVE_DVIPS@ +HAVE_EPSTOPDF = @HAVE_EPSTOPDF@ +HAVE_FIG2DEV = @HAVE_FIG2DEV@ +HAVE_GMP = @HAVE_GMP@ +HAVE_GSL = @HAVE_GSL@ +HAVE_JADETEX = @HAVE_JADETEX@ +HAVE_PNGTOPNM = @HAVE_PNGTOPNM@ +HAVE_PNMTOPS = @HAVE_PNMTOPS@ +HAVE_PS2PDF = @HAVE_PS2PDF@ +HAVE_XMLLINT = @HAVE_XMLLINT@ +HOST_CPU = @HOST_CPU@ +HTML_DIR = @HTML_DIR@ +INET_ATON_LIBS = @INET_ATON_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTROSPECTION_CFLAGS = @INTROSPECTION_CFLAGS@ +INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ +INTROSPECTION_GENERATE = @INTROSPECTION_GENERATE@ +INTROSPECTION_GIRDIR = @INTROSPECTION_GIRDIR@ +INTROSPECTION_LIBS = @INTROSPECTION_LIBS@ +INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@ +INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ +INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBDIR = @LIBDIR@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOCALEDIR = @LOCALEDIR@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@ +PACKAGE_VERSION_MICRO = @PACKAGE_VERSION_MICRO@ +PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@ +PACKAGE_VERSION_NANO = @PACKAGE_VERSION_NANO@ +PACKAGE_VERSION_RELEASE = @PACKAGE_VERSION_RELEASE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL_PATH = @PERL_PATH@ +PKG_CONFIG = @PKG_CONFIG@ +PLUGINDIR = @PLUGINDIR@ +POSUB = @POSUB@ +PROFILE_CFLAGS = @PROFILE_CFLAGS@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +USE_NLS = @USE_NLS@ +VALGRIND_CFLAGS = @VALGRIND_CFLAGS@ +VALGRIND_LIBS = @VALGRIND_LIBS@ +VALGRIND_PATH = @VALGRIND_PATH@ +VERSION = @VERSION@ +WARNING_CFLAGS = @WARNING_CFLAGS@ +WIN32_LIBS = @WIN32_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +XML_CATALOG = @XML_CATALOG@ +XSLTPROC = @XSLTPROC@ +XSLTPROC_FLAGS = @XSLTPROC_FLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +lt_ECHO = @lt_ECHO@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +plugindir = @plugindir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +lib_LTLIBRARIES = libgstreamer-@GST_MAJORMINOR@.la +@GST_DISABLE_REGISTRY_FALSE@GST_REGISTRY_SRC = gstregistrybinary.c +@GST_DISABLE_REGISTRY_TRUE@GST_REGISTRY_SRC = +@GST_DISABLE_PARSE_FALSE@SUBDIRS_PARSE = parse +@GST_DISABLE_PARSE_TRUE@SUBDIRS_PARSE = +@GST_DISABLE_PARSE_FALSE@GST_PARSE_LA = parse/libgstparse.la +@GST_DISABLE_PARSE_TRUE@GST_PARSE_LA = +@GST_DISABLE_TRACE_FALSE@GST_TRACE_SRC = gsttrace.c +@GST_DISABLE_TRACE_TRUE@GST_TRACE_SRC = +@GST_DISABLE_PLUGIN_FALSE@GST_PLUGIN_SRC = gstplugin.c +@GST_DISABLE_PLUGIN_TRUE@GST_PLUGIN_SRC = +SUBDIRS = $(SUBDIRS_PARSE) +DIST_SUBDIRS = parse + +# make variables for all generated source and header files to make the +# distinction clear +built_header_configure = gstconfig.h gstversion.h +built_header_make = gstenumtypes.h gstmarshal.h +built_source_make = gstenumtypes.c gstmarshal.c +EXTRA_libgstreamer_@GST_MAJORMINOR@_la_SOURCES = \ + gstmarshal.list gsttrace.c \ + gstregistrybinary.c + + +# temporarily not used +# glib-compat.c +libgstreamer_@GST_MAJORMINOR@_la_SOURCES = \ + gst.c \ + gstobject.c \ + gstbin.c \ + gstbuffer.c \ + gstbufferlist.c \ + gstbufferpool.c \ + gstbus.c \ + gstcaps.c \ + gstchildproxy.c \ + gstclock.c \ + gstdatetime.c \ + gstdebugutils.c \ + gstelement.c \ + gstelementfactory.c \ + gsterror.c \ + gstevent.c \ + gstfilter.c \ + gstformat.c \ + gstghostpad.c \ + gstindex.c \ + gstindexfactory.c \ + gstinfo.c \ + gstiterator.c \ + gstatomicqueue.c \ + gstmessage.c \ + gstmeta.c \ + gstmemory.c \ + gstminiobject.c \ + gstpad.c \ + gstpadtemplate.c \ + gstparamspecs.c \ + gstpipeline.c \ + gstplugin.c \ + gstpluginfeature.c \ + gstpluginloader.c \ + gstpoll.c \ + gstpreset.c \ + gstquark.c \ + gstquery.c \ + gstregistry.c \ + gstregistrychunks.c \ + gstsegment.c \ + gststructure.c \ + gstsystemclock.c \ + gsttaglist.c \ + gsttagsetter.c \ + gsttask.c \ + gsttaskpool.c \ + $(GST_TRACE_SRC) \ + gsttypefind.c \ + gsttypefindfactory.c \ + gsturi.c \ + gstutils.c \ + gstvalue.c \ + gstparse.c \ + $(GST_REGISTRY_SRC) + + +# do not put files in the distribution that are generated +nodist_libgstreamer_@GST_MAJORMINOR@_la_SOURCES = $(built_source_make) + +# BUILT_SOURCES are built on make all/check/install before all other targets +BUILT_SOURCES = \ + $(built_header_configure) \ + $(built_header_make) \ + $(built_source_make) + +# CLEANFILES is for files generated by make +CLEANFILES = $(built_header_make) $(built_source_make) \ + $(as_dll_cleanfiles) *.gcno *.gcda *.gcov *.gcov.out \ + $(am__append_1) +# DISTCLEANFILES is for files generated by configure +DISTCLEANFILES = $(built_header_configure) +libgstreamer_@GST_MAJORMINOR@_la_CFLAGS = \ + -D_GNU_SOURCE \ + -DGST_EXPORTS \ + -DG_LOG_DOMAIN=g_log_domain_gstreamer \ + -DGST_MAJORMINOR=\""$(GST_MAJORMINOR)"\" \ + -DGST_DISABLE_DEPRECATED \ + $(VALGRIND_CFLAGS) \ + $(GST_ALL_CFLAGS) + +libgstreamer_@GST_MAJORMINOR@_la_LIBADD = \ + $(GST_PARSE_LA) \ + $(GST_ALL_LIBS) \ + $(WIN32_LIBS) \ + $(LIBM) + +libgstreamer_@GST_MAJORMINOR@_la_LDFLAGS = \ + $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) + +libgstreamer_@GST_MAJORMINOR@includedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst +gst_headers = \ + gst.h \ + glib-compat.h \ + gstobject.h \ + gstbin.h \ + gstbuffer.h \ + gstbufferlist.h \ + gstbufferpool.h \ + gstbus.h \ + gstcaps.h \ + gstchildproxy.h \ + gstclock.h \ + gstcompat.h \ + gstdatetime.h \ + gstdebugutils.h \ + gstelement.h \ + gstelementmetadata.h \ + gstelementfactory.h \ + gsterror.h \ + gstevent.h \ + gstfilter.h \ + gstformat.h \ + gstghostpad.h \ + gstindex.h \ + gstindexfactory.h \ + gstinfo.h \ + gstiterator.h \ + gstatomicqueue.h \ + gstmacros.h \ + gstmessage.h \ + gstmeta.h \ + gstmemory.h \ + gstminiobject.h \ + gstpad.h \ + gstpadtemplate.h \ + gstparamspecs.h \ + gstpipeline.h \ + gstplugin.h \ + gstpluginfeature.h \ + gstpoll.h \ + gstpreset.h \ + gstquery.h \ + gstsegment.h \ + gststructure.h \ + gstsystemclock.h \ + gsttaglist.h \ + gsttagsetter.h \ + gsttask.h \ + gsttaskpool.h \ + gsttrace.h \ + gsttypefind.h \ + gsttypefindfactory.h \ + gsturi.h \ + gstutils.h \ + gstvalue.h \ + gstregistry.h \ + gstparse.h + +libgstreamer_@GST_MAJORMINOR@include_HEADERS = $(gst_headers) math-compat.h +nodist_libgstreamer_@GST_MAJORMINOR@include_HEADERS = \ + $(built_header_configure) $(built_header_make) + +noinst_HEADERS = \ + gettext.h \ + glib-compat-private.h \ + gst-i18n-lib.h \ + gst-i18n-app.h \ + gstelementmetadata.h \ + gstpluginloader.h \ + gstquark.h \ + gstregistrybinary.h \ + gstregistrychunks.h \ + gst_private.h + +@HAVE_INTROSPECTION_TRUE@BUILT_GIRSOURCES = Gst-@GST_MAJORMINOR@.gir +@HAVE_INTROSPECTION_TRUE@gir_headers = $(patsubst %,$(srcdir)/%, $(libgstreamer_@GST_MAJORMINOR@include_HEADERS)) +@HAVE_INTROSPECTION_TRUE@gir_sources = $(patsubst %,$(srcdir)/%, $(libgstreamer_@GST_MAJORMINOR@_la_SOURCES)) + +# INTROSPECTION_GIRDIR/INTROSPECTION_TYPELIBDIR aren't the right place to +# install anything - we need to install inside our prefix. +@HAVE_INTROSPECTION_TRUE@girdir = $(datadir)/gir-1.0 +@HAVE_INTROSPECTION_TRUE@gir_DATA = $(BUILT_GIRSOURCES) +@HAVE_INTROSPECTION_TRUE@typelibsdir = $(libdir)/girepository-1.0/ +@HAVE_INTROSPECTION_TRUE@typelibs_DATA = $(BUILT_GIRSOURCES:.gir=.typelib) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-recursive + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu gst/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu gst/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +gstconfig.h: $(top_builddir)/config.status $(srcdir)/gstconfig.h.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +gstversion.h: $(top_builddir)/config.status $(srcdir)/gstversion.h.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)" + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libgstreamer-@GST_MAJORMINOR@.la: $(libgstreamer_@GST_MAJORMINOR@_la_OBJECTS) $(libgstreamer_@GST_MAJORMINOR@_la_DEPENDENCIES) + $(AM_V_CCLD)$(libgstreamer_@GST_MAJORMINOR@_la_LINK) -rpath $(libdir) $(libgstreamer_@GST_MAJORMINOR@_la_OBJECTS) $(libgstreamer_@GST_MAJORMINOR@_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gst.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbin.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbus.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstcaps.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstclock.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstelement.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsterror.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstevent.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstfilter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstformat.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstindex.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstinfo.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstiterator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmemory.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmessage.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmeta.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstobject.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpad.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstparse.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstplugin.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpoll.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpreset.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstquark.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstquery.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistry.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstsegment.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gststructure.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttask.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttrace.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsturi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstutils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstvalue.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +libgstreamer_@GST_MAJORMINOR@_la-gst.lo: gst.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gst.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gst.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gst.lo `test -f 'gst.c' || echo '$(srcdir)/'`gst.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gst.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gst.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gst.c' object='libgstreamer_@GST_MAJORMINOR@_la-gst.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gst.lo `test -f 'gst.c' || echo '$(srcdir)/'`gst.c + +libgstreamer_@GST_MAJORMINOR@_la-gstobject.lo: gstobject.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstobject.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstobject.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstobject.lo `test -f 'gstobject.c' || echo '$(srcdir)/'`gstobject.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstobject.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstobject.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstobject.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstobject.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstobject.lo `test -f 'gstobject.c' || echo '$(srcdir)/'`gstobject.c + +libgstreamer_@GST_MAJORMINOR@_la-gstbin.lo: gstbin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstbin.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbin.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbin.lo `test -f 'gstbin.c' || echo '$(srcdir)/'`gstbin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbin.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbin.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstbin.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstbin.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbin.lo `test -f 'gstbin.c' || echo '$(srcdir)/'`gstbin.c + +libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.lo: gstbuffer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.lo `test -f 'gstbuffer.c' || echo '$(srcdir)/'`gstbuffer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstbuffer.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbuffer.lo `test -f 'gstbuffer.c' || echo '$(srcdir)/'`gstbuffer.c + +libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.lo: gstbufferlist.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.lo `test -f 'gstbufferlist.c' || echo '$(srcdir)/'`gstbufferlist.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstbufferlist.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbufferlist.lo `test -f 'gstbufferlist.c' || echo '$(srcdir)/'`gstbufferlist.c + +libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.lo: gstbufferpool.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.lo `test -f 'gstbufferpool.c' || echo '$(srcdir)/'`gstbufferpool.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstbufferpool.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbufferpool.lo `test -f 'gstbufferpool.c' || echo '$(srcdir)/'`gstbufferpool.c + +libgstreamer_@GST_MAJORMINOR@_la-gstbus.lo: gstbus.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstbus.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbus.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbus.lo `test -f 'gstbus.c' || echo '$(srcdir)/'`gstbus.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbus.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstbus.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstbus.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstbus.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstbus.lo `test -f 'gstbus.c' || echo '$(srcdir)/'`gstbus.c + +libgstreamer_@GST_MAJORMINOR@_la-gstcaps.lo: gstcaps.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstcaps.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstcaps.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstcaps.lo `test -f 'gstcaps.c' || echo '$(srcdir)/'`gstcaps.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstcaps.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstcaps.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstcaps.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstcaps.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstcaps.lo `test -f 'gstcaps.c' || echo '$(srcdir)/'`gstcaps.c + +libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.lo: gstchildproxy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.lo `test -f 'gstchildproxy.c' || echo '$(srcdir)/'`gstchildproxy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstchildproxy.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstchildproxy.lo `test -f 'gstchildproxy.c' || echo '$(srcdir)/'`gstchildproxy.c + +libgstreamer_@GST_MAJORMINOR@_la-gstclock.lo: gstclock.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstclock.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstclock.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstclock.lo `test -f 'gstclock.c' || echo '$(srcdir)/'`gstclock.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstclock.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstclock.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstclock.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstclock.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstclock.lo `test -f 'gstclock.c' || echo '$(srcdir)/'`gstclock.c + +libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.lo: gstdatetime.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.lo `test -f 'gstdatetime.c' || echo '$(srcdir)/'`gstdatetime.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstdatetime.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstdatetime.lo `test -f 'gstdatetime.c' || echo '$(srcdir)/'`gstdatetime.c + +libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.lo: gstdebugutils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.lo `test -f 'gstdebugutils.c' || echo '$(srcdir)/'`gstdebugutils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstdebugutils.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstdebugutils.lo `test -f 'gstdebugutils.c' || echo '$(srcdir)/'`gstdebugutils.c + +libgstreamer_@GST_MAJORMINOR@_la-gstelement.lo: gstelement.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstelement.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstelement.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstelement.lo `test -f 'gstelement.c' || echo '$(srcdir)/'`gstelement.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstelement.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstelement.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstelement.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstelement.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstelement.lo `test -f 'gstelement.c' || echo '$(srcdir)/'`gstelement.c + +libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.lo: gstelementfactory.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.lo `test -f 'gstelementfactory.c' || echo '$(srcdir)/'`gstelementfactory.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstelementfactory.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstelementfactory.lo `test -f 'gstelementfactory.c' || echo '$(srcdir)/'`gstelementfactory.c + +libgstreamer_@GST_MAJORMINOR@_la-gsterror.lo: gsterror.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gsterror.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsterror.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gsterror.lo `test -f 'gsterror.c' || echo '$(srcdir)/'`gsterror.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsterror.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsterror.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsterror.c' object='libgstreamer_@GST_MAJORMINOR@_la-gsterror.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gsterror.lo `test -f 'gsterror.c' || echo '$(srcdir)/'`gsterror.c + +libgstreamer_@GST_MAJORMINOR@_la-gstevent.lo: gstevent.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstevent.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstevent.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstevent.lo `test -f 'gstevent.c' || echo '$(srcdir)/'`gstevent.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstevent.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstevent.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstevent.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstevent.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstevent.lo `test -f 'gstevent.c' || echo '$(srcdir)/'`gstevent.c + +libgstreamer_@GST_MAJORMINOR@_la-gstfilter.lo: gstfilter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstfilter.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstfilter.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstfilter.lo `test -f 'gstfilter.c' || echo '$(srcdir)/'`gstfilter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstfilter.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstfilter.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstfilter.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstfilter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstfilter.lo `test -f 'gstfilter.c' || echo '$(srcdir)/'`gstfilter.c + +libgstreamer_@GST_MAJORMINOR@_la-gstformat.lo: gstformat.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstformat.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstformat.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstformat.lo `test -f 'gstformat.c' || echo '$(srcdir)/'`gstformat.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstformat.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstformat.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstformat.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstformat.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstformat.lo `test -f 'gstformat.c' || echo '$(srcdir)/'`gstformat.c + +libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.lo: gstghostpad.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.lo `test -f 'gstghostpad.c' || echo '$(srcdir)/'`gstghostpad.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstghostpad.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstghostpad.lo `test -f 'gstghostpad.c' || echo '$(srcdir)/'`gstghostpad.c + +libgstreamer_@GST_MAJORMINOR@_la-gstindex.lo: gstindex.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstindex.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstindex.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstindex.lo `test -f 'gstindex.c' || echo '$(srcdir)/'`gstindex.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstindex.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstindex.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstindex.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstindex.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstindex.lo `test -f 'gstindex.c' || echo '$(srcdir)/'`gstindex.c + +libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.lo: gstindexfactory.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.lo `test -f 'gstindexfactory.c' || echo '$(srcdir)/'`gstindexfactory.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstindexfactory.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstindexfactory.lo `test -f 'gstindexfactory.c' || echo '$(srcdir)/'`gstindexfactory.c + +libgstreamer_@GST_MAJORMINOR@_la-gstinfo.lo: gstinfo.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstinfo.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstinfo.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstinfo.lo `test -f 'gstinfo.c' || echo '$(srcdir)/'`gstinfo.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstinfo.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstinfo.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstinfo.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstinfo.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstinfo.lo `test -f 'gstinfo.c' || echo '$(srcdir)/'`gstinfo.c + +libgstreamer_@GST_MAJORMINOR@_la-gstiterator.lo: gstiterator.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstiterator.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstiterator.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstiterator.lo `test -f 'gstiterator.c' || echo '$(srcdir)/'`gstiterator.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstiterator.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstiterator.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstiterator.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstiterator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstiterator.lo `test -f 'gstiterator.c' || echo '$(srcdir)/'`gstiterator.c + +libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.lo: gstatomicqueue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.lo `test -f 'gstatomicqueue.c' || echo '$(srcdir)/'`gstatomicqueue.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstatomicqueue.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstatomicqueue.lo `test -f 'gstatomicqueue.c' || echo '$(srcdir)/'`gstatomicqueue.c + +libgstreamer_@GST_MAJORMINOR@_la-gstmessage.lo: gstmessage.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstmessage.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmessage.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstmessage.lo `test -f 'gstmessage.c' || echo '$(srcdir)/'`gstmessage.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmessage.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmessage.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstmessage.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstmessage.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstmessage.lo `test -f 'gstmessage.c' || echo '$(srcdir)/'`gstmessage.c + +libgstreamer_@GST_MAJORMINOR@_la-gstmeta.lo: gstmeta.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstmeta.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmeta.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstmeta.lo `test -f 'gstmeta.c' || echo '$(srcdir)/'`gstmeta.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmeta.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmeta.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstmeta.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstmeta.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstmeta.lo `test -f 'gstmeta.c' || echo '$(srcdir)/'`gstmeta.c + +libgstreamer_@GST_MAJORMINOR@_la-gstmemory.lo: gstmemory.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstmemory.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmemory.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstmemory.lo `test -f 'gstmemory.c' || echo '$(srcdir)/'`gstmemory.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmemory.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmemory.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstmemory.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstmemory.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstmemory.lo `test -f 'gstmemory.c' || echo '$(srcdir)/'`gstmemory.c + +libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.lo: gstminiobject.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.lo `test -f 'gstminiobject.c' || echo '$(srcdir)/'`gstminiobject.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstminiobject.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstminiobject.lo `test -f 'gstminiobject.c' || echo '$(srcdir)/'`gstminiobject.c + +libgstreamer_@GST_MAJORMINOR@_la-gstpad.lo: gstpad.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstpad.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpad.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpad.lo `test -f 'gstpad.c' || echo '$(srcdir)/'`gstpad.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpad.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpad.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstpad.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstpad.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpad.lo `test -f 'gstpad.c' || echo '$(srcdir)/'`gstpad.c + +libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.lo: gstpadtemplate.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.lo `test -f 'gstpadtemplate.c' || echo '$(srcdir)/'`gstpadtemplate.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstpadtemplate.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpadtemplate.lo `test -f 'gstpadtemplate.c' || echo '$(srcdir)/'`gstpadtemplate.c + +libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.lo: gstparamspecs.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.lo `test -f 'gstparamspecs.c' || echo '$(srcdir)/'`gstparamspecs.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstparamspecs.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstparamspecs.lo `test -f 'gstparamspecs.c' || echo '$(srcdir)/'`gstparamspecs.c + +libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.lo: gstpipeline.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.lo `test -f 'gstpipeline.c' || echo '$(srcdir)/'`gstpipeline.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstpipeline.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpipeline.lo `test -f 'gstpipeline.c' || echo '$(srcdir)/'`gstpipeline.c + +libgstreamer_@GST_MAJORMINOR@_la-gstplugin.lo: gstplugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstplugin.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstplugin.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstplugin.lo `test -f 'gstplugin.c' || echo '$(srcdir)/'`gstplugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstplugin.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstplugin.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstplugin.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstplugin.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstplugin.lo `test -f 'gstplugin.c' || echo '$(srcdir)/'`gstplugin.c + +libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.lo: gstpluginfeature.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.lo `test -f 'gstpluginfeature.c' || echo '$(srcdir)/'`gstpluginfeature.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstpluginfeature.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpluginfeature.lo `test -f 'gstpluginfeature.c' || echo '$(srcdir)/'`gstpluginfeature.c + +libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.lo: gstpluginloader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.lo `test -f 'gstpluginloader.c' || echo '$(srcdir)/'`gstpluginloader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstpluginloader.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpluginloader.lo `test -f 'gstpluginloader.c' || echo '$(srcdir)/'`gstpluginloader.c + +libgstreamer_@GST_MAJORMINOR@_la-gstpoll.lo: gstpoll.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstpoll.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpoll.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpoll.lo `test -f 'gstpoll.c' || echo '$(srcdir)/'`gstpoll.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpoll.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpoll.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstpoll.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstpoll.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpoll.lo `test -f 'gstpoll.c' || echo '$(srcdir)/'`gstpoll.c + +libgstreamer_@GST_MAJORMINOR@_la-gstpreset.lo: gstpreset.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstpreset.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpreset.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpreset.lo `test -f 'gstpreset.c' || echo '$(srcdir)/'`gstpreset.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpreset.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstpreset.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstpreset.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstpreset.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstpreset.lo `test -f 'gstpreset.c' || echo '$(srcdir)/'`gstpreset.c + +libgstreamer_@GST_MAJORMINOR@_la-gstquark.lo: gstquark.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstquark.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstquark.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstquark.lo `test -f 'gstquark.c' || echo '$(srcdir)/'`gstquark.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstquark.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstquark.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstquark.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstquark.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstquark.lo `test -f 'gstquark.c' || echo '$(srcdir)/'`gstquark.c + +libgstreamer_@GST_MAJORMINOR@_la-gstquery.lo: gstquery.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstquery.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstquery.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstquery.lo `test -f 'gstquery.c' || echo '$(srcdir)/'`gstquery.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstquery.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstquery.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstquery.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstquery.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstquery.lo `test -f 'gstquery.c' || echo '$(srcdir)/'`gstquery.c + +libgstreamer_@GST_MAJORMINOR@_la-gstregistry.lo: gstregistry.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstregistry.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistry.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstregistry.lo `test -f 'gstregistry.c' || echo '$(srcdir)/'`gstregistry.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistry.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistry.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstregistry.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstregistry.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstregistry.lo `test -f 'gstregistry.c' || echo '$(srcdir)/'`gstregistry.c + +libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.lo: gstregistrychunks.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.lo `test -f 'gstregistrychunks.c' || echo '$(srcdir)/'`gstregistrychunks.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstregistrychunks.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstregistrychunks.lo `test -f 'gstregistrychunks.c' || echo '$(srcdir)/'`gstregistrychunks.c + +libgstreamer_@GST_MAJORMINOR@_la-gstsegment.lo: gstsegment.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstsegment.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstsegment.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstsegment.lo `test -f 'gstsegment.c' || echo '$(srcdir)/'`gstsegment.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstsegment.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstsegment.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstsegment.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstsegment.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstsegment.lo `test -f 'gstsegment.c' || echo '$(srcdir)/'`gstsegment.c + +libgstreamer_@GST_MAJORMINOR@_la-gststructure.lo: gststructure.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gststructure.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gststructure.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gststructure.lo `test -f 'gststructure.c' || echo '$(srcdir)/'`gststructure.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gststructure.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gststructure.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gststructure.c' object='libgstreamer_@GST_MAJORMINOR@_la-gststructure.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gststructure.lo `test -f 'gststructure.c' || echo '$(srcdir)/'`gststructure.c + +libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.lo: gstsystemclock.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.lo `test -f 'gstsystemclock.c' || echo '$(srcdir)/'`gstsystemclock.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstsystemclock.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstsystemclock.lo `test -f 'gstsystemclock.c' || echo '$(srcdir)/'`gstsystemclock.c + +libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.lo: gsttaglist.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.lo `test -f 'gsttaglist.c' || echo '$(srcdir)/'`gsttaglist.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsttaglist.c' object='libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttaglist.lo `test -f 'gsttaglist.c' || echo '$(srcdir)/'`gsttaglist.c + +libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.lo: gsttagsetter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.lo `test -f 'gsttagsetter.c' || echo '$(srcdir)/'`gsttagsetter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsttagsetter.c' object='libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttagsetter.lo `test -f 'gsttagsetter.c' || echo '$(srcdir)/'`gsttagsetter.c + +libgstreamer_@GST_MAJORMINOR@_la-gsttask.lo: gsttask.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gsttask.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttask.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttask.lo `test -f 'gsttask.c' || echo '$(srcdir)/'`gsttask.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttask.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttask.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsttask.c' object='libgstreamer_@GST_MAJORMINOR@_la-gsttask.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttask.lo `test -f 'gsttask.c' || echo '$(srcdir)/'`gsttask.c + +libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.lo: gsttaskpool.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.lo `test -f 'gsttaskpool.c' || echo '$(srcdir)/'`gsttaskpool.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsttaskpool.c' object='libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttaskpool.lo `test -f 'gsttaskpool.c' || echo '$(srcdir)/'`gsttaskpool.c + +libgstreamer_@GST_MAJORMINOR@_la-gsttrace.lo: gsttrace.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gsttrace.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttrace.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttrace.lo `test -f 'gsttrace.c' || echo '$(srcdir)/'`gsttrace.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttrace.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttrace.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsttrace.c' object='libgstreamer_@GST_MAJORMINOR@_la-gsttrace.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttrace.lo `test -f 'gsttrace.c' || echo '$(srcdir)/'`gsttrace.c + +libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.lo: gsttypefind.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.lo `test -f 'gsttypefind.c' || echo '$(srcdir)/'`gsttypefind.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsttypefind.c' object='libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttypefind.lo `test -f 'gsttypefind.c' || echo '$(srcdir)/'`gsttypefind.c + +libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.lo: gsttypefindfactory.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.lo `test -f 'gsttypefindfactory.c' || echo '$(srcdir)/'`gsttypefindfactory.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsttypefindfactory.c' object='libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gsttypefindfactory.lo `test -f 'gsttypefindfactory.c' || echo '$(srcdir)/'`gsttypefindfactory.c + +libgstreamer_@GST_MAJORMINOR@_la-gsturi.lo: gsturi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gsturi.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsturi.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gsturi.lo `test -f 'gsturi.c' || echo '$(srcdir)/'`gsturi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsturi.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gsturi.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsturi.c' object='libgstreamer_@GST_MAJORMINOR@_la-gsturi.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gsturi.lo `test -f 'gsturi.c' || echo '$(srcdir)/'`gsturi.c + +libgstreamer_@GST_MAJORMINOR@_la-gstutils.lo: gstutils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstutils.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstutils.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstutils.lo `test -f 'gstutils.c' || echo '$(srcdir)/'`gstutils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstutils.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstutils.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstutils.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstutils.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstutils.lo `test -f 'gstutils.c' || echo '$(srcdir)/'`gstutils.c + +libgstreamer_@GST_MAJORMINOR@_la-gstvalue.lo: gstvalue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstvalue.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstvalue.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstvalue.lo `test -f 'gstvalue.c' || echo '$(srcdir)/'`gstvalue.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstvalue.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstvalue.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstvalue.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstvalue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstvalue.lo `test -f 'gstvalue.c' || echo '$(srcdir)/'`gstvalue.c + +libgstreamer_@GST_MAJORMINOR@_la-gstparse.lo: gstparse.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstparse.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstparse.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstparse.lo `test -f 'gstparse.c' || echo '$(srcdir)/'`gstparse.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstparse.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstparse.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstparse.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstparse.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstparse.lo `test -f 'gstparse.c' || echo '$(srcdir)/'`gstparse.c + +libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.lo: gstregistrybinary.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.lo `test -f 'gstregistrybinary.c' || echo '$(srcdir)/'`gstregistrybinary.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstregistrybinary.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstregistrybinary.lo `test -f 'gstregistrybinary.c' || echo '$(srcdir)/'`gstregistrybinary.c + +libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.lo: gstenumtypes.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.lo `test -f 'gstenumtypes.c' || echo '$(srcdir)/'`gstenumtypes.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstenumtypes.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstenumtypes.lo `test -f 'gstenumtypes.c' || echo '$(srcdir)/'`gstenumtypes.c + +libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.lo: gstmarshal.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.lo -MD -MP -MF $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.Tpo -c -o libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.lo `test -f 'gstmarshal.c' || echo '$(srcdir)/'`gstmarshal.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.Tpo $(DEPDIR)/libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstmarshal.c' object='libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o libgstreamer_@GST_MAJORMINOR@_la-gstmarshal.lo `test -f 'gstmarshal.c' || echo '$(srcdir)/'`gstmarshal.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-girDATA: $(gir_DATA) + @$(NORMAL_INSTALL) + test -z "$(girdir)" || $(MKDIR_P) "$(DESTDIR)$(girdir)" + @list='$(gir_DATA)'; test -n "$(girdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(girdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(girdir)" || exit $$?; \ + done + +uninstall-girDATA: + @$(NORMAL_UNINSTALL) + @list='$(gir_DATA)'; test -n "$(girdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(girdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(girdir)" && rm -f $$files +install-typelibsDATA: $(typelibs_DATA) + @$(NORMAL_INSTALL) + test -z "$(typelibsdir)" || $(MKDIR_P) "$(DESTDIR)$(typelibsdir)" + @list='$(typelibs_DATA)'; test -n "$(typelibsdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(typelibsdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(typelibsdir)" || exit $$?; \ + done + +uninstall-typelibsDATA: + @$(NORMAL_UNINSTALL) + @list='$(typelibs_DATA)'; test -n "$(typelibsdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(typelibsdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(typelibsdir)" && rm -f $$files +install-libgstreamer_@GST_MAJORMINOR@includeHEADERS: $(libgstreamer_@GST_MAJORMINOR@include_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(libgstreamer_@GST_MAJORMINOR@includedir)" || $(MKDIR_P) "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)" + @list='$(libgstreamer_@GST_MAJORMINOR@include_HEADERS)'; test -n "$(libgstreamer_@GST_MAJORMINOR@includedir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)" || exit $$?; \ + done + +uninstall-libgstreamer_@GST_MAJORMINOR@includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libgstreamer_@GST_MAJORMINOR@include_HEADERS)'; test -n "$(libgstreamer_@GST_MAJORMINOR@includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)" && rm -f $$files +install-nodist_libgstreamer_@GST_MAJORMINOR@includeHEADERS: $(nodist_libgstreamer_@GST_MAJORMINOR@include_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(libgstreamer_@GST_MAJORMINOR@includedir)" || $(MKDIR_P) "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)" + @list='$(nodist_libgstreamer_@GST_MAJORMINOR@include_HEADERS)'; test -n "$(libgstreamer_@GST_MAJORMINOR@includedir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)" || exit $$?; \ + done + +uninstall-nodist_libgstreamer_@GST_MAJORMINOR@includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(nodist_libgstreamer_@GST_MAJORMINOR@include_HEADERS)'; test -n "$(libgstreamer_@GST_MAJORMINOR@includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)" && rm -f $$files + +# This directory's subdirectories are mostly independent; you can cd +# into them and run `make' without going through this Makefile. +# To change the values of `make' variables: instead of editing Makefiles, +# (1) if the variable is set in `config.status', edit `config.status' +# (which will cause the Makefiles to be regenerated when you run `make'); +# (2) otherwise, pass the desired values on the `make' command line. +$(RECURSIVE_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +$(RECURSIVE_CLEAN_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + rev=''; for subdir in $$list; do \ + if test "$$subdir" = "."; then :; else \ + rev="$$subdir $$rev"; \ + fi; \ + done; \ + rev="$$rev ."; \ + target=`echo $@ | sed s/-recursive//`; \ + for subdir in $$rev; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done && test -z "$$fail" +tags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \ + done +ctags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-recursive +all-am: Makefile $(LTLIBRARIES) $(DATA) $(HEADERS) all-local +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(girdir)" "$(DESTDIR)$(typelibsdir)" "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)" "$(DESTDIR)$(libgstreamer_@GST_MAJORMINOR@includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-recursive + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-recursive + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-girDATA \ + install-libgstreamer_@GST_MAJORMINOR@includeHEADERS \ + install-nodist_libgstreamer_@GST_MAJORMINOR@includeHEADERS \ + install-typelibsDATA + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-girDATA uninstall-libLTLIBRARIES \ + uninstall-libgstreamer_@GST_MAJORMINOR@includeHEADERS \ + uninstall-nodist_libgstreamer_@GST_MAJORMINOR@includeHEADERS \ + uninstall-typelibsDATA + +.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) all check \ + ctags-recursive install install-am install-strip \ + tags-recursive + +.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \ + all all-am all-local check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool ctags ctags-recursive \ + distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-girDATA install-html install-html-am \ + install-info install-info-am install-libLTLIBRARIES \ + install-libgstreamer_@GST_MAJORMINOR@includeHEADERS \ + install-man \ + install-nodist_libgstreamer_@GST_MAJORMINOR@includeHEADERS \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip install-typelibsDATA installcheck \ + installcheck-am installdirs installdirs-am maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-recursive uninstall uninstall-am uninstall-girDATA \ + uninstall-libLTLIBRARIES \ + uninstall-libgstreamer_@GST_MAJORMINOR@includeHEADERS \ + uninstall-nodist_libgstreamer_@GST_MAJORMINOR@includeHEADERS \ + uninstall-typelibsDATA + + +gstmarshal.h: gstmarshal.list + $(AM_V_GEN)glib-genmarshal --header --prefix=gst_marshal $(srcdir)/gstmarshal.list > gstmarshal.h.tmp && \ + mv gstmarshal.h.tmp gstmarshal.h + +gstmarshal.c: gstmarshal.list gst_private.h + $(AM_V_GEN)echo "#include \"gst_private.h\"" > gstmarshal.c.tmp && \ + echo "#include \"glib-object.h\"" >> gstmarshal.c.tmp && \ + echo "#include \"gstmarshal.h\"" >> gstmarshal.c.tmp && \ + glib-genmarshal --body --prefix=gst_marshal $(srcdir)/gstmarshal.list >> gstmarshal.c.tmp && \ + mv gstmarshal.c.tmp gstmarshal.c + +gstenumtypes.h: $(gst_headers) + $(AM_V_GEN)glib-mkenums \ + --fhead "#ifndef __GST_ENUM_TYPES_H__\n#define __GST_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \ + --fprod "\n/* enumerations from \"@filename@\" */\n" \ + --vhead "GType @enum_name@_get_type (void);\n#define GST_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \ + --ftail "G_END_DECLS\n\n#endif /* __GST_ENUM_TYPES_H__ */" \ + $^ > gstenumtypes.h + +gstenumtypes.c: $(gst_headers) + $(AM_V_GEN)glib-mkenums \ + --fhead "#include \"gst_private.h\"\n#include <gst/gst.h>\n#define C_ENUM(v) ((gint) v)\n#define C_FLAGS(v) ((guint) v)\n " \ + --fprod "\n/* enumerations from \"@filename@\" */" \ + --vhead "GType\n@enum_name@_get_type (void)\n{\n static gsize id = 0;\n static const G@Type@Value values[] = {" \ + --vprod " { C_@TYPE@(@VALUENAME@), \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n };\n\n if (g_once_init_enter (&id)) {\n GType tmp = g_@type@_register_static (\"@EnumName@\", values);\n g_once_init_leave (&id, tmp);\n }\n\n return (GType) id;\n}" \ + $^ > gstenumtypes.c + +%.c.gcov: .libs/libgstreamer_@GST_MAJORMINOR@_la-%.gcda %.c + $(GCOV) -b -f -o $^ > $@.out + +gcov: $(libgstreamer_@GST_MAJORMINOR@_la_SOURCES:=.gcov) + +Android.mk: Makefile.am + androgenizer -:PROJECT gstreamer -:SHARED libgstreamer-@GST_MAJORMINOR@ \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstreamer_@GST_MAJORMINOR@_la_SOURCES) \ + $(nodist_libgstreamer_@GST_MAJORMINOR@_la_SOURCES) \ + -:CFLAGS $(DEFS) $(libgstreamer_@GST_MAJORMINOR@_la_CFLAGS) \ + -:LDFLAGS $(libgstreamer_@GST_MAJORMINOR@_la_LDFLAGS) \ + $(libgstreamer_@GST_MAJORMINOR@_la_LIBADD) \ + -ldl \ + -:SUBDIR gst/parse \ + -:HEADER_TARGET gstreamer-@GST_MAJORMINOR@/gst \ + -:HEADERS $(libgstreamer_@GST_MAJORMINOR@include_HEADERS) \ + -:LIBFILTER_STATIC gstparse \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + > $@ + +@HAVE_INTROSPECTION_TRUE@Gst-@GST_MAJORMINOR@.gir: $(INTROSPECTION_SCANNER) libgstreamer-@GST_MAJORMINOR@.la +@HAVE_INTROSPECTION_TRUE@ $(AM_V_GEN)GST_PLUGIN_SYSTEM_PATH="" GST_PLUGIN_PATH="" GST_REGISTRY_UPDATE=no \ +@HAVE_INTROSPECTION_TRUE@ $(INTROSPECTION_SCANNER) -v --namespace Gst \ +@HAVE_INTROSPECTION_TRUE@ --nsversion=@GST_MAJORMINOR@ \ +@HAVE_INTROSPECTION_TRUE@ -I$(top_srcdir) \ +@HAVE_INTROSPECTION_TRUE@ -I$(top_builddir) \ +@HAVE_INTROSPECTION_TRUE@ -DIN_GOBJECT_INTROSPECTION=1 \ +@HAVE_INTROSPECTION_TRUE@ -DGST_USE_UNSTABLE_API \ +@HAVE_INTROSPECTION_TRUE@ --c-include='gst/gst.h' \ +@HAVE_INTROSPECTION_TRUE@ --library=libgstreamer-0.11.la \ +@HAVE_INTROSPECTION_TRUE@ --include=GLib-2.0 \ +@HAVE_INTROSPECTION_TRUE@ --include=GObject-2.0 \ +@HAVE_INTROSPECTION_TRUE@ --include=GModule-2.0 \ +@HAVE_INTROSPECTION_TRUE@ --libtool="$(top_builddir)/libtool" \ +@HAVE_INTROSPECTION_TRUE@ --pkg glib-2.0 \ +@HAVE_INTROSPECTION_TRUE@ --pkg gobject-2.0 \ +@HAVE_INTROSPECTION_TRUE@ --pkg gmodule-no-export-2.0 \ +@HAVE_INTROSPECTION_TRUE@ --pkg gthread-2.0 \ +@HAVE_INTROSPECTION_TRUE@ --pkg-export gstreamer-@GST_MAJORMINOR@ \ +@HAVE_INTROSPECTION_TRUE@ --add-init-section="gst_init(NULL, NULL);" \ +@HAVE_INTROSPECTION_TRUE@ --output $@ \ +@HAVE_INTROSPECTION_TRUE@ $(gir_headers) \ +@HAVE_INTROSPECTION_TRUE@ $(gir_sources) + +@HAVE_INTROSPECTION_TRUE@%.typelib: %.gir $(INTROSPECTION_COMPILER) +@HAVE_INTROSPECTION_TRUE@ $(AM_V_GEN)$(INTROSPECTION_COMPILER) --includedir=$(srcdir) --includedir=$(builddir) $(INTROSPECTION_COMPILER_OPTS) $< -o $(@F) + +# try to prevent packaging errors +check-libexecdir-consistency: + @if test "${GST_PLUGIN_SCANNER_INSTALLED}" != "${libexecdir}/gstreamer-$(GST_MAJORMINOR)/gst-plugin-scanner"; then \ + echo "*** Inconsistent libexecdir! Please use ./configure --libexecdir=/foo/bar"; \ + echo "*** to set the libexecdir and not make libexecdir=/foo/bar or the like."; \ + echo "*** The same goes for prefix, libdir etc."; \ + echo "*** ${GST_PLUGIN_SCANNER_INSTALLED} != ${libexecdir}/gstreamer-$(GST_MAJORMINOR)/gst-plugin-scanner"; \ + exit 1; \ + fi + +all-local: check-libexecdir-consistency + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/gst/gettext.h b/gst/gettext.h new file mode 100644 index 0000000..59902b3 --- /dev/null +++ b/gst/gettext.h @@ -0,0 +1,69 @@ +/* Convenience header for conditional use of GNU <libintl.h>. + Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc. + + This program 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, or (at your option) + any later version. + + This program 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 program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + +/* NLS can be disabled through the configure --disable-nls option. */ +#ifdef ENABLE_NLS + +/* Get declarations of GNU message catalog functions. */ +# include <libintl.h> + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of <locale.h> a NOP. We don't include <libintl.h> + as well because people using "gettext.h" will not include <libintl.h>, + and also including <libintl.h> would fail on SunOS 4, whereas <locale.h> + is OK. */ +#if defined(__sun) +# include <locale.h> +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *) (Msgid)) +# define dgettext(Domainname, Msgid) ((const char *) (Msgid)) +# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +#endif /* _LIBGETTEXT_H */ diff --git a/gst/glib-compat-private.h b/gst/glib-compat-private.h new file mode 100644 index 0000000..7b5d6cf --- /dev/null +++ b/gst/glib-compat-private.h @@ -0,0 +1,55 @@ +/* + * glib-compat.c + * Functions copied from glib 2.10 + * + * Copyright 2005 David Schleef <ds@schleef.org> + */ + +#ifndef __GLIB_COMPAT_PRIVATE_H__ +#define __GLIB_COMPAT_PRIVATE_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +#if !GLIB_CHECK_VERSION(2,25,0) + +#if defined (_MSC_VER) && !defined(_WIN64) +typedef struct _stat32 GStatBuf; +#else +typedef struct stat GStatBuf; +#endif + +#endif + +#if GLIB_CHECK_VERSION(2,26,0) +#define GLIB_HAS_GDATETIME +#endif + +/* See bug #651514 */ +#if GLIB_CHECK_VERSION(2,29,5) +#define G_ATOMIC_POINTER_COMPARE_AND_EXCHANGE(a,b,c) \ + g_atomic_pointer_compare_and_exchange ((a),(b),(c)) +#define G_ATOMIC_INT_COMPARE_AND_EXCHANGE(a,b,c) \ + g_atomic_int_compare_and_exchange ((a),(b),(c)) +#else +#define G_ATOMIC_POINTER_COMPARE_AND_EXCHANGE(a,b,c) \ + g_atomic_pointer_compare_and_exchange ((volatile gpointer *)(a),(b),(c)) +#define G_ATOMIC_INT_COMPARE_AND_EXCHANGE(a,b,c) \ + g_atomic_int_compare_and_exchange ((volatile int *)(a),(b),(c)) +#endif + +/* See bug #651514 */ +#if GLIB_CHECK_VERSION(2,29,5) +#define G_ATOMIC_INT_ADD(a,b) g_atomic_int_add ((a),(b)) +#else +#define G_ATOMIC_INT_ADD(a,b) g_atomic_int_exchange_and_add ((a),(b)) +#endif + +/* copies */ + +/* adaptations */ + +G_END_DECLS + +#endif diff --git a/gst/glib-compat.h b/gst/glib-compat.h new file mode 100644 index 0000000..7794306 --- /dev/null +++ b/gst/glib-compat.h @@ -0,0 +1,37 @@ +/* GStreamer + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * glib-compat.h: Public GLib compatibility shims + * + * 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. + */ + + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS file from + * glib-2.8.0 for a list of people on the GLib Team. See the ChangeLog files + * from glib-2.8.0 for a list of changes. These files are distributed with GLib + * at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __GST_GLIB_COMPAT_H__ +#define __GST_GLIB_COMPAT_H__ + +G_BEGIN_DECLS + +G_END_DECLS + +#endif /* __GST_GLIB_COMPAT_H__ */ diff --git a/gst/gst-i18n-app.h b/gst/gst-i18n-app.h new file mode 100644 index 0000000..06339b8 --- /dev/null +++ b/gst/gst-i18n-app.h @@ -0,0 +1,43 @@ +/* GStreamer + * Copyright (C) 2004 Thomas Vander Stichele <thomas@apestaart.org> + * + * gst-i18n-app.h: internationalization macros for the GStreamer tools + * + * 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_I18N_APP_H__ +#define __GST_I18N_APP_H__ + +#ifdef ENABLE_NLS + +#include "gettext.h" /* included with gettext distribution and copied */ + +/* we want to use shorthand _() for translating and N_() for marking */ +#define _(String) gettext (String) +#define N_(String) gettext_noop (String) +/* FIXME: if we need it, we can add Q_ as well, like in glib */ + +#else + +#define _(String) String +#define N_(String) String +#define ngettext(Singular,Plural,Count) ((Count>1)?Plural:Singular) + +#endif + +#endif /* __GST_I18N_APP_H__ */ diff --git a/gst/gst-i18n-lib.h b/gst/gst-i18n-lib.h new file mode 100644 index 0000000..e4eb45e --- /dev/null +++ b/gst/gst-i18n-lib.h @@ -0,0 +1,46 @@ +/* GStreamer + * Copyright (C) 2004 Thomas Vander Stichele <thomas@apestaart.org> + * + * gst-i18n-lib.h: internationalization macros for the GStreamer libraries + * + * 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_I18N_LIB_H__ +#define __GST_I18N_LIB_H__ + +#ifndef PACKAGE_NAME +#error You must include config.h before including this header. +#endif + +#ifdef ENABLE_NLS + +#include "gettext.h" /* included with gettext distribution and copied */ + +/* we want to use shorthand _() for translating and N_() for marking */ +#define _(String) dgettext (GETTEXT_PACKAGE, String) +#define N_(String) gettext_noop (String) +/* FIXME: if we need it, we can add Q_ as well, like in glib */ + +#else +#define _(String) String +#define N_(String) String +#define ngettext(Singular,Plural,Count) ((Count>1)?Plural:Singular) + +#endif + +#endif /* __GST_I18N_LIB_H__ */ diff --git a/gst/gst.c b/gst/gst.c new file mode 100644 index 0000000..4634758 --- /dev/null +++ b/gst/gst.c @@ -0,0 +1,1219 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gst.c: Initialization and non-pipeline operations + * + * 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:gst + * @short_description: Media library supporting arbitrary formats and filter + * graphs. + * + * GStreamer is a framework for constructing graphs of various filters + * (termed elements here) that will handle streaming media. Any discreet + * (packetizable) media type is supported, with provisions for automatically + * determining source type. Formatting/framing information is provided with + * a powerful negotiation framework. Plugins are heavily used to provide for + * all elements, allowing one to construct plugins outside of the GST + * library, even released binary-only if license require (please don't). + * GStreamer covers a wide range of use cases including: playback, recording, + * editing, serving streams, voice over ip and video calls. + * + * The <application>GStreamer</application> library should be initialized with + * gst_init() before it can be used. You should pass pointers to the main argc + * and argv variables so that GStreamer can process its own command line + * options, as shown in the following example. + * + * <example> + * <title>Initializing the gstreamer library</title> + * <programlisting language="c"> + * int + * main (int argc, char *argv[]) + * { + * // initialize the GStreamer library + * gst_init (&argc, &argv); + * ... + * } + * </programlisting> + * </example> + * + * It's allowed to pass two NULL pointers to gst_init() in case you don't want + * to pass the command line args to GStreamer. + * + * You can also use GOption to initialize your own parameters as shown in + * the next code fragment: + * <example> + * <title>Initializing own parameters when initializing gstreamer</title> + * <programlisting> + * static gboolean stats = FALSE; + * ... + * int + * main (int argc, char *argv[]) + * { + * GOptionEntry options[] = { + * {"tags", 't', 0, G_OPTION_ARG_NONE, &tags, + * N_("Output tags (also known as metadata)"), NULL}, + * {NULL} + * }; + * // must initialise the threading system before using any other GLib funtion + * if (!g_thread_supported ()) + * g_thread_init (NULL); + * ctx = g_option_context_new ("[ADDITIONAL ARGUMENTS]"); + * g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE); + * g_option_context_add_group (ctx, gst_init_get_option_group ()); + * if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + * g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); + * exit (1); + * } + * g_option_context_free (ctx); + * ... + * } + * </programlisting> + * </example> + * + * Use gst_version() to query the library version at runtime or use the + * GST_VERSION_* macros to find the version at compile time. Optionally + * gst_version_string() returns a printable string. + * + * The gst_deinit() call is used to clean up all internal resources used + * by <application>GStreamer</application>. It is mostly used in unit tests + * to check for leaks. + * + * Last reviewed on 2006-08-11 (0.10.10) + */ + +#include "gst_private.h" +#include "gstconfig.h" +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#ifdef HAVE_SYS_UTSNAME_H +#include <sys/utsname.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef G_OS_WIN32 +#define WIN32_LEAN_AND_MEAN /* prevents from including too many things */ +#include <windows.h> /* GetStdHandle, windows console */ +#endif + +#include "gst-i18n-lib.h" +#include <locale.h> /* for LC_ALL */ + +#include "gst.h" + +#define GST_CAT_DEFAULT GST_CAT_GST_INIT + +#define MAX_PATH_SPLIT 16 +#define GST_PLUGIN_SEPARATOR "," + +static gboolean gst_initialized = FALSE; +static gboolean gst_deinitialized = FALSE; + +#ifdef G_OS_WIN32 +HMODULE _priv_gst_dll_handle = NULL; +#endif + +#ifndef GST_DISABLE_REGISTRY +GList *_priv_gst_plugin_paths = NULL; /* for delayed processing in post_init */ + +extern gboolean _priv_gst_disable_registry_update; +#endif + +#ifndef GST_DISABLE_GST_DEBUG +extern const gchar *priv_gst_dump_dot_dir; +#endif + +/* defaults */ + +/* set to TRUE when segfaults need to be left as is */ +static gboolean _gst_disable_segtrap = FALSE; + +static gboolean init_pre (GOptionContext * context, GOptionGroup * group, + gpointer data, GError ** error); +static gboolean init_post (GOptionContext * context, GOptionGroup * group, + gpointer data, GError ** error); +#ifndef GST_DISABLE_OPTION_PARSING +static gboolean parse_goption_arg (const gchar * s_opt, + const gchar * arg, gpointer data, GError ** err); +#endif + +GSList *_priv_gst_preload_plugins = NULL; + +const gchar g_log_domain_gstreamer[] = "GStreamer"; + +static void +debug_log_handler (const gchar * log_domain, + GLogLevelFlags log_level, const gchar * message, gpointer user_data) +{ + g_log_default_handler (log_domain, log_level, message, user_data); + /* FIXME: do we still need this ? fatal errors these days are all + * other than core errors */ + /* g_on_error_query (NULL); */ +} + +enum +{ + ARG_VERSION = 1, + ARG_FATAL_WARNINGS, +#ifndef GST_DISABLE_GST_DEBUG + ARG_DEBUG_LEVEL, + ARG_DEBUG, + ARG_DEBUG_DISABLE, + ARG_DEBUG_NO_COLOR, + ARG_DEBUG_HELP, +#endif + ARG_PLUGIN_SPEW, + ARG_PLUGIN_PATH, + ARG_PLUGIN_LOAD, + ARG_SEGTRAP_DISABLE, + ARG_REGISTRY_UPDATE_DISABLE, + ARG_REGISTRY_FORK_DISABLE +}; + +/* debug-spec ::= category-spec [, category-spec]* + * category-spec ::= category:val | val + * category ::= [^:]+ + * val ::= [0-5] + */ + +#ifndef NUL +#define NUL '\0' +#endif + +#ifndef GST_DISABLE_GST_DEBUG +static gboolean +parse_debug_category (gchar * str, const gchar ** category) +{ + if (!str) + return FALSE; + + /* works in place */ + g_strstrip (str); + + if (str[0] != NUL) { + *category = str; + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_debug_level (gchar * str, GstDebugLevel * level) +{ + if (!str) + return FALSE; + + /* works in place */ + g_strstrip (str); + + if (str[0] != NUL && str[1] == NUL + && str[0] >= '0' && str[0] < '0' + GST_LEVEL_COUNT) { + *level = (GstDebugLevel) (str[0] - '0'); + return TRUE; + } + + return FALSE; +} + +static void +parse_debug_list (const gchar * list) +{ + gchar **split; + gchar **walk; + + g_assert (list); + + split = g_strsplit (list, ",", 0); + + for (walk = split; *walk; walk++) { + if (strchr (*walk, ':')) { + gchar **values = g_strsplit (*walk, ":", 2); + + if (values[0] && values[1]) { + GstDebugLevel level; + const gchar *category; + + if (parse_debug_category (values[0], &category) + && parse_debug_level (values[1], &level)) + gst_debug_set_threshold_for_name (category, level); + } + + g_strfreev (values); + } else { + GstDebugLevel level; + + if (parse_debug_level (*walk, &level)) + gst_debug_set_default_threshold (level); + } + } + + g_strfreev (split); +} +#endif + +#ifdef G_OS_WIN32 +BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); +BOOL WINAPI +DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + _priv_gst_dll_handle = (HMODULE) hinstDLL; + return TRUE; +} + +#endif + +/** + * gst_init_get_option_group: + * + * Returns a #GOptionGroup with GStreamer's argument specifications. The + * group is set up to use standard GOption callbacks, so when using this + * group in combination with GOption parsing methods, all argument parsing + * and initialization is automated. + * + * This function is useful if you want to integrate GStreamer with other + * libraries that use GOption (see g_option_context_add_group() ). + * + * If you use this function, you should make sure you initialise the GLib + * threading system as one of the very first things in your program + * (see the example at the beginning of this section). + * + * Returns: (transfer full): a pointer to GStreamer's option group. + */ + +GOptionGroup * +gst_init_get_option_group (void) +{ +#ifndef GST_DISABLE_OPTION_PARSING + GOptionGroup *group; + static const GOptionEntry gst_args[] = { + {"gst-version", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, N_("Print the GStreamer version"), NULL}, + {"gst-fatal-warnings", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, N_("Make all warnings fatal"), NULL}, +#ifndef GST_DISABLE_GST_DEBUG + {"gst-debug-help", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + N_("Print available debug categories and exit"), + NULL}, + {"gst-debug-level", 0, 0, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + N_("Default debug level from 1 (only error) to 5 (anything) or " + "0 for no output"), + N_("LEVEL")}, + {"gst-debug", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) parse_goption_arg, + N_("Comma-separated list of category_name:level pairs to set " + "specific levels for the individual categories. Example: " + "GST_AUTOPLUG:5,GST_ELEMENT_*:3"), + N_("LIST")}, + {"gst-debug-no-color", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, N_("Disable colored debugging output"), + NULL}, + {"gst-debug-disable", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, N_("Disable debugging"), NULL}, +#endif + {"gst-plugin-spew", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + N_("Enable verbose plugin loading diagnostics"), + NULL}, + {"gst-plugin-path", 0, 0, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + N_("Colon-separated paths containing plugins"), N_("PATHS")}, + {"gst-plugin-load", 0, 0, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + N_("Comma-separated list of plugins to preload in addition to the " + "list stored in environment variable GST_PLUGIN_PATH"), + N_("PLUGINS")}, + {"gst-disable-segtrap", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + N_("Disable trapping of segmentation faults during plugin loading"), + NULL}, + {"gst-disable-registry-update", 0, G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + N_("Disable updating the registry"), + NULL}, + {"gst-disable-registry-fork", 0, G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + N_("Disable spawning a helper process while scanning the registry"), + NULL}, + {NULL} + }; + + /* Since GLib 2.23.2 calling g_thread_init() 'late' is allowed and is + * automatically done as part of g_type_init() */ + if (glib_check_version (2, 23, 3)) { + /* The GLib threading system must be initialised before calling any other + * GLib function according to the documentation; if the application hasn't + * called gst_init() yet or initialised the threading system otherwise, we + * better issue a warning here (since chances are high that the application + * has already called other GLib functions such as g_option_context_new() */ + if (!g_thread_get_initialized ()) { + g_warning ("The GStreamer function gst_init_get_option_group() was\n" + "\tcalled, but the GLib threading system has not been initialised\n" + "\tyet, something that must happen before any other GLib function\n" + "\tis called. The application needs to be fixed so that it calls\n" + "\t if (!g_thread_get_initialized ()) g_thread_init(NULL);\n" + "\tas very first thing in its main() function. Please file a bug\n" + "\tagainst this application."); + g_thread_init (NULL); + } + } else { + /* GLib >= 2.23.2 */ + } + + group = g_option_group_new ("gst", _("GStreamer Options"), + _("Show GStreamer Options"), NULL, NULL); + g_option_group_set_parse_hooks (group, (GOptionParseFunc) init_pre, + (GOptionParseFunc) init_post); + + g_option_group_add_entries (group, gst_args); + g_option_group_set_translation_domain (group, GETTEXT_PACKAGE); + + return group; +#else + return NULL; +#endif +} + +/** + * gst_init_check: + * @argc: (inout) (allow-none): pointer to application's argc + * @argv: (inout) (array length=argc) (allow-none): pointer to application's argv + * @err: pointer to a #GError to which a message will be posted on error + * + * Initializes the GStreamer library, setting up internal path lists, + * registering built-in elements, and loading standard plugins. + * + * This function will return %FALSE if GStreamer could not be initialized + * for some reason. If you want your program to fail fatally, + * use gst_init() instead. + * + * This function should be called before calling any other GLib functions. If + * this is not an option, your program must initialise the GLib thread system + * using g_thread_init() before any other GLib functions are called. + * + * Returns: %TRUE if GStreamer could be initialized. + */ +gboolean +gst_init_check (int *argc, char **argv[], GError ** err) +{ +#ifndef GST_DISABLE_OPTION_PARSING + GOptionGroup *group; + GOptionContext *ctx; +#endif + gboolean res; + + if (!g_thread_get_initialized ()) + g_thread_init (NULL); + + if (gst_initialized) { + GST_DEBUG ("already initialized gst"); + return TRUE; + } +#ifndef GST_DISABLE_OPTION_PARSING + ctx = g_option_context_new ("- GStreamer initialization"); + g_option_context_set_ignore_unknown_options (ctx, TRUE); + group = gst_init_get_option_group (); + g_option_context_add_group (ctx, group); + res = g_option_context_parse (ctx, argc, argv, err); + g_option_context_free (ctx); +#else + init_pre (NULL, NULL, NULL, NULL); + init_post (NULL, NULL, NULL, NULL); + res = TRUE; +#endif + + gst_initialized = res; + + if (res) { + GST_INFO ("initialized GStreamer successfully"); + } else { + GST_INFO ("failed to initialize GStreamer"); + } + + return res; +} + +/** + * gst_init: + * @argc: (inout) (allow-none): pointer to application's argc + * @argv: (inout) (array length=argc) (allow-none): pointer to application's argv + * + * Initializes the GStreamer library, setting up internal path lists, + * registering built-in elements, and loading standard plugins. + * + * Unless the plugin registry is disabled at compile time, the registry will be + * loaded. By default this will also check if the registry cache needs to be + * updated and rescan all plugins if needed. See gst_update_registry() for + * details and section + * <link linkend="gst-running">Running GStreamer Applications</link> + * for how to disable automatic registry updates. + * + * This function should be called before calling any other GLib functions. If + * this is not an option, your program must initialise the GLib thread system + * using g_thread_init() before any other GLib functions are called. + * + * <note><para> + * This function will terminate your program if it was unable to initialize + * GStreamer for some reason. If you want your program to fall back, + * use gst_init_check() instead. + * </para></note> + * + * WARNING: This function does not work in the same way as corresponding + * functions in other glib-style libraries, such as gtk_init(). In + * particular, unknown command line options cause this function to + * abort program execution. + */ +void +gst_init (int *argc, char **argv[]) +{ + GError *err = NULL; + + if (!gst_init_check (argc, argv, &err)) { + g_print ("Could not initialize GStreamer: %s\n", + err ? err->message : "unknown error occurred"); + if (err) { + g_error_free (err); + } + exit (1); + } +} + +/** + * gst_is_initialized: + * + * Use this function to check if GStreamer has been initialized with gst_init() + * or gst_init_check(). + * + * Returns: TRUE if initialization has been done, FALSE otherwise. + * + * Since: 0.10.31 + */ +gboolean +gst_is_initialized (void) +{ + return gst_initialized; +} + +#ifndef GST_DISABLE_REGISTRY +static void +add_path_func (gpointer data, gpointer user_data) +{ + GST_INFO ("Adding plugin path: \"%s\", will scan later", (gchar *) data); + _priv_gst_plugin_paths = + g_list_append (_priv_gst_plugin_paths, g_strdup (data)); +} +#endif + +#ifndef GST_DISABLE_OPTION_PARSING +static void +prepare_for_load_plugin_func (gpointer data, gpointer user_data) +{ + _priv_gst_preload_plugins = + g_slist_prepend (_priv_gst_preload_plugins, g_strdup (data)); +} +#endif + +#ifndef GST_DISABLE_OPTION_PARSING +static void +split_and_iterate (const gchar * stringlist, const gchar * separator, + GFunc iterator, gpointer user_data) +{ + gchar **strings; + gint j = 0; + gchar *lastlist = g_strdup (stringlist); + + while (lastlist) { + strings = g_strsplit (lastlist, separator, MAX_PATH_SPLIT); + g_free (lastlist); + lastlist = NULL; + + while (strings[j]) { + iterator (strings[j], user_data); + if (++j == MAX_PATH_SPLIT) { + lastlist = g_strdup (strings[j]); + j = 0; + break; + } + } + g_strfreev (strings); + } +} +#endif + +/* we have no fail cases yet, but maybe in the future */ +static gboolean +init_pre (GOptionContext * context, GOptionGroup * group, gpointer data, + GError ** error) +{ + if (gst_initialized) { + GST_DEBUG ("already initialized"); + return TRUE; + } + + g_type_init (); + + /* we need threading to be enabled right here */ + g_assert (g_thread_get_initialized ()); + + _gst_debug_init (); + +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +#endif /* ENABLE_NLS */ + +#ifndef GST_DISABLE_GST_DEBUG + { + const gchar *debug_list; + + if (g_getenv ("GST_DEBUG_NO_COLOR") != NULL) + gst_debug_set_colored (FALSE); + + debug_list = g_getenv ("GST_DEBUG"); + if (debug_list) { + parse_debug_list (debug_list); + } + } + + priv_gst_dump_dot_dir = g_getenv ("GST_DEBUG_DUMP_DOT_DIR"); +#endif + /* This is the earliest we can make stuff show up in the logs. + * So give some useful info about GStreamer here */ + GST_INFO ("Initializing GStreamer Core Library version %s", VERSION); + GST_INFO ("Using library installed in %s", LIBDIR); + + /* Print some basic system details if possible (OS/architecture) */ +#ifdef HAVE_SYS_UTSNAME_H + { + struct utsname sys_details; + + if (uname (&sys_details) == 0) { + GST_INFO ("%s %s %s %s %s", sys_details.sysname, + sys_details.nodename, sys_details.release, sys_details.version, + sys_details.machine); + } + } +#endif + + return TRUE; +} + +static gboolean +gst_register_core_elements (GstPlugin * plugin) +{ + /* register some standard builtin types */ + if (!gst_element_register (plugin, "bin", GST_RANK_PRIMARY, + GST_TYPE_BIN) || + !gst_element_register (plugin, "pipeline", GST_RANK_PRIMARY, + GST_TYPE_PIPELINE) + ) + g_assert_not_reached (); + + return TRUE; +} + +/* + * this bit handles: + * - initalization of threads if we use them + * - log handler + * - initial output + * - initializes gst_format + * - registers a bunch of types for gst_objects + * + * - we don't have cases yet where this fails, but in the future + * we might and then it's nice to be able to return that + */ +static gboolean +init_post (GOptionContext * context, GOptionGroup * group, gpointer data, + GError ** error) +{ + GLogLevelFlags llf; + +#ifndef GST_DISABLE_TRACE + GstTrace *gst_trace; +#endif /* GST_DISABLE_TRACE */ + + if (gst_initialized) { + GST_DEBUG ("already initialized"); + return TRUE; + } + + llf = G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL; + g_log_set_handler (g_log_domain_gstreamer, llf, debug_log_handler, NULL); + + _priv_gst_quarks_initialize (); + _priv_gst_memory_initialize (); + _priv_gst_format_initialize (); + _priv_gst_query_initialize (); + _priv_gst_structure_initialize (); + _priv_gst_caps_initialize (); + _priv_gst_meta_initialize (); + + g_type_class_ref (gst_object_get_type ()); + g_type_class_ref (gst_pad_get_type ()); + g_type_class_ref (gst_element_factory_get_type ()); + g_type_class_ref (gst_element_get_type ()); + g_type_class_ref (gst_type_find_factory_get_type ()); + g_type_class_ref (gst_bin_get_type ()); + g_type_class_ref (gst_bus_get_type ()); + g_type_class_ref (gst_task_get_type ()); + g_type_class_ref (gst_clock_get_type ()); + + g_type_class_ref (gst_index_factory_get_type ()); + gst_uri_handler_get_type (); + + g_type_class_ref (gst_object_flags_get_type ()); + g_type_class_ref (gst_bin_flags_get_type ()); + g_type_class_ref (gst_buffer_flags_get_type ()); + g_type_class_ref (gst_buffer_copy_flags_get_type ()); + g_type_class_ref (gst_bus_flags_get_type ()); + g_type_class_ref (gst_bus_sync_reply_get_type ()); + g_type_class_ref (gst_caps_flags_get_type ()); + g_type_class_ref (gst_clock_return_get_type ()); + g_type_class_ref (gst_clock_entry_type_get_type ()); + g_type_class_ref (gst_clock_flags_get_type ()); + g_type_class_ref (gst_clock_type_get_type ()); + g_type_class_ref (gst_debug_graph_details_get_type ()); + g_type_class_ref (gst_state_get_type ()); + g_type_class_ref (gst_state_change_return_get_type ()); + g_type_class_ref (gst_state_change_get_type ()); + g_type_class_ref (gst_element_flags_get_type ()); + g_type_class_ref (gst_core_error_get_type ()); + g_type_class_ref (gst_library_error_get_type ()); + g_type_class_ref (gst_resource_error_get_type ()); + g_type_class_ref (gst_stream_error_get_type ()); + g_type_class_ref (gst_event_type_flags_get_type ()); + g_type_class_ref (gst_event_type_get_type ()); + g_type_class_ref (gst_seek_type_get_type ()); + g_type_class_ref (gst_seek_flags_get_type ()); + g_type_class_ref (gst_qos_type_get_type ()); + g_type_class_ref (gst_format_get_type ()); + g_type_class_ref (gst_index_certainty_get_type ()); + g_type_class_ref (gst_index_entry_type_get_type ()); + g_type_class_ref (gst_index_lookup_method_get_type ()); + g_type_class_ref (gst_assoc_flags_get_type ()); + g_type_class_ref (gst_index_resolver_method_get_type ()); + g_type_class_ref (gst_index_flags_get_type ()); + g_type_class_ref (gst_debug_level_get_type ()); + g_type_class_ref (gst_debug_color_flags_get_type ()); + g_type_class_ref (gst_iterator_result_get_type ()); + g_type_class_ref (gst_iterator_item_get_type ()); + g_type_class_ref (gst_message_type_get_type ()); + g_type_class_ref (gst_mini_object_flags_get_type ()); + g_type_class_ref (gst_pad_link_return_get_type ()); + g_type_class_ref (gst_pad_link_check_get_type ()); + g_type_class_ref (gst_flow_return_get_type ()); + g_type_class_ref (gst_activate_mode_get_type ()); + g_type_class_ref (gst_pad_direction_get_type ()); + g_type_class_ref (gst_pad_flags_get_type ()); + g_type_class_ref (gst_pad_presence_get_type ()); + g_type_class_ref (gst_pad_template_flags_get_type ()); + g_type_class_ref (gst_pipeline_flags_get_type ()); + g_type_class_ref (gst_plugin_error_get_type ()); + g_type_class_ref (gst_plugin_flags_get_type ()); + g_type_class_ref (gst_plugin_dependency_flags_get_type ()); + g_type_class_ref (gst_rank_get_type ()); + g_type_class_ref (gst_query_type_get_type ()); + g_type_class_ref (gst_buffering_mode_get_type ()); + g_type_class_ref (gst_stream_status_type_get_type ()); + g_type_class_ref (gst_structure_change_type_get_type ()); + g_type_class_ref (gst_tag_merge_mode_get_type ()); + g_type_class_ref (gst_tag_flag_get_type ()); + g_type_class_ref (gst_task_pool_get_type ()); + g_type_class_ref (gst_task_state_get_type ()); + g_type_class_ref (gst_alloc_trace_flags_get_type ()); + g_type_class_ref (gst_type_find_probability_get_type ()); + g_type_class_ref (gst_uri_type_get_type ()); + g_type_class_ref (gst_parse_error_get_type ()); + g_type_class_ref (gst_parse_flags_get_type ()); + g_type_class_ref (gst_search_mode_get_type ()); + g_type_class_ref (gst_progress_type_get_type ()); + g_type_class_ref (gst_buffer_pool_flags_get_type ()); + g_type_class_ref (gst_memory_flags_get_type ()); + g_type_class_ref (gst_map_flags_get_type ()); + g_type_class_ref (gst_caps_intersect_mode_get_type ()); + g_type_class_ref (gst_probe_type_get_type ()); + g_type_class_ref (gst_probe_return_get_type ()); + g_type_class_ref (gst_segment_flags_get_type ()); + + _priv_gst_event_initialize (); + _priv_gst_buffer_initialize (); + _priv_gst_message_initialize (); + _priv_gst_buffer_list_initialize (); + _priv_gst_value_initialize (); + g_type_class_ref (gst_param_spec_fraction_get_type ()); + _priv_gst_tag_initialize (); + gst_parse_context_get_type (); + + _priv_gst_plugin_initialize (); + + gst_g_error_get_type (); + + /* register core plugins */ + gst_plugin_register_static (GST_VERSION_MAJOR, GST_VERSION_MINOR, + "staticelements", "core elements linked into the GStreamer library", + gst_register_core_elements, VERSION, GST_LICENSE, PACKAGE, + GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); + + /* + * Any errors happening below this point are non-fatal, we therefore mark + * gstreamer as being initialized, since it is the case from a plugin point of + * view. + * + * If anything fails, it will be put back to FALSE in gst_init_check(). + * This allows some special plugins that would call gst_init() to not cause a + * looping effect (i.e. initializing GStreamer twice). + */ + gst_initialized = TRUE; + + if (!gst_update_registry ()) + return FALSE; + +#ifndef GST_DISABLE_TRACE + _gst_trace_on = 0; + if (_gst_trace_on) { + gst_trace = gst_trace_new ("gst.trace", 1024); + gst_trace_set_default (gst_trace); + } +#endif /* GST_DISABLE_TRACE */ + + GST_INFO ("GLib runtime version: %d.%d.%d", glib_major_version, + glib_minor_version, glib_micro_version); + GST_INFO ("GLib headers version: %d.%d.%d", GLIB_MAJOR_VERSION, + GLIB_MINOR_VERSION, GLIB_MICRO_VERSION); + + return TRUE; +} + +#ifndef GST_DISABLE_GST_DEBUG +static gboolean +select_all (GstPlugin * plugin, gpointer user_data) +{ + return TRUE; +} + +static gint +sort_by_category_name (gconstpointer a, gconstpointer b) +{ + return strcmp (gst_debug_category_get_name ((GstDebugCategory *) a), + gst_debug_category_get_name ((GstDebugCategory *) b)); +} + +static void +gst_debug_help (void) +{ + GSList *list, *walk; + GList *list2, *g; + + /* Need to ensure the registry is loaded to get debug categories */ + if (!init_post (NULL, NULL, NULL, NULL)) + exit (1); + + list2 = gst_registry_plugin_filter (gst_registry_get_default (), + select_all, FALSE, NULL); + + /* FIXME this is gross. why don't debug have categories PluginFeatures? */ + for (g = list2; g; g = g_list_next (g)) { + GstPlugin *plugin = GST_PLUGIN_CAST (g->data); + + gst_plugin_load (plugin); + } + g_list_free (list2); + + list = gst_debug_get_all_categories (); + walk = list = g_slist_sort (list, sort_by_category_name); + + g_print ("\n"); + g_print ("name level description\n"); + g_print ("---------------------+--------+--------------------------------\n"); + + while (walk) { + GstDebugCategory *cat = (GstDebugCategory *) walk->data; + + if (gst_debug_is_colored ()) { +#ifdef G_OS_WIN32 + gint color = gst_debug_construct_win_color (cat->color); + const gint clear = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + + SetConsoleTextAttribute (GetStdHandle (STD_OUTPUT_HANDLE), color); + g_print ("%-20s", gst_debug_category_get_name (cat)); + SetConsoleTextAttribute (GetStdHandle (STD_OUTPUT_HANDLE), clear); + g_print (" %1d %s ", gst_debug_category_get_threshold (cat), + gst_debug_level_get_name (gst_debug_category_get_threshold (cat))); + SetConsoleTextAttribute (GetStdHandle (STD_OUTPUT_HANDLE), color); + g_print ("%s", gst_debug_category_get_description (cat)); + SetConsoleTextAttribute (GetStdHandle (STD_OUTPUT_HANDLE), clear); + g_print ("\n"); +#else /* G_OS_WIN32 */ + gchar *color = gst_debug_construct_term_color (cat->color); + + g_print ("%s%-20s\033[00m %1d %s %s%s\033[00m\n", + color, + gst_debug_category_get_name (cat), + gst_debug_category_get_threshold (cat), + gst_debug_level_get_name (gst_debug_category_get_threshold (cat)), + color, gst_debug_category_get_description (cat)); + g_free (color); +#endif /* G_OS_WIN32 */ + } else { + g_print ("%-20s %1d %s %s\n", gst_debug_category_get_name (cat), + gst_debug_category_get_threshold (cat), + gst_debug_level_get_name (gst_debug_category_get_threshold (cat)), + gst_debug_category_get_description (cat)); + } + walk = g_slist_next (walk); + } + g_slist_free (list); + g_print ("\n"); +} +#endif + +#ifndef GST_DISABLE_OPTION_PARSING +static gboolean +parse_one_option (gint opt, const gchar * arg, GError ** err) +{ + switch (opt) { + case ARG_VERSION: + g_print ("GStreamer Core Library version %s\n", PACKAGE_VERSION); + exit (0); + case ARG_FATAL_WARNINGS:{ + GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); + fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal (fatal_mask); + break; + } +#ifndef GST_DISABLE_GST_DEBUG + case ARG_DEBUG_LEVEL:{ + GstDebugLevel tmp = GST_LEVEL_NONE; + + tmp = (GstDebugLevel) strtol (arg, NULL, 0); + if (tmp >= 0 && tmp < GST_LEVEL_COUNT) { + gst_debug_set_default_threshold (tmp); + } + break; + } + case ARG_DEBUG: + parse_debug_list (arg); + break; + case ARG_DEBUG_NO_COLOR: + gst_debug_set_colored (FALSE); + break; + case ARG_DEBUG_DISABLE: + gst_debug_set_active (FALSE); + break; + case ARG_DEBUG_HELP: + gst_debug_help (); + exit (0); +#endif + case ARG_PLUGIN_SPEW: + break; + case ARG_PLUGIN_PATH: +#ifndef GST_DISABLE_REGISTRY + split_and_iterate (arg, G_SEARCHPATH_SEPARATOR_S, add_path_func, NULL); +#endif /* GST_DISABLE_REGISTRY */ + break; + case ARG_PLUGIN_LOAD: + split_and_iterate (arg, ",", prepare_for_load_plugin_func, NULL); + break; + case ARG_SEGTRAP_DISABLE: + _gst_disable_segtrap = TRUE; + break; + case ARG_REGISTRY_UPDATE_DISABLE: +#ifndef GST_DISABLE_REGISTRY + _priv_gst_disable_registry_update = TRUE; +#endif + break; + case ARG_REGISTRY_FORK_DISABLE: + gst_registry_fork_set_enabled (FALSE); + break; + default: + g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_UNKNOWN_OPTION, + _("Unknown option")); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_goption_arg (const gchar * opt, + const gchar * arg, gpointer data, GError ** err) +{ + static const struct + { + const gchar *opt; + int val; + } options[] = { + { + "--gst-version", ARG_VERSION}, { + "--gst-fatal-warnings", ARG_FATAL_WARNINGS}, +#ifndef GST_DISABLE_GST_DEBUG + { + "--gst-debug-level", ARG_DEBUG_LEVEL}, { + "--gst-debug", ARG_DEBUG}, { + "--gst-debug-disable", ARG_DEBUG_DISABLE}, { + "--gst-debug-no-color", ARG_DEBUG_NO_COLOR}, { + "--gst-debug-help", ARG_DEBUG_HELP}, +#endif + { + "--gst-plugin-spew", ARG_PLUGIN_SPEW}, { + "--gst-plugin-path", ARG_PLUGIN_PATH}, { + "--gst-plugin-load", ARG_PLUGIN_LOAD}, { + "--gst-disable-segtrap", ARG_SEGTRAP_DISABLE}, { + "--gst-disable-registry-update", ARG_REGISTRY_UPDATE_DISABLE}, { + "--gst-disable-registry-fork", ARG_REGISTRY_FORK_DISABLE}, { + NULL} + }; + gint val = 0, n; + + for (n = 0; options[n].opt; n++) { + if (!strcmp (opt, options[n].opt)) { + val = options[n].val; + break; + } + } + + return parse_one_option (val, arg, err); +} +#endif + +/** + * gst_deinit: + * + * Clean up any resources created by GStreamer in gst_init(). + * + * It is normally not needed to call this function in a normal application + * as the resources will automatically be freed when the program terminates. + * This function is therefore mostly used by testsuites and other memory + * profiling tools. + * + * After this call GStreamer (including this method) should not be used anymore. + */ +void +gst_deinit (void) +{ + GstClock *clock; + + GST_INFO ("deinitializing GStreamer"); + + if (gst_deinitialized) { + GST_DEBUG ("already deinitialized"); + return; + } + + g_slist_foreach (_priv_gst_preload_plugins, (GFunc) g_free, NULL); + g_slist_free (_priv_gst_preload_plugins); + _priv_gst_preload_plugins = NULL; + +#ifndef GST_DISABLE_REGISTRY + g_list_foreach (_priv_gst_plugin_paths, (GFunc) g_free, NULL); + g_list_free (_priv_gst_plugin_paths); + _priv_gst_plugin_paths = NULL; +#endif + + clock = gst_system_clock_obtain (); + gst_object_unref (clock); + gst_object_unref (clock); + + _priv_gst_registry_cleanup (); + + g_type_class_unref (g_type_class_peek (gst_object_get_type ())); + g_type_class_unref (g_type_class_peek (gst_pad_get_type ())); + g_type_class_unref (g_type_class_peek (gst_element_factory_get_type ())); + g_type_class_unref (g_type_class_peek (gst_element_get_type ())); + g_type_class_unref (g_type_class_peek (gst_type_find_factory_get_type ())); + g_type_class_unref (g_type_class_peek (gst_bin_get_type ())); + g_type_class_unref (g_type_class_peek (gst_bus_get_type ())); + g_type_class_unref (g_type_class_peek (gst_task_get_type ())); + g_type_class_unref (g_type_class_peek (gst_index_factory_get_type ())); + g_type_class_unref (g_type_class_peek (gst_object_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_bin_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_buffer_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_buffer_copy_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_bus_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_bus_sync_reply_get_type ())); + g_type_class_unref (g_type_class_peek (gst_caps_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_clock_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_clock_return_get_type ())); + g_type_class_unref (g_type_class_peek (gst_clock_entry_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_clock_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_debug_graph_details_get_type ())); + g_type_class_unref (g_type_class_peek (gst_state_get_type ())); + g_type_class_unref (g_type_class_peek (gst_state_change_return_get_type ())); + g_type_class_unref (g_type_class_peek (gst_state_change_get_type ())); + g_type_class_unref (g_type_class_peek (gst_element_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_core_error_get_type ())); + g_type_class_unref (g_type_class_peek (gst_library_error_get_type ())); + g_type_class_unref (g_type_class_peek (gst_plugin_dependency_flags_get_type + ())); + g_type_class_unref (g_type_class_peek (gst_parse_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_resource_error_get_type ())); + g_type_class_unref (g_type_class_peek (gst_search_mode_get_type ())); + g_type_class_unref (g_type_class_peek (gst_stream_error_get_type ())); + g_type_class_unref (g_type_class_peek (gst_stream_status_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_structure_change_type_get_type + ())); + g_type_class_unref (g_type_class_peek (gst_event_type_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_event_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_seek_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_seek_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_qos_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_format_get_type ())); + g_type_class_unref (g_type_class_peek (gst_index_certainty_get_type ())); + g_type_class_unref (g_type_class_peek (gst_index_entry_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_index_lookup_method_get_type ())); + g_type_class_unref (g_type_class_peek (gst_assoc_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_index_resolver_method_get_type + ())); + g_type_class_unref (g_type_class_peek (gst_index_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_debug_level_get_type ())); + g_type_class_unref (g_type_class_peek (gst_debug_color_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_iterator_result_get_type ())); + g_type_class_unref (g_type_class_peek (gst_iterator_item_get_type ())); + g_type_class_unref (g_type_class_peek (gst_message_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_mini_object_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_pad_link_return_get_type ())); + g_type_class_unref (g_type_class_peek (gst_pad_link_check_get_type ())); + g_type_class_unref (g_type_class_peek (gst_flow_return_get_type ())); + g_type_class_unref (g_type_class_peek (gst_activate_mode_get_type ())); + g_type_class_unref (g_type_class_peek (gst_pad_direction_get_type ())); + g_type_class_unref (g_type_class_peek (gst_pad_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_pad_presence_get_type ())); + g_type_class_unref (g_type_class_peek (gst_pad_template_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_pipeline_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_plugin_error_get_type ())); + g_type_class_unref (g_type_class_peek (gst_plugin_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_rank_get_type ())); + g_type_class_unref (g_type_class_peek (gst_query_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_buffering_mode_get_type ())); + g_type_class_unref (g_type_class_peek (gst_tag_merge_mode_get_type ())); + g_type_class_unref (g_type_class_peek (gst_tag_flag_get_type ())); + g_type_class_unref (g_type_class_peek (gst_task_state_get_type ())); + g_type_class_unref (g_type_class_peek (gst_alloc_trace_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_type_find_probability_get_type + ())); + g_type_class_unref (g_type_class_peek (gst_uri_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_parse_error_get_type ())); + g_type_class_unref (g_type_class_peek (gst_param_spec_fraction_get_type ())); + g_type_class_unref (g_type_class_peek (gst_progress_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_buffer_pool_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_memory_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_map_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_caps_intersect_mode_get_type ())); + g_type_class_unref (g_type_class_peek (gst_probe_type_get_type ())); + g_type_class_unref (g_type_class_peek (gst_probe_return_get_type ())); + g_type_class_unref (g_type_class_peek (gst_segment_flags_get_type ())); + + gst_deinitialized = TRUE; + GST_INFO ("deinitialized GStreamer"); +} + +/** + * gst_version: + * @major: (out): pointer to a guint to store the major version number + * @minor: (out): pointer to a guint to store the minor version number + * @micro: (out): pointer to a guint to store the micro version number + * @nano: (out): pointer to a guint to store the nano version number + * + * Gets the version number of the GStreamer library. + */ +void +gst_version (guint * major, guint * minor, guint * micro, guint * nano) +{ + g_return_if_fail (major); + g_return_if_fail (minor); + g_return_if_fail (micro); + g_return_if_fail (nano); + + *major = GST_VERSION_MAJOR; + *minor = GST_VERSION_MINOR; + *micro = GST_VERSION_MICRO; + *nano = GST_VERSION_NANO; +} + +/** + * gst_version_string: + * + * This function returns a string that is useful for describing this version + * of GStreamer to the outside world: user agent strings, logging, ... + * + * Returns: (transfer full): a newly allocated string describing this version + * of GStreamer. + */ + +gchar * +gst_version_string (void) +{ + guint major, minor, micro, nano; + + gst_version (&major, &minor, µ, &nano); + if (nano == 0) + return g_strdup_printf ("GStreamer %d.%d.%d", major, minor, micro); + else if (nano == 1) + return g_strdup_printf ("GStreamer %d.%d.%d (GIT)", major, minor, micro); + else + return g_strdup_printf ("GStreamer %d.%d.%d (prerelease)", major, minor, + micro); +} + +/** + * gst_segtrap_is_enabled: + * + * Some functions in the GStreamer core might install a custom SIGSEGV handler + * to better catch and report errors to the application. Currently this feature + * is enabled by default when loading plugins. + * + * Applications might want to disable this behaviour with the + * gst_segtrap_set_enabled() function. This is typically done if the application + * wants to install its own handler without GStreamer interfering. + * + * Returns: %TRUE if GStreamer is allowed to install a custom SIGSEGV handler. + * + * Since: 0.10.10 + */ +gboolean +gst_segtrap_is_enabled (void) +{ + /* yeps, it's enabled when it's not disabled */ + return !_gst_disable_segtrap; +} + +/** + * gst_segtrap_set_enabled: + * @enabled: whether a custom SIGSEGV handler should be installed. + * + * Applications might want to disable/enable the SIGSEGV handling of + * the GStreamer core. See gst_segtrap_is_enabled() for more information. + * + * Since: 0.10.10 + */ +void +gst_segtrap_set_enabled (gboolean enabled) +{ + _gst_disable_segtrap = !enabled; +} diff --git a/gst/gst.h b/gst/gst.h new file mode 100644 index 0000000..da578d9 --- /dev/null +++ b/gst/gst.h @@ -0,0 +1,112 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gst.h: Main header for GStreamer, apps should include this + * + * 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_H__ +#define __GST_H__ + +#ifndef GST_USE_UNSTABLE_API +#warning "The GStreamer 0.11 API is still unstable and will change in future." +#warning "Define GST_USE_UNSTABLE_API to avoid this warning." +#endif + +#include <glib.h> + +#include <gst/glib-compat.h> + +#include <gst/gstenumtypes.h> +#include <gst/gstversion.h> + +#include <gst/gstbin.h> +#include <gst/gstbuffer.h> +#include <gst/gstbufferlist.h> +#include <gst/gstbufferpool.h> +#include <gst/gstcaps.h> +#include <gst/gstchildproxy.h> +#include <gst/gstclock.h> +#include <gst/gstdatetime.h> +#include <gst/gstdebugutils.h> +#include <gst/gstelement.h> +#include <gst/gstelementmetadata.h> +#include <gst/gsterror.h> +#include <gst/gstevent.h> +#include <gst/gstghostpad.h> +#include <gst/gstindex.h> +#include <gst/gstindexfactory.h> +#include <gst/gstinfo.h> +#include <gst/gstiterator.h> +#include <gst/gstmarshal.h> +#include <gst/gstmessage.h> +#include <gst/gstmemory.h> +#include <gst/gstminiobject.h> +#include <gst/gstobject.h> +#include <gst/gstpad.h> +#include <gst/gstparamspecs.h> +#include <gst/gstpipeline.h> +#include <gst/gstplugin.h> +#include <gst/gstpoll.h> +#include <gst/gstpreset.h> +#include <gst/gstquery.h> +#include <gst/gstregistry.h> +#include <gst/gstsegment.h> +#include <gst/gststructure.h> +#include <gst/gstsystemclock.h> +#include <gst/gsttaglist.h> +#include <gst/gsttagsetter.h> +#include <gst/gsttask.h> +#include <gst/gsttaskpool.h> +#include <gst/gsttrace.h> +#include <gst/gsttypefind.h> +#include <gst/gsttypefindfactory.h> +#include <gst/gsturi.h> +#include <gst/gstutils.h> +#include <gst/gstvalue.h> + +#include <gst/gstparse.h> + +/* API compatibility stuff */ +#include <gst/gstcompat.h> + +G_BEGIN_DECLS + +void gst_init (int *argc, char **argv[]); +gboolean gst_init_check (int *argc, char **argv[], + GError ** err); +gboolean gst_is_initialized (void); +GOptionGroup * gst_init_get_option_group (void); +void gst_deinit (void); + +void gst_version (guint *major, guint *minor, + guint *micro, guint *nano); +gchar * gst_version_string (void); + +gboolean gst_segtrap_is_enabled (void); +void gst_segtrap_set_enabled (gboolean enabled); + +gboolean gst_registry_fork_is_enabled (void); +void gst_registry_fork_set_enabled (gboolean enabled); + +gboolean gst_update_registry (void); + +G_END_DECLS + +#endif /* __GST_H__ */ diff --git a/gst/gst_private.h b/gst/gst_private.h new file mode 100644 index 0000000..3380c88 --- /dev/null +++ b/gst/gst_private.h @@ -0,0 +1,238 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gst_private.h: Private header for within libgst + * + * 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_PRIVATE_H__ +#define __GST_PRIVATE_H__ + +#ifdef HAVE_CONFIG_H +# ifndef GST_LICENSE /* don't include config.h twice, it has no guards */ +# include "config.h" +# endif +#endif + +/* This needs to be before glib.h, since it might be used in inline + * functions */ +extern const char g_log_domain_gstreamer[]; + +#include <glib.h> + +#include <stdlib.h> +#include <string.h> + +/* Needed for GstRegistry * */ +#include "gstregistry.h" +#include "gststructure.h" + +/* we need this in pretty much all files */ +#include "gstinfo.h" + +/* for the flags in the GstPluginDep structure below */ +#include "gstplugin.h" + +/* for the pad cache */ +#include "gstpad.h" + +/* for GstElement */ +#include "gstelement.h" + +G_BEGIN_DECLS + +/* used by gstparse.c and grammar.y */ +struct _GstParseContext { + GList * missing_elements; +}; + +/* used by gstplugin.c and gstregistrybinary.c */ +typedef struct { + /* details registered via gst_plugin_add_dependency() */ + GstPluginDependencyFlags flags; + gchar **env_vars; + gchar **paths; + gchar **names; + + /* information saved from the last time the plugin was loaded (-1 = unset) */ + guint env_hash; /* hash of content of environment variables in env_vars */ + guint stat_hash; /* hash of stat() on all relevant files and directories */ +} GstPluginDep; + +struct _GstPluginPrivate { + GList *deps; /* list of GstPluginDep structures */ + GstStructure *cache_data; +}; + +gboolean priv_gst_plugin_loading_have_whitelist (void); + +guint32 priv_gst_plugin_loading_get_whitelist_hash (void); + +gboolean priv_gst_plugin_desc_is_whitelisted (GstPluginDesc * desc, + const gchar * filename); + +gboolean _priv_plugin_deps_env_vars_changed (GstPlugin * plugin); +gboolean _priv_plugin_deps_files_changed (GstPlugin * plugin); + +gboolean _priv_gst_in_valgrind (void); + +/* init functions called from gst_init(). */ +void _priv_gst_quarks_initialize (void); +void _priv_gst_buffer_initialize (void); +void _priv_gst_buffer_list_initialize (void); +void _priv_gst_structure_initialize (void); +void _priv_gst_caps_initialize (void); +void _priv_gst_event_initialize (void); +void _priv_gst_format_initialize (void); +void _priv_gst_message_initialize (void); +void _priv_gst_memory_initialize (void); +void _priv_gst_meta_initialize (void); +void _priv_gst_plugin_initialize (void); +void _priv_gst_query_initialize (void); +void _priv_gst_tag_initialize (void); +void _priv_gst_value_initialize (void); + +/* Private registry functions */ +gboolean _priv_gst_registry_remove_cache_plugins (GstRegistry *registry); +void _priv_gst_registry_cleanup (void); +gboolean _gst_plugin_loader_client_run (void); + +/* Used in GstBin for manual state handling */ +void _priv_gst_element_state_changed (GstElement *element, GstState oldstate, + GstState newstate, GstState pending); + +/* used in both gststructure.c and gstcaps.c; numbers are completely made up */ +#define STRUCTURE_ESTIMATED_STRING_LEN(s) (16 + gst_structure_n_fields(s) * 22) + +gboolean priv_gst_structure_append_to_gstring (const GstStructure * structure, + GString * s); +/* registry cache backends */ +gboolean priv_gst_registry_binary_read_cache (GstRegistry * registry, const char *location); +gboolean priv_gst_registry_binary_write_cache (GstRegistry * registry, const char *location); + +/* used in gstvalue.c and gststructure.c */ +#define GST_ASCII_IS_STRING(c) (g_ascii_isalnum((c)) || ((c) == '_') || \ + ((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == ':') || \ + ((c) == '.')) + +/* This is only meant for internal uses */ +gint priv_gst_date_time_compare (gconstpointer dt1, gconstpointer dt2); + +#ifndef GST_DISABLE_REGISTRY +/* Secret variable to initialise gst without registry cache */ +extern gboolean _gst_disable_registry_cache; +#endif + +/* provide inline gst_g_value_get_foo_unchecked(), used in gststructure.c */ +#define DEFINE_INLINE_G_VALUE_GET_UNCHECKED(ret_type,name_type,v_field) \ +static inline ret_type \ +gst_g_value_get_##name_type##_unchecked (const GValue *value) \ +{ \ + return value->data[0].v_field; \ +} + +DEFINE_INLINE_G_VALUE_GET_UNCHECKED(gboolean,boolean,v_int) +DEFINE_INLINE_G_VALUE_GET_UNCHECKED(gint,int,v_int) +DEFINE_INLINE_G_VALUE_GET_UNCHECKED(guint,uint,v_uint) +DEFINE_INLINE_G_VALUE_GET_UNCHECKED(gint64,int64,v_int64) +DEFINE_INLINE_G_VALUE_GET_UNCHECKED(guint64,uint64,v_uint64) +DEFINE_INLINE_G_VALUE_GET_UNCHECKED(gfloat,float,v_float) +DEFINE_INLINE_G_VALUE_GET_UNCHECKED(gdouble,double,v_double) +DEFINE_INLINE_G_VALUE_GET_UNCHECKED(const gchar *,string,v_pointer) + + +/*** debugging categories *****************************************************/ + +#ifndef GST_REMOVE_GST_DEBUG + +GST_EXPORT GstDebugCategory *GST_CAT_GST_INIT; +GST_EXPORT GstDebugCategory *GST_CAT_AUTOPLUG; /* FIXME 0.11: remove? */ +GST_EXPORT GstDebugCategory *GST_CAT_AUTOPLUG_ATTEMPT; /* FIXME 0.11: remove? */ +GST_EXPORT GstDebugCategory *GST_CAT_PARENTAGE; +GST_EXPORT GstDebugCategory *GST_CAT_STATES; +GST_EXPORT GstDebugCategory *GST_CAT_SCHEDULING; +GST_EXPORT GstDebugCategory *GST_CAT_BUFFER; +GST_EXPORT GstDebugCategory *GST_CAT_BUFFER_LIST; +GST_EXPORT GstDebugCategory *GST_CAT_BUS; +GST_EXPORT GstDebugCategory *GST_CAT_CAPS; +GST_EXPORT GstDebugCategory *GST_CAT_CLOCK; +GST_EXPORT GstDebugCategory *GST_CAT_ELEMENT_PADS; +GST_EXPORT GstDebugCategory *GST_CAT_PADS; +GST_EXPORT GstDebugCategory *GST_CAT_PERFORMANCE; +GST_EXPORT GstDebugCategory *GST_CAT_PIPELINE; +GST_EXPORT GstDebugCategory *GST_CAT_PLUGIN_LOADING; +GST_EXPORT GstDebugCategory *GST_CAT_PLUGIN_INFO; +GST_EXPORT GstDebugCategory *GST_CAT_PROPERTIES; +GST_EXPORT GstDebugCategory *GST_CAT_NEGOTIATION; +GST_EXPORT GstDebugCategory *GST_CAT_REFCOUNTING; +GST_EXPORT GstDebugCategory *GST_CAT_ERROR_SYSTEM; +GST_EXPORT GstDebugCategory *GST_CAT_EVENT; +GST_EXPORT GstDebugCategory *GST_CAT_MESSAGE; +GST_EXPORT GstDebugCategory *GST_CAT_PARAMS; +GST_EXPORT GstDebugCategory *GST_CAT_CALL_TRACE; +GST_EXPORT GstDebugCategory *GST_CAT_SIGNAL; +GST_EXPORT GstDebugCategory *GST_CAT_PROBE; +GST_EXPORT GstDebugCategory *GST_CAT_REGISTRY; +GST_EXPORT GstDebugCategory *GST_CAT_QOS; +GST_EXPORT GstDebugCategory *GST_CAT_TYPES; /* FIXME 0.11: remove? */ + +/* Categories that should be completely private to + * libgstreamer should be done like this: */ +#define GST_CAT_POLL _priv_GST_CAT_POLL +extern GstDebugCategory *_priv_GST_CAT_POLL; + +#else + +#define GST_CAT_GST_INIT NULL +#define GST_CAT_AUTOPLUG NULL +#define GST_CAT_AUTOPLUG_ATTEMPT NULL +#define GST_CAT_PARENTAGE NULL +#define GST_CAT_STATES NULL +#define GST_CAT_SCHEDULING NULL +#define GST_CAT_DATAFLOW NULL +#define GST_CAT_BUFFER NULL +#define GST_CAT_BUFFER_LIST NULL +#define GST_CAT_BUS NULL +#define GST_CAT_CAPS NULL +#define GST_CAT_CLOCK NULL +#define GST_CAT_ELEMENT_PADS NULL +#define GST_CAT_PADS NULL +#define GST_CAT_PERFORMANCE NULL +#define GST_CAT_PIPELINE NULL +#define GST_CAT_PLUGIN_LOADING NULL +#define GST_CAT_PLUGIN_INFO NULL +#define GST_CAT_PROPERTIES NULL +#define GST_CAT_NEGOTIATION NULL +#define GST_CAT_REFCOUNTING NULL +#define GST_CAT_ERROR_SYSTEM NULL +#define GST_CAT_EVENT NULL +#define GST_CAT_MESSAGE NULL +#define GST_CAT_PARAMS NULL +#define GST_CAT_CALL_TRACE NULL +#define GST_CAT_SIGNAL NULL +#define GST_CAT_PROBE NULL +#define GST_CAT_REGISTRY NULL +#define GST_CAT_QOS NULL +#define GST_CAT_TYPES NULL +#define GST_CAT_POLL NULL + +#endif + + +G_END_DECLS +#endif /* __GST_PRIVATE_H__ */ diff --git a/gst/gstatomicqueue.c b/gst/gstatomicqueue.c new file mode 100644 index 0000000..5b21be6 --- /dev/null +++ b/gst/gstatomicqueue.c @@ -0,0 +1,419 @@ +/* GStreamer + * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com> + * 2011 Wim Taymans <wim.taymans@gmail.com> + * + * gstatomicqueue.c: + * + * 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. + */ + +#include "gst_private.h" + +#include <string.h> + +#include <gst/gst.h> +#include "gstatomicqueue.h" +#include "glib-compat-private.h" + +/** + * SECTION:gstatomicqueue + * @title: GstAtomicQueue + * @short_description: An atomic queue implementation + * + * The #GstAtomicQueue object implements a queue that can be used from multiple + * threads without performing any blocking operations. + * + * Since: 0.10.33 + */ + +/* By default the queue uses 2 * sizeof(gpointer) * clp2 (max_items) of + * memory. clp2(x) is the next power of two >= than x. + * + * The queue can operate in low memory mode, in which it consumes almost + * half the memory at the expense of extra overhead in the readers. This + * is disabled by default because even without LOW_MEM mode, the memory + * consumption is still lower than a plain GList. + */ +#undef LOW_MEM + +typedef struct _GstAQueueMem GstAQueueMem; + +struct _GstAQueueMem +{ + gint size; + gpointer *array; + volatile gint head; + volatile gint tail; + GstAQueueMem *next; + GstAQueueMem *free; +}; + +static guint +clp2 (guint n) +{ + guint res = 1; + + while (res < n) + res <<= 1; + + return res; +} + +static GstAQueueMem * +new_queue_mem (guint size, gint pos) +{ + GstAQueueMem *mem; + + mem = g_new (GstAQueueMem, 1); + + /* we keep the size as a mask for performance */ + mem->size = clp2 (MAX (size, 16)) - 1; + mem->array = g_new0 (gpointer, mem->size + 1); + mem->head = pos; + mem->tail = pos; + mem->next = NULL; + mem->free = NULL; + + return mem; +} + +static void +free_queue_mem (GstAQueueMem * mem) +{ + g_free (mem->array); + g_free (mem); +} + +struct _GstAtomicQueue +{ + volatile gint refcount; +#ifdef LOW_MEM + gint num_readers; +#endif + GstAQueueMem *head_mem; + GstAQueueMem *tail_mem; + GstAQueueMem *free_list; +}; + +static void +add_to_free_list (GstAtomicQueue * queue, GstAQueueMem * mem) +{ + do { + mem->free = g_atomic_pointer_get (&queue->free_list); + } while (!G_ATOMIC_POINTER_COMPARE_AND_EXCHANGE (&queue->free_list, + mem->free, mem)); +} + +static void +clear_free_list (GstAtomicQueue * queue) +{ + GstAQueueMem *free_list; + + /* take the free list and replace with NULL */ + do { + free_list = g_atomic_pointer_get (&queue->free_list); + if (free_list == NULL) + return; + } while (!G_ATOMIC_POINTER_COMPARE_AND_EXCHANGE (&queue->free_list, free_list, + NULL)); + + while (free_list) { + GstAQueueMem *next = free_list->free; + + free_queue_mem (free_list); + + free_list = next; + } +} + +/** + * gst_atomic_queue_new: + * @initial_size: initial queue size + * + * Create a new atomic queue instance. @initial_size will be rounded up to the + * nearest power of 2 and used as the initial size of the queue. + * + * Returns: a new #GstAtomicQueue + * + * Since: 0.10.33 + */ +GstAtomicQueue * +gst_atomic_queue_new (guint initial_size) +{ + GstAtomicQueue *queue; + + queue = g_new (GstAtomicQueue, 1); + + queue->refcount = 1; +#ifdef LOW_MEM + queue->num_readers = 0; +#endif + queue->head_mem = queue->tail_mem = new_queue_mem (initial_size, 0); + queue->free_list = NULL; + + return queue; +} + +/** + * gst_atomic_queue_ref: + * @queue: a #GstAtomicQueue + * + * Increase the refcount of @queue. + * + * Since: 0.10.33 + */ +void +gst_atomic_queue_ref (GstAtomicQueue * queue) +{ + g_return_if_fail (queue != NULL); + + g_atomic_int_inc (&queue->refcount); +} + +static void +gst_atomic_queue_free (GstAtomicQueue * queue) +{ + free_queue_mem (queue->head_mem); + if (queue->head_mem != queue->tail_mem) + free_queue_mem (queue->tail_mem); + clear_free_list (queue); + g_free (queue); +} + +/** + * gst_atomic_queue_unref: + * @queue: a #GstAtomicQueue + * + * Unref @queue and free the memory when the refcount reaches 0. + * + * Since: 0.10.33 + */ +void +gst_atomic_queue_unref (GstAtomicQueue * queue) +{ + g_return_if_fail (queue != NULL); + + if (g_atomic_int_dec_and_test (&queue->refcount)) + gst_atomic_queue_free (queue); +} + +/** + * gst_atomic_queue_peek: + * @queue: a #GstAtomicQueue + * + * Peek the head element of the queue without removing it from the queue. + * + * Returns: the head element of @queue or NULL when the queue is empty. + * + * Since: 0.10.33 + */ +gpointer +gst_atomic_queue_peek (GstAtomicQueue * queue) +{ + GstAQueueMem *head_mem; + gint head, tail, size; + + g_return_val_if_fail (queue != NULL, NULL); + + while (TRUE) { + GstAQueueMem *next; + + head_mem = g_atomic_pointer_get (&queue->head_mem); + + head = g_atomic_int_get (&head_mem->head); + tail = g_atomic_int_get (&head_mem->tail); + size = head_mem->size; + + /* when we are not empty, we can continue */ + if (G_LIKELY (head != tail)) + break; + + /* else array empty, try to take next */ + next = g_atomic_pointer_get (&head_mem->next); + if (next == NULL) + return NULL; + + /* now we try to move the next array as the head memory. If we fail to do that, + * some other reader managed to do it first and we retry */ + if (!G_ATOMIC_POINTER_COMPARE_AND_EXCHANGE (&queue->head_mem, head_mem, + next)) + continue; + + /* when we managed to swing the head pointer the old head is now + * useless and we add it to the freelist. We can't free the memory yet + * because we first need to make sure no reader is accessing it anymore. */ + add_to_free_list (queue, head_mem); + } + + return head_mem->array[head & size]; +} + +/** + * gst_atomic_queue_pop: + * @queue: a #GstAtomicQueue + * + * Get the head element of the queue. + * + * Returns: the head element of @queue or NULL when the queue is empty. + * + * Since: 0.10.33 + */ +gpointer +gst_atomic_queue_pop (GstAtomicQueue * queue) +{ + gpointer ret; + GstAQueueMem *head_mem; + gint head, tail, size; + + g_return_val_if_fail (queue != NULL, NULL); + +#ifdef LOW_MEM + g_atomic_int_inc (&queue->num_readers); +#endif + + do { + while (TRUE) { + GstAQueueMem *next; + + head_mem = g_atomic_pointer_get (&queue->head_mem); + + head = g_atomic_int_get (&head_mem->head); + tail = g_atomic_int_get (&head_mem->tail); + size = head_mem->size; + + /* when we are not empty, we can continue */ + if (G_LIKELY (head != tail)) + break; + + /* else array empty, try to take next */ + next = g_atomic_pointer_get (&head_mem->next); + if (next == NULL) + return NULL; + + /* now we try to move the next array as the head memory. If we fail to do that, + * some other reader managed to do it first and we retry */ + if (!G_ATOMIC_POINTER_COMPARE_AND_EXCHANGE (&queue->head_mem, head_mem, + next)) + continue; + + /* when we managed to swing the head pointer the old head is now + * useless and we add it to the freelist. We can't free the memory yet + * because we first need to make sure no reader is accessing it anymore. */ + add_to_free_list (queue, head_mem); + } + + ret = head_mem->array[head & size]; + } while (!g_atomic_int_compare_and_exchange (&head_mem->head, head, + head + 1)); + +#ifdef LOW_MEM + /* decrement number of readers, when we reach 0 readers we can be sure that + * none is accessing the memory in the free list and we can try to clean up */ + if (g_atomic_int_dec_and_test (&queue->num_readers)) + clear_free_list (queue); +#endif + + return ret; +} + +/** + * gst_atomic_queue_push: + * @queue: a #GstAtomicQueue + * @data: the data + * + * Append @data to the tail of the queue. + * + * Since: 0.10.33 + */ +void +gst_atomic_queue_push (GstAtomicQueue * queue, gpointer data) +{ + GstAQueueMem *tail_mem; + gint head, tail, size; + + g_return_if_fail (queue != NULL); + + do { + while (TRUE) { + GstAQueueMem *mem; + + tail_mem = g_atomic_pointer_get (&queue->tail_mem); + head = g_atomic_int_get (&tail_mem->head); + tail = g_atomic_int_get (&tail_mem->tail); + size = tail_mem->size; + + /* we're not full, continue */ + if (tail - head <= size) + break; + + /* else we need to grow the array, we store a mask so we have to add 1 */ + mem = new_queue_mem ((size << 1) + 1, tail); + + /* try to make our new array visible to other writers */ + if (!G_ATOMIC_POINTER_COMPARE_AND_EXCHANGE (&queue->tail_mem, tail_mem, + mem)) { + /* we tried to swap the new writer array but something changed. This is + * because some other writer beat us to it, we free our memory and try + * again */ + free_queue_mem (mem); + continue; + } + /* make sure that readers can find our new array as well. The one who + * manages to swap the pointer is the only one who can set the next + * pointer to the new array */ + g_atomic_pointer_set (&tail_mem->next, mem); + } + } while (!g_atomic_int_compare_and_exchange (&tail_mem->tail, tail, + tail + 1)); + + tail_mem->array[tail & size] = data; +} + +/** + * gst_atomic_queue_length: + * @queue: a #GstAtomicQueue + * + * Get the amount of items in the queue. + * + * Returns: the number of elements in the queue. + * + * Since: 0.10.33 + */ +guint +gst_atomic_queue_length (GstAtomicQueue * queue) +{ + GstAQueueMem *head_mem, *tail_mem; + gint head, tail; + + g_return_val_if_fail (queue != NULL, 0); + +#ifdef LOW_MEM + g_atomic_int_inc (&queue->num_readers); +#endif + + head_mem = g_atomic_pointer_get (&queue->head_mem); + head = g_atomic_int_get (&head_mem->head); + + tail_mem = g_atomic_pointer_get (&queue->tail_mem); + tail = g_atomic_int_get (&tail_mem->tail); + +#ifdef LOW_MEM + if (g_atomic_int_dec_and_test (&queue->num_readers)) + clear_free_list (queue); +#endif + + return tail - head; +} diff --git a/gst/gstatomicqueue.h b/gst/gstatomicqueue.h new file mode 100644 index 0000000..6d71010 --- /dev/null +++ b/gst/gstatomicqueue.h @@ -0,0 +1,55 @@ +/* GStreamer + * Copyright (C) 2009-2010 Edward Hervey <bilboed@bilboed.com> + * (C) 2011 Wim Taymans <wim.taymans@gmail.com> + * + * gstatomicqueue.h: + * + * 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. + */ + +#include <glib.h> + +#ifndef __GST_ATOMIC_QUEUE_H__ +#define __GST_ATOMIC_QUEUE_H__ + +G_BEGIN_DECLS + +/** + * GstAtomicQueue: + * + * Opaque atomic data queue. + * + * Use the acessor functions to get the stored values. + * + * Since: 0.10.33 + */ +typedef struct _GstAtomicQueue GstAtomicQueue; + + +GstAtomicQueue * gst_atomic_queue_new (guint initial_size); + +void gst_atomic_queue_ref (GstAtomicQueue * queue); +void gst_atomic_queue_unref (GstAtomicQueue * queue); + +void gst_atomic_queue_push (GstAtomicQueue* queue, gpointer data); +gpointer gst_atomic_queue_pop (GstAtomicQueue* queue); +gpointer gst_atomic_queue_peek (GstAtomicQueue* queue); + +guint gst_atomic_queue_length (GstAtomicQueue * queue); + +G_END_DECLS + +#endif /* __GST_ATOMIC_QUEUE_H__ */ diff --git a/gst/gstbin.c b/gst/gstbin.c new file mode 100644 index 0000000..1eebc0f --- /dev/null +++ b/gst/gstbin.c @@ -0,0 +1,3889 @@ +/* GStreamer + * + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2004 Wim Taymans <wim.taymans@gmail.com> + * + * gstbin.c: GstBin container object and support code + * + * 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. + * + * MT safe. + */ + +/** + * SECTION:gstbin + * @short_description: Base class and element that can contain other elements + * + * #GstBin is an element that can contain other #GstElement, allowing them to be + * managed as a group. + * Pads from the child elements can be ghosted to the bin, see #GstGhostPad. + * This makes the bin look like any other elements and enables creation of + * higher-level abstraction elements. + * + * A new #GstBin is created with gst_bin_new(). Use a #GstPipeline instead if you + * want to create a toplevel bin because a normal bin doesn't have a bus or + * handle clock distribution of its own. + * + * After the bin has been created you will typically add elements to it with + * gst_bin_add(). You can remove elements with gst_bin_remove(). + * + * An element can be retrieved from a bin with gst_bin_get_by_name(), using the + * elements name. gst_bin_get_by_name_recurse_up() is mainly used for internal + * purposes and will query the parent bins when the element is not found in the + * current bin. + * + * An iterator of elements in a bin can be retrieved with + * gst_bin_iterate_elements(). Various other iterators exist to retrieve the + * elements in a bin. + * + * gst_object_unref() is used to drop your reference to the bin. + * + * The #GstBin::element-added signal is fired whenever a new element is added to + * the bin. Likewise the #GstBin::element-removed signal is fired whenever an + * element is removed from the bin. + * + * <refsect2><title>Notes</title> + * <para> + * A #GstBin internally intercepts every #GstMessage posted by its children and + * implements the following default behaviour for each of them: + * <variablelist> + * <varlistentry> + * <term>GST_MESSAGE_EOS</term> + * <listitem><para>This message is only posted by sinks in the PLAYING + * state. If all sinks posted the EOS message, this bin will post and EOS + * message upwards.</para></listitem> + * </varlistentry> + * <varlistentry> + * <term>GST_MESSAGE_SEGMENT_START</term> + * <listitem><para>just collected and never forwarded upwards. + * The messages are used to decide when all elements have completed playback + * of their segment.</para></listitem> + * </varlistentry> + * <varlistentry> + * <term>GST_MESSAGE_SEGMENT_DONE</term> + * <listitem><para> Is posted by #GstBin when all elements that posted + * a SEGMENT_START have posted a SEGMENT_DONE.</para></listitem> + * </varlistentry> + * <varlistentry> + * <term>GST_MESSAGE_DURATION</term> + * <listitem><para> Is posted by an element that detected a change + * in the stream duration. The default bin behaviour is to clear any + * cached duration values so that the next duration query will perform + * a full duration recalculation. The duration change is posted to the + * application so that it can refetch the new duration with a duration + * query. Note that these messages can be posted before the bin is + * prerolled, in which case the duration query might fail. + * </para></listitem> + * </varlistentry> + * <varlistentry> + * <term>GST_MESSAGE_CLOCK_LOST</term> + * <listitem><para> This message is posted by an element when it + * can no longer provide a clock. The default bin behaviour is to + * check if the lost clock was the one provided by the bin. If so and + * the bin is currently in the PLAYING state, the message is forwarded to + * the bin parent. + * This message is also generated when a clock provider is removed from + * the bin. If this message is received by the application, it should + * PAUSE the pipeline and set it back to PLAYING to force a new clock + * distribution. + * </para></listitem> + * </varlistentry> + * <varlistentry> + * <term>GST_MESSAGE_CLOCK_PROVIDE</term> + * <listitem><para> This message is generated when an element + * can provide a clock. This mostly happens when a new clock + * provider is added to the bin. The default behaviour of the bin is to + * mark the currently selected clock as dirty, which will perform a clock + * recalculation the next time the bin is asked to provide a clock. + * This message is never sent tot the application but is forwarded to + * the parent of the bin. + * </para></listitem> + * </varlistentry> + * <varlistentry> + * <term>OTHERS</term> + * <listitem><para> posted upwards.</para></listitem> + * </varlistentry> + * </variablelist> + * + * + * A #GstBin implements the following default behaviour for answering to a + * #GstQuery: + * <variablelist> + * <varlistentry> + * <term>GST_QUERY_DURATION</term> + * <listitem><para>If the query has been asked before with the same format + * and the bin is a toplevel bin (ie. has no parent), + * use the cached previous value. If no previous value was cached, the + * query is sent to all sink elements in the bin and the MAXIMUM of all + * values is returned. If the bin is a toplevel bin the value is cached. + * If no sinks are available in the bin, the query fails. + * </para></listitem> + * </varlistentry> + * <varlistentry> + * <term>GST_QUERY_POSITION</term> + * <listitem><para>The query is sent to all sink elements in the bin and the + * MAXIMUM of all values is returned. If no sinks are available in the bin, + * the query fails. + * </para></listitem> + * </varlistentry> + * <varlistentry> + * <term>OTHERS</term> + * <listitem><para>the query is forwarded to all sink elements, the result + * of the first sink that answers the query successfully is returned. If no + * sink is in the bin, the query fails.</para></listitem> + * </varlistentry> + * </variablelist> + * + * A #GstBin will by default forward any event sent to it to all sink elements. + * If all the sinks return TRUE, the bin will also return TRUE, else FALSE is + * returned. If no sinks are in the bin, the event handler will return TRUE. + * + * </para> + * </refsect2> + * + * Last reviewed on 2006-04-28 (0.10.6) + */ + +#include "gst_private.h" + +#include "gstevent.h" +#include "gstbin.h" +#include "gstmarshal.h" +#include "gstinfo.h" +#include "gsterror.h" + +#include "gstindex.h" +#include "gstindexfactory.h" +#include "gstutils.h" +#include "gstchildproxy.h" + +/* latency is by default enabled now. + * live-preroll and no-live-preroll in the environment var GST_COMPAT + * to enables or disable it respectively. + */ +static gboolean enable_latency = TRUE; + +GST_DEBUG_CATEGORY_STATIC (bin_debug); +#define GST_CAT_DEFAULT bin_debug + +/* a bin is toplevel if it has no parent or when it is configured to behave like + * a toplevel bin */ +#define BIN_IS_TOPLEVEL(bin) ((GST_OBJECT_PARENT (bin) == NULL) || bin->priv->asynchandling) + +#define GST_BIN_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BIN, GstBinPrivate)) + +struct _GstBinPrivate +{ + gboolean asynchandling; + /* if we get an ASYNC_DONE message from ourselves, this means that the + * subclass will simulate ASYNC behaviour without having ASYNC children. When + * such an ASYNC_DONE message is posted while we are doing a state change, we + * have to process the message after finishing the state change even when no + * child returned GST_STATE_CHANGE_ASYNC. */ + gboolean pending_async_done; + + guint32 structure_cookie; + + /* cached index */ + GstIndex *index; + /* forward messages from our children */ + gboolean message_forward; + + gboolean posted_eos; +}; + +typedef struct +{ + GstBin *bin; + guint32 cookie; + GstState pending; +} BinContinueData; + +static void gst_bin_dispose (GObject * object); + +static void gst_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstStateChangeReturn gst_bin_change_state_func (GstElement * element, + GstStateChange transition); +static void gst_bin_state_changed (GstElement * element, GstState oldstate, + GstState newstate, GstState pending); +static GstStateChangeReturn gst_bin_get_state_func (GstElement * element, + GstState * state, GstState * pending, GstClockTime timeout); +static void bin_handle_async_done (GstBin * bin, GstStateChangeReturn ret, + gboolean flag_pending, gboolean reset_time); +static void bin_handle_async_start (GstBin * bin); +static void bin_push_state_continue (BinContinueData * data); +static void bin_do_eos (GstBin * bin); + +static gboolean gst_bin_add_func (GstBin * bin, GstElement * element); +static gboolean gst_bin_remove_func (GstBin * bin, GstElement * element); + +static void gst_bin_set_index_func (GstElement * element, GstIndex * index); +static GstIndex *gst_bin_get_index_func (GstElement * element); + +static GstClock *gst_bin_provide_clock_func (GstElement * element); +static gboolean gst_bin_set_clock_func (GstElement * element, GstClock * clock); + +static void gst_bin_handle_message_func (GstBin * bin, GstMessage * message); +static gboolean gst_bin_send_event (GstElement * element, GstEvent * event); +static GstBusSyncReply bin_bus_handler (GstBus * bus, + GstMessage * message, GstBin * bin); +static gboolean gst_bin_query (GstElement * element, GstQuery * query); + +static gboolean gst_bin_do_latency_func (GstBin * bin); + +static void bin_remove_messages (GstBin * bin, GstObject * src, + GstMessageType types); +static void gst_bin_continue_func (BinContinueData * data); +static gint bin_element_is_sink (GstElement * child, GstBin * bin); +static gint bin_element_is_src (GstElement * child, GstBin * bin); + +static GstIterator *gst_bin_sort_iterator_new (GstBin * bin); + +/* Bin signals and properties */ +enum +{ + ELEMENT_ADDED, + ELEMENT_REMOVED, + DO_LATENCY, + LAST_SIGNAL +}; + +#define DEFAULT_ASYNC_HANDLING FALSE +#define DEFAULT_MESSAGE_FORWARD FALSE + +enum +{ + PROP_0, + PROP_ASYNC_HANDLING, + PROP_MESSAGE_FORWARD, + PROP_LAST +}; + +static void gst_bin_child_proxy_init (gpointer g_iface, gpointer iface_data); + +static guint gst_bin_signals[LAST_SIGNAL] = { 0 }; + +#define _do_init \ +{ \ + const gchar *compat; \ + static const GInterfaceInfo iface_info = { \ + gst_bin_child_proxy_init, \ + NULL, \ + NULL}; \ + \ + g_type_add_interface_static (g_define_type_id, GST_TYPE_CHILD_PROXY, &iface_info); \ + \ + GST_DEBUG_CATEGORY_INIT (bin_debug, "bin", GST_DEBUG_BOLD, \ + "debugging info for the 'bin' container element"); \ + \ + /* compatibility stuff */ \ + compat = g_getenv ("GST_COMPAT"); \ + if (compat != NULL) { \ + if (strstr (compat, "no-live-preroll")) \ + enable_latency = FALSE; \ + else if (strstr (compat, "live-preroll")) \ + enable_latency = TRUE; \ + } \ +} + +#define gst_bin_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstBin, gst_bin, GST_TYPE_ELEMENT, _do_init); + +static GstObject * +gst_bin_child_proxy_get_child_by_index (GstChildProxy * child_proxy, + guint index) +{ + GstObject *res; + GstBin *bin; + + bin = GST_BIN_CAST (child_proxy); + + GST_OBJECT_LOCK (bin); + if ((res = g_list_nth_data (bin->children, index))) + gst_object_ref (res); + GST_OBJECT_UNLOCK (bin); + + return res; +} + +static guint +gst_bin_child_proxy_get_children_count (GstChildProxy * child_proxy) +{ + guint num; + GstBin *bin; + + bin = GST_BIN_CAST (child_proxy); + + GST_OBJECT_LOCK (bin); + num = bin->numchildren; + GST_OBJECT_UNLOCK (bin); + + return num; +} + +static void +gst_bin_child_proxy_init (gpointer g_iface, gpointer iface_data) +{ + GstChildProxyInterface *iface = g_iface; + + iface->get_children_count = gst_bin_child_proxy_get_children_count; + iface->get_child_by_index = gst_bin_child_proxy_get_child_by_index; +} + +static gboolean +_gst_boolean_accumulator (GSignalInvocationHint * ihint, + GValue * return_accu, const GValue * handler_return, gpointer dummy) +{ + gboolean myboolean; + + myboolean = g_value_get_boolean (handler_return); + if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP)) + g_value_set_boolean (return_accu, myboolean); + + GST_DEBUG ("invocation %d, %d", ihint->run_type, myboolean); + + /* stop emission */ + return FALSE; +} + +static void +gst_bin_class_init (GstBinClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GError *err; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + g_type_class_add_private (klass, sizeof (GstBinPrivate)); + + gobject_class->set_property = gst_bin_set_property; + gobject_class->get_property = gst_bin_get_property; + + /** + * GstBin:async-handling + * + * If set to #TRUE, the bin will handle asynchronous state changes. + * This should be used only if the bin subclass is modifying the state + * of its children on its own. + * + * Since: 0.10.13 + */ + g_object_class_install_property (gobject_class, PROP_ASYNC_HANDLING, + g_param_spec_boolean ("async-handling", "Async Handling", + "The bin will handle Asynchronous state changes", + DEFAULT_ASYNC_HANDLING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstBin::element-added: + * @bin: the #GstBin + * @element: the #GstElement that was added to the bin + * + * Will be emitted after the element was added to the bin. + */ + gst_bin_signals[ELEMENT_ADDED] = + g_signal_new ("element-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstBinClass, element_added), NULL, + NULL, gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_ELEMENT); + /** + * GstBin::element-removed: + * @bin: the #GstBin + * @element: the #GstElement that was removed from the bin + * + * Will be emitted after the element was removed from the bin. + */ + gst_bin_signals[ELEMENT_REMOVED] = + g_signal_new ("element-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstBinClass, element_removed), NULL, + NULL, gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_ELEMENT); + /** + * GstBin::do-latency: + * @bin: the #GstBin + * + * Will be emitted when the bin needs to perform latency calculations. This + * signal is only emited for toplevel bins or when async-handling is + * enabled. + * + * Only one signal handler is invoked. If no signals are connected, the + * default handler is invoked, which will query and distribute the lowest + * possible latency to all sinks. + * + * Connect to this signal if the default latency calculations are not + * sufficient, like when you need different latencies for different sinks in + * the same pipeline. + * + * Since: 0.10.22 + */ + gst_bin_signals[DO_LATENCY] = + g_signal_new ("do-latency", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstBinClass, do_latency), + _gst_boolean_accumulator, NULL, gst_marshal_BOOLEAN__VOID, + G_TYPE_BOOLEAN, 0, G_TYPE_NONE); + + /** + * GstBin:message-forward + * + * Forward all children messages, even those that would normally be filtered by + * the bin. This can be interesting when one wants to be notified of the EOS + * state of individual elements, for example. + * + * The messages are converted to an ELEMENT message with the bin as the + * source. The structure of the message is named 'GstBinForwarded' and contains + * a field named 'message' of type GST_TYPE_MESSAGE that contains the original + * forwarded message. + * + * Since: 0.10.31 + */ + g_object_class_install_property (gobject_class, PROP_MESSAGE_FORWARD, + g_param_spec_boolean ("message-forward", "Message Forward", + "Forwards all children messages", + DEFAULT_MESSAGE_FORWARD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gobject_class->dispose = gst_bin_dispose; + + gst_element_class_set_metadata (gstelement_class, "Generic bin", + "Generic/Bin", + "Simple container object", + "Erik Walthinsen <omega@cse.ogi.edu>," + "Wim Taymans <wim.taymans@gmail.com>"); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_bin_change_state_func); + gstelement_class->state_changed = GST_DEBUG_FUNCPTR (gst_bin_state_changed); + gstelement_class->get_state = GST_DEBUG_FUNCPTR (gst_bin_get_state_func); + gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_bin_get_index_func); + gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_bin_set_index_func); + gstelement_class->provide_clock = + GST_DEBUG_FUNCPTR (gst_bin_provide_clock_func); + gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_bin_set_clock_func); + + gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_bin_send_event); + gstelement_class->query = GST_DEBUG_FUNCPTR (gst_bin_query); + + klass->add_element = GST_DEBUG_FUNCPTR (gst_bin_add_func); + klass->remove_element = GST_DEBUG_FUNCPTR (gst_bin_remove_func); + klass->handle_message = GST_DEBUG_FUNCPTR (gst_bin_handle_message_func); + + klass->do_latency = GST_DEBUG_FUNCPTR (gst_bin_do_latency_func); + + GST_DEBUG ("creating bin thread pool"); + err = NULL; + klass->pool = + g_thread_pool_new ((GFunc) gst_bin_continue_func, NULL, -1, FALSE, &err); + if (err != NULL) { + g_critical ("could alloc threadpool %s", err->message); + } +} + +static void +gst_bin_init (GstBin * bin) +{ + GstBus *bus; + + bin->numchildren = 0; + bin->children = NULL; + bin->children_cookie = 0; + bin->messages = NULL; + bin->provided_clock = NULL; + bin->clock_dirty = FALSE; + + /* Set up a bus for listening to child elements */ + bus = g_object_new (GST_TYPE_BUS, "enable-async", FALSE, NULL); + bin->child_bus = bus; + GST_DEBUG_OBJECT (bin, "using bus %" GST_PTR_FORMAT " to listen to children", + bus); + gst_bus_set_sync_handler (bus, (GstBusSyncHandler) bin_bus_handler, bin); + + bin->priv = GST_BIN_GET_PRIVATE (bin); + bin->priv->asynchandling = DEFAULT_ASYNC_HANDLING; + bin->priv->structure_cookie = 0; + bin->priv->message_forward = DEFAULT_MESSAGE_FORWARD; +} + +static void +gst_bin_dispose (GObject * object) +{ + GstBin *bin = GST_BIN_CAST (object); + GstBus **child_bus_p = &bin->child_bus; + GstClock **provided_clock_p = &bin->provided_clock; + GstElement **clock_provider_p = &bin->clock_provider; + GstIndex **index_p = &bin->priv->index; + + GST_CAT_DEBUG_OBJECT (GST_CAT_REFCOUNTING, object, "dispose"); + + GST_OBJECT_LOCK (object); + gst_object_replace ((GstObject **) child_bus_p, NULL); + gst_object_replace ((GstObject **) provided_clock_p, NULL); + gst_object_replace ((GstObject **) clock_provider_p, NULL); + gst_object_replace ((GstObject **) index_p, NULL); + bin_remove_messages (bin, NULL, GST_MESSAGE_ANY); + GST_OBJECT_UNLOCK (object); + + while (bin->children) { + gst_bin_remove (bin, GST_ELEMENT_CAST (bin->children->data)); + } + if (G_UNLIKELY (bin->children != NULL)) { + g_critical ("could not remove elements from bin '%s'", + GST_STR_NULL (GST_OBJECT_NAME (object))); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/** + * gst_bin_new: + * @name: the name of the new bin + * + * Creates a new bin with the given name. + * + * Returns: (transfer full): a new #GstBin + */ +GstElement * +gst_bin_new (const gchar * name) +{ + return gst_element_factory_make ("bin", name); +} + +static void +gst_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstBin *gstbin; + + gstbin = GST_BIN_CAST (object); + + switch (prop_id) { + case PROP_ASYNC_HANDLING: + GST_OBJECT_LOCK (gstbin); + gstbin->priv->asynchandling = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (gstbin); + break; + case PROP_MESSAGE_FORWARD: + GST_OBJECT_LOCK (gstbin); + gstbin->priv->message_forward = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (gstbin); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstBin *gstbin; + + gstbin = GST_BIN_CAST (object); + + switch (prop_id) { + case PROP_ASYNC_HANDLING: + GST_OBJECT_LOCK (gstbin); + g_value_set_boolean (value, gstbin->priv->asynchandling); + GST_OBJECT_UNLOCK (gstbin); + break; + case PROP_MESSAGE_FORWARD: + GST_OBJECT_LOCK (gstbin); + g_value_set_boolean (value, gstbin->priv->message_forward); + GST_OBJECT_UNLOCK (gstbin); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* return the cached index */ +static GstIndex * +gst_bin_get_index_func (GstElement * element) +{ + GstBin *bin; + GstIndex *result; + + bin = GST_BIN_CAST (element); + + GST_OBJECT_LOCK (bin); + if ((result = bin->priv->index)) + gst_object_ref (result); + GST_OBJECT_UNLOCK (bin); + + return result; +} + +/* set the index on all elements in this bin + * + * MT safe + */ +static void +gst_bin_set_index_func (GstElement * element, GstIndex * index) +{ + GstBin *bin; + gboolean done; + GstIterator *it; + GstIndex *old; + GValue data = { 0, }; + + bin = GST_BIN_CAST (element); + + GST_OBJECT_LOCK (bin); + old = bin->priv->index; + if (G_UNLIKELY (old == index)) + goto was_set; + if (index) + gst_object_ref (index); + bin->priv->index = index; + GST_OBJECT_UNLOCK (bin); + + if (old) + gst_object_unref (old); + + it = gst_bin_iterate_elements (bin); + + /* set the index on all elements in the bin */ + done = FALSE; + while (!done) { + switch (gst_iterator_next (it, &data)) { + case GST_ITERATOR_OK: + { + GstElement *child = g_value_get_object (&data); + + GST_DEBUG_OBJECT (bin, "setting index on '%s'", + GST_ELEMENT_NAME (child)); + gst_element_set_index (child, index); + + g_value_reset (&data); + break; + } + case GST_ITERATOR_RESYNC: + GST_DEBUG_OBJECT (bin, "iterator doing resync"); + gst_iterator_resync (it); + break; + default: + case GST_ITERATOR_DONE: + GST_DEBUG_OBJECT (bin, "iterator done"); + done = TRUE; + break; + } + } + g_value_unset (&data); + gst_iterator_free (it); + return; + +was_set: + { + GST_DEBUG_OBJECT (bin, "index was already set"); + GST_OBJECT_UNLOCK (bin); + return; + } +} + +/* set the clock on all elements in this bin + * + * MT safe + */ +static gboolean +gst_bin_set_clock_func (GstElement * element, GstClock * clock) +{ + GstBin *bin; + gboolean done; + GstIterator *it; + gboolean res = TRUE; + GValue data = { 0, }; + + bin = GST_BIN_CAST (element); + + it = gst_bin_iterate_elements (bin); + + done = FALSE; + while (!done) { + switch (gst_iterator_next (it, &data)) { + case GST_ITERATOR_OK: + { + GstElement *child = g_value_get_object (&data); + + res &= gst_element_set_clock (child, clock); + + g_value_reset (&data); + break; + } + case GST_ITERATOR_RESYNC: + GST_DEBUG_OBJECT (bin, "iterator doing resync"); + gst_iterator_resync (it); + res = TRUE; + break; + default: + case GST_ITERATOR_DONE: + GST_DEBUG_OBJECT (bin, "iterator done"); + done = TRUE; + break; + } + } + g_value_unset (&data); + gst_iterator_free (it); + + return res; +} + +/* get the clock for this bin by asking all of the children in this bin + * + * The ref of the returned clock in increased so unref after usage. + * + * We loop the elements in state order and pick the last clock we can + * get. This makes sure we get a clock from the source. + * + * MT safe + */ +static GstClock * +gst_bin_provide_clock_func (GstElement * element) +{ + GstClock *result = NULL; + GstElement *provider = NULL; + GstBin *bin; + GstIterator *it; + gboolean done; + GValue val = { 0, }; + GstClock **provided_clock_p; + GstElement **clock_provider_p; + + bin = GST_BIN_CAST (element); + + GST_OBJECT_LOCK (bin); + if (!bin->clock_dirty) + goto not_dirty; + + GST_DEBUG_OBJECT (bin, "finding new clock"); + + it = gst_bin_sort_iterator_new (bin); + GST_OBJECT_UNLOCK (bin); + + done = FALSE; + while (!done) { + switch (gst_iterator_next (it, &val)) { + case GST_ITERATOR_OK: + { + GstElement *child = g_value_get_object (&val); + GstClock *clock; + + clock = gst_element_provide_clock (child); + if (clock) { + GST_DEBUG_OBJECT (bin, "found candidate clock %p by element %s", + clock, GST_ELEMENT_NAME (child)); + if (result) { + gst_object_unref (result); + gst_object_unref (provider); + } + result = clock; + provider = gst_object_ref (child); + } + + g_value_reset (&val); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (it); + break; + default: + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + g_value_unset (&val); + gst_iterator_free (it); + + GST_OBJECT_LOCK (bin); + if (!bin->clock_dirty) { + if (provider) + gst_object_unref (provider); + if (result) + gst_object_unref (result); + result = NULL; + + goto not_dirty; + } + + provided_clock_p = &bin->provided_clock; + clock_provider_p = &bin->clock_provider; + gst_object_replace ((GstObject **) provided_clock_p, (GstObject *) result); + gst_object_replace ((GstObject **) clock_provider_p, (GstObject *) provider); + bin->clock_dirty = FALSE; + GST_DEBUG_OBJECT (bin, + "provided new clock %" GST_PTR_FORMAT " by provider %" GST_PTR_FORMAT, + result, provider); + /* Provider is not being returned to caller, just the result */ + if (provider) + gst_object_unref (provider); + GST_OBJECT_UNLOCK (bin); + + return result; + +not_dirty: + { + if ((result = bin->provided_clock)) + gst_object_ref (result); + GST_DEBUG_OBJECT (bin, "returning old clock %p", result); + GST_OBJECT_UNLOCK (bin); + + return result; + } +} + +/* + * functions for manipulating cached messages + */ +typedef struct +{ + GstObject *src; + GstMessageType types; +} MessageFind; + +/* check if a message is of given src and type */ +static gint +message_check (GstMessage * message, MessageFind * target) +{ + gboolean eq = TRUE; + + if (target->src) + eq &= GST_MESSAGE_SRC (message) == target->src; + if (target->types) + eq &= (GST_MESSAGE_TYPE (message) & target->types) != 0; + GST_LOG ("looking at message %p: %d", message, eq); + + return (eq ? 0 : 1); +} + +static GList * +find_message (GstBin * bin, GstObject * src, GstMessageType types) +{ + GList *result; + MessageFind find; + + find.src = src; + find.types = types; + + result = g_list_find_custom (bin->messages, &find, + (GCompareFunc) message_check); + + if (result) { + GST_DEBUG_OBJECT (bin, "we found a message %p from %s matching types %08x", + result->data, GST_OBJECT_NAME (GST_MESSAGE_CAST (result->data)->src), + types); + } else { + GST_DEBUG_OBJECT (bin, "no message found matching types %08x", types); +#ifndef GST_DISABLE_GST_DEBUG + { + guint i; + + for (i = 0; i < 32; i++) + if (types & (1 << i)) + GST_DEBUG_OBJECT (bin, " %s", gst_message_type_get_name (1 << i)); + } +#endif + } + + return result; +} + +/* with LOCK, returns TRUE if message had a valid SRC, takes ownership of + * the message. + * + * A message that is cached and has the same SRC and type is replaced + * by the given message. + */ +static gboolean +bin_replace_message (GstBin * bin, GstMessage * message, GstMessageType types) +{ + GList *previous; + GstObject *src; + gboolean res = TRUE; + + if ((src = GST_MESSAGE_SRC (message))) { + /* first find the previous message posted by this element */ + if ((previous = find_message (bin, src, types))) { + GstMessage *previous_msg; + + /* if we found a previous message, replace it */ + previous_msg = previous->data; + previous->data = message; + + GST_DEBUG_OBJECT (bin, "replace old message %s from %s with %s message", + GST_MESSAGE_TYPE_NAME (previous_msg), GST_ELEMENT_NAME (src), + GST_MESSAGE_TYPE_NAME (message)); + + gst_message_unref (previous_msg); + } else { + /* keep new message */ + bin->messages = g_list_prepend (bin->messages, message); + + GST_DEBUG_OBJECT (bin, "got new message %p, %s from %s", + message, GST_MESSAGE_TYPE_NAME (message), GST_ELEMENT_NAME (src)); + } + } else { + GST_DEBUG_OBJECT (bin, "got message %s from (NULL), not processing", + GST_MESSAGE_TYPE_NAME (message)); + res = FALSE; + gst_message_unref (message); + } + return res; +} + +/* with LOCK. Remove all messages of given types */ +static void +bin_remove_messages (GstBin * bin, GstObject * src, GstMessageType types) +{ + MessageFind find; + GList *walk, *next; + + find.src = src; + find.types = types; + + for (walk = bin->messages; walk; walk = next) { + GstMessage *message = (GstMessage *) walk->data; + + next = g_list_next (walk); + + if (message_check (message, &find) == 0) { + GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message), + "deleting message %p of types 0x%08x", message, types); + bin->messages = g_list_delete_link (bin->messages, walk); + gst_message_unref (message); + } else { + GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message), + "not deleting message %p of type 0x%08x", message, + GST_MESSAGE_TYPE (message)); + } + } +} + + +/* Check if the bin is EOS. We do this by scanning all sinks and + * checking if they posted an EOS message. + * + * call with bin LOCK */ +static gboolean +is_eos (GstBin * bin, guint32 * seqnum) +{ + gboolean result; + gint n_eos = 0; + GList *walk, *msgs; + + result = TRUE; + for (walk = bin->children; walk; walk = g_list_next (walk)) { + GstElement *element; + + element = GST_ELEMENT_CAST (walk->data); + if (bin_element_is_sink (element, bin) == 0) { + /* check if element posted EOS */ + if ((msgs = + find_message (bin, GST_OBJECT_CAST (element), GST_MESSAGE_EOS))) { + GST_DEBUG ("sink '%s' posted EOS", GST_ELEMENT_NAME (element)); + *seqnum = gst_message_get_seqnum (GST_MESSAGE_CAST (msgs->data)); + n_eos++; + } else { + GST_DEBUG ("sink '%s' did not post EOS yet", + GST_ELEMENT_NAME (element)); + result = FALSE; + break; + } + } + } + /* FIXME: Some tests (e.g. elements/capsfilter) use + * pipelines with a dangling sinkpad but no sink element. + * These tests assume that no EOS message is ever + * posted on the bus so let's keep that behaviour. + * In valid pipelines this doesn't make a difference. + */ + return result && n_eos > 0; +} + +static void +unlink_pads (const GValue * item, gpointer user_data) +{ + GstPad *pad; + GstPad *peer; + + pad = g_value_get_object (item); + + if ((peer = gst_pad_get_peer (pad))) { + if (gst_pad_get_direction (pad) == GST_PAD_SRC) + gst_pad_unlink (pad, peer); + else + gst_pad_unlink (peer, pad); + gst_object_unref (peer); + } +} + +/* vmethod that adds an element to a bin + * + * MT safe + */ +static gboolean +gst_bin_add_func (GstBin * bin, GstElement * element) +{ + gchar *elem_name; + GstIterator *it; + gboolean is_sink, is_source; + GstMessage *clock_message = NULL, *async_message = NULL; + GstStateChangeReturn ret; + + GST_DEBUG_OBJECT (bin, "element :%s", GST_ELEMENT_NAME (element)); + + /* we obviously can't add ourself to ourself */ + if (G_UNLIKELY (GST_ELEMENT_CAST (element) == GST_ELEMENT_CAST (bin))) + goto adding_itself; + + /* get the element name to make sure it is unique in this bin. */ + GST_OBJECT_LOCK (element); + elem_name = g_strdup (GST_ELEMENT_NAME (element)); + is_sink = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_IS_SINK); + is_source = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_IS_SOURCE); + GST_OBJECT_UNLOCK (element); + + GST_OBJECT_LOCK (bin); + + /* then check to see if the element's name is already taken in the bin, + * we can safely take the lock here. This check is probably bogus because + * you can safely change the element name after this check and before setting + * the object parent. The window is very small though... */ + if (G_UNLIKELY (!gst_object_check_uniqueness (bin->children, elem_name))) + goto duplicate_name; + + /* set the element's parent and add the element to the bin's list of children */ + if (G_UNLIKELY (!gst_object_set_parent (GST_OBJECT_CAST (element), + GST_OBJECT_CAST (bin)))) + goto had_parent; + + /* if we add a sink we become a sink */ + if (is_sink) { + GST_CAT_DEBUG_OBJECT (GST_CAT_PARENTAGE, bin, "element \"%s\" was sink", + elem_name); + GST_OBJECT_FLAG_SET (bin, GST_ELEMENT_IS_SINK); + } + if (is_source) { + GST_CAT_DEBUG_OBJECT (GST_CAT_PARENTAGE, bin, "element \"%s\" was source", + elem_name); + GST_OBJECT_FLAG_SET (bin, GST_ELEMENT_IS_SOURCE); + } + if (gst_element_provides_clock (element)) { + GST_DEBUG_OBJECT (bin, "element \"%s\" can provide a clock", elem_name); + clock_message = + gst_message_new_clock_provide (GST_OBJECT_CAST (element), NULL, TRUE); + } + + bin->children = g_list_prepend (bin->children, element); + bin->numchildren++; + bin->children_cookie++; + bin->priv->structure_cookie++; + + /* distribute the bus */ + gst_element_set_bus (element, bin->child_bus); + + /* propagate the current base_time, start_time and clock */ + gst_element_set_base_time (element, GST_ELEMENT_CAST (bin)->base_time); + gst_element_set_start_time (element, GST_ELEMENT_START_TIME (bin)); + /* it's possible that the element did not accept the clock but + * that is not important right now. When the pipeline goes to PLAYING, + * a new clock will be selected */ + gst_element_set_clock (element, GST_ELEMENT_CLOCK (bin)); + /* set the cached index on the children */ + if (bin->priv->index) + gst_element_set_index (element, bin->priv->index); + + ret = GST_STATE_RETURN (bin); + /* no need to update the state if we are in error */ + if (ret == GST_STATE_CHANGE_FAILURE) + goto no_state_recalc; + + /* update the bin state, the new element could have been an ASYNC or + * NO_PREROLL element */ + ret = GST_STATE_RETURN (element); + GST_DEBUG_OBJECT (bin, "added %s element", + gst_element_state_change_return_get_name (ret)); + + switch (ret) { + case GST_STATE_CHANGE_ASYNC: + { + /* create message to track this aync element when it posts an async-done + * message */ + async_message = gst_message_new_async_start (GST_OBJECT_CAST (element)); + break; + } + case GST_STATE_CHANGE_NO_PREROLL: + /* ignore all async elements we might have and commit our state */ + bin_handle_async_done (bin, ret, FALSE, FALSE); + break; + case GST_STATE_CHANGE_FAILURE: + break; + default: + break; + } + +no_state_recalc: + GST_OBJECT_UNLOCK (bin); + + /* post the messages on the bus of the element so that the bin can handle + * them */ + if (clock_message) + gst_element_post_message (GST_ELEMENT_CAST (element), clock_message); + + if (async_message) + gst_element_post_message (GST_ELEMENT_CAST (element), async_message); + + /* unlink all linked pads */ + it = gst_element_iterate_pads (element); + gst_iterator_foreach (it, (GstIteratorForeachFunction) unlink_pads, NULL); + gst_iterator_free (it); + + GST_CAT_DEBUG_OBJECT (GST_CAT_PARENTAGE, bin, "added element \"%s\"", + elem_name); + g_free (elem_name); + + g_signal_emit (bin, gst_bin_signals[ELEMENT_ADDED], 0, element); + gst_child_proxy_child_added ((GstObject *) bin, (GstObject *) element); + + return TRUE; + + /* ERROR handling here */ +adding_itself: + { + GST_OBJECT_LOCK (bin); + g_warning ("Cannot add bin '%s' to itself", GST_ELEMENT_NAME (bin)); + GST_OBJECT_UNLOCK (bin); + return FALSE; + } +duplicate_name: + { + g_warning ("Name '%s' is not unique in bin '%s', not adding", + elem_name, GST_ELEMENT_NAME (bin)); + GST_OBJECT_UNLOCK (bin); + g_free (elem_name); + return FALSE; + } +had_parent: + { + g_warning ("Element '%s' already has parent", elem_name); + GST_OBJECT_UNLOCK (bin); + g_free (elem_name); + return FALSE; + } +} + +/** + * gst_bin_add: + * @bin: a #GstBin + * @element: (transfer full): the #GstElement to add + * + * Adds the given element to the bin. Sets the element's parent, and thus + * takes ownership of the element. An element can only be added to one bin. + * + * If the element's pads are linked to other pads, the pads will be unlinked + * before the element is added to the bin. + * + * <note> + * When you add an element to an already-running pipeline, you will have to + * take care to set the state of the newly-added element to the desired + * state (usually PLAYING or PAUSED, same you set the pipeline to originally) + * with gst_element_set_state(), or use gst_element_sync_state_with_parent(). + * The bin or pipeline will not take care of this for you. + * </note> + * + * MT safe. + * + * Returns: TRUE if the element could be added, FALSE if + * the bin does not want to accept the element. + */ +gboolean +gst_bin_add (GstBin * bin, GstElement * element) +{ + GstBinClass *bclass; + gboolean result; + + g_return_val_if_fail (GST_IS_BIN (bin), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + + bclass = GST_BIN_GET_CLASS (bin); + + if (G_UNLIKELY (bclass->add_element == NULL)) + goto no_function; + + GST_CAT_DEBUG (GST_CAT_PARENTAGE, "adding element %s to bin %s", + GST_STR_NULL (GST_ELEMENT_NAME (element)), + GST_STR_NULL (GST_ELEMENT_NAME (bin))); + + result = bclass->add_element (bin, element); + + return result; + + /* ERROR handling */ +no_function: + { + g_warning ("adding elements to bin '%s' is not supported", + GST_ELEMENT_NAME (bin)); + return FALSE; + } +} + +/* remove an element from the bin + * + * MT safe + */ +static gboolean +gst_bin_remove_func (GstBin * bin, GstElement * element) +{ + gchar *elem_name; + GstIterator *it; + gboolean is_sink, is_source, othersink, othersource, found; + GstMessage *clock_message = NULL; + GstClock **provided_clock_p; + GstElement **clock_provider_p; + GList *walk, *next; + gboolean other_async, this_async, have_no_preroll; + GstStateChangeReturn ret; + + GST_DEBUG_OBJECT (bin, "element :%s", GST_ELEMENT_NAME (element)); + + GST_OBJECT_LOCK (element); + /* Check if the element is already being removed and immediately + * return */ + if (G_UNLIKELY (GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_UNPARENTING))) + goto already_removing; + + GST_OBJECT_FLAG_SET (element, GST_ELEMENT_UNPARENTING); + /* grab element name so we can print it */ + elem_name = g_strdup (GST_ELEMENT_NAME (element)); + is_sink = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_IS_SINK); + is_source = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_IS_SOURCE); + GST_OBJECT_UNLOCK (element); + + /* unlink all linked pads */ + it = gst_element_iterate_pads (element); + gst_iterator_foreach (it, (GstIteratorForeachFunction) unlink_pads, NULL); + gst_iterator_free (it); + + GST_OBJECT_LOCK (bin); + found = FALSE; + othersink = FALSE; + othersource = FALSE; + have_no_preroll = FALSE; + /* iterate the elements, we collect which ones are async and no_preroll. We + * also remove the element when we find it. */ + for (walk = bin->children; walk; walk = next) { + GstElement *child = GST_ELEMENT_CAST (walk->data); + + next = g_list_next (walk); + + if (child == element) { + found = TRUE; + /* remove the element */ + bin->children = g_list_delete_link (bin->children, walk); + } else { + gboolean child_sink, child_source; + + GST_OBJECT_LOCK (child); + child_sink = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_IS_SINK); + child_source = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_IS_SOURCE); + /* when we remove a sink, check if there are other sinks. */ + if (is_sink && !othersink && child_sink) + othersink = TRUE; + if (is_source && !othersource && child_source) + othersource = TRUE; + /* check if we have NO_PREROLL children */ + if (GST_STATE_RETURN (child) == GST_STATE_CHANGE_NO_PREROLL) + have_no_preroll = TRUE; + GST_OBJECT_UNLOCK (child); + } + } + + /* the element must have been in the bin's list of children */ + if (G_UNLIKELY (!found)) + goto not_in_bin; + + /* we now removed the element from the list of elements, increment the cookie + * so that others can detect a change in the children list. */ + bin->numchildren--; + bin->children_cookie++; + bin->priv->structure_cookie++; + + if (is_sink && !othersink) { + /* we're not a sink anymore */ + GST_DEBUG_OBJECT (bin, "we removed the last sink"); + GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_IS_SINK); + } + if (is_source && !othersource) { + /* we're not a source anymore */ + GST_DEBUG_OBJECT (bin, "we removed the last source"); + GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_IS_SOURCE); + } + + + /* if the clock provider for this element is removed, we lost + * the clock as well, we need to inform the parent of this + * so that it can select a new clock */ + if (bin->clock_provider == element) { + GST_DEBUG_OBJECT (bin, "element \"%s\" provided the clock", elem_name); + bin->clock_dirty = TRUE; + clock_message = + gst_message_new_clock_lost (GST_OBJECT_CAST (bin), bin->provided_clock); + provided_clock_p = &bin->provided_clock; + clock_provider_p = &bin->clock_provider; + gst_object_replace ((GstObject **) provided_clock_p, NULL); + gst_object_replace ((GstObject **) clock_provider_p, NULL); + } + + /* remove messages for the element, if there was a pending ASYNC_START + * message we must see if removing the element caused the bin to lose its + * async state. */ + this_async = FALSE; + other_async = FALSE; + for (walk = bin->messages; walk; walk = next) { + GstMessage *message = (GstMessage *) walk->data; + GstElement *src = GST_ELEMENT_CAST (GST_MESSAGE_SRC (message)); + gboolean remove; + + next = g_list_next (walk); + remove = FALSE; + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ASYNC_START: + if (src == element) + this_async = TRUE; + else + other_async = TRUE; + + GST_DEBUG_OBJECT (src, "looking at message %p", message); + break; + case GST_MESSAGE_STRUCTURE_CHANGE: + { + GstElement *owner; + + GST_DEBUG_OBJECT (src, "looking at structure change message %p", + message); + /* it's unlikely that this message is still in the list of messages + * because this would mean that a link/unlink is busy in another thread + * while we remove the element. We still have to remove the message + * because we might not receive the done message anymore when the element + * is removed from the bin. */ + gst_message_parse_structure_change (message, NULL, &owner, NULL); + if (owner == element) + remove = TRUE; + break; + } + default: + break; + } + if (src == element) + remove = TRUE; + + if (remove) { + /* delete all message types */ + GST_DEBUG_OBJECT (src, "deleting message %p of element \"%s\"", + message, elem_name); + bin->messages = g_list_delete_link (bin->messages, walk); + gst_message_unref (message); + } + } + + /* get last return */ + ret = GST_STATE_RETURN (bin); + + /* no need to update the state if we are in error */ + if (ret == GST_STATE_CHANGE_FAILURE) + goto no_state_recalc; + + if (!other_async && this_async) { + /* all other elements were not async and we removed the async one, + * handle the async-done case because we are not async anymore now. */ + GST_DEBUG_OBJECT (bin, + "we removed the last async element, have no_preroll %d", + have_no_preroll); + + /* the current state return of the bin depends on if there are no_preroll + * elements in the pipeline or not */ + if (have_no_preroll) + ret = GST_STATE_CHANGE_NO_PREROLL; + else + ret = GST_STATE_CHANGE_SUCCESS; + + bin_handle_async_done (bin, ret, FALSE, FALSE); + } else { + GST_DEBUG_OBJECT (bin, + "recalc state preroll: %d, other async: %d, this async %d", + have_no_preroll, other_async, this_async); + + if (have_no_preroll) { + ret = GST_STATE_CHANGE_NO_PREROLL; + } else if (other_async) { + /* there are other async elements and we were not doing an async state + * change, change our pending state and go async */ + if (GST_STATE_PENDING (bin) == GST_STATE_VOID_PENDING) { + GST_STATE_NEXT (bin) = GST_STATE (bin); + GST_STATE_PENDING (bin) = GST_STATE (bin); + } + ret = GST_STATE_CHANGE_ASYNC; + } + GST_STATE_RETURN (bin) = ret; + } +no_state_recalc: + GST_OBJECT_UNLOCK (bin); + + if (clock_message) + gst_element_post_message (GST_ELEMENT_CAST (bin), clock_message); + + GST_CAT_INFO_OBJECT (GST_CAT_PARENTAGE, bin, "removed child \"%s\"", + elem_name); + g_free (elem_name); + + gst_element_set_bus (element, NULL); + + /* Clear the clock we provided to the element */ + gst_element_set_clock (element, NULL); + + /* we ref here because after the _unparent() the element can be disposed + * and we still need it to reset the UNPARENTING flag and fire a signal. */ + gst_object_ref (element); + gst_object_unparent (GST_OBJECT_CAST (element)); + + GST_OBJECT_LOCK (element); + GST_OBJECT_FLAG_UNSET (element, GST_ELEMENT_UNPARENTING); + GST_OBJECT_UNLOCK (element); + + g_signal_emit (bin, gst_bin_signals[ELEMENT_REMOVED], 0, element); + gst_child_proxy_child_removed ((GstObject *) bin, (GstObject *) element); + + /* element is really out of our control now */ + gst_object_unref (element); + + return TRUE; + + /* ERROR handling */ +not_in_bin: + { + g_warning ("Element '%s' is not in bin '%s'", elem_name, + GST_ELEMENT_NAME (bin)); + GST_OBJECT_UNLOCK (bin); + g_free (elem_name); + return FALSE; + } +already_removing: + { + GST_CAT_INFO_OBJECT (GST_CAT_PARENTAGE, bin, "already removing child"); + GST_OBJECT_UNLOCK (element); + return FALSE; + } +} + +/** + * gst_bin_remove: + * @bin: a #GstBin + * @element: (transfer none): the #GstElement to remove + * + * Removes the element from the bin, unparenting it as well. + * Unparenting the element means that the element will be dereferenced, + * so if the bin holds the only reference to the element, the element + * will be freed in the process of removing it from the bin. If you + * want the element to still exist after removing, you need to call + * gst_object_ref() before removing it from the bin. + * + * If the element's pads are linked to other pads, the pads will be unlinked + * before the element is removed from the bin. + * + * MT safe. + * + * Returns: TRUE if the element could be removed, FALSE if + * the bin does not want to remove the element. + */ +gboolean +gst_bin_remove (GstBin * bin, GstElement * element) +{ + GstBinClass *bclass; + gboolean result; + + g_return_val_if_fail (GST_IS_BIN (bin), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + + bclass = GST_BIN_GET_CLASS (bin); + + if (G_UNLIKELY (bclass->remove_element == NULL)) + goto no_function; + + GST_CAT_DEBUG (GST_CAT_PARENTAGE, "removing element %s from bin %s", + GST_ELEMENT_NAME (element), GST_ELEMENT_NAME (bin)); + + result = bclass->remove_element (bin, element); + + return result; + + /* ERROR handling */ +no_function: + { + g_warning ("removing elements from bin '%s' is not supported", + GST_ELEMENT_NAME (bin)); + return FALSE; + } +} + +/** + * gst_bin_iterate_elements: + * @bin: a #GstBin + * + * Gets an iterator for the elements in this bin. + * + * Each element yielded by the iterator will have its refcount increased, so + * unref after use. + * + * MT safe. Caller owns returned value. + * + * Returns: (transfer full): a #GstIterator of #GstElement, or NULL + */ +GstIterator * +gst_bin_iterate_elements (GstBin * bin) +{ + GstIterator *result; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + + GST_OBJECT_LOCK (bin); + result = gst_iterator_new_list (GST_TYPE_ELEMENT, + GST_OBJECT_GET_LOCK (bin), + &bin->children_cookie, &bin->children, (GObject *) bin, NULL); + GST_OBJECT_UNLOCK (bin); + + return result; +} + +static GstIteratorItem +iterate_child_recurse (GstIterator * it, const GValue * item) +{ + GstElement *child = g_value_get_object (item); + + if (GST_IS_BIN (child)) { + GstIterator *other = gst_bin_iterate_recurse (GST_BIN_CAST (child)); + + gst_iterator_push (it, other); + } + return GST_ITERATOR_ITEM_PASS; +} + +/** + * gst_bin_iterate_recurse: + * @bin: a #GstBin + * + * Gets an iterator for the elements in this bin. + * This iterator recurses into GstBin children. + * + * Each element yielded by the iterator will have its refcount increased, so + * unref after use. + * + * MT safe. Caller owns returned value. + * + * Returns: (transfer full): a #GstIterator of #GstElement, or NULL + */ +GstIterator * +gst_bin_iterate_recurse (GstBin * bin) +{ + GstIterator *result; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + + GST_OBJECT_LOCK (bin); + result = gst_iterator_new_list (GST_TYPE_ELEMENT, + GST_OBJECT_GET_LOCK (bin), + &bin->children_cookie, + &bin->children, + (GObject *) bin, (GstIteratorItemFunction) iterate_child_recurse); + GST_OBJECT_UNLOCK (bin); + + return result; +} + +/* returns 0 when TRUE because this is a GCompareFunc */ +/* MT safe */ +static gint +bin_element_is_sink (GstElement * child, GstBin * bin) +{ + gboolean is_sink; + + /* we lock the child here for the remainder of the function to + * get its name and flag safely. */ + GST_OBJECT_LOCK (child); + is_sink = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_IS_SINK); + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, + "child %s %s sink", GST_OBJECT_NAME (child), is_sink ? "is" : "is not"); + + GST_OBJECT_UNLOCK (child); + return is_sink ? 0 : 1; +} + +static gint +sink_iterator_filter (const GValue * vchild, GValue * vbin) +{ + GstBin *bin = g_value_get_object (vbin); + GstElement *child = g_value_get_object (vchild); + + return (bin_element_is_sink (child, bin)); +} + +/** + * gst_bin_iterate_sinks: + * @bin: a #GstBin + * + * Gets an iterator for all elements in the bin that have the + * #GST_ELEMENT_IS_SINK flag set. + * + * Each element yielded by the iterator will have its refcount increased, so + * unref after use. + * + * MT safe. Caller owns returned value. + * + * Returns: (transfer full): a #GstIterator of #GstElement, or NULL + */ +GstIterator * +gst_bin_iterate_sinks (GstBin * bin) +{ + GstIterator *children; + GstIterator *result; + GValue vbin = { 0, }; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + + g_value_init (&vbin, GST_TYPE_BIN); + g_value_set_object (&vbin, bin); + + children = gst_bin_iterate_elements (bin); + result = gst_iterator_filter (children, + (GCompareFunc) sink_iterator_filter, &vbin); + + g_value_unset (&vbin); + + return result; +} + +/* returns 0 when TRUE because this is a GCompareFunc */ +/* MT safe */ +static gint +bin_element_is_src (GstElement * child, GstBin * bin) +{ + gboolean is_src; + + /* we lock the child here for the remainder of the function to + * get its name and other info safely. */ + GST_OBJECT_LOCK (child); + is_src = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_IS_SOURCE); + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, + "child %s %s src", GST_OBJECT_NAME (child), is_src ? "is" : "is not"); + + GST_OBJECT_UNLOCK (child); + return is_src ? 0 : 1; +} + +static gint +src_iterator_filter (const GValue * vchild, GValue * vbin) +{ + GstBin *bin = g_value_get_object (vbin); + GstElement *child = g_value_get_object (vchild); + + return (bin_element_is_src (child, bin)); +} + +/** + * gst_bin_iterate_sources: + * @bin: a #GstBin + * + * Gets an iterator for all elements in the bin that have the + * #GST_ELEMENT_IS_SOURCE flag set. + * + * Each element yielded by the iterator will have its refcount increased, so + * unref after use. + * + * MT safe. Caller owns returned value. + * + * Returns: (transfer full): a #GstIterator of #GstElement, or NULL + */ +GstIterator * +gst_bin_iterate_sources (GstBin * bin) +{ + GstIterator *children; + GstIterator *result; + GValue vbin = { 0, }; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + + g_value_init (&vbin, GST_TYPE_BIN); + g_value_set_object (&vbin, bin); + + children = gst_bin_iterate_elements (bin); + result = gst_iterator_filter (children, + (GCompareFunc) src_iterator_filter, &vbin); + + g_value_unset (&vbin); + + return result; +} + +/* + * MT safe + */ +static GstStateChangeReturn +gst_bin_get_state_func (GstElement * element, GstState * state, + GstState * pending, GstClockTime timeout) +{ + GstStateChangeReturn ret; + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "getting state"); + + ret = + GST_ELEMENT_CLASS (parent_class)->get_state (element, state, pending, + timeout); + + return ret; +} + +/*********************************************** + * Topologically sorted iterator + * see http://en.wikipedia.org/wiki/Topological_sorting + * + * For each element in the graph, an entry is kept in a HashTable + * with its number of srcpad connections (degree). + * We then change state of all elements without dependencies + * (degree 0) and decrement the degree of all elements connected + * on the sinkpads. When an element reaches degree 0, its state is + * changed next. + * When all elements are handled the algorithm stops. + */ +typedef struct _GstBinSortIterator +{ + GstIterator it; + GQueue *queue; /* elements queued for state change */ + GstBin *bin; /* bin we iterate */ + gint mode; /* adding or removing dependency */ + GstElement *best; /* next element with least dependencies */ + gint best_deg; /* best degree */ + GHashTable *hash; /* hashtable with element dependencies */ + gboolean dirty; /* we detected structure change */ +} GstBinSortIterator; + +static void +gst_bin_sort_iterator_copy (const GstBinSortIterator * it, + GstBinSortIterator * copy) +{ + GHashTableIter iter; + gpointer key, value; + + copy->queue = g_queue_copy (it->queue); + g_queue_foreach (copy->queue, (GFunc) gst_object_ref, NULL); + + copy->bin = gst_object_ref (it->bin); + if (it->best) + copy->best = gst_object_ref (it->best); + + copy->hash = g_hash_table_new (NULL, NULL); + g_hash_table_iter_init (&iter, it->hash); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_insert (copy->hash, key, value); +} + +/* we add and subtract 1 to make sure we don't confuse NULL and 0 */ +#define HASH_SET_DEGREE(bit, elem, deg) \ + g_hash_table_replace (bit->hash, elem, GINT_TO_POINTER(deg+1)) +#define HASH_GET_DEGREE(bit, elem) \ + (GPOINTER_TO_INT(g_hash_table_lookup (bit->hash, elem))-1) + +/* add element to queue of next elements in the iterator. + * We push at the tail to give higher priority elements a + * chance first */ +static void +add_to_queue (GstBinSortIterator * bit, GstElement * element) +{ + GST_DEBUG_OBJECT (bit->bin, "adding '%s' to queue", + GST_ELEMENT_NAME (element)); + gst_object_ref (element); + g_queue_push_tail (bit->queue, element); + HASH_SET_DEGREE (bit, element, -1); +} + +static void +remove_from_queue (GstBinSortIterator * bit, GstElement * element) +{ + GList *find; + + if ((find = g_queue_find (bit->queue, element))) { + GST_DEBUG_OBJECT (bit->bin, "removing '%s' from queue", + GST_ELEMENT_NAME (element)); + + g_queue_delete_link (bit->queue, find); + gst_object_unref (element); + } else { + GST_DEBUG_OBJECT (bit->bin, "unable to remove '%s' from queue", + GST_ELEMENT_NAME (element)); + } +} + +/* clear the queue, unref all objects as we took a ref when + * we added them to the queue */ +static void +clear_queue (GQueue * queue) +{ + gpointer p; + + while ((p = g_queue_pop_head (queue))) + gst_object_unref (p); +} + +/* set all degrees to 0. Elements marked as a sink are + * added to the queue immediately. Since we only look at the SINK flag of the + * element, it is possible that we add non-sinks to the queue. These will be + * removed from the queue again when we can prove that it provides data for some + * other element. */ +static void +reset_degree (GstElement * element, GstBinSortIterator * bit) +{ + gboolean is_sink; + + /* sinks are added right away */ + GST_OBJECT_LOCK (element); + is_sink = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_IS_SINK); + GST_OBJECT_UNLOCK (element); + + if (is_sink) { + add_to_queue (bit, element); + } else { + /* others are marked with 0 and handled when sinks are done */ + HASH_SET_DEGREE (bit, element, 0); + } +} + +/* adjust the degree of all elements connected to the given + * element. If a degree of an element drops to 0, it is + * added to the queue of elements to schedule next. + * + * We have to make sure not to cross the bin boundary this element + * belongs to. + */ +static void +update_degree (GstElement * element, GstBinSortIterator * bit) +{ + gboolean linked = FALSE; + + GST_OBJECT_LOCK (element); + /* don't touch degree if element has no sinkpads */ + if (element->numsinkpads != 0) { + /* loop over all sinkpads, decrement degree for all connected + * elements in this bin */ + GList *pads; + + for (pads = element->sinkpads; pads; pads = g_list_next (pads)) { + GstPad *pad, *peer; + + pad = GST_PAD_CAST (pads->data); + + /* we're iterating over the sinkpads, check if it's busy in a link/unlink */ + if (G_UNLIKELY (find_message (bit->bin, GST_OBJECT_CAST (pad), + GST_MESSAGE_STRUCTURE_CHANGE))) { + /* mark the iterator as dirty because we won't be updating the degree + * of the peer parent now. This would result in the 'loop detected' + * later on because the peer parent element could become the best next + * element with a degree > 0. We will simply continue our state + * changes and we'll eventually resync when the unlink completed and + * the iterator cookie is updated. */ + bit->dirty = TRUE; + continue; + } + + if ((peer = gst_pad_get_peer (pad))) { + GstElement *peer_element; + + if ((peer_element = gst_pad_get_parent_element (peer))) { + GST_OBJECT_LOCK (peer_element); + /* check that we don't go outside of this bin */ + if (GST_OBJECT_CAST (peer_element)->parent == + GST_OBJECT_CAST (bit->bin)) { + gint old_deg, new_deg; + + old_deg = HASH_GET_DEGREE (bit, peer_element); + + /* check to see if we added an element as sink that was not really a + * sink because it was connected to some other element. */ + if (old_deg == -1) { + remove_from_queue (bit, peer_element); + old_deg = 0; + } + new_deg = old_deg + bit->mode; + + GST_DEBUG_OBJECT (bit->bin, + "change element %s, degree %d->%d, linked to %s", + GST_ELEMENT_NAME (peer_element), old_deg, new_deg, + GST_ELEMENT_NAME (element)); + + /* update degree, it is possible that an element was in 0 and + * reaches -1 here. This would mean that the element had no sinkpads + * but became linked while the state change was happening. We will + * resync on this with the structure change message. */ + if (new_deg == 0) { + /* degree hit 0, add to queue */ + add_to_queue (bit, peer_element); + } else { + HASH_SET_DEGREE (bit, peer_element, new_deg); + } + linked = TRUE; + } + GST_OBJECT_UNLOCK (peer_element); + gst_object_unref (peer_element); + } + gst_object_unref (peer); + } + } + } + if (!linked) { + GST_DEBUG_OBJECT (bit->bin, "element %s not linked on any sinkpads", + GST_ELEMENT_NAME (element)); + } + GST_OBJECT_UNLOCK (element); +} + +/* find the next best element not handled yet. This is the one + * with the lowest non-negative degree */ +static void +find_element (GstElement * element, GstBinSortIterator * bit) +{ + gint degree; + + /* element is already handled */ + if ((degree = HASH_GET_DEGREE (bit, element)) < 0) + return; + + /* first element or element with smaller degree */ + if (bit->best == NULL || bit->best_deg > degree) { + bit->best = element; + bit->best_deg = degree; + } +} + +/* get next element in iterator. the returned element has the + * refcount increased */ +static GstIteratorResult +gst_bin_sort_iterator_next (GstBinSortIterator * bit, GValue * result) +{ + GstElement *best; + GstBin *bin = bit->bin; + + /* empty queue, we have to find a next best element */ + if (g_queue_is_empty (bit->queue)) { + + bit->best = NULL; + bit->best_deg = G_MAXINT; + g_list_foreach (bin->children, (GFunc) find_element, bit); + if ((best = bit->best)) { + /* when we detected an unlink, don't warn because our degrees might be + * screwed up. We will resync later */ + if (bit->best_deg != 0 && !bit->dirty) { + /* we don't fail on this one yet */ + GST_WARNING_OBJECT (bin, "loop dected in graph"); + g_warning ("loop detected in the graph of bin '%s'!!", + GST_ELEMENT_NAME (bin)); + } + /* best unhandled element, schedule as next element */ + GST_DEBUG_OBJECT (bin, "queue empty, next best: %s", + GST_ELEMENT_NAME (best)); + HASH_SET_DEGREE (bit, best, -1); + g_value_set_object (result, best); + } else { + GST_DEBUG_OBJECT (bin, "queue empty, elements exhausted"); + /* no more unhandled elements, we are done */ + return GST_ITERATOR_DONE; + } + } else { + /* everything added to the queue got reffed */ + best = g_queue_pop_head (bit->queue); + g_value_set_object (result, best); + gst_object_unref (best); + } + + GST_DEBUG_OBJECT (bin, "queue head gives %s", GST_ELEMENT_NAME (best)); + /* update degrees of linked elements */ + update_degree (best, bit); + + return GST_ITERATOR_OK; +} + +/* clear queues, recalculate the degrees and restart. */ +static void +gst_bin_sort_iterator_resync (GstBinSortIterator * bit) +{ + GstBin *bin = bit->bin; + + GST_DEBUG_OBJECT (bin, "resync"); + bit->dirty = FALSE; + clear_queue (bit->queue); + /* reset degrees */ + g_list_foreach (bin->children, (GFunc) reset_degree, bit); + /* calc degrees, incrementing */ + bit->mode = 1; + g_list_foreach (bin->children, (GFunc) update_degree, bit); + /* for the rest of the function we decrement the degrees */ + bit->mode = -1; +} + +/* clear queues, unref bin and free iterator. */ +static void +gst_bin_sort_iterator_free (GstBinSortIterator * bit) +{ + GstBin *bin = bit->bin; + + GST_DEBUG_OBJECT (bin, "free"); + clear_queue (bit->queue); + g_queue_free (bit->queue); + g_hash_table_destroy (bit->hash); + gst_object_unref (bin); +} + +/* should be called with the bin LOCK held */ +static GstIterator * +gst_bin_sort_iterator_new (GstBin * bin) +{ + GstBinSortIterator *result; + + /* we don't need an ItemFunction because we ref the items in the _next + * method already */ + result = (GstBinSortIterator *) + gst_iterator_new (sizeof (GstBinSortIterator), + GST_TYPE_ELEMENT, + GST_OBJECT_GET_LOCK (bin), + &bin->priv->structure_cookie, + (GstIteratorCopyFunction) gst_bin_sort_iterator_copy, + (GstIteratorNextFunction) gst_bin_sort_iterator_next, + (GstIteratorItemFunction) NULL, + (GstIteratorResyncFunction) gst_bin_sort_iterator_resync, + (GstIteratorFreeFunction) gst_bin_sort_iterator_free); + result->queue = g_queue_new (); + result->hash = g_hash_table_new (NULL, NULL); + gst_object_ref (bin); + result->bin = bin; + gst_bin_sort_iterator_resync (result); + + return (GstIterator *) result; +} + +/** + * gst_bin_iterate_sorted: + * @bin: a #GstBin + * + * Gets an iterator for the elements in this bin in topologically + * sorted order. This means that the elements are returned from + * the most downstream elements (sinks) to the sources. + * + * This function is used internally to perform the state changes + * of the bin elements and for clock selection. + * + * Each element yielded by the iterator will have its refcount increased, so + * unref after use. + * + * MT safe. Caller owns returned value. + * + * Returns: (transfer full): a #GstIterator of #GstElement, or NULL + */ +GstIterator * +gst_bin_iterate_sorted (GstBin * bin) +{ + GstIterator *result; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + + GST_OBJECT_LOCK (bin); + result = gst_bin_sort_iterator_new (bin); + GST_OBJECT_UNLOCK (bin); + + return result; +} + +static GstStateChangeReturn +gst_bin_element_set_state (GstBin * bin, GstElement * element, + GstClockTime base_time, GstClockTime start_time, GstState current, + GstState next) +{ + GstStateChangeReturn ret; + GstState pending, child_current, child_pending; + gboolean locked; + GList *found; + + GST_STATE_LOCK (element); + + GST_OBJECT_LOCK (element); + /* set base_time and start time on child */ + GST_ELEMENT_START_TIME (element) = start_time; + element->base_time = base_time; + /* peel off the locked flag */ + locked = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_LOCKED_STATE); + /* Get the previous set_state result to preserve NO_PREROLL and ASYNC */ + ret = GST_STATE_RETURN (element); + child_current = GST_STATE (element); + child_pending = GST_STATE_PENDING (element); + GST_OBJECT_UNLOCK (element); + + /* skip locked elements */ + if (G_UNLIKELY (locked)) + goto locked; + + /* if the element was no preroll, just start changing the state regardless + * if it had async elements (in the case of a bin) because they won't preroll + * anyway. */ + if (G_UNLIKELY (ret == GST_STATE_CHANGE_NO_PREROLL)) { + GST_DEBUG_OBJECT (element, "element is NO_PREROLL, ignore async elements"); + goto no_preroll; + } + + GST_OBJECT_LOCK (bin); + pending = GST_STATE_PENDING (bin); + + /* Try not to change the state of elements that are already in the state we're + * going to */ + if (!(next == GST_STATE_PLAYING || child_pending != GST_STATE_VOID_PENDING || + (child_pending == GST_STATE_VOID_PENDING && + ((pending > child_current && next > child_current) || + (pending < child_current && next < child_current))))) + goto unneeded; + + /* the element was busy with an upwards async state change, we must wait for + * an ASYNC_DONE message before we attemp to change the state. */ + if ((found = + find_message (bin, GST_OBJECT_CAST (element), + GST_MESSAGE_ASYNC_START))) { +#ifndef GST_DISABLE_GST_DEBUG + GstMessage *message = GST_MESSAGE_CAST (found->data); + + GST_DEBUG_OBJECT (element, "element message %p, %s async busy", + message, GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))); +#endif + /* only wait for upward state changes */ + if (next > current) { + /* We found an async element check if we can force its state to change or + * if we have to wait for it to preroll. */ + if (G_UNLIKELY (!enable_latency)) { + g_warning ("Future versions of GStreamer will wait for element \"%s\"\n" + "\tto preroll in order to perform correct latency calculations.\n" + "\tPlease verify that the application continues to work correctly by\n" + "\tsetting the environment variable GST_COMPAT to a value containing\n" + "\tthe string 'live-preroll'.", GST_ELEMENT_NAME (element)); + goto no_latency; + } + goto was_busy; + } + } +no_latency: + GST_OBJECT_UNLOCK (bin); + +no_preroll: + GST_DEBUG_OBJECT (bin, + "setting element %s to %s, base_time %" GST_TIME_FORMAT, + GST_ELEMENT_NAME (element), gst_element_state_get_name (next), + GST_TIME_ARGS (base_time)); + + /* change state */ + ret = gst_element_set_state (element, next); + + GST_STATE_UNLOCK (element); + + return ret; + +locked: + { + GST_DEBUG_OBJECT (element, + "element is locked, return previous return %s", + gst_element_state_change_return_get_name (ret)); + GST_STATE_UNLOCK (element); + return ret; + } +was_busy: + { + GST_DEBUG_OBJECT (element, "element was busy, delaying state change"); + GST_OBJECT_UNLOCK (bin); + GST_STATE_UNLOCK (element); + return GST_STATE_CHANGE_ASYNC; + } +unneeded: + { + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "skipping transition from %s to %s, since bin pending" + " is %s : last change state return follows", + gst_element_state_get_name (child_current), + gst_element_state_get_name (next), + gst_element_state_get_name (pending)); + GST_OBJECT_UNLOCK (bin); + GST_STATE_UNLOCK (element); + return ret; + } +} + +/* gst_iterator_fold functions for pads_activate + * Stop the iterator if activating one pad failed. */ +static gboolean +activate_pads (const GValue * vpad, GValue * ret, gboolean * active) +{ + GstPad *pad = g_value_get_object (vpad); + gboolean cont = TRUE; + + if (!(cont = gst_pad_set_active (pad, *active))) + g_value_set_boolean (ret, FALSE); + + return cont; +} + +/* returns false on error or early cutout of the fold, true if all + * pads in @iter were (de)activated successfully. */ +static gboolean +iterator_activate_fold_with_resync (GstIterator * iter, gpointer user_data) +{ + GstIteratorResult ires; + GValue ret = { 0 }; + + /* no need to unset this later, it's just a boolean */ + g_value_init (&ret, G_TYPE_BOOLEAN); + g_value_set_boolean (&ret, TRUE); + + while (1) { + ires = gst_iterator_fold (iter, (GstIteratorFoldFunction) activate_pads, + &ret, user_data); + switch (ires) { + case GST_ITERATOR_RESYNC: + /* need to reset the result again */ + g_value_set_boolean (&ret, TRUE); + gst_iterator_resync (iter); + break; + case GST_ITERATOR_DONE: + /* all pads iterated, return collected value */ + goto done; + default: + /* iterator returned _ERROR or premature end with _OK, + * mark an error and exit */ + g_value_set_boolean (&ret, FALSE); + goto done; + } + } +done: + /* return collected value */ + return g_value_get_boolean (&ret); +} + +/* is called with STATE_LOCK + */ +static gboolean +gst_bin_src_pads_activate (GstBin * bin, gboolean active) +{ + GstIterator *iter; + gboolean fold_ok; + + GST_DEBUG_OBJECT (bin, "src_pads_activate with active %d", active); + + iter = gst_element_iterate_src_pads ((GstElement *) bin); + fold_ok = iterator_activate_fold_with_resync (iter, &active); + gst_iterator_free (iter); + if (G_UNLIKELY (!fold_ok)) + goto failed; + + GST_DEBUG_OBJECT (bin, "pads_activate successful"); + + return TRUE; + + /* ERRORS */ +failed: + { + GST_DEBUG_OBJECT (bin, "source pads_activate failed"); + return FALSE; + } +} + +/** + * gst_bin_recalculate_latency: + * @bin: a #GstBin + * + * Query @bin for the current latency using and reconfigures this latency to all the + * elements with a LATENCY event. + * + * This method is typically called on the pipeline when a #GST_MESSAGE_LATENCY + * is posted on the bus. + * + * This function simply emits the 'do-latency' signal so any custom latency + * calculations will be performed. + * + * Returns: %TRUE if the latency could be queried and reconfigured. + * + * Since: 0.10.22. + */ +gboolean +gst_bin_recalculate_latency (GstBin * bin) +{ + gboolean res; + + g_signal_emit (bin, gst_bin_signals[DO_LATENCY], 0, &res); + GST_DEBUG_OBJECT (bin, "latency returned %d", res); + + return res; +} + +static gboolean +gst_bin_do_latency_func (GstBin * bin) +{ + GstQuery *query; + GstElement *element; + GstClockTime min_latency, max_latency; + gboolean res; + + g_return_val_if_fail (GST_IS_BIN (bin), FALSE); + + element = GST_ELEMENT_CAST (bin); + + GST_DEBUG_OBJECT (element, "querying latency"); + + query = gst_query_new_latency (); + if ((res = gst_element_query (element, query))) { + gboolean live; + + gst_query_parse_latency (query, &live, &min_latency, &max_latency); + + GST_DEBUG_OBJECT (element, + "got min latency %" GST_TIME_FORMAT ", max latency %" + GST_TIME_FORMAT ", live %d", GST_TIME_ARGS (min_latency), + GST_TIME_ARGS (max_latency), live); + + if (max_latency < min_latency) { + /* this is an impossible situation, some parts of the pipeline might not + * work correctly. We post a warning for now. */ + GST_ELEMENT_WARNING (element, CORE, CLOCK, (NULL), + ("Impossible to configure latency: max %" GST_TIME_FORMAT " < min %" + GST_TIME_FORMAT ". Add queues or other buffering elements.", + GST_TIME_ARGS (max_latency), GST_TIME_ARGS (min_latency))); + } + + /* configure latency on elements */ + res = gst_element_send_event (element, gst_event_new_latency (min_latency)); + if (res) { + GST_INFO_OBJECT (element, "configured latency of %" GST_TIME_FORMAT, + GST_TIME_ARGS (min_latency)); + } else { + GST_WARNING_OBJECT (element, + "did not really configure latency of %" GST_TIME_FORMAT, + GST_TIME_ARGS (min_latency)); + } + } else { + /* this is not a real problem, we just don't configure any latency. */ + GST_WARNING_OBJECT (element, "failed to query latency"); + } + gst_query_unref (query); + + return res; +} + +static void +gst_bin_state_changed (GstElement * element, GstState oldstate, + GstState newstate, GstState pending) +{ + GstElementClass *pklass = (GstElementClass *) parent_class; + + if (newstate == GST_STATE_PLAYING && pending == GST_STATE_VOID_PENDING) + bin_do_eos (GST_BIN_CAST (element)); + + if (pklass->state_changed) + pklass->state_changed (element, oldstate, newstate, pending); +} + +static GstStateChangeReturn +gst_bin_change_state_func (GstElement * element, GstStateChange transition) +{ + GstBin *bin; + GstStateChangeReturn ret; + GstState current, next; + gboolean have_async; + gboolean have_no_preroll; + GstClockTime base_time, start_time; + GstIterator *it; + gboolean done; + GValue data = { 0, }; + + /* we don't need to take the STATE_LOCK, it is already taken */ + current = (GstState) GST_STATE_TRANSITION_CURRENT (transition); + next = (GstState) GST_STATE_TRANSITION_NEXT (transition); + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "changing state of children from %s to %s", + gst_element_state_get_name (current), gst_element_state_get_name (next)); + + bin = GST_BIN_CAST (element); + + switch (next) { + case GST_STATE_PLAYING: + { + gboolean toplevel; + + GST_OBJECT_LOCK (bin); + toplevel = BIN_IS_TOPLEVEL (bin); + GST_OBJECT_UNLOCK (bin); + + if (toplevel) + gst_bin_recalculate_latency (bin); + break; + } + case GST_STATE_PAUSED: + /* Clear EOS list on next PAUSED */ + GST_OBJECT_LOCK (bin); + GST_DEBUG_OBJECT (element, "clearing EOS elements"); + bin_remove_messages (bin, NULL, GST_MESSAGE_EOS); + bin->priv->posted_eos = FALSE; + GST_OBJECT_UNLOCK (bin); + if (current == GST_STATE_READY) + if (!(gst_bin_src_pads_activate (bin, TRUE))) + goto activate_failure; + break; + case GST_STATE_READY: + /* Clear message list on next READY */ + GST_OBJECT_LOCK (bin); + GST_DEBUG_OBJECT (element, "clearing all cached messages"); + bin_remove_messages (bin, NULL, GST_MESSAGE_ANY); + GST_OBJECT_UNLOCK (bin); + if (current == GST_STATE_PAUSED) + if (!(gst_bin_src_pads_activate (bin, FALSE))) + goto activate_failure; + break; + case GST_STATE_NULL: + if (current == GST_STATE_READY) + if (!(gst_bin_src_pads_activate (bin, FALSE))) + goto activate_failure; + break; + default: + break; + } + + /* this flag is used to make the async state changes return immediately. We + * don't want them to interfere with this state change */ + GST_OBJECT_LOCK (bin); + bin->polling = TRUE; + GST_OBJECT_UNLOCK (bin); + + /* iterate in state change order */ + it = gst_bin_iterate_sorted (bin); + + /* mark if we've seen an ASYNC element in the bin when we did a state change. + * Note how we don't reset this value when a resync happens, the reason being + * that the async element posted ASYNC_START and we want to post ASYNC_DONE + * even after a resync when the async element is gone */ + have_async = FALSE; + +restart: + /* take base_time */ + base_time = gst_element_get_base_time (element); + start_time = gst_element_get_start_time (element); + + have_no_preroll = FALSE; + + done = FALSE; + while (!done) { + switch (gst_iterator_next (it, &data)) { + case GST_ITERATOR_OK: + { + GstElement *child; + + child = g_value_get_object (&data); + + /* set state and base_time now */ + ret = gst_bin_element_set_state (bin, child, base_time, start_time, + current, next); + + switch (ret) { + case GST_STATE_CHANGE_SUCCESS: + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "child '%s' changed state to %d(%s) successfully", + GST_ELEMENT_NAME (child), next, + gst_element_state_get_name (next)); + break; + case GST_STATE_CHANGE_ASYNC: + { + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "child '%s' is changing state asynchronously to %s", + GST_ELEMENT_NAME (child), gst_element_state_get_name (next)); + have_async = TRUE; + break; + } + case GST_STATE_CHANGE_FAILURE:{ + GstObject *parent; + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "child '%s' failed to go to state %d(%s)", + GST_ELEMENT_NAME (child), + next, gst_element_state_get_name (next)); + + /* Only fail if the child is still inside + * this bin. It might've been removed already + * because of the error by the bin subclass + * to ignore the error. */ + parent = gst_object_get_parent (GST_OBJECT_CAST (child)); + if (parent == GST_OBJECT_CAST (element)) { + /* element is still in bin, really error now */ + gst_object_unref (parent); + goto done; + } + /* child removed from bin, let the resync code redo the state + * change */ + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "child '%s' was removed from the bin", + GST_ELEMENT_NAME (child)); + + if (parent) + gst_object_unref (parent); + + break; + } + case GST_STATE_CHANGE_NO_PREROLL: + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "child '%s' changed state to %d(%s) successfully without preroll", + GST_ELEMENT_NAME (child), next, + gst_element_state_get_name (next)); + have_no_preroll = TRUE; + break; + default: + g_assert_not_reached (); + break; + } + g_value_reset (&data); + break; + } + case GST_ITERATOR_RESYNC: + GST_CAT_DEBUG (GST_CAT_STATES, "iterator doing resync"); + gst_iterator_resync (it); + goto restart; + default: + case GST_ITERATOR_DONE: + GST_CAT_DEBUG (GST_CAT_STATES, "iterator done"); + done = TRUE; + break; + } + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (G_UNLIKELY (ret == GST_STATE_CHANGE_FAILURE)) + goto done; + + if (have_no_preroll) { + GST_CAT_DEBUG (GST_CAT_STATES, + "we have NO_PREROLL elements %s -> NO_PREROLL", + gst_element_state_change_return_get_name (ret)); + ret = GST_STATE_CHANGE_NO_PREROLL; + } else if (have_async) { + GST_CAT_DEBUG (GST_CAT_STATES, "we have ASYNC elements %s -> ASYNC", + gst_element_state_change_return_get_name (ret)); + ret = GST_STATE_CHANGE_ASYNC; + } + +done: + g_value_unset (&data); + gst_iterator_free (it); + + GST_OBJECT_LOCK (bin); + bin->polling = FALSE; + /* it's possible that we did not get ASYNC from the children while the bin is + * simulating ASYNC behaviour by posting an ASYNC_DONE message on the bus with + * itself as the source. In that case we still want to check if the state + * change completed. */ + if (ret != GST_STATE_CHANGE_ASYNC && !bin->priv->pending_async_done) { + /* no element returned ASYNC and there are no pending async_done messages, + * we can just complete. */ + GST_DEBUG_OBJECT (bin, "no async elements"); + goto state_end; + } + /* when we get here an ASYNC element was found */ + if (GST_STATE_TARGET (bin) <= GST_STATE_READY) { + /* we ignore ASYNC state changes when we go to READY or NULL */ + GST_DEBUG_OBJECT (bin, "target state %s <= READY", + gst_element_state_get_name (GST_STATE_TARGET (bin))); + goto state_end; + } + + GST_DEBUG_OBJECT (bin, "check async elements"); + /* check if all elements managed to commit their state already */ + if (!find_message (bin, NULL, GST_MESSAGE_ASYNC_START)) { + /* nothing found, remove all old ASYNC_DONE messages. This can happen when + * all the elements commited their state while we were doing the state + * change. We will still return ASYNC for consistency but we commit the + * state already so that a _get_state() will return immediately. */ + bin_remove_messages (bin, NULL, GST_MESSAGE_ASYNC_DONE); + + GST_DEBUG_OBJECT (bin, "async elements commited"); + bin_handle_async_done (bin, GST_STATE_CHANGE_SUCCESS, FALSE, FALSE); + } + +state_end: + bin->priv->pending_async_done = FALSE; + GST_OBJECT_UNLOCK (bin); + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "done changing bin's state from %s to %s, now in %s, ret %s", + gst_element_state_get_name (current), + gst_element_state_get_name (next), + gst_element_state_get_name (GST_STATE (element)), + gst_element_state_change_return_get_name (ret)); + + return ret; + + /* ERRORS */ +activate_failure: + { + GST_CAT_WARNING_OBJECT (GST_CAT_STATES, element, + "failure (de)activating src pads"); + return GST_STATE_CHANGE_FAILURE; + } +} + +/* + * This function is a utility event handler for seek events. + * It will send the event to all sinks or sources depending on the + * event-direction. + * + * Applications are free to override this behaviour and + * implement their own seek handler, but this will work for + * pretty much all cases in practice. + */ +static gboolean +gst_bin_send_event (GstElement * element, GstEvent * event) +{ + GstBin *bin = GST_BIN_CAST (element); + GstIterator *iter; + gboolean res = TRUE; + gboolean done = FALSE; + GValue data = { 0, }; + + if (GST_EVENT_IS_DOWNSTREAM (event)) { + iter = gst_bin_iterate_sources (bin); + GST_DEBUG_OBJECT (bin, "Sending %s event to src children", + GST_EVENT_TYPE_NAME (event)); + } else { + iter = gst_bin_iterate_sinks (bin); + GST_DEBUG_OBJECT (bin, "Sending %s event to sink children", + GST_EVENT_TYPE_NAME (event)); + } + + while (!done) { + switch (gst_iterator_next (iter, &data)) { + case GST_ITERATOR_OK: + { + GstElement *child = g_value_get_object (&data);; + + gst_event_ref (event); + res &= gst_element_send_event (child, event); + g_value_reset (&data); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + res = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + case GST_ITERATOR_ERROR: + g_assert_not_reached (); + break; + } + } + g_value_unset (&data); + gst_iterator_free (iter); + gst_event_unref (event); + + return res; +} + +/* this is the function called by the threadpool. When async elements commit + * their state, this function will attempt to bring the bin to the next state. + */ +static void +gst_bin_continue_func (BinContinueData * data) +{ + GstBin *bin; + GstState current, next, pending; + GstStateChange transition; + + bin = data->bin; + pending = data->pending; + + GST_DEBUG_OBJECT (bin, "waiting for state lock"); + GST_STATE_LOCK (bin); + + GST_DEBUG_OBJECT (bin, "doing state continue"); + GST_OBJECT_LOCK (bin); + + /* if a new state change happened after this thread was scheduled, we return + * immediately. */ + if (data->cookie != GST_ELEMENT_CAST (bin)->state_cookie) + goto interrupted; + + current = GST_STATE (bin); + next = GST_STATE_GET_NEXT (current, pending); + transition = (GstStateChange) GST_STATE_TRANSITION (current, next); + + GST_STATE_NEXT (bin) = next; + GST_STATE_PENDING (bin) = pending; + /* mark busy */ + GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC; + GST_OBJECT_UNLOCK (bin); + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, + "continue state change %s to %s, final %s", + gst_element_state_get_name (current), + gst_element_state_get_name (next), gst_element_state_get_name (pending)); + + gst_element_change_state (GST_ELEMENT_CAST (bin), transition); + + GST_STATE_UNLOCK (bin); + GST_DEBUG_OBJECT (bin, "state continue done"); + + gst_object_unref (bin); + g_slice_free (BinContinueData, data); + return; + +interrupted: + { + GST_OBJECT_UNLOCK (bin); + GST_STATE_UNLOCK (bin); + GST_DEBUG_OBJECT (bin, "state continue aborted due to intervening change"); + gst_object_unref (bin); + g_slice_free (BinContinueData, data); + return; + } +} + +static GstBusSyncReply +bin_bus_handler (GstBus * bus, GstMessage * message, GstBin * bin) +{ + GstBinClass *bclass; + + bclass = GST_BIN_GET_CLASS (bin); + if (bclass->handle_message) + bclass->handle_message (bin, message); + else + gst_message_unref (message); + + return GST_BUS_DROP; +} + +static void +bin_push_state_continue (BinContinueData * data) +{ + GstBinClass *klass; + GstBin *bin; + + /* ref was taken */ + bin = data->bin; + klass = GST_BIN_GET_CLASS (bin); + + GST_DEBUG_OBJECT (bin, "pushing continue on thread pool"); + g_thread_pool_push (klass->pool, data, NULL); +} + +/* an element started an async state change, if we were not busy with a state + * change, we perform a lost state. + * This function is called with the OBJECT lock. + */ +static void +bin_handle_async_start (GstBin * bin) +{ + GstState old_state, new_state; + gboolean toplevel; + GstMessage *amessage = NULL; + + if (GST_STATE_RETURN (bin) == GST_STATE_CHANGE_FAILURE) + goto had_error; + + /* get our toplevel state */ + toplevel = BIN_IS_TOPLEVEL (bin); + + /* prepare an ASYNC_START message, we always post the start message even if we + * are busy with a state change or when we are NO_PREROLL. */ + if (!toplevel) + /* non toplevel bin, prepare async-start for the parent */ + amessage = gst_message_new_async_start (GST_OBJECT_CAST (bin)); + + if (bin->polling || GST_STATE_PENDING (bin) != GST_STATE_VOID_PENDING) + goto was_busy; + + /* async starts are ignored when we are NO_PREROLL */ + if (GST_STATE_RETURN (bin) == GST_STATE_CHANGE_NO_PREROLL) + goto was_no_preroll; + + old_state = GST_STATE (bin); + + /* when we PLAYING we go back to PAUSED, when preroll happens, we go back to + * PLAYING after optionally redistributing the base_time. */ + if (old_state > GST_STATE_PAUSED) + new_state = GST_STATE_PAUSED; + else + new_state = old_state; + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, + "lost state of %s, new %s", gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state)); + + GST_STATE (bin) = new_state; + GST_STATE_NEXT (bin) = new_state; + GST_STATE_PENDING (bin) = new_state; + GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC; + GST_OBJECT_UNLOCK (bin); + + /* post message */ + _priv_gst_element_state_changed (GST_ELEMENT_CAST (bin), new_state, new_state, + new_state); + +post_start: + if (amessage) { + /* post our ASYNC_START. */ + GST_DEBUG_OBJECT (bin, "posting ASYNC_START to parent"); + gst_element_post_message (GST_ELEMENT_CAST (bin), amessage); + } + GST_OBJECT_LOCK (bin); + + return; + +had_error: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "we had an error"); + return; + } +was_busy: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "state change busy"); + GST_OBJECT_UNLOCK (bin); + goto post_start; + } +was_no_preroll: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "ignoring, we are NO_PREROLL"); + GST_OBJECT_UNLOCK (bin); + goto post_start; + } +} + +/* this function is called when there are no more async elements in the bin. We + * post a state changed message and an ASYNC_DONE message. + * This function is called with the OBJECT lock. + */ +static void +bin_handle_async_done (GstBin * bin, GstStateChangeReturn ret, + gboolean flag_pending, gboolean reset_time) +{ + GstState current, pending, target; + GstStateChangeReturn old_ret; + GstState old_state, old_next; + gboolean toplevel, state_changed = FALSE; + GstMessage *amessage = NULL; + BinContinueData *cont = NULL; + + if (GST_STATE_RETURN (bin) == GST_STATE_CHANGE_FAILURE) + goto had_error; + + pending = GST_STATE_PENDING (bin); + + if (bin->polling) + goto was_busy; + + /* check if there is something to commit */ + if (pending == GST_STATE_VOID_PENDING) + goto nothing_pending; + + old_ret = GST_STATE_RETURN (bin); + GST_STATE_RETURN (bin) = ret; + + /* move to the next target state */ + target = GST_STATE_TARGET (bin); + pending = GST_STATE_PENDING (bin) = target; + + amessage = gst_message_new_async_done (GST_OBJECT_CAST (bin), reset_time); + + old_state = GST_STATE (bin); + /* this is the state we should go to next */ + old_next = GST_STATE_NEXT (bin); + + if (old_next != GST_STATE_PLAYING) { + GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, + "committing state from %s to %s, old pending %s", + gst_element_state_get_name (old_state), + gst_element_state_get_name (old_next), + gst_element_state_get_name (pending)); + + /* update current state */ + current = GST_STATE (bin) = old_next; + } else { + GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, + "setting state from %s to %s, pending %s", + gst_element_state_get_name (old_state), + gst_element_state_get_name (old_state), + gst_element_state_get_name (pending)); + current = old_state; + } + + /* get our toplevel state */ + toplevel = BIN_IS_TOPLEVEL (bin); + + /* see if we reached the final state. If we are not toplevel, we also have to + * stop here, the parent will continue our state. */ + if ((pending == current) || !toplevel) { + GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, + "completed state change, pending VOID"); + + /* mark VOID pending */ + pending = GST_STATE_VOID_PENDING; + GST_STATE_PENDING (bin) = pending; + GST_STATE_NEXT (bin) = GST_STATE_VOID_PENDING; + } else { + GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, + "continue state change, pending %s", + gst_element_state_get_name (pending)); + + cont = g_slice_new (BinContinueData); + + /* ref to the bin */ + cont->bin = gst_object_ref (bin); + /* cookie to detect concurrent state change */ + cont->cookie = GST_ELEMENT_CAST (bin)->state_cookie; + /* pending target state */ + cont->pending = pending; + /* mark busy */ + GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC; + GST_STATE_NEXT (bin) = GST_STATE_GET_NEXT (old_state, pending); + } + + if (old_next != GST_STATE_PLAYING) { + if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) { + state_changed = TRUE; + } + } + GST_OBJECT_UNLOCK (bin); + + if (state_changed) { + _priv_gst_element_state_changed (GST_ELEMENT_CAST (bin), old_state, + old_next, pending); + } + if (amessage) { + /* post our combined ASYNC_DONE when all is ASYNC_DONE. */ + GST_DEBUG_OBJECT (bin, "posting ASYNC_DONE to parent"); + gst_element_post_message (GST_ELEMENT_CAST (bin), amessage); + } + + GST_OBJECT_LOCK (bin); + if (cont) { + /* toplevel, start continue state */ + GST_DEBUG_OBJECT (bin, "all async-done, starting state continue"); + bin_push_state_continue (cont); + } else { + GST_DEBUG_OBJECT (bin, "state change complete"); + GST_STATE_BROADCAST (bin); + } + return; + +had_error: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "we had an error"); + return; + } +was_busy: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "state change busy"); + /* if we were busy with a state change and we are requested to flag a + * pending async done, we do so here */ + if (flag_pending) + bin->priv->pending_async_done = TRUE; + return; + } +nothing_pending: + { + GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "nothing pending"); + return; + } +} + +static void +bin_do_eos (GstBin * bin) +{ + guint32 seqnum = 0; + gboolean eos; + + GST_OBJECT_LOCK (bin); + /* If all sinks are EOS, we're in PLAYING and no state change is pending + * we forward the EOS message to the parent bin or application + */ + eos = GST_STATE (bin) == GST_STATE_PLAYING + && GST_STATE_PENDING (bin) == GST_STATE_VOID_PENDING + && is_eos (bin, &seqnum); + GST_OBJECT_UNLOCK (bin); + + if (eos + && g_atomic_int_compare_and_exchange (&bin->priv->posted_eos, FALSE, + TRUE)) { + GstMessage *tmessage; + tmessage = gst_message_new_eos (GST_OBJECT_CAST (bin)); + gst_message_set_seqnum (tmessage, seqnum); + GST_DEBUG_OBJECT (bin, + "all sinks posted EOS, posting seqnum #%" G_GUINT32_FORMAT, seqnum); + gst_element_post_message (GST_ELEMENT_CAST (bin), tmessage); + } +} + +/* must be called with the object lock. This function releases the lock to post + * the message. */ +static void +bin_do_message_forward (GstBin * bin, GstMessage * message) +{ + if (bin->priv->message_forward) { + GstMessage *forwarded; + + GST_DEBUG_OBJECT (bin, "pass %s message upward", + GST_MESSAGE_TYPE_NAME (message)); + GST_OBJECT_UNLOCK (bin); + + /* we need to convert these messages to element messages so that our parent + * bin can easily ignore them and so that the application can easily + * distinguish between the internally forwarded and the real messages. */ + forwarded = gst_message_new_element (GST_OBJECT_CAST (bin), + gst_structure_new ("GstBinForwarded", + "message", GST_TYPE_MESSAGE, message, NULL)); + + gst_element_post_message (GST_ELEMENT_CAST (bin), forwarded); + + GST_OBJECT_LOCK (bin); + } +} + +/* handle child messages: + * + * This method is called synchronously when a child posts a message on + * the internal bus. + * + * GST_MESSAGE_EOS: This message is only posted by sinks + * in the PLAYING state. If all sinks posted the EOS message, post + * one upwards. + * + * GST_MESSAGE_STATE_DIRTY: Deprecated + * + * GST_MESSAGE_SEGMENT_START: just collect, never forward upwards. If an + * element posts segment_start twice, only the last message is kept. + * + * GST_MESSAGE_SEGMENT_DONE: replace SEGMENT_START message from same poster + * with the segment_done message. If there are no more segment_start + * messages, post segment_done message upwards. + * + * GST_MESSAGE_DURATION: remove all previously cached duration messages. + * Whenever someone performs a duration query on the bin, we store the + * result so we can answer it quicker the next time. Any element that + * changes its duration marks our cached values invalid. + * This message is also posted upwards. This is currently disabled + * because too many elements don't post DURATION messages when the + * duration changes. + * + * GST_MESSAGE_CLOCK_LOST: This message is posted by an element when it + * can no longer provide a clock. The default bin behaviour is to + * check if the lost clock was the one provided by the bin. If so and + * we are currently in the PLAYING state, we forward the message to + * our parent. + * This message is also generated when we remove a clock provider from + * a bin. If this message is received by the application, it should + * PAUSE the pipeline and set it back to PLAYING to force a new clock + * and a new base_time distribution. + * + * GST_MESSAGE_CLOCK_PROVIDE: This message is generated when an element + * can provide a clock. This mostly happens when we add a new clock + * provider to the bin. The default behaviour of the bin is to mark the + * currently selected clock as dirty, which will perform a clock + * recalculation the next time we are asked to provide a clock. + * This message is never sent to the application but is forwarded to + * the parent. + * + * GST_MESSAGE_ASYNC_START: Create an internal ELEMENT message that stores + * the state of the element and the fact that the element will need a + * new base_time. This message is not forwarded to the application. + * + * GST_MESSAGE_ASYNC_DONE: Find the internal ELEMENT message we kept for the + * element when it posted ASYNC_START. If all elements are done, post a + * ASYNC_DONE message to the parent. + * + * OTHER: post upwards. + */ +static void +gst_bin_handle_message_func (GstBin * bin, GstMessage * message) +{ + GstObject *src; + GstMessageType type; + GstMessage *tmessage; + guint32 seqnum; + + src = GST_MESSAGE_SRC (message); + type = GST_MESSAGE_TYPE (message); + + GST_DEBUG_OBJECT (bin, "[msg %p] handling child %s message of type %s", + message, src ? GST_ELEMENT_NAME (src) : "(NULL)", + GST_MESSAGE_TYPE_NAME (message)); + + switch (type) { + case GST_MESSAGE_ERROR: + { + GST_OBJECT_LOCK (bin); + /* flag error */ + GST_DEBUG_OBJECT (bin, "got ERROR message, unlocking state change"); + GST_STATE_RETURN (bin) = GST_STATE_CHANGE_FAILURE; + GST_STATE_BROADCAST (bin); + GST_OBJECT_UNLOCK (bin); + + goto forward; + } + case GST_MESSAGE_EOS: + { + + /* collect all eos messages from the children */ + GST_OBJECT_LOCK (bin); + bin_do_message_forward (bin, message); + /* ref message for future use */ + bin_replace_message (bin, message, GST_MESSAGE_EOS); + GST_OBJECT_UNLOCK (bin); + + bin_do_eos (bin); + break; + } + case GST_MESSAGE_STATE_DIRTY: + { + GST_WARNING_OBJECT (bin, "received deprecated STATE_DIRTY message"); + + /* free message */ + gst_message_unref (message); + break; + } + case GST_MESSAGE_SEGMENT_START:{ + gboolean post = FALSE; + GstFormat format; + gint64 position; + + gst_message_parse_segment_start (message, &format, &position); + seqnum = gst_message_get_seqnum (message); + + GST_OBJECT_LOCK (bin); + bin_do_message_forward (bin, message); + /* if this is the first segment-start, post to parent but not to the + * application */ + if (!find_message (bin, NULL, GST_MESSAGE_SEGMENT_START) && + (GST_OBJECT_PARENT (bin) != NULL)) { + post = TRUE; + } + /* replace any previous segment_start message from this source + * with the new segment start message */ + bin_replace_message (bin, message, GST_MESSAGE_SEGMENT_START); + GST_OBJECT_UNLOCK (bin); + if (post) { + tmessage = gst_message_new_segment_start (GST_OBJECT_CAST (bin), + format, position); + gst_message_set_seqnum (tmessage, seqnum); + + /* post segment start with initial format and position. */ + GST_DEBUG_OBJECT (bin, "posting SEGMENT_START (%u) bus message: %p", + seqnum, message); + gst_element_post_message (GST_ELEMENT_CAST (bin), tmessage); + } + break; + } + case GST_MESSAGE_SEGMENT_DONE: + { + gboolean post = FALSE; + GstFormat format; + gint64 position; + + gst_message_parse_segment_done (message, &format, &position); + seqnum = gst_message_get_seqnum (message); + + GST_OBJECT_LOCK (bin); + bin_do_message_forward (bin, message); + bin_replace_message (bin, message, GST_MESSAGE_SEGMENT_START); + /* if there are no more segment_start messages, everybody posted + * a segment_done and we can post one on the bus. */ + + /* we don't care who still has a pending segment start */ + if (!find_message (bin, NULL, GST_MESSAGE_SEGMENT_START)) { + /* nothing found */ + post = TRUE; + /* remove all old segment_done messages */ + bin_remove_messages (bin, NULL, GST_MESSAGE_SEGMENT_DONE); + } + GST_OBJECT_UNLOCK (bin); + if (post) { + tmessage = gst_message_new_segment_done (GST_OBJECT_CAST (bin), + format, position); + gst_message_set_seqnum (tmessage, seqnum); + + /* post segment done with latest format and position. */ + GST_DEBUG_OBJECT (bin, "posting SEGMENT_DONE (%u) bus message: %p", + seqnum, message); + gst_element_post_message (GST_ELEMENT_CAST (bin), tmessage); + } + break; + } + case GST_MESSAGE_DURATION: + { + /* remove all cached duration messages, next time somebody asks + * for duration, we will recalculate. */ + GST_OBJECT_LOCK (bin); + bin_remove_messages (bin, NULL, GST_MESSAGE_DURATION); + GST_OBJECT_UNLOCK (bin); + goto forward; + } + case GST_MESSAGE_CLOCK_LOST: + { + GstClock **provided_clock_p; + GstElement **clock_provider_p; + gboolean playing, provided, forward; + GstClock *clock; + + gst_message_parse_clock_lost (message, &clock); + + GST_OBJECT_LOCK (bin); + bin->clock_dirty = TRUE; + /* if we lost the clock that we provided, post to parent but + * only if we are PLAYING. */ + provided = (clock == bin->provided_clock); + playing = (GST_STATE (bin) == GST_STATE_PLAYING); + forward = playing & provided; + if (provided) { + GST_DEBUG_OBJECT (bin, + "Lost clock %" GST_PTR_FORMAT " provided by %" GST_PTR_FORMAT, + bin->provided_clock, bin->clock_provider); + provided_clock_p = &bin->provided_clock; + clock_provider_p = &bin->clock_provider; + gst_object_replace ((GstObject **) provided_clock_p, NULL); + gst_object_replace ((GstObject **) clock_provider_p, NULL); + } + GST_DEBUG_OBJECT (bin, "provided %d, playing %d, forward %d", + provided, playing, forward); + GST_OBJECT_UNLOCK (bin); + + if (forward) + goto forward; + + /* free message */ + gst_message_unref (message); + break; + } + case GST_MESSAGE_CLOCK_PROVIDE: + { + gboolean forward; + + GST_OBJECT_LOCK (bin); + bin->clock_dirty = TRUE; + /* a new clock is available, post to parent but not + * to the application */ + forward = GST_OBJECT_PARENT (bin) != NULL; + GST_OBJECT_UNLOCK (bin); + + if (forward) + goto forward; + + /* free message */ + gst_message_unref (message); + break; + } + case GST_MESSAGE_ASYNC_START: + { + GstState target; + + GST_DEBUG_OBJECT (bin, "ASYNC_START message %p, %s", message, + src ? GST_OBJECT_NAME (src) : "(NULL)"); + + GST_OBJECT_LOCK (bin); + bin_do_message_forward (bin, message); + + /* we ignore the message if we are going to <= READY */ + if ((target = GST_STATE_TARGET (bin)) <= GST_STATE_READY) + goto ignore_start_message; + + /* takes ownership of the message */ + bin_replace_message (bin, message, GST_MESSAGE_ASYNC_START); + + bin_handle_async_start (bin); + GST_OBJECT_UNLOCK (bin); + break; + + ignore_start_message: + { + GST_DEBUG_OBJECT (bin, "ignoring message, target %s", + gst_element_state_get_name (target)); + GST_OBJECT_UNLOCK (bin); + gst_message_unref (message); + break; + } + } + case GST_MESSAGE_ASYNC_DONE: + { + gboolean reset_time; + GstState target; + + GST_DEBUG_OBJECT (bin, "ASYNC_DONE message %p, %s", message, + src ? GST_OBJECT_NAME (src) : "(NULL)"); + + gst_message_parse_async_done (message, &reset_time); + + GST_OBJECT_LOCK (bin); + bin_do_message_forward (bin, message); + + /* ignore messages if we are shutting down */ + if ((target = GST_STATE_TARGET (bin)) <= GST_STATE_READY) + goto ignore_done_message; + + bin_replace_message (bin, message, GST_MESSAGE_ASYNC_START); + /* if there are no more ASYNC_START messages, everybody posted + * a ASYNC_DONE and we can post one on the bus. When checking, we + * don't care who still has a pending ASYNC_START */ + if (!find_message (bin, NULL, GST_MESSAGE_ASYNC_START)) { + /* nothing found, remove all old ASYNC_DONE messages */ + bin_remove_messages (bin, NULL, GST_MESSAGE_ASYNC_DONE); + + GST_DEBUG_OBJECT (bin, "async elements commited"); + /* when we get an async done message when a state change was busy, we + * need to set the pending_done flag so that at the end of the state + * change we can see if we need to verify pending async elements, hence + * the TRUE argument here. */ + bin_handle_async_done (bin, GST_STATE_CHANGE_SUCCESS, TRUE, reset_time); + } else { + GST_DEBUG_OBJECT (bin, "there are more async elements pending"); + } + GST_OBJECT_UNLOCK (bin); + break; + + ignore_done_message: + { + GST_DEBUG_OBJECT (bin, "ignoring message, target %s", + gst_element_state_get_name (target)); + GST_OBJECT_UNLOCK (bin); + gst_message_unref (message); + break; + } + } + case GST_MESSAGE_STRUCTURE_CHANGE: + { + gboolean busy; + + gst_message_parse_structure_change (message, NULL, NULL, &busy); + + GST_OBJECT_LOCK (bin); + if (busy) { + /* while the pad is busy, avoid following it when doing state changes. + * Don't update the cookie yet, we will do that after the structure + * change finished and we are ready to inspect the new updated + * structure. */ + bin_replace_message (bin, message, GST_MESSAGE_STRUCTURE_CHANGE); + message = NULL; + } else { + /* a pad link/unlink ended, signal the state change iterator that we + * need to resync by updating the structure_cookie. */ + bin_remove_messages (bin, GST_MESSAGE_SRC (message), + GST_MESSAGE_STRUCTURE_CHANGE); + bin->priv->structure_cookie++; + } + GST_OBJECT_UNLOCK (bin); + + if (message) + gst_message_unref (message); + + break; + } + default: + goto forward; + } + return; + +forward: + { + /* Send all other messages upward */ + GST_DEBUG_OBJECT (bin, "posting message upward"); + gst_element_post_message (GST_ELEMENT_CAST (bin), message); + return; + } +} + +/* generic struct passed to all query fold methods */ +typedef struct +{ + GstQuery *query; + gint64 min; + gint64 max; + gboolean live; +} QueryFold; + +typedef void (*QueryInitFunction) (GstBin * bin, QueryFold * fold); +typedef void (*QueryDoneFunction) (GstBin * bin, QueryFold * fold); + +/* for duration/position we collect all durations/positions and take + * the MAX of all valid results */ +static void +bin_query_min_max_init (GstBin * bin, QueryFold * fold) +{ + fold->min = 0; + fold->max = -1; + fold->live = FALSE; +} + +static gboolean +bin_query_duration_fold (const GValue * vitem, GValue * ret, QueryFold * fold) +{ + GstElement *item = g_value_get_object (vitem); + + if (gst_element_query (item, fold->query)) { + gint64 duration; + + g_value_set_boolean (ret, TRUE); + + gst_query_parse_duration (fold->query, NULL, &duration); + + GST_DEBUG_OBJECT (item, "got duration %" G_GINT64_FORMAT, duration); + + if (duration > fold->max) + fold->max = duration; + } + + return TRUE; +} + +static void +bin_query_duration_done (GstBin * bin, QueryFold * fold) +{ + GstFormat format; + + gst_query_parse_duration (fold->query, &format, NULL); + /* store max in query result */ + gst_query_set_duration (fold->query, format, fold->max); + + GST_DEBUG_OBJECT (bin, "max duration %" G_GINT64_FORMAT, fold->max); + + /* and cache now */ + GST_OBJECT_LOCK (bin); + bin->messages = g_list_prepend (bin->messages, + gst_message_new_duration (GST_OBJECT_CAST (bin), format, fold->max)); + GST_OBJECT_UNLOCK (bin); +} + +static gboolean +bin_query_position_fold (const GValue * vitem, GValue * ret, QueryFold * fold) +{ + GstElement *item = g_value_get_object (vitem); + + if (gst_element_query (item, fold->query)) { + gint64 position; + + g_value_set_boolean (ret, TRUE); + + gst_query_parse_position (fold->query, NULL, &position); + + GST_DEBUG_OBJECT (item, "got position %" G_GINT64_FORMAT, position); + + if (position > fold->max) + fold->max = position; + } + + return TRUE; +} + +static void +bin_query_position_done (GstBin * bin, QueryFold * fold) +{ + GstFormat format; + + gst_query_parse_position (fold->query, &format, NULL); + /* store max in query result */ + gst_query_set_position (fold->query, format, fold->max); + + GST_DEBUG_OBJECT (bin, "max position %" G_GINT64_FORMAT, fold->max); +} + +static gboolean +bin_query_latency_fold (const GValue * vitem, GValue * ret, QueryFold * fold) +{ + GstElement *item = g_value_get_object (vitem); + + if (gst_element_query (item, fold->query)) { + GstClockTime min, max; + gboolean live; + + gst_query_parse_latency (fold->query, &live, &min, &max); + + GST_DEBUG_OBJECT (item, + "got latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT + ", live %d", GST_TIME_ARGS (min), GST_TIME_ARGS (max), live); + + /* for the combined latency we collect the MAX of all min latencies and + * the MIN of all max latencies */ + if (live) { + if (min > fold->min) + fold->min = min; + if (fold->max == -1) + fold->max = max; + else if (max < fold->max) + fold->max = max; + if (fold->live == FALSE) + fold->live = live; + } + } else { + g_value_set_boolean (ret, FALSE); + GST_DEBUG_OBJECT (item, "failed query"); + } + + return TRUE; +} + +static void +bin_query_latency_done (GstBin * bin, QueryFold * fold) +{ + /* store max in query result */ + gst_query_set_latency (fold->query, fold->live, fold->min, fold->max); + + GST_DEBUG_OBJECT (bin, + "latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT + ", live %d", GST_TIME_ARGS (fold->min), GST_TIME_ARGS (fold->max), + fold->live); +} + +/* generic fold, return first valid result */ +static gboolean +bin_query_generic_fold (const GValue * vitem, GValue * ret, QueryFold * fold) +{ + GstElement *item = g_value_get_object (vitem); + gboolean res; + + if ((res = gst_element_query (item, fold->query))) { + g_value_set_boolean (ret, TRUE); + GST_DEBUG_OBJECT (item, "answered query %p", fold->query); + } + + /* and stop as soon as we have a valid result */ + return !res; +} + +static gboolean +gst_bin_query (GstElement * element, GstQuery * query) +{ + GstBin *bin = GST_BIN_CAST (element); + GstIterator *iter; + gboolean res = FALSE; + GstIteratorFoldFunction fold_func; + QueryInitFunction fold_init = NULL; + QueryDoneFunction fold_done = NULL; + QueryFold fold_data; + GValue ret = { 0 }; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_DURATION: + { + GList *cached; + GstFormat qformat; + + gst_query_parse_duration (query, &qformat, NULL); + + /* find cached duration query */ + GST_OBJECT_LOCK (bin); + for (cached = bin->messages; cached; cached = g_list_next (cached)) { + GstMessage *message = (GstMessage *) cached->data; + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_DURATION && + GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (bin)) { + GstFormat format; + gint64 duration; + + gst_message_parse_duration (message, &format, &duration); + + /* if cached same format, copy duration in query result */ + if (format == qformat) { + GST_DEBUG_OBJECT (bin, "return cached duration %" G_GINT64_FORMAT, + duration); + GST_OBJECT_UNLOCK (bin); + + gst_query_set_duration (query, qformat, duration); + res = TRUE; + goto exit; + } + } + } + GST_OBJECT_UNLOCK (bin); + /* no cached value found, iterate and collect durations */ + fold_func = (GstIteratorFoldFunction) bin_query_duration_fold; + fold_init = bin_query_min_max_init; + fold_done = bin_query_duration_done; + break; + } + case GST_QUERY_POSITION: + { + fold_func = (GstIteratorFoldFunction) bin_query_position_fold; + fold_init = bin_query_min_max_init; + fold_done = bin_query_position_done; + break; + } + case GST_QUERY_LATENCY: + { + fold_func = (GstIteratorFoldFunction) bin_query_latency_fold; + fold_init = bin_query_min_max_init; + fold_done = bin_query_latency_done; + res = TRUE; + break; + } + default: + fold_func = (GstIteratorFoldFunction) bin_query_generic_fold; + break; + } + + fold_data.query = query; + + /* set the result of the query to FALSE initially */ + g_value_init (&ret, G_TYPE_BOOLEAN); + g_value_set_boolean (&ret, res); + + iter = gst_bin_iterate_sinks (bin); + GST_DEBUG_OBJECT (bin, "Sending query %p (type %s) to sink children", + query, GST_QUERY_TYPE_NAME (query)); + + if (fold_init) + fold_init (bin, &fold_data); + + while (TRUE) { + GstIteratorResult ires; + + ires = gst_iterator_fold (iter, fold_func, &ret, &fold_data); + + switch (ires) { + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + if (fold_init) + fold_init (bin, &fold_data); + g_value_set_boolean (&ret, res); + break; + case GST_ITERATOR_OK: + case GST_ITERATOR_DONE: + res = g_value_get_boolean (&ret); + if (fold_done != NULL && res) + fold_done (bin, &fold_data); + goto done; + default: + res = FALSE; + goto done; + } + } +done: + gst_iterator_free (iter); + +exit: + GST_DEBUG_OBJECT (bin, "query %p result %d", query, res); + + return res; +} + +static gint +compare_name (const GValue * velement, const gchar * name) +{ + gint eq; + GstElement *element = g_value_get_object (velement); + + GST_OBJECT_LOCK (element); + eq = strcmp (GST_ELEMENT_NAME (element), name); + GST_OBJECT_UNLOCK (element); + + return eq; +} + +/** + * gst_bin_get_by_name: + * @bin: a #GstBin + * @name: the element name to search for + * + * Gets the element with the given name from a bin. This + * function recurses into child bins. + * + * Returns NULL if no element with the given name is found in the bin. + * + * MT safe. Caller owns returned reference. + * + * Returns: (transfer full): the #GstElement with the given name, or NULL + */ +GstElement * +gst_bin_get_by_name (GstBin * bin, const gchar * name) +{ + GstIterator *children; + GValue result = { 0, }; + GstElement *element; + gboolean found; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + + GST_CAT_INFO (GST_CAT_PARENTAGE, "[%s]: looking up child element %s", + GST_ELEMENT_NAME (bin), name); + + children = gst_bin_iterate_recurse (bin); + found = gst_iterator_find_custom (children, + (GCompareFunc) compare_name, &result, (gpointer) name); + gst_iterator_free (children); + + if (found) { + element = g_value_dup_object (&result); + g_value_unset (&result); + } else { + element = NULL; + } + + return element; +} + +/** + * gst_bin_get_by_name_recurse_up: + * @bin: a #GstBin + * @name: the element name to search for + * + * Gets the element with the given name from this bin. If the + * element is not found, a recursion is performed on the parent bin. + * + * Returns NULL if: + * - no element with the given name is found in the bin + * + * MT safe. Caller owns returned reference. + * + * Returns: (transfer full): the #GstElement with the given name, or NULL + */ +GstElement * +gst_bin_get_by_name_recurse_up (GstBin * bin, const gchar * name) +{ + GstElement *result; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + g_return_val_if_fail (name != NULL, NULL); + + result = gst_bin_get_by_name (bin, name); + + if (!result) { + GstObject *parent; + + parent = gst_object_get_parent (GST_OBJECT_CAST (bin)); + if (parent) { + if (GST_IS_BIN (parent)) { + result = gst_bin_get_by_name_recurse_up (GST_BIN_CAST (parent), name); + } + gst_object_unref (parent); + } + } + + return result; +} + +static gint +compare_interface (const GValue * velement, GValue * interface) +{ + GstElement *element = g_value_get_object (velement); + GType interface_type = (GType) g_value_get_pointer (interface); + gint ret; + + if (G_TYPE_CHECK_INSTANCE_TYPE (element, interface_type)) { + ret = 0; + } else { + ret = 1; + } + return ret; +} + +/** + * gst_bin_get_by_interface: + * @bin: a #GstBin + * @iface: the #GType of an interface + * + * Looks for an element inside the bin that implements the given + * interface. If such an element is found, it returns the element. + * You can cast this element to the given interface afterwards. If you want + * all elements that implement the interface, use + * gst_bin_iterate_all_by_interface(). This function recurses into child bins. + * + * MT safe. Caller owns returned reference. + * + * Returns: (transfer full): A #GstElement inside the bin implementing the interface + */ +GstElement * +gst_bin_get_by_interface (GstBin * bin, GType iface) +{ + GstIterator *children; + GValue result = { 0, }; + GstElement *element; + gboolean found; + GValue viface = { 0, }; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface), NULL); + + g_value_init (&viface, G_TYPE_POINTER); + g_value_set_pointer (&viface, (gpointer) iface); + + children = gst_bin_iterate_recurse (bin); + found = gst_iterator_find_custom (children, (GCompareFunc) compare_interface, + &result, &viface); + gst_iterator_free (children); + + if (found) { + element = g_value_dup_object (&result); + g_value_unset (&result); + } else { + element = NULL; + } + g_value_unset (&viface); + + return element; +} + +/** + * gst_bin_iterate_all_by_interface: + * @bin: a #GstBin + * @iface: the #GType of an interface + * + * Looks for all elements inside the bin that implements the given + * interface. You can safely cast all returned elements to the given interface. + * The function recurses inside child bins. The iterator will yield a series + * of #GstElement that should be unreffed after use. + * + * Each element yielded by the iterator will have its refcount increased, so + * unref after use. + * + * MT safe. Caller owns returned value. + * + * Returns: (transfer full): a #GstIterator of #GstElement for all elements + * in the bin implementing the given interface, or NULL + */ +GstIterator * +gst_bin_iterate_all_by_interface (GstBin * bin, GType iface) +{ + GstIterator *children; + GstIterator *result; + GValue viface = { 0, }; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface), NULL); + + g_value_init (&viface, G_TYPE_POINTER); + g_value_set_pointer (&viface, (gpointer) iface); + + children = gst_bin_iterate_recurse (bin); + result = gst_iterator_filter (children, (GCompareFunc) compare_interface, + &viface); + + g_value_unset (&viface); + + return result; +} diff --git a/gst/gstbin.h b/gst/gstbin.h new file mode 100644 index 0000000..19c7634 --- /dev/null +++ b/gst/gstbin.h @@ -0,0 +1,190 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstbin.h: Header for GstBin container object + * + * 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_BIN_H__ +#define __GST_BIN_H__ + +#include <gst/gstelement.h> +#include <gst/gstiterator.h> +#include <gst/gstbus.h> + +G_BEGIN_DECLS + +#define GST_TYPE_BIN (gst_bin_get_type ()) +#define GST_IS_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_BIN)) +#define GST_IS_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_BIN)) +#define GST_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_BIN, GstBinClass)) +#define GST_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_BIN, GstBin)) +#define GST_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_BIN, GstBinClass)) +#define GST_BIN_CAST(obj) ((GstBin*)(obj)) + +/** + * GstBinFlags: + * @GST_BIN_FLAG_LAST: the last enum in the series of flags for bins. + * Derived classes can use this as first value in a list of flags. + * + * GstBinFlags are a set of flags specific to bins. Most are set/used + * internally. They can be checked using the GST_OBJECT_FLAG_IS_SET () macro, + * and (un)set using GST_OBJECT_FLAG_SET () and GST_OBJECT_FLAG_UNSET (). + */ +typedef enum { + /* padding */ + GST_BIN_FLAG_LAST = (GST_ELEMENT_FLAG_LAST << 5) +} GstBinFlags; + +typedef struct _GstBin GstBin; +typedef struct _GstBinClass GstBinClass; +typedef struct _GstBinPrivate GstBinPrivate; + +/** + * GST_BIN_NUMCHILDREN: + * @bin: a #GstBin + * + * Gets the number of children in a bin. + */ +#define GST_BIN_NUMCHILDREN(bin) (GST_BIN_CAST(bin)->numchildren) +/** + * GST_BIN_CHILDREN: + * @bin: a #GstBin + * + * Gets the list with children in a bin. + */ +#define GST_BIN_CHILDREN(bin) (GST_BIN_CAST(bin)->children) +/** + * GST_BIN_CHILDREN_COOKIE: + * @bin: a #GstBin + * + * Gets the children cookie that watches the children list. + */ +#define GST_BIN_CHILDREN_COOKIE(bin) (GST_BIN_CAST(bin)->children_cookie) + +/** + * GstBin: + * @numchildren: the number of children in this bin + * @children: the list of children in this bin + * @children_cookie: updated whenever @children changes + * @child_bus: internal bus for handling child messages + * @messages: queued and cached messages + * @polling: the bin is currently calculating its state + * @state_dirty: the bin needs to recalculate its state (deprecated) + * @clock_dirty: the bin needs to select a new clock + * @provided_clock: the last clock selected + * @clock_provider: the element that provided @provided_clock + * + * The GstBin base class. Subclasses can access these fields provided + * the LOCK is taken. + */ +struct _GstBin { + GstElement element; + + /*< public >*/ /* with LOCK */ + /* our children, subclass are supposed to update these + * fields to reflect their state with _iterate_*() */ + gint numchildren; + GList *children; + guint32 children_cookie; + + GstBus *child_bus; + GList *messages; + + gboolean polling; + gboolean state_dirty; + + gboolean clock_dirty; + GstClock *provided_clock; + GstElement *clock_provider; + + /*< private >*/ + GstBinPrivate *priv; + + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstBinClass: + * @parent_class: bin parent class + * @add_element: method to add an element to a bin + * @remove_element: method to remove an element from a bin + * @handle_message: method to handle a message from the children + * + * Subclasses can override the @add_element and @remove_element to + * update the list of children in the bin. + * + * The @handle_message method can be overridden to implement custom + * message handling. @handle_message takes ownership of the message, just like + * #gst_element_post_message. + */ +struct _GstBinClass { + GstElementClass parent_class; + + /*< private >*/ + GThreadPool *pool; + + /* signals */ + void (*element_added) (GstBin *bin, GstElement *child); + void (*element_removed) (GstBin *bin, GstElement *child); + + /*< public >*/ + /* virtual methods for subclasses */ + gboolean (*add_element) (GstBin *bin, GstElement *element); + gboolean (*remove_element) (GstBin *bin, GstElement *element); + + void (*handle_message) (GstBin *bin, GstMessage *message); + + /*< private >*/ + /* signal added 0.10.22 */ + gboolean (*do_latency) (GstBin *bin); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_bin_get_type (void); +GstElement* gst_bin_new (const gchar *name); + +/* add and remove elements from the bin */ +gboolean gst_bin_add (GstBin *bin, GstElement *element); +gboolean gst_bin_remove (GstBin *bin, GstElement *element); + +/* retrieve a single child */ +GstElement* gst_bin_get_by_name (GstBin *bin, const gchar *name); +GstElement* gst_bin_get_by_name_recurse_up (GstBin *bin, const gchar *name); +GstElement* gst_bin_get_by_interface (GstBin *bin, GType iface); + +/* retrieve multiple children */ +GstIterator* gst_bin_iterate_elements (GstBin *bin); +GstIterator* gst_bin_iterate_sorted (GstBin *bin); +GstIterator* gst_bin_iterate_recurse (GstBin *bin); + +GstIterator* gst_bin_iterate_sinks (GstBin *bin); +GstIterator* gst_bin_iterate_sources (GstBin *bin); +GstIterator* gst_bin_iterate_all_by_interface (GstBin *bin, GType iface); + +/* latency */ +gboolean gst_bin_recalculate_latency (GstBin * bin); + + +G_END_DECLS + + +#endif /* __GST_BIN_H__ */ diff --git a/gst/gstbuffer.c b/gst/gstbuffer.c new file mode 100644 index 0000000..2366e2b --- /dev/null +++ b/gst/gstbuffer.c @@ -0,0 +1,1618 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstbuffer.c: Buffer operations + * + * 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:gstbuffer + * @short_description: Data-passing buffer type, supporting sub-buffers. + * @see_also: #GstPad, #GstMiniObject + * + * Buffers are the basic unit of data transfer in GStreamer. The #GstBuffer + * type provides all the state necessary to define the regions of memory as + * part of a stream. Region copies are also supported, allowing a smaller + * region of a buffer to become its own buffer, with mechanisms in place to + * ensure that neither memory space goes away prematurely. + * + * Buffers are usually created with gst_buffer_new(). After a buffer has been + * created one will typically allocate memory for it and set the size of the + * buffer data. The following example creates a buffer that can hold a given + * video frame with a given width, height and bits per plane. + * <example> + * <title>Creating a buffer for a video frame</title> + * <programlisting> + * GstBuffer *buffer; + * gint size, width, height, bpp; + * ... + * size = width * height * bpp; + * buffer = gst_buffer_new (); + * GST_BUFFER_SIZE (buffer) = size; + * GST_BUFFER_MALLOCDATA (buffer) = g_malloc (size); + * GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer); + * ... + * </programlisting> + * </example> + * + * Alternatively, use gst_buffer_new_and_alloc() + * to create a buffer with preallocated data of a given size. + * + * The data pointed to by the buffer can be retrieved with the GST_BUFFER_DATA() + * macro. The size of the data can be found with GST_BUFFER_SIZE(). For buffers + * of size 0, the data pointer is undefined (usually NULL) and should never be used. + * + * If an element knows what pad you will push the buffer out on, it should use + * gst_pad_alloc_buffer() instead to create a buffer. This allows downstream + * elements to provide special buffers to write in, like hardware buffers. + * + * A buffer has a pointer to a #GstCaps describing the media type of the data + * in the buffer. Attach caps to the buffer with gst_buffer_set_caps(); this + * is typically done before pushing out a buffer using gst_pad_push() so that + * the downstream element knows the type of the buffer. + * + * A buffer will usually have a timestamp, and a duration, but neither of these + * are guaranteed (they may be set to #GST_CLOCK_TIME_NONE). Whenever a + * meaningful value can be given for these, they should be set. The timestamp + * and duration are measured in nanoseconds (they are #GstClockTime values). + * + * A buffer can also have one or both of a start and an end offset. These are + * media-type specific. For video buffers, the start offset will generally be + * the frame number. For audio buffers, it will be the number of samples + * produced so far. For compressed data, it could be the byte offset in a + * source or destination file. Likewise, the end offset will be the offset of + * the end of the buffer. These can only be meaningfully interpreted if you + * know the media type of the buffer (the #GstCaps set on it). Either or both + * can be set to #GST_BUFFER_OFFSET_NONE. + * + * gst_buffer_ref() is used to increase the refcount of a buffer. This must be + * done when you want to keep a handle to the buffer after pushing it to the + * next element. + * + * To efficiently create a smaller buffer out of an existing one, you can + * use gst_buffer_copy_region(). + * + * If a plug-in wants to modify the buffer data or metadata in-place, it should + * first obtain a buffer that is safe to modify by using + * gst_buffer_make_writable(). This function is optimized so that a copy will + * only be made when it is necessary. + * + * Several flags of the buffer can be set and unset with the + * GST_BUFFER_FLAG_SET() and GST_BUFFER_FLAG_UNSET() macros. Use + * GST_BUFFER_FLAG_IS_SET() to test if a certain #GstBufferFlag is set. + * + * Buffers can be efficiently merged into a larger buffer with + * gst_buffer_span(), which avoids memory copies when the gst_buffer_is_span_fast() + * function returns TRUE. + * + * An element should either unref the buffer or push it out on a src pad + * using gst_pad_push() (see #GstPad). + * + * Buffers are usually freed by unreffing them with gst_buffer_unref(). When + * the refcount drops to 0, any data pointed to by the buffer is unreffed as + * well. + * + * Last reviewed on March 30, 2011 (0.11.0) + */ +#include "gst_private.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "gstbuffer.h" +#include "gstbufferpool.h" +#include "gstinfo.h" +#include "gstutils.h" +#include "gstminiobject.h" +#include "gstversion.h" + +GType _gst_buffer_type = 0; + +static GstMemory *_gst_buffer_arr_span (GstMemory ** mem[], gsize len[], + guint n, gsize offset, gsize size, gboolean writable); + +typedef struct _GstMetaItem GstMetaItem; + +struct _GstMetaItem +{ + GstMetaItem *next; + GstMeta meta; +}; +#define ITEM_SIZE(info) ((info)->size + sizeof (GstMetaItem)) + +#define GST_BUFFER_MEM_MAX 16 + +#define GST_BUFFER_MEM_LEN(b) (((GstBufferImpl *)(b))->len) +#define GST_BUFFER_MEM_ARRAY(b) (((GstBufferImpl *)(b))->mem) +#define GST_BUFFER_MEM_PTR(b,i) (((GstBufferImpl *)(b))->mem[i]) +#define GST_BUFFER_BUFMEM(b) (((GstBufferImpl *)(b))->bufmem) +#define GST_BUFFER_META(b) (((GstBufferImpl *)(b))->item) + +typedef struct +{ + GstBuffer buffer; + + /* the memory blocks */ + guint len; + GstMemory *mem[GST_BUFFER_MEM_MAX]; + + /* memory of the buffer when allocated from 1 chunk */ + GstMemory *bufmem; + + /* FIXME, make metadata allocation more efficient by using part of the + * GstBufferImpl */ + GstMetaItem *item; +} GstBufferImpl; + +static GstMemory * +_span_memory (GstBuffer * buffer, gsize offset, gsize size, gboolean writable) +{ + GstMemory *span, **mem[1]; + gsize len[1]; + + /* not enough room, span buffers */ + mem[0] = GST_BUFFER_MEM_ARRAY (buffer); + len[0] = GST_BUFFER_MEM_LEN (buffer); + + if (size == -1) + size = gst_buffer_get_size (buffer); + + span = _gst_buffer_arr_span (mem, len, 1, offset, size, writable); + + return span; +} + +static void +_replace_memory (GstBuffer * buffer, GstMemory * mem) +{ + gsize len, i; + + /* unref old buffers */ + len = GST_BUFFER_MEM_LEN (buffer); + for (i = 0; i < len; i++) + gst_memory_unref (GST_BUFFER_MEM_PTR (buffer, i)); + + /* replace with single spanned buffer */ + GST_BUFFER_MEM_PTR (buffer, 0) = mem; + GST_BUFFER_MEM_LEN (buffer) = 1; +} + +static inline void +_memory_add (GstBuffer * buffer, guint idx, GstMemory * mem) +{ + guint i, len = GST_BUFFER_MEM_LEN (buffer); + + if (G_UNLIKELY (len >= GST_BUFFER_MEM_MAX)) { + /* too many buffer, span them. */ + /* FIXME, there is room for improvement here: We could only try to merge + * 2 buffers to make some room. If we can't efficiently merge 2 buffers we + * could try to only merge the two smallest buffers to avoid memcpy, etc. */ + _replace_memory (buffer, _span_memory (buffer, 0, -1, FALSE)); + /* we now have 1 single spanned buffer */ + len = 1; + } + + if (idx == -1) + idx = len; + + for (i = len; i > idx; i--) { + /* move buffers to insert, FIXME, we need to insert first and then merge */ + GST_BUFFER_MEM_PTR (buffer, i) = GST_BUFFER_MEM_PTR (buffer, i - 1); + } + /* and insert the new buffer */ + GST_BUFFER_MEM_PTR (buffer, idx) = mem; + GST_BUFFER_MEM_LEN (buffer) = len + 1; +} + +GST_DEFINE_MINI_OBJECT_TYPE (GstBuffer, gst_buffer); + +void +_priv_gst_buffer_initialize (void) +{ + _gst_buffer_type = gst_buffer_get_type (); +} + +/** + * gst_buffer_copy_into: + * @dest: a destination #GstBuffer + * @src: a source #GstBuffer + * @flags: flags indicating what metadata fields should be copied. + * @offset: offset to copy from + * @size: total size to copy. If -1, all data is copied. + * + * Copies the information from @src into @dest. + * + * @flags indicate which fields will be copied. + */ +void +gst_buffer_copy_into (GstBuffer * dest, GstBuffer * src, + GstBufferCopyFlags flags, gsize offset, gsize size) +{ + GstMetaItem *walk; + gsize bufsize; + + g_return_if_fail (dest != NULL); + g_return_if_fail (src != NULL); + + /* nothing to copy if the buffers are the same */ + if (G_UNLIKELY (dest == src)) + return; + + g_return_if_fail (gst_buffer_is_writable (dest)); + + bufsize = gst_buffer_get_size (src); + g_return_if_fail (bufsize >= offset); + if (size == -1) + size = bufsize - offset; + g_return_if_fail (bufsize >= offset + size); + + GST_CAT_LOG (GST_CAT_BUFFER, "copy %p to %p, offset %" G_GSIZE_FORMAT + "-%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT, src, dest, offset, size, + bufsize); + + if (flags & GST_BUFFER_COPY_FLAGS) { + /* copy flags */ + GST_MINI_OBJECT_FLAGS (dest) = GST_MINI_OBJECT_FLAGS (src); + } + + if (flags & GST_BUFFER_COPY_TIMESTAMPS) { + if (offset == 0) { + GST_BUFFER_TIMESTAMP (dest) = GST_BUFFER_TIMESTAMP (src); + GST_BUFFER_OFFSET (dest) = GST_BUFFER_OFFSET (src); + if (size == bufsize) { + GST_BUFFER_DURATION (dest) = GST_BUFFER_DURATION (src); + GST_BUFFER_OFFSET_END (dest) = GST_BUFFER_OFFSET_END (src); + } + } else { + GST_BUFFER_TIMESTAMP (dest) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (dest) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET (dest) = GST_BUFFER_OFFSET_NONE; + GST_BUFFER_OFFSET_END (dest) = GST_BUFFER_OFFSET_NONE; + } + } + + if (flags & GST_BUFFER_COPY_MEMORY) { + GstMemory *mem; + gsize skip, left, len, i, bsize; + + len = GST_BUFFER_MEM_LEN (src); + left = size; + skip = offset; + + /* copy and make regions of the memory */ + for (i = 0; i < len && left > 0; i++) { + mem = GST_BUFFER_MEM_PTR (src, i); + bsize = gst_memory_get_sizes (mem, NULL, NULL); + + if (bsize <= skip) { + /* don't copy buffer */ + skip -= bsize; + } else { + gsize tocopy; + + tocopy = MIN (bsize - skip, left); + if (mem->flags & GST_MEMORY_FLAG_NO_SHARE) { + /* no share, always copy then */ + mem = gst_memory_copy (mem, skip, tocopy); + skip = 0; + } else if (tocopy < bsize) { + /* we need to clip something */ + mem = gst_memory_share (mem, skip, tocopy); + skip = 0; + } else { + mem = gst_memory_ref (mem); + } + _memory_add (dest, -1, mem); + left -= tocopy; + } + } + if (flags & GST_BUFFER_COPY_MERGE) { + _replace_memory (dest, _span_memory (dest, 0, size, FALSE)); + } + } + + for (walk = GST_BUFFER_META (src); walk; walk = walk->next) { + GstMeta *meta = &walk->meta; + const GstMetaInfo *info = meta->info; + + if (info->copy_func) + info->copy_func (dest, meta, src, offset, size); + } +} + +static GstBuffer * +_gst_buffer_copy (GstBuffer * buffer) +{ + GstBuffer *copy; + + g_return_val_if_fail (buffer != NULL, NULL); + + /* create a fresh new buffer */ + copy = gst_buffer_new (); + + /* we simply copy everything from our parent */ + gst_buffer_copy_into (copy, buffer, GST_BUFFER_COPY_ALL, 0, -1); + + return copy; +} + +/* the default dispose function revives the buffer and returns it to the + * pool when there is a pool */ +static gboolean +_gst_buffer_dispose (GstBuffer * buffer) +{ + GstBufferPool *pool; + + /* no pool, do free */ + if ((pool = buffer->pool) == NULL) + return TRUE; + + /* keep the buffer alive */ + gst_buffer_ref (buffer); + /* return the buffer to the pool */ + GST_CAT_LOG (GST_CAT_BUFFER, "release %p to pool %p", buffer, pool); + gst_buffer_pool_release_buffer (pool, buffer); + + return FALSE; +} + +static void +_gst_buffer_free (GstBuffer * buffer) +{ + GstMetaItem *walk, *next; + guint i, len; + gsize msize; + + g_return_if_fail (buffer != NULL); + + GST_CAT_LOG (GST_CAT_BUFFER, "finalize %p", buffer); + + /* free metadata */ + for (walk = GST_BUFFER_META (buffer); walk; walk = next) { + GstMeta *meta = &walk->meta; + const GstMetaInfo *info = meta->info; + + /* call free_func if any */ + if (info->free_func) + info->free_func (meta, buffer); + + next = walk->next; + /* and free the slice */ + g_slice_free1 (ITEM_SIZE (info), walk); + } + + /* get the size, when unreffing the memory, we could also unref the buffer + * itself */ + msize = GST_MINI_OBJECT_SIZE (buffer); + + /* free our memory */ + len = GST_BUFFER_MEM_LEN (buffer); + for (i = 0; i < len; i++) + gst_memory_unref (GST_BUFFER_MEM_PTR (buffer, i)); + + /* we set msize to 0 when the buffer is part of the memory block */ + if (msize) + g_slice_free1 (msize, buffer); + else + gst_memory_unref (GST_BUFFER_BUFMEM (buffer)); +} + +static void +gst_buffer_init (GstBufferImpl * buffer, gsize size) +{ + gst_mini_object_init (GST_MINI_OBJECT_CAST (buffer), _gst_buffer_type, size); + + buffer->buffer.mini_object.copy = + (GstMiniObjectCopyFunction) _gst_buffer_copy; + buffer->buffer.mini_object.dispose = + (GstMiniObjectDisposeFunction) _gst_buffer_dispose; + buffer->buffer.mini_object.free = + (GstMiniObjectFreeFunction) _gst_buffer_free; + + GST_BUFFER (buffer)->pool = NULL; + GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET (buffer) = GST_BUFFER_OFFSET_NONE; + GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET_NONE; + + GST_BUFFER_MEM_LEN (buffer) = 0; + GST_BUFFER_META (buffer) = NULL; +} + +/** + * gst_buffer_new: + * + * Creates a newly allocated buffer without any data. + * + * MT safe. + * + * Returns: (transfer full): the new #GstBuffer. + */ +GstBuffer * +gst_buffer_new (void) +{ + GstBufferImpl *newbuf; + + newbuf = g_slice_new (GstBufferImpl); + GST_CAT_LOG (GST_CAT_BUFFER, "new %p", newbuf); + + gst_buffer_init (newbuf, sizeof (GstBufferImpl)); + + return GST_BUFFER_CAST (newbuf); +} + +/** + * gst_buffer_new_allocate: + * @allocator: the #GstAllocator to use + * @size: the size in bytes of the new buffer's data. + * @align: the alignment of the buffer memory + * + * Tries to create a newly allocated buffer with data of the given size and + * alignment from @allocator. If the requested amount of memory can't be + * allocated, NULL will be returned. The allocated buffer memory is not cleared. + * + * When @allocator is NULL, the default memory allocator will be used. + * + * Allocator buffer memory will be aligned to multiples of (@align + 1) bytes. + * + * Note that when @size == 0, the buffer will not have memory associated with it. + * + * MT safe. + * + * Returns: (transfer full): a new #GstBuffer, or NULL if the memory couldn't + * be allocated. + */ +GstBuffer * +gst_buffer_new_allocate (const GstAllocator * allocator, gsize size, + gsize align) +{ + GstBuffer *newbuf; + GstMemory *mem; +#if 0 + guint8 *data; + gsize asize; +#endif + +#if 1 + if (size > 0) { + mem = gst_allocator_alloc (allocator, size, align); + if (G_UNLIKELY (mem == NULL)) + goto no_memory; + } else { + mem = NULL; + } + + newbuf = gst_buffer_new (); + + if (mem != NULL) + _memory_add (newbuf, -1, mem); + + GST_CAT_LOG (GST_CAT_BUFFER, + "new buffer %p of size %" G_GSIZE_FORMAT " from allocator %p", newbuf, + size, allocator); +#endif + +#if 0 + asize = sizeof (GstBufferImpl) + size; + data = g_slice_alloc (asize); + if (G_UNLIKELY (data == NULL)) + goto no_memory; + + newbuf = GST_BUFFER_CAST (data); + + gst_buffer_init ((GstBufferImpl *) data, asize); + if (size > 0) { + mem = gst_memory_new_wrapped (0, data + sizeof (GstBufferImpl), NULL, + size, 0, size); + _memory_add (newbuf, -1, mem); + } +#endif + +#if 0 + /* allocate memory and buffer, it might be interesting to do this but there + * are many complications. We need to keep the memory mapped to access the + * buffer fields and the memory for the buffer might be just very slow. We + * also need to do some more magic to get the alignment right. */ + asize = sizeof (GstBufferImpl) + size; + mem = gst_allocator_alloc (allocator, asize, align); + if (G_UNLIKELY (mem == NULL)) + goto no_memory; + + /* map the data part and init the buffer in it, set the buffer size to 0 so + * that a finalize won't free the buffer */ + data = gst_memory_map (mem, &asize, NULL, GST_MAP_WRITE); + gst_buffer_init ((GstBufferImpl *) data, 0); + gst_memory_unmap (mem, data, asize); + + /* strip off the buffer */ + gst_memory_resize (mem, sizeof (GstBufferImpl), size); + + newbuf = GST_BUFFER_CAST (data); + GST_BUFFER_BUFMEM (newbuf) = mem; + + if (size > 0) + _memory_add (newbuf, -1, gst_memory_ref (mem)); +#endif + + return newbuf; + + /* ERRORS */ +no_memory: + { + GST_CAT_WARNING (GST_CAT_BUFFER, + "failed to allocate %" G_GSIZE_FORMAT " bytes", size); + return NULL; + } +} + +/** + * gst_buffer_new_wrapped_full: + * @data: data to wrap + * @free_func: function to free @data + * @offset: offset in @data of valid data + * @size: size of valid data in @data starting at @offset + * + * Creates a new buffer that wraps the given @data. Valid data is set + * to start at @offset and up to @size. If no @free_func is provided, + * buffer memory is marked READONLY. + * + * MT safe. + * + * Returns: (transfer full): a new #GstBuffer + */ +GstBuffer * +gst_buffer_new_wrapped_full (gpointer data, GFreeFunc free_func, gsize offset, + gsize size) +{ + GstBuffer *newbuf; + + g_return_val_if_fail (offset <= size, NULL); + + newbuf = gst_buffer_new (); + gst_buffer_take_memory (newbuf, -1, + gst_memory_new_wrapped (free_func ? 0 : GST_MEMORY_FLAG_READONLY, + data, free_func, offset + size, offset, size)); + + return newbuf; +} + +/** + * gst_buffer_new_wrapped: + * @data: data to wrap + * @size: allocated size of @data + * + * Creates a new buffer that wraps the given @data. + * + * MT safe. + * + * Returns: (transfer full): a new #GstBuffer + */ +GstBuffer * +gst_buffer_new_wrapped (gpointer data, gsize size) +{ + return gst_buffer_new_wrapped_full (data, g_free, 0, size); +} + +/** + * gst_buffer_n_memory: + * @buffer: a #GstBuffer. + * + * Get the amount of memory blocks that this buffer has. + * + * Returns: (transfer full): the amount of memory block in this buffer. + */ +guint +gst_buffer_n_memory (GstBuffer * buffer) +{ + g_return_val_if_fail (GST_IS_BUFFER (buffer), 0); + + return GST_BUFFER_MEM_LEN (buffer); +} + +/** + * gst_buffer_take_memory: + * @buffer: a #GstBuffer. + * @idx: the index to add the memory at, or -1 to append it to the end + * @mem: (transfer full): a #GstMemory. + * + * Add the memory block @mem to @buffer at @idx. This function takes ownership + * of @mem and thus doesn't increase its refcount. + */ +void +gst_buffer_take_memory (GstBuffer * buffer, gint idx, GstMemory * mem) +{ + g_return_if_fail (GST_IS_BUFFER (buffer)); + g_return_if_fail (gst_buffer_is_writable (buffer)); + g_return_if_fail (mem != NULL); + g_return_if_fail (idx == -1 || + (idx >= 0 && idx <= GST_BUFFER_MEM_LEN (buffer))); + + _memory_add (buffer, idx, mem); +} + +static GstMemory * +_get_memory (GstBuffer * buffer, guint idx, gboolean write) +{ + GstMemory *mem; + + mem = GST_BUFFER_MEM_PTR (buffer, idx); + + if (G_UNLIKELY (write && !GST_MEMORY_IS_WRITABLE (mem))) { + GstMemory *copy; + GST_CAT_LOG (GST_CAT_BUFFER, + "making writable copy of memory %p in buffer %p", mem, buffer); + /* replace with a writable copy */ + copy = gst_memory_copy (mem, 0, -1); + GST_BUFFER_MEM_PTR (buffer, idx) = copy; + gst_memory_unref (mem); + mem = copy; + } + return mem; +} + +/** + * gst_buffer_peek_memory: + * @buffer: a #GstBuffer. + * @idx: an index + * @flags: #GstMapFlags + * + * Get the memory block in @buffer at @idx for memory access in @flags. + * This function does not return a refcount to the memory block. The memory + * block stays valid for as long as the caller has a valid reference to @buffer. + * + * @buffer should be writable when @flags contains #GST_MAP_WRITE. If the memory + * at @idx is not writable, a new writable copy will be installed in @buffer and + * returned. + * + * Returns: a #GstMemory at @idx. + */ +GstMemory * +gst_buffer_peek_memory (GstBuffer * buffer, guint idx, GstMapFlags flags) +{ + GstMemory *mem; + gboolean write; + + write = (flags & GST_MAP_WRITE) != 0; + + g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL); + g_return_val_if_fail (idx < GST_BUFFER_MEM_LEN (buffer), NULL); + + /* check if we can write when asked for write access */ + if (G_UNLIKELY (write && !gst_buffer_is_writable (buffer))) + goto not_writable; + + mem = _get_memory (buffer, idx, write); + + return mem; + + /* ERRORS */ +not_writable: + { + g_return_val_if_fail (gst_buffer_is_writable (buffer), NULL); + return NULL; + } +} + +/** + * gst_buffer_remove_memory_range: + * @buffer: a #GstBuffer. + * @idx: an index + * @length: a length + * + * Remove @len memory blocks in @buffer starting from @idx. + * + * @length can be -1, in which case all memory starting from @idx is removed. + */ +void +gst_buffer_remove_memory_range (GstBuffer * buffer, guint idx, guint length) +{ + guint len, i, end; + + g_return_if_fail (GST_IS_BUFFER (buffer)); + g_return_if_fail (gst_buffer_is_writable (buffer)); + + len = GST_BUFFER_MEM_LEN (buffer); + if (length == -1) { + g_return_if_fail (idx < len); + length = len - idx; + } + + end = idx + length; + for (i = idx; i < end; i++) + gst_memory_unref (GST_BUFFER_MEM_PTR (buffer, i)); + + if (end != len) { + g_memmove (&GST_BUFFER_MEM_PTR (buffer, idx), + &GST_BUFFER_MEM_PTR (buffer, end), (len - end) * sizeof (gpointer)); + } + GST_BUFFER_MEM_LEN (buffer) = len - length; +} + +/** + * gst_buffer_get_sizes: + * @buffer: a #GstBuffer. + * @offset: a pointer to the offset + * @maxsize: a pointer to the maxsize + * + * Get the total size of all memory blocks in @buffer. + * + * When not %NULL, @offset will contain the offset of the data in the first + * memory block in @buffer and @maxsize will contain the sum of the size + * and @offset and the amount of extra padding on the last memory block. + * @offset and @maxsize can be used to resize the buffer with + * gst_buffer_resize(). + * + * Returns: the total size of the memory in @buffer. + */ +gsize +gst_buffer_get_sizes (GstBuffer * buffer, gsize * offset, gsize * maxsize) +{ + guint len; + gsize size; + GstMemory *mem; + + g_return_val_if_fail (GST_IS_BUFFER (buffer), 0); + + len = GST_BUFFER_MEM_LEN (buffer); + + if (G_LIKELY (len == 1)) { + /* common case */ + mem = GST_BUFFER_MEM_PTR (buffer, 0); + size = gst_memory_get_sizes (mem, offset, maxsize); + } else { + guint i; + gsize extra, offs; + + size = offs = extra = 0; + for (i = 0; i < len; i++) { + gsize s, o, ms; + + mem = GST_BUFFER_MEM_PTR (buffer, i); + s = gst_memory_get_sizes (mem, &o, &ms); + + if (s) { + if (size == 0) + /* first size, take accumulated data before as the offset */ + offs = extra + o; + /* add sizes */ + size += s; + /* save the amount of data after this block */ + extra = ms - (o + s); + } else { + /* empty block, add as extra */ + extra += ms; + } + } + if (offset) + *offset = offs; + if (maxsize) + *maxsize = offs + size + extra; + } + return size; +} + +/** + * gst_buffer_resize: + * @buffer: a #GstBuffer. + * @offset: the offset adjustement + * @size: the new size + * + * Set the total size of the buffer + */ +void +gst_buffer_resize (GstBuffer * buffer, gssize offset, gsize size) +{ + guint len; + guint i; + gsize bsize, bufsize, bufoffs, bufmax; + GstMemory *mem; + + g_return_if_fail (gst_buffer_is_writable (buffer)); + + bufsize = gst_buffer_get_sizes (buffer, &bufoffs, &bufmax); + + GST_CAT_LOG (GST_CAT_BUFFER, "trim %p %" G_GSSIZE_FORMAT "-%" G_GSIZE_FORMAT + " size:%" G_GSIZE_FORMAT " offs:%" G_GSIZE_FORMAT " max:%" G_GSIZE_FORMAT, + buffer, offset, size, bufsize, bufoffs, bufmax); + + /* we can't go back further than the current offset or past the end of the + * buffer */ + g_return_if_fail ((offset < 0 && bufoffs >= -offset) || (offset >= 0 + && bufoffs + offset <= bufmax)); + if (size == -1) { + g_return_if_fail (bufsize >= offset); + size = bufsize - offset; + } + g_return_if_fail (bufmax >= bufoffs + offset + size); + + len = GST_BUFFER_MEM_LEN (buffer); + + /* copy and trim */ + for (i = 0; i < len; i++) { + gsize left, noffs; + + mem = GST_BUFFER_MEM_PTR (buffer, i); + bsize = gst_memory_get_sizes (mem, NULL, NULL); + + noffs = 0; + /* last buffer always gets resized to the remaining size */ + if (i + 1 == len) + left = size; + /* shrink buffers before the offset */ + else if ((gssize) bsize <= offset) { + left = 0; + noffs = offset - bsize; + offset = 0; + } + /* clip other buffers */ + else + left = MIN (bsize - offset, size); + + if (offset != 0 || left != bsize) { + /* we need to clip something */ + if (GST_MEMORY_IS_WRITABLE (mem)) { + gst_memory_resize (mem, offset, left); + } else { + GstMemory *tmp; + + if (mem->flags & GST_MEMORY_FLAG_NO_SHARE) + tmp = gst_memory_copy (mem, offset, left); + else + tmp = gst_memory_share (mem, offset, left); + + gst_memory_unref (mem); + mem = tmp; + } + } + offset = noffs; + size -= left; + + GST_BUFFER_MEM_PTR (buffer, i) = mem; + } +} + +/** + * gst_buffer_map: + * @buffer: a #GstBuffer. + * @size: a location for the size + * @maxsize: a location for the max size + * @flags: flags for the mapping + * + * This function return a pointer to the memory in @buffer. @flags describe the + * desired access of the memory. When @flags is #GST_MAP_WRITE, @buffer should + * be writable (as returned from gst_buffer_is_writable()). + * + * @size and @maxsize will contain the current valid number of bytes in the + * returned memory area and the total maximum mount of bytes available in the + * returned memory area respectively. + * + * When @buffer is writable but the memory isn't, a writable copy will + * automatically be created and returned. The readonly copy of the buffer memory + * will then also be replaced with this writable copy. + * + * When the buffer contains multiple memory blocks, the returned pointer will be + * a concatenation of the memory blocks. + * + * Returns: a pointer to the memory for the buffer. + */ +gpointer +gst_buffer_map (GstBuffer * buffer, gsize * size, gsize * maxsize, + GstMapFlags flags) +{ + guint len; + gpointer data; + GstMemory *mem; + gboolean write, writable; + + g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL); + + write = (flags & GST_MAP_WRITE) != 0; + writable = gst_buffer_is_writable (buffer); + + /* check if we can write when asked for write access */ + if (G_UNLIKELY (write && !writable)) + goto not_writable; + + len = GST_BUFFER_MEM_LEN (buffer); + + if (G_UNLIKELY (len == 0)) { + /* no memory, return immediately */ + if (size) + *size = 0; + if (maxsize) + *maxsize = 0; + return NULL; + } + + if (G_LIKELY (len == 1)) { + /* we can take the first one */ + mem = GST_BUFFER_MEM_PTR (buffer, 0); + } else { + /* we need to span memory */ + if (writable) { + /* if we can write, we can change the memory with the spanned + * memory */ + mem = _span_memory (buffer, 0, -1, write); + _replace_memory (buffer, mem); + } else { + gsize bsize; + + /* extract all data in new memory, FIXME slow!! */ + bsize = gst_buffer_get_size (buffer); + + data = g_malloc (bsize); + gst_buffer_extract (buffer, 0, data, bsize); + if (size) + *size = bsize; + if (maxsize) + *maxsize = bsize; + return data; + } + } + + if (G_UNLIKELY (write && !GST_MEMORY_IS_WRITABLE (mem))) { + GstMemory *copy; + /* replace with a writable copy */ + copy = gst_memory_copy (mem, 0, -1); + GST_BUFFER_MEM_PTR (buffer, 0) = copy; + gst_memory_unref (mem); + mem = copy; + } + + data = gst_memory_map (mem, size, maxsize, flags); + + return data; + + /* ERROR */ +not_writable: + { + g_return_val_if_fail (gst_buffer_is_writable (buffer), NULL); + return NULL; + } +} + +/** + * gst_buffer_unmap: + * @buffer: a #GstBuffer. + * @data: the previously mapped data + * @size: the size of @data + * + * Release the memory previously mapped with gst_buffer_map(). + * + * Returns: #TRUE on success. #FALSE can be returned when the new size is larger + * than the maxsize of the memory. + */ +gboolean +gst_buffer_unmap (GstBuffer * buffer, gpointer data, gsize size) +{ + gboolean result; + guint len; + + g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE); + + len = GST_BUFFER_MEM_LEN (buffer); + + if (G_LIKELY (len == 1)) { + GstMemory *mem = GST_BUFFER_MEM_PTR (buffer, 0); + + result = gst_memory_unmap (mem, data, size); + } else { + /* this must have been from read-only access. After _map, the buffer either + * only contains 1 memory block or it allocated memory to join memory + * blocks. It's not allowed to add buffers between _map and _unmap. */ + g_free (data); + result = TRUE; + } + return result; +} + +/** + * gst_buffer_fill: + * @buffer: a #GstBuffer. + * @offset: the offset to fill + * @src: the source address + * @size: the size to fill + * + * Copy @size bytes from @src to @buffer at @offset. + * + * Returns: The amount of bytes copied. This value can be lower than @size + * when @buffer did not contain enough data. + */ +gsize +gst_buffer_fill (GstBuffer * buffer, gsize offset, gconstpointer src, + gsize size) +{ + gsize i, len, left; + const guint8 *ptr = src; + + g_return_val_if_fail (GST_IS_BUFFER (buffer), 0); + g_return_val_if_fail (gst_buffer_is_writable (buffer), 0); + g_return_val_if_fail (src != NULL, 0); + + len = GST_BUFFER_MEM_LEN (buffer); + left = size; + + for (i = 0; i < len && left > 0; i++) { + guint8 *data; + gsize ssize, tocopy; + GstMemory *mem; + + mem = _get_memory (buffer, i, TRUE); + + data = gst_memory_map (mem, &ssize, NULL, GST_MAP_WRITE); + if (ssize > offset) { + /* we have enough */ + tocopy = MIN (ssize - offset, left); + memcpy (data + offset, ptr, tocopy); + left -= tocopy; + ptr += tocopy; + offset = 0; + } else { + /* offset past buffer, skip */ + offset -= ssize; + } + gst_memory_unmap (mem, data, ssize); + } + return size - left; +} + +/** + * gst_buffer_extract: + * @buffer: a #GstBuffer. + * @offset: the offset to extract + * @dest: the destination address + * @size: the size to extract + * + * Copy @size bytes starting from @offset in @buffer to @dest. + * + * Returns: The amount of bytes extracted. This value can be lower than @size + * when @buffer did not contain enough data. + */ +gsize +gst_buffer_extract (GstBuffer * buffer, gsize offset, gpointer dest, gsize size) +{ + gsize i, len, left; + guint8 *ptr = dest; + + g_return_val_if_fail (GST_IS_BUFFER (buffer), 0); + g_return_val_if_fail (dest != NULL, 0); + + len = GST_BUFFER_MEM_LEN (buffer); + left = size; + + for (i = 0; i < len && left > 0; i++) { + guint8 *data; + gsize ssize, tocopy; + GstMemory *mem; + + mem = GST_BUFFER_MEM_PTR (buffer, i); + + data = gst_memory_map (mem, &ssize, NULL, GST_MAP_READ); + if (ssize > offset) { + /* we have enough */ + tocopy = MIN (ssize - offset, left); + memcpy (ptr, data + offset, tocopy); + left -= tocopy; + ptr += tocopy; + offset = 0; + } else { + /* offset past buffer, skip */ + offset -= ssize; + } + gst_memory_unmap (mem, data, ssize); + } + return size - left; +} + +/** + * gst_buffer_memcmp: + * @buffer: a #GstBuffer. + * @offset: the offset in @buffer + * @mem: the memory to compare + * @size: the size to compare + * + * Compare @size bytes starting from @offset in @buffer with the memory in @mem. + * + * Returns: 0 if the memory is equal. + */ +gint +gst_buffer_memcmp (GstBuffer * buffer, gsize offset, gconstpointer mem, + gsize size) +{ + gsize i, len; + const guint8 *ptr = mem; + gint res = 0; + + g_return_val_if_fail (GST_IS_BUFFER (buffer), 0); + g_return_val_if_fail (mem != NULL, 0); + + len = GST_BUFFER_MEM_LEN (buffer); + + for (i = 0; i < len && size > 0 && res == 0; i++) { + guint8 *data; + gsize ssize, tocmp; + GstMemory *mem; + + mem = GST_BUFFER_MEM_PTR (buffer, i); + + data = gst_memory_map (mem, &ssize, NULL, GST_MAP_READ); + if (ssize > offset) { + /* we have enough */ + tocmp = MIN (ssize - offset, size); + res = memcmp (ptr, data + offset, tocmp); + size -= tocmp; + ptr += tocmp; + offset = 0; + } else { + /* offset past buffer, skip */ + offset -= ssize; + } + gst_memory_unmap (mem, data, ssize); + } + return res; +} + +/** + * gst_buffer_memset: + * @buffer: a #GstBuffer. + * @offset: the offset in @buffer + * @val: the value to set + * @size: the size to set + * + * Fill @buf with @size bytes with @val starting from @offset. + * + * Returns: The amount of bytes filled. This value can be lower than @size + * when @buffer did not contain enough data. + */ +gsize +gst_buffer_memset (GstBuffer * buffer, gsize offset, guint8 val, gsize size) +{ + gsize i, len, left; + + g_return_val_if_fail (GST_IS_BUFFER (buffer), 0); + g_return_val_if_fail (gst_buffer_is_writable (buffer), 0); + + len = GST_BUFFER_MEM_LEN (buffer); + left = size; + + for (i = 0; i < len && left > 0; i++) { + guint8 *data; + gsize ssize, toset; + GstMemory *mem; + + mem = GST_BUFFER_MEM_PTR (buffer, i); + + data = gst_memory_map (mem, &ssize, NULL, GST_MAP_WRITE); + if (ssize > offset) { + /* we have enough */ + toset = MIN (ssize - offset, left); + memset (data + offset, val, toset); + left -= toset; + offset = 0; + } else { + /* offset past buffer, skip */ + offset -= ssize; + } + gst_memory_unmap (mem, data, ssize); + } + return size - left; +} + +/** + * gst_buffer_copy_region: + * @parent: a #GstBuffer. + * @flags: the #GstBufferCopyFlags + * @offset: the offset into parent #GstBuffer at which the new sub-buffer + * begins. + * @size: the size of the new #GstBuffer sub-buffer, in bytes. + * + * Creates a sub-buffer from @parent at @offset and @size. + * This sub-buffer uses the actual memory space of the parent buffer. + * This function will copy the offset and timestamp fields when the + * offset is 0. If not, they will be set to #GST_CLOCK_TIME_NONE and + * #GST_BUFFER_OFFSET_NONE. + * If @offset equals 0 and @size equals the total size of @buffer, the + * duration and offset end fields are also copied. If not they will be set + * to #GST_CLOCK_TIME_NONE and #GST_BUFFER_OFFSET_NONE. + * + * MT safe. + * + * Returns: (transfer full): the new #GstBuffer or NULL if the arguments were + * invalid. + */ +GstBuffer * +gst_buffer_copy_region (GstBuffer * buffer, GstBufferCopyFlags flags, + gsize offset, gsize size) +{ + GstBuffer *copy; + + g_return_val_if_fail (buffer != NULL, NULL); + + /* create the new buffer */ + copy = gst_buffer_new (); + + GST_CAT_LOG (GST_CAT_BUFFER, "new region copy %p of %p %" G_GSIZE_FORMAT + "-%" G_GSIZE_FORMAT, copy, buffer, offset, size); + + gst_buffer_copy_into (copy, buffer, flags, offset, size); + + return copy; +} + +static gboolean +_gst_buffer_arr_is_span_fast (GstMemory ** mem[], gsize len[], guint n, + gsize * offset, GstMemory ** parent) +{ + GstMemory *mcur, *mprv; + gboolean have_offset = FALSE; + guint count, i; + + mcur = mprv = NULL; + for (count = 0; count < n; count++) { + gsize offs, clen; + GstMemory **cmem; + + cmem = mem[count]; + clen = len[count]; + + for (i = 0; i < clen; i++) { + if (mcur) + mprv = mcur; + mcur = cmem[i]; + + if (mprv && mcur) { + /* check is memory is contiguous */ + if (!gst_memory_is_span (mprv, mcur, &offs)) + return FALSE; + + if (!have_offset) { + if (offset) + *offset = offs; + if (parent) + *parent = mprv->parent; + + have_offset = TRUE; + } + } + } + } + return have_offset; +} + +static GstMemory * +_gst_buffer_arr_span (GstMemory ** mem[], gsize len[], guint n, gsize offset, + gsize size, gboolean writable) +{ + GstMemory *span, *parent = NULL; + gsize poffset = 0; + + if (!writable + && _gst_buffer_arr_is_span_fast (mem, len, n, &poffset, &parent)) { + if (parent->flags & GST_MEMORY_FLAG_NO_SHARE) + span = gst_memory_copy (parent, offset + poffset, size); + else + span = gst_memory_share (parent, offset + poffset, size); + } else { + gsize count, left; + guint8 *dest, *ptr; + + span = gst_allocator_alloc (NULL, size, 0); + dest = gst_memory_map (span, NULL, NULL, GST_MAP_WRITE); + + ptr = dest; + left = size; + + for (count = 0; count < n; count++) { + gsize i, tocopy, clen, ssize; + guint8 *src; + GstMemory **cmem; + + cmem = mem[count]; + clen = len[count]; + + for (i = 0; i < clen && left > 0; i++) { + src = gst_memory_map (cmem[i], &ssize, NULL, GST_MAP_READ); + tocopy = MIN (ssize, left); + if (tocopy > offset) { + memcpy (ptr, src + offset, tocopy - offset); + left -= tocopy; + ptr += tocopy; + offset = 0; + } else { + offset -= tocopy; + } + gst_memory_unmap (cmem[i], src, ssize); + } + } + gst_memory_unmap (span, dest, size); + } + return span; +} + +/** + * gst_buffer_is_span_fast: + * @buf1: the first #GstBuffer. + * @buf2: the second #GstBuffer. + * + * Determines whether a gst_buffer_span() can be done without copying + * the contents, that is, whether the data areas are contiguous sub-buffers of + * the same buffer. + * + * MT safe. + * Returns: TRUE if the buffers are contiguous, + * FALSE if a copy would be required. + */ +gboolean +gst_buffer_is_span_fast (GstBuffer * buf1, GstBuffer * buf2) +{ + GstMemory **mem[2]; + gsize len[2]; + + g_return_val_if_fail (GST_IS_BUFFER (buf1), FALSE); + g_return_val_if_fail (GST_IS_BUFFER (buf2), FALSE); + g_return_val_if_fail (buf1->mini_object.refcount > 0, FALSE); + g_return_val_if_fail (buf2->mini_object.refcount > 0, FALSE); + + mem[0] = GST_BUFFER_MEM_ARRAY (buf1); + len[0] = GST_BUFFER_MEM_LEN (buf1); + mem[1] = GST_BUFFER_MEM_ARRAY (buf2); + len[1] = GST_BUFFER_MEM_LEN (buf2); + + return _gst_buffer_arr_is_span_fast (mem, len, 2, NULL, NULL); +} + +/** + * gst_buffer_span: + * @buf1: the first source #GstBuffer to merge. + * @offset: the offset in the first buffer from where the new + * buffer should start. + * @buf2: the second source #GstBuffer to merge. + * @size: the total size of the new buffer. + * + * Creates a new buffer that consists of part of buf1 and buf2. + * Logically, buf1 and buf2 are concatenated into a single larger + * buffer, and a new buffer is created at the given offset inside + * this space, with a given length. + * + * If the two source buffers are children of the same larger buffer, + * and are contiguous, the new buffer will be a child of the shared + * parent, and thus no copying is necessary. you can use + * gst_buffer_is_span_fast() to determine if a memcpy will be needed. + * + * MT safe. + * + * Returns: (transfer full): the new #GstBuffer that spans the two source + * buffers, or NULL if the arguments are invalid. + */ +GstBuffer * +gst_buffer_span (GstBuffer * buf1, gsize offset, GstBuffer * buf2, gsize size) +{ + GstBuffer *newbuf; + GstMemory *span; + GstMemory **mem[2]; + gsize len[2], len1, len2; + + g_return_val_if_fail (GST_IS_BUFFER (buf1), NULL); + g_return_val_if_fail (GST_IS_BUFFER (buf2), NULL); + g_return_val_if_fail (buf1->mini_object.refcount > 0, NULL); + g_return_val_if_fail (buf2->mini_object.refcount > 0, NULL); + len1 = gst_buffer_get_size (buf1); + len2 = gst_buffer_get_size (buf2); + g_return_val_if_fail (len1 + len2 > offset, NULL); + if (size == -1) + size = len1 + len2 - offset; + else + g_return_val_if_fail (size <= len1 + len2 - offset, NULL); + + mem[0] = GST_BUFFER_MEM_ARRAY (buf1); + len[0] = GST_BUFFER_MEM_LEN (buf1); + mem[1] = GST_BUFFER_MEM_ARRAY (buf2); + len[1] = GST_BUFFER_MEM_LEN (buf2); + + span = _gst_buffer_arr_span (mem, len, 2, offset, size, FALSE); + + newbuf = gst_buffer_new (); + _memory_add (newbuf, -1, span); + +#if 0 + /* if the offset is 0, the new buffer has the same timestamp as buf1 */ + if (offset == 0) { + GST_BUFFER_OFFSET (newbuf) = GST_BUFFER_OFFSET (buf1); + GST_BUFFER_TIMESTAMP (newbuf) = GST_BUFFER_TIMESTAMP (buf1); + + /* if we completely merged the two buffers (appended), we can + * calculate the duration too. Also make sure we's not messing with + * invalid DURATIONS */ + if (buf1->size + buf2->size == len) { + if (GST_BUFFER_DURATION_IS_VALID (buf1) && + GST_BUFFER_DURATION_IS_VALID (buf2)) { + /* add duration */ + GST_BUFFER_DURATION (newbuf) = GST_BUFFER_DURATION (buf1) + + GST_BUFFER_DURATION (buf2); + } + if (GST_BUFFER_OFFSET_END_IS_VALID (buf2)) { + /* add offset_end */ + GST_BUFFER_OFFSET_END (newbuf) = GST_BUFFER_OFFSET_END (buf2); + } + } + } +#endif + + return newbuf; +} + +/** + * gst_buffer_get_meta: + * @buffer: a #GstBuffer + * @info: a #GstMetaInfo + * + * Get the metadata for the api in @info on buffer. When there is no such + * metadata, NULL is returned. + * + * Note that the result metadata might not be of the implementation @info. + * + * Returns: the metadata for the api in @info on @buffer. + */ +GstMeta * +gst_buffer_get_meta (GstBuffer * buffer, const GstMetaInfo * info) +{ + GstMetaItem *item; + GstMeta *result = NULL; + + g_return_val_if_fail (buffer != NULL, NULL); + g_return_val_if_fail (info != NULL, NULL); + + /* find GstMeta of the requested API */ + for (item = GST_BUFFER_META (buffer); item; item = item->next) { + GstMeta *meta = &item->meta; + if (meta->info->api == info->api) { + result = meta; + break; + } + } + return result; +} + +/** + * gst_buffer_add_meta: + * @buffer: a #GstBuffer + * @info: a #GstMetaInfo + * @params: params for @info + * + * Add metadata for @info to @buffer using the parameters in @params. + * + * Returns: the metadata for the api in @info on @buffer. + */ +GstMeta * +gst_buffer_add_meta (GstBuffer * buffer, const GstMetaInfo * info, + gpointer params) +{ + GstMetaItem *item; + GstMeta *result = NULL; + gsize size; + + g_return_val_if_fail (buffer != NULL, NULL); + g_return_val_if_fail (info != NULL, NULL); + + /* create a new slice */ + GST_CAT_DEBUG (GST_CAT_BUFFER, "alloc metadata %s of size %" G_GSIZE_FORMAT, + g_type_name (info->type), info->size); + + size = ITEM_SIZE (info); + item = g_slice_alloc (size); + result = &item->meta; + result->info = info; + + /* call the init_func when needed */ + if (info->init_func) + if (!info->init_func (result, params, buffer)) + goto init_failed; + + /* and add to the list of metadata */ + item->next = GST_BUFFER_META (buffer); + GST_BUFFER_META (buffer) = item; + + return result; + +init_failed: + { + g_slice_free1 (size, item); + return NULL; + } +} + +/** + * gst_buffer_remove_meta: + * @buffer: a #GstBuffer + * @meta: a #GstMeta + * + * Remove the metadata for @meta on @buffer. + * + * Returns: %TRUE if the metadata existed and was removed, %FALSE if no such + * metadata was on @buffer. + */ +gboolean +gst_buffer_remove_meta (GstBuffer * buffer, GstMeta * meta) +{ + GstMetaItem *walk, *prev; + + g_return_val_if_fail (buffer != NULL, FALSE); + g_return_val_if_fail (meta != NULL, FALSE); + + /* find the metadata and delete */ + prev = GST_BUFFER_META (buffer); + for (walk = prev; walk; walk = walk->next) { + GstMeta *m = &walk->meta; + if (m == meta) { + const GstMetaInfo *info = meta->info; + + /* remove from list */ + if (GST_BUFFER_META (buffer) == walk) + GST_BUFFER_META (buffer) = walk->next; + else + prev->next = walk->next; + /* call free_func if any */ + if (info->free_func) + info->free_func (m, buffer); + + /* and free the slice */ + g_slice_free1 (ITEM_SIZE (info), walk); + break; + } + prev = walk; + } + return walk != NULL; +} + +/** + * gst_buffer_iterate_meta: + * @buffer: a #GstBuffer + * @state: an opaque state pointer + * + * Retrieve the next #GstMeta after @current. If @state points + * to %NULL, the first metadata is returned. + * + * @state will be updated with an opage state pointer + * + * Returns: The next #GstMeta or %NULL when there are no more items. + */ +GstMeta * +gst_buffer_iterate_meta (GstBuffer * buffer, gpointer * state) +{ + GstMetaItem **meta; + + g_return_val_if_fail (buffer != NULL, NULL); + g_return_val_if_fail (state != NULL, NULL); + + meta = (GstMetaItem **) state; + if (*meta == NULL) + /* state NULL, move to first item */ + *meta = GST_BUFFER_META (buffer); + else + /* state !NULL, move to next item in list */ + *meta = (*meta)->next; + + if (*meta) + return &(*meta)->meta; + else + return NULL; +} diff --git a/gst/gstbuffer.h b/gst/gstbuffer.h new file mode 100644 index 0000000..a25af4d --- /dev/null +++ b/gst/gstbuffer.h @@ -0,0 +1,499 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstbuffer.h: Header for GstBuffer object + * + * 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_BUFFER_H__ +#define __GST_BUFFER_H__ + +#include <gst/gstminiobject.h> +#include <gst/gstclock.h> +#include <gst/gstcaps.h> +#include <gst/gstmemory.h> + +G_BEGIN_DECLS + +extern GType _gst_buffer_type; + +typedef struct _GstBuffer GstBuffer; +typedef struct _GstBufferPool GstBufferPool; + +/** + * GST_BUFFER_TRACE_NAME: + * + * The name used for tracing memory allocations. + */ +#define GST_BUFFER_TRACE_NAME "GstBuffer" + +#define GST_TYPE_BUFFER (_gst_buffer_type) +#define GST_IS_BUFFER(obj) (GST_IS_MINI_OBJECT_TYPE(obj, GST_TYPE_BUFFER)) +#define GST_BUFFER_CAST(obj) ((GstBuffer *)(obj)) +#define GST_BUFFER(obj) (GST_BUFFER_CAST(obj)) + +/** + * GST_BUFFER_FLAGS: + * @buf: a #GstBuffer. + * + * A flags word containing #GstBufferFlag flags set on this buffer. + */ +#define GST_BUFFER_FLAGS(buf) GST_MINI_OBJECT_FLAGS(buf) +/** + * GST_BUFFER_FLAG_IS_SET: + * @buf: a #GstBuffer. + * @flag: the #GstBufferFlag to check. + * + * Gives the status of a specific flag on a buffer. + */ +#define GST_BUFFER_FLAG_IS_SET(buf,flag) GST_MINI_OBJECT_FLAG_IS_SET (buf, flag) +/** + * GST_BUFFER_FLAG_SET: + * @buf: a #GstBuffer. + * @flag: the #GstBufferFlag to set. + * + * Sets a buffer flag on a buffer. + */ +#define GST_BUFFER_FLAG_SET(buf,flag) GST_MINI_OBJECT_FLAG_SET (buf, flag) +/** + * GST_BUFFER_FLAG_UNSET: + * @buf: a #GstBuffer. + * @flag: the #GstBufferFlag to clear. + * + * Clears a buffer flag. + */ +#define GST_BUFFER_FLAG_UNSET(buf,flag) GST_MINI_OBJECT_FLAG_UNSET (buf, flag) + +/** + * GST_BUFFER_TIMESTAMP: + * @buf: a #GstBuffer.: + * + * The timestamp in nanoseconds (as a #GstClockTime) of the data in the buffer. + * Value will be %GST_CLOCK_TIME_NONE if the timestamp is unknown. + * + */ +#define GST_BUFFER_TIMESTAMP(buf) (GST_BUFFER_CAST(buf)->timestamp) +/** + * GST_BUFFER_DURATION: + * @buf: a #GstBuffer. + * + * The duration in nanoseconds (as a #GstClockTime) of the data in the buffer. + * Value will be %GST_CLOCK_TIME_NONE if the duration is unknown. + */ +#define GST_BUFFER_DURATION(buf) (GST_BUFFER_CAST(buf)->duration) +/** + * GST_BUFFER_OFFSET: + * @buf: a #GstBuffer. + * + * The offset in the source file of the beginning of this buffer. + */ +#define GST_BUFFER_OFFSET(buf) (GST_BUFFER_CAST(buf)->offset) +/** + * GST_BUFFER_OFFSET_END: + * @buf: a #GstBuffer. + * + * The offset in the source file of the end of this buffer. + */ +#define GST_BUFFER_OFFSET_END(buf) (GST_BUFFER_CAST(buf)->offset_end) + +/** + * GST_BUFFER_OFFSET_NONE: + * + * Constant for no-offset return results. + */ +#define GST_BUFFER_OFFSET_NONE ((guint64)-1) + +/** + * GST_BUFFER_DURATION_IS_VALID: + * @buffer: a #GstBuffer + * + * Tests if the duration is known. + */ +#define GST_BUFFER_DURATION_IS_VALID(buffer) (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer))) +/** + * GST_BUFFER_TIMESTAMP_IS_VALID: + * @buffer: a #GstBuffer + * + * Tests if the timestamp is known. + */ +#define GST_BUFFER_TIMESTAMP_IS_VALID(buffer) (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer))) +/** + * GST_BUFFER_OFFSET_IS_VALID: + * @buffer: a #GstBuffer + * + * Tests if the start offset is known. + */ +#define GST_BUFFER_OFFSET_IS_VALID(buffer) (GST_BUFFER_OFFSET (buffer) != GST_BUFFER_OFFSET_NONE) +/** + * GST_BUFFER_OFFSET_END_IS_VALID: + * @buffer: a #GstBuffer + * + * Tests if the end offset is known. + */ +#define GST_BUFFER_OFFSET_END_IS_VALID(buffer) (GST_BUFFER_OFFSET_END (buffer) != GST_BUFFER_OFFSET_NONE) +/** + * GST_BUFFER_IS_DISCONT: + * @buffer: a #GstBuffer + * + * Tests if the buffer marks a discontinuity in the stream. + * + * Since: 0.10.9 + */ +#define GST_BUFFER_IS_DISCONT(buffer) (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) + +/** + * GstBufferFlags: + * @GST_BUFFER_FLAG_LIVE: the buffer is live data and should be discarded in + * the PAUSED state. + * @GST_BUFFER_FLAG_DECODE_ONLY: the buffer contains data that should be dropped + * because it will be clipped against the segment + * boundaries or because it does not contain data + * that should be shown to the user. + * @GST_BUFFER_FLAG_DISCONT: the buffer marks a data discontinuity in the stream. + * This typically occurs after a seek or a dropped buffer + * from a live or network source. + * @GST_BUFFER_FLAG_RESYNC: the buffer timestamp might have a discontinuity + * and this buffer is a good point to resynchronize. + * @GST_BUFFER_FLAG_CORRUPTED: the buffer data is corrupted. + * @GST_BUFFER_FLAG_MARKER: the buffer contains a media specific marker. for + * video this is typically the end of a frame boundary, for audio + * this is usually the end of a talkspurt. + * @GST_BUFFER_FLAG_HEADER: the buffer contains header information that is + * needed to decode the following data + * @GST_BUFFER_FLAG_GAP: the buffer has been created to fill a gap in the + * stream and contains media neutral data (elements can + * switch to optimized code path that ignores the buffer + * content). + * @GST_BUFFER_FLAG_DROPPABLE: the buffer can be dropped without breaking the + * stream, for example to reduce bandwidth. + * @GST_BUFFER_FLAG_DELTA_UNIT: this unit cannot be decoded independently. + * @GST_BUFFER_FLAG_IN_CAPS: the buffer has been added as a field in a #GstCaps. + * @GST_BUFFER_FLAG_LAST: additional media specific flags can be added starting from + * this flag. + * + * A set of buffer flags used to describe properties of a #GstBuffer. + */ +typedef enum { + GST_BUFFER_FLAG_LIVE = (GST_MINI_OBJECT_FLAG_LAST << 0), + GST_BUFFER_FLAG_DECODE_ONLY = (GST_MINI_OBJECT_FLAG_LAST << 1), + GST_BUFFER_FLAG_DISCONT = (GST_MINI_OBJECT_FLAG_LAST << 2), + GST_BUFFER_FLAG_RESYNC = (GST_MINI_OBJECT_FLAG_LAST << 3), + GST_BUFFER_FLAG_CORRUPTED = (GST_MINI_OBJECT_FLAG_LAST << 4), + GST_BUFFER_FLAG_MARKER = (GST_MINI_OBJECT_FLAG_LAST << 5), + GST_BUFFER_FLAG_HEADER = (GST_MINI_OBJECT_FLAG_LAST << 6), + GST_BUFFER_FLAG_GAP = (GST_MINI_OBJECT_FLAG_LAST << 7), + GST_BUFFER_FLAG_DROPPABLE = (GST_MINI_OBJECT_FLAG_LAST << 8), + GST_BUFFER_FLAG_DELTA_UNIT = (GST_MINI_OBJECT_FLAG_LAST << 9), + GST_BUFFER_FLAG_IN_CAPS = (GST_MINI_OBJECT_FLAG_LAST << 10), + + GST_BUFFER_FLAG_LAST = (GST_MINI_OBJECT_FLAG_LAST << 20) +} GstBufferFlags; + +/** + * GstBuffer: + * @mini_object: the parent structure + * @pool: pointer to the pool owner of the buffer + * @timestamp: timestamp of the buffer, can be #GST_CLOCK_TIME_NONE when the + * timestamp is not known or relevant. + * @duration: duration in time of the buffer data, can be #GST_CLOCK_TIME_NONE + * when the duration is not known or relevant. + * @offset: a media specific offset for the buffer data. + * For video frames, this is the frame number of this buffer. + * For audio samples, this is the offset of the first sample in this buffer. + * For file data or compressed data this is the byte offset of the first + * byte in this buffer. + * @offset_end: the last offset contained in this buffer. It has the same + * format as @offset. + * + * The structure of a #GstBuffer. Use the associated macros to access the public + * variables. + */ +struct _GstBuffer { + GstMiniObject mini_object; + + /*< public >*/ /* with COW */ + GstBufferPool *pool; + + /* timestamp */ + GstClockTime timestamp; + GstClockTime duration; + + /* media specific offset */ + guint64 offset; + guint64 offset_end; +}; + +GType gst_buffer_get_type (void); + +/* allocation */ +GstBuffer * gst_buffer_new (void); +GstBuffer * gst_buffer_new_allocate (const GstAllocator * allocator, gsize size, gsize align); +GstBuffer * gst_buffer_new_wrapped_full (gpointer data, GFreeFunc free_func, gsize offset, gsize size); +GstBuffer * gst_buffer_new_wrapped (gpointer data, gsize size); + +/* memory blocks */ +guint gst_buffer_n_memory (GstBuffer *buffer); +void gst_buffer_take_memory (GstBuffer *buffer, gint idx, GstMemory *mem); +GstMemory * gst_buffer_peek_memory (GstBuffer *buffer, guint idx, GstMapFlags flags); +void gst_buffer_remove_memory_range (GstBuffer *buffer, guint idx, guint length); + +/** + * gst_buffer_remove_memory: + * @b: a #GstBuffer. + * @i: an index + * + * Remove the memory block in @b at @i. + */ +#define gst_buffer_remove_memory(b,i) gst_buffer_remove_memory_range ((b), (i), 1) + +gsize gst_buffer_fill (GstBuffer *buffer, gsize offset, + gconstpointer src, gsize size); +gsize gst_buffer_extract (GstBuffer *buffer, gsize offset, + gpointer dest, gsize size); +gint gst_buffer_memcmp (GstBuffer *buffer, gsize offset, + gconstpointer mem, gsize size); +gsize gst_buffer_memset (GstBuffer *buffer, gsize offset, + guint8 val, gsize size); + +gsize gst_buffer_get_sizes (GstBuffer *buffer, gsize *offset, gsize *maxsize); +void gst_buffer_resize (GstBuffer *buffer, gssize offset, gsize size); + +/** + * gst_buffer_get_size: + * @b: a #GstBuffer. + * + * Get the size of @b. + */ +#define gst_buffer_get_size(b) gst_buffer_get_sizes ((b), NULL, NULL) +/** + * gst_buffer_set_size: + * @b: a #GstBuffer. + * @s: a new size + * + * Set the size of @b to @s. This will remove or trim the memory blocks + * in the buffer. + */ +#define gst_buffer_set_size(b,s) gst_buffer_resize ((b), 0, (s)) + +/* getting memory */ +gpointer gst_buffer_map (GstBuffer *buffer, gsize *size, gsize *maxsize, + GstMapFlags flags); +gboolean gst_buffer_unmap (GstBuffer *buffer, gpointer data, gsize size); + +/* refcounting */ +/** + * gst_buffer_ref: + * @buf: a #GstBuffer. + * + * Increases the refcount of the given buffer by one. + * + * Note that the refcount affects the writeability + * of @buf and its metadata, see gst_buffer_is_writable() and + * gst_buffer_is_metadata_writable(). It is + * important to note that keeping additional references to + * GstBuffer instances can potentially increase the number + * of memcpy operations in a pipeline. + * + * Returns: (transfer full): @buf + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstBuffer * gst_buffer_ref (GstBuffer * buf); +#endif + +static inline GstBuffer * +gst_buffer_ref (GstBuffer * buf) +{ + return (GstBuffer *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (buf)); +} + +/** + * gst_buffer_unref: + * @buf: (transfer full): a #GstBuffer. + * + * Decreases the refcount of the buffer. If the refcount reaches 0, the buffer + * will be freed. If GST_BUFFER_MALLOCDATA() is non-NULL, this pointer will + * also be freed at this time. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void gst_buffer_unref (GstBuffer * buf); +#endif + +static inline void +gst_buffer_unref (GstBuffer * buf) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (buf)); +} + +/* copy buffer */ +/** + * gst_buffer_copy: + * @buf: a #GstBuffer. + * + * Create a copy of the given buffer. This will also make a newly allocated + * copy of the data the source buffer contains. + * + * Returns: (transfer full): a new copy of @buf. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstBuffer * gst_buffer_copy (const GstBuffer * buf); +#endif + +static inline GstBuffer * +gst_buffer_copy (const GstBuffer * buf) +{ + return GST_BUFFER (gst_mini_object_copy (GST_MINI_OBJECT_CONST_CAST (buf))); +} + + +/** + * GstBufferCopyFlags: + * @GST_BUFFER_COPY_NONE: copy nothing + * @GST_BUFFER_COPY_FLAGS: flag indicating that buffer flags should be copied + * @GST_BUFFER_COPY_TIMESTAMPS: flag indicating that buffer timestamp, duration, + * offset and offset_end should be copied + * @GST_BUFFER_COPY_MEMORY: flag indicating that buffer memory should be copied + * and appended to already existing memory + * @GST_BUFFER_COPY_MERGE: flag indicating that buffer memory should be + * merged + * + * A set of flags that can be provided to the gst_buffer_copy_into() + * function to specify which items should be copied. + */ +typedef enum { + GST_BUFFER_COPY_NONE = 0, + GST_BUFFER_COPY_FLAGS = (1 << 0), + GST_BUFFER_COPY_TIMESTAMPS = (1 << 1), + GST_BUFFER_COPY_MEMORY = (1 << 2), + GST_BUFFER_COPY_MERGE = (1 << 3) +} GstBufferCopyFlags; + +/** + * GST_BUFFER_COPY_METADATA: + * + * Combination of all possible metadata fields that can be copied with + * gst_buffer_copy_into(). + */ +#define GST_BUFFER_COPY_METADATA (GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS) + +/** + * GST_BUFFER_COPY_ALL: + * + * Combination of all possible fields that can be copied with + * gst_buffer_copy_into(). + */ +#define GST_BUFFER_COPY_ALL ((GstBufferCopyFlags)(GST_BUFFER_COPY_METADATA | GST_BUFFER_COPY_MEMORY)) + +/* copies memory or metadata into newly allocated buffer */ +void gst_buffer_copy_into (GstBuffer *dest, GstBuffer *src, + GstBufferCopyFlags flags, + gsize offset, gsize size); + +/** + * gst_buffer_is_writable: + * @buf: a #GstBuffer + * + * Tests if you can safely write data into a buffer's data array or validly + * modify the caps and timestamp metadata. Metadata in a GstBuffer is always + * writable, but it is only safe to change it when there is only one owner + * of the buffer - ie, the refcount is 1. + */ +#define gst_buffer_is_writable(buf) gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (buf)) +/** + * gst_buffer_make_writable: + * @buf: (transfer full): a #GstBuffer + * + * Makes a writable buffer from the given buffer. If the source buffer is + * already writable, this will simply return the same buffer. A copy will + * otherwise be made using gst_buffer_copy(). + * + * Returns: (transfer full): a writable buffer which may or may not be the + * same as @buf + */ +#define gst_buffer_make_writable(buf) GST_BUFFER_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (buf))) + +/** + * gst_buffer_replace: + * @obuf: (inout) (transfer full): pointer to a pointer to a #GstBuffer to be + * replaced. + * @nbuf: (transfer none) (allow-none): pointer to a #GstBuffer that will + * replace the buffer pointed to by @obuf. + * + * Modifies a pointer to a #GstBuffer to point to a different #GstBuffer. The + * modification is done atomically (so this is useful for ensuring thread safety + * in some cases), and the reference counts are updated appropriately (the old + * buffer is unreffed, the new is reffed). + * + * Either @nbuf or the #GstBuffer pointed to by @obuf may be NULL. + */ +#define gst_buffer_replace(obuf,nbuf) \ +G_STMT_START { \ + GstBuffer **___obufaddr = (GstBuffer **)(obuf); \ + gst_mini_object_replace ((GstMiniObject **)___obufaddr, \ + GST_MINI_OBJECT_CAST (nbuf)); \ +} G_STMT_END + +/* creating a region */ +GstBuffer* gst_buffer_copy_region (GstBuffer *parent, GstBufferCopyFlags flags, + gsize offset, gsize size); + +/* span, two buffers, intelligently */ +gboolean gst_buffer_is_span_fast (GstBuffer *buf1, GstBuffer *buf2); +GstBuffer* gst_buffer_span (GstBuffer *buf1, gsize offset, GstBuffer *buf2, gsize size); + +/* metadata */ +#include <gst/gstmeta.h> + +GstMeta * gst_buffer_get_meta (GstBuffer *buffer, const GstMetaInfo *info); +GstMeta * gst_buffer_add_meta (GstBuffer *buffer, const GstMetaInfo *info, + gpointer params); +gboolean gst_buffer_remove_meta (GstBuffer *buffer, GstMeta *meta); + +GstMeta * gst_buffer_iterate_meta (GstBuffer *buffer, gpointer *state); + +/** + * gst_value_set_buffer: + * @v: a #GValue to receive the data + * @b: (transfer none): a #GstBuffer to assign to the GstValue + * + * Sets @b as the value of @v. Caller retains reference to buffer. + */ +#define gst_value_set_buffer(v,b) g_value_set_boxed((v),(b)) +/** + * gst_value_take_buffer: + * @v: a #GValue to receive the data + * @b: (transfer full): a #GstBuffer to assign to the GstValue + * + * Sets @b as the value of @v. Caller gives away reference to buffer. + */ +#define gst_value_take_buffer(v,b) g_value_take_boxed(v,(b)) +/** + * gst_value_get_buffer: + * @v: a #GValue to query + * + * Receives a #GstBuffer as the value of @v. Does not return a reference to + * the buffer, so the pointer is only valid for as long as the caller owns + * a reference to @v. + * + * Returns: (transfer none): buffer + */ +#define gst_value_get_buffer(v) GST_BUFFER_CAST (g_value_get_boxed(v)) + +G_END_DECLS + +#endif /* __GST_BUFFER_H__ */ diff --git a/gst/gstbufferlist.c b/gst/gstbufferlist.c new file mode 100644 index 0000000..06f817f --- /dev/null +++ b/gst/gstbufferlist.c @@ -0,0 +1,284 @@ +/* GStreamer + * Copyright (C) 2009 Axis Communications <dev-gstreamer at axis dot com> + * @author Jonas Holmberg <jonas dot holmberg at axis dot com> + * + * gstbufferlist.c: Buffer list + * + * 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:gstbufferlist + * @short_description: Lists of buffers for data-passing + * @see_also: #GstPad, #GstMiniObject + * + * Buffer lists are an object containing a list of buffers. + * + * Buffer lists are created with gst_buffer_list_new() and filled with data + * using a gst_buffer_list_insert(). + * + */ +#include "gst_private.h" + +#include "gstbuffer.h" +#include "gstbufferlist.h" + +#define GST_CAT_DEFAULT GST_CAT_BUFFER_LIST + +/** + * GstBufferList: + * + * Opaque list of grouped buffers. + * + * Since: 0.10.24 + */ +struct _GstBufferList +{ + GstMiniObject mini_object; + + GArray *array; +}; + +GType _gst_buffer_list_type = 0; + +GST_DEFINE_MINI_OBJECT_TYPE (GstBufferList, gst_buffer_list); + +void +_priv_gst_buffer_list_initialize (void) +{ + _gst_buffer_list_type = gst_buffer_list_get_type (); +} + +static GstBufferList * +_gst_buffer_list_copy (GstBufferList * list) +{ + GstBufferList *copy; + guint i, len; + + len = list->array->len; + copy = gst_buffer_list_sized_new (len); + + /* add and ref all buffers in the array */ + for (i = 0; i < len; i++) { + GstBuffer *buf = g_array_index (list->array, GstBuffer *, i); + buf = gst_buffer_ref (buf); + g_array_append_val (copy->array, buf); + } + return copy; +} + +static void +_gst_buffer_list_free (GstBufferList * list) +{ + guint i, len; + GST_LOG ("free %p", list); + + /* unrefs all buffers too */ + len = list->array->len; + for (i = 0; i < len; i++) + gst_buffer_unref (g_array_index (list->array, GstBuffer *, i)); + g_array_free (list->array, TRUE); + + g_slice_free1 (GST_MINI_OBJECT_SIZE (list), list); +} + +static void +gst_buffer_list_init (GstBufferList * list, gsize size, guint asize) +{ + gst_mini_object_init (GST_MINI_OBJECT_CAST (list), _gst_buffer_list_type, + size); + + list->mini_object.copy = (GstMiniObjectCopyFunction) _gst_buffer_list_copy; + list->mini_object.free = (GstMiniObjectFreeFunction) _gst_buffer_list_free; + + list->array = g_array_sized_new (FALSE, FALSE, sizeof (GstBuffer *), asize); + + GST_LOG ("init %p", list); +} + +/** + * gst_buffer_list_sized_new: + * @size: an initial reserved size + * + * Creates a new, empty #GstBufferList. The caller is responsible for unreffing + * the returned #GstBufferList. The list will have @size space preallocated so + * that memory reallocations can be avoided. + * + * Free-function: gst_buffer_list_unref + * + * Returns: (transfer full): the new #GstBufferList. gst_buffer_list_unref() + * after usage. + * + * Since: 0.10.24 + */ +GstBufferList * +gst_buffer_list_sized_new (guint size) +{ + GstBufferList *list; + + list = g_slice_new0 (GstBufferList); + + GST_LOG ("new %p", list); + + gst_buffer_list_init (list, sizeof (GstBufferList), size); + + return list; +} + +/** + * gst_buffer_list_new: + * + * Creates a new, empty #GstBufferList. The caller is responsible for unreffing + * the returned #GstBufferList. + * + * Free-function: gst_buffer_list_unref + * + * Returns: (transfer full): the new #GstBufferList. gst_buffer_list_unref() + * after usage. + * + * Since: 0.10.24 + */ +GstBufferList * +gst_buffer_list_new (void) +{ + return gst_buffer_list_sized_new (8); +} + +/** + * gst_buffer_list_len: + * @list: a #GstBufferList + * + * Returns the number of buffers in @list. + * + * Returns: the number of buffers in the buffer list + * + * Since: 0.10.24 + */ +guint +gst_buffer_list_len (GstBufferList * list) +{ + g_return_val_if_fail (GST_IS_BUFFER_LIST (list), 0); + + return list->array->len; +} + +/** + * gst_buffer_list_foreach: + * @list: a #GstBufferList + * @func: (scope call): a #GstBufferListFunc to call + * @user_data: (closure): user data passed to @func + * + * Call @func with @data for each buffer in @list. + * + * @func can modify the passed buffer pointer or its contents. The return value + * of @func define if this function returns or if the remaining buffers in a + * group should be skipped. + * + * Since: 0.10.24 + */ +void +gst_buffer_list_foreach (GstBufferList * list, GstBufferListFunc func, + gpointer user_data) +{ + guint i, len; + + g_return_if_fail (GST_IS_BUFFER_LIST (list)); + g_return_if_fail (func != NULL); + + len = list->array->len; + for (i = 0; i < len;) { + GstBuffer *buf, *buf_ret; + gboolean ret; + + buf = buf_ret = g_array_index (list->array, GstBuffer *, i); + ret = func (&buf_ret, i, user_data); + + /* Check if the function changed the buffer */ + if (buf != buf_ret) { + if (buf_ret == NULL) { + g_array_remove_index (list->array, i); + } else { + g_array_index (list->array, GstBuffer *, i) = buf_ret; + } + } + + if (!ret) + break; + + /* If the buffer was not removed by func go to the next buffer */ + if (buf_ret != NULL) + i++; + } +} + +/** + * gst_buffer_list_get: + * @list: a #GstBufferList + * @idx: the index + * + * Get the buffer at @idx. + * + * Returns: (transfer none): the buffer at @idx in @group or NULL when there + * is no buffer. The buffer remains valid as long as @list is valid. + * + * Since: 0.10.24 + */ +GstBuffer * +gst_buffer_list_get (GstBufferList * list, guint idx) +{ + GstBuffer *buf; + + g_return_val_if_fail (GST_IS_BUFFER_LIST (list), NULL); + g_return_val_if_fail (idx < list->array->len, NULL); + + buf = g_array_index (list->array, GstBuffer *, idx); + + return buf; +} + +/** + * gst_buffer_list_insert: + * @list: a #GstBufferList + * @idx: the index + * @buffer: a #GstBuffer + * + * Insert @buffer at @idx in @list. Other buffers are moved to make room for + * this new buffer. + * + * A -1 value for @idx will append the buffer at the end. + */ +void +gst_buffer_list_insert (GstBufferList * list, guint idx, GstBuffer * buffer) +{ + g_return_if_fail (GST_IS_BUFFER_LIST (list)); + g_return_if_fail (buffer != NULL); + + if (idx == -1) + g_array_append_val (list->array, buffer); + else { + g_return_if_fail (idx < list->array->len); + g_array_insert_val (list->array, idx, buffer); + } +} + +void +gst_buffer_list_remove (GstBufferList * list, guint idx, guint length) +{ + g_return_if_fail (GST_IS_BUFFER_LIST (list)); + g_return_if_fail (idx < list->array->len); + + g_array_remove_range (list->array, idx, length); +} diff --git a/gst/gstbufferlist.h b/gst/gstbufferlist.h new file mode 100644 index 0000000..7275dad --- /dev/null +++ b/gst/gstbufferlist.h @@ -0,0 +1,176 @@ +/* GStreamer + * Copyright (C) 2009 Axis Communications <dev-gstreamer at axis dot com> + * @author Jonas Holmberg <jonas dot holmberg at axis dot com> + * + * gstbufferlist.h: Header for GstBufferList object + * + * 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_BUFFER_LIST_H__ +#define __GST_BUFFER_LIST_H__ + +#include <gst/gstbuffer.h> + +G_BEGIN_DECLS + +extern GType _gst_buffer_list_type; + +#define GST_TYPE_BUFFER_LIST (_gst_buffer_list_type) +#define GST_IS_BUFFER_LIST(obj) (GST_IS_MINI_OBJECT_TYPE(obj, GST_TYPE_BUFFER_LIST)) +#define GST_BUFFER_LIST_CAST(obj) ((GstBufferList *)obj) +#define GST_BUFFER_LIST(obj) (GST_BUFFER_LIST_CAST(obj)) + +typedef struct _GstBufferList GstBufferList; + +/** + * GstBufferListFunc: + * @buffer: pointer the buffer + * @idx: the index of @buffer + * @user_data: user data passed to gst_buffer_list_foreach() + * + * A function that will be called from gst_buffer_list_foreach(). The @buffer + * field will point to a the reference of the buffer at @idx. + * + * When this function returns %TRUE, the next buffer will be + * returned. When %FALSE is returned, gst_buffer_list_foreach() will return. + * + * When @buffer is set to NULL, the item will be removed from the bufferlist. + * When @buffer has been made writable, the new buffer reference can be assigned + * to @buffer. This function is responsible for unreffing the old buffer when + * removing or modifying. + * + * Returns: %FALSE when gst_buffer_list_foreach() should stop + */ +typedef gboolean (*GstBufferListFunc) (GstBuffer **buffer, guint idx, + gpointer user_data); + + +/* refcounting */ +/** + * gst_buffer_list_ref: + * @list: a #GstBufferList + * + * Increases the refcount of the given buffer list by one. + * + * Note that the refcount affects the writeability of @list and its data, see + * gst_buffer_list_make_writable(). It is important to note that keeping + * additional references to GstBufferList instances can potentially increase + * the number of memcpy operations in a pipeline. + * + * Returns: (transfer full): @list + * + * Since: 0.10.24 + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstBufferList * gst_buffer_list_ref (GstBufferList * list); +#endif + +static inline GstBufferList * +gst_buffer_list_ref (GstBufferList * list) +{ + return GST_BUFFER_LIST_CAST (gst_mini_object_ref (GST_MINI_OBJECT_CAST ( + list))); +} + +/** + * gst_buffer_list_unref: + * @list: (transfer full): a #GstBufferList + * + * Decreases the refcount of the buffer list. If the refcount reaches 0, the + * buffer list will be freed. + * + * Since: 0.10.24 + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void gst_buffer_list_unref (GstBufferList * list); +#endif + +static inline void +gst_buffer_list_unref (GstBufferList * list) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (list)); +} + +/* copy */ +/** + * gst_buffer_list_copy: + * @list: a #GstBufferList + * + * Create a shallow copy of the given buffer list. This will make a newly + * allocated copy of the source list with copies of buffer pointers. The + * refcount of buffers pointed to will be increased by one. + * + * Returns: (transfer full): a new copy of @list. + * + * Since: 0.10.24 + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstBufferList * gst_buffer_list_copy (const GstBufferList * list); +#endif + +static inline GstBufferList * +gst_buffer_list_copy (const GstBufferList * list) +{ + return GST_BUFFER_LIST_CAST (gst_mini_object_copy (GST_MINI_OBJECT_CONST_CAST (list))); +} + +/** + * gst_buffer_list_is_writable: + * @list: a #GstBufferList + * + * Tests if you can safely add buffers and groups into a buffer list. + * + * Since: 0.10.24 + */ +#define gst_buffer_list_is_writable(list) gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (list)) + +/** + * gst_buffer_list_make_writable: + * @list: (transfer full): a #GstBufferList + * + * Makes a writable buffer list from the given buffer list. If the source buffer + * list is already writable, this will simply return the same buffer list. A + * copy will otherwise be made using gst_buffer_list_copy(). + * + * Returns: (transfer full): a writable list, which may or may not be the + * same as @list + * + * Since: 0.10.24 + */ +#define gst_buffer_list_make_writable(list) GST_BUFFER_LIST_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (list))) + +GType gst_buffer_list_get_type (void); + +/* allocation */ +GstBufferList * gst_buffer_list_new (void); +GstBufferList * gst_buffer_list_sized_new (guint size); + +guint gst_buffer_list_len (GstBufferList *list); + +GstBuffer * gst_buffer_list_get (GstBufferList *list, guint idx); +void gst_buffer_list_insert (GstBufferList *list, guint idx, GstBuffer *buffer); +void gst_buffer_list_remove (GstBufferList *list, guint idx, guint length); + +void gst_buffer_list_foreach (GstBufferList *list, + GstBufferListFunc func, + gpointer user_data); + +#define gst_buffer_list_add(l,b) gst_buffer_list_insert((l),-1,(b)); + +G_END_DECLS + +#endif /* __GST_BUFFER_LIST_H__ */ diff --git a/gst/gstbufferpool.c b/gst/gstbufferpool.c new file mode 100644 index 0000000..bb91167 --- /dev/null +++ b/gst/gstbufferpool.c @@ -0,0 +1,954 @@ +/* GStreamer + * Copyright (C) 2010 Wim Taymans <wim.taymans@gmail.com> + * + * gstbufferpool.c: GstBufferPool baseclass + * + * 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:gstbufferpool + * @short_description: Pool for buffers + * @see_also: #GstBuffer + * + */ + +#include "gst_private.h" + +#include <errno.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include <sys/types.h> + +#include "gstinfo.h" +#include "gstquark.h" + +#include "gstbufferpool.h" + +GST_DEBUG_CATEGORY_STATIC (gst_buffer_pool_debug); +#define GST_CAT_DEFAULT gst_buffer_pool_debug + +#define GST_BUFFER_POOL_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BUFFER_POOL, GstBufferPoolPrivate)) + +#define GST_BUFFER_POOL_LOCK(pool) (g_static_rec_mutex_lock(&pool->priv->rec_lock)) +#define GST_BUFFER_POOL_UNLOCK(pool) (g_static_rec_mutex_unlock(&pool->priv->rec_lock)) + +struct _GstBufferPoolPrivate +{ + GStaticRecMutex rec_lock; + guint size; + guint min_buffers; + guint max_buffers; + guint prefix; + guint align; +}; + +enum +{ + /* add more above */ + LAST_SIGNAL +}; + +static void gst_buffer_pool_finalize (GObject * object); + +G_DEFINE_TYPE (GstBufferPool, gst_buffer_pool, GST_TYPE_OBJECT); + +static gboolean default_start (GstBufferPool * pool); +static gboolean default_stop (GstBufferPool * pool); +static gboolean default_set_config (GstBufferPool * pool, + GstStructure * config); +static GstFlowReturn default_alloc_buffer (GstBufferPool * pool, + GstBuffer ** buffer, GstBufferPoolParams * params); +static GstFlowReturn default_acquire_buffer (GstBufferPool * pool, + GstBuffer ** buffer, GstBufferPoolParams * params); +static void default_reset_buffer (GstBufferPool * pool, GstBuffer * buffer, + GstBufferPoolParams * params); +static void default_free_buffer (GstBufferPool * pool, GstBuffer * buffer); +static void default_release_buffer (GstBufferPool * pool, GstBuffer * buffer); + +static void +gst_buffer_pool_class_init (GstBufferPoolClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstBufferPoolPrivate)); + + gobject_class->finalize = gst_buffer_pool_finalize; + + klass->start = default_start; + klass->stop = default_stop; + klass->set_config = default_set_config; + klass->acquire_buffer = default_acquire_buffer; + klass->reset_buffer = default_reset_buffer; + klass->alloc_buffer = default_alloc_buffer; + klass->release_buffer = default_release_buffer; + klass->free_buffer = default_free_buffer; + + GST_DEBUG_CATEGORY_INIT (gst_buffer_pool_debug, "bufferpool", 0, + "bufferpool debug"); +} + +static void +gst_buffer_pool_init (GstBufferPool * pool) +{ + pool->priv = GST_BUFFER_POOL_GET_PRIVATE (pool); + + g_static_rec_mutex_init (&pool->priv->rec_lock); + + pool->poll = gst_poll_new_timer (); + pool->queue = gst_atomic_queue_new (10); + pool->flushing = TRUE; + pool->active = FALSE; + pool->configured = FALSE; + pool->started = FALSE; + pool->config = gst_structure_id_empty_new (GST_QUARK (BUFFER_POOL_CONFIG)); + gst_buffer_pool_config_set (pool->config, NULL, 0, 0, 0, 0, 0); + gst_poll_write_control (pool->poll); + + GST_DEBUG_OBJECT (pool, "created"); +} + +static void +gst_buffer_pool_finalize (GObject * object) +{ + GstBufferPool *pool; + + pool = GST_BUFFER_POOL_CAST (object); + + GST_DEBUG_OBJECT (pool, "finalize"); + + gst_buffer_pool_set_active (pool, FALSE); + gst_atomic_queue_unref (pool->queue); + gst_poll_free (pool->poll); + gst_structure_free (pool->config); + g_static_rec_mutex_free (&pool->priv->rec_lock); + + G_OBJECT_CLASS (gst_buffer_pool_parent_class)->finalize (object); +} + +/** + * gst_buffer_pool_new: + * + * Creates a new #GstBufferPool instance. + * + * Returns: a new #GstBufferPool instance + */ +GstBufferPool * +gst_buffer_pool_new (void) +{ + GstBufferPool *result; + + result = g_object_newv (GST_TYPE_BUFFER_POOL, 0, NULL); + GST_DEBUG_OBJECT (result, "created new buffer pool"); + + return result; +} + +static GstFlowReturn +default_alloc_buffer (GstBufferPool * pool, GstBuffer ** buffer, + GstBufferPoolParams * params) +{ + GstBufferPoolPrivate *priv = pool->priv; + GstMemory *mem; + + *buffer = gst_buffer_new (); + + mem = gst_allocator_alloc (NULL, priv->size + priv->prefix, priv->align); + gst_memory_resize (mem, priv->prefix, priv->size); + gst_buffer_take_memory (*buffer, -1, mem); + + return GST_FLOW_OK; +} + +/* the default implementation for preallocating the buffers + * in the pool */ +static gboolean +default_start (GstBufferPool * pool) +{ + guint i; + GstBufferPoolPrivate *priv = pool->priv; + GstBufferPoolClass *pclass; + + pclass = GST_BUFFER_POOL_GET_CLASS (pool); + + /* no alloc function, error */ + if (G_UNLIKELY (pclass->alloc_buffer == NULL)) + goto no_alloc; + + /* we need to prealloc buffers */ + for (i = 0; i < priv->min_buffers; i++) { + GstBuffer *buffer; + + if (pclass->alloc_buffer (pool, &buffer, NULL) != GST_FLOW_OK) + goto alloc_failed; + + GST_LOG_OBJECT (pool, "prealloced buffer %d: %p", i, buffer); + /* release to the queue, we call the vmethod directly, we don't need to do + * the other refcount handling right now. */ + if (G_LIKELY (pclass->release_buffer)) + pclass->release_buffer (pool, buffer); + } + return TRUE; + + /* ERRORS */ +no_alloc: + { + GST_WARNING_OBJECT (pool, "no alloc function"); + return FALSE; + } +alloc_failed: + { + GST_WARNING_OBJECT (pool, "alloc function failed"); + return FALSE; + } +} + +/* must be called with the lock */ +static gboolean +do_start (GstBufferPool * pool) +{ + if (!pool->started) { + GstBufferPoolClass *pclass; + + pclass = GST_BUFFER_POOL_GET_CLASS (pool); + + GST_LOG_OBJECT (pool, "starting"); + /* start the pool, subclasses should allocate buffers and put them + * in the queue */ + if (G_LIKELY (pclass->start)) { + if (!pclass->start (pool)) + return FALSE; + } + pool->started = TRUE; + } + return TRUE; +} + + +static void +default_free_buffer (GstBufferPool * pool, GstBuffer * buffer) +{ + gst_buffer_unref (buffer); +} + +/* must be called with the lock */ +static gboolean +default_stop (GstBufferPool * pool) +{ + GstBuffer *buffer; + GstBufferPoolClass *pclass; + + pclass = GST_BUFFER_POOL_GET_CLASS (pool); + + /* clear the pool */ + while ((buffer = gst_atomic_queue_pop (pool->queue))) { + GST_LOG_OBJECT (pool, "freeing %p", buffer); + gst_poll_read_control (pool->poll); + + if (G_LIKELY (pclass->free_buffer)) + pclass->free_buffer (pool, buffer); + } + return TRUE; +} + +/* must be called with the lock */ +static gboolean +do_stop (GstBufferPool * pool) +{ + if (pool->started) { + GstBufferPoolClass *pclass; + + pclass = GST_BUFFER_POOL_GET_CLASS (pool); + + GST_LOG_OBJECT (pool, "stopping"); + if (G_LIKELY (pclass->stop)) { + if (!pclass->stop (pool)) + return FALSE; + } + pool->started = FALSE; + } + return TRUE; +} + +/** + * gst_buffer_pool_set_active: + * @pool: a #GstBufferPool + * @active: the new active state + * + * Control the active state of @pool. When the pool is active, new calls to + * gst_buffer_pool_acquire_buffer() will return with GST_FLOW_WRONG_STATE. + * + * Activating the bufferpool will preallocate all resources in the pool based on + * the configuration of the pool. + * + * Deactivating will free the resources again when there are no outstanding + * buffers. When there are outstanding buffers, they will be freed as soon as + * they are all returned to the pool. + * + * Returns: %FALSE when the pool was not configured or when preallocation of the + * buffers failed. + */ +gboolean +gst_buffer_pool_set_active (GstBufferPool * pool, gboolean active) +{ + gboolean res = TRUE; + + g_return_val_if_fail (GST_IS_BUFFER_POOL (pool), FALSE); + + GST_LOG_OBJECT (pool, "active %d", active); + + GST_BUFFER_POOL_LOCK (pool); + /* just return if we are already in the right state */ + if (pool->active == active) + goto was_ok; + + /* we need to be configured */ + if (!pool->configured) + goto not_configured; + + if (active) { + if (!do_start (pool)) + goto start_failed; + + /* unset the flushing state now */ + gst_poll_read_control (pool->poll); + g_atomic_int_set (&pool->flushing, FALSE); + } else { + gint outstanding; + + /* set to flushing first */ + g_atomic_int_set (&pool->flushing, TRUE); + gst_poll_write_control (pool->poll); + + /* when all buffers are in the pool, free them. Else they will be + * freed when they are released */ + outstanding = g_atomic_int_get (&pool->outstanding); + GST_LOG_OBJECT (pool, "outstanding buffers %d", outstanding); + if (outstanding == 0) { + if (!do_stop (pool)) + goto stop_failed; + } + } + pool->active = active; + GST_BUFFER_POOL_UNLOCK (pool); + + return res; + +was_ok: + { + GST_DEBUG_OBJECT (pool, "pool was in the right state"); + GST_BUFFER_POOL_UNLOCK (pool); + return TRUE; + } +not_configured: + { + GST_ERROR_OBJECT (pool, "pool was not configured"); + GST_BUFFER_POOL_UNLOCK (pool); + return FALSE; + } +start_failed: + { + GST_ERROR_OBJECT (pool, "start failed"); + GST_BUFFER_POOL_UNLOCK (pool); + return FALSE; + } +stop_failed: + { + GST_WARNING_OBJECT (pool, "stop failed"); + GST_BUFFER_POOL_UNLOCK (pool); + return FALSE; + } +} + +/** + * gst_buffer_pool_is_active: + * @pool: a #GstBufferPool + * + * Check if @pool is active. A pool can be activated with the + * gst_buffer_pool_set_active() call. + * + * Returns: %TRUE when the pool is active. + */ +gboolean +gst_buffer_pool_is_active (GstBufferPool * pool) +{ + gboolean res; + + GST_BUFFER_POOL_LOCK (pool); + res = pool->active; + GST_BUFFER_POOL_UNLOCK (pool); + + return res; +} + +static gboolean +default_set_config (GstBufferPool * pool, GstStructure * config) +{ + GstBufferPoolPrivate *priv = pool->priv; + const GstCaps *caps; + guint size, min_buffers, max_buffers; + guint prefix, align; + + /* parse the config and keep around */ + if (!gst_buffer_pool_config_get (config, &caps, &size, &min_buffers, + &max_buffers, &prefix, &align)) + goto wrong_config; + + GST_DEBUG_OBJECT (pool, "config %" GST_PTR_FORMAT, config); + + priv->size = size; + priv->min_buffers = min_buffers; + priv->max_buffers = max_buffers; + priv->prefix = prefix; + priv->align = align; + + return TRUE; + +wrong_config: + { + GST_WARNING_OBJECT (pool, "invalid config %" GST_PTR_FORMAT, config); + return FALSE; + } +} + +/** + * gst_buffer_pool_set_config: + * @pool: a #GstBufferPool + * @config: a #GstStructure + * + * Set the configuration of the pool. The pool must be inactive and all buffers + * allocated form this pool must be returned or else this function will do + * nothing and return FALSE. + * + * @config is a #GstStructure that contains the configuration parameters for + * the pool. A default and mandatory set of parameters can be configured with + * gst_buffer_pool_config_set(). This function takes ownership of @config. + * + * Returns: TRUE when the configuration could be set. + */ +gboolean +gst_buffer_pool_set_config (GstBufferPool * pool, GstStructure * config) +{ + gboolean result; + GstBufferPoolClass *pclass; + + g_return_val_if_fail (GST_IS_BUFFER_POOL (pool), FALSE); + g_return_val_if_fail (config != NULL, FALSE); + + GST_BUFFER_POOL_LOCK (pool); + /* can't change the settings when active */ + if (pool->active) + goto was_active; + + /* we can't change when outstanding buffers */ + if (g_atomic_int_get (&pool->outstanding) != 0) + goto have_outstanding; + + pclass = GST_BUFFER_POOL_GET_CLASS (pool); + + /* set the new config */ + if (G_LIKELY (pclass->set_config)) + result = pclass->set_config (pool, config); + else + result = FALSE; + + if (result) { + if (pool->config) + gst_structure_free (pool->config); + pool->config = config; + + /* now we are configured */ + pool->configured = TRUE; + } + GST_BUFFER_POOL_UNLOCK (pool); + + return result; + + /* ERRORS */ +was_active: + { + GST_WARNING_OBJECT (pool, "can't change config, we are active"); + GST_BUFFER_POOL_UNLOCK (pool); + return FALSE; + } +have_outstanding: + { + GST_WARNING_OBJECT (pool, "can't change config, have outstanding buffers"); + GST_BUFFER_POOL_UNLOCK (pool); + return FALSE; + } +} + +/** + * gst_buffer_pool_get_config: + * @pool: a #GstBufferPool + * + * Get a copy of the current configuration of the pool. This configuration + * can either be modified and used for the gst_buffer_pool_set_config() call + * or it must be freed after usage. + * + * Returns: a copy of the current configuration of @pool. use + * gst_structure_free() after usage or gst_buffer_pool_set_config(). + */ +GstStructure * +gst_buffer_pool_get_config (GstBufferPool * pool) +{ + GstStructure *result; + + g_return_val_if_fail (GST_IS_BUFFER_POOL (pool), NULL); + + GST_BUFFER_POOL_UNLOCK (pool); + result = gst_structure_copy (pool->config); + GST_BUFFER_POOL_UNLOCK (pool); + + return result; +} + +static const gchar *empty_option[] = { NULL }; + +/** + * gst_buffer_pool_get_options: + * @pool: a #GstBufferPool + * + * Get a NULL terminated array of string with supported bufferpool options for + * @pool. An option would typically be enabled with + * gst_buffer_pool_config_add_option(). + * + * Returns: a NULL terminated array of strings. + */ +const gchar ** +gst_buffer_pool_get_options (GstBufferPool * pool) +{ + GstBufferPoolClass *pclass; + const gchar **result; + + g_return_val_if_fail (GST_IS_BUFFER_POOL (pool), NULL); + + pclass = GST_BUFFER_POOL_GET_CLASS (pool); + + if (G_LIKELY (pclass->get_options)) { + if ((result = pclass->get_options (pool)) == NULL) + goto invalid_result; + } else + result = empty_option; + + return result; + + /* ERROR */ +invalid_result: + { + g_warning ("bufferpool subclass returned NULL options"); + return empty_option; + } +} + +/** + * gst_buffer_pool_has_option: + * @pool: a #GstBufferPool + * @option: an option + * + * Check if the bufferpool supports @option. + * + * Returns: a NULL terminated array of strings. + */ +gboolean +gst_buffer_pool_has_option (GstBufferPool * pool, const gchar * option) +{ + guint i; + const gchar **options; + + g_return_val_if_fail (GST_IS_BUFFER_POOL (pool), FALSE); + g_return_val_if_fail (option != NULL, FALSE); + + options = gst_buffer_pool_get_options (pool); + + for (i = 0; options[i]; i++) { + if (g_str_equal (options[i], option)) + return TRUE; + } + return FALSE; +} + +/** + * gst_buffer_pool_config_set: + * @config: a #GstBufferPool configuration + * @caps: caps for the buffers + * @size: the size of each buffer, not including prefix + * @min_buffers: the minimum amount of buffers to allocate. + * @max_buffers: the maximum amount of buffers to allocate or 0 for unlimited. + * @prefix: prefix each buffer with this many bytes + * @align: alignment of the buffer data. + * + * Configure @config with the given parameters. + */ +void +gst_buffer_pool_config_set (GstStructure * config, const GstCaps * caps, + guint size, guint min_buffers, guint max_buffers, guint prefix, guint align) +{ + g_return_if_fail (config != NULL); + + gst_structure_id_set (config, + GST_QUARK (CAPS), GST_TYPE_CAPS, caps, + GST_QUARK (SIZE), G_TYPE_UINT, size, + GST_QUARK (MIN_BUFFERS), G_TYPE_UINT, min_buffers, + GST_QUARK (MAX_BUFFERS), G_TYPE_UINT, max_buffers, + GST_QUARK (PREFIX), G_TYPE_UINT, prefix, + GST_QUARK (ALIGN), G_TYPE_UINT, align, NULL); +} + +/** + * gst_buffer_pool_config_add_option: + * @config: a #GstBufferPool configuration + * @option: an option to add + * + * Enabled the option in @config. This will instruct the @bufferpool to enable + * the specified option on the buffers that it allocates. + * + * The supported options by @pool can be retrieved with gst_buffer_pool_get_options(). + */ +void +gst_buffer_pool_config_add_option (GstStructure * config, const gchar * option) +{ + GValueArray *array; + const GValue *value; + GValue option_value = { 0 }; + gint i; + + g_return_if_fail (config != NULL); + + value = gst_structure_id_get_value (config, GST_QUARK (OPTIONS)); + if (value) { + array = (GValueArray *) g_value_get_boxed (value); + } else { + GValue new_array_val = { 0, }; + + array = g_value_array_new (0); + + g_value_init (&new_array_val, G_TYPE_VALUE_ARRAY); + g_value_take_boxed (&new_array_val, array); + + gst_structure_id_take_value (config, GST_QUARK (OPTIONS), &new_array_val); + } + for (i = 0; i < array->n_values; i++) { + value = g_value_array_get_nth (array, i); + if (g_str_equal (option, g_value_get_string (value))) + return; + } + g_value_init (&option_value, G_TYPE_STRING); + g_value_set_string (&option_value, option); + g_value_array_append (array, &option_value); + g_value_unset (&option_value); +} + +/** + * gst_buffer_pool_config_n_options: + * @config: a #GstBufferPool configuration + * + * Retrieve the number of values currently stored in the + * options array of the @config structure. + * + * Returns: the options array size as a #guint. + */ +guint +gst_buffer_pool_config_n_options (GstStructure * config) +{ + GValueArray *array; + const GValue *value; + guint size = 0; + + g_return_val_if_fail (config != NULL, 0); + + value = gst_structure_id_get_value (config, GST_QUARK (OPTIONS)); + if (value) { + array = (GValueArray *) g_value_get_boxed (value); + size = array->n_values; + } + return size; +} + +/** + * gst_buffer_pool_config_get_option: + * @config: a #GstBufferPool configuration + * @index: position in the option array to read + * + * Parse an available @config and get the option + * at @index of the options API array. + * + * Returns: a #gchar of the option at @index. + */ +const gchar * +gst_buffer_pool_config_get_option (GstStructure * config, guint index) +{ + const GValue *value; + const gchar *ret = NULL; + + g_return_val_if_fail (config != NULL, 0); + + value = gst_structure_id_get_value (config, GST_QUARK (OPTIONS)); + if (value) { + GValueArray *array; + GValue *option_value; + + array = (GValueArray *) g_value_get_boxed (value); + option_value = g_value_array_get_nth (array, index); + + if (option_value) + ret = g_value_get_string (option_value); + } + return ret; +} + +/** + * gst_buffer_pool_config_has_option: + * @config: a #GstBufferPool configuration + * @option: an option + * + * Check if @config contains @option + * + * Returns: TRUE if the options array contains @option. + */ +gboolean +gst_buffer_pool_config_has_option (GstStructure * config, const gchar * option) +{ + const GValue *value; + + g_return_val_if_fail (config != NULL, 0); + + value = gst_structure_id_get_value (config, GST_QUARK (OPTIONS)); + if (value) { + GValueArray *array; + GValue *option_value; + gint i; + + array = (GValueArray *) g_value_get_boxed (value); + for (i = 0; i < array->n_values; i++) { + option_value = g_value_array_get_nth (array, i); + if (g_str_equal (option, g_value_get_string (option_value))) + return TRUE; + } + } + return FALSE; +} + +/** + * gst_buffer_pool_config_get: + * @config: a #GstBufferPool configuration + * @caps: the caps of buffers + * @size: the size of each buffer, not including prefix + * @min_buffers: the minimum amount of buffers to allocate. + * @max_buffers: the maximum amount of buffers to allocate or 0 for unlimited. + * @prefix: prefix each buffer with this many bytes + * @align: alignment of the buffer data. + * + * Get the configuration values from @config. + */ +gboolean +gst_buffer_pool_config_get (GstStructure * config, const GstCaps ** caps, + guint * size, guint * min_buffers, guint * max_buffers, guint * prefix, + guint * align) +{ + g_return_val_if_fail (config != NULL, FALSE); + + return gst_structure_id_get (config, + GST_QUARK (CAPS), GST_TYPE_CAPS, caps, + GST_QUARK (SIZE), G_TYPE_UINT, size, + GST_QUARK (MIN_BUFFERS), G_TYPE_UINT, min_buffers, + GST_QUARK (MAX_BUFFERS), G_TYPE_UINT, max_buffers, + GST_QUARK (PREFIX), G_TYPE_UINT, prefix, + GST_QUARK (ALIGN), G_TYPE_UINT, align, NULL); +} + +static GstFlowReturn +default_acquire_buffer (GstBufferPool * pool, GstBuffer ** buffer, + GstBufferPoolParams * params) +{ + GstFlowReturn result; + GstBufferPoolClass *pclass; + GstBufferPoolPrivate *priv = pool->priv; + + pclass = GST_BUFFER_POOL_GET_CLASS (pool); + + while (TRUE) { + if (G_UNLIKELY (g_atomic_int_get (&pool->flushing))) + goto flushing; + + /* try to get a buffer from the queue */ + *buffer = gst_atomic_queue_pop (pool->queue); + if (G_LIKELY (*buffer)) { + gst_poll_read_control (pool->poll); + result = GST_FLOW_OK; + GST_LOG_OBJECT (pool, "acquired buffer %p", *buffer); + break; + } + + /* no buffer */ + if (priv->max_buffers == 0) { + /* no max_buffers, we allocate some more */ + if (G_LIKELY (pclass->alloc_buffer)) { + result = pclass->alloc_buffer (pool, buffer, params); + } else + result = GST_FLOW_NOT_SUPPORTED; + GST_LOG_OBJECT (pool, "alloc buffer %p", *buffer); + break; + } + + /* check if we need to wait */ + if (params && (params->flags & GST_BUFFER_POOL_FLAG_DONTWAIT)) { + GST_LOG_OBJECT (pool, "no more buffers"); + result = GST_FLOW_UNEXPECTED; + break; + } + + /* now wait */ + GST_LOG_OBJECT (pool, "waiting for free buffers"); + gst_poll_wait (pool->poll, GST_CLOCK_TIME_NONE); + } + + return result; + + /* ERRORS */ +flushing: + { + GST_DEBUG_OBJECT (pool, "we are flushing"); + return GST_FLOW_WRONG_STATE; + } +} + +static inline void +dec_outstanding (GstBufferPool * pool) +{ + if (g_atomic_int_dec_and_test (&pool->outstanding)) { + /* all buffers are returned to the pool, see if we need to free them */ + if (g_atomic_int_get (&pool->flushing)) { + /* take the lock so that set_active is not run concurrently */ + GST_BUFFER_POOL_LOCK (pool); + /* recheck the flushing state in the lock, the pool could have been + * set to active again */ + if (g_atomic_int_get (&pool->flushing)) + do_stop (pool); + + GST_BUFFER_POOL_UNLOCK (pool); + } + } +} + +static void +default_reset_buffer (GstBufferPool * pool, GstBuffer * buffer, + GstBufferPoolParams * params) +{ + GST_BUFFER_FLAGS (buffer) = 0; + + GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET (buffer) = GST_BUFFER_OFFSET_NONE; + GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET_NONE; +} + +/** + * gst_buffer_pool_acquire_buffer: + * @pool: a #GstBufferPool + * @buffer: a location for a #GstBuffer + * @params: parameters. + * + * Acquire a buffer from @pool. @buffer should point to a memory location that + * can hold a pointer to the new buffer. + * + * @params can be NULL or contain optional parameters to influence the allocation. + * + * Returns: a #GstFlowReturn such as GST_FLOW_WRONG_STATE when the pool is + * inactive. + */ +GstFlowReturn +gst_buffer_pool_acquire_buffer (GstBufferPool * pool, GstBuffer ** buffer, + GstBufferPoolParams * params) +{ + GstBufferPoolClass *pclass; + GstFlowReturn result; + + g_return_val_if_fail (GST_IS_BUFFER_POOL (pool), GST_FLOW_ERROR); + g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); + + pclass = GST_BUFFER_POOL_GET_CLASS (pool); + + /* assume we'll have one more outstanding buffer we need to do that so + * that concurrent set_active doesn't clear the buffers */ + g_atomic_int_inc (&pool->outstanding); + + if (G_LIKELY (pclass->acquire_buffer)) + result = pclass->acquire_buffer (pool, buffer, params); + else + result = GST_FLOW_NOT_SUPPORTED; + + if (G_LIKELY (result == GST_FLOW_OK)) { + /* all buffers from the pool point to the pool and have the refcount of the + * pool incremented */ + (*buffer)->pool = gst_object_ref (pool); + /* now reset the buffer when needed */ + if (G_LIKELY (pclass->reset_buffer)) + pclass->reset_buffer (pool, *buffer, params); + } else { + dec_outstanding (pool); + } + + return result; +} + +static void +default_release_buffer (GstBufferPool * pool, GstBuffer * buffer) +{ + /* keep it around in our queue */ + GST_LOG_OBJECT (pool, "released buffer %p", buffer); + gst_atomic_queue_push (pool->queue, buffer); + gst_poll_write_control (pool->poll); +} + +/** + * gst_buffer_pool_release_buffer: + * @pool: a #GstBufferPool + * @buffer: a #GstBuffer + * + * Release @buffer to @pool. @buffer should have previously been allocated from + * @pool with gst_buffer_pool_acquire_buffer(). + * + * This function is usually called automatically when the last ref on @buffer + * disappears. + */ +void +gst_buffer_pool_release_buffer (GstBufferPool * pool, GstBuffer * buffer) +{ + GstBufferPoolClass *pclass; + + g_return_if_fail (GST_IS_BUFFER_POOL (pool)); + g_return_if_fail (buffer != NULL); + + /* check that the buffer is ours, all buffers returned to the pool have the + * pool member set to NULL and the pool refcount decreased */ + if (!g_atomic_pointer_compare_and_exchange ((gpointer *) & buffer->pool, + pool, NULL)) + return; + + pclass = GST_BUFFER_POOL_GET_CLASS (pool); + + if (G_LIKELY (pclass->release_buffer)) + pclass->release_buffer (pool, buffer); + + dec_outstanding (pool); + + /* decrease the refcount that the buffer had to us */ + gst_object_unref (pool); +} diff --git a/gst/gstbufferpool.h b/gst/gstbufferpool.h new file mode 100644 index 0000000..639fd26 --- /dev/null +++ b/gst/gstbufferpool.h @@ -0,0 +1,214 @@ +/* GStreamer + * Copyright (C) 2010 Wim Taymans <wim.taymans@gmail.com> + * + * gstbufferpool.h: Header for GstBufferPool object + * + * 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_BUFFER_POOL_H__ +#define __GST_BUFFER_POOL_H__ + +#include <gst/gstminiobject.h> +#include <gst/gstatomicqueue.h> +#include <gst/gstpoll.h> +#include <gst/gstclock.h> +#include <gst/gstpad.h> +#include <gst/gstbuffer.h> + +G_BEGIN_DECLS + +typedef struct _GstBufferPoolPrivate GstBufferPoolPrivate; +typedef struct _GstBufferPoolClass GstBufferPoolClass; + +/** + * GST_BUFFER_POOL_TRACE_NAME: + * + * The name used for tracing memory allocations. + */ +#define GST_BUFFER_POOL_TRACE_NAME "GstBufferPool" + +#define GST_TYPE_BUFFER_POOL (gst_buffer_pool_get_type()) +#define GST_IS_BUFFER_POOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_BUFFER_POOL)) +#define GST_IS_BUFFER_POOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_BUFFER_POOL)) +#define GST_BUFFER_POOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_BUFFER_POOL, GstBufferPoolClass)) +#define GST_BUFFER_POOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_BUFFER_POOL, GstBufferPool)) +#define GST_BUFFER_POOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_BUFFER_POOL, GstBufferPoolClass)) +#define GST_BUFFER_POOL_CAST(obj) ((GstBufferPool *)(obj)) + +/** + * GstBufferPoolFlags: + * @GST_BUFFER_POOL_FLAG_NONE: no flags + * @GST_BUFFER_POOL_FLAG_KEY_UNIT: buffer is keyframe + * @GST_BUFFER_POOL_FLAG_DONTWAIT: don't wait for buffer. This makes the + * acquire_buffer method return GST_FLOW_UNEXPECTED. + * @GST_BUFFER_POOL_FLAG_DISCONT: buffer is discont + * @GST_BUFFER_POOL_FLAG_LAST: last flag, subclasses can use private flags + * starting from this value. + * + * Additional flags to control the allocation of a buffer + */ +typedef enum { + GST_BUFFER_POOL_FLAG_NONE = 0, + GST_BUFFER_POOL_FLAG_KEY_UNIT = (1 << 0), + GST_BUFFER_POOL_FLAG_DONTWAIT = (1 << 1), + GST_BUFFER_POOL_FLAG_DISCONT = (1 << 2), + GST_BUFFER_POOL_FLAG_LAST = (1 << 16), +} GstBufferPoolFlags; + +/** + * GstBufferPoolParams: + * @format: the format of @start and @stop + * @start: the start position + * @stop: the stop position + * @flags: additional flags + * + * Parameters passed to the gst_buffer_pool_acquire_buffer() function to control the + * allocation of the buffer. + * + * The default implementation ignores the @start and @stop members but other + * implementations can use this extra information to decide what buffer to + * return. + */ +typedef struct _GstBufferPoolParams { + GstFormat format; + gint64 start; + gint64 stop; + GstBufferPoolFlags flags; +} GstBufferPoolParams; + +/** + * GST_BUFFER_POOL_IS_FLUSHING: + * @pool: a GstBufferPool + * + * Check if the bufferpool is flushing. Subclasses might want to check the + * state of the pool in the acquire function. + */ +#define GST_BUFFER_POOL_IS_FLUSHING(pool) (g_atomic_int_get (&pool->flushing)) + +/** + * GstBufferPool: + * @object: the parent structure + * + * The structure of a #GstBufferPool. Use the associated macros to access the public + * variables. + */ +struct _GstBufferPool { + GstObject object; + + /*< private >*/ + gboolean active; + gboolean flushing; + gboolean started; + gint outstanding; + GstAtomicQueue *queue; + GstPoll *poll; + + gboolean configured; + GstStructure *config; + + GstBufferPoolPrivate *priv; + + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstBufferPoolClass: + * @object_class: Object parent class + * @get_options: get a list of options supported by this pool + * @set_config: apply the bufferpool configuration. The default configuration + * will parse the default config parameters + * @start: start the bufferpool. The default implementation will preallocate + * min-buffers buffers and put them in the queue + * @stop: stop the bufferpool. the default implementation will free the + * preallocated buffers. This function is called when all the buffers are + * returned to the pool. + * @acquire_buffer: get a new buffer from the pool. The default implementation + * will take a buffer from the queue and optionally wait for a buffer to + * be released when there are no buffers available. + * @alloc_buffer: allocate a buffer. the default implementation allocates + * buffers from the default memory allocator and with the configured + * size, prefix and alignment. + * @reset_buffer: reset the buffer to its state when it was freshly allocated. + * The default implementation will clear the flags and timestamps. + * @release_buffer: release a buffer back in the pool. The default + * implementation will put the buffer back in the queue and notify any + * blocking acquire_buffer calls. + * @free_buffer: free a buffer. The default implementation unrefs the buffer. + * + * The GstBufferPool class. + */ +struct _GstBufferPoolClass { + GstObjectClass object_class; + + /* vmethods */ + const gchar ** (*get_options) (GstBufferPool *pool); + gboolean (*set_config) (GstBufferPool *pool, GstStructure *config); + + gboolean (*start) (GstBufferPool *pool); + gboolean (*stop) (GstBufferPool *pool); + + GstFlowReturn (*acquire_buffer) (GstBufferPool *pool, GstBuffer **buffer, + GstBufferPoolParams *params); + GstFlowReturn (*alloc_buffer) (GstBufferPool *pool, GstBuffer **buffer, + GstBufferPoolParams *params); + void (*reset_buffer) (GstBufferPool *pool, GstBuffer *buffer, + GstBufferPoolParams *params); + void (*release_buffer) (GstBufferPool *pool, GstBuffer *buffer); + void (*free_buffer) (GstBufferPool *pool, GstBuffer *buffer); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_buffer_pool_get_type (void); + +/* allocation */ +GstBufferPool * gst_buffer_pool_new (void); + +/* state management */ +gboolean gst_buffer_pool_set_active (GstBufferPool *pool, gboolean active); +gboolean gst_buffer_pool_is_active (GstBufferPool *pool); + +gboolean gst_buffer_pool_set_config (GstBufferPool *pool, GstStructure *config); +GstStructure * gst_buffer_pool_get_config (GstBufferPool *pool); + +const gchar ** gst_buffer_pool_get_options (GstBufferPool *pool); +gboolean gst_buffer_pool_has_option (GstBufferPool *pool, const gchar *option); + +/* helpers for configuring the config structure */ +void gst_buffer_pool_config_set (GstStructure *config, const GstCaps *caps, + guint size, guint min_buffers, guint max_buffers, + guint prefix, guint align); +gboolean gst_buffer_pool_config_get (GstStructure *config, const GstCaps **caps, + guint *size, guint *min_buffers, guint *max_buffers, + guint *prefix, guint *align); + +/* options */ +guint gst_buffer_pool_config_n_options (GstStructure *config); +void gst_buffer_pool_config_add_option (GstStructure *config, const gchar *option); +const gchar * gst_buffer_pool_config_get_option (GstStructure *config, guint index); +gboolean gst_buffer_pool_config_has_option (GstStructure *config, const gchar *option); + +/* buffer management */ +GstFlowReturn gst_buffer_pool_acquire_buffer (GstBufferPool *pool, GstBuffer **buffer, + GstBufferPoolParams *params); +void gst_buffer_pool_release_buffer (GstBufferPool *pool, GstBuffer *buffer); + +G_END_DECLS + +#endif /* __GST_BUFFER_POOL_H__ */ diff --git a/gst/gstbus.c b/gst/gstbus.c new file mode 100644 index 0000000..e3a15f7 --- /dev/null +++ b/gst/gstbus.c @@ -0,0 +1,1310 @@ +/* GStreamer + * Copyright (C) 2004 Wim Taymans <wim@fluendo.com> + * + * gstbus.c: GstBus subsystem + * + * 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:gstbus + * @short_description: Asynchronous message bus subsystem + * @see_also: #GstMessage, #GstElement + * + * The #GstBus is an object responsible for delivering #GstMessage packets in + * a first-in first-out way from the streaming threads (see #GstTask) to the + * application. + * + * Since the application typically only wants to deal with delivery of these + * messages from one thread, the GstBus will marshall the messages between + * different threads. This is important since the actual streaming of media + * is done in another thread than the application. + * + * The GstBus provides support for #GSource based notifications. This makes it + * possible to handle the delivery in the glib mainloop. + * + * The #GSource callback function gst_bus_async_signal_func() can be used to + * convert all bus messages into signal emissions. + * + * A message is posted on the bus with the gst_bus_post() method. With the + * gst_bus_peek() and gst_bus_pop() methods one can look at or retrieve a + * previously posted message. + * + * The bus can be polled with the gst_bus_poll() method. This methods blocks + * up to the specified timeout value until one of the specified messages types + * is posted on the bus. The application can then gst_bus_pop() the messages + * from the bus to handle them. + * Alternatively the application can register an asynchronous bus function + * using gst_bus_add_watch_full() or gst_bus_add_watch(). This function will + * install a #GSource in the default glib main loop and will deliver messages + * a short while after they have been posted. Note that the main loop should + * be running for the asynchronous callbacks. + * + * It is also possible to get messages from the bus without any thread + * marshalling with the gst_bus_set_sync_handler() method. This makes it + * possible to react to a message in the same thread that posted the + * message on the bus. This should only be used if the application is able + * to deal with messages from different threads. + * + * Every #GstPipeline has one bus. + * + * Note that a #GstPipeline will set its bus into flushing state when changing + * from READY to NULL state. + * + * Last reviewed on 2006-03-12 (0.10.5) + */ + +#include "gst_private.h" +#include <errno.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include <sys/types.h> + +#include "gstinfo.h" +#include "gstpoll.h" + +#include "gstbus.h" + +#define GST_CAT_DEFAULT GST_CAT_BUS +/* bus signals */ +enum +{ + SYNC_MESSAGE, + ASYNC_MESSAGE, + /* add more above */ + LAST_SIGNAL +}; + +#define DEFAULT_ENABLE_ASYNC (TRUE) + +enum +{ + PROP_0, + PROP_ENABLE_ASYNC +}; + +static void gst_bus_dispose (GObject * object); + +static GstObjectClass *parent_class = NULL; +static guint gst_bus_signals[LAST_SIGNAL] = { 0 }; + +struct _GstBusPrivate +{ + guint num_sync_message_emitters; + GSource *watch_id; + + gboolean enable_async; + GstPoll *poll; + GPollFD pollfd; +}; + +G_DEFINE_TYPE (GstBus, gst_bus, GST_TYPE_OBJECT); + +static void +gst_bus_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstBus *bus = GST_BUS_CAST (object); + + switch (prop_id) { + case PROP_ENABLE_ASYNC: + bus->priv->enable_async = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_bus_constructed (GObject * object) +{ + GstBus *bus = GST_BUS_CAST (object); + + if (bus->priv->enable_async) { + bus->priv->poll = gst_poll_new_timer (); + gst_poll_get_read_gpollfd (bus->priv->poll, &bus->priv->pollfd); + } +} + +static void +gst_bus_class_init (GstBusClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->dispose = gst_bus_dispose; + gobject_class->set_property = gst_bus_set_property; + gobject_class->constructed = gst_bus_constructed; + + /* GstBus:enable-async: + * + * Enable async message delivery support for bus watches, + * gst_bus_pop() and similar API. Without this only the + * synchronous message handlers are called. + * + * This property is used to create the child element buses + * in #GstBin. + * + * Since: 0.10.33 + */ + g_object_class_install_property (gobject_class, PROP_ENABLE_ASYNC, + g_param_spec_boolean ("enable-async", "Enable Async", + "Enable async message delivery for bus watches and gst_bus_pop()", + DEFAULT_ENABLE_ASYNC, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstBus::sync-message: + * @bus: the object which received the signal + * @message: the message that has been posted synchronously + * + * A message has been posted on the bus. This signal is emitted from the + * thread that posted the message so one has to be careful with locking. + * + * This signal will not be emitted by default, you have to set up + * gst_bus_sync_signal_handler() as a sync handler if you want this + * signal to be emitted when a message is posted on the bus, like this: + * <programlisting> + * gst_bus_set_sync_handler (bus, gst_bus_sync_signal_handler, yourdata); + * </programlisting> + */ + gst_bus_signals[SYNC_MESSAGE] = + g_signal_new ("sync-message", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + G_STRUCT_OFFSET (GstBusClass, sync_message), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, GST_TYPE_MESSAGE); + + /** + * GstBus::message: + * @bus: the object which received the signal + * @message: the message that has been posted asynchronously + * + * A message has been posted on the bus. This signal is emitted from a + * GSource added to the mainloop. this signal will only be emitted when + * there is a mainloop running. + */ + gst_bus_signals[ASYNC_MESSAGE] = + g_signal_new ("message", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + G_STRUCT_OFFSET (GstBusClass, message), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, GST_TYPE_MESSAGE); + + g_type_class_add_private (klass, sizeof (GstBusPrivate)); +} + +static void +gst_bus_init (GstBus * bus) +{ + bus->queue = gst_atomic_queue_new (32); + bus->queue_lock = g_mutex_new (); + + bus->priv = G_TYPE_INSTANCE_GET_PRIVATE (bus, GST_TYPE_BUS, GstBusPrivate); + bus->priv->enable_async = DEFAULT_ENABLE_ASYNC; + + GST_DEBUG_OBJECT (bus, "created"); +} + +static void +gst_bus_dispose (GObject * object) +{ + GstBus *bus = GST_BUS (object); + + if (bus->queue) { + GstMessage *message; + + g_mutex_lock (bus->queue_lock); + do { + message = gst_atomic_queue_pop (bus->queue); + if (message) + gst_message_unref (message); + } while (message != NULL); + gst_atomic_queue_unref (bus->queue); + bus->queue = NULL; + g_mutex_unlock (bus->queue_lock); + g_mutex_free (bus->queue_lock); + bus->queue_lock = NULL; + + if (bus->priv->poll) + gst_poll_free (bus->priv->poll); + bus->priv->poll = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/** + * gst_bus_new: + * + * Creates a new #GstBus instance. + * + * Returns: (transfer full): a new #GstBus instance + */ +GstBus * +gst_bus_new (void) +{ + GstBus *result; + + result = g_object_newv (gst_bus_get_type (), 0, NULL); + GST_DEBUG_OBJECT (result, "created new bus"); + + return result; +} + +/** + * gst_bus_post: + * @bus: a #GstBus to post on + * @message: (transfer full): the #GstMessage to post + * + * Post a message on the given bus. Ownership of the message + * is taken by the bus. + * + * Returns: TRUE if the message could be posted, FALSE if the bus is flushing. + * + * MT safe. + */ +gboolean +gst_bus_post (GstBus * bus, GstMessage * message) +{ + GstBusSyncReply reply = GST_BUS_PASS; + GstBusSyncHandler handler; + gboolean emit_sync_message; + gpointer handler_data; + + g_return_val_if_fail (GST_IS_BUS (bus), FALSE); + g_return_val_if_fail (GST_IS_MESSAGE (message), FALSE); + + GST_DEBUG_OBJECT (bus, "[msg %p] posting on bus %" GST_PTR_FORMAT, message, + message); + + GST_OBJECT_LOCK (bus); + /* check if the bus is flushing */ + if (GST_OBJECT_FLAG_IS_SET (bus, GST_BUS_FLUSHING)) + goto is_flushing; + + handler = bus->sync_handler; + handler_data = bus->sync_handler_data; + emit_sync_message = bus->priv->num_sync_message_emitters > 0; + GST_OBJECT_UNLOCK (bus); + + /* first call the sync handler if it is installed */ + if (handler) + reply = handler (bus, message, handler_data); + + /* emit sync-message if requested to do so via + gst_bus_enable_sync_message_emission. terrible but effective */ + if (emit_sync_message && reply != GST_BUS_DROP + && handler != gst_bus_sync_signal_handler) + gst_bus_sync_signal_handler (bus, message, NULL); + + /* If this is a bus without async message delivery + * always drop the message */ + if (!bus->priv->poll) + reply = GST_BUS_DROP; + + /* now see what we should do with the message */ + switch (reply) { + case GST_BUS_DROP: + /* drop the message */ + GST_DEBUG_OBJECT (bus, "[msg %p] dropped", message); + break; + case GST_BUS_PASS: + /* pass the message to the async queue, refcount passed in the queue */ + GST_DEBUG_OBJECT (bus, "[msg %p] pushing on async queue", message); + gst_atomic_queue_push (bus->queue, message); + gst_poll_write_control (bus->priv->poll); + GST_DEBUG_OBJECT (bus, "[msg %p] pushed on async queue", message); + + break; + case GST_BUS_ASYNC: + { + /* async delivery, we need a mutex and a cond to block + * on */ + GMutex *lock = g_mutex_new (); + GCond *cond = g_cond_new (); + + GST_MESSAGE_COND (message) = cond; + GST_MESSAGE_GET_LOCK (message) = lock; + + GST_DEBUG_OBJECT (bus, "[msg %p] waiting for async delivery", message); + + /* now we lock the message mutex, send the message to the async + * queue. When the message is handled by the app and destroyed, + * the cond will be signalled and we can continue */ + g_mutex_lock (lock); + + gst_atomic_queue_push (bus->queue, message); + gst_poll_write_control (bus->priv->poll); + + /* now block till the message is freed */ + g_cond_wait (cond, lock); + g_mutex_unlock (lock); + + GST_DEBUG_OBJECT (bus, "[msg %p] delivered asynchronously", message); + + g_mutex_free (lock); + g_cond_free (cond); + break; + } + default: + g_warning ("invalid return from bus sync handler"); + break; + } + return TRUE; + + /* ERRORS */ +is_flushing: + { + GST_DEBUG_OBJECT (bus, "bus is flushing"); + gst_message_unref (message); + GST_OBJECT_UNLOCK (bus); + + return FALSE; + } +} + +/** + * gst_bus_have_pending: + * @bus: a #GstBus to check + * + * Check if there are pending messages on the bus that + * should be handled. + * + * Returns: TRUE if there are messages on the bus to be handled, FALSE + * otherwise. + * + * MT safe. + */ +gboolean +gst_bus_have_pending (GstBus * bus) +{ + gboolean result; + + g_return_val_if_fail (GST_IS_BUS (bus), FALSE); + + /* see if there is a message on the bus */ + result = gst_atomic_queue_length (bus->queue) != 0; + + return result; +} + +/** + * gst_bus_set_flushing: + * @bus: a #GstBus + * @flushing: whether or not to flush the bus + * + * If @flushing, flush out and unref any messages queued in the bus. Releases + * references to the message origin objects. Will flush future messages until + * gst_bus_set_flushing() sets @flushing to #FALSE. + * + * MT safe. + */ +void +gst_bus_set_flushing (GstBus * bus, gboolean flushing) +{ + GstMessage *message; + + GST_OBJECT_LOCK (bus); + + if (flushing) { + GST_OBJECT_FLAG_SET (bus, GST_BUS_FLUSHING); + + GST_DEBUG_OBJECT (bus, "set bus flushing"); + + while ((message = gst_bus_pop (bus))) + gst_message_unref (message); + } else { + GST_DEBUG_OBJECT (bus, "unset bus flushing"); + GST_OBJECT_FLAG_UNSET (bus, GST_BUS_FLUSHING); + } + + GST_OBJECT_UNLOCK (bus); +} + +/** + * gst_bus_timed_pop_filtered: + * @bus: a #GstBus to pop from + * @timeout: a timeout in nanoseconds, or GST_CLOCK_TIME_NONE to wait forever + * @types: message types to take into account, GST_MESSAGE_ANY for any type + * + * Get a message from the bus whose type matches the message type mask @types, + * waiting up to the specified timeout (and discarding any messages that do not + * match the mask provided). + * + * If @timeout is 0, this function behaves like gst_bus_pop_filtered(). If + * @timeout is #GST_CLOCK_TIME_NONE, this function will block forever until a + * matching message was posted on the bus. + * + * Returns: (transfer full): a #GstMessage matching the filter in @types, + * or NULL if no matching message was found on the bus until the timeout + * expired. The message is taken from the bus and needs to be unreffed + * with gst_message_unref() after usage. + * + * MT safe. + * + * Since: 0.10.15 + */ +GstMessage * +gst_bus_timed_pop_filtered (GstBus * bus, GstClockTime timeout, + GstMessageType types) +{ + GstMessage *message; + GTimeVal now, then; + gboolean first_round = TRUE; + GstClockTime elapsed = 0; + + g_return_val_if_fail (GST_IS_BUS (bus), NULL); + g_return_val_if_fail (types != 0, NULL); + g_return_val_if_fail (timeout == 0 || bus->priv->poll != NULL, NULL); + + g_mutex_lock (bus->queue_lock); + + while (TRUE) { + gint ret; + + GST_LOG_OBJECT (bus, "have %d messages", + gst_atomic_queue_length (bus->queue)); + + while ((message = gst_atomic_queue_pop (bus->queue))) { + if (bus->priv->poll) + gst_poll_read_control (bus->priv->poll); + GST_DEBUG_OBJECT (bus, "got message %p, %s, type mask is %u", + message, GST_MESSAGE_TYPE_NAME (message), (guint) types); + if ((GST_MESSAGE_TYPE (message) & types) != 0) { + /* exit the loop, we have a message */ + goto beach; + } else { + GST_DEBUG_OBJECT (bus, "discarding message, does not match mask"); + gst_message_unref (message); + message = NULL; + } + } + + /* no need to wait, exit loop */ + if (timeout == 0) + break; + + else if (timeout != GST_CLOCK_TIME_NONE) { + if (first_round) { + g_get_current_time (&then); + first_round = FALSE; + } else { + g_get_current_time (&now); + + elapsed = GST_TIMEVAL_TO_TIME (now) - GST_TIMEVAL_TO_TIME (then); + + if (elapsed > timeout) + break; + } + } + + /* only here in timeout case */ + g_assert (bus->priv->poll); + g_mutex_unlock (bus->queue_lock); + ret = gst_poll_wait (bus->priv->poll, timeout - elapsed); + g_mutex_lock (bus->queue_lock); + + if (ret == 0) { + GST_INFO_OBJECT (bus, "timed out, breaking loop"); + break; + } else { + GST_INFO_OBJECT (bus, "we got woken up, recheck for message"); + } + } + +beach: + + g_mutex_unlock (bus->queue_lock); + + return message; +} + + +/** + * gst_bus_timed_pop: + * @bus: a #GstBus to pop + * @timeout: a timeout + * + * Get a message from the bus, waiting up to the specified timeout. + * + * If @timeout is 0, this function behaves like gst_bus_pop(). If @timeout is + * #GST_CLOCK_TIME_NONE, this function will block forever until a message was + * posted on the bus. + * + * Returns: (transfer full): the #GstMessage that is on the bus after the + * specified timeout or NULL if the bus is empty after the timeout expired. + * The message is taken from the bus and needs to be unreffed with + * gst_message_unref() after usage. + * + * MT safe. + * + * Since: 0.10.12 + */ +GstMessage * +gst_bus_timed_pop (GstBus * bus, GstClockTime timeout) +{ + g_return_val_if_fail (GST_IS_BUS (bus), NULL); + + return gst_bus_timed_pop_filtered (bus, timeout, GST_MESSAGE_ANY); +} + +/** + * gst_bus_pop_filtered: + * @bus: a #GstBus to pop + * @types: message types to take into account + * + * Get a message matching @type from the bus. Will discard all messages on + * the bus that do not match @type and that have been posted before the first + * message that does match @type. If there is no message matching @type on + * the bus, all messages will be discarded. + * + * Returns: (transfer full): the next #GstMessage matching @type that is on + * the bus, or NULL if the bus is empty or there is no message matching + * @type. The message is taken from the bus and needs to be unreffed with + * gst_message_unref() after usage. + * + * MT safe. + * + * Since: 0.10.15 + */ +GstMessage * +gst_bus_pop_filtered (GstBus * bus, GstMessageType types) +{ + g_return_val_if_fail (GST_IS_BUS (bus), NULL); + g_return_val_if_fail (types != 0, NULL); + + return gst_bus_timed_pop_filtered (bus, 0, types); +} + +/** + * gst_bus_pop: + * @bus: a #GstBus to pop + * + * Get a message from the bus. + * + * Returns: (transfer full): the #GstMessage that is on the bus, or NULL if the + * bus is empty. The message is taken from the bus and needs to be unreffed + * with gst_message_unref() after usage. + * + * MT safe. + */ +GstMessage * +gst_bus_pop (GstBus * bus) +{ + g_return_val_if_fail (GST_IS_BUS (bus), NULL); + + return gst_bus_timed_pop_filtered (bus, 0, GST_MESSAGE_ANY); +} + +/** + * gst_bus_peek: + * @bus: a #GstBus + * + * Peek the message on the top of the bus' queue. The message will remain + * on the bus' message queue. A reference is returned, and needs to be unreffed + * by the caller. + * + * Returns: (transfer full): the #GstMessage that is on the bus, or NULL if the + * bus is empty. + * + * MT safe. + */ +GstMessage * +gst_bus_peek (GstBus * bus) +{ + GstMessage *message; + + g_return_val_if_fail (GST_IS_BUS (bus), NULL); + + g_mutex_lock (bus->queue_lock); + message = gst_atomic_queue_peek (bus->queue); + if (message) + gst_message_ref (message); + g_mutex_unlock (bus->queue_lock); + + GST_DEBUG_OBJECT (bus, "peek on bus, got message %p", message); + + return message; +} + +/** + * gst_bus_set_sync_handler: + * @bus: a #GstBus to install the handler on + * @func: The handler function to install + * @data: User data that will be sent to the handler function. + * + * Sets the synchronous handler on the bus. The function will be called + * every time a new message is posted on the bus. Note that the function + * will be called in the same thread context as the posting object. This + * function is usually only called by the creator of the bus. Applications + * should handle messages asynchronously using the gst_bus watch and poll + * functions. + * + * You cannot replace an existing sync_handler. You can pass NULL to this + * function, which will clear the existing handler. + */ +void +gst_bus_set_sync_handler (GstBus * bus, GstBusSyncHandler func, gpointer data) +{ + g_return_if_fail (GST_IS_BUS (bus)); + + GST_OBJECT_LOCK (bus); + + /* Assert if the user attempts to replace an existing sync_handler, + * other than to clear it */ + if (func != NULL && bus->sync_handler != NULL) + goto no_replace; + + bus->sync_handler = func; + bus->sync_handler_data = data; + GST_OBJECT_UNLOCK (bus); + + return; + +no_replace: + { + GST_OBJECT_UNLOCK (bus); + g_warning ("cannot replace existing sync handler"); + return; + } +} + +/* GSource for the bus + */ +typedef struct +{ + GSource source; + GstBus *bus; +} GstBusSource; + +static gboolean +gst_bus_source_prepare (GSource * source, gint * timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean +gst_bus_source_check (GSource * source) +{ + GstBusSource *bsrc = (GstBusSource *) source; + + return bsrc->bus->priv->pollfd.revents & (G_IO_IN | G_IO_HUP | G_IO_ERR); +} + +static gboolean +gst_bus_source_dispatch (GSource * source, GSourceFunc callback, + gpointer user_data) +{ + GstBusFunc handler = (GstBusFunc) callback; + GstBusSource *bsource = (GstBusSource *) source; + GstMessage *message; + gboolean keep; + GstBus *bus; + + g_return_val_if_fail (bsource != NULL, FALSE); + + bus = bsource->bus; + + g_return_val_if_fail (GST_IS_BUS (bus), FALSE); + + message = gst_bus_pop (bus); + + /* The message queue might be empty if some other thread or callback set + * the bus to flushing between check/prepare and dispatch */ + if (G_UNLIKELY (message == NULL)) + return TRUE; + + if (!handler) + goto no_handler; + + GST_DEBUG_OBJECT (bus, "source %p calling dispatch with %p", source, message); + + keep = handler (bus, message, user_data); + gst_message_unref (message); + + GST_DEBUG_OBJECT (bus, "source %p handler returns %d", source, keep); + + return keep; + +no_handler: + { + g_warning ("GstBus watch dispatched without callback\n" + "You must call g_source_set_callback()."); + gst_message_unref (message); + return FALSE; + } +} + +static void +gst_bus_source_finalize (GSource * source) +{ + GstBusSource *bsource = (GstBusSource *) source; + GstBus *bus; + + bus = bsource->bus; + + GST_DEBUG_OBJECT (bus, "finalize source %p", source); + + GST_OBJECT_LOCK (bus); + if (bus->priv->watch_id == source) + bus->priv->watch_id = NULL; + GST_OBJECT_UNLOCK (bus); + + gst_object_unref (bsource->bus); + bsource->bus = NULL; +} + +static GSourceFuncs gst_bus_source_funcs = { + gst_bus_source_prepare, + gst_bus_source_check, + gst_bus_source_dispatch, + gst_bus_source_finalize +}; + +/** + * gst_bus_create_watch: + * @bus: a #GstBus to create the watch for + * + * Create watch for this bus. The GSource will be dispatched whenever + * a message is on the bus. After the GSource is dispatched, the + * message is popped off the bus and unreffed. + * + * Returns: (transfer full): a #GSource that can be added to a mainloop. + */ +GSource * +gst_bus_create_watch (GstBus * bus) +{ + GstBusSource *source; + + g_return_val_if_fail (GST_IS_BUS (bus), NULL); + g_return_val_if_fail (bus->priv->poll != NULL, NULL); + + source = (GstBusSource *) g_source_new (&gst_bus_source_funcs, + sizeof (GstBusSource)); + source->bus = gst_object_ref (bus); + g_source_add_poll ((GSource *) source, &bus->priv->pollfd); + + return (GSource *) source; +} + +/* must be called with the bus OBJECT LOCK */ +static guint +gst_bus_add_watch_full_unlocked (GstBus * bus, gint priority, + GstBusFunc func, gpointer user_data, GDestroyNotify notify) +{ + GMainContext *ctx; + guint id; + GSource *source; + + if (bus->priv->watch_id) { + GST_ERROR_OBJECT (bus, + "Tried to add new watch while one was already there"); + return 0; + } + + source = gst_bus_create_watch (bus); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); + + g_source_set_callback (source, (GSourceFunc) func, user_data, notify); + + ctx = g_main_context_get_thread_default (); + id = g_source_attach (source, ctx); + g_source_unref (source); + + if (id) { + bus->priv->watch_id = source; + } + + GST_DEBUG_OBJECT (bus, "New source %p with id %u", source, id); + return id; +} + +/** + * gst_bus_add_watch_full: + * @bus: a #GstBus to create the watch for. + * @priority: The priority of the watch. + * @func: A function to call when a message is received. + * @user_data: user data passed to @func. + * @notify: the function to call when the source is removed. + * + * Adds a bus watch to the default main context with the given @priority (e.g. + * %G_PRIORITY_DEFAULT). Since 0.10.33 it is also possible to use a non-default + * main context set up using g_main_context_push_thread_default() (before + * one had to create a bus watch source and attach it to the desired main + * context 'manually'). + * + * This function is used to receive asynchronous messages in the main loop. + * There can only be a single bus watch per bus, you must remove it before you + * can set a new one. + * + * When @func is called, the message belongs to the caller; if you want to + * keep a copy of it, call gst_message_ref() before leaving @func. + * + * The watch can be removed using g_source_remove() or by returning FALSE + * from @func. + * + * Returns: The event source id. + * + * MT safe. + */ +guint +gst_bus_add_watch_full (GstBus * bus, gint priority, + GstBusFunc func, gpointer user_data, GDestroyNotify notify) +{ + guint id; + + g_return_val_if_fail (GST_IS_BUS (bus), 0); + + GST_OBJECT_LOCK (bus); + id = gst_bus_add_watch_full_unlocked (bus, priority, func, user_data, notify); + GST_OBJECT_UNLOCK (bus); + + return id; +} + +/** + * gst_bus_add_watch: + * @bus: a #GstBus to create the watch for + * @func: A function to call when a message is received. + * @user_data: user data passed to @func. + * + * Adds a bus watch to the default main context with the default priority + * (%G_PRIORITY_DEFAULT). Since 0.10.33 it is also possible to use a non-default + * main context set up using g_main_context_push_thread_default() (before + * one had to create a bus watch source and attach it to the desired main + * context 'manually'). + * + * This function is used to receive asynchronous messages in the main loop. + * There can only be a single bus watch per bus, you must remove it before you + * can set a new one. + * + * The watch can be removed using g_source_remove() or by returning FALSE + * from @func. + * + * Returns: The event source id. + * + * MT safe. + */ +guint +gst_bus_add_watch (GstBus * bus, GstBusFunc func, gpointer user_data) +{ + return gst_bus_add_watch_full (bus, G_PRIORITY_DEFAULT, func, + user_data, NULL); +} + +typedef struct +{ + GMainLoop *loop; + guint timeout_id; + gboolean source_running; + GstMessageType events; + GstMessage *message; +} GstBusPollData; + +static void +poll_func (GstBus * bus, GstMessage * message, GstBusPollData * poll_data) +{ + GstMessageType type; + + if (!g_main_loop_is_running (poll_data->loop)) { + GST_DEBUG ("mainloop %p not running", poll_data->loop); + return; + } + + type = GST_MESSAGE_TYPE (message); + + if (type & poll_data->events) { + g_assert (poll_data->message == NULL); + /* keep ref to message */ + poll_data->message = gst_message_ref (message); + GST_DEBUG ("mainloop %p quit", poll_data->loop); + g_main_loop_quit (poll_data->loop); + } else { + GST_DEBUG ("type %08x does not match %08x", type, poll_data->events); + } +} + +static gboolean +poll_timeout (GstBusPollData * poll_data) +{ + GST_DEBUG ("mainloop %p quit", poll_data->loop); + g_main_loop_quit (poll_data->loop); + + /* we don't remove the GSource as this would free our poll_data, + * which we still need */ + return TRUE; +} + +static void +poll_destroy (GstBusPollData * poll_data, gpointer unused) +{ + poll_data->source_running = FALSE; + if (!poll_data->timeout_id) { + g_main_loop_unref (poll_data->loop); + g_slice_free (GstBusPollData, poll_data); + } +} + +static void +poll_destroy_timeout (GstBusPollData * poll_data) +{ + poll_data->timeout_id = 0; + if (!poll_data->source_running) { + g_main_loop_unref (poll_data->loop); + g_slice_free (GstBusPollData, poll_data); + } +} + +/** + * gst_bus_poll: + * @bus: a #GstBus + * @events: a mask of #GstMessageType, representing the set of message types to + * poll for. + * @timeout: the poll timeout, as a #GstClockTimeDiff, or -1 to poll + * indefinitely. + * + * Poll the bus for messages. Will block while waiting for messages to come. + * You can specify a maximum time to poll with the @timeout parameter. If + * @timeout is negative, this function will block indefinitely. + * + * All messages not in @events will be popped off the bus and will be ignored. + * + * Because poll is implemented using the "message" signal enabled by + * gst_bus_add_signal_watch(), calling gst_bus_poll() will cause the "message" + * signal to be emitted for every message that poll sees. Thus a "message" + * signal handler will see the same messages that this function sees -- neither + * will steal messages from the other. + * + * This function will run a main loop from the default main context when + * polling. + * + * You should never use this function, since it is pure evil. This is + * especially true for GUI applications based on Gtk+ or Qt, but also for any + * other non-trivial application that uses the GLib main loop. As this function + * runs a GLib main loop, any callback attached to the default GLib main + * context may be invoked. This could be timeouts, GUI events, I/O events etc.; + * even if gst_bus_poll() is called with a 0 timeout. Any of these callbacks + * may do things you do not expect, e.g. destroy the main application window or + * some other resource; change other application state; display a dialog and + * run another main loop until the user clicks it away. In short, using this + * function may add a lot of complexity to your code through unexpected + * re-entrancy and unexpected changes to your application's state. + * + * For 0 timeouts use gst_bus_pop_filtered() instead of this function; for + * other short timeouts use gst_bus_timed_pop_filtered(); everything else is + * better handled by setting up an asynchronous bus watch and doing things + * from there. + * + * Returns: (transfer full): the message that was received, or NULL if the + * poll timed out. The message is taken from the bus and needs to be + * unreffed with gst_message_unref() after usage. + */ +GstMessage * +gst_bus_poll (GstBus * bus, GstMessageType events, GstClockTimeDiff timeout) +{ + GstBusPollData *poll_data; + GstMessage *ret; + gulong id; + + poll_data = g_slice_new (GstBusPollData); + poll_data->source_running = TRUE; + poll_data->loop = g_main_loop_new (NULL, FALSE); + poll_data->events = events; + poll_data->message = NULL; + + if (timeout >= 0) + poll_data->timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, + timeout / GST_MSECOND, (GSourceFunc) poll_timeout, poll_data, + (GDestroyNotify) poll_destroy_timeout); + else + poll_data->timeout_id = 0; + + id = g_signal_connect_data (bus, "message", G_CALLBACK (poll_func), poll_data, + (GClosureNotify) poll_destroy, 0); + + /* these can be nested, so it's ok */ + gst_bus_add_signal_watch (bus); + + GST_DEBUG ("running mainloop %p", poll_data->loop); + g_main_loop_run (poll_data->loop); + GST_DEBUG ("mainloop stopped %p", poll_data->loop); + + gst_bus_remove_signal_watch (bus); + + /* holds a ref */ + ret = poll_data->message; + + if (poll_data->timeout_id) + g_source_remove (poll_data->timeout_id); + + /* poll_data will be freed now */ + g_signal_handler_disconnect (bus, id); + + GST_DEBUG_OBJECT (bus, "finished poll with message %p", ret); + + return ret; +} + +/** + * gst_bus_async_signal_func: + * @bus: a #GstBus + * @message: the #GstMessage received + * @data: user data + * + * A helper #GstBusFunc that can be used to convert all asynchronous messages + * into signals. + * + * Returns: TRUE + */ +gboolean +gst_bus_async_signal_func (GstBus * bus, GstMessage * message, gpointer data) +{ + GQuark detail = 0; + + g_return_val_if_fail (GST_IS_BUS (bus), TRUE); + g_return_val_if_fail (message != NULL, TRUE); + + detail = gst_message_type_to_quark (GST_MESSAGE_TYPE (message)); + + g_signal_emit (bus, gst_bus_signals[ASYNC_MESSAGE], detail, message); + + /* we never remove this source based on signal emission return values */ + return TRUE; +} + +/** + * gst_bus_sync_signal_handler: + * @bus: a #GstBus + * @message: the #GstMessage received + * @data: user data + * + * A helper GstBusSyncHandler that can be used to convert all synchronous + * messages into signals. + * + * Returns: GST_BUS_PASS + */ +GstBusSyncReply +gst_bus_sync_signal_handler (GstBus * bus, GstMessage * message, gpointer data) +{ + GQuark detail = 0; + + g_return_val_if_fail (GST_IS_BUS (bus), GST_BUS_DROP); + g_return_val_if_fail (message != NULL, GST_BUS_DROP); + + detail = gst_message_type_to_quark (GST_MESSAGE_TYPE (message)); + + g_signal_emit (bus, gst_bus_signals[SYNC_MESSAGE], detail, message); + + return GST_BUS_PASS; +} + +/** + * gst_bus_enable_sync_message_emission: + * @bus: a #GstBus on which you want to receive the "sync-message" signal + * + * Instructs GStreamer to emit the "sync-message" signal after running the bus's + * sync handler. This function is here so that code can ensure that they can + * synchronously receive messages without having to affect what the bin's sync + * handler is. + * + * This function may be called multiple times. To clean up, the caller is + * responsible for calling gst_bus_disable_sync_message_emission() as many times + * as this function is called. + * + * While this function looks similar to gst_bus_add_signal_watch(), it is not + * exactly the same -- this function enables <emphasis>synchronous</emphasis> emission of + * signals when messages arrive; gst_bus_add_signal_watch() adds an idle callback + * to pop messages off the bus <emphasis>asynchronously</emphasis>. The sync-message signal + * comes from the thread of whatever object posted the message; the "message" + * signal is marshalled to the main thread via the main loop. + * + * MT safe. + */ +void +gst_bus_enable_sync_message_emission (GstBus * bus) +{ + g_return_if_fail (GST_IS_BUS (bus)); + + GST_OBJECT_LOCK (bus); + bus->priv->num_sync_message_emitters++; + GST_OBJECT_UNLOCK (bus); +} + +/** + * gst_bus_disable_sync_message_emission: + * @bus: a #GstBus on which you previously called + * gst_bus_enable_sync_message_emission() + * + * Instructs GStreamer to stop emitting the "sync-message" signal for this bus. + * See gst_bus_enable_sync_message_emission() for more information. + * + * In the event that multiple pieces of code have called + * gst_bus_enable_sync_message_emission(), the sync-message emissions will only + * be stopped after all calls to gst_bus_enable_sync_message_emission() were + * "cancelled" by calling this function. In this way the semantics are exactly + * the same as gst_object_ref() that which calls enable should also call + * disable. + * + * MT safe. + */ +void +gst_bus_disable_sync_message_emission (GstBus * bus) +{ + g_return_if_fail (GST_IS_BUS (bus)); + g_return_if_fail (bus->num_signal_watchers == 0); + + GST_OBJECT_LOCK (bus); + bus->priv->num_sync_message_emitters--; + GST_OBJECT_UNLOCK (bus); +} + +/** + * gst_bus_add_signal_watch_full: + * @bus: a #GstBus on which you want to receive the "message" signal + * @priority: The priority of the watch. + * + * Adds a bus signal watch to the default main context with the given @priority + * (e.g. %G_PRIORITY_DEFAULT). Since 0.10.33 it is also possible to use a + * non-default main context set up using g_main_context_push_thread_default() + * (before one had to create a bus watch source and attach it to the desired + * main context 'manually'). + * + * After calling this statement, the bus will emit the "message" signal for each + * message posted on the bus when the main loop is running. + * + * This function may be called multiple times. To clean up, the caller is + * responsible for calling gst_bus_remove_signal_watch() as many times as this + * function is called. + * + * There can only be a single bus watch per bus, you most remove all signal watch + * before you can set another type of watch. + * + * MT safe. + */ +void +gst_bus_add_signal_watch_full (GstBus * bus, gint priority) +{ + g_return_if_fail (GST_IS_BUS (bus)); + + /* I know the callees don't take this lock, so go ahead and abuse it */ + GST_OBJECT_LOCK (bus); + + if (bus->num_signal_watchers > 0) + goto done; + + /* this should not fail because the counter above takes care of it */ + g_assert (bus->signal_watch_id == 0); + + bus->signal_watch_id = + gst_bus_add_watch_full_unlocked (bus, priority, gst_bus_async_signal_func, + NULL, NULL); + + if (G_UNLIKELY (bus->signal_watch_id == 0)) + goto add_failed; + +done: + + bus->num_signal_watchers++; + + GST_OBJECT_UNLOCK (bus); + return; + + /* ERRORS */ +add_failed: + { + g_critical ("Could not add signal watch to bus %s", GST_OBJECT_NAME (bus)); + GST_OBJECT_UNLOCK (bus); + return; + } +} + +/** + * gst_bus_add_signal_watch: + * @bus: a #GstBus on which you want to receive the "message" signal + * + * Adds a bus signal watch to the default main context with the default priority + * (%G_PRIORITY_DEFAULT). Since 0.10.33 it is also possible to use a non-default + * main context set up using g_main_context_push_thread_default() (before + * one had to create a bus watch source and attach it to the desired main + * context 'manually'). + * + * After calling this statement, the bus will emit the "message" signal for each + * message posted on the bus. + * + * This function may be called multiple times. To clean up, the caller is + * responsible for calling gst_bus_remove_signal_watch() as many times as this + * function is called. + * + * MT safe. + */ +void +gst_bus_add_signal_watch (GstBus * bus) +{ + gst_bus_add_signal_watch_full (bus, G_PRIORITY_DEFAULT); +} + +/** + * gst_bus_remove_signal_watch: + * @bus: a #GstBus you previously added a signal watch to + * + * Removes a signal watch previously added with gst_bus_add_signal_watch(). + * + * MT safe. + */ +void +gst_bus_remove_signal_watch (GstBus * bus) +{ + guint id = 0; + + g_return_if_fail (GST_IS_BUS (bus)); + + /* I know the callees don't take this lock, so go ahead and abuse it */ + GST_OBJECT_LOCK (bus); + + if (bus->num_signal_watchers == 0) + goto error; + + bus->num_signal_watchers--; + + if (bus->num_signal_watchers > 0) + goto done; + + id = bus->signal_watch_id; + bus->signal_watch_id = 0; + + GST_DEBUG_OBJECT (bus, "removing signal watch %u", id); + +done: + GST_OBJECT_UNLOCK (bus); + + if (id) + g_source_remove (id); + + return; + + /* ERRORS */ +error: + { + g_critical ("Bus %s has no signal watches attached", GST_OBJECT_NAME (bus)); + GST_OBJECT_UNLOCK (bus); + return; + } +} diff --git a/gst/gstbus.h b/gst/gstbus.h new file mode 100644 index 0000000..732591f --- /dev/null +++ b/gst/gstbus.h @@ -0,0 +1,193 @@ +/* GStreamer + * Copyright (C) 2004 Wim Taymans <wim@fluendo.com> + * + * gstbus.h: Header for GstBus subsystem + * + * 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_BUS_H__ +#define __GST_BUS_H__ + +typedef struct _GstBus GstBus; +typedef struct _GstBusPrivate GstBusPrivate; +typedef struct _GstBusClass GstBusClass; + +#include <gst/gstmessage.h> +#include <gst/gstclock.h> +#include <gst/gstatomicqueue.h> + +G_BEGIN_DECLS + +/* --- standard type macros --- */ +#define GST_TYPE_BUS (gst_bus_get_type ()) +#define GST_BUS(bus) (G_TYPE_CHECK_INSTANCE_CAST ((bus), GST_TYPE_BUS, GstBus)) +#define GST_IS_BUS(bus) (G_TYPE_CHECK_INSTANCE_TYPE ((bus), GST_TYPE_BUS)) +#define GST_BUS_CLASS(bclass) (G_TYPE_CHECK_CLASS_CAST ((bclass), GST_TYPE_BUS, GstBusClass)) +#define GST_IS_BUS_CLASS(bclass) (G_TYPE_CHECK_CLASS_TYPE ((bclass), GST_TYPE_BUS)) +#define GST_BUS_GET_CLASS(bus) (G_TYPE_INSTANCE_GET_CLASS ((bus), GST_TYPE_BUS, GstBusClass)) +#define GST_BUS_CAST(bus) ((GstBus*)(bus)) + +/** + * GstBusFlags: + * @GST_BUS_FLUSHING: The bus is currently dropping all messages + * @GST_BUS_FLAG_LAST: offset to define more flags + * + * The standard flags that a bus may have. + */ +typedef enum { + GST_BUS_FLUSHING = (GST_OBJECT_FLAG_LAST << 0), + /* padding */ + GST_BUS_FLAG_LAST = (GST_OBJECT_FLAG_LAST << 1) +} GstBusFlags; + +/** + * GstBusSyncReply: + * @GST_BUS_DROP: drop the message + * @GST_BUS_PASS: pass the message to the async queue + * @GST_BUS_ASYNC: pass message to async queue, continue if message is handled + * + * The result values for a GstBusSyncHandler. + */ +typedef enum +{ + GST_BUS_DROP = 0, + GST_BUS_PASS = 1, + GST_BUS_ASYNC = 2 +} GstBusSyncReply; + +/** + * GstBusSyncHandler: + * @bus: the #GstBus that sent the message + * @message: the #GstMessage + * @data: user data that has been given, when registering the handler + * + * Handler will be invoked synchronously, when a new message has been injected + * into the bus. This function is mostly used internally. Only one sync handler + * can be attached to a given bus. + * + * If the handler returns GST_BUS_DROP, it should unref the message, else the + * message should not be unreffed by the sync handler. + * + * Returns: #GstBusSyncReply stating what to do with the message + */ +typedef GstBusSyncReply (*GstBusSyncHandler) (GstBus * bus, GstMessage * message, gpointer data); + +/** + * GstBusFunc: + * @bus: the #GstBus that sent the message + * @message: the #GstMessage + * @data: user data that has been given, when registering the handler + * + * Specifies the type of function passed to gst_bus_add_watch() or + * gst_bus_add_watch_full(), which is called from the mainloop when a message + * is available on the bus. + * + * The message passed to the function will be unreffed after execution of this + * function so it should not be freed in the function. + * + * Note that this function is used as a GSourceFunc which means that returning + * FALSE will remove the GSource from the mainloop. + * + * Returns: %FALSE if the event source should be removed. + */ +typedef gboolean (*GstBusFunc) (GstBus * bus, GstMessage * message, gpointer data); + +/** + * GstBus: + * + * The opaque #GstBus data structure. + */ +struct _GstBus +{ + GstObject object; + + /*< private >*/ + GstAtomicQueue *queue; + GMutex *queue_lock; + + GstBusSyncHandler sync_handler; + gpointer sync_handler_data; + + guint signal_watch_id; + guint num_signal_watchers; + + /*< private >*/ + GstBusPrivate *priv; + gpointer _gst_reserved[GST_PADDING - 1]; +}; + +struct _GstBusClass +{ + GstObjectClass parent_class; + + /* signals */ + void (*message) (GstBus *bus, GstMessage *message); + void (*sync_message) (GstBus *bus, GstMessage *message); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_bus_get_type (void); + +GstBus* gst_bus_new (void); + +gboolean gst_bus_post (GstBus * bus, GstMessage * message); + +gboolean gst_bus_have_pending (GstBus * bus); +GstMessage * gst_bus_peek (GstBus * bus); +GstMessage * gst_bus_pop (GstBus * bus); +GstMessage * gst_bus_pop_filtered (GstBus * bus, GstMessageType types); +GstMessage * gst_bus_timed_pop (GstBus * bus, GstClockTime timeout); +GstMessage * gst_bus_timed_pop_filtered (GstBus * bus, GstClockTime timeout, GstMessageType types); +void gst_bus_set_flushing (GstBus * bus, gboolean flushing); + +/* synchronous dispatching */ +void gst_bus_set_sync_handler (GstBus * bus, GstBusSyncHandler func, + gpointer data); +/* GSource based dispatching */ +GSource * gst_bus_create_watch (GstBus * bus); +guint gst_bus_add_watch_full (GstBus * bus, + gint priority, + GstBusFunc func, + gpointer user_data, + GDestroyNotify notify); +guint gst_bus_add_watch (GstBus * bus, + GstBusFunc func, + gpointer user_data); + +/* polling the bus */ +GstMessage* gst_bus_poll (GstBus *bus, GstMessageType events, + GstClockTimeDiff timeout); + +/* signal based dispatching helper functions. */ +gboolean gst_bus_async_signal_func (GstBus *bus, GstMessage *message, + gpointer data); +GstBusSyncReply gst_bus_sync_signal_handler (GstBus *bus, GstMessage *message, + gpointer data); + +/* convenience api to add/remove a gsource that emits the async signals */ +void gst_bus_add_signal_watch (GstBus * bus); +void gst_bus_add_signal_watch_full (GstBus * bus, gint priority); +void gst_bus_remove_signal_watch (GstBus * bus); + +void gst_bus_enable_sync_message_emission (GstBus * bus); +void gst_bus_disable_sync_message_emission (GstBus * bus); + +G_END_DECLS + +#endif /* __GST_BUS_H__ */ diff --git a/gst/gstcaps.c b/gst/gstcaps.c new file mode 100644 index 0000000..7d6acfe --- /dev/null +++ b/gst/gstcaps.c @@ -0,0 +1,1943 @@ +/* GStreamer + * Copyright (C) <2003> David A. Schleef <ds@schleef.org> + * + * 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:gstcaps + * @short_description: Structure describing sets of media formats + * @see_also: #GstStructure + * + * Caps (capabilities) are lighweight refcounted objects describing media types. + * They are composed of an array of #GstStructure. + * + * Caps are exposed on #GstPadTemplate to describe all possible types a + * given pad can handle. They are also stored in the #GstRegistry along with + * a description of the #GstElement. + * + * Caps are exposed on the element pads using the gst_pad_get_caps() pad + * function. This function describes the possible types that the pad can + * handle or produce at runtime. + * + * Caps are also attached to buffers to describe to content of the data + * pointed to by the buffer with gst_buffer_set_caps(). Caps attached to + * a #GstBuffer allow for format negotiation upstream and downstream. + * + * A #GstCaps can be constructed with the following code fragment: + * + * <example> + * <title>Creating caps</title> + * <programlisting> + * GstCaps *caps; + * caps = gst_caps_new_simple ("video/x-raw", + * "format", G_TYPE_STRING, "I420"), + * "framerate", GST_TYPE_FRACTION, 25, 1, + * "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, + * "width", G_TYPE_INT, 320, + * "height", G_TYPE_INT, 240, + * NULL); + * </programlisting> + * </example> + * + * A #GstCaps is fixed when it has no properties with ranges or lists. Use + * gst_caps_is_fixed() to test for fixed caps. Only fixed caps can be + * set on a #GstPad or #GstBuffer. + * + * Various methods exist to work with the media types such as subtracting + * or intersecting. + * + * Last reviewed on 2007-02-13 (0.10.10) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <string.h> +#include <signal.h> + +#include "gst_private.h" +#include <gst/gst.h> +#include <gobject/gvaluecollector.h> + +#define DEBUG_REFCOUNT + + +#define GST_CAPS_ARRAY(c) ((GPtrArray *)((c)->priv)) + +#define GST_CAPS_LEN(c) (GST_CAPS_ARRAY(c)->len) + +#define IS_WRITABLE(caps) \ + (GST_CAPS_REFCOUNT_VALUE (caps) == 1) + +/* same as gst_caps_is_any () */ +#define CAPS_IS_ANY(caps) \ + (GST_CAPS_FLAGS(caps) & GST_CAPS_FLAGS_ANY) + +/* same as gst_caps_is_empty () */ +#define CAPS_IS_EMPTY(caps) \ + (!CAPS_IS_ANY(caps) && CAPS_IS_EMPTY_SIMPLE(caps)) + +#define CAPS_IS_EMPTY_SIMPLE(caps) \ + ((GST_CAPS_ARRAY (caps) == NULL) || (GST_CAPS_LEN (caps) == 0)) + +/* quick way to get a caps structure at an index without doing a type or array + * length check */ +#define gst_caps_get_structure_unchecked(caps, index) \ + ((GstStructure *)g_ptr_array_index (GST_CAPS_ARRAY (caps), (index))) +/* quick way to append a structure without checking the args */ +#define gst_caps_append_structure_unchecked(caps, structure) G_STMT_START{\ + GstStructure *__s=structure; \ + if (gst_structure_set_parent_refcount (__s, &GST_MINI_OBJECT_REFCOUNT(caps))) \ + g_ptr_array_add (GST_CAPS_ARRAY (caps), __s); \ +}G_STMT_END + +/* lock to protect multiple invocations of static caps to caps conversion */ +G_LOCK_DEFINE_STATIC (static_caps_lock); + +static void gst_caps_transform_to_string (const GValue * src_value, + GValue * dest_value); +static gboolean gst_caps_from_string_inplace (GstCaps * caps, + const gchar * string); + +GType _gst_caps_type = 0; + +GST_DEFINE_MINI_OBJECT_TYPE (GstCaps, gst_caps); + +void +_priv_gst_caps_initialize (void) +{ + _gst_caps_type = gst_caps_get_type (); + + g_value_register_transform_func (_gst_caps_type, + G_TYPE_STRING, gst_caps_transform_to_string); +} + +static GstCaps * +_gst_caps_copy (const GstCaps * caps) +{ + GstCaps *newcaps; + GstStructure *structure; + guint i, n; + + g_return_val_if_fail (GST_IS_CAPS (caps), NULL); + + newcaps = gst_caps_new_empty (); + GST_CAPS_FLAGS (newcaps) = GST_CAPS_FLAGS (caps); + n = GST_CAPS_LEN (caps); + + for (i = 0; i < n; i++) { + structure = gst_caps_get_structure_unchecked (caps, i); + gst_caps_append_structure (newcaps, gst_structure_copy (structure)); + } + + return newcaps; +} + +/* creation/deletion */ +static void +_gst_caps_free (GstCaps * caps) +{ + GstStructure *structure; + guint i, len; + + /* The refcount must be 0, but since we're only called by gst_caps_unref, + * don't bother testing. */ + len = GST_CAPS_LEN (caps); + /* This can be used to get statistics about caps sizes */ + /*GST_CAT_INFO (GST_CAT_CAPS, "caps size: %d", len); */ + for (i = 0; i < len; i++) { + structure = (GstStructure *) gst_caps_get_structure_unchecked (caps, i); + gst_structure_set_parent_refcount (structure, NULL); + gst_structure_free (structure); + } + g_ptr_array_free (GST_CAPS_ARRAY (caps), TRUE); + +#ifdef DEBUG_REFCOUNT + GST_CAT_LOG (GST_CAT_CAPS, "freeing caps %p", caps); +#endif + g_slice_free1 (GST_MINI_OBJECT_SIZE (caps), caps); +} + +static void +gst_caps_init (GstCaps * caps, gsize size) +{ + gst_mini_object_init (GST_MINI_OBJECT_CAST (caps), _gst_caps_type, size); + + caps->mini_object.copy = (GstMiniObjectCopyFunction) _gst_caps_copy; + caps->mini_object.dispose = NULL; + caps->mini_object.free = (GstMiniObjectFreeFunction) _gst_caps_free; + + /* the 32 has been determined by logging caps sizes in _gst_caps_free + * but g_ptr_array uses 16 anyway if it expands once, so this does not help + * in practise + * GST_CAPS_ARRAY (caps) = g_ptr_array_sized_new (32); + */ + caps->priv = g_ptr_array_new (); +} + +/** + * gst_caps_new_empty: + * + * Creates a new #GstCaps that is empty. That is, the returned + * #GstCaps contains no media formats. + * Caller is responsible for unreffing the returned caps. + * + * Returns: (transfer full): the new #GstCaps + */ +GstCaps * +gst_caps_new_empty (void) +{ + GstCaps *caps; + + caps = g_slice_new (GstCaps); + + gst_caps_init (caps, sizeof (GstCaps)); + +#ifdef DEBUG_REFCOUNT + GST_CAT_TRACE (GST_CAT_CAPS, "created caps %p", caps); +#endif + + return caps; +} + +/** + * gst_caps_new_any: + * + * Creates a new #GstCaps that indicates that it is compatible with + * any media format. + * + * Returns: (transfer full): the new #GstCaps + */ +GstCaps * +gst_caps_new_any (void) +{ + GstCaps *caps = gst_caps_new_empty (); + + GST_CAPS_FLAG_SET (caps, GST_CAPS_FLAGS_ANY); + + return caps; +} + +/** + * gst_caps_new_simple: + * @media_type: the media type of the structure + * @fieldname: first field to set + * @...: additional arguments + * + * Creates a new #GstCaps that contains one #GstStructure. The + * structure is defined by the arguments, which have the same format + * as gst_structure_new(). + * Caller is responsible for unreffing the returned caps. + * + * Returns: (transfer full): the new #GstCaps + */ +GstCaps * +gst_caps_new_simple (const char *media_type, const char *fieldname, ...) +{ + GstCaps *caps; + GstStructure *structure; + va_list var_args; + + caps = gst_caps_new_empty (); + + va_start (var_args, fieldname); + structure = gst_structure_new_valist (media_type, fieldname, var_args); + va_end (var_args); + + if (structure) + gst_caps_append_structure_unchecked (caps, structure); + else + gst_caps_replace (&caps, NULL); + + return caps; +} + +/** + * gst_caps_new_full: + * @struct1: the first structure to add + * @...: additional structures to add + * + * Creates a new #GstCaps and adds all the structures listed as + * arguments. The list must be NULL-terminated. The structures + * are not copied; the returned #GstCaps owns the structures. + * + * Returns: (transfer full): the new #GstCaps + */ +GstCaps * +gst_caps_new_full (GstStructure * struct1, ...) +{ + GstCaps *caps; + va_list var_args; + + va_start (var_args, struct1); + caps = gst_caps_new_full_valist (struct1, var_args); + va_end (var_args); + + return caps; +} + +/** + * gst_caps_new_full_valist: + * @structure: the first structure to add + * @var_args: additional structures to add + * + * Creates a new #GstCaps and adds all the structures listed as + * arguments. The list must be NULL-terminated. The structures + * are not copied; the returned #GstCaps owns the structures. + * + * Returns: (transfer full): the new #GstCaps + */ +GstCaps * +gst_caps_new_full_valist (GstStructure * structure, va_list var_args) +{ + GstCaps *caps; + + caps = gst_caps_new_empty (); + + while (structure) { + gst_caps_append_structure_unchecked (caps, structure); + structure = va_arg (var_args, GstStructure *); + } + + return caps; +} + +GType +gst_static_caps_get_type (void) +{ + static GType staticcaps_type = 0; + + if (G_UNLIKELY (staticcaps_type == 0)) { + staticcaps_type = g_pointer_type_register_static ("GstStaticCaps"); + } + return staticcaps_type; +} + + +/** + * gst_static_caps_get: + * @static_caps: the #GstStaticCaps to convert + * + * Converts a #GstStaticCaps to a #GstCaps. + * + * Returns: (transfer full): a pointer to the #GstCaps. Unref after usage. + * Since the core holds an additional ref to the returned caps, + * use gst_caps_make_writable() on the returned caps to modify it. + */ +GstCaps * +gst_static_caps_get (GstStaticCaps * static_caps) +{ + GstCaps *caps; + + g_return_val_if_fail (static_caps != NULL, NULL); + + caps = (GstCaps *) static_caps; + + /* refcount is 0 when we need to convert */ + if (G_UNLIKELY (GST_CAPS_REFCOUNT_VALUE (caps) == 0)) { + const char *string; + GstCaps temp; + + G_LOCK (static_caps_lock); + /* check if other thread already updated */ + if (G_UNLIKELY (GST_CAPS_REFCOUNT_VALUE (caps) > 0)) + goto done; + + string = static_caps->string; + + if (G_UNLIKELY (string == NULL)) + goto no_string; + + GST_CAT_TRACE (GST_CAT_CAPS, "creating %p", static_caps); + + /* we construct the caps on the stack, then copy over the struct into our + * real caps, refcount last. We do this because we must leave the refcount + * of the result caps to 0 so that other threads don't run away with the + * caps while we are constructing it. */ + gst_caps_init (&temp, sizeof (GstCaps)); + + /* convert to string */ + if (G_UNLIKELY (!gst_caps_from_string_inplace (&temp, string))) + g_critical ("Could not convert static caps \"%s\"", string); + + gst_caps_init (caps, sizeof (GstCaps)); + /* now copy stuff over to the real caps. */ + GST_CAPS_FLAGS (caps) = GST_CAPS_FLAGS (&temp); + caps->priv = GST_CAPS_ARRAY (&temp); + + GST_CAT_TRACE (GST_CAT_CAPS, "created %p", static_caps); + done: + G_UNLOCK (static_caps_lock); + } + /* ref the caps, makes it not writable */ + gst_caps_ref (caps); + + return caps; + + /* ERRORS */ +no_string: + { + G_UNLOCK (static_caps_lock); + g_warning ("static caps %p string is NULL", static_caps); + return NULL; + } +} + +/** + * gst_static_caps_cleanup: + * @static_caps: the #GstStaticCaps to convert + * + * Clean up the caps contained in @static_caps when the refcount is 0. + */ +void +gst_static_caps_cleanup (GstStaticCaps * static_caps) +{ + GstCaps *caps = (GstCaps *) static_caps; + + /* FIXME: this is not threadsafe */ + if (GST_CAPS_REFCOUNT_VALUE (caps) == 1) { + GstStructure *structure; + guint i, clen; + + clen = GST_CAPS_LEN (caps); + + for (i = 0; i < clen; i++) { + structure = (GstStructure *) gst_caps_get_structure (caps, i); + gst_structure_set_parent_refcount (structure, NULL); + gst_structure_free (structure); + } + g_ptr_array_free (GST_CAPS_ARRAY (caps), TRUE); + GST_CAPS_REFCOUNT (caps) = 0; + } +} + +/* manipulation */ + +static GstStructure * +gst_caps_remove_and_get_structure (GstCaps * caps, guint idx) +{ + /* don't use index_fast, gst_caps_do_simplify relies on the order */ + GstStructure *s = g_ptr_array_remove_index (GST_CAPS_ARRAY (caps), idx); + + gst_structure_set_parent_refcount (s, NULL); + return s; +} + +/** + * gst_caps_steal_structure: + * @caps: the #GstCaps to retrieve from + * @index: Index of the structure to retrieve + * + * Retrieves the stucture with the given index from the list of structures + * contained in @caps. The caller becomes the owner of the returned structure. + * + * Returns: (transfer full): a pointer to the #GstStructure corresponding + * to @index. + * + * Since: 0.10.30 + */ +GstStructure * +gst_caps_steal_structure (GstCaps * caps, guint index) +{ + g_return_val_if_fail (caps != NULL, NULL); + g_return_val_if_fail (IS_WRITABLE (caps), NULL); + + if (G_UNLIKELY (index >= GST_CAPS_LEN (caps))) + return NULL; + + return gst_caps_remove_and_get_structure (caps, index); +} + +/** + * gst_caps_append: + * @caps1: the #GstCaps that will be appended to + * @caps2: (transfer full): the #GstCaps to append + * + * Appends the structures contained in @caps2 to @caps1. The structures in + * @caps2 are not copied -- they are transferred to @caps1, and then @caps2 is + * freed. If either caps is ANY, the resulting caps will be ANY. + */ +void +gst_caps_append (GstCaps * caps1, GstCaps * caps2) +{ + GstStructure *structure; + int i; + + g_return_if_fail (GST_IS_CAPS (caps1)); + g_return_if_fail (GST_IS_CAPS (caps2)); + g_return_if_fail (IS_WRITABLE (caps1)); + g_return_if_fail (IS_WRITABLE (caps2)); + + if (G_UNLIKELY (CAPS_IS_ANY (caps1) || CAPS_IS_ANY (caps2))) { + /* FIXME: this leaks */ + GST_CAPS_FLAGS (caps1) |= GST_CAPS_FLAGS_ANY; + for (i = GST_CAPS_LEN (caps2) - 1; i >= 0; i--) { + structure = gst_caps_remove_and_get_structure (caps2, i); + gst_structure_free (structure); + } + } else { + for (i = GST_CAPS_LEN (caps2); i; i--) { + structure = gst_caps_remove_and_get_structure (caps2, 0); + gst_caps_append_structure_unchecked (caps1, structure); + } + } + gst_caps_unref (caps2); /* guaranteed to free it */ +} + +/** + * gst_caps_merge: + * @caps1: the #GstCaps that will take the new entries + * @caps2: (transfer full): the #GstCaps to merge in + * + * Appends the structures contained in @caps2 to @caps1 if they are not yet + * expressed by @caps1. The structures in @caps2 are not copied -- they are + * transferred to @caps1, and then @caps2 is freed. + * If either caps is ANY, the resulting caps will be ANY. + * + * Since: 0.10.10 + */ +void +gst_caps_merge (GstCaps * caps1, GstCaps * caps2) +{ + GstStructure *structure; + int i; + + g_return_if_fail (GST_IS_CAPS (caps1)); + g_return_if_fail (GST_IS_CAPS (caps2)); + g_return_if_fail (IS_WRITABLE (caps1)); + g_return_if_fail (IS_WRITABLE (caps2)); + + if (G_UNLIKELY (CAPS_IS_ANY (caps1))) { + for (i = GST_CAPS_LEN (caps2) - 1; i >= 0; i--) { + structure = gst_caps_remove_and_get_structure (caps2, i); + gst_structure_free (structure); + } + } else if (G_UNLIKELY (CAPS_IS_ANY (caps2))) { + GST_CAPS_FLAGS (caps1) |= GST_CAPS_FLAGS_ANY; + for (i = GST_CAPS_LEN (caps1) - 1; i >= 0; i--) { + structure = gst_caps_remove_and_get_structure (caps1, i); + gst_structure_free (structure); + } + } else { + for (i = GST_CAPS_LEN (caps2); i; i--) { + structure = gst_caps_remove_and_get_structure (caps2, 0); + gst_caps_merge_structure (caps1, structure); + } + /* this is too naive + GstCaps *com = gst_caps_intersect (caps1, caps2); + GstCaps *add = gst_caps_subtract (caps2, com); + + GST_DEBUG ("common : %d", gst_caps_get_size (com)); + GST_DEBUG ("adding : %d", gst_caps_get_size (add)); + gst_caps_append (caps1, add); + gst_caps_unref (com); + */ + } + gst_caps_unref (caps2); /* guaranteed to free it */ +} + +/** + * gst_caps_append_structure: + * @caps: the #GstCaps that will be appended to + * @structure: (transfer full): the #GstStructure to append + * + * Appends @structure to @caps. The structure is not copied; @caps + * becomes the owner of @structure. + */ +void +gst_caps_append_structure (GstCaps * caps, GstStructure * structure) +{ + g_return_if_fail (GST_IS_CAPS (caps)); + g_return_if_fail (IS_WRITABLE (caps)); + + if (G_LIKELY (structure)) { + gst_caps_append_structure_unchecked (caps, structure); + } +} + +/** + * gst_caps_remove_structure: + * @caps: the #GstCaps to remove from + * @idx: Index of the structure to remove + * + * removes the stucture with the given index from the list of structures + * contained in @caps. + */ +void +gst_caps_remove_structure (GstCaps * caps, guint idx) +{ + GstStructure *structure; + + g_return_if_fail (caps != NULL); + g_return_if_fail (idx <= gst_caps_get_size (caps)); + g_return_if_fail (IS_WRITABLE (caps)); + + structure = gst_caps_remove_and_get_structure (caps, idx); + gst_structure_free (structure); +} + +/** + * gst_caps_merge_structure: + * @caps: the #GstCaps that will the new structure + * @structure: (transfer full): the #GstStructure to merge + * + * Appends @structure to @caps if its not already expressed by @caps. The + * structure is not copied; @caps becomes the owner of @structure. + */ +void +gst_caps_merge_structure (GstCaps * caps, GstStructure * structure) +{ + g_return_if_fail (GST_IS_CAPS (caps)); + g_return_if_fail (IS_WRITABLE (caps)); + + if (G_LIKELY (structure)) { + GstStructure *structure1; + int i; + gboolean unique = TRUE; + + /* check each structure */ + for (i = GST_CAPS_LEN (caps) - 1; i >= 0; i--) { + structure1 = gst_caps_get_structure_unchecked (caps, i); + /* if structure is a subset of structure1, then skip it */ + if (gst_structure_is_subset (structure, structure1)) { + unique = FALSE; + break; + } + } + if (unique) { + gst_caps_append_structure_unchecked (caps, structure); + } else { + gst_structure_free (structure); + } + } +} + +/** + * gst_caps_get_size: + * @caps: a #GstCaps + * + * Gets the number of structures contained in @caps. + * + * Returns: the number of structures that @caps contains + */ +guint +gst_caps_get_size (const GstCaps * caps) +{ + g_return_val_if_fail (GST_IS_CAPS (caps), 0); + + return GST_CAPS_LEN (caps); +} + +/** + * gst_caps_get_structure: + * @caps: a #GstCaps + * @index: the index of the structure + * + * Finds the structure in @caps that has the index @index, and + * returns it. + * + * WARNING: This function takes a const GstCaps *, but returns a + * non-const GstStructure *. This is for programming convenience -- + * the caller should be aware that structures inside a constant + * #GstCaps should not be modified. However, if you know the caps + * are writable, either because you have just copied them or made + * them writable with gst_caps_make_writable(), you may modify the + * structure returned in the usual way, e.g. with functions like + * gst_structure_set(). + * + * You do not need to free or unref the structure returned, it + * belongs to the #GstCaps. + * + * Returns: (transfer none): a pointer to the #GstStructure corresponding + * to @index + */ +GstStructure * +gst_caps_get_structure (const GstCaps * caps, guint index) +{ + g_return_val_if_fail (GST_IS_CAPS (caps), NULL); + g_return_val_if_fail (index < GST_CAPS_LEN (caps), NULL); + + return gst_caps_get_structure_unchecked (caps, index); +} + +/** + * gst_caps_copy_nth: + * @caps: the #GstCaps to copy + * @nth: the nth structure to copy + * + * Creates a new #GstCaps and appends a copy of the nth structure + * contained in @caps. + * + * Returns: (transfer full): the new #GstCaps + */ +GstCaps * +gst_caps_copy_nth (const GstCaps * caps, guint nth) +{ + GstCaps *newcaps; + GstStructure *structure; + + g_return_val_if_fail (GST_IS_CAPS (caps), NULL); + + newcaps = gst_caps_new_empty (); + GST_CAPS_FLAGS (newcaps) = GST_CAPS_FLAGS (caps); + + if (G_LIKELY (GST_CAPS_LEN (caps) > nth)) { + structure = gst_caps_get_structure_unchecked (caps, nth); + gst_caps_append_structure_unchecked (newcaps, + gst_structure_copy (structure)); + } + + return newcaps; +} + +/** + * gst_caps_truncate: + * @caps: the #GstCaps to truncate + * + * Destructively discard all but the first structure from @caps. Useful when + * fixating. @caps must be writable. + */ +void +gst_caps_truncate (GstCaps * caps) +{ + gint i; + + g_return_if_fail (GST_IS_CAPS (caps)); + g_return_if_fail (IS_WRITABLE (caps)); + + i = GST_CAPS_LEN (caps) - 1; + + while (i > 0) + gst_caps_remove_structure (caps, i--); +} + +/** + * gst_caps_set_value: + * @caps: a writable caps + * @field: name of the field to set + * @value: value to set the field to + * + * Sets the given @field on all structures of @caps to the given @value. + * This is a convenience function for calling gst_structure_set_value() on + * all structures of @caps. + * + * Since: 0.10.26 + **/ +void +gst_caps_set_value (GstCaps * caps, const char *field, const GValue * value) +{ + guint i, len; + + g_return_if_fail (GST_IS_CAPS (caps)); + g_return_if_fail (IS_WRITABLE (caps)); + g_return_if_fail (field != NULL); + g_return_if_fail (G_IS_VALUE (value)); + + len = GST_CAPS_LEN (caps); + for (i = 0; i < len; i++) { + GstStructure *structure = gst_caps_get_structure_unchecked (caps, i); + gst_structure_set_value (structure, field, value); + } +} + +/** + * gst_caps_set_simple_valist: + * @caps: the #GstCaps to set + * @field: first field to set + * @varargs: additional parameters + * + * Sets fields in a #GstCaps. The arguments must be passed in the same + * manner as gst_structure_set(), and be NULL-terminated. + * <note>Prior to GStreamer version 0.10.26, this function failed when + * @caps was not simple. If your code needs to work with those versions + * of GStreamer, you may only call this function when GST_CAPS_IS_SIMPLE() + * is %TRUE for @caps.</note> + */ +void +gst_caps_set_simple_valist (GstCaps * caps, const char *field, va_list varargs) +{ + GValue value = { 0, }; + + g_return_if_fail (GST_IS_CAPS (caps)); + g_return_if_fail (IS_WRITABLE (caps)); + + while (field) { + GType type; + char *err; + + type = va_arg (varargs, GType); + + if (G_UNLIKELY (type == G_TYPE_DATE)) { + g_warning ("Don't use G_TYPE_DATE, use GST_TYPE_DATE instead\n"); + type = GST_TYPE_DATE; + } + G_VALUE_COLLECT_INIT (&value, type, varargs, 0, &err); + if (G_UNLIKELY (err)) { + g_critical ("%s", err); + return; + } + + gst_caps_set_value (caps, field, &value); + + g_value_unset (&value); + + field = va_arg (varargs, const gchar *); + } +} + +/** + * gst_caps_set_simple: + * @caps: the #GstCaps to set + * @field: first field to set + * @...: additional parameters + * + * Sets fields in a #GstCaps. The arguments must be passed in the same + * manner as gst_structure_set(), and be NULL-terminated. + * <note>Prior to GStreamer version 0.10.26, this function failed when + * @caps was not simple. If your code needs to work with those versions + * of GStreamer, you may only call this function when GST_CAPS_IS_SIMPLE() + * is %TRUE for @caps.</note> + */ +void +gst_caps_set_simple (GstCaps * caps, const char *field, ...) +{ + va_list var_args; + + g_return_if_fail (GST_IS_CAPS (caps)); + g_return_if_fail (IS_WRITABLE (caps)); + + va_start (var_args, field); + gst_caps_set_simple_valist (caps, field, var_args); + va_end (var_args); +} + +/* tests */ + +/** + * gst_caps_is_any: + * @caps: the #GstCaps to test + * + * Determines if @caps represents any media format. + * + * Returns: TRUE if @caps represents any format. + */ +gboolean +gst_caps_is_any (const GstCaps * caps) +{ + g_return_val_if_fail (GST_IS_CAPS (caps), FALSE); + + return (CAPS_IS_ANY (caps)); +} + +/** + * gst_caps_is_empty: + * @caps: the #GstCaps to test + * + * Determines if @caps represents no media formats. + * + * Returns: TRUE if @caps represents no formats. + */ +gboolean +gst_caps_is_empty (const GstCaps * caps) +{ + g_return_val_if_fail (GST_IS_CAPS (caps), FALSE); + + if (CAPS_IS_ANY (caps)) + return FALSE; + + return CAPS_IS_EMPTY_SIMPLE (caps); +} + +static gboolean +gst_caps_is_fixed_foreach (GQuark field_id, const GValue * value, + gpointer unused) +{ + return gst_value_is_fixed (value); +} + +/** + * gst_caps_is_fixed: + * @caps: the #GstCaps to test + * + * Fixed #GstCaps describe exactly one format, that is, they have exactly + * one structure, and each field in the structure describes a fixed type. + * Examples of non-fixed types are GST_TYPE_INT_RANGE and GST_TYPE_LIST. + * + * Returns: TRUE if @caps is fixed + */ +gboolean +gst_caps_is_fixed (const GstCaps * caps) +{ + GstStructure *structure; + + g_return_val_if_fail (GST_IS_CAPS (caps), FALSE); + + if (GST_CAPS_LEN (caps) != 1) + return FALSE; + + structure = gst_caps_get_structure_unchecked (caps, 0); + + return gst_structure_foreach (structure, gst_caps_is_fixed_foreach, NULL); +} + +/** + * gst_caps_is_equal_fixed: + * @caps1: the #GstCaps to test + * @caps2: the #GstCaps to test + * + * Tests if two #GstCaps are equal. This function only works on fixed + * #GstCaps. + * + * Returns: TRUE if the arguments represent the same format + */ +gboolean +gst_caps_is_equal_fixed (const GstCaps * caps1, const GstCaps * caps2) +{ + GstStructure *struct1, *struct2; + + g_return_val_if_fail (gst_caps_is_fixed (caps1), FALSE); + g_return_val_if_fail (gst_caps_is_fixed (caps2), FALSE); + + struct1 = gst_caps_get_structure_unchecked (caps1, 0); + struct2 = gst_caps_get_structure_unchecked (caps2, 0); + + return gst_structure_is_equal (struct1, struct2); +} + +/** + * gst_caps_is_always_compatible: + * @caps1: the #GstCaps to test + * @caps2: the #GstCaps to test + * + * A given #GstCaps structure is always compatible with another if + * every media format that is in the first is also contained in the + * second. That is, @caps1 is a subset of @caps2. + * + * Returns: TRUE if @caps1 is a subset of @caps2. + */ +gboolean +gst_caps_is_always_compatible (const GstCaps * caps1, const GstCaps * caps2) +{ + g_return_val_if_fail (GST_IS_CAPS (caps1), FALSE); + g_return_val_if_fail (GST_IS_CAPS (caps2), FALSE); + + return gst_caps_is_subset (caps1, caps2); +} + +/** + * gst_caps_is_subset: + * @subset: a #GstCaps + * @superset: a potentially greater #GstCaps + * + * Checks if all caps represented by @subset are also represented by @superset. + * <note>This function does not work reliably if optional properties for caps + * are included on one caps and omitted on the other.</note> + * + * Returns: %TRUE if @subset is a subset of @superset + */ +gboolean +gst_caps_is_subset (const GstCaps * subset, const GstCaps * superset) +{ + GstStructure *s1, *s2; + gboolean ret = TRUE; + gint i, j; + + g_return_val_if_fail (subset != NULL, FALSE); + g_return_val_if_fail (superset != NULL, FALSE); + + if (CAPS_IS_EMPTY (subset) || CAPS_IS_ANY (superset)) + return TRUE; + if (CAPS_IS_ANY (subset) || CAPS_IS_EMPTY (superset)) + return FALSE; + + for (i = GST_CAPS_LEN (subset) - 1; i >= 0; i--) { + for (j = GST_CAPS_LEN (superset) - 1; j >= 0; j--) { + s1 = gst_caps_get_structure_unchecked (subset, i); + s2 = gst_caps_get_structure_unchecked (superset, j); + if (gst_structure_is_subset (s1, s2)) { + /* If we found a superset, continue with the next + * subset structure */ + break; + } + } + /* If we found no superset for this subset structure + * we return FALSE immediately */ + if (j == -1) { + ret = FALSE; + break; + } + } + + return ret; +} + +/** + * gst_caps_is_subset_structure: + * @caps: a #GstCaps + * @structure: a potential #GstStructure subset of @caps + * + * Checks if @structure is a subset of @caps. See gst_caps_is_subset() + * for more information. + * + * Returns: %TRUE if @structure is a subset of @caps + * + * Since: 0.10.35 + */ +gboolean +gst_caps_is_subset_structure (const GstCaps * caps, + const GstStructure * structure) +{ + GstStructure *s; + gint i; + + g_return_val_if_fail (caps != NULL, FALSE); + g_return_val_if_fail (structure != NULL, FALSE); + + if (CAPS_IS_ANY (caps)) + return TRUE; + if (CAPS_IS_EMPTY (caps)) + return FALSE; + + for (i = GST_CAPS_LEN (caps) - 1; i >= 0; i--) { + s = gst_caps_get_structure_unchecked (caps, i); + if (gst_structure_is_subset (structure, s)) { + /* If we found a superset return TRUE */ + return TRUE; + } + } + + return FALSE; +} + +/** + * gst_caps_is_equal: + * @caps1: a #GstCaps + * @caps2: another #GstCaps + * + * Checks if the given caps represent the same set of caps. + * <note>This function does not work reliably if optional properties for caps + * are included on one caps and omitted on the other.</note> + * + * This function deals correctly with passing NULL for any of the caps. + * + * Returns: TRUE if both caps are equal. + */ +gboolean +gst_caps_is_equal (const GstCaps * caps1, const GstCaps * caps2) +{ + /* FIXME 0.11: NULL pointers are no valid Caps but indicate an error + * So there should be an assertion that caps1 and caps2 != NULL */ + + /* NULL <-> NULL is allowed here */ + if (G_UNLIKELY (caps1 == caps2)) + return TRUE; + + /* one of them NULL => they are different (can't be both NULL because + * we checked that above) */ + if (G_UNLIKELY (caps1 == NULL || caps2 == NULL)) + return FALSE; + + if (G_UNLIKELY (gst_caps_is_fixed (caps1) && gst_caps_is_fixed (caps2))) + return gst_caps_is_equal_fixed (caps1, caps2); + + return gst_caps_is_subset (caps1, caps2) && gst_caps_is_subset (caps2, caps1); +} + +/* intersect operation */ + +/** + * gst_caps_can_intersect: + * @caps1: a #GstCaps to intersect + * @caps2: a #GstCaps to intersect + * + * Tries intersecting @caps1 and @caps2 and reports whether the result would not + * be empty + * + * Returns: %TRUE if intersection would be not empty + * + * Since: 0.10.25 + */ +gboolean +gst_caps_can_intersect (const GstCaps * caps1, const GstCaps * caps2) +{ + guint64 i; /* index can be up to 2 * G_MAX_UINT */ + guint j, k, len1, len2; + GstStructure *struct1; + GstStructure *struct2; + + g_return_val_if_fail (GST_IS_CAPS (caps1), FALSE); + g_return_val_if_fail (GST_IS_CAPS (caps2), FALSE); + + /* caps are exactly the same pointers */ + if (G_UNLIKELY (caps1 == caps2)) + return TRUE; + + /* empty caps on either side, return empty */ + if (G_UNLIKELY (CAPS_IS_EMPTY (caps1) || CAPS_IS_EMPTY (caps2))) + return FALSE; + + /* one of the caps is any */ + if (G_UNLIKELY (CAPS_IS_ANY (caps1) || CAPS_IS_ANY (caps2))) + return TRUE; + + /* run zigzag on top line then right line, this preserves the caps order + * much better than a simple loop. + * + * This algorithm zigzags over the caps structures as demonstrated in + * the folowing matrix: + * + * caps1 0 1 2 3 + * +------------- total distance: +------------- + * | 1 2 4 7 0 | 0 1 2 3 + * caps2 | 3 5 8 10 1 | 1 2 3 4 + * | 6 9 11 12 2 | 2 3 4 5 + * + * First we iterate over the caps1 structures (top line) intersecting + * the structures diagonally down, then we iterate over the caps2 + * structures. The result is that the intersections are ordered based on the + * sum of the indexes in the list. + */ + len1 = GST_CAPS_LEN (caps1); + len2 = GST_CAPS_LEN (caps2); + for (i = 0; i < len1 + len2 - 1; i++) { + /* superset index goes from 0 to sgst_caps_structure_intersectuperset->structs->len-1 */ + j = MIN (i, len1 - 1); + /* subset index stays 0 until i reaches superset->structs->len, then it + * counts up from 1 to subset->structs->len - 1 */ + k = (i > j) ? (i - j) : 0; /* MAX (0, i - j) */ + + /* now run the diagonal line, end condition is the left or bottom + * border */ + while (k < len2) { + struct1 = gst_caps_get_structure_unchecked (caps1, j); + struct2 = gst_caps_get_structure_unchecked (caps2, k); + + if (gst_structure_can_intersect (struct1, struct2)) { + return TRUE; + } + /* move down left */ + k++; + if (G_UNLIKELY (j == 0)) + break; /* so we don't roll back to G_MAXUINT */ + j--; + } + } + return FALSE; +} + +static GstCaps * +gst_caps_intersect_zig_zag (const GstCaps * caps1, const GstCaps * caps2) +{ + guint64 i; /* index can be up to 2 * G_MAX_UINT */ + guint j, k, len1, len2; + + GstStructure *struct1; + GstStructure *struct2; + GstCaps *dest; + GstStructure *istruct; + + /* caps are exactly the same pointers, just copy one caps */ + if (G_UNLIKELY (caps1 == caps2)) + return _gst_caps_copy (caps1); + + /* empty caps on either side, return empty */ + if (G_UNLIKELY (CAPS_IS_EMPTY (caps1) || CAPS_IS_EMPTY (caps2))) + return gst_caps_new_empty (); + + /* one of the caps is any, just copy the other caps */ + if (G_UNLIKELY (CAPS_IS_ANY (caps1))) + return _gst_caps_copy (caps2); + if (G_UNLIKELY (CAPS_IS_ANY (caps2))) + return _gst_caps_copy (caps1); + + dest = gst_caps_new_empty (); + + /* run zigzag on top line then right line, this preserves the caps order + * much better than a simple loop. + * + * This algorithm zigzags over the caps structures as demonstrated in + * the folowing matrix: + * + * caps1 + * +------------- + * | 1 2 4 7 + * caps2 | 3 5 8 10 + * | 6 9 11 12 + * + * First we iterate over the caps1 structures (top line) intersecting + * the structures diagonally down, then we iterate over the caps2 + * structures. + */ + len1 = GST_CAPS_LEN (caps1); + len2 = GST_CAPS_LEN (caps2); + for (i = 0; i < len1 + len2 - 1; i++) { + /* caps1 index goes from 0 to GST_CAPS_LEN (caps1)-1 */ + j = MIN (i, len1 - 1); + /* caps2 index stays 0 until i reaches GST_CAPS_LEN (caps1), then it counts + * up from 1 to GST_CAPS_LEN (caps2) - 1 */ + k = (i > j) ? (i - j) : 0; /* MAX (0, i - j) */ + + /* now run the diagonal line, end condition is the left or bottom + * border */ + while (k < len2) { + struct1 = gst_caps_get_structure_unchecked (caps1, j); + struct2 = gst_caps_get_structure_unchecked (caps2, k); + + istruct = gst_structure_intersect (struct1, struct2); + + gst_caps_merge_structure (dest, istruct); + /* move down left */ + k++; + if (G_UNLIKELY (j == 0)) + break; /* so we don't roll back to G_MAXUINT */ + j--; + } + } + return dest; +} + +/** + * gst_caps_intersect_first: + * @caps1: a #GstCaps to intersect + * @caps2: a #GstCaps to intersect + * + * Creates a new #GstCaps that contains all the formats that are common + * to both @caps1 and @caps2. + * + * Unlike @gst_caps_intersect, the returned caps will be ordered in a similar + * fashion as @caps1. + * + * Returns: the new #GstCaps + */ +static GstCaps * +gst_caps_intersect_first (const GstCaps * caps1, const GstCaps * caps2) +{ + guint64 i; /* index can be up to 2 * G_MAX_UINT */ + guint j, len1, len2; + + GstStructure *struct1; + GstStructure *struct2; + GstCaps *dest; + GstStructure *istruct; + + /* caps are exactly the same pointers, just copy one caps */ + if (G_UNLIKELY (caps1 == caps2)) + return gst_caps_copy (caps1); + + /* empty caps on either side, return empty */ + if (G_UNLIKELY (CAPS_IS_EMPTY (caps1) || CAPS_IS_EMPTY (caps2))) + return gst_caps_new_empty (); + + /* one of the caps is any, just copy the other caps */ + if (G_UNLIKELY (CAPS_IS_ANY (caps1))) + return gst_caps_copy (caps2); + if (G_UNLIKELY (CAPS_IS_ANY (caps2))) + return gst_caps_copy (caps1); + + dest = gst_caps_new_empty (); + + len1 = GST_CAPS_LEN (caps1); + len2 = GST_CAPS_LEN (caps2); + for (i = 0; i < len1; i++) { + struct1 = gst_caps_get_structure_unchecked (caps1, i); + for (j = 0; j < len2; j++) { + struct2 = gst_caps_get_structure_unchecked (caps2, j); + istruct = gst_structure_intersect (struct1, struct2); + if (istruct) + gst_caps_merge_structure (dest, istruct); + } + } + + return dest; +} + +/** + * gst_caps_intersect_full: + * @caps1: a #GstCaps to intersect + * @caps2: a #GstCaps to intersect + * @mode: The intersection algorithm/mode to use + * + * Creates a new #GstCaps that contains all the formats that are common + * to both @caps1 and @caps2, the order is defined by the #GstCapsIntersectMode + * used. + * + * Returns: the new #GstCaps + * Since: 0.10.33 + */ +GstCaps * +gst_caps_intersect_full (const GstCaps * caps1, const GstCaps * caps2, + GstCapsIntersectMode mode) +{ + g_return_val_if_fail (GST_IS_CAPS (caps1), NULL); + g_return_val_if_fail (GST_IS_CAPS (caps2), NULL); + + switch (mode) { + case GST_CAPS_INTERSECT_FIRST: + return gst_caps_intersect_first (caps1, caps2); + default: + g_warning ("Unknown caps intersect mode: %d", mode); + /* fallthrough */ + case GST_CAPS_INTERSECT_ZIG_ZAG: + return gst_caps_intersect_zig_zag (caps1, caps2); + } +} + +/** + * gst_caps_intersect: + * @caps1: a #GstCaps to intersect + * @caps2: a #GstCaps to intersect + * + * Creates a new #GstCaps that contains all the formats that are common + * to both @caps1 and @caps2. Defaults to %GST_CAPS_INTERSECT_ZIG_ZAG mode. + * + * Returns: the new #GstCaps + */ +GstCaps * +gst_caps_intersect (const GstCaps * caps1, const GstCaps * caps2) +{ + return gst_caps_intersect_full (caps1, caps2, GST_CAPS_INTERSECT_ZIG_ZAG); +} + + +/* subtract operation */ + +typedef struct +{ + const GstStructure *subtract_from; + GSList *put_into; +} +SubtractionEntry; + +static gboolean +gst_caps_structure_subtract_field (GQuark field_id, const GValue * value, + gpointer user_data) +{ + SubtractionEntry *e = user_data; + GValue subtraction = { 0, }; + const GValue *other; + GstStructure *structure; + + other = gst_structure_id_get_value (e->subtract_from, field_id); + if (!other) { + return FALSE; + } + if (!gst_value_subtract (&subtraction, other, value)) + return TRUE; + if (gst_value_compare (&subtraction, other) == GST_VALUE_EQUAL) { + g_value_unset (&subtraction); + return FALSE; + } else { + structure = gst_structure_copy (e->subtract_from); + gst_structure_id_set_value (structure, field_id, &subtraction); + g_value_unset (&subtraction); + e->put_into = g_slist_prepend (e->put_into, structure); + return TRUE; + } +} + +static gboolean +gst_caps_structure_subtract (GSList ** into, const GstStructure * minuend, + const GstStructure * subtrahend) +{ + SubtractionEntry e; + gboolean ret; + + e.subtract_from = minuend; + e.put_into = NULL; + + ret = gst_structure_foreach ((GstStructure *) subtrahend, + gst_caps_structure_subtract_field, &e); + if (ret) { + *into = e.put_into; + } else { + GSList *walk; + + for (walk = e.put_into; walk; walk = g_slist_next (walk)) { + gst_structure_free (walk->data); + } + g_slist_free (e.put_into); + } + return ret; +} + +/** + * gst_caps_subtract: + * @minuend: #GstCaps to subtract from + * @subtrahend: #GstCaps to subtract + * + * Subtracts the @subtrahend from the @minuend. + * <note>This function does not work reliably if optional properties for caps + * are included on one caps and omitted on the other.</note> + * + * Returns: the resulting caps + */ +GstCaps * +gst_caps_subtract (const GstCaps * minuend, const GstCaps * subtrahend) +{ + guint i, j, sublen; + GstStructure *min; + GstStructure *sub; + GstCaps *dest = NULL, *src; + + g_return_val_if_fail (minuend != NULL, NULL); + g_return_val_if_fail (subtrahend != NULL, NULL); + + if (CAPS_IS_EMPTY (minuend) || CAPS_IS_ANY (subtrahend)) { + return gst_caps_new_empty (); + } + if (CAPS_IS_EMPTY_SIMPLE (subtrahend)) + return _gst_caps_copy (minuend); + + /* FIXME: Do we want this here or above? + The reason we need this is that there is no definition about what + ANY means for specific types, so it's not possible to reduce ANY partially + You can only remove everything or nothing and that is done above. + Note: there's a test that checks this behaviour. */ + g_return_val_if_fail (!CAPS_IS_ANY (minuend), NULL); + sublen = GST_CAPS_LEN (subtrahend); + g_assert (sublen > 0); + + src = _gst_caps_copy (minuend); + for (i = 0; i < sublen; i++) { + guint srclen; + + sub = gst_caps_get_structure_unchecked (subtrahend, i); + if (dest) { + gst_caps_unref (src); + src = dest; + } + dest = gst_caps_new_empty (); + srclen = GST_CAPS_LEN (src); + for (j = 0; j < srclen; j++) { + min = gst_caps_get_structure_unchecked (src, j); + if (gst_structure_get_name_id (min) == gst_structure_get_name_id (sub)) { + GSList *list; + + if (gst_caps_structure_subtract (&list, min, sub)) { + GSList *walk; + + for (walk = list; walk; walk = g_slist_next (walk)) { + gst_caps_append_structure_unchecked (dest, + (GstStructure *) walk->data); + } + g_slist_free (list); + } else { + gst_caps_append_structure_unchecked (dest, gst_structure_copy (min)); + } + } else { + gst_caps_append_structure_unchecked (dest, gst_structure_copy (min)); + } + } + if (CAPS_IS_EMPTY_SIMPLE (dest)) { + gst_caps_unref (src); + return dest; + } + } + + gst_caps_unref (src); + gst_caps_do_simplify (dest); + return dest; +} + +/* union operation */ + +#if 0 +static GstStructure * +gst_caps_structure_union (const GstStructure * struct1, + const GstStructure * struct2) +{ + int i; + GstStructure *dest; + const GstStructureField *field1; + const GstStructureField *field2; + int ret; + + /* FIXME this doesn't actually work */ + + if (struct1->name != struct2->name) + return NULL; + + dest = gst_structure_id_empty_new (struct1->name); + + for (i = 0; i < struct1->fields->len; i++) { + GValue dest_value = { 0 }; + + field1 = GST_STRUCTURE_FIELD (struct1, i); + field2 = gst_structure_id_get_field (struct2, field1->name); + + if (field2 == NULL) { + continue; + } else { + if (gst_value_union (&dest_value, &field1->value, &field2->value)) { + gst_structure_set_value (dest, g_quark_to_string (field1->name), + &dest_value); + } else { + ret = gst_value_compare (&field1->value, &field2->value); + } + } + } + + return dest; +} +#endif + +/** + * gst_caps_union: + * @caps1: a #GstCaps to union + * @caps2: a #GstCaps to union + * + * Creates a new #GstCaps that contains all the formats that are in + * either @caps1 and @caps2. + * + * Returns: the new #GstCaps + */ +GstCaps * +gst_caps_union (const GstCaps * caps1, const GstCaps * caps2) +{ + GstCaps *dest1; + GstCaps *dest2; + + /* NULL pointers are no correct GstCaps */ + g_return_val_if_fail (caps1 != NULL, NULL); + g_return_val_if_fail (caps2 != NULL, NULL); + + if (CAPS_IS_EMPTY (caps1)) + return _gst_caps_copy (caps2); + + if (CAPS_IS_EMPTY (caps2)) + return _gst_caps_copy (caps1); + + if (CAPS_IS_ANY (caps1) || CAPS_IS_ANY (caps2)) + return gst_caps_new_any (); + + dest1 = _gst_caps_copy (caps1); + dest2 = _gst_caps_copy (caps2); + gst_caps_append (dest1, dest2); + + gst_caps_do_simplify (dest1); + return dest1; +} + +/* normalize/simplify operations */ + +typedef struct _NormalizeForeach +{ + GstCaps *caps; + GstStructure *structure; +} +NormalizeForeach; + +static gboolean +gst_caps_normalize_foreach (GQuark field_id, const GValue * value, gpointer ptr) +{ + NormalizeForeach *nf = (NormalizeForeach *) ptr; + GValue val = { 0 }; + guint i; + + if (G_VALUE_TYPE (value) == GST_TYPE_LIST) { + guint len = gst_value_list_get_size (value); + for (i = 1; i < len; i++) { + const GValue *v = gst_value_list_get_value (value, i); + GstStructure *structure = gst_structure_copy (nf->structure); + + gst_structure_id_set_value (structure, field_id, v); + gst_caps_append_structure_unchecked (nf->caps, structure); + } + + gst_value_init_and_copy (&val, gst_value_list_get_value (value, 0)); + gst_structure_id_set_value (nf->structure, field_id, &val); + g_value_unset (&val); + + return FALSE; + } + return TRUE; +} + +/** + * gst_caps_normalize: + * @caps: a #GstCaps to normalize + * + * Creates a new #GstCaps that represents the same set of formats as + * @caps, but contains no lists. Each list is expanded into separate + * @GstStructures. + * + * Returns: the new #GstCaps + */ +GstCaps * +gst_caps_normalize (const GstCaps * caps) +{ + NormalizeForeach nf; + GstCaps *newcaps; + guint i; + + g_return_val_if_fail (GST_IS_CAPS (caps), NULL); + + newcaps = _gst_caps_copy (caps); + nf.caps = newcaps; + + for (i = 0; i < gst_caps_get_size (newcaps); i++) { + nf.structure = gst_caps_get_structure_unchecked (newcaps, i); + + while (!gst_structure_foreach (nf.structure, + gst_caps_normalize_foreach, &nf)); + } + + return newcaps; +} + +static gint +gst_caps_compare_structures (gconstpointer one, gconstpointer two) +{ + gint ret; + const GstStructure *struct1 = *((const GstStructure **) one); + const GstStructure *struct2 = *((const GstStructure **) two); + + /* FIXME: this orders alphabetically, but ordering the quarks might be faster + So what's the best way? */ + ret = strcmp (gst_structure_get_name (struct1), + gst_structure_get_name (struct2)); + if (ret) + return ret; + + return gst_structure_n_fields (struct2) - gst_structure_n_fields (struct1); +} + +typedef struct +{ + GQuark name; + GValue value; + GstStructure *compare; +} +UnionField; + +static gboolean +gst_caps_structure_figure_out_union (GQuark field_id, const GValue * value, + gpointer user_data) +{ + UnionField *u = user_data; + const GValue *val = gst_structure_id_get_value (u->compare, field_id); + + if (!val) { + if (u->name) + g_value_unset (&u->value); + return FALSE; + } + if (gst_value_compare (val, value) == GST_VALUE_EQUAL) + return TRUE; + if (u->name) { + g_value_unset (&u->value); + return FALSE; + } + u->name = field_id; + gst_value_union (&u->value, val, value); + return TRUE; +} + +static gboolean +gst_caps_structure_simplify (GstStructure ** result, + const GstStructure * simplify, GstStructure * compare) +{ + GSList *list; + UnionField field = { 0, {0,}, NULL }; + + /* try to subtract to get a real subset */ + if (gst_caps_structure_subtract (&list, simplify, compare)) { + if (list == NULL) { /* no result */ + *result = NULL; + return TRUE; + } else if (list->next == NULL) { /* one result */ + *result = list->data; + g_slist_free (list); + return TRUE; + } else { /* multiple results */ + g_slist_foreach (list, (GFunc) gst_structure_free, NULL); + g_slist_free (list); + list = NULL; + } + } + + /* try to union both structs */ + field.compare = compare; + if (gst_structure_foreach ((GstStructure *) simplify, + gst_caps_structure_figure_out_union, &field)) { + gboolean ret = FALSE; + + /* now we know all of simplify's fields are the same in compare + * but at most one field: field.name */ + if (G_IS_VALUE (&field.value)) { + if (gst_structure_n_fields (simplify) == gst_structure_n_fields (compare)) { + gst_structure_id_set_value (compare, field.name, &field.value); + *result = NULL; + ret = TRUE; + } + g_value_unset (&field.value); + } else if (gst_structure_n_fields (simplify) <= + gst_structure_n_fields (compare)) { + /* compare is just more specific, will be optimized away later */ + /* FIXME: do this here? */ + GST_LOG ("found a case that will be optimized later."); + } else { + gchar *one = gst_structure_to_string (simplify); + gchar *two = gst_structure_to_string (compare); + + GST_ERROR + ("caps mismatch: structures %s and %s claim to be possible to unify, but aren't", + one, two); + g_free (one); + g_free (two); + } + return ret; + } + + return FALSE; +} + +static void +gst_caps_switch_structures (GstCaps * caps, GstStructure * old, + GstStructure * new, gint i) +{ + gst_structure_set_parent_refcount (old, NULL); + gst_structure_free (old); + gst_structure_set_parent_refcount (new, &GST_CAPS_REFCOUNT (caps)); + g_ptr_array_index (GST_CAPS_ARRAY (caps), i) = new; +} + +/** + * gst_caps_do_simplify: + * @caps: a #GstCaps to simplify + * + * Modifies the given @caps inplace into a representation that represents the + * same set of formats, but in a simpler form. Component structures that are + * identical are merged. Component structures that have values that can be + * merged are also merged. + * + * Returns: TRUE, if the caps could be simplified + */ +gboolean +gst_caps_do_simplify (GstCaps * caps) +{ + GstStructure *simplify, *compare, *result = NULL; + gint i, j, start; + gboolean changed = FALSE; + + g_return_val_if_fail (caps != NULL, FALSE); + g_return_val_if_fail (IS_WRITABLE (caps), FALSE); + + if (gst_caps_get_size (caps) < 2) + return FALSE; + + g_ptr_array_sort (GST_CAPS_ARRAY (caps), gst_caps_compare_structures); + + start = GST_CAPS_LEN (caps) - 1; + for (i = GST_CAPS_LEN (caps) - 1; i >= 0; i--) { + simplify = gst_caps_get_structure_unchecked (caps, i); + if (gst_structure_get_name_id (simplify) != + gst_structure_get_name_id (gst_caps_get_structure_unchecked (caps, + start))) + start = i; + for (j = start; j >= 0; j--) { + if (j == i) + continue; + compare = gst_caps_get_structure_unchecked (caps, j); + if (gst_structure_get_name_id (simplify) != + gst_structure_get_name_id (compare)) { + break; + } + if (gst_caps_structure_simplify (&result, simplify, compare)) { + if (result) { + gst_caps_switch_structures (caps, simplify, result, i); + simplify = result; + } else { + gst_caps_remove_structure (caps, i); + start--; + break; + } + changed = TRUE; + } + } + } + + if (!changed) + return FALSE; + + /* gst_caps_do_simplify (caps); */ + return TRUE; +} + +/** + * gst_caps_fixate: + * @caps: a #GstCaps to fixate + * + * Modifies the given @caps inplace into a representation with only fixed + * values. First the caps will be truncated and then the first structure will be + * fixated with gst_structure_fixate(). @caps should be writable. + */ +void +gst_caps_fixate (GstCaps * caps) +{ + GstStructure *s; + + g_return_if_fail (GST_IS_CAPS (caps)); + g_return_if_fail (IS_WRITABLE (caps)); + + /* default fixation */ + gst_caps_truncate (caps); + s = gst_caps_get_structure (caps, 0); + gst_structure_fixate (s); +} + +/* utility */ + +/** + * gst_caps_to_string: + * @caps: a #GstCaps + * + * Converts @caps to a string representation. This string representation + * can be converted back to a #GstCaps by gst_caps_from_string(). + * + * For debugging purposes its easier to do something like this: + * |[ + * GST_LOG ("caps are %" GST_PTR_FORMAT, caps); + * ]| + * This prints the caps in human readble form. + * + * Returns: (transfer full): a newly allocated string representing @caps. + */ +gchar * +gst_caps_to_string (const GstCaps * caps) +{ + guint i, slen, clen; + GString *s; + + /* NOTE: This function is potentially called by the debug system, + * so any calls to gst_log() (and GST_DEBUG(), GST_LOG(), etc.) + * should be careful to avoid recursion. This includes any functions + * called by gst_caps_to_string. In particular, calls should + * not use the GST_PTR_FORMAT extension. */ + + if (caps == NULL) { + return g_strdup ("NULL"); + } + if (CAPS_IS_ANY (caps)) { + return g_strdup ("ANY"); + } + if (CAPS_IS_EMPTY_SIMPLE (caps)) { + return g_strdup ("EMPTY"); + } + + /* estimate a rough string length to avoid unnecessary reallocs in GString */ + slen = 0; + clen = GST_CAPS_LEN (caps); + for (i = 0; i < clen; i++) { + slen += + STRUCTURE_ESTIMATED_STRING_LEN (gst_caps_get_structure_unchecked (caps, + i)); + } + + s = g_string_sized_new (slen); + for (i = 0; i < clen; i++) { + GstStructure *structure; + + if (i > 0) { + /* ';' is now added by gst_structure_to_string */ + g_string_append_c (s, ' '); + } + + structure = gst_caps_get_structure_unchecked (caps, i); + priv_gst_structure_append_to_gstring (structure, s); + } + if (s->len && s->str[s->len - 1] == ';') { + /* remove latest ';' */ + s->str[--s->len] = '\0'; + } + return g_string_free (s, FALSE); +} + +static gboolean +gst_caps_from_string_inplace (GstCaps * caps, const gchar * string) +{ + GstStructure *structure; + gchar *s; + + if (strcmp ("ANY", string) == 0) { + GST_CAPS_FLAGS (caps) = GST_CAPS_FLAGS_ANY; + return TRUE; + } + if (strcmp ("EMPTY", string) == 0) { + return TRUE; + } + + structure = gst_structure_from_string (string, &s); + if (structure == NULL) { + return FALSE; + } + gst_caps_append_structure_unchecked (caps, structure); + + do { + + while (g_ascii_isspace (*s)) + s++; + if (*s == '\0') { + break; + } + structure = gst_structure_from_string (s, &s); + if (structure == NULL) { + return FALSE; + } + gst_caps_append_structure_unchecked (caps, structure); + + } while (TRUE); + + return TRUE; +} + +/** + * gst_caps_from_string: + * @string: a string to convert to #GstCaps + * + * Converts @caps from a string representation. + * + * Returns: (transfer full): a newly allocated #GstCaps + */ +GstCaps * +gst_caps_from_string (const gchar * string) +{ + GstCaps *caps; + + g_return_val_if_fail (string, FALSE); + + caps = gst_caps_new_empty (); + if (gst_caps_from_string_inplace (caps, string)) { + return caps; + } else { + gst_caps_unref (caps); + return NULL; + } +} + +static void +gst_caps_transform_to_string (const GValue * src_value, GValue * dest_value) +{ + g_return_if_fail (G_IS_VALUE (src_value)); + g_return_if_fail (G_IS_VALUE (dest_value)); + g_return_if_fail (G_VALUE_HOLDS (src_value, GST_TYPE_CAPS)); + g_return_if_fail (G_VALUE_HOLDS (dest_value, G_TYPE_STRING) + || G_VALUE_HOLDS (dest_value, G_TYPE_POINTER)); + + g_value_take_string (dest_value, + gst_caps_to_string (gst_value_get_caps (src_value))); +} diff --git a/gst/gstcaps.h b/gst/gstcaps.h new file mode 100644 index 0000000..567f7b8 --- /dev/null +++ b/gst/gstcaps.h @@ -0,0 +1,419 @@ +/* GStreamer + * Copyright (C) 2003 David A. Schleef <ds@schleef.org> + * + * 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_CAPS_H__ +#define __GST_CAPS_H__ + +#include <gst/gstconfig.h> +#include <gst/gstminiobject.h> +#include <gst/gststructure.h> +#include <gst/glib-compat.h> + +G_BEGIN_DECLS + +extern GType _gst_caps_type; + +#define GST_TYPE_CAPS (_gst_caps_type) +#define GST_IS_CAPS(obj) (GST_IS_MINI_OBJECT_TYPE((obj), GST_TYPE_CAPS)) +#define GST_CAPS_CAST(obj) ((GstCaps*)(obj)) +#define GST_CAPS(obj) (GST_CAPS_CAST(obj)) + +#define GST_TYPE_STATIC_CAPS (gst_static_caps_get_type()) + +/** + * GstCapsFlags: + * @GST_CAPS_FLAGS_ANY: Caps has no specific content, but can contain + * anything. + * + * Extra flags for a caps. + */ +typedef enum { + GST_CAPS_FLAGS_ANY = (GST_MINI_OBJECT_FLAG_LAST << 0) +} GstCapsFlags; + +/** + * GstCapsIntersectMode: + * @GST_CAPS_INTERSECT_ZIG_ZAG : Zig-zags over both caps. + * @GST_CAPS_INTERSECT_FIRST : Keeps the first caps order. + * + * Modes of caps intersection + * + * @GST_CAPS_INTERSECT_ZIG_ZAG tries to preserve overall order of both caps + * by iterating on the caps' structures as the following matrix shows: + * |[ + * caps1 + * +------------- + * | 1 2 4 7 + * caps2 | 3 5 8 10 + * | 6 9 11 12 + * ]| + * Used when there is no explicit precedence of one caps over the other. e.g. + * tee's sink pad getcaps function, it will probe its src pad peers' for their + * caps and intersect them with this mode. + * + * @GST_CAPS_INTERSECT_FIRST is useful when an element wants to preserve + * another element's caps priority order when intersecting with its own caps. + * Example: If caps1 is [A, B, C] and caps2 is [E, B, D, A], the result + * would be [A, B], maintaining the first caps priority on the intersection. + * + * Since: 0.10.33 + */ +typedef enum { + GST_CAPS_INTERSECT_ZIG_ZAG = 0, + GST_CAPS_INTERSECT_FIRST = 1 +} GstCapsIntersectMode; + +/** + * GST_CAPS_ANY: + * + * Means that the element/pad can output 'anything'. Useful for elements + * that output unknown media, such as filesrc. + */ +#define GST_CAPS_ANY gst_caps_new_any() +/** + * GST_CAPS_NONE: + * + * The opposite of %GST_CAPS_ANY: it means that the pad/element outputs an + * undefined media type that can not be detected. + */ +#define GST_CAPS_NONE gst_caps_new_empty() + +/** + * GST_STATIC_CAPS_ANY: + * + * Creates a new #GstCaps static caps that matches anything. + * This can be used in pad templates. + */ +#define GST_STATIC_CAPS_ANY GST_STATIC_CAPS("ANY") +/** + * GST_STATIC_CAPS_NONE: + * + * Creates a new #GstCaps static caps that matches nothing. + * This can be used in pad templates. + */ +#define GST_STATIC_CAPS_NONE GST_STATIC_CAPS("NONE") + +/** + * GST_CAPS_IS_SIMPLE: + * @caps: the #GstCaps instance to check + * + * Convenience macro that checks if the number of structures in the given caps + * is exactly one. + */ +#define GST_CAPS_IS_SIMPLE(caps) (gst_caps_get_size(caps) == 1) + +/** + * GST_STATIC_CAPS: + * @string: the string describing the caps + * + * Creates a new #GstCaps static caps from an input string. + * This can be used in pad templates. + */ +#define GST_STATIC_CAPS(string) \ +{ \ + /* miniobject */ { { 0, 0, 0, 0, NULL, NULL, NULL }, \ + /* caps */ NULL }, \ + /* string */ string, \ + GST_PADDING_INIT \ +} + +typedef struct _GstCaps GstCaps; +typedef struct _GstStaticCaps GstStaticCaps; + +/** + * GST_CAPS_FLAGS: + * @caps: a #GstCaps. + * + * A flags word containing #GstCapsFlags flags set on this caps. + */ +#define GST_CAPS_FLAGS(caps) GST_MINI_OBJECT_FLAGS(caps) + +/* refcount */ +/** + * GST_CAPS_REFCOUNT: + * @caps: a #GstCaps + * + * Get access to the reference count field of the caps + */ +#define GST_CAPS_REFCOUNT(caps) GST_MINI_OBJECT_REFCOUNT(caps) +/** + * GST_CAPS_REFCOUNT_VALUE: + * @caps: a #GstCaps + * + * Get the reference count value of the caps. + */ +#define GST_CAPS_REFCOUNT_VALUE(caps) GST_MINI_OBJECT_REFCOUNT_VALUE(caps) + +/** + * GST_CAPS_FLAG_IS_SET: + * @caps: a #GstCaps. + * @flag: the #GstCapsFlags to check. + * + * Gives the status of a specific flag on a caps. + */ +#define GST_CAPS_FLAG_IS_SET(caps,flag) GST_MINI_OBJECT_FLAG_IS_SET (caps, flag) +/** + * GST_CAPS_FLAG_SET: + * @caps: a #GstCaps. + * @flag: the #GstCapsFlags to set. + * + * Sets a caps flag on a caps. + */ +#define GST_CAPS_FLAG_SET(caps,flag) GST_MINI_OBJECT_FLAG_SET (caps, flag) +/** + * GST_CAPS_FLAG_UNSET: + * @caps: a #GstCaps. + * @flag: the #GstCapsFlags to clear. + * + * Clears a caps flag. + */ +#define GST_CAPS_FLAG_UNSET(caps,flag) GST_MINI_OBJECT_FLAG_UNSET (caps, flag) + +/* refcounting */ +/** + * gst_caps_ref: + * @caps: the #GstCaps to reference + * + * Add a reference to a #GstCaps object. + * + * From this point on, until the caller calls gst_caps_unref() or + * gst_caps_make_writable(), it is guaranteed that the caps object will not + * change. This means its structures won't change, etc. To use a #GstCaps + * object, you must always have a refcount on it -- either the one made + * implicitly by e.g. gst_caps_new_simple(), or via taking one explicitly with + * this function. + * + * Returns: the same #GstCaps object. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstCaps * gst_caps_ref (GstCaps * caps); +#endif + +static inline GstCaps * +gst_caps_ref (GstCaps * caps) +{ + return (GstCaps *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (caps)); +} + +/** + * gst_caps_unref: + * @caps: a #GstCaps. + * + * Unref a #GstCaps and and free all its structures and the + * structures' values when the refcount reaches 0. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void gst_caps_unref (GstCaps * caps); +#endif + +static inline void +gst_caps_unref (GstCaps * caps) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (caps)); +} + +/* copy caps */ +/** + * gst_caps_copy: + * @caps: a #GstCaps. + * + * Creates a new #GstCaps as a copy of the old @caps. The new caps will have a + * refcount of 1, owned by the caller. The structures are copied as well. + * + * Note that this function is the semantic equivalent of a gst_caps_ref() + * followed by a gst_caps_make_writable(). If you only want to hold on to a + * reference to the data, you should use gst_caps_ref(). + * + * When you are finished with the caps, call gst_caps_unref() on it. + * + * Returns: the new #GstCaps + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstCaps * gst_caps_copy (const GstCaps * caps); +#endif + +static inline GstCaps * +gst_caps_copy (const GstCaps * caps) +{ + return GST_CAPS (gst_mini_object_copy (GST_MINI_OBJECT_CAST (caps))); +} + +/** + * gst_caps_is_writable: + * @caps: a #GstCaps + * + * Tests if you can safely modify @caps. It is only safe to modify caps when + * there is only one owner of the caps - ie, the refcount is 1. + */ +#define gst_caps_is_writable(caps) gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (caps)) + +/** + * gst_caps_make_writable: + * @caps: (transfer full): a #GstCaps + * + * Returns a writable copy of @caps. + * + * If there is only one reference count on @caps, the caller must be the owner, + * and so this function will return the caps object unchanged. If on the other + * hand there is more than one reference on the object, a new caps object will + * be returned. The caller's reference on @caps will be removed, and instead the + * caller will own a reference to the returned object. + * + * In short, this function unrefs the caps in the argument and refs the caps + * that it returns. Don't access the argument after calling this function. See + * also: gst_caps_ref(). + * + * Returns: (transfer full): a writable caps which may or may not be the + * same as @caps + */ +#define gst_caps_make_writable(caps) GST_CAPS_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (caps))) + +/** + * gst_caps_replace: + * @ocaps: (inout) (transfer full): pointer to a pointer to a #GstCaps to be + * replaced. + * @ncaps: (transfer none) (allow-none): pointer to a #GstCaps that will + * replace the caps pointed to by @ocaps. + * + * Modifies a pointer to a #GstCaps to point to a different #GstCaps. The + * modification is done atomically (so this is useful for ensuring thread safety + * in some cases), and the reference counts are updated appropriately (the old + * caps is unreffed, the new is reffed). + * + * Either @ncaps or the #GstCaps pointed to by @ocaps may be NULL. + */ +#define gst_caps_replace(ocaps,ncaps) \ +G_STMT_START { \ + GstCaps **___ocapsaddr = (GstCaps **)(ocaps); \ + gst_mini_object_replace ((GstMiniObject **)___ocapsaddr, \ + GST_MINI_OBJECT_CAST (ncaps)); \ +} G_STMT_END + +/** + * GstCaps: + * @mini_object: the parent type + * + * Object describing media types. + */ +struct _GstCaps { + GstMiniObject mini_object; + + /*< private >*/ + gpointer priv; +}; + +/** + * GstStaticCaps: + * @caps: the cached #GstCaps + * @string: a string describing a caps + * + * Datastructure to initialize #GstCaps from a string description usually + * used in conjunction with GST_STATIC_CAPS() and gst_static_caps_get() to + * instantiate a #GstCaps. + */ +struct _GstStaticCaps { + /*< public >*/ + GstCaps caps; + const char *string; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_caps_get_type (void); + +GstCaps * gst_caps_new_empty (void); +GstCaps * gst_caps_new_any (void); +GstCaps * gst_caps_new_simple (const char *media_type, + const char *fieldname, + ...); +GstCaps * gst_caps_new_full (GstStructure *struct1, ...); +GstCaps * gst_caps_new_full_valist (GstStructure *structure, + va_list var_args); + +GType gst_static_caps_get_type (void); +GstCaps * gst_static_caps_get (GstStaticCaps *static_caps); +void gst_static_caps_cleanup (GstStaticCaps *static_caps); + +/* manipulation */ +void gst_caps_append (GstCaps *caps1, + GstCaps *caps2); +void gst_caps_merge (GstCaps *caps1, + GstCaps *caps2); +void gst_caps_append_structure (GstCaps *caps, + GstStructure *structure); +void gst_caps_remove_structure (GstCaps *caps, guint idx); +void gst_caps_merge_structure (GstCaps *caps, + GstStructure *structure); +guint gst_caps_get_size (const GstCaps *caps); +GstStructure * gst_caps_get_structure (const GstCaps *caps, + guint index); +GstStructure * gst_caps_steal_structure (GstCaps *caps, + guint index); +GstCaps * gst_caps_copy_nth (const GstCaps *caps, guint nth); +void gst_caps_truncate (GstCaps *caps); +void gst_caps_set_value (GstCaps *caps, + const char *field, + const GValue *value); +void gst_caps_set_simple (GstCaps *caps, + const char *field, ...) G_GNUC_NULL_TERMINATED; +void gst_caps_set_simple_valist (GstCaps *caps, + const char *field, + va_list varargs); + +/* tests */ +gboolean gst_caps_is_any (const GstCaps *caps); +gboolean gst_caps_is_empty (const GstCaps *caps); +gboolean gst_caps_is_fixed (const GstCaps *caps); +gboolean gst_caps_is_always_compatible (const GstCaps *caps1, + const GstCaps *caps2); +gboolean gst_caps_is_subset (const GstCaps *subset, + const GstCaps *superset); +gboolean gst_caps_is_subset_structure (const GstCaps *caps, + const GstStructure *structure); +gboolean gst_caps_is_equal (const GstCaps *caps1, + const GstCaps *caps2); +gboolean gst_caps_is_equal_fixed (const GstCaps *caps1, + const GstCaps *caps2); +gboolean gst_caps_can_intersect (const GstCaps * caps1, + const GstCaps * caps2); + + +/* operations */ +GstCaps * gst_caps_intersect (const GstCaps *caps1, + const GstCaps *caps2); +GstCaps * gst_caps_intersect_full (const GstCaps *caps1, + const GstCaps *caps2, + GstCapsIntersectMode mode); +GstCaps * gst_caps_subtract (const GstCaps *minuend, + const GstCaps *subtrahend); +GstCaps * gst_caps_union (const GstCaps *caps1, + const GstCaps *caps2); +GstCaps * gst_caps_normalize (const GstCaps *caps); +gboolean gst_caps_do_simplify (GstCaps *caps); + +void gst_caps_fixate (GstCaps *caps); + +/* utility */ +gchar * gst_caps_to_string (const GstCaps *caps); +GstCaps * gst_caps_from_string (const gchar *string); + +G_END_DECLS + +#endif /* __GST_CAPS_H__ */ diff --git a/gst/gstchildproxy.c b/gst/gstchildproxy.c new file mode 100644 index 0000000..5607b49 --- /dev/null +++ b/gst/gstchildproxy.c @@ -0,0 +1,538 @@ +/* GStreamer + * Copyright (C) 2005 Stefan Kost <ensonic@users.sf.net> + * + * gstchildproxy.c: interface for multi child elements + * + * 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:gstchildproxy + * @short_description: Interface for multi child elements. + * @see_also: #GstBin + * + * This interface abstracts handling of property sets for elements with + * children. Imagine elements such as mixers or polyphonic generators. They all + * have multiple #GstPad or some kind of voice objects. Another use case are + * container elements like #GstBin. + * The element implementing the interface acts as a parent for those child + * objects. + * + * By implementing this interface the child properties can be accessed from the + * parent element by using gst_child_proxy_get() and gst_child_proxy_set(). + * + * Property names are written as "child-name::property-name". The whole naming + * scheme is recursive. Thus "child1::child2::property" is valid too, if + * "child1" and "child2" implement the #GstChildProxy interface. + */ +/* FIXME-0.11: + * it would be nice to make gst_child_proxy_get_child_by_name virtual too and + * use GObject instead of GstObject. We could eventually provide the current + * implementation as a default if children are GstObjects. + * This change would allow to propose the interface for inclusion with + * glib/gobject. IMHO this is useful for GtkContainer and compound widgets too. + */ + +#include "gst_private.h" + +#include "gstchildproxy.h" +#include "gstmarshal.h" +#include <gobject/gvaluecollector.h> + +/* signals */ +enum +{ + CHILD_ADDED, + CHILD_REMOVED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/** + * gst_child_proxy_get_child_by_name: + * @parent: the parent object to get the child from + * @name: the childs name + * + * Looks up a child element by the given name. + * + * Implementors can use #GstObject together with gst_object_get_name() + * + * Returns: (transfer full): the child object or %NULL if not found. Unref + * after usage. + * + * MT safe. + */ +GstObject * +gst_child_proxy_get_child_by_name (GstChildProxy * parent, const gchar * name) +{ + guint count, i; + GstObject *object, *result; + gchar *object_name; + + g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), NULL); + g_return_val_if_fail (name != NULL, NULL); + + result = NULL; + + count = gst_child_proxy_get_children_count (parent); + for (i = 0; i < count; i++) { + gboolean eq; + + if (!(object = gst_child_proxy_get_child_by_index (parent, i))) + continue; + + object_name = gst_object_get_name (object); + if (object_name == NULL) { + g_warning ("child %u of parent %s has no name", i, + GST_OBJECT_NAME (parent)); + goto next; + } + eq = g_str_equal (object_name, name); + g_free (object_name); + + if (eq) { + result = object; + break; + } + next: + gst_object_unref (object); + } + return result; +} + +/** + * gst_child_proxy_get_child_by_index: + * @parent: the parent object to get the child from + * @index: the childs position in the child list + * + * Fetches a child by its number. + * + * Returns: (transfer full): the child object or %NULL if not found (index + * too high). Unref after usage. + * + * MT safe. + */ +GstObject * +gst_child_proxy_get_child_by_index (GstChildProxy * parent, guint index) +{ + g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), NULL); + + return (GST_CHILD_PROXY_GET_INTERFACE (parent)->get_child_by_index (parent, + index)); +} + +/** + * gst_child_proxy_get_children_count: + * @parent: the parent object + * + * Gets the number of child objects this parent contains. + * + * Returns: the number of child objects + * + * MT safe. + */ +guint +gst_child_proxy_get_children_count (GstChildProxy * parent) +{ + g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), 0); + + return (GST_CHILD_PROXY_GET_INTERFACE (parent)->get_children_count (parent)); +} + +/** + * gst_child_proxy_lookup: + * @object: object to lookup the property in + * @name: name of the property to look up + * @target: (out) (allow-none) (transfer full): pointer to a #GstObject that + * takes the real object to set property on + * @pspec: (out) (allow-none) (transfer full): pointer to take the #GParamSpec + * describing the property + * + * Looks up which object and #GParamSpec would be effected by the given @name. + * + * Returns: TRUE if @target and @pspec could be found. FALSE otherwise. In that + * case the values for @pspec and @target are not modified. Unref @target after + * usage. + * + * MT safe. + */ +gboolean +gst_child_proxy_lookup (GstObject * object, const gchar * name, + GstObject ** target, GParamSpec ** pspec) +{ + gboolean res = FALSE; + gchar **names, **current; + + g_return_val_if_fail (GST_IS_OBJECT (object), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + gst_object_ref (object); + + current = names = g_strsplit (name, "::", -1); + while (current[1]) { + GstObject *next; + + if (!GST_IS_CHILD_PROXY (object)) { + GST_INFO + ("object %s is not a parent, so you cannot request a child by name %s", + GST_OBJECT_NAME (object), current[0]); + break; + } + next = gst_child_proxy_get_child_by_name (GST_CHILD_PROXY (object), + current[0]); + if (!next) { + GST_INFO ("no such object %s", current[0]); + break; + } + gst_object_unref (object); + object = next; + current++; + } + if (current[1] == NULL) { + GParamSpec *spec = + g_object_class_find_property (G_OBJECT_GET_CLASS (object), current[0]); + if (spec == NULL) { + GST_INFO ("no param spec named %s", current[0]); + } else { + if (pspec) + *pspec = spec; + if (target) { + gst_object_ref (object); + *target = object; + } + res = TRUE; + } + } + gst_object_unref (object); + g_strfreev (names); + return res; +} + +/** + * gst_child_proxy_get_property: + * @object: object to query + * @name: name of the property + * @value: (out caller-allocates): a #GValue that should take the result. + * + * Gets a single property using the GstChildProxy mechanism. + * You are responsible for freeing it by calling g_value_unset() + */ +void +gst_child_proxy_get_property (GstObject * object, const gchar * name, + GValue * value) +{ + GParamSpec *pspec; + GstObject *target; + + g_return_if_fail (GST_IS_OBJECT (object)); + g_return_if_fail (name != NULL); + g_return_if_fail (G_IS_VALUE (value)); + + if (!gst_child_proxy_lookup (object, name, &target, &pspec)) + goto not_found; + + g_object_get_property (G_OBJECT (target), pspec->name, value); + gst_object_unref (target); + + return; + +not_found: + { + g_warning ("no property %s in object %s", name, GST_OBJECT_NAME (object)); + return; + } +} + +/** + * gst_child_proxy_get_valist: + * @object: the object to query + * @first_property_name: name of the first property to get + * @var_args: return location for the first property, followed optionally by more name/return location pairs, followed by NULL + * + * Gets properties of the parent object and its children. + */ +void +gst_child_proxy_get_valist (GstObject * object, + const gchar * first_property_name, va_list var_args) +{ + const gchar *name; + gchar *error = NULL; + GValue value = { 0, }; + GParamSpec *pspec; + GstObject *target; + + g_return_if_fail (G_IS_OBJECT (object)); + + name = first_property_name; + + /* iterate over pairs */ + while (name) { + if (!gst_child_proxy_lookup (object, name, &target, &pspec)) + goto not_found; + + g_value_init (&value, pspec->value_type); + g_object_get_property (G_OBJECT (target), pspec->name, &value); + gst_object_unref (target); + + G_VALUE_LCOPY (&value, var_args, 0, &error); + if (error) + goto cant_copy; + g_value_unset (&value); + name = va_arg (var_args, gchar *); + } + return; + +not_found: + { + g_warning ("no property %s in object %s", name, GST_OBJECT_NAME (object)); + return; + } +cant_copy: + { + g_warning ("error copying value %s in object %s: %s", pspec->name, + GST_OBJECT_NAME (object), error); + g_value_unset (&value); + return; + } +} + +/** + * gst_child_proxy_get: + * @object: the parent object + * @first_property_name: name of the first property to get + * @...: return location for the first property, followed optionally by more name/return location pairs, followed by NULL + * + * Gets properties of the parent object and its children. + */ +void +gst_child_proxy_get (GstObject * object, const gchar * first_property_name, ...) +{ + va_list var_args; + + g_return_if_fail (GST_IS_OBJECT (object)); + + va_start (var_args, first_property_name); + gst_child_proxy_get_valist (object, first_property_name, var_args); + va_end (var_args); +} + +/** + * gst_child_proxy_set_property: + * @object: the parent object + * @name: name of the property to set + * @value: new #GValue for the property + * + * Sets a single property using the GstChildProxy mechanism. + */ +void +gst_child_proxy_set_property (GstObject * object, const gchar * name, + const GValue * value) +{ + GParamSpec *pspec; + GstObject *target; + + g_return_if_fail (GST_IS_OBJECT (object)); + g_return_if_fail (name != NULL); + g_return_if_fail (G_IS_VALUE (value)); + + if (!gst_child_proxy_lookup (object, name, &target, &pspec)) + goto not_found; + + g_object_set_property (G_OBJECT (target), pspec->name, value); + gst_object_unref (target); + return; + +not_found: + { + g_warning ("cannot set property %s on object %s", name, + GST_OBJECT_NAME (object)); + return; + } +} + +/** + * gst_child_proxy_set_valist: + * @object: the parent object + * @first_property_name: name of the first property to set + * @var_args: value for the first property, followed optionally by more name/value pairs, followed by NULL + * + * Sets properties of the parent object and its children. + */ +void +gst_child_proxy_set_valist (GstObject * object, + const gchar * first_property_name, va_list var_args) +{ + const gchar *name; + gchar *error = NULL; + GValue value = { 0, }; + GParamSpec *pspec; + GstObject *target; + + g_return_if_fail (G_IS_OBJECT (object)); + + name = first_property_name; + + /* iterate over pairs */ + while (name) { + if (!gst_child_proxy_lookup (object, name, &target, &pspec)) + goto not_found; + + G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args, + G_VALUE_NOCOPY_CONTENTS, &error); + + if (error) + goto cant_copy; + + g_object_set_property (G_OBJECT (target), pspec->name, &value); + gst_object_unref (target); + + g_value_unset (&value); + name = va_arg (var_args, gchar *); + } + return; + +not_found: + { + g_warning ("no property %s in object %s", name, GST_OBJECT_NAME (object)); + return; + } +cant_copy: + { + g_warning ("error copying value %s in object %s: %s", pspec->name, + GST_OBJECT_NAME (object), error); + g_value_unset (&value); + gst_object_unref (target); + return; + } +} + +/** + * gst_child_proxy_set: + * @object: the parent object + * @first_property_name: name of the first property to set + * @...: value for the first property, followed optionally by more name/value pairs, followed by NULL + * + * Sets properties of the parent object and its children. + */ +void +gst_child_proxy_set (GstObject * object, const gchar * first_property_name, ...) +{ + va_list var_args; + + g_return_if_fail (GST_IS_OBJECT (object)); + + va_start (var_args, first_property_name); + gst_child_proxy_set_valist (object, first_property_name, var_args); + va_end (var_args); +} + +/** + * gst_child_proxy_child_added: + * @object: the parent object + * @child: the newly added child + * + * Emits the "child-added" signal. + */ +void +gst_child_proxy_child_added (GstObject * object, GstObject * child) +{ + g_signal_emit (G_OBJECT (object), signals[CHILD_ADDED], 0, child); +} + +/** + * gst_child_proxy_child_removed: + * @object: the parent object + * @child: the removed child + * + * Emits the "child-removed" signal. + */ +void +gst_child_proxy_child_removed (GstObject * object, GstObject * child) +{ + g_signal_emit (G_OBJECT (object), signals[CHILD_REMOVED], 0, child); +} + +/* gobject methods */ + +static void +gst_child_proxy_base_init (gpointer g_class) +{ + static gboolean initialized = FALSE; + + if (!initialized) { + /* create interface signals and properties here. */ + /** + * GstChildProxy::child-added: + * @child_proxy: the #GstChildProxy + * @object: the #GObject that was added + * + * Will be emitted after the @object was added to the @child_proxy. + */ + /* FIXME 0.11: use GST_TYPE_OBJECT as GstChildProxy only + * supports GstObjects */ + signals[CHILD_ADDED] = + g_signal_new ("child-added", G_TYPE_FROM_CLASS (g_class), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstChildProxyInterface, + child_added), NULL, NULL, gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + /** + * GstChildProxy::child-removed: + * @child_proxy: the #GstChildProxy + * @object: the #GObject that was removed + * + * Will be emitted after the @object was removed from the @child_proxy. + */ + /* FIXME 0.11: use GST_TYPE_OBJECT as GstChildProxy only + * supports GstObjects */ + signals[CHILD_REMOVED] = + g_signal_new ("child-removed", G_TYPE_FROM_CLASS (g_class), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstChildProxyInterface, + child_removed), NULL, NULL, gst_marshal_VOID__OBJECT, G_TYPE_NONE, + 1, G_TYPE_OBJECT); + + initialized = TRUE; + } +} + +GType +gst_child_proxy_get_type (void) +{ + static volatile gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + static const GTypeInfo info = { + sizeof (GstChildProxyInterface), + gst_child_proxy_base_init, /* base_init */ + NULL, /* base_finalize */ + NULL, /* class_init */ + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL /* instance_init */ + }; + + _type = + g_type_register_static (G_TYPE_INTERFACE, "GstChildProxy", &info, 0); + + g_type_interface_add_prerequisite (_type, GST_TYPE_OBJECT); + g_once_init_leave (&type, (gsize) _type); + } + return type; +} diff --git a/gst/gstchildproxy.h b/gst/gstchildproxy.h new file mode 100644 index 0000000..ebb2d14 --- /dev/null +++ b/gst/gstchildproxy.h @@ -0,0 +1,91 @@ +/* GStreamer + * Copyright (C) 2005 Stefan Kost <ensonic@users.sf.net> + * + * gstchildproxy.h: interface header for multi child elements + * + * 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_CHILD_PROXY_H__ +#define __GST_CHILD_PROXY_H__ + +#include <glib-object.h> +#include <gst/gst.h> + +G_BEGIN_DECLS + + +#define GST_TYPE_CHILD_PROXY (gst_child_proxy_get_type ()) +#define GST_CHILD_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CHILD_PROXY, GstChildProxy)) +#define GST_IS_CHILD_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CHILD_PROXY)) +#define GST_CHILD_PROXY_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GST_TYPE_CHILD_PROXY, GstChildProxyInterface)) + +/** + * GstChildProxy: + * + * Opaque #GstChildProxy data structure. + */ +typedef struct _GstChildProxy GstChildProxy; /* dummy object */ +typedef struct _GstChildProxyInterface GstChildProxyInterface; + +/** + * GstChildProxyInterface: + * @parent: parent interface type. + * @get_child_by_index: virtual method to fetch the child + * @get_children_count: virtual method to get the children count + * + * #GstChildProxy interface. + */ +struct _GstChildProxyInterface +{ + GTypeInterface parent; + + /* methods */ + GstObject *(*get_child_by_index) (GstChildProxy * parent, guint index); + guint (*get_children_count) (GstChildProxy * parent); + /*< private >*/ + /* signals */ + void (*child_added) (GstChildProxy * parent, GstObject * child); + void (*child_removed) (GstChildProxy * parent, GstObject * child); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_child_proxy_get_type (void); + +GstObject *gst_child_proxy_get_child_by_name (GstChildProxy * parent, const gchar * name); +GstObject *gst_child_proxy_get_child_by_index (GstChildProxy * parent, guint index); +guint gst_child_proxy_get_children_count (GstChildProxy * parent); + +gboolean gst_child_proxy_lookup (GstObject *object, const gchar *name, + GstObject **target, GParamSpec **pspec); +void gst_child_proxy_get_property (GstObject * object, const gchar *name, GValue *value); +void gst_child_proxy_get_valist (GstObject * object, + const gchar * first_property_name, va_list var_args); +void gst_child_proxy_get (GstObject * object, const gchar * first_property_name, + ...) G_GNUC_NULL_TERMINATED; +void gst_child_proxy_set_property (GstObject * object, const gchar *name, const GValue *value); +void gst_child_proxy_set_valist (GstObject* object, + const gchar * first_property_name, va_list var_args); +void gst_child_proxy_set (GstObject * object, const gchar * first_property_name, + ...) G_GNUC_NULL_TERMINATED; +void gst_child_proxy_child_added (GstObject * object, GstObject * child); +void gst_child_proxy_child_removed (GstObject * object, GstObject * child); + +G_END_DECLS + +#endif /* __GST_CHILD_PROXY_H__ */ diff --git a/gst/gstclock.c b/gst/gstclock.c new file mode 100644 index 0000000..b167238 --- /dev/null +++ b/gst/gstclock.c @@ -0,0 +1,1488 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2004 Wim Taymans <wim@fluendo.com> + * + * gstclock.c: Clock subsystem for maintaining time sync + * + * 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:gstclock + * @short_description: Abstract class for global clocks + * @see_also: #GstSystemClock, #GstPipeline + * + * GStreamer uses a global clock to synchronize the plugins in a pipeline. + * Different clock implementations are possible by implementing this abstract + * base class or, more conveniently, by subclassing #GstSystemClock. + * + * The #GstClock returns a monotonically increasing time with the method + * gst_clock_get_time(). Its accuracy and base time depend on the specific + * clock implementation but time is always expressed in nanoseconds. Since the + * baseline of the clock is undefined, the clock time returned is not + * meaningful in itself, what matters are the deltas between two clock times. + * The time returned by a clock is called the absolute time. + * + * The pipeline uses the clock to calculate the running time. Usually all + * renderers synchronize to the global clock using the buffer timestamps, the + * newsegment events and the element's base time, see #GstPipeline. + * + * A clock implementation can support periodic and single shot clock + * notifications both synchronous and asynchronous. + * + * One first needs to create a #GstClockID for the periodic or single shot + * notification using gst_clock_new_single_shot_id() or + * gst_clock_new_periodic_id(). + * + * To perform a blocking wait for the specific time of the #GstClockID use the + * gst_clock_id_wait(). To receive a callback when the specific time is reached + * in the clock use gst_clock_id_wait_async(). Both these calls can be + * interrupted with the gst_clock_id_unschedule() call. If the blocking wait is + * unscheduled a return value of #GST_CLOCK_UNSCHEDULED is returned. + * + * Periodic callbacks scheduled async will be repeatedly called automatically + * until it is unscheduled. To schedule a sync periodic callback, + * gst_clock_id_wait() should be called repeatedly. + * + * The async callbacks can happen from any thread, either provided by the core + * or from a streaming thread. The application should be prepared for this. + * + * A #GstClockID that has been unscheduled cannot be used again for any wait + * operation, a new #GstClockID should be created and the old unscheduled one + * should be destroyed with gst_clock_id_unref(). + * + * It is possible to perform a blocking wait on the same #GstClockID from + * multiple threads. However, registering the same #GstClockID for multiple + * async notifications is not possible, the callback will only be called for + * the thread registering the entry last. + * + * None of the wait operations unref the #GstClockID, the owner is responsible + * for unreffing the ids itself. This holds for both periodic and single shot + * notifications. The reason being that the owner of the #GstClockID has to + * keep a handle to the #GstClockID to unblock the wait on FLUSHING events or + * state changes and if the entry would be unreffed automatically, the handle + * might become invalid without any notification. + * + * These clock operations do not operate on the running time, so the callbacks + * will also occur when not in PLAYING state as if the clock just keeps on + * running. Some clocks however do not progress when the element that provided + * the clock is not PLAYING. + * + * When a clock has the #GST_CLOCK_FLAG_CAN_SET_MASTER flag set, it can be + * slaved to another #GstClock with the gst_clock_set_master(). The clock will + * then automatically be synchronized to this master clock by repeatedly + * sampling the master clock and the slave clock and recalibrating the slave + * clock with gst_clock_set_calibration(). This feature is mostly useful for + * plugins that have an internal clock but must operate with another clock + * selected by the #GstPipeline. They can track the offset and rate difference + * of their internal clock relative to the master clock by using the + * gst_clock_get_calibration() function. + * + * The master/slave synchronisation can be tuned with the #GstClock:timeout, + * #GstClock:window-size and #GstClock:window-threshold properties. + * The #GstClock:timeout property defines the interval to sample the master + * clock and run the calibration functions. #GstClock:window-size defines the + * number of samples to use when calibrating and #GstClock:window-threshold + * defines the minimum number of samples before the calibration is performed. + * + * Last reviewed on 2009-05-21 (0.10.24) + */ + + +#include "gst_private.h" +#include <time.h> + +#include "gstclock.h" +#include "gstinfo.h" +#include "gstutils.h" + +#ifndef GST_DISABLE_TRACE +/* #define GST_WITH_ALLOC_TRACE */ +#include "gsttrace.h" +static GstAllocTrace *_gst_clock_entry_trace; +#endif + +/* #define DEBUGGING_ENABLED */ + +#define DEFAULT_STATS FALSE +#define DEFAULT_WINDOW_SIZE 32 +#define DEFAULT_WINDOW_THRESHOLD 4 +#define DEFAULT_TIMEOUT GST_SECOND / 10 + +enum +{ + PROP_0, + PROP_STATS, + PROP_WINDOW_SIZE, + PROP_WINDOW_THRESHOLD, + PROP_TIMEOUT +}; + +struct _GstClockPrivate +{ + gint pre_count; + gint post_count; +}; + +/* seqlocks */ +#define read_seqbegin(clock) \ + g_atomic_int_get (&clock->ABI.priv->post_count); + +static inline gboolean +read_seqretry (GstClock * clock, gint seq) +{ + /* no retry if the seqnum did not change */ + if (G_LIKELY (seq == g_atomic_int_get (&clock->ABI.priv->pre_count))) + return FALSE; + + /* wait for the writer to finish and retry */ + GST_OBJECT_LOCK (clock); + GST_OBJECT_UNLOCK (clock); + return TRUE; +} + +#define write_seqlock(clock) \ +G_STMT_START { \ + GST_OBJECT_LOCK (clock); \ + g_atomic_int_inc (&clock->ABI.priv->pre_count); \ +} G_STMT_END; + +#define write_sequnlock(clock) \ +G_STMT_START { \ + g_atomic_int_inc (&clock->ABI.priv->post_count); \ + GST_OBJECT_UNLOCK (clock); \ +} G_STMT_END; + +static void gst_clock_dispose (GObject * object); +static void gst_clock_finalize (GObject * object); + +static void gst_clock_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_clock_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_clock_update_stats (GstClock * clock); + + +static GstObjectClass *parent_class = NULL; + +/* static guint gst_clock_signals[LAST_SIGNAL] = { 0 }; */ + +static GstClockID +gst_clock_entry_new (GstClock * clock, GstClockTime time, + GstClockTime interval, GstClockEntryType type) +{ + GstClockEntry *entry; + + entry = g_slice_new (GstClockEntry); +#ifndef GST_DISABLE_TRACE + gst_alloc_trace_new (_gst_clock_entry_trace, entry); +#endif + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "created entry %p, time %" GST_TIME_FORMAT, entry, GST_TIME_ARGS (time)); + + entry->refcount = 1; + entry->clock = clock; + entry->type = type; + entry->time = time; + entry->interval = interval; + entry->status = GST_CLOCK_OK; + entry->func = NULL; + entry->user_data = NULL; + entry->destroy_data = NULL; + entry->unscheduled = FALSE; + entry->woken_up = FALSE; + + return (GstClockID) entry; +} + +/* WARNING : Does not modify the refcount + * WARNING : Do not use if a pending clock operation is happening on that entry */ +static gboolean +gst_clock_entry_reinit (GstClock * clock, GstClockEntry * entry, + GstClockTime time, GstClockTime interval, GstClockEntryType type) +{ + g_return_val_if_fail (entry->status != GST_CLOCK_BUSY, FALSE); + g_return_val_if_fail (entry->clock == clock, FALSE); + + entry->type = type; + entry->time = time; + entry->interval = interval; + entry->status = GST_CLOCK_OK; + entry->unscheduled = FALSE; + entry->woken_up = FALSE; + + return TRUE; +} + +/** + * gst_clock_single_shot_id_reinit: + * @clock: a #GstClock + * @id: a #GstClockID + * @time: The requested time. + * + * Reinitializes the provided single shot @id to the provided time. Does not + * modify the reference count. + * + * Returns: %TRUE if the GstClockID could be reinitialized to the provided + * @time, else %FALSE. + * + * Since: 0.10.32 + */ +gboolean +gst_clock_single_shot_id_reinit (GstClock * clock, GstClockID id, + GstClockTime time) +{ + return gst_clock_entry_reinit (clock, (GstClockEntry *) id, time, + GST_CLOCK_TIME_NONE, GST_CLOCK_ENTRY_SINGLE); +} + +/** + * gst_clock_periodic_id_reinit: + * @clock: a #GstClock + * @id: a #GstClockID + * @start_time: the requested start time + * @interval: the requested interval + * + * Reinitializes the provided periodic @id to the provided start time and + * interval. Does not modify the reference count. + * + * Returns: %TRUE if the GstClockID could be reinitialized to the provided + * @time, else %FALSE. + * + * Since: 0.10.33 + * + */ +gboolean +gst_clock_periodic_id_reinit (GstClock * clock, GstClockID id, + GstClockTime start_time, GstClockTime interval) +{ + return gst_clock_entry_reinit (clock, (GstClockEntry *) id, start_time, + interval, GST_CLOCK_ENTRY_PERIODIC); +} + +/** + * gst_clock_id_ref: + * @id: The #GstClockID to ref + * + * Increase the refcount of given @id. + * + * Returns: (transfer full): The same #GstClockID with increased refcount. + * + * MT safe. + */ +GstClockID +gst_clock_id_ref (GstClockID id) +{ + g_return_val_if_fail (id != NULL, NULL); + + g_atomic_int_inc (&((GstClockEntry *) id)->refcount); + + return id; +} + +static void +_gst_clock_id_free (GstClockID id) +{ + GstClockEntry *entry; + g_return_if_fail (id != NULL); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "freed entry %p", id); + entry = (GstClockEntry *) id; + if (entry->destroy_data) + entry->destroy_data (entry->user_data); + +#ifndef GST_DISABLE_TRACE + gst_alloc_trace_free (_gst_clock_entry_trace, id); +#endif + g_slice_free (GstClockEntry, id); +} + +/** + * gst_clock_id_unref: + * @id: (transfer full): The #GstClockID to unref + * + * Unref given @id. When the refcount reaches 0 the + * #GstClockID will be freed. + * + * MT safe. + */ +void +gst_clock_id_unref (GstClockID id) +{ + gint zero; + + g_return_if_fail (id != NULL); + + zero = g_atomic_int_dec_and_test (&((GstClockEntry *) id)->refcount); + /* if we ended up with the refcount at zero, free the id */ + if (zero) { + _gst_clock_id_free (id); + } +} + +/** + * gst_clock_new_single_shot_id: + * @clock: The #GstClockID to get a single shot notification from + * @time: the requested time + * + * Get a #GstClockID from @clock to trigger a single shot + * notification at the requested time. The single shot id should be + * unreffed after usage. + * + * Free-function: gst_clock_id_unref + * + * Returns: (transfer full): a #GstClockID that can be used to request the + * time notification. + * + * MT safe. + */ +GstClockID +gst_clock_new_single_shot_id (GstClock * clock, GstClockTime time) +{ + g_return_val_if_fail (GST_IS_CLOCK (clock), NULL); + + return gst_clock_entry_new (clock, + time, GST_CLOCK_TIME_NONE, GST_CLOCK_ENTRY_SINGLE); +} + +/** + * gst_clock_new_periodic_id: + * @clock: The #GstClockID to get a periodic notification id from + * @start_time: the requested start time + * @interval: the requested interval + * + * Get an ID from @clock to trigger a periodic notification. + * The periodic notifications will start at time @start_time and + * will then be fired with the given @interval. @id should be unreffed + * after usage. + * + * Free-function: gst_clock_id_unref + * + * Returns: (transfer full): a #GstClockID that can be used to request the + * time notification. + * + * MT safe. + */ +GstClockID +gst_clock_new_periodic_id (GstClock * clock, GstClockTime start_time, + GstClockTime interval) +{ + g_return_val_if_fail (GST_IS_CLOCK (clock), NULL); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start_time), NULL); + g_return_val_if_fail (interval != 0, NULL); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (interval), NULL); + + return gst_clock_entry_new (clock, + start_time, interval, GST_CLOCK_ENTRY_PERIODIC); +} + +/** + * gst_clock_id_compare_func + * @id1: A #GstClockID + * @id2: A #GstClockID to compare with + * + * Compares the two #GstClockID instances. This function can be used + * as a GCompareFunc when sorting ids. + * + * Returns: negative value if a < b; zero if a = b; positive value if a > b + * + * MT safe. + */ +gint +gst_clock_id_compare_func (gconstpointer id1, gconstpointer id2) +{ + GstClockEntry *entry1, *entry2; + + entry1 = (GstClockEntry *) id1; + entry2 = (GstClockEntry *) id2; + + if (GST_CLOCK_ENTRY_TIME (entry1) > GST_CLOCK_ENTRY_TIME (entry2)) { + return 1; + } + if (GST_CLOCK_ENTRY_TIME (entry1) < GST_CLOCK_ENTRY_TIME (entry2)) { + return -1; + } + return 0; +} + +/** + * gst_clock_id_get_time + * @id: The #GstClockID to query + * + * Get the time of the clock ID + * + * Returns: the time of the given clock id. + * + * MT safe. + */ +GstClockTime +gst_clock_id_get_time (GstClockID id) +{ + g_return_val_if_fail (id != NULL, GST_CLOCK_TIME_NONE); + + return GST_CLOCK_ENTRY_TIME ((GstClockEntry *) id); +} + +/** + * gst_clock_id_wait + * @id: The #GstClockID to wait on + * @jitter: (out) (allow-none): a pointer that will contain the jitter, + * can be %NULL. + * + * Perform a blocking wait on @id. + * @id should have been created with gst_clock_new_single_shot_id() + * or gst_clock_new_periodic_id() and should not have been unscheduled + * with a call to gst_clock_id_unschedule(). + * + * If the @jitter argument is not %NULL and this function returns #GST_CLOCK_OK + * or #GST_CLOCK_EARLY, it will contain the difference + * against the clock and the time of @id when this method was + * called. + * Positive values indicate how late @id was relative to the clock + * (in which case this function will return #GST_CLOCK_EARLY). + * Negative values indicate how much time was spent waiting on the clock + * before this function returned. + * + * Returns: the result of the blocking wait. #GST_CLOCK_EARLY will be returned + * if the current clock time is past the time of @id, #GST_CLOCK_OK if + * @id was scheduled in time. #GST_CLOCK_UNSCHEDULED if @id was + * unscheduled with gst_clock_id_unschedule(). + * + * MT safe. + */ +GstClockReturn +gst_clock_id_wait (GstClockID id, GstClockTimeDiff * jitter) +{ + GstClockEntry *entry; + GstClock *clock; + GstClockReturn res; + GstClockTime requested; + GstClockClass *cclass; + + g_return_val_if_fail (id != NULL, GST_CLOCK_ERROR); + + entry = (GstClockEntry *) id; + requested = GST_CLOCK_ENTRY_TIME (entry); + + clock = GST_CLOCK_ENTRY_CLOCK (entry); + + /* can't sync on invalid times */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested))) + goto invalid_time; + + cclass = GST_CLOCK_GET_CLASS (clock); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "waiting on clock entry %p", id); + + /* if we have a wait_jitter function, use that */ + if (G_UNLIKELY (cclass->wait == NULL)) + goto not_supported; + + res = cclass->wait (clock, entry, jitter); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "done waiting entry %p, res: %d", id, res); + + if (entry->type == GST_CLOCK_ENTRY_PERIODIC) + entry->time = requested + entry->interval; + + if (G_UNLIKELY (clock->stats)) + gst_clock_update_stats (clock); + + return res; + + /* ERRORS */ +invalid_time: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "invalid time requested, returning _BADTIME"); + return GST_CLOCK_BADTIME; + } +not_supported: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "clock wait is not supported"); + return GST_CLOCK_UNSUPPORTED; + } +} + +/** + * gst_clock_id_wait_async_full: + * @id: a #GstClockID to wait on + * @func: The callback function + * @user_data: User data passed in the callback + * @destroy_data: #GDestroyNotify for user_data + * + * Register a callback on the given #GstClockID @id with the given + * function and user_data. When passing a #GstClockID with an invalid + * time to this function, the callback will be called immediately + * with a time set to GST_CLOCK_TIME_NONE. The callback will + * be called when the time of @id has been reached. + * + * The callback @func can be invoked from any thread, either provided by the + * core or from a streaming thread. The application should be prepared for this. + * + * Returns: the result of the non blocking wait. + * + * MT safe. + * + * Since: 0.10.30 + */ +GstClockReturn +gst_clock_id_wait_async_full (GstClockID id, + GstClockCallback func, gpointer user_data, GDestroyNotify destroy_data) +{ + GstClockEntry *entry; + GstClock *clock; + GstClockReturn res; + GstClockClass *cclass; + GstClockTime requested; + + g_return_val_if_fail (id != NULL, GST_CLOCK_ERROR); + g_return_val_if_fail (func != NULL, GST_CLOCK_ERROR); + + entry = (GstClockEntry *) id; + requested = GST_CLOCK_ENTRY_TIME (entry); + clock = GST_CLOCK_ENTRY_CLOCK (entry); + + /* can't sync on invalid times */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested))) + goto invalid_time; + + cclass = GST_CLOCK_GET_CLASS (clock); + + if (G_UNLIKELY (cclass->wait_async == NULL)) + goto not_supported; + + entry->func = func; + entry->user_data = user_data; + entry->destroy_data = destroy_data; + + res = cclass->wait_async (clock, entry); + + return res; + + /* ERRORS */ +invalid_time: + { + (func) (clock, GST_CLOCK_TIME_NONE, id, user_data); + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "invalid time requested, returning _BADTIME"); + return GST_CLOCK_BADTIME; + } +not_supported: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "clock wait is not supported"); + return GST_CLOCK_UNSUPPORTED; + } +} + +/** + * gst_clock_id_wait_async: + * @id: a #GstClockID to wait on + * @func: The callback function + * @user_data: User data passed in the callback + * + * Register a callback on the given #GstClockID @id with the given + * function and user_data. When passing a #GstClockID with an invalid + * time to this function, the callback will be called immediately + * with a time set to GST_CLOCK_TIME_NONE. The callback will + * be called when the time of @id has been reached. + * + * The callback @func can be invoked from any thread, either provided by the + * core or from a streaming thread. The application should be prepared for this. + * + * Returns: the result of the non blocking wait. + * + * MT safe. + */ +GstClockReturn +gst_clock_id_wait_async (GstClockID id, + GstClockCallback func, gpointer user_data) +{ + return gst_clock_id_wait_async_full (id, func, user_data, NULL); +} + +/** + * gst_clock_id_unschedule: + * @id: The id to unschedule + * + * Cancel an outstanding request with @id. This can either + * be an outstanding async notification or a pending sync notification. + * After this call, @id cannot be used anymore to receive sync or + * async notifications, you need to create a new #GstClockID. + * + * MT safe. + */ +void +gst_clock_id_unschedule (GstClockID id) +{ + GstClockEntry *entry; + GstClock *clock; + GstClockClass *cclass; + + g_return_if_fail (id != NULL); + + entry = (GstClockEntry *) id; + clock = entry->clock; + + cclass = GST_CLOCK_GET_CLASS (clock); + + if (G_LIKELY (cclass->unschedule)) + cclass->unschedule (clock, entry); +} + + +/* + * GstClock abstract base class implementation + */ +G_DEFINE_TYPE (GstClock, gst_clock, GST_TYPE_OBJECT); + +static void +gst_clock_class_init (GstClockClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + +#ifndef GST_DISABLE_TRACE + _gst_clock_entry_trace = + gst_alloc_trace_register (GST_CLOCK_ENTRY_TRACE_NAME); +#endif + + gobject_class->dispose = gst_clock_dispose; + gobject_class->finalize = gst_clock_finalize; + gobject_class->set_property = gst_clock_set_property; + gobject_class->get_property = gst_clock_get_property; + + g_object_class_install_property (gobject_class, PROP_STATS, + g_param_spec_boolean ("stats", "Stats", + "Enable clock stats (unimplemented)", DEFAULT_STATS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_WINDOW_SIZE, + g_param_spec_int ("window-size", "Window size", + "The size of the window used to calculate rate and offset", 2, 1024, + DEFAULT_WINDOW_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_WINDOW_THRESHOLD, + g_param_spec_int ("window-threshold", "Window threshold", + "The threshold to start calculating rate and offset", 2, 1024, + DEFAULT_WINDOW_THRESHOLD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_TIMEOUT, + g_param_spec_uint64 ("timeout", "Timeout", + "The amount of time, in nanoseconds, to sample master and slave clocks", + 0, G_MAXUINT64, DEFAULT_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (klass, sizeof (GstClockPrivate)); +} + +static void +gst_clock_init (GstClock * clock) +{ + clock->last_time = 0; + clock->entries = NULL; + clock->entries_changed = g_cond_new (); + clock->stats = FALSE; + + clock->ABI.priv = + G_TYPE_INSTANCE_GET_PRIVATE (clock, GST_TYPE_CLOCK, GstClockPrivate); + + clock->internal_calibration = 0; + clock->external_calibration = 0; + clock->rate_numerator = 1; + clock->rate_denominator = 1; + + clock->slave_lock = g_mutex_new (); + clock->window_size = DEFAULT_WINDOW_SIZE; + clock->window_threshold = DEFAULT_WINDOW_THRESHOLD; + clock->filling = TRUE; + clock->time_index = 0; + clock->timeout = DEFAULT_TIMEOUT; + clock->times = g_new0 (GstClockTime, 4 * clock->window_size); +} + +static void +gst_clock_dispose (GObject * object) +{ + GstClock *clock = GST_CLOCK (object); + GstClock **master_p; + + GST_OBJECT_LOCK (clock); + master_p = &clock->master; + gst_object_replace ((GstObject **) master_p, NULL); + GST_OBJECT_UNLOCK (clock); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_clock_finalize (GObject * object) +{ + GstClock *clock = GST_CLOCK (object); + + GST_CLOCK_SLAVE_LOCK (clock); + if (clock->clockid) { + gst_clock_id_unschedule (clock->clockid); + gst_clock_id_unref (clock->clockid); + clock->clockid = NULL; + } + g_free (clock->times); + clock->times = NULL; + GST_CLOCK_SLAVE_UNLOCK (clock); + + g_cond_free (clock->entries_changed); + g_mutex_free (clock->slave_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/** + * gst_clock_set_resolution + * @clock: a #GstClock + * @resolution: The resolution to set + * + * Set the accuracy of the clock. Some clocks have the possibility to operate + * with different accuracy at the expense of more resource usage. There is + * normally no need to change the default resolution of a clock. The resolution + * of a clock can only be changed if the clock has the + * #GST_CLOCK_FLAG_CAN_SET_RESOLUTION flag set. + * + * Returns: the new resolution of the clock. + */ +GstClockTime +gst_clock_set_resolution (GstClock * clock, GstClockTime resolution) +{ + GstClockClass *cclass; + + g_return_val_if_fail (GST_IS_CLOCK (clock), 0); + g_return_val_if_fail (resolution != 0, 0); + + cclass = GST_CLOCK_GET_CLASS (clock); + + if (cclass->change_resolution) + clock->resolution = + cclass->change_resolution (clock, clock->resolution, resolution); + + return clock->resolution; +} + +/** + * gst_clock_get_resolution + * @clock: a #GstClock + * + * Get the accuracy of the clock. The accuracy of the clock is the granularity + * of the values returned by gst_clock_get_time(). + * + * Returns: the resolution of the clock in units of #GstClockTime. + * + * MT safe. + */ +GstClockTime +gst_clock_get_resolution (GstClock * clock) +{ + GstClockClass *cclass; + + g_return_val_if_fail (GST_IS_CLOCK (clock), 0); + + cclass = GST_CLOCK_GET_CLASS (clock); + + if (cclass->get_resolution) + return cclass->get_resolution (clock); + + return 1; +} + +/** + * gst_clock_adjust_unlocked + * @clock: a #GstClock to use + * @internal: a clock time + * + * Converts the given @internal clock time to the external time, adjusting for the + * rate and reference time set with gst_clock_set_calibration() and making sure + * that the returned time is increasing. This function should be called with the + * clock's OBJECT_LOCK held and is mainly used by clock subclasses. + * + * This function is the reverse of gst_clock_unadjust_unlocked(). + * + * Returns: the converted time of the clock. + */ +GstClockTime +gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal) +{ + GstClockTime ret, cinternal, cexternal, cnum, cdenom; + + /* get calibration values for readability */ + cinternal = clock->internal_calibration; + cexternal = clock->external_calibration; + cnum = clock->rate_numerator; + cdenom = clock->rate_denominator; + + /* avoid divide by 0 */ + if (G_UNLIKELY (cdenom == 0)) + cnum = cdenom = 1; + + /* The formula is (internal - cinternal) * cnum / cdenom + cexternal + * + * Since we do math on unsigned 64-bit ints we have to special case for + * internal < cinternal to get the sign right. this case is not very common, + * though. + */ + if (G_LIKELY (internal >= cinternal)) { + ret = internal - cinternal; + ret = gst_util_uint64_scale (ret, cnum, cdenom); + ret += cexternal; + } else { + ret = cinternal - internal; + ret = gst_util_uint64_scale (ret, cnum, cdenom); + /* clamp to 0 */ + if (G_LIKELY (cexternal > ret)) + ret = cexternal - ret; + else + ret = 0; + } + + /* make sure the time is increasing */ + clock->last_time = MAX (ret, clock->last_time); + + return clock->last_time; +} + +/** + * gst_clock_unadjust_unlocked + * @clock: a #GstClock to use + * @external: an external clock time + * + * Converts the given @external clock time to the internal time of @clock, + * using the rate and reference time set with gst_clock_set_calibration(). + * This function should be called with the clock's OBJECT_LOCK held and + * is mainly used by clock subclasses. + * + * This function is the reverse of gst_clock_adjust_unlocked(). + * + * Returns: the internal time of the clock corresponding to @external. + * + * Since: 0.10.13 + */ +GstClockTime +gst_clock_unadjust_unlocked (GstClock * clock, GstClockTime external) +{ + GstClockTime ret, cinternal, cexternal, cnum, cdenom; + + /* get calibration values for readability */ + cinternal = clock->internal_calibration; + cexternal = clock->external_calibration; + cnum = clock->rate_numerator; + cdenom = clock->rate_denominator; + + /* avoid divide by 0 */ + if (G_UNLIKELY (cnum == 0)) + cnum = cdenom = 1; + + /* The formula is (external - cexternal) * cdenom / cnum + cinternal */ + if (G_LIKELY (external >= cexternal)) { + ret = external - cexternal; + ret = gst_util_uint64_scale (ret, cdenom, cnum); + ret += cinternal; + } else { + ret = cexternal - external; + ret = gst_util_uint64_scale (ret, cdenom, cnum); + if (G_LIKELY (cinternal > ret)) + ret = cinternal - ret; + else + ret = 0; + } + return ret; +} + +/** + * gst_clock_get_internal_time + * @clock: a #GstClock to query + * + * Gets the current internal time of the given clock. The time is returned + * unadjusted for the offset and the rate. + * + * Returns: the internal time of the clock. Or GST_CLOCK_TIME_NONE when + * given invalid input. + * + * MT safe. + */ +GstClockTime +gst_clock_get_internal_time (GstClock * clock) +{ + GstClockTime ret; + GstClockClass *cclass; + + g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE); + + cclass = GST_CLOCK_GET_CLASS (clock); + + if (G_UNLIKELY (cclass->get_internal_time == NULL)) + goto not_supported; + + ret = cclass->get_internal_time (clock); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "internal time %" GST_TIME_FORMAT, + GST_TIME_ARGS (ret)); + + return ret; + + /* ERRORS */ +not_supported: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "internal time not supported, return 0"); + return G_GINT64_CONSTANT (0); + } +} + +/** + * gst_clock_get_time + * @clock: a #GstClock to query + * + * Gets the current time of the given clock. The time is always + * monotonically increasing and adjusted according to the current + * offset and rate. + * + * Returns: the time of the clock. Or GST_CLOCK_TIME_NONE when + * given invalid input. + * + * MT safe. + */ +GstClockTime +gst_clock_get_time (GstClock * clock) +{ + GstClockTime ret; + gint seq; + + g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE); + + do { + /* reget the internal time when we retry to get the most current + * timevalue */ + ret = gst_clock_get_internal_time (clock); + + seq = read_seqbegin (clock); + /* this will scale for rate and offset */ + ret = gst_clock_adjust_unlocked (clock, ret); + } while (read_seqretry (clock, seq)); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "adjusted time %" GST_TIME_FORMAT, + GST_TIME_ARGS (ret)); + + return ret; +} + +/** + * gst_clock_set_calibration + * @clock: a #GstClock to calibrate + * @internal: a reference internal time + * @external: a reference external time + * @rate_num: the numerator of the rate of the clock relative to its + * internal time + * @rate_denom: the denominator of the rate of the clock + * + * Adjusts the rate and time of @clock. A rate of 1/1 is the normal speed of + * the clock. Values bigger than 1/1 make the clock go faster. + * + * @internal and @external are calibration parameters that arrange that + * gst_clock_get_time() should have been @external at internal time @internal. + * This internal time should not be in the future; that is, it should be less + * than the value of gst_clock_get_internal_time() when this function is called. + * + * Subsequent calls to gst_clock_get_time() will return clock times computed as + * follows: + * + * <programlisting> + * time = (internal_time - internal) * rate_num / rate_denom + external + * </programlisting> + * + * This formula is implemented in gst_clock_adjust_unlocked(). Of course, it + * tries to do the integer arithmetic as precisely as possible. + * + * Note that gst_clock_get_time() always returns increasing values so when you + * move the clock backwards, gst_clock_get_time() will report the previous value + * until the clock catches up. + * + * MT safe. + */ +void +gst_clock_set_calibration (GstClock * clock, GstClockTime internal, GstClockTime + external, GstClockTime rate_num, GstClockTime rate_denom) +{ + g_return_if_fail (GST_IS_CLOCK (clock)); + g_return_if_fail (rate_num != GST_CLOCK_TIME_NONE); + g_return_if_fail (rate_denom > 0 && rate_denom != GST_CLOCK_TIME_NONE); + + write_seqlock (clock); + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "internal %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT " %" + G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT " = %f", GST_TIME_ARGS (internal), + GST_TIME_ARGS (external), rate_num, rate_denom, + gst_guint64_to_gdouble (rate_num) / gst_guint64_to_gdouble (rate_denom)); + + clock->internal_calibration = internal; + clock->external_calibration = external; + clock->rate_numerator = rate_num; + clock->rate_denominator = rate_denom; + write_sequnlock (clock); +} + +/** + * gst_clock_get_calibration + * @clock: a #GstClock + * @internal: (out) (allow-none): a location to store the internal time + * @external: (out) (allow-none): a location to store the external time + * @rate_num: (out) (allow-none): a location to store the rate numerator + * @rate_denom: (out) (allow-none): a location to store the rate denominator + * + * Gets the internal rate and reference time of @clock. See + * gst_clock_set_calibration() for more information. + * + * @internal, @external, @rate_num, and @rate_denom can be left %NULL if the + * caller is not interested in the values. + * + * MT safe. + */ +void +gst_clock_get_calibration (GstClock * clock, GstClockTime * internal, + GstClockTime * external, GstClockTime * rate_num, GstClockTime * rate_denom) +{ + gint seq; + + g_return_if_fail (GST_IS_CLOCK (clock)); + + do { + seq = read_seqbegin (clock); + if (rate_num) + *rate_num = clock->rate_numerator; + if (rate_denom) + *rate_denom = clock->rate_denominator; + if (external) + *external = clock->external_calibration; + if (internal) + *internal = clock->internal_calibration; + } while (read_seqretry (clock, seq)); +} + +/* will be called repeatedly to sample the master and slave clock + * to recalibrate the clock */ +static gboolean +gst_clock_slave_callback (GstClock * master, GstClockTime time, + GstClockID id, GstClock * clock) +{ + GstClockTime stime, mtime; + gdouble r_squared; + + stime = gst_clock_get_internal_time (clock); + mtime = gst_clock_get_time (master); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "master %" GST_TIME_FORMAT ", slave %" GST_TIME_FORMAT, + GST_TIME_ARGS (mtime), GST_TIME_ARGS (stime)); + + gst_clock_add_observation (clock, stime, mtime, &r_squared); + + /* FIXME, we can use the r_squared value to adjust the timeout + * value of the clockid */ + + return TRUE; +} + +/** + * gst_clock_set_master + * @clock: a #GstClock + * @master: (allow-none): a master #GstClock + * + * Set @master as the master clock for @clock. @clock will be automatically + * calibrated so that gst_clock_get_time() reports the same time as the + * master clock. + * + * A clock provider that slaves its clock to a master can get the current + * calibration values with gst_clock_get_calibration(). + * + * @master can be %NULL in which case @clock will not be slaved anymore. It will + * however keep reporting its time adjusted with the last configured rate + * and time offsets. + * + * Returns: %TRUE if the clock is capable of being slaved to a master clock. + * Trying to set a master on a clock without the + * #GST_CLOCK_FLAG_CAN_SET_MASTER flag will make this function return %FALSE. + * + * MT safe. + */ +gboolean +gst_clock_set_master (GstClock * clock, GstClock * master) +{ + GstClock **master_p; + + g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE); + g_return_val_if_fail (master != clock, FALSE); + + GST_OBJECT_LOCK (clock); + /* we always allow setting the master to NULL */ + if (master && !GST_OBJECT_FLAG_IS_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER)) + goto not_supported; + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "slaving %p to master clock %p", clock, master); + GST_OBJECT_UNLOCK (clock); + + GST_CLOCK_SLAVE_LOCK (clock); + if (clock->clockid) { + gst_clock_id_unschedule (clock->clockid); + gst_clock_id_unref (clock->clockid); + clock->clockid = NULL; + } + if (master) { + clock->filling = TRUE; + clock->time_index = 0; + /* use the master periodic id to schedule sampling and + * clock calibration. */ + clock->clockid = gst_clock_new_periodic_id (master, + gst_clock_get_time (master), clock->timeout); + gst_clock_id_wait_async_full (clock->clockid, + (GstClockCallback) gst_clock_slave_callback, + gst_object_ref (clock), (GDestroyNotify) gst_object_unref); + } + GST_CLOCK_SLAVE_UNLOCK (clock); + + GST_OBJECT_LOCK (clock); + master_p = &clock->master; + gst_object_replace ((GstObject **) master_p, (GstObject *) master); + GST_OBJECT_UNLOCK (clock); + + return TRUE; + + /* ERRORS */ +not_supported: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "cannot be slaved to a master clock"); + GST_OBJECT_UNLOCK (clock); + return FALSE; + } +} + +/** + * gst_clock_get_master: + * @clock: a #GstClock + * + * Get the master clock that @clock is slaved to or %NULL when the clock is + * not slaved to any master clock. + * + * Returns: (transfer full): a master #GstClock or %NULL when this clock is + * not slaved to a master clock. Unref after usage. + * + * MT safe. + */ +GstClock * +gst_clock_get_master (GstClock * clock) +{ + GstClock *result = NULL; + + g_return_val_if_fail (GST_IS_CLOCK (clock), NULL); + + GST_OBJECT_LOCK (clock); + if (clock->master) + result = gst_object_ref (clock->master); + GST_OBJECT_UNLOCK (clock); + + return result; +} + +/* http://mathworld.wolfram.com/LeastSquaresFitting.html + * with SLAVE_LOCK + */ +static gboolean +do_linear_regression (GstClock * clock, GstClockTime * m_num, + GstClockTime * m_denom, GstClockTime * b, GstClockTime * xbase, + gdouble * r_squared) +{ + GstClockTime *newx, *newy; + GstClockTime xmin, ymin, xbar, ybar, xbar4, ybar4; + GstClockTimeDiff sxx, sxy, syy; + GstClockTime *x, *y; + gint i, j; + guint n; + + xbar = ybar = sxx = syy = sxy = 0; + + x = clock->times; + y = clock->times + 2; + n = clock->filling ? clock->time_index : clock->window_size; + +#ifdef DEBUGGING_ENABLED + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "doing regression on:"); + for (i = j = 0; i < n; i++, j += 4) + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, x[j], y[j]); +#endif + + xmin = ymin = G_MAXUINT64; + for (i = j = 0; i < n; i++, j += 4) { + xmin = MIN (xmin, x[j]); + ymin = MIN (ymin, y[j]); + } + +#ifdef DEBUGGING_ENABLED + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "min x: %" G_GUINT64_FORMAT, + xmin); + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "min y: %" G_GUINT64_FORMAT, + ymin); +#endif + + newx = clock->times + 1; + newy = clock->times + 3; + + /* strip off unnecessary bits of precision */ + for (i = j = 0; i < n; i++, j += 4) { + newx[j] = x[j] - xmin; + newy[j] = y[j] - ymin; + } + +#ifdef DEBUGGING_ENABLED + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "reduced numbers:"); + for (i = j = 0; i < n; i++, j += 4) + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, newx[j], newy[j]); +#endif + + /* have to do this precisely otherwise the results are pretty much useless. + * should guarantee that none of these accumulators can overflow */ + + /* quantities on the order of 1e10 -> 30 bits; window size a max of 2^10, so + this addition could end up around 2^40 or so -- ample headroom */ + for (i = j = 0; i < n; i++, j += 4) { + xbar += newx[j]; + ybar += newy[j]; + } + xbar /= n; + ybar /= n; + +#ifdef DEBUGGING_ENABLED + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " xbar = %" G_GUINT64_FORMAT, + xbar); + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " ybar = %" G_GUINT64_FORMAT, + ybar); +#endif + + /* multiplying directly would give quantities on the order of 1e20 -> 60 bits; + times the window size that's 70 which is too much. Instead we (1) subtract + off the xbar*ybar in the loop instead of after, to avoid accumulation; (2) + shift off 4 bits from each multiplicand, giving an expected ceiling of 52 + bits, which should be enough. Need to check the incoming range and domain + to ensure this is an appropriate loss of precision though. */ + xbar4 = xbar >> 4; + ybar4 = ybar >> 4; + for (i = j = 0; i < n; i++, j += 4) { + GstClockTime newx4, newy4; + + newx4 = newx[j] >> 4; + newy4 = newy[j] >> 4; + + sxx += newx4 * newx4 - xbar4 * xbar4; + syy += newy4 * newy4 - ybar4 * ybar4; + sxy += newx4 * newy4 - xbar4 * ybar4; + } + + if (G_UNLIKELY (sxx == 0)) + goto invalid; + + *m_num = sxy; + *m_denom = sxx; + *xbase = xmin; + *b = (ybar + ymin) - gst_util_uint64_scale (xbar, *m_num, *m_denom); + *r_squared = ((double) sxy * (double) sxy) / ((double) sxx * (double) syy); + +#ifdef DEBUGGING_ENABLED + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " m = %g", + ((double) *m_num) / *m_denom); + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " b = %" G_GUINT64_FORMAT, + *b); + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " xbase = %" G_GUINT64_FORMAT, + *xbase); + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " r2 = %g", *r_squared); +#endif + + return TRUE; + +invalid: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "sxx == 0, regression failed"); + return FALSE; + } +} + +/** + * gst_clock_add_observation + * @clock: a #GstClock + * @slave: a time on the slave + * @master: a time on the master + * @r_squared: (out): a pointer to hold the result + * + * The time @master of the master clock and the time @slave of the slave + * clock are added to the list of observations. If enough observations + * are available, a linear regression algorithm is run on the + * observations and @clock is recalibrated. + * + * If this functions returns %TRUE, @r_squared will contain the + * correlation coefficient of the interpolation. A value of 1.0 + * means a perfect regression was performed. This value can + * be used to control the sampling frequency of the master and slave + * clocks. + * + * Returns: %TRUE if enough observations were added to run the + * regression algorithm. + * + * MT safe. + */ +gboolean +gst_clock_add_observation (GstClock * clock, GstClockTime slave, + GstClockTime master, gdouble * r_squared) +{ + GstClockTime m_num, m_denom, b, xbase; + + g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE); + g_return_val_if_fail (r_squared != NULL, FALSE); + + GST_CLOCK_SLAVE_LOCK (clock); + + GST_CAT_LOG_OBJECT (GST_CAT_CLOCK, clock, + "adding observation slave %" GST_TIME_FORMAT ", master %" GST_TIME_FORMAT, + GST_TIME_ARGS (slave), GST_TIME_ARGS (master)); + + clock->times[(4 * clock->time_index)] = slave; + clock->times[(4 * clock->time_index) + 2] = master; + + clock->time_index++; + if (G_UNLIKELY (clock->time_index == clock->window_size)) { + clock->filling = FALSE; + clock->time_index = 0; + } + + if (G_UNLIKELY (clock->filling + && clock->time_index < clock->window_threshold)) + goto filling; + + if (!do_linear_regression (clock, &m_num, &m_denom, &b, &xbase, r_squared)) + goto invalid; + + GST_CLOCK_SLAVE_UNLOCK (clock); + + GST_CAT_LOG_OBJECT (GST_CAT_CLOCK, clock, + "adjusting clock to m=%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT ", b=%" + G_GUINT64_FORMAT " (rsquared=%g)", m_num, m_denom, b, *r_squared); + + /* if we have a valid regression, adjust the clock */ + gst_clock_set_calibration (clock, xbase, b, m_num, m_denom); + + return TRUE; + +filling: + { + GST_CLOCK_SLAVE_UNLOCK (clock); + return FALSE; + } +invalid: + { + /* no valid regression has been done, ignore the result then */ + GST_CLOCK_SLAVE_UNLOCK (clock); + return TRUE; + } +} + +static void +gst_clock_update_stats (GstClock * clock) +{ +} + +static void +gst_clock_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstClock *clock; + + clock = GST_CLOCK (object); + + switch (prop_id) { + case PROP_STATS: + GST_OBJECT_LOCK (clock); + clock->stats = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (clock); + break; + case PROP_WINDOW_SIZE: + GST_CLOCK_SLAVE_LOCK (clock); + clock->window_size = g_value_get_int (value); + clock->window_threshold = + MIN (clock->window_threshold, clock->window_size); + clock->times = + g_renew (GstClockTime, clock->times, 4 * clock->window_size); + /* restart calibration */ + clock->filling = TRUE; + clock->time_index = 0; + GST_CLOCK_SLAVE_UNLOCK (clock); + break; + case PROP_WINDOW_THRESHOLD: + GST_CLOCK_SLAVE_LOCK (clock); + clock->window_threshold = + MIN (g_value_get_int (value), clock->window_size); + GST_CLOCK_SLAVE_UNLOCK (clock); + break; + case PROP_TIMEOUT: + GST_CLOCK_SLAVE_LOCK (clock); + clock->timeout = g_value_get_uint64 (value); + GST_CLOCK_SLAVE_UNLOCK (clock); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clock_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstClock *clock; + + clock = GST_CLOCK (object); + + switch (prop_id) { + case PROP_STATS: + GST_OBJECT_LOCK (clock); + g_value_set_boolean (value, clock->stats); + GST_OBJECT_UNLOCK (clock); + break; + case PROP_WINDOW_SIZE: + GST_CLOCK_SLAVE_LOCK (clock); + g_value_set_int (value, clock->window_size); + GST_CLOCK_SLAVE_UNLOCK (clock); + break; + case PROP_WINDOW_THRESHOLD: + GST_CLOCK_SLAVE_LOCK (clock); + g_value_set_int (value, clock->window_threshold); + GST_CLOCK_SLAVE_UNLOCK (clock); + break; + case PROP_TIMEOUT: + GST_CLOCK_SLAVE_LOCK (clock); + g_value_set_uint64 (value, clock->timeout); + GST_CLOCK_SLAVE_UNLOCK (clock); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} diff --git a/gst/gstclock.h b/gst/gstclock.h new file mode 100644 index 0000000..2bc9d80 --- /dev/null +++ b/gst/gstclock.h @@ -0,0 +1,574 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstclock.h: Header for clock subsystem + * + * 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_CLOCK_H__ +#define __GST_CLOCK_H__ + +#include <gst/gstobject.h> + +G_BEGIN_DECLS + +/* --- standard type macros --- */ +#define GST_TYPE_CLOCK (gst_clock_get_type ()) +#define GST_CLOCK(clock) (G_TYPE_CHECK_INSTANCE_CAST ((clock), GST_TYPE_CLOCK, GstClock)) +#define GST_IS_CLOCK(clock) (G_TYPE_CHECK_INSTANCE_TYPE ((clock), GST_TYPE_CLOCK)) +#define GST_CLOCK_CLASS(cclass) (G_TYPE_CHECK_CLASS_CAST ((cclass), GST_TYPE_CLOCK, GstClockClass)) +#define GST_IS_CLOCK_CLASS(cclass) (G_TYPE_CHECK_CLASS_TYPE ((cclass), GST_TYPE_CLOCK)) +#define GST_CLOCK_GET_CLASS(clock) (G_TYPE_INSTANCE_GET_CLASS ((clock), GST_TYPE_CLOCK, GstClockClass)) +#define GST_CLOCK_CAST(clock) ((GstClock*)(clock)) + +#define GST_CLOCK_SLAVE_LOCK(clock) g_mutex_lock (GST_CLOCK_CAST (clock)->slave_lock) +#define GST_CLOCK_SLAVE_UNLOCK(clock) g_mutex_unlock (GST_CLOCK_CAST (clock)->slave_lock) + +/** + * GstClockTime: + * + * A datatype to hold a time, measured in nanoseconds. + */ +typedef guint64 GstClockTime; + +/** + * GST_TYPE_CLOCK_TIME: + * + * The #GType of a #GstClockTime. + */ +#define GST_TYPE_CLOCK_TIME G_TYPE_UINT64 + +/** + * GstClockTimeDiff: + * + * A datatype to hold a time difference, measured in nanoseconds. + */ +typedef gint64 GstClockTimeDiff; +/** + * GstClockID: + * + * A datatype to hold the handle to an outstanding sync or async clock callback. + */ +typedef gpointer GstClockID; + +/** + * GST_CLOCK_TIME_NONE: + * + * Constant to define an undefined clock time. + */ +#define GST_CLOCK_TIME_NONE ((GstClockTime) -1) +/** + * GST_CLOCK_TIME_IS_VALID: + * @time: clock time to validate + * + * Tests if a given #GstClockTime represents a valid defined time. + */ +#define GST_CLOCK_TIME_IS_VALID(time) (((GstClockTime)(time)) != GST_CLOCK_TIME_NONE) + +/** + * GST_SECOND: + * + * Constant that defines one GStreamer second. + */ +#define GST_SECOND (G_USEC_PER_SEC * G_GINT64_CONSTANT (1000)) +/** + * GST_MSECOND: + * + * Constant that defines one GStreamer millisecond. + */ +#define GST_MSECOND (GST_SECOND / G_GINT64_CONSTANT (1000)) +/** + * GST_USECOND: + * + * Constant that defines one GStreamer microsecond. + */ +#define GST_USECOND (GST_SECOND / G_GINT64_CONSTANT (1000000)) +/** + * GST_NSECOND: + * + * Constant that defines one GStreamer nanosecond + */ +#define GST_NSECOND (GST_SECOND / G_GINT64_CONSTANT (1000000000)) + + +/** + * GST_TIME_AS_SECONDS: + * @time: the time + * + * Convert a #GstClockTime to seconds. + * + * Since: 0.10.16 + */ +#define GST_TIME_AS_SECONDS(time) ((time) / GST_SECOND) +/** + * GST_TIME_AS_MSECONDS: + * @time: the time + * + * Convert a #GstClockTime to milliseconds (1/1000 of a second). + * + * Since: 0.10.16 + */ +#define GST_TIME_AS_MSECONDS(time) ((time) / G_GINT64_CONSTANT (1000000)) +/** + * GST_TIME_AS_USECONDS: + * @time: the time + * + * Convert a #GstClockTime to microseconds (1/1000000 of a second). + * + * Since: 0.10.16 + */ +#define GST_TIME_AS_USECONDS(time) ((time) / G_GINT64_CONSTANT (1000)) +/** + * GST_TIME_AS_NSECONDS: + * @time: the time + * + * Convert a #GstClockTime to nanoseconds (1/1000000000 of a second). + * + * Since: 0.10.16 + */ +#define GST_TIME_AS_NSECONDS(time) (time) + +/** + * GST_CLOCK_DIFF: + * @s: the first time + * @e: the second time + * + * Calculate a difference between two clock times as a #GstClockTimeDiff. + * The difference is calculated as @e - @s. + */ +#define GST_CLOCK_DIFF(s, e) (GstClockTimeDiff)((e) - (s)) + +/** + * GST_TIMEVAL_TO_TIME: + * @tv: the timeval to convert + * + * Convert a #GTimeVal to a #GstClockTime. + */ +#define GST_TIMEVAL_TO_TIME(tv) (GstClockTime)((tv).tv_sec * GST_SECOND + (tv).tv_usec * GST_USECOND) + +/** + * GST_TIME_TO_TIMEVAL: + * @t: The #GstClockTime to convert + * @tv: The target timeval + * + * Convert a #GstClockTime to a #GTimeVal + * + * <note>on 32-bit systems, a timeval has a range of only 2^32 - 1 seconds, + * which is about 68 years. Expect trouble if you want to schedule stuff + * in your pipeline for 2038.</note> + */ +#define GST_TIME_TO_TIMEVAL(t,tv) \ +G_STMT_START { \ + (tv).tv_sec = ((GstClockTime) (t)) / GST_SECOND; \ + (tv).tv_usec = (((GstClockTime) (t)) - \ + ((GstClockTime) (tv).tv_sec) * GST_SECOND) \ + / GST_USECOND; \ +} G_STMT_END + +/** + * GST_TIMESPEC_TO_TIME: + * @ts: the timespec to convert + * + * Convert a struct timespec (see man pselect) to a #GstClockTime. + */ +#define GST_TIMESPEC_TO_TIME(ts) (GstClockTime)((ts).tv_sec * GST_SECOND + (ts).tv_nsec * GST_NSECOND) +/** + * GST_TIME_TO_TIMESPEC: + * @t: The #GstClockTime to convert + * @ts: The target timespec + * + * Convert a #GstClockTime to a struct timespec (see man pselect) + */ +#define GST_TIME_TO_TIMESPEC(t,ts) \ +G_STMT_START { \ + (ts).tv_sec = (t) / GST_SECOND; \ + (ts).tv_nsec = ((t) - (ts).tv_sec * GST_SECOND) / GST_NSECOND; \ +} G_STMT_END + +/* timestamp debugging macros */ +/** + * GST_TIME_FORMAT: + * + * A string that can be used in printf-like format strings to display a + * #GstClockTime value in h:m:s format. Use GST_TIME_ARGS() to construct + * the matching arguments. + * + * Example: + * |[ + * printf("%" GST_TIME_FORMAT "\n", GST_TIME_ARGS(ts)); + * ]| + */ +#define GST_TIME_FORMAT "u:%02u:%02u.%09u" +/** + * GST_TIME_ARGS: + * @t: a #GstClockTime + * + * Format @t for the #GST_TIME_FORMAT format string. Note: @t will be + * evaluated more than once. + */ +#define GST_TIME_ARGS(t) \ + GST_CLOCK_TIME_IS_VALID (t) ? \ + (guint) (((GstClockTime)(t)) / (GST_SECOND * 60 * 60)) : 99, \ + GST_CLOCK_TIME_IS_VALID (t) ? \ + (guint) ((((GstClockTime)(t)) / (GST_SECOND * 60)) % 60) : 99, \ + GST_CLOCK_TIME_IS_VALID (t) ? \ + (guint) ((((GstClockTime)(t)) / GST_SECOND) % 60) : 99, \ + GST_CLOCK_TIME_IS_VALID (t) ? \ + (guint) (((GstClockTime)(t)) % GST_SECOND) : 999999999 + +/** + * GST_CLOCK_ENTRY_TRACE_NAME: + * + * The name used for tracing clock entry allocations. + */ +#define GST_CLOCK_ENTRY_TRACE_NAME "GstClockEntry" + +typedef struct _GstClockEntry GstClockEntry; +typedef struct _GstClock GstClock; +typedef struct _GstClockClass GstClockClass; +typedef struct _GstClockPrivate GstClockPrivate; + +/* --- prototype for async callbacks --- */ +/** + * GstClockCallback: + * @clock: The clock that triggered the callback + * @time: The time it was triggered + * @id: The #GstClockID that expired + * @user_data: user data passed in the gst_clock_id_wait_async() function + * + * The function prototype of the callback. + * + * Returns: %TRUE or %FALSE (currently unused) + */ +typedef gboolean (*GstClockCallback) (GstClock *clock, GstClockTime time, + GstClockID id, gpointer user_data); +/** + * GstClockReturn: + * @GST_CLOCK_OK: The operation succeeded. + * @GST_CLOCK_EARLY: The operation was scheduled too late. + * @GST_CLOCK_UNSCHEDULED: The clockID was unscheduled + * @GST_CLOCK_BUSY: The ClockID is busy + * @GST_CLOCK_BADTIME: A bad time was provided to a function. + * @GST_CLOCK_ERROR: An error occurred + * @GST_CLOCK_UNSUPPORTED: Operation is not supported + * @GST_CLOCK_DONE: The ClockID is done waiting (Since: 0.10.32) + * + * The return value of a clock operation. + */ +typedef enum +{ + GST_CLOCK_OK = 0, + GST_CLOCK_EARLY = 1, + GST_CLOCK_UNSCHEDULED = 2, + GST_CLOCK_BUSY = 3, + GST_CLOCK_BADTIME = 4, + GST_CLOCK_ERROR = 5, + GST_CLOCK_UNSUPPORTED = 6, + GST_CLOCK_DONE = 7 +} GstClockReturn; + +/** + * GstClockEntryType: + * @GST_CLOCK_ENTRY_SINGLE: a single shot timeout + * @GST_CLOCK_ENTRY_PERIODIC: a periodic timeout request + * + * The type of the clock entry + */ +typedef enum { + GST_CLOCK_ENTRY_SINGLE, + GST_CLOCK_ENTRY_PERIODIC +} GstClockEntryType; + +/** + * GST_CLOCK_ENTRY: + * @entry: the entry to cast + * + * Cast to a clock entry + */ +#define GST_CLOCK_ENTRY(entry) ((GstClockEntry *)(entry)) +/** + * GST_CLOCK_ENTRY_CLOCK: + * @entry: the entry to query + * + * Get the owner clock of the entry + */ +#define GST_CLOCK_ENTRY_CLOCK(entry) ((entry)->clock) +/** + * GST_CLOCK_ENTRY_TYPE: + * @entry: the entry to query + * + * Get the type of the clock entry + */ +#define GST_CLOCK_ENTRY_TYPE(entry) ((entry)->type) +/** + * GST_CLOCK_ENTRY_TIME: + * @entry: the entry to query + * + * Get the requested time of this entry + */ +#define GST_CLOCK_ENTRY_TIME(entry) ((entry)->time) +/** + * GST_CLOCK_ENTRY_INTERVAL: + * @entry: the entry to query + * + * Get the interval of this periodic entry + */ +#define GST_CLOCK_ENTRY_INTERVAL(entry) ((entry)->interval) +/** + * GST_CLOCK_ENTRY_STATUS: + * @entry: the entry to query + * + * The status of the entry + */ +#define GST_CLOCK_ENTRY_STATUS(entry) ((entry)->status) + +/** + * GstClockEntry: + * @refcount: reference counter (read-only) + * + * All pending timeouts or periodic notifies are converted into + * an entry. + * Note that GstClockEntry should be treated as an opaque structure. It must + * not be extended or allocated using a custom allocator. + */ +struct _GstClockEntry { + gint refcount; + /*< protected >*/ + GstClock *clock; + GstClockEntryType type; + GstClockTime time; + GstClockTime interval; + GstClockReturn status; + GstClockCallback func; + gpointer user_data; + GDestroyNotify destroy_data; + gboolean unscheduled; + gboolean woken_up; +}; + +/** + * GstClockFlags: + * @GST_CLOCK_FLAG_CAN_DO_SINGLE_SYNC: clock can do a single sync timeout request + * @GST_CLOCK_FLAG_CAN_DO_SINGLE_ASYNC: clock can do a single async timeout request + * @GST_CLOCK_FLAG_CAN_DO_PERIODIC_SYNC: clock can do sync periodic timeout requests + * @GST_CLOCK_FLAG_CAN_DO_PERIODIC_ASYNC: clock can do async periodic timeout callbacks + * @GST_CLOCK_FLAG_CAN_SET_RESOLUTION: clock's resolution can be changed + * @GST_CLOCK_FLAG_CAN_SET_MASTER: clock can be slaved to a master clock + * @GST_CLOCK_FLAG_LAST: subclasses can add additional flags starting from this flag + * + * The capabilities of this clock + */ +typedef enum { + GST_CLOCK_FLAG_CAN_DO_SINGLE_SYNC = (GST_OBJECT_FLAG_LAST << 0), + GST_CLOCK_FLAG_CAN_DO_SINGLE_ASYNC = (GST_OBJECT_FLAG_LAST << 1), + GST_CLOCK_FLAG_CAN_DO_PERIODIC_SYNC = (GST_OBJECT_FLAG_LAST << 2), + GST_CLOCK_FLAG_CAN_DO_PERIODIC_ASYNC = (GST_OBJECT_FLAG_LAST << 3), + GST_CLOCK_FLAG_CAN_SET_RESOLUTION = (GST_OBJECT_FLAG_LAST << 4), + GST_CLOCK_FLAG_CAN_SET_MASTER = (GST_OBJECT_FLAG_LAST << 5), + /* padding */ + GST_CLOCK_FLAG_LAST = (GST_OBJECT_FLAG_LAST << 8) +} GstClockFlags; + +/** + * GST_CLOCK_FLAGS: + * @clock: the clock to query + * + * Gets the #GstClockFlags clock flags. + */ +#define GST_CLOCK_FLAGS(clock) GST_OBJECT_FLAGS(clock) + +/** + * GST_CLOCK_COND: + * @clock: the clock to query + * + * Gets the #GCond that gets signalled when the entries of the clock + * changed. + */ +#define GST_CLOCK_COND(clock) (GST_CLOCK_CAST(clock)->entries_changed) +/** + * GST_CLOCK_WAIT: + * @clock: the clock to wait on + * + * Wait on the clock until the entries changed. + */ +#define GST_CLOCK_WAIT(clock) g_cond_wait(GST_CLOCK_COND(clock),GST_OBJECT_GET_LOCK(clock)) +/** + * GST_CLOCK_TIMED_WAIT: + * @clock: the clock to wait on + * @tv: a #GTimeVal to wait. + * + * Wait on the clock until the entries changed or the specified timeout + * occurred. + */ +#define GST_CLOCK_TIMED_WAIT(clock,tv) g_cond_timed_wait(GST_CLOCK_COND(clock),GST_OBJECT_GET_LOCK(clock),tv) +/** + * GST_CLOCK_BROADCAST: + * @clock: the clock to broadcast + * + * Signal that the entries in the clock have changed. + */ +#define GST_CLOCK_BROADCAST(clock) g_cond_broadcast(GST_CLOCK_COND(clock)) + +/** + * GstClock: + * + * #GstClock base structure. The values of this structure are + * protected for subclasses, use the methods to use the #GstClock. + */ +struct _GstClock { + GstObject object; + + GMutex *slave_lock; /* order: SLAVE_LOCK, OBJECT_LOCK */ + + /*< protected >*/ /* with LOCK */ + GstClockTime internal_calibration; + GstClockTime external_calibration; + GstClockTime rate_numerator; + GstClockTime rate_denominator; + GstClockTime last_time; + GList *entries; + GCond *entries_changed; + + /*< private >*/ /* with LOCK */ + GstClockTime resolution; + gboolean stats; + + /* for master/slave clocks */ + GstClock *master; + + /* with SLAVE_LOCK */ + gboolean filling; + gint window_size; + gint window_threshold; + gint time_index; + GstClockTime timeout; + GstClockTime *times; + GstClockID clockid; + + /*< private >*/ + union { + GstClockPrivate *priv; + GstClockTime _gst_reserved[GST_PADDING]; + } ABI; +}; + +/** + * GstClockClass: + * @parent_class: the parent class structure + * @change_resolution: change the resolution of the clock. Not all values might + * be acceptable. The new resolution should be returned. + * @get_resolution: get the resolution of the clock. + * @get_internal_time: get the internal unadjusted time of the clock. + * implement @wait_jitter instead. + * @wait: perform a blocking wait on the given #GstClockEntry and return + * the jitter. + * @wait_async: perform an asynchronous wait for the given #GstClockEntry. + * @unschedule: unblock a blocking or async wait operation. + * + * GStreamer clock class. Override the vmethods to implement the clock + * functionality. + */ +struct _GstClockClass { + GstObjectClass parent_class; + + /*< public >*/ + /* vtable */ + GstClockTime (*change_resolution) (GstClock *clock, + GstClockTime old_resolution, + GstClockTime new_resolution); + GstClockTime (*get_resolution) (GstClock *clock); + + GstClockTime (*get_internal_time) (GstClock *clock); + + /* waiting on an ID */ + GstClockReturn (*wait) (GstClock *clock, GstClockEntry *entry, + GstClockTimeDiff *jitter); + GstClockReturn (*wait_async) (GstClock *clock, GstClockEntry *entry); + void (*unschedule) (GstClock *clock, GstClockEntry *entry); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_clock_get_type (void); + +GstClockTime gst_clock_set_resolution (GstClock *clock, + GstClockTime resolution); +GstClockTime gst_clock_get_resolution (GstClock *clock); + +GstClockTime gst_clock_get_time (GstClock *clock); +void gst_clock_set_calibration (GstClock *clock, GstClockTime internal, + GstClockTime external, + GstClockTime rate_num, + GstClockTime rate_denom); +void gst_clock_get_calibration (GstClock *clock, GstClockTime *internal, + GstClockTime *external, + GstClockTime *rate_num, + GstClockTime *rate_denom); + +/* master/slave clocks */ +gboolean gst_clock_set_master (GstClock *clock, GstClock *master); +GstClock* gst_clock_get_master (GstClock *clock); +gboolean gst_clock_add_observation (GstClock *clock, GstClockTime slave, + GstClockTime master, gdouble *r_squared); + + +/* getting and adjusting internal/external time */ +GstClockTime gst_clock_get_internal_time (GstClock *clock); +GstClockTime gst_clock_adjust_unlocked (GstClock *clock, GstClockTime internal); +GstClockTime gst_clock_unadjust_unlocked (GstClock * clock, GstClockTime external); + + +/* creating IDs that can be used to get notifications */ +GstClockID gst_clock_new_single_shot_id (GstClock *clock, + GstClockTime time); +GstClockID gst_clock_new_periodic_id (GstClock *clock, + GstClockTime start_time, + GstClockTime interval); + +/* reference counting */ +GstClockID gst_clock_id_ref (GstClockID id); +void gst_clock_id_unref (GstClockID id); + +/* operations on IDs */ +gint gst_clock_id_compare_func (gconstpointer id1, gconstpointer id2); + +GstClockTime gst_clock_id_get_time (GstClockID id); +GstClockReturn gst_clock_id_wait (GstClockID id, + GstClockTimeDiff *jitter); +GstClockReturn gst_clock_id_wait_async (GstClockID id, + GstClockCallback func, + gpointer user_data); +GstClockReturn gst_clock_id_wait_async_full (GstClockID id, + GstClockCallback func, + gpointer user_data, + GDestroyNotify destroy_data); +void gst_clock_id_unschedule (GstClockID id); + +gboolean gst_clock_single_shot_id_reinit (GstClock * clock, + GstClockID id, + GstClockTime time); +gboolean gst_clock_periodic_id_reinit (GstClock * clock, + GstClockID id, + GstClockTime start_time, + GstClockTime interval); + +G_END_DECLS + +#endif /* __GST_CLOCK_H__ */ diff --git a/gst/gstcompat.h b/gst/gstcompat.h new file mode 100644 index 0000000..cba5fc2 --- /dev/null +++ b/gst/gstcompat.h @@ -0,0 +1,61 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2004 Wim Taymans <wim@fluendo.com> + * + * gstcompat.h: backwards compatibility stuff + * + * 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:gstcompat + * @short_description: Deprecated API entries + * + * Please do not use these in new code. + * These symbols are only available by defining GST_DISABLE_DEPRECATED. + * This can be done in CFLAGS for compiling old code. + */ + +/* API compatibility stuff */ +#ifndef __GSTCOMPAT_H__ +#define __GSTCOMPAT_H__ + +G_BEGIN_DECLS + +/* added to ease the transition to 0.11 */ +#define gst_element_class_set_details_simple gst_element_class_set_metadata + +#define gst_element_factory_get_longname(f) gst_element_factory_get_metadata(f, GST_ELEMENT_METADATA_LONGNAME) +#define gst_element_factory_get_klass(f) gst_element_factory_get_metadata(f, GST_ELEMENT_METADATA_KLASS) +#define gst_element_factory_get_description(f) gst_element_factory_get_metadata(f, GST_ELEMENT_METADATA_DESCRIPTION) +#define gst_element_factory_get_author(f) gst_element_factory_get_metadata(f, GST_ELEMENT_METADATA_AUTHOR) +#define gst_element_factory_get_documentation_uri(f) gst_element_factory_get_metadata(f, GST_ELEMENT_METADATA_DOC_URI) +#define gst_element_factory_get_icon_name(f) gst_element_factory_get_metadata(f, GST_ELEMENT_METADATA_ICON_NAME) + +#define gst_pad_get_caps_reffed(p) gst_pad_get_caps(p) +#define gst_pad_peer_get_caps_reffed(p) gst_pad_peer_get_caps(p) + +//#define gst_buffer_create_sub(b,o,s) gst_buffer_copy_region(b,GST_BUFFER_COPY_ALL,o,s) + +#define gst_buffer_new_and_alloc(s) gst_buffer_new_allocate(NULL, s, 0) + + +#ifndef GST_DISABLE_DEPRECATED + +#endif /* not GST_DISABLE_DEPRECATED */ + +G_END_DECLS + +#endif /* __GSTCOMPAT_H__ */ diff --git a/gst/gstconfig.h.in b/gst/gstconfig.h.in new file mode 100644 index 0000000..7af2496 --- /dev/null +++ b/gst/gstconfig.h.in @@ -0,0 +1,199 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2004,2005 Wim Taymans <wim@fluendo.com> + * + * gstconfig.h: GST_DISABLE_* macros for build configuration + * + * 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:gstconfig + * @short_description: Build configuration options + * + * This describes the configuration options for GStreamer. When building + * GStreamer there are a lot of parts (known internally as "subsystems" ) that + * can be disabled for various reasons. The most common reasons are speed and + * size, which is important because GStreamer is designed to run on embedded + * systems. + * + * If a subsystem is disabled, most of this changes are done in an API + * compatible way, so you don't need to adapt your code in most cases. It is + * never done in an ABI compatible way though. So if you want to disable a + * suybsystem, you have to rebuild all programs depending on GStreamer, too. + * + * If a subsystem is disabled in GStreamer, a value is defined in + * <gst/gst.h>. You can check this if you do subsystem-specific stuff. + * <example id="example-gstconfig"> + * <title>Doing subsystem specific things</title> + * <programlisting> + * &hash;ifndef GST_DISABLE_GST_DEBUG + * // do stuff specific to the debugging subsystem + * &hash;endif // GST_DISABLE_GST_DEBUG + * </programlisting> + * </example> + */ + +#ifndef __GST_CONFIG_H__ +#define __GST_CONFIG_H__ + +/* trick gtk-doc into believing these symbols are defined (yes, it's ugly) */ + +#if 0 +#define GST_DISABLE_GST_DEBUG 1 +#define GST_DISABLE_PARSE 1 +#define GST_DISABLE_TRACE 1 +#define GST_DISABLE_ALLOC_TRACE 1 +#define GST_DISABLE_REGISTRY 1 +#define GST_DISABLE_PLUGIN 1 +#define GST_HAVE_GLIB_2_8 1 +#endif + +/***** default padding of structures *****/ +#define GST_PADDING 4 +#define GST_PADDING_INIT { NULL } + +/***** padding for very extensible base classes *****/ +#define GST_PADDING_LARGE 20 + +/***** disabling of subsystems *****/ + +/** + * GST_DISABLE_GST_DEBUG: + * + * Configures the inclusion of the debugging subsystem + */ +@GST_DISABLE_GST_DEBUG_DEFINE@ + +/** + * GST_DISABLE_PARSE: + * + * Configures the inclusion of the gst-lauch parser + */ +@GST_DISABLE_PARSE_DEFINE@ + +/** + * GST_DISABLE_TRACE: + * + * Configures the inclusion of a resource tracing facillity + * (seems to be unused) + */ +@GST_DISABLE_TRACE_DEFINE@ + +/** + * GST_DISABLE_ALLOC_TRACE: + * + * Configures the use of a memory tracer based on the resource tracer + * if TRACE is disabled, ALLOC_TRACE is disabled as well + */ +@GST_DISABLE_ALLOC_TRACE_DEFINE@ + +/** + * GST_DISABLE_REGISTRY: + * + * Configures the use of the plugin registry. + * If one disables this, required plugins need to be loaded and registered + * manually + */ +@GST_DISABLE_REGISTRY_DEFINE@ + +/* FIXME: test and document these! */ +/* Configures the use of external plugins */ +@GST_DISABLE_PLUGIN_DEFINE@ + +/* printf extension format */ +/** + * GST_PTR_FORMAT: + * + * printf format type used to debug GStreamer types. + * This can only be used on types whose size is >= sizeof(gpointer). + */ +@GST_PRINTF_EXTENSION_POINTER_FORMAT_DEFINE@ +/** + * GST_SEGMENT_FORMAT: + * + * printf format type used to debug GStreamer segments. + * This can only be used on pointers to GstSegment structures. + * + * Since: 0.10.10 + */ +@GST_PRINTF_EXTENSION_SEGMENT_FORMAT_DEFINE@ + +/* whether or not GST_PTR_FORMAT or GST_SEGMENT_FORMAT are using + * the printf extension mechanism. This is for internal use in our + * header files so we know whether we can use G_GNUC_PRINTF or not */ +@GST_USING_PRINTF_EXTENSION_DEFINE@ + +/* GST_DISABLE_PRINTF_EXTENSION: + * + * Define this to debug your debug log messages and make gcc spew warnings + * if printf format string and arguments don't match up (this is usually + * not the case when libc and gcc are used because printf format warnings + * have to be disabled when the printf extension mechanism is in use). + * + * Note that using this option disables 'pretty logging' of GStreamer objects + * like caps, tags, structures, events, pads etc., so that only their address + * will be printed in the log. + * + * This define only disables use of the special registered printf format + * extensions in the code compiled with it defined. It does not stop + * GStreamer from registering these extensions in the first place if it + * was compiled against a libc that supports this. + * + * (not official API) + */ +/* If GLib is not using the system printf, we can't use the registered + * extensions because the GLib-internal printf won't know how to parse them */ +#if defined(GST_DISABLE_PRINTF_EXTENSION) || !defined(GLIB_USING_SYSTEM_PRINTF) + #undef GST_PTR_FORMAT + #define GST_PTR_FORMAT "p" + #undef GST_SEGMENT_FORMAT + #define GST_SEGMENT_FORMAT "p" + #undef GST_USING_PRINTF_EXTENSION +#endif + +/* whether or not the CPU supports unaligned access */ +@GST_HAVE_UNALIGNED_ACCESS_DEFINE@ + +/** + * GST_EXPORT: + * + * Export the given variable from the built shared object. + * + * On Windows, this exports the variable from the DLL. + * On other platforms, this gets defined to "extern". + */ +/** + * GST_PLUGIN_EXPORT: + * + * Export the plugin's definition. + * + * On Windows, this exports the plugin definition from the DLL. + * On other platforms, this gets defined as a no-op. + */ +#ifdef _MSC_VER +#define GST_PLUGIN_EXPORT __declspec(dllexport) extern +#ifdef GST_EXPORTS +#define GST_EXPORT __declspec(dllexport) extern +#else +#define GST_EXPORT __declspec(dllimport) extern +#endif +#else /* not _MSC_VER */ +#define GST_PLUGIN_EXPORT +#define GST_EXPORT extern +#endif + +#endif /* __GST_CONFIG_H__ */ diff --git a/gst/gstdatetime.c b/gst/gstdatetime.c new file mode 100644 index 0000000..60f709f --- /dev/null +++ b/gst/gstdatetime.c @@ -0,0 +1,816 @@ +/* GStreamer + * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk> + * + * 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 "glib-compat-private.h" +#include "gst_private.h" +#include "gstdatetime.h" +#include <glib.h> +#include <math.h> + +/** + * SECTION:gstdatetime + * @title: GstDateTime + * @short_description: A date, time and timezone structure + * + * Struct to store date, time and timezone information altogether. + * #GstDateTime is refcounted and immutable. + * + * Date information is handled using the proleptic Gregorian calendar. + * + * Provides basic creation functions and accessor functions to its fields. + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_get_year: + * @datetime: a #GstDateTime + * + * Returns the year of this #GstDateTime + * + * Return value: The year of this #GstDateTime + * Since: 0.10.31 + */ + +/** + * gst_date_time_get_month: + * @datetime: a #GstDateTime + * + * Returns the month of this #GstDateTime. January is 1, February is 2, etc.. + * + * Return value: The month of this #GstDateTime + * Since: 0.10.31 + */ + +/** + * gst_date_time_get_day: + * @datetime: a #GstDateTime + * + * Returns the day of this #GstDateTime. + * + * Return value: The day of this #GstDateTime + * Since: 0.10.31 + */ + +/** + * gst_date_time_get_hour: + * @datetime: a #GstDateTime + * + * Retrieves the hour of the day represented by @datetime in the gregorian + * calendar. The return is in the range of 0 to 23. + * + * Return value: the hour of the day + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_get_microsecond: + * @datetime: a #GstDateTime + * + * Retrieves the fractional part of the seconds in microseconds represented by + * @datetime in the gregorian calendar. + * + * Return value: the microsecond of the second + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_get_minute: + * @datetime: a #GstDateTime + * + * Retrieves the minute of the hour represented by @datetime in the gregorian + * calendar. + * + * Return value: the minute of the hour + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_get_second: + * @datetime: a #GstDateTime + * + * Retrieves the second of the minute represented by @datetime in the gregorian + * calendar. + * + * Return value: the second represented by @datetime + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_get_second: + * @datetime: a #GstDateTime + * + * Retrieves the second of the minute represented by @datetime in the gregorian + * calendar. + * + * Return value: the second represented by @datetime + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_get_time_zone_offset: + * @datetime: a #GstDateTime + * + * Retrieves the offset from UTC in hours that the timezone specified + * by @datetime represents. Timezones ahead (to the east) of UTC have positive + * values, timezones before (to the west) of UTC have negative values. + * If @datetime represents UTC time, then the offset is zero. + * + * Return value: the offset from UTC in hours + * Since: 0.10.31 + */ + +/** + * gst_date_time_new_from_unix_epoch_local_time: + * @secs: seconds from the Unix epoch + * + * Creates a new #GstDateTime using the time since Jan 1, 1970 specified by + * @secs. The #GstDateTime is in the local timezone. + * + * Free-function: gst_date_time_unref + * + * Return value: (transfer full): the newly created #GstDateTime + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_new_from_unix_epoch_utc: + * @secs: seconds from the Unix epoch + * + * Creates a new #GstDateTime using the time since Jan 1, 1970 specified by + * @secs. The #GstDateTime is in the UTC timezone. + * + * Free-function: gst_date_time_unref + * + * Return value: (transfer full): the newly created #GstDateTime + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_new_local_time: + * @year: the gregorian year + * @month: the gregorian month + * @day: the day of the gregorian month + * @hour: the hour of the day + * @minute: the minute of the hour + * @seconds: the second of the minute + * + * Creates a new #GstDateTime using the date and times in the gregorian calendar + * in the local timezone. + * + * @year should be from 1 to 9999, @month should be from 1 to 12, @day from + * 1 to 31, @hour from 0 to 23, @minutes and @seconds from 0 to 59. + * + * Free-function: gst_date_time_unref + * + * Return value: (transfer full): the newly created #GstDateTime + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_new: + * @tzoffset: Offset from UTC in hours. + * @year: the gregorian year + * @month: the gregorian month + * @day: the day of the gregorian month + * @hour: the hour of the day + * @minute: the minute of the hour + * @seconds: the second of the minute + * + * Creates a new #GstDateTime using the date and times in the gregorian calendar + * in the supplied timezone. + * + * @year should be from 1 to 9999, @month should be from 1 to 12, @day from + * 1 to 31, @hour from 0 to 23, @minutes and @seconds from 0 to 59. + * + * Note that @tzoffset is a float and was chosen so for being able to handle + * some fractional timezones, while it still keeps the readability of + * represeting it in hours for most timezones. + * + * Free-function: gst_date_time_unref + * + * Return value: (transfer full): the newly created #GstDateTime + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_new_now_local_time: + * + * Creates a new #GstDateTime representing the current date and time. + * + * Free-function: gst_date_time_unref + * + * Return value: (transfer full): the newly created #GstDateTime which should + * be freed with gst_date_time_unref(). + * + * Since: 0.10.31 + */ + +/** + * gst_date_time_new_now_utc: + * + * Creates a new #GstDateTime that represents the current instant at Universal + * coordinated time. + * + * Free-function: gst_date_time_unref + * + * Return value: (transfer full): the newly created #GstDateTime which should + * be freed with gst_date_time_unref(). + * + * Since: 0.10.31 + */ + + +#define GST_DATE_TIME_SEC_PER_DAY (G_GINT64_CONSTANT (86400)) +#define GST_DATE_TIME_USEC_PER_DAY (G_GINT64_CONSTANT (86400000000)) +#define GST_DATE_TIME_USEC_PER_HOUR (G_GINT64_CONSTANT (3600000000)) +#define GST_DATE_TIME_USEC_PER_MINUTE (G_GINT64_CONSTANT (60000000)) +#define GST_DATE_TIME_USEC_PER_SECOND (G_GINT64_CONSTANT (1000000)) +#define GST_DATE_TIME_USEC_PER_MILLISECOND (G_GINT64_CONSTANT (1000)) + +/* Jan 5th 2011 (Edward) : GLib's GDateTime is broken in regards to gmt offset + * on macosx. Re-enable it once the following bug is fixed: + * https://bugzilla.gnome.org/show_bug.cgi?id=638666 */ +#ifdef HAVE_OSX +#undef GLIB_HAS_GDATETIME +#endif + + +#ifndef GLIB_HAS_GDATETIME + +#define MAX_SUPPORTED_YEAR 9999 +#define GREGORIAN_LEAP(y) (((y%4)==0)&&(!(((y%100)==0)&&((y%400)!=0)))) + +static const guint16 days_in_months[2][13] = { + {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +}; + +struct _GstDateTime +{ + /* + * As we don't have a datetime math API, we can have fields split here. + * (There is still some math done internally, but nothing really relevant). + * + * If we ever add one, we should go for a days since some epoch counter. + * (Proleptic Gregorian with 0001-01-01 as day 1) + */ + gint16 year; + gint8 month; + gint8 day; + guint64 usec; /* Microsecond timekeeping within Day */ + + gint tzoffset; + + volatile gint ref_count; +}; + +/* + * Returns the utc offset in seconds for this time structure + */ +static gint +gmt_offset (struct tm *tm, time_t t) +{ +#if defined (HAVE_TM_GMTOFF) + return tm->tm_gmtoff; +#else + struct tm g; + time_t t2; +#ifdef HAVE_GMTIME_R + gmtime_r (&t, &g); +#else + g = *gmtime (&t); +#endif + t2 = mktime (&g); + return (int) difftime (t, t2); +#endif +} + +static void +gst_date_time_set_local_timezone (GstDateTime * dt) +{ + struct tm tt; + time_t t; + + g_return_if_fail (dt != NULL); + + memset (&tt, 0, sizeof (tt)); + + tt.tm_mday = gst_date_time_get_day (dt); + tt.tm_mon = gst_date_time_get_month (dt) - 1; + tt.tm_year = gst_date_time_get_year (dt) - 1900; + tt.tm_hour = gst_date_time_get_hour (dt); + tt.tm_min = gst_date_time_get_minute (dt); + tt.tm_sec = gst_date_time_get_second (dt); + + t = mktime (&tt); + + dt->tzoffset = gmt_offset (&tt, t) / 60; +} + +static GstDateTime * +gst_date_time_alloc (void) +{ + GstDateTime *datetime; + + datetime = g_slice_new0 (GstDateTime); + datetime->ref_count = 1; + + return datetime; +} + +static void +gst_date_time_free (GstDateTime * datetime) +{ + g_slice_free (GstDateTime, datetime); +} + +static GstDateTime * +gst_date_time_new_from_date (gint year, gint month, gint day) +{ + GstDateTime *dt; + + g_return_val_if_fail (year > 0 && year <= 9999, NULL); + g_return_val_if_fail ((month > 0 && month <= 12), NULL); + g_return_val_if_fail ((day > 0 && day <= 31), NULL); + + dt = gst_date_time_alloc (); + + dt->year = year; + dt->month = month; + dt->day = day; + gst_date_time_set_local_timezone (dt); + + return dt; +} + +gint +gst_date_time_get_year (const GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, 0); + + return datetime->year; +} + +gint +gst_date_time_get_month (const GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, 0); + + return datetime->month; +} + +gint +gst_date_time_get_day (const GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, 0); + + return datetime->day; +} + +gint +gst_date_time_get_hour (const GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, 0); + return (datetime->usec / GST_DATE_TIME_USEC_PER_HOUR); +} + +gint +gst_date_time_get_microsecond (const GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, 0); + return (datetime->usec % GST_DATE_TIME_USEC_PER_SECOND); +} + +gint +gst_date_time_get_minute (const GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, 0); + return (datetime->usec % GST_DATE_TIME_USEC_PER_HOUR) / + GST_DATE_TIME_USEC_PER_MINUTE; +} + +gint +gst_date_time_get_second (const GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, 0); + return (datetime->usec % GST_DATE_TIME_USEC_PER_MINUTE) / + GST_DATE_TIME_USEC_PER_SECOND; +} + +gfloat +gst_date_time_get_time_zone_offset (const GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, 0); + + return datetime->tzoffset / 60.0f; +} + +GstDateTime * +gst_date_time_new_from_unix_epoch_local_time (gint64 secs) +{ + GstDateTime *dt; + struct tm tm; + time_t tt; + + memset (&tm, 0, sizeof (tm)); + tt = (time_t) secs; + +#ifdef HAVE_LOCALTIME_R + localtime_r (&tt, &tm); +#else + memcpy (&tm, localtime (&tt), sizeof (struct tm)); +#endif + + dt = gst_date_time_new (0, tm.tm_year + 1900, + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + gst_date_time_set_local_timezone (dt); + return dt; +} + +GstDateTime * +gst_date_time_new_from_unix_epoch_utc (gint64 secs) +{ + GstDateTime *dt; + struct tm tm; + time_t tt; + + memset (&tm, 0, sizeof (tm)); + tt = (time_t) secs; + +#ifdef HAVE_GMTIME_R + gmtime_r (&tt, &tm); +#else + memcpy (&tm, gmtime (&tt), sizeof (struct tm)); +#endif + + dt = gst_date_time_new (0, tm.tm_year + 1900, + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + return dt; +} + +GstDateTime * +gst_date_time_new_local_time (gint year, gint month, gint day, gint hour, + gint minute, gdouble seconds) +{ + GstDateTime *dt; + + dt = gst_date_time_new (0, year, month, day, hour, minute, seconds); + + gst_date_time_set_local_timezone (dt); + + return dt; +} + +GstDateTime * +gst_date_time_new (gfloat tzoffset, gint year, gint month, gint day, gint hour, + gint minute, gdouble seconds) +{ + GstDateTime *dt; + + g_return_val_if_fail (hour >= 0 && hour < 24, NULL); + g_return_val_if_fail (minute >= 0 && minute < 60, NULL); + g_return_val_if_fail (seconds >= 0 && seconds < 60, NULL); + g_return_val_if_fail (tzoffset >= -12.0 && tzoffset <= 12.0, NULL); + + if (!(dt = gst_date_time_new_from_date (year, month, day))) + return NULL; + + dt->usec = (hour * GST_DATE_TIME_USEC_PER_HOUR) + + (minute * GST_DATE_TIME_USEC_PER_MINUTE) + + (guint64) (floor (seconds * GST_DATE_TIME_USEC_PER_SECOND + 0.5)); + + /* we store in minutes */ + dt->tzoffset = (gint) (tzoffset * 60.0); + + return dt; +} + +GstDateTime * +gst_date_time_new_now_local_time (void) +{ + GstDateTime *datetime; + GTimeVal tv; + g_get_current_time (&tv); + + datetime = gst_date_time_new_from_unix_epoch_local_time (tv.tv_sec); + datetime->usec += tv.tv_usec; + gst_date_time_set_local_timezone (datetime); + return datetime; +} + +static GstDateTime * +gst_date_time_copy (const GstDateTime * dt) +{ + GstDateTime *copy = gst_date_time_alloc (); + + memcpy (copy, dt, sizeof (GstDateTime)); + copy->ref_count = 1; + + return copy; +} + +static GstDateTime * +gst_date_time_to_utc (const GstDateTime * dt) +{ + GstDateTime *utc; + gint64 usec; + gint days; + gint leap; + + g_return_val_if_fail (dt != NULL, NULL); + + utc = gst_date_time_copy (dt); + + usec = dt->usec - dt->tzoffset * GST_DATE_TIME_USEC_PER_MINUTE; + days = usec / GST_DATE_TIME_USEC_PER_DAY; + if (usec < 0) + days--; + utc->day += days; + + leap = GREGORIAN_LEAP (utc->year) ? 1 : 0; + + /* check if we should update month/year */ + if (utc->day < 1) { + if (utc->month == 1) { + utc->year--; + utc->month = 12; + } else { + utc->month--; + } + if (GREGORIAN_LEAP (utc->year)) + utc->day = days_in_months[1][utc->month]; + else + utc->day = days_in_months[0][utc->month]; + } else if (utc->day > days_in_months[leap][utc->month]) { + if (utc->month == 12) { + utc->year++; + utc->month = 1; + } else { + utc->month++; + } + utc->day = 1; + } + + if (usec < 0) + utc->usec = + GST_DATE_TIME_USEC_PER_DAY + (usec % GST_DATE_TIME_USEC_PER_DAY); + else + utc->usec = usec % GST_DATE_TIME_USEC_PER_DAY; + + return utc; +} + +GstDateTime * +gst_date_time_new_now_utc (void) +{ + GstDateTime *now, *utc; + + now = gst_date_time_new_now_local_time (); + utc = gst_date_time_to_utc (now); + gst_date_time_unref (now); + return utc; +} + +gint +priv_gst_date_time_compare (gconstpointer dt1, gconstpointer dt2) +{ + GstDateTime *a, *b; + gint res = 0; + + a = gst_date_time_to_utc (dt1); + b = gst_date_time_to_utc (dt2); + +#define GST_DATE_TIME_COMPARE_VALUE(a,b,v) \ + if ((a)->v > (b)->v) { \ + res = 1; \ + goto done; \ + } else if ((a)->v < (b)->v) { \ + res = -1; \ + goto done; \ + } + + GST_DATE_TIME_COMPARE_VALUE (a, b, year); + GST_DATE_TIME_COMPARE_VALUE (a, b, month); + GST_DATE_TIME_COMPARE_VALUE (a, b, day); + GST_DATE_TIME_COMPARE_VALUE (a, b, usec); + +#undef GST_DATE_TIME_COMPARE_VALUE + +done: + gst_date_time_unref (a); + gst_date_time_unref (b); + return res; +} + +#else + +struct _GstDateTime +{ + GDateTime *datetime; + + volatile gint ref_count; +}; + +static GstDateTime * +gst_date_time_new_from_gdatetime (GDateTime * dt) +{ + GstDateTime *gst_dt; + + if (!dt) + return NULL; + + gst_dt = g_slice_new (GstDateTime); + gst_dt->datetime = dt; + gst_dt->ref_count = 1; + return gst_dt; +} + +gint +gst_date_time_get_year (const GstDateTime * datetime) +{ + return g_date_time_get_year (datetime->datetime); +} + +gint +gst_date_time_get_month (const GstDateTime * datetime) +{ + return g_date_time_get_month (datetime->datetime); +} + +gint +gst_date_time_get_day (const GstDateTime * datetime) +{ + return g_date_time_get_day_of_month (datetime->datetime); +} + +gint +gst_date_time_get_hour (const GstDateTime * datetime) +{ + return g_date_time_get_hour (datetime->datetime); +} + +gint +gst_date_time_get_minute (const GstDateTime * datetime) +{ + return g_date_time_get_minute (datetime->datetime); +} + +gint +gst_date_time_get_second (const GstDateTime * datetime) +{ + return g_date_time_get_second (datetime->datetime); +} + +gint +gst_date_time_get_microsecond (const GstDateTime * datetime) +{ + return g_date_time_get_microsecond (datetime->datetime); +} + +gfloat +gst_date_time_get_time_zone_offset (const GstDateTime * datetime) +{ + return (g_date_time_get_utc_offset (datetime->datetime) / + G_USEC_PER_SEC) / 3600.0; +} + +GstDateTime * +gst_date_time_new_from_unix_epoch_local_time (gint64 secs) +{ + return + gst_date_time_new_from_gdatetime (g_date_time_new_from_unix_local (secs)); +} + +GstDateTime * +gst_date_time_new_from_unix_epoch_utc (gint64 secs) +{ + return + gst_date_time_new_from_gdatetime (g_date_time_new_from_unix_utc (secs)); +} + +GstDateTime * +gst_date_time_new_local_time (gint year, gint month, gint day, gint hour, + gint minute, gdouble seconds) +{ + return gst_date_time_new_from_gdatetime (g_date_time_new_local (year, month, + day, hour, minute, seconds)); +} + +GstDateTime * +gst_date_time_new_now_local_time (void) +{ + return gst_date_time_new_from_gdatetime (g_date_time_new_now_local ()); +} + +GstDateTime * +gst_date_time_new_now_utc (void) +{ + return gst_date_time_new_from_gdatetime (g_date_time_new_now_utc ()); +} + +gint +priv_gst_date_time_compare (gconstpointer dt1, gconstpointer dt2) +{ + const GstDateTime *datetime1 = dt1; + const GstDateTime *datetime2 = dt2; + return g_date_time_compare (datetime1->datetime, datetime2->datetime); +} + +GstDateTime * +gst_date_time_new (gfloat tzoffset, gint year, gint month, gint day, gint hour, + gint minute, gdouble seconds) +{ + gchar buf[6]; + GTimeZone *tz; + GDateTime *dt; + gint tzhour, tzminute; + + tzhour = (gint) ABS (tzoffset); + tzminute = (gint) ((ABS (tzoffset) - tzhour) * 60); + + g_snprintf (buf, 6, "%c%02d%02d", tzoffset >= 0 ? '+' : '-', tzhour, + tzminute); + + tz = g_time_zone_new (buf); + + dt = g_date_time_new (tz, year, month, day, hour, minute, seconds); + g_time_zone_unref (tz); + return gst_date_time_new_from_gdatetime (dt); +} + +static void +gst_date_time_free (GstDateTime * datetime) +{ + g_date_time_unref (datetime->datetime); + g_slice_free (GstDateTime, datetime); +} + +#endif + +/** + * gst_date_time_ref: + * @datetime: a #GstDateTime + * + * Atomically increments the reference count of @datetime by one. + * + * Return value: (transfer full): the reference @datetime + * + * Since: 0.10.31 + */ +GstDateTime * +gst_date_time_ref (GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, NULL); + g_return_val_if_fail (datetime->ref_count > 0, NULL); + g_atomic_int_inc (&datetime->ref_count); + return datetime; +} + +/** + * gst_date_time_unref: + * @datetime: (transfer full): a #GstDateTime + * + * Atomically decrements the reference count of @datetime by one. When the + * reference count reaches zero, the structure is freed. + * + * Since: 0.10.31 + */ +void +gst_date_time_unref (GstDateTime * datetime) +{ + g_return_if_fail (datetime != NULL); + g_return_if_fail (datetime->ref_count > 0); + + if (g_atomic_int_dec_and_test (&datetime->ref_count)) + gst_date_time_free (datetime); +} diff --git a/gst/gstdatetime.h b/gst/gstdatetime.h new file mode 100644 index 0000000..eeeb020 --- /dev/null +++ b/gst/gstdatetime.h @@ -0,0 +1,66 @@ +/* GStreamer + * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk> + * + * 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_DATE_TIME_H__ +#define __GST_DATE_TIME_H__ + +#include <time.h> +#include <glib.h> + +G_BEGIN_DECLS + +/** + * GstDateTime: + * + * Opaque, immutable, refcounted struct that stores date, time and timezone + * information. It currently supports ranges from 0001-01-01 to + * 9999-12-31 in the Gregorian proleptic calendar. + * + * Use the acessor functions to get the stored values. + */ +typedef struct _GstDateTime GstDateTime; + +gint gst_date_time_get_year (const GstDateTime * datetime); +gint gst_date_time_get_month (const GstDateTime * datetime); +gint gst_date_time_get_day (const GstDateTime * datetime); +gint gst_date_time_get_hour (const GstDateTime * datetime); +gint gst_date_time_get_minute (const GstDateTime * datetime); +gint gst_date_time_get_second (const GstDateTime * datetime); +gint gst_date_time_get_microsecond (const GstDateTime * datetime); +gfloat gst_date_time_get_time_zone_offset (const GstDateTime * datetime); + +GstDateTime *gst_date_time_new_from_unix_epoch_local_time (gint64 secs); +GstDateTime *gst_date_time_new_from_unix_epoch_utc (gint64 secs); +GstDateTime *gst_date_time_new_local_time (gint year, gint month, + gint day, gint hour, + gint minute, + gdouble seconds); +GstDateTime *gst_date_time_new (gfloat tzoffset, + gint year, gint month, + gint day, gint hour, + gint minute, + gdouble seconds); +GstDateTime *gst_date_time_new_now_local_time (void); +GstDateTime *gst_date_time_new_now_utc (void); + +GstDateTime *gst_date_time_ref (GstDateTime * datetime); +void gst_date_time_unref (GstDateTime * datetime); + +G_END_DECLS +#endif /* __GST_DATE_TIME_H__ */ diff --git a/gst/gstdebugutils.c b/gst/gstdebugutils.c new file mode 100644 index 0000000..de74850 --- /dev/null +++ b/gst/gstdebugutils.c @@ -0,0 +1,745 @@ +/* GStreamer + * Copyright (C) 2007 Stefan Kost <ensonic@users.sf.net> + * + * gstdebugutils.c: debugging and analysis utillities + * + * 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. + */ +/* TODO: + * edge [ constraint=false ]; + * this creates strange graphs ("minlen=0" is better) + * try puting src/sink ghostpads for each bin into invisible clusters + * + * for more compact nodes, try + * - changing node-shape from box into record + * - use labels like : element [ label="{element | <src> src | <sink> sink}"] + * - point to record-connectors : element1:src -> element2:sink + * - we use head/tail labels for pad-caps right now + * - this does not work well, as dot seems to not look at their size when + * doing the layout + * - we could add the caps to the pad itself, then we should use one line per + * caps (simple caps = one line) + */ + +#include "gst_private.h" +#include "gstdebugutils.h" + +#ifndef GST_DISABLE_GST_DEBUG + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "gstinfo.h" +#include "gstbin.h" +#include "gstobject.h" +#include "gstghostpad.h" +#include "gstpad.h" +#include "gstutils.h" +#include "gstvalue.h" + +/*** PIPELINE GRAPHS **********************************************************/ + +const gchar *priv_gst_dump_dot_dir; /* NULL *//* set from gst.c */ + +const gchar spaces[] = { + " " /* 32 */ + " " /* 64 */ + " " /* 96 */ + " " /* 128 */ +}; + +extern GstClockTime _priv_gst_info_start_time; + +static gchar * +debug_dump_make_object_name (GstObject * element) +{ + return g_strcanon (g_strdup_printf ("%s_%p", GST_OBJECT_NAME (element), + element), G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "_", '_'); +} + +static gchar * +debug_dump_get_element_state (GstElement * element) +{ + gchar *state_name = NULL; + const gchar *state_icons = "~0-=>"; + GstState state = GST_STATE_VOID_PENDING, pending = GST_STATE_VOID_PENDING; + + gst_element_get_state (element, &state, &pending, 0); + if (pending == GST_STATE_VOID_PENDING) { + state_name = g_strdup_printf ("\\n[%c]", state_icons[state]); + } else { + state_name = g_strdup_printf ("\\n[%c] -> [%c]", state_icons[state], + state_icons[pending]); + } + return state_name; +} + +static gchar * +debug_dump_get_element_params (GstElement * element) +{ + gchar *param_name = NULL; + GParamSpec **properties, *property; + GValue value = { 0, }; + guint i, number_of_properties; + gchar *tmp, *value_str; + + /* get paramspecs and show non-default properties */ + properties = + g_object_class_list_properties (G_OBJECT_CLASS (GST_ELEMENT_GET_CLASS + (element)), &number_of_properties); + if (properties) { + for (i = 0; i < number_of_properties; i++) { + property = properties[i]; + + /* ski some properties */ + if (!(property->flags & G_PARAM_READABLE)) + continue; + if (!strcmp (property->name, "name")) + continue; + + g_value_init (&value, property->value_type); + g_object_get_property (G_OBJECT (element), property->name, &value); + if (!(g_param_value_defaults (property, &value))) { + tmp = g_strdup_value_contents (&value); + value_str = g_strescape (tmp, NULL); + g_free (tmp); + if (param_name) { + tmp = param_name; + param_name = g_strdup_printf ("%s\\n%s=%s", + tmp, property->name, value_str); + g_free (tmp); + } else { + param_name = g_strdup_printf ("\\n%s=%s", property->name, value_str); + } + g_free (value_str); + } + g_value_unset (&value); + } + g_free (properties); + } + return param_name; +} + +static void +debug_dump_pad (GstPad * pad, const gchar * color_name, + const gchar * element_name, GstDebugGraphDetails details, FILE * out, + const gint indent) +{ + GstPadTemplate *pad_templ; + GstPadPresence presence; + gchar *pad_name; + const gchar *style_name; + const gchar *spc = &spaces[MAX (sizeof (spaces) - (1 + indent * 2), 0)]; + + pad_name = debug_dump_make_object_name (GST_OBJECT (pad)); + + /* pad availability */ + style_name = "filled,solid"; + if ((pad_templ = gst_pad_get_pad_template (pad))) { + presence = GST_PAD_TEMPLATE_PRESENCE (pad_templ); + if (presence == GST_PAD_SOMETIMES) { + style_name = "filled,dotted"; + } else if (presence == GST_PAD_REQUEST) { + style_name = "filled,dashed"; + } + } + if (details & GST_DEBUG_GRAPH_SHOW_STATES) { + gchar pad_flags[5]; + const gchar *activation_mode = "-><"; + + /* check if pad flags */ + pad_flags[0] = GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_BLOCKED) ? 'B' : 'b'; + pad_flags[1] = GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FLUSHING) ? 'F' : 'f'; + pad_flags[2] = GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_IN_GETCAPS) ? 'G' : 'g'; + pad_flags[3] = GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_BLOCKING) ? 'B' : 'b'; + pad_flags[4] = '\0'; + + fprintf (out, + "%s %s_%s [color=black, fillcolor=\"%s\", label=\"%s\\n[%c][%s]\", height=\"0.2\", style=\"%s\"];\n", + spc, element_name, pad_name, color_name, GST_OBJECT_NAME (pad), + activation_mode[pad->mode], pad_flags, style_name); + } else { + fprintf (out, + "%s %s_%s [color=black, fillcolor=\"%s\", label=\"%s\", height=\"0.2\", style=\"%s\"];\n", + spc, element_name, pad_name, color_name, GST_OBJECT_NAME (pad), + style_name); + } + + g_free (pad_name); +} + +static void +debug_dump_element_pad (GstPad * pad, GstElement * element, + GstDebugGraphDetails details, FILE * out, const gint indent) +{ + GstElement *target_element; + GstPad *target_pad, *tmp_pad; + GstPadDirection dir; + gchar *element_name; + gchar *target_element_name; + const gchar *color_name; + + dir = gst_pad_get_direction (pad); + element_name = debug_dump_make_object_name (GST_OBJECT (element)); + if (GST_IS_GHOST_PAD (pad)) { + color_name = + (dir == GST_PAD_SRC) ? "#ffdddd" : ((dir == + GST_PAD_SINK) ? "#ddddff" : "#ffffff"); + /* output target-pad so that it belongs to this element */ + if ((tmp_pad = gst_ghost_pad_get_target (GST_GHOST_PAD (pad)))) { + if ((target_pad = gst_pad_get_peer (tmp_pad))) { + if ((target_element = gst_pad_get_parent_element (target_pad))) { + target_element_name = + debug_dump_make_object_name (GST_OBJECT (target_element)); + } else { + target_element_name = g_strdup (""); + } + debug_dump_pad (target_pad, color_name, target_element_name, details, + out, indent); + g_free (target_element_name); + if (target_element) + gst_object_unref (target_element); + gst_object_unref (target_pad); + } + gst_object_unref (tmp_pad); + } + } else { + color_name = + (dir == GST_PAD_SRC) ? "#ffaaaa" : ((dir == + GST_PAD_SINK) ? "#aaaaff" : "#cccccc"); + } + /* pads */ + debug_dump_pad (pad, color_name, element_name, details, out, indent); + g_free (element_name); +} + +static gboolean +string_append_field (GQuark field, const GValue * value, gpointer ptr) +{ + GString *str = (GString *) ptr; + gchar *value_str = gst_value_serialize (value); + gchar *esc_value_str; + + /* some enums can become really long */ + if (strlen (value_str) > 25) { + gint pos = 24; + + /* truncate */ + value_str[25] = '\0'; + + /* mirror any brackets and quotes */ + if (value_str[0] == '<') + value_str[pos--] = '>'; + if (value_str[0] == '[') + value_str[pos--] = ']'; + if (value_str[0] == '(') + value_str[pos--] = ')'; + if (value_str[0] == '{') + value_str[pos--] = '}'; + if (value_str[0] == '"') + value_str[pos--] = '"'; + if (pos != 24) + value_str[pos--] = ' '; + /* elippsize */ + value_str[pos--] = '.'; + value_str[pos--] = '.'; + value_str[pos--] = '.'; + } + esc_value_str = g_strescape (value_str, NULL); + + g_string_append_printf (str, " %18s: %s\\l", g_quark_to_string (field), + esc_value_str); + + g_free (value_str); + g_free (esc_value_str); + return TRUE; +} + +static gchar * +debug_dump_describe_caps (GstCaps * caps, GstDebugGraphDetails details) +{ + gchar *media = NULL; + + if (details & GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS) { + + if (gst_caps_is_any (caps) || gst_caps_is_empty (caps)) { + media = gst_caps_to_string (caps); + + } else { + GString *str = NULL; + guint i; + guint slen = 0; + + for (i = 0; i < gst_caps_get_size (caps); i++) { + slen += 25 + + STRUCTURE_ESTIMATED_STRING_LEN (gst_caps_get_structure (caps, i)); + } + + str = g_string_sized_new (slen); + for (i = 0; i < gst_caps_get_size (caps); i++) { + GstStructure *structure = gst_caps_get_structure (caps, i); + + g_string_append (str, gst_structure_get_name (structure)); + g_string_append (str, "\\l"); + + gst_structure_foreach (structure, string_append_field, (gpointer) str); + } + + media = g_string_free (str, FALSE); + } + + } else { + if (GST_CAPS_IS_SIMPLE (caps)) + media = + g_strdup (gst_structure_get_name (gst_caps_get_structure (caps, 0))); + else + media = g_strdup ("*"); + } + return media; +} + +static void +debug_dump_element_pad_link (GstPad * pad, GstElement * element, + GstDebugGraphDetails details, FILE * out, const gint indent) +{ + GstElement *peer_element, *target_element; + GstPad *peer_pad, *target_pad, *tmp_pad; + GstCaps *caps, *peer_caps; + gchar *media = NULL; + gchar *media_src = NULL, *media_sink = NULL; + gchar *pad_name, *element_name; + gchar *peer_pad_name, *peer_element_name; + gchar *target_pad_name, *target_element_name; + const gchar *spc = &spaces[MAX (sizeof (spaces) - (1 + indent * 2), 0)]; + + if ((peer_pad = gst_pad_get_peer (pad))) { + if ((details & GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE) || + (details & GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS) + ) { + caps = gst_pad_get_current_caps (pad); + if (!caps) + caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + peer_caps = gst_pad_get_current_caps (peer_pad); + if (!peer_caps) + peer_caps = gst_caps_copy (gst_pad_get_pad_template_caps (peer_pad)); + + media = debug_dump_describe_caps (caps, details); + /* check if peer caps are different */ + if (peer_caps && !gst_caps_is_equal (caps, peer_caps)) { + gchar *tmp; + + tmp = debug_dump_describe_caps (peer_caps, details); + if (gst_pad_get_direction (pad) == GST_PAD_SRC) { + media_src = media; + media_sink = tmp; + } else { + media_src = tmp; + media_sink = media; + } + media = NULL; + } + gst_caps_unref (peer_caps); + gst_caps_unref (caps); + } + + pad_name = debug_dump_make_object_name (GST_OBJECT (pad)); + if (element) { + element_name = debug_dump_make_object_name (GST_OBJECT (element)); + } else { + element_name = g_strdup (""); + } + peer_pad_name = debug_dump_make_object_name (GST_OBJECT (peer_pad)); + if ((peer_element = gst_pad_get_parent_element (peer_pad))) { + peer_element_name = + debug_dump_make_object_name (GST_OBJECT (peer_element)); + } else { + peer_element_name = g_strdup (""); + } + + if (GST_IS_GHOST_PAD (pad)) { + if ((tmp_pad = gst_ghost_pad_get_target (GST_GHOST_PAD (pad)))) { + if ((target_pad = gst_pad_get_peer (tmp_pad))) { + target_pad_name = + debug_dump_make_object_name (GST_OBJECT (target_pad)); + if ((target_element = gst_pad_get_parent_element (target_pad))) { + target_element_name = + debug_dump_make_object_name (GST_OBJECT (target_element)); + } else { + target_element_name = g_strdup (""); + } + /* src ghostpad relationship */ + fprintf (out, "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n", spc, + target_element_name, target_pad_name, element_name, pad_name); + + g_free (target_pad_name); + g_free (target_element_name); + if (target_element) + gst_object_unref (target_element); + gst_object_unref (target_pad); + } + gst_object_unref (tmp_pad); + } + } + if (GST_IS_GHOST_PAD (peer_pad)) { + if ((tmp_pad = gst_ghost_pad_get_target (GST_GHOST_PAD (peer_pad)))) { + if ((target_pad = gst_pad_get_peer (tmp_pad))) { + target_pad_name = + debug_dump_make_object_name (GST_OBJECT (target_pad)); + if ((target_element = gst_pad_get_parent_element (target_pad))) { + target_element_name = + debug_dump_make_object_name (GST_OBJECT (target_element)); + } else { + target_element_name = g_strdup (""); + } + /* sink ghostpad relationship */ + fprintf (out, "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n", spc, + peer_element_name, peer_pad_name, + target_element_name, target_pad_name); + /* FIXME: we are missing links from the proxy pad + * theoretically we need to: + * pad=gst_object_ref(target_pad); + * goto line 280: if ((peer_pad = gst_pad_get_peer (pad))) + * as this would be ugly we need to refactor ... + */ + debug_dump_element_pad_link (target_pad, target_element, details, out, + indent); + g_free (target_pad_name); + g_free (target_element_name); + if (target_element) + gst_object_unref (target_element); + gst_object_unref (target_pad); + } + gst_object_unref (tmp_pad); + } + } + + /* pad link */ + if (media) { + fprintf (out, "%s%s_%s -> %s_%s [label=\"%s\"]\n", spc, + element_name, pad_name, peer_element_name, peer_pad_name, media); + g_free (media); + } else if (media_src && media_sink) { + /* dot has some issues with placement of head and taillabels, + * we need an empty label to make space */ + fprintf (out, "%s%s_%s -> %s_%s [labeldistance=\"10\", labelangle=\"0\", " + "label=\" \", " + "headlabel=\"%s\", taillabel=\"%s\"]\n", + spc, element_name, pad_name, peer_element_name, peer_pad_name, + media_src, media_sink); + g_free (media_src); + g_free (media_sink); + } else { + fprintf (out, "%s%s_%s -> %s_%s\n", spc, + element_name, pad_name, peer_element_name, peer_pad_name); + } + + g_free (pad_name); + g_free (element_name); + g_free (peer_pad_name); + g_free (peer_element_name); + if (peer_element) + gst_object_unref (peer_element); + gst_object_unref (peer_pad); + } +} + +static void +debug_dump_element_pads (GstIterator * pad_iter, GstPad * pad, + GstElement * element, GstDebugGraphDetails details, FILE * out, + const gint indent, guint * src_pads, guint * sink_pads) +{ + GValue item = { 0, }; + gboolean pads_done; + GstPadDirection dir; + + pads_done = FALSE; + while (!pads_done) { + switch (gst_iterator_next (pad_iter, &item)) { + case GST_ITERATOR_OK: + pad = g_value_get_object (&item); + debug_dump_element_pad (pad, element, details, out, indent); + dir = gst_pad_get_direction (pad); + if (dir == GST_PAD_SRC) + (*src_pads)++; + else if (dir == GST_PAD_SINK) + (*sink_pads)++; + g_value_reset (&item); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pad_iter); + break; + case GST_ITERATOR_ERROR: + case GST_ITERATOR_DONE: + pads_done = TRUE; + break; + } + } +} + +/* + * debug_dump_element: + * @bin: the bin that should be analyzed + * @out: file to write to + * @indent: level of graph indentation + * + * Helper for _gst_debug_bin_to_dot_file() to recursively dump a pipeline. + */ +static void +debug_dump_element (GstBin * bin, GstDebugGraphDetails details, FILE * out, + const gint indent) +{ + GstIterator *element_iter, *pad_iter; + gboolean elements_done, pads_done; + GValue item = { 0, }; + GValue item2 = { 0, }; + GstElement *element; + GstPad *pad = NULL; + guint src_pads, sink_pads; + gchar *element_name; + gchar *state_name = NULL; + gchar *param_name = NULL; + const gchar *spc = &spaces[MAX (sizeof (spaces) - (1 + indent * 2), 0)]; + + element_iter = gst_bin_iterate_elements (bin); + elements_done = FALSE; + while (!elements_done) { + switch (gst_iterator_next (element_iter, &item)) { + case GST_ITERATOR_OK: + element = g_value_get_object (&item); + element_name = debug_dump_make_object_name (GST_OBJECT (element)); + + if (details & GST_DEBUG_GRAPH_SHOW_STATES) { + state_name = debug_dump_get_element_state (GST_ELEMENT (element)); + } + if (details & GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS) { + param_name = debug_dump_get_element_params (GST_ELEMENT (element)); + } + /* elements */ + fprintf (out, "%ssubgraph cluster_%s {\n", spc, element_name); + fprintf (out, "%s fontname=\"Bitstream Vera Sans\";\n", spc); + fprintf (out, "%s fontsize=\"8\";\n", spc); + fprintf (out, "%s style=filled;\n", spc); + fprintf (out, "%s color=black;\n\n", spc); + fprintf (out, "%s label=\"%s\\n%s%s%s\";\n", spc, + G_OBJECT_TYPE_NAME (element), GST_OBJECT_NAME (element), + (state_name ? state_name : ""), (param_name ? param_name : "") + ); + if (state_name) { + g_free (state_name); + state_name = NULL; + } + if (param_name) { + g_free (param_name); + param_name = NULL; + } + g_free (element_name); + + src_pads = sink_pads = 0; + if ((pad_iter = gst_element_iterate_sink_pads (element))) { + debug_dump_element_pads (pad_iter, pad, element, details, out, indent, + &src_pads, &sink_pads); + gst_iterator_free (pad_iter); + } + if ((pad_iter = gst_element_iterate_src_pads (element))) { + debug_dump_element_pads (pad_iter, pad, element, details, out, indent, + &src_pads, &sink_pads); + gst_iterator_free (pad_iter); + } + if (GST_IS_BIN (element)) { + fprintf (out, "%s fillcolor=\"#ffffff\";\n", spc); + /* recurse */ + debug_dump_element (GST_BIN (element), details, out, indent + 1); + } else { + if (src_pads && !sink_pads) + fprintf (out, "%s fillcolor=\"#ffaaaa\";\n", spc); + else if (!src_pads && sink_pads) + fprintf (out, "%s fillcolor=\"#aaaaff\";\n", spc); + else if (src_pads && sink_pads) + fprintf (out, "%s fillcolor=\"#aaffaa\";\n", spc); + else + fprintf (out, "%s fillcolor=\"#ffffff\";\n", spc); + } + fprintf (out, "%s}\n\n", spc); + if ((pad_iter = gst_element_iterate_pads (element))) { + pads_done = FALSE; + while (!pads_done) { + switch (gst_iterator_next (pad_iter, &item2)) { + case GST_ITERATOR_OK: + pad = g_value_get_object (&item2); + if (gst_pad_is_linked (pad) + && gst_pad_get_direction (pad) == GST_PAD_SRC) { + debug_dump_element_pad_link (pad, element, details, out, + indent); + } + g_value_reset (&item2); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pad_iter); + break; + case GST_ITERATOR_ERROR: + case GST_ITERATOR_DONE: + pads_done = TRUE; + break; + } + } + g_value_unset (&item2); + gst_iterator_free (pad_iter); + } + g_value_reset (&item); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (element_iter); + break; + case GST_ITERATOR_ERROR: + case GST_ITERATOR_DONE: + elements_done = TRUE; + break; + } + } + g_value_unset (&item); + gst_iterator_free (element_iter); +} + +/* + * _gst_debug_bin_to_dot_file: + * @bin: the top-level pipeline that should be analyzed + * @file_name: output base filename (e.g. "myplayer") + * + * To aid debugging applications one can use this method to write out the whole + * network of gstreamer elements that form the pipeline into an dot file. + * This file can be processed with graphviz to get an image. + * <informalexample><programlisting> + * dot -Tpng -oimage.png graph_lowlevel.dot + * </programlisting></informalexample> + */ +void +_gst_debug_bin_to_dot_file (GstBin * bin, GstDebugGraphDetails details, + const gchar * file_name) +{ + gchar *full_file_name = NULL; + FILE *out; + + g_return_if_fail (GST_IS_BIN (bin)); + + if (G_LIKELY (priv_gst_dump_dot_dir == NULL)) + return; + + if (!file_name) { + file_name = g_get_application_name (); + if (!file_name) + file_name = "unnamed"; + } + + full_file_name = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.dot", + priv_gst_dump_dot_dir, file_name); + + if ((out = fopen (full_file_name, "wb"))) { + gchar *state_name = NULL; + gchar *param_name = NULL; + + if (details & GST_DEBUG_GRAPH_SHOW_STATES) { + state_name = debug_dump_get_element_state (GST_ELEMENT (bin)); + } + if (details & GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS) { + param_name = debug_dump_get_element_params (GST_ELEMENT (bin)); + } + + /* write header */ + fprintf (out, + "digraph pipeline {\n" + " rankdir=LR;\n" + " fontname=\"sans\";\n" + " fontsize=\"10\";\n" + " labelloc=t;\n" + " nodesep=.1;\n" + " ranksep=.2;\n" + " label=\"<%s>\\n%s%s%s\";\n" + " node [style=filled, shape=box, fontsize=\"9\", fontname=\"sans\", margin=\"0.0,0.0\"];\n" + " edge [labelfontsize=\"6\", fontsize=\"9\", fontname=\"monospace\"];\n" + "\n", G_OBJECT_TYPE_NAME (bin), GST_OBJECT_NAME (bin), + (state_name ? state_name : ""), (param_name ? param_name : "") + ); + if (state_name) + g_free (state_name); + if (param_name) + g_free (param_name); + + debug_dump_element (bin, details, out, 1); + + /* write footer */ + fprintf (out, "}\n"); + fclose (out); + GST_INFO ("wrote bin graph to : '%s'", full_file_name); + } else { + GST_WARNING ("Failed to open file '%s' for writing: %s", full_file_name, + g_strerror (errno)); + } + g_free (full_file_name); +} + +/* + * _gst_debug_bin_to_dot_file_with_ts: + * @bin: the top-level pipeline that should be analyzed + * @file_name: output base filename (e.g. "myplayer") + * + * This works like _gst_debug_bin_to_dot_file(), but adds the current timestamp + * to the filename, so that it can be used to take multiple snapshots. + */ +void +_gst_debug_bin_to_dot_file_with_ts (GstBin * bin, GstDebugGraphDetails details, + const gchar * file_name) +{ + gchar *ts_file_name = NULL; + GstClockTime elapsed; + + g_return_if_fail (GST_IS_BIN (bin)); + + if (!file_name) { + file_name = g_get_application_name (); + if (!file_name) + file_name = "unnamed"; + } + + /* add timestamp */ + elapsed = GST_CLOCK_DIFF (_priv_gst_info_start_time, + gst_util_get_timestamp ()); + + /* we don't use GST_TIME_FORMAT as such filenames would fail on some + * filesystems like fat */ + ts_file_name = + g_strdup_printf ("%u.%02u.%02u.%09u-%s", GST_TIME_ARGS (elapsed), + file_name); + + _gst_debug_bin_to_dot_file (bin, details, ts_file_name); + g_free (ts_file_name); +} +#else /* !GST_DISABLE_GST_DEBUG */ +#ifndef GST_REMOVE_DISABLED +void +_gst_debug_bin_to_dot_file (GstBin * bin, GstDebugGraphDetails details, + const gchar * file_name) +{ +} + +void +_gst_debug_bin_to_dot_file_with_ts (GstBin * bin, GstDebugGraphDetails details, + const gchar * file_name) +{ +} +#endif /* GST_REMOVE_DISABLED */ +#endif /* GST_DISABLE_GST_DEBUG */ diff --git a/gst/gstdebugutils.h b/gst/gstdebugutils.h new file mode 100644 index 0000000..1f18308 --- /dev/null +++ b/gst/gstdebugutils.h @@ -0,0 +1,109 @@ +/* GStreamer + * Copyright (C) 2007 Stefan Kost <ensonic@users.sf.net> + * + * gstdebugutils.h: debugging and analysis utillities + * + * 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 __GSTDEBUGUTILS_H__ +#define __GSTDEBUGUTILS_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gst/gstconfig.h> +#include <gst/gstbin.h> + +G_BEGIN_DECLS + +/** + * GstDebugGraphDetails: + * @GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE: show caps-name on edges + * @GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS: show caps-details on edges + * @GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS: show modified parameters on elements + * @GST_DEBUG_GRAPH_SHOW_STATES: show element states + * @GST_DEBUG_GRAPH_SHOW_ALL: show all details + * + * Available details for pipeline graphs produced by GST_DEBUG_BIN_TO_DOT_FILE() + * and GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(). + * + * Since: 0.10.15 + */ +typedef enum { + GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE = (1<<0), + GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS = (1<<1), + GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS = (1<<2), + GST_DEBUG_GRAPH_SHOW_STATES = (1<<3), + GST_DEBUG_GRAPH_SHOW_ALL = ((1<<4)-1) +} GstDebugGraphDetails; + + +/********** pipeline graphs **********/ + +void _gst_debug_bin_to_dot_file (GstBin *bin, GstDebugGraphDetails details, const gchar *file_name); +void _gst_debug_bin_to_dot_file_with_ts (GstBin *bin, GstDebugGraphDetails details, const gchar *file_name); + +#ifndef GST_DISABLE_GST_DEBUG + +/** + * GST_DEBUG_BIN_TO_DOT_FILE: + * @bin: the top-level pipeline that should be analyzed + * @details: details to show in the graph, e.g. #GST_DEBUG_GRAPH_SHOW_ALL or + * one or more other #GstDebugGraphDetails flags. + * @file_name: output base filename (e.g. "myplayer") + * + * To aid debugging applications one can use this method to write out the whole + * network of gstreamer elements that form the pipeline into an dot file. + * This file can be processed with graphviz to get an image. + * <informalexample><programlisting> + * dot -Tpng -oimage.png graph_lowlevel.dot + * </programlisting></informalexample> + * + * The macro is only active if gstreamer is configured with + * "--gst-enable-gst-debug" and the environment variable + * GST_DEBUG_DUMP_DOT_DIR is set to a basepath (e.g. /tmp). + * + * Since: 0.10.15 + */ +#define GST_DEBUG_BIN_TO_DOT_FILE(bin, details, file_name) _gst_debug_bin_to_dot_file (bin, details, file_name) + +/** + * GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS: + * @bin: the top-level pipeline that should be analyzed + * @details: details to show in the graph, e.g. #GST_DEBUG_GRAPH_SHOW_ALL or + * one or more other #GstDebugGraphDetails flags. + * @file_name: output base filename (e.g. "myplayer") + * + * This works like GST_DEBUG_BIN_TO_DOT_FILE(), but adds the current timestamp + * to the filename, so that it can be used to take multiple snapshots. + * + * Since: 0.10.15 + */ +#define GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(bin, details, file_name) _gst_debug_bin_to_dot_file_with_ts (bin, details, file_name) + + +#else /* GST_DISABLE_GST_DEBUG */ + + +#define GST_DEBUG_BIN_TO_DOT_FILE(bin, details, file_name) +#define GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(bin, details, file_name) + +#endif /* GST_DISABLE_GST_DEBUG */ + +G_END_DECLS + +#endif /* __GSTDEBUGUTILS_H__ */ + diff --git a/gst/gstelement.c b/gst/gstelement.c new file mode 100644 index 0000000..91b12be --- /dev/null +++ b/gst/gstelement.c @@ -0,0 +1,2992 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2004 Wim Taymans <wim@fluendo.com> + * + * gstelement.c: The base element, all elements derive from this + * + * 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:gstelement + * @short_description: Abstract base class for all pipeline elements + * @see_also: #GstElementFactory, #GstPad + * + * GstElement is the abstract base class needed to construct an element that + * can be used in a GStreamer pipeline. Please refer to the plugin writers + * guide for more information on creating #GstElement subclasses. + * + * The name of a #GstElement can be get with gst_element_get_name() and set with + * gst_element_set_name(). For speed, GST_ELEMENT_NAME() can be used in the + * core when using the appropriate locking. Do not use this in plug-ins or + * applications in order to retain ABI compatibility. + * + * All elements have pads (of the type #GstPad). These pads link to pads on + * other elements. #GstBuffer flow between these linked pads. + * A #GstElement has a #GList of #GstPad structures for all their input (or sink) + * and output (or source) pads. + * Core and plug-in writers can add and remove pads with gst_element_add_pad() + * and gst_element_remove_pad(). + * + * An existing pad of an element can be retrieved by name with + * gst_element_get_static_pad(). A new dynamic pad can be created using + * gst_element_request_pad() with a #GstPadTemplate or + * gst_element_get_request_pad() with the template name such as "src_\%d". + * An iterator of all pads can be retrieved with gst_element_iterate_pads(). + * + * Elements can be linked through their pads. + * If the link is straightforward, use the gst_element_link() + * convenience function to link two elements, or gst_element_link_many() + * for more elements in a row. + * Use gst_element_link_filtered() to link two elements constrained by + * a specified set of #GstCaps. + * For finer control, use gst_element_link_pads() and + * gst_element_link_pads_filtered() to specify the pads to link on + * each element by name. + * + * Each element has a state (see #GstState). You can get and set the state + * of an element with gst_element_get_state() and gst_element_set_state(). + * Setting a state triggers a #GstStateChange. To get a string representation + * of a #GstState, use gst_element_state_get_name(). + * + * You can get and set a #GstClock on an element using gst_element_get_clock() + * and gst_element_set_clock(). + * Some elements can provide a clock for the pipeline if + * gst_element_provides_clock() returns %TRUE. With the + * gst_element_provide_clock() method one can retrieve the clock provided by + * such an element. + * Not all elements require a clock to operate correctly. If + * gst_element_requires_clock() returns %TRUE, a clock should be set on the + * element with gst_element_set_clock(). + * + * Note that clock slection and distribution is normally handled by the + * toplevel #GstPipeline so the clock functions are only to be used in very + * specific situations. + * + * Last reviewed on 2009-05-29 (0.10.24) + */ + +#include "gst_private.h" +#include <glib.h> +#include <stdarg.h> +#include <gobject/gvaluecollector.h> + +#include "gstelement.h" +#include "gstelementmetadata.h" +#include "gstenumtypes.h" +#include "gstbus.h" +#include "gstmarshal.h" +#include "gsterror.h" +#include "gstevent.h" +#include "gstutils.h" +#include "gstinfo.h" +#include "gstvalue.h" +#include "gst-i18n-lib.h" + +/* Element signals and args */ +enum +{ + PAD_ADDED, + PAD_REMOVED, + NO_MORE_PADS, + /* add more above */ + LAST_SIGNAL +}; + +enum +{ + ARG_0 + /* FILL ME */ +}; + +static void gst_element_class_init (GstElementClass * klass); +static void gst_element_init (GstElement * element); +static void gst_element_base_class_init (gpointer g_class); +static void gst_element_base_class_finalize (gpointer g_class); + +static void gst_element_dispose (GObject * object); +static void gst_element_finalize (GObject * object); + +static GstStateChangeReturn gst_element_change_state_func (GstElement * element, + GstStateChange transition); +static GstStateChangeReturn gst_element_get_state_func (GstElement * element, + GstState * state, GstState * pending, GstClockTime timeout); +static GstStateChangeReturn gst_element_set_state_func (GstElement * element, + GstState state); +static void gst_element_set_bus_func (GstElement * element, GstBus * bus); + +static gboolean gst_element_default_send_event (GstElement * element, + GstEvent * event); +static gboolean gst_element_default_query (GstElement * element, + GstQuery * query); + +static GstPadTemplate + * gst_element_class_get_request_pad_template (GstElementClass * + element_class, const gchar * name); + +static GstObjectClass *parent_class = NULL; +static guint gst_element_signals[LAST_SIGNAL] = { 0 }; + +/* this is used in gstelementfactory.c:gst_element_register() */ +GQuark _gst_elementclass_factory = 0; + +GType +gst_element_get_type (void) +{ + static volatile gsize gst_element_type = 0; + + if (g_once_init_enter (&gst_element_type)) { + GType _type; + static const GTypeInfo element_info = { + sizeof (GstElementClass), + gst_element_base_class_init, + gst_element_base_class_finalize, + (GClassInitFunc) gst_element_class_init, + NULL, + NULL, + sizeof (GstElement), + 0, + (GInstanceInitFunc) gst_element_init, + NULL + }; + + _type = g_type_register_static (GST_TYPE_OBJECT, "GstElement", + &element_info, G_TYPE_FLAG_ABSTRACT); + + _gst_elementclass_factory = + g_quark_from_static_string ("GST_ELEMENTCLASS_FACTORY"); + g_once_init_leave (&gst_element_type, _type); + } + return gst_element_type; +} + +static void +gst_element_class_init (GstElementClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + /** + * GstElement::pad-added: + * @gstelement: the object which received the signal + * @new_pad: the pad that has been added + * + * a new #GstPad has been added to the element. Note that this signal will + * usually be emitted from the context of the streaming thread. Also keep in + * mind that if you add new elements to the pipeline in the signal handler + * you will need to set them to the desired target state with + * gst_element_set_state() or gst_element_sync_state_with_parent(). + */ + gst_element_signals[PAD_ADDED] = + g_signal_new ("pad-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstElementClass, pad_added), NULL, NULL, + gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_PAD); + /** + * GstElement::pad-removed: + * @gstelement: the object which received the signal + * @old_pad: the pad that has been removed + * + * a #GstPad has been removed from the element + */ + gst_element_signals[PAD_REMOVED] = + g_signal_new ("pad-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstElementClass, pad_removed), NULL, NULL, + gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_PAD); + /** + * GstElement::no-more-pads: + * @gstelement: the object which received the signal + * + * This signals that the element will not generate more dynamic pads. + * Note that this signal will usually be emitted from the context of + * the streaming thread. + */ + gst_element_signals[NO_MORE_PADS] = + g_signal_new ("no-more-pads", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstElementClass, no_more_pads), NULL, + NULL, gst_marshal_VOID__VOID, G_TYPE_NONE, 0); + + gobject_class->dispose = gst_element_dispose; + gobject_class->finalize = gst_element_finalize; + + klass->change_state = GST_DEBUG_FUNCPTR (gst_element_change_state_func); + klass->set_state = GST_DEBUG_FUNCPTR (gst_element_set_state_func); + klass->get_state = GST_DEBUG_FUNCPTR (gst_element_get_state_func); + klass->set_bus = GST_DEBUG_FUNCPTR (gst_element_set_bus_func); + klass->query = GST_DEBUG_FUNCPTR (gst_element_default_query); + klass->send_event = GST_DEBUG_FUNCPTR (gst_element_default_send_event); + klass->numpadtemplates = 0; + + klass->elementfactory = NULL; +} + +static void +gst_element_base_class_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + GList *node, *padtemplates; + + /* Copy the element details here so elements can inherit the + * details from their base class and classes only need to set + * the details in class_init instead of base_init */ + element_class->metadata = + element_class->metadata ? gst_structure_copy (element_class->metadata) : + gst_structure_empty_new ("metadata"); + + /* Copy the pad templates so elements inherit them + * from their base class but elements can add pad templates in class_init + * instead of base_init. + */ + padtemplates = g_list_copy (element_class->padtemplates); + for (node = padtemplates; node != NULL; node = node->next) { + GstPadTemplate *tmpl = (GstPadTemplate *) node->data; + gst_object_ref (tmpl); + } + element_class->padtemplates = padtemplates; + + /* set the factory, see gst_element_register() */ + element_class->elementfactory = + g_type_get_qdata (G_TYPE_FROM_CLASS (element_class), + _gst_elementclass_factory); + GST_DEBUG ("type %s : factory %p", G_OBJECT_CLASS_NAME (element_class), + element_class->elementfactory); +} + +static void +gst_element_base_class_finalize (gpointer g_class) +{ + GstElementClass *klass = GST_ELEMENT_CLASS (g_class); + + g_list_foreach (klass->padtemplates, (GFunc) gst_object_unref, NULL); + g_list_free (klass->padtemplates); + + gst_structure_free (klass->metadata); +} + +static void +gst_element_init (GstElement * element) +{ + GST_STATE (element) = GST_STATE_NULL; + GST_STATE_TARGET (element) = GST_STATE_NULL; + GST_STATE_NEXT (element) = GST_STATE_VOID_PENDING; + GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING; + GST_STATE_RETURN (element) = GST_STATE_CHANGE_SUCCESS; + + g_static_rec_mutex_init (&element->state_lock); + element->state_cond = g_cond_new (); +} + +/** + * gst_element_release_request_pad: + * @element: a #GstElement to release the request pad of. + * @pad: the #GstPad to release. + * + * Makes the element free the previously requested pad as obtained + * with gst_element_get_request_pad(). + * + * This does not unref the pad. If the pad was created by using + * gst_element_get_request_pad(), gst_element_release_request_pad() needs to be + * followed by gst_object_unref() to free the @pad. + * + * MT safe. + */ +void +gst_element_release_request_pad (GstElement * element, GstPad * pad) +{ + GstElementClass *oclass; + + g_return_if_fail (GST_IS_ELEMENT (element)); + g_return_if_fail (GST_IS_PAD (pad)); + + oclass = GST_ELEMENT_GET_CLASS (element); + + /* if the element implements a custom release function we call that, else we + * simply remove the pad from the element */ + if (oclass->release_pad) + (oclass->release_pad) (element, pad); + else + gst_element_remove_pad (element, pad); +} + +/** + * gst_element_requires_clock: + * @element: a #GstElement to query + * + * Query if the element requires a clock. + * + * Returns: %TRUE if the element requires a clock + * + * MT safe. + */ +gboolean +gst_element_requires_clock (GstElement * element) +{ + gboolean result; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + + result = (GST_ELEMENT_GET_CLASS (element)->set_clock != NULL); + + return result; +} + +/** + * gst_element_provides_clock: + * @element: a #GstElement to query + * + * Query if the element provides a clock. A #GstClock provided by an + * element can be used as the global #GstClock for the pipeline. + * An element that can provide a clock is only required to do so in the PAUSED + * state, this means when it is fully negotiated and has allocated the resources + * to operate the clock. + * + * Returns: %TRUE if the element provides a clock + * + * MT safe. + */ +gboolean +gst_element_provides_clock (GstElement * element) +{ + gboolean result; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + + result = (GST_ELEMENT_GET_CLASS (element)->provide_clock != NULL); + + return result; +} + +/** + * gst_element_provide_clock: + * @element: a #GstElement to query + * + * Get the clock provided by the given element. + * <note>An element is only required to provide a clock in the PAUSED + * state. Some elements can provide a clock in other states.</note> + * + * Returns: (transfer full): the GstClock provided by the element or %NULL + * if no clock could be provided. Unref after usage. + * + * MT safe. + */ +GstClock * +gst_element_provide_clock (GstElement * element) +{ + GstClock *result = NULL; + GstElementClass *oclass; + + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + + oclass = GST_ELEMENT_GET_CLASS (element); + + if (oclass->provide_clock) + result = oclass->provide_clock (element); + + return result; +} + +/** + * gst_element_set_clock: + * @element: a #GstElement to set the clock for. + * @clock: the #GstClock to set for the element. + * + * Sets the clock for the element. This function increases the + * refcount on the clock. Any previously set clock on the object + * is unreffed. + * + * Returns: %TRUE if the element accepted the clock. An element can refuse a + * clock when it, for example, is not able to slave its internal clock to the + * @clock or when it requires a specific clock to operate. + * + * MT safe. + */ +gboolean +gst_element_set_clock (GstElement * element, GstClock * clock) +{ + GstElementClass *oclass; + gboolean res = TRUE; + GstClock **clock_p; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (clock == NULL || GST_IS_CLOCK (clock), FALSE); + + oclass = GST_ELEMENT_GET_CLASS (element); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, element, "setting clock %p", clock); + + if (oclass->set_clock) + res = oclass->set_clock (element, clock); + + if (res) { + /* only update the clock pointer if the element accepted the clock */ + GST_OBJECT_LOCK (element); + clock_p = &element->clock; + gst_object_replace ((GstObject **) clock_p, (GstObject *) clock); + GST_OBJECT_UNLOCK (element); + } + return res; +} + +/** + * gst_element_get_clock: + * @element: a #GstElement to get the clock of. + * + * Gets the currently configured clock of the element. This is the clock as was + * last set with gst_element_set_clock(). + * + * Returns: (transfer full): the #GstClock of the element. unref after usage. + * + * MT safe. + */ +GstClock * +gst_element_get_clock (GstElement * element) +{ + GstClock *result; + + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + + GST_OBJECT_LOCK (element); + if ((result = element->clock)) + gst_object_ref (result); + GST_OBJECT_UNLOCK (element); + + return result; +} + +/** + * gst_element_set_base_time: + * @element: a #GstElement. + * @time: the base time to set. + * + * Set the base time of an element. See gst_element_get_base_time(). + * + * MT safe. + */ +void +gst_element_set_base_time (GstElement * element, GstClockTime time) +{ + GstClockTime old; + + g_return_if_fail (GST_IS_ELEMENT (element)); + + GST_OBJECT_LOCK (element); + old = element->base_time; + element->base_time = time; + GST_OBJECT_UNLOCK (element); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, element, + "set base_time=%" GST_TIME_FORMAT ", old %" GST_TIME_FORMAT, + GST_TIME_ARGS (time), GST_TIME_ARGS (old)); +} + +/** + * gst_element_get_base_time: + * @element: a #GstElement. + * + * Returns the base time of the element. The base time is the + * absolute time of the clock when this element was last put to + * PLAYING. Subtracting the base time from the clock time gives + * the running time of the element. + * + * Returns: the base time of the element. + * + * MT safe. + */ +GstClockTime +gst_element_get_base_time (GstElement * element) +{ + GstClockTime result; + + g_return_val_if_fail (GST_IS_ELEMENT (element), GST_CLOCK_TIME_NONE); + + GST_OBJECT_LOCK (element); + result = element->base_time; + GST_OBJECT_UNLOCK (element); + + return result; +} + +/** + * gst_element_set_start_time: + * @element: a #GstElement. + * @time: the base time to set. + * + * Set the start time of an element. The start time of the element is the + * running time of the element when it last went to the PAUSED state. In READY + * or after a flushing seek, it is set to 0. + * + * Toplevel elements like #GstPipeline will manage the start_time and + * base_time on its children. Setting the start_time to #GST_CLOCK_TIME_NONE + * on such a toplevel element will disable the distribution of the base_time to + * the children and can be useful if the application manages the base_time + * itself, for example if you want to synchronize capture from multiple + * pipelines, and you can also ensure that the pipelines have the same clock. + * + * MT safe. + * + * Since: 0.10.24 + */ +void +gst_element_set_start_time (GstElement * element, GstClockTime time) +{ + GstClockTime old; + + g_return_if_fail (GST_IS_ELEMENT (element)); + + GST_OBJECT_LOCK (element); + old = GST_ELEMENT_START_TIME (element); + GST_ELEMENT_START_TIME (element) = time; + GST_OBJECT_UNLOCK (element); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, element, + "set start_time=%" GST_TIME_FORMAT ", old %" GST_TIME_FORMAT, + GST_TIME_ARGS (time), GST_TIME_ARGS (old)); +} + +/** + * gst_element_get_start_time: + * @element: a #GstElement. + * + * Returns the start time of the element. The start time is the + * running time of the clock when this element was last put to PAUSED. + * + * Usually the start_time is managed by a toplevel element such as + * #GstPipeline. + * + * MT safe. + * + * Returns: the start time of the element. + * + * Since: 0.10.24 + */ +GstClockTime +gst_element_get_start_time (GstElement * element) +{ + GstClockTime result; + + g_return_val_if_fail (GST_IS_ELEMENT (element), GST_CLOCK_TIME_NONE); + + GST_OBJECT_LOCK (element); + result = GST_ELEMENT_START_TIME (element); + GST_OBJECT_UNLOCK (element); + + return result; +} + +/** + * gst_element_is_indexable: + * @element: a #GstElement. + * + * Queries if the element can be indexed. + * + * Returns: TRUE if the element can be indexed. + * + * MT safe. + */ +gboolean +gst_element_is_indexable (GstElement * element) +{ + gboolean result; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + + result = (GST_ELEMENT_GET_CLASS (element)->set_index != NULL); + + return result; +} + +/** + * gst_element_set_index: + * @element: a #GstElement. + * @index: (transfer none): a #GstIndex. + * + * Set @index on the element. The refcount of the index + * will be increased, any previously set index is unreffed. + * + * MT safe. + */ +void +gst_element_set_index (GstElement * element, GstIndex * index) +{ + GstElementClass *oclass; + + g_return_if_fail (GST_IS_ELEMENT (element)); + g_return_if_fail (index == NULL || GST_IS_INDEX (index)); + + oclass = GST_ELEMENT_GET_CLASS (element); + + if (oclass->set_index) + oclass->set_index (element, index); +} + +/** + * gst_element_get_index: + * @element: a #GstElement. + * + * Gets the index from the element. + * + * Returns: (transfer full): a #GstIndex or %NULL when no index was set on the + * element. unref after usage. + * + * MT safe. + */ +GstIndex * +gst_element_get_index (GstElement * element) +{ + GstElementClass *oclass; + GstIndex *result = NULL; + + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + + oclass = GST_ELEMENT_GET_CLASS (element); + + if (oclass->get_index) + result = oclass->get_index (element); + + return result; +} + +/** + * gst_element_add_pad: + * @element: a #GstElement to add the pad to. + * @pad: (transfer full): the #GstPad to add to the element. + * + * Adds a pad (link point) to @element. @pad's parent will be set to @element; + * see gst_object_set_parent() for refcounting information. + * + * Pads are not automatically activated so elements should perform the needed + * steps to activate the pad in case this pad is added in the PAUSED or PLAYING + * state. See gst_pad_set_active() for more information about activating pads. + * + * The pad and the element should be unlocked when calling this function. + * + * This function will emit the #GstElement::pad-added signal on the element. + * + * Returns: %TRUE if the pad could be added. This function can fail when + * a pad with the same name already existed or the pad already had another + * parent. + * + * MT safe. + */ +gboolean +gst_element_add_pad (GstElement * element, GstPad * pad) +{ + gchar *pad_name; + gboolean flushing; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + /* locking pad to look at the name */ + GST_OBJECT_LOCK (pad); + pad_name = g_strdup (GST_PAD_NAME (pad)); + GST_CAT_INFO_OBJECT (GST_CAT_ELEMENT_PADS, element, "adding pad '%s'", + GST_STR_NULL (pad_name)); + flushing = GST_PAD_IS_FLUSHING (pad); + GST_OBJECT_UNLOCK (pad); + + /* then check to see if there's already a pad by that name here */ + GST_OBJECT_LOCK (element); + if (G_UNLIKELY (!gst_object_check_uniqueness (element->pads, pad_name))) + goto name_exists; + + /* try to set the pad's parent */ + if (G_UNLIKELY (!gst_object_set_parent (GST_OBJECT_CAST (pad), + GST_OBJECT_CAST (element)))) + goto had_parent; + + /* check for flushing pads */ + if (flushing && (GST_STATE (element) > GST_STATE_READY || + GST_STATE_NEXT (element) == GST_STATE_PAUSED)) { + g_warning ("adding flushing pad '%s' to running element '%s', you need to " + "use gst_pad_set_active(pad,TRUE) before adding it.", + GST_STR_NULL (pad_name), GST_ELEMENT_NAME (element)); + /* unset flushing */ + GST_OBJECT_LOCK (pad); + GST_PAD_UNSET_FLUSHING (pad); + GST_OBJECT_UNLOCK (pad); + } + + g_free (pad_name); + + /* add it to the list */ + switch (gst_pad_get_direction (pad)) { + case GST_PAD_SRC: + element->srcpads = g_list_prepend (element->srcpads, pad); + element->numsrcpads++; + break; + case GST_PAD_SINK: + element->sinkpads = g_list_prepend (element->sinkpads, pad); + element->numsinkpads++; + break; + default: + goto no_direction; + } + element->pads = g_list_prepend (element->pads, pad); + element->numpads++; + element->pads_cookie++; + GST_OBJECT_UNLOCK (element); + + /* emit the PAD_ADDED signal */ + g_signal_emit (element, gst_element_signals[PAD_ADDED], 0, pad); + + return TRUE; + + /* ERROR cases */ +name_exists: + { + g_critical ("Padname %s is not unique in element %s, not adding", + pad_name, GST_ELEMENT_NAME (element)); + GST_OBJECT_UNLOCK (element); + g_free (pad_name); + return FALSE; + } +had_parent: + { + g_critical + ("Pad %s already has parent when trying to add to element %s", + pad_name, GST_ELEMENT_NAME (element)); + GST_OBJECT_UNLOCK (element); + g_free (pad_name); + return FALSE; + } +no_direction: + { + GST_OBJECT_LOCK (pad); + g_critical + ("Trying to add pad %s to element %s, but it has no direction", + GST_OBJECT_NAME (pad), GST_ELEMENT_NAME (element)); + GST_OBJECT_UNLOCK (pad); + GST_OBJECT_UNLOCK (element); + return FALSE; + } +} + +/** + * gst_element_remove_pad: + * @element: a #GstElement to remove pad from. + * @pad: (transfer none): the #GstPad to remove from the element. + * + * Removes @pad from @element. @pad will be destroyed if it has not been + * referenced elsewhere using gst_object_unparent(). + * + * This function is used by plugin developers and should not be used + * by applications. Pads that were dynamically requested from elements + * with gst_element_get_request_pad() should be released with the + * gst_element_release_request_pad() function instead. + * + * Pads are not automatically deactivated so elements should perform the needed + * steps to deactivate the pad in case this pad is removed in the PAUSED or + * PLAYING state. See gst_pad_set_active() for more information about + * deactivating pads. + * + * The pad and the element should be unlocked when calling this function. + * + * This function will emit the #GstElement::pad-removed signal on the element. + * + * Returns: %TRUE if the pad could be removed. Can return %FALSE if the + * pad does not belong to the provided element. + * + * MT safe. + */ +gboolean +gst_element_remove_pad (GstElement * element, GstPad * pad) +{ + GstPad *peer; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + /* locking pad to look at the name and parent */ + GST_OBJECT_LOCK (pad); + GST_CAT_INFO_OBJECT (GST_CAT_ELEMENT_PADS, element, "removing pad '%s'", + GST_STR_NULL (GST_PAD_NAME (pad))); + + if (G_UNLIKELY (GST_PAD_PARENT (pad) != element)) + goto not_our_pad; + GST_OBJECT_UNLOCK (pad); + + /* unlink */ + if ((peer = gst_pad_get_peer (pad))) { + /* window for MT unsafeness, someone else could unlink here + * and then we call unlink with wrong pads. The unlink + * function would catch this and safely return failed. */ + if (GST_PAD_IS_SRC (pad)) + gst_pad_unlink (pad, peer); + else + gst_pad_unlink (peer, pad); + + gst_object_unref (peer); + } + + GST_OBJECT_LOCK (element); + /* remove it from the list */ + switch (gst_pad_get_direction (pad)) { + case GST_PAD_SRC: + element->srcpads = g_list_remove (element->srcpads, pad); + element->numsrcpads--; + break; + case GST_PAD_SINK: + element->sinkpads = g_list_remove (element->sinkpads, pad); + element->numsinkpads--; + break; + default: + g_critical ("Removing pad without direction???"); + break; + } + element->pads = g_list_remove (element->pads, pad); + element->numpads--; + element->pads_cookie++; + GST_OBJECT_UNLOCK (element); + + /* emit the PAD_REMOVED signal before unparenting and losing the last ref. */ + g_signal_emit (element, gst_element_signals[PAD_REMOVED], 0, pad); + + gst_object_unparent (GST_OBJECT_CAST (pad)); + + return TRUE; + + /* ERRORS */ +not_our_pad: + { + /* locking order is element > pad */ + GST_OBJECT_UNLOCK (pad); + + GST_OBJECT_LOCK (element); + GST_OBJECT_LOCK (pad); + g_critical ("Padname %s:%s does not belong to element %s when removing", + GST_DEBUG_PAD_NAME (pad), GST_ELEMENT_NAME (element)); + GST_OBJECT_UNLOCK (pad); + GST_OBJECT_UNLOCK (element); + return FALSE; + } +} + +/** + * gst_element_no_more_pads: + * @element: a #GstElement + * + * Use this function to signal that the element does not expect any more pads + * to show up in the current pipeline. This function should be called whenever + * pads have been added by the element itself. Elements with #GST_PAD_SOMETIMES + * pad templates use this in combination with autopluggers to figure out that + * the element is done initializing its pads. + * + * This function emits the #GstElement::no-more-pads signal. + * + * MT safe. + */ +void +gst_element_no_more_pads (GstElement * element) +{ + g_return_if_fail (GST_IS_ELEMENT (element)); + + g_signal_emit (element, gst_element_signals[NO_MORE_PADS], 0); +} + +static gint +pad_compare_name (GstPad * pad1, const gchar * name) +{ + gint result; + + GST_OBJECT_LOCK (pad1); + result = strcmp (GST_PAD_NAME (pad1), name); + GST_OBJECT_UNLOCK (pad1); + + return result; +} + +/** + * gst_element_get_static_pad: + * @element: a #GstElement to find a static pad of. + * @name: the name of the static #GstPad to retrieve. + * + * Retrieves a pad from @element by name. This version only retrieves + * already-existing (i.e. 'static') pads. + * + * Returns: (transfer full): the requested #GstPad if found, otherwise %NULL. + * unref after usage. + * + * MT safe. + */ +GstPad * +gst_element_get_static_pad (GstElement * element, const gchar * name) +{ + GList *find; + GstPad *result = NULL; + + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + g_return_val_if_fail (name != NULL, NULL); + + GST_OBJECT_LOCK (element); + find = + g_list_find_custom (element->pads, name, (GCompareFunc) pad_compare_name); + if (find) { + result = GST_PAD_CAST (find->data); + gst_object_ref (result); + } + + if (result == NULL) { + GST_CAT_INFO (GST_CAT_ELEMENT_PADS, "no such pad '%s' in element \"%s\"", + name, GST_ELEMENT_NAME (element)); + } else { + GST_CAT_INFO (GST_CAT_ELEMENT_PADS, "found pad %s:%s", + GST_ELEMENT_NAME (element), name); + } + GST_OBJECT_UNLOCK (element); + + return result; +} + +static GstPad * +_gst_element_request_pad (GstElement * element, GstPadTemplate * templ, + const gchar * name, const GstCaps * caps) +{ + GstPad *newpad = NULL; + GstElementClass *oclass; + + oclass = GST_ELEMENT_GET_CLASS (element); + +#ifndef G_DISABLE_CHECKS + /* Some sanity checking here */ + if (name) { + GstPad *pad; + + /* Is this the template name? */ + if (strstr (name, "%") || !strchr (templ->name_template, '%')) { + g_return_val_if_fail (strcmp (name, templ->name_template) == 0, NULL); + } else { + const gchar *str, *data; + gchar *endptr; + + /* Otherwise check if it's a valid name for the name template */ + str = strchr (templ->name_template, '%'); + g_return_val_if_fail (str != NULL, NULL); + g_return_val_if_fail (strncmp (templ->name_template, name, + str - templ->name_template) == 0, NULL); + g_return_val_if_fail (strlen (name) > str - templ->name_template, NULL); + + data = name + (str - templ->name_template); + + /* Can either be %s or %d or %u, do sanity checking for %d */ + if (*(str + 1) == 'd') { + gint64 tmp; + + /* it's an int */ + tmp = g_ascii_strtoll (data, &endptr, 10); + g_return_val_if_fail (tmp >= G_MININT && tmp <= G_MAXINT + && *endptr == '\0', NULL); + } else if (*(str + 1) == 'u') { + guint64 tmp; + + /* it's an int */ + tmp = g_ascii_strtoull (data, &endptr, 10); + g_return_val_if_fail (tmp <= G_MAXUINT && *endptr == '\0', NULL); + } + } + + pad = gst_element_get_static_pad (element, name); + if (pad) { + gst_object_unref (pad); + /* FIXME 0.11: Change this to g_return_val_if_fail() */ + g_critical ("Element %s already has a pad named %s, the behaviour of " + " gst_element_get_request_pad() for existing pads is undefined!", + GST_ELEMENT_NAME (element), name); + } + } +#endif + + if (oclass->request_new_pad) + newpad = (oclass->request_new_pad) (element, templ, name, caps); + + if (newpad) + gst_object_ref (newpad); + + return newpad; +} + +/** + * gst_element_get_request_pad: + * @element: a #GstElement to find a request pad of. + * @name: the name of the request #GstPad to retrieve. + * + * Retrieves a pad from the element by name (e.g. "src_\%d"). This version only + * retrieves request pads. The pad should be released with + * gst_element_release_request_pad(). + * + * This method is slow and will be deprecated in the future. New code should + * use gst_element_request_pad() with the requested template. + * + * Returns: (transfer full): requested #GstPad if found, otherwise %NULL. + * Release after usage. + */ +GstPad * +gst_element_get_request_pad (GstElement * element, const gchar * name) +{ + GstPadTemplate *templ = NULL; + GstPad *pad; + const gchar *req_name = NULL; + gboolean templ_found = FALSE; + GList *list; + const gchar *data; + gchar *str, *endptr = NULL; + GstElementClass *class; + + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + g_return_val_if_fail (name != NULL, NULL); + + class = GST_ELEMENT_GET_CLASS (element); + + /* if the name contains a %, we assume it's the complete template name. Get + * the template and try to get a pad */ + if (strstr (name, "%")) { + templ = gst_element_class_get_request_pad_template (class, name); + req_name = NULL; + if (templ) + templ_found = TRUE; + } else { + /* there is no % in the name, try to find a matching template */ + list = class->padtemplates; + while (!templ_found && list) { + templ = (GstPadTemplate *) list->data; + if (templ->presence == GST_PAD_REQUEST) { + GST_CAT_DEBUG (GST_CAT_PADS, "comparing %s to %s", name, + templ->name_template); + /* see if we find an exact match */ + if (strcmp (name, templ->name_template) == 0) { + templ_found = TRUE; + req_name = name; + break; + } + /* Because of sanity checks in gst_pad_template_new(), we know that %s + and %d and %u, occurring at the end of the name_template, are the only + possibilities. */ + else if ((str = strchr (templ->name_template, '%')) + && strncmp (templ->name_template, name, + str - templ->name_template) == 0 + && strlen (name) > str - templ->name_template) { + data = name + (str - templ->name_template); + if (*(str + 1) == 'd') { + glong tmp; + + /* it's an int */ + tmp = strtol (data, &endptr, 10); + if (tmp != G_MINLONG && tmp != G_MAXLONG && endptr && + *endptr == '\0') { + templ_found = TRUE; + req_name = name; + break; + } + } else if (*(str + 1) == 'u') { + gulong tmp; + + /* it's an int */ + tmp = strtoul (data, &endptr, 10); + if (tmp != G_MAXULONG && endptr && *endptr == '\0') { + templ_found = TRUE; + req_name = name; + break; + } + } else { + /* it's a string */ + templ_found = TRUE; + req_name = name; + break; + } + } + } + list = list->next; + } + } + + if (!templ_found) + return NULL; + + pad = _gst_element_request_pad (element, templ, req_name, NULL); + + return pad; +} + +/** + * gst_element_request_pad: + * @element: a #GstElement to find a request pad of. + * @templ: a #GstPadTemplate of which we want a pad of. + * @name: (transfer none) (allow-none): the name of the request #GstPad + * to retrieve. Can be %NULL. + * @caps: (transfer none) (allow-none): the caps of the pad we want to + * request. Can be %NULL. + * + * Retrieves a request pad from the element according to the provided template. + * Pad templates can be looked up using + * gst_element_factory_get_static_pad_templates(). + * + * If the @caps are specified and the element implements thew new + * request_new_pad_full virtual method, the element will use them to select + * which pad to create. + * + * The pad should be released with gst_element_release_request_pad(). + * + * Returns: (transfer full): requested #GstPad if found, otherwise %NULL. + * Release after usage. + * + * Since: 0.10.32 + */ +GstPad * +gst_element_request_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name, const GstCaps * caps) +{ + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + g_return_val_if_fail (templ != NULL, NULL); + + return _gst_element_request_pad (element, templ, name, caps); +} + +static GstIterator * +gst_element_iterate_pad_list (GstElement * element, GList ** padlist) +{ + GstIterator *result; + + GST_OBJECT_LOCK (element); + result = gst_iterator_new_list (GST_TYPE_PAD, + GST_OBJECT_GET_LOCK (element), + &element->pads_cookie, padlist, (GObject *) element, NULL); + GST_OBJECT_UNLOCK (element); + + return result; +} + +/** + * gst_element_iterate_pads: + * @element: a #GstElement to iterate pads of. + * + * Retrieves an iterator of @element's pads. The iterator should + * be freed after usage. Also more specialized iterators exists such as + * gst_element_iterate_src_pads() or gst_element_iterate_sink_pads(). + * + * Returns: (transfer full): the #GstIterator of #GstPad. Unref each pad + * after use. + * + * MT safe. + */ +GstIterator * +gst_element_iterate_pads (GstElement * element) +{ + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + + return gst_element_iterate_pad_list (element, &element->pads); +} + +/** + * gst_element_iterate_src_pads: + * @element: a #GstElement. + * + * Retrieves an iterator of @element's source pads. + * + * Returns: (transfer full): the #GstIterator of #GstPad. Unref each pad + * after use. + * + * MT safe. + */ +GstIterator * +gst_element_iterate_src_pads (GstElement * element) +{ + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + + return gst_element_iterate_pad_list (element, &element->srcpads); +} + +/** + * gst_element_iterate_sink_pads: + * @element: a #GstElement. + * + * Retrieves an iterator of @element's sink pads. + * + * Returns: (transfer full): the #GstIterator of #GstPad. Unref each pad + * after use. + * + * MT safe. + */ +GstIterator * +gst_element_iterate_sink_pads (GstElement * element) +{ + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + + return gst_element_iterate_pad_list (element, &element->sinkpads); +} + +/** + * gst_element_class_add_pad_template: + * @klass: the #GstElementClass to add the pad template to. + * @templ: (transfer full): a #GstPadTemplate to add to the element class. + * + * Adds a padtemplate to an element class. This is mainly used in the _class_init + * functions of classes. If a pad template with the same name as an already + * existing one is added the old one is replaced by the new one. + * + */ +void +gst_element_class_add_pad_template (GstElementClass * klass, + GstPadTemplate * templ) +{ + GList *template_list = klass->padtemplates; + + g_return_if_fail (GST_IS_ELEMENT_CLASS (klass)); + g_return_if_fail (GST_IS_PAD_TEMPLATE (templ)); + + /* If we already have a pad template with the same name replace the + * old one. */ + while (template_list) { + GstPadTemplate *padtempl = (GstPadTemplate *) template_list->data; + + /* Found pad with the same name, replace and return */ + if (strcmp (templ->name_template, padtempl->name_template) == 0) { + gst_object_unref (padtempl); + template_list->data = templ; + return; + } + template_list = g_list_next (template_list); + } + + /* Take ownership of the floating ref */ + g_object_ref_sink (templ); + + klass->padtemplates = g_list_append (klass->padtemplates, templ); + klass->numpadtemplates++; +} + +/** + * gst_element_class_add_metadata: + * @klass: class to set metadata for + * @key: the key to set + * @value: the value to set + * + * Set @key with @value as metadata in @klass. + */ +void +gst_element_class_add_metadata (GstElementClass * klass, + const gchar * key, const gchar * value) +{ + g_return_if_fail (GST_IS_ELEMENT_CLASS (klass)); + g_return_if_fail (key != NULL); + g_return_if_fail (value != NULL); + + gst_structure_set ((GstStructure *) klass->metadata, + key, G_TYPE_STRING, value, NULL); +} + +/** + * gst_element_class_set_metadata: + * @klass: class to set metadata for + * @longname: The long English name of the element. E.g. "File Sink" + * @classification: String describing the type of element, as an unordered list + * separated with slashes ('/'). See draft-klass.txt of the design docs + * for more details and common types. E.g: "Sink/File" + * @description: Sentence describing the purpose of the element. + * E.g: "Write stream to a file" + * @author: Name and contact details of the author(s). Use \n to separate + * multiple author metadata. E.g: "Joe Bloggs <joe.blogs at foo.com>" + * + * Sets the detailed information for a #GstElementClass. + * <note>This function is for use in _class_init functions only.</note> + */ +void +gst_element_class_set_metadata (GstElementClass * klass, + const gchar * longname, const gchar * classification, + const gchar * description, const gchar * author) +{ + g_return_if_fail (GST_IS_ELEMENT_CLASS (klass)); + + gst_structure_set ((GstStructure *) klass->metadata, + GST_ELEMENT_METADATA_LONGNAME, G_TYPE_STRING, longname, + GST_ELEMENT_METADATA_KLASS, G_TYPE_STRING, classification, + GST_ELEMENT_METADATA_DESCRIPTION, G_TYPE_STRING, description, + GST_ELEMENT_METADATA_AUTHOR, G_TYPE_STRING, author, NULL); +} + +/** + * gst_element_class_get_metadata: + * @klass: class to get metadata for + * @key: the key to get + * + * Get metadata with @key in @klass. + * + * Returns: the metadata for @key. + */ +const gchar * +gst_element_class_get_metadata (GstElementClass * klass, const gchar * key) +{ + g_return_val_if_fail (GST_IS_ELEMENT_CLASS (klass), NULL); + g_return_val_if_fail (key != NULL, NULL); + + return gst_structure_get_string ((GstStructure *) klass->metadata, key); +} + +/** + * gst_element_class_get_pad_template_list: + * @element_class: a #GstElementClass to get pad templates of. + * + * Retrieves a list of the pad templates associated with @element_class. The + * list must not be modified by the calling code. + * <note>If you use this function in the #GInstanceInitFunc of an object class + * that has subclasses, make sure to pass the g_class parameter of the + * #GInstanceInitFunc here.</note> + * + * Returns: (transfer none) (element-type Gst.PadTemplate): the #GList of + * pad templates. + */ +GList * +gst_element_class_get_pad_template_list (GstElementClass * element_class) +{ + g_return_val_if_fail (GST_IS_ELEMENT_CLASS (element_class), NULL); + + return element_class->padtemplates; +} + +/** + * gst_element_class_get_pad_template: + * @element_class: a #GstElementClass to get the pad template of. + * @name: the name of the #GstPadTemplate to get. + * + * Retrieves a padtemplate from @element_class with the given name. + * <note>If you use this function in the #GInstanceInitFunc of an object class + * that has subclasses, make sure to pass the g_class parameter of the + * #GInstanceInitFunc here.</note> + * + * Returns: (transfer none): the #GstPadTemplate with the given name, or %NULL + * if none was found. No unreferencing is necessary. + */ +GstPadTemplate * +gst_element_class_get_pad_template (GstElementClass * + element_class, const gchar * name) +{ + GList *padlist; + + g_return_val_if_fail (GST_IS_ELEMENT_CLASS (element_class), NULL); + g_return_val_if_fail (name != NULL, NULL); + + padlist = element_class->padtemplates; + + while (padlist) { + GstPadTemplate *padtempl = (GstPadTemplate *) padlist->data; + + if (strcmp (padtempl->name_template, name) == 0) + return padtempl; + + padlist = g_list_next (padlist); + } + + return NULL; +} + +static GstPadTemplate * +gst_element_class_get_request_pad_template (GstElementClass * + element_class, const gchar * name) +{ + GstPadTemplate *tmpl; + + tmpl = gst_element_class_get_pad_template (element_class, name); + if (tmpl != NULL && tmpl->presence == GST_PAD_REQUEST) + return tmpl; + + return NULL; +} + +/* get a random pad on element of the given direction. + * The pad is random in a sense that it is the first pad that is (optionaly) linked. + */ +static GstPad * +gst_element_get_random_pad (GstElement * element, + gboolean need_linked, GstPadDirection dir) +{ + GstPad *result = NULL; + GList *pads; + + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "getting a random pad"); + + switch (dir) { + case GST_PAD_SRC: + GST_OBJECT_LOCK (element); + pads = element->srcpads; + break; + case GST_PAD_SINK: + GST_OBJECT_LOCK (element); + pads = element->sinkpads; + break; + default: + goto wrong_direction; + } + for (; pads; pads = g_list_next (pads)) { + GstPad *pad = GST_PAD_CAST (pads->data); + + GST_OBJECT_LOCK (pad); + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "checking pad %s:%s", + GST_DEBUG_PAD_NAME (pad)); + + if (need_linked && !GST_PAD_IS_LINKED (pad)) { + /* if we require a linked pad, and it is not linked, continue the + * search */ + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "pad %s:%s is not linked", + GST_DEBUG_PAD_NAME (pad)); + GST_OBJECT_UNLOCK (pad); + continue; + } else { + /* found a pad, stop search */ + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "found pad %s:%s", + GST_DEBUG_PAD_NAME (pad)); + GST_OBJECT_UNLOCK (pad); + result = pad; + break; + } + } + if (result) + gst_object_ref (result); + + GST_OBJECT_UNLOCK (element); + + return result; + + /* ERROR handling */ +wrong_direction: + { + g_warning ("unknown pad direction %d", dir); + return NULL; + } +} + +static gboolean +gst_element_default_send_event (GstElement * element, GstEvent * event) +{ + gboolean result = FALSE; + GstPad *pad; + + pad = GST_EVENT_IS_DOWNSTREAM (event) ? + gst_element_get_random_pad (element, TRUE, GST_PAD_SRC) : + gst_element_get_random_pad (element, TRUE, GST_PAD_SINK); + + if (pad) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "pushing %s event to random %s pad %s:%s", + GST_EVENT_TYPE_NAME (event), + (GST_PAD_DIRECTION (pad) == GST_PAD_SRC ? "src" : "sink"), + GST_DEBUG_PAD_NAME (pad)); + + result = gst_pad_push_event (pad, event); + gst_object_unref (pad); + } else { + GST_CAT_INFO (GST_CAT_ELEMENT_PADS, "can't send %s event on element %s", + GST_EVENT_TYPE_NAME (event), GST_ELEMENT_NAME (element)); + gst_event_unref (event); + } + return result; +} + +/** + * gst_element_send_event: + * @element: a #GstElement to send the event to. + * @event: (transfer full): the #GstEvent to send to the element. + * + * Sends an event to an element. If the element doesn't implement an + * event handler, the event will be pushed on a random linked sink pad for + * upstream events or a random linked source pad for downstream events. + * + * This function takes owership of the provided event so you should + * gst_event_ref() it if you want to reuse the event after this call. + * + * Returns: %TRUE if the event was handled. + * + * MT safe. + */ +gboolean +gst_element_send_event (GstElement * element, GstEvent * event) +{ + GstElementClass *oclass; + gboolean result = FALSE; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + oclass = GST_ELEMENT_GET_CLASS (element); + + GST_STATE_LOCK (element); + if (oclass->send_event) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "send %s event on element %s", + GST_EVENT_TYPE_NAME (event), GST_ELEMENT_NAME (element)); + result = oclass->send_event (element, event); + } else { + result = gst_element_default_send_event (element, event); + } + GST_STATE_UNLOCK (element); + + return result; +} + +/** + * gst_element_seek: + * @element: a #GstElement to send the event to. + * @rate: The new playback rate + * @format: The format of the seek values + * @flags: The optional seek flags. + * @cur_type: The type and flags for the new current position + * @cur: The value of the new current position + * @stop_type: The type and flags for the new stop position + * @stop: The value of the new stop position + * + * Sends a seek event to an element. See gst_event_new_seek() for the details of + * the parameters. The seek event is sent to the element using + * gst_element_send_event(). + * + * Returns: %TRUE if the event was handled. + * + * MT safe. + */ +gboolean +gst_element_seek (GstElement * element, gdouble rate, GstFormat format, + GstSeekFlags flags, GstSeekType cur_type, gint64 cur, + GstSeekType stop_type, gint64 stop) +{ + GstEvent *event; + gboolean result; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + + event = + gst_event_new_seek (rate, format, flags, cur_type, cur, stop_type, stop); + result = gst_element_send_event (element, event); + + return result; +} + +/** + * gst_element_get_query_types: + * @element: a #GstElement to query + * + * Get an array of query types from the element. + * If the element doesn't implement a query types function, + * the query will be forwarded to the peer of a random linked sink pad. + * + * Returns: An array of #GstQueryType elements that should not + * be freed or modified. + * + * MT safe. + */ +const GstQueryType * +gst_element_get_query_types (GstElement * element) +{ + GstElementClass *oclass; + const GstQueryType *result = NULL; + + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + + oclass = GST_ELEMENT_GET_CLASS (element); + + if (oclass->get_query_types) { + result = oclass->get_query_types (element); + } else { + GstPad *pad = gst_element_get_random_pad (element, TRUE, GST_PAD_SINK); + + if (pad) { + GstPad *peer = gst_pad_get_peer (pad); + + if (peer) { + result = gst_pad_get_query_types (peer); + + gst_object_unref (peer); + } + gst_object_unref (pad); + } + } + return result; +} + +static gboolean +gst_element_default_query (GstElement * element, GstQuery * query) +{ + gboolean result = FALSE; + GstPad *pad; + + pad = gst_element_get_random_pad (element, FALSE, GST_PAD_SRC); + if (pad) { + result = gst_pad_query (pad, query); + + gst_object_unref (pad); + } else { + pad = gst_element_get_random_pad (element, TRUE, GST_PAD_SINK); + if (pad) { + GstPad *peer = gst_pad_get_peer (pad); + + if (peer) { + result = gst_pad_query (peer, query); + + gst_object_unref (peer); + } + gst_object_unref (pad); + } + } + return result; +} + +/** + * gst_element_query: + * @element: a #GstElement to perform the query on. + * @query: (transfer none): the #GstQuery. + * + * Performs a query on the given element. + * + * For elements that don't implement a query handler, this function + * forwards the query to a random srcpad or to the peer of a + * random linked sinkpad of this element. + * + * Please note that some queries might need a running pipeline to work. + * + * Returns: TRUE if the query could be performed. + * + * MT safe. + */ +gboolean +gst_element_query (GstElement * element, GstQuery * query) +{ + GstElementClass *oclass; + gboolean result = FALSE; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (query != NULL, FALSE); + + oclass = GST_ELEMENT_GET_CLASS (element); + + if (oclass->query) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "send query on element %s", + GST_ELEMENT_NAME (element)); + result = oclass->query (element, query); + } else { + result = gst_element_default_query (element, query); + } + return result; +} + +/** + * gst_element_post_message: + * @element: a #GstElement posting the message + * @message: (transfer full): a #GstMessage to post + * + * Post a message on the element's #GstBus. This function takes ownership of the + * message; if you want to access the message after this call, you should add an + * additional reference before calling. + * + * Returns: %TRUE if the message was successfully posted. The function returns + * %FALSE if the element did not have a bus. + * + * MT safe. + */ +gboolean +gst_element_post_message (GstElement * element, GstMessage * message) +{ + GstBus *bus; + gboolean result = FALSE; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (message != NULL, FALSE); + + GST_OBJECT_LOCK (element); + bus = element->bus; + + if (G_UNLIKELY (bus == NULL)) + goto no_bus; + + gst_object_ref (bus); + GST_OBJECT_UNLOCK (element); + + /* we release the element lock when posting the message so that any + * (synchronous) message handlers can operate on the element */ + result = gst_bus_post (bus, message); + gst_object_unref (bus); + + return result; + + /* ERRORS */ +no_bus: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_MESSAGE, element, + "not posting message %p: no bus", message); + GST_OBJECT_UNLOCK (element); + gst_message_unref (message); + return FALSE; + } +} + +/** + * _gst_element_error_printf: + * @format: the printf-like format to use, or %NULL + * + * This function is only used internally by the gst_element_error() macro. + * + * Returns: (transfer full): a newly allocated string, or %NULL if the format + * was %NULL or "" + * + * MT safe. + */ +gchar * +_gst_element_error_printf (const gchar * format, ...) +{ + va_list args; + gchar *buffer; + + if (format == NULL) + return NULL; + if (format[0] == 0) + return NULL; + + va_start (args, format); + buffer = g_strdup_vprintf (format, args); + va_end (args); + return buffer; +} + +/** + * gst_element_message_full: + * @element: a #GstElement to send message from + * @type: the #GstMessageType + * @domain: the GStreamer GError domain this message belongs to + * @code: the GError code belonging to the domain + * @text: (allow-none) (transfer full): an allocated text string to be used + * as a replacement for the default message connected to code, + * or %NULL + * @debug: (allow-none) (transfer full): an allocated debug message to be + * used as a replacement for the default debugging information, + * or %NULL + * @file: the source code file where the error was generated + * @function: the source code function where the error was generated + * @line: the source code line where the error was generated + * + * Post an error, warning or info message on the bus from inside an element. + * + * @type must be of #GST_MESSAGE_ERROR, #GST_MESSAGE_WARNING or + * #GST_MESSAGE_INFO. + * + * MT safe. + */ +void gst_element_message_full + (GstElement * element, GstMessageType type, + GQuark domain, gint code, gchar * text, + gchar * debug, const gchar * file, const gchar * function, gint line) +{ + GError *gerror = NULL; + gchar *name; + gchar *sent_text; + gchar *sent_debug; + gboolean has_debug = TRUE; + GstMessage *message = NULL; + + /* checks */ + GST_CAT_DEBUG_OBJECT (GST_CAT_MESSAGE, element, "start"); + g_return_if_fail (GST_IS_ELEMENT (element)); + g_return_if_fail ((type == GST_MESSAGE_ERROR) || + (type == GST_MESSAGE_WARNING) || (type == GST_MESSAGE_INFO)); + + /* check if we send the given text or the default error text */ + if ((text == NULL) || (text[0] == 0)) { + /* text could have come from g_strdup_printf (""); */ + g_free (text); + sent_text = gst_error_get_message (domain, code); + } else + sent_text = text; + + /* construct a sent_debug with extra information from source */ + if ((debug == NULL) || (debug[0] == 0)) { + /* debug could have come from g_strdup_printf (""); */ + has_debug = FALSE; + } + + name = gst_object_get_path_string (GST_OBJECT_CAST (element)); + if (has_debug) + sent_debug = g_strdup_printf ("%s(%d): %s (): %s:\n%s", + file, line, function, name, debug); + else + sent_debug = g_strdup_printf ("%s(%d): %s (): %s", + file, line, function, name); + g_free (name); + g_free (debug); + + /* create gerror and post message */ + GST_CAT_INFO_OBJECT (GST_CAT_ERROR_SYSTEM, element, "posting message: %s", + sent_text); + gerror = g_error_new_literal (domain, code, sent_text); + + switch (type) { + case GST_MESSAGE_ERROR: + message = + gst_message_new_error (GST_OBJECT_CAST (element), gerror, sent_debug); + break; + case GST_MESSAGE_WARNING: + message = gst_message_new_warning (GST_OBJECT_CAST (element), gerror, + sent_debug); + break; + case GST_MESSAGE_INFO: + message = gst_message_new_info (GST_OBJECT_CAST (element), gerror, + sent_debug); + break; + default: + g_assert_not_reached (); + break; + } + gst_element_post_message (element, message); + + GST_CAT_INFO_OBJECT (GST_CAT_ERROR_SYSTEM, element, "posted %s message: %s", + (type == GST_MESSAGE_ERROR ? "error" : "warning"), sent_text); + + /* cleanup */ + g_error_free (gerror); + g_free (sent_debug); + g_free (sent_text); +} + +/** + * gst_element_is_locked_state: + * @element: a #GstElement. + * + * Checks if the state of an element is locked. + * If the state of an element is locked, state changes of the parent don't + * affect the element. + * This way you can leave currently unused elements inside bins. Just lock their + * state before changing the state from #GST_STATE_NULL. + * + * MT safe. + * + * Returns: TRUE, if the element's state is locked. + */ +gboolean +gst_element_is_locked_state (GstElement * element) +{ + gboolean result; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + + GST_OBJECT_LOCK (element); + result = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_LOCKED_STATE); + GST_OBJECT_UNLOCK (element); + + return result; +} + +/** + * gst_element_set_locked_state: + * @element: a #GstElement + * @locked_state: TRUE to lock the element's state + * + * Locks the state of an element, so state changes of the parent don't affect + * this element anymore. + * + * MT safe. + * + * Returns: TRUE if the state was changed, FALSE if bad parameters were given + * or the elements state-locking needed no change. + */ +gboolean +gst_element_set_locked_state (GstElement * element, gboolean locked_state) +{ + gboolean old; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + + GST_OBJECT_LOCK (element); + old = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_LOCKED_STATE); + + if (G_UNLIKELY (old == locked_state)) + goto was_ok; + + if (locked_state) { + GST_CAT_DEBUG (GST_CAT_STATES, "locking state of element %s", + GST_ELEMENT_NAME (element)); + GST_OBJECT_FLAG_SET (element, GST_ELEMENT_LOCKED_STATE); + } else { + GST_CAT_DEBUG (GST_CAT_STATES, "unlocking state of element %s", + GST_ELEMENT_NAME (element)); + GST_OBJECT_FLAG_UNSET (element, GST_ELEMENT_LOCKED_STATE); + } + GST_OBJECT_UNLOCK (element); + + return TRUE; + +was_ok: + { + GST_CAT_DEBUG (GST_CAT_STATES, + "elements %s was already in locked state %d", + GST_ELEMENT_NAME (element), old); + GST_OBJECT_UNLOCK (element); + + return FALSE; + } +} + +/** + * gst_element_sync_state_with_parent: + * @element: a #GstElement. + * + * Tries to change the state of the element to the same as its parent. + * If this function returns FALSE, the state of element is undefined. + * + * Returns: TRUE, if the element's state could be synced to the parent's state. + * + * MT safe. + */ +gboolean +gst_element_sync_state_with_parent (GstElement * element) +{ + GstElement *parent; + GstState target; + GstStateChangeReturn ret; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + + if ((parent = GST_ELEMENT_CAST (gst_element_get_parent (element)))) { + GstState parent_current, parent_pending; + + GST_OBJECT_LOCK (parent); + parent_current = GST_STATE (parent); + parent_pending = GST_STATE_PENDING (parent); + GST_OBJECT_UNLOCK (parent); + + /* set to pending if there is one, else we set it to the current state of + * the parent */ + if (parent_pending != GST_STATE_VOID_PENDING) + target = parent_pending; + else + target = parent_current; + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "syncing state (%s) to parent %s %s (%s, %s)", + gst_element_state_get_name (GST_STATE (element)), + GST_ELEMENT_NAME (parent), gst_element_state_get_name (target), + gst_element_state_get_name (parent_current), + gst_element_state_get_name (parent_pending)); + + ret = gst_element_set_state (element, target); + if (ret == GST_STATE_CHANGE_FAILURE) + goto failed; + + gst_object_unref (parent); + + return TRUE; + } else { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "element has no parent"); + } + return FALSE; + + /* ERROR */ +failed: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "syncing state failed (%s)", + gst_element_state_change_return_get_name (ret)); + gst_object_unref (parent); + return FALSE; + } +} + +/* MT safe */ +static GstStateChangeReturn +gst_element_get_state_func (GstElement * element, + GstState * state, GstState * pending, GstClockTime timeout) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE; + GstState old_pending; + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "getting state, timeout %" + GST_TIME_FORMAT, GST_TIME_ARGS (timeout)); + + GST_OBJECT_LOCK (element); + ret = GST_STATE_RETURN (element); + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "RETURN is %s", + gst_element_state_change_return_get_name (ret)); + + /* we got an error, report immediately */ + if (ret == GST_STATE_CHANGE_FAILURE) + goto done; + + /* we got no_preroll, report immediately */ + if (ret == GST_STATE_CHANGE_NO_PREROLL) + goto done; + + /* no need to wait async if we are not async */ + if (ret != GST_STATE_CHANGE_ASYNC) + goto done; + + old_pending = GST_STATE_PENDING (element); + if (old_pending != GST_STATE_VOID_PENDING) { + GTimeVal *timeval, abstimeout; + guint32 cookie; + + if (timeout != GST_CLOCK_TIME_NONE) { + glong add = timeout / 1000; + + if (add == 0) + goto done; + + /* make timeout absolute */ + g_get_current_time (&abstimeout); + g_time_val_add (&abstimeout, add); + timeval = &abstimeout; + } else { + timeval = NULL; + } + /* get cookie to detect state changes during waiting */ + cookie = element->state_cookie; + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "waiting for element to commit state"); + + /* we have a pending state change, wait for it to complete */ + if (!GST_STATE_TIMED_WAIT (element, timeval)) { + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "timed out"); + /* timeout triggered */ + ret = GST_STATE_CHANGE_ASYNC; + } else { + if (cookie != element->state_cookie) + goto interrupted; + + /* could be success or failure */ + if (old_pending == GST_STATE (element)) { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "got success"); + ret = GST_STATE_CHANGE_SUCCESS; + } else { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "got failure"); + ret = GST_STATE_CHANGE_FAILURE; + } + } + /* if nothing is pending anymore we can return SUCCESS */ + if (GST_STATE_PENDING (element) == GST_STATE_VOID_PENDING) { + GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "nothing pending"); + ret = GST_STATE_CHANGE_SUCCESS; + } + } + +done: + if (state) + *state = GST_STATE (element); + if (pending) + *pending = GST_STATE_PENDING (element); + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "state current: %s, pending: %s, result: %s", + gst_element_state_get_name (GST_STATE (element)), + gst_element_state_get_name (GST_STATE_PENDING (element)), + gst_element_state_change_return_get_name (ret)); + GST_OBJECT_UNLOCK (element); + + return ret; + +interrupted: + { + if (state) + *state = GST_STATE_VOID_PENDING; + if (pending) + *pending = GST_STATE_VOID_PENDING; + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "interruped"); + + GST_OBJECT_UNLOCK (element); + + return GST_STATE_CHANGE_FAILURE; + } +} + +/** + * gst_element_get_state: + * @element: a #GstElement to get the state of. + * @state: (out) (allow-none): a pointer to #GstState to hold the state. + * Can be %NULL. + * @pending: (out) (allow-none): a pointer to #GstState to hold the pending + * state. Can be %NULL. + * @timeout: a #GstClockTime to specify the timeout for an async + * state change or %GST_CLOCK_TIME_NONE for infinite timeout. + * + * Gets the state of the element. + * + * For elements that performed an ASYNC state change, as reported by + * gst_element_set_state(), this function will block up to the + * specified timeout value for the state change to complete. + * If the element completes the state change or goes into + * an error, this function returns immediately with a return value of + * %GST_STATE_CHANGE_SUCCESS or %GST_STATE_CHANGE_FAILURE respectively. + * + * For elements that did not return %GST_STATE_CHANGE_ASYNC, this function + * returns the current and pending state immediately. + * + * This function returns %GST_STATE_CHANGE_NO_PREROLL if the element + * successfully changed its state but is not able to provide data yet. + * This mostly happens for live sources that only produce data in + * %GST_STATE_PLAYING. While the state change return is equivalent to + * %GST_STATE_CHANGE_SUCCESS, it is returned to the application to signal that + * some sink elements might not be able to complete their state change because + * an element is not producing data to complete the preroll. When setting the + * element to playing, the preroll will complete and playback will start. + * + * Returns: %GST_STATE_CHANGE_SUCCESS if the element has no more pending state + * and the last state change succeeded, %GST_STATE_CHANGE_ASYNC if the + * element is still performing a state change or + * %GST_STATE_CHANGE_FAILURE if the last state change failed. + * + * MT safe. + */ +GstStateChangeReturn +gst_element_get_state (GstElement * element, + GstState * state, GstState * pending, GstClockTime timeout) +{ + GstElementClass *oclass; + GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; + + g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE); + + oclass = GST_ELEMENT_GET_CLASS (element); + + if (oclass->get_state) + result = (oclass->get_state) (element, state, pending, timeout); + + return result; +} + +/** + * gst_element_abort_state: + * @element: a #GstElement to abort the state of. + * + * Abort the state change of the element. This function is used + * by elements that do asynchronous state changes and find out + * something is wrong. + * + * This function should be called with the STATE_LOCK held. + * + * MT safe. + */ +void +gst_element_abort_state (GstElement * element) +{ + GstState pending; + +#ifndef GST_DISABLE_GST_DEBUG + GstState old_state; +#endif + + g_return_if_fail (GST_IS_ELEMENT (element)); + + GST_OBJECT_LOCK (element); + pending = GST_STATE_PENDING (element); + + if (pending == GST_STATE_VOID_PENDING || + GST_STATE_RETURN (element) == GST_STATE_CHANGE_FAILURE) + goto nothing_aborted; + +#ifndef GST_DISABLE_GST_DEBUG + old_state = GST_STATE (element); + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "aborting state from %s to %s", gst_element_state_get_name (old_state), + gst_element_state_get_name (pending)); +#endif + + /* flag error */ + GST_STATE_RETURN (element) = GST_STATE_CHANGE_FAILURE; + + GST_STATE_BROADCAST (element); + GST_OBJECT_UNLOCK (element); + + return; + +nothing_aborted: + { + GST_OBJECT_UNLOCK (element); + return; + } +} + +/* Not static because GstBin has manual state handling too */ +void +_priv_gst_element_state_changed (GstElement * element, GstState oldstate, + GstState newstate, GstState pending) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); + GstMessage *message; + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "notifying about state-changed %s to %s (%s pending)", + gst_element_state_get_name (oldstate), + gst_element_state_get_name (newstate), + gst_element_state_get_name (pending)); + + if (klass->state_changed) + klass->state_changed (element, oldstate, newstate, pending); + + message = gst_message_new_state_changed (GST_OBJECT_CAST (element), + oldstate, newstate, pending); + gst_element_post_message (element, message); +} + +/** + * gst_element_continue_state: + * @element: a #GstElement to continue the state change of. + * @ret: The previous state return value + * + * Commit the state change of the element and proceed to the next + * pending state if any. This function is used + * by elements that do asynchronous state changes. + * The core will normally call this method automatically when an + * element returned %GST_STATE_CHANGE_SUCCESS from the state change function. + * + * If after calling this method the element still has not reached + * the pending state, the next state change is performed. + * + * This method is used internally and should normally not be called by plugins + * or applications. + * + * Returns: The result of the commit state change. + * + * MT safe. + */ +GstStateChangeReturn +gst_element_continue_state (GstElement * element, GstStateChangeReturn ret) +{ + GstStateChangeReturn old_ret; + GstState old_state, old_next; + GstState current, next, pending; + GstStateChange transition; + + GST_OBJECT_LOCK (element); + old_ret = GST_STATE_RETURN (element); + GST_STATE_RETURN (element) = ret; + pending = GST_STATE_PENDING (element); + + /* check if there is something to commit */ + if (pending == GST_STATE_VOID_PENDING) + goto nothing_pending; + + old_state = GST_STATE (element); + /* this is the state we should go to next */ + old_next = GST_STATE_NEXT (element); + /* update current state */ + current = GST_STATE (element) = old_next; + + /* see if we reached the final state */ + if (pending == current) + goto complete; + + next = GST_STATE_GET_NEXT (current, pending); + transition = (GstStateChange) GST_STATE_TRANSITION (current, next); + + GST_STATE_NEXT (element) = next; + /* mark busy */ + GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC; + GST_OBJECT_UNLOCK (element); + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "committing state from %s to %s, pending %s, next %s", + gst_element_state_get_name (old_state), + gst_element_state_get_name (old_next), + gst_element_state_get_name (pending), gst_element_state_get_name (next)); + + _priv_gst_element_state_changed (element, old_state, old_next, pending); + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "continue state change %s to %s, final %s", + gst_element_state_get_name (current), + gst_element_state_get_name (next), gst_element_state_get_name (pending)); + + ret = gst_element_change_state (element, transition); + + return ret; + +nothing_pending: + { + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "nothing pending"); + GST_OBJECT_UNLOCK (element); + return ret; + } +complete: + { + GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING; + GST_STATE_NEXT (element) = GST_STATE_VOID_PENDING; + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "completed state change to %s", gst_element_state_get_name (pending)); + GST_OBJECT_UNLOCK (element); + + /* don't post silly messages with the same state. This can happen + * when an element state is changed to what it already was. For bins + * this can be the result of a lost state, which we check with the + * previous return value. + * We do signal the cond though as a _get_state() might be blocking + * on it. */ + if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) + _priv_gst_element_state_changed (element, old_state, old_next, + GST_STATE_VOID_PENDING); + + GST_STATE_BROADCAST (element); + + return ret; + } +} + +/** + * gst_element_lost_state: + * @element: a #GstElement the state is lost of + * + * Brings the element to the lost state. The current state of the + * element is copied to the pending state so that any call to + * gst_element_get_state() will return %GST_STATE_CHANGE_ASYNC. + * + * An ASYNC_START message is posted. If the element was PLAYING, it will + * go to PAUSED. The element will be restored to its PLAYING state by + * the parent pipeline when it prerolls again. + * + * This is mostly used for elements that lost their preroll buffer + * in the %GST_STATE_PAUSED or %GST_STATE_PLAYING state after a flush, + * they will go to their pending state again when a new preroll buffer is + * queued. This function can only be called when the element is currently + * not in error or an async state change. + * + * This function is used internally and should normally not be called from + * plugins or applications. + */ +void +gst_element_lost_state (GstElement * element) +{ + GstState old_state, new_state; + GstMessage *message; + + g_return_if_fail (GST_IS_ELEMENT (element)); + + GST_OBJECT_LOCK (element); + if (GST_STATE_RETURN (element) == GST_STATE_CHANGE_FAILURE) + goto nothing_lost; + + if (GST_STATE_PENDING (element) != GST_STATE_VOID_PENDING) + goto only_async_start; + + old_state = GST_STATE (element); + + /* when we were PLAYING, the new state is PAUSED. We will also not + * automatically go to PLAYING but let the parent bin(s) set us to PLAYING + * when we preroll. */ + if (old_state > GST_STATE_PAUSED) + new_state = GST_STATE_PAUSED; + else + new_state = old_state; + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "lost state of %s to %s", gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state)); + + GST_STATE (element) = new_state; + GST_STATE_NEXT (element) = new_state; + GST_STATE_PENDING (element) = new_state; + GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC; + GST_OBJECT_UNLOCK (element); + + _priv_gst_element_state_changed (element, new_state, new_state, new_state); + + message = gst_message_new_async_start (GST_OBJECT_CAST (element)); + gst_element_post_message (element, message); + + return; + +nothing_lost: + { + GST_OBJECT_UNLOCK (element); + return; + } +only_async_start: + { + GST_OBJECT_UNLOCK (element); + + message = gst_message_new_async_start (GST_OBJECT_CAST (element)); + gst_element_post_message (element, message); + return; + } +} + +/** + * gst_element_set_state: + * @element: a #GstElement to change state of. + * @state: the element's new #GstState. + * + * Sets the state of the element. This function will try to set the + * requested state by going through all the intermediary states and calling + * the class's state change function for each. + * + * This function can return #GST_STATE_CHANGE_ASYNC, in which case the + * element will perform the remainder of the state change asynchronously in + * another thread. + * An application can use gst_element_get_state() to wait for the completion + * of the state change or it can wait for a state change message on the bus. + * + * State changes to %GST_STATE_READY or %GST_STATE_NULL never return + * #GST_STATE_CHANGE_ASYNC. + * + * Returns: Result of the state change using #GstStateChangeReturn. + * + * MT safe. + */ +GstStateChangeReturn +gst_element_set_state (GstElement * element, GstState state) +{ + GstElementClass *oclass; + GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; + + g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE); + + oclass = GST_ELEMENT_GET_CLASS (element); + + if (oclass->set_state) + result = (oclass->set_state) (element, state); + + return result; +} + +/* + * default set state function, calculates the next state based + * on current state and calls the change_state function + */ +static GstStateChangeReturn +gst_element_set_state_func (GstElement * element, GstState state) +{ + GstState current, next, old_pending; + GstStateChangeReturn ret; + GstStateChange transition; + GstStateChangeReturn old_ret; + + g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE); + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "set_state to %s", + gst_element_state_get_name (state)); + + /* state lock is taken to protect the set_state() and get_state() + * procedures, it does not lock any variables. */ + GST_STATE_LOCK (element); + + /* now calculate how to get to the new state */ + GST_OBJECT_LOCK (element); + old_ret = GST_STATE_RETURN (element); + /* previous state change returned an error, remove all pending + * and next states */ + if (old_ret == GST_STATE_CHANGE_FAILURE) { + GST_STATE_NEXT (element) = GST_STATE_VOID_PENDING; + GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING; + GST_STATE_RETURN (element) = GST_STATE_CHANGE_SUCCESS; + } + + current = GST_STATE (element); + next = GST_STATE_NEXT (element); + old_pending = GST_STATE_PENDING (element); + + /* this is the (new) state we should go to. TARGET is the last state we set on + * the element. */ + if (state != GST_STATE_TARGET (element)) { + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "setting target state to %s", gst_element_state_get_name (state)); + GST_STATE_TARGET (element) = state; + /* increment state cookie so that we can track each state change. We only do + * this if this is actually a new state change. */ + element->state_cookie++; + } + GST_STATE_PENDING (element) = state; + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "current %s, old_pending %s, next %s, old return %s", + gst_element_state_get_name (current), + gst_element_state_get_name (old_pending), + gst_element_state_get_name (next), + gst_element_state_change_return_get_name (old_ret)); + + /* if the element was busy doing a state change, we just update the + * target state, it'll get to it async then. */ + if (old_pending != GST_STATE_VOID_PENDING) { + /* upwards state change will happen ASYNC */ + if (old_pending <= state) + goto was_busy; + /* element is going to this state already */ + else if (next == state) + goto was_busy; + /* element was performing an ASYNC upward state change and + * we request to go downward again. Start from the next pending + * state then. */ + else if (next > state + && GST_STATE_RETURN (element) == GST_STATE_CHANGE_ASYNC) { + current = next; + } + } + next = GST_STATE_GET_NEXT (current, state); + /* now we store the next state */ + GST_STATE_NEXT (element) = next; + /* mark busy, we need to check that there is actually a state change + * to be done else we could accidentally override SUCCESS/NO_PREROLL and + * the default element change_state function has no way to know what the + * old value was... could consider this a FIXME...*/ + if (current != next) + GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC; + + transition = (GstStateChange) GST_STATE_TRANSITION (current, next); + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "%s: setting state from %s to %s", + (next != state ? "intermediate" : "final"), + gst_element_state_get_name (current), gst_element_state_get_name (next)); + + /* now signal any waiters, they will error since the cookie was incremented */ + GST_STATE_BROADCAST (element); + + GST_OBJECT_UNLOCK (element); + + ret = gst_element_change_state (element, transition); + + GST_STATE_UNLOCK (element); + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "returned %s", + gst_element_state_change_return_get_name (ret)); + + return ret; + +was_busy: + { + GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC; + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "element was busy with async state change"); + GST_OBJECT_UNLOCK (element); + + GST_STATE_UNLOCK (element); + + return GST_STATE_CHANGE_ASYNC; + } +} + +/** + * gst_element_change_state: + * @element: a #GstElement + * @transition: the requested transition + * + * Perform @transition on @element. + * + * This function must be called with STATE_LOCK held and is mainly used + * internally. + * + * Returns: the #GstStateChangeReturn of the state transition. + */ +GstStateChangeReturn +gst_element_change_state (GstElement * element, GstStateChange transition) +{ + GstElementClass *oclass; + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + oclass = GST_ELEMENT_GET_CLASS (element); + + /* call the state change function so it can set the state */ + if (oclass->change_state) + ret = (oclass->change_state) (element, transition); + else + ret = GST_STATE_CHANGE_FAILURE; + + switch (ret) { + case GST_STATE_CHANGE_FAILURE: + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "have FAILURE change_state return"); + /* state change failure */ + gst_element_abort_state (element); + break; + case GST_STATE_CHANGE_ASYNC: + { + GstState target; + + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "element will change state ASYNC"); + + target = GST_STATE_TARGET (element); + + if (target > GST_STATE_READY) + goto async; + + /* else we just continue the state change downwards */ + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "forcing commit state %s <= %s", + gst_element_state_get_name (target), + gst_element_state_get_name (GST_STATE_READY)); + + ret = gst_element_continue_state (element, GST_STATE_CHANGE_SUCCESS); + break; + } + case GST_STATE_CHANGE_SUCCESS: + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "element changed state SUCCESS"); + /* we can commit the state now which will proceeed to + * the next state */ + ret = gst_element_continue_state (element, ret); + break; + case GST_STATE_CHANGE_NO_PREROLL: + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "element changed state NO_PREROLL"); + /* we can commit the state now which will proceeed to + * the next state */ + ret = gst_element_continue_state (element, ret); + break; + default: + goto invalid_return; + } + + GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "exit state change %d", ret); + + return ret; + +async: + GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "exit async state change %d", + ret); + + return ret; + + /* ERROR */ +invalid_return: + { + GST_OBJECT_LOCK (element); + /* somebody added a GST_STATE_ and forgot to do stuff here ! */ + g_critical ("%s: unknown return value %d from a state change function", + GST_ELEMENT_NAME (element), ret); + + /* we are in error now */ + ret = GST_STATE_CHANGE_FAILURE; + GST_STATE_RETURN (element) = ret; + GST_OBJECT_UNLOCK (element); + + return ret; + } +} + +/* gst_iterator_fold functions for pads_activate + * Stop the iterator if activating one pad failed. */ +static gboolean +activate_pads (const GValue * vpad, GValue * ret, gboolean * active) +{ + GstPad *pad = g_value_get_object (vpad); + gboolean cont = TRUE; + + if (!(cont = gst_pad_set_active (pad, *active))) + g_value_set_boolean (ret, FALSE); + + return cont; +} + +/* returns false on error or early cutout of the fold, true if all + * pads in @iter were (de)activated successfully. */ +static gboolean +iterator_activate_fold_with_resync (GstIterator * iter, + GstIteratorFoldFunction func, gpointer user_data) +{ + GstIteratorResult ires; + GValue ret = { 0 }; + + /* no need to unset this later, it's just a boolean */ + g_value_init (&ret, G_TYPE_BOOLEAN); + g_value_set_boolean (&ret, TRUE); + + while (1) { + ires = gst_iterator_fold (iter, func, &ret, user_data); + switch (ires) { + case GST_ITERATOR_RESYNC: + /* need to reset the result again */ + g_value_set_boolean (&ret, TRUE); + gst_iterator_resync (iter); + break; + case GST_ITERATOR_DONE: + /* all pads iterated, return collected value */ + goto done; + default: + /* iterator returned _ERROR or premature end with _OK, + * mark an error and exit */ + g_value_set_boolean (&ret, FALSE); + goto done; + } + } +done: + /* return collected value */ + return g_value_get_boolean (&ret); +} + +/* is called with STATE_LOCK + * + * Pads are activated from source pads to sinkpads. + */ +static gboolean +gst_element_pads_activate (GstElement * element, gboolean active) +{ + GstIterator *iter; + gboolean res; + + GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element, + "pads_activate with active %d", active); + + iter = gst_element_iterate_src_pads (element); + res = + iterator_activate_fold_with_resync (iter, + (GstIteratorFoldFunction) activate_pads, &active); + gst_iterator_free (iter); + if (G_UNLIKELY (!res)) + goto src_failed; + + iter = gst_element_iterate_sink_pads (element); + res = + iterator_activate_fold_with_resync (iter, + (GstIteratorFoldFunction) activate_pads, &active); + gst_iterator_free (iter); + if (G_UNLIKELY (!res)) + goto sink_failed; + + GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element, + "pads_activate successful"); + + return TRUE; + + /* ERRORS */ +src_failed: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element, + "source pads_activate failed"); + return FALSE; + } +sink_failed: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element, + "sink pads_activate failed"); + return FALSE; + } +} + +/* is called with STATE_LOCK */ +static GstStateChangeReturn +gst_element_change_state_func (GstElement * element, GstStateChange transition) +{ + GstState state, next; + GstStateChangeReturn result = GST_STATE_CHANGE_SUCCESS; + + g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE); + + state = (GstState) GST_STATE_TRANSITION_CURRENT (transition); + next = GST_STATE_TRANSITION_NEXT (transition); + + /* if the element already is in the given state, we just return success */ + if (next == GST_STATE_VOID_PENDING || state == next) + goto was_ok; + + GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, + "default handler tries setting state from %s to %s (%04x)", + gst_element_state_get_name (state), + gst_element_state_get_name (next), transition); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (!gst_element_pads_activate (element, TRUE)) { + result = GST_STATE_CHANGE_FAILURE; + } + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + case GST_STATE_CHANGE_READY_TO_NULL: + /* deactivate pads in both cases, since they are activated on + ready->paused but the element might not have made it to paused */ + if (!gst_element_pads_activate (element, FALSE)) { + result = GST_STATE_CHANGE_FAILURE; + } + break; + default: + /* this will catch real but unhandled state changes; + * can only be caused by: + * - a new state was added + * - somehow the element was asked to jump across an intermediate state + */ + g_warning ("Unhandled state change from %s to %s", + gst_element_state_get_name (state), + gst_element_state_get_name (next)); + break; + } + return result; + +was_ok: + { + GST_OBJECT_LOCK (element); + result = GST_STATE_RETURN (element); + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, + "element is already in the %s state", + gst_element_state_get_name (state)); + GST_OBJECT_UNLOCK (element); + + return result; + } +} + +/** + * gst_element_get_factory: + * @element: a #GstElement to request the element factory of. + * + * Retrieves the factory that was used to create this element. + * + * Returns: (transfer none): the #GstElementFactory used for creating this + * element. no refcounting is needed. + */ +GstElementFactory * +gst_element_get_factory (GstElement * element) +{ + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + + return GST_ELEMENT_GET_CLASS (element)->elementfactory; +} + +static void +gst_element_dispose (GObject * object) +{ + GstElement *element = GST_ELEMENT_CAST (object); + GstClock **clock_p; + GstBus **bus_p; + + GST_CAT_INFO_OBJECT (GST_CAT_REFCOUNTING, element, "dispose"); + + if (GST_STATE (element) != GST_STATE_NULL) + goto not_null; + + GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element, + "removing %d pads", g_list_length (element->pads)); + /* first we break all our links with the outside */ + while (element->pads && element->pads->data) { + /* don't call _remove_pad with NULL */ + gst_element_remove_pad (element, GST_PAD_CAST (element->pads->data)); + } + if (G_UNLIKELY (element->pads != NULL)) { + g_critical ("could not remove pads from element %s", + GST_STR_NULL (GST_OBJECT_NAME (object))); + } + + GST_OBJECT_LOCK (element); + clock_p = &element->clock; + bus_p = &element->bus; + gst_object_replace ((GstObject **) clock_p, NULL); + gst_object_replace ((GstObject **) bus_p, NULL); + GST_OBJECT_UNLOCK (element); + + GST_CAT_INFO_OBJECT (GST_CAT_REFCOUNTING, element, "parent class dispose"); + + G_OBJECT_CLASS (parent_class)->dispose (object); + + return; + + /* ERRORS */ +not_null: + { + gboolean is_locked; + + is_locked = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_LOCKED_STATE); + g_critical + ("\nTrying to dispose element %s, but it is in %s%s instead of the NULL" + " state.\n" + "You need to explicitly set elements to the NULL state before\n" + "dropping the final reference, to allow them to clean up.\n" + "This problem may also be caused by a refcounting bug in the\n" + "application or some element.\n", + GST_OBJECT_NAME (element), + gst_element_state_get_name (GST_STATE (element)), + is_locked ? " (locked)" : ""); + return; + } +} + +static void +gst_element_finalize (GObject * object) +{ + GstElement *element = GST_ELEMENT_CAST (object); + + GST_CAT_INFO_OBJECT (GST_CAT_REFCOUNTING, element, "finalize"); + + g_cond_free (element->state_cond); + g_static_rec_mutex_free (&element->state_lock); + + GST_CAT_INFO_OBJECT (GST_CAT_REFCOUNTING, element, "finalize parent"); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_element_set_bus_func (GstElement * element, GstBus * bus) +{ + GstBus **bus_p; + + g_return_if_fail (GST_IS_ELEMENT (element)); + + GST_CAT_DEBUG_OBJECT (GST_CAT_PARENTAGE, element, "setting bus to %p", bus); + + GST_OBJECT_LOCK (element); + bus_p = &GST_ELEMENT_BUS (element); + gst_object_replace ((GstObject **) bus_p, GST_OBJECT_CAST (bus)); + GST_OBJECT_UNLOCK (element); +} + +/** + * gst_element_set_bus: + * @element: a #GstElement to set the bus of. + * @bus: (transfer none): the #GstBus to set. + * + * Sets the bus of the element. Increases the refcount on the bus. + * For internal use only, unless you're testing elements. + * + * MT safe. + */ +void +gst_element_set_bus (GstElement * element, GstBus * bus) +{ + GstElementClass *oclass; + + g_return_if_fail (GST_IS_ELEMENT (element)); + + oclass = GST_ELEMENT_GET_CLASS (element); + + if (oclass->set_bus) + oclass->set_bus (element, bus); +} + +/** + * gst_element_get_bus: + * @element: a #GstElement to get the bus of. + * + * Returns the bus of the element. Note that only a #GstPipeline will provide a + * bus for the application. + * + * Returns: (transfer full): the element's #GstBus. unref after usage. + * + * MT safe. + */ +GstBus * +gst_element_get_bus (GstElement * element) +{ + GstBus *result = NULL; + + g_return_val_if_fail (GST_IS_ELEMENT (element), result); + + GST_OBJECT_LOCK (element); + if ((result = GST_ELEMENT_BUS (element))) + gst_object_ref (result); + GST_OBJECT_UNLOCK (element); + + GST_CAT_DEBUG_OBJECT (GST_CAT_BUS, element, "got bus %" GST_PTR_FORMAT, + result); + + return result; +} diff --git a/gst/gstelement.h b/gst/gstelement.h new file mode 100644 index 0000000..9cc30ed --- /dev/null +++ b/gst/gstelement.h @@ -0,0 +1,811 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000,2004 Wim Taymans <wim@fluendo.com> + * + * gstelement.h: Header for GstElement + * + * 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_ELEMENT_H__ +#define __GST_ELEMENT_H__ + +/* gstelement.h and gstelementfactory.h include eachother */ +typedef struct _GstElement GstElement; +typedef struct _GstElementClass GstElementClass; + +/* gstmessage.h needs State */ +/** + * GstState: + * @GST_STATE_VOID_PENDING: no pending state. + * @GST_STATE_NULL : the NULL state or initial state of an element. + * @GST_STATE_READY : the element is ready to go to PAUSED. + * @GST_STATE_PAUSED : the element is PAUSED, it is ready to accept and + * process data. Sink elements however only accept one + * buffer and then block. + * @GST_STATE_PLAYING : the element is PLAYING, the #GstClock is running and + * the data is flowing. + * + * The possible states an element can be in. States can be changed using + * gst_element_set_state() and checked using gst_element_get_state(). + */ +typedef enum { + GST_STATE_VOID_PENDING = 0, + GST_STATE_NULL = 1, + GST_STATE_READY = 2, + GST_STATE_PAUSED = 3, + GST_STATE_PLAYING = 4 +} GstState; + + +#include <gst/gstconfig.h> +#include <gst/gstobject.h> +#include <gst/gstpad.h> +#include <gst/gstbus.h> +#include <gst/gstclock.h> +#include <gst/gstelementfactory.h> +#include <gst/gstplugin.h> +#include <gst/gstpluginfeature.h> +#include <gst/gstindex.h> +#include <gst/gstindexfactory.h> +#include <gst/gstiterator.h> +#include <gst/gstmessage.h> +#include <gst/gstquery.h> +#include <gst/gsttaglist.h> + +G_BEGIN_DECLS + +#define GST_TYPE_ELEMENT (gst_element_get_type ()) +#define GST_IS_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_ELEMENT)) +#define GST_IS_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_ELEMENT)) +#define GST_ELEMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_ELEMENT, GstElementClass)) +#define GST_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_ELEMENT, GstElement)) +#define GST_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_ELEMENT, GstElementClass)) +#define GST_ELEMENT_CAST(obj) ((GstElement*)(obj)) + +/** + * GstStateChangeReturn: + * @GST_STATE_CHANGE_FAILURE : the state change failed + * @GST_STATE_CHANGE_SUCCESS : the state change succeeded + * @GST_STATE_CHANGE_ASYNC : the state change will happen asynchronously + * @GST_STATE_CHANGE_NO_PREROLL: the state change succeeded but the element + * cannot produce data in %GST_STATE_PAUSED. + * This typically happens with live sources. + * + * The possible return values from a state change function such as + * gst_element_set_state(). Only @GST_STATE_CHANGE_FAILURE is a real failure. + */ +typedef enum { + GST_STATE_CHANGE_FAILURE = 0, + GST_STATE_CHANGE_SUCCESS = 1, + GST_STATE_CHANGE_ASYNC = 2, + GST_STATE_CHANGE_NO_PREROLL = 3 +} GstStateChangeReturn; + +/* NOTE: this probably should be done with an #ifdef to decide + * whether to safe-cast or to just do the non-checking cast. + */ + +/** + * GST_STATE: + * @elem: a #GstElement to return state for. + * + * This macro returns the current #GstState of the element. + */ +#define GST_STATE(elem) (GST_ELEMENT_CAST(elem)->current_state) + +/** + * GST_STATE_NEXT: + * @elem: a #GstElement to return the next state for. + * + * This macro returns the next #GstState of the element. + */ +#define GST_STATE_NEXT(elem) (GST_ELEMENT_CAST(elem)->next_state) + +/** + * GST_STATE_PENDING: + * @elem: a #GstElement to return the pending state for. + * + * This macro returns the currently pending #GstState of the element. + */ +#define GST_STATE_PENDING(elem) (GST_ELEMENT_CAST(elem)->pending_state) + +/** + * GST_STATE_TARGET: + * @elem: a #GstElement to return the target state for. + * + * This macro returns the target #GstState of the element. + * + * Since: 0.10.13 + */ +#define GST_STATE_TARGET(elem) (GST_ELEMENT_CAST(elem)->target_state) + +/** + * GST_STATE_RETURN: + * @elem: a #GstElement to return the last state result for. + * + * This macro returns the last #GstStateChangeReturn value. + */ +#define GST_STATE_RETURN(elem) (GST_ELEMENT_CAST(elem)->last_return) + +#define __GST_SIGN(val) ((val) < 0 ? -1 : ((val) > 0 ? 1 : 0)) +/** + * GST_STATE_GET_NEXT: + * @cur: A starting #GstState + * @pending: A target #GstState + * + * Given a current state @cur and a target state @pending, calculate the next (intermediate) + * #GstState. + */ +#define GST_STATE_GET_NEXT(cur,pending) ((GstState)((cur) + __GST_SIGN ((gint)(pending) - (gint)(cur)))) +/** + * GST_STATE_TRANSITION: + * @cur: A current state + * @next: A next state + * + * Given a current state @cur and a next state @next, calculate the associated + * #GstStateChange transition. + */ +#define GST_STATE_TRANSITION(cur,next) ((GstStateChange)(((cur)<<3)|(next))) +/** + * GST_STATE_TRANSITION_CURRENT: + * @trans: A #GstStateChange + * + * Given a state transition @trans, extract the current #GstState. + */ +#define GST_STATE_TRANSITION_CURRENT(trans) ((GstState)((trans)>>3)) +/** + * GST_STATE_TRANSITION_NEXT: + * @trans: A #GstStateChange + * + * Given a state transition @trans, extract the next #GstState. + */ +#define GST_STATE_TRANSITION_NEXT(trans) ((GstState)((trans)&0x7)) + +/** + * GstStateChange: + * @GST_STATE_CHANGE_NULL_TO_READY : state change from NULL to READY. + * <itemizedlist> + * <listitem><para> + * The element must check if the resources it needs are available. Device + * sinks and -sources typically try to probe the device to constrain their + * caps. + * </para></listitem> + * <listitem><para> + * The element opens the device (in case feature need to be probed). + * </para></listitem> + * </itemizedlist> + * @GST_STATE_CHANGE_READY_TO_PAUSED : state change from READY to PAUSED. + * <itemizedlist> + * <listitem><para> + * The element pads are activated in order to receive data in PAUSED. + * Streaming threads are started. + * </para></listitem> + * <listitem><para> + * Some elements might need to return %GST_STATE_CHANGE_ASYNC and complete + * the state change when they have enough information. It is a requirement + * for sinks to return %GST_STATE_CHANGE_ASYNC and complete the state change + * when they receive the first buffer or %GST_EVENT_EOS (preroll). + * Sinks also block the dataflow when in PAUSED. + * </para></listitem> + * <listitem><para> + * A pipeline resets the running_time to 0. + * </para></listitem> + * <listitem><para> + * Live sources return %GST_STATE_CHANGE_NO_PREROLL and don't generate data. + * </para></listitem> + * </itemizedlist> + * @GST_STATE_CHANGE_PAUSED_TO_PLAYING: state change from PAUSED to PLAYING. + * <itemizedlist> + * <listitem><para> + * Most elements ignore this state change. + * </para></listitem> + * <listitem><para> + * The pipeline selects a #GstClock and distributes this to all the children + * before setting them to PLAYING. This means that it is only alowed to + * synchronize on the #GstClock in the PLAYING state. + * </para></listitem> + * <listitem><para> + * The pipeline uses the #GstClock and the running_time to calculate the + * base_time. The base_time is distributed to all children when performing + * the state change. + * </para></listitem> + * <listitem><para> + * Sink elements stop blocking on the preroll buffer or event and start + * rendering the data. + * </para></listitem> + * <listitem><para> + * Sinks can post %GST_MESSAGE_EOS in the PLAYING state. It is not allowed + * to post %GST_MESSAGE_EOS when not in the PLAYING state. + * </para></listitem> + * <listitem><para> + * While streaming in PAUSED or PLAYING elements can create and remove + * sometimes pads. + * </para></listitem> + * <listitem><para> + * Live sources start generating data and return %GST_STATE_CHANGE_SUCCESS. + * </para></listitem> + * </itemizedlist> + * @GST_STATE_CHANGE_PLAYING_TO_PAUSED: state change from PLAYING to PAUSED. + * <itemizedlist> + * <listitem><para> + * Most elements ignore this state change. + * </para></listitem> + * <listitem><para> + * The pipeline calculates the running_time based on the last selected + * #GstClock and the base_time. It stores this information to continue + * playback when going back to the PLAYING state. + * </para></listitem> + * <listitem><para> + * Sinks unblock any #GstClock wait calls. + * </para></listitem> + * <listitem><para> + * When a sink does not have a pending buffer to play, it returns + * %GST_STATE_CHANGE_ASYNC from this state change and completes the state + * change when it receives a new buffer or an %GST_EVENT_EOS. + * </para></listitem> + * <listitem><para> + * Any queued %GST_MESSAGE_EOS items are removed since they will be reposted + * when going back to the PLAYING state. The EOS messages are queued in + * #GstBin containers. + * </para></listitem> + * <listitem><para> + * Live sources stop generating data and return %GST_STATE_CHANGE_NO_PREROLL. + * </para></listitem> + * </itemizedlist> + * @GST_STATE_CHANGE_PAUSED_TO_READY : state change from PAUSED to READY. + * <itemizedlist> + * <listitem><para> + * Sinks unblock any waits in the preroll. + * </para></listitem> + * <listitem><para> + * Elements unblock any waits on devices + * </para></listitem> + * <listitem><para> + * Chain or get_range functions return %GST_FLOW_WRONG_STATE. + * </para></listitem> + * <listitem><para> + * The element pads are deactivated so that streaming becomes impossible and + * all streaming threads are stopped. + * </para></listitem> + * <listitem><para> + * The sink forgets all negotiated formats + * </para></listitem> + * <listitem><para> + * Elements remove all sometimes pads + * </para></listitem> + * </itemizedlist> + * @GST_STATE_CHANGE_READY_TO_NULL : state change from READY to NULL. + * <itemizedlist> + * <listitem><para> + * Elements close devices + * </para></listitem> + * <listitem><para> + * Elements reset any internal state. + * </para></listitem> + * </itemizedlist> + * + * These are the different state changes an element goes through. + * %GST_STATE_NULL ⇒ %GST_STATE_PLAYING is called an upwards state change + * and %GST_STATE_PLAYING ⇒ %GST_STATE_NULL a downwards state change. + */ +typedef enum /*< flags=0 >*/ +{ + GST_STATE_CHANGE_NULL_TO_READY = (GST_STATE_NULL<<3) | GST_STATE_READY, + GST_STATE_CHANGE_READY_TO_PAUSED = (GST_STATE_READY<<3) | GST_STATE_PAUSED, + GST_STATE_CHANGE_PAUSED_TO_PLAYING = (GST_STATE_PAUSED<<3) | GST_STATE_PLAYING, + GST_STATE_CHANGE_PLAYING_TO_PAUSED = (GST_STATE_PLAYING<<3) | GST_STATE_PAUSED, + GST_STATE_CHANGE_PAUSED_TO_READY = (GST_STATE_PAUSED<<3) | GST_STATE_READY, + GST_STATE_CHANGE_READY_TO_NULL = (GST_STATE_READY<<3) | GST_STATE_NULL +} GstStateChange; + +/** + * GstElementFlags: + * @GST_ELEMENT_LOCKED_STATE: ignore state changes from parent + * @GST_ELEMENT_IS_SINK: the element is a sink + * @GST_ELEMENT_UNPARENTING: Child is being removed from the parent bin. + * gst_bin_remove() on a child already being removed immediately returns FALSE + * @GST_ELEMENT_IS_SOURCE: the element is a source. Since 0.10.31 + * @GST_ELEMENT_FLAG_LAST: offset to define more flags + * + * The standard flags that an element may have. + */ +typedef enum +{ + GST_ELEMENT_LOCKED_STATE = (GST_OBJECT_FLAG_LAST << 0), + GST_ELEMENT_IS_SINK = (GST_OBJECT_FLAG_LAST << 1), + GST_ELEMENT_UNPARENTING = (GST_OBJECT_FLAG_LAST << 2), + GST_ELEMENT_IS_SOURCE = (GST_OBJECT_FLAG_LAST << 3), + /* padding */ + GST_ELEMENT_FLAG_LAST = (GST_OBJECT_FLAG_LAST << 16) +} GstElementFlags; + +/** + * GST_ELEMENT_IS_LOCKED_STATE: + * @elem: A #GstElement to query + * + * Check if the element is in the locked state and therefore will ignore state + * changes from its parent object. + */ +#define GST_ELEMENT_IS_LOCKED_STATE(elem) (GST_OBJECT_FLAG_IS_SET(elem,GST_ELEMENT_LOCKED_STATE)) + +/** + * GST_ELEMENT_NAME: + * @elem: A #GstElement to query + * + * Gets the name of this element. Use only in core as this is not + * ABI-compatible. Others use gst_element_get_name() + */ +#define GST_ELEMENT_NAME(elem) (GST_OBJECT_NAME(elem)) + +/** + * GST_ELEMENT_PARENT: + * @elem: A #GstElement to query + * + * Get the parent object of this element. + */ +#define GST_ELEMENT_PARENT(elem) (GST_ELEMENT_CAST(GST_OBJECT_PARENT(elem))) + +/** + * GST_ELEMENT_BUS: + * @elem: A #GstElement to query + * + * Get the message bus of this element. + */ +#define GST_ELEMENT_BUS(elem) (GST_ELEMENT_CAST(elem)->bus) + +/** + * GST_ELEMENT_CLOCK: + * @elem: A #GstElement to query + * + * Get the clock of this element + */ +#define GST_ELEMENT_CLOCK(elem) (GST_ELEMENT_CAST(elem)->clock) + +/** + * GST_ELEMENT_PADS: + * @elem: A #GstElement to query + * + * Get the pads of this elements. + */ +#define GST_ELEMENT_PADS(elem) (GST_ELEMENT_CAST(elem)->pads) + +/** + * GST_ELEMENT_START_TIME: + * @elem: a #GstElement to return the start time for. + * + * This macro returns the start_time of the @elem. The start_time is the + * running_time of the pipeline when the element went to PAUSED. + * + * Since: 0.10.24 + */ +#define GST_ELEMENT_START_TIME(elem) (GST_ELEMENT_CAST(elem)->start_time) + +/** + * GST_ELEMENT_ERROR: + * @el: the element that generates the error + * @domain: like CORE, LIBRARY, RESOURCE or STREAM (see #gstreamer-GstGError) + * @code: error code defined for that domain (see #gstreamer-GstGError) + * @text: the message to display (format string and args enclosed in + parentheses) + * @debug: debugging information for the message (format string and args + enclosed in parentheses) + * + * Utility function that elements can use in case they encountered a fatal + * data processing error. The pipeline will post an error message and the + * application will be requested to stop further media processing. + */ +#define GST_ELEMENT_ERROR(el, domain, code, text, debug) \ +G_STMT_START { \ + gchar *__txt = _gst_element_error_printf text; \ + gchar *__dbg = _gst_element_error_printf debug; \ + if (__txt) \ + GST_WARNING_OBJECT (el, "error: %s", __txt); \ + if (__dbg) \ + GST_WARNING_OBJECT (el, "error: %s", __dbg); \ + gst_element_message_full (GST_ELEMENT(el), GST_MESSAGE_ERROR, \ + GST_ ## domain ## _ERROR, GST_ ## domain ## _ERROR_ ## code, \ + __txt, __dbg, __FILE__, GST_FUNCTION, __LINE__); \ +} G_STMT_END + +/** + * GST_ELEMENT_WARNING: + * @el: the element that generates the warning + * @domain: like CORE, LIBRARY, RESOURCE or STREAM (see #gstreamer-GstGError) + * @code: error code defined for that domain (see #gstreamer-GstGError) + * @text: the message to display (format string and args enclosed in + parentheses) + * @debug: debugging information for the message (format string and args + enclosed in parentheses) + * + * Utility function that elements can use in case they encountered a non-fatal + * data processing problem. The pipeline will post a warning message and the + * application will be informed. + */ +#define GST_ELEMENT_WARNING(el, domain, code, text, debug) \ +G_STMT_START { \ + gchar *__txt = _gst_element_error_printf text; \ + gchar *__dbg = _gst_element_error_printf debug; \ + if (__txt) \ + GST_WARNING_OBJECT (el, "warning: %s", __txt); \ + if (__dbg) \ + GST_WARNING_OBJECT (el, "warning: %s", __dbg); \ + gst_element_message_full (GST_ELEMENT(el), GST_MESSAGE_WARNING, \ + GST_ ## domain ## _ERROR, GST_ ## domain ## _ERROR_ ## code, \ + __txt, __dbg, __FILE__, GST_FUNCTION, __LINE__); \ +} G_STMT_END + +/** + * GST_ELEMENT_INFO: + * @el: the element that generates the information + * @domain: like CORE, LIBRARY, RESOURCE or STREAM (see #gstreamer-GstGError) + * @code: error code defined for that domain (see #gstreamer-GstGError) + * @text: the message to display (format string and args enclosed in + parentheses) + * @debug: debugging information for the message (format string and args + enclosed in parentheses) + * + * Utility function that elements can use in case they want to inform + * the application of something noteworthy that is not an error. + * The pipeline will post a info message and the + * application will be informed. + * + * Since: 0.10.12 + */ +#define GST_ELEMENT_INFO(el, domain, code, text, debug) \ +G_STMT_START { \ + gchar *__txt = _gst_element_error_printf text; \ + gchar *__dbg = _gst_element_error_printf debug; \ + if (__txt) \ + GST_INFO_OBJECT (el, "info: %s", __txt); \ + if (__dbg) \ + GST_INFO_OBJECT (el, "info: %s", __dbg); \ + gst_element_message_full (GST_ELEMENT(el), GST_MESSAGE_INFO, \ + GST_ ## domain ## _ERROR, GST_ ## domain ## _ERROR_ ## code, \ + __txt, __dbg, __FILE__, GST_FUNCTION, __LINE__); \ +} G_STMT_END + +/* the state change mutexes and conds */ +/** + * GST_STATE_GET_LOCK: + * @elem: a #GstElement + * + * Get a reference to the state lock of @elem. + * This lock is used by the core. It is taken while getting or setting + * the state, during state changes, and while finalizing. + */ +#define GST_STATE_GET_LOCK(elem) (&(GST_ELEMENT_CAST(elem)->state_lock)) +/** + * GST_STATE_GET_COND: + * @elem: a #GstElement + * + * Get the conditional used to signal the completion of a state change. + */ +#define GST_STATE_GET_COND(elem) (GST_ELEMENT_CAST(elem)->state_cond) + +#define GST_STATE_LOCK(elem) g_static_rec_mutex_lock(GST_STATE_GET_LOCK(elem)) +#define GST_STATE_TRYLOCK(elem) g_static_rec_mutex_trylock(GST_STATE_GET_LOCK(elem)) +#define GST_STATE_UNLOCK(elem) g_static_rec_mutex_unlock(GST_STATE_GET_LOCK(elem)) +#define GST_STATE_UNLOCK_FULL(elem) g_static_rec_mutex_unlock_full(GST_STATE_GET_LOCK(elem)) +#define GST_STATE_LOCK_FULL(elem,t) g_static_rec_mutex_lock_full(GST_STATE_GET_LOCK(elem), t) +#define GST_STATE_WAIT(elem) g_cond_wait (GST_STATE_GET_COND (elem), \ + GST_OBJECT_GET_LOCK (elem)) +#define GST_STATE_TIMED_WAIT(elem, timeval) g_cond_timed_wait (GST_STATE_GET_COND (elem), \ + GST_OBJECT_GET_LOCK (elem), timeval) +#define GST_STATE_SIGNAL(elem) g_cond_signal (GST_STATE_GET_COND (elem)); +#define GST_STATE_BROADCAST(elem) g_cond_broadcast (GST_STATE_GET_COND (elem)); + +/** + * GstElement: + * @state_lock: Used to serialize execution of gst_element_set_state() + * @state_cond: Used to signal completion of a state change + * @state_cookie: Used to detect concurrent execution of + * gst_element_set_state() and gst_element_get_state() + * @target_state: the target state of an element as set by the application + * @current_state: the current state of an element + * @next_state: the next state of an element, can be #GST_STATE_VOID_PENDING if + * the element is in the correct state. + * @pending_state: the final state the element should go to, can be + * #GST_STATE_VOID_PENDING if the element is in the correct state + * @last_return: the last return value of an element state change + * @bus: the bus of the element. This bus is provided to the element by the + * parent element or the application. A #GstPipeline has a bus of its own. + * @clock: the clock of the element. This clock is usually provided to the + * element by the toplevel #GstPipeline. + * @base_time: the time of the clock right before the element is set to + * PLAYING. Subtracting @base_time from the current clock time in the PLAYING + * state will yield the running_time against the clock. + * @start_time: the running_time of the last PAUSED state + * @numpads: number of pads of the element, includes both source and sink pads. + * @pads: list of pads + * @numsrcpads: number of source pads of the element. + * @srcpads: list of source pads + * @numsinkpads: number of sink pads of the element. + * @sinkpads: list of sink pads + * @pads_cookie: updated whenever the a pad is added or removed + * + * GStreamer element abstract base class. + */ +struct _GstElement +{ + GstObject object; + + /*< public >*/ /* with LOCK */ + GStaticRecMutex state_lock; + + /* element state */ + GCond *state_cond; + guint32 state_cookie; + GstState target_state; + GstState current_state; + GstState next_state; + GstState pending_state; + GstStateChangeReturn last_return; + + GstBus *bus; + + /* allocated clock */ + GstClock *clock; + GstClockTimeDiff base_time; /* NULL/READY: 0 - PAUSED: current time - PLAYING: difference to clock */ + GstClockTime start_time; + + /* element pads, these lists can only be iterated while holding + * the LOCK or checking the cookie after each LOCK. */ + guint16 numpads; + GList *pads; + guint16 numsrcpads; + GList *srcpads; + guint16 numsinkpads; + GList *sinkpads; + guint32 pads_cookie; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstElementClass: + * @parent_class: the parent class structure + * @metadata: metadata for elements of this class + * @elementfactory: the #GstElementFactory that creates these elements + * @padtemplates: a #GList of #GstPadTemplate + * @numpadtemplates: the number of padtemplates + * @pad_templ_cookie: changed whenever the padtemplates change + * @request_new_pad: called when a new pad is requested + * @release_pad: called when a request pad is to be released + * @get_state: get the state of the element + * @set_state: set a new state on the element + * @change_state: called by @set_state to perform an incremental state change + * @set_bus: set a #GstBus on the element + * @provide_clock: gets the #GstClock provided by the element + * @set_clock: set the #GstClock on the element + * @get_index: set a #GstIndex on the element + * @set_index: get the #GstIndex of an element + * @send_event: send a #GstEvent to the element + * @get_query_types: get the supported #GstQueryType of this element + * @query: perform a #GstQuery on the element + * @state_changed: called immediately after a new state was set. Since: 0.10.35. + * + * GStreamer element class. Override the vmethods to implement the element + * functionality. + */ +struct _GstElementClass +{ + GstObjectClass parent_class; + + /*< public >*/ + /* the element metadata */ + gpointer metadata; + + /* factory that the element was created from */ + GstElementFactory *elementfactory; + + /* templates for our pads */ + GList *padtemplates; + gint numpadtemplates; + guint32 pad_templ_cookie; + + /*< private >*/ + /* signal callbacks */ + void (*pad_added) (GstElement *element, GstPad *pad); + void (*pad_removed) (GstElement *element, GstPad *pad); + void (*no_more_pads) (GstElement *element); + + /*< public >*/ + /* virtual methods for subclasses */ + + /* request/release pads */ + GstPad* (*request_new_pad) (GstElement *element, GstPadTemplate *templ, + const gchar* name, const GstCaps *caps); + void (*release_pad) (GstElement *element, GstPad *pad); + + /* state changes */ + GstStateChangeReturn (*get_state) (GstElement * element, GstState * state, + GstState * pending, GstClockTime timeout); + GstStateChangeReturn (*set_state) (GstElement *element, GstState state); + GstStateChangeReturn (*change_state) (GstElement *element, GstStateChange transition); + void (*state_changed) (GstElement *element, GstState oldstate, + GstState newstate, GstState pending); + + /* bus */ + void (*set_bus) (GstElement * element, GstBus * bus); + + /* set/get clocks */ + GstClock* (*provide_clock) (GstElement *element); + gboolean (*set_clock) (GstElement *element, GstClock *clock); + + /* index */ + GstIndex* (*get_index) (GstElement *element); + void (*set_index) (GstElement *element, GstIndex *index); + + /* query functions */ + gboolean (*send_event) (GstElement *element, GstEvent *event); + + const GstQueryType* (*get_query_types) (GstElement *element); + gboolean (*query) (GstElement *element, GstQuery *query); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +/* element class pad templates */ +void gst_element_class_add_pad_template (GstElementClass *klass, GstPadTemplate *templ); +GstPadTemplate* gst_element_class_get_pad_template (GstElementClass *element_class, const gchar *name); +GList* gst_element_class_get_pad_template_list (GstElementClass *element_class); + +/* element class meta data */ +void gst_element_class_set_metadata (GstElementClass *klass, + const gchar *longname, + const gchar *classification, + const gchar *description, + const gchar *author); +void gst_element_class_add_metadata (GstElementClass * klass, + const gchar * key, const gchar * value); +const gchar * gst_element_class_get_metadata (GstElementClass * klass, + const gchar * key); + + +/* element instance */ +GType gst_element_get_type (void); + +/* basic name and parentage stuff from GstObject */ + +/** + * gst_element_get_name: + * @elem: a #GstElement to get the name of @elem. + * + * Returns a copy of the name of @elem. + * Caller should g_free() the return value after usage. + * For a nameless element, this returns NULL, which you can safely g_free() + * as well. + * + * Returns: (transfer full): the name of @elem. g_free() after usage. MT safe. + * + */ +#define gst_element_get_name(elem) gst_object_get_name(GST_OBJECT_CAST(elem)) + +/** + * gst_element_set_name: + * @elem: a #GstElement to set the name of. + * @name: the new name + * + * Sets the name of the element, getting rid of the old name if there was one. + */ +#define gst_element_set_name(elem,name) gst_object_set_name(GST_OBJECT_CAST(elem),name) + +/** + * gst_element_get_parent: + * @elem: a #GstElement to get the parent of. + * + * Get the parent of an element. + * + * Returns: (transfer full): the parent of an element. + */ +#define gst_element_get_parent(elem) gst_object_get_parent(GST_OBJECT_CAST(elem)) + +/** + * gst_element_set_parent: + * @elem: a #GstElement to set the parent of. + * @parent: the new parent #GstObject of the element. + * + * Sets the parent of an element. + */ +#define gst_element_set_parent(elem,parent) gst_object_set_parent(GST_OBJECT_CAST(elem),parent) + +/* clocking */ +gboolean gst_element_requires_clock (GstElement *element); +gboolean gst_element_provides_clock (GstElement *element); +GstClock* gst_element_provide_clock (GstElement *element); +GstClock* gst_element_get_clock (GstElement *element); +gboolean gst_element_set_clock (GstElement *element, GstClock *clock); +void gst_element_set_base_time (GstElement *element, GstClockTime time); +GstClockTime gst_element_get_base_time (GstElement *element); +void gst_element_set_start_time (GstElement *element, GstClockTime time); +GstClockTime gst_element_get_start_time (GstElement *element); + +/* indexes */ +gboolean gst_element_is_indexable (GstElement *element); +void gst_element_set_index (GstElement *element, GstIndex *index); +GstIndex* gst_element_get_index (GstElement *element); + +/* bus */ +void gst_element_set_bus (GstElement * element, GstBus * bus); +GstBus * gst_element_get_bus (GstElement * element); + +/* pad management */ +gboolean gst_element_add_pad (GstElement *element, GstPad *pad); +gboolean gst_element_remove_pad (GstElement *element, GstPad *pad); +void gst_element_no_more_pads (GstElement *element); + +GstPad* gst_element_get_static_pad (GstElement *element, const gchar *name); +GstPad* gst_element_get_request_pad (GstElement *element, const gchar *name); +GstPad* gst_element_request_pad (GstElement *element, GstPadTemplate *templ, + const gchar * name, const GstCaps *caps); +void gst_element_release_request_pad (GstElement *element, GstPad *pad); + +GstIterator * gst_element_iterate_pads (GstElement * element); +GstIterator * gst_element_iterate_src_pads (GstElement * element); +GstIterator * gst_element_iterate_sink_pads (GstElement * element); + +/* event/query/format stuff */ +gboolean gst_element_send_event (GstElement *element, GstEvent *event); +gboolean gst_element_seek (GstElement *element, gdouble rate, + GstFormat format, GstSeekFlags flags, + GstSeekType cur_type, gint64 cur, + GstSeekType stop_type, gint64 stop); +const GstQueryType* gst_element_get_query_types (GstElement *element); +gboolean gst_element_query (GstElement *element, GstQuery *query); + +/* messages */ +gboolean gst_element_post_message (GstElement * element, GstMessage * message); + +/* error handling */ +/* gcc versions < 3.3 warn about NULL being passed as format to printf */ +#if (defined(GST_USING_PRINTF_EXTENSION) || !defined(__GNUC__) || (__GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 3)) +gchar * _gst_element_error_printf (const gchar *format, ...); +#else +gchar * _gst_element_error_printf (const gchar *format, ...) G_GNUC_PRINTF (1, 2); +#endif +void gst_element_message_full (GstElement * element, GstMessageType type, + GQuark domain, gint code, gchar * text, + gchar * debug, const gchar * file, + const gchar * function, gint line); + +/* state management */ +gboolean gst_element_is_locked_state (GstElement *element); +gboolean gst_element_set_locked_state (GstElement *element, gboolean locked_state); +gboolean gst_element_sync_state_with_parent (GstElement *element); + +GstStateChangeReturn gst_element_get_state (GstElement * element, + GstState * state, + GstState * pending, + GstClockTime timeout); +GstStateChangeReturn gst_element_set_state (GstElement *element, GstState state); + +void gst_element_abort_state (GstElement * element); +GstStateChangeReturn gst_element_change_state (GstElement * element, + GstStateChange transition); +GstStateChangeReturn gst_element_continue_state (GstElement * element, + GstStateChangeReturn ret); +void gst_element_lost_state (GstElement * element); + +/* factory management */ +GstElementFactory* gst_element_get_factory (GstElement *element); + +G_END_DECLS + +#endif /* __GST_ELEMENT_H__ */ diff --git a/gst/gstelementfactory.c b/gst/gstelementfactory.c new file mode 100644 index 0000000..647674e --- /dev/null +++ b/gst/gstelementfactory.c @@ -0,0 +1,815 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gstelementfactory.c: GstElementFactory object, support routines + * + * 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:gstelementfactory + * @short_description: Create GstElements from a factory + * @see_also: #GstElement, #GstPlugin, #GstPluginFeature, #GstPadTemplate. + * + * #GstElementFactory is used to create instances of elements. A + * GstElementfactory can be added to a #GstPlugin as it is also a + * #GstPluginFeature. + * + * Use the gst_element_factory_find() and gst_element_factory_create() + * functions to create element instances or use gst_element_factory_make() as a + * convenient shortcut. + * + * The following code example shows you how to create a GstFileSrc element. + * + * <example> + * <title>Using an element factory</title> + * <programlisting language="c"> + * #include <gst/gst.h> + * + * GstElement *src; + * GstElementFactory *srcfactory; + * + * gst_init (&argc, &argv); + * + * srcfactory = gst_element_factory_find ("filesrc"); + * g_return_if_fail (srcfactory != NULL); + * src = gst_element_factory_create (srcfactory, "src"); + * g_return_if_fail (src != NULL); + * ... + * </programlisting> + * </example> + * + * Last reviewed on 2005-11-23 (0.9.5) + */ + +#include "gst_private.h" + +#include "gstelement.h" +#include "gstelementmetadata.h" +#include "gstinfo.h" +#include "gsturi.h" +#include "gstregistry.h" +#include "gst.h" + +#include "glib-compat-private.h" + +GST_DEBUG_CATEGORY_STATIC (element_factory_debug); +#define GST_CAT_DEFAULT element_factory_debug + +static void gst_element_factory_finalize (GObject * object); +static void gst_element_factory_cleanup (GstElementFactory * factory); + +/* static guint gst_element_factory_signals[LAST_SIGNAL] = { 0 }; */ + +/* this is defined in gstelement.c */ +extern GQuark _gst_elementclass_factory; + +#define _do_init \ +{ \ + GST_DEBUG_CATEGORY_INIT (element_factory_debug, "GST_ELEMENT_FACTORY", \ + GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_RED, \ + "element factories keep information about installed elements"); \ +} + +G_DEFINE_TYPE_WITH_CODE (GstElementFactory, gst_element_factory, + GST_TYPE_PLUGIN_FEATURE, _do_init); + +static void +gst_element_factory_class_init (GstElementFactoryClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gst_element_factory_finalize; +} + +static void +gst_element_factory_init (GstElementFactory * factory) +{ + factory->staticpadtemplates = NULL; + factory->numpadtemplates = 0; + + factory->uri_type = GST_URI_UNKNOWN; + factory->uri_protocols = NULL; + + factory->interfaces = NULL; +} + +static void +gst_element_factory_finalize (GObject * object) +{ + GstElementFactory *factory = GST_ELEMENT_FACTORY (object); + + gst_element_factory_cleanup (factory); + G_OBJECT_CLASS (gst_element_factory_parent_class)->finalize (object); +} + +/** + * gst_element_factory_find: + * @name: name of factory to find + * + * Search for an element factory of the given name. Refs the returned + * element factory; caller is responsible for unreffing. + * + * Returns: (transfer full): #GstElementFactory if found, NULL otherwise + */ +GstElementFactory * +gst_element_factory_find (const gchar * name) +{ + GstPluginFeature *feature; + + g_return_val_if_fail (name != NULL, NULL); + + feature = gst_registry_find_feature (gst_registry_get_default (), name, + GST_TYPE_ELEMENT_FACTORY); + if (feature) + return GST_ELEMENT_FACTORY (feature); + + /* this isn't an error, for instance when you query if an element factory is + * present */ + GST_LOG ("no such element factory \"%s\"", name); + return NULL; +} + +static void +gst_element_factory_cleanup (GstElementFactory * factory) +{ + GList *item; + + if (factory->metadata) { + gst_structure_free ((GstStructure *) factory->metadata); + factory->metadata = NULL; + } + if (factory->type) { + factory->type = G_TYPE_INVALID; + } + + for (item = factory->staticpadtemplates; item; item = item->next) { + GstStaticPadTemplate *templ = item->data; + + gst_static_caps_cleanup (&templ->static_caps); + g_slice_free (GstStaticPadTemplate, templ); + } + g_list_free (factory->staticpadtemplates); + factory->staticpadtemplates = NULL; + factory->numpadtemplates = 0; + factory->uri_type = GST_URI_UNKNOWN; + if (factory->uri_protocols) { + g_strfreev (factory->uri_protocols); + factory->uri_protocols = NULL; + } + + g_list_free (factory->interfaces); + factory->interfaces = NULL; +} + +/** + * gst_element_register: + * @plugin: (allow-none): #GstPlugin to register the element with, or NULL for + * a static element (note that passing NULL only works in GStreamer 0.10.13 + * and later) + * @name: name of elements of this type + * @rank: rank of element (higher rank means more importance when autoplugging) + * @type: GType of element to register + * + * Create a new elementfactory capable of instantiating objects of the + * @type and add the factory to @plugin. + * + * Returns: TRUE, if the registering succeeded, FALSE on error + */ +gboolean +gst_element_register (GstPlugin * plugin, const gchar * name, guint rank, + GType type) +{ + GstPluginFeature *existing_feature; + GstRegistry *registry; + GstElementFactory *factory; + GType *interfaces; + guint n_interfaces, i; + GstElementClass *klass; + GList *item; + + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (g_type_is_a (type, GST_TYPE_ELEMENT), FALSE); + + registry = gst_registry_get_default (); + + /* check if feature already exists, if it exists there is no need to update it + * when the registry is getting updated, outdated plugins and all their + * features are removed and readded. + */ + existing_feature = gst_registry_lookup_feature (registry, name); + if (existing_feature) { + GST_DEBUG_OBJECT (registry, "update existing feature %p (%s)", + existing_feature, name); + factory = GST_ELEMENT_FACTORY_CAST (existing_feature); + factory->type = type; + existing_feature->loaded = TRUE; + g_type_set_qdata (type, _gst_elementclass_factory, factory); + gst_object_unref (existing_feature); + return TRUE; + } + + factory = + GST_ELEMENT_FACTORY_CAST (g_object_newv (GST_TYPE_ELEMENT_FACTORY, 0, + NULL)); + gst_plugin_feature_set_name (GST_PLUGIN_FEATURE_CAST (factory), name); + GST_LOG_OBJECT (factory, "Created new elementfactory for type %s", + g_type_name (type)); + + /* provide info needed during class structure setup */ + g_type_set_qdata (type, _gst_elementclass_factory, factory); + klass = GST_ELEMENT_CLASS (g_type_class_ref (type)); +#if 0 + /* FIXME */ + if ((klass->details.longname == NULL) || + (klass->details.klass == NULL) || (klass->details.author == NULL)) + goto detailserror; +#endif + + factory->type = type; + factory->metadata = gst_structure_copy ((GstStructure *) klass->metadata); + + for (item = klass->padtemplates; item; item = item->next) { + GstPadTemplate *templ = item->data; + GstStaticPadTemplate *newt; + gchar *caps_string = gst_caps_to_string (templ->caps); + + newt = g_slice_new (GstStaticPadTemplate); + newt->name_template = g_intern_string (templ->name_template); + newt->direction = templ->direction; + newt->presence = templ->presence; + newt->static_caps.caps.mini_object.refcount = 0; + newt->static_caps.string = g_intern_string (caps_string); + factory->staticpadtemplates = + g_list_append (factory->staticpadtemplates, newt); + + g_free (caps_string); + } + factory->numpadtemplates = klass->numpadtemplates; + + /* special stuff for URI handling */ + if (g_type_is_a (type, GST_TYPE_URI_HANDLER)) { + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) + g_type_interface_peek (klass, GST_TYPE_URI_HANDLER); + + if (!iface || !iface->get_type || !iface->get_protocols) + goto urierror; + if (iface->get_type) + factory->uri_type = iface->get_type (factory->type); + if (!GST_URI_TYPE_IS_VALID (factory->uri_type)) + goto urierror; + if (iface->get_protocols) + factory->uri_protocols = iface->get_protocols (factory->type); + if (!factory->uri_protocols) + goto urierror; + } + + interfaces = g_type_interfaces (type, &n_interfaces); + for (i = 0; i < n_interfaces; i++) { + __gst_element_factory_add_interface (factory, g_type_name (interfaces[i])); + } + g_free (interfaces); + + if (plugin && plugin->desc.name) { + GST_PLUGIN_FEATURE_CAST (factory)->plugin_name = plugin->desc.name; + GST_PLUGIN_FEATURE_CAST (factory)->plugin = plugin; + g_object_add_weak_pointer ((GObject *) plugin, + (gpointer *) & GST_PLUGIN_FEATURE_CAST (factory)->plugin); + } else { + GST_PLUGIN_FEATURE_CAST (factory)->plugin_name = "NULL"; + GST_PLUGIN_FEATURE_CAST (factory)->plugin = NULL; + } + gst_plugin_feature_set_rank (GST_PLUGIN_FEATURE_CAST (factory), rank); + GST_PLUGIN_FEATURE_CAST (factory)->loaded = TRUE; + + gst_registry_add_feature (registry, GST_PLUGIN_FEATURE_CAST (factory)); + + return TRUE; + + /* ERRORS */ +urierror: + { + GST_WARNING_OBJECT (factory, "error with uri handler!"); + gst_element_factory_cleanup (factory); + return FALSE; + } + +#if 0 +detailserror: + { + GST_WARNING_OBJECT (factory, + "The GstElementDetails don't seem to have been set properly"); + gst_element_factory_cleanup (factory); + return FALSE; + } +#endif +} + +/** + * gst_element_factory_create: + * @factory: factory to instantiate + * @name: (allow-none): name of new element, or NULL to automatically create + * a unique name + * + * Create a new element of the type defined by the given elementfactory. + * It will be given the name supplied, since all elements require a name as + * their first argument. + * + * Returns: (transfer full): new #GstElement or NULL if the element couldn't + * be created + */ +GstElement * +gst_element_factory_create (GstElementFactory * factory, const gchar * name) +{ + GstElement *element; + GstElementClass *oclass; + GstElementFactory *newfactory; + + g_return_val_if_fail (factory != NULL, NULL); + + newfactory = + GST_ELEMENT_FACTORY (gst_plugin_feature_load (GST_PLUGIN_FEATURE + (factory))); + + if (newfactory == NULL) + goto load_failed; + + factory = newfactory; + + if (name) + GST_INFO ("creating element \"%s\" named \"%s\"", + GST_OBJECT_NAME (factory), GST_STR_NULL (name)); + else + GST_INFO ("creating element \"%s\"", GST_OBJECT_NAME (factory)); + + if (factory->type == 0) + goto no_type; + + /* create an instance of the element, cast so we don't assert on NULL + * also set name as early as we can + */ + if (name) + element = + GST_ELEMENT_CAST (g_object_new (factory->type, "name", name, NULL)); + else + element = GST_ELEMENT_CAST (g_object_newv (factory->type, 0, NULL)); + if (G_UNLIKELY (element == NULL)) + goto no_element; + + /* fill in the pointer to the factory in the element class. The + * class will not be unreffed currently. + * Be thread safe as there might be 2 threads creating the first instance of + * an element at the same moment + */ + oclass = GST_ELEMENT_GET_CLASS (element); + if (!G_ATOMIC_POINTER_COMPARE_AND_EXCHANGE (&oclass->elementfactory, NULL, + factory)) + gst_object_unref (factory); + + GST_DEBUG ("created element \"%s\"", GST_OBJECT_NAME (factory)); + + return element; + + /* ERRORS */ +load_failed: + { + GST_WARNING_OBJECT (factory, + "loading plugin containing feature %s returned NULL!", name); + return NULL; + } +no_type: + { + GST_WARNING_OBJECT (factory, "factory has no type"); + gst_object_unref (factory); + return NULL; + } +no_element: + { + GST_WARNING_OBJECT (factory, "could not create element"); + gst_object_unref (factory); + return NULL; + } +} + +/** + * gst_element_factory_make: + * @factoryname: a named factory to instantiate + * @name: (allow-none): name of new element, or NULL to automatically create + * a unique name + * + * Create a new element of the type defined by the given element factory. + * If name is NULL, then the element will receive a guaranteed unique name, + * consisting of the element factory name and a number. + * If name is given, it will be given the name supplied. + * + * Returns: (transfer full): new #GstElement or NULL if unable to create element + */ +GstElement * +gst_element_factory_make (const gchar * factoryname, const gchar * name) +{ + GstElementFactory *factory; + GstElement *element; + + g_return_val_if_fail (factoryname != NULL, NULL); + g_return_val_if_fail (gst_is_initialized (), NULL); + + GST_LOG ("gstelementfactory: make \"%s\" \"%s\"", + factoryname, GST_STR_NULL (name)); + + factory = gst_element_factory_find (factoryname); + if (factory == NULL) + goto no_factory; + + GST_LOG_OBJECT (factory, "found factory %p", factory); + element = gst_element_factory_create (factory, name); + if (element == NULL) + goto create_failed; + + gst_object_unref (factory); + return element; + + /* ERRORS */ +no_factory: + { + GST_INFO ("no such element factory \"%s\"!", factoryname); + return NULL; + } +create_failed: + { + GST_INFO_OBJECT (factory, "couldn't create instance!"); + gst_object_unref (factory); + return NULL; + } +} + +void +__gst_element_factory_add_static_pad_template (GstElementFactory * factory, + GstStaticPadTemplate * templ) +{ + g_return_if_fail (factory != NULL); + g_return_if_fail (templ != NULL); + + factory->staticpadtemplates = + g_list_append (factory->staticpadtemplates, templ); + factory->numpadtemplates++; +} + +/** + * gst_element_factory_get_element_type: + * @factory: factory to get managed #GType from + * + * Get the #GType for elements managed by this factory. The type can + * only be retrieved if the element factory is loaded, which can be + * assured with gst_plugin_feature_load(). + * + * Returns: the #GType for elements managed by this factory or 0 if + * the factory is not loaded. + */ +GType +gst_element_factory_get_element_type (GstElementFactory * factory) +{ + g_return_val_if_fail (GST_IS_ELEMENT_FACTORY (factory), 0); + + return factory->type; +} + +const gchar * +gst_element_factory_get_metadata (GstElementFactory * factory, + const gchar * key) +{ + return gst_structure_get_string ((GstStructure *) factory->metadata, key); +} + +/** + * gst_element_factory_get_num_pad_templates: + * @factory: a #GstElementFactory + * + * Gets the number of pad_templates in this factory. + * + * Returns: the number of pad_templates + */ +guint +gst_element_factory_get_num_pad_templates (GstElementFactory * factory) +{ + g_return_val_if_fail (GST_IS_ELEMENT_FACTORY (factory), 0); + + return factory->numpadtemplates; +} + +/** + * __gst_element_factory_add_interface: + * @elementfactory: The elementfactory to add the interface to + * @interfacename: Name of the interface + * + * Adds the given interfacename to the list of implemented interfaces of the + * element. + */ +void +__gst_element_factory_add_interface (GstElementFactory * elementfactory, + const gchar * interfacename) +{ + g_return_if_fail (GST_IS_ELEMENT_FACTORY (elementfactory)); + g_return_if_fail (interfacename != NULL); + g_return_if_fail (interfacename[0] != '\0'); /* no empty string */ + + elementfactory->interfaces = + g_list_prepend (elementfactory->interfaces, + (gpointer) g_intern_string (interfacename)); +} + +/** + * gst_element_factory_get_static_pad_templates: + * @factory: a #GstElementFactory + * + * Gets the #GList of #GstStaticPadTemplate for this factory. + * + * Returns: (transfer none) (element-type Gst.StaticPadTemplate): the + * static pad templates + */ +const GList * +gst_element_factory_get_static_pad_templates (GstElementFactory * factory) +{ + g_return_val_if_fail (GST_IS_ELEMENT_FACTORY (factory), NULL); + + return factory->staticpadtemplates; +} + +/** + * gst_element_factory_get_uri_type: + * @factory: a #GstElementFactory + * + * Gets the type of URIs the element supports or #GST_URI_UNKNOWN if none. + * + * Returns: type of URIs this element supports + */ +gint +gst_element_factory_get_uri_type (GstElementFactory * factory) +{ + g_return_val_if_fail (GST_IS_ELEMENT_FACTORY (factory), GST_URI_UNKNOWN); + + return factory->uri_type; +} + +/** + * gst_element_factory_get_uri_protocols: + * @factory: a #GstElementFactory + * + * Gets a NULL-terminated array of protocols this element supports or NULL if + * no protocols are supported. You may not change the contents of the returned + * array, as it is still owned by the element factory. Use g_strdupv() to + * make a copy of the protocol string array if you need to. + * + * Returns: (transfer none) (array zero-terminated=1): the supported protocols + * or NULL + */ +gchar ** +gst_element_factory_get_uri_protocols (GstElementFactory * factory) +{ + g_return_val_if_fail (GST_IS_ELEMENT_FACTORY (factory), NULL); + + return factory->uri_protocols; +} + +/** + * gst_element_factory_has_interface: + * @factory: a #GstElementFactory + * @interfacename: an interface name + * + * Check if @factory implements the interface with name @interfacename. + * + * Returns: #TRUE when @factory implement the interface. + * + * Since: 0.10.14 + */ +gboolean +gst_element_factory_has_interface (GstElementFactory * factory, + const gchar * interfacename) +{ + GList *walk; + + g_return_val_if_fail (GST_IS_ELEMENT_FACTORY (factory), FALSE); + + for (walk = factory->interfaces; walk; walk = g_list_next (walk)) { + gchar *iname = (gchar *) walk->data; + + if (!strcmp (iname, interfacename)) + return TRUE; + } + return FALSE; +} + + +typedef struct +{ + GstElementFactoryListType type; + GstRank minrank; +} FilterData; + + +/** + * gst_element_factory_list_is_type: + * @factory: a #GstElementFactory + * @type: a #GstElementFactoryListType + * + * Check if @factory is of the given types. + * + * Returns: %TRUE if @factory is of @type. + * + * Since: 0.10.31 + */ +gboolean +gst_element_factory_list_is_type (GstElementFactory * factory, + GstElementFactoryListType type) +{ + gboolean res = FALSE; + const gchar *klass; + + klass = + gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS); + + /* Filter by element type first, as soon as it matches + * one type, we skip all other tests */ + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_SINK)) + res = (strstr (klass, "Sink") != NULL); + + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_SRC)) + res = (strstr (klass, "Source") != NULL); + + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_DECODER)) + res = (strstr (klass, "Decoder") != NULL); + + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_ENCODER)) + res = (strstr (klass, "Encoder") != NULL); + + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_MUXER)) + res = (strstr (klass, "Muxer") != NULL); + + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_DEMUXER)) + res = (strstr (klass, "Demux") != NULL); + + /* FIXME : We're actually parsing two Classes here... */ + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_PARSER)) + res = ((strstr (klass, "Parser") != NULL) + && (strstr (klass, "Codec") != NULL)); + + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER)) + res = (strstr (klass, "Depayloader") != NULL); + + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_PAYLOADER)) + res = (strstr (klass, "Payloader") != NULL); + + if (!res && (type & GST_ELEMENT_FACTORY_TYPE_FORMATTER)) + res = (strstr (klass, "Formatter") != NULL); + + /* Filter by media type now, we only test if it + * matched any of the types above. */ + if (res + && (type & (GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO | + GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO | + GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE))) + res = ((type & GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) + && (strstr (klass, "Audio") != NULL)) + || ((type & GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO) + && (strstr (klass, "Video") != NULL)) + || ((type & GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE) + && (strstr (klass, "Image") != NULL)); + + return res; +} + +static gboolean +element_filter (GstPluginFeature * feature, FilterData * data) +{ + gboolean res; + + /* we only care about element factories */ + if (G_UNLIKELY (!GST_IS_ELEMENT_FACTORY (feature))) + return FALSE; + + res = (gst_plugin_feature_get_rank (feature) >= data->minrank) && + gst_element_factory_list_is_type (GST_ELEMENT_FACTORY_CAST (feature), + data->type); + + return res; +} + +/** + * gst_element_factory_list_get_elements: + * @type: a #GstElementFactoryListType + * @minrank: Minimum rank + * + * Get a list of factories that match the given @type. Only elements + * with a rank greater or equal to @minrank will be returned. + * The list of factories is returned by decreasing rank. + * + * Returns: (transfer full) (element-type Gst.ElementFactory): a #GList of + * #GstElementFactory elements. Use gst_plugin_feature_list_free() after + * usage. + * + * Since: 0.10.31 + */ +GList * +gst_element_factory_list_get_elements (GstElementFactoryListType type, + GstRank minrank) +{ + GList *result; + FilterData data; + + /* prepare type */ + data.type = type; + data.minrank = minrank; + + /* get the feature list using the filter */ + result = gst_default_registry_feature_filter ((GstPluginFeatureFilter) + element_filter, FALSE, &data); + + /* sort on rank and name */ + result = g_list_sort (result, gst_plugin_feature_rank_compare_func); + + return result; +} + +/** + * gst_element_factory_list_filter: + * @list: (transfer none) (element-type Gst.ElementFactory): a #GList of + * #GstElementFactory to filter + * @caps: a #GstCaps + * @direction: a #GstPadDirection to filter on + * @subsetonly: whether to filter on caps subsets or not. + * + * Filter out all the elementfactories in @list that can handle @caps in + * the given direction. + * + * If @subsetonly is %TRUE, then only the elements whose pads templates + * are a complete superset of @caps will be returned. Else any element + * whose pad templates caps can intersect with @caps will be returned. + * + * Returns: (transfer full) (element-type Gst.ElementFactory): a #GList of + * #GstElementFactory elements that match the given requisits. + * Use #gst_plugin_feature_list_free after usage. + * + * Since: 0.10.31 + */ +GList * +gst_element_factory_list_filter (GList * list, + const GstCaps * caps, GstPadDirection direction, gboolean subsetonly) +{ + GList *result = NULL; + + GST_DEBUG ("finding factories"); + + /* loop over all the factories */ + for (; list; list = list->next) { + GstElementFactory *factory; + const GList *templates; + GList *walk; + + factory = (GstElementFactory *) list->data; + + GST_DEBUG ("Trying %s", + gst_plugin_feature_get_name ((GstPluginFeature *) factory)); + + /* get the templates from the element factory */ + templates = gst_element_factory_get_static_pad_templates (factory); + for (walk = (GList *) templates; walk; walk = g_list_next (walk)) { + GstStaticPadTemplate *templ = walk->data; + + /* we only care about the sink templates */ + if (templ->direction == direction) { + GstCaps *tmpl_caps; + + /* try to intersect the caps with the caps of the template */ + tmpl_caps = gst_static_caps_get (&templ->static_caps); + + /* FIXME, intersect is not the right method, we ideally want to check + * for a subset here */ + + /* check if the intersection is empty */ + if ((subsetonly && gst_caps_is_subset (caps, tmpl_caps)) || + (!subsetonly && gst_caps_can_intersect (caps, tmpl_caps))) { + /* non empty intersection, we can use this element */ + result = g_list_prepend (result, gst_object_ref (factory)); + gst_caps_unref (tmpl_caps); + break; + } + gst_caps_unref (tmpl_caps); + } + } + } + return g_list_reverse (result); +} diff --git a/gst/gstelementfactory.h b/gst/gstelementfactory.h new file mode 100644 index 0000000..ee99594 --- /dev/null +++ b/gst/gstelementfactory.h @@ -0,0 +1,249 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000,2004 Wim Taymans <wim@fluendo.com> + * + * gstelement.h: Header for GstElement + * + * 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_ELEMENT_FACTORY_H__ +#define __GST_ELEMENT_FACTORY_H__ + +typedef struct _GstElementFactory GstElementFactory; +typedef struct _GstElementFactoryClass GstElementFactoryClass; + +#include <gst/gstconfig.h> +#include <gst/gstelement.h> +#include <gst/gstobject.h> +#include <gst/gstplugin.h> +#include <gst/gstpluginfeature.h> +#include <gst/gstpadtemplate.h> +#include <gst/gstiterator.h> + +G_BEGIN_DECLS + +#define GST_TYPE_ELEMENT_FACTORY (gst_element_factory_get_type()) +#define GST_ELEMENT_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ELEMENT_FACTORY,\ + GstElementFactory)) +#define GST_ELEMENT_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ELEMENT_FACTORY,\ + GstElementFactoryClass)) +#define GST_IS_ELEMENT_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ELEMENT_FACTORY)) +#define GST_IS_ELEMENT_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ELEMENT_FACTORY)) +#define GST_ELEMENT_FACTORY_CAST(obj) ((GstElementFactory *)(obj)) + +/** + * GstElementFactory: + * + * The opaque #GstElementFactory data structure. + */ +struct _GstElementFactory { + GstPluginFeature parent; + + GType type; /* unique GType of element or 0 if not loaded */ + + gpointer metadata; + + GList * staticpadtemplates; /* GstStaticPadTemplate list */ + guint numpadtemplates; + + /* URI interface stuff */ + guint uri_type; + gchar ** uri_protocols; + + GList * interfaces; /* interface type names this element implements */ + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstElementFactoryClass { + GstPluginFeatureClass parent_class; + + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_element_factory_get_type (void); + +GstElementFactory * gst_element_factory_find (const gchar *name); + +GType gst_element_factory_get_element_type (GstElementFactory *factory); + +const gchar * gst_element_factory_get_metadata (GstElementFactory *factory, const gchar *key); + +guint gst_element_factory_get_num_pad_templates (GstElementFactory *factory); +const GList * gst_element_factory_get_static_pad_templates (GstElementFactory *factory); + +gint gst_element_factory_get_uri_type (GstElementFactory *factory); +gchar ** gst_element_factory_get_uri_protocols (GstElementFactory *factory); + +gboolean gst_element_factory_has_interface (GstElementFactory *factory, + const gchar *interfacename); + +GstElement* gst_element_factory_create (GstElementFactory *factory, + const gchar *name); +GstElement* gst_element_factory_make (const gchar *factoryname, const gchar *name); + +/* FIXME 0.11: move these two into our private headers */ +void __gst_element_factory_add_static_pad_template (GstElementFactory *elementfactory, + GstStaticPadTemplate *templ); +void __gst_element_factory_add_interface (GstElementFactory *elementfactory, + const gchar *interfacename); +gboolean gst_element_register (GstPlugin *plugin, const gchar *name, + guint rank, GType type); + +/* Factory list functions */ + +/** + * GstFactoryListType: + * @GST_ELEMENT_FACTORY_TYPE_DECODER: Decoder elements + * @GST_ELEMENT_FACTORY_TYPE_ENCODER: Encoder elements + * @GST_ELEMENT_FACTORY_TYPE_SINK: Sink elements + * @GST_ELEMENT_FACTORY_TYPE_SRC: Source elements + * @GST_ELEMENT_FACTORY_TYPE_MUXER: Muxer elements + * @GST_ELEMENT_FACTORY_TYPE_DEMUXER: Demuxer elements + * @GST_ELEMENT_FACTORY_TYPE_PARSER: Parser elements + * @GST_ELEMENT_FACTORY_TYPE_PAYLOADER: Payloader elements + * @GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER: Depayloader elements + * @GST_ELEMENT_FACTORY_TYPE_MAX_ELEMENTS: Private, do not use + * @GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO: Elements handling video media types + * @GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO: Elements handling audio media types + * @GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE: Elements handling image media types + * @GST_ELEMENT_FACTORY_TYPE_MEDIA_SUBTITLE: Elements handling subtitle media types + * @GST_ELEMENT_FACTORY_TYPE_MEDIA_METADATA: Elements handling metadata media types + * + * The type of #GstElementFactory to filter. + * + * All @GstFactoryListType up to @GST_ELEMENT_FACTORY_TYPE_MAX_ELEMENTS are exclusive. + * + * If one or more of the MEDIA types are specified, then only elements + * matching the specified media types will be selected. + * + * Since: 0.10.31 + */ + +typedef guint64 GstElementFactoryListType; + +#define GST_ELEMENT_FACTORY_TYPE_DECODER (G_GUINT64_CONSTANT (1) << 0) +#define GST_ELEMENT_FACTORY_TYPE_ENCODER (G_GUINT64_CONSTANT (1) << 1) +#define GST_ELEMENT_FACTORY_TYPE_SINK (G_GUINT64_CONSTANT (1) << 2) +#define GST_ELEMENT_FACTORY_TYPE_SRC (G_GUINT64_CONSTANT (1) << 3) +#define GST_ELEMENT_FACTORY_TYPE_MUXER (G_GUINT64_CONSTANT (1) << 4) +#define GST_ELEMENT_FACTORY_TYPE_DEMUXER (G_GUINT64_CONSTANT (1) << 5) +#define GST_ELEMENT_FACTORY_TYPE_PARSER (G_GUINT64_CONSTANT (1) << 6) +#define GST_ELEMENT_FACTORY_TYPE_PAYLOADER (G_GUINT64_CONSTANT (1) << 7) +#define GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER (G_GUINT64_CONSTANT (1) << 8) +#define GST_ELEMENT_FACTORY_TYPE_FORMATTER (G_GUINT64_CONSTANT (1) << 9) + +#define GST_ELEMENT_FACTORY_TYPE_MAX_ELEMENTS (G_GUINT64_CONSTANT (1) << 48) + +#define GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO (G_GUINT64_CONSTANT (1) << 49) +#define GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO (G_GUINT64_CONSTANT (1) << 50) +#define GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE (G_GUINT64_CONSTANT (1) << 51) +#define GST_ELEMENT_FACTORY_TYPE_MEDIA_SUBTITLE (G_GUINT64_CONSTANT (1) << 52) +#define GST_ELEMENT_FACTORY_TYPE_MEDIA_METADATA (G_GUINT64_CONSTANT (1) << 53) + +/** + * GST_ELEMENT_FACTORY_TYPE_ANY: + * + * Elements of any of the defined GST_ELEMENT_FACTORY_LIST types + * + * Since: 0.10.31 + */ +#define GST_ELEMENT_FACTORY_TYPE_ANY ((G_GUINT64_CONSTANT (1) << 49) - 1) + +/** + * GST_ELEMENT_FACTORY_TYPE_MEDIA_ANY: + * + * Elements matching any of the defined GST_ELEMENT_FACTORY_TYPE_MEDIA types + * + * Note: Do not use this if you wish to not filter against any of the defined + * media types. If you wish to do this, simply don't specify any + * GST_ELEMENT_FACTORY_TYPE_MEDIA flag. + * + * Since: 0.10.31 + */ +#define GST_ELEMENT_FACTORY_TYPE_MEDIA_ANY (~G_GUINT64_CONSTANT (0) << 48) + +/** + * GST_ELEMENT_FACTORY_TYPE_VIDEO_ENCODER: + * + * All encoders handling video or image media types + * + * Since: 0.10.31 + */ +#define GST_ELEMENT_FACTORY_TYPE_VIDEO_ENCODER (GST_ELEMENT_FACTORY_TYPE_ENCODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO | GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE) + +/** + * GST_ELEMENT_FACTORY_TYPE_AUDIO_ENCODER: + * + * All encoders handling audio media types + * + * Since: 0.10.31 + */ +#define GST_ELEMENT_FACTORY_TYPE_AUDIO_ENCODER (GST_ELEMENT_FACTORY_TYPE_ENCODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) + +/** + * GST_ELEMENT_FACTORY_TYPE_AUDIOVIDEO_SINKS: + * + * All sinks handling audio, video or image media types + * + * Since: 0.10.31 + */ +#define GST_ELEMENT_FACTORY_TYPE_AUDIOVIDEO_SINKS (GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO | GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE) + +/** + * GST_ELEMENT_FACTORY_TYPE_DECODABLE: + * + * All elements used to 'decode' streams (decoders, demuxers, parsers, depayloaders) + * + * Since: 0.10.31 + */ +#define GST_ELEMENT_FACTORY_TYPE_DECODABLE \ + (GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_DEMUXER | GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER | GST_ELEMENT_FACTORY_TYPE_PARSER) + +/* Element klass defines */ +#define GST_ELEMENT_FACTORY_KLASS_DECODER "Decoder" +#define GST_ELEMENT_FACTORY_KLASS_ENCODER "Encoder" +#define GST_ELEMENT_FACTORY_KLASS_SINK "Sink" +#define GST_ELEMENT_FACTORY_KLASS_SRC "Source" +#define GST_ELEMENT_FACTORY_KLASS_MUXER "Muxer" +#define GST_ELEMENT_FACTORY_KLASS_DEMUXER "Demuxer" +#define GST_ELEMENT_FACTORY_KLASS_PARSER "Parser" +#define GST_ELEMENT_FACTORY_KLASS_PAYLOADER "Payloader" +#define GST_ELEMENT_FACTORY_KLASS_DEPAYLOADER "Depayloader" +#define GST_ELEMENT_FACTORY_KLASS_FORMATTER "Formatter" + +#define GST_ELEMENT_FACTORY_KLASS_MEDIA_VIDEO "Video" +#define GST_ELEMENT_FACTORY_KLASS_MEDIA_AUDIO "Audio" +#define GST_ELEMENT_FACTORY_KLASS_MEDIA_IMAGE "Image" +#define GST_ELEMENT_FACTORY_KLASS_MEDIA_SUBTITLE "Subtitle" +#define GST_ELEMENT_FACTORY_KLASS_MEDIA_METADATA "Metadata" + +gboolean gst_element_factory_list_is_type (GstElementFactory *factory, + GstElementFactoryListType type); + +GList * gst_element_factory_list_get_elements (GstElementFactoryListType type, + GstRank minrank); + + +GList * gst_element_factory_list_filter (GList *list, const GstCaps *caps, + GstPadDirection direction, + gboolean subsetonly); +G_END_DECLS + +#endif /* __GST_ELEMENT_FACTORY_H__ */ diff --git a/gst/gstelementmetadata.h b/gst/gstelementmetadata.h new file mode 100644 index 0000000..6c331d9 --- /dev/null +++ b/gst/gstelementmetadata.h @@ -0,0 +1,77 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000,2004 Wim Taymans <wim@fluendo.com> + * + * gstelementmetadata.h: Metadata for GstElement classes + * + * 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_ELEMENT_METADATA_H__ +#define __GST_ELEMENT_METADATA_H__ + +G_BEGIN_DECLS + +/** + * GST_ELEMENT_METADATA_LONGNAME: + * + * The long English name of the element. E.g. "File Sink" + */ +#define GST_ELEMENT_METADATA_LONGNAME "long-name" +/** + * GST_ELEMENT_METADATA_KLASS: + * + * String describing the type of element, as an unordered list + * separated with slashes ('/'). See draft-klass.txt of the design docs + * for more details and common types. E.g: "Sink/File" + */ +#define GST_ELEMENT_METADATA_KLASS "klass" + +/** + * GST_ELEMENT_METADATA_DESCRIPTION: + * + * Sentence describing the purpose of the element. + * E.g: "Write stream to a file" + */ +#define GST_ELEMENT_METADATA_DESCRIPTION "description" +/** + * GST_ELEMENT_METADATA_AUTHOR: + * + * Name and contact details of the author(s). Use \n to separate + * multiple author details. + * E.g: "Joe Bloggs <joe.blogs at foo.com>" + */ +#define GST_ELEMENT_METADATA_AUTHOR "author" + +/** + * GST_ELEMENT_METADATA_DOC_URI: + * + * Set uri pointing to user documentation. Applications can use this to show + * help for e.g. effects to users. + */ +#define GST_ELEMENT_METADATA_DOC_URI "doc-uri" +/** + * GST_ELEMENT_METADATA_ICON_NAME: + * + * Elements that bridge to certain other products can include an icon of that + * used product. Application can show the icon in menus/selectors to help + * identifying specific elements. + */ +#define GST_ELEMENT_METADATA_ICON_NAME "icon-name" + +G_END_DECLS + +#endif /* __GST_ELEMENT_METADATA_H__ */ diff --git a/gst/gsterror.c b/gst/gsterror.c new file mode 100644 index 0000000..f3f2d22 --- /dev/null +++ b/gst/gsterror.c @@ -0,0 +1,331 @@ +/* GStreamer + * Copyright (C) <2003> David A. Schleef <ds@schleef.org> + * + * 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:gsterror + * @short_description: Categorized error messages + * @see_also: #GstMessage + * + * GStreamer elements can throw non-fatal warnings and fatal errors. + * Higher-level elements and applications can programatically filter + * the ones they are interested in or can recover from, + * and have a default handler handle the rest of them. + * + * The rest of this section will use the term <quote>error</quote> + * to mean both (non-fatal) warnings and (fatal) errors; they are treated + * similarly. + * + * Errors from elements are the combination of a #GError and a debug string. + * The #GError contains: + * - a domain type: CORE, LIBRARY, RESOURCE or STREAM + * - a code: an enum value specific to the domain + * - a translated, human-readable message + * - a non-translated additional debug string, which also contains + * - file and line information + * + * Elements do not have the context required to decide what to do with + * errors. As such, they should only inform about errors, and stop their + * processing. In short, an element doesn't know what it is being used for. + * + * It is the application or compound element using the given element that + * has more context about the use of the element. Errors can be received by + * listening to the #GstBus of the element/pipeline for #GstMessage objects with + * the type %GST_MESSAGE_ERROR or %GST_MESSAGE_WARNING. The thrown errors should + * be inspected, and filtered if appropriate. + * + * An application is expected to, by default, present the user with a + * dialog box (or an equivalent) showing the error message. The dialog + * should also allow a way to get at the additional debug information, + * so the user can provide bug reporting information. + * + * A compound element is expected to forward errors by default higher up + * the hierarchy; this is done by default in the same way as for other types + * of #GstMessage. + * + * When applications or compound elements trigger errors that they can + * recover from, they can filter out these errors and take appropriate action. + * For example, an application that gets an error from xvimagesink + * that indicates all XVideo ports are taken, the application can attempt + * to use another sink instead. + * + * Elements throw errors using the #GST_ELEMENT_ERROR convenience macro: + * + * <example> + * <title>Throwing an error</title> + * <programlisting> + * GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, + * (_("No file name specified for reading.")), (NULL)); + * </programlisting> + * </example> + * + * Things to keep in mind: + * <itemizedlist> + * <listitem><para>Don't go off inventing new error codes. The ones + * currently provided should be enough. If you find your type of error + * does not fit the current codes, you should use FAILED.</para></listitem> + * <listitem><para>Don't provide a message if the default one suffices. + * this keeps messages more uniform. Use (NULL) - not forgetting the + * parentheses.</para></listitem> + * <listitem><para>If you do supply a custom message, it should be + * marked for translation. The message should start with a capital + * and end with a period. The message should describe the error in short, + * in a human-readable form, and without any complex technical terms. + * A user interface will present this message as the first thing a user + * sees. Details, technical info, ... should go in the debug string. + * </para></listitem> + * <listitem><para>The debug string can be as you like. Again, use (NULL) + * if there's nothing to add - file and line number will still be + * passed. #GST_ERROR_SYSTEM can be used as a shortcut to give + * debug information on a system call error.</para></listitem> + * </itemizedlist> + * + * Last reviewed on 2006-09-15 (0.10.10) + */ + +/* FIXME 0.11: the entire error system needs an overhaul - it's not very + * useful the way it is. Also, we need to be able to specify additional + * 'details' for errors (e.g. disk/file/resource error -> out-of-space; or + * put the url/filename/device name that caused the error somewhere) + * without having to add enums for every little thing. + * + * FIXME 0.11: get rid of GST_{CORE,LIBRARY,RESOURCE,STREAM}_ERROR_NUM_ERRORS. + * Maybe also replace _quark() functions with g_quark_from_static_string()? + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gst_private.h" +#include <gst/gst.h> +#include "gst-i18n-lib.h" + +#define QUARK_FUNC(string) \ +GQuark gst_ ## string ## _error_quark (void) { \ + static GQuark quark; \ + if (!quark) \ + quark = g_quark_from_static_string ("gst-" # string "-error-quark"); \ + return quark; } + +/* FIXME: Deprecate when we depend on GLib 2.26 */ +GType +gst_g_error_get_type (void) +{ +#if GLIB_CHECK_VERSION(2,25,2) + return g_error_get_type (); +#else + static GType type = 0; + + if (G_UNLIKELY (type == 0)) + type = g_boxed_type_register_static ("GstGError", + (GBoxedCopyFunc) g_error_copy, (GBoxedFreeFunc) g_error_free); + return type; +#endif +} + +#define FILE_A_BUG " Please file a bug at " PACKAGE_BUGREPORT "." + +static const gchar * +gst_error_get_core_error (GstCoreError code) +{ + switch (code) { + case GST_CORE_ERROR_FAILED: + return _("GStreamer encountered a general core library error."); + case GST_CORE_ERROR_TOO_LAZY: + return _("GStreamer developers were too lazy to assign an error code " + "to this error." FILE_A_BUG); + case GST_CORE_ERROR_NOT_IMPLEMENTED: + return _("Internal GStreamer error: code not implemented." FILE_A_BUG); + case GST_CORE_ERROR_STATE_CHANGE: + return _("GStreamer error: state change failed and some element failed " + "to post a proper error message with the reason for the failure."); + case GST_CORE_ERROR_PAD: + return _("Internal GStreamer error: pad problem." FILE_A_BUG); + case GST_CORE_ERROR_THREAD: + return _("Internal GStreamer error: thread problem." FILE_A_BUG); + case GST_CORE_ERROR_NEGOTIATION: + return _("Internal GStreamer error: negotiation problem." FILE_A_BUG); + case GST_CORE_ERROR_EVENT: + return _("Internal GStreamer error: event problem." FILE_A_BUG); + case GST_CORE_ERROR_SEEK: + return _("Internal GStreamer error: seek problem." FILE_A_BUG); + case GST_CORE_ERROR_CAPS: + return _("Internal GStreamer error: caps problem." FILE_A_BUG); + case GST_CORE_ERROR_TAG: + return _("Internal GStreamer error: tag problem." FILE_A_BUG); + case GST_CORE_ERROR_MISSING_PLUGIN: + return _("Your GStreamer installation is missing a plug-in."); + case GST_CORE_ERROR_CLOCK: + return _("Internal GStreamer error: clock problem." FILE_A_BUG); + case GST_CORE_ERROR_DISABLED: + return _("This application is trying to use GStreamer functionality " + "that has been disabled."); + case GST_CORE_ERROR_NUM_ERRORS: + default: + break; + } + return NULL; +} + +static const gchar * +gst_error_get_library_error (GstLibraryError code) +{ + switch (code) { + case GST_LIBRARY_ERROR_FAILED: + return _("GStreamer encountered a general supporting library error."); + case GST_LIBRARY_ERROR_TOO_LAZY: + return _("GStreamer developers were too lazy to assign an error code " + "to this error." FILE_A_BUG); + case GST_LIBRARY_ERROR_INIT: + return _("Could not initialize supporting library."); + case GST_LIBRARY_ERROR_SHUTDOWN: + return _("Could not close supporting library."); + case GST_LIBRARY_ERROR_SETTINGS: + return _("Could not configure supporting library."); + case GST_LIBRARY_ERROR_ENCODE: + return _("Encoding error."); + case GST_LIBRARY_ERROR_NUM_ERRORS: + default: + break; + } + return NULL; +} + +static const gchar * +gst_error_get_resource_error (GstResourceError code) +{ + switch (code) { + case GST_RESOURCE_ERROR_FAILED: + return _("GStreamer encountered a general resource error."); + case GST_RESOURCE_ERROR_TOO_LAZY: + return _("GStreamer developers were too lazy to assign an error code " + "to this error." FILE_A_BUG); + case GST_RESOURCE_ERROR_NOT_FOUND: + return _("Resource not found."); + case GST_RESOURCE_ERROR_BUSY: + return _("Resource busy or not available."); + case GST_RESOURCE_ERROR_OPEN_READ: + return _("Could not open resource for reading."); + case GST_RESOURCE_ERROR_OPEN_WRITE: + return _("Could not open resource for writing."); + case GST_RESOURCE_ERROR_OPEN_READ_WRITE: + return _("Could not open resource for reading and writing."); + case GST_RESOURCE_ERROR_CLOSE: + return _("Could not close resource."); + case GST_RESOURCE_ERROR_READ: + return _("Could not read from resource."); + case GST_RESOURCE_ERROR_WRITE: + return _("Could not write to resource."); + case GST_RESOURCE_ERROR_SEEK: + return _("Could not perform seek on resource."); + case GST_RESOURCE_ERROR_SYNC: + return _("Could not synchronize on resource."); + case GST_RESOURCE_ERROR_SETTINGS: + return _("Could not get/set settings from/on resource."); + case GST_RESOURCE_ERROR_NO_SPACE_LEFT: + return _("No space left on the resource."); + case GST_RESOURCE_ERROR_NUM_ERRORS: + default: + break; + } + return NULL; +} + +static const gchar * +gst_error_get_stream_error (GstStreamError code) +{ + switch (code) { + case GST_STREAM_ERROR_FAILED: + return _("GStreamer encountered a general stream error."); + case GST_STREAM_ERROR_TOO_LAZY: + return _("GStreamer developers were too lazy to assign an error code " + "to this error." FILE_A_BUG); + case GST_STREAM_ERROR_NOT_IMPLEMENTED: + return _("Element doesn't implement handling of this stream. " + "Please file a bug."); + case GST_STREAM_ERROR_TYPE_NOT_FOUND: + return _("Could not determine type of stream."); + case GST_STREAM_ERROR_WRONG_TYPE: + return _("The stream is of a different type than handled by this " + "element."); + case GST_STREAM_ERROR_CODEC_NOT_FOUND: + return _("There is no codec present that can handle the stream's type."); + case GST_STREAM_ERROR_DECODE: + return _("Could not decode stream."); + case GST_STREAM_ERROR_ENCODE: + return _("Could not encode stream."); + case GST_STREAM_ERROR_DEMUX: + return _("Could not demultiplex stream."); + case GST_STREAM_ERROR_MUX: + return _("Could not multiplex stream."); + case GST_STREAM_ERROR_FORMAT: + return _("The stream is in the wrong format."); + case GST_STREAM_ERROR_DECRYPT: + return _("The stream is encrypted and decryption is not supported."); + case GST_STREAM_ERROR_DECRYPT_NOKEY: + return _("The stream is encrypted and can't be decrypted because no " + "suitable key has been supplied."); + case GST_STREAM_ERROR_NUM_ERRORS: + default: + break; + } + + return NULL; +} + +QUARK_FUNC (core); +QUARK_FUNC (library); +QUARK_FUNC (resource); +QUARK_FUNC (stream); + +/** + * gst_error_get_message: + * @domain: the GStreamer error domain this error belongs to. + * @code: the error code belonging to the domain. + * + * Get a string describing the error message in the current locale. + * + * Returns: (transfer full): a newly allocated string describing + * the error message (in UTF-8 encoding) + */ +gchar * +gst_error_get_message (GQuark domain, gint code) +{ + const gchar *message = NULL; + + if (domain == GST_CORE_ERROR) + message = gst_error_get_core_error ((GstCoreError) code); + else if (domain == GST_LIBRARY_ERROR) + message = gst_error_get_library_error ((GstLibraryError) code); + else if (domain == GST_RESOURCE_ERROR) + message = gst_error_get_resource_error ((GstResourceError) code); + else if (domain == GST_STREAM_ERROR) + message = gst_error_get_stream_error ((GstStreamError) code); + else { + g_warning ("No error messages for domain %s", g_quark_to_string (domain)); + return g_strdup_printf (_("No error message for domain %s."), + g_quark_to_string (domain)); + } + if (message) + return g_strdup (message); + else + return + g_strdup_printf (_ + ("No standard error message for domain %s and code %d."), + g_quark_to_string (domain), code); +} diff --git a/gst/gsterror.h b/gst/gsterror.h new file mode 100644 index 0000000..d722ce0 --- /dev/null +++ b/gst/gsterror.h @@ -0,0 +1,255 @@ +/* GStreamer + * Copyright (C) 2004 Thomas Vander Stichele <thomas at apestaart dot org> + * + * 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_ERROR_H__ +#define __GST_ERROR_H__ + +#include <glib.h> +#include <glib-object.h> +#include <errno.h> + +G_BEGIN_DECLS +/* + * we define FIXME error domains: + * GST_CORE_ERROR + * GST_LIBRARY_ERROR + * GST_RESOURCE_ERROR + * GST_STREAM_ERROR + * + * Check GError API docs for rationale for naming. + */ +/** + * GstCoreError: + * @GST_CORE_ERROR_FAILED: a general error which doesn't fit in any other + * category. Make sure you add a custom message to the error call. + * @GST_CORE_ERROR_TOO_LAZY: do not use this except as a placeholder for + * deciding where to go while developing code. + * @GST_CORE_ERROR_NOT_IMPLEMENTED: use this when you do not want to implement + * this functionality yet. + * @GST_CORE_ERROR_STATE_CHANGE: used for state change errors. + * @GST_CORE_ERROR_PAD: used for pad-related errors. + * @GST_CORE_ERROR_THREAD: used for thread-related errors. + * @GST_CORE_ERROR_NEGOTIATION: used for negotiation-related errors. + * @GST_CORE_ERROR_EVENT: used for event-related errors. + * @GST_CORE_ERROR_SEEK: used for seek-related errors. + * @GST_CORE_ERROR_CAPS: used for caps-related errors. + * @GST_CORE_ERROR_TAG: used for negotiation-related errors. + * @GST_CORE_ERROR_MISSING_PLUGIN: used if a plugin is missing. + * @GST_CORE_ERROR_CLOCK: used for clock related errors. + * @GST_CORE_ERROR_DISABLED: used if functionality has been disabled at + * compile time (Since: 0.10.13). + * @GST_CORE_ERROR_NUM_ERRORS: the number of core error types. + * + * Core errors are errors inside the core GStreamer library. + */ +/* FIXME: should we divide in numerical blocks so we can easily add + for example PAD errors later ? */ +typedef enum +{ + GST_CORE_ERROR_FAILED = 1, + GST_CORE_ERROR_TOO_LAZY, + GST_CORE_ERROR_NOT_IMPLEMENTED, + GST_CORE_ERROR_STATE_CHANGE, + GST_CORE_ERROR_PAD, + GST_CORE_ERROR_THREAD, + GST_CORE_ERROR_NEGOTIATION, + GST_CORE_ERROR_EVENT, + GST_CORE_ERROR_SEEK, + GST_CORE_ERROR_CAPS, + GST_CORE_ERROR_TAG, + GST_CORE_ERROR_MISSING_PLUGIN, + GST_CORE_ERROR_CLOCK, + GST_CORE_ERROR_DISABLED, + GST_CORE_ERROR_NUM_ERRORS +} GstCoreError; + +/** + * GstLibraryError: + * @GST_LIBRARY_ERROR_FAILED: a general error which doesn't fit in any other + * category. Make sure you add a custom message to the error call. + * @GST_LIBRARY_ERROR_TOO_LAZY: do not use this except as a placeholder for + * deciding where to go while developing code. + * @GST_LIBRARY_ERROR_INIT: used when the library could not be opened. + * @GST_LIBRARY_ERROR_SHUTDOWN: used when the library could not be closed. + * @GST_LIBRARY_ERROR_SETTINGS: used when the library doesn't accept settings. + * @GST_LIBRARY_ERROR_ENCODE: used when the library generated an encoding error. + * @GST_LIBRARY_ERROR_NUM_ERRORS: the number of library error types. + * + * Library errors are for errors from the library being used by elements + * (initializing, finalizing, settings, ...) + */ +typedef enum +{ + GST_LIBRARY_ERROR_FAILED = 1, + GST_LIBRARY_ERROR_TOO_LAZY, + GST_LIBRARY_ERROR_INIT, + GST_LIBRARY_ERROR_SHUTDOWN, + GST_LIBRARY_ERROR_SETTINGS, + GST_LIBRARY_ERROR_ENCODE, + GST_LIBRARY_ERROR_NUM_ERRORS +} GstLibraryError; + +/** + * GstResourceError: + * @GST_RESOURCE_ERROR_FAILED: a general error which doesn't fit in any other + * category. Make sure you add a custom message to the error call. + * @GST_RESOURCE_ERROR_TOO_LAZY: do not use this except as a placeholder for + * deciding where to go while developing code. + * @GST_RESOURCE_ERROR_NOT_FOUND: used when the resource could not be found. + * @GST_RESOURCE_ERROR_BUSY: used when resource is busy. + * @GST_RESOURCE_ERROR_OPEN_READ: used when resource fails to open for reading. + * @GST_RESOURCE_ERROR_OPEN_WRITE: used when resource fails to open for writing. + * @GST_RESOURCE_ERROR_OPEN_READ_WRITE: used when resource cannot be opened for + * both reading and writing, or either (but unspecified which). + * @GST_RESOURCE_ERROR_CLOSE: used when the resource can't be closed. + * @GST_RESOURCE_ERROR_READ: used when the resource can't be read from. + * @GST_RESOURCE_ERROR_WRITE: used when the resource can't be written to. + * @GST_RESOURCE_ERROR_SEEK: used when a seek on the resource fails. + * @GST_RESOURCE_ERROR_SYNC: used when a synchronize on the resource fails. + * @GST_RESOURCE_ERROR_SETTINGS: used when settings can't be manipulated on. + * @GST_RESOURCE_ERROR_NO_SPACE_LEFT: used when the resource has no space left. + * @GST_RESOURCE_ERROR_NUM_ERRORS: the number of resource error types. + * + * Resource errors are for any resource used by an element: + * memory, files, network connections, process space, ... + * They're typically used by source and sink elements. + */ +typedef enum +{ + GST_RESOURCE_ERROR_FAILED = 1, + GST_RESOURCE_ERROR_TOO_LAZY, + GST_RESOURCE_ERROR_NOT_FOUND, + GST_RESOURCE_ERROR_BUSY, + GST_RESOURCE_ERROR_OPEN_READ, + GST_RESOURCE_ERROR_OPEN_WRITE, + GST_RESOURCE_ERROR_OPEN_READ_WRITE, + GST_RESOURCE_ERROR_CLOSE, + GST_RESOURCE_ERROR_READ, + GST_RESOURCE_ERROR_WRITE, + GST_RESOURCE_ERROR_SEEK, + GST_RESOURCE_ERROR_SYNC, + GST_RESOURCE_ERROR_SETTINGS, + GST_RESOURCE_ERROR_NO_SPACE_LEFT, + GST_RESOURCE_ERROR_NUM_ERRORS +} GstResourceError; + +/** + * GstStreamError: + * @GST_STREAM_ERROR_FAILED: a general error which doesn't fit in any other + * category. Make sure you add a custom message to the error call. + * @GST_STREAM_ERROR_TOO_LAZY: do not use this except as a placeholder for + * deciding where to go while developing code. + * @GST_STREAM_ERROR_NOT_IMPLEMENTED: use this when you do not want to implement + * this functionality yet. + * @GST_STREAM_ERROR_TYPE_NOT_FOUND: used when the element doesn't know the + * stream's type. + * @GST_STREAM_ERROR_WRONG_TYPE: used when the element doesn't handle this type + * of stream. + * @GST_STREAM_ERROR_CODEC_NOT_FOUND: used when there's no codec to handle the + * stream's type. + * @GST_STREAM_ERROR_DECODE: used when decoding fails. + * @GST_STREAM_ERROR_ENCODE: used when encoding fails. + * @GST_STREAM_ERROR_DEMUX: used when demuxing fails. + * @GST_STREAM_ERROR_MUX: used when muxing fails. + * @GST_STREAM_ERROR_FORMAT: used when the stream is of the wrong format + * (for example, wrong caps). + * @GST_STREAM_ERROR_DECRYPT: used when the stream is encrypted and can't be + * decrypted because this is not supported by the element. (Since: 0.10.20) + * @GST_STREAM_ERROR_DECRYPT_NOKEY: used when the stream is encrypted and + * can't be decrypted because no suitable key is available. (Since: 0.10.20) + * @GST_STREAM_ERROR_NUM_ERRORS: the number of stream error types. + * + * Stream errors are for anything related to the stream being processed: + * format errors, media type errors, ... + * They're typically used by decoders, demuxers, converters, ... + */ +typedef enum +{ + GST_STREAM_ERROR_FAILED = 1, + GST_STREAM_ERROR_TOO_LAZY, + GST_STREAM_ERROR_NOT_IMPLEMENTED, + GST_STREAM_ERROR_TYPE_NOT_FOUND, + GST_STREAM_ERROR_WRONG_TYPE, + GST_STREAM_ERROR_CODEC_NOT_FOUND, + GST_STREAM_ERROR_DECODE, + GST_STREAM_ERROR_ENCODE, + GST_STREAM_ERROR_DEMUX, + GST_STREAM_ERROR_MUX, + GST_STREAM_ERROR_FORMAT, + GST_STREAM_ERROR_DECRYPT, + GST_STREAM_ERROR_DECRYPT_NOKEY, + GST_STREAM_ERROR_NUM_ERRORS +} GstStreamError; + +#define GST_TYPE_G_ERROR (gst_g_error_get_type ()) + +/** + * GST_LIBRARY_ERROR: + * + * Error domain for library loading. Errors in this domain will + * be from the #GstLibraryError enumeration. + * See #GError for information on error domains. + */ +#define GST_LIBRARY_ERROR gst_library_error_quark () +/** + * GST_RESOURCE_ERROR: + * + * Error domain for resource handling. Errors in this domain will + * be from the #GstResourceError enumeration. + * See #GError for information on error domains. + */ +#define GST_RESOURCE_ERROR gst_resource_error_quark () +/** + * GST_CORE_ERROR: + * + * Error domain for core system. Errors in this domain will + * be from the #GstCoreError enumeration. + * See #GError for information on error domains. + */ +#define GST_CORE_ERROR gst_core_error_quark () +/** + * GST_STREAM_ERROR: + * + * Error domain for media stream processing. Errors in this domain will + * be from the #GstStreamError enumeration. + * See #GError for information on error domains. + */ +#define GST_STREAM_ERROR gst_stream_error_quark () + +/** + * GST_ERROR_SYSTEM: + * + * Builds a string using errno describing the previously failed system + * call. To be used as the debug argument in #GST_ELEMENT_ERROR. + */ +#define GST_ERROR_SYSTEM ("system error: %s", g_strerror (errno)) + +/* Hide this compatibility type from introspection */ +#ifndef __GI_SCANNER__ +GType gst_g_error_get_type (void); +#endif + +gchar *gst_error_get_message (GQuark domain, gint code); +GQuark gst_stream_error_quark (void); +GQuark gst_core_error_quark (void); +GQuark gst_resource_error_quark (void); +GQuark gst_library_error_quark (void); + +G_END_DECLS +#endif /* __GST_ERROR_H__ */ diff --git a/gst/gstevent.c b/gst/gstevent.c new file mode 100644 index 0000000..030b9d4 --- /dev/null +++ b/gst/gstevent.c @@ -0,0 +1,1311 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstevent.c: GstEvent subsystem + * + * 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:gstevent + * @short_description: Structure describing events that are passed up and down + * a pipeline + * @see_also: #GstPad, #GstElement + * + * The event class provides factory methods to construct events for sending + * and functions to query (parse) received events. + * + * Events are usually created with gst_event_new_*() which takes event-type + * specific parameters as arguments. + * To send an event application will usually use gst_element_send_event() and + * elements will use gst_pad_send_event() or gst_pad_push_event(). + * The event should be unreffed with gst_event_unref() if it has not been sent. + * + * Events that have been received can be parsed with their respective + * gst_event_parse_*() functions. It is valid to pass %NULL for unwanted details. + * + * Events are passed between elements in parallel to the data stream. Some events + * are serialized with buffers, others are not. Some events only travel downstream, + * others only upstream. Some events can travel both upstream and downstream. + * + * The events are used to signal special conditions in the datastream such as + * EOS (end of stream) or the start of a new stream-segment. + * Events are also used to flush the pipeline of any pending data. + * + * Most of the event API is used inside plugins. Applications usually only + * construct and use seek events. + * To do that gst_event_new_seek() is used to create a seek event. It takes + * the needed parameters to specify seeking time and mode. + * <example> + * <title>performing a seek on a pipeline</title> + * <programlisting> + * GstEvent *event; + * gboolean result; + * ... + * // construct a seek event to play the media from second 2 to 5, flush + * // the pipeline to decrease latency. + * event = gst_event_new_seek (1.0, + * GST_FORMAT_TIME, + * GST_SEEK_FLAG_FLUSH, + * GST_SEEK_TYPE_SET, 2 * GST_SECOND, + * GST_SEEK_TYPE_SET, 5 * GST_SECOND); + * ... + * result = gst_element_send_event (pipeline, event); + * if (!result) + * g_warning ("seek failed"); + * ... + * </programlisting> + * </example> + * + * Last reviewed on 2006-09-6 (0.10.10) + */ + + +#include "gst_private.h" +#include <string.h> /* memcpy */ + +#include "gstinfo.h" +#include "gstevent.h" +#include "gstenumtypes.h" +#include "gstutils.h" +#include "gstquark.h" + +GType _gst_event_type = 0; + +typedef struct +{ + GstEvent event; + + GstStructure *structure; +} GstEventImpl; + +#define GST_EVENT_STRUCTURE(e) (((GstEventImpl *)(e))->structure) + +typedef struct +{ + const gint type; + const gchar *name; + GQuark quark; +} GstEventQuarks; + +static GstEventQuarks event_quarks[] = { + {GST_EVENT_UNKNOWN, "unknown", 0}, + {GST_EVENT_FLUSH_START, "flush-start", 0}, + {GST_EVENT_FLUSH_STOP, "flush-stop", 0}, + {GST_EVENT_EOS, "eos", 0}, + {GST_EVENT_CAPS, "caps", 0}, + {GST_EVENT_SEGMENT, "segment", 0}, + {GST_EVENT_TAG, "tag", 0}, + {GST_EVENT_BUFFERSIZE, "buffersize", 0}, + {GST_EVENT_SINK_MESSAGE, "sink-message", 0}, + {GST_EVENT_QOS, "qos", 0}, + {GST_EVENT_SEEK, "seek", 0}, + {GST_EVENT_NAVIGATION, "navigation", 0}, + {GST_EVENT_LATENCY, "latency", 0}, + {GST_EVENT_STEP, "step", 0}, + {GST_EVENT_RECONFIGURE, "reconfigure", 0}, + {GST_EVENT_CUSTOM_UPSTREAM, "custom-upstream", 0}, + {GST_EVENT_CUSTOM_DOWNSTREAM, "custom-downstream", 0}, + {GST_EVENT_CUSTOM_DOWNSTREAM_OOB, "custom-downstream-oob", 0}, + {GST_EVENT_CUSTOM_BOTH, "custom-both", 0}, + {GST_EVENT_CUSTOM_BOTH_OOB, "custom-both-oob", 0}, + + {0, NULL, 0} +}; + +GST_DEFINE_MINI_OBJECT_TYPE (GstEvent, gst_event); + +void +_priv_gst_event_initialize (void) +{ + gint i; + + _gst_event_type = gst_event_get_type (); + + g_type_class_ref (gst_seek_flags_get_type ()); + g_type_class_ref (gst_seek_type_get_type ()); + + for (i = 0; event_quarks[i].name; i++) { + event_quarks[i].quark = g_quark_from_static_string (event_quarks[i].name); + } +} + +/** + * gst_event_type_get_name: + * @type: the event type + * + * Get a printable name for the given event type. Do not modify or free. + * + * Returns: a reference to the static name of the event. + */ +const gchar * +gst_event_type_get_name (GstEventType type) +{ + gint i; + + for (i = 0; event_quarks[i].name; i++) { + if (type == event_quarks[i].type) + return event_quarks[i].name; + } + return "unknown"; +} + +/** + * gst_event_type_to_quark: + * @type: the event type + * + * Get the unique quark for the given event type. + * + * Returns: the quark associated with the event type + */ +GQuark +gst_event_type_to_quark (GstEventType type) +{ + gint i; + + for (i = 0; event_quarks[i].name; i++) { + if (type == event_quarks[i].type) + return event_quarks[i].quark; + } + return 0; +} + +/** + * gst_event_type_get_flags: + * @type: a #GstEventType + * + * Gets the #GstEventTypeFlags associated with @type. + * + * Returns: a #GstEventTypeFlags. + */ +GstEventTypeFlags +gst_event_type_get_flags (GstEventType type) +{ + GstEventTypeFlags ret; + + ret = type & ((1 << GST_EVENT_STICKY_SHIFT) - 1); + + return ret; +} + +static void +_gst_event_free (GstEvent * event) +{ + GstStructure *s; + + g_return_if_fail (event != NULL); + g_return_if_fail (GST_IS_EVENT (event)); + + GST_CAT_LOG (GST_CAT_EVENT, "freeing event %p type %s", event, + GST_EVENT_TYPE_NAME (event)); + + s = GST_EVENT_STRUCTURE (event); + + if (s) { + gst_structure_set_parent_refcount (s, NULL); + gst_structure_free (s); + } + + g_slice_free1 (GST_MINI_OBJECT_SIZE (event), event); +} + +static void gst_event_init (GstEventImpl * event, gsize size, + GstEventType type); + +static GstEvent * +_gst_event_copy (GstEvent * event) +{ + GstEventImpl *copy; + GstStructure *s; + + copy = g_slice_new0 (GstEventImpl); + + gst_event_init (copy, sizeof (GstEventImpl), GST_EVENT_TYPE (event)); + + GST_EVENT_TIMESTAMP (copy) = GST_EVENT_TIMESTAMP (event); + GST_EVENT_SEQNUM (copy) = GST_EVENT_SEQNUM (event); + + s = GST_EVENT_STRUCTURE (event); + if (s) { + GST_EVENT_STRUCTURE (copy) = gst_structure_copy (s); + gst_structure_set_parent_refcount (GST_EVENT_STRUCTURE (copy), + ©->event.mini_object.refcount); + } + return GST_EVENT_CAST (copy); +} + +static void +gst_event_init (GstEventImpl * event, gsize size, GstEventType type) +{ + gst_mini_object_init (GST_MINI_OBJECT_CAST (event), _gst_event_type, size); + + event->event.mini_object.copy = (GstMiniObjectCopyFunction) _gst_event_copy; + event->event.mini_object.free = (GstMiniObjectFreeFunction) _gst_event_free; + + GST_EVENT_TYPE (event) = type; + GST_EVENT_TIMESTAMP (event) = GST_CLOCK_TIME_NONE; + GST_EVENT_SEQNUM (event) = gst_util_seqnum_next (); +} + +static GstEvent * +gst_event_new (GstEventType type) +{ + GstEventImpl *event; + + event = g_slice_new0 (GstEventImpl); + + GST_CAT_DEBUG (GST_CAT_EVENT, "creating new event %p %s %d", event, + gst_event_type_get_name (type), type); + + gst_event_init (event, sizeof (GstEventImpl), type); + + return GST_EVENT_CAST (event); +} + +/** + * gst_event_new_custom: + * @type: The type of the new event + * @structure: (transfer full): the structure for the event. The event will + * take ownership of the structure. + * + * Create a new custom-typed event. This can be used for anything not + * handled by other event-specific functions to pass an event to another + * element. + * + * Make sure to allocate an event type with the #GST_EVENT_MAKE_TYPE macro, + * assigning a free number and filling in the correct direction and + * serialization flags. + * + * New custom events can also be created by subclassing the event type if + * needed. + * + * Returns: (transfer full): the new custom event. + */ +GstEvent * +gst_event_new_custom (GstEventType type, GstStructure * structure) +{ + GstEvent *event; + + /* structure must not have a parent */ + event = gst_event_new (type); + if (structure) { + if (!gst_structure_set_parent_refcount (structure, + &event->mini_object.refcount)) + goto had_parent; + + GST_EVENT_STRUCTURE (event) = structure; + } + return event; + + /* ERRORS */ +had_parent: + { + gst_event_unref (event); + g_warning ("structure is already owned by another object"); + return NULL; + } +} + +/** + * gst_event_get_structure: + * @event: The #GstEvent. + * + * Access the structure of the event. + * + * Returns: The structure of the event. The structure is still + * owned by the event, which means that you should not free it and + * that the pointer becomes invalid when you free the event. + * + * MT safe. + */ +const GstStructure * +gst_event_get_structure (GstEvent * event) +{ + g_return_val_if_fail (GST_IS_EVENT (event), NULL); + + return GST_EVENT_STRUCTURE (event); +} + +/** + * gst_event_writable_structure: + * @event: The #GstEvent. + * + * Get a writable version of the structure. + * + * Returns: The structure of the event. The structure is still + * owned by the event, which means that you should not free it and + * that the pointer becomes invalid when you free the event. + * This function checks if @event is writable and will never return NULL. + * + * MT safe. + */ +GstStructure * +gst_event_writable_structure (GstEvent * event) +{ + GstStructure *structure; + + g_return_val_if_fail (GST_IS_EVENT (event), NULL); + g_return_val_if_fail (gst_event_is_writable (event), NULL); + + structure = GST_EVENT_STRUCTURE (event); + + if (structure == NULL) { + structure = + gst_structure_id_empty_new (gst_event_type_to_quark (GST_EVENT_TYPE + (event))); + gst_structure_set_parent_refcount (structure, &event->mini_object.refcount); + GST_EVENT_STRUCTURE (event) = structure; + } + return structure; +} + +/** + * gst_event_has_name: + * @event: The #GstEvent. + * @name: name to check + * + * Checks if @event has the given @name. This function is usually used to + * check the name of a custom event. + * + * Returns: %TRUE if @name matches the name of the event structure. + * + * Since: 0.10.20 + */ +gboolean +gst_event_has_name (GstEvent * event, const gchar * name) +{ + g_return_val_if_fail (GST_IS_EVENT (event), FALSE); + + if (GST_EVENT_STRUCTURE (event) == NULL) + return FALSE; + + return gst_structure_has_name (GST_EVENT_STRUCTURE (event), name); +} + +/** + * gst_event_get_seqnum: + * @event: A #GstEvent. + * + * Retrieve the sequence number of a event. + * + * Events have ever-incrementing sequence numbers, which may also be set + * explicitly via gst_event_set_seqnum(). Sequence numbers are typically used to + * indicate that a event corresponds to some other set of events or messages, + * for example an EOS event corresponding to a SEEK event. It is considered good + * practice to make this correspondence when possible, though it is not + * required. + * + * Note that events and messages share the same sequence number incrementor; + * two events or messages will never have the same sequence number unless + * that correspondence was made explicitly. + * + * Returns: The event's sequence number. + * + * MT safe. + * + * Since: 0.10.22 + */ +guint32 +gst_event_get_seqnum (GstEvent * event) +{ + g_return_val_if_fail (GST_IS_EVENT (event), -1); + + return GST_EVENT_SEQNUM (event); +} + +/** + * gst_event_set_seqnum: + * @event: A #GstEvent. + * @seqnum: A sequence number. + * + * Set the sequence number of a event. + * + * This function might be called by the creator of a event to indicate that the + * event relates to other events or messages. See gst_event_get_seqnum() for + * more information. + * + * MT safe. + * + * Since: 0.10.22 + */ +void +gst_event_set_seqnum (GstEvent * event, guint32 seqnum) +{ + g_return_if_fail (GST_IS_EVENT (event)); + + GST_EVENT_SEQNUM (event) = seqnum; +} + +/* FIXME 0.11: It would be nice to have flush events + * that don't reset the running time in the sinks + */ + +/** + * gst_event_new_flush_start: + * + * Allocate a new flush start event. The flush start event can be sent + * upstream and downstream and travels out-of-bounds with the dataflow. + * + * It marks pads as being flushing and will make them return + * #GST_FLOW_WRONG_STATE when used for data flow with gst_pad_push(), + * gst_pad_chain(), gst_pad_alloc_buffer(), gst_pad_get_range() and + * gst_pad_pull_range(). Any event (except a #GST_EVENT_FLUSH_STOP) received + * on a flushing pad will return %FALSE immediately. + * + * Elements should unlock any blocking functions and exit their streaming + * functions as fast as possible when this event is received. + * + * This event is typically generated after a seek to flush out all queued data + * in the pipeline so that the new media is played as soon as possible. + * + * Returns: (transfer full): a new flush start event. + */ +GstEvent * +gst_event_new_flush_start (void) +{ + return gst_event_new (GST_EVENT_FLUSH_START); +} + +/** + * gst_event_new_flush_stop: + * @reset_time: if time should be reset + * + * Allocate a new flush stop event. The flush stop event can be sent + * upstream and downstream and travels serialized with the dataflow. + * It is typically sent after sending a FLUSH_START event to make the + * pads accept data again. + * + * Elements can process this event synchronized with the dataflow since + * the preceeding FLUSH_START event stopped the dataflow. + * + * This event is typically generated to complete a seek and to resume + * dataflow. + * + * Returns: (transfer full): a new flush stop event. + */ +GstEvent * +gst_event_new_flush_stop (gboolean reset_time) +{ + GstEvent *event; + + GST_CAT_INFO (GST_CAT_EVENT, "creating flush stop %d", reset_time); + + event = gst_event_new_custom (GST_EVENT_FLUSH_STOP, + gst_structure_id_new (GST_QUARK (EVENT_FLUSH_STOP), + GST_QUARK (RESET_TIME), G_TYPE_BOOLEAN, reset_time, NULL)); + + return event; +} + +/** + * gst_event_parse_flush_stop: + * @event: The event to parse + * @reset_time: (out): if time should be reset + * + * Parse the FLUSH_STOP event and retrieve the @reset_time member. + */ +void +gst_event_parse_flush_stop (GstEvent * event, gboolean * reset_time) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP); + + structure = GST_EVENT_STRUCTURE (event); + if (G_LIKELY (reset_time)) + *reset_time = + g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (RESET_TIME))); +} + +/** + * gst_event_new_eos: + * + * Create a new EOS event. The eos event can only travel downstream + * synchronized with the buffer flow. Elements that receive the EOS + * event on a pad can return #GST_FLOW_UNEXPECTED as a #GstFlowReturn + * when data after the EOS event arrives. + * + * The EOS event will travel down to the sink elements in the pipeline + * which will then post the #GST_MESSAGE_EOS on the bus after they have + * finished playing any buffered data. + * + * When all sinks have posted an EOS message, an EOS message is + * forwarded to the application. + * + * The EOS event itself will not cause any state transitions of the pipeline. + * + * Returns: (transfer full): the new EOS event. + */ +GstEvent * +gst_event_new_eos (void) +{ + return gst_event_new (GST_EVENT_EOS); +} + +/** + * gst_event_new_caps: + * @caps: a #GstCaps + * + * Create a new CAPS event for @caps. The caps event can only travel downstream + * synchronized with the buffer flow and contains the format of the buffers + * that will follow after the event. + * + * Returns: (transfer full): the new CAPS event. + */ +GstEvent * +gst_event_new_caps (GstCaps * caps) +{ + GstEvent *event; + + g_return_val_if_fail (caps != NULL, NULL); + g_return_val_if_fail (gst_caps_is_fixed (caps), NULL); + + GST_CAT_INFO (GST_CAT_EVENT, "creating caps event %" GST_PTR_FORMAT, caps); + + event = gst_event_new_custom (GST_EVENT_CAPS, + gst_structure_id_new (GST_QUARK (EVENT_CAPS), + GST_QUARK (CAPS), GST_TYPE_CAPS, caps, NULL)); + + return event; +} + +/** + * gst_event_parse_caps: + * @event: The event to parse + * @caps: (out): A pointer to the caps + * + * Get the caps from @event. The caps remains valid as long as @event remains + * valid. + */ +void +gst_event_parse_caps (GstEvent * event, GstCaps ** caps) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_CAPS); + + structure = GST_EVENT_STRUCTURE (event); + if (G_LIKELY (caps)) + *caps = + g_value_get_boxed (gst_structure_id_get_value (structure, + GST_QUARK (CAPS))); +} + +/** + * gst_event_new_segment: + * @segment: a #GstSegment + * + * Create a new SEGMENT event for @segment. The segment event can only travel + * downstream synchronized with the buffer flow and contains timing information + * and playback properties for the buffers that will follow. + * + * The newsegment event marks the range of buffers to be processed. All + * data not within the segment range is not to be processed. This can be + * used intelligently by plugins to apply more efficient methods of skipping + * unneeded data. The valid range is expressed with the @start and @stop + * values. + * + * The time value of the segment is used in conjunction with the start + * value to convert the buffer timestamps into the stream time. This is + * usually done in sinks to report the current stream_time. + * @time represents the stream_time of a buffer carrying a timestamp of + * @start. @time cannot be -1. + * + * @start cannot be -1, @stop can be -1. If there + * is a valid @stop given, it must be greater or equal the @start, including + * when the indicated playback @rate is < 0. + * + * The @applied_rate value provides information about any rate adjustment that + * has already been made to the timestamps and content on the buffers of the + * stream. (@rate * @applied_rate) should always equal the rate that has been + * requested for playback. For example, if an element has an input segment + * with intended playback @rate of 2.0 and applied_rate of 1.0, it can adjust + * incoming timestamps and buffer content by half and output a newsegment event + * with @rate of 1.0 and @applied_rate of 2.0 + * + * After a newsegment event, the buffer stream time is calculated with: + * + * time + (TIMESTAMP(buf) - start) * ABS (rate * applied_rate) + * + * Returns: (transfer full): the new SEGMENT event. + */ +GstEvent * +gst_event_new_segment (GstSegment * segment) +{ + GstEvent *event; + + g_return_val_if_fail (segment != NULL, NULL); + + GST_CAT_INFO (GST_CAT_EVENT, "creating segment event %" GST_SEGMENT_FORMAT, + segment); + + event = gst_event_new_custom (GST_EVENT_SEGMENT, + gst_structure_id_new (GST_QUARK (EVENT_SEGMENT), + GST_QUARK (SEGMENT), GST_TYPE_SEGMENT, segment, NULL)); + + return event; +} + +/** + * gst_event_parse_segment: + * @event: The event to parse + * @segment: (out) (transfer none): a pointer to a #GstSegment + * + * Parses a segment @event and stores the result in the given @segment location. + * @segment remains valid only until the @event is freed. Don't modify the segment + * and make a copy if you want to modify it or store it for later use. + */ +void +gst_event_parse_segment (GstEvent * event, const GstSegment ** segment) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT); + + if (segment) { + structure = GST_EVENT_STRUCTURE (event); + *segment = g_value_get_boxed (gst_structure_id_get_value (structure, + GST_QUARK (SEGMENT))); + } +} + +/** + * gst_event_copy_segment: + * @event: The event to parse + * @segment: a pointer to a #GstSegment + * + * Parses a segment @event and copies the #GstSegment into the location + * given by @segment. + */ +void +gst_event_copy_segment (GstEvent * event, GstSegment * segment) +{ + const GstSegment *src; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT); + + if (segment) { + gst_event_parse_segment (event, &src); + gst_segment_copy_into (src, segment); + } +} + +/** + * gst_event_new_tag: + * @taglist: (transfer full): metadata list. The event will take ownership + * of the taglist. + * + * Generates a metadata tag event from the given @taglist. + * + * Returns: (transfer full): a new #GstEvent + */ +GstEvent * +gst_event_new_tag (GstTagList * taglist) +{ + g_return_val_if_fail (taglist != NULL, NULL); + + return gst_event_new_custom (GST_EVENT_TAG, (GstStructure *) taglist); +} + +/** + * gst_event_parse_tag: + * @event: a tag event + * @taglist: (out) (transfer none): pointer to metadata list + * + * Parses a tag @event and stores the results in the given @taglist location. + * No reference to the taglist will be returned, it remains valid only until + * the @event is freed. Don't modify or free the taglist, make a copy if you + * want to modify it or store it for later use. + */ +void +gst_event_parse_tag (GstEvent * event, GstTagList ** taglist) +{ + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_TAG); + + if (taglist) + *taglist = (GstTagList *) GST_EVENT_STRUCTURE (event); +} + +/* buffersize event */ +/** + * gst_event_new_buffer_size: + * @format: buffer format + * @minsize: minimum buffer size + * @maxsize: maximum buffer size + * @async: thread behavior + * + * Create a new buffersize event. The event is sent downstream and notifies + * elements that they should provide a buffer of the specified dimensions. + * + * When the @async flag is set, a thread boundary is preferred. + * + * Returns: (transfer full): a new #GstEvent + */ +GstEvent * +gst_event_new_buffer_size (GstFormat format, gint64 minsize, + gint64 maxsize, gboolean async) +{ + GstEvent *event; + GstStructure *structure; + + GST_CAT_INFO (GST_CAT_EVENT, + "creating buffersize format %s, minsize %" G_GINT64_FORMAT + ", maxsize %" G_GINT64_FORMAT ", async %d", gst_format_get_name (format), + minsize, maxsize, async); + + structure = gst_structure_id_new (GST_QUARK (EVENT_BUFFER_SIZE), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (MINSIZE), G_TYPE_INT64, minsize, + GST_QUARK (MAXSIZE), G_TYPE_INT64, maxsize, + GST_QUARK (ASYNC), G_TYPE_BOOLEAN, async, NULL); + event = gst_event_new_custom (GST_EVENT_BUFFERSIZE, structure); + + return event; +} + +/** + * gst_event_parse_buffer_size: + * @event: The event to query + * @format: (out): A pointer to store the format in + * @minsize: (out): A pointer to store the minsize in + * @maxsize: (out): A pointer to store the maxsize in + * @async: (out): A pointer to store the async-flag in + * + * Get the format, minsize, maxsize and async-flag in the buffersize event. + */ +void +gst_event_parse_buffer_size (GstEvent * event, GstFormat * format, + gint64 * minsize, gint64 * maxsize, gboolean * async) +{ + const GstStructure *structure; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_BUFFERSIZE); + + structure = GST_EVENT_STRUCTURE (event); + if (format) + *format = (GstFormat) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (minsize) + *minsize = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (MINSIZE))); + if (maxsize) + *maxsize = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (MAXSIZE))); + if (async) + *async = + g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (ASYNC))); +} + +/** + * gst_event_new_qos: + * @type: the QoS type + * @proportion: the proportion of the qos message + * @diff: The time difference of the last Clock sync + * @timestamp: The timestamp of the buffer + * + * Allocate a new qos event with the given values. + * The QOS event is generated in an element that wants an upstream + * element to either reduce or increase its rate because of + * high/low CPU load or other resource usage such as network performance or + * throttling. Typically sinks generate these events for each buffer + * they receive. + * + * @type indicates the reason for the QoS event. #GST_QOS_TYPE_OVERFLOW is + * used when a buffer arrived in time or when the sink cannot keep up with + * the upstream datarate. #GST_QOS_TYPE_UNDERFLOW is when the sink is not + * receiving buffers fast enough and thus has to drop late buffers. + * #GST_QOS_TYPE_THROTTLE is used when the datarate is artificially limited + * by the application, for example to reduce power consumption. + * + * @proportion indicates the real-time performance of the streaming in the + * element that generated the QoS event (usually the sink). The value is + * generally computed based on more long term statistics about the streams + * timestamps compared to the clock. + * A value < 1.0 indicates that the upstream element is producing data faster + * than real-time. A value > 1.0 indicates that the upstream element is not + * producing data fast enough. 1.0 is the ideal @proportion value. The + * proportion value can safely be used to lower or increase the quality of + * the element. + * + * @diff is the difference against the clock in running time of the last + * buffer that caused the element to generate the QOS event. A negative value + * means that the buffer with @timestamp arrived in time. A positive value + * indicates how late the buffer with @timestamp was. When throttling is + * enabled, @diff will be set to the requested throttling interval. + * + * @timestamp is the timestamp of the last buffer that cause the element + * to generate the QOS event. It is expressed in running time and thus an ever + * increasing value. + * + * The upstream element can use the @diff and @timestamp values to decide + * whether to process more buffers. For possitive @diff, all buffers with + * timestamp <= @timestamp + @diff will certainly arrive late in the sink + * as well. A (negative) @diff value so that @timestamp + @diff would yield a + * result smaller than 0 is not allowed. + * + * The application can use general event probes to intercept the QoS + * event and implement custom application specific QoS handling. + * + * Returns: (transfer full): a new QOS event. + */ +GstEvent * +gst_event_new_qos (GstQOSType type, gdouble proportion, + GstClockTimeDiff diff, GstClockTime timestamp) +{ + GstEvent *event; + GstStructure *structure; + + /* diff must be positive or timestamp + diff must be positive */ + g_return_val_if_fail (diff >= 0 || -diff <= timestamp, NULL); + + GST_CAT_INFO (GST_CAT_EVENT, + "creating qos type %d, proportion %lf, diff %" G_GINT64_FORMAT + ", timestamp %" GST_TIME_FORMAT, type, proportion, + diff, GST_TIME_ARGS (timestamp)); + + structure = gst_structure_id_new (GST_QUARK (EVENT_QOS), + GST_QUARK (TYPE), GST_TYPE_QOS_TYPE, type, + GST_QUARK (PROPORTION), G_TYPE_DOUBLE, proportion, + GST_QUARK (DIFF), G_TYPE_INT64, diff, + GST_QUARK (TIMESTAMP), G_TYPE_UINT64, timestamp, NULL); + event = gst_event_new_custom (GST_EVENT_QOS, structure); + + return event; +} + +/** + * gst_event_parse_qos: + * @event: The event to query + * @type: (out): A pointer to store the QoS type in + * @proportion: (out): A pointer to store the proportion in + * @diff: (out): A pointer to store the diff in + * @timestamp: (out): A pointer to store the timestamp in + * + * Get the type, proportion, diff and timestamp in the qos event. See + * gst_event_new_qos() for more information about the different QoS values. + */ +void +gst_event_parse_qos (GstEvent * event, GstQOSType * type, + gdouble * proportion, GstClockTimeDiff * diff, GstClockTime * timestamp) +{ + const GstStructure *structure; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_QOS); + + structure = GST_EVENT_STRUCTURE (event); + if (type) + *type = (GstQOSType) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (TYPE))); + if (proportion) + *proportion = + g_value_get_double (gst_structure_id_get_value (structure, + GST_QUARK (PROPORTION))); + if (diff) + *diff = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (DIFF))); + if (timestamp) + *timestamp = + g_value_get_uint64 (gst_structure_id_get_value (structure, + GST_QUARK (TIMESTAMP))); +} + +/** + * gst_event_new_seek: + * @rate: The new playback rate + * @format: The format of the seek values + * @flags: The optional seek flags + * @start_type: The type and flags for the new start position + * @start: The value of the new start position + * @stop_type: The type and flags for the new stop position + * @stop: The value of the new stop position + * + * Allocate a new seek event with the given parameters. + * + * The seek event configures playback of the pipeline between @start to @stop + * at the speed given in @rate, also called a playback segment. + * The @start and @stop values are expressed in @format. + * + * A @rate of 1.0 means normal playback rate, 2.0 means double speed. + * Negatives values means backwards playback. A value of 0.0 for the + * rate is not allowed and should be accomplished instead by PAUSING the + * pipeline. + * + * A pipeline has a default playback segment configured with a start + * position of 0, a stop position of -1 and a rate of 1.0. The currently + * configured playback segment can be queried with #GST_QUERY_SEGMENT. + * + * @start_type and @stop_type specify how to adjust the currently configured + * start and stop fields in playback segment. Adjustments can be made relative + * or absolute to the last configured values. A type of #GST_SEEK_TYPE_NONE + * means that the position should not be updated. + * + * When the rate is positive and @start has been updated, playback will start + * from the newly configured start position. + * + * For negative rates, playback will start from the newly configured stop + * position (if any). If the stop position if updated, it must be different from + * -1 for negative rates. + * + * It is not possible to seek relative to the current playback position, to do + * this, PAUSE the pipeline, query the current playback position with + * #GST_QUERY_POSITION and update the playback segment current position with a + * #GST_SEEK_TYPE_SET to the desired position. + * + * Returns: (transfer full): a new seek event. + */ +GstEvent * +gst_event_new_seek (gdouble rate, GstFormat format, GstSeekFlags flags, + GstSeekType start_type, gint64 start, GstSeekType stop_type, gint64 stop) +{ + GstEvent *event; + GstStructure *structure; + + g_return_val_if_fail (rate != 0.0, NULL); + + if (format == GST_FORMAT_TIME) { + GST_CAT_INFO (GST_CAT_EVENT, + "creating seek rate %lf, format TIME, flags %d, " + "start_type %d, start %" GST_TIME_FORMAT ", " + "stop_type %d, stop %" GST_TIME_FORMAT, + rate, flags, start_type, GST_TIME_ARGS (start), + stop_type, GST_TIME_ARGS (stop)); + } else { + GST_CAT_INFO (GST_CAT_EVENT, + "creating seek rate %lf, format %s, flags %d, " + "start_type %d, start %" G_GINT64_FORMAT ", " + "stop_type %d, stop %" G_GINT64_FORMAT, + rate, gst_format_get_name (format), flags, start_type, start, stop_type, + stop); + } + + structure = gst_structure_id_new (GST_QUARK (EVENT_SEEK), + GST_QUARK (RATE), G_TYPE_DOUBLE, rate, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (FLAGS), GST_TYPE_SEEK_FLAGS, flags, + GST_QUARK (CUR_TYPE), GST_TYPE_SEEK_TYPE, start_type, + GST_QUARK (CUR), G_TYPE_INT64, start, + GST_QUARK (STOP_TYPE), GST_TYPE_SEEK_TYPE, stop_type, + GST_QUARK (STOP), G_TYPE_INT64, stop, NULL); + event = gst_event_new_custom (GST_EVENT_SEEK, structure); + + return event; +} + +/** + * gst_event_parse_seek: + * @event: a seek event + * @rate: (out): result location for the rate + * @format: (out): result location for the stream format + * @flags: (out): result location for the #GstSeekFlags + * @start_type: (out): result location for the #GstSeekType of the start position + * @start: (out): result location for the start postion expressed in @format + * @stop_type: (out): result location for the #GstSeekType of the stop position + * @stop: (out): result location for the stop postion expressed in @format + * + * Parses a seek @event and stores the results in the given result locations. + */ +void +gst_event_parse_seek (GstEvent * event, gdouble * rate, + GstFormat * format, GstSeekFlags * flags, GstSeekType * start_type, + gint64 * start, GstSeekType * stop_type, gint64 * stop) +{ + const GstStructure *structure; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_SEEK); + + structure = GST_EVENT_STRUCTURE (event); + if (rate) + *rate = + g_value_get_double (gst_structure_id_get_value (structure, + GST_QUARK (RATE))); + if (format) + *format = (GstFormat) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (flags) + *flags = (GstSeekFlags) + g_value_get_flags (gst_structure_id_get_value (structure, + GST_QUARK (FLAGS))); + if (start_type) + *start_type = (GstSeekType) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (CUR_TYPE))); + if (start) + *start = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (CUR))); + if (stop_type) + *stop_type = (GstSeekType) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (STOP_TYPE))); + if (stop) + *stop = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (STOP))); +} + +/** + * gst_event_new_navigation: + * @structure: (transfer full): description of the event. The event will take + * ownership of the structure. + * + * Create a new navigation event from the given description. + * + * Returns: (transfer full): a new #GstEvent + */ +GstEvent * +gst_event_new_navigation (GstStructure * structure) +{ + g_return_val_if_fail (structure != NULL, NULL); + + return gst_event_new_custom (GST_EVENT_NAVIGATION, structure); +} + +/** + * gst_event_new_latency: + * @latency: the new latency value + * + * Create a new latency event. The event is sent upstream from the sinks and + * notifies elements that they should add an additional @latency to the + * running time before synchronising against the clock. + * + * The latency is mostly used in live sinks and is always expressed in + * the time format. + * + * Returns: (transfer full): a new #GstEvent + * + * Since: 0.10.12 + */ +GstEvent * +gst_event_new_latency (GstClockTime latency) +{ + GstEvent *event; + GstStructure *structure; + + GST_CAT_INFO (GST_CAT_EVENT, + "creating latency event %" GST_TIME_FORMAT, GST_TIME_ARGS (latency)); + + structure = gst_structure_id_new (GST_QUARK (EVENT_LATENCY), + GST_QUARK (LATENCY), G_TYPE_UINT64, latency, NULL); + event = gst_event_new_custom (GST_EVENT_LATENCY, structure); + + return event; +} + +/** + * gst_event_parse_latency: + * @event: The event to query + * @latency: (out): A pointer to store the latency in. + * + * Get the latency in the latency event. + * + * Since: 0.10.12 + */ +void +gst_event_parse_latency (GstEvent * event, GstClockTime * latency) +{ + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_LATENCY); + + if (latency) + *latency = + g_value_get_uint64 (gst_structure_id_get_value (GST_EVENT_STRUCTURE + (event), GST_QUARK (LATENCY))); +} + +/** + * gst_event_new_step: + * @format: the format of @amount + * @amount: the amount of data to step + * @rate: the step rate + * @flush: flushing steps + * @intermediate: intermediate steps + * + * Create a new step event. The purpose of the step event is to instruct a sink + * to skip @amount (expressed in @format) of media. It can be used to implement + * stepping through the video frame by frame or for doing fast trick modes. + * + * A rate of <= 0.0 is not allowed, pause the pipeline or reverse the playback + * direction of the pipeline to get the same effect. + * + * The @flush flag will clear any pending data in the pipeline before starting + * the step operation. + * + * The @intermediate flag instructs the pipeline that this step operation is + * part of a larger step operation. + * + * Returns: (transfer full): a new #GstEvent + * + * Since: 0.10.24 + */ +GstEvent * +gst_event_new_step (GstFormat format, guint64 amount, gdouble rate, + gboolean flush, gboolean intermediate) +{ + GstEvent *event; + GstStructure *structure; + + g_return_val_if_fail (rate > 0.0, NULL); + + GST_CAT_INFO (GST_CAT_EVENT, "creating step event"); + + structure = gst_structure_id_new (GST_QUARK (EVENT_STEP), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (AMOUNT), G_TYPE_UINT64, amount, + GST_QUARK (RATE), G_TYPE_DOUBLE, rate, + GST_QUARK (FLUSH), G_TYPE_BOOLEAN, flush, + GST_QUARK (INTERMEDIATE), G_TYPE_BOOLEAN, intermediate, NULL); + event = gst_event_new_custom (GST_EVENT_STEP, structure); + + return event; +} + +/** + * gst_event_parse_step: + * @event: The event to query + * @format: (out) (allow-none): a pointer to store the format in + * @amount: (out) (allow-none): a pointer to store the amount in + * @rate: (out) (allow-none): a pointer to store the rate in + * @flush: (out) (allow-none): a pointer to store the flush boolean in + * @intermediate: (out) (allow-none): a pointer to store the intermediate + * boolean in + * + * Parse the step event. + * + * Since: 0.10.24 + */ +void +gst_event_parse_step (GstEvent * event, GstFormat * format, guint64 * amount, + gdouble * rate, gboolean * flush, gboolean * intermediate) +{ + const GstStructure *structure; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_STEP); + + structure = GST_EVENT_STRUCTURE (event); + if (format) + *format = + (GstFormat) g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (amount) + *amount = g_value_get_uint64 (gst_structure_id_get_value (structure, + GST_QUARK (AMOUNT))); + if (rate) + *rate = g_value_get_double (gst_structure_id_get_value (structure, + GST_QUARK (RATE))); + if (flush) + *flush = g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (FLUSH))); + if (intermediate) + *intermediate = g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (INTERMEDIATE))); +} + +/** + * gst_event_new_reconfigure: + + * Create a new reconfigure event. The purpose of the reconfingure event is + * to travel upstream and make elements renegotiate their caps or reconfigure + * their buffer pools. This is useful when changing properties on elements + * or changing the topology of the pipeline. + * + * Returns: (transfer full): a new #GstEvent + * + * Since: 0.11.0 + */ +GstEvent * +gst_event_new_reconfigure (void) +{ + GstEvent *event; + + GST_CAT_INFO (GST_CAT_EVENT, "creating reconfigure event"); + + event = gst_event_new_custom (GST_EVENT_RECONFIGURE, NULL); + + return event; +} + +/** + * gst_event_new_sink_message: + * @msg: (transfer none): the #GstMessage to be posted + * + * Create a new sink-message event. The purpose of the sink-message event is + * to instruct a sink to post the message contained in the event synchronized + * with the stream. + * + * Returns: (transfer full): a new #GstEvent + * + * Since: 0.10.26 + */ +/* FIXME 0.11: take ownership of msg for consistency? */ +GstEvent * +gst_event_new_sink_message (GstMessage * msg) +{ + GstEvent *event; + GstStructure *structure; + + g_return_val_if_fail (msg != NULL, NULL); + + GST_CAT_INFO (GST_CAT_EVENT, "creating sink-message event"); + + structure = gst_structure_id_new (GST_QUARK (EVENT_SINK_MESSAGE), + GST_QUARK (MESSAGE), GST_TYPE_MESSAGE, msg, NULL); + event = gst_event_new_custom (GST_EVENT_SINK_MESSAGE, structure); + + return event; +} + +/** + * gst_event_parse_sink_message: + * @event: The event to query + * @msg: (out) (transfer full): a pointer to store the #GstMessage in. + * + * Parse the sink-message event. Unref @msg after usage. + * + * Since: 0.10.26 + */ +void +gst_event_parse_sink_message (GstEvent * event, GstMessage ** msg) +{ + const GstStructure *structure; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_SINK_MESSAGE); + + structure = GST_EVENT_STRUCTURE (event); + if (msg) + *msg = + GST_MESSAGE (g_value_dup_boxed (gst_structure_id_get_value + (structure, GST_QUARK (MESSAGE)))); +} diff --git a/gst/gstevent.h b/gst/gstevent.h new file mode 100644 index 0000000..95013ba --- /dev/null +++ b/gst/gstevent.h @@ -0,0 +1,497 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstevent.h: Header for GstEvent subsystem + * + * 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_EVENT_H__ +#define __GST_EVENT_H__ + +typedef struct _GstEvent GstEvent; + +/** + * GstEventTypeFlags: + * @GST_EVENT_TYPE_UPSTREAM: Set if the event can travel upstream. + * @GST_EVENT_TYPE_DOWNSTREAM: Set if the event can travel downstream. + * @GST_EVENT_TYPE_SERIALIZED: Set if the event should be serialized with data + * flow. + * @GST_EVENT_TYPE_STICKY: Set if the event is sticky on the pads. + * + * #GstEventTypeFlags indicate the aspects of the different #GstEventType + * values. You can get the type flags of a #GstEventType with the + * gst_event_type_get_flags() function. + */ +typedef enum { + GST_EVENT_TYPE_UPSTREAM = 1 << 0, + GST_EVENT_TYPE_DOWNSTREAM = 1 << 1, + GST_EVENT_TYPE_SERIALIZED = 1 << 2, + GST_EVENT_TYPE_STICKY = 1 << 3 +} GstEventTypeFlags; + +/** + * GST_EVENT_TYPE_BOTH: + * + * The same thing as #GST_EVENT_TYPE_UPSTREAM | #GST_EVENT_TYPE_DOWNSTREAM. + */ +#define GST_EVENT_TYPE_BOTH \ + (GST_EVENT_TYPE_UPSTREAM | GST_EVENT_TYPE_DOWNSTREAM) + +#define GST_EVENT_MAX_STICKY 16 +#define GST_EVENT_STICKY_SHIFT 8 +#define GST_EVENT_NUM_SHIFT (GST_EVENT_STICKY_SHIFT + 4) + +/** + * GST_EVENT_MAKE_TYPE: + * @num: the event number to create + * @idx: the index in the sticky array + * @flags: the event flags + * + * when making custom event types, use this macro with the num and + * the given flags + */ +#define GST_EVENT_MAKE_TYPE(num,idx,flags) \ + (((num) << GST_EVENT_NUM_SHIFT) | ((idx) << GST_EVENT_STICKY_SHIFT) | (flags)) + +#define FLAG(name) GST_EVENT_TYPE_##name + +#define GST_EVENT_STICKY_IDX_TYPE(type) (((type) >> GST_EVENT_STICKY_SHIFT) & 0xf) +#define GST_EVENT_STICKY_IDX(ev) GST_EVENT_STICKY_IDX_TYPE(GST_EVENT_TYPE(ev)) + +/** + * GstEventType: + * @GST_EVENT_UNKNOWN: unknown event. + * @GST_EVENT_FLUSH_START: Start a flush operation. This event clears all data + * from the pipeline and unblock all streaming threads. + * @GST_EVENT_FLUSH_STOP: Stop a flush operation. This event resets the + * running-time of the pipeline. + * @GST_EVENT_CAPS: #GstCaps event. Notify the pad of a new media type. + * @GST_EVENT_SEGMENT: A new media segment follows in the dataflow. The + * segment events contains information for clipping buffers and + * converting buffer timestamps to running-time and + * stream-time. + * @GST_EVENT_TAG: A new set of metadata tags has been found in the stream. + * @GST_EVENT_BUFFERSIZE: Notification of buffering requirements. Currently not + * used yet. + * @GST_EVENT_SINK_MESSAGE: An event that sinks turn into a message. Used to + * send messages that should be emitted in sync with + * rendering. + * Since: 0.10.26 + * @GST_EVENT_EOS: End-Of-Stream. No more data is to be expected to follow + * without a SEGMENT event. + * @GST_EVENT_QOS: A quality message. Used to indicate to upstream elements + * that the downstream elements should adjust their processing + * rate. + * @GST_EVENT_SEEK: A request for a new playback position and rate. + * @GST_EVENT_NAVIGATION: Navigation events are usually used for communicating + * user requests, such as mouse or keyboard movements, + * to upstream elements. + * @GST_EVENT_LATENCY: Notification of new latency adjustment. Sinks will use + * the latency information to adjust their synchronisation. + * Since: 0.10.12 + * @GST_EVENT_STEP: A request for stepping through the media. Sinks will usually + * execute the step operation. Since: 0.10.24 + * @GST_EVENT_RECONFIGURE: A request for upstream renegotiating caps and reconfiguring. + * Since: 0.11.0 + * @GST_EVENT_CUSTOM_UPSTREAM: Upstream custom event + * @GST_EVENT_CUSTOM_DOWNSTREAM: Downstream custom event that travels in the + * data flow. + * @GST_EVENT_CUSTOM_DOWNSTREAM_OOB: Custom out-of-band downstream event. + * @GST_EVENT_CUSTOM_BOTH: Custom upstream or downstream event. + * In-band when travelling downstream. + * @GST_EVENT_CUSTOM_BOTH_OOB: Custom upstream or downstream out-of-band event. + * + * #GstEventType lists the standard event types that can be sent in a pipeline. + * + * The custom event types can be used for private messages between elements + * that can't be expressed using normal + * GStreamer buffer passing semantics. Custom events carry an arbitrary + * #GstStructure. + * Specific custom events are distinguished by the name of the structure. + */ +/* NOTE: keep in sync with quark registration in gstevent.c */ +typedef enum { + GST_EVENT_UNKNOWN = GST_EVENT_MAKE_TYPE (0, 0, 0), + /* bidirectional events */ + GST_EVENT_FLUSH_START = GST_EVENT_MAKE_TYPE (1, 0, FLAG(BOTH)), + GST_EVENT_FLUSH_STOP = GST_EVENT_MAKE_TYPE (2, 0, FLAG(BOTH) | FLAG(SERIALIZED)), + /* downstream serialized events */ + GST_EVENT_CAPS = GST_EVENT_MAKE_TYPE (5, 1, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), + GST_EVENT_SEGMENT = GST_EVENT_MAKE_TYPE (6, 2, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), + GST_EVENT_TAG = GST_EVENT_MAKE_TYPE (7, 3, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), + GST_EVENT_BUFFERSIZE = GST_EVENT_MAKE_TYPE (8, 4, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), + GST_EVENT_SINK_MESSAGE = GST_EVENT_MAKE_TYPE (9, 5, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), + GST_EVENT_EOS = GST_EVENT_MAKE_TYPE (10, 6, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), + + /* upstream events */ + GST_EVENT_QOS = GST_EVENT_MAKE_TYPE (15, 0, FLAG(UPSTREAM)), + GST_EVENT_SEEK = GST_EVENT_MAKE_TYPE (16, 0, FLAG(UPSTREAM)), + GST_EVENT_NAVIGATION = GST_EVENT_MAKE_TYPE (17, 0, FLAG(UPSTREAM)), + GST_EVENT_LATENCY = GST_EVENT_MAKE_TYPE (18, 0, FLAG(UPSTREAM)), + GST_EVENT_STEP = GST_EVENT_MAKE_TYPE (19, 0, FLAG(UPSTREAM)), + GST_EVENT_RECONFIGURE = GST_EVENT_MAKE_TYPE (20, 0, FLAG(UPSTREAM)), + + /* custom events start here */ + GST_EVENT_CUSTOM_UPSTREAM = GST_EVENT_MAKE_TYPE (32, 0, FLAG(UPSTREAM)), + GST_EVENT_CUSTOM_DOWNSTREAM = GST_EVENT_MAKE_TYPE (32, 0, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)), + GST_EVENT_CUSTOM_DOWNSTREAM_OOB = GST_EVENT_MAKE_TYPE (32, 0, FLAG(DOWNSTREAM)), + GST_EVENT_CUSTOM_BOTH = GST_EVENT_MAKE_TYPE (32, 0, FLAG(BOTH) | FLAG(SERIALIZED)), + GST_EVENT_CUSTOM_BOTH_OOB = GST_EVENT_MAKE_TYPE (32, 0, FLAG(BOTH)) +} GstEventType; +#undef FLAG + +#include <gst/gstminiobject.h> +#include <gst/gstformat.h> +#include <gst/gstobject.h> +#include <gst/gstclock.h> +#include <gst/gststructure.h> +#include <gst/gsttaglist.h> +#include <gst/gstsegment.h> +#include <gst/gstsegment.h> +#include <gst/gstmessage.h> + +G_BEGIN_DECLS + +extern GType _gst_event_type; + +#define GST_TYPE_EVENT (_gst_event_type) +#define GST_IS_EVENT(obj) (GST_IS_MINI_OBJECT_TYPE (obj, GST_TYPE_EVENT)) +#define GST_EVENT_CAST(obj) ((GstEvent *)(obj)) +#define GST_EVENT(obj) (GST_EVENT_CAST(obj)) + +/** + * GST_EVENT_TRACE_NAME: + * + * The name used for memory allocation tracing + */ +#define GST_EVENT_TRACE_NAME "GstEvent" + +/** + * GST_EVENT_TYPE: + * @event: the event to query + * + * Get the #GstEventType of the event. + */ +#define GST_EVENT_TYPE(event) (GST_EVENT_CAST(event)->type) + +/** + * GST_EVENT_TYPE_NAME: + * @event: the event to query + * + * Get a constant string representation of the #GstEventType of the event. + */ +#define GST_EVENT_TYPE_NAME(event) (gst_event_type_get_name(GST_EVENT_TYPE(event))) + +/** + * GST_EVENT_TIMESTAMP: + * @event: the event to query + * + * Get the #GstClockTime timestamp of the event. This is the time when the event + * was created. + */ +#define GST_EVENT_TIMESTAMP(event) (GST_EVENT_CAST(event)->timestamp) + +/** + * GST_EVENT_SEQNUM: + * @event: the event to query + * + * The sequence number of @event. + */ +#define GST_EVENT_SEQNUM(event) (GST_EVENT_CAST(event)->seqnum) + +/** + * GST_EVENT_IS_UPSTREAM: + * @ev: the event to query + * + * Check if an event can travel upstream. + */ +#define GST_EVENT_IS_UPSTREAM(ev) !!(GST_EVENT_TYPE (ev) & GST_EVENT_TYPE_UPSTREAM) +/** + * GST_EVENT_IS_DOWNSTREAM: + * @ev: the event to query + * + * Check if an event can travel downstream. + */ +#define GST_EVENT_IS_DOWNSTREAM(ev) !!(GST_EVENT_TYPE (ev) & GST_EVENT_TYPE_DOWNSTREAM) +/** + * GST_EVENT_IS_SERIALIZED: + * @ev: the event to query + * + * Check if an event is serialized with the data stream. + */ +#define GST_EVENT_IS_SERIALIZED(ev) !!(GST_EVENT_TYPE (ev) & GST_EVENT_TYPE_SERIALIZED) +/** + * GST_EVENT_IS_STICKY: + * @ev: the event to query + * + * Check if an event is sticky on the pads. + */ +#define GST_EVENT_IS_STICKY(ev) !!(GST_EVENT_TYPE (ev) & GST_EVENT_TYPE_STICKY) + +/** + * gst_event_is_writable: + * @ev: a #GstEvent + * + * Tests if you can safely write data into a event's structure or validly + * modify the seqnum and timestamp field. + */ +#define gst_event_is_writable(ev) gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (ev)) +/** + * gst_event_make_writable: + * @ev: (transfer full): a #GstEvent + * + * Makes a writable event from the given event. If the source event is + * already writable, this will simply return the same event. A copy will + * otherwise be made using gst_event_copy(). + * + * Returns: (transfer full): a writable event which may or may not be the + * same as @ev + */ +#define gst_event_make_writable(ev) GST_EVENT_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (ev))) +/** + * gst_event_replace: + * @old_event: (inout) (transfer full): pointer to a pointer to a #GstEvent + * to be replaced. + * @new_event: (allow-none) (transfer none): pointer to a #GstEvent that will + * replace the event pointed to by @old_event. + * + * Modifies a pointer to a #GstEvent to point to a different #GstEvent. The + * modification is done atomically (so this is useful for ensuring thread safety + * in some cases), and the reference counts are updated appropriately (the old + * event is unreffed, the new one is reffed). + * + * Either @new_event or the #GstEvent pointed to by @old_event may be NULL. + * + * Returns: TRUE if @new_event was different from @old_event + */ +#define gst_event_replace(old_event,new_event) \ + gst_mini_object_replace ((GstMiniObject **)(old_event), GST_MINI_OBJECT_CAST (new_event)) +/** + * gst_event_steal: + * @old_event: (inout) (transfer full): pointer to a pointer to a #GstEvent + * to be stolen. + * + * Atomically replace the #GstEvent pointed to by @old_event with NULL and + * return the original event. + * + * Returns: the #GstEvent that was in @old_event + */ +#define gst_event_steal(old_event) \ + GST_EVENT_CAST (gst_mini_object_steal ((GstMiniObject **)(old_event))) +/** + * gst_event_take: + * @old_event: (inout) (transfer full): pointer to a pointer to a #GstEvent + * to be stolen. + * @new_event: (allow-none) (transfer full): pointer to a #GstEvent that will + * replace the event pointed to by @old_event. + * + * Modifies a pointer to a #GstEvent to point to a different #GstEvent. This + * function is similar to gst_event_replace() except that it takes ownership of + * @new_event. + * + * Either @new_event or the #GstEvent pointed to by @old_event may be NULL. + * + * Returns: TRUE if @new_event was different from @old_event + */ +#define gst_event_take(old_event,new_event) \ + gst_mini_object_take ((GstMiniObject **)(old_event), GST_MINI_OBJECT_CAST (new_event)) + +/** + * GstQOSType: + * @GST_QOS_TYPE_OVERFLOW: The QoS event type that is produced when downstream + * elements are producing data too quickly and the element can't keep up + * processing the data. Upstream should reduce their processing rate. This + * type is also used when buffers arrive early or in time. + * @GST_QOS_TYPE_UNDERFLOW: The QoS event type that is produced when downstream + * elements are producing data too slowly and need to speed up their processing + * rate. + * @GST_QOS_TYPE_THROTTLE: The QoS event type that is produced when the + * application enabled throttling to limit the datarate. + * + * The different types of QoS events that can be given to the + * gst_event_new_qos() method. + * + * Since: 0.10.33 + */ +typedef enum { + GST_QOS_TYPE_OVERFLOW = 0, + GST_QOS_TYPE_UNDERFLOW = 1, + GST_QOS_TYPE_THROTTLE = 2 +} GstQOSType; + +/** + * GstEvent: + * @mini_object: the parent structure + * @type: the #GstEventType of the event + * @timestamp: the timestamp of the event + * @seqnum: the sequence number of the event + * + * A #GstEvent. + */ +struct _GstEvent { + GstMiniObject mini_object; + + /*< public >*/ /* with COW */ + GstEventType type; + guint64 timestamp; + guint32 seqnum; +}; + +const gchar* gst_event_type_get_name (GstEventType type); +GQuark gst_event_type_to_quark (GstEventType type); +GstEventTypeFlags + gst_event_type_get_flags (GstEventType type); + + +/* refcounting */ +/** + * gst_event_ref: + * @event: The event to refcount + * + * Increase the refcount of this event. + * + * Returns: (transfer full): @event (for convenience when doing assignments) + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstEvent * gst_event_ref (GstEvent * event); +#endif + +static inline GstEvent * +gst_event_ref (GstEvent * event) +{ + return (GstEvent *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (event)); +} + +/** + * gst_event_unref: + * @event: (transfer full): the event to refcount + * + * Decrease the refcount of an event, freeing it if the refcount reaches 0. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void gst_event_unref (GstEvent * event); +#endif + +static inline void +gst_event_unref (GstEvent * event) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (event)); +} + +/* copy event */ +/** + * gst_event_copy: + * @event: The event to copy + * + * Copy the event using the event specific copy function. + * + * Returns: (transfer full): the new event + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstEvent * gst_event_copy (const GstEvent * event); +#endif + +static inline GstEvent * +gst_event_copy (const GstEvent * event) +{ + return GST_EVENT_CAST (gst_mini_object_copy (GST_MINI_OBJECT_CONST_CAST (event))); +} + +GType gst_event_get_type (void); + +/* custom event */ +GstEvent* gst_event_new_custom (GstEventType type, GstStructure *structure); + +const GstStructure * + gst_event_get_structure (GstEvent *event); +GstStructure * gst_event_writable_structure (GstEvent *event); + +gboolean gst_event_has_name (GstEvent *event, const gchar *name); + +/* identifiers for events and messages */ +guint32 gst_event_get_seqnum (GstEvent *event); +void gst_event_set_seqnum (GstEvent *event, guint32 seqnum); + +/* flush events */ +GstEvent * gst_event_new_flush_start (void); + +GstEvent * gst_event_new_flush_stop (gboolean reset_time); +void gst_event_parse_flush_stop (GstEvent *event, gboolean *reset_time); + +/* EOS event */ +GstEvent * gst_event_new_eos (void); + +/* Caps events */ +GstEvent * gst_event_new_caps (GstCaps *caps); +void gst_event_parse_caps (GstEvent *event, GstCaps **caps); + +/* segment event */ +GstEvent* gst_event_new_segment (GstSegment *segment); +void gst_event_parse_segment (GstEvent *event, const GstSegment **segment); +void gst_event_copy_segment (GstEvent *event, GstSegment *segment); + +/* tag event */ +GstEvent* gst_event_new_tag (GstTagList *taglist); +void gst_event_parse_tag (GstEvent *event, GstTagList **taglist); + +/* buffer */ +GstEvent * gst_event_new_buffer_size (GstFormat format, gint64 minsize, gint64 maxsize, + gboolean async); +void gst_event_parse_buffer_size (GstEvent *event, GstFormat *format, gint64 *minsize, + gint64 *maxsize, gboolean *async); + +/* sink message */ +GstEvent* gst_event_new_sink_message (GstMessage *msg); +void gst_event_parse_sink_message (GstEvent *event, GstMessage **msg); + +/* QOS events */ +GstEvent* gst_event_new_qos (GstQOSType type, gdouble proportion, + GstClockTimeDiff diff, GstClockTime timestamp); +void gst_event_parse_qos (GstEvent *event, GstQOSType *type, + gdouble *proportion, GstClockTimeDiff *diff, + GstClockTime *timestamp); +/* seek event */ +GstEvent* gst_event_new_seek (gdouble rate, GstFormat format, GstSeekFlags flags, + GstSeekType start_type, gint64 start, + GstSeekType stop_type, gint64 stop); +void gst_event_parse_seek (GstEvent *event, gdouble *rate, GstFormat *format, + GstSeekFlags *flags, + GstSeekType *start_type, gint64 *start, + GstSeekType *stop_type, gint64 *stop); + +/* navigation event */ +GstEvent* gst_event_new_navigation (GstStructure *structure); + +/* latency event */ +GstEvent* gst_event_new_latency (GstClockTime latency); +void gst_event_parse_latency (GstEvent *event, GstClockTime *latency); + +/* step event */ +GstEvent* gst_event_new_step (GstFormat format, guint64 amount, gdouble rate, + gboolean flush, gboolean intermediate); +void gst_event_parse_step (GstEvent *event, GstFormat *format, guint64 *amount, + gdouble *rate, gboolean *flush, gboolean *intermediate); + +/* renegotiate event */ +GstEvent* gst_event_new_reconfigure (void); + +G_END_DECLS + +#endif /* __GST_EVENT_H__ */ diff --git a/gst/gstfilter.c b/gst/gstfilter.c new file mode 100644 index 0000000..7d6c055 --- /dev/null +++ b/gst/gstfilter.c @@ -0,0 +1,88 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * + * 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:gstfilter + * @short_description: A utility function to filter GLists. + * + * <example> + * <title>Filtering a list</title> + * <programlisting> + * GList *node; + * GstObject *result = NULL; + * + * node = gst_filter_run (list, (GstFilterFunc) my_filter, TRUE, NULL); + * if (node) { + * result = GST_OBJECT (node->data); + * gst_object_ref (result); + * g_list_free (node); + * } + * </programlisting> + * </example> + */ +#include "gst_private.h" +#include <gst/gstfilter.h> + +/** + * gst_filter_run: + * @list: a linked list + * @func: (scope call): the function to execute for each item + * @first: flag to stop execution after a successful item + * @user_data: (closure): user data + * + * Iterates over the elements in @list, calling @func with the + * list item data for each item. If @func returns TRUE, @data is + * prepended to the list of results returned. If @first is true, + * the search is halted after the first result is found. + * + * Since gst_filter_run() knows nothing about the type of @data, no + * reference will be taken (if @data refers to an object) and no copy of + * @data wil be made in any other way when prepending @data to the list of + * results. + * + * Returns: (transfer container): the list of results. Free with g_list_free() + * when no longer needed (the data contained in the list is a flat copy + * and does need to be unreferenced or freed). + */ +GList * +gst_filter_run (const GList * list, GstFilterFunc func, gboolean first, + gpointer user_data) +{ + const GList *walk = list; + GList *result = NULL; + + while (walk) { + gboolean res = TRUE; + gpointer data = walk->data; + + walk = g_list_next (walk); + + if (func) + res = func (data, user_data); + + if (res) { + result = g_list_prepend (result, data); + + if (first) + break; + } + } + + return result; +} diff --git a/gst/gstfilter.h b/gst/gstfilter.h new file mode 100644 index 0000000..1728f00 --- /dev/null +++ b/gst/gstfilter.h @@ -0,0 +1,44 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * + * 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_FILTER_H__ +#define __GST_FILTER_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +/** + * GstFilterFunc: + * @obj: the object + * @user_data: filter data + * + * Function prototype for a filter callback that can be use in gst_filter_run(). + * The function should apply its filtering to @obj. Additional data passed to + * gst_filter_run() are in @data. + * + * Returns: %TRUE for success. + */ +typedef gboolean (*GstFilterFunc) (gpointer obj, gpointer user_data); + +GList* gst_filter_run (const GList *list, GstFilterFunc func, gboolean first, gpointer user_data); + +G_END_DECLS + +#endif /* __GST_FILTER_H_ */ diff --git a/gst/gstformat.c b/gst/gstformat.c new file mode 100644 index 0000000..24c3d0f --- /dev/null +++ b/gst/gstformat.c @@ -0,0 +1,264 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstformat.c: GstFormat registration + * + * 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:gstformat + * @short_description: Dynamically register new data formats + * @see_also: #GstPad, #GstElement + * + * GstFormats functions are used to register a new format to the gstreamer + * core. Formats can be used to perform seeking or conversions/query + * operations. + */ + + +#include "gst_private.h" +#include <string.h> +#include "gstformat.h" +#include "gstenumtypes.h" + +static GStaticMutex mutex = G_STATIC_MUTEX_INIT; +static GList *_gst_formats = NULL; +static GHashTable *_nick_to_format = NULL; +static GHashTable *_format_to_nick = NULL; +static guint32 _n_values = 1; /* we start from 1 because 0 reserved for UNDEFINED */ + +static GstFormatDefinition standard_definitions[] = { + {GST_FORMAT_DEFAULT, "default", "Default format for the media type", 0}, + {GST_FORMAT_BYTES, "bytes", "Bytes", 0}, + {GST_FORMAT_TIME, "time", "Time", 0}, + {GST_FORMAT_BUFFERS, "buffers", "Buffers", 0}, + {GST_FORMAT_PERCENT, "percent", "Percent", 0}, + {GST_FORMAT_UNDEFINED, NULL, NULL, 0} +}; + +void +_priv_gst_format_initialize (void) +{ + GstFormatDefinition *standards = standard_definitions; + + g_static_mutex_lock (&mutex); + if (_nick_to_format == NULL) { + _nick_to_format = g_hash_table_new (g_str_hash, g_str_equal); + _format_to_nick = g_hash_table_new (NULL, NULL); + } + + while (standards->nick) { + standards->quark = g_quark_from_static_string (standards->nick); + g_hash_table_insert (_nick_to_format, (gpointer) standards->nick, + standards); + g_hash_table_insert (_format_to_nick, GINT_TO_POINTER (standards->value), + standards); + + _gst_formats = g_list_append (_gst_formats, standards); + standards++; + _n_values++; + } + /* getting the type registers the enum */ + g_type_class_ref (gst_format_get_type ()); + g_static_mutex_unlock (&mutex); +} + +/** + * gst_format_get_name: + * @format: a #GstFormat + * + * Get a printable name for the given format. Do not modify or free. + * + * Returns: a reference to the static name of the format or NULL if + * the format is unknown. + */ +const gchar * +gst_format_get_name (GstFormat format) +{ + const GstFormatDefinition *def; + const gchar *result; + + if ((def = gst_format_get_details (format)) != NULL) + result = def->nick; + else + result = NULL; + + return result; +} + +/** + * gst_format_to_quark: + * @format: a #GstFormat + * + * Get the unique quark for the given format. + * + * Returns: the quark associated with the format or 0 if the format + * is unknown. + */ +GQuark +gst_format_to_quark (GstFormat format) +{ + const GstFormatDefinition *def; + GQuark result; + + if ((def = gst_format_get_details (format)) != NULL) + result = def->quark; + else + result = 0; + + return result; +} + +/** + * gst_format_register: + * @nick: The nick of the new format + * @description: The description of the new format + * + * Create a new GstFormat based on the nick or return an + * already registered format with that nick. + * + * Returns: A new GstFormat or an already registered format + * with the same nick. + * + * MT safe. + */ +GstFormat +gst_format_register (const gchar * nick, const gchar * description) +{ + GstFormatDefinition *format; + GstFormat query; + + g_return_val_if_fail (nick != NULL, GST_FORMAT_UNDEFINED); + g_return_val_if_fail (description != NULL, GST_FORMAT_UNDEFINED); + + query = gst_format_get_by_nick (nick); + if (query != GST_FORMAT_UNDEFINED) + return query; + + g_static_mutex_lock (&mutex); + format = g_slice_new (GstFormatDefinition); + format->value = (GstFormat) _n_values; + format->nick = g_strdup (nick); + format->description = g_strdup (description); + format->quark = g_quark_from_static_string (format->nick); + + g_hash_table_insert (_nick_to_format, (gpointer) format->nick, format); + g_hash_table_insert (_format_to_nick, GINT_TO_POINTER (format->value), + format); + _gst_formats = g_list_append (_gst_formats, format); + _n_values++; + g_static_mutex_unlock (&mutex); + + return format->value; +} + +/** + * gst_format_get_by_nick: + * @nick: The nick of the format + * + * Return the format registered with the given nick. + * + * Returns: The format with @nick or GST_FORMAT_UNDEFINED + * if the format was not registered. + */ +GstFormat +gst_format_get_by_nick (const gchar * nick) +{ + GstFormatDefinition *format; + + g_return_val_if_fail (nick != NULL, GST_FORMAT_UNDEFINED); + + g_static_mutex_lock (&mutex); + format = g_hash_table_lookup (_nick_to_format, nick); + g_static_mutex_unlock (&mutex); + + if (format != NULL) + return format->value; + else + return GST_FORMAT_UNDEFINED; +} + +/** + * gst_formats_contains: + * @formats: The format array to search + * @format: the format to find + * + * See if the given format is inside the format array. + * + * Returns: TRUE if the format is found inside the array + */ +gboolean +gst_formats_contains (const GstFormat * formats, GstFormat format) +{ + if (!formats) + return FALSE; + + while (*formats) { + if (*formats == format) + return TRUE; + + formats++; + } + return FALSE; +} + + +/** + * gst_format_get_details: + * @format: The format to get details of + * + * Get details about the given format. + * + * Returns: The #GstFormatDefinition for @format or NULL on failure. + * + * MT safe. + */ +const GstFormatDefinition * +gst_format_get_details (GstFormat format) +{ + const GstFormatDefinition *result; + + g_static_mutex_lock (&mutex); + result = g_hash_table_lookup (_format_to_nick, GINT_TO_POINTER (format)); + g_static_mutex_unlock (&mutex); + + return result; +} + +/** + * gst_format_iterate_definitions: + * + * Iterate all the registered formats. The format definition is read + * only. + * + * Returns: (transfer full): a GstIterator of #GstFormatDefinition. + */ +GstIterator * +gst_format_iterate_definitions (void) +{ + GstIterator *result; + + g_static_mutex_lock (&mutex); + /* FIXME: register a boxed type for GstFormatDefinition */ + result = gst_iterator_new_list (G_TYPE_POINTER, + g_static_mutex_get_mutex (&mutex), &_n_values, &_gst_formats, NULL, NULL); + g_static_mutex_unlock (&mutex); + + return result; +} diff --git a/gst/gstformat.h b/gst/gstformat.h new file mode 100644 index 0000000..9f8b362 --- /dev/null +++ b/gst/gstformat.h @@ -0,0 +1,113 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * + * gstformat.h: Header for GstFormat types used in queries and + * seeking. + * + * 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_FORMAT_H__ +#define __GST_FORMAT_H__ + +#include <glib.h> + +#include <gst/gstiterator.h> + +G_BEGIN_DECLS + +/** + * GstFormat: + * @GST_FORMAT_UNDEFINED: undefined format + * @GST_FORMAT_DEFAULT: the default format of the pad/element. This can be + * samples for raw audio, frames/fields for raw video (some, but not all, + * elements support this; use @GST_FORMAT_TIME if you don't have a good + * reason to query for samples/frames) + * @GST_FORMAT_BYTES: bytes + * @GST_FORMAT_TIME: time in nanoseconds + * @GST_FORMAT_BUFFERS: buffers (few, if any, elements implement this as of + * May 2009) + * @GST_FORMAT_PERCENT: percentage of stream (few, if any, elements implement + * this as of May 2009) + * + * Standard predefined formats + */ +/* NOTE: don't forget to update the table in gstformat.c when changing + * this enum */ +typedef enum { + GST_FORMAT_UNDEFINED = 0, /* must be first in list */ + GST_FORMAT_DEFAULT = 1, + GST_FORMAT_BYTES = 2, + GST_FORMAT_TIME = 3, + GST_FORMAT_BUFFERS = 4, + GST_FORMAT_PERCENT = 5 +} GstFormat; + +/* a percentage is always relative to 1000000 */ +/** + * GST_FORMAT_PERCENT_MAX: + * + * The PERCENT format is between 0 and this value + */ +#define GST_FORMAT_PERCENT_MAX G_GINT64_CONSTANT (1000000) +/** + * GST_FORMAT_PERCENT_SCALE: + * + * The value used to scale down the reported PERCENT format value to + * its real value. + */ +#define GST_FORMAT_PERCENT_SCALE G_GINT64_CONSTANT (10000) + +typedef struct _GstFormatDefinition GstFormatDefinition; + +/** + * GstFormatDefinition: + * @value: The unique id of this format + * @nick: A short nick of the format + * @description: A longer description of the format + * @quark: A quark for the nick + * + * A format definition + */ +struct _GstFormatDefinition +{ + GstFormat value; + const gchar *nick; + const gchar *description; + GQuark quark; +}; + +const gchar* gst_format_get_name (GstFormat format); +GQuark gst_format_to_quark (GstFormat format); + +/* register a new format */ +GstFormat gst_format_register (const gchar *nick, + const gchar *description); +GstFormat gst_format_get_by_nick (const gchar *nick); + +/* check if a format is in an array of formats */ +gboolean gst_formats_contains (const GstFormat *formats, GstFormat format); + +/* query for format details */ +const GstFormatDefinition* + gst_format_get_details (GstFormat format); +GstIterator* gst_format_iterate_definitions (void); + +G_END_DECLS + +#endif /* __GST_FORMAT_H__ */ diff --git a/gst/gstghostpad.c b/gst/gstghostpad.c new file mode 100644 index 0000000..f739988 --- /dev/null +++ b/gst/gstghostpad.c @@ -0,0 +1,1312 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2005 Andy Wingo <wingo@pobox.com> + * 2006 Edward Hervey <bilboed@bilboed.com> + * + * gstghostpad.c: Proxy pads + * + * 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:gstghostpad + * @short_description: Pseudo link pads + * @see_also: #GstPad + * + * GhostPads are useful when organizing pipelines with #GstBin like elements. + * The idea here is to create hierarchical element graphs. The bin element + * contains a sub-graph. Now one would like to treat the bin-element like any + * other #GstElement. This is where GhostPads come into play. A GhostPad acts as + * a proxy for another pad. Thus the bin can have sink and source ghost-pads + * that are associated with sink and source pads of the child elements. + * + * If the target pad is known at creation time, gst_ghost_pad_new() is the + * function to use to get a ghost-pad. Otherwise one can use gst_ghost_pad_new_no_target() + * to create the ghost-pad and use gst_ghost_pad_set_target() to establish the + * association later on. + * + * Note that GhostPads add overhead to the data processing of a pipeline. + * + * Last reviewed on 2005-11-18 (0.9.5) + */ + +#include "gst_private.h" +#include "gstinfo.h" + +#include "gstghostpad.h" +#include "gst.h" + +#define GST_CAT_DEFAULT GST_CAT_PADS + +#define GST_PROXY_PAD_CAST(obj) ((GstProxyPad *)obj) +#define GST_PROXY_PAD_PRIVATE(obj) (GST_PROXY_PAD_CAST (obj)->priv) +#define GST_PROXY_PAD_TARGET(pad) (GST_PROXY_PAD_PRIVATE (pad)->target) +#define GST_PROXY_PAD_INTERNAL(pad) (GST_PROXY_PAD_PRIVATE (pad)->internal) +#define GST_PROXY_PAD_RETARGET(pad) (GST_PROXY_PAD_PRIVATE (pad)->retarget) +#define GST_PROXY_GET_LOCK(pad) (GST_PROXY_PAD_PRIVATE (pad)->proxy_lock) +#define GST_PROXY_LOCK(pad) (g_mutex_lock (GST_PROXY_GET_LOCK (pad))) +#define GST_PROXY_UNLOCK(pad) (g_mutex_unlock (GST_PROXY_GET_LOCK (pad))) + +struct _GstProxyPadPrivate +{ + /* with PROXY_LOCK */ + GMutex *proxy_lock; + GstPad *target; + GstPad *internal; + gboolean retarget; +}; + +G_DEFINE_TYPE (GstProxyPad, gst_proxy_pad, GST_TYPE_PAD); + +static GstPad *gst_proxy_pad_get_target (GstPad * pad); + +static void gst_proxy_pad_dispose (GObject * object); +static void gst_proxy_pad_finalize (GObject * object); + +/** + * gst_proxy_pad_query_type_default: + * @pad: a #GstPad. + * + * Invoke the default query type handler of the proxy pad. + * + * Returns: (transfer none) (array zero-terminated=1): a zero-terminated array + * of #GstQueryType. + * + * Since: 0.10.35 + */ +const GstQueryType * +gst_proxy_pad_query_type_default (GstPad * pad) +{ + GstPad *target; + const GstQueryType *res = NULL; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), NULL); + + if (!(target = gst_proxy_pad_get_target (pad))) + goto no_target; + + res = gst_pad_get_query_types (target); + gst_object_unref (target); + + return res; + + /* ERRORS */ +no_target: + { + GST_DEBUG_OBJECT (pad, "no target pad"); + return FALSE; + } +} + +/** + * gst_proxy_pad_event_default: + * @pad: a #GstPad to push the event to. + * @event: (transfer full): the #GstEvent to send to the pad. + * + * Invoke the default event of the proxy pad. + * + * Returns: TRUE if the event was handled. + * + * Since: 0.10.35 + */ +gboolean +gst_proxy_pad_event_default (GstPad * pad, GstEvent * event) +{ + gboolean res; + GstPad *internal; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), FALSE); + g_return_val_if_fail (GST_IS_EVENT (event), FALSE); + + internal = GST_PROXY_PAD_INTERNAL (pad); + res = gst_pad_push_event (internal, event); + + return res; +} + +/** + * gst_proxy_pad_query_default: + * @pad: a #GstPad to invoke the default query on. + * @query: (transfer none): the #GstQuery to perform. + * + * Invoke the default query function of the proxy pad. + * + * Returns: TRUE if the query could be performed. + * + * Since: 0.10.35 + */ +gboolean +gst_proxy_pad_query_default (GstPad * pad, GstQuery * query) +{ + gboolean res; + GstPad *target; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), FALSE); + g_return_val_if_fail (GST_IS_QUERY (query), FALSE); + + if (!(target = gst_proxy_pad_get_target (pad))) + goto no_target; + + res = gst_pad_query (target, query); + gst_object_unref (target); + + return res; + + /* ERRORS */ +no_target: + { + GST_DEBUG_OBJECT (pad, "no target pad"); + return FALSE; + } +} + +/** + * gst_proyx_pad_iterate_internal_links_default: + * @pad: the #GstPad to get the internal links of. + * + * Invoke the default iterate internal links function of the proxy pad. + * + * Returns: a #GstIterator of #GstPad, or NULL if @pad has no parent. Unref each + * returned pad with gst_object_unref(). + * + * Since: 0.10.35 + */ +GstIterator * +gst_proxy_pad_iterate_internal_links_default (GstPad * pad) +{ + GstIterator *res = NULL; + GstPad *internal; + GValue v = { 0, }; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), NULL); + + internal = GST_PROXY_PAD_INTERNAL (pad); + g_value_init (&v, GST_TYPE_PAD); + g_value_set_object (&v, internal); + res = gst_iterator_new_single (GST_TYPE_PAD, &v); + g_value_unset (&v); + gst_object_unref (internal); + + return res; +} + +/** + * gst_proxy_pad_chain_default: + * @pad: a sink #GstPad, returns GST_FLOW_ERROR if not. + * @buffer: (transfer full): the #GstBuffer to send, return GST_FLOW_ERROR + * if not. + * + * Invoke the default chain function of the proxy pad. + * + * Returns: a #GstFlowReturn from the pad. + * + * Since: 0.10.35 + */ +GstFlowReturn +gst_proxy_pad_chain_default (GstPad * pad, GstBuffer * buffer) +{ + GstFlowReturn res; + GstPad *internal; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR); + + internal = GST_PROXY_PAD_INTERNAL (pad); + res = gst_pad_push (internal, buffer); + + return res; +} + +/** + * gst_proxy_pad_chain_list_default: + * @pad: a sink #GstPad, returns GST_FLOW_ERROR if not. + * @list: (transfer full): the #GstBufferList to send, return GST_FLOW_ERROR + * if not. + * + * Invoke the default chain list function of the proxy pad. + * + * Returns: a #GstFlowReturn from the pad. + * + * Since: 0.10.35 + */ +GstFlowReturn +gst_proxy_pad_chain_list_default (GstPad * pad, GstBufferList * list) +{ + GstFlowReturn res; + GstPad *internal; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_IS_BUFFER_LIST (list), GST_FLOW_ERROR); + + internal = GST_PROXY_PAD_INTERNAL (pad); + res = gst_pad_push_list (internal, list); + + return res; +} + +/** + * gst_proxy_pad_get_range_default: + * @pad: a src #GstPad, returns #GST_FLOW_ERROR if not. + * @offset: The start offset of the buffer + * @size: The length of the buffer + * @buffer: (out callee-allocates): a pointer to hold the #GstBuffer, + * returns #GST_FLOW_ERROR if %NULL. + * + * Invoke the default getrange function of the proxy pad. + * + * Returns: a #GstFlowReturn from the pad. + * + * Since: 0.10.35 + */ +GstFlowReturn +gst_proxy_pad_getrange_default (GstPad * pad, guint64 offset, guint size, + GstBuffer ** buffer) +{ + GstFlowReturn res; + GstPad *internal; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); + + internal = GST_PROXY_PAD_INTERNAL (pad); + res = gst_pad_pull_range (internal, offset, size, buffer); + + return res; +} + +/** + * gst_proxy_pad_getcaps_default: + * @pad: a #GstPad to get the capabilities of. + * @filter: a #GstCaps filter. + * + * Invoke the default getcaps function of the proxy pad. + * + * Returns: (transfer full): the caps of the pad with incremented ref-count + * + * Since: 0.10.35 + */ +GstCaps * +gst_proxy_pad_getcaps_default (GstPad * pad, GstCaps * filter) +{ + GstPad *target; + GstCaps *res; + GstPadTemplate *templ; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), NULL); + + templ = GST_PAD_PAD_TEMPLATE (pad); + target = gst_proxy_pad_get_target (pad); + if (target) { + /* if we have a real target, proxy the call */ + res = gst_pad_get_caps (target, filter); + + GST_DEBUG_OBJECT (pad, "get caps of target %s:%s : %" GST_PTR_FORMAT, + GST_DEBUG_PAD_NAME (target), res); + + gst_object_unref (target); + + /* filter against the template */ + if (templ && res) { + GstCaps *filt, *tmp; + + filt = GST_PAD_TEMPLATE_CAPS (templ); + if (filt) { + tmp = gst_caps_intersect_full (res, filt, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (res); + res = tmp; + GST_DEBUG_OBJECT (pad, + "filtered against template gives %" GST_PTR_FORMAT, res); + } + } + } else { + /* else, if we have a template, use its caps. */ + if (templ) { + res = GST_PAD_TEMPLATE_CAPS (templ); + GST_DEBUG_OBJECT (pad, + "using pad template %p with caps %p %" GST_PTR_FORMAT, templ, res, + res); + res = gst_caps_ref (res); + + if (filter) { + GstCaps *intersection = + gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST); + + gst_caps_unref (res); + res = intersection; + } + + goto done; + } + + /* last resort, any caps */ + GST_DEBUG_OBJECT (pad, "pad has no template, returning ANY"); + res = gst_caps_new_any (); + } + +done: + return res; +} + +/** + * gst_proxy_pad_acceptcaps_default: + * @pad: a #GstPad to check + * @caps: a #GstCaps to check on the pad + * + * Invoke the default acceptcaps function of the proxy pad. + * + * Returns: TRUE if the pad can accept the caps. + * + * Since: 0.10.35 + */ +gboolean +gst_proxy_pad_acceptcaps_default (GstPad * pad, GstCaps * caps) +{ + GstPad *target; + gboolean res; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), FALSE); + g_return_val_if_fail (caps == NULL || GST_IS_CAPS (caps), FALSE); + + target = gst_proxy_pad_get_target (pad); + if (target) { + res = gst_pad_accept_caps (target, caps); + gst_object_unref (target); + } else { + GST_DEBUG_OBJECT (pad, "no target"); + /* We don't have a target, we return TRUE and we assume that any future + * target will be able to deal with any configured caps. */ + res = TRUE; + } + + return res; +} + +/** + * gst_proxy_pad_fixatecaps_default: + * @pad: a #GstPad to fixate + * @caps: the #GstCaps to fixate + * + * Invoke the default fixatecaps function of the proxy pad. + * + * Since: 0.10.35 + */ +void +gst_proxy_pad_fixatecaps_default (GstPad * pad, GstCaps * caps) +{ + GstPad *target; + + g_return_if_fail (GST_IS_PROXY_PAD (pad)); + g_return_if_fail (GST_IS_CAPS (caps)); + + if (!(target = gst_proxy_pad_get_target (pad))) + goto no_target; + + gst_pad_fixate_caps (target, caps); + gst_object_unref (target); + + return; + + /* ERRORS */ +no_target: + { + GST_DEBUG_OBJECT (pad, "no target"); + return; + } +} + +static gboolean +gst_proxy_pad_set_target_unlocked (GstPad * pad, GstPad * target) +{ + GstPad *oldtarget; + + if (target) { + GST_LOG_OBJECT (pad, "setting target %s:%s", GST_DEBUG_PAD_NAME (target)); + + if (G_UNLIKELY (GST_PAD_DIRECTION (pad) != GST_PAD_DIRECTION (target))) + goto wrong_direction; + } else + GST_LOG_OBJECT (pad, "clearing target"); + + /* clear old target */ + if ((oldtarget = GST_PROXY_PAD_TARGET (pad))) + gst_object_unref (oldtarget); + + /* set and ref new target if any */ + if (target) + GST_PROXY_PAD_TARGET (pad) = gst_object_ref (target); + else + GST_PROXY_PAD_TARGET (pad) = NULL; + + return TRUE; + + /* ERRORS */ +wrong_direction: + { + GST_ERROR_OBJECT (pad, + "target pad doesn't have the same direction as ourself"); + return FALSE; + } +} + +static gboolean +gst_proxy_pad_set_target (GstPad * pad, GstPad * target) +{ + gboolean result; + + GST_PROXY_LOCK (pad); + result = gst_proxy_pad_set_target_unlocked (pad, target); + GST_PROXY_UNLOCK (pad); + + return result; +} + +static GstPad * +gst_proxy_pad_get_target (GstPad * pad) +{ + GstPad *target; + + GST_PROXY_LOCK (pad); + target = GST_PROXY_PAD_TARGET (pad); + if (target) + gst_object_ref (target); + GST_PROXY_UNLOCK (pad); + + return target; +} + +/** + * gst_proxy_pad_get_internal: + * @pad: the #GstProxyPad + * + * Get the internal pad of @pad. Unref target pad after usage. + * + * The internal pad of a #GstGhostPad is the internally used + * pad of opposite direction, which is used to link to the target. + * + * Returns: (transfer full): the target #GstProxyPad, can be NULL. + * Unref target pad after usage. + * + * Since: 0.10.35 + */ +GstProxyPad * +gst_proxy_pad_get_internal (GstProxyPad * pad) +{ + GstPad *internal; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), NULL); + + GST_PROXY_LOCK (pad); + internal = GST_PROXY_PAD_INTERNAL (pad); + if (internal) + gst_object_ref (internal); + GST_PROXY_UNLOCK (pad); + + return GST_PROXY_PAD_CAST (internal); +} + +/** + * gst_proxy_pad_unlink_default: + * @pad: a #GstPad to unlink + * + * Invoke the default unlink function of the proxy pad. + * + * Since: 0.10.35 + */ +void +gst_proxy_pad_unlink_default (GstPad * pad) +{ + GstPad *internal; + + /* don't do anything if this unlink resulted from retargeting the pad + * controlled by the ghostpad. We only want to invalidate the target pad when + * the element suddenly unlinked with our internal pad. */ + if (GST_PROXY_PAD_RETARGET (pad)) + return; + + internal = GST_PROXY_PAD_INTERNAL (pad); + + GST_DEBUG_OBJECT (pad, "pad is unlinked"); + + gst_proxy_pad_set_target (internal, NULL); +} + +static void +gst_proxy_pad_class_init (GstProxyPadClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstProxyPadPrivate)); + + gobject_class->dispose = gst_proxy_pad_dispose; + gobject_class->finalize = gst_proxy_pad_finalize; + + /* Register common function pointer descriptions */ + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_query_type_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_event_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_query_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_iterate_internal_links_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_getcaps_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_acceptcaps_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_fixatecaps_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_unlink_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_chain_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_chain_list_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_proxy_pad_getrange_default); +} + +static void +gst_proxy_pad_dispose (GObject * object) +{ + GstPad *pad = GST_PAD (object); + GstPad **target_p; + + GST_PROXY_LOCK (pad); + /* remove and unref the target */ + target_p = &GST_PROXY_PAD_TARGET (pad); + gst_object_replace ((GstObject **) target_p, NULL); + /* The internal is only cleared by GstGhostPad::dispose, since it is the + * parent of non-ghost GstProxyPad and owns the refcount on the internal. + */ + GST_PROXY_UNLOCK (pad); + + G_OBJECT_CLASS (gst_proxy_pad_parent_class)->dispose (object); +} + +static void +gst_proxy_pad_finalize (GObject * object) +{ + GstProxyPad *pad = GST_PROXY_PAD (object); + + g_mutex_free (GST_PROXY_GET_LOCK (pad)); + GST_PROXY_GET_LOCK (pad) = NULL; + + G_OBJECT_CLASS (gst_proxy_pad_parent_class)->finalize (object); +} + +static void +gst_proxy_pad_init (GstProxyPad * ppad) +{ + GstPad *pad = (GstPad *) ppad; + + GST_PROXY_PAD_PRIVATE (ppad) = G_TYPE_INSTANCE_GET_PRIVATE (ppad, + GST_TYPE_PROXY_PAD, GstProxyPadPrivate); + GST_PROXY_GET_LOCK (pad) = g_mutex_new (); + + gst_pad_set_query_type_function (pad, gst_proxy_pad_query_type_default); + gst_pad_set_event_function (pad, gst_proxy_pad_event_default); + gst_pad_set_query_function (pad, gst_proxy_pad_query_default); + gst_pad_set_iterate_internal_links_function (pad, + gst_proxy_pad_iterate_internal_links_default); + + gst_pad_set_getcaps_function (pad, gst_proxy_pad_getcaps_default); + gst_pad_set_acceptcaps_function (pad, gst_proxy_pad_acceptcaps_default); + gst_pad_set_fixatecaps_function (pad, gst_proxy_pad_fixatecaps_default); + gst_pad_set_unlink_function (pad, gst_proxy_pad_unlink_default); +} + + +/*********************************************************************** + * Ghost pads, implemented as a pair of proxy pads (sort of) + */ + + +#define GST_GHOST_PAD_PRIVATE(obj) (GST_GHOST_PAD_CAST (obj)->priv) + +struct _GstGhostPadPrivate +{ + /* with PROXY_LOCK */ + gboolean constructed; +}; + +G_DEFINE_TYPE (GstGhostPad, gst_ghost_pad, GST_TYPE_PROXY_PAD); + +static void gst_ghost_pad_dispose (GObject * object); + +/** + * gst_ghost_pad_internal_activate_push_default: + * @pad: the #GstPad to activate or deactivate. + * @active: whether the pad should be active or not. + * + * Invoke the default activate push function of a proxy pad that is + * owned by a ghost pad. + * + * Returns: %TRUE if the operation was successful. + * + * Since: 0.10.35 + */ +gboolean +gst_ghost_pad_internal_activate_push_default (GstPad * pad, gboolean active) +{ + gboolean ret; + GstPad *other; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), FALSE); + + GST_LOG_OBJECT (pad, "%sactivate push on %s:%s, we're ok", + (active ? "" : "de"), GST_DEBUG_PAD_NAME (pad)); + + /* in both cases (SRC and SINK) we activate just the internal pad. The targets + * will be activated later (or already in case of a ghost sinkpad). */ + other = GST_PROXY_PAD_INTERNAL (pad); + ret = gst_pad_activate_push (other, active); + + return ret; +} + +/** + * gst_ghost_pad_internal_activate_pull_default: + * @pad: the #GstPad to activate or deactivate. + * @active: whether the pad should be active or not. + * + * Invoke the default activate pull function of a proxy pad that is + * owned by a ghost pad. + * + * Returns: %TRUE if the operation was successful. + * + * Since: 0.10.35 + */ +gboolean +gst_ghost_pad_internal_activate_pull_default (GstPad * pad, gboolean active) +{ + gboolean ret; + GstPad *other; + + g_return_val_if_fail (GST_IS_PROXY_PAD (pad), FALSE); + + GST_LOG_OBJECT (pad, "%sactivate pull on %s:%s", (active ? "" : "de"), + GST_DEBUG_PAD_NAME (pad)); + + if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC) { + /* we are activated in pull mode by our peer element, which is a sinkpad + * that wants to operate in pull mode. This activation has to propagate + * upstream through the pipeline. We call the internal activation function, + * which will trigger gst_ghost_pad_activate_pull_default, which propagates even + * further upstream */ + GST_LOG_OBJECT (pad, "pad is src, activate internal"); + other = GST_PROXY_PAD_INTERNAL (pad); + ret = gst_pad_activate_pull (other, active); + } else if (G_LIKELY ((other = gst_pad_get_peer (pad)))) { + /* We are SINK, the ghostpad is SRC, we propagate the activation upstream + * since we hold a pointer to the upstream peer. */ + GST_LOG_OBJECT (pad, "activating peer"); + ret = gst_pad_activate_pull (other, active); + gst_object_unref (other); + } else { + /* this is failure, we can't activate pull if there is no peer */ + GST_LOG_OBJECT (pad, "not src and no peer, failing"); + ret = FALSE; + } + + return ret; +} + +/** + * gst_ghost_pad_activate_push_default: + * @pad: the #GstPad to activate or deactivate. + * @active: whether the pad should be active or not. + * + * Invoke the default activate push function of a ghost pad. + * + * Returns: %TRUE if the operation was successful. + * + * Since: 0.10.35 + */ +gboolean +gst_ghost_pad_activate_push_default (GstPad * pad, gboolean active) +{ + gboolean ret; + GstPad *other; + + g_return_val_if_fail (GST_IS_GHOST_PAD (pad), FALSE); + + GST_LOG_OBJECT (pad, "%sactivate push on %s:%s, proxy internal", + (active ? "" : "de"), GST_DEBUG_PAD_NAME (pad)); + + /* just activate the internal pad */ + other = GST_PROXY_PAD_INTERNAL (pad); + ret = gst_pad_activate_push (other, active); + + return ret; +} + +/** + * gst_ghost_pad_activate_pull_default: + * @pad: the #GstPad to activate or deactivate. + * @active: whether the pad should be active or not. + * + * Invoke the default activate pull function of a ghost pad. + * + * Returns: %TRUE if the operation was successful. + * + * Since: 0.10.35 + */ +gboolean +gst_ghost_pad_activate_pull_default (GstPad * pad, gboolean active) +{ + gboolean ret; + GstPad *other; + + g_return_val_if_fail (GST_IS_GHOST_PAD (pad), FALSE); + + GST_LOG_OBJECT (pad, "%sactivate pull on %s:%s", (active ? "" : "de"), + GST_DEBUG_PAD_NAME (pad)); + + if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC) { + /* the ghostpad is SRC and activated in pull mode by its peer, call the + * activation function of the internal pad to propagate the activation + * upstream */ + GST_LOG_OBJECT (pad, "pad is src, activate internal"); + other = GST_PROXY_PAD_INTERNAL (pad); + ret = gst_pad_activate_pull (other, active); + } else if (G_LIKELY ((other = gst_pad_get_peer (pad)))) { + /* We are SINK and activated by the internal pad, propagate activation + * upstream because we hold a ref to the upstream peer */ + GST_LOG_OBJECT (pad, "activating peer"); + ret = gst_pad_activate_pull (other, active); + gst_object_unref (other); + } else { + /* no peer, we fail */ + GST_LOG_OBJECT (pad, "pad not src and no peer, failing"); + ret = FALSE; + } + + return ret; +} + +/** + * gst_ghost_pad_link_default: + * @pad: the #GstPad to link. + * @peer: the #GstPad peer + * + * Invoke the default link function of a ghost pad. + * + * Returns: #GstPadLinkReturn of the operation + * + * Since: 0.10.35 + */ +GstPadLinkReturn +gst_ghost_pad_link_default (GstPad * pad, GstPad * peer) +{ + GstPadLinkReturn ret; + GstPad *internal; + + g_return_val_if_fail (GST_IS_GHOST_PAD (pad), GST_PAD_LINK_REFUSED); + g_return_val_if_fail (GST_IS_PAD (peer), GST_PAD_LINK_REFUSED); + + GST_DEBUG_OBJECT (pad, "linking ghostpad"); + + internal = GST_PROXY_PAD_INTERNAL (pad); + if (!gst_proxy_pad_set_target (internal, peer)) + goto target_failed; + + ret = GST_PAD_LINK_OK; + /* if we are a source pad, we should call the peer link function + * if the peer has one, see design docs. */ + if (GST_PAD_IS_SRC (pad)) { + if (GST_PAD_LINKFUNC (peer)) { + ret = GST_PAD_LINKFUNC (peer) (peer, pad); + if (ret != GST_PAD_LINK_OK) + goto link_failed; + } + } + return ret; + + /* ERRORS */ +target_failed: + { + GST_DEBUG_OBJECT (pad, "setting target failed"); + return GST_PAD_LINK_REFUSED; + } +link_failed: + { + GST_DEBUG_OBJECT (pad, "linking failed"); + /* clear target again */ + gst_proxy_pad_set_target (internal, NULL); + return ret; + } +} + +/** + * gst_ghost_pad_unlink_default: + * @pad: the #GstPad to link. + * + * Invoke the default unlink function of a ghost pad. + * + * Since: 0.10.35 + */ +void +gst_ghost_pad_unlink_default (GstPad * pad) +{ + GstPad *internal; + + g_return_if_fail (GST_IS_GHOST_PAD (pad)); + + internal = GST_PROXY_PAD_INTERNAL (pad); + + GST_DEBUG_OBJECT (pad, "unlinking ghostpad"); + + /* The target of the internal pad is no longer valid */ + gst_proxy_pad_set_target (internal, NULL); +} + +static void +gst_ghost_pad_class_init (GstGhostPadClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstGhostPadPrivate)); + + gobject_class->dispose = gst_ghost_pad_dispose; + + GST_DEBUG_REGISTER_FUNCPTR (gst_ghost_pad_activate_pull_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_ghost_pad_activate_push_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_ghost_pad_link_default); +} + +static void +gst_ghost_pad_init (GstGhostPad * pad) +{ + GST_GHOST_PAD_PRIVATE (pad) = G_TYPE_INSTANCE_GET_PRIVATE (pad, + GST_TYPE_GHOST_PAD, GstGhostPadPrivate); + + gst_pad_set_activatepull_function (GST_PAD_CAST (pad), + gst_ghost_pad_activate_pull_default); + gst_pad_set_activatepush_function (GST_PAD_CAST (pad), + gst_ghost_pad_activate_push_default); +} + +static void +gst_ghost_pad_dispose (GObject * object) +{ + GstPad *pad; + GstPad *internal; + GstPad *peer; + + pad = GST_PAD (object); + + GST_DEBUG_OBJECT (pad, "dispose"); + + gst_ghost_pad_set_target (GST_GHOST_PAD (pad), NULL); + + /* Unlink here so that gst_pad_dispose doesn't. That would lead to a call to + * gst_ghost_pad_unlink_default when the ghost pad is in an inconsistent state */ + peer = gst_pad_get_peer (pad); + if (peer) { + if (GST_PAD_IS_SRC (pad)) + gst_pad_unlink (pad, peer); + else + gst_pad_unlink (peer, pad); + + gst_object_unref (peer); + } + + GST_PROXY_LOCK (pad); + internal = GST_PROXY_PAD_INTERNAL (pad); + + gst_pad_set_activatepull_function (internal, NULL); + gst_pad_set_activatepush_function (internal, NULL); + + /* disposes of the internal pad, since the ghostpad is the only possible object + * that has a refcount on the internal pad. */ + gst_object_unparent (GST_OBJECT_CAST (internal)); + GST_PROXY_PAD_INTERNAL (pad) = NULL; + + GST_PROXY_UNLOCK (pad); + + G_OBJECT_CLASS (gst_ghost_pad_parent_class)->dispose (object); +} + +/** + * gst_ghost_pad_construct: + * @gpad: the newly allocated ghost pad + * + * Finish initialization of a newly allocated ghost pad. + * + * This function is most useful in language bindings and when subclassing + * #GstGhostPad; plugin and application developers normally will not call this + * function. Call this function directly after a call to g_object_new + * (GST_TYPE_GHOST_PAD, "direction", @dir, ..., NULL). + * + * Returns: %TRUE if the construction succeeds, %FALSE otherwise. + * + * Since: 0.10.22 + */ +gboolean +gst_ghost_pad_construct (GstGhostPad * gpad) +{ + GstPadDirection dir, otherdir; + GstPadTemplate *templ; + GstPad *pad, *internal; + + g_return_val_if_fail (GST_IS_GHOST_PAD (gpad), FALSE); + g_return_val_if_fail (GST_GHOST_PAD_PRIVATE (gpad)->constructed == FALSE, + FALSE); + + g_object_get (gpad, "direction", &dir, "template", &templ, NULL); + + g_return_val_if_fail (dir != GST_PAD_UNKNOWN, FALSE); + + pad = GST_PAD (gpad); + + /* Set directional padfunctions for ghostpad */ + if (dir == GST_PAD_SINK) { + gst_pad_set_chain_function (pad, gst_proxy_pad_chain_default); + gst_pad_set_chain_list_function (pad, gst_proxy_pad_chain_list_default); + } else { + gst_pad_set_getrange_function (pad, gst_proxy_pad_getrange_default); + } + + /* link/unlink functions */ + gst_pad_set_link_function (pad, gst_ghost_pad_link_default); + gst_pad_set_unlink_function (pad, gst_ghost_pad_unlink_default); + + /* INTERNAL PAD, it always exists and is child of the ghostpad */ + otherdir = (dir == GST_PAD_SRC) ? GST_PAD_SINK : GST_PAD_SRC; + if (templ) { + internal = + g_object_new (GST_TYPE_PROXY_PAD, "name", NULL, + "direction", otherdir, "template", templ, NULL); + /* release ref obtained via g_object_get */ + gst_object_unref (templ); + } else { + internal = + g_object_new (GST_TYPE_PROXY_PAD, "name", NULL, + "direction", otherdir, NULL); + } + GST_PAD_UNSET_FLUSHING (internal); + + /* Set directional padfunctions for internal pad */ + if (dir == GST_PAD_SRC) { + gst_pad_set_chain_function (internal, gst_proxy_pad_chain_default); + gst_pad_set_chain_list_function (internal, + gst_proxy_pad_chain_list_default); + } else { + gst_pad_set_getrange_function (internal, gst_proxy_pad_getrange_default); + } + + GST_PROXY_LOCK (pad); + + /* now make the ghostpad a parent of the internal pad */ + if (!gst_object_set_parent (GST_OBJECT_CAST (internal), + GST_OBJECT_CAST (pad))) + goto parent_failed; + + /* The ghostpad is the parent of the internal pad and is the only object that + * can have a refcount on the internal pad. + * At this point, the GstGhostPad has a refcount of 1, and the internal pad has + * a refcount of 1. + * When the refcount of the GstGhostPad drops to 0, the ghostpad will dispose + * its refcount on the internal pad in the dispose method by un-parenting it. + * This is why we don't take extra refcounts in the assignments below + */ + GST_PROXY_PAD_INTERNAL (pad) = internal; + GST_PROXY_PAD_INTERNAL (internal) = pad; + + /* special activation functions for the internal pad */ + gst_pad_set_activatepull_function (internal, + gst_ghost_pad_internal_activate_pull_default); + gst_pad_set_activatepush_function (internal, + gst_ghost_pad_internal_activate_push_default); + + GST_PROXY_UNLOCK (pad); + + GST_GHOST_PAD_PRIVATE (gpad)->constructed = TRUE; + return TRUE; + + /* ERRORS */ +parent_failed: + { + GST_WARNING_OBJECT (gpad, "Could not set internal pad %s:%s", + GST_DEBUG_PAD_NAME (internal)); + g_critical ("Could not set internal pad %s:%s", + GST_DEBUG_PAD_NAME (internal)); + GST_PROXY_UNLOCK (pad); + gst_object_unref (internal); + return FALSE; + } +} + +static GstPad * +gst_ghost_pad_new_full (const gchar * name, GstPadDirection dir, + GstPadTemplate * templ) +{ + GstGhostPad *ret; + + g_return_val_if_fail (dir != GST_PAD_UNKNOWN, NULL); + + /* OBJECT CREATION */ + if (templ) { + ret = g_object_new (GST_TYPE_GHOST_PAD, "name", name, + "direction", dir, "template", templ, NULL); + } else { + ret = g_object_new (GST_TYPE_GHOST_PAD, "name", name, + "direction", dir, NULL); + } + + if (!gst_ghost_pad_construct (ret)) + goto construct_failed; + + return GST_PAD_CAST (ret); + +construct_failed: + /* already logged */ + gst_object_unref (ret); + return NULL; +} + +/** + * gst_ghost_pad_new_no_target: + * @name: (allow-none): the name of the new pad, or NULL to assign a default name. + * @dir: the direction of the ghostpad + * + * Create a new ghostpad without a target with the given direction. + * A target can be set on the ghostpad later with the + * gst_ghost_pad_set_target() function. + * + * The created ghostpad will not have a padtemplate. + * + * Returns: (transfer full): a new #GstPad, or NULL in case of an error. + */ +GstPad * +gst_ghost_pad_new_no_target (const gchar * name, GstPadDirection dir) +{ + GstPad *ret; + + g_return_val_if_fail (dir != GST_PAD_UNKNOWN, NULL); + + GST_LOG ("name:%s, direction:%d", GST_STR_NULL (name), dir); + + ret = gst_ghost_pad_new_full (name, dir, NULL); + + return ret; +} + +/** + * gst_ghost_pad_new: + * @name: (allow-none): the name of the new pad, or NULL to assign a default name + * @target: (transfer none): the pad to ghost. + * + * Create a new ghostpad with @target as the target. The direction will be taken + * from the target pad. @target must be unlinked. + * + * Will ref the target. + * + * Returns: (transfer full): a new #GstPad, or NULL in case of an error. + */ +GstPad * +gst_ghost_pad_new (const gchar * name, GstPad * target) +{ + GstPad *ret; + + g_return_val_if_fail (GST_IS_PAD (target), NULL); + g_return_val_if_fail (!gst_pad_is_linked (target), NULL); + + GST_LOG ("name:%s, target:%s:%s", GST_STR_NULL (name), + GST_DEBUG_PAD_NAME (target)); + + if ((ret = gst_ghost_pad_new_no_target (name, GST_PAD_DIRECTION (target)))) + if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (ret), target)) + goto set_target_failed; + + return ret; + + /* ERRORS */ +set_target_failed: + { + GST_WARNING_OBJECT (ret, "failed to set target %s:%s", + GST_DEBUG_PAD_NAME (target)); + gst_object_unref (ret); + return NULL; + } +} + +/** + * gst_ghost_pad_new_from_template: + * @name: (allow-none): the name of the new pad, or NULL to assign a default name. + * @target: (transfer none): the pad to ghost. + * @templ: (transfer none): the #GstPadTemplate to use on the ghostpad. + * + * Create a new ghostpad with @target as the target. The direction will be taken + * from the target pad. The template used on the ghostpad will be @template. + * + * Will ref the target. + * + * Returns: (transfer full): a new #GstPad, or NULL in case of an error. + * + * Since: 0.10.10 + */ + +GstPad * +gst_ghost_pad_new_from_template (const gchar * name, GstPad * target, + GstPadTemplate * templ) +{ + GstPad *ret; + + g_return_val_if_fail (GST_IS_PAD (target), NULL); + g_return_val_if_fail (!gst_pad_is_linked (target), NULL); + g_return_val_if_fail (templ != NULL, NULL); + g_return_val_if_fail (GST_PAD_TEMPLATE_DIRECTION (templ) == + GST_PAD_DIRECTION (target), NULL); + + GST_LOG ("name:%s, target:%s:%s, templ:%p", GST_STR_NULL (name), + GST_DEBUG_PAD_NAME (target), templ); + + if ((ret = gst_ghost_pad_new_full (name, GST_PAD_DIRECTION (target), templ))) + if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (ret), target)) + goto set_target_failed; + + return ret; + + /* ERRORS */ +set_target_failed: + { + GST_WARNING_OBJECT (ret, "failed to set target %s:%s", + GST_DEBUG_PAD_NAME (target)); + gst_object_unref (ret); + return NULL; + } +} + +/** + * gst_ghost_pad_new_no_target_from_template: + * @name: (allow-none): the name of the new pad, or NULL to assign a default name + * @templ: (transfer none): the #GstPadTemplate to create the ghostpad from. + * + * Create a new ghostpad based on @templ, without setting a target. The + * direction will be taken from the @templ. + * + * Returns: (transfer full): a new #GstPad, or NULL in case of an error. + * + * Since: 0.10.10 + */ +GstPad * +gst_ghost_pad_new_no_target_from_template (const gchar * name, + GstPadTemplate * templ) +{ + GstPad *ret; + + g_return_val_if_fail (templ != NULL, NULL); + + ret = + gst_ghost_pad_new_full (name, GST_PAD_TEMPLATE_DIRECTION (templ), templ); + + return ret; +} + +/** + * gst_ghost_pad_get_target: + * @gpad: the #GstGhostPad + * + * Get the target pad of @gpad. Unref target pad after usage. + * + * Returns: (transfer full): the target #GstPad, can be NULL if the ghostpad + * has no target set. Unref target pad after usage. + */ +GstPad * +gst_ghost_pad_get_target (GstGhostPad * gpad) +{ + GstPad *ret; + + g_return_val_if_fail (GST_IS_GHOST_PAD (gpad), NULL); + + ret = gst_proxy_pad_get_target (GST_PAD_CAST (gpad)); + + GST_DEBUG_OBJECT (gpad, "get target %s:%s", GST_DEBUG_PAD_NAME (ret)); + + return ret; +} + +/** + * gst_ghost_pad_set_target: + * @gpad: the #GstGhostPad + * @newtarget: (transfer none) (allow-none): the new pad target + * + * Set the new target of the ghostpad @gpad. Any existing target + * is unlinked and links to the new target are established. if @newtarget is + * NULL the target will be cleared. + * + * Returns: (transfer full): TRUE if the new target could be set. This function + * can return FALSE when the internal pads could not be linked. + */ +gboolean +gst_ghost_pad_set_target (GstGhostPad * gpad, GstPad * newtarget) +{ + GstPad *internal; + GstPad *oldtarget; + gboolean result; + GstPadLinkReturn lret; + + g_return_val_if_fail (GST_IS_GHOST_PAD (gpad), FALSE); + g_return_val_if_fail (GST_PAD_CAST (gpad) != newtarget, FALSE); + g_return_val_if_fail (newtarget != GST_PROXY_PAD_INTERNAL (gpad), FALSE); + + /* no need for locking, the internal pad's lifecycle is directly linked to the + * ghostpad's */ + internal = GST_PROXY_PAD_INTERNAL (gpad); + + if (newtarget) + GST_DEBUG_OBJECT (gpad, "set target %s:%s", GST_DEBUG_PAD_NAME (newtarget)); + else + GST_DEBUG_OBJECT (gpad, "clearing target"); + + /* clear old target */ + GST_PROXY_LOCK (gpad); + if ((oldtarget = GST_PROXY_PAD_TARGET (gpad))) { + + GST_PROXY_PAD_RETARGET (internal) = TRUE; + + /* unlink internal pad */ + if (GST_PAD_IS_SRC (internal)) + gst_pad_unlink (internal, oldtarget); + else + gst_pad_unlink (oldtarget, internal); + + GST_PROXY_PAD_RETARGET (internal) = FALSE; + } + + result = gst_proxy_pad_set_target_unlocked (GST_PAD_CAST (gpad), newtarget); + GST_PROXY_UNLOCK (gpad); + + if (result && newtarget) { + /* and link to internal pad without any checks */ + GST_DEBUG_OBJECT (gpad, "connecting internal pad to target"); + + if (GST_PAD_IS_SRC (internal)) + lret = + gst_pad_link_full (internal, newtarget, GST_PAD_LINK_CHECK_NOTHING); + else + lret = + gst_pad_link_full (newtarget, internal, GST_PAD_LINK_CHECK_NOTHING); + + if (lret != GST_PAD_LINK_OK) + goto link_failed; + } + + return result; + + /* ERRORS */ +link_failed: + { + GST_WARNING_OBJECT (gpad, "could not link internal and target, reason:%d", + lret); + /* and unset target again */ + GST_PROXY_LOCK (gpad); + gst_proxy_pad_set_target_unlocked (GST_PAD_CAST (gpad), NULL); + GST_PROXY_UNLOCK (gpad); + return FALSE; + } +} diff --git a/gst/gstghostpad.h b/gst/gstghostpad.h new file mode 100644 index 0000000..8eb010a --- /dev/null +++ b/gst/gstghostpad.h @@ -0,0 +1,133 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * 2005 Andy Wingo <wingo@pobox.com> + * + * gstghostpad.h: Proxy pads + * + * 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_GHOST_PAD_H__ +#define __GST_GHOST_PAD_H__ + + +#include <gst/gstpad.h> + + +G_BEGIN_DECLS + +#define GST_TYPE_PROXY_PAD (gst_proxy_pad_get_type ()) +#define GST_IS_PROXY_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PROXY_PAD)) +#define GST_IS_PROXY_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PROXY_PAD)) +#define GST_PROXY_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PROXY_PAD, GstProxyPad)) +#define GST_PROXY_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PROXY_PAD, GstProxyPadClass)) + +typedef struct _GstProxyPad GstProxyPad; +typedef struct _GstProxyPadPrivate GstProxyPadPrivate; +typedef struct _GstProxyPadClass GstProxyPadClass; + +struct _GstProxyPad +{ + GstPad pad; + + /*< private >*/ + GstProxyPadPrivate *priv; +}; + +struct _GstProxyPadClass +{ + GstPadClass parent_class; + + /*< private >*/ + gpointer _gst_reserved[1]; +}; + +GType gst_proxy_pad_get_type (void); + +GstProxyPad* gst_proxy_pad_get_internal (GstProxyPad *pad); + + +const GstQueryType* gst_proxy_pad_query_type_default (GstPad *pad); +gboolean gst_proxy_pad_event_default (GstPad *pad, GstEvent *event); +gboolean gst_proxy_pad_query_default (GstPad *pad, GstQuery *query); +GstIterator* gst_proxy_pad_iterate_internal_links_default (GstPad *pad); +GstFlowReturn gst_proxy_pad_chain_default (GstPad *pad, GstBuffer *buffer); +GstFlowReturn gst_proxy_pad_chain_list_default (GstPad *pad, GstBufferList *list); +GstFlowReturn gst_proxy_pad_getrange_default (GstPad *pad, guint64 offset, guint size, GstBuffer **buffer); +GstCaps* gst_proxy_pad_getcaps_default (GstPad *pad, GstCaps * filter); +gboolean gst_proxy_pad_acceptcaps_default (GstPad *pad, GstCaps *caps); +void gst_proxy_pad_fixatecaps_default (GstPad *pad, GstCaps *caps); +void gst_proxy_pad_unlink_default (GstPad * pad); + +#define GST_TYPE_GHOST_PAD (gst_ghost_pad_get_type ()) +#define GST_IS_GHOST_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_GHOST_PAD)) +#define GST_IS_GHOST_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_GHOST_PAD)) +#define GST_GHOST_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_GHOST_PAD, GstGhostPad)) +#define GST_GHOST_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_GHOST_PAD, GstGhostPadClass)) +#define GST_GHOST_PAD_CAST(obj) ((GstGhostPad*)(obj)) + +/** + * GstGhostPad: + * + * Opaque #GstGhostPad structure. + */ +typedef struct _GstGhostPad GstGhostPad; +typedef struct _GstGhostPadPrivate GstGhostPadPrivate; +typedef struct _GstGhostPadClass GstGhostPadClass; + +struct _GstGhostPad +{ + GstProxyPad pad; + + /*< private >*/ + GstGhostPadPrivate *priv; +}; + +struct _GstGhostPadClass +{ + GstProxyPadClass parent_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + + +GType gst_ghost_pad_get_type (void); + +GstPad* gst_ghost_pad_new (const gchar *name, GstPad *target); +GstPad* gst_ghost_pad_new_no_target (const gchar *name, GstPadDirection dir); + +GstPad* gst_ghost_pad_new_from_template (const gchar *name, GstPad * target, GstPadTemplate * templ); +GstPad* gst_ghost_pad_new_no_target_from_template (const gchar *name, GstPadTemplate * templ); + +GstPad* gst_ghost_pad_get_target (GstGhostPad *gpad); +gboolean gst_ghost_pad_set_target (GstGhostPad *gpad, GstPad *newtarget); + +gboolean gst_ghost_pad_construct (GstGhostPad *gpad); + +void gst_ghost_pad_unlink_default (GstPad * pad); +GstPadLinkReturn gst_ghost_pad_link_default (GstPad * pad, GstPad * peer); +gboolean gst_ghost_pad_activate_pull_default (GstPad * pad, gboolean active); +gboolean gst_ghost_pad_activate_push_default (GstPad * pad, gboolean active); + +gboolean gst_ghost_pad_internal_activate_push_default (GstPad * pad, gboolean active); +gboolean gst_ghost_pad_internal_activate_pull_default (GstPad * pad, gboolean active); + +G_END_DECLS + +#endif /* __GST_GHOST_PAD_H__ */ diff --git a/gst/gstindex.c b/gst/gstindex.c new file mode 100644 index 0000000..d168eee --- /dev/null +++ b/gst/gstindex.c @@ -0,0 +1,1009 @@ +/* GStreamer + * Copyright (C) 2001 RidgeRun (http://www.ridgerun.com/) + * Written by Erik Walthinsen <omega@ridgerun.com> + * + * gstindex.c: Index for mappings and other data + * + * 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:gstindex + * @short_description: Generate indexes on objects + * @see_also: #GstIndexFactory + * + * GstIndex is used to generate a stream index of one or more elements + * in a pipeline. + * + * Elements will overload the set_index and get_index virtual methods in + * #GstElement. When streaming data, the element will add index entries if it + * has an index set. + * + * Each element that adds to the index will do that using a writer_id. The + * writer_id is obtained from gst_index_get_writer_id(). + * + * The application that wants to index the stream will create a new index object + * using gst_index_new() or gst_index_factory_make(). The index is assigned to a + * specific element, a bin or the whole pipeline. This will cause indexable + * elements to add entires to the index while playing. + */ + +/* FIXME: complete gobject annotations */ +/* FIXME-0.11: cleanup API + * - no one seems to use GstIndexGroup, GstIndexCertainty + * + * - the API for application to use the index is mostly missing + * - apps need to get a list of writers + * - apps need to be able to iterate over each writers index entry collection + * - gst_index_get_assoc_entry() should pass ownership + * - the GstIndexEntry structure is large and contains repetitive information + * - we want to allow Indexers to implement a saner storage and create + * GstIndexEntries on demand (the app has to free them), might even make + * sense to ask the app to provide a ptr and fill it. + */ + +#include "gst_private.h" + +#include "gstinfo.h" +#include "gstindex.h" +#include "gstindexfactory.h" +#include "gstmarshal.h" +#include "gstregistry.h" +/* for constructing an entry name */ +#include "gstelement.h" +#include "gstpad.h" +#include "gstinfo.h" + +/* Index signals and args */ +enum +{ + ENTRY_ADDED, + LAST_SIGNAL +}; + +enum +{ + ARG_0, + ARG_RESOLVER + /* FILL ME */ +}; + +GST_DEBUG_CATEGORY_STATIC (index_debug); +#define GST_CAT_DEFAULT index_debug + +static void gst_index_finalize (GObject * object); + +static void gst_index_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_index_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstIndexGroup *gst_index_group_new (guint groupnum); +static void gst_index_group_free (GstIndexGroup * group); + +static gboolean gst_index_path_resolver (GstIndex * index, GstObject * writer, + gchar ** writer_string, gpointer data); +static gboolean gst_index_gtype_resolver (GstIndex * index, GstObject * writer, + gchar ** writer_string, gpointer data); +static void gst_index_add_entry (GstIndex * index, GstIndexEntry * entry); + +static GstObject *parent_class = NULL; +static guint gst_index_signals[LAST_SIGNAL] = { 0 }; + +typedef struct +{ + GstIndexResolverMethod method; + GstIndexResolver resolver; + gpointer user_data; +} +ResolverEntry; + +static const ResolverEntry resolvers[] = { + {GST_INDEX_RESOLVER_CUSTOM, NULL, NULL}, + {GST_INDEX_RESOLVER_GTYPE, gst_index_gtype_resolver, NULL}, + {GST_INDEX_RESOLVER_PATH, gst_index_path_resolver, NULL}, +}; + +#define GST_TYPE_INDEX_RESOLVER (gst_index_resolver_get_type()) +static GType +gst_index_resolver_get_type (void) +{ + static GType index_resolver_type = 0; + static const GEnumValue index_resolver[] = { + {GST_INDEX_RESOLVER_CUSTOM, "GST_INDEX_RESOLVER_CUSTOM", "custom"}, + {GST_INDEX_RESOLVER_GTYPE, "GST_INDEX_RESOLVER_GTYPE", "gtype"}, + {GST_INDEX_RESOLVER_PATH, "GST_INDEX_RESOLVER_PATH", "path"}, + {0, NULL, NULL}, + }; + + if (!index_resolver_type) { + index_resolver_type = + g_enum_register_static ("GstIndexResolver", index_resolver); + } + return index_resolver_type; +} + +GType +gst_index_entry_get_type (void) +{ + static GType index_entry_type = 0; + + if (!index_entry_type) { + index_entry_type = g_boxed_type_register_static ("GstIndexEntry", + (GBoxedCopyFunc) gst_index_entry_copy, + (GBoxedFreeFunc) gst_index_entry_free); + } + return index_entry_type; +} + +#define _do_init \ +{ \ + GST_DEBUG_CATEGORY_INIT (index_debug, "GST_INDEX", GST_DEBUG_BOLD, \ + "Generic indexing support"); \ +} + +G_DEFINE_TYPE_WITH_CODE (GstIndex, gst_index, GST_TYPE_OBJECT, _do_init); + +static void +gst_index_class_init (GstIndexClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + /** + * GstIndex::entry-added + * @gstindex: the object which received the signal. + * @arg1: The entry added to the index. + * + * Is emitted when a new entry is added to the index. + */ + gst_index_signals[ENTRY_ADDED] = + g_signal_new ("entry-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstIndexClass, entry_added), NULL, NULL, + gst_marshal_VOID__BOXED, G_TYPE_NONE, 1, GST_TYPE_INDEX_ENTRY); + + gobject_class->set_property = gst_index_set_property; + gobject_class->get_property = gst_index_get_property; + gobject_class->finalize = gst_index_finalize; + + g_object_class_install_property (gobject_class, ARG_RESOLVER, + g_param_spec_enum ("resolver", "Resolver", + "Select a predefined object to string mapper", + GST_TYPE_INDEX_RESOLVER, GST_INDEX_RESOLVER_PATH, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_index_init (GstIndex * index) +{ + index->curgroup = gst_index_group_new (0); + index->maxgroup = 0; + index->groups = g_list_prepend (NULL, index->curgroup); + + index->writers = g_hash_table_new (NULL, NULL); + index->last_id = 0; + + index->method = GST_INDEX_RESOLVER_PATH; + index->resolver = resolvers[index->method].resolver; + index->resolver_user_data = resolvers[index->method].user_data; + + GST_OBJECT_FLAG_SET (index, GST_INDEX_WRITABLE); + GST_OBJECT_FLAG_SET (index, GST_INDEX_READABLE); + + GST_DEBUG ("created new index"); +} + +static void +gst_index_free_writer (gpointer key, gpointer value, gpointer user_data) +{ + GstIndexEntry *entry = (GstIndexEntry *) value; + + if (entry) { + gst_index_entry_free (entry); + } +} + +static void +gst_index_finalize (GObject * object) +{ + GstIndex *index = GST_INDEX (object); + + if (index->groups) { + g_list_foreach (index->groups, (GFunc) gst_index_group_free, NULL); + g_list_free (index->groups); + index->groups = NULL; + } + + if (index->writers) { + g_hash_table_foreach (index->writers, gst_index_free_writer, NULL); + g_hash_table_destroy (index->writers); + index->writers = NULL; + } + + if (index->filter_user_data && index->filter_user_data_destroy) + index->filter_user_data_destroy (index->filter_user_data); + + if (index->resolver_user_data && index->resolver_user_data_destroy) + index->resolver_user_data_destroy (index->resolver_user_data); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_index_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstIndex *index; + + index = GST_INDEX (object); + + switch (prop_id) { + case ARG_RESOLVER: + index->method = (GstIndexResolverMethod) g_value_get_enum (value); + index->resolver = resolvers[index->method].resolver; + index->resolver_user_data = resolvers[index->method].user_data; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_index_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstIndex *index; + + index = GST_INDEX (object); + + switch (prop_id) { + case ARG_RESOLVER: + g_value_set_enum (value, index->method); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstIndexGroup * +gst_index_group_new (guint groupnum) +{ + GstIndexGroup *indexgroup = g_slice_new (GstIndexGroup); + + indexgroup->groupnum = groupnum; + indexgroup->entries = NULL; + indexgroup->certainty = GST_INDEX_UNKNOWN; + indexgroup->peergroup = -1; + + GST_DEBUG ("created new index group %d", groupnum); + + return indexgroup; +} + +static void +gst_index_group_free (GstIndexGroup * group) +{ + g_slice_free (GstIndexGroup, group); +} + +/** + * gst_index_new: + * + * Create a new dummy index object. Use gst_element_set_index() to assign that + * to an element or pipeline. This index is not storing anything, but will + * still emit e.g. the #GstIndex::entry-added signal. + * + * Returns: (transfer full): a new index object + */ +GstIndex * +gst_index_new (void) +{ + GstIndex *index; + + index = g_object_newv (gst_index_get_type (), 0, NULL); + + return index; +} + +/** + * gst_index_commit: + * @index: the index to commit + * @id: the writer that commited the index + * + * Tell the index that the writer with the given id is done + * with this index and is not going to write any more entries + * to it. + */ +void +gst_index_commit (GstIndex * index, gint id) +{ + GstIndexClass *iclass; + + iclass = GST_INDEX_GET_CLASS (index); + + if (iclass->commit) + iclass->commit (index, id); +} + + +/** + * gst_index_get_group: + * @index: the index to get the current group from + * + * Get the id of the current group. + * + * Returns: the id of the current group. + */ +gint +gst_index_get_group (GstIndex * index) +{ + return index->curgroup->groupnum; +} + +/** + * gst_index_new_group: + * @index: the index to create the new group in + * + * Create a new group for the given index. It will be + * set as the current group. + * + * Returns: the id of the newly created group. + */ +gint +gst_index_new_group (GstIndex * index) +{ + index->curgroup = gst_index_group_new (++index->maxgroup); + index->groups = g_list_append (index->groups, index->curgroup); + GST_DEBUG ("created new group %d in index", index->maxgroup); + return index->maxgroup; +} + +/** + * gst_index_set_group: + * @index: the index to set the new group in + * @groupnum: the groupnumber to set + * + * Set the current groupnumber to the given argument. + * + * Returns: TRUE if the operation succeeded, FALSE if the group + * did not exist. + */ +gboolean +gst_index_set_group (GstIndex * index, gint groupnum) +{ + GList *list; + GstIndexGroup *indexgroup; + + /* first check for null change */ + if (groupnum == index->curgroup->groupnum) + return TRUE; + + /* else search for the proper group */ + list = index->groups; + while (list) { + indexgroup = (GstIndexGroup *) (list->data); + list = g_list_next (list); + if (indexgroup->groupnum == groupnum) { + index->curgroup = indexgroup; + GST_DEBUG ("switched to index group %d", indexgroup->groupnum); + return TRUE; + } + } + + /* couldn't find the group in question */ + GST_DEBUG ("couldn't find index group %d", groupnum); + return FALSE; +} + +/** + * gst_index_set_certainty: + * @index: the index to set the certainty on + * @certainty: the certainty to set + * + * Set the certainty of the given index. + */ +void +gst_index_set_certainty (GstIndex * index, GstIndexCertainty certainty) +{ + index->curgroup->certainty = certainty; +} + +/** + * gst_index_get_certainty: + * @index: the index to get the certainty of + * + * Get the certainty of the given index. + * + * Returns: the certainty of the index. + */ +GstIndexCertainty +gst_index_get_certainty (GstIndex * index) +{ + return index->curgroup->certainty; +} + +/** + * gst_index_set_filter: + * @index: the index to register the filter on + * @filter: the filter to register + * @user_data: data passed to the filter function + * + * Lets the app register a custom filter function so that + * it can select what entries should be stored in the index. + */ +void +gst_index_set_filter (GstIndex * index, + GstIndexFilter filter, gpointer user_data) +{ + g_return_if_fail (GST_IS_INDEX (index)); + + gst_index_set_filter_full (index, filter, user_data, NULL); +} + +/** + * gst_index_set_filter_full: + * @index: the index to register the filter on + * @filter: the filter to register + * @user_data: data passed to the filter function + * @user_data_destroy: function to call when @user_data is unset + * + * Lets the app register a custom filter function so that + * it can select what entries should be stored in the index. + */ +void +gst_index_set_filter_full (GstIndex * index, + GstIndexFilter filter, gpointer user_data, GDestroyNotify user_data_destroy) +{ + g_return_if_fail (GST_IS_INDEX (index)); + + if (index->filter_user_data && index->filter_user_data_destroy) + index->filter_user_data_destroy (index->filter_user_data); + + index->filter = filter; + index->filter_user_data = user_data; + index->filter_user_data_destroy = user_data_destroy; +} + +/** + * gst_index_set_resolver: + * @index: the index to register the resolver on + * @resolver: the resolver to register + * @user_data: data passed to the resolver function + * + * Lets the app register a custom function to map index + * ids to writer descriptions. + */ +void +gst_index_set_resolver (GstIndex * index, + GstIndexResolver resolver, gpointer user_data) +{ + gst_index_set_resolver_full (index, resolver, user_data, NULL); +} + +/** + * gst_index_set_resolver_full: + * @index: the index to register the resolver on + * @resolver: the resolver to register + * @user_data: data passed to the resolver function + * @user_data_destroy: destroy function for @user_data + * + * Lets the app register a custom function to map index + * ids to writer descriptions. + * + * Since: 0.10.18 + */ +void +gst_index_set_resolver_full (GstIndex * index, GstIndexResolver resolver, + gpointer user_data, GDestroyNotify user_data_destroy) +{ + g_return_if_fail (GST_IS_INDEX (index)); + + if (index->resolver_user_data && index->resolver_user_data_destroy) + index->resolver_user_data_destroy (index->resolver_user_data); + + index->resolver = resolver; + index->resolver_user_data = user_data; + index->resolver_user_data_destroy = user_data_destroy; + index->method = GST_INDEX_RESOLVER_CUSTOM; +} + +/** + * gst_index_entry_copy: + * @entry: the entry to copy + * + * Copies an entry and returns the result. + * + * Free-function: gst_index_entry_free + * + * Returns: (transfer full): a newly allocated #GstIndexEntry. + */ +GstIndexEntry * +gst_index_entry_copy (GstIndexEntry * entry) +{ + GstIndexEntry *new_entry = g_slice_new (GstIndexEntry); + + memcpy (new_entry, entry, sizeof (GstIndexEntry)); + return new_entry; +} + +/** + * gst_index_entry_free: + * @entry: (transfer full): the entry to free + * + * Free the memory used by the given entry. + */ +void +gst_index_entry_free (GstIndexEntry * entry) +{ + switch (entry->type) { + case GST_INDEX_ENTRY_ID: + if (entry->data.id.description) { + g_free (entry->data.id.description); + entry->data.id.description = NULL; + } + break; + case GST_INDEX_ENTRY_ASSOCIATION: + if (entry->data.assoc.assocs) { + g_free (entry->data.assoc.assocs); + entry->data.assoc.assocs = NULL; + } + break; + case GST_INDEX_ENTRY_OBJECT: + break; + case GST_INDEX_ENTRY_FORMAT: + break; + } + + g_slice_free (GstIndexEntry, entry); +} + +/** + * gst_index_add_format: + * @index: the index to add the entry to + * @id: the id of the index writer + * @format: the format to add to the index + * + * Adds a format entry into the index. This function is + * used to map dynamic GstFormat ids to their original + * format key. + * + * Free-function: gst_index_entry_free + * + * Returns: (transfer full): a pointer to the newly added entry in the index. + */ +GstIndexEntry * +gst_index_add_format (GstIndex * index, gint id, GstFormat format) +{ + GstIndexEntry *entry; + const GstFormatDefinition *def; + + g_return_val_if_fail (GST_IS_INDEX (index), NULL); + g_return_val_if_fail (format != 0, NULL); + + if (!GST_INDEX_IS_WRITABLE (index) || id == -1) + return NULL; + + entry = g_slice_new (GstIndexEntry); + entry->type = GST_INDEX_ENTRY_FORMAT; + entry->id = id; + entry->data.format.format = format; + + def = gst_format_get_details (format); + entry->data.format.key = def->nick; + + gst_index_add_entry (index, entry); + + return entry; +} + +/** + * gst_index_add_id: + * @index: the index to add the entry to + * @id: the id of the index writer + * @description: the description of the index writer + * + * Add an id entry into the index. + * + * Returns: a pointer to the newly added entry in the index. + */ +GstIndexEntry * +gst_index_add_id (GstIndex * index, gint id, gchar * description) +{ + GstIndexEntry *entry; + + g_return_val_if_fail (GST_IS_INDEX (index), NULL); + g_return_val_if_fail (description != NULL, NULL); + + if (!GST_INDEX_IS_WRITABLE (index) || id == -1) + return NULL; + + entry = g_slice_new (GstIndexEntry); + entry->type = GST_INDEX_ENTRY_ID; + entry->id = id; + entry->data.id.description = description; + + gst_index_add_entry (index, entry); + + return entry; +} + +static gboolean +gst_index_path_resolver (GstIndex * index, GstObject * writer, + gchar ** writer_string, gpointer data) +{ + *writer_string = gst_object_get_path_string (writer); + + return TRUE; +} + +static gboolean +gst_index_gtype_resolver (GstIndex * index, GstObject * writer, + gchar ** writer_string, gpointer data) +{ + g_return_val_if_fail (writer != NULL, FALSE); + + if (GST_IS_PAD (writer)) { + GstObject *element = gst_object_get_parent (GST_OBJECT (writer)); + gchar *name; + + name = gst_object_get_name (writer); + if (element) { + *writer_string = g_strdup_printf ("%s.%s", + G_OBJECT_TYPE_NAME (element), name); + gst_object_unref (element); + } else { + *writer_string = name; + name = NULL; + } + + g_free (name); + + } else { + *writer_string = g_strdup (G_OBJECT_TYPE_NAME (writer)); + } + + return TRUE; +} + +/** + * gst_index_get_writer_id: + * @index: the index to get a unique write id for + * @writer: the GstObject to allocate an id for + * @id: a pointer to a gint to hold the id + * + * Before entries can be added to the index, a writer + * should obtain a unique id. The methods to add new entries + * to the index require this id as an argument. + * + * The application can implement a custom function to map the writer object + * to a string. That string will be used to register or look up an id + * in the index. + * + * <note> + * The caller must not hold @writer's #GST_OBJECT_LOCK, as the default + * resolver may call functions that take the object lock as well, and + * the lock is not recursive. + * </note> + * + * Returns: TRUE if the writer would be mapped to an id. + */ +gboolean +gst_index_get_writer_id (GstIndex * index, GstObject * writer, gint * id) +{ + gchar *writer_string = NULL; + GstIndexEntry *entry; + GstIndexClass *iclass; + gboolean success = FALSE; + + g_return_val_if_fail (GST_IS_INDEX (index), FALSE); + g_return_val_if_fail (GST_IS_OBJECT (writer), FALSE); + g_return_val_if_fail (id, FALSE); + + *id = -1; + + /* first try to get a previously cached id */ + entry = g_hash_table_lookup (index->writers, writer); + if (entry == NULL) { + + iclass = GST_INDEX_GET_CLASS (index); + + /* let the app make a string */ + if (index->resolver) { + gboolean res; + + res = + index->resolver (index, writer, &writer_string, + index->resolver_user_data); + if (!res) + return FALSE; + } else { + g_warning ("no resolver found"); + return FALSE; + } + + /* if the index has a resolver, make it map this string to an id */ + if (iclass->get_writer_id) { + success = iclass->get_writer_id (index, id, writer_string); + } + /* if the index could not resolve, we allocate one ourselves */ + if (!success) { + *id = ++index->last_id; + } + + entry = gst_index_add_id (index, *id, writer_string); + if (!entry) { + /* index is probably not writable, make an entry anyway + * to keep it in our cache */ + entry = g_slice_new (GstIndexEntry); + entry->type = GST_INDEX_ENTRY_ID; + entry->id = *id; + entry->data.id.description = writer_string; + } + g_hash_table_insert (index->writers, writer, entry); + } else { + *id = entry->id; + } + + return TRUE; +} + +static void +gst_index_add_entry (GstIndex * index, GstIndexEntry * entry) +{ + GstIndexClass *iclass; + + iclass = GST_INDEX_GET_CLASS (index); + + if (iclass->add_entry) { + iclass->add_entry (index, entry); + } + + g_signal_emit (index, gst_index_signals[ENTRY_ADDED], 0, entry); +} + +/** + * gst_index_add_associationv: + * @index: the index to add the entry to + * @id: the id of the index writer + * @flags: optinal flags for this entry + * @n: number of associations + * @list: list of associations + * + * Associate given format/value pairs with each other. + * + * Returns: a pointer to the newly added entry in the index. + */ +GstIndexEntry * +gst_index_add_associationv (GstIndex * index, gint id, GstAssocFlags flags, + gint n, const GstIndexAssociation * list) +{ + GstIndexEntry *entry; + + g_return_val_if_fail (n > 0, NULL); + g_return_val_if_fail (list != NULL, NULL); + g_return_val_if_fail (GST_IS_INDEX (index), NULL); + + if (!GST_INDEX_IS_WRITABLE (index) || id == -1) + return NULL; + + entry = g_slice_new (GstIndexEntry); + + entry->type = GST_INDEX_ENTRY_ASSOCIATION; + entry->id = id; + entry->data.assoc.flags = flags; + entry->data.assoc.assocs = g_memdup (list, sizeof (GstIndexAssociation) * n); + entry->data.assoc.nassocs = n; + + gst_index_add_entry (index, entry); + + return entry; +} + +/** + * gst_index_add_association: + * @index: the index to add the entry to + * @id: the id of the index writer + * @flags: optinal flags for this entry + * @format: the format of the value + * @value: the value + * @...: other format/value pairs or 0 to end the list + * + * Associate given format/value pairs with each other. + * Be sure to pass gint64 values to this functions varargs, + * you might want to use a gint64 cast to be sure. + * + * Returns: a pointer to the newly added entry in the index. + */ +GstIndexEntry * +gst_index_add_association (GstIndex * index, gint id, GstAssocFlags flags, + GstFormat format, gint64 value, ...) +{ + va_list args; + GstIndexEntry *entry; + GstIndexAssociation *list; + gint n_assocs = 0; + GstFormat cur_format; + GArray *array; + + g_return_val_if_fail (GST_IS_INDEX (index), NULL); + g_return_val_if_fail (format != 0, NULL); + + if (!GST_INDEX_IS_WRITABLE (index) || id == -1) + return NULL; + + array = g_array_new (FALSE, FALSE, sizeof (GstIndexAssociation)); + + { + GstIndexAssociation a; + + a.format = format; + a.value = value; + n_assocs = 1; + g_array_append_val (array, a); + } + + va_start (args, value); + + while ((cur_format = va_arg (args, GstFormat))) { + GstIndexAssociation a; + + a.format = cur_format; + a.value = va_arg (args, gint64); + n_assocs++; + g_array_append_val (array, a); + } + + va_end (args); + + list = (GstIndexAssociation *) g_array_free (array, FALSE); + + entry = gst_index_add_associationv (index, id, flags, n_assocs, list); + g_free (list); + + return entry; +} + +/** + * gst_index_add_object: + * @index: the index to add the object to + * @id: the id of the index writer + * @key: a key for the object + * @type: the GType of the object + * @object: a pointer to the object to add + * + * Add the given object to the index with the given key. + * + * This function is not yet implemented. + * + * Returns: a pointer to the newly added entry in the index. + */ +GstIndexEntry * +gst_index_add_object (GstIndex * index, gint id, gchar * key, + GType type, gpointer object) +{ + if (!GST_INDEX_IS_WRITABLE (index) || id == -1) + return NULL; + + return NULL; +} + +static gint +gst_index_compare_func (gconstpointer a, gconstpointer b, gpointer user_data) +{ + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} + +/** + * gst_index_get_assoc_entry: + * @index: the index to search + * @id: the id of the index writer + * @method: The lookup method to use + * @flags: Flags for the entry + * @format: the format of the value + * @value: the value to find + * + * Finds the given format/value in the index + * + * Returns: the entry associated with the value or NULL if the + * value was not found. + */ +GstIndexEntry * +gst_index_get_assoc_entry (GstIndex * index, gint id, + GstIndexLookupMethod method, GstAssocFlags flags, + GstFormat format, gint64 value) +{ + g_return_val_if_fail (GST_IS_INDEX (index), NULL); + + if (id == -1) + return NULL; + + return gst_index_get_assoc_entry_full (index, id, method, flags, format, + value, gst_index_compare_func, NULL); +} + +/** + * gst_index_get_assoc_entry_full: + * @index: the index to search + * @id: the id of the index writer + * @method: The lookup method to use + * @flags: Flags for the entry + * @format: the format of the value + * @value: the value to find + * @func: the function used to compare entries + * @user_data: user data passed to the compare function + * + * Finds the given format/value in the index with the given + * compare function and user_data. + * + * Returns: the entry associated with the value or NULL if the + * value was not found. + */ +GstIndexEntry * +gst_index_get_assoc_entry_full (GstIndex * index, gint id, + GstIndexLookupMethod method, GstAssocFlags flags, + GstFormat format, gint64 value, GCompareDataFunc func, gpointer user_data) +{ + GstIndexClass *iclass; + + g_return_val_if_fail (GST_IS_INDEX (index), NULL); + + if (id == -1) + return NULL; + + iclass = GST_INDEX_GET_CLASS (index); + + if (iclass->get_assoc_entry) + return iclass->get_assoc_entry (index, id, method, flags, format, value, + func, user_data); + + return NULL; +} + +/** + * gst_index_entry_assoc_map: + * @entry: the index to search + * @format: the format of the value the find + * @value: a pointer to store the value + * + * Gets alternative formats associated with the indexentry. + * + * Returns: TRUE if there was a value associated with the given + * format. + */ +gboolean +gst_index_entry_assoc_map (GstIndexEntry * entry, + GstFormat format, gint64 * value) +{ + gint i; + + g_return_val_if_fail (entry != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + for (i = 0; i < GST_INDEX_NASSOCS (entry); i++) { + if (GST_INDEX_ASSOC_FORMAT (entry, i) == format) { + *value = GST_INDEX_ASSOC_VALUE (entry, i); + return TRUE; + } + } + return FALSE; +} diff --git a/gst/gstindex.h b/gst/gstindex.h new file mode 100644 index 0000000..9447a38 --- /dev/null +++ b/gst/gstindex.h @@ -0,0 +1,424 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * + * gstindex.h: Header for GstIndex, base class to handle efficient + * storage or caching of seeking information. + * + * 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_INDEX_H__ +#define __GST_INDEX_H__ + +#include <gst/gstobject.h> +#include <gst/gstformat.h> +#include <gst/gstpluginfeature.h> + +G_BEGIN_DECLS + +#define GST_TYPE_INDEX (gst_index_get_type ()) +#define GST_INDEX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_INDEX, GstIndex)) +#define GST_IS_INDEX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_INDEX)) +#define GST_INDEX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_INDEX, GstIndexClass)) +#define GST_IS_INDEX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_INDEX)) +#define GST_INDEX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_INDEX, GstIndexClass)) + +#define GST_TYPE_INDEX_ENTRY (gst_index_entry_get_type()) + +typedef struct _GstIndexEntry GstIndexEntry; +typedef struct _GstIndexGroup GstIndexGroup; +typedef struct _GstIndex GstIndex; +typedef struct _GstIndexClass GstIndexClass; + +/** + * GstIndexCertainty: + * @GST_INDEX_UNKNOWN: accuracy is not known + * @GST_INDEX_CERTAIN: accuracy is perfect + * @GST_INDEX_FUZZY: accuracy is fuzzy + * + * The certainty of a group in the index. + */ +typedef enum { + GST_INDEX_UNKNOWN, + GST_INDEX_CERTAIN, + GST_INDEX_FUZZY +} GstIndexCertainty; + +/** + * GstIndexEntryType: + * @GST_INDEX_ENTRY_ID: This entry is an id that maps an index id to its owner object + * @GST_INDEX_ENTRY_ASSOCIATION: This entry is an association between formats + * @GST_INDEX_ENTRY_OBJECT: An object + * @GST_INDEX_ENTRY_FORMAT: A format definition + * + * The different types of entries in the index. + */ +typedef enum { + GST_INDEX_ENTRY_ID, + GST_INDEX_ENTRY_ASSOCIATION, + GST_INDEX_ENTRY_OBJECT, + GST_INDEX_ENTRY_FORMAT +} GstIndexEntryType; + +/** + * GstIndexLookupMethod: + * @GST_INDEX_LOOKUP_EXACT: There has to be an exact indexentry with the given format/value + * @GST_INDEX_LOOKUP_BEFORE: The exact entry or the one before it + * @GST_INDEX_LOOKUP_AFTER: The exact entry or the one after it + * + * Specify the method to find an index entry in the index. + */ +typedef enum { + GST_INDEX_LOOKUP_EXACT, + GST_INDEX_LOOKUP_BEFORE, + GST_INDEX_LOOKUP_AFTER +} GstIndexLookupMethod; + +/** + * GST_INDEX_NASSOCS: + * @entry: The entry to query + * + * Get the number of associations in the entry. + */ +#define GST_INDEX_NASSOCS(entry) ((entry)->data.assoc.nassocs) + +/** + * GST_INDEX_ASSOC_FLAGS: + * @entry: The entry to query + * + * Get the flags for this entry. + */ +#define GST_INDEX_ASSOC_FLAGS(entry) ((entry)->data.assoc.flags) + +/** + * GST_INDEX_ASSOC_FORMAT: + * @entry: The entry to query + * @i: The format index + * + * Get the i-th format of the entry. + */ +#define GST_INDEX_ASSOC_FORMAT(entry,i) ((entry)->data.assoc.assocs[(i)].format) + +/** + * GST_INDEX_ASSOC_VALUE: + * @entry: The entry to query + * @i: The value index + * + * Get the i-th value of the entry. + */ +#define GST_INDEX_ASSOC_VALUE(entry,i) ((entry)->data.assoc.assocs[(i)].value) + +typedef struct _GstIndexAssociation GstIndexAssociation; + +/** + * GstIndexAssociation: + * @format: the format of the association + * @value: the value of the association + * + * An association in an entry. + */ +struct _GstIndexAssociation { + GstFormat format; + gint64 value; +}; + +/** + * GstAssocFlags: + * @GST_ASSOCIATION_FLAG_NONE: no extra flags + * @GST_ASSOCIATION_FLAG_KEY_UNIT: the entry marks a key unit, a key unit is one + * that marks a place where one can randomly seek to. + * @GST_ASSOCIATION_FLAG_DELTA_UNIT: the entry marks a delta unit, a delta unit + * is one that marks a place where one can relatively seek to. + * @GST_ASSOCIATION_FLAG_LAST: extra user defined flags should start here. + * + * Flags for an association entry. + */ +typedef enum { + GST_ASSOCIATION_FLAG_NONE = 0, + GST_ASSOCIATION_FLAG_KEY_UNIT = (1 << 0), + GST_ASSOCIATION_FLAG_DELTA_UNIT = (1 << 1), + + /* new flags should start here */ + GST_ASSOCIATION_FLAG_LAST = (1 << 8) +} GstAssocFlags; + +/** + * GST_INDEX_FORMAT_FORMAT: + * @entry: The entry to query + * + * Get the format of the format entry + */ +#define GST_INDEX_FORMAT_FORMAT(entry) ((entry)->data.format.format) + +/** + * GST_INDEX_FORMAT_KEY: + * @entry: The entry to query + * + * Get the key of the format entry + */ +#define GST_INDEX_FORMAT_KEY(entry) ((entry)->data.format.key) + +/** + * GST_INDEX_ID_INVALID: + * + * Constant for an invalid index id + */ +#define GST_INDEX_ID_INVALID (-1) + +/** + * GST_INDEX_ID_DESCRIPTION: + * @entry: The entry to query + * + * Get the description of the id entry + */ +#define GST_INDEX_ID_DESCRIPTION(entry) ((entry)->data.id.description) + +/** + * GstIndexEntry: + * + * The basic element of an index. + */ +struct _GstIndexEntry { + /*< private >*/ + GstIndexEntryType type; + gint id; + + union { + struct { + gchar *description; + } id; + struct { + gint nassocs; + GstIndexAssociation + *assocs; + GstAssocFlags flags; + } assoc; + struct { + gchar *key; + GType type; + gpointer object; + } object; + struct { + GstFormat format; + const gchar *key; + } format; + } data; +}; + +/** + * GstIndexGroup: + * + * A group of related entries in an index. + */ + +struct _GstIndexGroup { + /*< private >*/ + /* unique ID of group in index */ + gint groupnum; + + /* list of entries */ + GList *entries; + + /* the certainty level of the group */ + GstIndexCertainty certainty; + + /* peer group that contains more certain entries */ + gint peergroup; +}; + +/** + * GstIndexFilter: + * @index: The index being queried + * @entry: The entry to be added. + * @user_data: User data passed to the function. + * + * Function to filter out entries in the index. + * + * Returns: This function should return %TRUE if the entry is to be added + * to the index, %FALSE otherwise. + * + */ +typedef gboolean (*GstIndexFilter) (GstIndex *index, + GstIndexEntry *entry, + gpointer user_data); +/** + * GstIndexResolverMethod: + * @GST_INDEX_RESOLVER_CUSTOM: Use a custom resolver + * @GST_INDEX_RESOLVER_GTYPE: Resolve based on the GType of the object + * @GST_INDEX_RESOLVER_PATH: Resolve on the path in graph + * + * The method used to resolve index writers + */ +typedef enum { + GST_INDEX_RESOLVER_CUSTOM, + GST_INDEX_RESOLVER_GTYPE, + GST_INDEX_RESOLVER_PATH +} GstIndexResolverMethod; + +/** + * GstIndexResolver: + * @index: the index being queried. + * @writer: The object that wants to write + * @writer_string: A description of the writer. + * @user_data: user_data as registered + * + * Function to resolve ids to writer descriptions. + * + * Returns: %TRUE if an id could be assigned to the writer. + */ +typedef gboolean (*GstIndexResolver) (GstIndex *index, + GstObject *writer, + gchar **writer_string, + gpointer user_data); + +/** + * GstIndexFlags: + * @GST_INDEX_WRITABLE: The index is writable + * @GST_INDEX_READABLE: The index is readable + * @GST_INDEX_FLAG_LAST: First flag that can be used by subclasses + * + * Flags for this index + */ +typedef enum { + GST_INDEX_WRITABLE = (GST_OBJECT_FLAG_LAST << 0), + GST_INDEX_READABLE = (GST_OBJECT_FLAG_LAST << 1), + + GST_INDEX_FLAG_LAST = (GST_OBJECT_FLAG_LAST << 8) +} GstIndexFlags; + +/** + * GST_INDEX_IS_READABLE: + * @obj: The index to check + * + * Check if the index can be read from + */ +#define GST_INDEX_IS_READABLE(obj) (GST_OBJECT_FLAG_IS_SET (obj, GST_INDEX_READABLE)) + +/** + * GST_INDEX_IS_WRITABLE: + * @obj: The index to check + * + * Check if the index can be written to + */ +#define GST_INDEX_IS_WRITABLE(obj) (GST_OBJECT_FLAG_IS_SET (obj, GST_INDEX_WRITABLE)) + +/** + * GstIndex: + * + * Opaque #GstIndex structure. + */ +struct _GstIndex { + GstObject object; + + /*< private >*/ + GList *groups; + GstIndexGroup *curgroup; + gint maxgroup; + + GstIndexResolverMethod method; + GstIndexResolver resolver; + gpointer resolver_user_data; + GDestroyNotify resolver_user_data_destroy; + + GstIndexFilter filter; + gpointer filter_user_data; + GDestroyNotify filter_user_data_destroy; + + GHashTable *writers; + gint last_id; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstIndexClass { + GstObjectClass parent_class; + + /*< protected >*/ + gboolean (*get_writer_id) (GstIndex *index, gint *writer_id, gchar *writer_string); + + void (*commit) (GstIndex *index, gint id); + + /* abstract methods */ + void (*add_entry) (GstIndex *index, GstIndexEntry *entry); + + GstIndexEntry* (*get_assoc_entry) (GstIndex *index, gint id, + GstIndexLookupMethod method, GstAssocFlags flags, + GstFormat format, gint64 value, + GCompareDataFunc func, + gpointer user_data); + /* signals */ + void (*entry_added) (GstIndex *index, GstIndexEntry *entry); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_index_get_type (void); +GstIndex* gst_index_new (void); +void gst_index_commit (GstIndex *index, gint id); + +gint gst_index_get_group (GstIndex *index); +gint gst_index_new_group (GstIndex *index); +gboolean gst_index_set_group (GstIndex *index, gint groupnum); + +void gst_index_set_certainty (GstIndex *index, + GstIndexCertainty certainty); +GstIndexCertainty gst_index_get_certainty (GstIndex *index); + +void gst_index_set_filter (GstIndex *index, + GstIndexFilter filter, gpointer user_data); +void gst_index_set_filter_full (GstIndex *index, + GstIndexFilter filter, gpointer user_data, + GDestroyNotify user_data_destroy); +void gst_index_set_resolver (GstIndex *index, + GstIndexResolver resolver, gpointer user_data); +void gst_index_set_resolver_full (GstIndex *index, GstIndexResolver resolver, + gpointer user_data, + GDestroyNotify user_data_destroy); + +gboolean gst_index_get_writer_id (GstIndex *index, GstObject *writer, gint *id); + +GstIndexEntry* gst_index_add_format (GstIndex *index, gint id, GstFormat format); +GstIndexEntry* gst_index_add_associationv (GstIndex * index, gint id, GstAssocFlags flags, + gint n, const GstIndexAssociation * list); +GstIndexEntry* gst_index_add_association (GstIndex *index, gint id, GstAssocFlags flags, + GstFormat format, gint64 value, ...); +GstIndexEntry* gst_index_add_object (GstIndex *index, gint id, gchar *key, + GType type, gpointer object); +GstIndexEntry* gst_index_add_id (GstIndex *index, gint id, + gchar *description); + +GstIndexEntry* gst_index_get_assoc_entry (GstIndex *index, gint id, + GstIndexLookupMethod method, GstAssocFlags flags, + GstFormat format, gint64 value); +GstIndexEntry* gst_index_get_assoc_entry_full (GstIndex *index, gint id, + GstIndexLookupMethod method, GstAssocFlags flags, + GstFormat format, gint64 value, + GCompareDataFunc func, + gpointer user_data); + +/* working with index entries */ +GType gst_index_entry_get_type (void); +GstIndexEntry * gst_index_entry_copy (GstIndexEntry *entry); +void gst_index_entry_free (GstIndexEntry *entry); +gboolean gst_index_entry_assoc_map (GstIndexEntry *entry, + GstFormat format, gint64 *value); + +G_END_DECLS + +#endif /* __GST_INDEX_H__ */ diff --git a/gst/gstindexfactory.c b/gst/gstindexfactory.c new file mode 100644 index 0000000..111a0ce --- /dev/null +++ b/gst/gstindexfactory.c @@ -0,0 +1,214 @@ +/* GStreamer + * Copyright (C) 2001 RidgeRun (http://www.ridgerun.com/) + * Written by Erik Walthinsen <omega@ridgerun.com> + * + * gstindexfactory.c: Index for mappings and other data + * + * 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:gstindexfactory + * @short_description: Create GstIndexes from a factory + * @see_also: #GstIndex + * + * GstIndexFactory is used to dynamically create GstIndex implementations. + */ + + +#include "gst_private.h" + +#include "gstinfo.h" +#include "gstindex.h" +#include "gstindexfactory.h" +#include "gstmarshal.h" +#include "gstregistry.h" + +static void gst_index_factory_finalize (GObject * object); + +static GstPluginFeatureClass *factory_parent_class = NULL; + +/* static guint gst_index_factory_signals[LAST_SIGNAL] = { 0 }; */ +G_DEFINE_TYPE (GstIndexFactory, gst_index_factory, GST_TYPE_PLUGIN_FEATURE); + +static void +gst_index_factory_class_init (GstIndexFactoryClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + factory_parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = gst_index_factory_finalize; +} + +static void +gst_index_factory_init (GstIndexFactory * factory) +{ +} + +static void +gst_index_factory_finalize (GObject * object) +{ + GstIndexFactory *factory = GST_INDEX_FACTORY (object); + + g_free (factory->longdesc); + + G_OBJECT_CLASS (factory_parent_class)->finalize (object); + +} + +/** + * gst_index_factory_new: + * @name: name of indexfactory to create + * @longdesc: long description of indexfactory to create + * @type: the GType of the GstIndex element of this factory + * + * Create a new indexfactory with the given parameters + * + * Returns: (transfer full): a new #GstIndexFactory. + */ +GstIndexFactory * +gst_index_factory_new (const gchar * name, const gchar * longdesc, GType type) +{ + GstIndexFactory *factory; + + g_return_val_if_fail (name != NULL, NULL); + + factory = + GST_INDEX_FACTORY (g_object_new (GST_TYPE_INDEX_FACTORY, "name", name, + NULL)); + + if (factory->longdesc) + g_free (factory->longdesc); + factory->longdesc = g_strdup (longdesc); + factory->type = type; + + return factory; +} + +/** + * gst_index_factory_destroy: + * @factory: factory to destroy + * + * Removes the index from the global list. + */ +void +gst_index_factory_destroy (GstIndexFactory * factory) +{ + g_return_if_fail (factory != NULL); + + /* we don't free the struct bacause someone might have a handle to it.. */ + /* FIXME: gst_index_factory_destroy */ +} + +/** + * gst_index_factory_find: + * @name: name of indexfactory to find + * + * Search for an indexfactory of the given name. + * + * Returns: (transfer full): #GstIndexFactory if found, NULL otherwise + */ +GstIndexFactory * +gst_index_factory_find (const gchar * name) +{ + GstPluginFeature *feature; + + g_return_val_if_fail (name != NULL, NULL); + + GST_DEBUG ("gstindex: find \"%s\"", name); + + feature = gst_registry_find_feature (gst_registry_get_default (), name, + GST_TYPE_INDEX_FACTORY); + if (feature) + return GST_INDEX_FACTORY (feature); + + return NULL; +} + +/** + * gst_index_factory_create: + * @factory: the factory used to create the instance + * + * Create a new #GstIndex instance from the + * given indexfactory. + * + * Returns: (transfer full): a new #GstIndex instance. + */ +GstIndex * +gst_index_factory_create (GstIndexFactory * factory) +{ + GstIndexFactory *newfactory; + GstIndex *new = NULL; + + g_return_val_if_fail (factory != NULL, NULL); + + newfactory = + GST_INDEX_FACTORY (gst_plugin_feature_load (GST_PLUGIN_FEATURE + (factory))); + if (newfactory == NULL) + return NULL; + + new = GST_INDEX (g_object_newv (newfactory->type, 0, NULL)); + + gst_object_unref (newfactory); + + return new; +} + +/** + * gst_index_factory_make: + * @name: the name of the factory used to create the instance + * + * Create a new #GstIndex instance from the + * indexfactory with the given name. + * + * Returns: (transfer full): a new #GstIndex instance. + */ +GstIndex * +gst_index_factory_make (const gchar * name) +{ + GstIndexFactory *factory; + GstIndex *index; + + g_return_val_if_fail (name != NULL, NULL); + + factory = gst_index_factory_find (name); + + if (factory == NULL) + goto no_factory; + + index = gst_index_factory_create (factory); + + if (index == NULL) + goto create_failed; + + gst_object_unref (factory); + return index; + + /* ERRORS */ +no_factory: + { + GST_INFO ("no such index factory \"%s\"!", name); + return NULL; + } +create_failed: + { + GST_INFO_OBJECT (factory, "couldn't create instance!"); + gst_object_unref (factory); + return NULL; + } +} diff --git a/gst/gstindexfactory.h b/gst/gstindexfactory.h new file mode 100644 index 0000000..f5e9ef3 --- /dev/null +++ b/gst/gstindexfactory.h @@ -0,0 +1,76 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * + * gstindexfactory.h: Header for GstIndexFactory, base class to handle efficient + * storage or caching of seeking information. + * + * 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_INDEX_FACTORY_H__ +#define __GST_INDEX_FACTORY_H__ + +#include <gst/gstobject.h> +#include <gst/gstformat.h> +#include <gst/gstpluginfeature.h> + +G_BEGIN_DECLS + +#define GST_TYPE_INDEX_FACTORY (gst_index_factory_get_type()) +#define GST_INDEX_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_INDEX_FACTORY, GstIndexFactory)) +#define GST_IS_INDEX_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_INDEX_FACTORY)) +#define GST_INDEX_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_INDEX_FACTORY, GstIndexFactoryClass)) +#define GST_IS_INDEX_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_INDEX_FACTORY)) +#define GST_INDEX_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_INDEX_FACTORY, GstIndexFactoryClass)) + +typedef struct _GstIndexFactory GstIndexFactory; +typedef struct _GstIndexFactoryClass GstIndexFactoryClass; + +/** + * GstIndexFactory: + * + * The GstIndexFactory object + */ +struct _GstIndexFactory { + GstPluginFeature feature; + + gchar *longdesc; /* long description of the index (well, don't overdo it..) */ + GType type; /* unique GType of the index */ + + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstIndexFactoryClass { + GstPluginFeatureClass parent; + + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_index_factory_get_type (void); + +GstIndexFactory* gst_index_factory_new (const gchar *name, + const gchar *longdesc, GType type); +void gst_index_factory_destroy (GstIndexFactory *factory); + +GstIndexFactory* gst_index_factory_find (const gchar *name); + +GstIndex* gst_index_factory_create (GstIndexFactory *factory); +GstIndex* gst_index_factory_make (const gchar *name); + +G_END_DECLS + +#endif /* __GST_INDEX_FACTORY_H__ */ diff --git a/gst/gstinfo.c b/gst/gstinfo.c new file mode 100644 index 0000000..1a62b83 --- /dev/null +++ b/gst/gstinfo.c @@ -0,0 +1,2003 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * Copyright (C) 2008-2009 Tim-Philipp Müller <tim centricular net> + * + * gstinfo.c: debugging functions + * + * 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:gstinfo + * @short_description: Debugging and logging facilities + * @see_also: #gstreamer-gstconfig, #gstreamer-Gst for command line parameters + * and environment variables that affect the debugging output. + * + * GStreamer's debugging subsystem is an easy way to get information about what + * the application is doing. It is not meant for programming errors. Use GLib + * methods (g_warning and friends) for that. + * + * The debugging subsystem works only after GStreamer has been initialized + * - for example by calling gst_init(). + * + * The debugging subsystem is used to log informational messages while the + * application runs. Each messages has some properties attached to it. Among + * these properties are the debugging category, the severity (called "level" + * here) and an optional #GObject it belongs to. Each of these messages is sent + * to all registered debugging handlers, which then handle the messages. + * GStreamer attaches a default handler on startup, which outputs requested + * messages to stderr. + * + * Messages are output by using shortcut macros like #GST_DEBUG, + * #GST_CAT_ERROR_OBJECT or similar. These all expand to calling gst_debug_log() + * with the right parameters. + * The only thing a developer will probably want to do is define his own + * categories. This is easily done with 3 lines. At the top of your code, + * declare + * the variables and set the default category. + * <informalexample> + * <programlisting> + * GST_DEBUG_CATEGORY_STATIC (my_category); // define category (statically) + * &hash;define GST_CAT_DEFAULT my_category // set as default + * </programlisting> + * </informalexample> + * After that you only need to initialize the category. + * <informalexample> + * <programlisting> + * GST_DEBUG_CATEGORY_INIT (my_category, "my category", + * 0, "This is my very own"); + * </programlisting> + * </informalexample> + * Initialization must be done before the category is used first. + * Plugins do this + * in their plugin_init function, libraries and applications should do that + * during their initialization. + * + * The whole debugging subsystem can be disabled at build time with passing the + * --disable-gst-debug switch to configure. If this is done, every function, + * macro and even structs described in this file evaluate to default values or + * nothing at all. + * So don't take addresses of these functions or use other tricks. + * If you must do that for some reason, there is still an option. + * If the debugging + * subsystem was compiled out, #GST_DISABLE_GST_DEBUG is defined in + * <gst/gst.h>, + * so you can check that before doing your trick. + * Disabling the debugging subsystem will give you a slight (read: unnoticeable) + * speed increase and will reduce the size of your compiled code. The GStreamer + * library itself becomes around 10% smaller. + * + * Please note that there are naming conventions for the names of debugging + * categories. These are explained at GST_DEBUG_CATEGORY_INIT(). + */ + +#define GST_INFO_C +#include "gst_private.h" +#include "gstinfo.h" + +#undef gst_debug_remove_log_function +#undef gst_debug_add_log_function + +#ifndef GST_DISABLE_GST_DEBUG + +#ifdef HAVE_DLFCN_H +# include <dlfcn.h> +#endif +#ifdef HAVE_PRINTF_EXTENSION +# include <printf.h> +#endif +#include <stdio.h> /* fprintf */ +#include <glib/gstdio.h> +#include <errno.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> /* getpid on UNIX */ +#endif +#ifdef HAVE_PROCESS_H +# include <process.h> /* getpid on win32 */ +#endif +#include <string.h> /* G_VA_COPY */ +#ifdef G_OS_WIN32 +# define WIN32_LEAN_AND_MEAN /* prevents from including too many things */ +# include <windows.h> /* GetStdHandle, windows console */ +#endif + +#include "gst_private.h" +#include "gstutils.h" +#include "gstquark.h" +#include "gstsegment.h" +#ifdef HAVE_VALGRIND_VALGRIND_H +# include <valgrind/valgrind.h> +#endif +#include <glib/gprintf.h> /* g_sprintf */ + +#endif /* !GST_DISABLE_GST_DEBUG */ + +/* we want these symbols exported even if debug is disabled, to maintain + * ABI compatibility. Unless GST_REMOVE_DISABLED is defined. */ +#if !defined(GST_DISABLE_GST_DEBUG) || !defined(GST_REMOVE_DISABLED) + +/* disabled by default, as soon as some threshold is set > NONE, + * it becomes enabled. */ +gboolean __gst_debug_enabled = FALSE; +GstDebugLevel __gst_debug_min = GST_LEVEL_NONE; + +GstDebugCategory *GST_CAT_DEFAULT = NULL; + +GstDebugCategory *GST_CAT_GST_INIT = NULL; +GstDebugCategory *GST_CAT_AUTOPLUG = NULL; +GstDebugCategory *GST_CAT_AUTOPLUG_ATTEMPT = NULL; +GstDebugCategory *GST_CAT_PARENTAGE = NULL; +GstDebugCategory *GST_CAT_STATES = NULL; +GstDebugCategory *GST_CAT_SCHEDULING = NULL; + +GstDebugCategory *GST_CAT_BUFFER = NULL; +GstDebugCategory *GST_CAT_BUFFER_LIST = NULL; +GstDebugCategory *GST_CAT_BUS = NULL; +GstDebugCategory *GST_CAT_CAPS = NULL; +GstDebugCategory *GST_CAT_CLOCK = NULL; +GstDebugCategory *GST_CAT_ELEMENT_PADS = NULL; +GstDebugCategory *GST_CAT_PADS = NULL; +GstDebugCategory *GST_CAT_PERFORMANCE = NULL; +GstDebugCategory *GST_CAT_PIPELINE = NULL; +GstDebugCategory *GST_CAT_PLUGIN_LOADING = NULL; +GstDebugCategory *GST_CAT_PLUGIN_INFO = NULL; +GstDebugCategory *GST_CAT_PROPERTIES = NULL; +GstDebugCategory *GST_CAT_TYPES = NULL; +GstDebugCategory *GST_CAT_NEGOTIATION = NULL; +GstDebugCategory *GST_CAT_REFCOUNTING = NULL; +GstDebugCategory *GST_CAT_ERROR_SYSTEM = NULL; +GstDebugCategory *GST_CAT_EVENT = NULL; +GstDebugCategory *GST_CAT_MESSAGE = NULL; +GstDebugCategory *GST_CAT_PARAMS = NULL; +GstDebugCategory *GST_CAT_CALL_TRACE = NULL; +GstDebugCategory *GST_CAT_SIGNAL = NULL; +GstDebugCategory *GST_CAT_PROBE = NULL; +GstDebugCategory *GST_CAT_REGISTRY = NULL; +GstDebugCategory *GST_CAT_QOS = NULL; +GstDebugCategory *_priv_GST_CAT_POLL = NULL; + + +#endif /* !defined(GST_DISABLE_GST_DEBUG) || !defined(GST_REMOVE_DISABLED) */ + +#ifndef GST_DISABLE_GST_DEBUG + +/* underscore is to prevent conflict with GST_CAT_DEBUG define */ +GST_DEBUG_CATEGORY_STATIC (_GST_CAT_DEBUG); + +/* time of initialization, so we get useful debugging output times + * FIXME: we use this in gstdebugutils.c, what about a function + macro to + * get the running time: GST_DEBUG_RUNNING_TIME + */ +GstClockTime _priv_gst_info_start_time; + +#if 0 +#if defined __sgi__ +#include <rld_interface.h> +typedef struct DL_INFO +{ + const char *dli_fname; + void *dli_fbase; + const char *dli_sname; + void *dli_saddr; + int dli_version; + int dli_reserved1; + long dli_reserved[4]; +} +Dl_info; + +#define _RLD_DLADDR 14 +int dladdr (void *address, Dl_info * dl); + +int +dladdr (void *address, Dl_info * dl) +{ + void *v; + + v = _rld_new_interface (_RLD_DLADDR, address, dl); + return (int) v; +} +#endif /* __sgi__ */ +#endif + +static void gst_debug_reset_threshold (gpointer category, gpointer unused); +static void gst_debug_reset_all_thresholds (void); + +#ifdef HAVE_PRINTF_EXTENSION +static int _gst_info_printf_extension_ptr (FILE * stream, + const struct printf_info *info, const void *const *args); +static int _gst_info_printf_extension_segment (FILE * stream, + const struct printf_info *info, const void *const *args); +#ifdef HAVE_REGISTER_PRINTF_SPECIFIER +static int _gst_info_printf_extension_arginfo (const struct printf_info *info, + size_t n, int *argtypes, int *size); +#else +static int _gst_info_printf_extension_arginfo (const struct printf_info *info, + size_t n, int *argtypes); +#endif +#endif + +struct _GstDebugMessage +{ + gchar *message; + const gchar *format; + va_list arguments; +}; + +/* list of all name/level pairs from --gst-debug and GST_DEBUG */ +static GStaticMutex __level_name_mutex = G_STATIC_MUTEX_INIT; +static GSList *__level_name = NULL; +typedef struct +{ + GPatternSpec *pat; + GstDebugLevel level; +} +LevelNameEntry; + +/* list of all categories */ +static GStaticMutex __cat_mutex = G_STATIC_MUTEX_INIT; +static GSList *__categories = NULL; + +/* all registered debug handlers */ +typedef struct +{ + GstLogFunction func; + gpointer user_data; +} +LogFuncEntry; +static GStaticMutex __log_func_mutex = G_STATIC_MUTEX_INIT; +static GSList *__log_functions = NULL; + +#define PRETTY_TAGS_DEFAULT TRUE +static gboolean pretty_tags = PRETTY_TAGS_DEFAULT; + +static volatile gint G_GNUC_MAY_ALIAS __default_level = GST_LEVEL_DEFAULT; +static volatile gint G_GNUC_MAY_ALIAS __use_color = 1; + +static FILE *log_file; + +/* FIXME: export this? */ +gboolean +_priv_gst_in_valgrind (void) +{ + static enum + { + GST_VG_UNCHECKED, + GST_VG_NO_VALGRIND, + GST_VG_INSIDE + } + in_valgrind = GST_VG_UNCHECKED; + + if (in_valgrind == GST_VG_UNCHECKED) { +#ifdef HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + GST_CAT_INFO (GST_CAT_GST_INIT, "we're running inside valgrind"); + printf ("GStreamer has detected that it is running inside valgrind.\n"); + printf ("It might now take different code paths to ease debugging.\n"); + printf ("Of course, this may also lead to different bugs.\n"); + in_valgrind = GST_VG_INSIDE; + } else { + GST_CAT_LOG (GST_CAT_GST_INIT, "not doing extra valgrind stuff"); + in_valgrind = GST_VG_NO_VALGRIND; + } +#else + in_valgrind = GST_VG_NO_VALGRIND; +#endif + g_assert (in_valgrind == GST_VG_NO_VALGRIND || + in_valgrind == GST_VG_INSIDE); + } + return (in_valgrind == GST_VG_INSIDE); +} + +/** + * _gst_debug_init: + * + * Initializes the debugging system. + * Normally you don't want to call this, because gst_init() does it for you. + */ +void +_gst_debug_init (void) +{ + const gchar *env; + + env = g_getenv ("GST_DEBUG_FILE"); + if (env != NULL && *env != '\0') { + if (strcmp (env, "-") == 0) { + log_file = stdout; + } else { + log_file = g_fopen (env, "w"); + if (log_file == NULL) { + g_printerr ("Could not open log file '%s' for writing: %s\n", env, + g_strerror (errno)); + log_file = stderr; + } + } + } else { + log_file = stderr; + } + + /* get time we started for debugging messages */ + _priv_gst_info_start_time = gst_util_get_timestamp (); + +#ifdef HAVE_PRINTF_EXTENSION +#ifdef HAVE_REGISTER_PRINTF_SPECIFIER + register_printf_specifier (GST_PTR_FORMAT[0], _gst_info_printf_extension_ptr, + _gst_info_printf_extension_arginfo); + register_printf_specifier (GST_SEGMENT_FORMAT[0], + _gst_info_printf_extension_segment, _gst_info_printf_extension_arginfo); +#else + register_printf_function (GST_PTR_FORMAT[0], _gst_info_printf_extension_ptr, + _gst_info_printf_extension_arginfo); + register_printf_function (GST_SEGMENT_FORMAT[0], + _gst_info_printf_extension_segment, _gst_info_printf_extension_arginfo); +#endif +#endif + + /* do NOT use a single debug function before this line has been run */ + GST_CAT_DEFAULT = _gst_debug_category_new ("default", + GST_DEBUG_UNDERLINE, NULL); + _GST_CAT_DEBUG = _gst_debug_category_new ("GST_DEBUG", + GST_DEBUG_BOLD | GST_DEBUG_FG_YELLOW, "debugging subsystem"); + + gst_debug_add_log_function (gst_debug_log_default, NULL); + + /* FIXME: add descriptions here */ + GST_CAT_GST_INIT = _gst_debug_category_new ("GST_INIT", + GST_DEBUG_BOLD | GST_DEBUG_FG_RED, NULL); + GST_CAT_AUTOPLUG = _gst_debug_category_new ("GST_AUTOPLUG", + GST_DEBUG_BOLD | GST_DEBUG_FG_BLUE, NULL); + GST_CAT_AUTOPLUG_ATTEMPT = _gst_debug_category_new ("GST_AUTOPLUG_ATTEMPT", + GST_DEBUG_BOLD | GST_DEBUG_FG_CYAN | GST_DEBUG_BG_BLUE, NULL); + GST_CAT_PARENTAGE = _gst_debug_category_new ("GST_PARENTAGE", + GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_RED, NULL); + GST_CAT_STATES = _gst_debug_category_new ("GST_STATES", + GST_DEBUG_BOLD | GST_DEBUG_FG_RED, NULL); + GST_CAT_SCHEDULING = _gst_debug_category_new ("GST_SCHEDULING", + GST_DEBUG_BOLD | GST_DEBUG_FG_MAGENTA, NULL); + GST_CAT_BUFFER = _gst_debug_category_new ("GST_BUFFER", + GST_DEBUG_BOLD | GST_DEBUG_BG_GREEN, NULL); + GST_CAT_BUFFER_LIST = _gst_debug_category_new ("GST_BUFFER_LIST", + GST_DEBUG_BOLD | GST_DEBUG_BG_GREEN, NULL); + GST_CAT_BUS = _gst_debug_category_new ("GST_BUS", GST_DEBUG_BG_YELLOW, NULL); + GST_CAT_CAPS = _gst_debug_category_new ("GST_CAPS", + GST_DEBUG_BOLD | GST_DEBUG_FG_BLUE, NULL); + GST_CAT_CLOCK = _gst_debug_category_new ("GST_CLOCK", + GST_DEBUG_BOLD | GST_DEBUG_FG_YELLOW, NULL); + GST_CAT_ELEMENT_PADS = _gst_debug_category_new ("GST_ELEMENT_PADS", + GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_RED, NULL); + GST_CAT_PADS = _gst_debug_category_new ("GST_PADS", + GST_DEBUG_BOLD | GST_DEBUG_FG_RED | GST_DEBUG_BG_RED, NULL); + GST_CAT_PERFORMANCE = _gst_debug_category_new ("GST_PERFORMANCE", + GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_RED, NULL); + GST_CAT_PIPELINE = _gst_debug_category_new ("GST_PIPELINE", + GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_RED, NULL); + GST_CAT_PLUGIN_LOADING = _gst_debug_category_new ("GST_PLUGIN_LOADING", + GST_DEBUG_BOLD | GST_DEBUG_FG_CYAN, NULL); + GST_CAT_PLUGIN_INFO = _gst_debug_category_new ("GST_PLUGIN_INFO", + GST_DEBUG_BOLD | GST_DEBUG_FG_CYAN, NULL); + GST_CAT_PROPERTIES = _gst_debug_category_new ("GST_PROPERTIES", + GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_BLUE, NULL); + GST_CAT_TYPES = _gst_debug_category_new ("GST_TYPES", + GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_RED, NULL); + GST_CAT_NEGOTIATION = _gst_debug_category_new ("GST_NEGOTIATION", + GST_DEBUG_BOLD | GST_DEBUG_FG_BLUE, NULL); + GST_CAT_REFCOUNTING = _gst_debug_category_new ("GST_REFCOUNTING", + GST_DEBUG_BOLD | GST_DEBUG_FG_RED | GST_DEBUG_BG_BLUE, NULL); + GST_CAT_ERROR_SYSTEM = _gst_debug_category_new ("GST_ERROR_SYSTEM", + GST_DEBUG_BOLD | GST_DEBUG_FG_RED | GST_DEBUG_BG_WHITE, NULL); + + GST_CAT_EVENT = _gst_debug_category_new ("GST_EVENT", + GST_DEBUG_BOLD | GST_DEBUG_FG_BLUE, NULL); + GST_CAT_MESSAGE = _gst_debug_category_new ("GST_MESSAGE", + GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_RED, NULL); + GST_CAT_PARAMS = _gst_debug_category_new ("GST_PARAMS", + GST_DEBUG_BOLD | GST_DEBUG_FG_BLACK | GST_DEBUG_BG_YELLOW, NULL); + GST_CAT_CALL_TRACE = _gst_debug_category_new ("GST_CALL_TRACE", + GST_DEBUG_BOLD, NULL); + GST_CAT_SIGNAL = _gst_debug_category_new ("GST_SIGNAL", + GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_RED, NULL); + GST_CAT_PROBE = _gst_debug_category_new ("GST_PROBE", + GST_DEBUG_BOLD | GST_DEBUG_FG_GREEN, "pad probes"); + GST_CAT_REGISTRY = _gst_debug_category_new ("GST_REGISTRY", 0, "registry"); + GST_CAT_QOS = _gst_debug_category_new ("GST_QOS", 0, "QoS"); + _priv_GST_CAT_POLL = _gst_debug_category_new ("GST_POLL", 0, "poll"); + + + /* print out the valgrind message if we're in valgrind */ + _priv_gst_in_valgrind (); + + env = g_getenv ("GST_DEBUG_OPTIONS"); + if (env != NULL) { + if (strstr (env, "full_tags") || strstr (env, "full-tags")) + pretty_tags = FALSE; + else if (strstr (env, "pretty_tags") || strstr (env, "pretty-tags")) + pretty_tags = TRUE; + } +} + +/* we can't do this further above, because we initialize the GST_CAT_DEFAULT struct */ +#define GST_CAT_DEFAULT _GST_CAT_DEBUG + +/** + * gst_debug_log: + * @category: category to log + * @level: level of the message is in + * @file: the file that emitted the message, usually the __FILE__ identifier + * @function: the function that emitted the message + * @line: the line from that the message was emitted, usually __LINE__ + * @object: (transfer none) (allow-none): the object this message relates to, + * or NULL if none + * @format: a printf style format string + * @...: optional arguments for the format + * + * Logs the given message using the currently registered debugging handlers. + */ +void +gst_debug_log (GstDebugCategory * category, GstDebugLevel level, + const gchar * file, const gchar * function, gint line, + GObject * object, const gchar * format, ...) +{ + va_list var_args; + + va_start (var_args, format); + gst_debug_log_valist (category, level, file, function, line, object, format, + var_args); + va_end (var_args); +} + +#ifdef _MSC_VER +/* based on g_basename(), which we can't use because it was deprecated */ +static inline const gchar * +gst_path_basename (const gchar * file_name) +{ + register const gchar *base; + + base = strrchr (file_name, G_DIR_SEPARATOR); + + { + const gchar *q = strrchr (file_name, '/'); + if (base == NULL || (q != NULL && q > base)) + base = q; + } + + if (base) + return base + 1; + + if (g_ascii_isalpha (file_name[0]) && file_name[1] == ':') + return file_name + 2; + + return file_name; +} +#endif + +/** + * gst_debug_log_valist: + * @category: category to log + * @level: level of the message is in + * @file: the file that emitted the message, usually the __FILE__ identifier + * @function: the function that emitted the message + * @line: the line from that the message was emitted, usually __LINE__ + * @object: (transfer none) (allow-none): the object this message relates to, + * or NULL if none + * @format: a printf style format string + * @args: optional arguments for the format + * + * Logs the given message using the currently registered debugging handlers. + */ +void +gst_debug_log_valist (GstDebugCategory * category, GstDebugLevel level, + const gchar * file, const gchar * function, gint line, + GObject * object, const gchar * format, va_list args) +{ + GstDebugMessage message; + LogFuncEntry *entry; + GSList *handler; + + g_return_if_fail (category != NULL); + g_return_if_fail (file != NULL); + g_return_if_fail (function != NULL); + g_return_if_fail (format != NULL); + + /* The predefined macro __FILE__ is always the exact path given to the + * compiler with MSVC, which may or may not be the basename. We work + * around it at runtime to improve the readability. */ +#ifdef _MSC_VER + file = gst_path_basename (file); +#endif + + message.message = NULL; + message.format = format; + G_VA_COPY (message.arguments, args); + + handler = __log_functions; + while (handler) { + entry = handler->data; + handler = g_slist_next (handler); + entry->func (category, level, file, function, line, object, &message, + entry->user_data); + } + g_free (message.message); + va_end (message.arguments); +} + +/** + * gst_debug_message_get: + * @message: a debug message + * + * Gets the string representation of a #GstDebugMessage. This function is used + * in debug handlers to extract the message. + * + * Returns: the string representation of a #GstDebugMessage. + */ +const gchar * +gst_debug_message_get (GstDebugMessage * message) +{ + if (message->message == NULL) { + message->message = g_strdup_vprintf (message->format, message->arguments); + } + return message->message; +} + +#define MAX_BUFFER_DUMP_STRING_LEN 100 + +/* structure_to_pretty_string: + * @structure: a #GstStructure + * + * Converts @structure to a human-readable string representation. Basically + * the same as gst_structure_to_string(), but if the structure contains large + * buffers such as images the hex representation of those buffers will be + * shortened so that the string remains readable. + * + * Returns: a newly-allocated string. g_free() when no longer needed. + */ +static gchar * +structure_to_pretty_string (const GstStructure * s) +{ + gchar *str, *pos, *end; + + str = gst_structure_to_string (s); + if (str == NULL) + return NULL; + + pos = str; + while ((pos = strstr (pos, "(buffer)"))) { + guint count = 0; + + pos += strlen ("(buffer)"); + for (end = pos; *end != '\0' && *end != ';' && *end != ' '; ++end) + ++count; + if (count > MAX_BUFFER_DUMP_STRING_LEN) { + memcpy (pos + MAX_BUFFER_DUMP_STRING_LEN - 6, "..", 2); + memcpy (pos + MAX_BUFFER_DUMP_STRING_LEN - 4, pos + count - 4, 4); + g_memmove (pos + MAX_BUFFER_DUMP_STRING_LEN, pos + count, + strlen (pos + count) + 1); + pos += MAX_BUFFER_DUMP_STRING_LEN; + } + } + + return str; +} + +static inline gchar * +gst_info_structure_to_string (const GstStructure * s) +{ + if (G_UNLIKELY (pretty_tags && s->name == GST_QUARK (TAGLIST))) + return structure_to_pretty_string (s); + else + return gst_structure_to_string (s); +} + +static gchar * +gst_debug_print_object (gpointer ptr) +{ + GObject *object = (GObject *) ptr; + +#ifdef unused + /* This is a cute trick to detect unmapped memory, but is unportable, + * slow, screws around with madvise, and not actually that useful. */ + { + int ret; + + ret = madvise ((void *) ((unsigned long) ptr & (~0xfff)), 4096, 0); + if (ret == -1 && errno == ENOMEM) { + buffer = g_strdup_printf ("%p (unmapped memory)", ptr); + } + } +#endif + + /* nicely printed object */ + if (object == NULL) { + return g_strdup ("(NULL)"); + } + if (*(GType *) ptr == GST_TYPE_CAPS) { + return gst_caps_to_string ((const GstCaps *) ptr); + } + if (*(GType *) ptr == GST_TYPE_STRUCTURE) { + return gst_info_structure_to_string ((const GstStructure *) ptr); + } +#ifdef USE_POISONING + if (*(guint32 *) ptr == 0xffffffff) { + return g_strdup_printf ("<poisoned@%p>", ptr); + } +#endif + if (GST_IS_PAD (object) && GST_OBJECT_NAME (object)) { + return g_strdup_printf ("<%s:%s>", GST_DEBUG_PAD_NAME (object)); + } + if (GST_IS_OBJECT (object) && GST_OBJECT_NAME (object)) { + return g_strdup_printf ("<%s>", GST_OBJECT_NAME (object)); + } + if (G_IS_OBJECT (object)) { + return g_strdup_printf ("<%s@%p>", G_OBJECT_TYPE_NAME (object), object); + } + if (GST_IS_MESSAGE (object)) { + GstMessage *msg = GST_MESSAGE_CAST (object); + gchar *s, *ret; + const GstStructure *structure; + + structure = gst_message_get_structure (msg); + + if (structure) { + s = gst_info_structure_to_string (structure); + } else { + s = g_strdup ("(NULL)"); + } + + ret = g_strdup_printf ("%s message from element '%s': %s", + GST_MESSAGE_TYPE_NAME (msg), (msg->src != NULL) ? + GST_ELEMENT_NAME (msg->src) : "(NULL)", s); + g_free (s); + return ret; + } + if (GST_IS_QUERY (object)) { + GstQuery *query = GST_QUERY_CAST (object); + const GstStructure *structure; + + structure = gst_query_get_structure (query); + + if (structure) { + return gst_info_structure_to_string (structure); + } else { + const gchar *query_type_name; + + query_type_name = gst_query_type_get_name (query->type); + if (G_LIKELY (query_type_name != NULL)) { + return g_strdup_printf ("%s query", query_type_name); + } else { + return g_strdup_printf ("query of unknown type %d", query->type); + } + } + } + if (GST_IS_EVENT (object)) { + GstEvent *event = GST_EVENT_CAST (object); + gchar *s, *ret; + GstStructure *structure; + + structure = (GstStructure *) gst_event_get_structure (event); + if (structure) { + s = gst_info_structure_to_string (structure); + } else { + s = g_strdup ("(NULL)"); + } + + ret = g_strdup_printf ("%s event at time %" + GST_TIME_FORMAT ": %s", + GST_EVENT_TYPE_NAME (event), GST_TIME_ARGS (event->timestamp), s); + g_free (s); + return ret; + } + + return g_strdup_printf ("%p", ptr); +} + +#ifdef HAVE_PRINTF_EXTENSION + +static gchar * +gst_debug_print_segment (gpointer ptr) +{ + GstSegment *segment = (GstSegment *) ptr; + + /* nicely printed segment */ + if (segment == NULL) { + return g_strdup ("(NULL)"); + } + + switch (segment->format) { + case GST_FORMAT_UNDEFINED:{ + return g_strdup_printf ("UNDEFINED segment"); + } + case GST_FORMAT_TIME:{ + return g_strdup_printf ("time segment start=%" GST_TIME_FORMAT + ", stop=%" GST_TIME_FORMAT ", rate=%f, applied_rate=%f" + ", flags=0x%02x, time=%" GST_TIME_FORMAT ", base=%" GST_TIME_FORMAT, + GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->stop), + segment->rate, segment->applied_rate, (guint) segment->flags, + GST_TIME_ARGS (segment->time), GST_TIME_ARGS (segment->base)); + } + default:{ + const gchar *format_name; + + format_name = gst_format_get_name (segment->format); + if (G_UNLIKELY (format_name == NULL)) + format_name = "(UNKNOWN FORMAT)"; + return g_strdup_printf ("%s segment start=%" G_GINT64_FORMAT + ", stop=%" G_GINT64_FORMAT ", rate=%f, applied_rate=%f" + ", flags=0x%02x, time=%" GST_TIME_FORMAT ", base=%" GST_TIME_FORMAT, + format_name, segment->start, segment->stop, segment->rate, + segment->applied_rate, (guint) segment->flags, + GST_TIME_ARGS (segment->time), GST_TIME_ARGS (segment->base)); + } + } +} + +#endif /* HAVE_PRINTF_EXTENSION */ + +/** + * gst_debug_construct_term_color: + * @colorinfo: the color info + * + * Constructs a string that can be used for getting the desired color in color + * terminals. + * You need to free the string after use. + * + * Returns: (transfer full) (type gchar*): a string containing the color + * definition + */ +gchar * +gst_debug_construct_term_color (guint colorinfo) +{ + GString *color; + + color = g_string_new ("\033[00"); + + if (colorinfo & GST_DEBUG_BOLD) { + g_string_append_len (color, ";01", 3); + } + if (colorinfo & GST_DEBUG_UNDERLINE) { + g_string_append_len (color, ";04", 3); + } + if (colorinfo & GST_DEBUG_FG_MASK) { + g_string_append_printf (color, ";3%1d", colorinfo & GST_DEBUG_FG_MASK); + } + if (colorinfo & GST_DEBUG_BG_MASK) { + g_string_append_printf (color, ";4%1d", + (colorinfo & GST_DEBUG_BG_MASK) >> 4); + } + g_string_append_c (color, 'm'); + + return g_string_free (color, FALSE); +} + +/** + * gst_debug_construct_win_color: + * @colorinfo: the color info + * + * Constructs an integer that can be used for getting the desired color in + * windows' terminals (cmd.exe). As there is no mean to underline, we simply + * ignore this attribute. + * + * This function returns 0 on non-windows machines. + * + * Returns: an integer containing the color definition + * + * Since: 0.10.23 + */ +gint +gst_debug_construct_win_color (guint colorinfo) +{ + gint color = 0; +#ifdef G_OS_WIN32 + static const guchar ansi_to_win_fg[8] = { + 0, /* black */ + FOREGROUND_RED, /* red */ + FOREGROUND_GREEN, /* green */ + FOREGROUND_RED | FOREGROUND_GREEN, /* yellow */ + FOREGROUND_BLUE, /* blue */ + FOREGROUND_RED | FOREGROUND_BLUE, /* magenta */ + FOREGROUND_GREEN | FOREGROUND_BLUE, /* cyan */ + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE /* white */ + }; + static const guchar ansi_to_win_bg[8] = { + 0, + BACKGROUND_RED, + BACKGROUND_GREEN, + BACKGROUND_RED | BACKGROUND_GREEN, + BACKGROUND_BLUE, + BACKGROUND_RED | BACKGROUND_BLUE, + BACKGROUND_GREEN | FOREGROUND_BLUE, + BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE + }; + + /* we draw black as white, as cmd.exe can only have black bg */ + if (colorinfo == 0) { + return ansi_to_win_fg[7]; + } + + if (colorinfo & GST_DEBUG_BOLD) { + color |= FOREGROUND_INTENSITY; + } + if (colorinfo & GST_DEBUG_FG_MASK) { + color |= ansi_to_win_fg[colorinfo & GST_DEBUG_FG_MASK]; + } + if (colorinfo & GST_DEBUG_BG_MASK) { + color |= ansi_to_win_bg[(colorinfo & GST_DEBUG_BG_MASK) >> 4]; + } +#endif + return color; +} + +/* width of %p varies depending on actual value of pointer, which can make + * output unevenly aligned if multiple threads are involved, hence the %14p + * (should really be %18p, but %14p seems a good compromise between too many + * white spaces and likely unalignment on my system) */ +#if defined (GLIB_SIZEOF_VOID_P) && GLIB_SIZEOF_VOID_P == 8 +#define PTR_FMT "%14p" +#else +#define PTR_FMT "%10p" +#endif +#define PID_FMT "%5d" +#define CAT_FMT "%20s %s:%d:%s:%s" + +#ifdef G_OS_WIN32 +static const guchar levelcolormap[GST_LEVEL_COUNT] = { + /* GST_LEVEL_NONE */ + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, + /* GST_LEVEL_ERROR */ + FOREGROUND_RED | FOREGROUND_INTENSITY, + /* GST_LEVEL_WARNING */ + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, + /* GST_LEVEL_INFO */ + FOREGROUND_GREEN | FOREGROUND_INTENSITY, + /* GST_LEVEL_DEBUG */ + FOREGROUND_GREEN | FOREGROUND_BLUE, + /* GST_LEVEL_LOG */ + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, + /* GST_LEVEL_FIXME */ + FOREGROUND_RED | FOREGROUND_GREEN, + /* GST_LEVEL_TRACE */ + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, + /* placeholder for log level 8 */ + 0, + /* GST_LEVEL_MEMDUMP */ + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE +}; + +static const guchar available_colors[] = { + FOREGROUND_RED, FOREGROUND_GREEN, FOREGROUND_RED | FOREGROUND_GREEN, + FOREGROUND_BLUE, FOREGROUND_RED | FOREGROUND_BLUE, + FOREGROUND_GREEN | FOREGROUND_BLUE, +}; +#else +static const gchar *levelcolormap[GST_LEVEL_COUNT] = { + "\033[37m", /* GST_LEVEL_NONE */ + "\033[31;01m", /* GST_LEVEL_ERROR */ + "\033[33;01m", /* GST_LEVEL_WARNING */ + "\033[32;01m", /* GST_LEVEL_INFO */ + "\033[36m", /* GST_LEVEL_DEBUG */ + "\033[37m", /* GST_LEVEL_LOG */ + "\033[33;01m", /* GST_LEVEL_FIXME */ + "\033[37m", /* GST_LEVEL_TRACE */ + "\033[37m", /* placeholder for log level 8 */ + "\033[37m" /* GST_LEVEL_MEMDUMP */ +}; +#endif + +/** + * gst_debug_log_default: + * @category: category to log + * @level: level of the message + * @file: the file that emitted the message, usually the __FILE__ identifier + * @function: the function that emitted the message + * @line: the line from that the message was emitted, usually __LINE__ + * @message: the actual message + * @object: (transfer none) (allow-none): the object this message relates to, + * or NULL if none + * @unused: an unused variable, reserved for some user_data. + * + * The default logging handler used by GStreamer. Logging functions get called + * whenever a macro like GST_DEBUG or similar is used. This function outputs the + * message and additional info to stderr (or the log file specified via the + * GST_DEBUG_FILE environment variable). + * + * You can add other handlers by using gst_debug_add_log_function(). + * And you can remove this handler by calling + * gst_debug_remove_log_function(gst_debug_log_default); + */ +void +gst_debug_log_default (GstDebugCategory * category, GstDebugLevel level, + const gchar * file, const gchar * function, gint line, + GObject * object, GstDebugMessage * message, gpointer unused) +{ + gint pid; + GstClockTime elapsed; + gchar *obj = NULL; + gboolean is_colored; + + if (level > gst_debug_category_get_threshold (category)) + return; + + pid = getpid (); + is_colored = gst_debug_is_colored (); + + if (object) { + obj = gst_debug_print_object (object); + } else { + obj = g_strdup (""); + } + + elapsed = GST_CLOCK_DIFF (_priv_gst_info_start_time, + gst_util_get_timestamp ()); + + if (is_colored) { +#ifndef G_OS_WIN32 + /* colors, non-windows */ + gchar *color = NULL; + const gchar *clear; + gchar pidcolor[10]; + const gchar *levelcolor; + + color = gst_debug_construct_term_color (gst_debug_category_get_color + (category)); + clear = "\033[00m"; + g_sprintf (pidcolor, "\033[3%1dm", pid % 6 + 31); + levelcolor = levelcolormap[level]; + +#define PRINT_FMT " %s"PID_FMT"%s "PTR_FMT" %s%s%s %s"CAT_FMT"%s %s\n" + fprintf (log_file, "%" GST_TIME_FORMAT PRINT_FMT, GST_TIME_ARGS (elapsed), + pidcolor, pid, clear, g_thread_self (), levelcolor, + gst_debug_level_get_name (level), clear, color, + gst_debug_category_get_name (category), file, line, function, obj, + clear, gst_debug_message_get (message)); + fflush (log_file); +#undef PRINT_FMT + g_free (color); +#else + /* colors, windows. We take a lock to keep colors and content together. + * Maybe there is a better way but for now this will do the right + * thing. */ + static GStaticMutex win_print_mutex = G_STATIC_MUTEX_INIT; + const gint clear = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; +#define SET_COLOR(c) G_STMT_START { \ + if (log_file == stderr) \ + SetConsoleTextAttribute (GetStdHandle (STD_ERROR_HANDLE), (c)); \ + } G_STMT_END + g_static_mutex_lock (&win_print_mutex); + /* timestamp */ + fprintf (log_file, "%" GST_TIME_FORMAT " ", GST_TIME_ARGS (elapsed)); + fflush (log_file); + /* pid */ + SET_COLOR (available_colors[pid % G_N_ELEMENTS (available_colors)]); + fprintf (log_file, PID_FMT, pid); + fflush (log_file); + /* thread */ + SET_COLOR (clear); + fprintf (log_file, " " PTR_FMT " ", g_thread_self ()); + fflush (log_file); + /* level */ + SET_COLOR (levelcolormap[level]); + fprintf (log_file, "%s ", gst_debug_level_get_name (level)); + fflush (log_file); + /* category */ + SET_COLOR (gst_debug_construct_win_color (gst_debug_category_get_color + (category))); + fprintf (log_file, CAT_FMT, gst_debug_category_get_name (category), + file, line, function, obj); + fflush (log_file); + /* message */ + SET_COLOR (clear); + fprintf (log_file, " %s\n", gst_debug_message_get (message)); + fflush (log_file); + g_static_mutex_unlock (&win_print_mutex); +#endif + } else { + /* no color, all platforms */ +#define PRINT_FMT " "PID_FMT" "PTR_FMT" %s "CAT_FMT" %s\n" + fprintf (log_file, "%" GST_TIME_FORMAT PRINT_FMT, GST_TIME_ARGS (elapsed), + pid, g_thread_self (), gst_debug_level_get_name (level), + gst_debug_category_get_name (category), file, line, function, obj, + gst_debug_message_get (message)); + fflush (log_file); +#undef PRINT_FMT + } + + g_free (obj); +} + +/** + * gst_debug_level_get_name: + * @level: the level to get the name for + * + * Get the string representation of a debugging level + * + * Returns: the name + */ +const gchar * +gst_debug_level_get_name (GstDebugLevel level) +{ + switch (level) { + case GST_LEVEL_NONE: + return ""; + case GST_LEVEL_ERROR: + return "ERROR "; + case GST_LEVEL_WARNING: + return "WARN "; + case GST_LEVEL_INFO: + return "INFO "; + case GST_LEVEL_DEBUG: + return "DEBUG "; + case GST_LEVEL_LOG: + return "LOG "; + case GST_LEVEL_FIXME: + return "FIXME "; + case GST_LEVEL_TRACE: + return "TRACE "; + case GST_LEVEL_MEMDUMP: + return "MEMDUMP"; + default: + g_warning ("invalid level specified for gst_debug_level_get_name"); + return ""; + } +} + +/** + * gst_debug_add_log_function: + * @func: the function to use + * @data: (closure): user data + * + * Adds the logging function to the list of logging functions. + * Be sure to use #G_GNUC_NO_INSTRUMENT on that function, it is needed. + */ +void +gst_debug_add_log_function (GstLogFunction func, gpointer data) +{ + LogFuncEntry *entry; + GSList *list; + + if (func == NULL) + func = gst_debug_log_default; + + entry = g_slice_new (LogFuncEntry); + entry->func = func; + entry->user_data = data; + /* FIXME: we leak the old list here - other threads might access it right now + * in gst_debug_logv. Another solution is to lock the mutex in gst_debug_logv, + * but that is waaay costly. + * It'd probably be clever to use some kind of RCU here, but I don't know + * anything about that. + */ + g_static_mutex_lock (&__log_func_mutex); + list = g_slist_copy (__log_functions); + __log_functions = g_slist_prepend (list, entry); + g_static_mutex_unlock (&__log_func_mutex); + + GST_DEBUG ("prepended log function %p (user data %p) to log functions", + func, data); +} + +static gint +gst_debug_compare_log_function_by_func (gconstpointer entry, gconstpointer func) +{ + gpointer entryfunc = (gpointer) (((LogFuncEntry *) entry)->func); + + return (entryfunc < func) ? -1 : (entryfunc > func) ? 1 : 0; +} + +static gint +gst_debug_compare_log_function_by_data (gconstpointer entry, gconstpointer data) +{ + gpointer entrydata = ((LogFuncEntry *) entry)->user_data; + + return (entrydata < data) ? -1 : (entrydata > data) ? 1 : 0; +} + +static guint +gst_debug_remove_with_compare_func (GCompareFunc func, gpointer data) +{ + GSList *found; + GSList *new; + guint removals = 0; + + g_static_mutex_lock (&__log_func_mutex); + new = __log_functions; + while ((found = g_slist_find_custom (new, data, func))) { + if (new == __log_functions) { + /* make a copy when we have the first hit, so that we modify the copy and + * make that the new list later */ + new = g_slist_copy (new); + continue; + } + g_slice_free (LogFuncEntry, found->data); + new = g_slist_delete_link (new, found); + removals++; + } + /* FIXME: We leak the old list here. See _add_log_function for why. */ + __log_functions = new; + g_static_mutex_unlock (&__log_func_mutex); + + return removals; +} + +/** + * gst_debug_remove_log_function: + * @func: the log function to remove + * + * Removes all registered instances of the given logging functions. + * + * Returns: How many instances of the function were removed + */ +guint +gst_debug_remove_log_function (GstLogFunction func) +{ + guint removals; + + if (func == NULL) + func = gst_debug_log_default; + + removals = + gst_debug_remove_with_compare_func + (gst_debug_compare_log_function_by_func, (gpointer) func); + GST_DEBUG ("removed log function %p %d times from log function list", func, + removals); + + return removals; +} + +/** + * gst_debug_remove_log_function_by_data: + * @data: user data of the log function to remove + * + * Removes all registered instances of log functions with the given user data. + * + * Returns: How many instances of the function were removed + */ +guint +gst_debug_remove_log_function_by_data (gpointer data) +{ + guint removals; + + removals = + gst_debug_remove_with_compare_func + (gst_debug_compare_log_function_by_data, data); + GST_DEBUG + ("removed %d log functions with user data %p from log function list", + removals, data); + + return removals; +} + +/** + * gst_debug_set_colored: + * @colored: Whether to use colored output or not + * + * Sets or unsets the use of coloured debugging output. + * + * This function may be called before gst_init(). + */ +void +gst_debug_set_colored (gboolean colored) +{ + g_atomic_int_set (&__use_color, (gint) colored); +} + +/** + * gst_debug_is_colored: + * + * Checks if the debugging output should be colored. + * + * Returns: TRUE, if the debug output should be colored. + */ +gboolean +gst_debug_is_colored (void) +{ + return (gboolean) g_atomic_int_get (&__use_color); +} + +/** + * gst_debug_set_active: + * @active: Whether to use debugging output or not + * + * If activated, debugging messages are sent to the debugging + * handlers. + * It makes sense to deactivate it for speed issues. + * <note><para>This function is not threadsafe. It makes sense to only call it + * during initialization.</para></note> + */ +void +gst_debug_set_active (gboolean active) +{ + __gst_debug_enabled = active; + if (active) + __gst_debug_min = GST_LEVEL_COUNT; + else + __gst_debug_min = GST_LEVEL_NONE; +} + +/** + * gst_debug_is_active: + * + * Checks if debugging output is activated. + * + * Returns: TRUE, if debugging is activated + */ +gboolean +gst_debug_is_active (void) +{ + return __gst_debug_enabled; +} + +/** + * gst_debug_set_default_threshold: + * @level: level to set + * + * Sets the default threshold to the given level and updates all categories to + * use this threshold. + * + * This function may be called before gst_init(). + */ +void +gst_debug_set_default_threshold (GstDebugLevel level) +{ + g_atomic_int_set (&__default_level, level); + gst_debug_reset_all_thresholds (); +} + +/** + * gst_debug_get_default_threshold: + * + * Returns the default threshold that is used for new categories. + * + * Returns: the default threshold level + */ +GstDebugLevel +gst_debug_get_default_threshold (void) +{ + return (GstDebugLevel) g_atomic_int_get (&__default_level); +} + +static void +gst_debug_reset_threshold (gpointer category, gpointer unused) +{ + GstDebugCategory *cat = (GstDebugCategory *) category; + GSList *walk; + + g_static_mutex_lock (&__level_name_mutex); + walk = __level_name; + while (walk) { + LevelNameEntry *entry = walk->data; + + walk = g_slist_next (walk); + if (g_pattern_match_string (entry->pat, cat->name)) { + GST_LOG ("category %s matches pattern %p - gets set to level %d", + cat->name, entry->pat, entry->level); + gst_debug_category_set_threshold (cat, entry->level); + goto exit; + } + } + gst_debug_category_set_threshold (cat, gst_debug_get_default_threshold ()); + +exit: + g_static_mutex_unlock (&__level_name_mutex); +} + +static void +gst_debug_reset_all_thresholds (void) +{ + g_static_mutex_lock (&__cat_mutex); + g_slist_foreach (__categories, gst_debug_reset_threshold, NULL); + g_static_mutex_unlock (&__cat_mutex); +} + +static void +for_each_threshold_by_entry (gpointer data, gpointer user_data) +{ + GstDebugCategory *cat = (GstDebugCategory *) data; + LevelNameEntry *entry = (LevelNameEntry *) user_data; + + if (g_pattern_match_string (entry->pat, cat->name)) { + GST_LOG ("category %s matches pattern %p - gets set to level %d", + cat->name, entry->pat, entry->level); + gst_debug_category_set_threshold (cat, entry->level); + } +} + +/** + * gst_debug_set_threshold_for_name: + * @name: name of the categories to set + * @level: level to set them to + * + * Sets all categories which match the given glob style pattern to the given + * level. + */ +void +gst_debug_set_threshold_for_name (const gchar * name, GstDebugLevel level) +{ + GPatternSpec *pat; + LevelNameEntry *entry; + + g_return_if_fail (name != NULL); + + pat = g_pattern_spec_new (name); + entry = g_slice_new (LevelNameEntry); + entry->pat = pat; + entry->level = level; + g_static_mutex_lock (&__level_name_mutex); + __level_name = g_slist_prepend (__level_name, entry); + g_static_mutex_unlock (&__level_name_mutex); + g_static_mutex_lock (&__cat_mutex); + g_slist_foreach (__categories, for_each_threshold_by_entry, entry); + g_static_mutex_unlock (&__cat_mutex); +} + +/** + * gst_debug_unset_threshold_for_name: + * @name: name of the categories to set + * + * Resets all categories with the given name back to the default level. + */ +void +gst_debug_unset_threshold_for_name (const gchar * name) +{ + GSList *walk; + GPatternSpec *pat; + + g_return_if_fail (name != NULL); + + pat = g_pattern_spec_new (name); + g_static_mutex_lock (&__level_name_mutex); + walk = __level_name; + /* improve this if you want, it's mighty slow */ + while (walk) { + LevelNameEntry *entry = walk->data; + + if (g_pattern_spec_equal (entry->pat, pat)) { + __level_name = g_slist_remove_link (__level_name, walk); + g_pattern_spec_free (entry->pat); + g_slice_free (LevelNameEntry, entry); + g_slist_free_1 (walk); + walk = __level_name; + } + } + g_static_mutex_unlock (&__level_name_mutex); + g_pattern_spec_free (pat); + gst_debug_reset_all_thresholds (); +} + +GstDebugCategory * +_gst_debug_category_new (const gchar * name, guint color, + const gchar * description) +{ + GstDebugCategory *cat; + + g_return_val_if_fail (name != NULL, NULL); + + cat = g_slice_new (GstDebugCategory); + cat->name = g_strdup (name); + cat->color = color; + if (description != NULL) { + cat->description = g_strdup (description); + } else { + cat->description = g_strdup ("no description"); + } + g_atomic_int_set (&cat->threshold, 0); + gst_debug_reset_threshold (cat, NULL); + + /* add to category list */ + g_static_mutex_lock (&__cat_mutex); + __categories = g_slist_prepend (__categories, cat); + g_static_mutex_unlock (&__cat_mutex); + + return cat; +} + +/** + * gst_debug_category_free: + * @category: #GstDebugCategory to free. + * + * Removes and frees the category and all associated resources. + */ +void +gst_debug_category_free (GstDebugCategory * category) +{ + if (category == NULL) + return; + + /* remove from category list */ + g_static_mutex_lock (&__cat_mutex); + __categories = g_slist_remove (__categories, category); + g_static_mutex_unlock (&__cat_mutex); + + g_free ((gpointer) category->name); + g_free ((gpointer) category->description); + g_slice_free (GstDebugCategory, category); +} + +/** + * gst_debug_category_set_threshold: + * @category: a #GstDebugCategory to set threshold of. + * @level: the #GstDebugLevel threshold to set. + * + * Sets the threshold of the category to the given level. Debug information will + * only be output if the threshold is lower or equal to the level of the + * debugging message. + * <note><para> + * Do not use this function in production code, because other functions may + * change the threshold of categories as side effect. It is however a nice + * function to use when debugging (even from gdb). + * </para></note> + */ +void +gst_debug_category_set_threshold (GstDebugCategory * category, + GstDebugLevel level) +{ + g_return_if_fail (category != NULL); + + if (level > __gst_debug_min) { + __gst_debug_enabled = TRUE; + __gst_debug_min = level; + } + + g_atomic_int_set (&category->threshold, level); +} + +/** + * gst_debug_category_reset_threshold: + * @category: a #GstDebugCategory to reset threshold of. + * + * Resets the threshold of the category to the default level. Debug information + * will only be output if the threshold is lower or equal to the level of the + * debugging message. + * Use this function to set the threshold back to where it was after using + * gst_debug_category_set_threshold(). + */ +void +gst_debug_category_reset_threshold (GstDebugCategory * category) +{ + gst_debug_reset_threshold (category, NULL); +} + +/** + * gst_debug_category_get_threshold: + * @category: a #GstDebugCategory to get threshold of. + * + * Returns the threshold of a #GstDebugCategory. + * + * Returns: the #GstDebugLevel that is used as threshold. + */ +GstDebugLevel +gst_debug_category_get_threshold (GstDebugCategory * category) +{ + return (GstDebugLevel) g_atomic_int_get (&category->threshold); +} + +/** + * gst_debug_category_get_name: + * @category: a #GstDebugCategory to get name of. + * + * Returns the name of a debug category. + * + * Returns: the name of the category. + */ +const gchar * +gst_debug_category_get_name (GstDebugCategory * category) +{ + return category->name; +} + +/** + * gst_debug_category_get_color: + * @category: a #GstDebugCategory to get the color of. + * + * Returns the color of a debug category used when printing output in this + * category. + * + * Returns: the color of the category. + */ +guint +gst_debug_category_get_color (GstDebugCategory * category) +{ + return category->color; +} + +/** + * gst_debug_category_get_description: + * @category: a #GstDebugCategory to get the description of. + * + * Returns the description of a debug category. + * + * Returns: the description of the category. + */ +const gchar * +gst_debug_category_get_description (GstDebugCategory * category) +{ + return category->description; +} + +/** + * gst_debug_get_all_categories: + * + * Returns a snapshot of a all categories that are currently in use . This list + * may change anytime. + * The caller has to free the list after use. + * + * Returns: (transfer container) (element-type Gst.DebugCategory): the list of + * debug categories + */ +GSList * +gst_debug_get_all_categories (void) +{ + GSList *ret; + + g_static_mutex_lock (&__cat_mutex); + ret = g_slist_copy (__categories); + g_static_mutex_unlock (&__cat_mutex); + + return ret; +} + +GstDebugCategory * +_gst_debug_get_category (const gchar * name) +{ + GstDebugCategory *ret = NULL; + GSList *node; + + for (node = __categories; node; node = g_slist_next (node)) { + ret = (GstDebugCategory *) node->data; + if (!strcmp (name, ret->name)) { + return ret; + } + } + return NULL; +} + +/*** FUNCTION POINTERS ********************************************************/ + +static GHashTable *__gst_function_pointers; /* NULL */ +static GStaticMutex __dbg_functions_mutex = G_STATIC_MUTEX_INIT; + +/* This function MUST NOT return NULL */ +const gchar * +_gst_debug_nameof_funcptr (GstDebugFuncPtr func) +{ + gchar *ptrname; + +#ifdef HAVE_DLADDR + Dl_info dl_info; +#endif + + if (G_UNLIKELY (func == NULL)) + return "(NULL)"; + + g_static_mutex_lock (&__dbg_functions_mutex); + if (G_LIKELY (__gst_function_pointers)) { + ptrname = g_hash_table_lookup (__gst_function_pointers, (gpointer) func); + g_static_mutex_unlock (&__dbg_functions_mutex); + if (G_LIKELY (ptrname)) + return ptrname; + } else { + g_static_mutex_unlock (&__dbg_functions_mutex); + } + /* we need to create an entry in the hash table for this one so we don't leak + * the name */ +#ifdef HAVE_DLADDR + if (dladdr ((gpointer) func, &dl_info) && dl_info.dli_sname) { + gchar *name = g_strdup (dl_info.dli_sname); + + _gst_debug_register_funcptr (func, name); + return name; + } else +#endif + { + gchar *name = g_strdup_printf ("%p", (gpointer) func); + + _gst_debug_register_funcptr (func, name); + return name; + } +} + +void +_gst_debug_register_funcptr (GstDebugFuncPtr func, const gchar * ptrname) +{ + gpointer ptr = (gpointer) func; + + g_static_mutex_lock (&__dbg_functions_mutex); + + if (!__gst_function_pointers) + __gst_function_pointers = g_hash_table_new (g_direct_hash, g_direct_equal); + if (!g_hash_table_lookup (__gst_function_pointers, ptr)) + g_hash_table_insert (__gst_function_pointers, ptr, (gpointer) ptrname); + + g_static_mutex_unlock (&__dbg_functions_mutex); +} + +/*** PRINTF EXTENSIONS ********************************************************/ + +#ifdef HAVE_PRINTF_EXTENSION +static int +_gst_info_printf_extension_ptr (FILE * stream, const struct printf_info *info, + const void *const *args) +{ + char *buffer; + int len; + void *ptr; + + buffer = NULL; + ptr = *(void **) args[0]; + + buffer = gst_debug_print_object (ptr); + len = fprintf (stream, "%*s", (info->left ? -info->width : info->width), + buffer); + + g_free (buffer); + return len; +} + +static int +_gst_info_printf_extension_segment (FILE * stream, + const struct printf_info *info, const void *const *args) +{ + char *buffer; + int len; + void *ptr; + + buffer = NULL; + ptr = *(void **) args[0]; + + buffer = gst_debug_print_segment (ptr); + len = fprintf (stream, "%*s", (info->left ? -info->width : info->width), + buffer); + + g_free (buffer); + return len; +} + +#ifdef HAVE_REGISTER_PRINTF_SPECIFIER +static int +_gst_info_printf_extension_arginfo (const struct printf_info *info, size_t n, + int *argtypes, int *size) +#else +static int +_gst_info_printf_extension_arginfo (const struct printf_info *info, size_t n, + int *argtypes) +#endif +{ + if (n > 0) { + argtypes[0] = PA_POINTER; +#ifdef HAVE_REGISTER_PRINTF_SPECIFIER + *size = sizeof (gpointer); +#endif + } + return 1; +} +#endif /* HAVE_PRINTF_EXTENSION */ + +static void +gst_info_dump_mem_line (gchar * linebuf, gsize linebuf_size, + const guint8 * mem, gsize mem_offset, gsize mem_size) +{ + gchar hexstr[50], ascstr[18], digitstr[4]; + + if (mem_size > 16) + mem_size = 16; + + hexstr[0] = '\0'; + ascstr[0] = '\0'; + + if (mem != NULL) { + guint i = 0; + + mem += mem_offset; + while (i < mem_size) { + ascstr[i] = (g_ascii_isprint (mem[i])) ? mem[i] : '.'; + g_snprintf (digitstr, sizeof (digitstr), "%02x ", mem[i]); + g_strlcat (hexstr, digitstr, sizeof (hexstr)); + ++i; + } + ascstr[i] = '\0'; + } + + g_snprintf (linebuf, linebuf_size, "%08x: %-48.48s %-16.16s", + (guint) mem_offset, hexstr, ascstr); +} + +void +_gst_debug_dump_mem (GstDebugCategory * cat, const gchar * file, + const gchar * func, gint line, GObject * obj, const gchar * msg, + const guint8 * data, guint length) +{ + guint off = 0; + + gst_debug_log ((cat), GST_LEVEL_MEMDUMP, file, func, line, obj, "--------" + "-------------------------------------------------------------------"); + + if (msg != NULL && *msg != '\0') { + gst_debug_log ((cat), GST_LEVEL_MEMDUMP, file, func, line, obj, "%s", msg); + } + + while (off < length) { + gchar buf[128]; + + /* gst_info_dump_mem_line will process 16 bytes at most */ + gst_info_dump_mem_line (buf, sizeof (buf), data, off, length - off); + gst_debug_log (cat, GST_LEVEL_MEMDUMP, file, func, line, obj, "%s", buf); + off += 16; + } + + gst_debug_log ((cat), GST_LEVEL_MEMDUMP, file, func, line, obj, "--------" + "-------------------------------------------------------------------"); +} + +#else /* !GST_DISABLE_GST_DEBUG */ +#ifndef GST_REMOVE_DISABLED + +GstDebugCategory * +_gst_debug_category_new (const gchar * name, guint color, + const gchar * description) +{ + return NULL; +} + +void +_gst_debug_register_funcptr (GstDebugFuncPtr func, const gchar * ptrname) +{ +} + +/* This function MUST NOT return NULL */ +const gchar * +_gst_debug_nameof_funcptr (GstDebugFuncPtr func) +{ + return "(NULL)"; +} + +void +gst_debug_log (GstDebugCategory * category, GstDebugLevel level, + const gchar * file, const gchar * function, gint line, + GObject * object, const gchar * format, ...) +{ +} + +void +gst_debug_log_valist (GstDebugCategory * category, GstDebugLevel level, + const gchar * file, const gchar * function, gint line, + GObject * object, const gchar * format, va_list args) +{ +} + +const gchar * +gst_debug_message_get (GstDebugMessage * message) +{ + return ""; +} + +void +gst_debug_log_default (GstDebugCategory * category, GstDebugLevel level, + const gchar * file, const gchar * function, gint line, + GObject * object, GstDebugMessage * message, gpointer unused) +{ +} + +const gchar * +gst_debug_level_get_name (GstDebugLevel level) +{ + return "NONE"; +} + +void +gst_debug_add_log_function (GstLogFunction func, gpointer data) +{ +} + +guint +gst_debug_remove_log_function (GstLogFunction func) +{ + return 0; +} + +guint +gst_debug_remove_log_function_by_data (gpointer data) +{ + return 0; +} + +void +gst_debug_set_active (gboolean active) +{ +} + +gboolean +gst_debug_is_active (void) +{ + return FALSE; +} + +void +gst_debug_set_colored (gboolean colored) +{ +} + +gboolean +gst_debug_is_colored (void) +{ + return FALSE; +} + +void +gst_debug_set_default_threshold (GstDebugLevel level) +{ +} + +GstDebugLevel +gst_debug_get_default_threshold (void) +{ + return GST_LEVEL_NONE; +} + +void +gst_debug_set_threshold_for_name (const gchar * name, GstDebugLevel level) +{ +} + +void +gst_debug_unset_threshold_for_name (const gchar * name) +{ +} + +void +gst_debug_category_free (GstDebugCategory * category) +{ +} + +void +gst_debug_category_set_threshold (GstDebugCategory * category, + GstDebugLevel level) +{ +} + +void +gst_debug_category_reset_threshold (GstDebugCategory * category) +{ +} + +GstDebugLevel +gst_debug_category_get_threshold (GstDebugCategory * category) +{ + return GST_LEVEL_NONE; +} + +const gchar * +gst_debug_category_get_name (GstDebugCategory * category) +{ + return ""; +} + +guint +gst_debug_category_get_color (GstDebugCategory * category) +{ + return 0; +} + +const gchar * +gst_debug_category_get_description (GstDebugCategory * category) +{ + return ""; +} + +GSList * +gst_debug_get_all_categories (void) +{ + return NULL; +} + +GstDebugCategory * +_gst_debug_get_category (const gchar * name) +{ + return NULL; +} + +gchar * +gst_debug_construct_term_color (guint colorinfo) +{ + return g_strdup ("00"); +} + +gint +gst_debug_construct_win_color (guint colorinfo) +{ + return 0; +} + +gboolean +_priv_gst_in_valgrind (void) +{ + return FALSE; +} + +void +_gst_debug_dump_mem (GstDebugCategory * cat, const gchar * file, + const gchar * func, gint line, GObject * obj, const gchar * msg, + const guint8 * data, guint length) +{ +} +#endif /* GST_REMOVE_DISABLED */ +#endif /* GST_DISABLE_GST_DEBUG */ + + +#ifdef GST_ENABLE_FUNC_INSTRUMENTATION +/* FIXME make this thread specific */ +static GSList *stack_trace = NULL; + +void +__cyg_profile_func_enter (void *this_fn, void *call_site) + G_GNUC_NO_INSTRUMENT; + void __cyg_profile_func_enter (void *this_fn, void *call_site) +{ + gchar *name = _gst_debug_nameof_funcptr (this_fn); + gchar *site = _gst_debug_nameof_funcptr (call_site); + + GST_CAT_DEBUG (GST_CAT_CALL_TRACE, "entering function %s from %s", name, + site); + stack_trace = + g_slist_prepend (stack_trace, g_strdup_printf ("%8p in %s from %p (%s)", + this_fn, name, call_site, site)); + + g_free (name); + g_free (site); +} + +void +__cyg_profile_func_exit (void *this_fn, void *call_site) + G_GNUC_NO_INSTRUMENT; + void __cyg_profile_func_exit (void *this_fn, void *call_site) +{ + gchar *name = _gst_debug_nameof_funcptr (this_fn); + + GST_CAT_DEBUG (GST_CAT_CALL_TRACE, "leaving function %s", name); + g_free (stack_trace->data); + stack_trace = g_slist_delete_link (stack_trace, stack_trace); + + g_free (name); +} + +/** + * gst_debug_print_stack_trace: + * + * If GST_ENABLE_FUNC_INSTRUMENTATION is defined a stacktrace is available for + * gstreamer code, which can be printed with this function. + */ +void +gst_debug_print_stack_trace (void) +{ + GSList *walk = stack_trace; + gint count = 0; + + if (walk) + walk = g_slist_next (walk); + + while (walk) { + gchar *name = (gchar *) walk->data; + + g_print ("#%-2d %s\n", count++, name); + + walk = g_slist_next (walk); + } +} +#else +void +gst_debug_print_stack_trace (void) +{ + /* nothing because it's compiled out */ +} + +#endif /* GST_ENABLE_FUNC_INSTRUMENTATION */ diff --git a/gst/gstinfo.h b/gst/gstinfo.h new file mode 100644 index 0000000..f841cd7 --- /dev/null +++ b/gst/gstinfo.h @@ -0,0 +1,1549 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gstinfo.h: debugging functions + * + * 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 __GSTINFO_H__ +#define __GSTINFO_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gst/gstconfig.h> + +G_BEGIN_DECLS + +/** + * GstDebugLevel: + * @GST_LEVEL_NONE: No debugging level specified or desired. Used to deactivate + * debugging output. + * @GST_LEVEL_ERROR: Error messages are to be used only when an error occured + * that stops the application from keeping working correctly. + * An examples is gst_element_error, which outputs a message with this priority. + * It does not mean that the application is terminating as with g_errror. + * @GST_LEVEL_WARNING: Warning messages are to inform about abnormal behaviour + * that could lead to problems or weird behaviour later on. An example of this + * would be clocking issues ("your computer is pretty slow") or broken input + * data ("Can't synchronize to stream.") + * @GST_LEVEL_INFO: Informational messages should be used to keep the developer + * updated about what is happening. + * Examples where this should be used are when a typefind function has + * successfully determined the type of the stream or when an mp3 plugin detects + * the format to be used. ("This file has mono sound.") + * @GST_LEVEL_DEBUG: Debugging messages should be used when something common + * happens that is not the expected default behavior, or something that's + * useful to know but doesn't happen all the time (ie. per loop iteration or + * buffer processed or event handled). + * An example would be notifications about state changes or receiving/sending + * of events. + * @GST_LEVEL_LOG: Log messages are messages that are very common but might be + * useful to know. As a rule of thumb a pipeline that is iterating as expected + * should never output anything else but LOG messages. Use this log level to + * log recurring information in chain functions and loop functions, for + * example. + * @GST_LEVEL_FIXME: Fixme messages are messages that indicate that something + * in the executed code path is not fully implemented or handled yet. Note + * that this does not replace proper error handling in any way, the purpose + * of this message is to make it easier to spot incomplete/unfinished pieces + * of code when reading the debug log. (Since: 0.10.23) + * @GST_LEVEL_TRACE: Tracing-related messages (Since: 0.10.30) + * Examples for this are referencing/dereferencing of objects. + * @GST_LEVEL_MEMDUMP: memory dump messages are used to log (small) chunks of + * data as memory dumps in the log. They will be displayed as hexdump with + * ASCII characters. (Since: 0.10.23) + * @GST_LEVEL_COUNT: The number of defined debugging levels. + * + * The level defines the importance of a debugging message. The more important a + * message is, the greater the probability that the debugging system outputs it. + */ +typedef enum { + GST_LEVEL_NONE = 0, + GST_LEVEL_ERROR, + GST_LEVEL_WARNING, + GST_LEVEL_INFO, + GST_LEVEL_DEBUG, + GST_LEVEL_LOG, + GST_LEVEL_FIXME = 6, + GST_LEVEL_TRACE = 7, + /* add more */ + GST_LEVEL_MEMDUMP = 9, + /* add more */ + GST_LEVEL_COUNT +} GstDebugLevel; + +/** + * GST_LEVEL_DEFAULT: + * + * Defines the default debugging level to be used with GStreamer. It is normally + * set to #GST_LEVEL_NONE so nothing get printed. + * As it can be configured at compile time, developer builds may chose to + * override that though. + * You can use this as an argument to gst_debug_set_default_threshold() to + * reset the debugging output to default behaviour. + */ +#ifndef GST_LEVEL_DEFAULT +#define GST_LEVEL_DEFAULT GST_LEVEL_NONE +#endif + +/* defines for format (colors etc) + * don't change them around, it uses terminal layout + * Terminal color strings: + * 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed + * Text color codes: + * 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white + * Background color codes: + * 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white + */ +/** + * GstDebugColorFlags: + * @GST_DEBUG_FG_BLACK: Use black as foreground color. + * @GST_DEBUG_FG_RED: Use red as foreground color. + * @GST_DEBUG_FG_GREEN: Use green as foreground color. + * @GST_DEBUG_FG_YELLOW: Use yellow as foreground color. + * @GST_DEBUG_FG_BLUE: Use blue as foreground color. + * @GST_DEBUG_FG_MAGENTA: Use magenta as foreground color. + * @GST_DEBUG_FG_CYAN: Use cyan as foreground color. + * @GST_DEBUG_FG_WHITE: Use white as foreground color. + * @GST_DEBUG_BG_BLACK: Use black as background color. + * @GST_DEBUG_BG_RED: Use red as background color. + * @GST_DEBUG_BG_GREEN: Use green as background color. + * @GST_DEBUG_BG_YELLOW: Use yellow as background color. + * @GST_DEBUG_BG_BLUE: Use blue as background color. + * @GST_DEBUG_BG_MAGENTA: Use magenta as background color. + * @GST_DEBUG_BG_CYAN: Use cyan as background color. + * @GST_DEBUG_BG_WHITE: Use white as background color. + * @GST_DEBUG_BOLD: Make the output bold. + * @GST_DEBUG_UNDERLINE: Underline the output. + * + * These are some terminal style flags you can use when creating your + * debugging categories to make them stand out in debugging output. + */ +typedef enum { + /* colors */ + GST_DEBUG_FG_BLACK = 0x0000, + GST_DEBUG_FG_RED = 0x0001, + GST_DEBUG_FG_GREEN = 0x0002, + GST_DEBUG_FG_YELLOW = 0x0003, + GST_DEBUG_FG_BLUE = 0x0004, + GST_DEBUG_FG_MAGENTA = 0x0005, + GST_DEBUG_FG_CYAN = 0x0006, + GST_DEBUG_FG_WHITE = 0x0007, + /* background colors */ + GST_DEBUG_BG_BLACK = 0x0000, + GST_DEBUG_BG_RED = 0x0010, + GST_DEBUG_BG_GREEN = 0x0020, + GST_DEBUG_BG_YELLOW = 0x0030, + GST_DEBUG_BG_BLUE = 0x0040, + GST_DEBUG_BG_MAGENTA = 0x0050, + GST_DEBUG_BG_CYAN = 0x0060, + GST_DEBUG_BG_WHITE = 0x0070, + /* other formats */ + GST_DEBUG_BOLD = 0x0100, + GST_DEBUG_UNDERLINE = 0x0200 +} GstDebugColorFlags; + +#define GST_DEBUG_FG_MASK (0x000F) +#define GST_DEBUG_BG_MASK (0x00F0) +#define GST_DEBUG_FORMAT_MASK (0xFF00) + +typedef struct _GstDebugCategory GstDebugCategory; +/** + * GstDebugCategory: + * + * This is the struct that describes the categories. Once initialized with + * #GST_DEBUG_CATEGORY_INIT, its values can't be changed anymore. + */ +struct _GstDebugCategory { + /*< private >*/ + gint threshold; + guint color; /* see defines above */ + + const gchar * name; + const gchar * description; +}; + +/********** some convenience macros for debugging **********/ + +/** + * GST_STR_NULL: + * @str: The string to check. + * + * Macro to use when a string must not be NULL, but may be NULL. If the string + * is NULL, "(NULL)" is printed instead. + * In GStreamer printf string arguments may not be NULL, because on some + * platforms (ie Solaris) the libc crashes in that case. This includes debugging + * strings. + */ +#define GST_STR_NULL(str) ((str) ? (str) : "(NULL)") + +/* FIXME, not MT safe */ +/** + * GST_DEBUG_PAD_NAME: + * @pad: The pad to debug. + * + * Evaluates to 2 strings, that describe the pad. Often used in debugging + * statements. + */ +#define GST_DEBUG_PAD_NAME(pad) \ + (pad != NULL) ? \ + ((GST_OBJECT_PARENT(pad) != NULL) ? \ + GST_STR_NULL (GST_OBJECT_NAME (GST_OBJECT_PARENT(pad))) : \ + "''" ) : "''", \ + (pad != NULL) ? GST_STR_NULL (GST_OBJECT_NAME (pad)) : "''" + +/** + * GST_FUNCTION: + * + * This macro should evaluate to the name of the current function and be should + * be defined when configuring your project, as it is compiler dependant. If it + * is not defined, some default value is used. It is used to provide debugging + * output with the function name of the message. + * + * Note that this is different from G_STRFUNC as we do not want the full + * function signature in C++ code. + */ +#ifndef GST_FUNCTION +#if defined (__GNUC__) || (defined (_MSC_VER) && _MSC_VER >= 1300) +# define GST_FUNCTION ((const char*) (__FUNCTION__)) +#elif defined (__STDC__) && defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +# define GST_FUNCTION ((const char*) (__func__)) +#else +# define GST_FUNCTION ((const char*) ("???")) +#endif +#endif /* ifndef GST_FUNCTION */ + + +typedef struct _GstDebugMessage GstDebugMessage; + +/** + * GstLogFunction: + * @category: a #GstDebugCategory + * @level: a #GstDebugLevel + * @file: file name + * @function: function name + * @line: line number + * @object: a #GObject + * @message: the message + * @data: user data for the log function + * + * Function prototype for a logging function that can be registered with + * gst_debug_add_log_function(). + * Use G_GNUC_NO_INSTRUMENT on that function. + */ +typedef void (*GstLogFunction) (GstDebugCategory * category, + GstDebugLevel level, + const gchar * file, + const gchar * function, + gint line, + GObject * object, + GstDebugMessage * message, + gpointer data); + + +/* FIXME 0.11: move this into private headers */ +void _gst_debug_init (void); + + +#ifdef GST_USING_PRINTF_EXTENSION + +/* not using G_GNUC_PRINTF, since gcc will choke on GST_PTR_FORMAT being %P */ +void gst_debug_log (GstDebugCategory * category, + GstDebugLevel level, + const gchar * file, + const gchar * function, + gint line, + GObject * object, + const gchar * format, + ...) G_GNUC_NO_INSTRUMENT; + +#else /* GST_USING_PRINTF_EXTENSION */ + +void gst_debug_log (GstDebugCategory * category, + GstDebugLevel level, + const gchar * file, + const gchar * function, + gint line, + GObject * object, + const gchar * format, + ...) G_GNUC_PRINTF (7, 8) G_GNUC_NO_INSTRUMENT; + +#endif /* GST_USING_PRINTF_EXTENSION */ + +void gst_debug_log_valist (GstDebugCategory * category, + GstDebugLevel level, + const gchar * file, + const gchar * function, + gint line, + GObject * object, + const gchar * format, + va_list args) G_GNUC_NO_INSTRUMENT; + +/* do not use this function, use the GST_DEBUG_CATEGORY_INIT macro */ +GstDebugCategory *_gst_debug_category_new (const gchar * name, + guint color, + const gchar * description); +/* do not use this function, use the GST_DEBUG_CATEGORY_GET macro */ +GstDebugCategory *_gst_debug_get_category (const gchar *name); + + +/* do not use this function, use the GST_CAT_MEMDUMP_* macros */ +void _gst_debug_dump_mem (GstDebugCategory * cat, const gchar * file, + const gchar * func, gint line, GObject * obj, const gchar * msg, + const guint8 * data, guint length); + +/* we define this to avoid a compiler warning regarding a cast from a function + * pointer to a void pointer + * (see https://bugzilla.gnome.org/show_bug.cgi?id=309253) + */ +typedef void (* GstDebugFuncPtr) (void); + +/* do no use these functions, use the GST_DEBUG*_FUNCPTR macros */ +void _gst_debug_register_funcptr (GstDebugFuncPtr func, + const gchar * ptrname); +const gchar * + _gst_debug_nameof_funcptr (GstDebugFuncPtr func) G_GNUC_NO_INSTRUMENT; + + +const gchar * gst_debug_message_get (GstDebugMessage * message); + +void gst_debug_log_default (GstDebugCategory * category, + GstDebugLevel level, + const gchar * file, + const gchar * function, + gint line, + GObject * object, + GstDebugMessage * message, + gpointer unused) G_GNUC_NO_INSTRUMENT; + +const gchar * gst_debug_level_get_name (GstDebugLevel level); + +void gst_debug_add_log_function (GstLogFunction func, + gpointer data); + +guint gst_debug_remove_log_function (GstLogFunction func); +guint gst_debug_remove_log_function_by_data (gpointer data); + +void gst_debug_set_active (gboolean active); +gboolean gst_debug_is_active (void); + +void gst_debug_set_colored (gboolean colored); +gboolean gst_debug_is_colored (void); + +void gst_debug_set_default_threshold (GstDebugLevel level); +GstDebugLevel gst_debug_get_default_threshold (void); +void gst_debug_set_threshold_for_name (const gchar * name, + GstDebugLevel level); +void gst_debug_unset_threshold_for_name (const gchar * name); + + +void gst_debug_category_free (GstDebugCategory * category); +void gst_debug_category_set_threshold (GstDebugCategory * category, + GstDebugLevel level); +void gst_debug_category_reset_threshold (GstDebugCategory * category); +GstDebugLevel gst_debug_category_get_threshold (GstDebugCategory * category); +const gchar * gst_debug_category_get_name (GstDebugCategory * category); +guint gst_debug_category_get_color (GstDebugCategory * category); +const gchar * gst_debug_category_get_description (GstDebugCategory * category); +GSList * gst_debug_get_all_categories (void); + + +gchar * gst_debug_construct_term_color (guint colorinfo); +gint gst_debug_construct_win_color (guint colorinfo); + + +#ifndef GST_DISABLE_GST_DEBUG + +#define gst_debug_add_log_function(func,data) \ +G_STMT_START{ \ + if (func == gst_debug_log_default) { \ + gst_debug_add_log_function(NULL,data); \ + } else { \ + gst_debug_add_log_function(func,data); \ + } \ +}G_STMT_END + +#define gst_debug_remove_log_function(func) \ + (func == gst_debug_log_default) ? \ + gst_debug_remove_log_function(NULL) : \ + gst_debug_remove_log_function(func) + +/** + * GST_DEBUG_CATEGORY: + * @cat: the category + * + * Defines a GstDebugCategory variable. + * This macro expands to nothing if debugging is disabled. + */ +#define GST_DEBUG_CATEGORY(cat) GstDebugCategory *cat = NULL +/** + * GST_DEBUG_CATEGORY_EXTERN: + * @cat: the category + * + * Declares a GstDebugCategory variable as extern. Use in header files. + * This macro expands to nothing if debugging is disabled. + */ +#define GST_DEBUG_CATEGORY_EXTERN(cat) extern GstDebugCategory *cat + +/** + * GST_DEBUG_CATEGORY_STATIC: + * @cat: the category + * + * Defines a static GstDebugCategory variable. + * This macro expands to nothing if debugging is disabled. + */ +#define GST_DEBUG_CATEGORY_STATIC(cat) static GstDebugCategory *cat = NULL + +/** + * GST_DEBUG_CATEGORY_INIT: + * @cat: the category to initialize. + * @name: the name of the category. + * @color: the colors to use for a color representation or 0 for no color. + * @description: optional description of the category. + * + * Initializes a new #GstDebugCategory with the given properties and set to + * the default threshold. + * + * <note> + * <para> + * This macro expands to nothing if debugging is disabled. + * </para> + * <para> + * When naming your category, please follow the following conventions to ensure + * that the pattern matching for categories works as expected. It is not + * earth-shattering if you don't follow these conventions, but it would be nice + * for everyone. + * </para> + * <para> + * If you define a category for a plugin or a feature of it, name the category + * like the feature. So if you wanted to write a "filesrc" element, you would + * name the category "filesrc". Use lowercase letters only. + * If you define more than one category for the same element, append an + * underscore and an identifier to your categories, like this: "filesrc_cache" + * </para> + * <para> + * If you create a library or an application using debugging categories, use a + * common prefix followed by an underscore for all your categories. GStreamer + * uses the GST prefix so GStreamer categories look like "GST_STATES". Be sure + * to include uppercase letters. + * </para> + * </note> + */ +#define GST_DEBUG_CATEGORY_INIT(cat,name,color,description) G_STMT_START{\ + if (cat == NULL) \ + cat = _gst_debug_category_new (name,color,description); \ +}G_STMT_END + +/** + * GST_DEBUG_CATEGORY_GET: + * @cat: the category to initialize. + * @name: log category name + * + * Looks up an existing #GstDebugCategory by its @name and sets @cat. If the + * category is not found, but GST_CAT_DEFAULT is defined, that is assigned to + * @cat. Otherwise @cat will be NULL. + * + * |[ + * GST_DEBUG_CATEGORY_STATIC (gst_myplugin_debug); + * #define GST_CAT_DEFAULT gst_myplugin_debug + * GST_DEBUG_CATEGORY_STATIC (GST_CAT_PERFORMANCE); + * ... + * GST_DEBUG_CATEGORY_INIT (gst_myplugin_debug, "myplugin", 0, "nice element"); + * GST_DEBUG_CATEGORY_GET (GST_CAT_PERFORMANCE, "GST_PERFORMANCE"); + * ]| + * + * Since: 0.10.24 + */ +#ifdef GST_CAT_DEFAULT +#define GST_DEBUG_CATEGORY_GET(cat,name) G_STMT_START{\ + cat = _gst_debug_get_category (name); \ + if (!cat) { \ + cat = GST_CAT_DEFAULT; \ + } \ +}G_STMT_END +#else +#define GST_DEBUG_CATEGORY_GET(cat,name) G_STMT_START{\ + cat = _gst_debug_get_category (name); \ +}G_STMT_END +#endif + +/** + * GST_CAT_DEFAULT: + * + * Default gstreamer core debug log category. Please define your own. + */ +GST_EXPORT GstDebugCategory * GST_CAT_DEFAULT; +/* this symbol may not be used */ +extern gboolean __gst_debug_enabled; + +/* since 0.10.7, the min debug level, used for quickly discarding debug + * messages that fall under the threshold. */ +GST_EXPORT GstDebugLevel __gst_debug_min; + +/** + * GST_CAT_LEVEL_LOG: + * @cat: category to use + * @level: the severity of the message + * @object: the #GObject the message belongs to or NULL if none + * @...: A printf-style message to output + * + * Outputs a debugging message. This is the most general macro for outputting + * debugging messages. You will probably want to use one of the ones described + * below. + */ +#ifdef G_HAVE_ISO_VARARGS +#define GST_CAT_LEVEL_LOG(cat,level,object,...) G_STMT_START{ \ + if (G_UNLIKELY (level <= __gst_debug_min)) { \ + gst_debug_log ((cat), (level), __FILE__, GST_FUNCTION, __LINE__, \ + (GObject *) (object), __VA_ARGS__); \ + } \ +}G_STMT_END +#else /* G_HAVE_GNUC_VARARGS */ +#ifdef G_HAVE_GNUC_VARARGS +#define GST_CAT_LEVEL_LOG(cat,level,object,args...) G_STMT_START{ \ + if (G_UNLIKELY (level <= __gst_debug_min)) { \ + gst_debug_log ((cat), (level), __FILE__, GST_FUNCTION, __LINE__, \ + (GObject *) (object), ##args ); \ + } \ +}G_STMT_END +#else /* no variadic macros, use inline */ +static inline void +GST_CAT_LEVEL_LOG_valist (GstDebugCategory * cat, + GstDebugLevel level, gpointer object, const char *format, va_list varargs) +{ + if (G_UNLIKELY (level <= __gst_debug_min)) { + gst_debug_log_valist (cat, level, "", "", 0, (GObject *) object, format, + varargs); + } +} + +static inline void +GST_CAT_LEVEL_LOG (GstDebugCategory * cat, GstDebugLevel level, + gpointer object, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, level, object, format, varargs); + va_end (varargs); +} +#endif +#endif /* G_HAVE_ISO_VARARGS */ + +/* This one doesn't have varargs in the macro, so it's different than all the + * other macros and hence in a separate block right here. Docs chunks are + * with the other doc chunks below though. */ +#define __GST_CAT_MEMDUMP_LOG(cat,object,msg,data,length) G_STMT_START{ \ + if (G_UNLIKELY (GST_LEVEL_MEMDUMP <= __gst_debug_min)) { \ + _gst_debug_dump_mem ((cat), __FILE__, GST_FUNCTION, __LINE__, \ + (GObject *) (object), (msg), (data), (length)); \ + } \ +}G_STMT_END + +#define GST_CAT_MEMDUMP_OBJECT(cat,obj,msg,data,length) \ + __GST_CAT_MEMDUMP_LOG(cat,obj,msg,data,length) +#define GST_CAT_MEMDUMP(cat,msg,data,length) \ + __GST_CAT_MEMDUMP_LOG(cat,NULL,msg,data,length) +#define GST_MEMDUMP_OBJECT(obj,msg,data,length) \ + __GST_CAT_MEMDUMP_LOG(GST_CAT_DEFAULT,obj,msg,data,length) +#define GST_MEMDUMP(msg,data,length) \ + __GST_CAT_MEMDUMP_LOG(GST_CAT_DEFAULT,NULL,msg,data,length) + +/** + * GST_CAT_ERROR_OBJECT: + * @cat: category to use + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output an error message belonging to the given object in the given category. + */ +/** + * GST_CAT_WARNING_OBJECT: + * @cat: category to use + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output a warning message belonging to the given object in the given category. + */ +/** + * GST_CAT_INFO_OBJECT: + * @cat: category to use + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output an informational message belonging to the given object in the given + * category. + */ +/** + * GST_CAT_DEBUG_OBJECT: + * @cat: category to use + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output an debugging message belonging to the given object in the given category. + */ +/** + * GST_CAT_LOG_OBJECT: + * @cat: category to use + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output an logging message belonging to the given object in the given category. + */ +/** + * GST_CAT_FIXME_OBJECT: + * @cat: category to use + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output a fixme message belonging to the given object in the given category. + * + * Since: 0.10.23 + */ +/** + * GST_CAT_TRACE_OBJECT: + * @cat: category to use + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output a tracing message belonging to the given object in the given + * category. + * + * Since: 0.10.30 + */ +/** + * GST_CAT_MEMDUMP_OBJECT: + * @cat: category to use + * @obj: the #GObject the message belongs to + * @msg: message string to log with the data + * @data: pointer to the data to output + * @length: length of the data to output + * + * Output a hexdump of @data relating to the given object in the given + * category. + * + * Since: 0.10.23 + */ + + +/** + * GST_CAT_ERROR: + * @cat: category to use + * @...: printf-style message to output + * + * Output an error message in the given category. + */ +/** + * GST_CAT_WARNING: + * @cat: category to use + * @...: printf-style message to output + * + * Output an warning message in the given category. + */ +/** + * GST_CAT_INFO: + * @cat: category to use + * @...: printf-style message to output + * + * Output an informational message in the given category. + */ +/** + * GST_CAT_DEBUG: + * @cat: category to use + * @...: printf-style message to output + * + * Output an debugging message in the given category. + */ +/** + * GST_CAT_LOG: + * @cat: category to use + * @...: printf-style message to output + * + * Output an logging message in the given category. + */ +/** + * GST_CAT_FIXME: + * @cat: category to use + * @...: printf-style message to output + * + * Output an fixme message in the given category. + * + * Since: 0.10.23 + */ +/** + * GST_CAT_TRACE: + * @cat: category to use + * @...: printf-style message to output + * + * Output a tracing message in the given category. + * + * Since: 0.10.30 + */ +/** + * GST_CAT_MEMDUMP: + * @cat: category to use + * @msg: message string to log with the data + * @data: pointer to the data to output + * @length: length of the data to output + * + * Output a hexdump of @data in the given category. + * + * Since: 0.10.23 + */ + + +/** + * GST_ERROR_OBJECT: + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output an error message belonging to the given object in the default category. + */ +/** + * GST_WARNING_OBJECT: + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output a warning message belonging to the given object in the default category. + */ +/** + * GST_INFO_OBJECT: + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output an informational message belonging to the given object in the default + * category. + */ +/** + * GST_DEBUG_OBJECT: + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output a debugging message belonging to the given object in the default + * category. + */ +/** + * GST_LOG_OBJECT: + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output a logging message belonging to the given object in the default category. + */ +/** + * GST_FIXME_OBJECT: + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output a fixme message belonging to the given object in the default category. + * + * Since: 0.10.23 + */ +/** + * GST_TRACE_OBJECT: + * @obj: the #GObject the message belongs to + * @...: printf-style message to output + * + * Output a tracing message belonging to the given object in the default category. + * + * Since: 0.10.30 + */ +/** + * GST_MEMDUMP_OBJECT: + * @obj: the #GObject the message belongs to + * @msg: message string to log with the data + * @data: pointer to the data to output + * @length: length of the data to output + * + * Output a logging message belonging to the given object in the default category. + * + * Since: 0.10.23 + */ + + +/** + * GST_ERROR: + * @...: printf-style message to output + * + * Output an error message in the default category. + */ +/** + * GST_WARNING: + * @...: printf-style message to output + * + * Output a warning message in the default category. + */ +/** + * GST_INFO: + * @...: printf-style message to output + * + * Output an informational message in the default category. + */ +/** + * GST_DEBUG: + * @...: printf-style message to output + * + * Output a debugging message in the default category. + */ +/** + * GST_LOG: + * @...: printf-style message to output + * + * Output a logging message in the default category. + */ +/** + * GST_FIXME: + * @...: printf-style message to output + * + * Output a fixme message in the default category. + * + * Since: 0.10.23 + */ +/** + * GST_TRACE: + * @...: printf-style message to output + * + * Output a tracing message in the default category. + * + * Since: 0.10.30 + */ +/** + * GST_MEMDUMP: + * @msg: message string to log with the data + * @data: pointer to the data to output + * @length: length of the data to output + * + * Output a hexdump of @data. + * + * Since: 0.10.23 + */ + +#ifdef G_HAVE_ISO_VARARGS + +#define GST_CAT_ERROR_OBJECT(cat,obj,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_ERROR, obj, __VA_ARGS__) +#define GST_CAT_WARNING_OBJECT(cat,obj,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_WARNING, obj, __VA_ARGS__) +#define GST_CAT_INFO_OBJECT(cat,obj,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_INFO, obj, __VA_ARGS__) +#define GST_CAT_DEBUG_OBJECT(cat,obj,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_DEBUG, obj, __VA_ARGS__) +#define GST_CAT_LOG_OBJECT(cat,obj,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_LOG, obj, __VA_ARGS__) +#define GST_CAT_FIXME_OBJECT(cat,obj,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_FIXME, obj, __VA_ARGS__) +#define GST_CAT_TRACE_OBJECT(cat,obj,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_TRACE, obj, __VA_ARGS__) + +#define GST_CAT_ERROR(cat,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_ERROR, NULL, __VA_ARGS__) +#define GST_CAT_WARNING(cat,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_WARNING, NULL, __VA_ARGS__) +#define GST_CAT_INFO(cat,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_INFO, NULL, __VA_ARGS__) +#define GST_CAT_DEBUG(cat,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_DEBUG, NULL, __VA_ARGS__) +#define GST_CAT_LOG(cat,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_LOG, NULL, __VA_ARGS__) +#define GST_CAT_FIXME(cat,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_FIXME, NULL, __VA_ARGS__) +#define GST_CAT_TRACE(cat,...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_TRACE, NULL, __VA_ARGS__) + +#define GST_ERROR_OBJECT(obj,...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_ERROR, obj, __VA_ARGS__) +#define GST_WARNING_OBJECT(obj,...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_WARNING, obj, __VA_ARGS__) +#define GST_INFO_OBJECT(obj,...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_INFO, obj, __VA_ARGS__) +#define GST_DEBUG_OBJECT(obj,...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_DEBUG, obj, __VA_ARGS__) +#define GST_LOG_OBJECT(obj,...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_LOG, obj, __VA_ARGS__) +#define GST_FIXME_OBJECT(obj,...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_FIXME, obj, __VA_ARGS__) +#define GST_TRACE_OBJECT(obj,...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_TRACE, obj, __VA_ARGS__) + +#define GST_ERROR(...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_ERROR, NULL, __VA_ARGS__) +#define GST_WARNING(...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_WARNING, NULL, __VA_ARGS__) +#define GST_INFO(...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_INFO, NULL, __VA_ARGS__) +#define GST_DEBUG(...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_DEBUG, NULL, __VA_ARGS__) +#define GST_LOG(...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_LOG, NULL, __VA_ARGS__) +#define GST_FIXME(...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_FIXME, NULL, __VA_ARGS__) +#define GST_TRACE(...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_TRACE, NULL, __VA_ARGS__) + +#else +#ifdef G_HAVE_GNUC_VARARGS + +#define GST_CAT_ERROR_OBJECT(cat,obj,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_ERROR, obj, ##args ) +#define GST_CAT_WARNING_OBJECT(cat,obj,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_WARNING, obj, ##args ) +#define GST_CAT_INFO_OBJECT(cat,obj,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_INFO, obj, ##args ) +#define GST_CAT_DEBUG_OBJECT(cat,obj,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_DEBUG, obj, ##args ) +#define GST_CAT_LOG_OBJECT(cat,obj,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_LOG, obj, ##args ) +#define GST_CAT_FIXME_OBJECT(cat,obj,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_FIXME, obj, ##args ) +#define GST_CAT_TRACE_OBJECT(cat,obj,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_TRACE, obj, ##args ) + +#define GST_CAT_ERROR(cat,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_ERROR, NULL, ##args ) +#define GST_CAT_WARNING(cat,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_WARNING, NULL, ##args ) +#define GST_CAT_INFO(cat,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_INFO, NULL, ##args ) +#define GST_CAT_DEBUG(cat,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_DEBUG, NULL, ##args ) +#define GST_CAT_LOG(cat,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_LOG, NULL, ##args ) +#define GST_CAT_FIXME(cat,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_FIXME, NULL, ##args ) +#define GST_CAT_TRACE(cat,args...) GST_CAT_LEVEL_LOG (cat, GST_LEVEL_TRACE, NULL, ##args ) + +#define GST_ERROR_OBJECT(obj,args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_ERROR, obj, ##args ) +#define GST_WARNING_OBJECT(obj,args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_WARNING, obj, ##args ) +#define GST_INFO_OBJECT(obj,args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_INFO, obj, ##args ) +#define GST_DEBUG_OBJECT(obj,args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_DEBUG, obj, ##args ) +#define GST_LOG_OBJECT(obj,args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_LOG, obj, ##args ) +#define GST_FIXME_OBJECT(obj,args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_FIXME, obj, ##args ) +#define GST_TRACE_OBJECT(obj,args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_TRACE, obj, ##args ) + +#define GST_ERROR(args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_ERROR, NULL, ##args ) +#define GST_WARNING(args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_WARNING, NULL, ##args ) +#define GST_INFO(args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_INFO, NULL, ##args ) +#define GST_DEBUG(args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_DEBUG, NULL, ##args ) +#define GST_LOG(args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_LOG, NULL, ##args ) +#define GST_FIXME(args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_FIXME, NULL, ##args ) +#define GST_TRACE(args...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_TRACE, NULL, ##args ) + +#else +/* no variadic macros, use inline */ +static inline void +GST_CAT_ERROR_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_ERROR, obj, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_WARNING_OBJECT (GstDebugCategory * cat, gpointer obj, + const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_WARNING, obj, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_INFO_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_INFO, obj, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_DEBUG_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_DEBUG, obj, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_LOG_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_LOG, obj, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_FIXME_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_FIXME, obj, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_TRACE_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_TRACE, obj, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_ERROR (GstDebugCategory * cat, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_ERROR, NULL, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_WARNING (GstDebugCategory * cat, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_WARNING, NULL, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_INFO (GstDebugCategory * cat, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_INFO, NULL, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_DEBUG (GstDebugCategory * cat, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_DEBUG, NULL, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_LOG (GstDebugCategory * cat, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_LOG, NULL, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_FIXME (GstDebugCategory * cat, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_FIXME, NULL, format, varargs); + va_end (varargs); +} + +static inline void +GST_CAT_TRACE (GstDebugCategory * cat, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (cat, GST_LEVEL_TRACE, NULL, format, varargs); + va_end (varargs); +} + +static inline void +GST_ERROR_OBJECT (gpointer obj, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_ERROR, obj, format, + varargs); + va_end (varargs); +} + +static inline void +GST_WARNING_OBJECT (gpointer obj, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_WARNING, obj, format, + varargs); + va_end (varargs); +} + +static inline void +GST_INFO_OBJECT (gpointer obj, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_INFO, obj, format, + varargs); + va_end (varargs); +} + +static inline void +GST_DEBUG_OBJECT (gpointer obj, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_DEBUG, obj, format, + varargs); + va_end (varargs); +} + +static inline void +GST_LOG_OBJECT (gpointer obj, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_LOG, obj, format, + varargs); + va_end (varargs); +} + +static inline void +GST_FIXME_OBJECT (gpointer obj, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_FIXME, obj, format, + varargs); + va_end (varargs); +} + +static inline void +GST_TRACE_OBJECT (gpointer obj, const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_TRACE, obj, format, + varargs); + va_end (varargs); +} + +static inline void +GST_ERROR (const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_ERROR, NULL, format, + varargs); + va_end (varargs); +} + +static inline void +GST_WARNING (const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_WARNING, NULL, format, + varargs); + va_end (varargs); +} + +static inline void +GST_INFO (const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_INFO, NULL, format, + varargs); + va_end (varargs); +} + +static inline void +GST_DEBUG (const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_DEBUG, NULL, format, + varargs); + va_end (varargs); +} + +static inline void +GST_LOG (const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_LOG, NULL, + format, varargs); + va_end (varargs); +} + +static inline void +GST_FIXME (const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_FIXME, NULL, format, + varargs); + va_end (varargs); +} + +static inline void +GST_TRACE (const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_DEFAULT, GST_LEVEL_TRACE, NULL, format, + varargs); + va_end (varargs); +} +#endif +#endif + + +/********** function pointer stuff **********/ + +/** + * GST_DEBUG_REGISTER_FUNCPTR: + * @ptr: pointer to the function to register + * + * Register a pointer to a function with its name, so it can later be used by + * GST_DEBUG_FUNCPTR_NAME(). + * + * Use this variant of #GST_DEBUG_FUNCPTR if you do not need to use @ptr. + * + * Since: 0.10.26 + */ +#define GST_DEBUG_REGISTER_FUNCPTR(ptr) \ + _gst_debug_register_funcptr((GstDebugFuncPtr)(ptr), #ptr) +/** + * GST_DEBUG_FUNCPTR: + * @ptr: pointer to the function to register + * + * Register a pointer to a function with its name, so it can later be used by + * GST_DEBUG_FUNCPTR_NAME(). + * + * Returns: the value passed to @ptr. + */ +#define GST_DEBUG_FUNCPTR(ptr) \ + (_gst_debug_register_funcptr((GstDebugFuncPtr)(ptr), #ptr) , ptr) + +/** + * GST_DEBUG_FUNCPTR_NAME: + * @ptr: address of the function of which to look up the name + * + * Retrieves the name of the function, if it was previously registered with + * GST_DEBUG_FUNCPTR(). If not, it returns a description of the pointer. + * + * This macro returns a constant string which must not be modified or + * freed by the caller. + */ +#define GST_DEBUG_FUNCPTR_NAME(ptr) \ + _gst_debug_nameof_funcptr((GstDebugFuncPtr)ptr) + + +#else /* GST_DISABLE_GST_DEBUG */ + + +#ifndef GST_INFO_C + +#if defined(__GNUC__) && __GNUC__ >= 3 +# pragma GCC poison gst_debug_log +# pragma GCC poison gst_debug_log_valist +# pragma GCC poison _gst_debug_category_new +#endif + +#define __gst_debug_min GST_LEVEL_NONE + +#define _gst_debug_init() G_STMT_START{ }G_STMT_END + +#define gst_debug_set_default_threshold(level) G_STMT_START{ }G_STMT_END +#define gst_debug_get_default_threshold() (GST_LEVEL_NONE) + +#define gst_debug_level_get_name(level) ("NONE") +#define gst_debug_message_get(message) ("") +#define gst_debug_add_log_function(func,data) G_STMT_START{ }G_STMT_END +#define gst_debug_set_active(active) G_STMT_START{ }G_STMT_END +#define gst_debug_is_active() (FALSE) +#define gst_debug_set_colored(colored) G_STMT_START{ }G_STMT_END +#define gst_debug_is_colored() (FALSE) +#define gst_debug_set_default_threshold(level) G_STMT_START{ }G_STMT_END +#define gst_debug_get_default_threshold() (GST_LEVEL_NONE) +#define gst_debug_set_threshold_for_name(name,level) G_STMT_START{ }G_STMT_END +#define gst_debug_unset_threshold_for_name(name) G_STMT_START{ }G_STMT_END + +/* we are using dummy function prototypes here to eat ';' as these macros are + * used outside of functions */ +#define GST_DEBUG_CATEGORY(var) void _gst_debug_dummy_##var (void) +#define GST_DEBUG_CATEGORY_EXTERN(var) void _gst_debug_dummy_extern_##var (void) +#define GST_DEBUG_CATEGORY_STATIC(var) void _gst_debug_dummy_static_##var (void) + +#define GST_DEBUG_CATEGORY_INIT(var,name,color,desc) G_STMT_START{ }G_STMT_END +#define GST_DEBUG_CATEGORY_GET(var,name) G_STMT_START{ }G_STMT_END +#define gst_debug_category_free(category) G_STMT_START{ }G_STMT_END +#define gst_debug_category_set_threshold(category,level) G_STMT_START{ }G_STMT_END +#define gst_debug_category_reset_threshold(category) G_STMT_START{ }G_STMT_END +#define gst_debug_category_get_threshold(category) (GST_LEVEL_NONE) +#define gst_debug_category_get_name(cat) ("") +#define gst_debug_category_get_color(cat) (0) +#define gst_debug_category_get_description(cat) ("") +#define gst_debug_get_all_categories() (NULL) +#define gst_debug_construct_term_color(colorinfo) (g_strdup ("00")) +#define gst_debug_construct_win_color(colorinfo) (0) + +#endif /* !GST_INFO_C */ + +#ifdef G_HAVE_ISO_VARARGS + +#define GST_CAT_LEVEL_LOG(cat,level,...) G_STMT_START{ }G_STMT_END + +#define GST_CAT_ERROR_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_WARNING_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_INFO_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_DEBUG_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_LOG_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_FIXME_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_TRACE_OBJECT(...) G_STMT_START{ }G_STMT_END + +#define GST_CAT_ERROR(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_WARNING(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_INFO(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_DEBUG(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_LOG(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_FIXME(...) G_STMT_START{ }G_STMT_END +#define GST_CAT_TRACE(...) G_STMT_START{ }G_STMT_END + +#define GST_ERROR_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_WARNING_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_INFO_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_DEBUG_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_LOG_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_FIXME_OBJECT(...) G_STMT_START{ }G_STMT_END +#define GST_TRACE_OBJECT(...) G_STMT_START{ }G_STMT_END + +#define GST_ERROR(...) G_STMT_START{ }G_STMT_END +#define GST_WARNING(...) G_STMT_START{ }G_STMT_END +#define GST_INFO(...) G_STMT_START{ }G_STMT_END +#define GST_DEBUG(...) G_STMT_START{ }G_STMT_END +#define GST_LOG(...) G_STMT_START{ }G_STMT_END +#define GST_FIXME(...) G_STMT_START{ }G_STMT_END +#define GST_TRACE(...) G_STMT_START{ }G_STMT_END + +#else /* !G_HAVE_ISO_VARARGS */ +#ifdef G_HAVE_GNUC_VARARGS + +#define GST_CAT_LEVEL_LOG(cat,level,args...) G_STMT_START{ }G_STMT_END + +#define GST_CAT_ERROR_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_WARNING_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_INFO_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_DEBUG_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_LOG_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_FIXME_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_TRACE_OBJECT(args...) G_STMT_START{ }G_STMT_END + +#define GST_CAT_ERROR(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_WARNING(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_INFO(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_DEBUG(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_LOG(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_FIXME(args...) G_STMT_START{ }G_STMT_END +#define GST_CAT_TRACE(args...) G_STMT_START{ }G_STMT_END + +#define GST_ERROR_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_WARNING_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_INFO_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_DEBUG_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_LOG_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_FIXME_OBJECT(args...) G_STMT_START{ }G_STMT_END +#define GST_TRACE_OBJECT(args...) G_STMT_START{ }G_STMT_END + +#define GST_ERROR(args...) G_STMT_START{ }G_STMT_END +#define GST_WARNING(args...) G_STMT_START{ }G_STMT_END +#define GST_INFO(args...) G_STMT_START{ }G_STMT_END +#define GST_DEBUG(args...) G_STMT_START{ }G_STMT_END +#define GST_LOG(args...) G_STMT_START{ }G_STMT_END +#define GST_FIXME(args...) G_STMT_START{ }G_STMT_END +#define GST_TRACE(args...) G_STMT_START{ }G_STMT_END + +#else /* !G_HAVE_GNUC_VARARGS */ +static inline void +GST_CAT_LEVEL_LOG_valist (GstDebugCategory * cat, + GstDebugLevel level, gpointer object, const char *format, va_list varargs) +{ +} + +static inline void +GST_CAT_ERROR_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ +} + +static inline void +GST_CAT_WARNING_OBJECT (GstDebugCategory * cat, gpointer obj, + const char *format, ...) +{ +} + +static inline void +GST_CAT_INFO_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ +} + +static inline void +GST_CAT_DEBUG_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ +} + +static inline void +GST_CAT_LOG_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ +} + +static inline void +GST_CAT_FIXME_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ +} + +static inline void +GST_CAT_TRACE_OBJECT (GstDebugCategory * cat, gpointer obj, const char *format, + ...) +{ +} + +static inline void +GST_CAT_ERROR (GstDebugCategory * cat, const char *format, ...) +{ +} + +static inline void +GST_CAT_WARNING (GstDebugCategory * cat, const char *format, ...) +{ +} + +static inline void +GST_CAT_INFO (GstDebugCategory * cat, const char *format, ...) +{ +} + +static inline void +GST_CAT_DEBUG (GstDebugCategory * cat, const char *format, ...) +{ +} + +static inline void +GST_CAT_LOG (GstDebugCategory * cat, const char *format, ...) +{ +} + +static inline void +GST_CAT_FIXME (GstDebugCategory * cat, const char *format, ...) +{ +} + +static inline void +GST_CAT_TRACE (GstDebugCategory * cat, const char *format, ...) +{ +} + +static inline void +GST_ERROR_OBJECT (gpointer obj, const char *format, ...) +{ +} + +static inline void +GST_WARNING_OBJECT (gpointer obj, const char *format, ...) +{ +} + +static inline void +GST_INFO_OBJECT (gpointer obj, const char *format, ...) +{ +} + +static inline void +GST_DEBUG_OBJECT (gpointer obj, const char *format, ...) +{ +} + +static inline void +GST_LOG_OBJECT (gpointer obj, const char *format, ...) +{ +} + +static inline void +GST_FIXME_OBJECT (gpointer obj, const char *format, ...) +{ +} + +static inline void +GST_TRACE_OBJECT (gpointer obj, const char *format, ...) +{ +} + +static inline void +GST_ERROR (const char *format, ...) +{ +} + +static inline void +GST_WARNING (const char *format, ...) +{ +} + +static inline void +GST_INFO (const char *format, ...) +{ +} + +static inline void +GST_DEBUG (const char *format, ...) +{ +} + +static inline void +GST_LOG (const char *format, ...) +{ +} + +static inline void +GST_FIXME (const char *format, ...) +{ +} + +static inline void +GST_TRACE (const char *format, ...) +{ +} + +#endif /* G_HAVE_GNUC_VARARGS */ +#endif /* G_HAVE_ISO_VARARGS */ + +#define GST_DEBUG_REGISTER_FUNCPTR(ptr) G_STMT_START{ }G_STMT_END +#define GST_DEBUG_FUNCPTR(ptr) (ptr) +#define GST_DEBUG_FUNCPTR_NAME(ptr) (g_strdup_printf ("%p", ptr)) + +#define GST_CAT_MEMDUMP_OBJECT(cat,obj,msg,data,length) G_STMT_START{ }G_STMT_END +#define GST_CAT_MEMDUMP(cat,msg,data,length) G_STMT_START{ }G_STMT_END +#define GST_MEMDUMP_OBJECT(obj,msg,data,length) G_STMT_START{ }G_STMT_END +#define GST_MEMDUMP(msg,data,length) G_STMT_START{ }G_STMT_END + +#endif /* GST_DISABLE_GST_DEBUG */ + + +void gst_debug_print_stack_trace (void); + +G_END_DECLS + +#endif /* __GSTINFO_H__ */ diff --git a/gst/gstiterator.c b/gst/gstiterator.c new file mode 100644 index 0000000..56675b4 --- /dev/null +++ b/gst/gstiterator.c @@ -0,0 +1,830 @@ +/* GStreamer + * Copyright (C) 2004 Wim Taymans <wim@fluendo.com> + * Copyright (C) 2011 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * gstiterator.h: Base class for iterating datastructures. + * + * 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:gstiterator + * @short_description: Object to retrieve multiple elements in a threadsafe + * way. + * @see_also: #GstElement, #GstBin + * + * A GstIterator is used to retrieve multiple objects from another object in + * a threadsafe way. + * + * Various GStreamer objects provide access to their internal structures using + * an iterator. + * + * In general, whenever calling a GstIterator function results in your code + * receiving a refcounted object, the refcount for that object will have been + * increased. Your code is responsible for unrefing that object after use. + * + * The basic use pattern of an iterator is as follows: + * + * <example> + * <title>Using an iterator</title> + * <programlisting> + * it = _get_iterator(object); + * done = FALSE; + * while (!done) { + * switch (gst_iterator_next (it, &item)) { + * case GST_ITERATOR_OK: + * ... use/change item here... + * g_value_reset (&item); + * break; + * case GST_ITERATOR_RESYNC: + * ...rollback changes to items... + * gst_iterator_resync (it); + * break; + * case GST_ITERATOR_ERROR: + * ...wrong parameters were given... + * done = TRUE; + * break; + * case GST_ITERATOR_DONE: + * done = TRUE; + * break; + * } + * } + * g_value_unset (&item); + * gst_iterator_free (it); + * </programlisting> + * </example> + * + * Last reviewed on 2009-06-16 (0.10.24) + */ + +#include "gst_private.h" +#include <gst/gstiterator.h> + +GstIterator * +gst_iterator_copy (const GstIterator * it) +{ + GstIterator *copy; + + copy = g_slice_copy (it->size, it); + if (it->copy) + it->copy (it, copy); + + return copy; +} + +GType +gst_iterator_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) + type = g_boxed_type_register_static ("GstIterator", + (GBoxedCopyFunc) gst_iterator_copy, (GBoxedFreeFunc) gst_iterator_free); + return type; +} + +static void +gst_iterator_init (GstIterator * it, + guint size, + GType type, + GMutex * lock, + guint32 * master_cookie, + GstIteratorCopyFunction copy, + GstIteratorNextFunction next, + GstIteratorItemFunction item, + GstIteratorResyncFunction resync, GstIteratorFreeFunction free) +{ + it->size = size; + it->type = type; + it->lock = lock; + it->master_cookie = master_cookie; + it->cookie = *master_cookie; + it->copy = copy; + it->next = next; + it->item = item; + it->resync = resync; + it->free = free; + it->pushed = NULL; +} + +/** + * gst_iterator_new: + * @size: the size of the iterator structure + * @type: #GType of children + * @lock: pointer to a #GMutex. + * @master_cookie: pointer to a guint32 that is changed when the items in the + * iterator changed. + * @copy: copy function + * @next: function to get next item + * @item: function to call on each item retrieved + * @resync: function to resync the iterator + * @free: function to free the iterator + * + * Create a new iterator. This function is mainly used for objects + * implementing the next/resync/free function to iterate a data structure. + * + * For each item retrieved, the @item function is called with the lock + * held. The @free function is called when the iterator is freed. + * + * Returns: the new #GstIterator. + * + * MT safe. + */ +GstIterator * +gst_iterator_new (guint size, + GType type, + GMutex * lock, + guint32 * master_cookie, + GstIteratorCopyFunction copy, + GstIteratorNextFunction next, + GstIteratorItemFunction item, + GstIteratorResyncFunction resync, GstIteratorFreeFunction free) +{ + GstIterator *result; + + g_return_val_if_fail (size >= sizeof (GstIterator), NULL); + g_return_val_if_fail (g_type_qname (type) != 0, NULL); + g_return_val_if_fail (master_cookie != NULL, NULL); + g_return_val_if_fail (next != NULL, NULL); + g_return_val_if_fail (resync != NULL, NULL); + g_return_val_if_fail (free != NULL, NULL); + + result = g_slice_alloc0 (size); + gst_iterator_init (result, size, type, lock, master_cookie, copy, next, item, + resync, free); + + return result; +} + +/* + * list iterator + */ +typedef struct _GstListIterator +{ + GstIterator iterator; + GObject *owner; + GList **orig; + GList *list; /* pointer in list */ + + void (*set_value) (GValue * value, gpointer item); +} GstListIterator; + +static void +gst_list_iterator_copy (const GstListIterator * it, GstListIterator * copy) +{ + if (copy->owner) + g_object_ref (copy->owner); +} + +static GstIteratorResult +gst_list_iterator_next (GstListIterator * it, GValue * elem) +{ + gpointer data; + + if (it->list == NULL) + return GST_ITERATOR_DONE; + + data = it->list->data; + it->list = g_list_next (it->list); + + it->set_value (elem, data); + + return GST_ITERATOR_OK; +} + +static void +gst_list_iterator_resync (GstListIterator * it) +{ + it->list = *it->orig; +} + +static void +gst_list_iterator_free (GstListIterator * it) +{ + if (it->owner) + g_object_unref (it->owner); +} + +/** + * gst_iterator_new_list: + * @type: #GType of elements + * @lock: pointer to a #GMutex protecting the list. + * @master_cookie: pointer to a guint32 that is incremented when the list + * is changed. + * @list: pointer to the list + * @owner: object owning the list + * @item: function to call on each item retrieved + * + * Create a new iterator designed for iterating @list. + * + * The list you iterate is usually part of a data structure @owner and is + * protected with @lock. + * + * The iterator will use @lock to retrieve the next item of the list and it + * will then call the @item function before releasing @lock again. + * + * When a concurrent update to the list is performed, usually by @owner while + * holding @lock, @master_cookie will be updated. The iterator implementation + * will notice the update of the cookie and will return %GST_ITERATOR_RESYNC to + * the user of the iterator in the next call to gst_iterator_next(). + * + * Returns: the new #GstIterator for @list. + * + * MT safe. + */ +GstIterator * +gst_iterator_new_list (GType type, + GMutex * lock, guint32 * master_cookie, GList ** list, GObject * owner, + GstIteratorItemFunction item) +{ + GstListIterator *result; + gpointer set_value; + + if (g_type_is_a (type, G_TYPE_OBJECT)) { + set_value = g_value_set_object; + } else if (g_type_is_a (type, G_TYPE_BOXED)) { + set_value = g_value_set_boxed; + } else if (g_type_is_a (type, G_TYPE_POINTER)) { + set_value = g_value_set_pointer; + } else if (g_type_is_a (type, G_TYPE_STRING)) { + set_value = g_value_set_string; + } else { + g_critical ("List iterators can only be created for lists containing " + "instances of GObject, boxed types, pointer types and strings"); + return NULL; + } + + /* no need to lock, nothing can change here */ + result = (GstListIterator *) gst_iterator_new (sizeof (GstListIterator), + type, + lock, + master_cookie, + (GstIteratorCopyFunction) gst_list_iterator_copy, + (GstIteratorNextFunction) gst_list_iterator_next, + (GstIteratorItemFunction) item, + (GstIteratorResyncFunction) gst_list_iterator_resync, + (GstIteratorFreeFunction) gst_list_iterator_free); + + result->owner = owner ? g_object_ref (owner) : NULL; + result->orig = list; + result->list = *list; + result->set_value = set_value; + + return GST_ITERATOR (result); +} + +static void +gst_iterator_pop (GstIterator * it) +{ + if (it->pushed) { + gst_iterator_free (it->pushed); + it->pushed = NULL; + } +} + +/** + * gst_iterator_next: + * @it: The #GstIterator to iterate + * @elem: (out caller-allocates): pointer to hold next element + * + * Get the next item from the iterator in @elem. + * + * Only when this function returns %GST_ITERATOR_OK, @elem will contain a valid + * value. @elem must have been initialized to the type of the iterator or + * initialized to zeroes with g_value_unset(). The caller is responsible for + * unsetting or resetting @elem with g_value_unset() or g_value_reset() + * after usage. + * + * When this function returns %GST_ITERATOR_DONE, no more elements can be + * retrieved from @it. + * + * A return value of %GST_ITERATOR_RESYNC indicates that the element list was + * concurrently updated. The user of @it should call gst_iterator_resync() to + * get the newly updated list. + * + * A return value of %GST_ITERATOR_ERROR indicates an unrecoverable fatal error. + * + * Returns: The result of the iteration. Unset @elem after usage. + * + * MT safe. + */ +GstIteratorResult +gst_iterator_next (GstIterator * it, GValue * elem) +{ + GstIteratorResult result; + + g_return_val_if_fail (it != NULL, GST_ITERATOR_ERROR); + g_return_val_if_fail (elem != NULL, GST_ITERATOR_ERROR); + g_return_val_if_fail (G_VALUE_TYPE (elem) == G_TYPE_INVALID + || G_VALUE_HOLDS (elem, it->type), GST_ITERATOR_ERROR); + + if (G_VALUE_TYPE (elem) == G_TYPE_INVALID) + g_value_init (elem, it->type); + +restart: + if (it->pushed) { + result = gst_iterator_next (it->pushed, elem); + if (result == GST_ITERATOR_DONE) { + /* we are done with this iterator, pop it and + * fallthrough iterating the main iterator again. */ + gst_iterator_pop (it); + } else { + return result; + } + } + + if (G_LIKELY (it->lock)) + g_mutex_lock (it->lock); + + if (G_UNLIKELY (*it->master_cookie != it->cookie)) { + result = GST_ITERATOR_RESYNC; + goto done; + } + + result = it->next (it, elem); + if (result == GST_ITERATOR_OK && it->item) { + GstIteratorItem itemres; + + itemres = it->item (it, elem); + switch (itemres) { + case GST_ITERATOR_ITEM_SKIP: + if (G_LIKELY (it->lock)) + g_mutex_unlock (it->lock); + g_value_reset (elem); + goto restart; + case GST_ITERATOR_ITEM_END: + result = GST_ITERATOR_DONE; + g_value_reset (elem); + break; + case GST_ITERATOR_ITEM_PASS: + break; + } + } + +done: + if (G_LIKELY (it->lock)) + g_mutex_unlock (it->lock); + + return result; +} + +/** + * gst_iterator_resync: + * @it: The #GstIterator to resync + * + * Resync the iterator. this function is mostly called + * after gst_iterator_next() returned %GST_ITERATOR_RESYNC. + * + * When an iterator was pushed on @it, it will automatically be popped again + * with this function. + * + * MT safe. + */ +void +gst_iterator_resync (GstIterator * it) +{ + g_return_if_fail (it != NULL); + + gst_iterator_pop (it); + + if (G_LIKELY (it->lock)) + g_mutex_lock (it->lock); + it->resync (it); + it->cookie = *it->master_cookie; + if (G_LIKELY (it->lock)) + g_mutex_unlock (it->lock); +} + +/** + * gst_iterator_free: + * @it: The #GstIterator to free + * + * Free the iterator. + * + * MT safe. + */ +void +gst_iterator_free (GstIterator * it) +{ + g_return_if_fail (it != NULL); + + gst_iterator_pop (it); + + it->free (it); + + g_slice_free1 (it->size, it); +} + +/** + * gst_iterator_push: + * @it: The #GstIterator to use + * @other: The #GstIterator to push + * + * Pushes @other iterator onto @it. All calls performed on @it are + * forwarded to @other. If @other returns %GST_ITERATOR_DONE, it is + * popped again and calls are handled by @it again. + * + * This function is mainly used by objects implementing the iterator + * next function to recurse into substructures. + * + * When gst_iterator_resync() is called on @it, @other will automatically be + * popped. + * + * MT safe. + */ +void +gst_iterator_push (GstIterator * it, GstIterator * other) +{ + g_return_if_fail (it != NULL); + g_return_if_fail (other != NULL); + + it->pushed = other; +} + +typedef struct _GstIteratorFilter +{ + GstIterator iterator; + GstIterator *slave; + + GCompareFunc func; + GValue user_data; + gboolean have_user_data; +} GstIteratorFilter; + +static GstIteratorResult +filter_next (GstIteratorFilter * it, GValue * elem) +{ + GstIteratorResult result = GST_ITERATOR_ERROR; + gboolean done = FALSE; + GValue item = { 0, }; + + while (G_LIKELY (!done)) { + result = gst_iterator_next (it->slave, &item); + switch (result) { + case GST_ITERATOR_OK: + if (G_LIKELY (GST_ITERATOR (it)->lock)) + g_mutex_unlock (GST_ITERATOR (it)->lock); + if (it->func (&item, &it->user_data) == 0) { + g_value_copy (&item, elem); + done = TRUE; + } + g_value_reset (&item); + if (G_LIKELY (GST_ITERATOR (it)->lock)) + g_mutex_lock (GST_ITERATOR (it)->lock); + break; + case GST_ITERATOR_RESYNC: + case GST_ITERATOR_DONE: + done = TRUE; + break; + default: + g_assert_not_reached (); + break; + } + } + g_value_unset (&item); + return result; +} + +static void +filter_copy (const GstIteratorFilter * it, GstIteratorFilter * copy) +{ + copy->slave = gst_iterator_copy (it->slave); + + if (it->have_user_data) { + memset (©->user_data, 0, sizeof (copy->user_data)); + g_value_init (©->user_data, G_VALUE_TYPE (&it->user_data)); + g_value_copy (&it->user_data, ©->user_data); + } +} + +static void +filter_resync (GstIteratorFilter * it) +{ + gst_iterator_resync (it->slave); +} + +static void +filter_free (GstIteratorFilter * it) +{ + if (it->have_user_data) + g_value_unset (&it->user_data); + gst_iterator_free (it->slave); +} + +/** + * gst_iterator_filter: + * @it: The #GstIterator to filter + * @func: (scope call): the compare function to select elements + * @user_data: (closure): user data passed to the compare function + * + * Create a new iterator from an existing iterator. The new iterator + * will only return those elements that match the given compare function @func. + * The first parameter that is passed to @func is the #GValue of the current + * iterator element and the second parameter is @user_data. @func should + * return 0 for elements that should be included in the filtered iterator. + * + * When this iterator is freed, @it will also be freed. + * + * Returns: (transfer full): a new #GstIterator. + * + * MT safe. + */ +GstIterator * +gst_iterator_filter (GstIterator * it, GCompareFunc func, + const GValue * user_data) +{ + GstIteratorFilter *result; + + g_return_val_if_fail (it != NULL, NULL); + g_return_val_if_fail (func != NULL, NULL); + + result = (GstIteratorFilter *) gst_iterator_new (sizeof (GstIteratorFilter), + it->type, it->lock, it->master_cookie, + (GstIteratorCopyFunction) filter_copy, + (GstIteratorNextFunction) filter_next, + (GstIteratorItemFunction) NULL, + (GstIteratorResyncFunction) filter_resync, + (GstIteratorFreeFunction) filter_free); + + it->lock = NULL; + result->func = func; + if (user_data) { + g_value_init (&result->user_data, G_VALUE_TYPE (user_data)); + g_value_copy (user_data, &result->user_data); + result->have_user_data = TRUE; + } else { + result->have_user_data = FALSE; + } + result->slave = it; + + return GST_ITERATOR (result); +} + +/** + * gst_iterator_fold: + * @it: The #GstIterator to fold over + * @func: (scope call): the fold function + * @ret: the seed value passed to the fold function + * @user_data: (closure): user data passed to the fold function + * + * Folds @func over the elements of @iter. That is to say, @func will be called + * as @func (object, @ret, @user_data) for each object in @it. The normal use + * of this procedure is to accumulate the results of operating on the objects in + * @ret. + * + * This procedure can be used (and is used internally) to implement the + * gst_iterator_foreach() and gst_iterator_find_custom() operations. + * + * The fold will proceed as long as @func returns TRUE. When the iterator has no + * more arguments, %GST_ITERATOR_DONE will be returned. If @func returns FALSE, + * the fold will stop, and %GST_ITERATOR_OK will be returned. Errors or resyncs + * will cause fold to return %GST_ITERATOR_ERROR or %GST_ITERATOR_RESYNC as + * appropriate. + * + * The iterator will not be freed. + * + * Returns: A #GstIteratorResult, as described above. + * + * MT safe. + */ +GstIteratorResult +gst_iterator_fold (GstIterator * it, GstIteratorFoldFunction func, + GValue * ret, gpointer user_data) +{ + GValue item = { 0, }; + GstIteratorResult result; + + while (1) { + result = gst_iterator_next (it, &item); + switch (result) { + case GST_ITERATOR_OK: + if (!func (&item, ret, user_data)) + goto fold_done; + + g_value_reset (&item); + break; + case GST_ITERATOR_RESYNC: + case GST_ITERATOR_ERROR: + goto fold_done; + case GST_ITERATOR_DONE: + goto fold_done; + } + } + +fold_done: + g_value_unset (&item); + + return result; +} + +typedef struct +{ + GstIteratorForeachFunction func; + gpointer user_data; +} ForeachFoldData; + +static gboolean +foreach_fold_func (const GValue * item, GValue * unused, ForeachFoldData * data) +{ + data->func (item, data->user_data); + return TRUE; +} + +/** + * gst_iterator_foreach: + * @it: The #GstIterator to iterate + * @func: (scope call): the function to call for each element. + * @user_data: (closure): user data passed to the function + * + * Iterate over all element of @it and call the given function @func for + * each element. + * + * Returns: the result call to gst_iterator_fold(). The iterator will not be + * freed. + * + * MT safe. + */ +GstIteratorResult +gst_iterator_foreach (GstIterator * it, GstIteratorForeachFunction func, + gpointer user_data) +{ + ForeachFoldData data; + + data.func = func; + data.user_data = user_data; + + return gst_iterator_fold (it, (GstIteratorFoldFunction) foreach_fold_func, + NULL, &data); +} + +typedef struct +{ + GCompareFunc func; + gpointer user_data; + gboolean found; +} FindCustomFoldData; + +static gboolean +find_custom_fold_func (const GValue * item, GValue * ret, + FindCustomFoldData * data) +{ + if (data->func (item, data->user_data) == 0) { + data->found = TRUE; + g_value_copy (item, ret); + return FALSE; + } else { + return TRUE; + } +} + +/** + * gst_iterator_find_custom: + * @it: The #GstIterator to iterate + * @func: (scope call): the compare function to use + * @elem: (out): pointer to a #GValue where to store the result + * @user_data: (closure): user data passed to the compare function + * + * Find the first element in @it that matches the compare function @func. + * @func should return 0 when the element is found. The first parameter + * to @func will be the current element of the iterator and the + * second parameter will be @user_data. + * The result will be stored in @elem if a result is found. + * + * The iterator will not be freed. + * + * This function will return FALSE if an error happened to the iterator + * or if the element wasn't found. + * + * Returns: Returns TRUE if the element was found, else FALSE. + * + * MT safe. + */ +gboolean +gst_iterator_find_custom (GstIterator * it, GCompareFunc func, + GValue * elem, gpointer user_data) +{ + GstIteratorResult res; + FindCustomFoldData data; + + g_return_val_if_fail (G_VALUE_TYPE (elem) == G_TYPE_INVALID + || G_VALUE_HOLDS (elem, it->type), GST_ITERATOR_ERROR); + + if (G_VALUE_TYPE (elem) == G_TYPE_INVALID) + g_value_init (elem, it->type); + + data.func = func; + data.user_data = user_data; + data.found = FALSE; + + do { + res = + gst_iterator_fold (it, (GstIteratorFoldFunction) find_custom_fold_func, + elem, &data); + if (res == GST_ITERATOR_RESYNC) + gst_iterator_resync (it); + } while (res == GST_ITERATOR_RESYNC); + + if (!data.found) + g_value_unset (elem); + + return data.found; +} + +typedef struct +{ + GstIterator parent; + GValue object; + gboolean visited; + gboolean empty; +} GstSingleObjectIterator; + +static guint32 _single_object_dummy_cookie = 0; + +static void +gst_single_object_iterator_copy (const GstSingleObjectIterator * it, + GstSingleObjectIterator * copy) +{ + if (!it->empty) { + memset (©->object, 0, sizeof (copy->object)); + g_value_init (©->object, it->parent.type); + g_value_copy (&it->object, ©->object); + } +} + +static GstIteratorResult +gst_single_object_iterator_next (GstSingleObjectIterator * it, GValue * result) +{ + if (it->visited || it->empty) + return GST_ITERATOR_DONE; + + g_value_copy (&it->object, result); + it->visited = TRUE; + + return GST_ITERATOR_OK; +} + +static void +gst_single_object_iterator_resync (GstSingleObjectIterator * it) +{ + it->visited = FALSE; +} + +static void +gst_single_object_iterator_free (GstSingleObjectIterator * it) +{ + if (!it->empty) + g_value_unset (&it->object); +} + +/** + * gst_iterator_new_single: + * @type: #GType of the passed object + * @object: object that this iterator should return + * + * This #GstIterator is a convenient iterator for the common + * case where a #GstIterator needs to be returned but only + * a single object has to be considered. This happens often + * for the #GstPadIterIntLinkFunction. + * + * Returns: the new #GstIterator for @object. + * + * Since: 0.10.25 + */ +GstIterator * +gst_iterator_new_single (GType type, const GValue * object) +{ + GstSingleObjectIterator *result; + + result = (GstSingleObjectIterator *) + gst_iterator_new (sizeof (GstSingleObjectIterator), + type, NULL, &_single_object_dummy_cookie, + (GstIteratorCopyFunction) gst_single_object_iterator_copy, + (GstIteratorNextFunction) gst_single_object_iterator_next, + (GstIteratorItemFunction) NULL, + (GstIteratorResyncFunction) gst_single_object_iterator_resync, + (GstIteratorFreeFunction) gst_single_object_iterator_free); + + if (object) { + g_value_init (&result->object, type); + g_value_copy (object, &result->object); + result->empty = FALSE; + } else { + result->empty = TRUE; + } + result->visited = FALSE; + + return GST_ITERATOR (result); +} diff --git a/gst/gstiterator.h b/gst/gstiterator.h new file mode 100644 index 0000000..3443402 --- /dev/null +++ b/gst/gstiterator.h @@ -0,0 +1,273 @@ +/* GStreamer + * Copyright (C) 2004 Wim Taymans <wim@fluendo.com> + * Copyright (C) 2011 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * gstiterator.h: Header for GstIterator + * + * 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_ITERATOR_H__ +#define __GST_ITERATOR_H__ + +#include <glib-object.h> /* for GValue in the fold */ +#include <gst/gstconfig.h> + +G_BEGIN_DECLS + +#define GST_TYPE_ITERATOR (gst_iterator_get_type ()) + +/** + * GstIteratorResult: + * @GST_ITERATOR_DONE: No more items in the iterator + * @GST_ITERATOR_OK: An item was retrieved + * @GST_ITERATOR_RESYNC: Datastructure changed while iterating + * @GST_ITERATOR_ERROR: An error happened + * + * The result of gst_iterator_next(). + */ +typedef enum { + GST_ITERATOR_DONE = 0, + GST_ITERATOR_OK = 1, + GST_ITERATOR_RESYNC = 2, + GST_ITERATOR_ERROR = 3 +} GstIteratorResult; + +typedef struct _GstIterator GstIterator; + +/** + * GstIteratorItem: + * @GST_ITERATOR_ITEM_SKIP: Skip this item + * @GST_ITERATOR_ITEM_PASS: Return item + * @GST_ITERATOR_ITEM_END: Stop after this item. + * + * The result of a #GstIteratorItemFunction. + */ +typedef enum { + GST_ITERATOR_ITEM_SKIP = 0, + GST_ITERATOR_ITEM_PASS = 1, + GST_ITERATOR_ITEM_END = 2 +} GstIteratorItem; + +/** + * GstIteratorCopyFunction: + * @it: The original iterator + * @copy: The copied iterator + * + * This function will be called when creating a copy of @it and should + * create a copy of all custom iterator fields or increase their + * reference counts. + */ +typedef void (*GstIteratorCopyFunction) (const GstIterator *it, GstIterator *copy); + +/** + * GstIteratorItemFunction: + * @it: the iterator + * @item: the item being retrieved. + * + * The function that will be called after the next item of the iterator + * has been retrieved. This function can be used to skip items or stop + * the iterator. + * + * The function will be called with the iterator lock held. + * + * Returns: the result of the operation. + */ +typedef GstIteratorItem (*GstIteratorItemFunction) (GstIterator *it, const GValue * item); + +/** + * GstIteratorNextFunction: + * @it: the iterator + * @result: a pointer to hold the next item + * + * The function that will be called when the next element of the iterator + * should be retrieved. + * + * Implementors of a #GstIterator should implement this + * function and pass it to the constructor of the custom iterator. + * The function will be called with the iterator lock held. + * + * Returns: the result of the operation. + */ +typedef GstIteratorResult (*GstIteratorNextFunction) (GstIterator *it, GValue *result); +/** + * GstIteratorResyncFunction: + * @it: the iterator + * + * This function will be called whenever a concurrent update happened + * to the iterated datastructure. The implementor of the iterator should + * restart the iterator from the beginning and clean up any state it might + * have. + * + * Implementors of a #GstIterator should implement this + * function and pass it to the constructor of the custom iterator. + * The function will be called with the iterator lock held. + */ +typedef void (*GstIteratorResyncFunction) (GstIterator *it); +/** + * GstIteratorFreeFunction: + * @it: the iterator + * + * This function will be called when the iterator is freed. + * + * Implementors of a #GstIterator should implement this + * function and pass it to the constructor of the custom iterator. + * The function will be called with the iterator lock held. + */ +typedef void (*GstIteratorFreeFunction) (GstIterator *it); + +/** + * GstIteratorForeachFunction: + * @item: The item + * @user_data: User data + * + * A function that is called by gst_iterator_foreach() for every element. + */ +typedef void (*GstIteratorForeachFunction) (const GValue * item, gpointer user_data); + +/** + * GstIteratorFoldFunction: + * @item: the item to fold + * @ret: a #GValue collecting the result + * @user_data: data passed to gst_iterator_fold() + * + * A function to be passed to gst_iterator_fold(). + * + * Returns: TRUE if the fold should continue, FALSE if it should stop. + */ +typedef gboolean (*GstIteratorFoldFunction) (const GValue * item, GValue * ret, gpointer user_data); + +/** + * GST_ITERATOR: + * @it: the #GstIterator value + * + * Macro to cast to a #GstIterator + */ +#define GST_ITERATOR(it) ((GstIterator*)(it)) +/** + * GST_ITERATOR_LOCK: + * @it: the #GstIterator to get the lock of + * + * Macro to get the lock protecting the datastructure being iterated. + */ +#define GST_ITERATOR_LOCK(it) (GST_ITERATOR(it)->lock) +/** + * GST_ITERATOR_COOKIE: + * @it: the #GstIterator to get the cookie of + * + * Macro to get the cookie of a #GstIterator. The cookie of the + * iterator is the value of the master cookie when the iterator + * was created. + * Whenever the iterator is iterated, the value is compared to the + * value of the master cookie. If they are different, a concurrent + * modification happened to the iterator and a resync is needed. + */ +#define GST_ITERATOR_COOKIE(it) (GST_ITERATOR(it)->cookie) +/** + * GST_ITERATOR_ORIG_COOKIE: + * @it: the #GstIterator to get the master cookie of + * + * Macro to get a pointer to where the master cookie is stored. The + * master cookie protects the structure being iterated and gets updated + * whenever the datastructure changes. + */ +#define GST_ITERATOR_ORIG_COOKIE(it) (GST_ITERATOR(it)->master_cookie) + +/** + * GstIterator: + * @copy: The function to copy the iterator + * @next: The function to get the next item in the iterator + * @item: The function to be called for each item retrieved + * @resync: The function to call when a resync is needed. + * @free: The function to call when the iterator is freed + * @pushed: The iterator that is currently pushed with gst_iterator_push() + * @type: The type of the object that this iterator will return + * @lock: The lock protecting the data structure and the cookie. + * @cookie: The cookie; the value of the master_cookie when this iterator was + * created. + * @master_cookie: A pointer to the master cookie. + * @size: the size of the iterator + * + * #GstIterator base structure. The values of this structure are + * protected for subclasses, use the methods to use the #GstIterator. + */ +struct _GstIterator { + /*< protected >*/ + GstIteratorCopyFunction copy; + GstIteratorNextFunction next; + GstIteratorItemFunction item; + GstIteratorResyncFunction resync; + GstIteratorFreeFunction free; + + GstIterator *pushed; /* pushed iterator */ + + GType type; + GMutex *lock; + guint32 cookie; /* cookie of the iterator */ + guint32 *master_cookie; /* pointer to guint32 holding the cookie when this + iterator was created */ + guint size; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_iterator_get_type (void); + +/* creating iterators */ +GstIterator* gst_iterator_new (guint size, + GType type, + GMutex *lock, + guint32 *master_cookie, + GstIteratorCopyFunction copy, + GstIteratorNextFunction next, + GstIteratorItemFunction item, + GstIteratorResyncFunction resync, + GstIteratorFreeFunction free); + +GstIterator* gst_iterator_new_list (GType type, + GMutex *lock, + guint32 *master_cookie, + GList **list, + GObject * owner, + GstIteratorItemFunction item); + +GstIterator* gst_iterator_new_single (GType type, + const GValue * object); + +GstIterator* gst_iterator_copy (const GstIterator *it); + +/* using iterators */ +GstIteratorResult gst_iterator_next (GstIterator *it, GValue * elem); +void gst_iterator_resync (GstIterator *it); +void gst_iterator_free (GstIterator *it); + +void gst_iterator_push (GstIterator *it, GstIterator *other); + +/* higher-order functions that operate on iterators */ +GstIterator* gst_iterator_filter (GstIterator *it, GCompareFunc func, + const GValue * user_data); +GstIteratorResult gst_iterator_fold (GstIterator *it, + GstIteratorFoldFunction func, + GValue *ret, gpointer user_data); +GstIteratorResult gst_iterator_foreach (GstIterator *it, + GstIteratorForeachFunction func, gpointer user_data); +gboolean gst_iterator_find_custom (GstIterator *it, GCompareFunc func, + GValue *elem, gpointer user_data); + +G_END_DECLS + +#endif /* __GST_ITERATOR_H__ */ diff --git a/gst/gstmacros.h b/gst/gstmacros.h new file mode 100644 index 0000000..e38af53 --- /dev/null +++ b/gst/gstmacros.h @@ -0,0 +1,54 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * + * 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_MACROS_H__ +#define __GST_MACROS_H__ + +G_BEGIN_DECLS + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) +# define GST_GNUC_CONSTRUCTOR \ + __attribute__ ((constructor)) +#else /* !__GNUC__ */ +# define GST_GNUC_CONSTRUCTOR +#endif /* !__GNUC__ */ + +#if defined (__GNUC__) && !defined (GST_IMPLEMENT_INLINES) +# define GST_INLINE_FUNC extern __inline__ +# define GST_CAN_INLINE 1 +#elif defined(_MSC_VER) +# define GST_INLINE_FUNC extern __inline +# define GST_CAN_INLINE 1 +#else +# define GST_INLINE_FUNC extern +# undef GST_CAN_INLINE +#endif + +#if (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) && !defined(restrict) +# if defined(__GNUC__) && __GNUC__ >= 4 +# define restrict __restrict__ +# else +# define restrict +# endif +#endif + +G_END_DECLS + +#endif /* __GST_MACROS_H__ */ + + diff --git a/gst/gstmarshal.list b/gst/gstmarshal.list new file mode 100644 index 0000000..b9fc164 --- /dev/null +++ b/gst/gstmarshal.list @@ -0,0 +1,25 @@ +VOID:VOID +VOID:BOOLEAN +VOID:INT +VOID:STRING +VOID:BOXED +VOID:BOXED,OBJECT +VOID:POINTER +VOID:POINTER,OBJECT +VOID:OBJECT +VOID:OBJECT,OBJECT +VOID:OBJECT,PARAM +VOID:OBJECT,POINTER +VOID:OBJECT,BOXED +VOID:OBJECT,BOXED,STRING +VOID:OBJECT,OBJECT,STRING +VOID:OBJECT,STRING +VOID:INT,INT +VOID:INT64 +VOID:UINT,BOXED +VOID:UINT,POINTER +BOOLEAN:VOID +BOOLEAN:POINTER +BOOLEAN:BOXED +POINTER:POINTER +BOXED:BOXED diff --git a/gst/gstmemory.c b/gst/gstmemory.c new file mode 100644 index 0000000..ebaefc3 --- /dev/null +++ b/gst/gstmemory.c @@ -0,0 +1,677 @@ +/* GStreamer + * Copyright (C) 2011 Wim Taymans <wim.taymans@gmail.be> + * + * gstmemory.c: memory block handling + * + * 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:gstmemory + * @short_description: refcounted wrapper for memory blocks + * @see_also: #GstBuffer + * + * GstMemory is a lightweight refcounted object that wraps a region of memory. + * They are typically used to manage the data of a #GstBuffer. + * + * Memory is usually created by allocators with a gst_allocator_alloc() + * method call. When NULL is used as the allocator, the default allocator will + * be used. + * + * New allocators can be registered with gst_allocator_register(). + * Allocators are identified by name and can be retrieved with + * gst_allocator_find(). + * + * New memory can be created with gst_memory_new_wrapped() that wraps the memory + * allocated elsewhere. + * + * Refcounting of the memory block is performed with gst_memory_ref() and + * gst_memory_unref(). + * + * The size of the memory can be retrieved and changed with + * gst_memory_get_sizes() and gst_memory_resize() respectively. + * + * Getting access to the data of the memory is performed with gst_memory_map(). + * After the memory access is completed, gst_memory_unmap() should be called. + * + * Memory can be copied with gst_memory_copy(), which will returnn a writable + * copy. gst_memory_share() will create a new memory block that shares the + * memory with an existing memory block at a custom offset and with a custom + * size. + * + * Memory can be efficiently merged when gst_memory_is_span() returns TRUE. + * + * Last reviewed on 2011-06-08 (0.11.0) + */ + +#include "config.h" +#include "gst_private.h" +#include "gstmemory.h" + + +/* buffer alignment in bytes - 1 + * an alignment of 7 would be the same as malloc() guarantees + */ +#if defined(MEMORY_ALIGNMENT_MALLOC) +size_t gst_memory_alignment = 7; +#elif defined(MEMORY_ALIGNMENT_PAGESIZE) +/* we fill this in in the _init method */ +size_t gst_memory_alignment = 0; +#elif defined(MEMORY_ALIGNMENT) +size_t gst_memory_alignment = MEMORY_ALIGNMENT - 1; +#else +#error "No memory alignment configured" +size_t gst_memory_alignment = 0; +#endif + +struct _GstAllocator +{ + GQuark name; + + GstMemoryInfo info; +}; + +/* default memory implementation */ +typedef struct +{ + GstMemory mem; + gsize slice_size; + guint8 *data; + GFreeFunc free_func; + gsize maxsize; + gsize offset; + gsize size; +} GstMemoryDefault; + +/* the default allocator */ +static const GstAllocator *_default_allocator; + +/* our predefined allocators */ +static const GstAllocator *_default_mem_impl; + +/* initialize the fields */ +static void +_default_mem_init (GstMemoryDefault * mem, GstMemoryFlags flags, + GstMemory * parent, gsize slice_size, gpointer data, + GFreeFunc free_func, gsize maxsize, gsize offset, gsize size) +{ + mem->mem.allocator = _default_mem_impl; + mem->mem.flags = flags; + mem->mem.refcount = 1; + mem->mem.parent = parent ? gst_memory_ref (parent) : NULL; + mem->slice_size = slice_size; + mem->data = data; + mem->free_func = free_func; + mem->maxsize = maxsize; + mem->offset = offset; + mem->size = size; +} + +/* create a new memory block that manages the given memory */ +static GstMemoryDefault * +_default_mem_new (GstMemoryFlags flags, GstMemory * parent, gpointer data, + GFreeFunc free_func, gsize maxsize, gsize offset, gsize size) +{ + GstMemoryDefault *mem; + gsize slice_size; + + slice_size = sizeof (GstMemoryDefault); + + mem = g_slice_alloc (slice_size); + _default_mem_init (mem, flags, parent, slice_size, + data, free_func, maxsize, offset, size); + + return mem; +} + +/* allocate the memory and structure in one block */ +static GstMemoryDefault * +_default_mem_new_block (gsize maxsize, gsize align, gsize offset, gsize size) +{ + GstMemoryDefault *mem; + gsize aoffset, slice_size; + guint8 *data; + + /* ensure configured alignment */ + align |= gst_memory_alignment; + /* allocate more to compensate for alignment */ + maxsize += align; + /* alloc header and data in one block */ + slice_size = sizeof (GstMemoryDefault) + maxsize; + + mem = g_slice_alloc (slice_size); + if (mem == NULL) + return NULL; + + data = (guint8 *) mem + sizeof (GstMemoryDefault); + + if ((aoffset = ((guintptr) data & align))) + aoffset = (align + 1) - aoffset; + + _default_mem_init (mem, 0, NULL, slice_size, data, NULL, maxsize, + aoffset + offset, size); + + return mem; +} + +static GstMemory * +_default_mem_alloc (const GstAllocator * allocator, gsize maxsize, gsize align) +{ + return (GstMemory *) _default_mem_new_block (maxsize, align, 0, maxsize); +} + +static gsize +_default_mem_get_sizes (GstMemoryDefault * mem, gsize * offset, gsize * maxsize) +{ + if (offset) + *offset = mem->offset; + if (maxsize) + *maxsize = mem->maxsize; + + return mem->size; +} + +static void +_default_mem_resize (GstMemoryDefault * mem, gssize offset, gsize size) +{ + g_return_if_fail (size + mem->offset + offset <= mem->maxsize); + + mem->offset += offset; + mem->size = size; +} + +static gpointer +_default_mem_map (GstMemoryDefault * mem, gsize * size, gsize * maxsize, + GstMapFlags flags) +{ + if (size) + *size = mem->size; + if (maxsize) + *maxsize = mem->maxsize; + + return mem->data + mem->offset; +} + +static gboolean +_default_mem_unmap (GstMemoryDefault * mem, gpointer data, gsize size) +{ + if (size != -1) + mem->size = size; + return TRUE; +} + +static void +_default_mem_free (GstMemoryDefault * mem) +{ + if (mem->mem.parent) + gst_memory_unref (mem->mem.parent); + + if (mem->free_func) + mem->free_func (mem->data); + + g_slice_free1 (mem->slice_size, mem); +} + +static GstMemoryDefault * +_default_mem_copy (GstMemoryDefault * mem, gssize offset, gsize size) +{ + GstMemoryDefault *copy; + + if (size == -1) + size = mem->size > offset ? mem->size - offset : 0; + + copy = _default_mem_new_block (mem->maxsize, 0, mem->offset + offset, size); + memcpy (copy->data, mem->data, mem->maxsize); + + return copy; +} + +static GstMemoryDefault * +_default_mem_share (GstMemoryDefault * mem, gssize offset, gsize size) +{ + GstMemoryDefault *sub; + GstMemory *parent; + + /* find the real parent */ + if ((parent = mem->mem.parent) == NULL) + parent = (GstMemory *) mem; + + if (size == -1) + size = mem->size - offset; + + sub = _default_mem_new (parent->flags, parent, mem->data, NULL, mem->maxsize, + mem->offset + offset, size); + + return sub; +} + +static gboolean +_default_mem_is_span (GstMemoryDefault * mem1, GstMemoryDefault * mem2, + gsize * offset) +{ + + if (offset) { + GstMemoryDefault *parent; + + parent = (GstMemoryDefault *) mem1->mem.parent; + + *offset = mem1->offset - parent->offset; + } + + /* and memory is contiguous */ + return mem1->data + mem1->offset + mem1->size == mem2->data + mem2->offset; +} + +static GstMemory * +_fallback_copy (GstMemory * mem, gssize offset, gsize size) +{ + GstMemory *copy; + guint8 *data, *dest; + gsize msize; + + data = gst_memory_map (mem, &msize, NULL, GST_MAP_READ); + if (size == -1) + size = msize > offset ? msize - offset : 0; + /* use the same allocator as the memory we copy, FIXME, alignment? */ + copy = gst_allocator_alloc (mem->allocator, size, 0); + dest = gst_memory_map (copy, NULL, NULL, GST_MAP_WRITE); + memcpy (dest, data + offset, size); + gst_memory_unmap (copy, dest, size); + + gst_memory_unmap (mem, data, msize); + + return (GstMemory *) copy; +} + +static gboolean +_fallback_is_span (GstMemory * mem1, GstMemory * mem2, gsize * offset) +{ + return FALSE; +} + +static GStaticRWLock lock = G_STATIC_RW_LOCK_INIT; +static GHashTable *allocators; + +void +_priv_gst_memory_initialize (void) +{ + static const GstMemoryInfo _mem_info = { + (GstMemoryAllocFunction) _default_mem_alloc, + (GstMemoryGetSizesFunction) _default_mem_get_sizes, + (GstMemoryResizeFunction) _default_mem_resize, + (GstMemoryMapFunction) _default_mem_map, + (GstMemoryUnmapFunction) _default_mem_unmap, + (GstMemoryFreeFunction) _default_mem_free, + (GstMemoryCopyFunction) _default_mem_copy, + (GstMemoryShareFunction) _default_mem_share, + (GstMemoryIsSpanFunction) _default_mem_is_span, + NULL + }; + + allocators = g_hash_table_new (g_str_hash, g_str_equal); + +#ifdef HAVE_GETPAGESIZE +#ifdef MEMORY_ALIGNMENT_PAGESIZE + gst_memory_alignment = getpagesize () - 1; +#endif +#endif + + GST_DEBUG ("memory alignment: %" G_GSIZE_FORMAT, gst_memory_alignment); + + _default_mem_impl = gst_allocator_register (GST_ALLOCATOR_SYSMEM, &_mem_info); + + _default_allocator = _default_mem_impl; +} + +/** + * gst_memory_new_wrapped: + * @flags: #GstMemoryFlags + * @data: data to wrap + * @free_func: function to free @data + * @maxsize: allocated size of @data + * @offset: offset in @data + * @size: size of valid data + * + * Allocate a new memory block that wraps the given @data. + * + * Returns: a new #GstMemory. + */ +GstMemory * +gst_memory_new_wrapped (GstMemoryFlags flags, gpointer data, + GFreeFunc free_func, gsize maxsize, gsize offset, gsize size) +{ + GstMemoryDefault *mem; + + g_return_val_if_fail (data != NULL, NULL); + g_return_val_if_fail (offset + size <= maxsize, NULL); + + mem = _default_mem_new (flags, NULL, data, free_func, maxsize, offset, size); + + return (GstMemory *) mem; +} + +/** + * gst_memory_ref: + * @mem: a #GstMemory + * + * Increases the refcount of @mem. + * + * Returns: @mem with increased refcount + */ +GstMemory * +gst_memory_ref (GstMemory * mem) +{ + g_return_val_if_fail (mem != NULL, NULL); + + g_atomic_int_inc (&mem->refcount); + + return mem; +} + +/** + * gst_memory_unref: + * @mem: a #GstMemory + * + * Decreases the refcount of @mem. When the refcount reaches 0, the free + * function of @mem will be called. + */ +void +gst_memory_unref (GstMemory * mem) +{ + g_return_if_fail (mem != NULL); + g_return_if_fail (mem->allocator != NULL); + + if (g_atomic_int_dec_and_test (&mem->refcount)) + mem->allocator->info.free (mem); +} + +/** + * gst_memory_get_sizes: + * @mem: a #GstMemory + * @offset: pointer to offset + * @maxsize: pointer to maxsize + * + * Get the current @size, @offset and @maxsize of @mem. + * + * Returns: the current sizes of @mem + */ +gsize +gst_memory_get_sizes (GstMemory * mem, gsize * offset, gsize * maxsize) +{ + g_return_val_if_fail (mem != NULL, 0); + + return mem->allocator->info.get_sizes (mem, offset, maxsize); +} + +/** + * gst_memory_resize: + * @mem: a #GstMemory + * @offset: a new offset + * @size: a new size + * + * Resize the memory region. @mem should be writable and offset + size should be + * less than the maxsize of @mem. + */ +void +gst_memory_resize (GstMemory * mem, gssize offset, gsize size) +{ + g_return_if_fail (mem != NULL); + g_return_if_fail (GST_MEMORY_IS_WRITABLE (mem)); + + mem->allocator->info.resize (mem, offset, size); +} + +/** + * gst_memory_map: + * @mem: a #GstMemory + * @size: pointer for size + * @maxsize: pointer for maxsize + * @flags: mapping flags + * + * Get a pointer to the memory of @mem that can be accessed according to @flags. + * + * @size and @maxsize will contain the size of the memory and the maximum + * allocated memory of @mem respectively. They can be set to NULL. + * + * Returns: a pointer to the memory of @mem. + */ +gpointer +gst_memory_map (GstMemory * mem, gsize * size, gsize * maxsize, + GstMapFlags flags) +{ + g_return_val_if_fail (mem != NULL, NULL); + g_return_val_if_fail (!(flags & GST_MAP_WRITE) || + GST_MEMORY_IS_WRITABLE (mem), NULL); + + return mem->allocator->info.map (mem, size, maxsize, flags); +} + +/** + * gst_memory_unmap: + * @mem: a #GstMemory + * @data: data to unmap + * @size: new size of @mem + * + * Release the memory pointer obtained with gst_memory_map() and set the size of + * the memory to @size. @size can be set to -1 when the size should not be + * updated. + * + * Returns: TRUE when the memory was release successfully. + */ +gboolean +gst_memory_unmap (GstMemory * mem, gpointer data, gsize size) +{ + g_return_val_if_fail (mem != NULL, FALSE); + + return mem->allocator->info.unmap (mem, data, size); +} + +/** + * gst_memory_copy: + * @mem: a #GstMemory + * @offset: an offset to copy + * @size: size to copy + * + * Return a copy of @size bytes from @mem starting from @offset. This copy is + * guaranteed to be writable. @size can be set to -1 to return a copy all bytes + * from @offset. + * + * Returns: a new #GstMemory. + */ +GstMemory * +gst_memory_copy (GstMemory * mem, gssize offset, gsize size) +{ + g_return_val_if_fail (mem != NULL, NULL); + + return mem->allocator->info.copy (mem, offset, size); +} + +/** + * gst_memory_share: + * @mem: a #GstMemory + * @offset: an offset to share + * @size: size to share + * + * Return a shared copy of @size bytes from @mem starting from @offset. No memory + * copy is performed and the memory region is simply shared. The result is + * guaranteed to be not-writable. @size can be set to -1 to return a share all bytes + * from @offset. + * + * Returns: a new #GstMemory. + */ +GstMemory * +gst_memory_share (GstMemory * mem, gssize offset, gsize size) +{ + g_return_val_if_fail (mem != NULL, NULL); + + return mem->allocator->info.share (mem, offset, size); +} + +/** + * gst_memory_is_span: + * @mem1: a #GstMemory + * @mem2: a #GstMemory + * @offset: a pointer to a result offset + * + * Check if @mem1 and mem2 share the memory with a common parent memory object + * and that the memory is contiguous. + * + * If this is the case, the memory of @mem1 and @mem2 can be merged + * efficiently by performing gst_memory_share() on the parent object from + * the returned @offset. + * + * Returns: %TRUE if the memory is contiguous and of a common parent. + */ +gboolean +gst_memory_is_span (GstMemory * mem1, GstMemory * mem2, gsize * offset) +{ + g_return_val_if_fail (mem1 != NULL, FALSE); + g_return_val_if_fail (mem2 != NULL, FALSE); + + /* need to have the same allocators */ + if (mem1->allocator != mem2->allocator) + return FALSE; + + /* need to have the same parent */ + if (mem1->parent == NULL || mem1->parent != mem2->parent) + return FALSE; + + /* and memory is contiguous */ + if (!mem1->allocator->info.is_span (mem1, mem2, offset)) + return FALSE; + + return TRUE; +} + +/** + * gst_allocator_register: + * @name: the name of the allocator + * @info: #GstMemoryInfo + * + * Registers the memory allocator with @name and implementation functions + * @info. + * + * All functions in @info are mandatory exept the copy and is_span + * functions, which will have a default implementation when left NULL. + * + * The user_data field in @info will be passed to all calls of the alloc + * function. + * + * Returns: a new #GstAllocator. + */ +const GstAllocator * +gst_allocator_register (const gchar * name, const GstMemoryInfo * info) +{ + GstAllocator *allocator; + +#define INSTALL_FALLBACK(_t) \ + if (allocator->info._t == NULL) allocator->info._t = _fallback_ ##_t; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (info->alloc != NULL, NULL); + g_return_val_if_fail (info->get_sizes != NULL, NULL); + g_return_val_if_fail (info->resize != NULL, NULL); + g_return_val_if_fail (info->map != NULL, NULL); + g_return_val_if_fail (info->unmap != NULL, NULL); + g_return_val_if_fail (info->free != NULL, NULL); + g_return_val_if_fail (info->share != NULL, NULL); + + allocator = g_slice_new (GstAllocator); + allocator->name = g_quark_from_string (name); + allocator->info = *info; + INSTALL_FALLBACK (copy); + INSTALL_FALLBACK (is_span); +#undef INSTALL_FALLBACK + + GST_DEBUG ("registering allocator \"%s\"", name); + + g_static_rw_lock_writer_lock (&lock); + g_hash_table_insert (allocators, (gpointer) name, (gpointer) allocator); + g_static_rw_lock_writer_unlock (&lock); + + return allocator; +} + +/** + * gst_allocator_find: + * @name: the name of the allocator + * + * Find a previously registered allocator with @name. When @name is NULL, the + * default allocator will be returned. + * + * Returns: a #GstAllocator or NULL when the allocator with @name was not + * registered. + */ +const GstAllocator * +gst_allocator_find (const gchar * name) +{ + const GstAllocator *allocator; + + g_static_rw_lock_reader_lock (&lock); + if (name) { + allocator = g_hash_table_lookup (allocators, (gconstpointer) name); + } else { + allocator = _default_allocator; + } + g_static_rw_lock_reader_unlock (&lock); + + return allocator; +} + +/** + * gst_allocator_set_default: + * @allocator: a #GstAllocator + * + * Set the default allocator. + */ +void +gst_allocator_set_default (const GstAllocator * allocator) +{ + g_return_if_fail (allocator != NULL); + + g_static_rw_lock_writer_lock (&lock); + _default_allocator = allocator; + g_static_rw_lock_writer_unlock (&lock); +} + +/** + * gst_allocator_alloc: + * @allocator: a #GstAllocator to use + * @maxsize: allocated size of @data + * @align: alignment for the data + * + * Use @allocator to allocate a new memory block with memory that is at least + * @maxsize big and has the given alignment. + * + * When @allocator is NULL, the default allocator will be used. + * + * @align is given as a bitmask so that @align + 1 equals the amount of bytes to + * align to. For example, to align to 8 bytes, use an alignment of 7. + * + * Returns: a new #GstMemory. + */ +GstMemory * +gst_allocator_alloc (const GstAllocator * allocator, gsize maxsize, gsize align) +{ + g_return_val_if_fail (((align + 1) & align) == 0, NULL); + + if (allocator == NULL) + allocator = _default_allocator; + + return allocator->info.alloc (allocator, maxsize, align, + allocator->info.user_data); +} diff --git a/gst/gstmemory.h b/gst/gstmemory.h new file mode 100644 index 0000000..c1f8e67 --- /dev/null +++ b/gst/gstmemory.h @@ -0,0 +1,302 @@ +/* GStreamer + * Copyright (C) 2009 Wim Taymans <wim.taymans@gmail.be> + * + * gstmemory.h: Header for memory blocks + * + * 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_MEMORY_H__ +#define __GST_MEMORY_H__ + +#include <gst/gstconfig.h> + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _GstMemory GstMemory; +typedef struct _GstMemoryInfo GstMemoryInfo; +typedef struct _GstAllocator GstAllocator; + +extern gsize gst_memory_alignment; + +/** + * GstMemoryFlags: + * @GST_MEMORY_FLAG_READONLY: memory is readonly. It is not allowed to map the + * memory with #GST_MAP_WRITE. + * @GST_MEMORY_FLAG_NO_SHARE: memory must not be shared. Copies will have to be + * made when this memory needs to be shared between buffers. + * @GST_MEMORY_FLAG_LAST: first flag that can be used for custom purposes + * + * Flags for wrapped memory. + */ +typedef enum { + GST_MEMORY_FLAG_READONLY = (1 << 0), + GST_MEMORY_FLAG_NO_SHARE = (1 << 1), + + GST_MEMORY_FLAG_LAST = (1 << 24) +} GstMemoryFlags; + +/** + * GST_MEMORY_IS_WRITABLE: + * @mem: a #GstMemory + * + * Check if @mem is writable. + */ +#define GST_MEMORY_IS_WRITABLE(mem) (((mem)->refcount == 1) && \ + (((mem)->parent == NULL) || ((mem)->parent->refcount == 1)) && \ + (((mem)->flags & GST_MEMORY_FLAG_READONLY) == 0)) + +/** + * GstMemory: + * @allocator: pointer to the #GstAllocator + * @flags: memory flags + * @refcount: refcount + * @parent: parent memory block + * + * Base structure for memory implementations. Custom memory will put this structure + * as the first member of their structure. + */ +struct _GstMemory { + const GstAllocator *allocator; + + GstMemoryFlags flags; + gint refcount; + GstMemory *parent; +}; + +/** + * GstMapFlags: + * @GST_MAP_READ: map for read access + * @GST_MAP_WRITE: map for write access + * + * Flags used when mapping memory + */ +typedef enum { + GST_MAP_READ = (1 << 0), + GST_MAP_WRITE = (1 << 1), +} GstMapFlags; + +/** + * GST_MAP_READWRITE: + * + * Map for readwrite access + */ +#define GST_MAP_READWRITE (GST_MAP_READ | GST_MAP_WRITE) + +/** + * GST_ALLOCATOR_SYSMEM: + * + * The allocator name for the default system memory allocator + */ +#define GST_ALLOCATOR_SYSMEM "SystemMemory" + +/** + * GstMemoryAllocFunction: + * @allocator: a #GstAllocator + * @maxsize: the maxsize + * @align: the alignment + * @user_data: user data + * + * Allocate a new #GstMemory from @allocator that can hold at least @maxsize bytes + * and is aligned to (@align + 1) bytes. + * + * @user_data is the data that was used when registering @allocator. + * + * Returns: a newly allocated #GstMemory. Free with gst_memory_unref() + */ +typedef GstMemory * (*GstMemoryAllocFunction) (const GstAllocator *allocator, + gsize maxsize, gsize align, + gpointer user_data); + +/** + * GstMemoryGetSizesFunction: + * @mem: a #GstMemory + * @offset: result pointer for offset + * @maxsize: result pointer for maxsize + * + * Retrieve the size, offset and maxsize of @mem. + * + * Returns: the size of @mem, the offset and the maximum allocated size in @maxsize. + */ +typedef gsize (*GstMemoryGetSizesFunction) (GstMemory *mem, gsize *offset, gsize *maxsize); + +/** + * GstMemoryResizeFunction: + * @mem: a #GstMemory + * @offset: the offset adjustement + * @size: the new size + * + * Adjust the size and offset of @mem. @offset bytes will be adjusted from the + * current first byte in @mem as retrieved with gst_memory_map() and the new + * size will be set to @size. + * + * @size can be set to -1, which will only adjust the offset. + */ +typedef void (*GstMemoryResizeFunction) (GstMemory *mem, gssize offset, gsize size); + +/** + * GstMemoryMapFunction: + * @mem: a #GstMemory + * @size: pointer for the size + * @maxsize: pointer for the maxsize + * @flags: access mode for the memory + * + * Get the memory of @mem that can be accessed according to the mode specified + * in @flags. @size and @maxsize will respectively contain the current amount of + * valid bytes in the returned memory and the maximum allocated memory. + * @size and @maxsize can optionally be set to NULL. + * + * Returns: a pointer to memory. @size bytes are currently used from the + * returned pointer and @maxsize bytes can potentially be used. + */ +typedef gpointer (*GstMemoryMapFunction) (GstMemory *mem, gsize *size, gsize *maxsize, + GstMapFlags flags); + +/** + * GstMemoryUnmapFunction: + * @mem: a #GstMemory + * @data: the data pointer + * @size: the new size + * + * Return the pointer previously retrieved with gst_memory_map() and adjust the + * size of the memory with @size. @size can optionally be set to -1 to not + * modify the size. + * + * Returns: %TRUE on success. + */ +typedef gboolean (*GstMemoryUnmapFunction) (GstMemory *mem, gpointer data, gsize size); + +/** + * GstMemoryFreeFunction: + * @mem: a #GstMemory + * + * Free the memory used by @mem. This function is usually called when the + * refcount of the @mem has reached 0. + */ +typedef void (*GstMemoryFreeFunction) (GstMemory *mem); + +/** + * GstMemoryCopyFunction: + * @mem: a #GstMemory + * @offset: an offset + * @size: a size + * + * Copy @size bytes from @mem starting at @offset and return them wrapped in a + * new GstMemory object. + * If @size is set to -1, all bytes starting at @offset are copied. + * + * Returns: a new #GstMemory object wrapping a copy of the requested region in + * @mem. + */ +typedef GstMemory * (*GstMemoryCopyFunction) (GstMemory *mem, gssize offset, gsize size); + +/** + * GstMemoryShareFunction: + * @mem: a #GstMemory + * @offset: an offset + * @size: a size + * + * Share @size bytes from @mem starting at @offset and return them wrapped in a + * new GstMemory object. If @size is set to -1, all bytes starting at @offset are + * shared. This function does not make a copy of the bytes in @mem. + * + * Returns: a new #GstMemory object sharing the requested region in @mem. + */ +typedef GstMemory * (*GstMemoryShareFunction) (GstMemory *mem, gssize offset, gsize size); + +/** + * GstMemoryIsSpanFunction: + * @mem1: a #GstMemory + * @mem2: a #GstMemory + * @offset: a result offset + * + * Check if @mem1 and @mem2 occupy contiguous memory and return the offset of + * @mem1 in the parent buffer in @offset. + * + * Returns: %TRUE if @mem1 and @mem2 are in contiguous memory. + */ +typedef gboolean (*GstMemoryIsSpanFunction) (GstMemory *mem1, GstMemory *mem2, gsize *offset); + +/** + * GstMemoryInfo: + * @alloc: the implementation of the GstMemoryAllocFunction + * @get_sizes: the implementation of the GstMemoryGetSizesFunction + * @resize: the implementation of the GstMemoryResizeFunction + * @map: the implementation of the GstMemoryMapFunction + * @unmap: the implementation of the GstMemoryUnmapFunction + * @free: the implementation of the GstMemoryFreeFunction + * @copy: the implementation of the GstMemoryCopyFunction + * @share: the implementation of the GstMemoryShareFunction + * @is_span: the implementation of the GstMemoryIsSpanFunction + * @user_data: generic user data for the allocator + * + * The #GstMemoryInfo is used to register new memory allocators and contain + * the implementations for various memory operations. + */ +struct _GstMemoryInfo { + GstMemoryAllocFunction alloc; + GstMemoryGetSizesFunction get_sizes; + GstMemoryResizeFunction resize; + GstMemoryMapFunction map; + GstMemoryUnmapFunction unmap; + GstMemoryFreeFunction free; + + GstMemoryCopyFunction copy; + GstMemoryShareFunction share; + GstMemoryIsSpanFunction is_span; + + gpointer user_data; +}; + +/* allocators */ +const GstAllocator * gst_allocator_register (const gchar *name, const GstMemoryInfo *info); +const GstAllocator * gst_allocator_find (const gchar *name); + +void gst_allocator_set_default (const GstAllocator * allocator); + +/* allocating memory blocks */ +GstMemory * gst_allocator_alloc (const GstAllocator * allocator, + gsize maxsize, gsize align); + +GstMemory * gst_memory_new_wrapped (GstMemoryFlags flags, gpointer data, GFreeFunc free_func, + gsize maxsize, gsize offset, gsize size); + +/* refcounting */ +GstMemory * gst_memory_ref (GstMemory *mem); +void gst_memory_unref (GstMemory *mem); + +/* getting/setting memory properties */ +gsize gst_memory_get_sizes (GstMemory *mem, gsize *offset, gsize *maxsize); +void gst_memory_resize (GstMemory *mem, gssize offset, gsize size); + +/* retrieving data */ +gpointer gst_memory_map (GstMemory *mem, gsize *size, gsize *maxsize, + GstMapFlags flags); +gboolean gst_memory_unmap (GstMemory *mem, gpointer data, gsize size); + +/* copy and subregions */ +GstMemory * gst_memory_copy (GstMemory *mem, gssize offset, gsize size); +GstMemory * gst_memory_share (GstMemory *mem, gssize offset, gsize size); + +/* span memory */ +gboolean gst_memory_is_span (GstMemory *mem1, GstMemory *mem2, gsize *offset); + +G_END_DECLS + +#endif /* __GST_MEMORY_H__ */ diff --git a/gst/gstmessage.c b/gst/gstmessage.c new file mode 100644 index 0000000..41e7ccc --- /dev/null +++ b/gst/gstmessage.c @@ -0,0 +1,2158 @@ +/* GStreamer + * Copyright (C) 2004 Wim Taymans <wim@fluendo.com> + * + * gstmessage.c: GstMessage subsystem + * + * 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:gstmessage + * @short_description: Lightweight objects to signal the application of + * pipeline events + * @see_also: #GstBus, #GstMiniObject, #GstElement + * + * Messages are implemented as a subclass of #GstMiniObject with a generic + * #GstStructure as the content. This allows for writing custom messages without + * requiring an API change while allowing a wide range of different types + * of messages. + * + * Messages are posted by objects in the pipeline and are passed to the + * application using the #GstBus. + + * The basic use pattern of posting a message on a #GstBus is as follows: + * + * <example> + * <title>Posting a #GstMessage</title> + * <programlisting> + * gst_bus_post (bus, gst_message_new_eos()); + * </programlisting> + * </example> + * + * A #GstElement usually posts messages on the bus provided by the parent + * container using gst_element_post_message(). + * + * Last reviewed on 2005-11-09 (0.9.4) + */ + + +#include "gst_private.h" +#include <string.h> /* memcpy */ +#include "gsterror.h" +#include "gstenumtypes.h" +#include "gstinfo.h" +#include "gstmessage.h" +#include "gsttaglist.h" +#include "gstutils.h" +#include "gstquark.h" + + +typedef struct +{ + GstMessage message; + + GstStructure *structure; +} GstMessageImpl; + +#define GST_MESSAGE_STRUCTURE(m) (((GstMessageImpl *)(m))->structure) + +typedef struct +{ + const gint type; + const gchar *name; + GQuark quark; +} GstMessageQuarks; + +static GstMessageQuarks message_quarks[] = { + {GST_MESSAGE_UNKNOWN, "unknown", 0}, + {GST_MESSAGE_EOS, "eos", 0}, + {GST_MESSAGE_ERROR, "error", 0}, + {GST_MESSAGE_WARNING, "warning", 0}, + {GST_MESSAGE_INFO, "info", 0}, + {GST_MESSAGE_TAG, "tag", 0}, + {GST_MESSAGE_BUFFERING, "buffering", 0}, + {GST_MESSAGE_STATE_CHANGED, "state-changed", 0}, + {GST_MESSAGE_STATE_DIRTY, "state-dirty", 0}, + {GST_MESSAGE_STEP_DONE, "step-done", 0}, + {GST_MESSAGE_CLOCK_PROVIDE, "clock-provide", 0}, + {GST_MESSAGE_CLOCK_LOST, "clock-lost", 0}, + {GST_MESSAGE_NEW_CLOCK, "new-clock", 0}, + {GST_MESSAGE_STRUCTURE_CHANGE, "structure-change", 0}, + {GST_MESSAGE_STREAM_STATUS, "stream-status", 0}, + {GST_MESSAGE_APPLICATION, "application", 0}, + {GST_MESSAGE_ELEMENT, "element", 0}, + {GST_MESSAGE_SEGMENT_START, "segment-start", 0}, + {GST_MESSAGE_SEGMENT_DONE, "segment-done", 0}, + {GST_MESSAGE_DURATION, "duration", 0}, + {GST_MESSAGE_LATENCY, "latency", 0}, + {GST_MESSAGE_ASYNC_START, "async-start", 0}, + {GST_MESSAGE_ASYNC_DONE, "async-done", 0}, + {GST_MESSAGE_REQUEST_STATE, "request-state", 0}, + {GST_MESSAGE_STEP_START, "step-start", 0}, + {GST_MESSAGE_QOS, "qos", 0}, + {GST_MESSAGE_PROGRESS, "progress", 0}, + {0, NULL, 0} +}; + +static GType _gst_message_type = 0; +GST_DEFINE_MINI_OBJECT_TYPE (GstMessage, gst_message); + +void +_priv_gst_message_initialize (void) +{ + gint i; + + GST_CAT_INFO (GST_CAT_GST_INIT, "init messages"); + + /* the GstMiniObject types need to be class_ref'd once before it can be + * done from multiple threads; + * see http://bugzilla.gnome.org/show_bug.cgi?id=304551 */ + gst_message_get_type (); + + for (i = 0; message_quarks[i].name; i++) { + message_quarks[i].quark = + g_quark_from_static_string (message_quarks[i].name); + } + + _gst_message_type = gst_message_get_type (); +} + +/** + * gst_message_type_get_name: + * @type: the message type + * + * Get a printable name for the given message type. Do not modify or free. + * + * Returns: a reference to the static name of the message. + */ +const gchar * +gst_message_type_get_name (GstMessageType type) +{ + gint i; + + for (i = 0; message_quarks[i].name; i++) { + if (type == message_quarks[i].type) + return message_quarks[i].name; + } + return "unknown"; +} + +/** + * gst_message_type_to_quark: + * @type: the message type + * + * Get the unique quark for the given message type. + * + * Returns: the quark associated with the message type + */ +GQuark +gst_message_type_to_quark (GstMessageType type) +{ + gint i; + + for (i = 0; message_quarks[i].name; i++) { + if (type == message_quarks[i].type) + return message_quarks[i].quark; + } + return 0; +} + +static void +_gst_message_free (GstMessage * message) +{ + GstStructure *structure; + + g_return_if_fail (message != NULL); + + GST_CAT_LOG (GST_CAT_MESSAGE, "finalize message %p", message); + + if (GST_MESSAGE_SRC (message)) { + gst_object_unref (GST_MESSAGE_SRC (message)); + GST_MESSAGE_SRC (message) = NULL; + } + + if (message->lock) { + GST_MESSAGE_LOCK (message); + GST_MESSAGE_SIGNAL (message); + GST_MESSAGE_UNLOCK (message); + } + + structure = GST_MESSAGE_STRUCTURE (message); + if (structure) { + gst_structure_set_parent_refcount (structure, NULL); + gst_structure_free (structure); + } + + g_slice_free1 (GST_MINI_OBJECT_SIZE (message), message); +} + +static GstMessage * +_gst_message_copy (GstMessage * message) +{ + GstMessageImpl *copy; + GstStructure *structure; + + GST_CAT_LOG (GST_CAT_MESSAGE, "copy message %p", message); + + copy = g_slice_new0 (GstMessageImpl); + + gst_mini_object_init (GST_MINI_OBJECT_CAST (copy), + _gst_message_type, sizeof (GstMessageImpl)); + + copy->message.mini_object.copy = + (GstMiniObjectCopyFunction) _gst_message_copy; + copy->message.mini_object.free = + (GstMiniObjectFreeFunction) _gst_message_free; + + GST_MESSAGE_TYPE (copy) = GST_MESSAGE_TYPE (message); + GST_MESSAGE_TIMESTAMP (copy) = GST_MESSAGE_TIMESTAMP (message); + GST_MESSAGE_SEQNUM (copy) = GST_MESSAGE_SEQNUM (message); + if (GST_MESSAGE_SRC (message)) { + GST_MESSAGE_SRC (copy) = gst_object_ref (GST_MESSAGE_SRC (message)); + } + + GST_MESSAGE_GET_LOCK (copy) = GST_MESSAGE_GET_LOCK (message); + GST_MESSAGE_COND (copy) = GST_MESSAGE_COND (message); + + structure = GST_MESSAGE_STRUCTURE (message); + if (structure) { + copy->structure = gst_structure_copy (structure); + gst_structure_set_parent_refcount (copy->structure, + ©->message.mini_object.refcount); + } + + return GST_MESSAGE_CAST (copy); +} + +/** + * gst_message_new_custom: + * @type: The #GstMessageType to distinguish messages + * @src: The object originating the message. + * @structure: (transfer full): the structure for the message. The message + * will take ownership of the structure. + * + * Create a new custom-typed message. This can be used for anything not + * handled by other message-specific functions to pass a message to the + * app. The structure field can be NULL. + * + * Returns: (transfer full): The new message. + * + * MT safe. + */ +GstMessage * +gst_message_new_custom (GstMessageType type, GstObject * src, + GstStructure * structure) +{ + GstMessageImpl *message; + + message = g_slice_new0 (GstMessageImpl); + + gst_mini_object_init (GST_MINI_OBJECT_CAST (message), + _gst_message_type, sizeof (GstMessageImpl)); + + message->message.mini_object.copy = + (GstMiniObjectCopyFunction) _gst_message_copy; + message->message.mini_object.free = + (GstMiniObjectFreeFunction) _gst_message_free; + + GST_CAT_LOG (GST_CAT_MESSAGE, "source %s: creating new message %p %s", + (src ? GST_OBJECT_NAME (src) : "NULL"), message, + gst_message_type_get_name (type)); + + GST_MESSAGE_TYPE (message) = type; + if (src) + gst_object_ref (src); + GST_MESSAGE_SRC (message) = src; + GST_MESSAGE_TIMESTAMP (message) = GST_CLOCK_TIME_NONE; + GST_MESSAGE_SEQNUM (message) = gst_util_seqnum_next (); + + if (structure) { + gst_structure_set_parent_refcount (structure, + &message->message.mini_object.refcount); + } + message->structure = structure; + + return GST_MESSAGE_CAST (message); +} + +/** + * gst_message_get_seqnum: + * @message: A #GstMessage. + * + * Retrieve the sequence number of a message. + * + * Messages have ever-incrementing sequence numbers, which may also be set + * explicitly via gst_message_set_seqnum(). Sequence numbers are typically used + * to indicate that a message corresponds to some other set of messages or + * events, for example a SEGMENT_DONE message corresponding to a SEEK event. It + * is considered good practice to make this correspondence when possible, though + * it is not required. + * + * Note that events and messages share the same sequence number incrementor; + * two events or messages will never have the same sequence number unless + * that correspondence was made explicitly. + * + * Returns: The message's sequence number. + * + * MT safe. + * + * Since: 0.10.22 + */ +guint32 +gst_message_get_seqnum (GstMessage * message) +{ + g_return_val_if_fail (GST_IS_MESSAGE (message), -1); + + return GST_MESSAGE_SEQNUM (message); +} + +/** + * gst_message_set_seqnum: + * @message: A #GstMessage. + * @seqnum: A sequence number. + * + * Set the sequence number of a message. + * + * This function might be called by the creator of a message to indicate that + * the message relates to other messages or events. See gst_message_get_seqnum() + * for more information. + * + * MT safe. + * + * Since: 0.10.22 + */ +void +gst_message_set_seqnum (GstMessage * message, guint32 seqnum) +{ + g_return_if_fail (GST_IS_MESSAGE (message)); + + GST_MESSAGE_SEQNUM (message) = seqnum; +} + +/** + * gst_message_new_eos: + * @src: (transfer none): The object originating the message. + * + * Create a new eos message. This message is generated and posted in + * the sink elements of a GstBin. The bin will only forward the EOS + * message to the application if all sinks have posted an EOS message. + * + * Returns: (transfer full): The new eos message. + * + * MT safe. + */ +GstMessage * +gst_message_new_eos (GstObject * src) +{ + GstMessage *message; + + message = gst_message_new_custom (GST_MESSAGE_EOS, src, NULL); + + return message; +} + +/** + * gst_message_new_error: + * @src: (transfer none): The object originating the message. + * @error: (transfer none): The GError for this message. + * @debug: A debugging string. + * + * Create a new error message. The message will copy @error and + * @debug. This message is posted by element when a fatal event + * occured. The pipeline will probably (partially) stop. The application + * receiving this message should stop the pipeline. + * + * Returns: (transfer full): the new error message. + * + * MT safe. + */ +GstMessage * +gst_message_new_error (GstObject * src, GError * error, const gchar * debug) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_ERROR), + GST_QUARK (GERROR), GST_TYPE_G_ERROR, error, + GST_QUARK (DEBUG), G_TYPE_STRING, debug, NULL); + message = gst_message_new_custom (GST_MESSAGE_ERROR, src, structure); + + return message; +} + +/** + * gst_message_new_warning: + * @src: (transfer none): The object originating the message. + * @error: (transfer none): The GError for this message. + * @debug: A debugging string. + * + * Create a new warning message. The message will make copies of @error and + * @debug. + * + * Returns: (transfer full): The new warning message. + * + * MT safe. + */ +GstMessage * +gst_message_new_warning (GstObject * src, GError * error, const gchar * debug) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_WARNING), + GST_QUARK (GERROR), GST_TYPE_G_ERROR, error, + GST_QUARK (DEBUG), G_TYPE_STRING, debug, NULL); + message = gst_message_new_custom (GST_MESSAGE_WARNING, src, structure); + + return message; +} + +/** + * gst_message_new_info: + * @src: (transfer none): The object originating the message. + * @error: (transfer none): The GError for this message. + * @debug: A debugging string. + * + * Create a new info message. The message will make copies of @error and + * @debug. + * + * MT safe. + * + * Returns: (transfer full): the new info message. + * + * Since: 0.10.12 + */ +GstMessage * +gst_message_new_info (GstObject * src, GError * error, const gchar * debug) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_INFO), + GST_QUARK (GERROR), GST_TYPE_G_ERROR, error, + GST_QUARK (DEBUG), G_TYPE_STRING, debug, NULL); + message = gst_message_new_custom (GST_MESSAGE_INFO, src, structure); + + return message; +} + +/** + * gst_message_new_tag: + * @src: (transfer none): The object originating the message. + * @tag_list: (transfer full): the tag list for the message. + * + * Create a new tag message. The message will take ownership of the tag list. + * The message is posted by elements that discovered a new taglist. + * + * Returns: (transfer full): the new tag message. + * + * MT safe. + */ +GstMessage * +gst_message_new_tag (GstObject * src, GstTagList * tag_list) +{ + GstMessage *message; + + g_return_val_if_fail (GST_IS_STRUCTURE (tag_list), NULL); + + message = + gst_message_new_custom (GST_MESSAGE_TAG, src, (GstStructure *) tag_list); + + return message; +} + +/** + * gst_message_new_buffering: + * @src: (transfer none): The object originating the message. + * @percent: The buffering percent + * + * Create a new buffering message. This message can be posted by an element that + * needs to buffer data before it can continue processing. @percent should be a + * value between 0 and 100. A value of 100 means that the buffering completed. + * + * When @percent is < 100 the application should PAUSE a PLAYING pipeline. When + * @percent is 100, the application can set the pipeline (back) to PLAYING. + * The application must be prepared to receive BUFFERING messages in the + * PREROLLING state and may only set the pipeline to PLAYING after receiving a + * message with @percent set to 100, which can happen after the pipeline + * completed prerolling. + * + * MT safe. + * + * Returns: (transfer full): The new buffering message. + * + * Since: 0.10.11 + */ +GstMessage * +gst_message_new_buffering (GstObject * src, gint percent) +{ + GstMessage *message; + GstStructure *structure; + + g_return_val_if_fail (percent >= 0 && percent <= 100, NULL); + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_BUFFERING), + GST_QUARK (BUFFER_PERCENT), G_TYPE_INT, percent, + GST_QUARK (BUFFERING_MODE), GST_TYPE_BUFFERING_MODE, GST_BUFFERING_STREAM, + GST_QUARK (AVG_IN_RATE), G_TYPE_INT, -1, + GST_QUARK (AVG_OUT_RATE), G_TYPE_INT, -1, + GST_QUARK (BUFFERING_LEFT), G_TYPE_INT64, G_GINT64_CONSTANT (-1), + GST_QUARK (ESTIMATED_TOTAL), G_TYPE_INT64, G_GINT64_CONSTANT (-1), NULL); + message = gst_message_new_custom (GST_MESSAGE_BUFFERING, src, structure); + + return message; +} + +/** + * gst_message_new_state_changed: + * @src: (transfer none): the object originating the message + * @oldstate: the previous state + * @newstate: the new (current) state + * @pending: the pending (target) state + * + * Create a state change message. This message is posted whenever an element + * changed its state. + * + * Returns: (transfer full): the new state change message. + * + * MT safe. + */ +GstMessage * +gst_message_new_state_changed (GstObject * src, + GstState oldstate, GstState newstate, GstState pending) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_STATE), + GST_QUARK (OLD_STATE), GST_TYPE_STATE, (gint) oldstate, + GST_QUARK (NEW_STATE), GST_TYPE_STATE, (gint) newstate, + GST_QUARK (PENDING_STATE), GST_TYPE_STATE, (gint) pending, NULL); + message = gst_message_new_custom (GST_MESSAGE_STATE_CHANGED, src, structure); + + return message; +} + +/** + * gst_message_new_state_dirty: + * @src: (transfer none): the object originating the message + * + * Create a state dirty message. This message is posted whenever an element + * changed its state asynchronously and is used internally to update the + * states of container objects. + * + * Returns: (transfer full): the new state dirty message. + * + * MT safe. + */ +GstMessage * +gst_message_new_state_dirty (GstObject * src) +{ + GstMessage *message; + + message = gst_message_new_custom (GST_MESSAGE_STATE_DIRTY, src, NULL); + + return message; +} + +/** + * gst_message_new_clock_provide: + * @src: (transfer none): the object originating the message. + * @clock: (transfer none): the clock it provides + * @ready: TRUE if the sender can provide a clock + * + * Create a clock provide message. This message is posted whenever an + * element is ready to provide a clock or lost its ability to provide + * a clock (maybe because it paused or became EOS). + * + * This message is mainly used internally to manage the clock + * selection. + * + * Returns: (transfer full): the new provide clock message. + * + * MT safe. + */ +GstMessage * +gst_message_new_clock_provide (GstObject * src, GstClock * clock, + gboolean ready) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_CLOCK_PROVIDE), + GST_QUARK (CLOCK), GST_TYPE_CLOCK, clock, + GST_QUARK (READY), G_TYPE_BOOLEAN, ready, NULL); + message = gst_message_new_custom (GST_MESSAGE_CLOCK_PROVIDE, src, structure); + + return message; +} + +/** + * gst_message_new_clock_lost: + * @src: (transfer none): the object originating the message. + * @clock: (transfer none): the clock that was lost + * + * Create a clock lost message. This message is posted whenever the + * clock is not valid anymore. + * + * If this message is posted by the pipeline, the pipeline will + * select a new clock again when it goes to PLAYING. It might therefore + * be needed to set the pipeline to PAUSED and PLAYING again. + * + * Returns: (transfer full): The new clock lost message. + * + * MT safe. + */ +GstMessage * +gst_message_new_clock_lost (GstObject * src, GstClock * clock) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_CLOCK_LOST), + GST_QUARK (CLOCK), GST_TYPE_CLOCK, clock, NULL); + message = gst_message_new_custom (GST_MESSAGE_CLOCK_LOST, src, structure); + + return message; +} + +/** + * gst_message_new_new_clock: + * @src: (transfer none): The object originating the message. + * @clock: (transfer none): the new selected clock + * + * Create a new clock message. This message is posted whenever the + * pipeline selectes a new clock for the pipeline. + * + * Returns: (transfer full): The new new clock message. + * + * MT safe. + */ +GstMessage * +gst_message_new_new_clock (GstObject * src, GstClock * clock) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_NEW_CLOCK), + GST_QUARK (CLOCK), GST_TYPE_CLOCK, clock, NULL); + message = gst_message_new_custom (GST_MESSAGE_NEW_CLOCK, src, structure); + + return message; +} + +/** + * gst_message_new_structure_change: + * @src: (transfer none): The object originating the message. + * @type: The change type. + * @owner: (transfer none): The owner element of @src. + * @busy: Whether the structure change is busy. + * + * Create a new structure change message. This message is posted when the + * structure of a pipeline is in the process of being changed, for example + * when pads are linked or unlinked. + * + * @src should be the sinkpad that unlinked or linked. + * + * Returns: (transfer full): the new structure change message. + * + * MT safe. + * + * Since: 0.10.22. + */ +GstMessage * +gst_message_new_structure_change (GstObject * src, GstStructureChangeType type, + GstElement * owner, gboolean busy) +{ + GstMessage *message; + GstStructure *structure; + + g_return_val_if_fail (GST_IS_PAD (src), NULL); + /* g_return_val_if_fail (GST_PAD_DIRECTION (src) == GST_PAD_SINK, NULL); */ + g_return_val_if_fail (GST_IS_ELEMENT (owner), NULL); + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_STRUCTURE_CHANGE), + GST_QUARK (TYPE), GST_TYPE_STRUCTURE_CHANGE_TYPE, type, + GST_QUARK (OWNER), GST_TYPE_ELEMENT, owner, + GST_QUARK (BUSY), G_TYPE_BOOLEAN, busy, NULL); + + message = gst_message_new_custom (GST_MESSAGE_STRUCTURE_CHANGE, src, + structure); + + return message; +} + +/** + * gst_message_new_segment_start: + * @src: (transfer none): The object originating the message. + * @format: The format of the position being played + * @position: The position of the segment being played + * + * Create a new segment message. This message is posted by elements that + * start playback of a segment as a result of a segment seek. This message + * is not received by the application but is used for maintenance reasons in + * container elements. + * + * Returns: (transfer full): the new segment start message. + * + * MT safe. + */ +GstMessage * +gst_message_new_segment_start (GstObject * src, GstFormat format, + gint64 position) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_SEGMENT_START), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (POSITION), G_TYPE_INT64, position, NULL); + message = gst_message_new_custom (GST_MESSAGE_SEGMENT_START, src, structure); + + return message; +} + +/** + * gst_message_new_segment_done: + * @src: (transfer none): the object originating the message. + * @format: The format of the position being done + * @position: The position of the segment being done + * + * Create a new segment done message. This message is posted by elements that + * finish playback of a segment as a result of a segment seek. This message + * is received by the application after all elements that posted a segment_start + * have posted the segment_done. + * + * Returns: (transfer full): the new segment done message. + * + * MT safe. + */ +GstMessage * +gst_message_new_segment_done (GstObject * src, GstFormat format, + gint64 position) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_SEGMENT_DONE), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (POSITION), G_TYPE_INT64, position, NULL); + message = gst_message_new_custom (GST_MESSAGE_SEGMENT_DONE, src, structure); + + return message; +} + +/** + * gst_message_new_application: + * @src: (transfer none): the object originating the message. + * @structure: (transfer full): the structure for the message. The message + * will take ownership of the structure. + * + * Create a new application-typed message. GStreamer will never create these + * messages; they are a gift from us to you. Enjoy. + * + * Returns: (transfer full): The new application message. + * + * MT safe. + */ +GstMessage * +gst_message_new_application (GstObject * src, GstStructure * structure) +{ + return gst_message_new_custom (GST_MESSAGE_APPLICATION, src, structure); +} + +/** + * gst_message_new_element: + * @src: (transfer none): The object originating the message. + * @structure: (transfer full): The structure for the message. The message + * will take ownership of the structure. + * + * Create a new element-specific message. This is meant as a generic way of + * allowing one-way communication from an element to an application, for example + * "the firewire cable was unplugged". The format of the message should be + * documented in the element's documentation. The structure field can be NULL. + * + * Returns: (transfer full): The new element message. + * + * MT safe. + */ +GstMessage * +gst_message_new_element (GstObject * src, GstStructure * structure) +{ + return gst_message_new_custom (GST_MESSAGE_ELEMENT, src, structure); +} + +/** + * gst_message_new_duration: + * @src: (transfer none): The object originating the message. + * @format: The format of the duration + * @duration: The new duration + * + * Create a new duration message. This message is posted by elements that + * know the duration of a stream in a specific format. This message + * is received by bins and is used to calculate the total duration of a + * pipeline. Elements may post a duration message with a duration of + * GST_CLOCK_TIME_NONE to indicate that the duration has changed and the + * cached duration should be discarded. The new duration can then be + * retrieved via a query. + * + * Returns: (transfer full): The new duration message. + * + * MT safe. + */ +GstMessage * +gst_message_new_duration (GstObject * src, GstFormat format, gint64 duration) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_DURATION), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (DURATION), G_TYPE_INT64, duration, NULL); + message = gst_message_new_custom (GST_MESSAGE_DURATION, src, structure); + + return message; +} + +/** + * gst_message_new_async_start: + * @src: (transfer none): The object originating the message. + * + * This message is posted by elements when they start an ASYNC state change. + * + * Returns: (transfer full): The new async_start message. + * + * MT safe. + */ +GstMessage * +gst_message_new_async_start (GstObject * src) +{ + GstMessage *message; + + message = gst_message_new_custom (GST_MESSAGE_ASYNC_START, src, NULL); + + return message; +} + +/** + * gst_message_new_async_done: + * @src: (transfer none): The object originating the message. + * @reset_time: if the running_time should be reset + * + * The message is posted when elements completed an ASYNC state change. + * @reset_time is set to TRUE when the element requests a new running_time + * before going to PLAYING. + * + * Returns: (transfer full): The new async_done message. + * + * MT safe. + */ +GstMessage * +gst_message_new_async_done (GstObject * src, gboolean reset_time) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_ASYNC_DONE), + GST_QUARK (RESET_TIME), G_TYPE_BOOLEAN, reset_time, NULL); + message = gst_message_new_custom (GST_MESSAGE_ASYNC_DONE, src, structure); + + return message; +} + +/** + * gst_message_new_latency: + * @src: (transfer none): The object originating the message. + * + * This message can be posted by elements when their latency requirements have + * changed. + * + * Returns: (transfer full): The new latency message. + * + * MT safe. + * + * Since: 0.10.12 + */ +GstMessage * +gst_message_new_latency (GstObject * src) +{ + GstMessage *message; + + message = gst_message_new_custom (GST_MESSAGE_LATENCY, src, NULL); + + return message; +} + +/** + * gst_message_new_request_state: + * @src: (transfer none): the object originating the message. + * @state: The new requested state + * + * This message can be posted by elements when they want to have their state + * changed. A typical use case would be an audio server that wants to pause the + * pipeline because a higher priority stream is being played. + * + * Returns: (transfer full): the new requst state message. + * + * MT safe. + * + * Since: 0.10.23 + */ +GstMessage * +gst_message_new_request_state (GstObject * src, GstState state) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_REQUEST_STATE), + GST_QUARK (NEW_STATE), GST_TYPE_STATE, (gint) state, NULL); + message = gst_message_new_custom (GST_MESSAGE_REQUEST_STATE, src, structure); + + return message; +} + +/** + * gst_message_get_structure: + * @message: The #GstMessage. + * + * Access the structure of the message. + * + * Returns: (transfer none): The structure of the message. The structure is + * still owned by the message, which means that you should not free it and + * that the pointer becomes invalid when you free the message. + * + * MT safe. + */ +const GstStructure * +gst_message_get_structure (GstMessage * message) +{ + g_return_val_if_fail (GST_IS_MESSAGE (message), NULL); + + return GST_MESSAGE_STRUCTURE (message); +} + +/** + * gst_message_has_name: + * @message: The #GstMessage. + * @name: name to check + * + * Checks if @message has the given @name. This function is usually used to + * check the name of a custom message. + * + * Returns: %TRUE if @name matches the name of the message structure. + * + * Since: 0.10.20 + */ +gboolean +gst_message_has_name (GstMessage * message, const gchar * name) +{ + GstStructure *structure; + + g_return_val_if_fail (GST_IS_MESSAGE (message), FALSE); + + structure = GST_MESSAGE_STRUCTURE (message); + if (structure == NULL) + return FALSE; + + return gst_structure_has_name (structure, name); +} + +/** + * gst_message_parse_tag: + * @message: A valid #GstMessage of type GST_MESSAGE_TAG. + * @tag_list: (out callee-allocates): return location for the tag-list. + * + * Extracts the tag list from the GstMessage. The tag list returned in the + * output argument is a copy; the caller must free it when done. + * + * Typical usage of this function might be: + * |[ + * ... + * switch (GST_MESSAGE_TYPE (msg)) { + * case GST_MESSAGE_TAG: { + * GstTagList *tags = NULL; + * + * gst_message_parse_tag (msg, &tags); + * g_print ("Got tags from element %s\n", GST_OBJECT_NAME (msg->src)); + * handle_tags (tags); + * gst_tag_list_free (tags); + * break; + * } + * ... + * } + * ... + * ]| + * + * MT safe. + */ +void +gst_message_parse_tag (GstMessage * message, GstTagList ** tag_list) +{ + GstStructure *ret; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_TAG); + g_return_if_fail (tag_list != NULL); + + ret = gst_structure_copy (GST_MESSAGE_STRUCTURE (message)); + gst_structure_remove_field (ret, "source-pad"); + + *tag_list = (GstTagList *) ret; +} + +/** + * gst_message_parse_buffering: + * @message: A valid #GstMessage of type GST_MESSAGE_BUFFERING. + * @percent: (out) (allow-none): Return location for the percent. + * + * Extracts the buffering percent from the GstMessage. see also + * gst_message_new_buffering(). + * + * MT safe. + * + * Since: 0.10.11 + */ +void +gst_message_parse_buffering (GstMessage * message, gint * percent) +{ + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_BUFFERING); + + if (percent) + *percent = + g_value_get_int (gst_structure_id_get_value (GST_MESSAGE_STRUCTURE + (message), GST_QUARK (BUFFER_PERCENT))); +} + +/** + * gst_message_set_buffering_stats: + * @message: A valid #GstMessage of type GST_MESSAGE_BUFFERING. + * @mode: a buffering mode + * @avg_in: the average input rate + * @avg_out: the average output rate + * @buffering_left: amount of buffering time left in milliseconds + * + * Configures the buffering stats values in @message. + * + * Since: 0.10.20 + */ +void +gst_message_set_buffering_stats (GstMessage * message, GstBufferingMode mode, + gint avg_in, gint avg_out, gint64 buffering_left) +{ + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_BUFFERING); + + gst_structure_id_set (GST_MESSAGE_STRUCTURE (message), + GST_QUARK (BUFFERING_MODE), GST_TYPE_BUFFERING_MODE, mode, + GST_QUARK (AVG_IN_RATE), G_TYPE_INT, avg_in, + GST_QUARK (AVG_OUT_RATE), G_TYPE_INT, avg_out, + GST_QUARK (BUFFERING_LEFT), G_TYPE_INT64, buffering_left, NULL); +} + +/** + * gst_message_parse_buffering_stats: + * @message: A valid #GstMessage of type GST_MESSAGE_BUFFERING. + * @mode: (out) (allow-none): a buffering mode, or NULL + * @avg_in: (out) (allow-none): the average input rate, or NULL + * @avg_out: (out) (allow-none): the average output rate, or NULL + * @buffering_left: (out) (allow-none): amount of buffering time left in + * milliseconds, or NULL + * + * Extracts the buffering stats values from @message. + * + * Since: 0.10.20 + */ +void +gst_message_parse_buffering_stats (GstMessage * message, + GstBufferingMode * mode, gint * avg_in, gint * avg_out, + gint64 * buffering_left) +{ + GstStructure *structure; + + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_BUFFERING); + + structure = GST_MESSAGE_STRUCTURE (message); + if (mode) + *mode = (GstBufferingMode) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (BUFFERING_MODE))); + if (avg_in) + *avg_in = g_value_get_int (gst_structure_id_get_value (structure, + GST_QUARK (AVG_IN_RATE))); + if (avg_out) + *avg_out = g_value_get_int (gst_structure_id_get_value (structure, + GST_QUARK (AVG_OUT_RATE))); + if (buffering_left) + *buffering_left = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (BUFFERING_LEFT))); +} + +/** + * gst_message_parse_state_changed: + * @message: a valid #GstMessage of type GST_MESSAGE_STATE_CHANGED + * @oldstate: (out) (allow-none): the previous state, or NULL + * @newstate: (out) (allow-none): the new (current) state, or NULL + * @pending: (out) (allow-none): the pending (target) state, or NULL + * + * Extracts the old and new states from the GstMessage. + * + * Typical usage of this function might be: + * |[ + * ... + * switch (GST_MESSAGE_TYPE (msg)) { + * case GST_MESSAGE_STATE_CHANGED: { + * GstState old_state, new_state; + * + * gst_message_parse_state_changed (msg, &old_state, &new_state, NULL); + * g_print ("Element %s changed state from %s to %s.\n", + * GST_OBJECT_NAME (msg->src), + * gst_element_state_get_name (old_state), + * gst_element_state_get_name (new_state)); + * break; + * } + * ... + * } + * ... + * ]| + * + * MT safe. + */ +void +gst_message_parse_state_changed (GstMessage * message, + GstState * oldstate, GstState * newstate, GstState * pending) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STATE_CHANGED); + + structure = GST_MESSAGE_STRUCTURE (message); + if (oldstate) + *oldstate = (GstState) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (OLD_STATE))); + if (newstate) + *newstate = (GstState) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (NEW_STATE))); + if (pending) + *pending = (GstState) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (PENDING_STATE))); +} + +/** + * gst_message_parse_clock_provide: + * @message: A valid #GstMessage of type GST_MESSAGE_CLOCK_PROVIDE. + * @clock: (out) (allow-none) (transfer none): a pointer to hold a clock + * object, or NULL + * @ready: (out) (allow-none): a pointer to hold the ready flag, or NULL + * + * Extracts the clock and ready flag from the GstMessage. + * The clock object returned remains valid until the message is freed. + * + * MT safe. + */ +void +gst_message_parse_clock_provide (GstMessage * message, GstClock ** clock, + gboolean * ready) +{ + const GValue *clock_gvalue; + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_CLOCK_PROVIDE); + + structure = GST_MESSAGE_STRUCTURE (message); + clock_gvalue = gst_structure_id_get_value (structure, GST_QUARK (CLOCK)); + g_return_if_fail (clock_gvalue != NULL); + g_return_if_fail (G_VALUE_TYPE (clock_gvalue) == GST_TYPE_CLOCK); + + if (ready) + *ready = + g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (READY))); + if (clock) + *clock = (GstClock *) g_value_get_object (clock_gvalue); +} + +/** + * gst_message_parse_clock_lost: + * @message: A valid #GstMessage of type GST_MESSAGE_CLOCK_LOST. + * @clock: (out) (allow-none) (transfer none): a pointer to hold the lost clock + * + * Extracts the lost clock from the GstMessage. + * The clock object returned remains valid until the message is freed. + * + * MT safe. + */ +void +gst_message_parse_clock_lost (GstMessage * message, GstClock ** clock) +{ + const GValue *clock_gvalue; + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_CLOCK_LOST); + + structure = GST_MESSAGE_STRUCTURE (message); + clock_gvalue = gst_structure_id_get_value (structure, GST_QUARK (CLOCK)); + g_return_if_fail (clock_gvalue != NULL); + g_return_if_fail (G_VALUE_TYPE (clock_gvalue) == GST_TYPE_CLOCK); + + if (clock) + *clock = (GstClock *) g_value_get_object (clock_gvalue); +} + +/** + * gst_message_parse_new_clock: + * @message: A valid #GstMessage of type GST_MESSAGE_NEW_CLOCK. + * @clock: (out) (allow-none) (transfer none): a pointer to hold the selected + * new clock + * + * Extracts the new clock from the GstMessage. + * The clock object returned remains valid until the message is freed. + * + * MT safe. + */ +void +gst_message_parse_new_clock (GstMessage * message, GstClock ** clock) +{ + const GValue *clock_gvalue; + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_NEW_CLOCK); + + structure = GST_MESSAGE_STRUCTURE (message); + clock_gvalue = gst_structure_id_get_value (structure, GST_QUARK (CLOCK)); + g_return_if_fail (clock_gvalue != NULL); + g_return_if_fail (G_VALUE_TYPE (clock_gvalue) == GST_TYPE_CLOCK); + + if (clock) + *clock = (GstClock *) g_value_get_object (clock_gvalue); +} + +/** + * gst_message_parse_structure_change: + * @message: A valid #GstMessage of type GST_MESSAGE_STRUCTURE_CHANGE. + * @type: (out): A pointer to hold the change type + * @owner: (out) (allow-none) (transfer none): The owner element of the + * message source + * @busy: (out) (allow-none): a pointer to hold whether the change is in + * progress or has been completed + * + * Extracts the change type and completion status from the GstMessage. + * + * MT safe. + * + * Since: 0.10.22 + */ +void +gst_message_parse_structure_change (GstMessage * message, + GstStructureChangeType * type, GstElement ** owner, gboolean * busy) +{ + const GValue *owner_gvalue; + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STRUCTURE_CHANGE); + + structure = GST_MESSAGE_STRUCTURE (message); + owner_gvalue = gst_structure_id_get_value (structure, GST_QUARK (OWNER)); + g_return_if_fail (owner_gvalue != NULL); + g_return_if_fail (G_VALUE_TYPE (owner_gvalue) == GST_TYPE_ELEMENT); + + if (type) + *type = (GstStructureChangeType) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (TYPE))); + if (owner) + *owner = (GstElement *) g_value_get_object (owner_gvalue); + if (busy) + *busy = + g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (BUSY))); +} + +/** + * gst_message_parse_error: + * @message: A valid #GstMessage of type GST_MESSAGE_ERROR. + * @gerror: (out) (allow-none) (transfer full): location for the GError + * @debug: (out) (allow-none) (transfer full): location for the debug message, + * or NULL + * + * Extracts the GError and debug string from the GstMessage. The values returned + * in the output arguments are copies; the caller must free them when done. + * + * Typical usage of this function might be: + * |[ + * ... + * switch (GST_MESSAGE_TYPE (msg)) { + * case GST_MESSAGE_ERROR: { + * GError *err = NULL; + * gchar *dbg_info = NULL; + * + * gst_message_parse_error (msg, &err, &dbg_info); + * g_printerr ("ERROR from element %s: %s\n", + * GST_OBJECT_NAME (msg->src), err->message); + * g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none"); + * g_error_free (err); + * g_free (dbg_info); + * break; + * } + * ... + * } + * ... + * ]| + * + * MT safe. + */ +void +gst_message_parse_error (GstMessage * message, GError ** gerror, gchar ** debug) +{ + const GValue *error_gvalue; + GError *error_val; + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR); + + structure = GST_MESSAGE_STRUCTURE (message); + error_gvalue = gst_structure_id_get_value (structure, GST_QUARK (GERROR)); + g_return_if_fail (error_gvalue != NULL); + g_return_if_fail (G_VALUE_TYPE (error_gvalue) == GST_TYPE_G_ERROR); + + error_val = (GError *) g_value_get_boxed (error_gvalue); + if (error_val) + *gerror = g_error_copy (error_val); + else + *gerror = NULL; + + if (debug) + *debug = + g_value_dup_string (gst_structure_id_get_value (structure, + GST_QUARK (DEBUG))); +} + +/** + * gst_message_parse_warning: + * @message: A valid #GstMessage of type GST_MESSAGE_WARNING. + * @gerror: (out) (allow-none) (transfer full): location for the GError + * @debug: (out) (allow-none) (transfer full): location for the debug message, + * or NULL + * + * Extracts the GError and debug string from the GstMessage. The values returned + * in the output arguments are copies; the caller must free them when done. + * + * MT safe. + */ +void +gst_message_parse_warning (GstMessage * message, GError ** gerror, + gchar ** debug) +{ + const GValue *error_gvalue; + GError *error_val; + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_WARNING); + + structure = GST_MESSAGE_STRUCTURE (message); + error_gvalue = gst_structure_id_get_value (structure, GST_QUARK (GERROR)); + g_return_if_fail (error_gvalue != NULL); + g_return_if_fail (G_VALUE_TYPE (error_gvalue) == GST_TYPE_G_ERROR); + + error_val = (GError *) g_value_get_boxed (error_gvalue); + if (error_val) + *gerror = g_error_copy (error_val); + else + *gerror = NULL; + + if (debug) + *debug = + g_value_dup_string (gst_structure_id_get_value (structure, + GST_QUARK (DEBUG))); +} + +/** + * gst_message_parse_info: + * @message: A valid #GstMessage of type GST_MESSAGE_INFO. + * @gerror: (out) (allow-none) (transfer full): location for the GError + * @debug: (out) (allow-none) (transfer full): location for the debug message, + * or NULL + * + * Extracts the GError and debug string from the GstMessage. The values returned + * in the output arguments are copies; the caller must free them when done. + * + * MT safe. + * + * Since: 0.10.12 + */ +void +gst_message_parse_info (GstMessage * message, GError ** gerror, gchar ** debug) +{ + const GValue *error_gvalue; + GError *error_val; + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_INFO); + + structure = GST_MESSAGE_STRUCTURE (message); + error_gvalue = gst_structure_id_get_value (structure, GST_QUARK (GERROR)); + g_return_if_fail (error_gvalue != NULL); + g_return_if_fail (G_VALUE_TYPE (error_gvalue) == GST_TYPE_G_ERROR); + + error_val = (GError *) g_value_get_boxed (error_gvalue); + if (error_val) + *gerror = g_error_copy (error_val); + else + *gerror = NULL; + + if (debug) + *debug = + g_value_dup_string (gst_structure_id_get_value (structure, + GST_QUARK (DEBUG))); +} + +/** + * gst_message_parse_segment_start: + * @message: A valid #GstMessage of type GST_MESSAGE_SEGMENT_START. + * @format: (out): Result location for the format, or NULL + * @position: (out): Result location for the position, or NULL + * + * Extracts the position and format from the segment start message. + * + * MT safe. + */ +void +gst_message_parse_segment_start (GstMessage * message, GstFormat * format, + gint64 * position) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_SEGMENT_START); + + structure = GST_MESSAGE_STRUCTURE (message); + if (format) + *format = (GstFormat) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (position) + *position = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (POSITION))); +} + +/** + * gst_message_parse_segment_done: + * @message: A valid #GstMessage of type GST_MESSAGE_SEGMENT_DONE. + * @format: (out): Result location for the format, or NULL + * @position: (out): Result location for the position, or NULL + * + * Extracts the position and format from the segment start message. + * + * MT safe. + */ +void +gst_message_parse_segment_done (GstMessage * message, GstFormat * format, + gint64 * position) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_SEGMENT_DONE); + + structure = GST_MESSAGE_STRUCTURE (message); + if (format) + *format = (GstFormat) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (position) + *position = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (POSITION))); +} + +/** + * gst_message_parse_duration: + * @message: A valid #GstMessage of type GST_MESSAGE_DURATION. + * @format: (out): Result location for the format, or NULL + * @duration: (out): Result location for the duration, or NULL + * + * Extracts the duration and format from the duration message. The duration + * might be GST_CLOCK_TIME_NONE, which indicates that the duration has + * changed. Applications should always use a query to retrieve the duration + * of a pipeline. + * + * MT safe. + */ +void +gst_message_parse_duration (GstMessage * message, GstFormat * format, + gint64 * duration) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_DURATION); + + structure = GST_MESSAGE_STRUCTURE (message); + if (format) + *format = (GstFormat) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (duration) + *duration = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (DURATION))); +} + +/** + * gst_message_parse_async_done: + * @message: A valid #GstMessage of type GST_MESSAGE_ASYNC_DONE. + * @reset_time: (out): Result location for the reset_time or NULL + * + * Extract the reset_time from the async_done message. + * + * MT safe. + */ +void +gst_message_parse_async_done (GstMessage * message, gboolean * reset_time) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ASYNC_DONE); + + structure = GST_MESSAGE_STRUCTURE (message); + if (reset_time) + *reset_time = + g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (RESET_TIME))); +} + +/** + * gst_message_parse_request_state: + * @message: A valid #GstMessage of type GST_MESSAGE_REQUEST_STATE. + * @state: (out): Result location for the requested state or NULL + * + * Extract the requested state from the request_state message. + * + * MT safe. + * + * Since: 0.10.23 + */ +void +gst_message_parse_request_state (GstMessage * message, GstState * state) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_REQUEST_STATE); + + structure = GST_MESSAGE_STRUCTURE (message); + if (state) + *state = (GstState) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (NEW_STATE))); +} + +/** + * gst_message_new_stream_status: + * @src: The object originating the message. + * @type: The stream status type. + * @owner: (transfer none): the owner element of @src. + * + * Create a new stream status message. This message is posted when a streaming + * thread is created/destroyed or when the state changed. + * + * Returns: (transfer full): the new stream status message. + * + * MT safe. + * + * Since: 0.10.24. + */ +GstMessage * +gst_message_new_stream_status (GstObject * src, GstStreamStatusType type, + GstElement * owner) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_STREAM_STATUS), + GST_QUARK (TYPE), GST_TYPE_STREAM_STATUS_TYPE, (gint) type, + GST_QUARK (OWNER), GST_TYPE_ELEMENT, owner, NULL); + message = gst_message_new_custom (GST_MESSAGE_STREAM_STATUS, src, structure); + + return message; +} + +/** + * gst_message_parse_stream_status: + * @message: A valid #GstMessage of type GST_MESSAGE_STREAM_STATUS. + * @type: (out): A pointer to hold the status type + * @owner: (out) (transfer none): The owner element of the message source + * + * Extracts the stream status type and owner the GstMessage. The returned + * owner remains valid for as long as the reference to @message is valid and + * should thus not be unreffed. + * + * MT safe. + * + * Since: 0.10.24. + */ +void +gst_message_parse_stream_status (GstMessage * message, + GstStreamStatusType * type, GstElement ** owner) +{ + const GValue *owner_gvalue; + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STREAM_STATUS); + + structure = GST_MESSAGE_STRUCTURE (message); + owner_gvalue = gst_structure_id_get_value (structure, GST_QUARK (OWNER)); + g_return_if_fail (owner_gvalue != NULL); + + if (type) + *type = (GstStreamStatusType) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (TYPE))); + if (owner) + *owner = (GstElement *) g_value_get_object (owner_gvalue); +} + +/** + * gst_message_set_stream_status_object: + * @message: A valid #GstMessage of type GST_MESSAGE_STREAM_STATUS. + * @object: the object controlling the streaming + * + * Configures the object handling the streaming thread. This is usually a + * GstTask object but other objects might be added in the future. + * + * Since: 0.10.24 + */ +void +gst_message_set_stream_status_object (GstMessage * message, + const GValue * object) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STREAM_STATUS); + + structure = GST_MESSAGE_STRUCTURE (message); + gst_structure_id_set_value (structure, GST_QUARK (OBJECT), object); +} + +/** + * gst_message_get_stream_status_object: + * @message: A valid #GstMessage of type GST_MESSAGE_STREAM_STATUS. + * + * Extracts the object managing the streaming thread from @message. + * + * Returns: a GValue containing the object that manages the streaming thread. + * This object is usually of type GstTask but other types can be added in the + * future. The object remains valid as long as @message is valid. + * + * Since: 0.10.24 + */ +const GValue * +gst_message_get_stream_status_object (GstMessage * message) +{ + const GValue *result; + GstStructure *structure; + + g_return_val_if_fail (GST_IS_MESSAGE (message), NULL); + g_return_val_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STREAM_STATUS, + NULL); + + structure = GST_MESSAGE_STRUCTURE (message); + result = gst_structure_id_get_value (structure, GST_QUARK (OBJECT)); + + return result; +} + +/** + * gst_message_new_step_done: + * @src: The object originating the message. + * @format: the format of @amount + * @amount: the amount of stepped data + * @rate: the rate of the stepped amount + * @flush: is this an flushing step + * @intermediate: is this an intermediate step + * @duration: the duration of the data + * @eos: the step caused EOS + * + * This message is posted by elements when they complete a part, when @intermediate set + * to TRUE, or a complete step operation. + * + * @duration will contain the amount of time (in GST_FORMAT_TIME) of the stepped + * @amount of media in format @format. + * + * Returns: (transfer full): the new step_done message. + * + * MT safe. + * + * Since: 0.10.24 + */ +GstMessage * +gst_message_new_step_done (GstObject * src, GstFormat format, guint64 amount, + gdouble rate, gboolean flush, gboolean intermediate, guint64 duration, + gboolean eos) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_STEP_DONE), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (AMOUNT), G_TYPE_UINT64, amount, + GST_QUARK (RATE), G_TYPE_DOUBLE, rate, + GST_QUARK (FLUSH), G_TYPE_BOOLEAN, flush, + GST_QUARK (INTERMEDIATE), G_TYPE_BOOLEAN, intermediate, + GST_QUARK (DURATION), G_TYPE_UINT64, duration, + GST_QUARK (EOS), G_TYPE_BOOLEAN, eos, NULL); + message = gst_message_new_custom (GST_MESSAGE_STEP_DONE, src, structure); + + return message; +} + +/** + * gst_message_parse_step_done: + * @message: A valid #GstMessage of type GST_MESSAGE_STEP_DONE. + * @format: (out) (allow-none): result location for the format + * @amount: (out) (allow-none): result location for the amount + * @rate: (out) (allow-none): result location for the rate + * @flush: (out) (allow-none): result location for the flush flag + * @intermediate: (out) (allow-none): result location for the intermediate flag + * @duration: (out) (allow-none): result location for the duration + * @eos: (out) (allow-none): result location for the EOS flag + * + * Extract the values the step_done message. + * + * MT safe. + * + * Since: 0.10.24 + */ +void +gst_message_parse_step_done (GstMessage * message, GstFormat * format, + guint64 * amount, gdouble * rate, gboolean * flush, gboolean * intermediate, + guint64 * duration, gboolean * eos) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STEP_DONE); + + structure = GST_MESSAGE_STRUCTURE (message); + gst_structure_id_get (structure, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (AMOUNT), G_TYPE_UINT64, amount, + GST_QUARK (RATE), G_TYPE_DOUBLE, rate, + GST_QUARK (FLUSH), G_TYPE_BOOLEAN, flush, + GST_QUARK (INTERMEDIATE), G_TYPE_BOOLEAN, intermediate, + GST_QUARK (DURATION), G_TYPE_UINT64, duration, + GST_QUARK (EOS), G_TYPE_BOOLEAN, eos, NULL); +} + +/** + * gst_message_new_step_start: + * @src: The object originating the message. + * @active: if the step is active or queued + * @format: the format of @amount + * @amount: the amount of stepped data + * @rate: the rate of the stepped amount + * @flush: is this an flushing step + * @intermediate: is this an intermediate step + * + * This message is posted by elements when they accept or activate a new step + * event for @amount in @format. + * + * @active is set to FALSE when the element accepted the new step event and has + * queued it for execution in the streaming threads. + * + * @active is set to TRUE when the element has activated the step operation and + * is now ready to start executing the step in the streaming thread. After this + * message is emited, the application can queue a new step operation in the + * element. + * + * Returns: (transfer full): The new step_start message. + * + * MT safe. + * + * Since: 0.10.24 + */ +GstMessage * +gst_message_new_step_start (GstObject * src, gboolean active, GstFormat format, + guint64 amount, gdouble rate, gboolean flush, gboolean intermediate) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_STEP_START), + GST_QUARK (ACTIVE), G_TYPE_BOOLEAN, active, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (AMOUNT), G_TYPE_UINT64, amount, + GST_QUARK (RATE), G_TYPE_DOUBLE, rate, + GST_QUARK (FLUSH), G_TYPE_BOOLEAN, flush, + GST_QUARK (INTERMEDIATE), G_TYPE_BOOLEAN, intermediate, NULL); + message = gst_message_new_custom (GST_MESSAGE_STEP_START, src, structure); + + return message; +} + +/** + * gst_message_parse_step_start: + * @message: A valid #GstMessage of type GST_MESSAGE_STEP_DONE. + * @active: (out) (allow-none): result location for the active flag + * @format: (out) (allow-none): result location for the format + * @amount: (out) (allow-none): result location for the amount + * @rate: (out) (allow-none): result location for the rate + * @flush: (out) (allow-none): result location for the flush flag + * @intermediate: (out) (allow-none): result location for the intermediate flag + * + * Extract the values from step_start message. + * + * MT safe. + * + * Since: 0.10.24 + */ +void +gst_message_parse_step_start (GstMessage * message, gboolean * active, + GstFormat * format, guint64 * amount, gdouble * rate, gboolean * flush, + gboolean * intermediate) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STEP_START); + + structure = GST_MESSAGE_STRUCTURE (message); + gst_structure_id_get (structure, + GST_QUARK (ACTIVE), G_TYPE_BOOLEAN, active, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (AMOUNT), G_TYPE_UINT64, amount, + GST_QUARK (RATE), G_TYPE_DOUBLE, rate, + GST_QUARK (FLUSH), G_TYPE_BOOLEAN, flush, + GST_QUARK (INTERMEDIATE), G_TYPE_BOOLEAN, intermediate, NULL); +} + +/** + * gst_message_new_qos: + * @src: The object originating the message. + * @live: if the message was generated by a live element + * @running_time: the running time of the buffer that generated the message + * @stream_time: the stream time of the buffer that generated the message + * @timestamp: the timestamps of the buffer that generated the message + * @duration: the duration of the buffer that generated the message + * + * A QOS message is posted on the bus whenever an element decides to drop a + * buffer because of QoS reasons or whenever it changes its processing strategy + * because of QoS reasons (quality adjustments such as processing at lower + * accuracy). + * + * This message can be posted by an element that performs synchronisation against the + * clock (live) or it could be dropped by an element that performs QoS because of QOS + * events received from a downstream element (!live). + * + * @running_time, @stream_time, @timestamp, @duration should be set to the + * respective running-time, stream-time, timestamp and duration of the (dropped) + * buffer that generated the QoS event. Values can be left to + * GST_CLOCK_TIME_NONE when unknown. + * + * Returns: (transfer full): The new qos message. + * + * MT safe. + * + * Since: 0.10.29 + */ +GstMessage * +gst_message_new_qos (GstObject * src, gboolean live, guint64 running_time, + guint64 stream_time, guint64 timestamp, guint64 duration) +{ + GstMessage *message; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_QOS), + GST_QUARK (LIVE), G_TYPE_BOOLEAN, live, + GST_QUARK (RUNNING_TIME), G_TYPE_UINT64, running_time, + GST_QUARK (STREAM_TIME), G_TYPE_UINT64, stream_time, + GST_QUARK (TIMESTAMP), G_TYPE_UINT64, timestamp, + GST_QUARK (DURATION), G_TYPE_UINT64, duration, + GST_QUARK (JITTER), G_TYPE_INT64, (gint64) 0, + GST_QUARK (PROPORTION), G_TYPE_DOUBLE, (gdouble) 1.0, + GST_QUARK (QUALITY), G_TYPE_INT, (gint) 1000000, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, GST_FORMAT_UNDEFINED, + GST_QUARK (PROCESSED), G_TYPE_UINT64, (guint64) - 1, + GST_QUARK (DROPPED), G_TYPE_UINT64, (guint64) - 1, NULL); + message = gst_message_new_custom (GST_MESSAGE_QOS, src, structure); + + return message; +} + +/** + * gst_message_set_qos_values: + * @message: A valid #GstMessage of type GST_MESSAGE_QOS. + * @jitter: The difference of the running-time against the deadline. + * @proportion: Long term prediction of the ideal rate relative to normal rate + * to get optimal quality. + * @quality: An element dependent integer value that specifies the current + * quality level of the element. The default maximum quality is 1000000. + * + * Set the QoS values that have been calculated/analysed from the QoS data + * + * MT safe. + * + * Since: 0.10.29 + */ +void +gst_message_set_qos_values (GstMessage * message, gint64 jitter, + gdouble proportion, gint quality) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_QOS); + + structure = GST_MESSAGE_STRUCTURE (message); + gst_structure_id_set (structure, + GST_QUARK (JITTER), G_TYPE_INT64, jitter, + GST_QUARK (PROPORTION), G_TYPE_DOUBLE, proportion, + GST_QUARK (QUALITY), G_TYPE_INT, quality, NULL); +} + +/** + * gst_message_set_qos_stats: + * @message: A valid #GstMessage of type GST_MESSAGE_QOS. + * @format: Units of the 'processed' and 'dropped' fields. Video sinks and video + * filters will use GST_FORMAT_BUFFERS (frames). Audio sinks and audio filters + * will likely use GST_FORMAT_DEFAULT (samples). + * @processed: Total number of units correctly processed since the last state + * change to READY or a flushing operation. + * @dropped: Total number of units dropped since the last state change to READY + * or a flushing operation. + * + * Set the QoS stats representing the history of the current continuous pipeline + * playback period. + * + * When @format is @GST_FORMAT_UNDEFINED both @dropped and @processed are + * invalid. Values of -1 for either @processed or @dropped mean unknown values. + * + * MT safe. + * + * Since: 0.10.29 + */ +void +gst_message_set_qos_stats (GstMessage * message, GstFormat format, + guint64 processed, guint64 dropped) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_QOS); + + structure = GST_MESSAGE_STRUCTURE (message); + gst_structure_id_set (structure, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (PROCESSED), G_TYPE_UINT64, processed, + GST_QUARK (DROPPED), G_TYPE_UINT64, dropped, NULL); +} + +/** + * gst_message_parse_qos: + * @message: A valid #GstMessage of type GST_MESSAGE_QOS. + * @live: (out) (allow-none): if the message was generated by a live element + * @running_time: (out) (allow-none): the running time of the buffer that + * generated the message + * @stream_time: (out) (allow-none): the stream time of the buffer that + * generated the message + * @timestamp: (out) (allow-none): the timestamps of the buffer that + * generated the message + * @duration: (out) (allow-none): the duration of the buffer that + * generated the message + * + * Extract the timestamps and live status from the QoS message. + * + * The returned values give the running_time, stream_time, timestamp and + * duration of the dropped buffer. Values of GST_CLOCK_TIME_NONE mean unknown + * values. + * + * MT safe. + * + * Since: 0.10.29 + */ +void +gst_message_parse_qos (GstMessage * message, gboolean * live, + guint64 * running_time, guint64 * stream_time, guint64 * timestamp, + guint64 * duration) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_QOS); + + structure = GST_MESSAGE_STRUCTURE (message); + gst_structure_id_get (structure, + GST_QUARK (LIVE), G_TYPE_BOOLEAN, live, + GST_QUARK (RUNNING_TIME), G_TYPE_UINT64, running_time, + GST_QUARK (STREAM_TIME), G_TYPE_UINT64, stream_time, + GST_QUARK (TIMESTAMP), G_TYPE_UINT64, timestamp, + GST_QUARK (DURATION), G_TYPE_UINT64, duration, NULL); +} + +/** + * gst_message_parse_qos_values: + * @message: A valid #GstMessage of type GST_MESSAGE_QOS. + * @jitter: (out) (allow-none): The difference of the running-time against + * the deadline. + * @proportion: (out) (allow-none): Long term prediction of the ideal rate + * relative to normal rate to get optimal quality. + * @quality: (out) (allow-none): An element dependent integer value that + * specifies the current quality level of the element. The default + * maximum quality is 1000000. + * + * Extract the QoS values that have been calculated/analysed from the QoS data + * + * MT safe. + * + * Since: 0.10.29 + */ +void +gst_message_parse_qos_values (GstMessage * message, gint64 * jitter, + gdouble * proportion, gint * quality) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_QOS); + + structure = GST_MESSAGE_STRUCTURE (message); + gst_structure_id_get (structure, + GST_QUARK (JITTER), G_TYPE_INT64, jitter, + GST_QUARK (PROPORTION), G_TYPE_DOUBLE, proportion, + GST_QUARK (QUALITY), G_TYPE_INT, quality, NULL); +} + +/** + * gst_message_parse_qos_stats: + * @message: A valid #GstMessage of type GST_MESSAGE_QOS. + * @format: (out) (allow-none): Units of the 'processed' and 'dropped' fields. + * Video sinks and video filters will use GST_FORMAT_BUFFERS (frames). + * Audio sinks and audio filters will likely use GST_FORMAT_DEFAULT + * (samples). + * @processed: (out) (allow-none): Total number of units correctly processed + * since the last state change to READY or a flushing operation. + * @dropped: (out) (allow-none): Total number of units dropped since the last + * state change to READY or a flushing operation. + * + * Extract the QoS stats representing the history of the current continuous + * pipeline playback period. + * + * When @format is @GST_FORMAT_UNDEFINED both @dropped and @processed are + * invalid. Values of -1 for either @processed or @dropped mean unknown values. + * + * MT safe. + * + * Since: 0.10.29 + */ +void +gst_message_parse_qos_stats (GstMessage * message, GstFormat * format, + guint64 * processed, guint64 * dropped) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_QOS); + + structure = GST_MESSAGE_STRUCTURE (message); + gst_structure_id_get (structure, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (PROCESSED), G_TYPE_UINT64, processed, + GST_QUARK (DROPPED), G_TYPE_UINT64, dropped, NULL); +} + +/** + * gst_message_new_progress: + * @src: The object originating the message. + * @type: a #GstProgressType + * @code: a progress code + * @text: free, user visible text describing the progress + * + * Progress messages are posted by elements when they use an asynchronous task + * to perform actions triggered by a state change. + * + * @code contains a well defined string describing the action. + * @test should contain a user visible string detailing the current action. + * + * Returns: (transfer full): The new qos message. + * + * Since: 0.10.33 + */ +GstMessage * +gst_message_new_progress (GstObject * src, GstProgressType type, + const gchar * code, const gchar * text) +{ + GstMessage *message; + GstStructure *structure; + gint percent = 100, timeout = -1; + + g_return_val_if_fail (code != NULL, NULL); + g_return_val_if_fail (text != NULL, NULL); + + if (type == GST_PROGRESS_TYPE_START || type == GST_PROGRESS_TYPE_CONTINUE) + percent = 0; + + structure = gst_structure_id_new (GST_QUARK (MESSAGE_PROGRESS), + GST_QUARK (TYPE), GST_TYPE_PROGRESS_TYPE, type, + GST_QUARK (CODE), G_TYPE_STRING, code, + GST_QUARK (TEXT), G_TYPE_STRING, text, + GST_QUARK (PERCENT), G_TYPE_INT, percent, + GST_QUARK (TIMEOUT), G_TYPE_INT, timeout, NULL); + message = gst_message_new_custom (GST_MESSAGE_PROGRESS, src, structure); + + return message; +} + +/** + * gst_message_parse_progress: + * @message: A valid #GstMessage of type GST_MESSAGE_PROGRESS. + * @type: (out) (allow-none): location for the type + * @code: (out) (allow-none) (transfer full): location for the code + * @text: (out) (allow-none) (transfer full): location for the text + * + * Parses the progress @type, @code and @text. + * + * Since: 0.10.33 + */ +void +gst_message_parse_progress (GstMessage * message, GstProgressType * type, + gchar ** code, gchar ** text) +{ + GstStructure *structure; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_PROGRESS); + + structure = GST_MESSAGE_STRUCTURE (message); + gst_structure_id_get (structure, + GST_QUARK (TYPE), GST_TYPE_PROGRESS_TYPE, type, + GST_QUARK (CODE), G_TYPE_STRING, code, + GST_QUARK (TEXT), G_TYPE_STRING, text, NULL); +} diff --git a/gst/gstmessage.h b/gst/gstmessage.h new file mode 100644 index 0000000..050be67 --- /dev/null +++ b/gst/gstmessage.h @@ -0,0 +1,549 @@ +/* GStreamer + * Copyright (C) 2004 Wim Taymans <wim@fluendo.com> + * + * gstmessage.h: Header for GstMessage subsystem + * + * 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_MESSAGE_H__ +#define __GST_MESSAGE_H__ + +G_BEGIN_DECLS + +typedef struct _GstMessage GstMessage; + +/** + * GstMessageType: + * @GST_MESSAGE_UNKNOWN: an undefined message + * @GST_MESSAGE_EOS: end-of-stream reached in a pipeline. The application will + * only receive this message in the PLAYING state and every time it sets a + * pipeline to PLAYING that is in the EOS state. The application can perform a + * flushing seek in the pipeline, which will undo the EOS state again. + * @GST_MESSAGE_ERROR: an error occured. When the application receives an error + * message it should stop playback of the pipeline and not assume that more + * data will be played. + * @GST_MESSAGE_WARNING: a warning occured. + * @GST_MESSAGE_INFO: an info message occured + * @GST_MESSAGE_TAG: a tag was found. + * @GST_MESSAGE_BUFFERING: the pipeline is buffering. When the application + * receives a buffering message in the PLAYING state for a non-live pipeline it + * must PAUSE the pipeline until the buffering completes, when the percentage + * field in the message is 100%. For live pipelines, no action must be + * performed and the buffering percentage can be used to inform the user about + * the progress. + * @GST_MESSAGE_STATE_CHANGED: a state change happened + * @GST_MESSAGE_STATE_DIRTY: an element changed state in a streaming thread. + * This message is deprecated. + * @GST_MESSAGE_STEP_DONE: a stepping operation finished. + * @GST_MESSAGE_CLOCK_PROVIDE: an element notifies its capability of providing + * a clock. This message is used internally and + * never forwarded to the application. + * @GST_MESSAGE_CLOCK_LOST: The current clock as selected by the pipeline became + * unusable. The pipeline will select a new clock on + * the next PLAYING state change. The application + * should set the pipeline to PAUSED and back to + * PLAYING when this message is received. + * @GST_MESSAGE_NEW_CLOCK: a new clock was selected in the pipeline. + * @GST_MESSAGE_STRUCTURE_CHANGE: the structure of the pipeline changed. This + * message is used internally and never forwarded to the application. + * @GST_MESSAGE_STREAM_STATUS: status about a stream, emitted when it starts, + * stops, errors, etc.. + * @GST_MESSAGE_APPLICATION: message posted by the application, possibly + * via an application-specific element. + * @GST_MESSAGE_ELEMENT: element-specific message, see the specific element's + * documentation + * @GST_MESSAGE_SEGMENT_START: pipeline started playback of a segment. This + * message is used internally and never forwarded to the application. + * @GST_MESSAGE_SEGMENT_DONE: pipeline completed playback of a segment. This + * message is forwarded to the application after all elements that posted + * @GST_MESSAGE_SEGMENT_START posted a GST_MESSAGE_SEGMENT_DONE message. + * @GST_MESSAGE_DURATION: The duration of a pipeline changed. The application + * can get the new duration with a duration query. + * @GST_MESSAGE_ASYNC_START: Posted by elements when they start an ASYNC + * #GstStateChange. This message is not forwarded to the application but is used + * internally. Since: 0.10.13. + * @GST_MESSAGE_ASYNC_DONE: Posted by elements when they complete an ASYNC + * #GstStateChange. The application will only receive this message from the toplevel + * pipeline. Since: 0.10.13 + * @GST_MESSAGE_LATENCY: Posted by elements when their latency changes. The + * application should recalculate and distribute a new latency. Since: 0.10.12 + * @GST_MESSAGE_REQUEST_STATE: Posted by elements when they want the pipeline to + * change state. This message is a suggestion to the application which can + * decide to perform the state change on (part of) the pipeline. Since: 0.10.23. + * @GST_MESSAGE_STEP_START: A stepping operation was started. Since: 0.10.24 + * @GST_MESSAGE_QOS: A buffer was dropped or an element changed its processing + * strategy for Quality of Service reasons. Since: 0.10.29 + * @GST_MESSAGE_PROGRESS: A progress message. Since: 0.10.33 + * @GST_MESSAGE_ANY: mask for all of the above messages. + * + * The different message types that are available. + */ +/* NOTE: keep in sync with quark registration in gstmessage.c + * NOTE: keep GST_MESSAGE_ANY a valid gint to avoid compiler warnings. + */ +typedef enum +{ + GST_MESSAGE_UNKNOWN = 0, + GST_MESSAGE_EOS = (1 << 0), + GST_MESSAGE_ERROR = (1 << 1), + GST_MESSAGE_WARNING = (1 << 2), + GST_MESSAGE_INFO = (1 << 3), + GST_MESSAGE_TAG = (1 << 4), + GST_MESSAGE_BUFFERING = (1 << 5), + GST_MESSAGE_STATE_CHANGED = (1 << 6), + GST_MESSAGE_STATE_DIRTY = (1 << 7), + GST_MESSAGE_STEP_DONE = (1 << 8), + GST_MESSAGE_CLOCK_PROVIDE = (1 << 9), + GST_MESSAGE_CLOCK_LOST = (1 << 10), + GST_MESSAGE_NEW_CLOCK = (1 << 11), + GST_MESSAGE_STRUCTURE_CHANGE = (1 << 12), + GST_MESSAGE_STREAM_STATUS = (1 << 13), + GST_MESSAGE_APPLICATION = (1 << 14), + GST_MESSAGE_ELEMENT = (1 << 15), + GST_MESSAGE_SEGMENT_START = (1 << 16), + GST_MESSAGE_SEGMENT_DONE = (1 << 17), + GST_MESSAGE_DURATION = (1 << 18), + GST_MESSAGE_LATENCY = (1 << 19), + GST_MESSAGE_ASYNC_START = (1 << 20), + GST_MESSAGE_ASYNC_DONE = (1 << 21), + GST_MESSAGE_REQUEST_STATE = (1 << 22), + GST_MESSAGE_STEP_START = (1 << 23), + GST_MESSAGE_QOS = (1 << 24), + GST_MESSAGE_PROGRESS = (1 << 25), + GST_MESSAGE_ANY = ~0 +} GstMessageType; + +#include <gst/gstminiobject.h> +#include <gst/gstobject.h> +#include <gst/gstelement.h> +#include <gst/gsttaglist.h> +#include <gst/gststructure.h> +#include <gst/gstquery.h> + +/** + * GST_MESSAGE_TRACE_NAME: + * + * The name used for memory allocation tracing + */ +#define GST_MESSAGE_TRACE_NAME "GstMessage" + +#define GST_TYPE_MESSAGE (gst_message_get_type()) +#define GST_IS_MESSAGE(obj) (GST_IS_MINI_OBJECT_TYPE (obj, GST_TYPE_MESSAGE)) +#define GST_MESSAGE_CAST(obj) ((GstMessage*)(obj)) +#define GST_MESSAGE(obj) (GST_MESSAGE_CAST(obj)) + +/* the lock is used to handle the synchronous handling of messages, + * the emiting thread is block until the handling thread processed + * the message using this mutex/cond pair */ +#define GST_MESSAGE_GET_LOCK(message) (GST_MESSAGE_CAST(message)->lock) +#define GST_MESSAGE_LOCK(message) g_mutex_lock(GST_MESSAGE_GET_LOCK(message)) +#define GST_MESSAGE_UNLOCK(message) g_mutex_unlock(GST_MESSAGE_GET_LOCK(message)) +#define GST_MESSAGE_COND(message) (GST_MESSAGE_CAST(message)->cond) +#define GST_MESSAGE_WAIT(message) g_cond_wait(GST_MESSAGE_COND(message),GST_MESSAGE_GET_LOCK(message)) +#define GST_MESSAGE_SIGNAL(message) g_cond_signal(GST_MESSAGE_COND(message)) + +/** + * GST_MESSAGE_TYPE: + * @message: a #GstMessage + * + * Get the #GstMessageType of @message. + */ +#define GST_MESSAGE_TYPE(message) (GST_MESSAGE_CAST(message)->type) +/** + * GST_MESSAGE_TYPE_NAME: + * @message: a #GstMessage + * + * Get a constant string representation of the #GstMessageType of @message. + * + * Since: 0.10.4 + */ +#define GST_MESSAGE_TYPE_NAME(message) gst_message_type_get_name(GST_MESSAGE_TYPE(message)) +/** + * GST_MESSAGE_TIMESTAMP: + * @message: a #GstMessage + * + * Get the timestamp of @message. This is the timestamp when the message + * was created. + */ +#define GST_MESSAGE_TIMESTAMP(message) (GST_MESSAGE_CAST(message)->timestamp) +/** + * GST_MESSAGE_SRC: + * @message: a #GstMessage + * + * Get the object that posted @message. + */ +#define GST_MESSAGE_SRC(message) (GST_MESSAGE_CAST(message)->src) + +/** + * GST_MESSAGE_SEQNUM: + * @message: a #GstMessage + * + * Get the sequence number of @message. + */ +#define GST_MESSAGE_SEQNUM(message) (GST_MESSAGE_CAST(message)->seqnum) + +/** + * GST_MESSAGE_SRC_NAME: + * @message: a #GstMessage + * + * Get the name of the object that posted @message. Returns "(NULL)" if + * the message has no source object set. + * + * Since: 0.10.24 + */ +#define GST_MESSAGE_SRC_NAME(message) (GST_MESSAGE_SRC(message) ? \ + GST_OBJECT_NAME (GST_MESSAGE_SRC(message)) : "(NULL)") + +/** + * GstStructureChangeType: + * @GST_STRUCTURE_CHANGE_TYPE_PAD_LINK: Pad linking is starting or done. + * @GST_STRUCTURE_CHANGE_TYPE_PAD_UNLINK: Pad unlinking is starting or done. + * + * The type of a %GST_MESSAGE_STRUCTURE_CHANGE. + * + * Since: 0.10.22 + */ +typedef enum { + GST_STRUCTURE_CHANGE_TYPE_PAD_LINK = 0, + GST_STRUCTURE_CHANGE_TYPE_PAD_UNLINK = 1 +} GstStructureChangeType; + +/** + * GstStreamStatusType: + * @GST_STREAM_STATUS_TYPE_CREATE: A new thread need to be created. + * @GST_STREAM_STATUS_TYPE_ENTER: a thread entered its loop function + * @GST_STREAM_STATUS_TYPE_LEAVE: a thread left its loop function + * @GST_STREAM_STATUS_TYPE_DESTROY: a thread is destroyed + * @GST_STREAM_STATUS_TYPE_START: a thread is started + * @GST_STREAM_STATUS_TYPE_PAUSE: a thread is paused + * @GST_STREAM_STATUS_TYPE_STOP: a thread is stopped + * + * The type of a %GST_MESSAGE_STREAM_STATUS. The stream status messages inform the + * application of new streaming threads and their status. + * + * Since: 0.10.24 + */ +typedef enum { + GST_STREAM_STATUS_TYPE_CREATE = 0, + GST_STREAM_STATUS_TYPE_ENTER = 1, + GST_STREAM_STATUS_TYPE_LEAVE = 2, + GST_STREAM_STATUS_TYPE_DESTROY = 3, + + GST_STREAM_STATUS_TYPE_START = 8, + GST_STREAM_STATUS_TYPE_PAUSE = 9, + GST_STREAM_STATUS_TYPE_STOP = 10 +} GstStreamStatusType; + +/** + * GstProgressType: + * @GST_PROGRESS_TYPE_START: A new task started. + * @GST_PROGRESS_TYPE_CONTINUE: A task completed and a new one continues. + * @GST_PROGRESS_TYPE_COMPLETE: A task completed. + * @GST_PROGRESS_TYPE_CANCELED: A task was canceled. + * @GST_PROGRESS_TYPE_ERROR: A task caused an error. An error message is also + * posted on the bus. + * + * The type of a %GST_MESSAGE_PROGRESS. The progress messages inform the + * application of the status of assynchronous tasks. + * + * Since: 0.10.33 + */ +typedef enum { + GST_PROGRESS_TYPE_START = 0, + GST_PROGRESS_TYPE_CONTINUE = 1, + GST_PROGRESS_TYPE_COMPLETE = 2, + GST_PROGRESS_TYPE_CANCELED = 3, + GST_PROGRESS_TYPE_ERROR = 4, +} GstProgressType; + +/** + * GstMessage: + * @mini_object: the parent structure + * @type: the #GstMessageType of the message + * @timestamp: the timestamp of the message + * @src: the src of the message + * @seqnum: the sequence number of the message + * + * A #GstMessage. + */ +struct _GstMessage +{ + GstMiniObject mini_object; + + /*< public > *//* with COW */ + GstMessageType type; + guint64 timestamp; + GstObject *src; + guint32 seqnum; + + /*< private >*//* with MESSAGE_LOCK */ + GMutex *lock; /* lock and cond for async delivery */ + GCond *cond; +}; + +GType gst_message_get_type (void); + +const gchar* gst_message_type_get_name (GstMessageType type); +GQuark gst_message_type_to_quark (GstMessageType type); + +/* refcounting */ +/** + * gst_message_ref: + * @msg: the message to ref + * + * Convenience macro to increase the reference count of the message. + * + * Returns: @msg (for convenience when doing assignments) + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstMessage * gst_message_ref (GstMessage * msg); +#endif + +static inline GstMessage * +gst_message_ref (GstMessage * msg) +{ + return (GstMessage *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (msg)); +} + +/** + * gst_message_unref: + * @msg: the message to unref + * + * Convenience macro to decrease the reference count of the message, possibly + * freeing it. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void gst_message_unref (GstMessage * msg); +#endif + +static inline void +gst_message_unref (GstMessage * msg) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (msg)); +} + +/* copy message */ +/** + * gst_message_copy: + * @msg: the message to copy + * + * Creates a copy of the message. Returns a copy of the message. + * + * Returns: (transfer full): a new copy of @msg. + * + * MT safe + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstMessage * gst_message_copy (const GstMessage * msg); +#endif + +static inline GstMessage * +gst_message_copy (const GstMessage * msg) +{ + return GST_MESSAGE_CAST (gst_mini_object_copy (GST_MINI_OBJECT_CONST_CAST (msg))); +} + +/** + * gst_message_is_writable: + * @msg: a #GstMessage + * + * Tests if you can safely write into a message's structure or validly + * modify the seqnum and timestamp fields. + */ +#define gst_message_is_writable(msg) gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (msg)) +/** + * gst_message_make_writable: + * @msg: (transfer full): the message to make writable + * + * Checks if a message is writable. If not, a writable copy is made and + * returned. + * + * Returns: (transfer full): a message (possibly a duplicate) that is writable. + * + * MT safe + */ +#define gst_message_make_writable(msg) GST_MESSAGE_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (msg))) +/** + * gst_message_replace: + * @old_message: (inout) (transfer full): pointer to a pointer to a #GstMessage + * to be replaced. + * @new_message: (allow-none) (transfer none): pointer to a #GstMessage that will + * replace the message pointed to by @old_message. + * + * Modifies a pointer to a #GstMessage to point to a different #GstMessage. The + * modification is done atomically (so this is useful for ensuring thread safety + * in some cases), and the reference counts are updated appropriately (the old + * message is unreffed, the new one is reffed). + * + * Either @new_message or the #GstMessage pointed to by @old_message may be NULL. + */ +#define gst_message_replace(old_message,new_message) \ + gst_mini_object_replace ((GstMiniObject **)(old_message), GST_MINI_OBJECT_CAST (new_message)) + + +/* custom messages */ +GstMessage * gst_message_new_custom (GstMessageType type, + GstObject * src, + GstStructure * structure); +const GstStructure * + gst_message_get_structure (GstMessage *message); + +gboolean gst_message_has_name (GstMessage *message, const gchar *name); + +/* identifiers for events and messages */ +guint32 gst_message_get_seqnum (GstMessage *message); +void gst_message_set_seqnum (GstMessage *message, guint32 seqnum); + +/* EOS */ +GstMessage * gst_message_new_eos (GstObject * src); + +/* ERROR */ + +GstMessage * gst_message_new_error (GstObject * src, GError * error, const gchar * debug); +void gst_message_parse_error (GstMessage *message, GError **gerror, gchar **debug); + +/* WARNING */ +GstMessage * gst_message_new_warning (GstObject * src, GError * error, const gchar * debug); +void gst_message_parse_warning (GstMessage *message, GError **gerror, gchar **debug); + +/* INFO */ +GstMessage * gst_message_new_info (GstObject * src, GError * error, const gchar * debug); +void gst_message_parse_info (GstMessage *message, GError **gerror, gchar **debug); + +/* TAG */ +GstMessage * gst_message_new_tag (GstObject * src, GstTagList * tag_list); +void gst_message_parse_tag (GstMessage *message, GstTagList **tag_list); + +/* BUFFERING */ +GstMessage * gst_message_new_buffering (GstObject * src, gint percent); +void gst_message_parse_buffering (GstMessage *message, gint *percent); +void gst_message_set_buffering_stats (GstMessage *message, GstBufferingMode mode, + gint avg_in, gint avg_out, + gint64 buffering_left); +void gst_message_parse_buffering_stats (GstMessage *message, GstBufferingMode *mode, + gint *avg_in, gint *avg_out, + gint64 *buffering_left); + +/* STATE_CHANGED */ +GstMessage * gst_message_new_state_changed (GstObject * src, GstState oldstate, + GstState newstate, GstState pending); +void gst_message_parse_state_changed (GstMessage *message, GstState *oldstate, + GstState *newstate, GstState *pending); + +/* STATE_DIRTY */ +GstMessage * gst_message_new_state_dirty (GstObject * src); + +/* STEP_DONE */ +GstMessage * gst_message_new_step_done (GstObject * src, GstFormat format, guint64 amount, + gdouble rate, gboolean flush, gboolean intermediate, + guint64 duration, gboolean eos); +void gst_message_parse_step_done (GstMessage * message, GstFormat *format, guint64 *amount, + gdouble *rate, gboolean *flush, gboolean *intermediate, + guint64 *duration, gboolean *eos); +/* CLOCK_PROVIDE */ +GstMessage * gst_message_new_clock_provide (GstObject * src, GstClock *clock, gboolean ready); +void gst_message_parse_clock_provide (GstMessage *message, GstClock **clock, + gboolean *ready); + +/* CLOCK_LOST */ +GstMessage * gst_message_new_clock_lost (GstObject * src, GstClock *clock); +void gst_message_parse_clock_lost (GstMessage *message, GstClock **clock); + +/* NEW_CLOCK */ +GstMessage * gst_message_new_new_clock (GstObject * src, GstClock *clock); +void gst_message_parse_new_clock (GstMessage *message, GstClock **clock); + +/* APPLICATION */ +GstMessage * gst_message_new_application (GstObject * src, GstStructure * structure); + +/* ELEMENT */ +GstMessage * gst_message_new_element (GstObject * src, GstStructure * structure); + +/* SEGMENT_START */ +GstMessage * gst_message_new_segment_start (GstObject * src, GstFormat format, gint64 position); +void gst_message_parse_segment_start (GstMessage *message, GstFormat *format, + gint64 *position); + +/* SEGMENT_DONE */ +GstMessage * gst_message_new_segment_done (GstObject * src, GstFormat format, gint64 position); +void gst_message_parse_segment_done (GstMessage *message, GstFormat *format, + gint64 *position); + +/* DURATION */ +GstMessage * gst_message_new_duration (GstObject * src, GstFormat format, gint64 duration); +void gst_message_parse_duration (GstMessage *message, GstFormat *format, + gint64 *duration); + +/* LATENCY */ +GstMessage * gst_message_new_latency (GstObject * src); + +/* ASYNC_START */ +GstMessage * gst_message_new_async_start (GstObject * src); + +/* ASYNC_DONE */ +GstMessage * gst_message_new_async_done (GstObject * src, gboolean reset_time); +void gst_message_parse_async_done (GstMessage *message, gboolean *reset_time); + +/* STRUCTURE CHANGE */ +GstMessage * gst_message_new_structure_change (GstObject * src, GstStructureChangeType type, + GstElement *owner, gboolean busy); +void gst_message_parse_structure_change (GstMessage *message, GstStructureChangeType *type, + GstElement **owner, gboolean *busy); + +/* STREAM STATUS */ +GstMessage * gst_message_new_stream_status (GstObject * src, GstStreamStatusType type, + GstElement *owner); +void gst_message_parse_stream_status (GstMessage *message, GstStreamStatusType *type, + GstElement **owner); +void gst_message_set_stream_status_object (GstMessage *message, const GValue *object); +const GValue * gst_message_get_stream_status_object (GstMessage *message); + +/* REQUEST_STATE */ +GstMessage * gst_message_new_request_state (GstObject * src, GstState state); +void gst_message_parse_request_state (GstMessage * message, GstState *state); + +/* STEP_START */ +GstMessage * gst_message_new_step_start (GstObject * src, gboolean active, GstFormat format, + guint64 amount, gdouble rate, gboolean flush, + gboolean intermediate); +void gst_message_parse_step_start (GstMessage * message, gboolean *active, GstFormat *format, + guint64 *amount, gdouble *rate, gboolean *flush, + gboolean *intermediate); + +/* QOS */ +GstMessage * gst_message_new_qos (GstObject * src, gboolean live, guint64 running_time, + guint64 stream_time, guint64 timestamp, guint64 duration); +void gst_message_set_qos_values (GstMessage * message, gint64 jitter, gdouble proportion, + gint quality); +void gst_message_set_qos_stats (GstMessage * message, GstFormat format, guint64 processed, + guint64 dropped); +void gst_message_parse_qos (GstMessage * message, gboolean * live, guint64 * running_time, + guint64 * stream_time, guint64 * timestamp, guint64 * duration); +void gst_message_parse_qos_values (GstMessage * message, gint64 * jitter, gdouble * proportion, + gint * quality); +void gst_message_parse_qos_stats (GstMessage * message, GstFormat * format, guint64 * processed, + guint64 * dropped); +/* PROGRESS */ +GstMessage * gst_message_new_progress (GstObject * src, GstProgressType type, const gchar *code, + const gchar *text); +void gst_message_parse_progress (GstMessage * message, GstProgressType * type, gchar ** code, + gchar ** text); + + +G_END_DECLS + +#endif /* __GST_MESSAGE_H__ */ diff --git a/gst/gstmeta.c b/gst/gstmeta.c new file mode 100644 index 0000000..078aef1 --- /dev/null +++ b/gst/gstmeta.c @@ -0,0 +1,153 @@ +/* GStreamer + * Copyright (C) 2011 Wim Taymans <wim.taymans@gmail.com> + * + * gstmeta.c: metadata operations + * + * 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:gstmeta + * @short_description: Buffer metadata + * + * Last reviewed on December 17th, 2009 (0.10.26) + */ +#include "gst_private.h" + +#include "gstbuffer.h" +#include "gstmeta.h" +#include "gstinfo.h" +#include "gstutils.h" + +static GHashTable *metainfo = NULL; +static GStaticRWLock lock = G_STATIC_RW_LOCK_INIT; + +void +_priv_gst_meta_initialize (void) +{ + metainfo = g_hash_table_new (g_str_hash, g_str_equal); +} + +/** + * gst_meta_register_info: + * @info: a #GstMetaInfo + * + * Register a #GstMetaInfo. The same @info can be retrieved later with + * gst_meta_get_info() by using @impl as the key. + * + * Returns: a #GstMetaInfo that can be used to access metadata. + */ + +const GstMetaInfo * +gst_meta_register (const gchar * api, const gchar * impl, gsize size, + GstMetaInitFunction init_func, GstMetaFreeFunction free_func, + GstMetaCopyFunction copy_func, GstMetaTransformFunction transform_func) +{ + GstMetaInfo *info; + + g_return_val_if_fail (api != NULL, NULL); + g_return_val_if_fail (impl != NULL, NULL); + g_return_val_if_fail (size != 0, NULL); + + info = g_slice_new (GstMetaInfo); + info->api = g_quark_from_string (api); + info->type = g_pointer_type_register_static (impl); + info->size = size; + info->init_func = init_func; + info->free_func = free_func; + info->copy_func = copy_func; + info->transform_func = transform_func; + + GST_DEBUG ("register \"%s\" implementing \"%s\" of size %" G_GSIZE_FORMAT, + api, impl, size); + + g_static_rw_lock_writer_lock (&lock); + g_hash_table_insert (metainfo, (gpointer) impl, (gpointer) info); + g_static_rw_lock_writer_unlock (&lock); + + return info; +} + +/** + * gst_meta_get_info: + * @impl: the name + * + * Lookup a previously registered meta info structure by its implementor name + * @impl. + * + * Returns: a #GstMetaInfo with @impl or #NULL when no such metainfo + * exists. + */ +const GstMetaInfo * +gst_meta_get_info (const gchar * impl) +{ + GstMetaInfo *info; + + g_return_val_if_fail (impl != NULL, NULL); + + g_static_rw_lock_reader_lock (&lock); + info = g_hash_table_lookup (metainfo, impl); + g_static_rw_lock_reader_unlock (&lock); + + return info; +} + +/* Timing metadata */ +static void +meta_timing_copy (GstBuffer * copybuf, GstMetaTiming * meta, + GstBuffer * buffer, gsize offset, gsize size) +{ + GstMetaTiming *timing; + + GST_DEBUG ("trans called from buffer %p to %p, meta %p," + "offset %" G_GSIZE_FORMAT ", size %" G_GSIZE_FORMAT, buffer, + copybuf, meta, offset, size); + + timing = gst_buffer_add_meta_timing (copybuf); + if (offset == 0) { + /* same offset, copy timestamps */ + timing->pts = meta->pts; + timing->dts = meta->dts; + if (size == gst_buffer_get_size (buffer)) { + /* same size, copy duration */ + timing->duration = meta->duration; + } else { + /* else clear */ + timing->duration = GST_CLOCK_TIME_NONE; + } + } else { + timing->pts = -1; + timing->dts = -1; + timing->duration = -1; + } + timing->clock_rate = meta->clock_rate; +} + +const GstMetaInfo * +gst_meta_timing_get_info (void) +{ + static const GstMetaInfo *meta_info = NULL; + + if (meta_info == NULL) { + meta_info = gst_meta_register ("GstMetaTiming", "GstMetaTiming", + sizeof (GstMetaTiming), + (GstMetaInitFunction) NULL, + (GstMetaFreeFunction) NULL, + (GstMetaCopyFunction) meta_timing_copy, + (GstMetaTransformFunction) NULL); + } + return meta_info; +} diff --git a/gst/gstmeta.h b/gst/gstmeta.h new file mode 100644 index 0000000..90d7fc2 --- /dev/null +++ b/gst/gstmeta.h @@ -0,0 +1,151 @@ +/* GStreamer + * Copyright (C) 2009 Wim Taymans <wim.taymans@gmail.be> + * + * gstmeta.h: Header for Metadata structures + * + * 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_META_H__ +#define __GST_META_H__ + +G_BEGIN_DECLS + +typedef struct _GstMeta GstMeta; +typedef struct _GstMetaInfo GstMetaInfo; + +/** + * GstMeta: + * @info: pointer to the #GstMetaInfo + * + * Base structure for metadata. Custom metadata will put this structure + * as the first member of their structure. + */ +struct _GstMeta { + const GstMetaInfo *info; +}; + +/** + * GST_META_TRACE_NAME: + * + * The name used for tracing memory allocations. + */ +#define GST_META_TRACE_NAME "GstMeta" + +/** + * GstMetaInitFunction: + * @meta: a #GstMeta + * @params: parameters passed to the init function + * @buffer: a #GstBuffer + * + * Function called when @meta is initialized in @buffer. + */ +typedef gboolean (*GstMetaInitFunction) (GstMeta *meta, gpointer params, GstBuffer *buffer); + +/** + * GstMetaFreeFunction: + * @meta: a #GstMeta + * @buffer: a #GstBuffer + * + * Function called when @meta is freed in @buffer. + */ +typedef void (*GstMetaFreeFunction) (GstMeta *meta, GstBuffer *buffer); + +typedef void (*GstMetaCopyFunction) (GstBuffer *dest, GstMeta *meta, + GstBuffer *buffer, gsize offset, gsize size); +/** + * GstMetaTransformFunction: + * @transbuf: a #GstBuffer + * @meta: a #GstMeta + * @buffer: a #GstBuffer + * @data: transform specific data. + * + * Function called for each @meta in @buffer as a result of performing a + * transformation on @transbuf. Additional type specific transform data + * is passed to the function. + * + * Implementations should check the type of the transform @data and parse + * additional type specific field that should be used to perform the transform. + */ +typedef void (*GstMetaTransformFunction) (GstBuffer *transbuf, GstMeta *meta, + GstBuffer *buffer, gpointer data); + +/** + * GstMetaInfo: + * @api: tag indentifying the metadata structure and api + * @type: type indentifying the implementor of the api + * @size: size of the metadata + * @init_func: function for initializing the metadata + * @free_func: function for freeing the metadata + * @copy_func: function for copying the metadata + * @transform_func: function for transforming the metadata + * + * The #GstMetaInfo provides information about a specific metadata + * structure. + */ +struct _GstMetaInfo { + GQuark api; + GType type; + gsize size; + + GstMetaInitFunction init_func; + GstMetaFreeFunction free_func; + GstMetaCopyFunction copy_func; + GstMetaTransformFunction transform_func; +}; + +const GstMetaInfo * gst_meta_register (const gchar *api, const gchar *impl, + gsize size, + GstMetaInitFunction init_func, + GstMetaFreeFunction free_func, + GstMetaCopyFunction copy_func, + GstMetaTransformFunction transform_func); +const GstMetaInfo * gst_meta_get_info (const gchar * impl); + +/* default metadata */ + +/* timing metadata */ +typedef struct _GstMetaTiming GstMetaTiming; + +const GstMetaInfo *gst_meta_timing_get_info(void); +#define GST_META_TIMING_INFO (gst_meta_timing_get_info()) + +/** + * GstMetaTiming: + * @meta: parent metadata + * @dts: the decoding timestamp + * @pts: the presentation timestamp + * @duration: the duration + * @clock_rate: the clock rate of the dts, pts and duration values + * + * Extra timing metadata + */ +struct _GstMetaTiming { + GstMeta meta; /* common meta header */ + + GstClockTime dts; /* decoding timestamp */ + GstClockTime pts; /* presentation timestamp */ + GstClockTime duration; /* duration of the data */ + GstClockTime clock_rate; /* clock rate for the above values */ +}; + +#define gst_buffer_get_meta_timing(b) ((GstMetaTiming*)gst_buffer_get_meta((b),GST_META_TIMING_INFO)) +#define gst_buffer_add_meta_timing(b) ((GstMetaTiming*)gst_buffer_add_meta((b),GST_META_TIMING_INFO,NULL)) + +G_END_DECLS + +#endif /* __GST_META_H__ */ diff --git a/gst/gstminiobject.c b/gst/gstminiobject.c new file mode 100644 index 0000000..78749dd --- /dev/null +++ b/gst/gstminiobject.c @@ -0,0 +1,443 @@ +/* GStreamer + * Copyright (C) 2005 David Schleef <ds@schleef.org> + * + * gstminiobject.h: Header for GstMiniObject + * + * 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:gstminiobject + * @short_description: Lightweight base class for the GStreamer object hierarchy + * + * #GstMiniObject is a baseclass like #GObject, but has been stripped down of + * features to be fast and small. + * It offers sub-classing and ref-counting in the same way as #GObject does. + * It has no properties and no signal-support though. + * + * Last reviewed on 2005-11-23 (0.9.5) + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gst/gst_private.h" +#include "gst/gstminiobject.h" +#include "gst/gstinfo.h" +#include <gobject/gvaluecollector.h> + +#define GST_DISABLE_TRACE + +#ifndef GST_DISABLE_TRACE +#include "gsttrace.h" +static GstAllocTrace *_gst_mini_object_trace; +#endif + +/* Mutex used for weak referencing */ +G_LOCK_DEFINE_STATIC (weak_refs_mutex); + +/** + * gst_mini_object_init: + * @mini_object: a #GstMiniObject + * @type: the #GType of the mini-object to create + * @size: the size of the data + * + * Initializes a mini-object with the desired type and size. + * + * MT safe + * + * Returns: (transfer full): the new mini-object. + */ +void +gst_mini_object_init (GstMiniObject * mini_object, GType type, gsize size) +{ + mini_object->type = type; + mini_object->refcount = 1; + mini_object->flags = 0; + mini_object->size = size; + mini_object->n_weak_refs = 0; + mini_object->weak_refs = NULL; +} + +/** + * gst_mini_object_copy: + * @mini_object: the mini-object to copy + * + * Creates a copy of the mini-object. + * + * MT safe + * + * Returns: (transfer full): the new mini-object. + */ +GstMiniObject * +gst_mini_object_copy (const GstMiniObject * mini_object) +{ + GstMiniObject *copy; + + g_return_val_if_fail (mini_object != NULL, NULL); + + if (mini_object->copy) + copy = mini_object->copy (mini_object); + else + copy = NULL; + + return copy; +} + +/** + * gst_mini_object_is_writable: + * @mini_object: the mini-object to check + * + * Checks if a mini-object is writable. A mini-object is writable + * if the reference count is one and the #GST_MINI_OBJECT_FLAG_READONLY + * flag is not set. Modification of a mini-object should only be + * done after verifying that it is writable. + * + * MT safe + * + * Returns: TRUE if the object is writable. + */ +gboolean +gst_mini_object_is_writable (const GstMiniObject * mini_object) +{ + g_return_val_if_fail (mini_object != NULL, FALSE); + + return (GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object) == 1); +} + +/** + * gst_mini_object_make_writable: + * @mini_object: (transfer full): the mini-object to make writable + * + * Checks if a mini-object is writable. If not, a writable copy is made and + * returned. This gives away the reference to the original mini object, + * and returns a reference to the new object. + * + * MT safe + * + * Returns: (transfer full): a mini-object (possibly the same pointer) that + * is writable. + */ +GstMiniObject * +gst_mini_object_make_writable (GstMiniObject * mini_object) +{ + GstMiniObject *ret; + + g_return_val_if_fail (mini_object != NULL, NULL); + + if (gst_mini_object_is_writable (mini_object)) { + ret = mini_object; + } else { + ret = gst_mini_object_copy (mini_object); + GST_CAT_DEBUG (GST_CAT_PERFORMANCE, "copy %s miniobject %p -> %p", + g_type_name (GST_MINI_OBJECT_TYPE (mini_object)), mini_object, ret); + gst_mini_object_unref (mini_object); + } + + return ret; +} + +/** + * gst_mini_object_ref: + * @mini_object: the mini-object + * + * Increase the reference count of the mini-object. + * + * Note that the refcount affects the writeability + * of @mini-object, see gst_mini_object_is_writable(). It is + * important to note that keeping additional references to + * GstMiniObject instances can potentially increase the number + * of memcpy operations in a pipeline, especially if the miniobject + * is a #GstBuffer. + * + * Returns: (transfer full): the mini-object. + */ +GstMiniObject * +gst_mini_object_ref (GstMiniObject * mini_object) +{ + g_return_val_if_fail (mini_object != NULL, NULL); + /* we can't assert that the refcount > 0 since the _free functions + * increments the refcount from 0 to 1 again to allow resurecting + * the object + g_return_val_if_fail (mini_object->refcount > 0, NULL); + */ + + GST_CAT_TRACE (GST_CAT_REFCOUNTING, "%p ref %d->%d", mini_object, + GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object), + GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object) + 1); + + g_atomic_int_inc (&mini_object->refcount); + + return mini_object; +} + +static void +weak_refs_notify (GstMiniObject * obj) +{ + guint i; + + for (i = 0; i < obj->n_weak_refs; i++) + obj->weak_refs[i].notify (obj->weak_refs[i].data, obj); + g_free (obj->weak_refs); +} + +/** + * gst_mini_object_unref: + * @mini_object: the mini-object + * + * Decreases the reference count of the mini-object, possibly freeing + * the mini-object. + */ +void +gst_mini_object_unref (GstMiniObject * mini_object) +{ + g_return_if_fail (mini_object != NULL); + g_return_if_fail (mini_object->refcount > 0); + + GST_CAT_TRACE (GST_CAT_REFCOUNTING, "%p unref %d->%d", + mini_object, + GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object), + GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object) - 1); + + if (G_UNLIKELY (g_atomic_int_dec_and_test (&mini_object->refcount))) { + gboolean do_free; + + if (mini_object->dispose) + do_free = mini_object->dispose (mini_object); + else + do_free = TRUE; + + /* if the subclass recycled the object (and returned FALSE) we don't + * want to free the instance anymore */ + if (G_LIKELY (do_free)) { + /* The weak reference stack is freed in the notification function */ + if (mini_object->n_weak_refs) + weak_refs_notify (mini_object); + +#ifndef GST_DISABLE_TRACE + gst_alloc_trace_free (_gst_mini_object_trace, mini_object); +#endif + if (mini_object->free) + mini_object->free (mini_object); + } + } +} + +/** + * gst_mini_object_replace: + * @olddata: (inout) (transfer full): pointer to a pointer to a mini-object to + * be replaced + * @newdata: pointer to new mini-object + * + * Atomically modifies a pointer to point to a new mini-object. + * The reference count of @olddata is decreased and the reference count of + * @newdata is increased. + * + * Either @newdata and the value pointed to by @olddata may be NULL. + * + * Returns: TRUE if @newdata was different from @olddata + */ +gboolean +gst_mini_object_replace (GstMiniObject ** olddata, GstMiniObject * newdata) +{ + GstMiniObject *olddata_val; + + g_return_val_if_fail (olddata != NULL, FALSE); + + GST_CAT_TRACE (GST_CAT_REFCOUNTING, "replace %p (%d) with %p (%d)", + *olddata, *olddata ? (*olddata)->refcount : 0, + newdata, newdata ? newdata->refcount : 0); + + olddata_val = g_atomic_pointer_get ((gpointer *) olddata); + + if (G_UNLIKELY (olddata_val == newdata)) + return FALSE; + + if (newdata) + gst_mini_object_ref (newdata); + + while (G_UNLIKELY (!g_atomic_pointer_compare_and_exchange ((gpointer *) + olddata, olddata_val, newdata))) { + olddata_val = g_atomic_pointer_get ((gpointer *) olddata); + if (G_UNLIKELY (olddata_val == newdata)) + break; + } + + if (olddata_val) + gst_mini_object_unref (olddata_val); + + return olddata_val != newdata; +} + +/** + * gst_mini_object_steal: + * @olddata: (inout) (transfer full): pointer to a pointer to a mini-object to + * be stolen + * + * Replace the current #GstMiniObject pointer to by @olddata with NULL and + * return the old value. + * + * Returns: the #GstMiniObject at @oldata + */ +GstMiniObject * +gst_mini_object_steal (GstMiniObject ** olddata) +{ + GstMiniObject *olddata_val; + + g_return_val_if_fail (olddata != NULL, NULL); + + GST_CAT_TRACE (GST_CAT_REFCOUNTING, "steal %p (%d)", + *olddata, *olddata ? (*olddata)->refcount : 0); + + do { + olddata_val = g_atomic_pointer_get ((gpointer *) olddata); + if (olddata_val == NULL) + break; + } while (G_UNLIKELY (!g_atomic_pointer_compare_and_exchange ((gpointer *) + olddata, olddata_val, NULL))); + + return olddata_val; +} + +/** + * gst_mini_object_take: + * @olddata: (inout) (transfer full): pointer to a pointer to a mini-object to + * be replaced + * @newdata: pointer to new mini-object + * + * Modifies a pointer to point to a new mini-object. The modification + * is done atomically. This version is similar to gst_mini_object_replace() + * except that it does not increase the refcount of @newdata and thus + * takes ownership of @newdata. + * + * Either @newdata and the value pointed to by @olddata may be NULL. + * + * Returns: TRUE if @newdata was different from @olddata + */ +gboolean +gst_mini_object_take (GstMiniObject ** olddata, GstMiniObject * newdata) +{ + GstMiniObject *olddata_val; + + g_return_val_if_fail (olddata != NULL, FALSE); + + GST_CAT_TRACE (GST_CAT_REFCOUNTING, "take %p (%d) with %p (%d)", + *olddata, *olddata ? (*olddata)->refcount : 0, + newdata, newdata ? newdata->refcount : 0); + + do { + olddata_val = g_atomic_pointer_get ((gpointer *) olddata); + if (G_UNLIKELY (olddata_val == newdata)) + break; + } while (G_UNLIKELY (!g_atomic_pointer_compare_and_exchange ((gpointer *) + olddata, olddata_val, newdata))); + + if (olddata_val) + gst_mini_object_unref (olddata_val); + + return olddata_val != newdata; +} + +/** + * gst_mini_object_weak_ref: (skip) + * @object: #GstMiniObject to reference weakly + * @notify: callback to invoke before the mini object is freed + * @data: extra data to pass to notify + * + * Adds a weak reference callback to a mini object. Weak references are + * used for notification when a mini object is finalized. They are called + * "weak references" because they allow you to safely hold a pointer + * to the mini object without calling gst_mini_object_ref() + * (gst_mini_object_ref() adds a strong reference, that is, forces the object + * to stay alive). + * + * Since: 0.10.35 + */ +void +gst_mini_object_weak_ref (GstMiniObject * object, + GstMiniObjectWeakNotify notify, gpointer data) +{ + guint i; + + g_return_if_fail (object != NULL); + g_return_if_fail (notify != NULL); + g_return_if_fail (GST_MINI_OBJECT_REFCOUNT_VALUE (object) >= 1); + + G_LOCK (weak_refs_mutex); + + if (object->n_weak_refs) { + /* Don't add the weak reference if it already exists. */ + for (i = 0; i < object->n_weak_refs; i++) { + if (object->weak_refs[i].notify == notify && + object->weak_refs[i].data == data) { + g_warning ("%s: Attempt to re-add existing weak ref %p(%p) failed.", + G_STRFUNC, notify, data); + goto found; + } + } + + i = object->n_weak_refs++; + object->weak_refs = + g_realloc (object->weak_refs, sizeof (object->weak_refs[0]) * i); + } else { + object->weak_refs = g_malloc0 (sizeof (object->weak_refs[0])); + object->n_weak_refs = 1; + i = 0; + } + object->weak_refs[i].notify = notify; + object->weak_refs[i].data = data; +found: + G_UNLOCK (weak_refs_mutex); +} + +/** + * gst_mini_object_weak_unref: (skip) + * @object: #GstMiniObject to remove a weak reference from + * @notify: callback to search for + * @data: data to search for + * + * Removes a weak reference callback to a mini object. + * + * Since: 0.10.35 + */ +void +gst_mini_object_weak_unref (GstMiniObject * object, + GstMiniObjectWeakNotify notify, gpointer data) +{ + gboolean found_one = FALSE; + + g_return_if_fail (object != NULL); + g_return_if_fail (notify != NULL); + + G_LOCK (weak_refs_mutex); + + if (object->n_weak_refs) { + guint i; + + for (i = 0; i < object->n_weak_refs; i++) + if (object->weak_refs[i].notify == notify && + object->weak_refs[i].data == data) { + found_one = TRUE; + object->n_weak_refs -= 1; + if (i != object->n_weak_refs) + object->weak_refs[i] = object->weak_refs[object->n_weak_refs]; + + break; + } + } + G_UNLOCK (weak_refs_mutex); + if (!found_one) + g_warning ("%s: couldn't find weak ref %p(%p)", G_STRFUNC, notify, data); +} diff --git a/gst/gstminiobject.h b/gst/gstminiobject.h new file mode 100644 index 0000000..65b4dfb --- /dev/null +++ b/gst/gstminiobject.h @@ -0,0 +1,228 @@ +/* GStreamer + * Copyright (C) 2005 David Schleef <ds@schleef.org> + * + * gstminiobject.h: Header for GstMiniObject + * + * 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_MINI_OBJECT_H__ +#define __GST_MINI_OBJECT_H__ + +#include <gst/gstconfig.h> + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GST_IS_MINI_OBJECT_TYPE(obj,type) ((obj) && GST_MINI_OBJECT_TYPE(obj) == (type)) +#define GST_MINI_OBJECT_CAST(obj) ((GstMiniObject*)(obj)) +#define GST_MINI_OBJECT_CONST_CAST(obj) ((const GstMiniObject*)(obj)) +#define GST_MINI_OBJECT(obj) (GST_MINI_OBJECT_CAST(obj)) + +typedef struct _GstMiniObject GstMiniObject; + +/** + * GstMiniObjectCopyFunction: + * @obj: MiniObject to copy + * + * Function prototype for methods to create copies of instances. + * + * Returns: reference to cloned instance. + */ +typedef GstMiniObject * (*GstMiniObjectCopyFunction) (const GstMiniObject *obj); +/** + * GstMiniObjectDisposeFunction: + * @obj: MiniObject to dispose + * + * Function prototype for when a miniobject has lost its last refcount. + * Implementation of the mini object are allowed to revive the + * passed object by doing a gst_mini_object_ref(). If the object is not + * revived after the dispose function, the function should return %TRUE + * and the memory associated with the object is freed. + * + * Returns: %TRUE if the object should be cleaned up. + */ +typedef gboolean (*GstMiniObjectDisposeFunction) (GstMiniObject *obj); +/** + * GstMiniObjectFreeFunction: + * @obj: MiniObject to free + * + * Virtual function prototype for methods to free ressources used by + * mini-objects. + */ +typedef void (*GstMiniObjectFreeFunction) (GstMiniObject *obj); + + /** + * GstMiniObjectWeakNotify: + * @data: data that was provided when the weak reference was established + * @where_the_mini_object_was: the mini object being finalized + * + * A #GstMiniObjectWeakNotify function can be added to a mini object as a + * callback that gets triggered when the mini object is finalized. Since the + * mini object is already being finalized when the #GstMiniObjectWeakNotify is + * called, there's not much you could do with the object, apart from e.g. using + * its adress as hash-index or the like. + * + * Since: 0.10.35 + */ +typedef void (*GstMiniObjectWeakNotify) (gpointer data, + GstMiniObject * where_the_mini_object_was); + +/** + * GST_MINI_OBJECT_FLAGS: + * @obj: MiniObject to return flags for. + * + * This macro returns the entire set of flags for the mini-object. + */ +#define GST_MINI_OBJECT_TYPE(obj) (GST_MINI_OBJECT_CAST(obj)->type) +/** + * GST_MINI_OBJECT_FLAGS: + * @obj: MiniObject to return flags for. + * + * This macro returns the entire set of flags for the mini-object. + */ +#define GST_MINI_OBJECT_FLAGS(obj) (GST_MINI_OBJECT_CAST(obj)->flags) +/** + * GST_MINI_OBJECT_FLAG_IS_SET: + * @obj: MiniObject to check for flags. + * @flag: Flag to check for + * + * This macro checks to see if the given flag is set. + */ +#define GST_MINI_OBJECT_FLAG_IS_SET(obj,flag) !!(GST_MINI_OBJECT_FLAGS (obj) & (flag)) +/** + * GST_MINI_OBJECT_FLAG_SET: + * @obj: MiniObject to set flag in. + * @flag: Flag to set, can by any number of bits in guint32. + * + * This macro sets the given bits. + */ +#define GST_MINI_OBJECT_FLAG_SET(obj,flag) (GST_MINI_OBJECT_FLAGS (obj) |= (flag)) +/** + * GST_MINI_OBJECT_FLAG_UNSET: + * @obj: MiniObject to unset flag in. + * @flag: Flag to set, must be a single bit in guint32. + * + * This macro usets the given bits. + */ +#define GST_MINI_OBJECT_FLAG_UNSET(obj,flag) (GST_MINI_OBJECT_FLAGS (obj) &= ~(flag)) + +/** + * GstMiniObjectFlags: + * @GST_MINI_OBJECT_FLAG_LAST: first flag that can be used by subclasses. + * + * Flags for the mini object + */ +typedef enum +{ + /* padding */ + GST_MINI_OBJECT_FLAG_LAST = (1<<4) +} GstMiniObjectFlags; + +/** + * GST_MINI_OBJECT_REFCOUNT: + * @obj: a #GstMiniObject + * + * Get access to the reference count field of the mini-object. + */ +#define GST_MINI_OBJECT_REFCOUNT(obj) ((GST_MINI_OBJECT_CAST(obj))->refcount) +/** + * GST_MINI_OBJECT_REFCOUNT_VALUE: + * @obj: a #GstMiniObject + * + * Get the reference count value of the mini-object. + */ +#define GST_MINI_OBJECT_REFCOUNT_VALUE(obj) (g_atomic_int_get (&(GST_MINI_OBJECT_CAST(obj))->refcount)) + +/** + * GST_MINI_OBJECT_SIZE: + * @obj: a #GstMiniObject + * + * Get the allocated size of @obj. + */ +#define GST_MINI_OBJECT_SIZE(obj) ((GST_MINI_OBJECT_CAST(obj))->size) + +/** + * GstMiniObject: + * @type: the GType of the object + * @refcount: atomic refcount + * @flags: extra flags. + * @size: the size of the structure + * @copy: a copy function + * @dispose: a dispose function + * @free: the free function + * + * Base class for refcounted lightweight objects. + * Ref Func: gst_mini_object_ref + * Unref Func: gst_mini_object_unref + * Set Value Func: g_value_set_boxed + * Get Value Func: g_value_get_boxed + */ +struct _GstMiniObject { + GType type; + + /*< public >*/ /* with COW */ + gint refcount; + guint flags; + gsize size; + + GstMiniObjectCopyFunction copy; + GstMiniObjectDisposeFunction dispose; + GstMiniObjectFreeFunction free; + + /* < private > */ + /* Used to keep track of weak ref notifies */ + guint n_weak_refs; + struct + { + GstMiniObjectWeakNotify notify; + gpointer data; + } *weak_refs; +}; + +void gst_mini_object_init (GstMiniObject *mini_object, + GType type, gsize size); + +GstMiniObject * gst_mini_object_copy (const GstMiniObject *mini_object); +gboolean gst_mini_object_is_writable (const GstMiniObject *mini_object); +GstMiniObject * gst_mini_object_make_writable (GstMiniObject *mini_object); + +/* refcounting */ +GstMiniObject * gst_mini_object_ref (GstMiniObject *mini_object); +void gst_mini_object_unref (GstMiniObject *mini_object); + +void gst_mini_object_weak_ref (GstMiniObject *object, + GstMiniObjectWeakNotify notify, + gpointer data); +void gst_mini_object_weak_unref (GstMiniObject *object, + GstMiniObjectWeakNotify notify, + gpointer data); + +gboolean gst_mini_object_replace (GstMiniObject **olddata, GstMiniObject *newdata); +gboolean gst_mini_object_take (GstMiniObject **olddata, GstMiniObject *newdata); +GstMiniObject * gst_mini_object_steal (GstMiniObject **olddata); + +#define GST_DEFINE_MINI_OBJECT_TYPE(TypeName,type_name) \ + G_DEFINE_BOXED_TYPE(TypeName,type_name, \ + (GBoxedCopyFunc) gst_mini_object_ref, \ + (GBoxedFreeFunc)gst_mini_object_unref) + +G_END_DECLS + +#endif + diff --git a/gst/gstobject.c b/gst/gstobject.c new file mode 100644 index 0000000..50c4c25 --- /dev/null +++ b/gst/gstobject.c @@ -0,0 +1,952 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstobject.c: Fundamental class used for all of GStreamer + * + * 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:gstobject + * @short_description: Base class for the GStreamer object hierarchy + * + * #GstObject provides a root for the object hierarchy tree filed in by the + * GStreamer library. It is currently a thin wrapper on top of + * #GObject. It is an abstract class that is not very usable on its own. + * + * #GstObject gives us basic refcounting, parenting functionality and locking. + * Most of the function are just extended for special GStreamer needs and can be + * found under the same name in the base class of #GstObject which is #GObject + * (e.g. g_object_ref() becomes gst_object_ref()). + * + * The most interesting difference between #GstObject and #GObject is the + * "floating" reference count. A #GObject is created with a reference count of + * 1, owned by the creator of the #GObject. (The owner of a reference is the + * code section that has the right to call gst_object_unref() in order to + * remove that reference.) A #GstObject is created with a reference count of 1 + * also, but it isn't owned by anyone; Instead, the initial reference count + * of a #GstObject is "floating". The floating reference can be removed by + * anyone at any time, by calling gst_object_sink(). gst_object_sink() does + * nothing if an object is already sunk (has no floating reference). + * + * When you add a #GstElement to its parent container, the parent container will + * do this: + * <informalexample> + * <programlisting> + * gst_object_ref (GST_OBJECT (child_element)); + * gst_object_sink (GST_OBJECT (child_element)); + * </programlisting> + * </informalexample> + * This means that the container now owns a reference to the child element + * (since it called gst_object_ref()), and the child element has no floating + * reference. + * + * The purpose of the floating reference is to keep the child element alive + * until you add it to a parent container, which then manages the lifetime of + * the object itself: + * <informalexample> + * <programlisting> + * element = gst_element_factory_make (factoryname, name); + * // element has one floating reference to keep it alive + * gst_bin_add (GST_BIN (bin), element); + * // element has one non-floating reference owned by the container + * </programlisting> + * </informalexample> + * + * Another effect of this is, that calling gst_object_unref() on a bin object, + * will also destoy all the #GstElement objects in it. The same is true for + * calling gst_bin_remove(). + * + * Special care has to be taken for all methods that gst_object_sink() an object + * since if the caller of those functions had a floating reference to the object, + * the object reference is now invalid. + * + * In contrast to #GObject instances, #GstObject adds a name property. The functions + * gst_object_set_name() and gst_object_get_name() are used to set/get the name + * of the object. + * + * Last reviewed on 2005-11-09 (0.9.4) + */ + +#include "gst_private.h" +#include "glib-compat-private.h" + +#include "gstobject.h" +#include "gstmarshal.h" +#include "gstinfo.h" +#include "gstutils.h" + +#ifndef GST_DISABLE_TRACE +#include "gsttrace.h" +static GstAllocTrace *_gst_object_trace; +#endif + +#define DEBUG_REFCOUNT + +/* Object signals and args */ +enum +{ + DEEP_NOTIFY, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_NAME, + PROP_PARENT, + PROP_LAST +}; + +enum +{ + SO_OBJECT_LOADED, + SO_LAST_SIGNAL +}; + +/* maps type name quark => count */ +static GData *object_name_counts = NULL; + +G_LOCK_DEFINE_STATIC (object_name_mutex); + +static void gst_object_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_object_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void gst_object_dispatch_properties_changed (GObject * object, + guint n_pspecs, GParamSpec ** pspecs); + +static void gst_object_dispose (GObject * object); +static void gst_object_finalize (GObject * object); + +static gboolean gst_object_set_name_default (GstObject * object); + +static guint gst_object_signals[LAST_SIGNAL] = { 0 }; + +static GParamSpec *properties[PROP_LAST]; + +G_DEFINE_ABSTRACT_TYPE (GstObject, gst_object, G_TYPE_INITIALLY_UNOWNED); + +static void +gst_object_class_init (GstObjectClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + +#ifndef GST_DISABLE_TRACE + _gst_object_trace = gst_alloc_trace_register (g_type_name (GST_TYPE_OBJECT)); +#endif + + gobject_class->set_property = gst_object_set_property; + gobject_class->get_property = gst_object_get_property; + + properties[PROP_NAME] = + g_param_spec_string ("name", "Name", "The name of the object", NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (gobject_class, PROP_NAME, + properties[PROP_NAME]); + + properties[PROP_PARENT] = + g_param_spec_object ("parent", "Parent", "The parent of the object", + GST_TYPE_OBJECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (gobject_class, PROP_PARENT, + properties[PROP_PARENT]); + + /** + * GstObject::deep-notify: + * @gstobject: a #GstObject + * @prop_object: the object that originated the signal + * @prop: the property that changed + * + * The deep notify signal is used to be notified of property changes. It is + * typically attached to the toplevel bin to receive notifications from all + * the elements contained in that bin. + */ + gst_object_signals[DEEP_NOTIFY] = + g_signal_new ("deep-notify", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | + G_SIGNAL_NO_HOOKS, G_STRUCT_OFFSET (GstObjectClass, deep_notify), NULL, + NULL, gst_marshal_VOID__OBJECT_PARAM, G_TYPE_NONE, 2, GST_TYPE_OBJECT, + G_TYPE_PARAM); + + klass->path_string_separator = "/"; + + /* see the comments at gst_object_dispatch_properties_changed */ + gobject_class->dispatch_properties_changed + = GST_DEBUG_FUNCPTR (gst_object_dispatch_properties_changed); + + gobject_class->dispose = gst_object_dispose; + gobject_class->finalize = gst_object_finalize; +} + +static void +gst_object_init (GstObject * object) +{ + object->lock = g_mutex_new (); + object->parent = NULL; + object->name = NULL; + GST_CAT_TRACE_OBJECT (GST_CAT_REFCOUNTING, object, "%p new", object); + +#ifndef GST_DISABLE_TRACE + gst_alloc_trace_new (_gst_object_trace, object); +#endif + + object->flags = 0; +} + +/** + * gst_object_ref: + * @object: a #GstObject to reference + * + * Increments the reference count on @object. This function + * does not take the lock on @object because it relies on + * atomic refcounting. + * + * This object returns the input parameter to ease writing + * constructs like : + * result = gst_object_ref (object->parent); + * + * Returns: (transfer full): A pointer to @object + */ +gpointer +gst_object_ref (gpointer object) +{ + g_return_val_if_fail (object != NULL, NULL); + +#ifdef DEBUG_REFCOUNT + GST_CAT_TRACE_OBJECT (GST_CAT_REFCOUNTING, object, "%p ref %d->%d", object, + ((GObject *) object)->ref_count, ((GObject *) object)->ref_count + 1); +#endif + g_object_ref (object); + + return object; +} + +/** + * gst_object_unref: + * @object: a #GstObject to unreference + * + * Decrements the reference count on @object. If reference count hits + * zero, destroy @object. This function does not take the lock + * on @object as it relies on atomic refcounting. + * + * The unref method should never be called with the LOCK held since + * this might deadlock the dispose function. + */ +void +gst_object_unref (gpointer object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (((GObject *) object)->ref_count > 0); + +#ifdef DEBUG_REFCOUNT + GST_CAT_TRACE_OBJECT (GST_CAT_REFCOUNTING, object, "%p unref %d->%d", object, + ((GObject *) object)->ref_count, ((GObject *) object)->ref_count - 1); +#endif + g_object_unref (object); +} + +/** + * gst_object_ref_sink: + * @object: a #GstObject to sink + * + * Increase the reference count of @object, and possibly remove the floating + * reference, if @object has a floating reference. + * + * In other words, if the object is floating, then this call "assumes ownership" + * of the floating reference, converting it to a normal reference by clearing + * the floating flag while leaving the reference count unchanged. If the object + * is not floating, then this call adds a new normal reference increasing the + * reference count by one. + */ +gpointer +gst_object_ref_sink (gpointer object) +{ + g_return_val_if_fail (object != NULL, NULL); + +#ifdef DEBUG_REFCOUNT + GST_CAT_TRACE_OBJECT (GST_CAT_REFCOUNTING, object, "%p ref_sink %d->%d", + object, ((GObject *) object)->ref_count, + ((GObject *) object)->ref_count + 1); +#endif + return g_object_ref_sink (object); +} + +/** + * gst_object_replace: + * @oldobj: (inout) (transfer full): pointer to a place of a #GstObject to + * replace + * @newobj: (transfer none): a new #GstObject + * + * Atomically modifies a pointer to point to a new object. + * The reference count of @oldobj is decreased and the reference count of + * @newobj is increased. + * + * Either @newobj and the value pointed to by @oldobj may be NULL. + * + * Returns: TRUE if @newobj was different from @oldobj + */ +gboolean +gst_object_replace (GstObject ** oldobj, GstObject * newobj) +{ + GstObject *oldptr; + + g_return_val_if_fail (oldobj != NULL, FALSE); + +#ifdef DEBUG_REFCOUNT + GST_CAT_TRACE (GST_CAT_REFCOUNTING, "replace %p %s (%d) with %p %s (%d)", + *oldobj, *oldobj ? GST_STR_NULL (GST_OBJECT_NAME (*oldobj)) : "(NONE)", + *oldobj ? G_OBJECT (*oldobj)->ref_count : 0, + newobj, newobj ? GST_STR_NULL (GST_OBJECT_NAME (newobj)) : "(NONE)", + newobj ? G_OBJECT (newobj)->ref_count : 0); +#endif + + oldptr = g_atomic_pointer_get ((gpointer *) oldobj); + + if (G_UNLIKELY (oldptr == newobj)) + return FALSE; + + if (newobj) + g_object_ref (newobj); + + while (G_UNLIKELY (!g_atomic_pointer_compare_and_exchange ((gpointer *) + oldobj, oldptr, newobj))) { + oldptr = g_atomic_pointer_get ((gpointer *) oldobj); + if (G_UNLIKELY (oldptr == newobj)) + break; + } + + if (oldptr) + g_object_unref (oldptr); + + return oldptr != newobj; +} + +/* dispose is called when the object has to release all links + * to other objects */ +static void +gst_object_dispose (GObject * object) +{ + GstObject *parent; + + GST_CAT_TRACE_OBJECT (GST_CAT_REFCOUNTING, object, "dispose"); + + GST_OBJECT_LOCK (object); + if ((parent = GST_OBJECT_PARENT (object))) + goto have_parent; + GST_OBJECT_PARENT (object) = NULL; + GST_OBJECT_UNLOCK (object); + + ((GObjectClass *) gst_object_parent_class)->dispose (object); + + return; + + /* ERRORS */ +have_parent: + { + g_critical ("\nTrying to dispose object \"%s\", but it still has a " + "parent \"%s\".\nYou need to let the parent manage the " + "object instead of unreffing the object directly.\n", + GST_OBJECT_NAME (object), GST_OBJECT_NAME (parent)); + GST_OBJECT_UNLOCK (object); + /* ref the object again to revive it in this error case */ + gst_object_ref (object); + return; + } +} + +/* finalize is called when the object has to free its resources */ +static void +gst_object_finalize (GObject * object) +{ + GstObject *gstobject = GST_OBJECT_CAST (object); + + GST_CAT_TRACE_OBJECT (GST_CAT_REFCOUNTING, object, "finalize"); + + g_signal_handlers_destroy (object); + + g_free (gstobject->name); + g_mutex_free (gstobject->lock); + +#ifndef GST_DISABLE_TRACE + gst_alloc_trace_free (_gst_object_trace, object); +#endif + + ((GObjectClass *) gst_object_parent_class)->finalize (object); +} + +/* Changing a GObject property of a GstObject will result in "deep-notify" + * signals being emitted by the object itself, as well as in each parent + * object. This is so that an application can connect a listener to the + * top-level bin to catch property-change notifications for all contained + * elements. + * + * MT safe. + */ +static void +gst_object_dispatch_properties_changed (GObject * object, + guint n_pspecs, GParamSpec ** pspecs) +{ + GstObject *gst_object, *parent, *old_parent; + guint i; +#ifndef GST_DISABLE_GST_DEBUG + gchar *name = NULL; + const gchar *debug_name; +#endif + + /* do the standard dispatching */ + ((GObjectClass *) + gst_object_parent_class)->dispatch_properties_changed (object, n_pspecs, + pspecs); + + gst_object = GST_OBJECT_CAST (object); +#ifndef GST_DISABLE_GST_DEBUG + if (G_UNLIKELY (__gst_debug_min >= GST_LEVEL_LOG)) { + name = gst_object_get_name (gst_object); + debug_name = GST_STR_NULL (name); + } else + debug_name = ""; +#endif + + /* now let the parent dispatch those, too */ + parent = gst_object_get_parent (gst_object); + while (parent) { + for (i = 0; i < n_pspecs; i++) { + GST_CAT_LOG_OBJECT (GST_CAT_PROPERTIES, parent, + "deep notification from %s (%s)", debug_name, pspecs[i]->name); + + g_signal_emit (parent, gst_object_signals[DEEP_NOTIFY], + g_quark_from_string (pspecs[i]->name), gst_object, pspecs[i]); + } + + old_parent = parent; + parent = gst_object_get_parent (old_parent); + gst_object_unref (old_parent); + } +#ifndef GST_DISABLE_GST_DEBUG + g_free (name); +#endif +} + +/** + * gst_object_default_deep_notify: + * @object: the #GObject that signalled the notify. + * @orig: a #GstObject that initiated the notify. + * @pspec: a #GParamSpec of the property. + * @excluded_props: (array zero-terminated=1) (element-type gchar*) + * (allow-none):a set of user-specified properties to exclude or + * NULL to show all changes. + * + * A default deep_notify signal callback for an object. The user data + * should contain a pointer to an array of strings that should be excluded + * from the notify. The default handler will print the new value of the property + * using g_print. + * + * MT safe. This function grabs and releases @object's LOCK for getting its + * path string. + */ +void +gst_object_default_deep_notify (GObject * object, GstObject * orig, + GParamSpec * pspec, gchar ** excluded_props) +{ + GValue value = { 0, }; /* the important thing is that value.type = 0 */ + gchar *str = NULL; + gchar *name = NULL; + + if (pspec->flags & G_PARAM_READABLE) { + /* let's not print these out for excluded properties... */ + while (excluded_props != NULL && *excluded_props != NULL) { + if (strcmp (pspec->name, *excluded_props) == 0) + return; + excluded_props++; + } + g_value_init (&value, pspec->value_type); + g_object_get_property (G_OBJECT (orig), pspec->name, &value); + + /* FIXME: handle flags */ + if (G_IS_PARAM_SPEC_ENUM (pspec)) { + GEnumValue *enum_value; + GEnumClass *klass = G_ENUM_CLASS (g_type_class_ref (pspec->value_type)); + + enum_value = g_enum_get_value (klass, g_value_get_enum (&value)); + + str = g_strdup_printf ("%s (%d)", enum_value->value_nick, + enum_value->value); + g_type_class_unref (klass); + } else { + str = g_strdup_value_contents (&value); + } + name = gst_object_get_path_string (orig); + g_print ("%s: %s = %s\n", name, pspec->name, str); + g_free (name); + g_free (str); + g_value_unset (&value); + } else { + name = gst_object_get_path_string (orig); + g_warning ("Parameter %s not readable in %s.", pspec->name, name); + g_free (name); + } +} + +static gboolean +gst_object_set_name_default (GstObject * object) +{ + const gchar *type_name; + gint count; + gchar *name; + GQuark q; + guint i, l; + + /* to ensure guaranteed uniqueness across threads, only one thread + * may ever assign a name */ + G_LOCK (object_name_mutex); + + if (!object_name_counts) { + g_datalist_init (&object_name_counts); + } + + q = g_type_qname (G_OBJECT_TYPE (object)); + count = GPOINTER_TO_INT (g_datalist_id_get_data (&object_name_counts, q)); + g_datalist_id_set_data (&object_name_counts, q, GINT_TO_POINTER (count + 1)); + + G_UNLOCK (object_name_mutex); + + /* GstFooSink -> foosink<N> */ + type_name = g_quark_to_string (q); + if (strncmp (type_name, "Gst", 3) == 0) + type_name += 3; + name = g_strdup_printf ("%s%d", type_name, count); + l = strlen (name); + for (i = 0; i < l; i++) + name[i] = g_ascii_tolower (name[i]); + + GST_OBJECT_LOCK (object); + if (G_UNLIKELY (object->parent != NULL)) + goto had_parent; + + g_free (object->name); + object->name = name; + + GST_OBJECT_UNLOCK (object); + + return TRUE; + +had_parent: + { + g_free (name); + GST_WARNING ("parented objects can't be renamed"); + GST_OBJECT_UNLOCK (object); + return FALSE; + } +} + +/** + * gst_object_set_name: + * @object: a #GstObject + * @name: new name of object + * + * Sets the name of @object, or gives @object a guaranteed unique + * name (if @name is NULL). + * This function makes a copy of the provided name, so the caller + * retains ownership of the name it sent. + * + * Returns: TRUE if the name could be set. Since Objects that have + * a parent cannot be renamed, this function returns FALSE in those + * cases. + * + * MT safe. This function grabs and releases @object's LOCK. + */ +gboolean +gst_object_set_name (GstObject * object, const gchar * name) +{ + gboolean result; + + g_return_val_if_fail (GST_IS_OBJECT (object), FALSE); + + GST_OBJECT_LOCK (object); + + /* parented objects cannot be renamed */ + if (G_UNLIKELY (object->parent != NULL)) + goto had_parent; + + if (name != NULL) { + g_free (object->name); + object->name = g_strdup (name); + GST_OBJECT_UNLOCK (object); + result = TRUE; + } else { + GST_OBJECT_UNLOCK (object); + result = gst_object_set_name_default (object); + } + /* FIXME-0.11: this misses a g_object_notify (object, "name"); unless called + * from gst_object_set_property. + * Ideally remove such custom setters (or make it static). + */ + return result; + + /* error */ +had_parent: + { + GST_WARNING ("parented objects can't be renamed"); + GST_OBJECT_UNLOCK (object); + return FALSE; + } +} + +/** + * gst_object_get_name: + * @object: a #GstObject + * + * Returns a copy of the name of @object. + * Caller should g_free() the return value after usage. + * For a nameless object, this returns NULL, which you can safely g_free() + * as well. + * + * Free-function: g_free + * + * Returns: (transfer full): the name of @object. g_free() after usage. + * + * MT safe. This function grabs and releases @object's LOCK. + */ +gchar * +gst_object_get_name (GstObject * object) +{ + gchar *result = NULL; + + g_return_val_if_fail (GST_IS_OBJECT (object), NULL); + + GST_OBJECT_LOCK (object); + result = g_strdup (object->name); + GST_OBJECT_UNLOCK (object); + + return result; +} + +/** + * gst_object_set_parent: + * @object: a #GstObject + * @parent: new parent of object + * + * Sets the parent of @object to @parent. The object's reference count will + * be incremented, and any floating reference will be removed (see gst_object_ref_sink()). + * + * Returns: TRUE if @parent could be set or FALSE when @object + * already had a parent or @object and @parent are the same. + * + * MT safe. Grabs and releases @object's LOCK. + */ +gboolean +gst_object_set_parent (GstObject * object, GstObject * parent) +{ + g_return_val_if_fail (GST_IS_OBJECT (object), FALSE); + g_return_val_if_fail (GST_IS_OBJECT (parent), FALSE); + g_return_val_if_fail (object != parent, FALSE); + + GST_CAT_DEBUG_OBJECT (GST_CAT_REFCOUNTING, object, + "set parent (ref and sink)"); + + GST_OBJECT_LOCK (object); + if (G_UNLIKELY (object->parent != NULL)) + goto had_parent; + + object->parent = parent; + g_object_ref_sink (object); + GST_OBJECT_UNLOCK (object); + + /* FIXME, this does not work, the deep notify takes the lock from the parent + * object and deadlocks when the parent holds its lock when calling this + * function (like _element_add_pad()) */ + /* g_object_notify_by_pspec ((GObject *)object, properties[PROP_PARENT]); */ + + return TRUE; + + /* ERROR handling */ +had_parent: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_REFCOUNTING, object, + "set parent failed, object already had a parent"); + GST_OBJECT_UNLOCK (object); + return FALSE; + } +} + +/** + * gst_object_get_parent: + * @object: a #GstObject + * + * Returns the parent of @object. This function increases the refcount + * of the parent object so you should gst_object_unref() it after usage. + * + * Returns: (transfer full): parent of @object, this can be NULL if @object + * has no parent. unref after usage. + * + * MT safe. Grabs and releases @object's LOCK. + */ +GstObject * +gst_object_get_parent (GstObject * object) +{ + GstObject *result = NULL; + + g_return_val_if_fail (GST_IS_OBJECT (object), NULL); + + GST_OBJECT_LOCK (object); + result = object->parent; + if (G_LIKELY (result)) + gst_object_ref (result); + GST_OBJECT_UNLOCK (object); + + return result; +} + +/** + * gst_object_unparent: + * @object: a #GstObject to unparent + * + * Clear the parent of @object, removing the associated reference. + * This function decreases the refcount of @object. + * + * MT safe. Grabs and releases @object's lock. + */ +void +gst_object_unparent (GstObject * object) +{ + GstObject *parent; + + g_return_if_fail (GST_IS_OBJECT (object)); + + GST_OBJECT_LOCK (object); + parent = object->parent; + + if (G_LIKELY (parent != NULL)) { + GST_CAT_TRACE_OBJECT (GST_CAT_REFCOUNTING, object, "unparent"); + object->parent = NULL; + GST_OBJECT_UNLOCK (object); + + /* g_object_notify_by_pspec ((GObject *)object, properties[PROP_PARENT]); */ + + gst_object_unref (object); + } else { + GST_OBJECT_UNLOCK (object); + } +} + +/** + * gst_object_has_ancestor: + * @object: a #GstObject to check + * @ancestor: a #GstObject to check as ancestor + * + * Check if @object has an ancestor @ancestor somewhere up in + * the hierarchy. One can e.g. check if a #GstElement is inside a #GstPipeline. + * + * Returns: TRUE if @ancestor is an ancestor of @object. + * + * MT safe. Grabs and releases @object's locks. + */ +gboolean +gst_object_has_ancestor (GstObject * object, GstObject * ancestor) +{ + GstObject *parent, *tmp; + + if (!ancestor || !object) + return FALSE; + + parent = gst_object_ref (object); + do { + if (parent == ancestor) { + gst_object_unref (parent); + return TRUE; + } + + tmp = gst_object_get_parent (parent); + gst_object_unref (parent); + parent = tmp; + } while (parent); + + return FALSE; +} + +/** + * gst_object_check_uniqueness: + * @list: (transfer none) (element-type Gst.Object): a list of #GstObject to + * check through + * @name: the name to search for + * + * Checks to see if there is any object named @name in @list. This function + * does not do any locking of any kind. You might want to protect the + * provided list with the lock of the owner of the list. This function + * will lock each #GstObject in the list to compare the name, so be + * carefull when passing a list with a locked object. + * + * Returns: TRUE if a #GstObject named @name does not appear in @list, + * FALSE if it does. + * + * MT safe. Grabs and releases the LOCK of each object in the list. + */ +gboolean +gst_object_check_uniqueness (GList * list, const gchar * name) +{ + gboolean result = TRUE; + + g_return_val_if_fail (name != NULL, FALSE); + + for (; list; list = g_list_next (list)) { + GstObject *child; + gboolean eq; + + child = GST_OBJECT_CAST (list->data); + + GST_OBJECT_LOCK (child); + eq = strcmp (GST_OBJECT_NAME (child), name) == 0; + GST_OBJECT_UNLOCK (child); + + if (G_UNLIKELY (eq)) { + result = FALSE; + break; + } + } + return result; +} + + +static void +gst_object_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstObject *gstobject; + + gstobject = GST_OBJECT_CAST (object); + + switch (prop_id) { + case PROP_NAME: + gst_object_set_name (gstobject, g_value_get_string (value)); + break; + case PROP_PARENT: + gst_object_set_parent (gstobject, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_object_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstObject *gstobject; + + gstobject = GST_OBJECT_CAST (object); + + switch (prop_id) { + case PROP_NAME: + g_value_take_string (value, gst_object_get_name (gstobject)); + break; + case PROP_PARENT: + g_value_take_object (value, gst_object_get_parent (gstobject)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/** + * gst_object_get_path_string: + * @object: a #GstObject + * + * Generates a string describing the path of @object in + * the object hierarchy. Only useful (or used) for debugging. + * + * Free-function: g_free + * + * Returns: (transfer full): a string describing the path of @object. You must + * g_free() the string after usage. + * + * MT safe. Grabs and releases the #GstObject's LOCK for all objects + * in the hierarchy. + */ +gchar * +gst_object_get_path_string (GstObject * object) +{ + GSList *parentage; + GSList *parents; + void *parent; + gchar *prevpath, *path; + const gchar *typename; + gchar *component; + const gchar *separator; + + /* ref object before adding to list */ + gst_object_ref (object); + parentage = g_slist_prepend (NULL, object); + + path = g_strdup (""); + + /* first walk the object hierarchy to build a list of the parents, + * be carefull here with refcounting. */ + do { + if (GST_IS_OBJECT (object)) { + parent = gst_object_get_parent (object); + /* add parents to list, refcount remains increased while + * we handle the object */ + if (parent) + parentage = g_slist_prepend (parentage, parent); + } else { + break; + } + object = parent; + } while (object != NULL); + + /* then walk the parent list and print them out. we need to + * decrease the refcounting on each element after we handled + * it. */ + for (parents = parentage; parents; parents = g_slist_next (parents)) { + if (G_IS_OBJECT (parents->data)) { + typename = G_OBJECT_TYPE_NAME (parents->data); + } else { + typename = NULL; + } + if (GST_IS_OBJECT (parents->data)) { + GstObject *item = GST_OBJECT_CAST (parents->data); + GstObjectClass *oclass = GST_OBJECT_GET_CLASS (item); + gchar *objname = gst_object_get_name (item); + + component = g_strdup_printf ("%s:%s", typename, objname); + separator = oclass->path_string_separator; + /* and unref now */ + gst_object_unref (item); + g_free (objname); + } else { + if (typename) { + component = g_strdup_printf ("%s:%p", typename, parents->data); + } else { + component = g_strdup_printf ("%p", parents->data); + } + separator = "/"; + } + + prevpath = path; + path = g_strjoin (separator, prevpath, component, NULL); + g_free (prevpath); + g_free (component); + } + + g_slist_free (parentage); + + return path; +} diff --git a/gst/gstobject.h b/gst/gstobject.h new file mode 100644 index 0000000..7c9ac04 --- /dev/null +++ b/gst/gstobject.h @@ -0,0 +1,234 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstobject.h: Header for base GstObject + * + * 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_OBJECT_H__ +#define __GST_OBJECT_H__ + +#include <gst/gstconfig.h> + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GST_TYPE_OBJECT (gst_object_get_type ()) +#define GST_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_OBJECT)) +#define GST_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_OBJECT)) +#define GST_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_OBJECT, GstObjectClass)) +#define GST_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_OBJECT, GstObject)) +#define GST_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_OBJECT, GstObjectClass)) +#define GST_OBJECT_CAST(obj) ((GstObject*)(obj)) +#define GST_OBJECT_CLASS_CAST(klass) ((GstObjectClass*)(klass)) + +/** + * GstObjectFlags: + * @GST_OBJECT_FLAG_LAST: subclasses can add additional flags starting from this flag + * + * The standard flags that an gstobject may have. + */ +typedef enum +{ + /* padding */ + GST_OBJECT_FLAG_LAST = (1<<4) +} GstObjectFlags; + +/** + * GST_OBJECT_REFCOUNT: + * @obj: a #GstObject + * + * Get access to the reference count field of the object. + */ +#define GST_OBJECT_REFCOUNT(obj) (((GObject*)(obj))->ref_count) +/** + * GST_OBJECT_REFCOUNT_VALUE: + * @obj: a #GstObject + * + * Get the reference count value of the object. + */ +#define GST_OBJECT_REFCOUNT_VALUE(obj) g_atomic_int_get ((gint *) &GST_OBJECT_REFCOUNT(obj)) + +/* we do a GST_OBJECT_CAST to avoid type checking, better call these + * function with a valid object! */ + +/** + * GST_OBJECT_GET_LOCK: + * @obj: a #GstObject + * + * Acquire a reference to the mutex of this object. + */ +#define GST_OBJECT_GET_LOCK(obj) (GST_OBJECT_CAST(obj)->lock) +/** + * GST_OBJECT_LOCK: + * @obj: a #GstObject to lock + * + * This macro will obtain a lock on the object, making serialization possible. + * It blocks until the lock can be obtained. + */ +#define GST_OBJECT_LOCK(obj) g_mutex_lock(GST_OBJECT_GET_LOCK(obj)) +/** + * GST_OBJECT_TRYLOCK: + * @obj: a #GstObject. + * + * This macro will try to obtain a lock on the object, but will return with + * FALSE if it can't get it immediately. + */ +#define GST_OBJECT_TRYLOCK(obj) g_mutex_trylock(GST_OBJECT_GET_LOCK(obj)) +/** + * GST_OBJECT_UNLOCK: + * @obj: a #GstObject to unlock. + * + * This macro releases a lock on the object. + */ +#define GST_OBJECT_UNLOCK(obj) g_mutex_unlock(GST_OBJECT_GET_LOCK(obj)) + + +/** + * GST_OBJECT_NAME: + * @obj: a #GstObject + * + * Get the name of this object + */ +#define GST_OBJECT_NAME(obj) (GST_OBJECT_CAST(obj)->name) +/** + * GST_OBJECT_PARENT: + * @obj: a #GstObject + * + * Get the parent of this object + */ +#define GST_OBJECT_PARENT(obj) (GST_OBJECT_CAST(obj)->parent) + + +/** + * GST_OBJECT_FLAGS: + * @obj: a #GstObject + * + * This macro returns the entire set of flags for the object. + */ +#define GST_OBJECT_FLAGS(obj) (GST_OBJECT_CAST (obj)->flags) +/** + * GST_OBJECT_FLAG_IS_SET: + * @obj: a #GstObject + * @flag: Flag to check for + * + * This macro checks to see if the given flag is set. + */ +#define GST_OBJECT_FLAG_IS_SET(obj,flag) ((GST_OBJECT_FLAGS (obj) & (flag)) == (flag)) +/** + * GST_OBJECT_FLAG_SET: + * @obj: a #GstObject + * @flag: Flag to set + * + * This macro sets the given bits. + */ +#define GST_OBJECT_FLAG_SET(obj,flag) (GST_OBJECT_FLAGS (obj) |= (flag)) +/** + * GST_OBJECT_FLAG_UNSET: + * @obj: a #GstObject + * @flag: Flag to set + * + * This macro usets the given bits. + */ +#define GST_OBJECT_FLAG_UNSET(obj,flag) (GST_OBJECT_FLAGS (obj) &= ~(flag)) + + +typedef struct _GstObject GstObject; +typedef struct _GstObjectClass GstObjectClass; + +/** + * GstObject: + * @lock: object LOCK + * @name: The name of the object + * @parent: this object's parent, weak ref + * @flags: flags for this object + * + * GStreamer base object class. + */ +struct _GstObject { + GInitiallyUnowned object; + + /*< public >*/ /* with LOCK */ + GMutex *lock; /* object LOCK */ + gchar *name; /* object name */ + GstObject *parent; /* this object's parent, weak ref */ + guint32 flags; + + /*< private >*/ + gpointer _gst_reserved; +}; + +/** + * GstObjectClass: + * @parent_class: parent + * @path_string_separator: separator used by gst_object_get_path_string() + * @deep_notify: default signal handler + * + * GStreamer base object class. + */ +struct _GstObjectClass { + GInitiallyUnownedClass parent_class; + + const gchar *path_string_separator; + + /* signals */ + void (*deep_notify) (GstObject * object, GstObject * orig, GParamSpec * pspec); + + /*< public >*/ + /* virtual methods for subclasses */ + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +/* normal GObject stuff */ +GType gst_object_get_type (void); + +/* name routines */ +gboolean gst_object_set_name (GstObject *object, const gchar *name); +gchar* gst_object_get_name (GstObject *object); + +/* parentage routines */ +gboolean gst_object_set_parent (GstObject *object, GstObject *parent); +GstObject* gst_object_get_parent (GstObject *object); +void gst_object_unparent (GstObject *object); +gboolean gst_object_has_ancestor (GstObject *object, GstObject *ancestor); + +void gst_object_default_deep_notify (GObject *object, GstObject *orig, + GParamSpec *pspec, gchar **excluded_props); + +/* refcounting + life cycle */ +gpointer gst_object_ref (gpointer object); +void gst_object_unref (gpointer object); +gpointer gst_object_ref_sink (gpointer object); + +/* replace object pointer */ +gboolean gst_object_replace (GstObject **oldobj, GstObject *newobj); + +/* printing out the 'path' of the object */ +gchar * gst_object_get_path_string (GstObject *object); + +/* misc utils */ +gboolean gst_object_check_uniqueness (GList *list, const gchar *name); + +G_END_DECLS + +#endif /* __GST_OBJECT_H__ */ + diff --git a/gst/gstpad.c b/gst/gstpad.c new file mode 100644 index 0000000..75732dc --- /dev/null +++ b/gst/gstpad.c @@ -0,0 +1,4934 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstpad.c: Pads for linking elements together + * + * 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:gstpad + * @short_description: Object contained by elements that allows links to + * other elements + * @see_also: #GstPadTemplate, #GstElement, #GstEvent + * + * A #GstElement is linked to other elements via "pads", which are extremely + * light-weight generic link points. + * After two pads are retrieved from an element with gst_element_get_pad(), + * the pads can be link with gst_pad_link(). (For quick links, + * you can also use gst_element_link(), which will make the obvious + * link for you if it's straightforward.) + * + * Pads are typically created from a #GstPadTemplate with + * gst_pad_new_from_template(). + * + * Pads have #GstCaps attached to it to describe the media type they are + * capable of dealing with. gst_pad_get_caps() and gst_pad_set_caps() are + * used to manipulate the caps of the pads. + * Pads created from a pad template cannot set capabilities that are + * incompatible with the pad template capabilities. + * + * Pads without pad templates can be created with gst_pad_new(), + * which takes a direction and a name as an argument. If the name is NULL, + * then a guaranteed unique name will be assigned to it. + * + * gst_pad_get_parent() will retrieve the #GstElement that owns the pad. + * + * A #GstElement creating a pad will typically use the various + * gst_pad_set_*_function() calls to register callbacks for various events + * on the pads. + * + * GstElements will use gst_pad_push() and gst_pad_pull_range() to push out + * or pull in a buffer. + * + * To send a #GstEvent on a pad, use gst_pad_send_event() and + * gst_pad_push_event(). + * + * Last reviewed on 2006-07-06 (0.10.9) + */ + +#include "gst_private.h" + +#include "gstpad.h" +#include "gstpadtemplate.h" +#include "gstenumtypes.h" +#include "gstmarshal.h" +#include "gstutils.h" +#include "gstinfo.h" +#include "gsterror.h" +#include "gstvalue.h" +#include "glib-compat-private.h" + +GST_DEBUG_CATEGORY_STATIC (debug_dataflow); +#define GST_CAT_DEFAULT GST_CAT_PADS + +/* Pad signals and args */ +enum +{ + PAD_LINKED, + PAD_UNLINKED, + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PAD_PROP_0, + PAD_PROP_CAPS, + PAD_PROP_DIRECTION, + PAD_PROP_TEMPLATE, + /* FILL ME */ +}; + +#define GST_PAD_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_PAD, GstPadPrivate)) + +/* we have a pending and an active event on the pad. On source pads only the + * active event is used. On sinkpads, events are copied to the pending entry and + * moved to the active event when the eventfunc returned TRUE. */ +typedef struct +{ + GstEvent *pending; + GstEvent *event; +} PadEvent; + +struct _GstPadPrivate +{ + PadEvent events[GST_EVENT_MAX_STICKY]; + + gint using; + gint probe_cookie; +}; + +typedef struct +{ + GHook hook; + guint cookie; +} GstProbe; + +#define PROBE_COOKIE(h) (((GstProbe *)(h))->cookie) + +typedef struct +{ + GstPad *pad; + GstProbeType mask; + gpointer type_data; + GstProbeReturn ret; + gboolean pass; + guint cookie; +} ProbeMarshall; + +static void gst_pad_dispose (GObject * object); +static void gst_pad_finalize (GObject * object); +static void gst_pad_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_pad_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstCaps *gst_pad_get_caps_unlocked (GstPad * pad, GstCaps * filter); +static void gst_pad_set_pad_template (GstPad * pad, GstPadTemplate * templ); +static gboolean gst_pad_activate_default (GstPad * pad); +static gboolean gst_pad_acceptcaps_default (GstPad * pad, GstCaps * caps); +static void gst_pad_fixate_caps_default (GstPad * pad, GstCaps * caps); +static GstFlowReturn gst_pad_chain_list_default (GstPad * pad, + GstBufferList * list); + +static GstObjectClass *parent_class = NULL; +static guint gst_pad_signals[LAST_SIGNAL] = { 0 }; + +static GParamSpec *pspec_caps = NULL; + +/* quarks for probe signals */ +static GQuark buffer_quark; +static GQuark buffer_list_quark; +static GQuark event_quark; + +typedef struct +{ + const gint ret; + const gchar *name; + GQuark quark; +} GstFlowQuarks; + +static GstFlowQuarks flow_quarks[] = { + {GST_FLOW_CUSTOM_SUCCESS, "custom-success", 0}, + {GST_FLOW_RESEND, "resend", 0}, + {GST_FLOW_OK, "ok", 0}, + {GST_FLOW_NOT_LINKED, "not-linked", 0}, + {GST_FLOW_WRONG_STATE, "wrong-state", 0}, + {GST_FLOW_UNEXPECTED, "unexpected", 0}, + {GST_FLOW_NOT_NEGOTIATED, "not-negotiated", 0}, + {GST_FLOW_ERROR, "error", 0}, + {GST_FLOW_NOT_SUPPORTED, "not-supported", 0}, + {GST_FLOW_CUSTOM_ERROR, "custom-error", 0} +}; + +/** + * gst_flow_get_name: + * @ret: a #GstFlowReturn to get the name of. + * + * Gets a string representing the given flow return. + * + * Returns: a static string with the name of the flow return. + */ +const gchar * +gst_flow_get_name (GstFlowReturn ret) +{ + gint i; + + ret = CLAMP (ret, GST_FLOW_CUSTOM_ERROR, GST_FLOW_CUSTOM_SUCCESS); + + for (i = 0; i < G_N_ELEMENTS (flow_quarks); i++) { + if (ret == flow_quarks[i].ret) + return flow_quarks[i].name; + } + return "unknown"; +} + +/** + * gst_flow_to_quark: + * @ret: a #GstFlowReturn to get the quark of. + * + * Get the unique quark for the given GstFlowReturn. + * + * Returns: the quark associated with the flow return or 0 if an + * invalid return was specified. + */ +GQuark +gst_flow_to_quark (GstFlowReturn ret) +{ + gint i; + + ret = CLAMP (ret, GST_FLOW_CUSTOM_ERROR, GST_FLOW_CUSTOM_SUCCESS); + + for (i = 0; i < G_N_ELEMENTS (flow_quarks); i++) { + if (ret == flow_quarks[i].ret) + return flow_quarks[i].quark; + } + return 0; +} + +#define _do_init \ +{ \ + gint i; \ + \ + buffer_quark = g_quark_from_static_string ("buffer"); \ + buffer_list_quark = g_quark_from_static_string ("bufferlist"); \ + event_quark = g_quark_from_static_string ("event"); \ + \ + for (i = 0; i < G_N_ELEMENTS (flow_quarks); i++) { \ + flow_quarks[i].quark = g_quark_from_static_string (flow_quarks[i].name); \ + } \ + \ + GST_DEBUG_CATEGORY_INIT (debug_dataflow, "GST_DATAFLOW", \ + GST_DEBUG_BOLD | GST_DEBUG_FG_GREEN, "dataflow inside pads"); \ +} + +G_DEFINE_TYPE_WITH_CODE (GstPad, gst_pad, GST_TYPE_OBJECT, _do_init); + +static void +gst_pad_class_init (GstPadClass * klass) +{ + GObjectClass *gobject_class; + GstObjectClass *gstobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstobject_class = GST_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GstPadPrivate)); + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->dispose = gst_pad_dispose; + gobject_class->finalize = gst_pad_finalize; + gobject_class->set_property = gst_pad_set_property; + gobject_class->get_property = gst_pad_get_property; + + /** + * GstPad::linked: + * @pad: the pad that emitted the signal + * @peer: the peer pad that has been connected + * + * Signals that a pad has been linked to the peer pad. + */ + gst_pad_signals[PAD_LINKED] = + g_signal_new ("linked", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstPadClass, linked), NULL, NULL, + gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_PAD); + /** + * GstPad::unlinked: + * @pad: the pad that emitted the signal + * @peer: the peer pad that has been disconnected + * + * Signals that a pad has been unlinked from the peer pad. + */ + gst_pad_signals[PAD_UNLINKED] = + g_signal_new ("unlinked", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstPadClass, unlinked), NULL, NULL, + gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_PAD); + + pspec_caps = g_param_spec_boxed ("caps", "Caps", + "The capabilities of the pad", GST_TYPE_CAPS, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (gobject_class, PAD_PROP_CAPS, pspec_caps); + + g_object_class_install_property (gobject_class, PAD_PROP_DIRECTION, + g_param_spec_enum ("direction", "Direction", "The direction of the pad", + GST_TYPE_PAD_DIRECTION, GST_PAD_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /* FIXME, Make G_PARAM_CONSTRUCT_ONLY when we fix ghostpads. */ + g_object_class_install_property (gobject_class, PAD_PROP_TEMPLATE, + g_param_spec_object ("template", "Template", + "The GstPadTemplate of this pad", GST_TYPE_PAD_TEMPLATE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstobject_class->path_string_separator = "."; + + /* Register common function pointer descriptions */ + GST_DEBUG_REGISTER_FUNCPTR (gst_pad_activate_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_pad_event_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_pad_get_query_types_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_pad_query_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_pad_iterate_internal_links_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_pad_acceptcaps_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_pad_chain_list_default); + GST_DEBUG_REGISTER_FUNCPTR (gst_pad_fixate_caps_default); +} + +static void +gst_pad_init (GstPad * pad) +{ + pad->priv = GST_PAD_GET_PRIVATE (pad); + + GST_PAD_DIRECTION (pad) = GST_PAD_UNKNOWN; + + GST_PAD_ACTIVATEFUNC (pad) = gst_pad_activate_default; + GST_PAD_EVENTFUNC (pad) = gst_pad_event_default; + GST_PAD_QUERYTYPEFUNC (pad) = gst_pad_get_query_types_default; + GST_PAD_QUERYFUNC (pad) = gst_pad_query_default; + GST_PAD_ITERINTLINKFUNC (pad) = gst_pad_iterate_internal_links_default; + GST_PAD_ACCEPTCAPSFUNC (pad) = gst_pad_acceptcaps_default; + GST_PAD_FIXATECAPSFUNC (pad) = gst_pad_fixate_caps_default; + GST_PAD_CHAINLISTFUNC (pad) = gst_pad_chain_list_default; + + GST_PAD_SET_FLUSHING (pad); + + g_static_rec_mutex_init (&pad->stream_rec_lock); + + pad->block_cond = g_cond_new (); + + g_hook_list_init (&pad->probes, sizeof (GstProbe)); +} + +/* called when setting the pad inactive. It removes all sticky events from + * the pad */ +static void +clear_events (PadEvent events[]) +{ + guint i; + + for (i = 0; i < GST_EVENT_MAX_STICKY; i++) { + gst_event_replace (&events[i].event, NULL); + gst_event_replace (&events[i].pending, NULL); + } +} + +/* The sticky event with @idx from the srcpad is copied to the + * pending event on the sinkpad (when different). + * This function applies the pad offsets in case of segment events. + * This will make sure that we send the event to the sinkpad event + * function when the next buffer of event arrives. + * Should be called with the OBJECT lock of both pads. + * This function returns TRUE when there is a pending event on the + * sinkpad */ +static gboolean +replace_event (GstPad * srcpad, GstPad * sinkpad, guint idx) +{ + PadEvent *srcev, *sinkev; + GstEvent *event; + gboolean pending = FALSE; + + srcev = &srcpad->priv->events[idx]; + + if ((event = srcev->event)) { + sinkev = &sinkpad->priv->events[idx]; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + { + GstSegment segment; + gint64 offset; + + offset = srcpad->offset + sinkpad->offset; + if (offset != 0) { + gst_event_copy_segment (event, &segment); + /* adjust the base time. FIXME, check negative times, try to tweak the + * start to do clipping on negative times */ + segment.base += offset; + /* make a new event from the updated segment */ + event = gst_event_new_segment (&segment); + } + break; + } + default: + break; + } + if (sinkev->event != event) { + /* put in the pending entry when different */ + gst_event_replace (&sinkev->pending, event); + pending = TRUE; + } + } + return pending; +} + + +static void +prepare_event_update (GstPad * srcpad, GstPad * sinkpad) +{ + gboolean pending; + gint i; + + /* make sure we push the events from the source to this new peer, for this we + * copy the events on the sinkpad and mark EVENTS_PENDING */ + pending = FALSE; + for (i = 0; i < GST_EVENT_MAX_STICKY; i++) + pending |= replace_event (srcpad, sinkpad, i); + + /* we had some new pending events, set our flag */ + if (pending) + GST_OBJECT_FLAG_SET (sinkpad, GST_PAD_NEED_EVENTS); +} + +/* should be called with the OBJECT_LOCK */ +static GstCaps * +get_pad_caps (GstPad * pad) +{ + GstCaps *caps = NULL; + GstEvent *event; + guint idx; + + idx = GST_EVENT_STICKY_IDX_TYPE (GST_EVENT_CAPS); + /* we can only use the caps when we have successfully send the caps + * event to the event function and is thus in the active entry */ + if ((event = pad->priv->events[idx].event)) + gst_event_parse_caps (event, &caps); + + return caps; +} + +static void +gst_pad_dispose (GObject * object) +{ + GstPad *pad = GST_PAD_CAST (object); + GstPad *peer; + + GST_CAT_DEBUG_OBJECT (GST_CAT_REFCOUNTING, pad, "dispose"); + + /* unlink the peer pad */ + if ((peer = gst_pad_get_peer (pad))) { + /* window for MT unsafeness, someone else could unlink here + * and then we call unlink with wrong pads. The unlink + * function would catch this and safely return failed. */ + if (GST_PAD_IS_SRC (pad)) + gst_pad_unlink (pad, peer); + else + gst_pad_unlink (peer, pad); + + gst_object_unref (peer); + } + + gst_pad_set_pad_template (pad, NULL); + + clear_events (pad->priv->events); + + g_hook_list_clear (&pad->probes); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_pad_finalize (GObject * object) +{ + GstPad *pad = GST_PAD_CAST (object); + GstTask *task; + + /* in case the task is still around, clean it up */ + if ((task = GST_PAD_TASK (pad))) { + gst_task_join (task); + GST_PAD_TASK (pad) = NULL; + gst_object_unref (task); + } + + g_static_rec_mutex_free (&pad->stream_rec_lock); + g_cond_free (pad->block_cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_pad_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + g_return_if_fail (GST_IS_PAD (object)); + + switch (prop_id) { + case PAD_PROP_DIRECTION: + GST_PAD_DIRECTION (object) = (GstPadDirection) g_value_get_enum (value); + break; + case PAD_PROP_TEMPLATE: + gst_pad_set_pad_template (GST_PAD_CAST (object), + (GstPadTemplate *) g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pad_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + g_return_if_fail (GST_IS_PAD (object)); + + switch (prop_id) { + case PAD_PROP_CAPS: + GST_OBJECT_LOCK (object); + g_value_set_boxed (value, get_pad_caps (GST_PAD_CAST (object))); + GST_OBJECT_UNLOCK (object); + break; + case PAD_PROP_DIRECTION: + g_value_set_enum (value, GST_PAD_DIRECTION (object)); + break; + case PAD_PROP_TEMPLATE: + g_value_set_object (value, GST_PAD_PAD_TEMPLATE (object)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/** + * gst_pad_new: + * @name: the name of the new pad. + * @direction: the #GstPadDirection of the pad. + * + * Creates a new pad with the given name in the given direction. + * If name is NULL, a guaranteed unique name (across all pads) + * will be assigned. + * This function makes a copy of the name so you can safely free the name. + * + * Returns: (transfer full): a new #GstPad, or NULL in case of an error. + * + * MT safe. + */ +GstPad * +gst_pad_new (const gchar * name, GstPadDirection direction) +{ + return g_object_new (GST_TYPE_PAD, + "name", name, "direction", direction, NULL); +} + +/** + * gst_pad_new_from_template: + * @templ: the pad template to use + * @name: the name of the element + * + * Creates a new pad with the given name from the given template. + * If name is NULL, a guaranteed unique name (across all pads) + * will be assigned. + * This function makes a copy of the name so you can safely free the name. + * + * Returns: (transfer full): a new #GstPad, or NULL in case of an error. + */ +GstPad * +gst_pad_new_from_template (GstPadTemplate * templ, const gchar * name) +{ + g_return_val_if_fail (GST_IS_PAD_TEMPLATE (templ), NULL); + + return g_object_new (GST_TYPE_PAD, + "name", name, "direction", templ->direction, "template", templ, NULL); +} + +/** + * gst_pad_new_from_static_template: + * @templ: the #GstStaticPadTemplate to use + * @name: the name of the element + * + * Creates a new pad with the given name from the given static template. + * If name is NULL, a guaranteed unique name (across all pads) + * will be assigned. + * This function makes a copy of the name so you can safely free the name. + * + * Returns: (transfer full): a new #GstPad, or NULL in case of an error. + */ +GstPad * +gst_pad_new_from_static_template (GstStaticPadTemplate * templ, + const gchar * name) +{ + GstPad *pad; + GstPadTemplate *template; + + template = gst_static_pad_template_get (templ); + pad = gst_pad_new_from_template (template, name); + gst_object_unref (template); + return pad; +} + +/** + * gst_pad_get_direction: + * @pad: a #GstPad to get the direction of. + * + * Gets the direction of the pad. The direction of the pad is + * decided at construction time so this function does not take + * the LOCK. + * + * Returns: the #GstPadDirection of the pad. + * + * MT safe. + */ +GstPadDirection +gst_pad_get_direction (GstPad * pad) +{ + GstPadDirection result; + + /* PAD_UNKNOWN is a little silly but we need some sort of + * error return value */ + g_return_val_if_fail (GST_IS_PAD (pad), GST_PAD_UNKNOWN); + + result = GST_PAD_DIRECTION (pad); + + return result; +} + +static gboolean +gst_pad_activate_default (GstPad * pad) +{ + return gst_pad_activate_push (pad, TRUE); +} + +static void +pre_activate (GstPad * pad, GstActivateMode new_mode) +{ + switch (new_mode) { + case GST_ACTIVATE_PUSH: + case GST_ACTIVATE_PULL: + GST_OBJECT_LOCK (pad); + GST_DEBUG_OBJECT (pad, "setting ACTIVATE_MODE %d, unset flushing", + new_mode); + GST_PAD_UNSET_FLUSHING (pad); + GST_PAD_ACTIVATE_MODE (pad) = new_mode; + GST_OBJECT_UNLOCK (pad); + break; + case GST_ACTIVATE_NONE: + GST_OBJECT_LOCK (pad); + GST_DEBUG_OBJECT (pad, "setting ACTIVATE_MODE NONE, set flushing"); + GST_PAD_SET_FLUSHING (pad); + GST_PAD_ACTIVATE_MODE (pad) = new_mode; + /* unlock blocked pads so element can resume and stop */ + GST_PAD_BLOCK_BROADCAST (pad); + GST_OBJECT_UNLOCK (pad); + break; + } +} + +static void +post_activate (GstPad * pad, GstActivateMode new_mode) +{ + switch (new_mode) { + case GST_ACTIVATE_PUSH: + case GST_ACTIVATE_PULL: + /* nop */ + break; + case GST_ACTIVATE_NONE: + /* ensures that streaming stops */ + GST_PAD_STREAM_LOCK (pad); + GST_DEBUG_OBJECT (pad, "stopped streaming"); + GST_OBJECT_LOCK (pad); + clear_events (pad->priv->events); + GST_OBJECT_UNLOCK (pad); + GST_PAD_STREAM_UNLOCK (pad); + break; + } +} + +/** + * gst_pad_set_active: + * @pad: the #GstPad to activate or deactivate. + * @active: whether or not the pad should be active. + * + * Activates or deactivates the given pad. + * Normally called from within core state change functions. + * + * If @active, makes sure the pad is active. If it is already active, either in + * push or pull mode, just return. Otherwise dispatches to the pad's activate + * function to perform the actual activation. + * + * If not @active, checks the pad's current mode and calls + * gst_pad_activate_push() or gst_pad_activate_pull(), as appropriate, with a + * FALSE argument. + * + * Returns: #TRUE if the operation was successful. + * + * MT safe. + */ +gboolean +gst_pad_set_active (GstPad * pad, gboolean active) +{ + GstActivateMode old; + gboolean ret = FALSE; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + GST_OBJECT_LOCK (pad); + old = GST_PAD_ACTIVATE_MODE (pad); + GST_OBJECT_UNLOCK (pad); + + if (active) { + switch (old) { + case GST_ACTIVATE_PUSH: + GST_DEBUG_OBJECT (pad, "activating pad from push"); + ret = TRUE; + break; + case GST_ACTIVATE_PULL: + GST_DEBUG_OBJECT (pad, "activating pad from pull"); + ret = TRUE; + break; + case GST_ACTIVATE_NONE: + GST_DEBUG_OBJECT (pad, "activating pad from none"); + ret = (GST_PAD_ACTIVATEFUNC (pad)) (pad); + break; + default: + GST_DEBUG_OBJECT (pad, "unknown activation mode!"); + break; + } + } else { + switch (old) { + case GST_ACTIVATE_PUSH: + GST_DEBUG_OBJECT (pad, "deactivating pad from push"); + ret = gst_pad_activate_push (pad, FALSE); + break; + case GST_ACTIVATE_PULL: + GST_DEBUG_OBJECT (pad, "deactivating pad from pull"); + ret = gst_pad_activate_pull (pad, FALSE); + break; + case GST_ACTIVATE_NONE: + GST_DEBUG_OBJECT (pad, "deactivating pad from none"); + ret = TRUE; + break; + default: + GST_DEBUG_OBJECT (pad, "unknown activation mode!"); + break; + } + } + + if (!ret) { + GST_OBJECT_LOCK (pad); + if (!active) { + g_critical ("Failed to deactivate pad %s:%s, very bad", + GST_DEBUG_PAD_NAME (pad)); + } else { + GST_WARNING_OBJECT (pad, "Failed to activate pad"); + } + GST_OBJECT_UNLOCK (pad); + } else { + if (!active) { + GST_OBJECT_LOCK (pad); + GST_OBJECT_FLAG_UNSET (pad, GST_PAD_NEED_RECONFIGURE); + GST_OBJECT_UNLOCK (pad); + } + } + + return ret; +} + +/** + * gst_pad_activate_pull: + * @pad: the #GstPad to activate or deactivate. + * @active: whether or not the pad should be active. + * + * Activates or deactivates the given pad in pull mode via dispatching to the + * pad's activatepullfunc. For use from within pad activation functions only. + * When called on sink pads, will first proxy the call to the peer pad, which + * is expected to activate its internally linked pads from within its + * activate_pull function. + * + * If you don't know what this is, you probably don't want to call it. + * + * Returns: TRUE if the operation was successful. + * + * MT safe. + */ +gboolean +gst_pad_activate_pull (GstPad * pad, gboolean active) +{ + GstActivateMode old, new; + GstPad *peer; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + GST_OBJECT_LOCK (pad); + old = GST_PAD_ACTIVATE_MODE (pad); + GST_OBJECT_UNLOCK (pad); + + if (active) { + switch (old) { + case GST_ACTIVATE_PULL: + GST_DEBUG_OBJECT (pad, "activating pad from pull, was ok"); + goto was_ok; + case GST_ACTIVATE_PUSH: + GST_DEBUG_OBJECT (pad, + "activating pad from push, deactivate push first"); + /* pad was activate in the wrong direction, deactivate it + * and reactivate it in pull mode */ + if (G_UNLIKELY (!gst_pad_activate_push (pad, FALSE))) + goto deactivate_failed; + /* fallthrough, pad is deactivated now. */ + case GST_ACTIVATE_NONE: + GST_DEBUG_OBJECT (pad, "activating pad from none"); + break; + } + } else { + switch (old) { + case GST_ACTIVATE_NONE: + GST_DEBUG_OBJECT (pad, "deactivating pad from none, was ok"); + goto was_ok; + case GST_ACTIVATE_PUSH: + GST_DEBUG_OBJECT (pad, "deactivating pad from push, weird"); + /* pad was activated in the other direction, deactivate it + * in push mode, this should not happen... */ + if (G_UNLIKELY (!gst_pad_activate_push (pad, FALSE))) + goto deactivate_failed; + /* everything is fine now */ + goto was_ok; + case GST_ACTIVATE_PULL: + GST_DEBUG_OBJECT (pad, "deactivating pad from pull"); + break; + } + } + + if (gst_pad_get_direction (pad) == GST_PAD_SINK) { + if ((peer = gst_pad_get_peer (pad))) { + GST_DEBUG_OBJECT (pad, "calling peer"); + if (G_UNLIKELY (!gst_pad_activate_pull (peer, active))) + goto peer_failed; + gst_object_unref (peer); + } else { + /* there is no peer, this is only fatal when we activate. When we + * deactivate, we must assume the application has unlinked the peer and + * will deactivate it eventually. */ + if (active) + goto not_linked; + else + GST_DEBUG_OBJECT (pad, "deactivating unlinked pad"); + } + } else { + if (G_UNLIKELY (GST_PAD_GETRANGEFUNC (pad) == NULL)) + goto failure; /* Can't activate pull on a src without a + getrange function */ + } + + new = active ? GST_ACTIVATE_PULL : GST_ACTIVATE_NONE; + pre_activate (pad, new); + + if (GST_PAD_ACTIVATEPULLFUNC (pad)) { + if (G_UNLIKELY (!GST_PAD_ACTIVATEPULLFUNC (pad) (pad, active))) + goto failure; + } else { + /* can happen for sinks of passthrough elements */ + } + + post_activate (pad, new); + + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "%s in pull mode", + active ? "activated" : "deactivated"); + + return TRUE; + +was_ok: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "already %s in pull mode", + active ? "activated" : "deactivated"); + return TRUE; + } +deactivate_failed: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, + "failed to %s in switch to pull from mode %d", + (active ? "activate" : "deactivate"), old); + return FALSE; + } +peer_failed: + { + GST_OBJECT_LOCK (peer); + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, + "activate_pull on peer (%s:%s) failed", GST_DEBUG_PAD_NAME (peer)); + GST_OBJECT_UNLOCK (peer); + gst_object_unref (peer); + return FALSE; + } +not_linked: + { + GST_CAT_INFO_OBJECT (GST_CAT_PADS, pad, "can't activate unlinked sink " + "pad in pull mode"); + return FALSE; + } +failure: + { + GST_OBJECT_LOCK (pad); + GST_CAT_INFO_OBJECT (GST_CAT_PADS, pad, "failed to %s in pull mode", + active ? "activate" : "deactivate"); + GST_PAD_SET_FLUSHING (pad); + GST_PAD_ACTIVATE_MODE (pad) = old; + GST_OBJECT_UNLOCK (pad); + return FALSE; + } +} + +/** + * gst_pad_activate_push: + * @pad: the #GstPad to activate or deactivate. + * @active: whether the pad should be active or not. + * + * Activates or deactivates the given pad in push mode via dispatching to the + * pad's activatepushfunc. For use from within pad activation functions only. + * + * If you don't know what this is, you probably don't want to call it. + * + * Returns: %TRUE if the operation was successful. + * + * MT safe. + */ +gboolean +gst_pad_activate_push (GstPad * pad, gboolean active) +{ + GstActivateMode old, new; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "trying to set %s in push mode", + active ? "activated" : "deactivated"); + + GST_OBJECT_LOCK (pad); + old = GST_PAD_ACTIVATE_MODE (pad); + GST_OBJECT_UNLOCK (pad); + + if (active) { + switch (old) { + case GST_ACTIVATE_PUSH: + GST_DEBUG_OBJECT (pad, "activating pad from push, was ok"); + goto was_ok; + case GST_ACTIVATE_PULL: + GST_DEBUG_OBJECT (pad, + "activating pad from push, deactivating pull first"); + /* pad was activate in the wrong direction, deactivate it + * an reactivate it in push mode */ + if (G_UNLIKELY (!gst_pad_activate_pull (pad, FALSE))) + goto deactivate_failed; + /* fallthrough, pad is deactivated now. */ + case GST_ACTIVATE_NONE: + GST_DEBUG_OBJECT (pad, "activating pad from none"); + break; + } + } else { + switch (old) { + case GST_ACTIVATE_NONE: + GST_DEBUG_OBJECT (pad, "deactivating pad from none, was ok"); + goto was_ok; + case GST_ACTIVATE_PULL: + GST_DEBUG_OBJECT (pad, "deactivating pad from pull, weird"); + /* pad was activated in the other direction, deactivate it + * in pull mode, this should not happen... */ + if (G_UNLIKELY (!gst_pad_activate_pull (pad, FALSE))) + goto deactivate_failed; + /* everything is fine now */ + goto was_ok; + case GST_ACTIVATE_PUSH: + GST_DEBUG_OBJECT (pad, "deactivating pad from push"); + break; + } + } + + new = active ? GST_ACTIVATE_PUSH : GST_ACTIVATE_NONE; + pre_activate (pad, new); + + if (GST_PAD_ACTIVATEPUSHFUNC (pad)) { + if (G_UNLIKELY (!GST_PAD_ACTIVATEPUSHFUNC (pad) (pad, active))) { + goto failure; + } + } else { + /* quite ok, element relies on state change func to prepare itself */ + } + + post_activate (pad, new); + + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "%s in push mode", + active ? "activated" : "deactivated"); + return TRUE; + +was_ok: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "already %s in push mode", + active ? "activated" : "deactivated"); + return TRUE; + } +deactivate_failed: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, + "failed to %s in switch to push from mode %d", + (active ? "activate" : "deactivate"), old); + return FALSE; + } +failure: + { + GST_OBJECT_LOCK (pad); + GST_CAT_INFO_OBJECT (GST_CAT_PADS, pad, "failed to %s in push mode", + active ? "activate" : "deactivate"); + GST_PAD_SET_FLUSHING (pad); + GST_PAD_ACTIVATE_MODE (pad) = old; + GST_OBJECT_UNLOCK (pad); + return FALSE; + } +} + +/** + * gst_pad_is_active: + * @pad: the #GstPad to query + * + * Query if a pad is active + * + * Returns: TRUE if the pad is active. + * + * MT safe. + */ +gboolean +gst_pad_is_active (GstPad * pad) +{ + gboolean result = FALSE; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + GST_OBJECT_LOCK (pad); + result = GST_PAD_IS_ACTIVE (pad); + GST_OBJECT_UNLOCK (pad); + + return result; +} + +/** + * gst_pad_add_probe: + * @pad: the #GstPad to add the probe to + * @mask: the probe mask + * @callback: #GstPadProbeCallback that will be called with notifications of + * the pad state + * @user_data: (closure): user data passed to the callback + * @destroy_data: #GDestroyNotify for user_data + * + * Be notified of different states of pads. The provided callback is called for + * every state that matches @mask. + * + * <note> + * Pad probe handlers are only called for source pads in push mode + * and sink pads in pull mode. + * </note> + * + * Returns: an id or 0 on error. The id can be used to remove the probe with + * gst_pad_remove_probe(). + * + * MT safe. + */ +gulong +gst_pad_add_probe (GstPad * pad, GstProbeType mask, + GstPadProbeCallback callback, gpointer user_data, + GDestroyNotify destroy_data) +{ + GHook *hook; + gulong res; + + g_return_val_if_fail (GST_IS_PAD (pad), 0); + g_return_val_if_fail (mask != 0, 0); + + GST_OBJECT_LOCK (pad); + /* make a new probe */ + hook = g_hook_alloc (&pad->probes); + + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, "adding probe for mask 0x%08x", + mask); + + /* when no contraints are given for the types, assume all types are + * acceptable */ + if ((mask & GST_PROBE_TYPE_DATA) == 0) + mask |= GST_PROBE_TYPE_DATA; + if ((mask & GST_PROBE_TYPE_SCHEDULING) == 0) + mask |= GST_PROBE_TYPE_SCHEDULING; + + /* store our flags and other fields */ + hook->flags |= (mask << G_HOOK_FLAG_USER_SHIFT); + hook->func = callback; + hook->data = user_data; + hook->destroy = destroy_data; + PROBE_COOKIE (hook) = 0; + + /* incremenent cookie so that the new hook get's called */ + pad->priv->probe_cookie++; + + /* add the probe */ + g_hook_prepend (&pad->probes, hook); + pad->num_probes++; + + /* get the id of the hook, we return this and it can be used to remove the + * probe later */ + res = hook->hook_id; + + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, "got probe id %lu", res); + + if (mask & GST_PROBE_TYPE_BLOCKING) { + /* we have a block probe */ + pad->num_blocked++; + GST_OBJECT_FLAG_SET (pad, GST_PAD_BLOCKED); + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, "added blocking probe, " + "now %d blocking probes", pad->num_blocked); + } + + /* call the callback if we need to be called for idle callbacks */ + if ((mask & GST_PROBE_TYPE_IDLE) && (callback != NULL)) { + if (pad->priv->using > 0) { + /* the pad is in use, we can't signal the idle callback yet. Since we set the + * flag above, the last thread to leave the push will do the callback. New + * threads going into the push will block. */ + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "pad is in use, delay idle callback"); + GST_OBJECT_UNLOCK (pad); + } else { + /* the pad is idle now, we can signal the idle callback now */ + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "pad is idle, trigger idle callback"); + GST_OBJECT_UNLOCK (pad); + + callback (pad, GST_PROBE_TYPE_IDLE, NULL, user_data); + } + } else { + GST_OBJECT_UNLOCK (pad); + } + return res; +} + +static void +cleanup_hook (GstPad * pad, GHook * hook) +{ + GstProbeType type; + + type = (hook->flags) >> G_HOOK_FLAG_USER_SHIFT; + + if (type & GST_PROBE_TYPE_BLOCKING) { + /* unblock when we remove the last blocking probe */ + pad->num_blocked--; + GST_DEBUG_OBJECT (pad, "remove blocking probe, now %d left", + pad->num_blocked); + if (pad->num_blocked == 0) { + GST_DEBUG_OBJECT (pad, "last blocking probe removed, unblocking"); + GST_OBJECT_FLAG_UNSET (pad, GST_PAD_BLOCKED); + GST_PAD_BLOCK_BROADCAST (pad); + } + } + g_hook_destroy_link (&pad->probes, hook); + pad->num_probes--; +} + +/** + * gst_pad_remove_probe: + * @pad: the #GstPad with the probe + * @id: the probe id to remove + * + * Remove the probe with @id from @pad. + * + * MT safe. + */ +void +gst_pad_remove_probe (GstPad * pad, gulong id) +{ + GHook *hook; + + g_return_if_fail (GST_IS_PAD (pad)); + + GST_OBJECT_LOCK (pad); + + hook = g_hook_get (&pad->probes, id); + if (hook == NULL) + goto not_found; + + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, "removing hook %ld", + hook->hook_id); + cleanup_hook (pad, hook); + GST_OBJECT_UNLOCK (pad); + + return; + +not_found: + { + GST_OBJECT_UNLOCK (pad); + g_warning ("%s: pad `%p' has no probe with id `%lu'", G_STRLOC, pad, id); + return; + } +} + +/** + * gst_pad_is_blocked: + * @pad: the #GstPad to query + * + * Checks if the pad is blocked or not. This function returns the + * last requested state of the pad. It is not certain that the pad + * is actually blocking at this point (see gst_pad_is_blocking()). + * + * Returns: TRUE if the pad is blocked. + * + * MT safe. + */ +gboolean +gst_pad_is_blocked (GstPad * pad) +{ + gboolean result = FALSE; + + g_return_val_if_fail (GST_IS_PAD (pad), result); + + GST_OBJECT_LOCK (pad); + result = GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_BLOCKED); + GST_OBJECT_UNLOCK (pad); + + return result; +} + +/** + * gst_pad_is_blocking: + * @pad: the #GstPad to query + * + * Checks if the pad is blocking or not. This is a guaranteed state + * of whether the pad is actually blocking on a #GstBuffer or a #GstEvent. + * + * Returns: TRUE if the pad is blocking. + * + * MT safe. + * + * Since: 0.10.11 + */ +gboolean +gst_pad_is_blocking (GstPad * pad) +{ + gboolean result = FALSE; + + g_return_val_if_fail (GST_IS_PAD (pad), result); + + GST_OBJECT_LOCK (pad); + /* the blocking flag is only valid if the pad is not flushing */ + result = GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_BLOCKING) && + !GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FLUSHING); + GST_OBJECT_UNLOCK (pad); + + return result; +} + +/** + * gst_pad_check_reconfigure: + * @pad: the #GstPad to check + * + * Check and clear the #GST_PAD_NEED_RECONFIGURE flag on @pad and return %TRUE + * if the flag was set. + * + * Returns: %TRUE is the GST_PAD_NEED_RECONFIGURE flag was set on @pad. + */ +gboolean +gst_pad_check_reconfigure (GstPad * pad) +{ + gboolean reconfigure; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + GST_OBJECT_LOCK (pad); + reconfigure = GST_PAD_NEEDS_RECONFIGURE (pad); + GST_OBJECT_FLAG_UNSET (pad, GST_PAD_NEED_RECONFIGURE); + GST_OBJECT_UNLOCK (pad); + + return reconfigure; +} + +/** + * gst_pad_mark_reconfigure: + * @pad: the #GstPad to mark + * + * Mark a pad for needing reconfiguration. The next call to + * gst_pad_check_reconfigure() will return %TRUE after this call. + */ +void +gst_pad_mark_reconfigure (GstPad * pad) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_OBJECT_LOCK (pad); + GST_OBJECT_FLAG_SET (pad, GST_PAD_NEED_RECONFIGURE); + GST_OBJECT_UNLOCK (pad); +} + +/** + * gst_pad_set_activate_function: + * @pad: a #GstPad. + * @activate: the #GstPadActivateFunction to set. + * + * Sets the given activate function for @pad. The activate function will + * dispatch to gst_pad_activate_push() or gst_pad_activate_pull() to perform + * the actual activation. Only makes sense to set on sink pads. + * + * Call this function if your sink pad can start a pull-based task. + */ +void +gst_pad_set_activate_function (GstPad * pad, GstPadActivateFunction activate) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_ACTIVATEFUNC (pad) = activate; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "activatefunc set to %s", + GST_DEBUG_FUNCPTR_NAME (activate)); +} + +/** + * gst_pad_set_activatepull_function: + * @pad: a #GstPad. + * @activatepull: the #GstPadActivateModeFunction to set. + * + * Sets the given activate_pull function for the pad. An activate_pull function + * prepares the element and any upstream connections for pulling. See XXX + * part-activation.txt for details. + */ +void +gst_pad_set_activatepull_function (GstPad * pad, + GstPadActivateModeFunction activatepull) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_ACTIVATEPULLFUNC (pad) = activatepull; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "activatepullfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (activatepull)); +} + +/** + * gst_pad_set_activatepush_function: + * @pad: a #GstPad. + * @activatepush: the #GstPadActivateModeFunction to set. + * + * Sets the given activate_push function for the pad. An activate_push function + * prepares the element for pushing. See XXX part-activation.txt for details. + */ +void +gst_pad_set_activatepush_function (GstPad * pad, + GstPadActivateModeFunction activatepush) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_ACTIVATEPUSHFUNC (pad) = activatepush; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "activatepushfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (activatepush)); +} + +/** + * gst_pad_set_chain_function: + * @pad: a sink #GstPad. + * @chain: the #GstPadChainFunction to set. + * + * Sets the given chain function for the pad. The chain function is called to + * process a #GstBuffer input buffer. see #GstPadChainFunction for more details. + */ +void +gst_pad_set_chain_function (GstPad * pad, GstPadChainFunction chain) +{ + g_return_if_fail (GST_IS_PAD (pad)); + g_return_if_fail (GST_PAD_IS_SINK (pad)); + + GST_PAD_CHAINFUNC (pad) = chain; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "chainfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (chain)); +} + +/** + * gst_pad_set_chain_list_function: + * @pad: a sink #GstPad. + * @chainlist: the #GstPadChainListFunction to set. + * + * Sets the given chain list function for the pad. The chainlist function is + * called to process a #GstBufferList input buffer list. See + * #GstPadChainListFunction for more details. + * + * Since: 0.10.24 + */ +void +gst_pad_set_chain_list_function (GstPad * pad, + GstPadChainListFunction chainlist) +{ + g_return_if_fail (GST_IS_PAD (pad)); + g_return_if_fail (GST_PAD_IS_SINK (pad)); + + GST_PAD_CHAINLISTFUNC (pad) = chainlist; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "chainlistfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (chainlist)); +} + +/** + * gst_pad_set_getrange_function: + * @pad: a source #GstPad. + * @get: the #GstPadGetRangeFunction to set. + * + * Sets the given getrange function for the pad. The getrange function is + * called to produce a new #GstBuffer to start the processing pipeline. see + * #GstPadGetRangeFunction for a description of the getrange function. + */ +void +gst_pad_set_getrange_function (GstPad * pad, GstPadGetRangeFunction get) +{ + g_return_if_fail (GST_IS_PAD (pad)); + g_return_if_fail (GST_PAD_IS_SRC (pad)); + + GST_PAD_GETRANGEFUNC (pad) = get; + + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "getrangefunc set to %s", + GST_DEBUG_FUNCPTR_NAME (get)); +} + +/** + * gst_pad_set_event_function: + * @pad: a #GstPad of either direction. + * @event: the #GstPadEventFunction to set. + * + * Sets the given event handler for the pad. + */ +void +gst_pad_set_event_function (GstPad * pad, GstPadEventFunction event) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_EVENTFUNC (pad) = event; + + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "eventfunc for set to %s", + GST_DEBUG_FUNCPTR_NAME (event)); +} + +/** + * gst_pad_set_query_function: + * @pad: a #GstPad of either direction. + * @query: the #GstPadQueryFunction to set. + * + * Set the given query function for the pad. + */ +void +gst_pad_set_query_function (GstPad * pad, GstPadQueryFunction query) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_QUERYFUNC (pad) = query; + + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "queryfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (query)); +} + +/** + * gst_pad_set_query_type_function: + * @pad: a #GstPad of either direction. + * @type_func: the #GstPadQueryTypeFunction to set. + * + * Set the given query type function for the pad. + */ +void +gst_pad_set_query_type_function (GstPad * pad, + GstPadQueryTypeFunction type_func) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_QUERYTYPEFUNC (pad) = type_func; + + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "querytypefunc set to %s", + GST_DEBUG_FUNCPTR_NAME (type_func)); +} + +/** + * gst_pad_get_query_types: + * @pad: a #GstPad. + * + * Get an array of supported queries that can be performed + * on this pad. + * + * Returns: (transfer none) (array zero-terminated=1): a zero-terminated array + * of #GstQueryType. + */ +const GstQueryType * +gst_pad_get_query_types (GstPad * pad) +{ + GstPadQueryTypeFunction func; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + if (G_UNLIKELY ((func = GST_PAD_QUERYTYPEFUNC (pad)) == NULL)) + goto no_func; + + return func (pad); + +no_func: + { + return NULL; + } +} + +static gboolean +gst_pad_get_query_types_dispatcher (GstPad * pad, const GstQueryType ** data) +{ + GstPad *peer; + + if ((peer = gst_pad_get_peer (pad))) { + *data = gst_pad_get_query_types (peer); + gst_object_unref (peer); + } + return TRUE; +} + +/** + * gst_pad_get_query_types_default: + * @pad: a #GstPad. + * + * Invoke the default query types function on the pad. This function will get + * the supported query type from the peer of an internally linked pad of @pad. + * + * Returns: (transfer none) (array zero-terminated=1): a zero-terminated array + * of #GstQueryType, or NULL if none of the internally-linked pads has a + * query types function. + */ +const GstQueryType * +gst_pad_get_query_types_default (GstPad * pad) +{ + GstQueryType *result = NULL; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + gst_pad_forward (pad, (GstPadForwardFunction) + gst_pad_get_query_types_dispatcher, &result); + + return result; +} + +/** + * gst_pad_set_iterate_internal_links_function: + * @pad: a #GstPad of either direction. + * @iterintlink: the #GstPadIterIntLinkFunction to set. + * + * Sets the given internal link iterator function for the pad. + * + * Since: 0.10.21 + */ +void +gst_pad_set_iterate_internal_links_function (GstPad * pad, + GstPadIterIntLinkFunction iterintlink) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_ITERINTLINKFUNC (pad) = iterintlink; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "internal link iterator set to %s", + GST_DEBUG_FUNCPTR_NAME (iterintlink)); +} + +/** + * gst_pad_set_link_function: + * @pad: a #GstPad. + * @link: the #GstPadLinkFunction to set. + * + * Sets the given link function for the pad. It will be called when + * the pad is linked with another pad. + * + * The return value #GST_PAD_LINK_OK should be used when the connection can be + * made. + * + * The return value #GST_PAD_LINK_REFUSED should be used when the connection + * cannot be made for some reason. + * + * If @link is installed on a source pad, it should call the #GstPadLinkFunction + * of the peer sink pad, if present. + */ +void +gst_pad_set_link_function (GstPad * pad, GstPadLinkFunction link) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_LINKFUNC (pad) = link; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "linkfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (link)); +} + +/** + * gst_pad_set_unlink_function: + * @pad: a #GstPad. + * @unlink: the #GstPadUnlinkFunction to set. + * + * Sets the given unlink function for the pad. It will be called + * when the pad is unlinked. + */ +void +gst_pad_set_unlink_function (GstPad * pad, GstPadUnlinkFunction unlink) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_UNLINKFUNC (pad) = unlink; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "unlinkfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (unlink)); +} + +/** + * gst_pad_set_getcaps_function: + * @pad: a #GstPad. + * @getcaps: the #GstPadGetCapsFunction to set. + * + * Sets the given getcaps function for the pad. @getcaps should return the + * allowable caps for a pad in the context of the element's state, its link to + * other elements, and the devices or files it has opened. These caps must be a + * subset of the pad template caps. In the NULL state with no links, @getcaps + * should ideally return the same caps as the pad template. In rare + * circumstances, an object property can affect the caps returned by @getcaps, + * but this is discouraged. + * + * You do not need to call this function if @pad's allowed caps are always the + * same as the pad template caps. This can only be true if the padtemplate + * has fixed simple caps. + * + * For most filters, the caps returned by @getcaps is directly affected by the + * allowed caps on other pads. For demuxers and decoders, the caps returned by + * the srcpad's getcaps function is directly related to the stream data. Again, + * @getcaps should return the most specific caps it reasonably can, since this + * helps with autoplugging. + * + * Note that the return value from @getcaps is owned by the caller, so the + * caller should unref the caps after usage. + */ +void +gst_pad_set_getcaps_function (GstPad * pad, GstPadGetCapsFunction getcaps) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_GETCAPSFUNC (pad) = getcaps; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "getcapsfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (getcaps)); +} + +/** + * gst_pad_set_acceptcaps_function: + * @pad: a #GstPad. + * @acceptcaps: the #GstPadAcceptCapsFunction to set. + * + * Sets the given acceptcaps function for the pad. The acceptcaps function + * will be called to check if the pad can accept the given caps. Setting the + * acceptcaps function to NULL restores the default behaviour of allowing + * any caps that matches the caps from gst_pad_get_caps(). + */ +void +gst_pad_set_acceptcaps_function (GstPad * pad, + GstPadAcceptCapsFunction acceptcaps) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_ACCEPTCAPSFUNC (pad) = acceptcaps; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "acceptcapsfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (acceptcaps)); +} + +/** + * gst_pad_set_fixatecaps_function: + * @pad: a #GstPad. + * @fixatecaps: the #GstPadFixateCapsFunction to set. + * + * Sets the given fixatecaps function for the pad. The fixatecaps function + * will be called whenever the default values for a GstCaps needs to be + * filled in. + */ +void +gst_pad_set_fixatecaps_function (GstPad * pad, + GstPadFixateCapsFunction fixatecaps) +{ + g_return_if_fail (GST_IS_PAD (pad)); + + GST_PAD_FIXATECAPSFUNC (pad) = fixatecaps; + GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "fixatecapsfunc set to %s", + GST_DEBUG_FUNCPTR_NAME (fixatecaps)); +} + +/** + * gst_pad_unlink: + * @srcpad: the source #GstPad to unlink. + * @sinkpad: the sink #GstPad to unlink. + * + * Unlinks the source pad from the sink pad. Will emit the #GstPad::unlinked + * signal on both pads. + * + * Returns: TRUE if the pads were unlinked. This function returns FALSE if + * the pads were not linked together. + * + * MT safe. + */ +gboolean +gst_pad_unlink (GstPad * srcpad, GstPad * sinkpad) +{ + gboolean result = FALSE; + GstElement *parent = NULL; + gint i; + + g_return_val_if_fail (GST_IS_PAD (srcpad), FALSE); + g_return_val_if_fail (GST_PAD_IS_SRC (srcpad), FALSE); + g_return_val_if_fail (GST_IS_PAD (sinkpad), FALSE); + g_return_val_if_fail (GST_PAD_IS_SINK (sinkpad), FALSE); + + GST_CAT_INFO (GST_CAT_ELEMENT_PADS, "unlinking %s:%s(%p) and %s:%s(%p)", + GST_DEBUG_PAD_NAME (srcpad), srcpad, + GST_DEBUG_PAD_NAME (sinkpad), sinkpad); + + /* We need to notify the parent before taking any pad locks as the bin in + * question might be waiting for a lock on the pad while holding its lock + * that our message will try to take. */ + if ((parent = GST_ELEMENT_CAST (gst_pad_get_parent (srcpad)))) { + if (GST_IS_ELEMENT (parent)) { + gst_element_post_message (parent, + gst_message_new_structure_change (GST_OBJECT_CAST (sinkpad), + GST_STRUCTURE_CHANGE_TYPE_PAD_UNLINK, parent, TRUE)); + } else { + gst_object_unref (parent); + parent = NULL; + } + } + + GST_OBJECT_LOCK (srcpad); + GST_OBJECT_LOCK (sinkpad); + + if (G_UNLIKELY (GST_PAD_PEER (srcpad) != sinkpad)) + goto not_linked_together; + + if (GST_PAD_UNLINKFUNC (srcpad)) { + GST_PAD_UNLINKFUNC (srcpad) (srcpad); + } + if (GST_PAD_UNLINKFUNC (sinkpad)) { + GST_PAD_UNLINKFUNC (sinkpad) (sinkpad); + } + + /* first clear peers */ + GST_PAD_PEER (srcpad) = NULL; + GST_PAD_PEER (sinkpad) = NULL; + + /* clear pending caps if any */ + for (i = 0; i < GST_EVENT_MAX_STICKY; i++) + gst_event_replace (&sinkpad->priv->events[i].pending, NULL); + + GST_OBJECT_UNLOCK (sinkpad); + GST_OBJECT_UNLOCK (srcpad); + + /* fire off a signal to each of the pads telling them + * that they've been unlinked */ + g_signal_emit (srcpad, gst_pad_signals[PAD_UNLINKED], 0, sinkpad); + g_signal_emit (sinkpad, gst_pad_signals[PAD_UNLINKED], 0, srcpad); + + GST_CAT_INFO (GST_CAT_ELEMENT_PADS, "unlinked %s:%s and %s:%s", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad)); + + result = TRUE; + +done: + if (parent != NULL) { + gst_element_post_message (parent, + gst_message_new_structure_change (GST_OBJECT_CAST (sinkpad), + GST_STRUCTURE_CHANGE_TYPE_PAD_UNLINK, parent, FALSE)); + gst_object_unref (parent); + } + return result; + + /* ERRORS */ +not_linked_together: + { + /* we do not emit a warning in this case because unlinking cannot + * be made MT safe.*/ + GST_OBJECT_UNLOCK (sinkpad); + GST_OBJECT_UNLOCK (srcpad); + goto done; + } +} + +/** + * gst_pad_is_linked: + * @pad: pad to check + * + * Checks if a @pad is linked to another pad or not. + * + * Returns: TRUE if the pad is linked, FALSE otherwise. + * + * MT safe. + */ +gboolean +gst_pad_is_linked (GstPad * pad) +{ + gboolean result; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + GST_OBJECT_LOCK (pad); + result = (GST_PAD_PEER (pad) != NULL); + GST_OBJECT_UNLOCK (pad); + + return result; +} + +/* get the caps from both pads and see if the intersection + * is not empty. + * + * This function should be called with the pad LOCK on both + * pads + */ +static gboolean +gst_pad_link_check_compatible_unlocked (GstPad * src, GstPad * sink, + GstPadLinkCheck flags) +{ + GstCaps *srccaps = NULL; + GstCaps *sinkcaps = NULL; + gboolean compatible = FALSE; + + if (!(flags & (GST_PAD_LINK_CHECK_CAPS | GST_PAD_LINK_CHECK_TEMPLATE_CAPS))) + return TRUE; + + /* Doing the expensive caps checking takes priority over only checking the template caps */ + if (flags & GST_PAD_LINK_CHECK_CAPS) { + srccaps = gst_pad_get_caps_unlocked (src, NULL); + sinkcaps = gst_pad_get_caps_unlocked (sink, NULL); + } else { + /* If one of the two pads doesn't have a template, consider the intersection + * as valid.*/ + if (G_UNLIKELY ((GST_PAD_PAD_TEMPLATE (src) == NULL) + || (GST_PAD_PAD_TEMPLATE (sink) == NULL))) { + compatible = TRUE; + goto done; + } + srccaps = gst_caps_ref (GST_PAD_TEMPLATE_CAPS (GST_PAD_PAD_TEMPLATE (src))); + sinkcaps = + gst_caps_ref (GST_PAD_TEMPLATE_CAPS (GST_PAD_PAD_TEMPLATE (sink))); + } + + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, src, "src caps %" GST_PTR_FORMAT, + srccaps); + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, sink, "sink caps %" GST_PTR_FORMAT, + sinkcaps); + + /* if we have caps on both pads we can check the intersection. If one + * of the caps is NULL, we return TRUE. */ + if (G_UNLIKELY (srccaps == NULL || sinkcaps == NULL)) { + if (srccaps) + gst_caps_unref (srccaps); + if (sinkcaps) + gst_caps_unref (sinkcaps); + goto done; + } + + compatible = gst_caps_can_intersect (srccaps, sinkcaps); + gst_caps_unref (srccaps); + gst_caps_unref (sinkcaps); + +done: + GST_CAT_DEBUG (GST_CAT_CAPS, "caps are %scompatible", + (compatible ? "" : "not")); + + return compatible; +} + +/* check if the grandparents of both pads are the same. + * This check is required so that we don't try to link + * pads from elements in different bins without ghostpads. + * + * The LOCK should be held on both pads + */ +static gboolean +gst_pad_link_check_hierarchy (GstPad * src, GstPad * sink) +{ + GstObject *psrc, *psink; + + psrc = GST_OBJECT_PARENT (src); + psink = GST_OBJECT_PARENT (sink); + + /* if one of the pads has no parent, we allow the link */ + if (G_UNLIKELY (psrc == NULL || psink == NULL)) + goto no_parent; + + /* only care about parents that are elements */ + if (G_UNLIKELY (!GST_IS_ELEMENT (psrc) || !GST_IS_ELEMENT (psink))) + goto no_element_parent; + + /* if the parents are the same, we have a loop */ + if (G_UNLIKELY (psrc == psink)) + goto same_parents; + + /* if they both have a parent, we check the grandparents. We can not lock + * the parent because we hold on the child (pad) and the locking order is + * parent >> child. */ + psrc = GST_OBJECT_PARENT (psrc); + psink = GST_OBJECT_PARENT (psink); + + /* if they have grandparents but they are not the same */ + if (G_UNLIKELY (psrc != psink)) + goto wrong_grandparents; + + return TRUE; + + /* ERRORS */ +no_parent: + { + GST_CAT_DEBUG (GST_CAT_CAPS, + "one of the pads has no parent %" GST_PTR_FORMAT " and %" + GST_PTR_FORMAT, psrc, psink); + return TRUE; + } +no_element_parent: + { + GST_CAT_DEBUG (GST_CAT_CAPS, + "one of the pads has no element parent %" GST_PTR_FORMAT " and %" + GST_PTR_FORMAT, psrc, psink); + return TRUE; + } +same_parents: + { + GST_CAT_DEBUG (GST_CAT_CAPS, "pads have same parent %" GST_PTR_FORMAT, + psrc); + return FALSE; + } +wrong_grandparents: + { + GST_CAT_DEBUG (GST_CAT_CAPS, + "pads have different grandparents %" GST_PTR_FORMAT " and %" + GST_PTR_FORMAT, psrc, psink); + return FALSE; + } +} + +/* FIXME leftover from an attempt at refactoring... */ +/* call with the two pads unlocked, when this function returns GST_PAD_LINK_OK, + * the two pads will be locked in the srcpad, sinkpad order. */ +static GstPadLinkReturn +gst_pad_link_prepare (GstPad * srcpad, GstPad * sinkpad, GstPadLinkCheck flags) +{ + GST_CAT_INFO (GST_CAT_PADS, "trying to link %s:%s and %s:%s", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad)); + + GST_OBJECT_LOCK (srcpad); + + if (G_UNLIKELY (GST_PAD_PEER (srcpad) != NULL)) + goto src_was_linked; + + GST_OBJECT_LOCK (sinkpad); + + if (G_UNLIKELY (GST_PAD_PEER (sinkpad) != NULL)) + goto sink_was_linked; + + /* check hierarchy, pads can only be linked if the grandparents + * are the same. */ + if ((flags & GST_PAD_LINK_CHECK_HIERARCHY) + && !gst_pad_link_check_hierarchy (srcpad, sinkpad)) + goto wrong_hierarchy; + + /* check pad caps for non-empty intersection */ + if (!gst_pad_link_check_compatible_unlocked (srcpad, sinkpad, flags)) + goto no_format; + + /* FIXME check pad scheduling for non-empty intersection */ + + return GST_PAD_LINK_OK; + +src_was_linked: + { + GST_CAT_INFO (GST_CAT_PADS, "src %s:%s was already linked to %s:%s", + GST_DEBUG_PAD_NAME (srcpad), + GST_DEBUG_PAD_NAME (GST_PAD_PEER (srcpad))); + /* we do not emit a warning in this case because unlinking cannot + * be made MT safe.*/ + GST_OBJECT_UNLOCK (srcpad); + return GST_PAD_LINK_WAS_LINKED; + } +sink_was_linked: + { + GST_CAT_INFO (GST_CAT_PADS, "sink %s:%s was already linked to %s:%s", + GST_DEBUG_PAD_NAME (sinkpad), + GST_DEBUG_PAD_NAME (GST_PAD_PEER (sinkpad))); + /* we do not emit a warning in this case because unlinking cannot + * be made MT safe.*/ + GST_OBJECT_UNLOCK (sinkpad); + GST_OBJECT_UNLOCK (srcpad); + return GST_PAD_LINK_WAS_LINKED; + } +wrong_hierarchy: + { + GST_CAT_INFO (GST_CAT_PADS, "pads have wrong hierarchy"); + GST_OBJECT_UNLOCK (sinkpad); + GST_OBJECT_UNLOCK (srcpad); + return GST_PAD_LINK_WRONG_HIERARCHY; + } +no_format: + { + GST_CAT_INFO (GST_CAT_PADS, "caps are incompatible"); + GST_OBJECT_UNLOCK (sinkpad); + GST_OBJECT_UNLOCK (srcpad); + return GST_PAD_LINK_NOFORMAT; + } +} + +/** + * gst_pad_can_link: + * @srcpad: the source #GstPad. + * @sinkpad: the sink #GstPad. + * + * Checks if the source pad and the sink pad are compatible so they can be + * linked. + * + * Returns: TRUE if the pads can be linked. + */ +gboolean +gst_pad_can_link (GstPad * srcpad, GstPad * sinkpad) +{ + GstPadLinkReturn result; + + /* generic checks */ + g_return_val_if_fail (GST_IS_PAD (srcpad), FALSE); + g_return_val_if_fail (GST_IS_PAD (sinkpad), FALSE); + + GST_CAT_INFO (GST_CAT_PADS, "check if %s:%s can link with %s:%s", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad)); + + /* gst_pad_link_prepare does everything for us, we only release the locks + * on the pads that it gets us. If this function returns !OK the locks are not + * taken anymore. */ + result = gst_pad_link_prepare (srcpad, sinkpad, GST_PAD_LINK_CHECK_DEFAULT); + if (result != GST_PAD_LINK_OK) + goto done; + + GST_OBJECT_UNLOCK (srcpad); + GST_OBJECT_UNLOCK (sinkpad); + +done: + return result == GST_PAD_LINK_OK; +} + +/** + * gst_pad_link_full: + * @srcpad: the source #GstPad to link. + * @sinkpad: the sink #GstPad to link. + * @flags: the checks to validate when linking + * + * Links the source pad and the sink pad. + * + * This variant of #gst_pad_link provides a more granular control on the + * checks being done when linking. While providing some considerable speedups + * the caller of this method must be aware that wrong usage of those flags + * can cause severe issues. Refer to the documentation of #GstPadLinkCheck + * for more information. + * + * MT Safe. + * + * Returns: A result code indicating if the connection worked or + * what went wrong. + * + * Since: 0.10.30 + */ +GstPadLinkReturn +gst_pad_link_full (GstPad * srcpad, GstPad * sinkpad, GstPadLinkCheck flags) +{ + GstPadLinkReturn result; + GstElement *parent; + GstPadLinkFunction srcfunc, sinkfunc; + + g_return_val_if_fail (GST_IS_PAD (srcpad), GST_PAD_LINK_REFUSED); + g_return_val_if_fail (GST_PAD_IS_SRC (srcpad), GST_PAD_LINK_WRONG_DIRECTION); + g_return_val_if_fail (GST_IS_PAD (sinkpad), GST_PAD_LINK_REFUSED); + g_return_val_if_fail (GST_PAD_IS_SINK (sinkpad), + GST_PAD_LINK_WRONG_DIRECTION); + + /* Notify the parent early. See gst_pad_unlink for details. */ + if (G_LIKELY ((parent = GST_ELEMENT_CAST (gst_pad_get_parent (srcpad))))) { + if (G_LIKELY (GST_IS_ELEMENT (parent))) { + gst_element_post_message (parent, + gst_message_new_structure_change (GST_OBJECT_CAST (sinkpad), + GST_STRUCTURE_CHANGE_TYPE_PAD_LINK, parent, TRUE)); + } else { + gst_object_unref (parent); + parent = NULL; + } + } + + /* prepare will also lock the two pads */ + result = gst_pad_link_prepare (srcpad, sinkpad, flags); + + if (G_UNLIKELY (result != GST_PAD_LINK_OK)) + goto done; + + /* must set peers before calling the link function */ + GST_PAD_PEER (srcpad) = sinkpad; + GST_PAD_PEER (sinkpad) = srcpad; + + /* make sure we update events */ + prepare_event_update (srcpad, sinkpad); + + /* get the link functions */ + srcfunc = GST_PAD_LINKFUNC (srcpad); + sinkfunc = GST_PAD_LINKFUNC (sinkpad); + + if (G_UNLIKELY (srcfunc || sinkfunc)) { + /* custom link functions, execute them */ + GST_OBJECT_UNLOCK (sinkpad); + GST_OBJECT_UNLOCK (srcpad); + + if (srcfunc) { + /* this one will call the peer link function */ + result = srcfunc (srcpad, sinkpad); + } else if (sinkfunc) { + /* if no source link function, we need to call the sink link + * function ourselves. */ + result = sinkfunc (sinkpad, srcpad); + } + + GST_OBJECT_LOCK (srcpad); + GST_OBJECT_LOCK (sinkpad); + + /* we released the lock, check if the same pads are linked still */ + if (GST_PAD_PEER (srcpad) != sinkpad || GST_PAD_PEER (sinkpad) != srcpad) + goto concurrent_link; + + if (G_UNLIKELY (result != GST_PAD_LINK_OK)) + goto link_failed; + } + GST_OBJECT_UNLOCK (sinkpad); + GST_OBJECT_UNLOCK (srcpad); + + /* fire off a signal to each of the pads telling them + * that they've been linked */ + g_signal_emit (srcpad, gst_pad_signals[PAD_LINKED], 0, sinkpad); + g_signal_emit (sinkpad, gst_pad_signals[PAD_LINKED], 0, srcpad); + + GST_CAT_INFO (GST_CAT_PADS, "linked %s:%s and %s:%s, successful", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad)); + + gst_pad_send_event (srcpad, gst_event_new_reconfigure ()); + +done: + if (G_LIKELY (parent)) { + gst_element_post_message (parent, + gst_message_new_structure_change (GST_OBJECT_CAST (sinkpad), + GST_STRUCTURE_CHANGE_TYPE_PAD_LINK, parent, FALSE)); + gst_object_unref (parent); + } + + return result; + + /* ERRORS */ +concurrent_link: + { + GST_CAT_INFO (GST_CAT_PADS, "concurrent link between %s:%s and %s:%s", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad)); + GST_OBJECT_UNLOCK (sinkpad); + GST_OBJECT_UNLOCK (srcpad); + + /* The other link operation succeeded first */ + result = GST_PAD_LINK_WAS_LINKED; + goto done; + } +link_failed: + { + GST_CAT_INFO (GST_CAT_PADS, "link between %s:%s and %s:%s failed", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad)); + + GST_PAD_PEER (srcpad) = NULL; + GST_PAD_PEER (sinkpad) = NULL; + + GST_OBJECT_UNLOCK (sinkpad); + GST_OBJECT_UNLOCK (srcpad); + + goto done; + } +} + +/** + * gst_pad_link: + * @srcpad: the source #GstPad to link. + * @sinkpad: the sink #GstPad to link. + * + * Links the source pad and the sink pad. + * + * Returns: A result code indicating if the connection worked or + * what went wrong. + * + * MT Safe. + */ +GstPadLinkReturn +gst_pad_link (GstPad * srcpad, GstPad * sinkpad) +{ + return gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_DEFAULT); +} + +static void +gst_pad_set_pad_template (GstPad * pad, GstPadTemplate * templ) +{ + GstPadTemplate **template_p; + + /* this function would need checks if it weren't static */ + + GST_OBJECT_LOCK (pad); + template_p = &pad->padtemplate; + gst_object_replace ((GstObject **) template_p, (GstObject *) templ); + GST_OBJECT_UNLOCK (pad); + + if (templ) + gst_pad_template_pad_created (templ, pad); +} + +/** + * gst_pad_get_pad_template: + * @pad: a #GstPad. + * + * Gets the template for @pad. + * + * Returns: (transfer full): the #GstPadTemplate from which this pad was + * instantiated, or %NULL if this pad has no template. Unref after + * usage. + */ +GstPadTemplate * +gst_pad_get_pad_template (GstPad * pad) +{ + GstPadTemplate *templ; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + templ = GST_PAD_PAD_TEMPLATE (pad); + + return (templ ? gst_object_ref (templ) : NULL); +} + +static GstCaps * +caps_with_getcaps (GstPad * pad, GstCaps * filter) +{ + GstCaps *result; + + if (GST_PAD_GETCAPSFUNC (pad) == NULL) + return NULL; + + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, + "dispatching to pad getcaps function with " + "filter %" GST_PTR_FORMAT, filter); + + GST_OBJECT_FLAG_SET (pad, GST_PAD_IN_GETCAPS); + GST_OBJECT_UNLOCK (pad); + result = GST_PAD_GETCAPSFUNC (pad) (pad, filter); + GST_OBJECT_LOCK (pad); + GST_OBJECT_FLAG_UNSET (pad, GST_PAD_IN_GETCAPS); + + if (G_UNLIKELY (result == NULL)) + goto null_caps; + + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, + "pad getcaps returned %" GST_PTR_FORMAT, result); + +#ifndef G_DISABLE_ASSERT + /* check that the returned caps are a real subset of the template caps */ + if (GST_PAD_PAD_TEMPLATE (pad)) { + const GstCaps *templ_caps = + GST_PAD_TEMPLATE_CAPS (GST_PAD_PAD_TEMPLATE (pad)); + if (!gst_caps_is_subset (result, templ_caps)) { + GstCaps *temp; + + GST_CAT_ERROR_OBJECT (GST_CAT_CAPS, pad, + "pad returned caps %" GST_PTR_FORMAT + " which are not a real subset of its template caps %" + GST_PTR_FORMAT, result, templ_caps); + g_warning + ("pad %s:%s returned caps which are not a real " + "subset of its template caps", GST_DEBUG_PAD_NAME (pad)); + temp = gst_caps_intersect (templ_caps, result); + gst_caps_unref (result); + result = temp; + } + } + if (filter) { + if (!gst_caps_is_subset (result, filter)) { + GstCaps *temp; + + GST_CAT_ERROR_OBJECT (GST_CAT_CAPS, pad, + "pad returned caps %" GST_PTR_FORMAT + " which are not a real subset of the filter caps %" + GST_PTR_FORMAT, result, filter); + g_warning ("pad %s:%s returned caps which are not a real " + "subset of the filter caps", GST_DEBUG_PAD_NAME (pad)); + /* FIXME: Order? But shouldn't happen anyway... */ + temp = gst_caps_intersect_full (filter, result, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (result); + result = temp; + } + } +#endif + + return result; + + /* ERRORS */ +null_caps: + { + g_critical ("pad %s:%s returned NULL caps from getcaps function", + GST_DEBUG_PAD_NAME (pad)); + return NULL; + } +} + +/* should be called with the pad LOCK held */ +/* refs the caps, so caller is responsible for getting it unreffed */ +static GstCaps * +gst_pad_get_caps_unlocked (GstPad * pad, GstCaps * filter) +{ + GstCaps *result = NULL; + GstPadTemplate *templ; + gboolean fixed_caps; + + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "get pad caps"); + + fixed_caps = GST_PAD_IS_FIXED_CAPS (pad); + + if (fixed_caps) { + /* fixed caps, try the negotiated caps first */ + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "fixed pad caps: trying pad caps"); + if ((result = get_pad_caps (pad))) + goto filter_done; + } + + /* try the getcaps function next */ + if ((result = caps_with_getcaps (pad, filter))) + goto done; + + if ((templ = GST_PAD_PAD_TEMPLATE (pad))) { + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "trying pad template caps"); + if ((result = GST_PAD_TEMPLATE_CAPS (templ))) + goto filter_done; + } + + if (!fixed_caps) { + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, + "non-fixed pad caps: trying pad caps"); + /* non fixed caps, try the negotiated caps */ + if ((result = get_pad_caps (pad))) + goto filter_done; + } + + /* this almost never happens */ + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "pad has no caps"); + result = gst_caps_new_empty (); + goto done; + +filter_done: + /* run the filter on the result */ + if (filter) { + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, + "using caps %p %" GST_PTR_FORMAT " with filter %p %" + GST_PTR_FORMAT, result, result, filter, filter); + result = gst_caps_intersect_full (filter, result, GST_CAPS_INTERSECT_FIRST); + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "result %p %" GST_PTR_FORMAT, + result, result); + } else { + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, + "using caps %p %" GST_PTR_FORMAT, result, result); + result = gst_caps_ref (result); + } +done: + return result; +} + +/** + * gst_pad_has_current_caps: + * @pad: a #GstPad to check + * + * Check if @pad has caps set on it with a #GST_EVENT_CAPS event. + * + * Returns: TRUE when @pad has caps associated with it. + */ +gboolean +gst_pad_has_current_caps (GstPad * pad) +{ + gboolean result; + GstCaps *caps; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + GST_OBJECT_LOCK (pad); + caps = get_pad_caps (pad); + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, + "check current pad caps %" GST_PTR_FORMAT, caps); + result = (caps != NULL); + GST_OBJECT_UNLOCK (pad); + + return result; +} + +/** + * gst_pad_get_current_caps: + * @pad: a #GstPad to get the current capabilities of. + * + * Gets the capabilities currently configured on @pad with the last + * #GST_EVENT_CAPS event. + * + * Returns: the current caps of the pad with incremented ref-count. + */ +GstCaps * +gst_pad_get_current_caps (GstPad * pad) +{ + GstCaps *result; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + GST_OBJECT_LOCK (pad); + if ((result = get_pad_caps (pad))) + gst_caps_ref (result); + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, + "get current pad caps %" GST_PTR_FORMAT, result); + GST_OBJECT_UNLOCK (pad); + + return result; +} + +/** + * gst_pad_get_caps: + * @pad: a #GstPad to get the capabilities of. + * @filter: suggested #GstCaps. + * + * Gets the capabilities this pad can produce or consume. + * Note that this method doesn't necessarily return the caps set by + * gst_pad_set_caps() - use gst_pad_get_current_caps() for that instead. + * gst_pad_get_caps returns all possible caps a pad can operate with, using + * the pad's get_caps function; + * this returns the pad template caps if not explicitly set. + * + * When called on sinkpads @filter contains the caps that + * upstream could produce in the order preferred by upstream. When + * called on srcpads @filter contains the caps accepted by + * downstream in the preffered order. @filter might be %NULL but + * if it is not %NULL the returned caps will be a subset of @filter. + * + * Note that this function does not return writable #GstCaps, use + * gst_caps_make_writable() before modifying the caps. + * + * Returns: the caps of the pad with incremented ref-count. + */ +GstCaps * +gst_pad_get_caps (GstPad * pad, GstCaps * filter) +{ + GstCaps *result = NULL; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + g_return_val_if_fail (filter == NULL || GST_IS_CAPS (filter), NULL); + + GST_OBJECT_LOCK (pad); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "get pad caps"); + + result = gst_pad_get_caps_unlocked (pad, filter); + + GST_OBJECT_UNLOCK (pad); + + return result; +} + + +/** + * gst_pad_peer_get_caps: + * @pad: a #GstPad to get the capabilities of. + * @filter: a #GstCaps filter. + * + * Gets the capabilities of the peer connected to this pad. Similar to + * gst_pad_get_caps(). + * + * When called on srcpads @filter contains the caps that + * upstream could produce in the order preferred by upstream. When + * called on sinkpads @filter contains the caps accepted by + * downstream in the preffered order. @filter might be %NULL but + * if it is not %NULL the returned caps will be a subset of @filter. + * + * Returns: the caps of the peer pad with incremented ref-count. This function + * returns %NULL when there is no peer pad. + */ +GstCaps * +gst_pad_peer_get_caps (GstPad * pad, GstCaps * filter) +{ + GstPad *peerpad; + GstCaps *result = NULL; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + g_return_val_if_fail (filter == NULL || GST_IS_CAPS (filter), NULL); + + GST_OBJECT_LOCK (pad); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "get peer caps"); + + peerpad = GST_PAD_PEER (pad); + if (G_UNLIKELY (peerpad == NULL)) + goto no_peer; + + gst_object_ref (peerpad); + GST_OBJECT_UNLOCK (pad); + + result = gst_pad_get_caps (peerpad, filter); + + gst_object_unref (peerpad); + + return result; + +no_peer: + { + GST_OBJECT_UNLOCK (pad); + return NULL; + } +} + +static void +gst_pad_fixate_caps_default (GstPad * pad, GstCaps * caps) +{ + /* default fixation */ + gst_caps_fixate (caps); +} + +/** + * gst_pad_fixate_caps: + * @pad: a #GstPad to fixate + * @caps: the #GstCaps to fixate + * + * Fixate a caps on the given pad. Modifies the caps in place, so you should + * make sure that the caps are actually writable (see gst_caps_make_writable()). + */ +void +gst_pad_fixate_caps (GstPad * pad, GstCaps * caps) +{ + GstPadFixateCapsFunction fixatefunc; + + g_return_if_fail (GST_IS_PAD (pad)); + g_return_if_fail (caps != NULL); + g_return_if_fail (!gst_caps_is_empty (caps)); + g_return_if_fail (!gst_caps_is_any (caps)); + + if (gst_caps_is_fixed (caps) || gst_caps_is_any (caps)) + return; + + g_return_if_fail (gst_caps_is_writable (caps)); + + if (G_LIKELY ((fixatefunc = GST_PAD_FIXATECAPSFUNC (pad)))) + fixatefunc (pad, caps); +} + +/* Default accept caps implementation just checks against + * against the allowed caps for the pad */ +static gboolean +gst_pad_acceptcaps_default (GstPad * pad, GstCaps * caps) +{ + /* get the caps and see if it intersects to something not empty */ + GstCaps *allowed; + gboolean result = FALSE; + + GST_DEBUG_OBJECT (pad, "caps %" GST_PTR_FORMAT, caps); + + allowed = gst_pad_get_caps (pad, NULL); + if (!allowed) + goto nothing_allowed; + + GST_DEBUG_OBJECT (pad, "allowed caps %" GST_PTR_FORMAT, allowed); + + result = gst_caps_is_subset (caps, allowed); + + gst_caps_unref (allowed); + + return result; + + /* ERRORS */ +nothing_allowed: + { + GST_DEBUG_OBJECT (pad, "no caps allowed on the pad"); + return FALSE; + } +} + +/** + * gst_pad_accept_caps: + * @pad: a #GstPad to check + * @caps: a #GstCaps to check on the pad + * + * Check if the given pad accepts the caps. + * + * Returns: TRUE if the pad can accept the caps. + */ +gboolean +gst_pad_accept_caps (GstPad * pad, GstCaps * caps) +{ + gboolean result; + GstPadAcceptCapsFunction acceptfunc; +#if 0 + GstCaps *existing = NULL; +#endif + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + /* any pad can be unnegotiated */ + if (caps == NULL) + return TRUE; + + /* lock for checking the existing caps */ + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "accept caps of %p", caps); +#if 0 + GST_OBJECT_LOCK (pad); + /* The current caps on a pad are trivially acceptable */ + if (G_LIKELY ((existing = GST_PAD_CAPS (pad)))) { + if (caps == existing || gst_caps_is_equal (caps, existing)) + goto is_same_caps; + } + GST_OBJECT_UNLOCK (pad); +#endif + acceptfunc = GST_PAD_ACCEPTCAPSFUNC (pad); + + /* Only null if the element explicitly unset it */ + if (G_UNLIKELY (acceptfunc == NULL)) + goto no_func; + + /* we can call the function */ + result = acceptfunc (pad, caps); + GST_DEBUG_OBJECT (pad, "acceptfunc returned %d", result); + + return result; + +#if 0 +is_same_caps: + { + GST_DEBUG_OBJECT (pad, "pad had same caps"); + GST_OBJECT_UNLOCK (pad); + return TRUE; + } +#endif +no_func: + { + GST_DEBUG_OBJECT (pad, "no acceptcaps function"); + return FALSE; + } +} + +/** + * gst_pad_peer_accept_caps: + * @pad: a #GstPad to check the peer of + * @caps: a #GstCaps to check on the pad + * + * Check if the peer of @pad accepts @caps. If @pad has no peer, this function + * returns TRUE. + * + * Returns: TRUE if the peer of @pad can accept the caps or @pad has no peer. + */ +gboolean +gst_pad_peer_accept_caps (GstPad * pad, GstCaps * caps) +{ + GstPad *peerpad; + gboolean result; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + GST_OBJECT_LOCK (pad); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "peer accept caps of (%p)", pad); + + peerpad = GST_PAD_PEER (pad); + if (G_UNLIKELY (peerpad == NULL)) + goto no_peer; + + gst_object_ref (peerpad); + /* release lock before calling external methods but keep ref to pad */ + GST_OBJECT_UNLOCK (pad); + + result = gst_pad_accept_caps (peerpad, caps); + + gst_object_unref (peerpad); + + return result; + +no_peer: + { + GST_OBJECT_UNLOCK (pad); + return TRUE; + } +} + +/** + * gst_pad_set_caps: + * @pad: a #GstPad to set the capabilities of. + * @caps: (transfer none): a #GstCaps to set. + * + * Sets the capabilities of this pad. The caps must be fixed. Any previous + * caps on the pad will be unreffed. This function refs the caps so you should + * unref if as soon as you don't need it anymore. + * It is possible to set NULL caps, which will make the pad unnegotiated + * again. + * + * Returns: TRUE if the caps could be set. FALSE if the caps were not fixed + * or bad parameters were provided to this function. + * + * MT safe. + */ +gboolean +gst_pad_set_caps (GstPad * pad, GstCaps * caps) +{ + GstEvent *event; + gboolean res = TRUE; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (caps != NULL && gst_caps_is_fixed (caps), FALSE); + + event = gst_event_new_caps (caps); + + if (GST_PAD_IS_SRC (pad)) + res = gst_pad_push_event (pad, event); + else + res = gst_pad_send_event (pad, event); + + return res; +} + +static gboolean +do_event_function (GstPad * pad, GstEvent * event, + GstPadEventFunction eventfunc, gboolean * caps_notify) +{ + gboolean result = TRUE, call_event = TRUE; + GstCaps *caps, *old, *templ; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CAPS: + { + /* backwards compatibility mode for caps */ + gst_event_parse_caps (event, &caps); + + /* See if pad accepts the caps */ + templ = gst_pad_get_pad_template_caps (pad); + if (!gst_caps_is_subset (caps, templ)) + goto not_accepted; + + /* check if it changed */ + if ((old = gst_pad_get_current_caps (pad))) { + call_event = !gst_caps_is_equal (caps, old); + gst_caps_unref (old); + } + if (call_event) + *caps_notify = TRUE; + gst_caps_unref (templ); + break; + } + default: + break; + } + + if (call_event) { + GST_DEBUG_OBJECT (pad, "calling event function with event %p", event); + result = eventfunc (pad, event); + } else { + gst_event_unref (event); + } + return result; + + /* ERRORS */ +not_accepted: + { + gst_caps_unref (templ); + gst_event_unref (event); + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, + "caps %" GST_PTR_FORMAT " not accepted", caps); + return FALSE; + } +} + +/* function to send all pending events on the sinkpad to the event + * function and collect the results. This function should be called with + * the object lock. The object lock might be released by this function. + */ +static GstFlowReturn +gst_pad_update_events (GstPad * pad) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint i; + GstPadEventFunction eventfunc; + GstEvent *event; + gboolean caps_notify = FALSE; + + if (G_UNLIKELY ((eventfunc = GST_PAD_EVENTFUNC (pad)) == NULL)) + goto no_function; + + for (i = 0; i < GST_EVENT_MAX_STICKY; i++) { + gboolean res; + PadEvent *ev; + + ev = &pad->priv->events[i]; + + /* skip without pending event */ + if ((event = gst_event_steal (&ev->pending)) == NULL) + continue; + + gst_event_ref (event); + GST_OBJECT_UNLOCK (pad); + + res = do_event_function (pad, event, eventfunc, &caps_notify); + + /* things could have changed while we release the lock, check if we still + * are handling the same event, if we don't something changed and we have + * to try again. FIXME. we need a cookie here. FIXME, we also want to remove + * that lock eventually and then do the retry elsewhere. */ + + if (res) { + /* make the event active */ + gst_event_take (&ev->event, event); + + /* notify caps change when needed */ + if (caps_notify) { + g_object_notify_by_pspec ((GObject *) pad, pspec_caps); + caps_notify = FALSE; + } + } else { + gst_event_unref (event); + ret = GST_FLOW_ERROR; + } + GST_OBJECT_LOCK (pad); + } + /* when we get here all events were successfully updated. */ + return ret; + + /* ERRORS */ +no_function: + { + g_warning ("pad %s:%s has no event handler, file a bug.", + GST_DEBUG_PAD_NAME (pad)); + return GST_FLOW_NOT_SUPPORTED; + } +} + +/** + * gst_pad_get_pad_template_caps: + * @pad: a #GstPad to get the template capabilities from. + * + * Gets the capabilities for @pad's template. + * + * Returns: (transfer full): the #GstCaps of this pad template. + * Unref after usage. + */ +GstCaps * +gst_pad_get_pad_template_caps (GstPad * pad) +{ + static GstStaticCaps anycaps = GST_STATIC_CAPS ("ANY"); + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + if (GST_PAD_PAD_TEMPLATE (pad)) + return gst_pad_template_get_caps (GST_PAD_PAD_TEMPLATE (pad)); + + return gst_static_caps_get (&anycaps); +} + +/** + * gst_pad_get_peer: + * @pad: a #GstPad to get the peer of. + * + * Gets the peer of @pad. This function refs the peer pad so + * you need to unref it after use. + * + * Returns: (transfer full): the peer #GstPad. Unref after usage. + * + * MT safe. + */ +GstPad * +gst_pad_get_peer (GstPad * pad) +{ + GstPad *result; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + GST_OBJECT_LOCK (pad); + result = GST_PAD_PEER (pad); + if (result) + gst_object_ref (result); + GST_OBJECT_UNLOCK (pad); + + return result; +} + +/** + * gst_pad_get_allowed_caps: + * @pad: a #GstPad. + * + * Gets the capabilities of the allowed media types that can flow through + * @pad and its peer. + * + * The allowed capabilities is calculated as the intersection of the results of + * calling gst_pad_get_caps() on @pad and its peer. The caller owns a reference + * on the resulting caps. + * + * Returns: (transfer full): the allowed #GstCaps of the pad link. Unref the + * caps when you no longer need it. This function returns NULL when @pad + * has no peer. + * + * MT safe. + */ +GstCaps * +gst_pad_get_allowed_caps (GstPad * pad) +{ + GstCaps *mycaps; + GstCaps *caps; + GstCaps *peercaps; + GstPad *peer; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + GST_OBJECT_LOCK (pad); + peer = GST_PAD_PEER (pad); + if (G_UNLIKELY (peer == NULL)) + goto no_peer; + + GST_CAT_DEBUG_OBJECT (GST_CAT_PROPERTIES, pad, "getting allowed caps"); + + gst_object_ref (peer); + GST_OBJECT_UNLOCK (pad); + mycaps = gst_pad_get_caps (pad, NULL); + + peercaps = gst_pad_get_caps (peer, NULL); + gst_object_unref (peer); + + caps = gst_caps_intersect (mycaps, peercaps); + gst_caps_unref (peercaps); + gst_caps_unref (mycaps); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CAPS, pad, "allowed caps %" GST_PTR_FORMAT, + caps); + + return caps; + +no_peer: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_PROPERTIES, pad, "no peer"); + GST_OBJECT_UNLOCK (pad); + + return NULL; + } +} + +/** + * gst_pad_iterate_internal_links_default: + * @pad: the #GstPad to get the internal links of. + * + * Iterate the list of pads to which the given pad is linked to inside of + * the parent element. + * This is the default handler, and thus returns an iterator of all of the + * pads inside the parent element with opposite direction. + * + * The caller must free this iterator after use with gst_iterator_free(). + * + * Returns: a #GstIterator of #GstPad, or NULL if @pad has no parent. Unref each + * returned pad with gst_object_unref(). + * + * Since: 0.10.21 + */ +GstIterator * +gst_pad_iterate_internal_links_default (GstPad * pad) +{ + GstIterator *res; + GList **padlist; + guint32 *cookie; + GMutex *lock; + gpointer owner; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + { + GstElement *parent; + + GST_OBJECT_LOCK (pad); + parent = GST_PAD_PARENT (pad); + if (!parent || !GST_IS_ELEMENT (parent)) + goto no_parent; + + gst_object_ref (parent); + GST_OBJECT_UNLOCK (pad); + + if (pad->direction == GST_PAD_SRC) + padlist = &parent->sinkpads; + else + padlist = &parent->srcpads; + + GST_DEBUG_OBJECT (pad, "Making iterator"); + + cookie = &parent->pads_cookie; + owner = parent; + lock = GST_OBJECT_GET_LOCK (parent); + } + + res = gst_iterator_new_list (GST_TYPE_PAD, + lock, cookie, padlist, (GObject *) owner, NULL); + + gst_object_unref (owner); + + return res; + + /* ERRORS */ +no_parent: + { + GST_OBJECT_UNLOCK (pad); + GST_DEBUG_OBJECT (pad, "no parent element"); + return NULL; + } +} + +/** + * gst_pad_iterate_internal_links: + * @pad: the GstPad to get the internal links of. + * + * Gets an iterator for the pads to which the given pad is linked to inside + * of the parent element. + * + * Each #GstPad element yielded by the iterator will have its refcount increased, + * so unref after use. + * + * Free-function: gst_iterator_free + * + * Returns: (transfer full): a new #GstIterator of #GstPad or %NULL when the + * pad does not have an iterator function configured. Use + * gst_iterator_free() after usage. + * + * Since: 0.10.21 + */ +GstIterator * +gst_pad_iterate_internal_links (GstPad * pad) +{ + GstIterator *res = NULL; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + if (GST_PAD_ITERINTLINKFUNC (pad)) + res = GST_PAD_ITERINTLINKFUNC (pad) (pad); + + return res; +} + +/** + * gst_pad_forward: + * @pad: a #GstPad + * @forward: a #GstPadForwardFunction + * @user_data: user data passed to @forward + * + * Calls @forward for all internally linked pads of @pad. This function deals with + * dynamically changing internal pads and will make sure that the @forward + * function is only called once for each pad. + * + * When @forward returns TRUE, no further pads will be processed. + * + * Returns: TRUE if one of the dispatcher functions returned TRUE. + */ +gboolean +gst_pad_forward (GstPad * pad, GstPadForwardFunction forward, + gpointer user_data) +{ + gboolean result = FALSE; + GstIterator *iter; + gboolean done = FALSE; + GValue item = { 0, }; + GList *pushed_pads = NULL; + + iter = gst_pad_iterate_internal_links (pad); + + if (!iter) + goto no_iter; + + while (!done) { + switch (gst_iterator_next (iter, &item)) { + case GST_ITERATOR_OK: + { + GstPad *intpad; + + intpad = g_value_get_object (&item); + + /* if already pushed, skip. FIXME, find something faster to tag pads */ + if (g_list_find (pushed_pads, intpad)) { + g_value_reset (&item); + break; + } + + GST_LOG_OBJECT (pad, "calling forward function on pad %s:%s", + GST_DEBUG_PAD_NAME (intpad)); + done = result = forward (intpad, user_data); + + pushed_pads = g_list_prepend (pushed_pads, intpad); + + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + /* We don't reset the result here because we don't push the event + * again on pads that got the event already and because we need + * to consider the result of the previous pushes */ + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_ERROR_OBJECT (pad, "Could not iterate over internally linked pads"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + g_value_unset (&item); + gst_iterator_free (iter); + + g_list_free (pushed_pads); + +no_iter: + return result; +} + +typedef struct +{ + GstEvent *event; + gboolean result; + gboolean dispatched; +} EventData; + +static gboolean +event_forward_func (GstPad * pad, EventData * data) +{ + /* for each pad we send to, we should ref the event; it's up + * to downstream to unref again when handled. */ + GST_LOG_OBJECT (pad, "Reffing and pushing event %p (%s) to %s:%s", + data->event, GST_EVENT_TYPE_NAME (data->event), GST_DEBUG_PAD_NAME (pad)); + + data->result |= gst_pad_push_event (pad, gst_event_ref (data->event)); + + data->dispatched = TRUE; + + /* don't stop */ + return FALSE; +} + +/** + * gst_pad_event_default: + * @pad: a #GstPad to call the default event handler on. + * @event: (transfer full): the #GstEvent to handle. + * + * Invokes the default event handler for the given pad. + * + * The EOS event will pause the task associated with @pad before it is forwarded + * to all internally linked pads, + * + * The CAPS event will never be forwarded. + * + * The the event is sent to all pads internally linked to @pad. This function + * takes ownership of @event. + * + * Returns: TRUE if the event was sent successfully. + */ +gboolean +gst_pad_event_default (GstPad * pad, GstEvent * event) +{ + gboolean result; + EventData data; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + GST_LOG_OBJECT (pad, "default event handler"); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + { + GST_DEBUG_OBJECT (pad, "pausing task because of eos"); + gst_pad_pause_task (pad); + break; + } + default: + break; + } + + data.event = event; + data.dispatched = FALSE; + data.result = FALSE; + + gst_pad_forward (pad, (GstPadForwardFunction) event_forward_func, &data); + + /* for sinkpads without a parent element or without internal links, nothing + * will be dispatched but we still want to return TRUE. */ + if (data.dispatched) + result = data.result; + else + result = TRUE; + + gst_event_unref (event); + + return result; +} + +/** + * gst_pad_query: + * @pad: a #GstPad to invoke the default query on. + * @query: (transfer none): the #GstQuery to perform. + * + * Dispatches a query to a pad. The query should have been allocated by the + * caller via one of the type-specific allocation functions. The element that + * the pad belongs to is responsible for filling the query with an appropriate + * response, which should then be parsed with a type-specific query parsing + * function. + * + * Again, the caller is responsible for both the allocation and deallocation of + * the query structure. + * + * Please also note that some queries might need a running pipeline to work. + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_pad_query (GstPad * pad, GstQuery * query) +{ + gboolean res; + GstPadQueryFunction func; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (GST_IS_QUERY (query), FALSE); + + GST_DEBUG_OBJECT (pad, "sending query %p (%s)", query, + GST_QUERY_TYPE_NAME (query)); + + if ((func = GST_PAD_QUERYFUNC (pad)) == NULL) + goto no_func; + + res = func (pad, query); + + GST_DEBUG_OBJECT (pad, "sent query %p (%s), result %d", query, + GST_QUERY_TYPE_NAME (query), res); + + return res; + +no_func: + { + GST_DEBUG_OBJECT (pad, "had no query function"); + return FALSE; + } +} + +/** + * gst_pad_peer_query: + * @pad: a #GstPad to invoke the peer query on. + * @query: (transfer none): the #GstQuery to perform. + * + * Performs gst_pad_query() on the peer of @pad. + * + * The caller is responsible for both the allocation and deallocation of + * the query structure. + * + * Returns: TRUE if the query could be performed. This function returns %FALSE + * if @pad has no peer. + * + * Since: 0.10.15 + */ +gboolean +gst_pad_peer_query (GstPad * pad, GstQuery * query) +{ + GstPad *peerpad; + gboolean result; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (GST_IS_QUERY (query), FALSE); + + GST_OBJECT_LOCK (pad); + + GST_DEBUG_OBJECT (pad, "peer query %p (%s)", query, + GST_QUERY_TYPE_NAME (query)); + + peerpad = GST_PAD_PEER (pad); + if (G_UNLIKELY (peerpad == NULL)) + goto no_peer; + + gst_object_ref (peerpad); + GST_OBJECT_UNLOCK (pad); + + result = gst_pad_query (peerpad, query); + + gst_object_unref (peerpad); + + return result; + + /* ERRORS */ +no_peer: + { + GST_WARNING_OBJECT (pad, "pad has no peer"); + GST_OBJECT_UNLOCK (pad); + return FALSE; + } +} + +/** + * gst_pad_query_default: + * @pad: a #GstPad to call the default query handler on. + * @query: (transfer none): the #GstQuery to handle. + * + * Invokes the default query handler for the given pad. + * The query is sent to all pads internally linked to @pad. Note that + * if there are many possible sink pads that are internally linked to + * @pad, only one will be sent the query. + * Multi-sinkpad elements should implement custom query handlers. + * + * Returns: TRUE if the query was performed successfully. + */ +gboolean +gst_pad_query_default (GstPad * pad, GstQuery * query) +{ + gboolean forward = TRUE, ret = FALSE; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_SCHEDULING: + forward = FALSE; + break; + case GST_QUERY_POSITION: + case GST_QUERY_SEEKING: + case GST_QUERY_FORMATS: + case GST_QUERY_LATENCY: + case GST_QUERY_JITTER: + case GST_QUERY_RATE: + case GST_QUERY_CONVERT: + case GST_QUERY_ALLOCATION: + default: + break; + } + + if (forward) { + ret = gst_pad_forward + (pad, (GstPadForwardFunction) gst_pad_peer_query, query); + } + return ret; +} + +static void +probe_hook_marshal (GHook * hook, ProbeMarshall * data) +{ + GstPad *pad = data->pad; + GstProbeType flags; + GstPadProbeCallback callback; + GstProbeReturn ret; + + /* if we have called this callback, do nothing */ + if (PROBE_COOKIE (hook) == data->cookie) + return; + + PROBE_COOKIE (hook) = data->cookie; + + flags = hook->flags >> G_HOOK_FLAG_USER_SHIFT; + + /* one of the data types */ + if ((flags & GST_PROBE_TYPE_DATA & data->mask) == 0) + return; + /* one of the scheduling types */ + if ((flags & GST_PROBE_TYPE_SCHEDULING & data->mask) == 0) + return; + /* all of the blocking types must match */ + if ((flags & GST_PROBE_TYPE_BLOCKING) != + (data->mask & GST_PROBE_TYPE_BLOCKING)) + return; + + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "hook %lu with flags 0x%08x matches", hook->hook_id, flags); + + callback = (GstPadProbeCallback) hook->func; + if (callback == NULL) + return; + + GST_OBJECT_UNLOCK (pad); + + ret = callback (pad, data->mask, data->type_data, hook->data); + + GST_OBJECT_LOCK (pad); + + switch (ret) { + case GST_PROBE_REMOVE: + /* remove the probe */ + GST_DEBUG_OBJECT (pad, "asked to remove hook"); + cleanup_hook (pad, hook); + break; + case GST_PROBE_DROP: + /* need to drop the data, make sure other probes don't get called + * anymore */ + GST_DEBUG_OBJECT (pad, "asked to drop item"); + data->mask = GST_PROBE_TYPE_INVALID; + data->ret = GST_PROBE_DROP; + break; + case GST_PROBE_PASS: + /* inform the pad block to let things pass */ + GST_DEBUG_OBJECT (pad, "asked to pass item"); + data->pass = TRUE; + break; + default: + GST_DEBUG_OBJECT (pad, "probe returned %d", ret); + break; + } +} + +#define PROBE(pad,mask,data,label) \ + G_STMT_START { \ + if (G_UNLIKELY (pad->num_probes)) { \ + ret = do_probe_callbacks (pad, mask, data); \ + if (G_UNLIKELY (ret != GST_FLOW_OK)) \ + goto label; \ + } \ + } G_STMT_END + +static GstFlowReturn +do_probe_callbacks (GstPad * pad, GstProbeType mask, gpointer type_data) +{ + ProbeMarshall data; + guint cookie; + + data.pad = pad; + data.mask = mask; + data.type_data = type_data; + data.ret = GST_PROBE_OK; + data.pass = FALSE; + data.cookie = pad->priv->probe_cookie++; + +again: + cookie = pad->priv->probe_cookie; + + g_hook_list_marshal (&pad->probes, FALSE, + (GHookMarshaller) probe_hook_marshal, &data); + + /* if the list changed, call the new callbacks (they will not have their + * cookie set to data.cookie */ + if (cookie != pad->priv->probe_cookie) { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "probe list changed, restarting"); + goto again; + } + + if (data.ret == GST_PROBE_DROP) + goto dropped; + + if (data.pass) + goto passed; + + if (mask & GST_PROBE_TYPE_BLOCK) { + while (GST_PAD_IS_BLOCKED (pad)) { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "we are blocked %d times", pad->num_blocked); + + /* we might have released the lock */ + if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad))) + goto flushing; + + /* now we block the streaming thread. It can be unlocked when we + * deactivate the pad (which will also set the FLUSHING flag) or + * when the pad is unblocked. A flushing event will also unblock + * the pad after setting the FLUSHING flag. */ + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "Waiting to be unblocked or set flushing"); + GST_OBJECT_FLAG_SET (pad, GST_PAD_BLOCKING); + GST_PAD_BLOCK_WAIT (pad); + GST_OBJECT_FLAG_UNSET (pad, GST_PAD_BLOCKING); + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, "We got unblocked"); + + if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad))) + goto flushing; + } + } + + return GST_FLOW_OK; + + /* ERRORS */ +flushing: + { + GST_DEBUG_OBJECT (pad, "pad is flushing"); + return GST_FLOW_WRONG_STATE; + } +dropped: + { + GST_DEBUG_OBJECT (pad, "data is dropped"); + return GST_FLOW_CUSTOM_SUCCESS; + } +passed: + { + GST_DEBUG_OBJECT (pad, "data is passed"); + return GST_FLOW_OK; + } +} + +/* pad offsets */ + +/** + * gst_pad_get_offset: + * @pad: a #GstPad + * + * Get the offset applied to the running time of @pad. @pad has to be a source + * pad. + * + * Returns: the offset. + */ +gint64 +gst_pad_get_offset (GstPad * pad) +{ + gint64 result; + + g_return_val_if_fail (GST_IS_PAD (pad), 0); + + GST_OBJECT_LOCK (pad); + result = pad->offset; + GST_OBJECT_UNLOCK (pad); + + return result; +} + +/** + * gst_pad_set_offset: + * @pad: a #GstPad + * @offset: the offset + * + * Set the offset that will be applied to the running time of @pad. + */ +void +gst_pad_set_offset (GstPad * pad, gint64 offset) +{ + guint idx; + GstPad *peer; + GstPad *tmp = NULL; + + g_return_if_fail (GST_IS_PAD (pad)); + + GST_OBJECT_LOCK (pad); + /* if nothing changed, do nothing */ + if (pad->offset == offset) + goto done; + + pad->offset = offset; + + /* if no peer, we just updated the offset */ + if ((peer = GST_PAD_PEER (pad)) == NULL) + goto done; + + /* switch pads around when dealing with a sinkpad */ + if (GST_PAD_IS_SINK (pad)) { + /* ref the peer so it doesn't go away when we release the lock */ + tmp = gst_object_ref (peer); + /* make sure we get the peer (the srcpad) */ + GST_OBJECT_UNLOCK (pad); + + /* swap pads */ + peer = pad; + pad = tmp; + + GST_OBJECT_LOCK (pad); + /* check if the pad didn't get relinked */ + if (GST_PAD_PEER (pad) != peer) + goto done; + + /* we can release the ref now */ + gst_object_unref (peer); + } + + /* the index of the segment event in the array */ + idx = GST_EVENT_STICKY_IDX_TYPE (GST_EVENT_SEGMENT); + + /* lock order is srcpad >> sinkpad */ + GST_OBJECT_LOCK (peer); + /* take the current segment event, adjust it and then place + * it on the sinkpad. events on the srcpad are always active. */ + if (replace_event (pad, peer, idx)) + GST_OBJECT_FLAG_SET (peer, GST_PAD_NEED_EVENTS); + + GST_OBJECT_UNLOCK (peer); + +done: + GST_OBJECT_UNLOCK (pad); +} + + +/********************************************************************** + * Data passing functions + */ + +/* this is the chain function that does not perform the additional argument + * checking for that little extra speed. + */ +static inline GstFlowReturn +gst_pad_chain_data_unchecked (GstPad * pad, GstProbeType type, void *data) +{ + GstFlowReturn ret; + gboolean needs_events; + + GST_PAD_STREAM_LOCK (pad); + + GST_OBJECT_LOCK (pad); + if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad))) + goto flushing; + + needs_events = GST_PAD_NEEDS_EVENTS (pad); + if (G_UNLIKELY (needs_events)) { + GST_OBJECT_FLAG_UNSET (pad, GST_PAD_NEED_EVENTS); + + GST_DEBUG_OBJECT (pad, "need to update all events"); + ret = gst_pad_update_events (pad); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto events_error; + } + + PROBE (pad, GST_PROBE_TYPE_PUSH | type, data, probe_stopped); + + GST_OBJECT_UNLOCK (pad); + + /* NOTE: we read the chainfunc unlocked. + * we cannot hold the lock for the pad so we might send + * the data to the wrong function. This is not really a + * problem since functions are assigned at creation time + * and don't change that often... */ + if (G_LIKELY (type & GST_PROBE_TYPE_BUFFER)) { + GstPadChainFunction chainfunc; + + if (G_UNLIKELY ((chainfunc = GST_PAD_CHAINFUNC (pad)) == NULL)) + goto no_function; + + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "calling chainfunction &%s with buffer %p, ts %" GST_TIME_FORMAT, + GST_DEBUG_FUNCPTR_NAME (chainfunc), + GST_BUFFER (data), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (data))); + + ret = chainfunc (pad, GST_BUFFER_CAST (data)); + + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "called chainfunction &%s with buffer %p, returned %s", + GST_DEBUG_FUNCPTR_NAME (chainfunc), data, gst_flow_get_name (ret)); + } else { + GstPadChainListFunction chainlistfunc; + + if (G_UNLIKELY ((chainlistfunc = GST_PAD_CHAINLISTFUNC (pad)) == NULL)) + goto no_function; + + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "calling chainlistfunction &%s", + GST_DEBUG_FUNCPTR_NAME (chainlistfunc)); + + ret = chainlistfunc (pad, GST_BUFFER_LIST_CAST (data)); + + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "called chainlistfunction &%s, returned %s", + GST_DEBUG_FUNCPTR_NAME (chainlistfunc), gst_flow_get_name (ret)); + } + + GST_PAD_STREAM_UNLOCK (pad); + + return ret; + + /* ERRORS */ +flushing: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "chaining, but pad was flushing"); + GST_OBJECT_UNLOCK (pad); + GST_PAD_STREAM_UNLOCK (pad); + return GST_FLOW_WRONG_STATE; + } +events_error: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, "events were not accepted"); + GST_OBJECT_UNLOCK (pad); + GST_PAD_STREAM_UNLOCK (pad); + return ret; + } +probe_stopped: + { + GST_OBJECT_UNLOCK (pad); + GST_PAD_STREAM_UNLOCK (pad); + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); + + switch (ret) { + case GST_FLOW_CUSTOM_SUCCESS: + GST_DEBUG_OBJECT (pad, "dropped buffer"); + ret = GST_FLOW_OK; + break; + default: + GST_DEBUG_OBJECT (pad, "en error occured %s", gst_flow_get_name (ret)); + break; + } + return ret; + } +no_function: + { + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "pushing, but not chainhandler"); + GST_ELEMENT_ERROR (GST_PAD_PARENT (pad), CORE, PAD, (NULL), + ("push on pad %s:%s but it has no chainfunction", + GST_DEBUG_PAD_NAME (pad))); + GST_PAD_STREAM_UNLOCK (pad); + return GST_FLOW_NOT_SUPPORTED; + } +} + +/** + * gst_pad_chain: + * @pad: a sink #GstPad, returns GST_FLOW_ERROR if not. + * @buffer: (transfer full): the #GstBuffer to send, return GST_FLOW_ERROR + * if not. + * + * Chain a buffer to @pad. + * + * The function returns #GST_FLOW_WRONG_STATE if the pad was flushing. + * + * If the buffer type is not acceptable for @pad (as negotiated with a + * preceeding GST_EVENT_CAPS event), this function returns + * #GST_FLOW_NOT_NEGOTIATED. + * + * The function proceeds calling the chain function installed on @pad (see + * gst_pad_set_chain_function()) and the return value of that function is + * returned to the caller. #GST_FLOW_NOT_SUPPORTED is returned if @pad has no + * chain function. + * + * In all cases, success or failure, the caller loses its reference to @buffer + * after calling this function. + * + * Returns: a #GstFlowReturn from the pad. + * + * MT safe. + */ +GstFlowReturn +gst_pad_chain (GstPad * pad, GstBuffer * buffer) +{ + g_return_val_if_fail (GST_IS_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_PAD_IS_SINK (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR); + + return gst_pad_chain_data_unchecked (pad, GST_PROBE_TYPE_BUFFER, buffer); +} + +static GstFlowReturn +gst_pad_chain_list_default (GstPad * pad, GstBufferList * list) +{ + guint i, len; + GstBuffer *buffer; + GstFlowReturn ret; + + GST_INFO_OBJECT (pad, "chaining each group in list as a merged buffer"); + + len = gst_buffer_list_len (list); + + ret = GST_FLOW_OK; + for (i = 0; i < len; i++) { + buffer = gst_buffer_list_get (list, i); + ret = + gst_pad_chain_data_unchecked (pad, GST_PROBE_TYPE_BUFFER, + gst_buffer_ref (buffer)); + if (ret != GST_FLOW_OK) + break; + } + gst_buffer_list_unref (list); + + return ret; +} + +/** + * gst_pad_chain_list: + * @pad: a sink #GstPad, returns GST_FLOW_ERROR if not. + * @list: (transfer full): the #GstBufferList to send, return GST_FLOW_ERROR + * if not. + * + * Chain a bufferlist to @pad. + * + * The function returns #GST_FLOW_WRONG_STATE if the pad was flushing. + * + * If @pad was not negotiated properly with a CAPS event, this function + * returns #GST_FLOW_NOT_NEGOTIATED. + * + * The function proceeds calling the chainlist function installed on @pad (see + * gst_pad_set_chain_list_function()) and the return value of that function is + * returned to the caller. #GST_FLOW_NOT_SUPPORTED is returned if @pad has no + * chainlist function. + * + * In all cases, success or failure, the caller loses its reference to @list + * after calling this function. + * + * MT safe. + * + * Returns: a #GstFlowReturn from the pad. + * + * Since: 0.10.24 + */ +GstFlowReturn +gst_pad_chain_list (GstPad * pad, GstBufferList * list) +{ + g_return_val_if_fail (GST_IS_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_PAD_IS_SINK (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_IS_BUFFER_LIST (list), GST_FLOW_ERROR); + + return gst_pad_chain_data_unchecked (pad, GST_PROBE_TYPE_BUFFER_LIST, list); +} + +static GstFlowReturn +gst_pad_push_data (GstPad * pad, GstProbeType type, void *data) +{ + GstPad *peer; + GstFlowReturn ret; + + GST_OBJECT_LOCK (pad); + if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad))) + goto flushing; + + type |= GST_PROBE_TYPE_PUSH; + + /* do block probes */ + PROBE (pad, type | GST_PROBE_TYPE_BLOCK, data, probe_stopped); + + /* do post-blocking probes */ + PROBE (pad, type, data, probe_stopped); + + if (G_UNLIKELY ((peer = GST_PAD_PEER (pad)) == NULL)) + goto not_linked; + + /* take ref to peer pad before releasing the lock */ + gst_object_ref (peer); + pad->priv->using++; + GST_OBJECT_UNLOCK (pad); + + ret = gst_pad_chain_data_unchecked (peer, type, data); + + gst_object_unref (peer); + + GST_OBJECT_LOCK (pad); + pad->priv->using--; + if (pad->priv->using == 0) { + /* pad is not active anymore, trigger idle callbacks */ + PROBE (pad, GST_PROBE_TYPE_PUSH | GST_PROBE_TYPE_IDLE, NULL, probe_stopped); + } + GST_OBJECT_UNLOCK (pad); + + return ret; + + /* ERROR recovery here */ + /* ERRORS */ +flushing: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "pushing, but pad was flushing"); + GST_OBJECT_UNLOCK (pad); + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); + return GST_FLOW_WRONG_STATE; + } +probe_stopped: + { + GST_OBJECT_UNLOCK (pad); + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); + + switch (ret) { + case GST_FLOW_CUSTOM_SUCCESS: + GST_DEBUG_OBJECT (pad, "dropped buffer"); + ret = GST_FLOW_OK; + break; + default: + GST_DEBUG_OBJECT (pad, "en error occured %s", gst_flow_get_name (ret)); + break; + } + return ret; + } +not_linked: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "pushing, but it was not linked"); + GST_OBJECT_UNLOCK (pad); + gst_mini_object_unref (GST_MINI_OBJECT_CAST (data)); + return GST_FLOW_NOT_LINKED; + } +} + +/** + * gst_pad_push: + * @pad: a source #GstPad, returns #GST_FLOW_ERROR if not. + * @buffer: (transfer full): the #GstBuffer to push returns GST_FLOW_ERROR + * if not. + * + * Pushes a buffer to the peer of @pad. + * + * This function will call installed block probes before triggering any + * installed data probes. + * + * The function proceeds calling gst_pad_chain() on the peer pad and returns + * the value from that function. If @pad has no peer, #GST_FLOW_NOT_LINKED will + * be returned. + * + * In all cases, success or failure, the caller loses its reference to @buffer + * after calling this function. + * + * Returns: a #GstFlowReturn from the peer pad. + * + * MT safe. + */ +GstFlowReturn +gst_pad_push (GstPad * pad, GstBuffer * buffer) +{ + g_return_val_if_fail (GST_IS_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_PAD_IS_SRC (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR); + + return gst_pad_push_data (pad, GST_PROBE_TYPE_BUFFER, buffer); +} + +/** + * gst_pad_push_list: + * @pad: a source #GstPad, returns #GST_FLOW_ERROR if not. + * @list: (transfer full): the #GstBufferList to push returns GST_FLOW_ERROR + * if not. + * + * Pushes a buffer list to the peer of @pad. + * + * This function will call installed block probes before triggering any + * installed data probes. + * + * The function proceeds calling the chain function on the peer pad and returns + * the value from that function. If @pad has no peer, #GST_FLOW_NOT_LINKED will + * be returned. If the peer pad does not have any installed chainlist function + * every group buffer of the list will be merged into a normal #GstBuffer and + * chained via gst_pad_chain(). + * + * In all cases, success or failure, the caller loses its reference to @list + * after calling this function. + * + * Returns: a #GstFlowReturn from the peer pad. + * + * MT safe. + * + * Since: 0.10.24 + */ +GstFlowReturn +gst_pad_push_list (GstPad * pad, GstBufferList * list) +{ + g_return_val_if_fail (GST_IS_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_PAD_IS_SRC (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_IS_BUFFER_LIST (list), GST_FLOW_ERROR); + + return gst_pad_push_data (pad, GST_PROBE_TYPE_BUFFER_LIST, list); +} + +static GstFlowReturn +gst_pad_get_range_unchecked (GstPad * pad, guint64 offset, guint size, + GstBuffer ** buffer) +{ + GstFlowReturn ret; + GstPadGetRangeFunction getrangefunc; + + GST_PAD_STREAM_LOCK (pad); + + GST_OBJECT_LOCK (pad); + if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad))) + goto flushing; + GST_OBJECT_UNLOCK (pad); + + if (G_UNLIKELY ((getrangefunc = GST_PAD_GETRANGEFUNC (pad)) == NULL)) + goto no_function; + + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "calling getrangefunc %s, offset %" + G_GUINT64_FORMAT ", size %u", + GST_DEBUG_FUNCPTR_NAME (getrangefunc), offset, size); + + ret = getrangefunc (pad, offset, size, buffer); + + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto get_range_failed; + + /* can only fire the signal if we have a valid buffer */ + GST_OBJECT_LOCK (pad); + PROBE (pad, GST_PROBE_TYPE_PULL | GST_PROBE_TYPE_BUFFER, *buffer, + probe_stopped); + GST_OBJECT_UNLOCK (pad); + + GST_PAD_STREAM_UNLOCK (pad); + + return ret; + + /* ERRORS */ +flushing: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "getrange, but pad was flushing"); + GST_OBJECT_UNLOCK (pad); + GST_PAD_STREAM_UNLOCK (pad); + return GST_FLOW_WRONG_STATE; + } +no_function: + { + GST_ELEMENT_ERROR (GST_PAD_PARENT (pad), CORE, PAD, (NULL), + ("getrange on pad %s:%s but it has no getrangefunction", + GST_DEBUG_PAD_NAME (pad))); + GST_PAD_STREAM_UNLOCK (pad); + return GST_FLOW_NOT_SUPPORTED; + } +probe_stopped: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "probe returned %s", gst_flow_get_name (ret)); + GST_OBJECT_UNLOCK (pad); + GST_PAD_STREAM_UNLOCK (pad); + gst_buffer_unref (*buffer); + *buffer = NULL; + return ret; + } +get_range_failed: + { + GST_PAD_STREAM_UNLOCK (pad); + *buffer = NULL; + GST_CAT_LEVEL_LOG (GST_CAT_SCHEDULING, + (ret >= GST_FLOW_UNEXPECTED) ? GST_LEVEL_INFO : GST_LEVEL_WARNING, + pad, "getrange failed, flow: %s", gst_flow_get_name (ret)); + return ret; + } +} + +/** + * gst_pad_get_range: + * @pad: a src #GstPad, returns #GST_FLOW_ERROR if not. + * @offset: The start offset of the buffer + * @size: The length of the buffer + * @buffer: (out callee-allocates): a pointer to hold the #GstBuffer, + * returns #GST_FLOW_ERROR if %NULL. + * + * When @pad is flushing this function returns #GST_FLOW_WRONG_STATE + * immediately and @buffer is %NULL. + * + * Calls the getrange function of @pad, see #GstPadGetRangeFunction for a + * description of a getrange function. If @pad has no getrange function + * installed (see gst_pad_set_getrange_function()) this function returns + * #GST_FLOW_NOT_SUPPORTED. + * + * This is a lowlevel function. Usualy gst_pad_pull_range() is used. + * + * Returns: a #GstFlowReturn from the pad. + * + * MT safe. + */ +GstFlowReturn +gst_pad_get_range (GstPad * pad, guint64 offset, guint size, + GstBuffer ** buffer) +{ + g_return_val_if_fail (GST_IS_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_PAD_IS_SRC (pad), GST_FLOW_ERROR); + g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); + + return gst_pad_get_range_unchecked (pad, offset, size, buffer); +} + +/** + * gst_pad_pull_range: + * @pad: a sink #GstPad, returns GST_FLOW_ERROR if not. + * @offset: The start offset of the buffer + * @size: The length of the buffer + * @buffer: (out callee-allocates): a pointer to hold the #GstBuffer, returns + * GST_FLOW_ERROR if %NULL. + * + * Pulls a @buffer from the peer pad. + * + * This function will first trigger the pad block signal if it was + * installed. + * + * When @pad is not linked #GST_FLOW_NOT_LINKED is returned else this + * function returns the result of gst_pad_get_range() on the peer pad. + * See gst_pad_get_range() for a list of return values and for the + * semantics of the arguments of this function. + * + * Returns: a #GstFlowReturn from the peer pad. + * When this function returns #GST_FLOW_OK, @buffer will contain a valid + * #GstBuffer that should be freed with gst_buffer_unref() after usage. + * @buffer may not be used or freed when any other return value than + * #GST_FLOW_OK is returned. + * + * MT safe. + */ +GstFlowReturn +gst_pad_pull_range (GstPad * pad, guint64 offset, guint size, + GstBuffer ** buffer) +{ + GstPad *peer; + GstFlowReturn ret; + gboolean needs_events; + + g_return_val_if_fail (GST_IS_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (GST_PAD_IS_SINK (pad), GST_FLOW_ERROR); + g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); + + GST_OBJECT_LOCK (pad); + if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad))) + goto flushing; + + PROBE (pad, GST_PROBE_TYPE_PULL | GST_PROBE_TYPE_BLOCK, NULL, + pre_probe_stopped); + + if (G_UNLIKELY ((peer = GST_PAD_PEER (pad)) == NULL)) + goto not_linked; + + gst_object_ref (peer); + pad->priv->using++; + GST_OBJECT_UNLOCK (pad); + + ret = gst_pad_get_range_unchecked (peer, offset, size, buffer); + + gst_object_unref (peer); + + GST_OBJECT_LOCK (pad); + pad->priv->using--; + if (pad->priv->using == 0) { + /* pad is not active anymore, trigger idle callbacks */ + PROBE (pad, GST_PROBE_TYPE_PULL | GST_PROBE_TYPE_IDLE, NULL, + post_probe_stopped); + } + + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto pull_range_failed; + + PROBE (pad, GST_PROBE_TYPE_PULL | GST_PROBE_TYPE_BUFFER, buffer, + post_probe_stopped); + + needs_events = GST_PAD_NEEDS_EVENTS (pad); + if (G_UNLIKELY (needs_events)) { + GST_OBJECT_FLAG_UNSET (pad, GST_PAD_NEED_EVENTS); + + GST_DEBUG_OBJECT (pad, "we need to update the events"); + ret = gst_pad_update_events (pad); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto events_error; + } + GST_OBJECT_UNLOCK (pad); + + return ret; + + /* ERROR recovery here */ +flushing: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "pullrange, but pad was flushing"); + GST_OBJECT_UNLOCK (pad); + return GST_FLOW_WRONG_STATE; + } +pre_probe_stopped: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, "pre probe returned %s", + gst_flow_get_name (ret)); + GST_OBJECT_UNLOCK (pad); + return ret; + } +not_linked: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "pulling range, but it was not linked"); + GST_OBJECT_UNLOCK (pad); + return GST_FLOW_NOT_LINKED; + } +pull_range_failed: + { + *buffer = NULL; + GST_OBJECT_UNLOCK (pad); + GST_CAT_LEVEL_LOG (GST_CAT_SCHEDULING, + (ret >= GST_FLOW_UNEXPECTED) ? GST_LEVEL_INFO : GST_LEVEL_WARNING, + pad, "pullrange failed, flow: %s", gst_flow_get_name (ret)); + return ret; + } +post_probe_stopped: + { + GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, + "post probe returned %s", gst_flow_get_name (ret)); + GST_OBJECT_UNLOCK (pad); + if (ret == GST_FLOW_OK) + gst_buffer_unref (*buffer); + *buffer = NULL; + return ret; + } +events_error: + { + GST_OBJECT_UNLOCK (pad); + gst_buffer_unref (*buffer); + *buffer = NULL; + GST_CAT_WARNING_OBJECT (GST_CAT_SCHEDULING, pad, + "pullrange returned events that were not accepted"); + return ret; + } +} + +/** + * gst_pad_push_event: + * @pad: a #GstPad to push the event to. + * @event: (transfer full): the #GstEvent to send to the pad. + * + * Sends the event to the peer of the given pad. This function is + * mainly used by elements to send events to their peer + * elements. + * + * This function takes owership of the provided event so you should + * gst_event_ref() it if you want to reuse the event after this call. + * + * Returns: TRUE if the event was handled. + * + * MT safe. + */ +gboolean +gst_pad_push_event (GstPad * pad, GstEvent * event) +{ + GstFlowReturn ret; + GstPad *peerpad; + gboolean result; + gboolean stored = FALSE; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + g_return_val_if_fail (GST_IS_EVENT (event), FALSE); + + GST_OBJECT_LOCK (pad); + + peerpad = GST_PAD_PEER (pad); + + /* Two checks to be made: + * . (un)set the FLUSHING flag for flushing events, + * . handle pad blocking */ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + GST_PAD_SET_FLUSHING (pad); + + if (G_UNLIKELY (GST_PAD_IS_BLOCKED (pad))) { + /* flush start will have set the FLUSHING flag and will then + * unlock all threads doing a GCond wait on the blocking pad. This + * will typically unblock the STREAMING thread blocked on a pad. */ + GST_LOG_OBJECT (pad, "Pad is blocked, not forwarding flush-start, " + "doing block signal."); + GST_PAD_BLOCK_BROADCAST (pad); + goto flushed; + } + break; + case GST_EVENT_FLUSH_STOP: + GST_PAD_UNSET_FLUSHING (pad); + if (G_UNLIKELY (GST_PAD_IS_BLOCKED (pad))) { + GST_LOG_OBJECT (pad, "Pad is blocked, not forwarding flush-stop"); + goto flushed; + } + break; + default: + { + /* store the event on the pad, but only on srcpads */ + if (GST_PAD_IS_SRC (pad) && GST_EVENT_IS_STICKY (event)) { + guint idx; + + idx = GST_EVENT_STICKY_IDX (event); + GST_LOG_OBJECT (pad, "storing sticky event %s at index %u", + GST_EVENT_TYPE_NAME (event), idx); + + /* srcpad sticky events always become active immediately */ + gst_event_replace (&pad->priv->events[idx].event, event); + + stored = TRUE; + } + + /* backwards compatibility mode for caps */ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CAPS: + { + GST_OBJECT_UNLOCK (pad); + + g_object_notify_by_pspec ((GObject *) pad, pspec_caps); + + GST_OBJECT_LOCK (pad); + /* the peerpad might have changed. Things we checked above could not + * have changed. */ + peerpad = GST_PAD_PEER (pad); + break; + } + case GST_EVENT_SEGMENT: + { + gint64 offset; + + offset = pad->offset; + /* check if we need to adjust the segment */ + if (offset != 0 && (peerpad != NULL)) { + GstSegment segment; + + /* copy segment values */ + gst_event_copy_segment (event, &segment); + gst_event_unref (event); + + /* adjust and make a new event with the offset applied */ + segment.base += offset; + event = gst_event_new_segment (&segment); + } + break; + } + case GST_EVENT_RECONFIGURE: + if (GST_PAD_IS_SINK (pad)) + GST_OBJECT_FLAG_SET (pad, GST_PAD_NEED_RECONFIGURE); + break; + default: + break; + } + + if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad))) + goto flushed; + + PROBE (pad, GST_PROBE_TYPE_PUSH | GST_PROBE_TYPE_EVENT + | GST_PROBE_TYPE_BLOCK, event, probe_stopped); + + break; + } + } + + /* send probes after modifying the events above */ + PROBE (pad, GST_PROBE_TYPE_PUSH | GST_PROBE_TYPE_EVENT, event, probe_stopped); + + /* now check the peer pad */ + if (peerpad == NULL) + goto not_linked; + + gst_object_ref (peerpad); + pad->priv->using++; + GST_OBJECT_UNLOCK (pad); + + GST_LOG_OBJECT (pad, "sending event %p (%s) to peerpad %" GST_PTR_FORMAT, + event, GST_EVENT_TYPE_NAME (event), peerpad); + + result = gst_pad_send_event (peerpad, event); + + /* Note: we gave away ownership of the event at this point but we can still + * print the old pointer */ + GST_LOG_OBJECT (pad, + "sent event %p to peerpad %" GST_PTR_FORMAT ", result %d", event, peerpad, + result); + + gst_object_unref (peerpad); + + GST_OBJECT_LOCK (pad); + pad->priv->using--; + if (pad->priv->using == 0) { + /* pad is not active anymore, trigger idle callbacks */ + PROBE (pad, GST_PROBE_TYPE_PUSH | GST_PROBE_TYPE_IDLE, NULL, probe_stopped); + } + GST_OBJECT_UNLOCK (pad); + + return result | stored; + + /* ERROR handling */ +flushed: + { + GST_DEBUG_OBJECT (pad, "We're flushing"); + GST_OBJECT_UNLOCK (pad); + gst_event_unref (event); + return stored; + } +probe_stopped: + { + GST_DEBUG_OBJECT (pad, "Probe returned %s", gst_flow_get_name (ret)); + GST_OBJECT_UNLOCK (pad); + gst_event_unref (event); + return stored; + } +not_linked: + { + GST_DEBUG_OBJECT (pad, "Dropping event because pad is not linked"); + GST_OBJECT_UNLOCK (pad); + gst_event_unref (event); + return stored; + } +} + +/** + * gst_pad_send_event: + * @pad: a #GstPad to send the event to. + * @event: (transfer full): the #GstEvent to send to the pad. + * + * Sends the event to the pad. This function can be used + * by applications to send events in the pipeline. + * + * If @pad is a source pad, @event should be an upstream event. If @pad is a + * sink pad, @event should be a downstream event. For example, you would not + * send a #GST_EVENT_EOS on a src pad; EOS events only propagate downstream. + * Furthermore, some downstream events have to be serialized with data flow, + * like EOS, while some can travel out-of-band, like #GST_EVENT_FLUSH_START. If + * the event needs to be serialized with data flow, this function will take the + * pad's stream lock while calling its event function. + * + * To find out whether an event type is upstream, downstream, or downstream and + * serialized, see #GstEventTypeFlags, gst_event_type_get_flags(), + * #GST_EVENT_IS_UPSTREAM, #GST_EVENT_IS_DOWNSTREAM, and + * #GST_EVENT_IS_SERIALIZED. Note that in practice that an application or + * plugin doesn't need to bother itself with this information; the core handles + * all necessary locks and checks. + * + * This function takes owership of the provided event so you should + * gst_event_ref() it if you want to reuse the event after this call. + * + * Returns: TRUE if the event was handled. + */ +gboolean +gst_pad_send_event (GstPad * pad, GstEvent * event) +{ + GstFlowReturn ret; + gboolean result = FALSE; + gboolean serialized, need_unlock = FALSE, needs_events, sticky; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + GST_OBJECT_LOCK (pad); + if (GST_PAD_IS_SINK (pad)) { + if (G_UNLIKELY (!GST_EVENT_IS_DOWNSTREAM (event))) + goto wrong_direction; + serialized = GST_EVENT_IS_SERIALIZED (event); + sticky = GST_EVENT_IS_STICKY (event); + } else if (GST_PAD_IS_SRC (pad)) { + if (G_UNLIKELY (!GST_EVENT_IS_UPSTREAM (event))) + goto wrong_direction; + /* events on srcpad never are serialized and sticky */ + serialized = sticky = FALSE; + } else + goto unknown_direction; + + /* get the flag first, we clear it when we have a FLUSH or a non-serialized + * event. */ + needs_events = GST_PAD_NEEDS_EVENTS (pad); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + GST_CAT_DEBUG_OBJECT (GST_CAT_EVENT, pad, + "have event type %d (FLUSH_START)", GST_EVENT_TYPE (event)); + + /* can't even accept a flush begin event when flushing */ + if (GST_PAD_IS_FLUSHING (pad)) + goto flushing; + + GST_PAD_SET_FLUSHING (pad); + GST_CAT_DEBUG_OBJECT (GST_CAT_EVENT, pad, "set flush flag"); + needs_events = FALSE; + break; + case GST_EVENT_FLUSH_STOP: + if (G_LIKELY (GST_PAD_ACTIVATE_MODE (pad) != GST_ACTIVATE_NONE)) { + GST_PAD_UNSET_FLUSHING (pad); + GST_CAT_DEBUG_OBJECT (GST_CAT_EVENT, pad, "cleared flush flag"); + } + GST_OBJECT_UNLOCK (pad); + /* grab stream lock */ + GST_PAD_STREAM_LOCK (pad); + need_unlock = TRUE; + GST_OBJECT_LOCK (pad); + needs_events = FALSE; + break; + case GST_EVENT_RECONFIGURE: + if (GST_PAD_IS_SRC (pad)) + GST_OBJECT_FLAG_SET (pad, GST_PAD_NEED_RECONFIGURE); + default: + GST_CAT_DEBUG_OBJECT (GST_CAT_EVENT, pad, "have event type %s", + GST_EVENT_TYPE_NAME (event)); + + if (serialized) { + /* lock order: STREAM_LOCK, LOCK, recheck flushing. */ + GST_OBJECT_UNLOCK (pad); + GST_PAD_STREAM_LOCK (pad); + need_unlock = TRUE; + GST_OBJECT_LOCK (pad); + } else { + /* don't forward events on non-serialized events */ + needs_events = FALSE; + } + + /* store the event on the pad, but only on srcpads. We need to store the + * event before checking the flushing flag. */ + if (sticky) { + guint idx; + PadEvent *ev; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + if (pad->offset != 0) { + GstSegment segment; + + /* copy segment values */ + gst_event_copy_segment (event, &segment); + gst_event_unref (event); + + /* adjust and make a new event with the offset applied */ + segment.base += pad->offset; + event = gst_event_new_segment (&segment); + } + break; + default: + break; + } + + idx = GST_EVENT_STICKY_IDX (event); + ev = &pad->priv->events[idx]; + + if (ev->event != event) { + GST_LOG_OBJECT (pad, "storing sticky event %s at index %u", + GST_EVENT_TYPE_NAME (event), idx); + gst_event_replace (&ev->pending, event); + /* set the flag so that we update the events next time. We would + * usually update below but we might be flushing too. */ + GST_OBJECT_FLAG_SET (pad, GST_PAD_NEED_EVENTS); + needs_events = TRUE; + } + } + /* now do the probe */ + if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad))) + goto flushing; + + PROBE (pad, GST_PROBE_TYPE_PUSH | GST_PROBE_TYPE_EVENT, event, + probe_stopped); + + break; + } + + if (G_UNLIKELY (needs_events)) { + GstFlowReturn ret; + + GST_OBJECT_FLAG_UNSET (pad, GST_PAD_NEED_EVENTS); + + GST_DEBUG_OBJECT (pad, "need to update all events"); + ret = gst_pad_update_events (pad); + if (ret != GST_FLOW_OK) + goto update_failed; + GST_OBJECT_UNLOCK (pad); + + gst_event_unref (event); + + result = TRUE; + } + + /* ensure to pass on event; + * note that a sticky event has already been updated above */ + if (G_LIKELY (!needs_events || !sticky)) { + GstPadEventFunction eventfunc; + + if (G_UNLIKELY ((eventfunc = GST_PAD_EVENTFUNC (pad)) == NULL)) + goto no_function; + + GST_OBJECT_UNLOCK (pad); + + result = eventfunc (pad, event); + } + + if (need_unlock) + GST_PAD_STREAM_UNLOCK (pad); + + GST_DEBUG_OBJECT (pad, "sent event, result %d", result); + + return result; + + /* ERROR handling */ +wrong_direction: + { + g_warning ("pad %s:%s sending %s event in wrong direction", + GST_DEBUG_PAD_NAME (pad), GST_EVENT_TYPE_NAME (event)); + GST_OBJECT_UNLOCK (pad); + gst_event_unref (event); + return FALSE; + } +unknown_direction: + { + g_warning ("pad %s:%s has invalid direction", GST_DEBUG_PAD_NAME (pad)); + GST_OBJECT_UNLOCK (pad); + gst_event_unref (event); + return FALSE; + } +no_function: + { + g_warning ("pad %s:%s has no event handler, file a bug.", + GST_DEBUG_PAD_NAME (pad)); + GST_OBJECT_UNLOCK (pad); + if (need_unlock) + GST_PAD_STREAM_UNLOCK (pad); + gst_event_unref (event); + return FALSE; + } +flushing: + { + GST_OBJECT_UNLOCK (pad); + if (need_unlock) + GST_PAD_STREAM_UNLOCK (pad); + GST_CAT_INFO_OBJECT (GST_CAT_EVENT, pad, + "Received event on flushing pad. Discarding"); + gst_event_unref (event); + return FALSE; + } +probe_stopped: + { + GST_DEBUG_OBJECT (pad, "probe returned %s", gst_flow_get_name (ret)); + GST_OBJECT_UNLOCK (pad); + if (need_unlock) + GST_PAD_STREAM_UNLOCK (pad); + gst_event_unref (event); + return FALSE; + } +update_failed: + { + GST_OBJECT_UNLOCK (pad); + if (need_unlock) + GST_PAD_STREAM_UNLOCK (pad); + GST_CAT_INFO_OBJECT (GST_CAT_EVENT, pad, "Update events failed"); + gst_event_unref (event); + return FALSE; + } +} + +/** + * gst_pad_set_element_private: + * @pad: the #GstPad to set the private data of. + * @priv: The private data to attach to the pad. + * + * Set the given private data gpointer on the pad. + * This function can only be used by the element that owns the pad. + * No locking is performed in this function. + */ +void +gst_pad_set_element_private (GstPad * pad, gpointer priv) +{ + pad->element_private = priv; +} + +/** + * gst_pad_get_element_private: + * @pad: the #GstPad to get the private data of. + * + * Gets the private data of a pad. + * No locking is performed in this function. + * + * Returns: (transfer none): a #gpointer to the private data. + */ +gpointer +gst_pad_get_element_private (GstPad * pad) +{ + return pad->element_private; +} + +/** + * gst_pad_get_sticky_event: + * @pad: the #GstPad to get the event from. + * @event_type: the #GstEventType that should be retrieved. + * + * Returns a new reference of the sticky event of type @event_type + * from the event. + * + * Returns: (transfer full): a #GstEvent of type @event_type. Unref after usage. + */ +GstEvent * +gst_pad_get_sticky_event (GstPad * pad, GstEventType event_type) +{ + GstEvent *event = NULL; + guint idx; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + g_return_val_if_fail ((event_type & GST_EVENT_TYPE_STICKY) != 0, NULL); + + idx = GST_EVENT_STICKY_IDX_TYPE (event_type); + + GST_OBJECT_LOCK (pad); + if ((event = pad->priv->events[idx].event)) { + gst_event_ref (event); + } + GST_OBJECT_UNLOCK (pad); + + return event; +} + +/** + * gst_pad_sticky_events_foreach: + * @pad: the #GstPad that should be used for iteration. + * @foreach_func: (scope call): the #GstPadStickyEventsForeachFunction that should be called for every event. + * @user_data: (closure): the optional user data. + * + * Iterates all active sticky events on @pad and calls @foreach_func for every + * event. If @foreach_func returns something else than GST_FLOW_OK the iteration + * is immediately stopped. + * + * Returns: GST_FLOW_OK if iteration was successful + */ +GstFlowReturn +gst_pad_sticky_events_foreach (GstPad * pad, + GstPadStickyEventsForeachFunction foreach_func, gpointer user_data) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint i; + GstEvent *event; + + g_return_val_if_fail (GST_IS_PAD (pad), GST_FLOW_ERROR); + g_return_val_if_fail (foreach_func != NULL, GST_FLOW_ERROR); + + GST_OBJECT_LOCK (pad); + +restart: + for (i = 0; i < GST_EVENT_MAX_STICKY; i++) { + gboolean res; + PadEvent *ev; + + ev = &pad->priv->events[i]; + + /* skip without active event */ + if ((event = ev->event) == NULL) + continue; + + gst_event_ref (event); + GST_OBJECT_UNLOCK (pad); + + res = foreach_func (pad, event, user_data); + + GST_OBJECT_LOCK (pad); + gst_event_unref (event); + + if (res != GST_FLOW_OK) { + ret = res; + break; + } + + /* things could have changed while we release the lock, check if we still + * are handling the same event, if we don't something changed and we have + * to try again. FIXME. we need a cookie here. */ + if (event != ev->event) { + GST_DEBUG_OBJECT (pad, "events changed, restarting"); + goto restart; + } + } + GST_OBJECT_UNLOCK (pad); + + return ret; +} + +static void +do_stream_status (GstPad * pad, GstStreamStatusType type, + GThread * thread, GstTask * task) +{ + GstElement *parent; + + GST_DEBUG_OBJECT (pad, "doing stream-status %d", type); + + if ((parent = GST_ELEMENT_CAST (gst_pad_get_parent (pad)))) { + if (GST_IS_ELEMENT (parent)) { + GstMessage *message; + GValue value = { 0 }; + + if (type == GST_STREAM_STATUS_TYPE_ENTER) { + gchar *tname, *ename, *pname; + + /* create a good task name */ + ename = gst_element_get_name (parent); + pname = gst_pad_get_name (pad); + tname = g_strdup_printf ("%s:%s", ename, pname); + g_free (ename); + g_free (pname); + + gst_object_set_name (GST_OBJECT_CAST (task), tname); + g_free (tname); + } + + message = gst_message_new_stream_status (GST_OBJECT_CAST (pad), + type, parent); + + g_value_init (&value, GST_TYPE_TASK); + g_value_set_object (&value, task); + gst_message_set_stream_status_object (message, &value); + g_value_unset (&value); + + GST_DEBUG_OBJECT (pad, "posting stream-status %d", type); + gst_element_post_message (parent, message); + } + gst_object_unref (parent); + } +} + +static void +pad_enter_thread (GstTask * task, GThread * thread, gpointer user_data) +{ + do_stream_status (GST_PAD_CAST (user_data), GST_STREAM_STATUS_TYPE_ENTER, + thread, task); +} + +static void +pad_leave_thread (GstTask * task, GThread * thread, gpointer user_data) +{ + do_stream_status (GST_PAD_CAST (user_data), GST_STREAM_STATUS_TYPE_LEAVE, + thread, task); +} + +static GstTaskThreadCallbacks thr_callbacks = { + pad_enter_thread, + pad_leave_thread, +}; + +/** + * gst_pad_start_task: + * @pad: the #GstPad to start the task of + * @func: the task function to call + * @data: data passed to the task function + * + * Starts a task that repeatedly calls @func with @data. This function + * is mostly used in pad activation functions to start the dataflow. + * The #GST_PAD_STREAM_LOCK of @pad will automatically be acquired + * before @func is called. + * + * Returns: a %TRUE if the task could be started. + */ +gboolean +gst_pad_start_task (GstPad * pad, GstTaskFunction func, gpointer data) +{ + GstTask *task; + gboolean res; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (func != NULL, FALSE); + + GST_DEBUG_OBJECT (pad, "start task"); + + GST_OBJECT_LOCK (pad); + task = GST_PAD_TASK (pad); + if (task == NULL) { + task = gst_task_create (func, data); + gst_task_set_lock (task, GST_PAD_GET_STREAM_LOCK (pad)); + gst_task_set_thread_callbacks (task, &thr_callbacks, pad, NULL); + GST_DEBUG_OBJECT (pad, "created task"); + GST_PAD_TASK (pad) = task; + gst_object_ref (task); + /* release lock to post the message */ + GST_OBJECT_UNLOCK (pad); + + do_stream_status (pad, GST_STREAM_STATUS_TYPE_CREATE, NULL, task); + + gst_object_unref (task); + + GST_OBJECT_LOCK (pad); + /* nobody else is supposed to have changed the pad now */ + if (GST_PAD_TASK (pad) != task) + goto concurrent_stop; + } + res = gst_task_set_state (task, GST_TASK_STARTED); + GST_OBJECT_UNLOCK (pad); + + return res; + + /* ERRORS */ +concurrent_stop: + { + GST_OBJECT_UNLOCK (pad); + return TRUE; + } +} + +/** + * gst_pad_pause_task: + * @pad: the #GstPad to pause the task of + * + * Pause the task of @pad. This function will also wait until the + * function executed by the task is finished if this function is not + * called from the task function. + * + * Returns: a TRUE if the task could be paused or FALSE when the pad + * has no task. + */ +gboolean +gst_pad_pause_task (GstPad * pad) +{ + GstTask *task; + gboolean res; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + GST_DEBUG_OBJECT (pad, "pause task"); + + GST_OBJECT_LOCK (pad); + task = GST_PAD_TASK (pad); + if (task == NULL) + goto no_task; + res = gst_task_set_state (task, GST_TASK_PAUSED); + GST_OBJECT_UNLOCK (pad); + + /* wait for task function to finish, this lock is recursive so it does nothing + * when the pause is called from the task itself */ + GST_PAD_STREAM_LOCK (pad); + GST_PAD_STREAM_UNLOCK (pad); + + return res; + +no_task: + { + GST_DEBUG_OBJECT (pad, "pad has no task"); + GST_OBJECT_UNLOCK (pad); + return FALSE; + } +} + +/** + * gst_pad_stop_task: + * @pad: the #GstPad to stop the task of + * + * Stop the task of @pad. This function will also make sure that the + * function executed by the task will effectively stop if not called + * from the GstTaskFunction. + * + * This function will deadlock if called from the GstTaskFunction of + * the task. Use gst_task_pause() instead. + * + * Regardless of whether the pad has a task, the stream lock is acquired and + * released so as to ensure that streaming through this pad has finished. + * + * Returns: a TRUE if the task could be stopped or FALSE on error. + */ +gboolean +gst_pad_stop_task (GstPad * pad) +{ + GstTask *task; + gboolean res; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + + GST_DEBUG_OBJECT (pad, "stop task"); + + GST_OBJECT_LOCK (pad); + task = GST_PAD_TASK (pad); + if (task == NULL) + goto no_task; + GST_PAD_TASK (pad) = NULL; + res = gst_task_set_state (task, GST_TASK_STOPPED); + GST_OBJECT_UNLOCK (pad); + + GST_PAD_STREAM_LOCK (pad); + GST_PAD_STREAM_UNLOCK (pad); + + if (!gst_task_join (task)) + goto join_failed; + + gst_object_unref (task); + + return res; + +no_task: + { + GST_DEBUG_OBJECT (pad, "no task"); + GST_OBJECT_UNLOCK (pad); + + GST_PAD_STREAM_LOCK (pad); + GST_PAD_STREAM_UNLOCK (pad); + + /* this is not an error */ + return TRUE; + } +join_failed: + { + /* this is bad, possibly the application tried to join the task from + * the task's thread. We install the task again so that it will be stopped + * again from the right thread next time hopefully. */ + GST_OBJECT_LOCK (pad); + GST_DEBUG_OBJECT (pad, "join failed"); + /* we can only install this task if there was no other task */ + if (GST_PAD_TASK (pad) == NULL) + GST_PAD_TASK (pad) = task; + GST_OBJECT_UNLOCK (pad); + + return FALSE; + } +} diff --git a/gst/gstpad.h b/gst/gstpad.h new file mode 100644 index 0000000..d9ed2c5 --- /dev/null +++ b/gst/gstpad.h @@ -0,0 +1,927 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * + * gstpad.h: Header for GstPad object + * + * 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_PAD_H__ +#define __GST_PAD_H__ + +#include <gst/gstconfig.h> + +typedef struct _GstPad GstPad; +typedef struct _GstPadPrivate GstPadPrivate; +typedef struct _GstPadClass GstPadClass; + +/** + * GstPadDirection: + * @GST_PAD_UNKNOWN: direction is unknown. + * @GST_PAD_SRC: the pad is a source pad. + * @GST_PAD_SINK: the pad is a sink pad. + * + * The direction of a pad. + */ +typedef enum { + GST_PAD_UNKNOWN, + GST_PAD_SRC, + GST_PAD_SINK +} GstPadDirection; + +#include <gst/gstobject.h> +#include <gst/gstbuffer.h> +#include <gst/gstbufferlist.h> +#include <gst/gstcaps.h> +#include <gst/gstpadtemplate.h> +#include <gst/gstevent.h> +#include <gst/gstquery.h> +#include <gst/gsttask.h> + +G_BEGIN_DECLS + +/* + * Pad base class + */ +#define GST_TYPE_PAD (gst_pad_get_type ()) +#define GST_IS_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PAD)) +#define GST_IS_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PAD)) +#define GST_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PAD, GstPad)) +#define GST_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PAD, GstPadClass)) +#define GST_PAD_CAST(obj) ((GstPad*)(obj)) + + + +/** + * GstPadLinkReturn: + * @GST_PAD_LINK_OK : link succeeded + * @GST_PAD_LINK_WRONG_HIERARCHY: pads have no common grandparent + * @GST_PAD_LINK_WAS_LINKED : pad was already linked + * @GST_PAD_LINK_WRONG_DIRECTION: pads have wrong direction + * @GST_PAD_LINK_NOFORMAT : pads do not have common format + * @GST_PAD_LINK_NOSCHED : pads cannot cooperate in scheduling + * @GST_PAD_LINK_REFUSED : refused for some reason + * + * Result values from gst_pad_link and friends. + */ +typedef enum { + GST_PAD_LINK_OK = 0, + GST_PAD_LINK_WRONG_HIERARCHY = -1, + GST_PAD_LINK_WAS_LINKED = -2, + GST_PAD_LINK_WRONG_DIRECTION = -3, + GST_PAD_LINK_NOFORMAT = -4, + GST_PAD_LINK_NOSCHED = -5, + GST_PAD_LINK_REFUSED = -6 +} GstPadLinkReturn; + +/** + * GST_PAD_LINK_FAILED: + * @ret: the #GstPadLinkReturn value + * + * Macro to test if the given #GstPadLinkReturn value indicates a failed + * link step. + */ +#define GST_PAD_LINK_FAILED(ret) ((ret) < GST_PAD_LINK_OK) + +/** + * GST_PAD_LINK_SUCCESSFUL: + * @ret: the #GstPadLinkReturn value + * + * Macro to test if the given #GstPadLinkReturn value indicates a successful + * link step. + */ +#define GST_PAD_LINK_SUCCESSFUL(ret) ((ret) >= GST_PAD_LINK_OK) + +/** + * GstFlowReturn: + * @GST_FLOW_RESEND: Resend buffer, possibly with new caps (not + * sent yet) (unused/unimplemented). + * @GST_FLOW_OK: Data passing was ok. + * @GST_FLOW_NOT_LINKED: Pad is not linked. + * @GST_FLOW_WRONG_STATE: Pad is in wrong state. + * @GST_FLOW_UNEXPECTED: Did not expect anything, like after EOS. + * @GST_FLOW_NOT_NEGOTIATED: Pad is not negotiated. + * @GST_FLOW_ERROR: Some (fatal) error occured. Element generating + * this error should post an error message with more + * details. + * @GST_FLOW_NOT_SUPPORTED: This operation is not supported. + * @GST_FLOW_CUSTOM_SUCCESS: Elements can use values starting from + * this (and higher) to define custom success + * codes. Since 0.10.7. + * @GST_FLOW_CUSTOM_SUCCESS_1: Pre-defined custom success code (define your + * custom success code to this to avoid compiler + * warnings). Since 0.10.29. + * @GST_FLOW_CUSTOM_SUCCESS_2: Pre-defined custom success code. Since 0.10.29. + * @GST_FLOW_CUSTOM_ERROR: Elements can use values starting from + * this (and lower) to define custom error codes. + * Since 0.10.7. + * @GST_FLOW_CUSTOM_ERROR_1: Pre-defined custom error code (define your + * custom error code to this to avoid compiler + * warnings). Since 0.10.29. + * @GST_FLOW_CUSTOM_ERROR_2: Pre-defined custom error code. Since 0.10.29. + * + * The result of passing data to a pad. + * + * Note that the custom return values should not be exposed outside of the + * element scope and are available since 0.10.7. + */ +/* FIXME 0.11: remove custom flow returns */ +typedef enum { + /* custom success starts here */ + GST_FLOW_CUSTOM_SUCCESS_2 = 102, + GST_FLOW_CUSTOM_SUCCESS_1 = 101, + GST_FLOW_CUSTOM_SUCCESS = 100, + + /* core predefined */ + GST_FLOW_RESEND = 1, + GST_FLOW_OK = 0, + /* expected failures */ + GST_FLOW_NOT_LINKED = -1, + GST_FLOW_WRONG_STATE = -2, + /* error cases */ + GST_FLOW_UNEXPECTED = -3, + GST_FLOW_NOT_NEGOTIATED = -4, + GST_FLOW_ERROR = -5, + GST_FLOW_NOT_SUPPORTED = -6, + + /* custom error starts here */ + GST_FLOW_CUSTOM_ERROR = -100, + GST_FLOW_CUSTOM_ERROR_1 = -101, + GST_FLOW_CUSTOM_ERROR_2 = -102 +} GstFlowReturn; + +const gchar* gst_flow_get_name (GstFlowReturn ret); +GQuark gst_flow_to_quark (GstFlowReturn ret); + +/** + * GstPadLinkCheck: + * @GST_PAD_LINK_CHECK_NOTHING: Don't check hierarchy or caps compatibility. + * @GST_PAD_LINK_CHECK_HIERARCHY: Check the pads have same parents/grandparents. + * Could be omitted if it is already known that the two elements that own the + * pads are in the same bin. + * @GST_PAD_LINK_CHECK_TEMPLATE_CAPS: Check if the pads are compatible by using + * their template caps. This is much faster than @GST_PAD_LINK_CHECK_CAPS, but + * would be unsafe e.g. if one pad has %GST_CAPS_ANY. + * @GST_PAD_LINK_CHECK_CAPS: Check if the pads are compatible by comparing the + * caps returned by gst_pad_get_caps(). + * + * The amount of checking to be done when linking pads. @GST_PAD_LINK_CHECK_CAPS + * and @GST_PAD_LINK_CHECK_TEMPLATE_CAPS are mutually exclusive. If both are + * specified, expensive but safe @GST_PAD_LINK_CHECK_CAPS are performed. + * + * <warning><para> + * Only disable some of the checks if you are 100% certain you know the link + * will not fail because of hierarchy/caps compatibility failures. If uncertain, + * use the default checks (%GST_PAD_LINK_CHECK_DEFAULT) or the regular methods + * for linking the pads. + * </para></warning> + * + * Since: 0.10.30 + */ + +typedef enum { + GST_PAD_LINK_CHECK_NOTHING = 0, + GST_PAD_LINK_CHECK_HIERARCHY = 1 << 0, + GST_PAD_LINK_CHECK_TEMPLATE_CAPS = 1 << 1, + GST_PAD_LINK_CHECK_CAPS = 1 << 2 +} GstPadLinkCheck; + +/** + * GST_PAD_LINK_CHECK_DEFAULT: + * + * The default checks done when linking pads (i.e. the ones used by + * gst_pad_link()). + * + * Since: 0.10.30 + */ +#define GST_PAD_LINK_CHECK_DEFAULT ((GstPadLinkCheck) (GST_PAD_LINK_CHECK_HIERARCHY | GST_PAD_LINK_CHECK_CAPS)) + +/** + * GstActivateMode: + * @GST_ACTIVATE_NONE: Pad will not handle dataflow + * @GST_ACTIVATE_PUSH: Pad handles dataflow in downstream push mode + * @GST_ACTIVATE_PULL: Pad handles dataflow in upstream pull mode + * + * The status of a GstPad. After activating a pad, which usually happens when the + * parent element goes from READY to PAUSED, the GstActivateMode defines if the + * pad operates in push or pull mode. + */ +typedef enum { + GST_ACTIVATE_NONE, + GST_ACTIVATE_PUSH, + GST_ACTIVATE_PULL +} GstActivateMode; + +/* pad states */ +/** + * GstPadActivateFunction: + * @pad: a #GstPad + * + * This function is called when the pad is activated during the element + * READY to PAUSED state change. By default this function will call the + * activate function that puts the pad in push mode but elements can + * override this function to activate the pad in pull mode if they wish. + * + * Returns: TRUE if the pad could be activated. + */ +typedef gboolean (*GstPadActivateFunction) (GstPad *pad); +/** + * GstPadActivateModeFunction: + * @pad: a #GstPad + * @active: activate or deactivate the pad. + * + * The prototype of the push and pull activate functions. + * + * Returns: TRUE if the pad could be activated or deactivated. + */ +typedef gboolean (*GstPadActivateModeFunction) (GstPad *pad, gboolean active); + + +/* data passing */ +/** + * GstPadChainFunction: + * @pad: the sink #GstPad that performed the chain. + * @buffer: the #GstBuffer that is chained, not %NULL. + * + * A function that will be called on sinkpads when chaining buffers. + * The function typically processes the data contained in the buffer and + * either consumes the data or passes it on to the internally linked pad(s). + * + * The implementer of this function receives a refcount to @buffer and should + * gst_buffer_unref() when the buffer is no longer needed. + * + * When a chain function detects an error in the data stream, it must post an + * error on the bus and return an appropriate #GstFlowReturn value. + * + * Returns: #GST_FLOW_OK for success + */ +typedef GstFlowReturn (*GstPadChainFunction) (GstPad *pad, GstBuffer *buffer); + +/** + * GstPadChainListFunction: + * @pad: the sink #GstPad that performed the chain. + * @list: the #GstBufferList that is chained, not %NULL. + * + * A function that will be called on sinkpads when chaining buffer lists. + * The function typically processes the data contained in the buffer list and + * either consumes the data or passes it on to the internally linked pad(s). + * + * The implementer of this function receives a refcount to @list and + * should gst_buffer_list_unref() when the list is no longer needed. + * + * When a chainlist function detects an error in the data stream, it must + * post an error on the bus and return an appropriate #GstFlowReturn value. + * + * Returns: #GST_FLOW_OK for success + */ +typedef GstFlowReturn (*GstPadChainListFunction) (GstPad *pad, GstBufferList *list); + +/** + * GstPadGetRangeFunction: + * @pad: the src #GstPad to perform the getrange on. + * @offset: the offset of the range + * @length: the length of the range + * @buffer: a memory location to hold the result buffer, cannot be NULL. + * + * This function will be called on source pads when a peer element + * request a buffer at the specified @offset and @length. If this function + * returns #GST_FLOW_OK, the result buffer will be stored in @buffer. The + * contents of @buffer is invalid for any other return value. + * + * This function is installed on a source pad with + * gst_pad_set_getrange_function() and can only be called on source pads after + * they are successfully activated with gst_pad_activate_pull(). + * + * @offset and @length are always given in byte units. @offset must normally be a value + * between 0 and the length in bytes of the data available on @pad. The + * length (duration in bytes) can be retrieved with a #GST_QUERY_DURATION or with a + * #GST_QUERY_SEEKING. + * + * Any @offset larger or equal than the length will make the function return + * #GST_FLOW_UNEXPECTED, which corresponds to EOS. In this case @buffer does not + * contain a valid buffer. + * + * The buffer size of @buffer will only be smaller than @length when @offset is + * near the end of the stream. In all other cases, the size of @buffer must be + * exactly the requested size. + * + * It is allowed to call this function with a 0 @length and valid @offset, in + * which case @buffer will contain a 0-sized buffer and the function returns + * #GST_FLOW_OK. + * + * When this function is called with a -1 @offset, the sequentially next buffer + * of length @length in the stream is returned. + * + * When this function is called with a -1 @length, a buffer with a default + * optimal length is returned in @buffer. The length might depend on the value + * of @offset. + * + * Returns: #GST_FLOW_OK for success and a valid buffer in @buffer. Any other + * return value leaves @buffer undefined. + */ +typedef GstFlowReturn (*GstPadGetRangeFunction) (GstPad *pad, guint64 offset, + guint length, GstBuffer **buffer); + +/** + * GstPadEventFunction: + * @pad: the #GstPad to handle the event. + * @event: the #GstEvent to handle. + * + * Function signature to handle an event for the pad. + * + * Returns: TRUE if the pad could handle the event. + */ +typedef gboolean (*GstPadEventFunction) (GstPad *pad, GstEvent *event); + + +/* internal links */ +/** + * GstPadIterIntLinkFunction: + * @pad: The #GstPad to query. + * + * The signature of the internal pad link iterator function. + * + * Returns: a new #GstIterator that will iterate over all pads that are + * linked to the given pad on the inside of the parent element. + * + * the caller must call gst_iterator_free() after usage. + * + * Since 0.10.21 + */ +typedef GstIterator* (*GstPadIterIntLinkFunction) (GstPad *pad); + +/* generic query function */ +/** + * GstPadQueryTypeFunction: + * @pad: a #GstPad to query + * + * The signature of the query types function. + * + * Returns: a constant array of query types + */ +typedef const GstQueryType* (*GstPadQueryTypeFunction) (GstPad *pad); + +/** + * GstPadQueryFunction: + * @pad: the #GstPad to query. + * @query: the #GstQuery object to execute + * + * The signature of the query function. + * + * Returns: TRUE if the query could be performed. + */ +typedef gboolean (*GstPadQueryFunction) (GstPad *pad, GstQuery *query); + + +/* linking */ +/** + * GstPadLinkFunction + * @pad: the #GstPad that is linked. + * @peer: the peer #GstPad of the link + * + * Function signature to handle a new link on the pad. + * + * Returns: the result of the link with the specified peer. + */ +typedef GstPadLinkReturn (*GstPadLinkFunction) (GstPad *pad, GstPad *peer); +/** + * GstPadUnlinkFunction + * @pad: the #GstPad that is linked. + * + * Function signature to handle a unlinking the pad prom its peer. + */ +typedef void (*GstPadUnlinkFunction) (GstPad *pad); + + +/* caps nego */ +/** + * GstPadGetCapsFunction: + * @pad: the #GstPad to get the capabilities of. + * @filter: filter #GstCaps. + * + * When called on sinkpads @filter contains the caps that + * upstream could produce in the order preferred by upstream. When + * called on srcpads @filter contains the caps accepted by + * downstream in the preffered order. @filter might be %NULL but if + * it is not %NULL only a subset of @filter must be returned. + * + * Returns a copy of the capabilities of the specified pad. By default this + * function will return the pad template capabilities, but can optionally + * be overridden by elements. + * + * Returns: a newly allocated copy #GstCaps of the pad. + */ +typedef GstCaps* (*GstPadGetCapsFunction) (GstPad *pad, GstCaps *filter); + +/** + * GstPadAcceptCapsFunction: + * @pad: the #GstPad to check + * @caps: the #GstCaps to check + * + * Check if @pad can accept @caps. By default this function will see if @caps + * intersect with the result from gst_pad_get_caps() by can be overridden to + * perform extra checks. + * + * Returns: TRUE if the caps can be accepted by the pad. + */ +typedef gboolean (*GstPadAcceptCapsFunction) (GstPad *pad, GstCaps *caps); +/** + * GstPadFixateCapsFunction: + * @pad: a #GstPad + * @caps: the #GstCaps to fixate + * + * Given possibly unfixed caps @caps, let @pad use its default preferred + * format to make a fixed caps. @caps should be writable. By default this + * function will pick the first value of any ranges or lists in the caps but + * elements can override this function to perform other behaviour. + */ +typedef void (*GstPadFixateCapsFunction) (GstPad *pad, GstCaps *caps); + +/* misc */ +/** + * GstPadForwardFunction: + * @pad: the #GstPad that is forwarded. + * @user_data: the gpointer to optional user data. + * + * A forward function is called for all internally linked pads, see + * gst_pad_forward(). + * + * Returns: TRUE if the dispatching procedure has to be stopped. + */ +typedef gboolean (*GstPadForwardFunction) (GstPad *pad, gpointer user_data); + +/** + * GstProbeType: + * @GST_PROBE_TYPE_INVALID: invalid probe type + * @GST_PROBE_TYPE_IDLE: probe idle pads and block + * @GST_PROBE_TYPE_BLOCK: probe and block pads + * @GST_PROBE_TYPE_BUFFER: probe buffers + * @GST_PROBE_TYPE_BUFFER_LIST: probe buffer lists + * @GST_PROBE_TYPE_EVENT: probe events + * @GST_PROBE_TYPE_PUSH: probe push + * @GST_PROBE_TYPE_PULL: probe pull + * + * The different probing types that can occur. When either one of + * @GST_PROBE_TYPE_IDLE or @GST_PROBE_TYPE_BLOCK is used, the probe will be a + * blocking probe. + */ +typedef enum +{ + GST_PROBE_TYPE_INVALID = 0, + /* flags to control blocking */ + GST_PROBE_TYPE_IDLE = (1 << 0), + GST_PROBE_TYPE_BLOCK = (1 << 1), + /* flags to select datatypes */ + GST_PROBE_TYPE_BUFFER = (1 << 2), + GST_PROBE_TYPE_BUFFER_LIST = (1 << 3), + GST_PROBE_TYPE_EVENT = (1 << 4), + /* flags to select scheduling mode */ + GST_PROBE_TYPE_PUSH = (1 << 5), + GST_PROBE_TYPE_PULL = (1 << 6), +} GstProbeType; + +#define GST_PROBE_TYPE_BLOCKING (GST_PROBE_TYPE_IDLE | GST_PROBE_TYPE_BLOCK) +#define GST_PROBE_TYPE_DATA (GST_PROBE_TYPE_BUFFER | GST_PROBE_TYPE_EVENT | \ + GST_PROBE_TYPE_BUFFER_LIST) +#define GST_PROBE_TYPE_SCHEDULING (GST_PROBE_TYPE_PUSH | GST_PROBE_TYPE_PULL) + +/** + * GstProbeReturn: + * @GST_PROBE_OK: normal probe return value + * @GST_PROBE_DROP: drop data in data probes + * @GST_PROBE_REMOVE: remove probe + * @GST_PROBE_PASS: pass the data item in the block probe and block on + * the next item + * + * Different return values for the #GstPadProbeCallback. + */ +typedef enum +{ + GST_PROBE_DROP, + GST_PROBE_OK, + GST_PROBE_REMOVE, + GST_PROBE_PASS, +} GstProbeReturn; + +/** + * GstPadProbeCallback + * @pad: the #GstPad that is blocked + * @type: the current probe type + * @type_data: type specific data + * @user_data: the gpointer to optional user data. + * + * Callback used by gst_pad_add_probe(). Gets called to notify about the current + * blocking type. + */ +typedef GstProbeReturn (*GstPadProbeCallback) (GstPad *pad, GstProbeType type, + gpointer type_data, gpointer user_data); + +/** + * GstPadStickyEventsForeachFunction: + * @pad: the #GstPad. + * @event: the sticky #GstEvent. + * @user_data: the #gpointer to optional user data. + * + * Callback used by gst_pad_sticky_events_foreach(). + * + * Returns: GST_FLOW_OK if the iteration should continue + */ +typedef GstFlowReturn (*GstPadStickyEventsForeachFunction) (GstPad *pad, GstEvent *event, gpointer user_data); + +/** + * GstPadFlags: + * @GST_PAD_BLOCKED: is dataflow on a pad blocked + * @GST_PAD_FLUSHING: is pad refusing buffers + * @GST_PAD_IN_GETCAPS: GstPadGetCapsFunction() is running now + * @GST_PAD_BLOCKING: is pad currently blocking on a buffer or event + * @GST_PAD_NEED_RECONFIGURE: the pad should be reconfigured/renegotiated. + * The flag has to be unset manually after + * reconfiguration happened. + * Since: 0.10.34. + * @GST_PAD_NEED_EVENTS: the pad has pending events + * @GST_PAD_FIXED_CAPS: the pad is using fixed caps this means that once the + * caps are set on the pad, the getcaps function only + * returns those caps. + * @GST_PAD_FLAG_LAST: offset to define more flags + * + * Pad state flags + */ +typedef enum { + GST_PAD_BLOCKED = (GST_OBJECT_FLAG_LAST << 0), + GST_PAD_FLUSHING = (GST_OBJECT_FLAG_LAST << 1), + GST_PAD_IN_GETCAPS = (GST_OBJECT_FLAG_LAST << 2), + GST_PAD_BLOCKING = (GST_OBJECT_FLAG_LAST << 4), + GST_PAD_NEED_RECONFIGURE = (GST_OBJECT_FLAG_LAST << 5), + GST_PAD_NEED_EVENTS = (GST_OBJECT_FLAG_LAST << 6), + GST_PAD_FIXED_CAPS = (GST_OBJECT_FLAG_LAST << 7), + /* padding */ + GST_PAD_FLAG_LAST = (GST_OBJECT_FLAG_LAST << 16) +} GstPadFlags; + +/** + * GstPad: + * @element_private: private data owned by the parent element + * @padtemplate: padtemplate for this pad + * @direction: the direction of the pad, cannot change after creating + * the pad. + * @stream_rec_lock: recursive stream lock of the pad, used to protect + * the data used in streaming. + * @task: task for this pad if the pad is actively driving dataflow. + * @block_cond: conditional to signal pad block + * @probes: installed probes + * @getcapsfunc: function to get caps of the pad + * @acceptcapsfunc: function to check if pad can accept caps + * @fixatecapsfunc: function to fixate caps + * @mode: current activation mode of the pad + * @activatefunc: pad activation function + * @activatepushfunc: function to activate/deactivate pad in push mode + * @activatepullfunc: function to activate/deactivate pad in pull mode + * @peer: the pad this pad is linked to + * @linkfunc: function called when pad is linked + * @unlinkfunc: function called when pad is unlinked + * @chainfunc: function to chain buffer to pad + * @chainlistfunc: function to chain a list of buffers to pad + * @getrangefunc: function to get a range of data from a pad + * @eventfunc: function to send an event to a pad + * @offset: the pad offset + * @querytypefunc: get list of supported queries + * @queryfunc: perform a query on the pad + * @iterintlinkfunc: get the internal links iterator of this pad + * + * The #GstPad structure. Use the functions to update the variables. + */ +struct _GstPad { + GstObject object; + + /*< public >*/ + gpointer element_private; + + GstPadTemplate *padtemplate; + + GstPadDirection direction; + + /*< public >*/ /* with STREAM_LOCK */ + /* streaming rec_lock */ + GStaticRecMutex stream_rec_lock; + GstTask *task; + + /*< public >*/ /* with LOCK */ + /* block cond, mutex is from the object */ + GCond *block_cond; + GHookList probes; + + /* the pad capabilities */ + GstPadGetCapsFunction getcapsfunc; + GstPadAcceptCapsFunction acceptcapsfunc; + GstPadFixateCapsFunction fixatecapsfunc; + + GstActivateMode mode; + GstPadActivateFunction activatefunc; + GstPadActivateModeFunction activatepushfunc; + GstPadActivateModeFunction activatepullfunc; + + /* pad link */ + GstPad *peer; + GstPadLinkFunction linkfunc; + GstPadUnlinkFunction unlinkfunc; + + /* data transport functions */ + GstPadChainFunction chainfunc; + GstPadChainListFunction chainlistfunc; + GstPadGetRangeFunction getrangefunc; + GstPadEventFunction eventfunc; + + /* pad offset */ + gint64 offset; + + /* generic query method */ + GstPadQueryTypeFunction querytypefunc; + GstPadQueryFunction queryfunc; + + /* internal links */ + GstPadIterIntLinkFunction iterintlinkfunc; + + /*< private >*/ + /* counts number of probes attached. */ + gint num_probes; + gint num_blocked; + + GstPadPrivate *priv; + + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstPadClass { + GstObjectClass parent_class; + + /* signal callbacks */ + void (*linked) (GstPad *pad, GstPad *peer); + void (*unlinked) (GstPad *pad, GstPad *peer); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + + +/***** helper macros *****/ +/* GstPad */ +#define GST_PAD_NAME(pad) (GST_OBJECT_NAME(pad)) +#define GST_PAD_PARENT(pad) (GST_ELEMENT_CAST(GST_OBJECT_PARENT(pad))) +#define GST_PAD_ELEMENT_PRIVATE(pad) (GST_PAD_CAST(pad)->element_private) +#define GST_PAD_PAD_TEMPLATE(pad) (GST_PAD_CAST(pad)->padtemplate) +#define GST_PAD_DIRECTION(pad) (GST_PAD_CAST(pad)->direction) +#define GST_PAD_TASK(pad) (GST_PAD_CAST(pad)->task) +#define GST_PAD_ACTIVATE_MODE(pad) (GST_PAD_CAST(pad)->mode) + +#define GST_PAD_ACTIVATEFUNC(pad) (GST_PAD_CAST(pad)->activatefunc) +#define GST_PAD_ACTIVATEPUSHFUNC(pad) (GST_PAD_CAST(pad)->activatepushfunc) +#define GST_PAD_ACTIVATEPULLFUNC(pad) (GST_PAD_CAST(pad)->activatepullfunc) +#define GST_PAD_CHAINFUNC(pad) (GST_PAD_CAST(pad)->chainfunc) +#define GST_PAD_CHAINLISTFUNC(pad) (GST_PAD_CAST(pad)->chainlistfunc) +#define GST_PAD_GETRANGEFUNC(pad) (GST_PAD_CAST(pad)->getrangefunc) +#define GST_PAD_EVENTFUNC(pad) (GST_PAD_CAST(pad)->eventfunc) +#define GST_PAD_QUERYTYPEFUNC(pad) (GST_PAD_CAST(pad)->querytypefunc) +#define GST_PAD_QUERYFUNC(pad) (GST_PAD_CAST(pad)->queryfunc) +#define GST_PAD_ITERINTLINKFUNC(pad) (GST_PAD_CAST(pad)->iterintlinkfunc) + +#define GST_PAD_PEER(pad) (GST_PAD_CAST(pad)->peer) +#define GST_PAD_LINKFUNC(pad) (GST_PAD_CAST(pad)->linkfunc) +#define GST_PAD_UNLINKFUNC(pad) (GST_PAD_CAST(pad)->unlinkfunc) + +#define GST_PAD_GETCAPSFUNC(pad) (GST_PAD_CAST(pad)->getcapsfunc) +#define GST_PAD_ACCEPTCAPSFUNC(pad) (GST_PAD_CAST(pad)->acceptcapsfunc) +#define GST_PAD_FIXATECAPSFUNC(pad) (GST_PAD_CAST(pad)->fixatecapsfunc) + +#define GST_PAD_IS_SRC(pad) (GST_PAD_DIRECTION(pad) == GST_PAD_SRC) +#define GST_PAD_IS_SINK(pad) (GST_PAD_DIRECTION(pad) == GST_PAD_SINK) + +#define GST_PAD_IS_LINKED(pad) (GST_PAD_PEER(pad) != NULL) + +#define GST_PAD_IS_ACTIVE(pad) (GST_PAD_ACTIVATE_MODE(pad) != GST_ACTIVATE_NONE) + +#define GST_PAD_IS_BLOCKED(pad) (GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_BLOCKED)) +#define GST_PAD_IS_BLOCKING(pad) (GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_BLOCKING)) +#define GST_PAD_IS_FLUSHING(pad) (GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FLUSHING)) +#define GST_PAD_IS_IN_GETCAPS(pad) (GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_IN_GETCAPS)) +#define GST_PAD_NEEDS_RECONFIGURE(pad) (GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_NEED_RECONFIGURE)) +#define GST_PAD_NEEDS_EVENTS(pad) (GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_NEED_EVENTS)) +#define GST_PAD_IS_FIXED_CAPS(pad) (GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FIXED_CAPS)) + +#define GST_PAD_SET_FLUSHING(pad) (GST_OBJECT_FLAG_SET (pad, GST_PAD_FLUSHING)) +#define GST_PAD_UNSET_FLUSHING(pad) (GST_OBJECT_FLAG_UNSET (pad, GST_PAD_FLUSHING)) + +/** + * GST_PAD_GET_STREAM_LOCK: + * @pad: a #GstPad + * + * Get the stream lock of @pad. The stream lock is protecting the + * resources used in the data processing functions of @pad. + */ +#define GST_PAD_GET_STREAM_LOCK(pad) (&(GST_PAD_CAST(pad)->stream_rec_lock)) +/** + * GST_PAD_STREAM_LOCK: + * @pad: a #GstPad + * + * Lock the stream lock of @pad. + */ +#define GST_PAD_STREAM_LOCK(pad) (g_static_rec_mutex_lock(GST_PAD_GET_STREAM_LOCK(pad))) +/** + * GST_PAD_STREAM_LOCK_FULL: + * @pad: a #GstPad + * @t: the number of times to recursively lock + * + * Lock the stream lock of @pad @t times. + */ +#define GST_PAD_STREAM_LOCK_FULL(pad,t) (g_static_rec_mutex_lock_full(GST_PAD_GET_STREAM_LOCK(pad), t)) +/** + * GST_PAD_STREAM_TRYLOCK: + * @pad: a #GstPad + * + * Try to Lock the stream lock of the pad, return TRUE if the lock could be + * taken. + */ +#define GST_PAD_STREAM_TRYLOCK(pad) (g_static_rec_mutex_trylock(GST_PAD_GET_STREAM_LOCK(pad))) +/** + * GST_PAD_STREAM_UNLOCK: + * @pad: a #GstPad + * + * Unlock the stream lock of @pad. + */ +#define GST_PAD_STREAM_UNLOCK(pad) (g_static_rec_mutex_unlock(GST_PAD_GET_STREAM_LOCK(pad))) +/** + * GST_PAD_STREAM_UNLOCK_FULL: + * @pad: a #GstPad + * + * Fully unlock the recursive stream lock of @pad, return the number of times + * @pad was locked. + */ +#define GST_PAD_STREAM_UNLOCK_FULL(pad) (g_static_rec_mutex_unlock_full(GST_PAD_GET_STREAM_LOCK(pad))) + +#define GST_PAD_BLOCK_GET_COND(pad) (GST_PAD_CAST(pad)->block_cond) +#define GST_PAD_BLOCK_WAIT(pad) (g_cond_wait(GST_PAD_BLOCK_GET_COND (pad), GST_OBJECT_GET_LOCK (pad))) +#define GST_PAD_BLOCK_SIGNAL(pad) (g_cond_signal(GST_PAD_BLOCK_GET_COND (pad))) +#define GST_PAD_BLOCK_BROADCAST(pad) (g_cond_broadcast(GST_PAD_BLOCK_GET_COND (pad))) + +GType gst_pad_get_type (void); + +/* creating pads */ +GstPad* gst_pad_new (const gchar *name, GstPadDirection direction); +GstPad* gst_pad_new_from_template (GstPadTemplate *templ, const gchar *name); +GstPad* gst_pad_new_from_static_template (GstStaticPadTemplate *templ, const gchar *name); + + +/** + * gst_pad_get_name: + * @pad: the pad to get the name from + * + * Get a copy of the name of the pad. g_free() after usage. + * + * MT safe. + */ +#define gst_pad_get_name(pad) gst_object_get_name (GST_OBJECT_CAST (pad)) +/** + * gst_pad_get_parent: + * @pad: the pad to get the parent of + * + * Get the parent of @pad. This function increases the refcount + * of the parent object so you should gst_object_unref() it after usage. + * Can return NULL if the pad did not have a parent. + * + * MT safe. + */ +#define gst_pad_get_parent(pad) gst_object_get_parent (GST_OBJECT_CAST (pad)) + +GstPadDirection gst_pad_get_direction (GstPad *pad); + +gboolean gst_pad_set_active (GstPad *pad, gboolean active); +gboolean gst_pad_is_active (GstPad *pad); +gboolean gst_pad_activate_pull (GstPad *pad, gboolean active); +gboolean gst_pad_activate_push (GstPad *pad, gboolean active); + +gulong gst_pad_add_probe (GstPad *pad, + GstProbeType mask, + GstPadProbeCallback callback, + gpointer user_data, + GDestroyNotify destroy_data); +void gst_pad_remove_probe (GstPad *pad, gulong id); + +gboolean gst_pad_is_blocked (GstPad *pad); +gboolean gst_pad_is_blocking (GstPad *pad); + +void gst_pad_mark_reconfigure (GstPad *pad); +gboolean gst_pad_check_reconfigure (GstPad *pad); + +void gst_pad_set_element_private (GstPad *pad, gpointer priv); +gpointer gst_pad_get_element_private (GstPad *pad); + +GstPadTemplate* gst_pad_get_pad_template (GstPad *pad); + +GstEvent* gst_pad_get_sticky_event (GstPad *pad, GstEventType event_type); +GstFlowReturn gst_pad_sticky_events_foreach (GstPad *pad, GstPadStickyEventsForeachFunction foreach_func, gpointer user_data); + +/* data passing setup functions */ +void gst_pad_set_activate_function (GstPad *pad, GstPadActivateFunction activate); +void gst_pad_set_activatepull_function (GstPad *pad, GstPadActivateModeFunction activatepull); +void gst_pad_set_activatepush_function (GstPad *pad, GstPadActivateModeFunction activatepush); +void gst_pad_set_chain_function (GstPad *pad, GstPadChainFunction chain); +void gst_pad_set_chain_list_function (GstPad *pad, GstPadChainListFunction chainlist); +void gst_pad_set_getrange_function (GstPad *pad, GstPadGetRangeFunction get); +void gst_pad_set_event_function (GstPad *pad, GstPadEventFunction event); + +/* pad links */ +void gst_pad_set_link_function (GstPad *pad, GstPadLinkFunction link); +void gst_pad_set_unlink_function (GstPad *pad, GstPadUnlinkFunction unlink); + +gboolean gst_pad_can_link (GstPad *srcpad, GstPad *sinkpad); +GstPadLinkReturn gst_pad_link (GstPad *srcpad, GstPad *sinkpad); +GstPadLinkReturn gst_pad_link_full (GstPad *srcpad, GstPad *sinkpad, GstPadLinkCheck flags); +gboolean gst_pad_unlink (GstPad *srcpad, GstPad *sinkpad); +gboolean gst_pad_is_linked (GstPad *pad); + +GstPad* gst_pad_get_peer (GstPad *pad); + +/* capsnego functions */ +void gst_pad_set_getcaps_function (GstPad *pad, GstPadGetCapsFunction getcaps); +void gst_pad_set_acceptcaps_function (GstPad *pad, GstPadAcceptCapsFunction acceptcaps); +void gst_pad_set_fixatecaps_function (GstPad *pad, GstPadFixateCapsFunction fixatecaps); + +GstCaps* gst_pad_get_pad_template_caps (GstPad *pad); + +/* capsnego function for linked/unlinked pads */ +GstCaps * gst_pad_get_current_caps (GstPad * pad); +gboolean gst_pad_has_current_caps (GstPad * pad); +GstCaps * gst_pad_get_caps (GstPad * pad, GstCaps *filter); +void gst_pad_fixate_caps (GstPad * pad, GstCaps *caps); +gboolean gst_pad_accept_caps (GstPad * pad, GstCaps *caps); +gboolean gst_pad_set_caps (GstPad * pad, GstCaps *caps); + +GstCaps * gst_pad_peer_get_caps (GstPad * pad, GstCaps *filter); +gboolean gst_pad_peer_accept_caps (GstPad * pad, GstCaps *caps); + +/* capsnego for linked pads */ +GstCaps * gst_pad_get_allowed_caps (GstPad * pad); + +/* pad offsets */ +gint64 gst_pad_get_offset (GstPad *pad); +void gst_pad_set_offset (GstPad *pad, gint64 offset); + +/* data passing functions to peer */ +GstFlowReturn gst_pad_push (GstPad *pad, GstBuffer *buffer); +GstFlowReturn gst_pad_push_list (GstPad *pad, GstBufferList *list); +GstFlowReturn gst_pad_pull_range (GstPad *pad, guint64 offset, guint size, + GstBuffer **buffer); +gboolean gst_pad_push_event (GstPad *pad, GstEvent *event); +gboolean gst_pad_event_default (GstPad *pad, GstEvent *event); + +/* data passing functions on pad */ +GstFlowReturn gst_pad_chain (GstPad *pad, GstBuffer *buffer); +GstFlowReturn gst_pad_chain_list (GstPad *pad, GstBufferList *list); +GstFlowReturn gst_pad_get_range (GstPad *pad, guint64 offset, guint size, + GstBuffer **buffer); +gboolean gst_pad_send_event (GstPad *pad, GstEvent *event); + +/* pad tasks */ +gboolean gst_pad_start_task (GstPad *pad, GstTaskFunction func, + gpointer data); +gboolean gst_pad_pause_task (GstPad *pad); +gboolean gst_pad_stop_task (GstPad *pad); + +/* internal links */ +void gst_pad_set_iterate_internal_links_function (GstPad * pad, + GstPadIterIntLinkFunction iterintlink); +GstIterator * gst_pad_iterate_internal_links (GstPad * pad); +GstIterator * gst_pad_iterate_internal_links_default (GstPad * pad); + + +/* generic query function */ +void gst_pad_set_query_type_function (GstPad *pad, GstPadQueryTypeFunction type_func); +const GstQueryType* gst_pad_get_query_types (GstPad *pad); +const GstQueryType* gst_pad_get_query_types_default (GstPad *pad); + +gboolean gst_pad_query (GstPad *pad, GstQuery *query); +gboolean gst_pad_peer_query (GstPad *pad, GstQuery *query); +void gst_pad_set_query_function (GstPad *pad, GstPadQueryFunction query); +gboolean gst_pad_query_default (GstPad *pad, GstQuery *query); + +/* misc helper functions */ +gboolean gst_pad_forward (GstPad *pad, GstPadForwardFunction forward, + gpointer user_data); + +G_END_DECLS + +#endif /* __GST_PAD_H__ */ diff --git a/gst/gstpadtemplate.c b/gst/gstpadtemplate.c new file mode 100644 index 0000000..bf8d576 --- /dev/null +++ b/gst/gstpadtemplate.c @@ -0,0 +1,456 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstpadtemplate.c: Templates for pad creation + * + * 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:gstpadtemplate + * @short_description: Describe the media type of a pad. + * @see_also: #GstPad, #GstElementFactory + * + * Padtemplates describe the possible media types a pad or an elementfactory can + * handle. This allows for both inspection of handled types before loading the + * element plugin as well as identifying pads on elements that are not yet + * created (request or sometimes pads). + * + * Pad and PadTemplates have #GstCaps attached to it to describe the media type + * they are capable of dealing with. gst_pad_template_get_caps() or + * GST_PAD_TEMPLATE_CAPS() are used to get the caps of a padtemplate. It's not + * possible to modify the caps of a padtemplate after creation. + * + * PadTemplates have a #GstPadPresence property which identifies the lifetime + * of the pad and that can be retrieved with GST_PAD_TEMPLATE_PRESENCE(). Also + * the direction of the pad can be retrieved from the #GstPadTemplate with + * GST_PAD_TEMPLATE_DIRECTION(). + * + * The GST_PAD_TEMPLATE_NAME_TEMPLATE () is important for GST_PAD_REQUEST pads + * because it has to be used as the name in the gst_element_get_request_pad() + * call to instantiate a pad from this template. + * + * Padtemplates can be created with gst_pad_template_new() or with + * gst_static_pad_template_get (), which creates a #GstPadTemplate from a + * #GstStaticPadTemplate that can be filled with the + * convenient GST_STATIC_PAD_TEMPLATE() macro. + * + * A padtemplate can be used to create a pad (see gst_pad_new_from_template() + * or gst_pad_new_from_static_template ()) or to add to an element class + * (see gst_element_class_add_pad_template ()). + * + * The following code example shows the code to create a pad from a padtemplate. + * <example> + * <title>Create a pad from a padtemplate</title> + * <programlisting> + * GstStaticPadTemplate my_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 + * "audio/x-raw-int, " + * "channels = (int) [ 1, 6 ]" + * ) + * ); + * void + * my_method (void) + * { + * GstPad *pad; + * pad = gst_pad_new_from_static_template (&my_template, "sink"); + * ... + * } + * </programlisting> + * </example> + * + * The following example shows you how to add the padtemplate to an + * element class, this is usually done in the class_init of the class: + * <informalexample> + * <programlisting> + * static void + * my_element_class_init (GstMyElementClass *klass) + * { + * GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + * + * gst_element_class_add_pad_template (gstelement_class, + * gst_static_pad_template_get (&my_template)); + * } + * </programlisting> + * </informalexample> + * + * Last reviewed on 2006-02-14 (0.10.3) + */ + +#include "gst_private.h" + +#include "gstpad.h" +#include "gstpadtemplate.h" +#include "gstenumtypes.h" +#include "gstmarshal.h" +#include "gstutils.h" +#include "gstinfo.h" +#include "gsterror.h" +#include "gstvalue.h" + +#define GST_CAT_DEFAULT GST_CAT_PADS + +enum +{ + PROP_NAME_TEMPLATE = 1, + PROP_DIRECTION, + PROP_PRESENCE, + PROP_CAPS +}; + +enum +{ + TEMPL_PAD_CREATED, + /* FILL ME */ + LAST_SIGNAL +}; + +static GstObject *parent_class = NULL; +static guint gst_pad_template_signals[LAST_SIGNAL] = { 0 }; + +static void gst_pad_template_dispose (GObject * object); +static void gst_pad_template_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_pad_template_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +G_DEFINE_TYPE (GstPadTemplate, gst_pad_template, GST_TYPE_OBJECT); + +static void +gst_pad_template_class_init (GstPadTemplateClass * klass) +{ + GObjectClass *gobject_class; + GstObjectClass *gstobject_class; + + gobject_class = (GObjectClass *) klass; + gstobject_class = (GstObjectClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + /** + * GstPadTemplate::pad-created: + * @pad_template: the object which received the signal. + * @pad: the pad that was created. + * + * This signal is fired when an element creates a pad from this template. + */ + gst_pad_template_signals[TEMPL_PAD_CREATED] = + g_signal_new ("pad-created", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstPadTemplateClass, pad_created), + NULL, NULL, gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_PAD); + + gobject_class->dispose = gst_pad_template_dispose; + + gobject_class->get_property = gst_pad_template_get_property; + gobject_class->set_property = gst_pad_template_set_property; + + /** + * GstPadTemplate:name-template + * + * The name template of the pad template. + * + * Since: 0.10.21 + */ + g_object_class_install_property (gobject_class, PROP_NAME_TEMPLATE, + g_param_spec_string ("name-template", "Name template", + "The name template of the pad template", NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /** + * GstPadTemplate:direction + * + * The direction of the pad described by the pad template. + * + * Since: 0.10.21 + */ + g_object_class_install_property (gobject_class, PROP_DIRECTION, + g_param_spec_enum ("direction", "Direction", + "The direction of the pad described by the pad template", + GST_TYPE_PAD_DIRECTION, GST_PAD_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /** + * GstPadTemplate:presence + * + * When the pad described by the pad template will become available. + * + * Since: 0.10.21 + */ + g_object_class_install_property (gobject_class, PROP_PRESENCE, + g_param_spec_enum ("presence", "Presence", + "When the pad described by the pad template will become available", + GST_TYPE_PAD_PRESENCE, GST_PAD_ALWAYS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /** + * GstPadTemplate:caps + * + * The capabilities of the pad described by the pad template. + * + * Since: 0.10.21 + */ + g_object_class_install_property (gobject_class, PROP_CAPS, + g_param_spec_boxed ("caps", "Caps", + "The capabilities of the pad described by the pad template", + GST_TYPE_CAPS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + gstobject_class->path_string_separator = "*"; +} + +static void +gst_pad_template_init (GstPadTemplate * templ) +{ +} + +static void +gst_pad_template_dispose (GObject * object) +{ + GstPadTemplate *templ = GST_PAD_TEMPLATE (object); + + g_free (GST_PAD_TEMPLATE_NAME_TEMPLATE (templ)); + if (GST_PAD_TEMPLATE_CAPS (templ)) { + gst_caps_unref (GST_PAD_TEMPLATE_CAPS (templ)); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/* ALWAYS padtemplates cannot have conversion specifications (like src_%d), + * since it doesn't make sense. + * SOMETIMES padtemplates can do whatever they want, they are provided by the + * element. + * REQUEST padtemplates can be reverse-parsed (the user asks for 'sink1', the + * 'sink%d' template is automatically selected), so we need to restrict their + * naming. + */ +static gboolean +name_is_valid (const gchar * name, GstPadPresence presence) +{ + const gchar *str; + + if (presence == GST_PAD_ALWAYS) { + if (strchr (name, '%')) { + g_warning ("invalid name template %s: conversion specifications are not" + " allowed for GST_PAD_ALWAYS padtemplates", name); + return FALSE; + } + } else if (presence == GST_PAD_REQUEST) { + if ((str = strchr (name, '%')) && strchr (str + 1, '%')) { + g_warning ("invalid name template %s: only one conversion specification" + " allowed in GST_PAD_REQUEST padtemplate", name); + return FALSE; + } + if (str && (*(str + 1) != 's' && *(str + 1) != 'd' && *(str + 1) != 'u')) { + g_warning ("invalid name template %s: conversion specification must be of" + " type '%%d', '%%u' or '%%s' for GST_PAD_REQUEST padtemplate", name); + return FALSE; + } + if (str && (*(str + 2) != '\0')) { + g_warning ("invalid name template %s: conversion specification must" + " appear at the end of the GST_PAD_REQUEST padtemplate name", name); + return FALSE; + } + } + + return TRUE; +} + +GType +gst_static_pad_template_get_type (void) +{ + static GType staticpadtemplate_type = 0; + + if (G_UNLIKELY (staticpadtemplate_type == 0)) { + staticpadtemplate_type = + g_pointer_type_register_static ("GstStaticPadTemplate"); + } + return staticpadtemplate_type; +} + +/** + * gst_static_pad_template_get: + * @pad_template: the static pad template + * + * Converts a #GstStaticPadTemplate into a #GstPadTemplate. + * + * Returns: (transfer full): a new #GstPadTemplate. + */ +/* FIXME0.11: rename to gst_pad_template_new_from_static_pad_template() */ +GstPadTemplate * +gst_static_pad_template_get (GstStaticPadTemplate * pad_template) +{ + GstPadTemplate *new; + GstCaps *caps; + + if (!name_is_valid (pad_template->name_template, pad_template->presence)) + return NULL; + + caps = gst_static_caps_get (&pad_template->static_caps); + + new = g_object_new (gst_pad_template_get_type (), + "name", pad_template->name_template, + "name-template", pad_template->name_template, + "direction", pad_template->direction, + "presence", pad_template->presence, "caps", caps, NULL); + + gst_caps_unref (caps); + + return new; +} + +/** + * gst_pad_template_new: + * @name_template: the name template. + * @direction: the #GstPadDirection of the template. + * @presence: the #GstPadPresence of the pad. + * @caps: a #GstCaps set for the template. + * + * Creates a new pad template with a name according to the given template + * and with the given arguments. + * + * Returns: (transfer full): a new #GstPadTemplate. + */ +GstPadTemplate * +gst_pad_template_new (const gchar * name_template, + GstPadDirection direction, GstPadPresence presence, GstCaps * caps) +{ + GstPadTemplate *new; + + g_return_val_if_fail (name_template != NULL, NULL); + g_return_val_if_fail (caps != NULL, NULL); + g_return_val_if_fail (direction == GST_PAD_SRC + || direction == GST_PAD_SINK, NULL); + g_return_val_if_fail (presence == GST_PAD_ALWAYS + || presence == GST_PAD_SOMETIMES || presence == GST_PAD_REQUEST, NULL); + + if (!name_is_valid (name_template, presence)) { + return NULL; + } + + new = g_object_new (gst_pad_template_get_type (), + "name", name_template, "name-template", name_template, + "direction", direction, "presence", presence, "caps", caps, NULL); + + return new; +} + +/** + * gst_static_pad_template_get_caps: + * @templ: a #GstStaticPadTemplate to get capabilities of. + * + * Gets the capabilities of the static pad template. + * + * Returns: (transfer full): the #GstCaps of the static pad template. + * Unref after usage. Since the core holds an additional + * ref to the returned caps, use gst_caps_make_writable() + * on the returned caps to modify it. + */ +GstCaps * +gst_static_pad_template_get_caps (GstStaticPadTemplate * templ) +{ + g_return_val_if_fail (templ, NULL); + + return (GstCaps *) gst_static_caps_get (&templ->static_caps); +} + +/** + * gst_pad_template_get_caps: + * @templ: a #GstPadTemplate to get capabilities of. + * + * Gets the capabilities of the pad template. + * + * Returns: (transfer full): the #GstCaps of the pad template. + * Unref after usage. + */ +GstCaps * +gst_pad_template_get_caps (GstPadTemplate * templ) +{ + GstCaps *caps; + g_return_val_if_fail (GST_IS_PAD_TEMPLATE (templ), NULL); + + caps = GST_PAD_TEMPLATE_CAPS (templ); + + return (caps ? gst_caps_ref (caps) : NULL); +} + +/** + * gst_pad_template_pad_created: + * @templ: a #GstPadTemplate that has been created + * @pad: the #GstPad that created it + * + * Emit the pad-created signal for this template when created by this pad. + */ +void +gst_pad_template_pad_created (GstPadTemplate * templ, GstPad * pad) +{ + g_signal_emit (templ, gst_pad_template_signals[TEMPL_PAD_CREATED], 0, pad); +} + +static void +gst_pad_template_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + /* these properties are all construct-only */ + switch (prop_id) { + case PROP_NAME_TEMPLATE: + GST_PAD_TEMPLATE_NAME_TEMPLATE (object) = g_value_dup_string (value); + break; + case PROP_DIRECTION: + GST_PAD_TEMPLATE_DIRECTION (object) = + (GstPadDirection) g_value_get_enum (value); + break; + case PROP_PRESENCE: + GST_PAD_TEMPLATE_PRESENCE (object) = + (GstPadPresence) g_value_get_enum (value); + break; + case PROP_CAPS: + GST_PAD_TEMPLATE_CAPS (object) = g_value_dup_boxed (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pad_template_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + /* these properties are all construct-only */ + switch (prop_id) { + case PROP_NAME_TEMPLATE: + g_value_set_string (value, GST_PAD_TEMPLATE_NAME_TEMPLATE (object)); + break; + case PROP_DIRECTION: + g_value_set_enum (value, GST_PAD_TEMPLATE_DIRECTION (object)); + break; + case PROP_PRESENCE: + g_value_set_enum (value, GST_PAD_TEMPLATE_PRESENCE (object)); + break; + case PROP_CAPS: + g_value_set_boxed (value, GST_PAD_TEMPLATE_CAPS (object)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} diff --git a/gst/gstpadtemplate.h b/gst/gstpadtemplate.h new file mode 100644 index 0000000..bc2bcee --- /dev/null +++ b/gst/gstpadtemplate.h @@ -0,0 +1,192 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * + * gstpadtemplate.h: Header for GstPadTemplate object + * + * 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_PAD_TEMPLATE_H__ +#define __GST_PAD_TEMPLATE_H__ + +#include <gst/gstconfig.h> + +typedef struct _GstPadTemplate GstPadTemplate; +typedef struct _GstPadTemplateClass GstPadTemplateClass; +typedef struct _GstStaticPadTemplate GstStaticPadTemplate; + +#include <gst/gstobject.h> +#include <gst/gstbuffer.h> +#include <gst/gstcaps.h> +#include <gst/gstevent.h> +#include <gst/gstquery.h> +#include <gst/gsttask.h> + +G_BEGIN_DECLS + +#define GST_TYPE_STATIC_PAD_TEMPLATE (gst_static_pad_template_get_type ()) + +#define GST_TYPE_PAD_TEMPLATE (gst_pad_template_get_type ()) +#define GST_PAD_TEMPLATE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PAD_TEMPLATE,GstPadTemplate)) +#define GST_PAD_TEMPLATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PAD_TEMPLATE,GstPadTemplateClass)) +#define GST_IS_PAD_TEMPLATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PAD_TEMPLATE)) +#define GST_IS_PAD_TEMPLATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PAD_TEMPLATE)) + +/** + * GstPadPresence: + * @GST_PAD_ALWAYS: the pad is always available + * @GST_PAD_SOMETIMES: the pad will become available depending on the media stream + * @GST_PAD_REQUEST: the pad is only available on request with + * gst_element_get_request_pad(). + * + * Indicates when this pad will become available. + */ +typedef enum { + GST_PAD_ALWAYS, + GST_PAD_SOMETIMES, + GST_PAD_REQUEST +} GstPadPresence; + +/** + * GST_PAD_TEMPLATE_NAME_TEMPLATE: + * @templ: the template to query + * + * Get the nametemplate of the padtemplate. + */ +#define GST_PAD_TEMPLATE_NAME_TEMPLATE(templ) (((GstPadTemplate *)(templ))->name_template) + +/** + * GST_PAD_TEMPLATE_DIRECTION: + * @templ: the template to query + * + * Get the #GstPadDirection of the padtemplate. + */ +#define GST_PAD_TEMPLATE_DIRECTION(templ) (((GstPadTemplate *)(templ))->direction) + +/** + * GST_PAD_TEMPLATE_PRESENCE: + * @templ: the template to query + * + * Get the #GstPadPresence of the padtemplate. + */ +#define GST_PAD_TEMPLATE_PRESENCE(templ) (((GstPadTemplate *)(templ))->presence) + +/** + * GST_PAD_TEMPLATE_CAPS: + * @templ: the template to query + * + * Get a handle to the padtemplate #GstCaps + */ +#define GST_PAD_TEMPLATE_CAPS(templ) (((GstPadTemplate *)(templ))->caps) + +/** + * GstPadTemplateFlags: + * @GST_PAD_TEMPLATE_FLAG_LAST: first flag that can be used by subclasses. + * + * Flags for the padtemplate + */ +typedef enum { + /* padding */ + GST_PAD_TEMPLATE_FLAG_LAST = (GST_OBJECT_FLAG_LAST << 4) +} GstPadTemplateFlags; + +/** + * GST_PAD_TEMPLATE_IS_FIXED: + * @templ: the template to query + * + * Check if the properties of the padtemplate are fixed + */ +#define GST_PAD_TEMPLATE_IS_FIXED(templ) (GST_OBJECT_FLAG_IS_SET(templ, GST_PAD_TEMPLATE_FIXED)) + +/** + * GstPadTemplate: + * + * The padtemplate object. + */ +struct _GstPadTemplate { + GstObject object; + + gchar *name_template; + GstPadDirection direction; + GstPadPresence presence; + GstCaps *caps; + + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstPadTemplateClass { + GstObjectClass parent_class; + + /* signal callbacks */ + void (*pad_created) (GstPadTemplate *templ, GstPad *pad); + + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstStaticPadTemplate: + * @name_template: the name of the template + * @direction: the direction of the template + * @presence: the presence of the template + * @static_caps: the caps of the template. + * + * Structure describing the #GstStaticPadTemplate. + */ +struct _GstStaticPadTemplate { + const gchar *name_template; + GstPadDirection direction; + GstPadPresence presence; + GstStaticCaps static_caps; +}; + +/** + * GST_STATIC_PAD_TEMPLATE: + * @padname: the name template of the pad + * @dir: the GstPadDirection of the pad + * @pres: the GstPadPresence of the pad + * @caps: the GstStaticCaps of the pad + * + * Convenience macro to fill the values of a GstStaticPadTemplate + * structure. + */ +#define GST_STATIC_PAD_TEMPLATE(padname, dir, pres, caps) \ +{ \ + /* name_template */ padname, \ + /* direction */ dir, \ + /* presence */ pres, \ + /* caps */ caps \ +} + +/* templates and factories */ +GType gst_pad_template_get_type (void); +GType gst_static_pad_template_get_type (void); + +GstPadTemplate* gst_pad_template_new (const gchar *name_template, + GstPadDirection direction, GstPadPresence presence, + GstCaps *caps); + +GstPadTemplate * gst_static_pad_template_get (GstStaticPadTemplate *pad_template); +GstCaps* gst_static_pad_template_get_caps (GstStaticPadTemplate *templ); +GstCaps* gst_pad_template_get_caps (GstPadTemplate *templ); + +void gst_pad_template_pad_created (GstPadTemplate * templ, GstPad * pad); + +G_END_DECLS + +#endif /* __GST_PAD_TEMPLATE_H__ */ + diff --git a/gst/gstparamspecs.c b/gst/gstparamspecs.c new file mode 100644 index 0000000..e75a944 --- /dev/null +++ b/gst/gstparamspecs.c @@ -0,0 +1,207 @@ +/* GStreamer - GParamSpecs for some of our types + * Copyright (C) 2007 Tim-Philipp Müller <tim centricular 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. + */ +/** + * SECTION:gstparamspec + * @short_description: GParamSpec implementations specific + * to GStreamer + * + * GParamSpec implementations specific to GStreamer. + * + * Last reviewed on 2008-03-11 (0.10.18) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gst_private.h" +#include "glib-compat-private.h" +#include "gstparamspecs.h" + +/* --- GstParamSpecFraction --- */ + +static void +_gst_param_fraction_init (GParamSpec * pspec) +{ + GstParamSpecFraction *fspec = GST_PARAM_SPEC_FRACTION (pspec); + + fspec->min_num = 0; + fspec->min_den = 1; + fspec->max_num = G_MAXINT; + fspec->max_den = 1; + fspec->def_num = 1; + fspec->def_den = 1; +} + +static void +_gst_param_fraction_set_default (GParamSpec * pspec, GValue * value) +{ + value->data[0].v_int = GST_PARAM_SPEC_FRACTION (pspec)->def_num; + value->data[1].v_int = GST_PARAM_SPEC_FRACTION (pspec)->def_den; +} + +static gboolean +_gst_param_fraction_validate (GParamSpec * pspec, GValue * value) +{ + GstParamSpecFraction *fspec = GST_PARAM_SPEC_FRACTION (pspec); + gboolean within_range = FALSE; + GValue f_this = { 0, }; + GValue f_min = { 0, }; + GValue f_max = { 0, }; + gint res; + + g_value_init (&f_this, GST_TYPE_FRACTION); + gst_value_set_fraction (&f_this, value->data[0].v_int, value->data[1].v_int); + + g_value_init (&f_min, GST_TYPE_FRACTION); + gst_value_set_fraction (&f_min, fspec->min_num, fspec->min_den); + + g_value_init (&f_max, GST_TYPE_FRACTION); + gst_value_set_fraction (&f_max, fspec->max_num, fspec->max_den); + + res = gst_value_compare (&f_min, &f_this); +#ifndef GST_DISABLE_GST_DEBUG + GST_LOG ("comparing %d/%d to %d/%d, result = %d", fspec->min_num, + fspec->min_den, value->data[0].v_int, value->data[1].v_int, res); +#endif + if (res != GST_VALUE_LESS_THAN && res != GST_VALUE_EQUAL) + goto out; + +#ifndef GST_DISABLE_GST_DEBUG + GST_LOG ("comparing %d/%d to %d/%d, result = %d", value->data[0].v_int, + value->data[1].v_int, fspec->max_num, fspec->max_den, res); +#endif + res = gst_value_compare (&f_this, &f_max); + if (res != GST_VALUE_LESS_THAN && res != GST_VALUE_EQUAL) + goto out; + + within_range = TRUE; + +out: + + g_value_unset (&f_min); + g_value_unset (&f_max); + g_value_unset (&f_this); + +#ifndef GST_DISABLE_GST_DEBUG + GST_LOG ("%swithin range", (within_range) ? "" : "not "); +#endif + + /* return FALSE if everything ok, otherwise TRUE */ + return !within_range; +} + +static gint +_gst_param_fraction_values_cmp (GParamSpec * pspec, const GValue * value1, + const GValue * value2) +{ + gint res; + + res = gst_value_compare (value1, value2); + + g_assert (res != GST_VALUE_UNORDERED); + + /* GST_VALUE_LESS_THAN is -1, EQUAL is 0, and GREATER_THAN is 1 */ + return res; +} + +GType +gst_param_spec_fraction_get_type (void) +{ + static GType type; /* 0 */ + + /* register GST_TYPE_PARAM_FRACTION */ + if (type == 0) { + static GParamSpecTypeInfo pspec_info = { + sizeof (GstParamSpecFraction), /* instance_size */ + 0, /* n_preallocs */ + _gst_param_fraction_init, /* instance_init */ + G_TYPE_INVALID, /* value_type */ + NULL, /* finalize */ + _gst_param_fraction_set_default, /* value_set_default */ + _gst_param_fraction_validate, /* value_validate */ + _gst_param_fraction_values_cmp, /* values_cmp */ + }; + pspec_info.value_type = GST_TYPE_FRACTION; + type = g_param_type_register_static ("GstParamFraction", &pspec_info); + } + return type; +} + +/** + * gst_param_spec_fraction: + * @name: canonical name of the property specified + * @nick: nick name for the property specified + * @blurb: description of the property specified + * @min_num: minimum value (fraction numerator) + * @min_denom: minimum value (fraction denominator) + * @max_num: maximum value (fraction numerator) + * @max_denom: maximum value (fraction denominator) + * @default_num: default value (fraction numerator) + * @default_denom: default value (fraction denominator) + * @flags: flags for the property specified + * + * This function creates a fraction GParamSpec for use by objects/elements + * that want to expose properties of fraction type. This function is typically + * used in connection with g_object_class_install_property() in a GObjects's + * instance_init function. + * + * Returns: a newly created parameter specification + * + * Since: 0.10.14 + */ +GParamSpec * +gst_param_spec_fraction (const gchar * name, const gchar * nick, + const gchar * blurb, gint min_num, gint min_denom, gint max_num, + gint max_denom, gint default_num, gint default_denom, GParamFlags flags) +{ + GstParamSpecFraction *fspec; + GParamSpec *pspec; + GValue default_val = { 0, }; + + fspec = + g_param_spec_internal (GST_TYPE_PARAM_FRACTION, name, nick, blurb, flags); + + fspec->min_num = min_num; + fspec->min_den = min_denom; + fspec->max_num = max_num; + fspec->max_den = max_denom; + fspec->def_num = default_num; + fspec->def_den = default_denom; + + pspec = G_PARAM_SPEC (fspec); + + /* check that min <= default <= max */ + g_value_init (&default_val, GST_TYPE_FRACTION); + gst_value_set_fraction (&default_val, default_num, default_denom); + /* validate returns TRUE if the validation fails */ + if (_gst_param_fraction_validate (pspec, &default_val)) { + g_critical ("GstParamSpec of type 'fraction' for property '%s' has a " + "default value of %d/%d, which is not within the allowed range of " + "%d/%d to %d/%d", name, default_num, default_denom, min_num, + min_denom, max_num, max_denom); + g_param_spec_ref (pspec); + g_param_spec_sink (pspec); + g_param_spec_unref (pspec); + pspec = NULL; + } + g_value_unset (&default_val); + + return pspec; +} diff --git a/gst/gstparamspecs.h b/gst/gstparamspecs.h new file mode 100644 index 0000000..c2cdf6f --- /dev/null +++ b/gst/gstparamspecs.h @@ -0,0 +1,128 @@ +/* GStreamer - GParamSpecs for some of our types + * Copyright (C) 2007 Tim-Philipp Müller <tim centricular 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. + */ + +#ifndef __GST_PARAMSPECS_H__ +#define __GST_PARAMSPECS_H__ + +#include <gst/gstvalue.h> + +G_BEGIN_DECLS + +/* --- paramspec flags */ + +/** + * GST_PARAM_CONTROLLABLE: + * + * Use this flag on GObject properties to signal they can make sense to be. + * controlled over time. This hint is used by the GstController. + */ +#define GST_PARAM_CONTROLLABLE (1 << (G_PARAM_USER_SHIFT + 1)) + +/** + * GST_PARAM_MUTABLE_READY: + * + * Use this flag on GObject properties of GstElements to indicate that + * they can be changed when the element is in the READY or lower state. + * + * Since: 0.10.23 + */ +#define GST_PARAM_MUTABLE_READY (1 << (G_PARAM_USER_SHIFT + 2)) + +/** + * GST_PARAM_MUTABLE_PAUSED: + * + * Use this flag on GObject properties of GstElements to indicate that + * they can be changed when the element is in the PAUSED or lower state. + * This flag implies GST_PARAM_MUTABLE_READY. + * + * Since: 0.10.23 + */ +#define GST_PARAM_MUTABLE_PAUSED (1 << (G_PARAM_USER_SHIFT + 3)) + +/** + * GST_PARAM_MUTABLE_PLAYING: + * + * Use this flag on GObject properties of GstElements to indicate that + * they can be changed when the element is in the PLAYING or lower state. + * This flag implies GST_PARAM_MUTABLE_PAUSED. + * + * Since: 0.10.23 + */ +#define GST_PARAM_MUTABLE_PLAYING (1 << (G_PARAM_USER_SHIFT + 4)) + +/** + * GST_PARAM_USER_SHIFT: + * + * Bits based on GST_PARAM_USER_SHIFT can be used by 3rd party applications. + */ +#define GST_PARAM_USER_SHIFT (1 << (G_PARAM_USER_SHIFT + 8)) + + +/* --- type macros --- */ + +#define GST_TYPE_PARAM_FRACTION (gst_param_spec_fraction_get_type ()) +#define GST_IS_PARAM_SPEC_FRACTION(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GST_TYPE_PARAM_FRACTION)) +#define GST_PARAM_SPEC_FRACTION(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GST_TYPE_PARAM_FRACTION, GstParamSpecFraction)) + + +/* --- get_type functions --- */ + +GType gst_param_spec_fraction_get_type (void); + + +/* --- typedefs & structures --- */ + +typedef struct _GstParamSpecFraction GstParamSpecFraction; + +/** + * GstParamSpecFraction: + * @parent_instance: super class + * @min_num: minimal numerator + * @min_den: minimal denominator + * @max_num: maximal numerator + * @max_den: maximal denominator + * @def_num: default numerator + * @def_den: default denominator + * + * A GParamSpec derived structure that contains the meta data for fractional + * properties. + */ +struct _GstParamSpecFraction { + GParamSpec parent_instance; + + gint min_num, min_den; + gint max_num, max_den; + gint def_num, def_den; +}; + + +/* --- GParamSpec prototypes --- */ + +GParamSpec * gst_param_spec_fraction (const gchar * name, + const gchar * nick, + const gchar * blurb, + gint min_num, gint min_denom, + gint max_num, gint max_denom, + gint default_num, gint default_denom, + GParamFlags flags); + +G_END_DECLS + +#endif /* __GST_PARAMSPECS_H__ */ + diff --git a/gst/gstparse.c b/gst/gstparse.c new file mode 100644 index 0000000..f60d758 --- /dev/null +++ b/gst/gstparse.c @@ -0,0 +1,360 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2002 Andy Wingo <wingo@pobox.com> + * 2008 Tim-Philipp Müller <tim centricular net> + * + * gstparse.c: get a pipeline from a text pipeline description + * + * 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:gstparse + * @short_description: Get a pipeline from a text pipeline description + * + * These function allow to create a pipeline based on the syntax used in the + * gst-launch utility (see man-page for syntax documentation). + * + * Please note that these functions take several measures to create + * somewhat dynamic pipelines. Due to that such pipelines are not always + * reusable (set the state to NULL and back to PLAYING). + */ + +#include "gst_private.h" +#include <string.h> + +#include "gstparse.h" +#include "gsterror.h" +#include "gstinfo.h" +#ifndef GST_DISABLE_PARSE +#include "parse/types.h" +#endif + +static void +_prepend_missing_element (gchar * element, GList ** list) +{ + *list = g_list_prepend (*list, g_strdup (element)); +} + +static GstParseContext * +gst_parse_context_copy (const GstParseContext * context) +{ + GstParseContext *ret = NULL; +#ifndef GST_DISABLE_PARSE + + ret = gst_parse_context_new (); + if (context) { + g_list_foreach (context->missing_elements, (GFunc) _prepend_missing_element, + &ret->missing_elements); + ret->missing_elements = g_list_reverse (ret->missing_elements); + } +#endif + return ret; +} + +GType +gst_parse_context_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + type = g_boxed_type_register_static ("GstParseContext", + (GBoxedCopyFunc) gst_parse_context_copy, + (GBoxedFreeFunc) gst_parse_context_free); + } + + return type; +} + +/** + * gst_parse_error_quark: + * + * Get the error quark used by the parsing subsystem. + * + * Returns: the quark of the parse errors. + */ +GQuark +gst_parse_error_quark (void) +{ + static GQuark quark = 0; + + if (!quark) + quark = g_quark_from_static_string ("gst_parse_error"); + return quark; +} + + +/** + * gst_parse_context_new: + * + * Allocates a parse context for use with gst_parse_launch_full() or + * gst_parse_launchv_full(). + * + * Free-function: gst_parse_context_free + * + * Returns: (transfer full): a newly-allocated parse context. Free with + * gst_parse_context_free() when no longer needed. + * + * Since: 0.10.20 + */ +GstParseContext * +gst_parse_context_new (void) +{ +#ifndef GST_DISABLE_PARSE + GstParseContext *ctx; + + ctx = g_slice_new (GstParseContext); + ctx->missing_elements = NULL; + + return ctx; +#else + return NULL; +#endif +} + +/** + * gst_parse_context_free: + * @context: (transfer full): a #GstParseContext + * + * Frees a parse context previously allocated with gst_parse_context_new(). + * + * Since: 0.10.20 + */ +void +gst_parse_context_free (GstParseContext * context) +{ +#ifndef GST_DISABLE_PARSE + if (context) { + g_list_foreach (context->missing_elements, (GFunc) g_free, NULL); + g_list_free (context->missing_elements); + g_slice_free (GstParseContext, context); + } +#endif +} + +/** + * gst_parse_context_get_missing_elements: + * @context: a #GstParseContext + * + * Retrieve missing elements from a previous run of gst_parse_launch_full() + * or gst_parse_launchv_full(). Will only return results if an error code + * of %GST_PARSE_ERROR_NO_SUCH_ELEMENT was returned. + * + * Returns: (transfer full) (array zero-terminated=1) (element-type gchar*): a + * NULL-terminated array of element factory name strings of missing + * elements. Free with g_strfreev() when no longer needed. + * + * Since: 0.10.20 + */ +gchar ** +gst_parse_context_get_missing_elements (GstParseContext * context) +{ +#ifndef GST_DISABLE_PARSE + gchar **arr; + GList *l; + guint len, i; + + g_return_val_if_fail (context != NULL, NULL); + + len = g_list_length (context->missing_elements); + + if (G_UNLIKELY (len == 0)) + return NULL; + + arr = g_new (gchar *, len + 1); + + for (i = 0, l = context->missing_elements; l != NULL; l = l->next, ++i) + arr[i] = g_strdup (l->data); + + arr[i] = NULL; + + return arr; +#else + return NULL; +#endif +} + +#ifndef GST_DISABLE_PARSE +static gchar * +_gst_parse_escape (const gchar * str) +{ + GString *gstr = NULL; + + g_return_val_if_fail (str != NULL, NULL); + + gstr = g_string_sized_new (strlen (str)); + + while (*str) { + if (*str == ' ') + g_string_append_c (gstr, '\\'); + g_string_append_c (gstr, *str); + str++; + } + + return g_string_free (gstr, FALSE); +} +#endif /* !GST_DISABLE_PARSE */ + +/** + * gst_parse_launchv: + * @argv: (in) (array zero-terminated=1): null-terminated array of arguments + * @error: pointer to a #GError + * + * Create a new element based on command line syntax. + * @error will contain an error message if an erroneuos pipeline is specified. + * An error does not mean that the pipeline could not be constructed. + * + * Returns: (transfer full): a new element on success and %NULL on failure. + */ +GstElement * +gst_parse_launchv (const gchar ** argv, GError ** error) +{ + return gst_parse_launchv_full (argv, NULL, GST_PARSE_FLAG_NONE, error); +} + +/** + * gst_parse_launchv_full: + * @argv: (in) (array zero-terminated=1): null-terminated array of arguments + * @context: (allow-none): a parse context allocated with + * gst_parse_context_new(), or %NULL + * @flags: parsing options, or #GST_PARSE_FLAG_NONE + * @error: pointer to a #GError (which must be initialised to %NULL) + * + * Create a new element based on command line syntax. + * @error will contain an error message if an erroneous pipeline is specified. + * An error does not mean that the pipeline could not be constructed. + * + * Returns: (transfer full): a new element on success; on failure, either %NULL + * or a partially-constructed bin or element will be returned and @error will + * be set (unless you passed #GST_PARSE_FLAG_FATAL_ERRORS in @flags, then + * %NULL will always be returned on failure) + * + * Since: 0.10.20 + */ +GstElement * +gst_parse_launchv_full (const gchar ** argv, GstParseContext * context, + GstParseFlags flags, GError ** error) +{ +#ifndef GST_DISABLE_PARSE + GstElement *element; + GString *str; + const gchar **argvp, *arg; + gchar *tmp; + + g_return_val_if_fail (argv != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* let's give it a nice size. */ + str = g_string_sized_new (1024); + + argvp = argv; + while (*argvp) { + arg = *argvp; + tmp = _gst_parse_escape (arg); + g_string_append (str, tmp); + g_free (tmp); + g_string_append_c (str, ' '); + argvp++; + } + + element = gst_parse_launch_full (str->str, context, flags, error); + + g_string_free (str, TRUE); + + return element; +#else + /* gst_parse_launch_full() will set a GST_CORE_ERROR_DISABLED error for us */ + return gst_parse_launch_full ("", NULL, 0, error); +#endif +} + +/** + * gst_parse_launch: + * @pipeline_description: the command line describing the pipeline + * @error: the error message in case of an erroneous pipeline. + * + * Create a new pipeline based on command line syntax. + * Please note that you might get a return value that is not %NULL even though + * the @error is set. In this case there was a recoverable parsing error and you + * can try to play the pipeline. + * + * Returns: (transfer full): a new element on success, %NULL on failure. If + * more than one toplevel element is specified by the @pipeline_description, + * all elements are put into a #GstPipeline, which than is returned. + */ +GstElement * +gst_parse_launch (const gchar * pipeline_description, GError ** error) +{ + return gst_parse_launch_full (pipeline_description, NULL, GST_PARSE_FLAG_NONE, + error); +} + +/** + * gst_parse_launch_full: + * @pipeline_description: the command line describing the pipeline + * @context: (allow-none): a parse context allocated with + * gst_parse_context_new(), or %NULL + * @flags: parsing options, or #GST_PARSE_FLAG_NONE + * @error: the error message in case of an erroneous pipeline. + * + * Create a new pipeline based on command line syntax. + * Please note that you might get a return value that is not %NULL even though + * the @error is set. In this case there was a recoverable parsing error and you + * can try to play the pipeline. + * + * Returns: (transfer full): a new element on success, %NULL on failure. If + * more than one toplevel element is specified by the @pipeline_description, + * all elements are put into a #GstPipeline, which then is returned. + * + * Since: 0.10.20 + */ +GstElement * +gst_parse_launch_full (const gchar * pipeline_description, + GstParseContext * context, GstParseFlags flags, GError ** error) +{ +#ifndef GST_DISABLE_PARSE + GstElement *element; + + g_return_val_if_fail (pipeline_description != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + GST_CAT_INFO (GST_CAT_PIPELINE, "parsing pipeline description '%s'", + pipeline_description); + + element = _gst_parse_launch (pipeline_description, error, context, flags); + + /* don't return partially constructed pipeline if FATAL_ERRORS was given */ + if (G_UNLIKELY (error != NULL && *error != NULL && element != NULL)) { + if ((flags & GST_PARSE_FLAG_FATAL_ERRORS)) { + gst_object_unref (element); + element = NULL; + } + } + + return element; +#else + gchar *msg; + + GST_WARNING ("Disabled API called"); + + msg = gst_error_get_message (GST_CORE_ERROR, GST_CORE_ERROR_DISABLED); + g_set_error (error, GST_CORE_ERROR, GST_CORE_ERROR_DISABLED, "%s", msg); + g_free (msg); + + return NULL; +#endif +} diff --git a/gst/gstparse.h b/gst/gstparse.h new file mode 100644 index 0000000..a0f27b3 --- /dev/null +++ b/gst/gstparse.h @@ -0,0 +1,119 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstparse.h: get a pipeline from a text pipeline description + * + * 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_PARSE_H__ +#define __GST_PARSE_H__ + +#include <gst/gstelement.h> + +G_BEGIN_DECLS + +GQuark gst_parse_error_quark (void); +/** + * GST_PARSE_ERROR: + * + * Get access to the error quark of the parse subsystem. + */ +#define GST_PARSE_ERROR gst_parse_error_quark () + +/** + * GstParseError: + * @GST_PARSE_ERROR_SYNTAX: A syntax error occured. + * @GST_PARSE_ERROR_NO_SUCH_ELEMENT: The description contained an unknown element + * @GST_PARSE_ERROR_NO_SUCH_PROPERTY: An element did not have a specified property + * @GST_PARSE_ERROR_LINK: There was an error linking two pads. + * @GST_PARSE_ERROR_COULD_NOT_SET_PROPERTY: There was an error setting a property + * @GST_PARSE_ERROR_EMPTY_BIN: An empty bin was specified. + * @GST_PARSE_ERROR_EMPTY: An empty description was specified + * + * The different parsing errors that can occur. + */ +typedef enum +{ + GST_PARSE_ERROR_SYNTAX, + GST_PARSE_ERROR_NO_SUCH_ELEMENT, + GST_PARSE_ERROR_NO_SUCH_PROPERTY, + GST_PARSE_ERROR_LINK, + GST_PARSE_ERROR_COULD_NOT_SET_PROPERTY, + GST_PARSE_ERROR_EMPTY_BIN, + GST_PARSE_ERROR_EMPTY +} GstParseError; + +/** + * GstParseFlags: + * @GST_PARSE_FLAG_NONE: Do not use any special parsing options. + * @GST_PARSE_FLAG_FATAL_ERRORS: Always return NULL when an error occurs + * (default behaviour is to return partially constructed bins or elements + * in some cases) + * + * Parsing options. + * + * Since: 0.10.20 + */ +typedef enum +{ + GST_PARSE_FLAG_NONE = 0, + GST_PARSE_FLAG_FATAL_ERRORS = (1 << 0) +} GstParseFlags; + +#define GST_TYPE_PARSE_CONTEXT (gst_parse_context_get_type()) + +/** + * GstParseContext: + * + * Opaque structure. + * + * Since: 0.10.20 + */ +typedef struct _GstParseContext GstParseContext; + +/* create, process and free a parse context */ + +GType gst_parse_context_get_type (void); +GstParseContext * gst_parse_context_new (void); + +gchar ** gst_parse_context_get_missing_elements (GstParseContext * context); + +void gst_parse_context_free (GstParseContext * context); + + +/* parse functions */ + +GstElement * gst_parse_launch (const gchar * pipeline_description, + GError ** error); + +GstElement * gst_parse_launchv (const gchar ** argv, + GError ** error); + +GstElement * gst_parse_launch_full (const gchar * pipeline_description, + GstParseContext * context, + GstParseFlags flags, + GError ** error); + +GstElement * gst_parse_launchv_full (const gchar ** argv, + GstParseContext * context, + GstParseFlags flags, + GError ** error); + +G_END_DECLS + +#endif /* __GST_PARSE_H__ */ diff --git a/gst/gstpipeline.c b/gst/gstpipeline.c new file mode 100644 index 0000000..4efc029 --- /dev/null +++ b/gst/gstpipeline.c @@ -0,0 +1,855 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2004,2005 Wim Taymans <wim@fluendo.com> + * + * gstpipeline.c: Overall pipeline management element + * + * 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:gstpipeline + * @short_description: Top-level bin with clocking and bus management + functionality. + * @see_also: #GstElement, #GstBin, #GstClock, #GstBus + * + * A #GstPipeline is a special #GstBin used as the toplevel container for + * the filter graph. The #GstPipeline will manage the selection and + * distribution of a global #GstClock as well as provide a #GstBus to the + * application. It will also implement a default behaviour for managing + * seek events (see gst_element_seek()). + * + * gst_pipeline_new() is used to create a pipeline. when you are done with + * the pipeline, use gst_object_unref() to free its resources including all + * added #GstElement objects (if not otherwise referenced). + * + * Elements are added and removed from the pipeline using the #GstBin + * methods like gst_bin_add() and gst_bin_remove() (see #GstBin). + * + * Before changing the state of the #GstPipeline (see #GstElement) a #GstBus + * can be retrieved with gst_pipeline_get_bus(). This bus can then be + * used to receive #GstMessage from the elements in the pipeline. + * + * By default, a #GstPipeline will automatically flush the pending #GstBus + * messages when going to the NULL state to ensure that no circular + * references exist when no messages are read from the #GstBus. This + * behaviour can be changed with gst_pipeline_set_auto_flush_bus(). + * + * When the #GstPipeline performs the PAUSED to PLAYING state change it will + * select a clock for the elements. The clock selection algorithm will by + * default select a clock provided by an element that is most upstream + * (closest to the source). For live pipelines (ones that return + * #GST_STATE_CHANGE_NO_PREROLL from the gst_element_set_state() call) this + * will select the clock provided by the live source. For normal pipelines + * this will select a clock provided by the sinks (most likely the audio + * sink). If no element provides a clock, a default #GstSystemClock is used. + * + * The clock selection can be controlled with the gst_pipeline_use_clock() + * method, which will enforce a given clock on the pipeline. With + * gst_pipeline_auto_clock() the default clock selection algorithm can be + * restored. + * + * A #GstPipeline maintains a running time for the elements. The running + * time is defined as the difference between the current clock time and + * the base time. When the pipeline goes to READY or a flushing seek is + * performed on it, the running time is reset to 0. When the pipeline is + * set from PLAYING to PAUSED, the current clock time is sampled and used to + * configure the base time for the elements when the pipeline is set + * to PLAYING again. The effect is that the running time (as the difference + * between the clock time and the base time) will count how much time was spent + * in the PLAYING state. This default behaviour can be changed with the + * gst_element_set_start_time() method. + * + * When sending a flushing seek event to a GstPipeline (see + * gst_element_seek()), it will make sure that the pipeline is properly + * PAUSED and resumed as well as set the new running time to 0 when the + * seek succeeded. + * + * Last reviewed on 2009-05-29 (0.10.24) + */ + +#include "gst_private.h" +#include "gsterror.h" +#include "gst-i18n-lib.h" + +#include "gstpipeline.h" +#include "gstinfo.h" +#include "gstsystemclock.h" +#include "gstutils.h" + +GST_DEBUG_CATEGORY_STATIC (pipeline_debug); +#define GST_CAT_DEFAULT pipeline_debug + +/* Pipeline signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +#define DEFAULT_DELAY 0 +#define DEFAULT_AUTO_FLUSH_BUS TRUE + +enum +{ + PROP_0, + PROP_DELAY, + PROP_AUTO_FLUSH_BUS +}; + +#define GST_PIPELINE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_PIPELINE, GstPipelinePrivate)) + +struct _GstPipelinePrivate +{ + /* with LOCK */ + gboolean auto_flush_bus; + + /* when we need to update stream_time or clock when going back to + * PLAYING*/ + GstClockTime last_start_time; + gboolean update_clock; +}; + + +static void gst_pipeline_dispose (GObject * object); +static void gst_pipeline_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_pipeline_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstClock *gst_pipeline_provide_clock_func (GstElement * element); +static GstStateChangeReturn gst_pipeline_change_state (GstElement * element, + GstStateChange transition); + +static void gst_pipeline_handle_message (GstBin * bin, GstMessage * message); + +/* static guint gst_pipeline_signals[LAST_SIGNAL] = { 0 }; */ + +#define _do_init \ +{ \ + GST_DEBUG_CATEGORY_INIT (pipeline_debug, "pipeline", GST_DEBUG_BOLD, \ + "debugging info for the 'pipeline' container element"); \ +} + +#define gst_pipeline_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstPipeline, gst_pipeline, GST_TYPE_BIN, _do_init); + +static void +gst_pipeline_class_init (GstPipelineClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GstBinClass *gstbin_class = GST_BIN_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GstPipelinePrivate)); + + gobject_class->set_property = gst_pipeline_set_property; + gobject_class->get_property = gst_pipeline_get_property; + + /** + * GstPipeline:delay + * + * The expected delay needed for elements to spin up to the + * PLAYING state expressed in nanoseconds. + * see gst_pipeline_set_delay() for more information on this option. + * + * Since: 0.10.5 + **/ + g_object_class_install_property (gobject_class, PROP_DELAY, + g_param_spec_uint64 ("delay", "Delay", + "Expected delay needed for elements " + "to spin up to PLAYING in nanoseconds", 0, G_MAXUINT64, DEFAULT_DELAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstPipeline:auto-flush-bus: + * + * Whether or not to automatically flush all messages on the + * pipeline's bus when going from READY to NULL state. Please see + * gst_pipeline_set_auto_flush_bus() for more information on this option. + * + * Since: 0.10.4 + **/ + g_object_class_install_property (gobject_class, PROP_AUTO_FLUSH_BUS, + g_param_spec_boolean ("auto-flush-bus", "Auto Flush Bus", + "Whether to automatically flush the pipeline's bus when going " + "from READY into NULL state", DEFAULT_AUTO_FLUSH_BUS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gobject_class->dispose = gst_pipeline_dispose; + + gst_element_class_set_metadata (gstelement_class, "Pipeline object", + "Generic/Bin", + "Complete pipeline object", + "Erik Walthinsen <omega@cse.ogi.edu>, Wim Taymans <wim@fluendo.com>"); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_pipeline_change_state); + gstelement_class->provide_clock = + GST_DEBUG_FUNCPTR (gst_pipeline_provide_clock_func); + gstbin_class->handle_message = + GST_DEBUG_FUNCPTR (gst_pipeline_handle_message); +} + +static void +gst_pipeline_init (GstPipeline * pipeline) +{ + GstBus *bus; + + pipeline->priv = GST_PIPELINE_GET_PRIVATE (pipeline); + + /* set default property values */ + pipeline->priv->auto_flush_bus = DEFAULT_AUTO_FLUSH_BUS; + pipeline->delay = DEFAULT_DELAY; + + /* create and set a default bus */ + bus = gst_bus_new (); +#if 0 + /* FIXME, disabled for 0.10.5 release as it caused to many regressions */ + /* Start our bus in flushing if appropriate */ + if (pipeline->priv->auto_flush_bus) + gst_bus_set_flushing (bus, TRUE); +#endif + + gst_element_set_bus (GST_ELEMENT_CAST (pipeline), bus); + GST_DEBUG_OBJECT (pipeline, "set bus %" GST_PTR_FORMAT " on pipeline", bus); + gst_object_unref (bus); +} + +static void +gst_pipeline_dispose (GObject * object) +{ + GstPipeline *pipeline = GST_PIPELINE (object); + GstClock **clock_p = &pipeline->fixed_clock; + + GST_CAT_DEBUG_OBJECT (GST_CAT_REFCOUNTING, pipeline, "dispose"); + + /* clear and unref any fixed clock */ + gst_object_replace ((GstObject **) clock_p, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_pipeline_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstPipeline *pipeline = GST_PIPELINE (object); + + switch (prop_id) { + case PROP_DELAY: + gst_pipeline_set_delay (pipeline, g_value_get_uint64 (value)); + break; + case PROP_AUTO_FLUSH_BUS: + gst_pipeline_set_auto_flush_bus (pipeline, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipeline_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstPipeline *pipeline = GST_PIPELINE (object); + + switch (prop_id) { + case PROP_DELAY: + g_value_set_uint64 (value, gst_pipeline_get_delay (pipeline)); + break; + case PROP_AUTO_FLUSH_BUS: + g_value_set_boolean (value, gst_pipeline_get_auto_flush_bus (pipeline)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* set the start_time to 0, this will cause us to select a new base_time and + * make the running_time start from 0 again. */ +static void +reset_start_time (GstPipeline * pipeline) +{ + GST_OBJECT_LOCK (pipeline); + if (GST_ELEMENT_START_TIME (pipeline) != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (pipeline, "reset start_time to 0"); + GST_ELEMENT_START_TIME (pipeline) = 0; + pipeline->priv->last_start_time = -1; + } else { + GST_DEBUG_OBJECT (pipeline, "application asked to not reset stream_time"); + } + GST_OBJECT_UNLOCK (pipeline); +} + +/** + * gst_pipeline_new: + * @name: name of new pipeline + * + * Create a new pipeline with the given name. + * + * Returns: (transfer full): newly created GstPipeline + * + * MT safe. + */ +GstElement * +gst_pipeline_new (const gchar * name) +{ + return gst_element_factory_make ("pipeline", name); +} + +/* takes a snapshot of the running_time of the pipeline and store this as the + * element start_time. This is the time we will set as the running_time of the + * pipeline when we go to PLAYING next. */ +static void +pipeline_update_start_time (GstElement * element) +{ + GstPipeline *pipeline = GST_PIPELINE_CAST (element); + GstClock *clock; + + GST_OBJECT_LOCK (element); + if ((clock = element->clock)) { + GstClockTime now; + + gst_object_ref (clock); + GST_OBJECT_UNLOCK (element); + + /* calculate the time when we stopped */ + now = gst_clock_get_time (clock); + gst_object_unref (clock); + + GST_OBJECT_LOCK (element); + /* store the current running time */ + if (GST_ELEMENT_START_TIME (pipeline) != GST_CLOCK_TIME_NONE) { + if (now != GST_CLOCK_TIME_NONE) + GST_ELEMENT_START_TIME (pipeline) = now - element->base_time; + else + GST_WARNING_OBJECT (element, + "Clock %s returned invalid time, can't calculate " + "running_time when going to the PAUSED state", + GST_OBJECT_NAME (clock)); + + /* we went to PAUSED, when going to PLAYING select clock and new + * base_time */ + pipeline->priv->update_clock = TRUE; + } + GST_DEBUG_OBJECT (element, + "start_time=%" GST_TIME_FORMAT ", now=%" GST_TIME_FORMAT + ", base_time %" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_ELEMENT_START_TIME (pipeline)), + GST_TIME_ARGS (now), GST_TIME_ARGS (element->base_time)); + } + GST_OBJECT_UNLOCK (element); +} + +/* MT safe */ +static GstStateChangeReturn +gst_pipeline_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn result = GST_STATE_CHANGE_SUCCESS; + GstPipeline *pipeline = GST_PIPELINE_CAST (element); + GstClock *clock; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + GST_OBJECT_LOCK (element); + if (element->bus) + gst_bus_set_flushing (element->bus, FALSE); + GST_OBJECT_UNLOCK (element); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_OBJECT_LOCK (element); + pipeline->priv->update_clock = TRUE; + GST_OBJECT_UNLOCK (element); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + { + GstClockTime now, start_time, last_start_time, delay; + gboolean update_clock; + GstClock *cur_clock; + + GST_DEBUG_OBJECT (element, "selecting clock and base_time"); + + GST_OBJECT_LOCK (element); + cur_clock = element->clock; + if (cur_clock) + gst_object_ref (cur_clock); + /* get the desired running_time of the first buffer aka the start_time */ + start_time = GST_ELEMENT_START_TIME (pipeline); + last_start_time = pipeline->priv->last_start_time; + pipeline->priv->last_start_time = start_time; + /* see if we need to update the clock */ + update_clock = pipeline->priv->update_clock; + pipeline->priv->update_clock = FALSE; + delay = pipeline->delay; + GST_OBJECT_UNLOCK (element); + + /* running time changed, either with a PAUSED or a flush, we need to check + * if there is a new clock & update the base time */ + if (update_clock || last_start_time != start_time) { + GST_DEBUG_OBJECT (pipeline, "Need to update start_time"); + + /* when going to PLAYING, select a clock when needed. If we just got + * flushed, we don't reselect the clock. */ + if (update_clock) { + GST_DEBUG_OBJECT (pipeline, "Need to update clock."); + clock = gst_element_provide_clock (element); + } else { + GST_DEBUG_OBJECT (pipeline, + "Don't need to update clock, using old clock."); + clock = gst_object_ref (cur_clock); + } + + if (clock) { + now = gst_clock_get_time (clock); + } else { + GST_DEBUG ("no clock, using base time of NONE"); + now = GST_CLOCK_TIME_NONE; + } + + if (clock != cur_clock) { + /* now distribute the clock (which could be NULL). If some + * element refuses the clock, this will return FALSE and + * we effectively fail the state change. */ + if (!gst_element_set_clock (element, clock)) + goto invalid_clock; + + /* if we selected and distributed a new clock, let the app + * know about it */ + gst_element_post_message (element, + gst_message_new_new_clock (GST_OBJECT_CAST (element), clock)); + } + + if (clock) + gst_object_unref (clock); + + if (start_time != GST_CLOCK_TIME_NONE && now != GST_CLOCK_TIME_NONE) { + GstClockTime new_base_time = now - start_time + delay; + GST_DEBUG_OBJECT (element, + "start_time=%" GST_TIME_FORMAT ", now=%" GST_TIME_FORMAT + ", base_time %" GST_TIME_FORMAT, + GST_TIME_ARGS (start_time), GST_TIME_ARGS (now), + GST_TIME_ARGS (new_base_time)); + + gst_element_set_base_time (element, new_base_time); + } else { + GST_DEBUG_OBJECT (pipeline, + "NOT adjusting base_time because start_time is NONE"); + } + } else { + GST_DEBUG_OBJECT (pipeline, + "NOT adjusting base_time because we selected one before"); + } + + if (cur_clock) + gst_object_unref (cur_clock); + break; + } + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + { + /* we take a start_time snapshot before calling the children state changes + * so that they know about when the pipeline PAUSED. */ + pipeline_update_start_time (element); + break; + } + case GST_STATE_CHANGE_PAUSED_TO_READY: + case GST_STATE_CHANGE_READY_TO_NULL: + break; + } + + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + { + reset_start_time (pipeline); + break; + } + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + { + /* Take a new snapshot of the start_time after calling the state change on + * all children. This will be the running_time of the pipeline when we go + * back to PLAYING */ + pipeline_update_start_time (element); + break; + } + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_NULL: + { + GstBus *bus; + gboolean auto_flush; + + /* grab some stuff before we release the lock to flush out the bus */ + GST_OBJECT_LOCK (element); + if ((bus = element->bus)) + gst_object_ref (bus); + auto_flush = pipeline->priv->auto_flush_bus; + GST_OBJECT_UNLOCK (element); + + if (bus) { + if (auto_flush) { + gst_bus_set_flushing (bus, TRUE); + } else { + GST_INFO_OBJECT (element, "not flushing bus, auto-flushing disabled"); + } + gst_object_unref (bus); + } + break; + } + } + return result; + + /* ERRORS */ +invalid_clock: + { + /* we generate this error when the selected clock was not + * accepted by some element */ + GST_ELEMENT_ERROR (pipeline, CORE, CLOCK, + (_("Selected clock cannot be used in pipeline.")), + ("Pipeline cannot operate with selected clock")); + GST_DEBUG_OBJECT (pipeline, + "Pipeline cannot operate with selected clock %p", clock); + if (clock) + gst_object_unref (clock); + return GST_STATE_CHANGE_FAILURE; + } +} + +/* intercept the bus messages from our children. We watch for the ASYNC_START + * message with is posted by the elements (sinks) that require a reset of the + * running_time after a flush. ASYNC_START also brings the pipeline back into + * the PAUSED, pending PAUSED state. When the ASYNC_DONE message is received the + * pipeline will redistribute the new base_time and will bring the elements back + * to the desired state of the pipeline. */ +static void +gst_pipeline_handle_message (GstBin * bin, GstMessage * message) +{ + GstPipeline *pipeline = GST_PIPELINE_CAST (bin); + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ASYNC_DONE: + { + gboolean reset_time; + + gst_message_parse_async_done (message, &reset_time); + + /* reset our running time if we need to distribute a new base_time to the + * children. */ + if (reset_time) + reset_start_time (pipeline); + + break; + } + case GST_MESSAGE_CLOCK_LOST: + { + GstClock *clock; + + gst_message_parse_clock_lost (message, &clock); + + GST_OBJECT_LOCK (bin); + if (clock == GST_ELEMENT_CAST (bin)->clock) { + GST_DEBUG_OBJECT (bin, "Used clock '%s' got lost", + GST_OBJECT_NAME (clock)); + pipeline->priv->update_clock = TRUE; + } + GST_OBJECT_UNLOCK (bin); + } + default: + break; + } + GST_BIN_CLASS (parent_class)->handle_message (bin, message); +} + +/** + * gst_pipeline_get_bus: + * @pipeline: a #GstPipeline + * + * Gets the #GstBus of @pipeline. The bus allows applications to receive + * #GstMessage packets. + * + * Returns: (transfer full): a #GstBus, unref after usage. + * + * MT safe. + */ +GstBus * +gst_pipeline_get_bus (GstPipeline * pipeline) +{ + return gst_element_get_bus (GST_ELEMENT_CAST (pipeline)); +} + +static GstClock * +gst_pipeline_provide_clock_func (GstElement * element) +{ + GstClock *clock = NULL; + GstPipeline *pipeline = GST_PIPELINE (element); + + /* if we have a fixed clock, use that one */ + GST_OBJECT_LOCK (pipeline); + if (GST_OBJECT_FLAG_IS_SET (pipeline, GST_PIPELINE_FLAG_FIXED_CLOCK)) { + clock = pipeline->fixed_clock; + if (clock) + gst_object_ref (clock); + GST_OBJECT_UNLOCK (pipeline); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "pipeline using fixed clock %p (%s)", + clock, clock ? GST_STR_NULL (GST_OBJECT_NAME (clock)) : "-"); + } else { + GST_OBJECT_UNLOCK (pipeline); + /* let the parent bin select a clock */ + clock = + GST_ELEMENT_CLASS (parent_class)->provide_clock (GST_ELEMENT + (pipeline)); + /* no clock, use a system clock */ + if (!clock) { + clock = gst_system_clock_obtain (); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "pipeline obtained system clock: %p (%s)", + clock, clock ? GST_STR_NULL (GST_OBJECT_NAME (clock)) : "-"); + } else { + GST_CAT_DEBUG (GST_CAT_CLOCK, "pipeline obtained clock: %p (%s)", + clock, clock ? GST_STR_NULL (GST_OBJECT_NAME (clock)) : "-"); + } + } + return clock; +} + +/** + * gst_pipeline_get_clock: + * @pipeline: a #GstPipeline + * + * Gets the current clock used by @pipeline. + * + * Returns: (transfer full): a #GstClock, unref after usage. + */ +GstClock * +gst_pipeline_get_clock (GstPipeline * pipeline) +{ + g_return_val_if_fail (GST_IS_PIPELINE (pipeline), NULL); + + return gst_pipeline_provide_clock_func (GST_ELEMENT_CAST (pipeline)); +} + + +/** + * gst_pipeline_use_clock: + * @pipeline: a #GstPipeline + * @clock: (transfer none): the clock to use + * + * Force @pipeline to use the given @clock. The pipeline will + * always use the given clock even if new clock providers are added + * to this pipeline. + * + * If @clock is NULL all clocking will be disabled which will make + * the pipeline run as fast as possible. + * + * MT safe. + */ +void +gst_pipeline_use_clock (GstPipeline * pipeline, GstClock * clock) +{ + GstClock **clock_p; + + g_return_if_fail (GST_IS_PIPELINE (pipeline)); + + GST_OBJECT_LOCK (pipeline); + GST_OBJECT_FLAG_SET (pipeline, GST_PIPELINE_FLAG_FIXED_CLOCK); + + clock_p = &pipeline->fixed_clock; + gst_object_replace ((GstObject **) clock_p, (GstObject *) clock); + GST_OBJECT_UNLOCK (pipeline); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "pipeline using fixed clock %p (%s)", clock, + (clock ? GST_OBJECT_NAME (clock) : "nil")); +} + +/** + * gst_pipeline_set_clock: + * @pipeline: a #GstPipeline + * @clock: (transfer none): the clock to set + * + * Set the clock for @pipeline. The clock will be distributed + * to all the elements managed by the pipeline. + * + * Returns: TRUE if the clock could be set on the pipeline. FALSE if + * some element did not accept the clock. + * + * MT safe. + */ +gboolean +gst_pipeline_set_clock (GstPipeline * pipeline, GstClock * clock) +{ + g_return_val_if_fail (pipeline != NULL, FALSE); + g_return_val_if_fail (GST_IS_PIPELINE (pipeline), FALSE); + + return + GST_ELEMENT_CLASS (parent_class)->set_clock (GST_ELEMENT_CAST (pipeline), + clock); +} + +/** + * gst_pipeline_auto_clock: + * @pipeline: a #GstPipeline + * + * Let @pipeline select a clock automatically. This is the default + * behaviour. + * + * Use this function if you previous forced a fixed clock with + * gst_pipeline_use_clock() and want to restore the default + * pipeline clock selection algorithm. + * + * MT safe. + */ +void +gst_pipeline_auto_clock (GstPipeline * pipeline) +{ + GstClock **clock_p; + + g_return_if_fail (pipeline != NULL); + g_return_if_fail (GST_IS_PIPELINE (pipeline)); + + GST_OBJECT_LOCK (pipeline); + GST_OBJECT_FLAG_UNSET (pipeline, GST_PIPELINE_FLAG_FIXED_CLOCK); + + clock_p = &pipeline->fixed_clock; + gst_object_replace ((GstObject **) clock_p, NULL); + GST_OBJECT_UNLOCK (pipeline); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "pipeline using automatic clock"); +} + +/** + * gst_pipeline_set_delay: + * @pipeline: a #GstPipeline + * @delay: the delay + * + * Set the expected delay needed for all elements to perform the + * PAUSED to PLAYING state change. @delay will be added to the + * base time of the elements so that they wait an additional @delay + * amount of time before starting to process buffers and cannot be + * #GST_CLOCK_TIME_NONE. + * + * This option is used for tuning purposes and should normally not be + * used. + * + * MT safe. + * + * Since: 0.10.5 + */ +void +gst_pipeline_set_delay (GstPipeline * pipeline, GstClockTime delay) +{ + g_return_if_fail (GST_IS_PIPELINE (pipeline)); + g_return_if_fail (delay != GST_CLOCK_TIME_NONE); + + GST_OBJECT_LOCK (pipeline); + pipeline->delay = delay; + GST_OBJECT_UNLOCK (pipeline); +} + +/** + * gst_pipeline_get_delay: + * @pipeline: a #GstPipeline + * + * Get the configured delay (see gst_pipeline_set_delay()). + * + * Returns: The configured delay. + * + * MT safe. + * + * Since: 0.10.5 + */ +GstClockTime +gst_pipeline_get_delay (GstPipeline * pipeline) +{ + GstClockTime res; + + g_return_val_if_fail (GST_IS_PIPELINE (pipeline), GST_CLOCK_TIME_NONE); + + GST_OBJECT_LOCK (pipeline); + res = pipeline->delay; + GST_OBJECT_UNLOCK (pipeline); + + return res; +} + +/** + * gst_pipeline_set_auto_flush_bus: + * @pipeline: a #GstPipeline + * @auto_flush: whether or not to automatically flush the bus when + * the pipeline goes from READY to NULL state + * + * Usually, when a pipeline goes from READY to NULL state, it automatically + * flushes all pending messages on the bus, which is done for refcounting + * purposes, to break circular references. + * + * This means that applications that update state using (async) bus messages + * (e.g. do certain things when a pipeline goes from PAUSED to READY) might + * not get to see messages when the pipeline is shut down, because they might + * be flushed before they can be dispatched in the main thread. This behaviour + * can be disabled using this function. + * + * It is important that all messages on the bus are handled when the + * automatic flushing is disabled else memory leaks will be introduced. + * + * MT safe. + * + * Since: 0.10.4 + */ +void +gst_pipeline_set_auto_flush_bus (GstPipeline * pipeline, gboolean auto_flush) +{ + g_return_if_fail (GST_IS_PIPELINE (pipeline)); + + GST_OBJECT_LOCK (pipeline); + pipeline->priv->auto_flush_bus = auto_flush; + GST_OBJECT_UNLOCK (pipeline); +} + +/** + * gst_pipeline_get_auto_flush_bus: + * @pipeline: a #GstPipeline + * + * Check if @pipeline will automatically flush messages when going to + * the NULL state. + * + * Returns: whether the pipeline will automatically flush its bus when + * going from READY to NULL state or not. + * + * MT safe. + * + * Since: 0.10.4 + */ +gboolean +gst_pipeline_get_auto_flush_bus (GstPipeline * pipeline) +{ + gboolean res; + + g_return_val_if_fail (GST_IS_PIPELINE (pipeline), FALSE); + + GST_OBJECT_LOCK (pipeline); + res = pipeline->priv->auto_flush_bus; + GST_OBJECT_UNLOCK (pipeline); + + return res; +} diff --git a/gst/gstpipeline.h b/gst/gstpipeline.h new file mode 100644 index 0000000..7dbf96f --- /dev/null +++ b/gst/gstpipeline.h @@ -0,0 +1,110 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstpipeline.h: Header for GstPipeline element + * + * 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_PIPELINE_H__ +#define __GST_PIPELINE_H__ + +#include <gst/gstbin.h> + +G_BEGIN_DECLS + +#define GST_TYPE_PIPELINE (gst_pipeline_get_type ()) +#define GST_PIPELINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PIPELINE, GstPipeline)) +#define GST_IS_PIPELINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PIPELINE)) +#define GST_PIPELINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PIPELINE, GstPipelineClass)) +#define GST_IS_PIPELINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PIPELINE)) +#define GST_PIPELINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PIPELINE, GstPipelineClass)) +#define GST_PIPELINE_CAST(obj) ((GstPipeline*)(obj)) + +typedef struct _GstPipeline GstPipeline; +typedef struct _GstPipelineClass GstPipelineClass; +typedef struct _GstPipelinePrivate GstPipelinePrivate; + +/** + * GstPipelineFlags: + * @GST_PIPELINE_FLAG_FIXED_CLOCK: this pipeline works with a fixed clock + * @GST_PIPELINE_FLAG_LAST: offset to define more flags + * + * Pipeline flags + */ +typedef enum { + GST_PIPELINE_FLAG_FIXED_CLOCK = (GST_BIN_FLAG_LAST << 0), + /* padding */ + GST_PIPELINE_FLAG_LAST = (GST_BIN_FLAG_LAST << 4) +} GstPipelineFlags; + +/** + * GstPipeline: + * @fixed_clock: The fixed clock of the pipeline, used when + * GST_PIPELINE_FLAG_FIXED_CLOCK is set. + * @stream_time: The stream time of the pipeline. A better name for this + * property would be the running_time, the total time spent in the + * PLAYING state without being flushed. (deprecated, use the start_time + * on GstElement). + * @delay: Extra delay added to base_time to compensate for computing delays + * when setting elements to PLAYING. + * + * The #GstPipeline structure. + */ +struct _GstPipeline { + GstBin bin; + + /*< public >*/ /* with LOCK */ + GstClock *fixed_clock; + + GstClockTime stream_time; + GstClockTime delay; + + /*< private >*/ + GstPipelinePrivate *priv; + + gpointer _gst_reserved[GST_PADDING-1]; +}; + +struct _GstPipelineClass { + GstBinClass parent_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_pipeline_get_type (void); +GstElement* gst_pipeline_new (const gchar *name); + +GstBus* gst_pipeline_get_bus (GstPipeline *pipeline); + +void gst_pipeline_use_clock (GstPipeline *pipeline, GstClock *clock); +gboolean gst_pipeline_set_clock (GstPipeline *pipeline, GstClock *clock); +GstClock* gst_pipeline_get_clock (GstPipeline *pipeline); +void gst_pipeline_auto_clock (GstPipeline *pipeline); + +void gst_pipeline_set_delay (GstPipeline *pipeline, GstClockTime delay); +GstClockTime gst_pipeline_get_delay (GstPipeline *pipeline); + +void gst_pipeline_set_auto_flush_bus (GstPipeline *pipeline, gboolean auto_flush); +gboolean gst_pipeline_get_auto_flush_bus (GstPipeline *pipeline); + +G_END_DECLS + +#endif /* __GST_PIPELINE_H__ */ + diff --git a/gst/gstplugin.c b/gst/gstplugin.c new file mode 100644 index 0000000..dfb5f0c --- /dev/null +++ b/gst/gstplugin.c @@ -0,0 +1,1859 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstplugin.c: Plugin subsystem for loading elements, types, and libs + * + * 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:gstplugin + * @short_description: Container for features loaded from a shared object module + * @see_also: #GstPluginFeature, #GstElementFactory + * + * GStreamer is extensible, so #GstElement instances can be loaded at runtime. + * A plugin system can provide one or more of the basic + * <application>GStreamer</application> #GstPluginFeature subclasses. + * + * A plugin should export a symbol <symbol>gst_plugin_desc</symbol> that is a + * struct of type #GstPluginDesc. + * the plugin loader will check the version of the core library the plugin was + * linked against and will create a new #GstPlugin. It will then call the + * #GstPluginInitFunc function that was provided in the + * <symbol>gst_plugin_desc</symbol>. + * + * Once you have a handle to a #GstPlugin (e.g. from the #GstRegistry), you + * can add any object that subclasses #GstPluginFeature. + * + * Usually plugins are always automaticlly loaded so you don't need to call + * gst_plugin_load() explicitly to bring it into memory. There are options to + * statically link plugins to an app or even use GStreamer without a plugin + * repository in which case gst_plugin_load() can be needed to bring the plugin + * into memory. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gst_private.h" + +#include <glib/gstdio.h> +#include <sys/types.h> +#ifdef HAVE_DIRENT_H +#include <dirent.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <signal.h> +#include <errno.h> +#include <string.h> + +#include "glib-compat-private.h" + +#include <gst/gst.h> + +#define GST_CAT_DEFAULT GST_CAT_PLUGIN_LOADING + +static guint _num_static_plugins; /* 0 */ +static GstPluginDesc *_static_plugins; /* NULL */ +static gboolean _gst_plugin_inited; +static gchar **_plugin_loading_whitelist; /* NULL */ + +/* static variables for segfault handling of plugin loading */ +static char *_gst_plugin_fault_handler_filename = NULL; + +/* list of valid licenses. + * One of these must be specified or the plugin won't be loaded + * Contact gstreamer-devel@lists.sourceforge.net if your license should be + * added. + * + * GPL: http://www.gnu.org/copyleft/gpl.html + * LGPL: http://www.gnu.org/copyleft/lesser.html + * QPL: http://www.trolltech.com/licenses/qpl.html + * MPL: http://www.opensource.org/licenses/mozilla1.1.php + * MIT/X11: http://www.opensource.org/licenses/mit-license.php + * 3-clause BSD: http://www.opensource.org/licenses/bsd-license.php + */ +static const gchar *valid_licenses[] = { + "LGPL", /* GNU Lesser General Public License */ + "GPL", /* GNU General Public License */ + "QPL", /* Trolltech Qt Public License */ + "GPL/QPL", /* Combi-license of GPL + QPL */ + "MPL", /* MPL 1.1 license */ + "BSD", /* 3-clause BSD license */ + "MIT/X11", /* MIT/X11 license */ + "Proprietary", /* Proprietary license */ + GST_LICENSE_UNKNOWN, /* some other license */ + NULL +}; + +static GstPlugin *gst_plugin_register_func (GstPlugin * plugin, + const GstPluginDesc * desc, gpointer user_data); +static void gst_plugin_desc_copy (GstPluginDesc * dest, + const GstPluginDesc * src); + +static void gst_plugin_ext_dep_free (GstPluginDep * dep); + +G_DEFINE_TYPE (GstPlugin, gst_plugin, GST_TYPE_OBJECT); + +static void +gst_plugin_init (GstPlugin * plugin) +{ + plugin->priv = + G_TYPE_INSTANCE_GET_PRIVATE (plugin, GST_TYPE_PLUGIN, GstPluginPrivate); +} + +static void +gst_plugin_finalize (GObject * object) +{ + GstPlugin *plugin = GST_PLUGIN_CAST (object); + GstRegistry *registry = gst_registry_get_default (); + GList *g; + + GST_DEBUG ("finalizing plugin %" GST_PTR_FORMAT, plugin); + for (g = registry->plugins; g; g = g->next) { + if (g->data == (gpointer) plugin) { + g_warning ("removing plugin that is still in registry"); + } + } + g_free (plugin->filename); + g_free (plugin->basename); + + g_list_foreach (plugin->priv->deps, (GFunc) gst_plugin_ext_dep_free, NULL); + g_list_free (plugin->priv->deps); + plugin->priv->deps = NULL; + + if (plugin->priv->cache_data) { + gst_structure_free (plugin->priv->cache_data); + } + + G_OBJECT_CLASS (gst_plugin_parent_class)->finalize (object); +} + +static void +gst_plugin_class_init (GstPluginClass * klass) +{ + G_OBJECT_CLASS (klass)->finalize = gst_plugin_finalize; + + g_type_class_add_private (klass, sizeof (GstPluginPrivate)); +} + +GQuark +gst_plugin_error_quark (void) +{ + static GQuark quark = 0; + + if (!quark) + quark = g_quark_from_static_string ("gst_plugin_error"); + return quark; +} + +/** + * gst_plugin_register_static: + * @major_version: the major version number of the GStreamer core that the + * plugin was compiled for, you can just use GST_VERSION_MAJOR here + * @minor_version: the minor version number of the GStreamer core that the + * plugin was compiled for, you can just use GST_VERSION_MINOR here + * @name: a unique name of the plugin (ideally prefixed with an application- or + * library-specific namespace prefix in order to avoid name conflicts in + * case a similar plugin with the same name ever gets added to GStreamer) + * @description: description of the plugin + * @init_func: (scope call): pointer to the init function of this plugin. + * @version: version string of the plugin + * @license: effective license of plugin. Must be one of the approved licenses + * (see #GstPluginDesc above) or the plugin will not be registered. + * @source: source module plugin belongs to + * @package: shipped package plugin belongs to + * @origin: URL to provider of plugin + * + * Registers a static plugin, ie. a plugin which is private to an application + * or library and contained within the application or library (as opposed to + * being shipped as a separate module file). + * + * You must make sure that GStreamer has been initialised (with gst_init() or + * via gst_init_get_option_group()) before calling this function. + * + * Returns: TRUE if the plugin was registered correctly, otherwise FALSE. + * + * Since: 0.10.16 + */ +gboolean +gst_plugin_register_static (gint major_version, gint minor_version, + const gchar * name, const gchar * description, GstPluginInitFunc init_func, + const gchar * version, const gchar * license, const gchar * source, + const gchar * package, const gchar * origin) +{ + GstPluginDesc desc = { major_version, minor_version, name, description, + init_func, version, license, source, package, origin, NULL, + }; + GstPlugin *plugin; + gboolean res = FALSE; + + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (description != NULL, FALSE); + g_return_val_if_fail (init_func != NULL, FALSE); + g_return_val_if_fail (version != NULL, FALSE); + g_return_val_if_fail (license != NULL, FALSE); + g_return_val_if_fail (source != NULL, FALSE); + g_return_val_if_fail (package != NULL, FALSE); + g_return_val_if_fail (origin != NULL, FALSE); + + /* make sure gst_init() has been called */ + g_return_val_if_fail (_gst_plugin_inited != FALSE, FALSE); + + GST_LOG ("attempting to load static plugin \"%s\" now...", name); + plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL); + if (gst_plugin_register_func (plugin, &desc, NULL) != NULL) { + GST_INFO ("registered static plugin \"%s\"", name); + res = gst_default_registry_add_plugin (plugin); + GST_INFO ("added static plugin \"%s\", result: %d", name, res); + } + return res; +} + +/** + * gst_plugin_register_static_full: + * @major_version: the major version number of the GStreamer core that the + * plugin was compiled for, you can just use GST_VERSION_MAJOR here + * @minor_version: the minor version number of the GStreamer core that the + * plugin was compiled for, you can just use GST_VERSION_MINOR here + * @name: a unique name of the plugin (ideally prefixed with an application- or + * library-specific namespace prefix in order to avoid name conflicts in + * case a similar plugin with the same name ever gets added to GStreamer) + * @description: description of the plugin + * @init_full_func: (scope call): pointer to the init function with user data + * of this plugin. + * @version: version string of the plugin + * @license: effective license of plugin. Must be one of the approved licenses + * (see #GstPluginDesc above) or the plugin will not be registered. + * @source: source module plugin belongs to + * @package: shipped package plugin belongs to + * @origin: URL to provider of plugin + * @user_data: gpointer to user data + * + * Registers a static plugin, ie. a plugin which is private to an application + * or library and contained within the application or library (as opposed to + * being shipped as a separate module file) with a #GstPluginInitFullFunc + * which allows user data to be passed to the callback function (useful + * for bindings). + * + * You must make sure that GStreamer has been initialised (with gst_init() or + * via gst_init_get_option_group()) before calling this function. + * + * Returns: TRUE if the plugin was registered correctly, otherwise FALSE. + * + * Since: 0.10.24 + * + */ +gboolean +gst_plugin_register_static_full (gint major_version, gint minor_version, + const gchar * name, const gchar * description, + GstPluginInitFullFunc init_full_func, const gchar * version, + const gchar * license, const gchar * source, const gchar * package, + const gchar * origin, gpointer user_data) +{ + GstPluginDesc desc = { major_version, minor_version, name, description, + (GstPluginInitFunc) init_full_func, version, license, source, package, + origin, NULL, + }; + GstPlugin *plugin; + gboolean res = FALSE; + + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (description != NULL, FALSE); + g_return_val_if_fail (init_full_func != NULL, FALSE); + g_return_val_if_fail (version != NULL, FALSE); + g_return_val_if_fail (license != NULL, FALSE); + g_return_val_if_fail (source != NULL, FALSE); + g_return_val_if_fail (package != NULL, FALSE); + g_return_val_if_fail (origin != NULL, FALSE); + + /* make sure gst_init() has been called */ + g_return_val_if_fail (_gst_plugin_inited != FALSE, FALSE); + + GST_LOG ("attempting to load static plugin \"%s\" now...", name); + plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL); + if (gst_plugin_register_func (plugin, &desc, user_data) != NULL) { + GST_INFO ("registered static plugin \"%s\"", name); + res = gst_default_registry_add_plugin (plugin); + GST_INFO ("added static plugin \"%s\", result: %d", name, res); + } + return res; +} + +void +_priv_gst_plugin_initialize (void) +{ + const gchar *whitelist; + guint i; + + _gst_plugin_inited = TRUE; + + whitelist = g_getenv ("GST_PLUGIN_LOADING_WHITELIST"); + if (whitelist != NULL && *whitelist != '\0') { + _plugin_loading_whitelist = g_strsplit (whitelist, + G_SEARCHPATH_SEPARATOR_S, -1); + for (i = 0; _plugin_loading_whitelist[i] != NULL; ++i) { + GST_INFO ("plugins whitelist entry: %s", _plugin_loading_whitelist[i]); + } + } + + /* now register all static plugins */ + GST_INFO ("registering %u static plugins", _num_static_plugins); + for (i = 0; i < _num_static_plugins; ++i) { + gst_plugin_register_static (_static_plugins[i].major_version, + _static_plugins[i].minor_version, _static_plugins[i].name, + _static_plugins[i].description, _static_plugins[i].plugin_init, + _static_plugins[i].version, _static_plugins[i].license, + _static_plugins[i].source, _static_plugins[i].package, + _static_plugins[i].origin); + } + + if (_static_plugins) { + free (_static_plugins); + _static_plugins = NULL; + _num_static_plugins = 0; + } +} + +/* Whitelist entry format: + * + * plugin1,plugin2@pathprefix or + * plugin1,plugin2@* or just + * plugin1,plugin2 or + * source-package@pathprefix or + * source-package@* or just + * source-package + * + * ie. the bit before the path will be checked against both the plugin + * name and the plugin's source package name, to keep the format simple. + */ +static gboolean +gst_plugin_desc_matches_whitelist_entry (GstPluginDesc * desc, + const gchar * filename, const gchar * pattern) +{ + const gchar *sep; + gboolean ret = FALSE; + gchar *name; + + GST_LOG ("Whitelist pattern '%s', plugin: %s of %s@%s", pattern, desc->name, + desc->source, GST_STR_NULL (filename)); + + /* do we have a path prefix? */ + sep = strchr (pattern, '@'); + if (sep != NULL && strcmp (sep, "@*") != 0 && strcmp (sep, "@") != 0) { + /* paths are not canonicalised or treated with realpath() here. This + * should be good enough for our use case, since we just use the paths + * autotools uses, and those will be constructed from the same prefix. */ + if (filename != NULL && !g_str_has_prefix (filename, sep + 1)) + return FALSE; + + GST_LOG ("%s matches path prefix %s", GST_STR_NULL (filename), sep + 1); + } + + if (sep != NULL) { + name = g_strndup (pattern, (gsize) (sep - pattern)); + } else { + name = g_strdup (pattern); + } + + g_strstrip (name); + if (!g_ascii_isalnum (*name)) { + GST_WARNING ("Invalid whitelist pattern: %s", pattern); + goto done; + } + + /* now check plugin names / source package name */ + if (strchr (name, ',') == NULL) { + /* only a single name: either a plugin name or the source package name */ + ret = (strcmp (desc->source, name) == 0 || strcmp (desc->name, name) == 0); + } else { + gchar **n, **names; + + /* multiple names: assume these are plugin names */ + names = g_strsplit (name, ",", -1); + for (n = names; n != NULL && *n != NULL; ++n) { + g_strstrip (*n); + if (strcmp (desc->name, *n) == 0) { + ret = TRUE; + break; + } + } + g_strfreev (names); + } + + GST_LOG ("plugin / source package name match: %d", ret); + +done: + + g_free (name); + return ret; +} + +gboolean +priv_gst_plugin_desc_is_whitelisted (GstPluginDesc * desc, + const gchar * filename) +{ + gchar **entry; + + if (_plugin_loading_whitelist == NULL) + return TRUE; + + for (entry = _plugin_loading_whitelist; *entry != NULL; ++entry) { + if (gst_plugin_desc_matches_whitelist_entry (desc, filename, *entry)) { + GST_LOG ("Plugin %s is in whitelist", filename); + return TRUE; + } + } + + GST_LOG ("Plugin %s (package %s, file %s) not in whitelist", desc->name, + desc->source, filename); + return FALSE; +} + +gboolean +priv_gst_plugin_loading_have_whitelist (void) +{ + return (_plugin_loading_whitelist != NULL); +} + +guint32 +priv_gst_plugin_loading_get_whitelist_hash (void) +{ + guint32 hash = 0; + + if (_plugin_loading_whitelist != NULL) { + gchar **w; + + for (w = _plugin_loading_whitelist; *w != NULL; ++w) + hash = (hash << 1) ^ g_str_hash (*w); + } + + return hash; +} + +/* this function could be extended to check if the plugin license matches the + * applications license (would require the app to register its license somehow). + * We'll wait for someone who's interested in it to code it :) + */ +static gboolean +gst_plugin_check_license (const gchar * license) +{ + const gchar **check_license = valid_licenses; + + g_assert (check_license); + + while (*check_license) { + if (strcmp (license, *check_license) == 0) + return TRUE; + check_license++; + } + return FALSE; +} + +static gboolean +gst_plugin_check_version (gint major, gint minor) +{ + /* return NULL if the major and minor version numbers are not compatible */ + /* with ours. */ + if (major != GST_VERSION_MAJOR || minor != GST_VERSION_MINOR) + return FALSE; + + return TRUE; +} + +static GstPlugin * +gst_plugin_register_func (GstPlugin * plugin, const GstPluginDesc * desc, + gpointer user_data) +{ + if (!gst_plugin_check_version (desc->major_version, desc->minor_version)) { + if (GST_CAT_DEFAULT) + GST_WARNING ("plugin \"%s\" has incompatible version, not loading", + plugin->filename); + return NULL; + } + + if (!desc->license || !desc->description || !desc->source || + !desc->package || !desc->origin) { + if (GST_CAT_DEFAULT) + GST_WARNING ("plugin \"%s\" has incorrect GstPluginDesc, not loading", + plugin->filename); + return NULL; + } + + if (!gst_plugin_check_license (desc->license)) { + if (GST_CAT_DEFAULT) + GST_WARNING ("plugin \"%s\" has invalid license \"%s\", not loading", + plugin->filename, desc->license); + return NULL; + } + + if (GST_CAT_DEFAULT) + GST_LOG ("plugin \"%s\" looks good", GST_STR_NULL (plugin->filename)); + + gst_plugin_desc_copy (&plugin->desc, desc); + + /* make resident so we're really sure it never gets unloaded again. + * Theoretically this is not needed, but practically it doesn't hurt. + * And we're rather safe than sorry. */ + if (plugin->module) + g_module_make_resident (plugin->module); + + if (user_data) { + if (!(((GstPluginInitFullFunc) (desc->plugin_init)) (plugin, user_data))) { + if (GST_CAT_DEFAULT) + GST_WARNING ("plugin \"%s\" failed to initialise", plugin->filename); + return NULL; + } + } else { + if (!((desc->plugin_init) (plugin))) { + if (GST_CAT_DEFAULT) + GST_WARNING ("plugin \"%s\" failed to initialise", plugin->filename); + return NULL; + } + } + + if (GST_CAT_DEFAULT) + GST_LOG ("plugin \"%s\" initialised", GST_STR_NULL (plugin->filename)); + + return plugin; +} + +#ifdef HAVE_SIGACTION +static struct sigaction oldaction; +static gboolean _gst_plugin_fault_handler_is_setup = FALSE; + +/* + * _gst_plugin_fault_handler_restore: + * segfault handler restorer + */ +static void +_gst_plugin_fault_handler_restore (void) +{ + if (!_gst_plugin_fault_handler_is_setup) + return; + + _gst_plugin_fault_handler_is_setup = FALSE; + + sigaction (SIGSEGV, &oldaction, NULL); +} + +/* + * _gst_plugin_fault_handler_sighandler: + * segfault handler implementation + */ +static void +_gst_plugin_fault_handler_sighandler (int signum) +{ + /* We need to restore the fault handler or we'll keep getting it */ + _gst_plugin_fault_handler_restore (); + + switch (signum) { + case SIGSEGV: + g_print ("\nERROR: "); + g_print ("Caught a segmentation fault while loading plugin file:\n"); + g_print ("%s\n\n", _gst_plugin_fault_handler_filename); + g_print ("Please either:\n"); + g_print ("- remove it and restart.\n"); + g_print + ("- run with --gst-disable-segtrap --gst-disable-registry-fork and debug.\n"); + exit (-1); + break; + default: + g_print ("Caught unhandled signal on plugin loading\n"); + break; + } +} + +/* + * _gst_plugin_fault_handler_setup: + * sets up the segfault handler + */ +static void +_gst_plugin_fault_handler_setup (void) +{ + struct sigaction action; + + /* if asked to leave segfaults alone, just return */ + if (!gst_segtrap_is_enabled ()) + return; + + if (_gst_plugin_fault_handler_is_setup) + return; + + _gst_plugin_fault_handler_is_setup = TRUE; + + memset (&action, 0, sizeof (action)); + action.sa_handler = _gst_plugin_fault_handler_sighandler; + + sigaction (SIGSEGV, &action, &oldaction); +} +#else /* !HAVE_SIGACTION */ +static void +_gst_plugin_fault_handler_restore (void) +{ +} + +static void +_gst_plugin_fault_handler_setup (void) +{ +} +#endif /* HAVE_SIGACTION */ + +/* g_time_val_from_iso8601() doesn't do quite what we want */ +static gboolean +check_release_datetime (const gchar * date_time) +{ + guint64 val; + + /* we require YYYY-MM-DD or YYYY-MM-DDTHH:MMZ format */ + if (!g_ascii_isdigit (*date_time)) + return FALSE; + + val = g_ascii_strtoull (date_time, (gchar **) & date_time, 10); + if (val < 2000 || val > 2100 || *date_time != '-') + return FALSE; + + val = g_ascii_strtoull (date_time + 1, (gchar **) & date_time, 10); + if (val == 0 || val > 12 || *date_time != '-') + return FALSE; + + val = g_ascii_strtoull (date_time + 1, (gchar **) & date_time, 10); + if (val == 0 || val > 32) + return FALSE; + + /* end of string or date/time separator + HH:MMZ */ + if (*date_time == 'T' || *date_time == ' ') { + val = g_ascii_strtoull (date_time + 1, (gchar **) & date_time, 10); + if (val > 24 || *date_time != ':') + return FALSE; + + val = g_ascii_strtoull (date_time + 1, (gchar **) & date_time, 10); + if (val > 59 || *date_time != 'Z') + return FALSE; + + ++date_time; + } + + return (*date_time == '\0'); +} + +static GStaticMutex gst_plugin_loading_mutex = G_STATIC_MUTEX_INIT; + +#define CHECK_PLUGIN_DESC_FIELD(desc,field,fn) \ + if (G_UNLIKELY ((desc)->field == NULL)) { \ + GST_ERROR ("GstPluginDesc for '%s' has no %s", fn, G_STRINGIFY (field)); \ + } + +/** + * gst_plugin_load_file: + * @filename: the plugin filename to load + * @error: pointer to a NULL-valued GError + * + * Loads the given plugin and refs it. Caller needs to unref after use. + * + * Returns: (transfer full): a reference to the existing loaded GstPlugin, a + * reference to the newly-loaded GstPlugin, or NULL if an error occurred. + */ +GstPlugin * +gst_plugin_load_file (const gchar * filename, GError ** error) +{ + GstPluginDesc *desc; + GstPlugin *plugin; + GModule *module; + gboolean ret; + gpointer ptr; + GStatBuf file_status; + GstRegistry *registry; + gboolean new_plugin = TRUE; + GModuleFlags flags; + + g_return_val_if_fail (filename != NULL, NULL); + + registry = gst_registry_get_default (); + g_static_mutex_lock (&gst_plugin_loading_mutex); + + plugin = gst_registry_lookup (registry, filename); + if (plugin) { + if (plugin->module) { + /* already loaded */ + g_static_mutex_unlock (&gst_plugin_loading_mutex); + return plugin; + } else { + /* load plugin and update fields */ + new_plugin = FALSE; + } + } + + GST_CAT_DEBUG (GST_CAT_PLUGIN_LOADING, "attempt to load plugin \"%s\"", + filename); + + if (g_module_supported () == FALSE) { + GST_CAT_DEBUG (GST_CAT_PLUGIN_LOADING, "module loading not supported"); + g_set_error (error, + GST_PLUGIN_ERROR, + GST_PLUGIN_ERROR_MODULE, "Dynamic loading not supported"); + goto return_error; + } + + if (g_stat (filename, &file_status)) { + GST_CAT_DEBUG (GST_CAT_PLUGIN_LOADING, "problem accessing file"); + g_set_error (error, + GST_PLUGIN_ERROR, + GST_PLUGIN_ERROR_MODULE, "Problem accessing file %s: %s", filename, + g_strerror (errno)); + goto return_error; + } + + flags = G_MODULE_BIND_LOCAL; + /* libgstpython.so is the gst-python plugin loader. It needs to be loaded with + * G_MODULE_BIND_LAZY. + * + * Ideally there should be a generic way for plugins to specify that they + * need to be loaded with _LAZY. + * */ + if (strstr (filename, "libgstpython")) + flags |= G_MODULE_BIND_LAZY; + + module = g_module_open (filename, flags); + if (module == NULL) { + GST_CAT_WARNING (GST_CAT_PLUGIN_LOADING, "module_open failed: %s", + g_module_error ()); + g_set_error (error, + GST_PLUGIN_ERROR, GST_PLUGIN_ERROR_MODULE, "Opening module failed: %s", + g_module_error ()); + /* If we failed to open the shared object, then it's probably because a + * plugin is linked against the wrong libraries. Print out an easy-to-see + * message in this case. */ + g_warning ("Failed to load plugin '%s': %s", filename, g_module_error ()); + goto return_error; + } + + ret = g_module_symbol (module, "gst_plugin_desc", &ptr); + if (!ret) { + GST_DEBUG ("Could not find plugin entry point in \"%s\"", filename); + g_set_error (error, + GST_PLUGIN_ERROR, + GST_PLUGIN_ERROR_MODULE, + "File \"%s\" is not a GStreamer plugin", filename); + g_module_close (module); + goto return_error; + } + + desc = (GstPluginDesc *) ptr; + + if (priv_gst_plugin_loading_have_whitelist () && + !priv_gst_plugin_desc_is_whitelisted (desc, filename)) { + GST_INFO ("Whitelist specified and plugin not in whitelist, not loading: " + "name=%s, package=%s, file=%s", desc->name, desc->source, filename); + g_set_error (error, GST_PLUGIN_ERROR, GST_PLUGIN_ERROR_MODULE, + "Not loading plugin file \"%s\", not in whitelist", filename); + g_module_close (module); + goto return_error; + } + + if (new_plugin) { + plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL); + plugin->file_mtime = file_status.st_mtime; + plugin->file_size = file_status.st_size; + plugin->filename = g_strdup (filename); + plugin->basename = g_path_get_basename (filename); + } + + plugin->module = module; + plugin->orig_desc = desc; + + if (new_plugin) { + /* check plugin description: complain about bad values but accept them, to + * maintain backwards compatibility (FIXME: 0.11) */ + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, name, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, description, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, version, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, license, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, source, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, package, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, origin, filename); + + if (plugin->orig_desc->release_datetime != NULL && + !check_release_datetime (plugin->orig_desc->release_datetime)) { + GST_ERROR ("GstPluginDesc for '%s' has invalid datetime '%s'", + filename, plugin->orig_desc->release_datetime); + plugin->orig_desc->release_datetime = NULL; + } + } + + GST_LOG ("Plugin %p for file \"%s\" prepared, calling entry function...", + plugin, filename); + + /* this is where we load the actual .so, so let's trap SIGSEGV */ + _gst_plugin_fault_handler_setup (); + _gst_plugin_fault_handler_filename = plugin->filename; + + GST_LOG ("Plugin %p for file \"%s\" prepared, registering...", + plugin, filename); + + if (!gst_plugin_register_func (plugin, plugin->orig_desc, NULL)) { + /* remove signal handler */ + _gst_plugin_fault_handler_restore (); + GST_DEBUG ("gst_plugin_register_func failed for plugin \"%s\"", filename); + /* plugin == NULL */ + g_set_error (error, + GST_PLUGIN_ERROR, + GST_PLUGIN_ERROR_MODULE, + "File \"%s\" appears to be a GStreamer plugin, but it failed to initialize", + filename); + goto return_error; + } + + /* remove signal handler */ + _gst_plugin_fault_handler_restore (); + _gst_plugin_fault_handler_filename = NULL; + GST_INFO ("plugin \"%s\" loaded", plugin->filename); + + if (new_plugin) { + gst_object_ref (plugin); + gst_default_registry_add_plugin (plugin); + } + + g_static_mutex_unlock (&gst_plugin_loading_mutex); + return plugin; + +return_error: + { + if (plugin) + gst_object_unref (plugin); + g_static_mutex_unlock (&gst_plugin_loading_mutex); + return NULL; + } +} + +static void +gst_plugin_desc_copy (GstPluginDesc * dest, const GstPluginDesc * src) +{ + dest->major_version = src->major_version; + dest->minor_version = src->minor_version; + dest->name = g_intern_string (src->name); + dest->description = g_intern_string (src->description); + dest->plugin_init = src->plugin_init; + dest->version = g_intern_string (src->version); + dest->license = g_intern_string (src->license); + dest->source = g_intern_string (src->source); + dest->package = g_intern_string (src->package); + dest->origin = g_intern_string (src->origin); + dest->release_datetime = g_intern_string (src->release_datetime); +} + +/** + * gst_plugin_get_name: + * @plugin: plugin to get the name of + * + * Get the short name of the plugin + * + * Returns: the name of the plugin + */ +const gchar * +gst_plugin_get_name (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, NULL); + + return plugin->desc.name; +} + +/** + * gst_plugin_get_description: + * @plugin: plugin to get long name of + * + * Get the long descriptive name of the plugin + * + * Returns: the long name of the plugin + */ +const gchar * +gst_plugin_get_description (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, NULL); + + return plugin->desc.description; +} + +/** + * gst_plugin_get_filename: + * @plugin: plugin to get the filename of + * + * get the filename of the plugin + * + * Returns: the filename of the plugin + */ +const gchar * +gst_plugin_get_filename (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, NULL); + + return plugin->filename; +} + +/** + * gst_plugin_get_version: + * @plugin: plugin to get the version of + * + * get the version of the plugin + * + * Returns: the version of the plugin + */ +const gchar * +gst_plugin_get_version (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, NULL); + + return plugin->desc.version; +} + +/** + * gst_plugin_get_license: + * @plugin: plugin to get the license of + * + * get the license of the plugin + * + * Returns: the license of the plugin + */ +const gchar * +gst_plugin_get_license (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, NULL); + + return plugin->desc.license; +} + +/** + * gst_plugin_get_source: + * @plugin: plugin to get the source of + * + * get the source module the plugin belongs to. + * + * Returns: the source of the plugin + */ +const gchar * +gst_plugin_get_source (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, NULL); + + return plugin->desc.source; +} + +/** + * gst_plugin_get_package: + * @plugin: plugin to get the package of + * + * get the package the plugin belongs to. + * + * Returns: the package of the plugin + */ +const gchar * +gst_plugin_get_package (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, NULL); + + return plugin->desc.package; +} + +/** + * gst_plugin_get_origin: + * @plugin: plugin to get the origin of + * + * get the URL where the plugin comes from + * + * Returns: the origin of the plugin + */ +const gchar * +gst_plugin_get_origin (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, NULL); + + return plugin->desc.origin; +} + +/** + * gst_plugin_get_module: + * @plugin: plugin to query + * + * Gets the #GModule of the plugin. If the plugin isn't loaded yet, NULL is + * returned. + * + * Returns: (transfer none): module belonging to the plugin or NULL if the + * plugin isn't loaded yet. + */ +GModule * +gst_plugin_get_module (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, NULL); + + return plugin->module; +} + +/** + * gst_plugin_is_loaded: + * @plugin: plugin to query + * + * queries if the plugin is loaded into memory + * + * Returns: TRUE is loaded, FALSE otherwise + */ +gboolean +gst_plugin_is_loaded (GstPlugin * plugin) +{ + g_return_val_if_fail (plugin != NULL, FALSE); + + return (plugin->module != NULL || plugin->filename == NULL); +} + +/** + * gst_plugin_get_cache_data: + * @plugin: a plugin + * + * Gets the plugin specific data cache. If it is %NULL there is no cached data + * stored. This is the case when the registry is getting rebuilt. + * + * Returns: (transfer none): The cached data as a #GstStructure or %NULL. + * + * Since: 0.10.24 + */ +const GstStructure * +gst_plugin_get_cache_data (GstPlugin * plugin) +{ + g_return_val_if_fail (GST_IS_PLUGIN (plugin), NULL); + + return plugin->priv->cache_data; +} + +/** + * gst_plugin_set_cache_data: + * @plugin: a plugin + * @cache_data: (transfer full): a structure containing the data to cache + * + * Adds plugin specific data to cache. Passes the ownership of the structure to + * the @plugin. + * + * The cache is flushed every time the registry is rebuilt. + * + * Since: 0.10.24 + */ +void +gst_plugin_set_cache_data (GstPlugin * plugin, GstStructure * cache_data) +{ + g_return_if_fail (GST_IS_PLUGIN (plugin)); + g_return_if_fail (GST_IS_STRUCTURE (cache_data)); + + if (plugin->priv->cache_data) { + gst_structure_free (plugin->priv->cache_data); + } + plugin->priv->cache_data = cache_data; +} + +#if 0 +/** + * gst_plugin_feature_list: + * @plugin: plugin to query + * @filter: the filter to use + * @first: only return first match + * @user_data: user data passed to the filter function + * + * Runs a filter against all plugin features and returns a GList with + * the results. If the first flag is set, only the first match is + * returned (as a list with a single object). + * + * Returns: a GList of features, g_list_free after use. + */ +GList * +gst_plugin_feature_filter (GstPlugin * plugin, + GstPluginFeatureFilter filter, gboolean first, gpointer user_data) +{ + GList *list; + GList *g; + + list = gst_filter_run (plugin->features, (GstFilterFunc) filter, first, + user_data); + for (g = list; g; g = g->next) { + gst_object_ref (plugin); + } + + return list; +} + +typedef struct +{ + GstPluginFeatureFilter filter; + gboolean first; + gpointer user_data; + GList *result; +} +FeatureFilterData; + +static gboolean +_feature_filter (GstPlugin * plugin, gpointer user_data) +{ + GList *result; + FeatureFilterData *data = (FeatureFilterData *) user_data; + + result = gst_plugin_feature_filter (plugin, data->filter, data->first, + data->user_data); + if (result) { + data->result = g_list_concat (data->result, result); + return TRUE; + } + return FALSE; +} + +/** + * gst_plugin_list_feature_filter: + * @list: a #GList of plugins to query + * @filter: the filter function to use + * @first: only return first match + * @user_data: user data passed to the filter function + * + * Runs a filter against all plugin features of the plugins in the given + * list and returns a GList with the results. + * If the first flag is set, only the first match is + * returned (as a list with a single object). + * + * Returns: a GList of features, g_list_free after use. + */ +GList * +gst_plugin_list_feature_filter (GList * list, + GstPluginFeatureFilter filter, gboolean first, gpointer user_data) +{ + FeatureFilterData data; + GList *result; + + data.filter = filter; + data.first = first; + data.user_data = user_data; + data.result = NULL; + + result = gst_filter_run (list, (GstFilterFunc) _feature_filter, first, &data); + g_list_free (result); + + return data.result; +} +#endif + +/** + * gst_plugin_name_filter: + * @plugin: the plugin to check + * @name: the name of the plugin + * + * A standard filter that returns TRUE when the plugin is of the + * given name. + * + * Returns: TRUE if the plugin is of the given name. + */ +gboolean +gst_plugin_name_filter (GstPlugin * plugin, const gchar * name) +{ + return (plugin->desc.name && !strcmp (plugin->desc.name, name)); +} + +#if 0 +/** + * gst_plugin_find_feature: + * @plugin: plugin to get the feature from + * @name: The name of the feature to find + * @type: The type of the feature to find + * + * Find a feature of the given name and type in the given plugin. + * + * Returns: a GstPluginFeature or NULL if the feature was not found. + */ +GstPluginFeature * +gst_plugin_find_feature (GstPlugin * plugin, const gchar * name, GType type) +{ + GList *walk; + GstPluginFeature *result = NULL; + GstTypeNameData data; + + g_return_val_if_fail (name != NULL, NULL); + + data.type = type; + data.name = name; + + walk = gst_filter_run (plugin->features, + (GstFilterFunc) gst_plugin_feature_type_name_filter, TRUE, &data); + + if (walk) { + result = GST_PLUGIN_FEATURE (walk->data); + + gst_object_ref (result); + gst_plugin_feature_list_free (walk); + } + + return result; +} +#endif + +#if 0 +static gboolean +gst_plugin_feature_name_filter (GstPluginFeature * feature, const gchar * name) +{ + return !strcmp (name, GST_PLUGIN_FEATURE_NAME (feature)); +} +#endif + +#if 0 +/** + * gst_plugin_find_feature_by_name: + * @plugin: plugin to get the feature from + * @name: The name of the feature to find + * + * Find a feature of the given name in the given plugin. + * + * Returns: a GstPluginFeature or NULL if the feature was not found. + */ +GstPluginFeature * +gst_plugin_find_feature_by_name (GstPlugin * plugin, const gchar * name) +{ + GList *walk; + GstPluginFeature *result = NULL; + + g_return_val_if_fail (name != NULL, NULL); + + walk = gst_filter_run (plugin->features, + (GstFilterFunc) gst_plugin_feature_name_filter, TRUE, (void *) name); + + if (walk) { + result = GST_PLUGIN_FEATURE (walk->data); + + gst_object_ref (result); + gst_plugin_feature_list_free (walk); + } + + return result; +} +#endif + +/** + * gst_plugin_load_by_name: + * @name: name of plugin to load + * + * Load the named plugin. Refs the plugin. + * + * Returns: (transfer full): a reference to a loaded plugin, or NULL on error. + */ +GstPlugin * +gst_plugin_load_by_name (const gchar * name) +{ + GstPlugin *plugin, *newplugin; + GError *error = NULL; + + GST_DEBUG ("looking up plugin %s in default registry", name); + plugin = gst_registry_find_plugin (gst_registry_get_default (), name); + if (plugin) { + GST_DEBUG ("loading plugin %s from file %s", name, plugin->filename); + newplugin = gst_plugin_load_file (plugin->filename, &error); + gst_object_unref (plugin); + + if (!newplugin) { + GST_WARNING ("load_plugin error: %s", error->message); + g_error_free (error); + return NULL; + } + /* newplugin was reffed by load_file */ + return newplugin; + } + + GST_DEBUG ("Could not find plugin %s in registry", name); + return NULL; +} + +/** + * gst_plugin_load: + * @plugin: (transfer none): plugin to load + * + * Loads @plugin. Note that the *return value* is the loaded plugin; @plugin is + * untouched. The normal use pattern of this function goes like this: + * + * <programlisting> + * GstPlugin *loaded_plugin; + * loaded_plugin = gst_plugin_load (plugin); + * // presumably, we're no longer interested in the potentially-unloaded plugin + * gst_object_unref (plugin); + * plugin = loaded_plugin; + * </programlisting> + * + * Returns: (transfer full): a reference to a loaded plugin, or NULL on error. + */ +GstPlugin * +gst_plugin_load (GstPlugin * plugin) +{ + GError *error = NULL; + GstPlugin *newplugin; + + if (gst_plugin_is_loaded (plugin)) { + return plugin; + } + + if (!(newplugin = gst_plugin_load_file (plugin->filename, &error))) + goto load_error; + + return newplugin; + +load_error: + { + GST_WARNING ("load_plugin error: %s", error->message); + g_error_free (error); + return NULL; + } +} + +/** + * gst_plugin_list_free: + * @list: (transfer full) (element-type Gst.Plugin): list of #GstPlugin + * + * Unrefs each member of @list, then frees the list. + */ +void +gst_plugin_list_free (GList * list) +{ + GList *g; + + for (g = list; g; g = g->next) { + gst_object_unref (GST_PLUGIN_CAST (g->data)); + } + g_list_free (list); +} + +/* ===== plugin dependencies ===== */ + +/* Scenarios: + * ENV + xyz where ENV can contain multiple values separated by SEPARATOR + * xyz may be "" (if ENV contains path to file rather than dir) + * ENV + *xyz same as above, but xyz acts as suffix filter + * ENV + xyz* same as above, but xyz acts as prefix filter (is this needed?) + * ENV + *xyz* same as above, but xyz acts as strstr filter (is this needed?) + * + * same as above, with additional paths hard-coded at compile-time: + * - only check paths + ... if ENV is not set or yields not paths + * - always check paths + ... in addition to ENV + * + * When user specifies set of environment variables, he/she may also use e.g. + * "HOME/.mystuff/plugins", and we'll expand the content of $HOME with the + * remainder + */ + +/* we store in registry: + * sets of: + * { + * - environment variables (array of strings) + * - last hash of env variable contents (uint) (so we can avoid doing stats + * if one of the env vars has changed; premature optimisation galore) + * - hard-coded paths (array of strings) + * - xyz filename/suffix/prefix strings (array of strings) + * - flags (int) + * - last hash of file/dir stats (int) + * } + * (= struct GstPluginDep) + */ + +static guint +gst_plugin_ext_dep_get_env_vars_hash (GstPlugin * plugin, GstPluginDep * dep) +{ + gchar **e; + guint hash; + + /* there's no deeper logic to what we do here; all we want to know (when + * checking if the plugin needs to be rescanned) is whether the content of + * one of the environment variables in the list is different from when it + * was last scanned */ + hash = 0; + for (e = dep->env_vars; e != NULL && *e != NULL; ++e) { + const gchar *val; + gchar env_var[256]; + + /* order matters: "val",NULL needs to yield a different hash than + * NULL,"val", so do a shift here whether the var is set or not */ + hash = hash << 5; + + /* want environment variable at beginning of string */ + if (!g_ascii_isalnum (**e)) { + GST_WARNING_OBJECT (plugin, "string prefix is not a valid environment " + "variable string: %s", *e); + continue; + } + + /* user is allowed to specify e.g. "HOME/.pitivi/plugins" */ + g_strlcpy (env_var, *e, sizeof (env_var)); + g_strdelimit (env_var, "/\\", '\0'); + + if ((val = g_getenv (env_var))) + hash += g_str_hash (val); + } + + return hash; +} + +gboolean +_priv_plugin_deps_env_vars_changed (GstPlugin * plugin) +{ + GList *l; + + for (l = plugin->priv->deps; l != NULL; l = l->next) { + GstPluginDep *dep = l->data; + + if (dep->env_hash != gst_plugin_ext_dep_get_env_vars_hash (plugin, dep)) + return TRUE; + } + + return FALSE; +} + +static GList * +gst_plugin_ext_dep_extract_env_vars_paths (GstPlugin * plugin, + GstPluginDep * dep) +{ + gchar **evars; + GList *paths = NULL; + + for (evars = dep->env_vars; evars != NULL && *evars != NULL; ++evars) { + const gchar *e; + gchar **components; + + /* want environment variable at beginning of string */ + if (!g_ascii_isalnum (**evars)) { + GST_WARNING_OBJECT (plugin, "string prefix is not a valid environment " + "variable string: %s", *evars); + continue; + } + + /* user is allowed to specify e.g. "HOME/.pitivi/plugins", which we want to + * split into the env_var name component and the path component */ + components = g_strsplit_set (*evars, "/\\", 2); + g_assert (components != NULL); + + e = g_getenv (components[0]); + GST_LOG_OBJECT (plugin, "expanding %s = '%s' (path suffix: %s)", + components[0], GST_STR_NULL (e), GST_STR_NULL (components[1])); + + if (components[1] != NULL) { + g_strdelimit (components[1], "/\\", G_DIR_SEPARATOR); + } + + if (e != NULL && *e != '\0') { + gchar **arr; + guint i; + + arr = g_strsplit (e, G_SEARCHPATH_SEPARATOR_S, -1); + + for (i = 0; arr != NULL && arr[i] != NULL; ++i) { + gchar *full_path; + + if (!g_path_is_absolute (arr[i])) { + GST_INFO_OBJECT (plugin, "ignoring environment variable content '%s'" + ": either not an absolute path or not a path at all", arr[i]); + continue; + } + + if (components[1] != NULL) { + full_path = g_build_filename (arr[i], components[1], NULL); + } else { + full_path = g_strdup (arr[i]); + } + + if (!g_list_find_custom (paths, full_path, (GCompareFunc) strcmp)) { + GST_LOG_OBJECT (plugin, "path: '%s'", full_path); + paths = g_list_prepend (paths, full_path); + full_path = NULL; + } else { + GST_LOG_OBJECT (plugin, "path: '%s' (duplicate,ignoring)", full_path); + g_free (full_path); + } + } + + g_strfreev (arr); + } + + g_strfreev (components); + } + + GST_LOG_OBJECT (plugin, "Extracted %d paths from environment", + g_list_length (paths)); + + return paths; +} + +static guint +gst_plugin_ext_dep_get_hash_from_stat_entry (GStatBuf * s) +{ + if (!(s->st_mode & (S_IFDIR | S_IFREG))) + return (guint) - 1; + + /* completely random formula */ + return ((s->st_size << 3) + (s->st_mtime << 5)) ^ s->st_ctime; +} + +static gboolean +gst_plugin_ext_dep_direntry_matches (GstPlugin * plugin, const gchar * entry, + const gchar ** filenames, GstPluginDependencyFlags flags) +{ + /* no filenames specified, match all entries for now (could probably + * optimise by just taking the dir stat hash or so) */ + if (filenames == NULL || *filenames == NULL || **filenames == '\0') + return TRUE; + + while (*filenames != NULL) { + /* suffix match? */ + if (((flags & GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX)) && + g_str_has_suffix (entry, *filenames)) { + return TRUE; + /* else it's an exact match that's needed */ + } else if (strcmp (entry, *filenames) == 0) { + return TRUE; + } + GST_LOG ("%s does not match %s, flags=0x%04x", entry, *filenames, flags); + ++filenames; + } + return FALSE; +} + +static guint +gst_plugin_ext_dep_scan_dir_and_match_names (GstPlugin * plugin, + const gchar * path, const gchar ** filenames, + GstPluginDependencyFlags flags, int depth) +{ + const gchar *entry; + gboolean recurse_dirs; + GError *err = NULL; + GDir *dir; + guint hash = 0; + + recurse_dirs = ! !(flags & GST_PLUGIN_DEPENDENCY_FLAG_RECURSE); + + dir = g_dir_open (path, 0, &err); + if (dir == NULL) { + GST_DEBUG_OBJECT (plugin, "g_dir_open(%s) failed: %s", path, err->message); + g_error_free (err); + return (guint) - 1; + } + + /* FIXME: we're assuming here that we always get the directory entries in + * the same order, and not in a random order */ + while ((entry = g_dir_read_name (dir))) { + gboolean have_match; + GStatBuf s; + gchar *full_path; + guint fhash; + + have_match = + gst_plugin_ext_dep_direntry_matches (plugin, entry, filenames, flags); + + /* avoid the stat if possible */ + if (!have_match && !recurse_dirs) + continue; + + full_path = g_build_filename (path, entry, NULL); + if (g_stat (full_path, &s) < 0) { + fhash = (guint) - 1; + GST_LOG_OBJECT (plugin, "stat: %s (error: %s)", full_path, + g_strerror (errno)); + } else if (have_match) { + fhash = gst_plugin_ext_dep_get_hash_from_stat_entry (&s); + GST_LOG_OBJECT (plugin, "stat: %s (result: %u)", full_path, fhash); + } else if ((s.st_mode & (S_IFDIR))) { + fhash = gst_plugin_ext_dep_scan_dir_and_match_names (plugin, full_path, + filenames, flags, depth + 1); + } else { + /* it's not a name match, we want to recurse, but it's not a directory */ + g_free (full_path); + continue; + } + + hash = (hash + fhash) << 1; + g_free (full_path); + } + + g_dir_close (dir); + return hash; +} + +static guint +gst_plugin_ext_dep_scan_path_with_filenames (GstPlugin * plugin, + const gchar * path, const gchar ** filenames, + GstPluginDependencyFlags flags) +{ + const gchar *empty_filenames[] = { "", NULL }; + gboolean recurse_into_dirs, partial_names; + guint i, hash = 0; + + /* to avoid special-casing below (FIXME?) */ + if (filenames == NULL || *filenames == NULL) + filenames = empty_filenames; + + recurse_into_dirs = ! !(flags & GST_PLUGIN_DEPENDENCY_FLAG_RECURSE); + partial_names = ! !(flags & GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX); + + /* if we can construct the exact paths to check with the data we have, just + * stat them one by one; this is more efficient than opening the directory + * and going through each entry to see if it matches one of our filenames. */ + if (!recurse_into_dirs && !partial_names) { + for (i = 0; filenames[i] != NULL; ++i) { + GStatBuf s; + gchar *full_path; + guint fhash; + + full_path = g_build_filename (path, filenames[i], NULL); + if (g_stat (full_path, &s) < 0) { + fhash = (guint) - 1; + GST_LOG_OBJECT (plugin, "stat: %s (error: %s)", full_path, + g_strerror (errno)); + } else { + fhash = gst_plugin_ext_dep_get_hash_from_stat_entry (&s); + GST_LOG_OBJECT (plugin, "stat: %s (result: %08x)", full_path, fhash); + } + hash = (hash + fhash) << 1; + g_free (full_path); + } + } else { + hash = gst_plugin_ext_dep_scan_dir_and_match_names (plugin, path, + filenames, flags, 0); + } + + return hash; +} + +static guint +gst_plugin_ext_dep_get_stat_hash (GstPlugin * plugin, GstPluginDep * dep) +{ + gboolean paths_are_default_only; + GList *scan_paths; + guint scan_hash = 0; + + GST_LOG_OBJECT (plugin, "start"); + + paths_are_default_only = + dep->flags & GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY; + + scan_paths = gst_plugin_ext_dep_extract_env_vars_paths (plugin, dep); + + if (scan_paths == NULL || !paths_are_default_only) { + gchar **paths; + + for (paths = dep->paths; paths != NULL && *paths != NULL; ++paths) { + const gchar *path = *paths; + + if (!g_list_find_custom (scan_paths, path, (GCompareFunc) strcmp)) { + GST_LOG_OBJECT (plugin, "path: '%s'", path); + scan_paths = g_list_prepend (scan_paths, g_strdup (path)); + } else { + GST_LOG_OBJECT (plugin, "path: '%s' (duplicate, ignoring)", path); + } + } + } + + /* not that the order really matters, but it makes debugging easier */ + scan_paths = g_list_reverse (scan_paths); + + while (scan_paths != NULL) { + const gchar *path = scan_paths->data; + + scan_hash += gst_plugin_ext_dep_scan_path_with_filenames (plugin, path, + (const gchar **) dep->names, dep->flags); + scan_hash = scan_hash << 1; + + g_free (scan_paths->data); + scan_paths = g_list_delete_link (scan_paths, scan_paths); + } + + GST_LOG_OBJECT (plugin, "done, scan_hash: %08x", scan_hash); + return scan_hash; +} + +gboolean +_priv_plugin_deps_files_changed (GstPlugin * plugin) +{ + GList *l; + + for (l = plugin->priv->deps; l != NULL; l = l->next) { + GstPluginDep *dep = l->data; + + if (dep->stat_hash != gst_plugin_ext_dep_get_stat_hash (plugin, dep)) + return TRUE; + } + + return FALSE; +} + +static void +gst_plugin_ext_dep_free (GstPluginDep * dep) +{ + g_strfreev (dep->env_vars); + g_strfreev (dep->paths); + g_strfreev (dep->names); + g_slice_free (GstPluginDep, dep); +} + +static gboolean +gst_plugin_ext_dep_strv_equal (gchar ** arr1, gchar ** arr2) +{ + if (arr1 == arr2) + return TRUE; + if (arr1 == NULL || arr2 == NULL) + return FALSE; + for (; *arr1 != NULL && *arr2 != NULL; ++arr1, ++arr2) { + if (strcmp (*arr1, *arr2) != 0) + return FALSE; + } + return (*arr1 == *arr2); +} + +static gboolean +gst_plugin_ext_dep_equals (GstPluginDep * dep, const gchar ** env_vars, + const gchar ** paths, const gchar ** names, GstPluginDependencyFlags flags) +{ + if (dep->flags != flags) + return FALSE; + + return gst_plugin_ext_dep_strv_equal (dep->env_vars, (gchar **) env_vars) && + gst_plugin_ext_dep_strv_equal (dep->paths, (gchar **) paths) && + gst_plugin_ext_dep_strv_equal (dep->names, (gchar **) names); +} + +/** + * gst_plugin_add_dependency: + * @plugin: a #GstPlugin + * @env_vars: NULL-terminated array of environment variables affecting the + * feature set of the plugin (e.g. an environment variable containing + * paths where to look for additional modules/plugins of a library), + * or NULL. Environment variable names may be followed by a path component + * which will be added to the content of the environment variable, e.g. + * "HOME/.mystuff/plugins". + * @paths: NULL-terminated array of directories/paths where dependent files + * may be. + * @names: NULL-terminated array of file names (or file name suffixes, + * depending on @flags) to be used in combination with the paths from + * @paths and/or the paths extracted from the environment variables in + * @env_vars, or NULL. + * @flags: optional flags, or #GST_PLUGIN_DEPENDENCY_FLAG_NONE + * + * Make GStreamer aware of external dependencies which affect the feature + * set of this plugin (ie. the elements or typefinders associated with it). + * + * GStreamer will re-inspect plugins with external dependencies whenever any + * of the external dependencies change. This is useful for plugins which wrap + * other plugin systems, e.g. a plugin which wraps a plugin-based visualisation + * library and makes visualisations available as GStreamer elements, or a + * codec loader which exposes elements and/or caps dependent on what external + * codec libraries are currently installed. + * + * Since: 0.10.22 + */ +void +gst_plugin_add_dependency (GstPlugin * plugin, const gchar ** env_vars, + const gchar ** paths, const gchar ** names, GstPluginDependencyFlags flags) +{ + GstPluginDep *dep; + GList *l; + + g_return_if_fail (GST_IS_PLUGIN (plugin)); + + if ((env_vars == NULL || env_vars[0] == NULL) && + (paths == NULL || paths[0] == NULL)) { + GST_DEBUG_OBJECT (plugin, + "plugin registered empty dependency set. Ignoring"); + return; + } + + for (l = plugin->priv->deps; l != NULL; l = l->next) { + if (gst_plugin_ext_dep_equals (l->data, env_vars, paths, names, flags)) { + GST_LOG_OBJECT (plugin, "dependency already registered"); + return; + } + } + + dep = g_slice_new (GstPluginDep); + + dep->env_vars = g_strdupv ((gchar **) env_vars); + dep->paths = g_strdupv ((gchar **) paths); + dep->names = g_strdupv ((gchar **) names); + dep->flags = flags; + + dep->env_hash = gst_plugin_ext_dep_get_env_vars_hash (plugin, dep); + dep->stat_hash = gst_plugin_ext_dep_get_stat_hash (plugin, dep); + + plugin->priv->deps = g_list_append (plugin->priv->deps, dep); + + GST_DEBUG_OBJECT (plugin, "added dependency:"); + for (; env_vars != NULL && *env_vars != NULL; ++env_vars) + GST_DEBUG_OBJECT (plugin, " evar: %s", *env_vars); + for (; paths != NULL && *paths != NULL; ++paths) + GST_DEBUG_OBJECT (plugin, " path: %s", *paths); + for (; names != NULL && *names != NULL; ++names) + GST_DEBUG_OBJECT (plugin, " name: %s", *names); +} + +/** + * gst_plugin_add_dependency_simple: + * @plugin: the #GstPlugin + * @env_vars: one or more environment variables (separated by ':', ';' or ','), + * or NULL. Environment variable names may be followed by a path component + * which will be added to the content of the environment variable, e.g. + * "HOME/.mystuff/plugins:MYSTUFF_PLUGINS_PATH" + * @paths: one ore more directory paths (separated by ':' or ';' or ','), + * or NULL. Example: "/usr/lib/mystuff/plugins" + * @names: one or more file names or file name suffixes (separated by commas), + * or NULL + * @flags: optional flags, or #GST_PLUGIN_DEPENDENCY_FLAG_NONE + * + * Make GStreamer aware of external dependencies which affect the feature + * set of this plugin (ie. the elements or typefinders associated with it). + * + * GStreamer will re-inspect plugins with external dependencies whenever any + * of the external dependencies change. This is useful for plugins which wrap + * other plugin systems, e.g. a plugin which wraps a plugin-based visualisation + * library and makes visualisations available as GStreamer elements, or a + * codec loader which exposes elements and/or caps dependent on what external + * codec libraries are currently installed. + * + * Convenience wrapper function for gst_plugin_add_dependency() which + * takes simple strings as arguments instead of string arrays, with multiple + * arguments separated by predefined delimiters (see above). + * + * Since: 0.10.22 + */ +void +gst_plugin_add_dependency_simple (GstPlugin * plugin, + const gchar * env_vars, const gchar * paths, const gchar * names, + GstPluginDependencyFlags flags) +{ + gchar **a_evars = NULL; + gchar **a_paths = NULL; + gchar **a_names = NULL; + + if (env_vars) + a_evars = g_strsplit_set (env_vars, ":;,", -1); + if (paths) + a_paths = g_strsplit_set (paths, ":;,", -1); + if (names) + a_names = g_strsplit_set (names, ",", -1); + + gst_plugin_add_dependency (plugin, (const gchar **) a_evars, + (const gchar **) a_paths, (const gchar **) a_names, flags); + + if (a_evars) + g_strfreev (a_evars); + if (a_paths) + g_strfreev (a_paths); + if (a_names) + g_strfreev (a_names); +} diff --git a/gst/gstplugin.h b/gst/gstplugin.h new file mode 100644 index 0000000..32bec0d --- /dev/null +++ b/gst/gstplugin.h @@ -0,0 +1,360 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstplugin.h: Header for plugin subsystem + * + * 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_PLUGIN_H__ +#define __GST_PLUGIN_H__ + +#include <gst/gstconfig.h> + +#include <time.h> /* time_t */ +#include <sys/types.h> /* off_t */ +#include <sys/stat.h> /* off_t */ +#include <gmodule.h> +#include <gst/gstobject.h> +#include <gst/gstmacros.h> +#include <gst/gststructure.h> + +G_BEGIN_DECLS + +typedef struct _GstPlugin GstPlugin; +typedef struct _GstPluginClass GstPluginClass; +typedef struct _GstPluginPrivate GstPluginPrivate; +typedef struct _GstPluginDesc GstPluginDesc; + +/** + * gst_plugin_error_quark: + * + * Get the error quark. + * + * Returns: The error quark used in GError messages + */ +GQuark gst_plugin_error_quark (void); +/** + * GST_PLUGIN_ERROR: + * + * The error message category quark + */ +#define GST_PLUGIN_ERROR gst_plugin_error_quark () + +/** + * GstPluginError: + * @GST_PLUGIN_ERROR_MODULE: The plugin could not be loaded + * @GST_PLUGIN_ERROR_DEPENDENCIES: The plugin has unresolved dependencies + * @GST_PLUGIN_ERROR_NAME_MISMATCH: The plugin has already be loaded from a different file + * + * The plugin loading errors + */ +typedef enum +{ + GST_PLUGIN_ERROR_MODULE, + GST_PLUGIN_ERROR_DEPENDENCIES, + GST_PLUGIN_ERROR_NAME_MISMATCH +} GstPluginError; + +/** + * GstPluginFlags: + * @GST_PLUGIN_FLAG_CACHED: Temporarily loaded plugins + * @GST_PLUGIN_FLAG_BLACKLISTED: The plugin won't be scanned (again) + * + * The plugin loading state + */ +typedef enum +{ + GST_PLUGIN_FLAG_CACHED = (1<<0), + GST_PLUGIN_FLAG_BLACKLISTED = (1<<1) +} GstPluginFlags; + +/** + * GstPluginDependencyFlags: + * @GST_PLUGIN_DEPENDENCY_FLAG_NONE : no special flags + * @GST_PLUGIN_DEPENDENCY_FLAG_RECURSE : recurse into subdirectories + * @GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY : use paths + * argument only if none of the environment variables is set + * @GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX : interpret + * filename argument as filter suffix and check all matching files in + * the directory + * + * Flags used in connection with gst_plugin_add_dependency(). + * + * Since: 0.10.22 + */ +typedef enum { + GST_PLUGIN_DEPENDENCY_FLAG_NONE = 0, + GST_PLUGIN_DEPENDENCY_FLAG_RECURSE = (1 << 0), + GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY = (1 << 1), + GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX = (1 << 2) +} GstPluginDependencyFlags; + +/** + * GstPluginInitFunc: + * @plugin: The plugin object + * + * A plugin should provide a pointer to a function of this type in the + * plugin_desc struct. + * This function will be called by the loader at startup. One would then + * register each #GstPluginFeature. + * + * Returns: %TRUE if plugin initialised successfully + */ +/* FIXME 0.11: Make return void */ +typedef gboolean (*GstPluginInitFunc) (GstPlugin *plugin); + +/** + * GstPluginInitFullFunc: + * @plugin: The plugin object + * @user_data: extra data + * + * A plugin should provide a pointer to a function of either #GstPluginInitFunc + * or this type in the plugin_desc struct. + * The function will be called by the loader at startup. One would then + * register each #GstPluginFeature. This version allows + * user data to be passed to init function (useful for bindings). + * + * Returns: %TRUE if plugin initialised successfully + * + * Since: 0.10.24 + * + */ +/* FIXME 0.11: Merge with GstPluginInitFunc */ +typedef gboolean (*GstPluginInitFullFunc) (GstPlugin *plugin, gpointer user_data); + +/** + * GstPluginDesc: + * @major_version: the major version number of core that plugin was compiled for + * @minor_version: the minor version number of core that plugin was compiled for + * @name: a unique name of the plugin + * @description: description of plugin + * @plugin_init: pointer to the init function of this plugin. + * @version: version of the plugin + * @license: effective license of plugin + * @source: source module plugin belongs to + * @package: shipped package plugin belongs to + * @origin: URL to provider of plugin + * @release_datetime: date time string in ISO 8601 format (or rather, a + * subset thereof), or NULL. Allowed are the following formats: + * "YYYY-MM-DD" and "YYY-MM-DDTHH:MMZ" (with 'T' a separator and 'Z' + * indicating UTC/Zulu time). This field should be set via the + * GST_PACKAGE_RELEASE_DATETIME preprocessor macro (Since: 0.10.31) + * + * A plugin should export a variable of this type called plugin_desc. The plugin + * loader will use the data provided there to initialize the plugin. + * + * The @licence parameter must be one of: LGPL, GPL, QPL, GPL/QPL, MPL, + * BSD, MIT/X11, Proprietary, unknown. + */ +struct _GstPluginDesc { + gint major_version; + gint minor_version; + const gchar *name; + const gchar *description; + GstPluginInitFunc plugin_init; + const gchar *version; + const gchar *license; + const gchar *source; + const gchar *package; + const gchar *origin; + const gchar *release_datetime; + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + + +#define GST_TYPE_PLUGIN (gst_plugin_get_type()) +#define GST_IS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLUGIN)) +#define GST_IS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLUGIN)) +#define GST_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLUGIN, GstPluginClass)) +#define GST_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLUGIN, GstPlugin)) +#define GST_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLUGIN, GstPluginClass)) +#define GST_PLUGIN_CAST(obj) ((GstPlugin*)(obj)) + +/** + * GstPlugin: + * + * The plugin object + */ +struct _GstPlugin { + GstObject object; + + /*< private >*/ + GstPluginDesc desc; + + GstPluginDesc *orig_desc; + + unsigned int flags; + + gchar * filename; + gchar * basename; /* base name (non-dir part) of plugin path */ + + GModule * module; /* contains the module if plugin is loaded */ + + off_t file_size; + time_t file_mtime; + gboolean registered; /* TRUE when the registry has seen a filename + * that matches the plugin's basename */ + + GstPluginPrivate *priv; + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstPluginClass { + GstObjectClass object_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +#ifdef GST_PACKAGE_RELEASE_DATETIME +#define __GST_PACKAGE_RELEASE_DATETIME GST_PACKAGE_RELEASE_DATETIME +#else +#define __GST_PACKAGE_RELEASE_DATETIME NULL +#endif + +/** + * GST_PLUGIN_DEFINE: + * @major: major version number of the gstreamer-core that plugin was compiled for + * @minor: minor version number of the gstreamer-core that plugin was compiled for + * @name: short, but unique name of the plugin + * @description: information about the purpose of the plugin + * @init: function pointer to the plugin_init method with the signature of <code>static gboolean plugin_init (GstPlugin * plugin)</code>. + * @version: full version string (e.g. VERSION from config.h) + * @license: under which licence the package has been released, e.g. GPL, LGPL. + * @package: the package-name (e.g. PACKAGE_NAME from config.h) + * @origin: a description from where the package comes from (e.g. the homepage URL) + * + * This macro needs to be used to define the entry point and meta data of a + * plugin. One would use this macro to export a plugin, so that it can be used + * by other applications. + * + * The macro uses a define named PACKAGE for the #GstPluginDesc,source field. + * When using autoconf, this is usually set automatically via the AC_INIT + * macro, and set in config.h. If you are not using autoconf, you will need to + * define PACKAGE yourself and set it to a short mnemonic string identifying + * your application/package, e.g. 'someapp' or 'my-plugins-foo. + * + * If defined, the GST_PACKAGE_RELEASE_DATETIME will also be used for the + * #GstPluginDesc,release_datetime field. + */ +#define GST_PLUGIN_DEFINE(major,minor,name,description,init,version,license,package,origin) \ +G_BEGIN_DECLS \ +GST_PLUGIN_EXPORT GstPluginDesc gst_plugin_desc = { \ + major, \ + minor, \ + name, \ + (gchar *) description, \ + init, \ + version, \ + license, \ + PACKAGE, \ + package, \ + origin, \ + __GST_PACKAGE_RELEASE_DATETIME, \ + GST_PADDING_INIT \ +}; \ +G_END_DECLS + +/** + * GST_LICENSE_UNKNOWN: + * + * To be used in GST_PLUGIN_DEFINE or GST_PLUGIN_DEFINE_STATIC if usure about + * the licence. + */ +#define GST_LICENSE_UNKNOWN "unknown" + + +/* function for filters */ +/** + * GstPluginFilter: + * @plugin: the plugin to check + * @user_data: the user_data that has been passed on e.g. gst_registry_plugin_filter() + * + * A function that can be used with e.g. gst_registry_plugin_filter() + * to get a list of plugins that match certain criteria. + * + * Returns: TRUE for a positive match, FALSE otherwise + */ +typedef gboolean (*GstPluginFilter) (GstPlugin *plugin, + gpointer user_data); + +GType gst_plugin_get_type (void); + +gboolean gst_plugin_register_static (gint major_version, + gint minor_version, + const gchar *name, + const gchar *description, + GstPluginInitFunc init_func, + const gchar *version, + const gchar *license, + const gchar *source, + const gchar *package, + const gchar *origin); + +gboolean gst_plugin_register_static_full (gint major_version, + gint minor_version, + const gchar *name, + const gchar *description, + GstPluginInitFullFunc init_full_func, + const gchar *version, + const gchar *license, + const gchar *source, + const gchar *package, + const gchar *origin, + gpointer user_data); + +const gchar* gst_plugin_get_name (GstPlugin *plugin); +const gchar* gst_plugin_get_description (GstPlugin *plugin); +const gchar* gst_plugin_get_filename (GstPlugin *plugin); +const gchar* gst_plugin_get_version (GstPlugin *plugin); +const gchar* gst_plugin_get_license (GstPlugin *plugin); +const gchar* gst_plugin_get_source (GstPlugin *plugin); +const gchar* gst_plugin_get_package (GstPlugin *plugin); +const gchar* gst_plugin_get_origin (GstPlugin *plugin); +const GstStructure* gst_plugin_get_cache_data (GstPlugin * plugin); +void gst_plugin_set_cache_data (GstPlugin * plugin, GstStructure *cache_data); + +GModule * gst_plugin_get_module (GstPlugin *plugin); +gboolean gst_plugin_is_loaded (GstPlugin *plugin); + +gboolean gst_plugin_name_filter (GstPlugin *plugin, const gchar *name); + +GstPlugin * gst_plugin_load_file (const gchar *filename, GError** error); + +GstPlugin * gst_plugin_load (GstPlugin *plugin); +GstPlugin * gst_plugin_load_by_name (const gchar *name); + +void gst_plugin_add_dependency (GstPlugin * plugin, + const gchar ** env_vars, + const gchar ** paths, + const gchar ** names, + GstPluginDependencyFlags flags); + +void gst_plugin_add_dependency_simple (GstPlugin * plugin, + const gchar * env_vars, + const gchar * paths, + const gchar * names, + GstPluginDependencyFlags flags); + +void gst_plugin_list_free (GList *list); + +G_END_DECLS + +#endif /* __GST_PLUGIN_H__ */ diff --git a/gst/gstpluginfeature.c b/gst/gstpluginfeature.c new file mode 100644 index 0000000..79925a6 --- /dev/null +++ b/gst/gstpluginfeature.c @@ -0,0 +1,387 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstpluginfeature.c: Abstract base class for all plugin features + * + * 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:gstpluginfeature + * @short_description: Base class for contents of a GstPlugin + * @see_also: #GstPlugin + * + * This is a base class for anything that can be added to a #GstPlugin. + */ + +#include "gst_private.h" + +#include "gstpluginfeature.h" +#include "gstplugin.h" +#include "gstregistry.h" +#include "gstinfo.h" + +#include <stdio.h> +#include <string.h> + +#define GST_CAT_DEFAULT GST_CAT_PLUGIN_LOADING + +static void gst_plugin_feature_finalize (GObject * object); + +/* static guint gst_plugin_feature_signals[LAST_SIGNAL] = { 0 }; */ + +G_DEFINE_ABSTRACT_TYPE (GstPluginFeature, gst_plugin_feature, GST_TYPE_OBJECT); + +static void +gst_plugin_feature_class_init (GstPluginFeatureClass * klass) +{ + G_OBJECT_CLASS (klass)->finalize = gst_plugin_feature_finalize; +} + +static void +gst_plugin_feature_init (GstPluginFeature * feature) +{ + /* do nothing, needed because of G_DEFINE_TYPE */ +} + +static void +gst_plugin_feature_finalize (GObject * object) +{ + GstPluginFeature *feature = GST_PLUGIN_FEATURE_CAST (object); + + GST_DEBUG ("finalizing feature %p: '%s'", feature, GST_OBJECT_NAME (feature)); + + if (feature->plugin != NULL) { + g_object_remove_weak_pointer ((GObject *) feature->plugin, + (gpointer *) & feature->plugin); + } + + G_OBJECT_CLASS (gst_plugin_feature_parent_class)->finalize (object); +} + +/** + * gst_plugin_feature_load: + * @feature: (transfer none): the plugin feature to check + * + * Loads the plugin containing @feature if it's not already loaded. @feature is + * unaffected; use the return value instead. + * + * Normally this function is used like this: + * |[ + * GstPluginFeature *loaded_feature; + * + * loaded_feature = gst_plugin_feature_load (feature); + * // presumably, we're no longer interested in the potentially-unloaded feature + * gst_object_unref (feature); + * feature = loaded_feature; + * ]| + * + * Returns: (transfer full): a reference to the loaded feature, or NULL on error + */ +GstPluginFeature * +gst_plugin_feature_load (GstPluginFeature * feature) +{ + GstPlugin *plugin; + GstPluginFeature *real_feature; + + g_return_val_if_fail (feature != NULL, FALSE); + g_return_val_if_fail (GST_IS_PLUGIN_FEATURE (feature), FALSE); + + GST_DEBUG ("loading plugin for feature %p; '%s'", feature, + GST_OBJECT_NAME (feature)); + if (feature->loaded) + return gst_object_ref (feature); + + GST_DEBUG ("loading plugin %s", feature->plugin_name); + plugin = gst_plugin_load_by_name (feature->plugin_name); + if (!plugin) + goto load_failed; + + GST_DEBUG ("loaded plugin %s", feature->plugin_name); + gst_object_unref (plugin); + + real_feature = + gst_registry_lookup_feature (gst_registry_get_default (), + GST_OBJECT_NAME (feature)); + + if (real_feature == NULL) + goto disappeared; + else if (!real_feature->loaded) + goto not_found; + + return real_feature; + + /* ERRORS */ +load_failed: + { + GST_WARNING ("Failed to load plugin containing feature '%s'.", + GST_OBJECT_NAME (feature)); + return NULL; + } +disappeared: + { + GST_INFO + ("Loaded plugin containing feature '%s', but feature disappeared.", + GST_OBJECT_NAME (feature)); + return NULL; + } +not_found: + { + GST_INFO ("Tried to load plugin containing feature '%s', but feature was " + "not found.", GST_OBJECT_NAME (real_feature)); + return NULL; + } +} + +/** + * gst_plugin_feature_type_name_filter: + * @feature: the #GstPluginFeature + * @data: (in): the type and name to check against + * + * Compares type and name of plugin feature. Can be used with gst_filter_run(). + * + * Returns: TRUE if equal. + */ +gboolean +gst_plugin_feature_type_name_filter (GstPluginFeature * feature, + GstTypeNameData * data) +{ + g_return_val_if_fail (GST_IS_PLUGIN_FEATURE (feature), FALSE); + + return ((data->type == 0 || data->type == G_OBJECT_TYPE (feature)) && + (data->name == NULL || !strcmp (data->name, GST_OBJECT_NAME (feature)))); +} + +/** + * gst_plugin_feature_set_rank: + * @feature: feature to rank + * @rank: rank value - higher number means more priority rank + * + * Specifies a rank for a plugin feature, so that autoplugging uses + * the most appropriate feature. + */ +void +gst_plugin_feature_set_rank (GstPluginFeature * feature, guint rank) +{ + g_return_if_fail (feature != NULL); + g_return_if_fail (GST_IS_PLUGIN_FEATURE (feature)); + + feature->rank = rank; +} + +/** + * gst_plugin_feature_get_rank: + * @feature: a feature + * + * Gets the rank of a plugin feature. + * + * Returns: The rank of the feature + */ +guint +gst_plugin_feature_get_rank (GstPluginFeature * feature) +{ + g_return_val_if_fail (GST_IS_PLUGIN_FEATURE (feature), GST_RANK_NONE); + + return feature->rank; +} + +/** + * gst_plugin_feature_list_free: + * @list: (transfer full) (element-type Gst.PluginFeature): list + * of #GstPluginFeature + * + * Unrefs each member of @list, then frees the list. + */ +void +gst_plugin_feature_list_free (GList * list) +{ + GList *g; + + for (g = list; g; g = g->next) { + GstPluginFeature *feature = GST_PLUGIN_FEATURE_CAST (g->data); + + gst_object_unref (feature); + } + g_list_free (list); +} + +/** + * gst_plugin_feature_list_copy: + * @list: (transfer none) (element-type Gst.PluginFeature): list + * of #GstPluginFeature + * + * Copies the list of features. Caller should call @gst_plugin_feature_list_free + * when done with the list. + * + * Returns: (transfer full) (element-type Gst.PluginFeature): a copy of @list, + * with each feature's reference count incremented. + * + * Since: 0.10.26 + */ +GList * +gst_plugin_feature_list_copy (GList * list) +{ + GList *new_list = NULL; + + if (G_LIKELY (list)) { + GList *last; + + new_list = g_list_alloc (); + new_list->data = g_object_ref ((GObject *) list->data); + new_list->prev = NULL; + last = new_list; + list = list->next; + while (list) { + last->next = g_list_alloc (); + last->next->prev = last; + last = last->next; + last->data = g_object_ref ((GObject *) list->data); + list = list->next; + } + last->next = NULL; + } + + return new_list; +} + +/** + * gst_plugin_feature_list_debug: + * @list: (transfer none) (element-type Gst.PluginFeature): a #GList of + * plugin features + * + * Debug the plugin feature names in @list. + * + * Since: 0.10.31 + */ +void +gst_plugin_feature_list_debug (GList * list) +{ +#ifndef GST_DISABLE_GST_DEBUG + while (list) { + GST_DEBUG ("%s", + gst_plugin_feature_get_name ((GstPluginFeature *) list->data)); + list = list->next; + } +#endif +} + +/** + * gst_plugin_feature_check_version: + * @feature: a feature + * @min_major: minimum required major version + * @min_minor: minimum required minor version + * @min_micro: minimum required micro version + * + * Checks whether the given plugin feature is at least + * the required version + * + * Returns: #TRUE if the plugin feature has at least + * the required version, otherwise #FALSE. + */ +gboolean +gst_plugin_feature_check_version (GstPluginFeature * feature, + guint min_major, guint min_minor, guint min_micro) +{ + GstRegistry *registry; + GstPlugin *plugin; + gboolean ret = FALSE; + + g_return_val_if_fail (feature != NULL, FALSE); + g_return_val_if_fail (GST_IS_PLUGIN_FEATURE (feature), FALSE); + + GST_DEBUG ("Looking up plugin '%s' containing plugin feature '%s'", + feature->plugin_name, GST_OBJECT_NAME (feature)); + + registry = gst_registry_get_default (); + plugin = gst_registry_find_plugin (registry, feature->plugin_name); + + if (plugin) { + const gchar *ver_str; + guint major, minor, micro, nano; + gint nscan; + + ver_str = gst_plugin_get_version (plugin); + g_return_val_if_fail (ver_str != NULL, FALSE); + + nscan = sscanf (ver_str, "%u.%u.%u.%u", &major, &minor, µ, &nano); + GST_DEBUG ("version string '%s' parsed to %d values", ver_str, nscan); + + if (nscan >= 3) { + if (major > min_major) + ret = TRUE; + else if (major < min_major) + ret = FALSE; + else if (minor > min_minor) + ret = TRUE; + else if (minor < min_minor) + ret = FALSE; + else if (micro > min_micro) + ret = TRUE; + /* micro is 1 smaller but we have a nano version, this is the upcoming + * release of the requested version and we're ok then */ + else if (nscan == 4 && nano > 0 && (micro + 1 == min_micro)) + ret = TRUE; + else + ret = (micro == min_micro); + + GST_DEBUG ("Checking whether %u.%u.%u >= %u.%u.%u? %s", major, minor, + micro, min_major, min_minor, min_micro, (ret) ? "yes" : "no"); + } else { + GST_WARNING ("Could not parse version string '%s' of plugin '%s'", + ver_str, feature->plugin_name); + } + + gst_object_unref (plugin); + } else { + GST_DEBUG ("Could not find plugin '%s'", feature->plugin_name); + } + + return ret; +} + +/** + * gst_plugin_feature_rank_compare_func: + * @p1: a #GstPluginFeature + * @p2: a #GstPluginFeature + * + * Compares the two given #GstPluginFeature instances. This function can be + * used as a #GCompareFunc when sorting by rank and then by name. + * + * Returns: negative value if the rank of p1 > the rank of p2 or the ranks are + * equal but the name of p1 comes before the name of p2; zero if the rank + * and names are equal; positive value if the rank of p1 < the rank of p2 or the + * ranks are equal but the name of p2 comes after the name of p1 + * + * Since: 0.10.31 + */ +gint +gst_plugin_feature_rank_compare_func (gconstpointer p1, gconstpointer p2) +{ + GstPluginFeature *f1, *f2; + gint diff; + + f1 = (GstPluginFeature *) p1; + f2 = (GstPluginFeature *) p2; + + diff = f2->rank - f1->rank; + if (diff != 0) + return diff; + + diff = strcmp (GST_OBJECT_NAME (f2), GST_OBJECT_NAME (f1)); + + return diff; +} diff --git a/gst/gstpluginfeature.h b/gst/gstpluginfeature.h new file mode 100644 index 0000000..b8db0f4 --- /dev/null +++ b/gst/gstpluginfeature.h @@ -0,0 +1,183 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstpluginfeature.h: Header for base GstPluginFeature + * + * 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_PLUGIN_FEATURE_H__ +#define __GST_PLUGIN_FEATURE_H__ + +#include <glib-object.h> +#include <gst/gstobject.h> +#include <gst/gstplugin.h> + +G_BEGIN_DECLS + +#define GST_TYPE_PLUGIN_FEATURE (gst_plugin_feature_get_type()) +#define GST_PLUGIN_FEATURE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLUGIN_FEATURE, GstPluginFeature)) +#define GST_IS_PLUGIN_FEATURE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLUGIN_FEATURE)) +#define GST_PLUGIN_FEATURE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLUGIN_FEATURE, GstPluginFeatureClass)) +#define GST_IS_PLUGIN_FEATURE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLUGIN_FEATURE)) +#define GST_PLUGIN_FEATURE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLUGIN_FEATURE, GstPluginFeatureClass)) +#define GST_PLUGIN_FEATURE_CAST(obj) ((GstPluginFeature*)(obj)) + +typedef struct _GstPluginFeature GstPluginFeature; +typedef struct _GstPluginFeatureClass GstPluginFeatureClass; + +/** + * GstRank: + * @GST_RANK_NONE: will be chosen last or not at all + * @GST_RANK_MARGINAL: unlikely to be chosen + * @GST_RANK_SECONDARY: likely to be chosen + * @GST_RANK_PRIMARY: will be chosen first + * + * Element priority ranks. Defines the order in which the autoplugger (or + * similar rank-picking mechanisms, such as e.g. gst_element_make_from_uri()) + * will choose this element over an alternative one with the same function. + * + * These constants serve as a rough guidance for defining the rank of a + * #GstPluginFeature. Any value is valid, including values bigger than + * @GST_RANK_PRIMARY. + */ +typedef enum { + GST_RANK_NONE = 0, + GST_RANK_MARGINAL = 64, + GST_RANK_SECONDARY = 128, + GST_RANK_PRIMARY = 256 +} GstRank; + +/** + * gst_plugin_feature_get_name: + * @feature: a #GstPluginFeature to get the name of @feature. + * + * Returns a copy of the name of @feature. + * Caller should g_free() the return value after usage. + * For a nameless plugin feature, this returns NULL, which you can safely g_free() + * as well. + * + * Returns: (transfer full): the name of @feature. g_free() after usage. MT safe. + * + */ +#define gst_plugin_feature_get_name(feature) gst_object_get_name(GST_OBJECT_CAST(feature)) + +/** + * gst_plugin_feature_set_name: + * @feature: a #GstPluginFeature to set the name of. + * @name: the new name + * + * Sets the name of the plugin feature, getting rid of the old name if there was one. + */ +#define gst_plugin_feature_set_name(feature,name) gst_object_set_name(GST_OBJECT_CAST(feature),name) + +/** + * GstPluginFeature: + * + * Opaque #GstPluginFeature structure. + */ +struct _GstPluginFeature { + GstObject object; + + /*< private >*/ + gboolean loaded; + guint rank; + + const gchar *plugin_name; + GstPlugin *plugin; /* weak ref */ + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING - 1]; +}; + +struct _GstPluginFeatureClass { + GstObjectClass parent_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstTypeNameData: + * @name: a name + * @type: a GType + * + * Structure used for filtering based on @name and @type. + */ +typedef struct { + const gchar *name; + GType type; +} GstTypeNameData; + +/** + * GstPluginFeatureFilter: + * @feature: the pluginfeature to check + * @user_data: the user_data that has been passed on e.g. + * gst_registry_feature_filter() + * + * A function that can be used with e.g. gst_registry_feature_filter() + * to get a list of pluginfeature that match certain criteria. + * + * Returns: %TRUE for a positive match, %FALSE otherwise + */ +typedef gboolean (*GstPluginFeatureFilter) (GstPluginFeature *feature, + gpointer user_data); + +/* normal GObject stuff */ +GType gst_plugin_feature_get_type (void); + +GstPluginFeature * + gst_plugin_feature_load (GstPluginFeature *feature); + +gboolean gst_plugin_feature_type_name_filter (GstPluginFeature *feature, + GstTypeNameData *data); + +void gst_plugin_feature_set_rank (GstPluginFeature *feature, guint rank); +guint gst_plugin_feature_get_rank (GstPluginFeature *feature); + +void gst_plugin_feature_list_free (GList *list); +GList *gst_plugin_feature_list_copy (GList *list); +void gst_plugin_feature_list_debug (GList *list); + +/** + * GST_PLUGIN_FEATURE_LIST_DEBUG: + * @list: (transfer none) (element-type Gst.PluginFeature): a #GList of + * plugin features + * + * Debug the plugin feature names in @list. + * + * Since: 0.10.31 + */ +#ifndef GST_DISABLE_GST_DEBUG +#define GST_PLUGIN_FEATURE_LIST_DEBUG(list) gst_plugin_feature_list_debug(list) +#else +#define GST_PLUGIN_FEATURE_LIST_DEBUG(list) +#endif + +gboolean gst_plugin_feature_check_version (GstPluginFeature *feature, + guint min_major, + guint min_minor, + guint min_micro); +gint gst_plugin_feature_rank_compare_func (gconstpointer p1, + gconstpointer p2); + +G_END_DECLS + + +#endif /* __GST_PLUGIN_FEATURE_H__ */ + diff --git a/gst/gstpluginloader.c b/gst/gstpluginloader.c new file mode 100644 index 0000000..8bb2df8 --- /dev/null +++ b/gst/gstpluginloader.c @@ -0,0 +1,1026 @@ +/* GStreamer + * Copyright (C) 2008 Jan Schmidt <jan.schmidt@sun.com> + * + * gstpluginloader.c: GstPluginLoader helper for loading plugin files + * out of process. + * + * 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_private.h> + +#ifndef G_OS_WIN32 +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#else +#define fsync(fd) _commit(fd) +#include <io.h> +#endif + +#ifdef HAVE_SYS_UTSNAME_H +#include <sys/utsname.h> +#endif + +#include <errno.h> + +#include <gst/gstconfig.h> + +#include <gst/gstpoll.h> +#include <gst/gstutils.h> + +#include <gst/gstpluginloader.h> +#include <gst/gstregistrychunks.h> +#include <gst/gstregistrybinary.h> + +/* IMPORTANT: Bump the version number if the plugin loader packet protocol + * changes. Changes in the binary registry format itself are handled by + * bumping the GST_MAGIC_BINARY_VERSION_STR + */ +static const guint32 loader_protocol_version = 3; + +#define GST_CAT_DEFAULT GST_CAT_PLUGIN_LOADING + +static GstPluginLoader *plugin_loader_new (GstRegistry * registry); +static gboolean plugin_loader_free (GstPluginLoader * loader); +static gboolean plugin_loader_load (GstPluginLoader * loader, + const gchar * filename, off_t file_size, time_t file_mtime); + +/* functions used in GstRegistry scanning */ +const GstPluginLoaderFuncs _priv_gst_plugin_loader_funcs = { + plugin_loader_new, plugin_loader_free, plugin_loader_load +}; + +typedef struct _PendingPluginEntry +{ + /* sequence number */ + guint32 tag; + gchar *filename; + off_t file_size; + time_t file_mtime; +} PendingPluginEntry; + +struct _GstPluginLoader +{ + GstRegistry *registry; + GstPoll *fdset; + + gboolean child_running; + GPid child_pid; + GstPollFD fd_w; + GstPollFD fd_r; + + gboolean is_child; + gboolean got_plugin_details; + + /* Transmit buffer */ + guint8 *tx_buf; + guint tx_buf_size; + guint tx_buf_write; + guint tx_buf_read; + + /* next sequence number (for PendingPluginEntry) */ + guint32 next_tag; + + guint8 *rx_buf; + guint rx_buf_size; + gboolean rx_done; + gboolean rx_got_sync; + + /* Head and tail of the pending plugins list. List of + PendingPluginEntry structs */ + GList *pending_plugins; + GList *pending_plugins_tail; +}; + +#define PACKET_EXIT 1 +#define PACKET_LOAD_PLUGIN 2 +#define PACKET_SYNC 3 +#define PACKET_PLUGIN_DETAILS 4 +#define PACKET_VERSION 5 + +#define BUF_INIT_SIZE 512 +#define BUF_GROW_EXTRA 512 +#define BUF_MAX_SIZE (32 * 1024 * 1024) + +#define HEADER_SIZE 12 +/* 4 magic hex bytes to mark each packet */ +#define HEADER_MAGIC 0xbefec0ae +#define ALIGNMENT (sizeof (void *)) + +static gboolean gst_plugin_loader_spawn (GstPluginLoader * loader); +static void put_packet (GstPluginLoader * loader, guint type, guint32 tag, + const guint8 * payload, guint32 payload_len); +static gboolean exchange_packets (GstPluginLoader * l); +static gboolean plugin_loader_replay_pending (GstPluginLoader * l); +static gboolean plugin_loader_load_and_sync (GstPluginLoader * l, + PendingPluginEntry * entry); +static void plugin_loader_create_blacklist_plugin (GstPluginLoader * l, + PendingPluginEntry * entry); +static void plugin_loader_cleanup_child (GstPluginLoader * loader); +static gboolean plugin_loader_sync_with_child (GstPluginLoader * l); + +static GstPluginLoader * +plugin_loader_new (GstRegistry * registry) +{ + GstPluginLoader *l = g_slice_new0 (GstPluginLoader); + + if (registry) + l->registry = gst_object_ref (registry); + l->fdset = gst_poll_new (FALSE); + gst_poll_fd_init (&l->fd_w); + gst_poll_fd_init (&l->fd_r); + + l->tx_buf_size = BUF_INIT_SIZE; + l->tx_buf = g_malloc (BUF_INIT_SIZE); + + l->next_tag = 0; + + l->rx_buf_size = BUF_INIT_SIZE; + l->rx_buf = g_malloc (BUF_INIT_SIZE); + + return l; +} + +static gboolean +plugin_loader_free (GstPluginLoader * loader) +{ + GList *cur; + gboolean got_plugin_details; + + fsync (loader->fd_w.fd); + + if (loader->child_running) { + put_packet (loader, PACKET_EXIT, 0, NULL, 0); + + /* Swap packets with the child until it exits cleanly */ + while (!loader->rx_done) { + if (exchange_packets (loader) || loader->rx_done) + continue; + + if (!plugin_loader_replay_pending (loader)) + break; + put_packet (loader, PACKET_EXIT, 0, NULL, 0); + } + + plugin_loader_cleanup_child (loader); + } else { + close (loader->fd_w.fd); + close (loader->fd_r.fd); + } + + gst_poll_free (loader->fdset); + + g_free (loader->rx_buf); + g_free (loader->tx_buf); + + if (loader->registry) + gst_object_unref (loader->registry); + + got_plugin_details = loader->got_plugin_details; + + /* Free any pending plugin entries */ + cur = loader->pending_plugins; + while (cur) { + PendingPluginEntry *entry = (PendingPluginEntry *) (cur->data); + g_free (entry->filename); + g_slice_free (PendingPluginEntry, entry); + + cur = g_list_delete_link (cur, cur); + } + + g_slice_free (GstPluginLoader, loader); + + return got_plugin_details; +} + +static gboolean +plugin_loader_load (GstPluginLoader * loader, const gchar * filename, + off_t file_size, time_t file_mtime) +{ + gint len; + PendingPluginEntry *entry; + + if (!gst_plugin_loader_spawn (loader)) + return FALSE; + + /* Send a packet to the child requesting that it load the given file */ + GST_LOG_OBJECT (loader->registry, + "Sending file %s to child. tag %u", filename, loader->next_tag); + + entry = g_slice_new (PendingPluginEntry); + entry->tag = loader->next_tag++; + entry->filename = g_strdup (filename); + entry->file_size = file_size; + entry->file_mtime = file_mtime; + loader->pending_plugins_tail = + g_list_append (loader->pending_plugins_tail, entry); + + if (loader->pending_plugins == NULL) + loader->pending_plugins = loader->pending_plugins_tail; + else + loader->pending_plugins_tail = g_list_next (loader->pending_plugins_tail); + + len = strlen (filename); + put_packet (loader, PACKET_LOAD_PLUGIN, entry->tag, + (guint8 *) filename, len + 1); + + if (!exchange_packets (loader)) { + if (!plugin_loader_replay_pending (loader)) + return FALSE; + } + + return TRUE; +} + +static gboolean +plugin_loader_replay_pending (GstPluginLoader * l) +{ + GList *cur, *next; + +restart: + if (!gst_plugin_loader_spawn (l)) + return FALSE; + + /* Load each plugin one by one synchronously until we find the + * crashing one */ + while ((cur = l->pending_plugins)) { + PendingPluginEntry *entry = (PendingPluginEntry *) (cur->data); + + if (!plugin_loader_load_and_sync (l, entry)) { + /* Create dummy plugin entry to block re-scanning this file */ + GST_ERROR ("Plugin file %s failed to load. Blacklisting", + entry->filename); + plugin_loader_create_blacklist_plugin (l, entry); + l->got_plugin_details = TRUE; + /* Now remove this crashy plugin from the head of the list */ + l->pending_plugins = g_list_delete_link (cur, cur); + g_free (entry->filename); + g_slice_free (PendingPluginEntry, entry); + if (l->pending_plugins == NULL) + l->pending_plugins_tail = NULL; + if (!gst_plugin_loader_spawn (l)) + return FALSE; + break; + } + } + + /* We exited after finding the crashing one. If there's any more pending, + * dispatch them post-haste, but don't wait */ + for (cur = l->pending_plugins; cur != NULL; cur = next) { + PendingPluginEntry *entry = (PendingPluginEntry *) (cur->data); + + next = g_list_next (cur); + + put_packet (l, PACKET_LOAD_PLUGIN, entry->tag, + (guint8 *) entry->filename, strlen (entry->filename) + 1); + + /* This might invalidate cur, which is why we grabbed 'next' above */ + if (!exchange_packets (l)) + goto restart; + } + + return TRUE; +} + +static gboolean +plugin_loader_sync_with_child (GstPluginLoader * l) +{ + put_packet (l, PACKET_SYNC, 0, NULL, 0); + + l->rx_got_sync = FALSE; + while (!l->rx_got_sync) { + if (!exchange_packets (l)) + return FALSE; + } + return TRUE; +} + +static gboolean +plugin_loader_load_and_sync (GstPluginLoader * l, PendingPluginEntry * entry) +{ + gint len; + + GST_DEBUG_OBJECT (l->registry, "Synchronously loading plugin file %s", + entry->filename); + + len = strlen (entry->filename); + put_packet (l, PACKET_LOAD_PLUGIN, entry->tag, + (guint8 *) entry->filename, len + 1); + + return plugin_loader_sync_with_child (l); +} + +static void +plugin_loader_create_blacklist_plugin (GstPluginLoader * l, + PendingPluginEntry * entry) +{ + GstPlugin *plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL); + + plugin->filename = g_strdup (entry->filename); + plugin->file_mtime = entry->file_mtime; + plugin->file_size = entry->file_size; + plugin->flags |= GST_PLUGIN_FLAG_BLACKLISTED; + + plugin->basename = g_path_get_basename (plugin->filename); + plugin->desc.name = g_intern_string (plugin->basename); + plugin->desc.description = "Plugin for blacklisted file"; + plugin->desc.version = "0.0.0"; + plugin->desc.license = "BLACKLIST"; + plugin->desc.source = plugin->desc.license; + plugin->desc.package = plugin->desc.license; + plugin->desc.origin = plugin->desc.license; + + GST_DEBUG ("Adding blacklist plugin '%s'", plugin->desc.name); + gst_registry_add_plugin (l->registry, plugin); +} + +#ifdef __APPLE__ +#if defined(__x86_64__) +#define USR_BIN_ARCH_SWITCH "-x86_64" +#elif defined(__i386__) +#define USR_BIN_ARCH_SWITCH "-i386" +#elif defined(__ppc__) +#define USR_BIN_ARCH_SWITCH "-ppc" +#elif defined(__ppc64__) +#define USR_BIN_ARCH_SWITCH "-ppc64" +#endif +#endif + +#define YES_MULTIARCH 1 +#define NO_MULTIARCH 2 + +#if defined (__APPLE__) && defined (USR_BIN_ARCH_SWITCH) +static gboolean +gst_plugin_loader_use_usr_bin_arch (void) +{ + static volatile gsize multiarch = 0; + + if (g_once_init_enter (&multiarch)) { + gsize res = NO_MULTIARCH; + +#ifdef HAVE_SYS_UTSNAME_H + { + struct utsname uname_data; + + if (uname (&uname_data) == 0) { + /* Check for OS X >= 10.5 (darwin kernel 9.0) */ + GST_LOG ("%s %s", uname_data.sysname, uname_data.release); + if (g_ascii_strcasecmp (uname_data.sysname, "Darwin") == 0 && + g_strtod (uname_data.release, NULL) >= 9.0) { + res = YES_MULTIARCH; + } + } + } +#endif + + GST_INFO ("multiarch: %s", (res == YES_MULTIARCH) ? "yes" : "no"); + g_once_init_leave (&multiarch, res); + } + return (multiarch == YES_MULTIARCH); +} +#endif /* __APPLE__ && USR_BIN_ARCH_SWITCH */ + +static gboolean +gst_plugin_loader_try_helper (GstPluginLoader * loader, gchar * location) +{ + char *argv[5] = { NULL, }; + int c = 0; + +#if defined (__APPLE__) && defined (USR_BIN_ARCH_SWITCH) + if (gst_plugin_loader_use_usr_bin_arch ()) { + argv[c++] = (char *) "/usr/bin/arch"; + argv[c++] = (char *) USR_BIN_ARCH_SWITCH; + } +#endif + argv[c++] = location; + argv[c++] = (char *) "-l"; + argv[c++] = NULL; + + if (c > 3) { + GST_LOG ("Trying to spawn gst-plugin-scanner helper at %s with arch %s", + location, argv[1]); + } else { + GST_LOG ("Trying to spawn gst-plugin-scanner helper at %s", location); + } + + if (!g_spawn_async_with_pipes (NULL, argv, NULL, + G_SPAWN_DO_NOT_REAP_CHILD /* | G_SPAWN_STDERR_TO_DEV_NULL */ , + NULL, NULL, &loader->child_pid, &loader->fd_w.fd, &loader->fd_r.fd, + NULL, NULL)) + return FALSE; + + gst_poll_add_fd (loader->fdset, &loader->fd_w); + gst_poll_add_fd (loader->fdset, &loader->fd_r); + + gst_poll_fd_ctl_read (loader->fdset, &loader->fd_r, TRUE); + + loader->tx_buf_write = loader->tx_buf_read = 0; + + put_packet (loader, PACKET_VERSION, 0, NULL, 0); + if (!plugin_loader_sync_with_child (loader)) + return FALSE; + + loader->child_running = TRUE; + + return TRUE; +} + +static gboolean +gst_plugin_loader_spawn (GstPluginLoader * loader) +{ + const gchar *env; + char *helper_bin; + gboolean res = FALSE; + + if (loader->child_running) + return TRUE; + + /* Find the gst-plugin-scanner: first try the env-var if it is set, + * otherwise use the installed version */ + env = g_getenv ("GST_PLUGIN_SCANNER"); + + if (env != NULL && *env != '\0') { + GST_LOG ("Trying GST_PLUGIN_SCANNER env var: %s", env); + helper_bin = g_strdup (env); + res = gst_plugin_loader_try_helper (loader, helper_bin); + g_free (helper_bin); + } + + if (!res) { + GST_LOG ("Trying installed plugin scanner"); + helper_bin = g_strdup (GST_PLUGIN_SCANNER_INSTALLED); + res = gst_plugin_loader_try_helper (loader, helper_bin); + g_free (helper_bin); + + if (!res) { + GST_INFO ("No gst-plugin-scanner available, or not working"); + } + } + + return loader->child_running; +} + +static void +plugin_loader_cleanup_child (GstPluginLoader * l) +{ + if (!l->child_running || l->is_child) + return; + + gst_poll_remove_fd (l->fdset, &l->fd_w); + gst_poll_remove_fd (l->fdset, &l->fd_r); + + close (l->fd_w.fd); + close (l->fd_r.fd); + +#ifndef G_OS_WIN32 + GST_LOG ("waiting for child process to exit"); + waitpid (l->child_pid, NULL, 0); +#else + g_warning ("FIXME: Implement child process shutdown for Win32"); +#endif + g_spawn_close_pid (l->child_pid); + + l->child_running = FALSE; +} + +gboolean +_gst_plugin_loader_client_run (void) +{ + GstPluginLoader *l; + + l = plugin_loader_new (NULL); + if (l == NULL) + return FALSE; + + /* On entry, the inward pipe is STDIN, and outward is STDOUT. + * Dup those somewhere better so that plugins printing things + * won't interfere with anything */ +#ifndef G_OS_WIN32 + { + int dup_fd; + + dup_fd = dup (0); /* STDIN */ + if (dup_fd == -1) { + GST_ERROR ("Failed to start. Could no dup STDIN, errno %d", errno); + return FALSE; + } + l->fd_r.fd = dup_fd; + close (0); + + dup_fd = dup (1); /* STDOUT */ + if (dup_fd == -1) { + GST_ERROR ("Failed to start. Could no dup STDOUT, errno %d", errno); + return FALSE; + } + l->fd_w.fd = dup_fd; + close (1); + + /* Dup stderr down to stdout so things that plugins print are visible, + * but don't care if it fails */ + dup2 (2, 1); + } +#else + /* FIXME: Use DuplicateHandle and friends on win32 */ + l->fd_w.fd = 1; /* STDOUT */ + l->fd_r.fd = 0; /* STDIN */ +#endif + + gst_poll_add_fd (l->fdset, &l->fd_w); + gst_poll_add_fd (l->fdset, &l->fd_r); + gst_poll_fd_ctl_read (l->fdset, &l->fd_r, TRUE); + + l->is_child = TRUE; + + GST_DEBUG ("Plugin scanner child running. Waiting for instructions"); + + /* Loop, listening for incoming packets on the fd and writing responses */ + while (!l->rx_done && exchange_packets (l)); + + plugin_loader_free (l); + + return TRUE; +} + +static void +put_packet (GstPluginLoader * l, guint type, guint32 tag, + const guint8 * payload, guint32 payload_len) +{ + guint8 *out; + guint len = payload_len + HEADER_SIZE; + + if (l->tx_buf_write + len >= l->tx_buf_size) { + GST_LOG ("Expanding tx buf from %d to %d for packet of size %d", + l->tx_buf_size, l->tx_buf_write + len + BUF_GROW_EXTRA, len); + l->tx_buf_size = l->tx_buf_write + len + BUF_GROW_EXTRA; + l->tx_buf = g_realloc (l->tx_buf, l->tx_buf_size); + } + + out = l->tx_buf + l->tx_buf_write; + + /* one byte packet type */ + out[0] = type; + /* 3 byte packet tag number */ + GST_WRITE_UINT24_BE (out + 1, tag); + /* 4 bytes packet length */ + GST_WRITE_UINT32_BE (out + 4, payload_len); + /* payload */ + memcpy (out + HEADER_SIZE, payload, payload_len); + /* Write magic into the header */ + GST_WRITE_UINT32_BE (out + 8, HEADER_MAGIC); + + l->tx_buf_write += len; + gst_poll_fd_ctl_write (l->fdset, &l->fd_w, TRUE); +} + +static void +put_chunk (GstPluginLoader * l, GstRegistryChunk * chunk, guint * pos) +{ + guint padsize = 0; + guint len; + guint8 *out; + + /* Might need to align the chunk */ + if (chunk->align && ((*pos) % ALIGNMENT) != 0) + padsize = ALIGNMENT - ((*pos) % ALIGNMENT); + + len = padsize + chunk->size; + + if (G_UNLIKELY (l->tx_buf_write + len >= l->tx_buf_size)) { + guint new_size = MAX (l->tx_buf_write + len, + l->tx_buf_size + l->tx_buf_size / 4) + BUF_GROW_EXTRA; + GST_LOG ("Expanding tx buf from %d to %d for chunk of size %d", + l->tx_buf_size, new_size, chunk->size); + l->tx_buf_size = new_size; + l->tx_buf = g_realloc (l->tx_buf, l->tx_buf_size); + } + + out = l->tx_buf + l->tx_buf_write; + /* Clear the padding */ + if (padsize) + memset (out, 0, padsize); + memcpy (out + padsize, chunk->data, chunk->size); + + l->tx_buf_write += len; + *pos += len; + + gst_poll_fd_ctl_write (l->fdset, &l->fd_w, TRUE); +}; + +static gboolean +write_one (GstPluginLoader * l) +{ + guint8 *out; + guint32 to_write, magic; + int res; + + if (l->tx_buf_read + HEADER_SIZE > l->tx_buf_write) + return FALSE; + + out = l->tx_buf + l->tx_buf_read; + + magic = GST_READ_UINT32_BE (out + 8); + if (magic != HEADER_MAGIC) { + GST_ERROR ("Packet magic number is missing. Memory corruption detected"); + goto fail_and_cleanup; + } + + to_write = GST_READ_UINT32_BE (out + 4) + HEADER_SIZE; + /* Check that the magic is intact, and the size is sensible */ + if (to_write > l->tx_buf_size) { + GST_ERROR ("Indicated packet size is too large. Corruption detected"); + goto fail_and_cleanup; + } + + l->tx_buf_read += to_write; + + GST_LOG ("Writing packet of size %d bytes to fd %d", to_write, l->fd_w.fd); + + do { + res = write (l->fd_w.fd, out, to_write); + if (G_UNLIKELY (res < 0)) { + if (errno == EAGAIN || errno == EINTR) + continue; + /* Failed to write -> child died */ + goto fail_and_cleanup; + } + to_write -= res; + out += res; + } while (to_write > 0); + + if (l->tx_buf_read == l->tx_buf_write) { + gst_poll_fd_ctl_write (l->fdset, &l->fd_w, FALSE); + l->tx_buf_read = l->tx_buf_write = 0; + } + + return TRUE; + +fail_and_cleanup: + plugin_loader_cleanup_child (l); + return FALSE; +} + +static gboolean +do_plugin_load (GstPluginLoader * l, const gchar * filename, guint tag) +{ + GstPlugin *newplugin; + GList *chunks = NULL; + + GST_DEBUG ("Plugin scanner loading file %s. tag %u", filename, tag); + +#if 0 /* Test code - crash based on filename */ + if (strstr (filename, "coreelements") == NULL) { + g_printerr ("Crashing on file %s\n", filename); + g_printerr ("%d", *(gint *) (NULL)); + } +#endif + + newplugin = gst_plugin_load_file ((gchar *) filename, NULL); + if (newplugin) { + guint hdr_pos; + guint offset; + + /* Now serialise the plugin details and send */ + if (!_priv_gst_registry_chunks_save_plugin (&chunks, + gst_registry_get_default (), newplugin)) + goto fail; + + /* Store where the header is, write an empty one, then write + * all the payload chunks, then fix up the header size */ + hdr_pos = l->tx_buf_write; + offset = HEADER_SIZE; + put_packet (l, PACKET_PLUGIN_DETAILS, tag, NULL, 0); + + if (chunks) { + GList *walk; + for (walk = chunks; walk; walk = g_list_next (walk)) { + GstRegistryChunk *cur = walk->data; + put_chunk (l, cur, &offset); + + _priv_gst_registry_chunk_free (cur); + } + + g_list_free (chunks); + + /* Store the size of the written payload */ + GST_WRITE_UINT32_BE (l->tx_buf + hdr_pos + 4, offset - HEADER_SIZE); + } +#if 0 /* Test code - corrupt the tx buffer based on filename */ + if (strstr (filename, "sink") != NULL) { + int fd, res; + g_printerr ("Corrupting tx buf on file %s\n", filename); + fd = open ("/dev/urandom", O_RDONLY); + res = read (fd, l->tx_buf, l->tx_buf_size); + close (fd); + } +#endif + + gst_object_unref (newplugin); + } else { + put_packet (l, PACKET_PLUGIN_DETAILS, tag, NULL, 0); + } + + return TRUE; +fail: + put_packet (l, PACKET_PLUGIN_DETAILS, tag, NULL, 0); + if (chunks) { + GList *walk; + for (walk = chunks; walk; walk = g_list_next (walk)) { + GstRegistryChunk *cur = walk->data; + + _priv_gst_registry_chunk_free (cur); + } + + g_list_free (chunks); + } + + return FALSE; +} + +static gboolean +check_protocol_version (GstPluginLoader * l, guint8 * payload, + guint payload_len) +{ + guint32 got_version; + guint8 *binary_reg_ver; + + if (payload_len < sizeof (guint32) + GST_MAGIC_BINARY_VERSION_LEN) + return FALSE; + + got_version = GST_READ_UINT32_BE (payload); + GST_LOG ("Got VERSION %u from child. Ours is %u", got_version, + loader_protocol_version); + if (got_version != loader_protocol_version) + return FALSE; + + binary_reg_ver = payload + sizeof (guint32); + if (strcmp ((gchar *) binary_reg_ver, GST_MAGIC_BINARY_VERSION_STR)) { + GST_LOG ("Binary chunk format of child is different. Ours: %s, child %s\n", + GST_MAGIC_BINARY_VERSION_STR, binary_reg_ver); + return FALSE; + } + + return TRUE; +}; + +static gboolean +handle_rx_packet (GstPluginLoader * l, + guint pack_type, guint32 tag, guint8 * payload, guint payload_len) +{ + gboolean res = TRUE; + + switch (pack_type) { + case PACKET_EXIT: + gst_poll_fd_ctl_read (l->fdset, &l->fd_r, FALSE); + if (l->is_child) { + /* Respond */ + put_packet (l, PACKET_EXIT, 0, NULL, 0); + } + l->rx_done = TRUE; + return TRUE; + case PACKET_LOAD_PLUGIN:{ + if (!l->is_child) + return TRUE; + + /* Payload is the filename to load */ + res = do_plugin_load (l, (gchar *) payload, tag); + + break; + } + case PACKET_PLUGIN_DETAILS:{ + gchar *tmp = (gchar *) payload; + PendingPluginEntry *entry = NULL; + GList *cur; + + GST_DEBUG_OBJECT (l->registry, + "Received plugin details from child w/ tag %u. %d bytes info", + tag, payload_len); + + /* Assume that tagged details come back in the order + * we requested, and delete anything before (but not + * including) this one */ + cur = l->pending_plugins; + while (cur) { + PendingPluginEntry *e = (PendingPluginEntry *) (cur->data); + + if (e->tag > tag) + break; + + if (e->tag == tag) { + entry = e; + break; + } else { + cur = g_list_delete_link (cur, cur); + g_free (e->filename); + g_slice_free (PendingPluginEntry, e); + } + } + + l->pending_plugins = cur; + if (cur == NULL) + l->pending_plugins_tail = NULL; + + if (payload_len > 0) { + GstPlugin *newplugin = NULL; + if (!_priv_gst_registry_chunks_load_plugin (l->registry, &tmp, + tmp + payload_len, &newplugin)) { + /* Got garbage from the child, so fail and trigger replay of plugins */ + GST_ERROR_OBJECT (l->registry, + "Problems loading plugin details with tag %u from scanner", tag); + return FALSE; + } + + newplugin->flags &= ~GST_PLUGIN_FLAG_CACHED; + GST_LOG_OBJECT (l->registry, + "marking plugin %p as registered as %s", newplugin, + newplugin->filename); + newplugin->registered = TRUE; + + /* We got a set of plugin details - remember it for later */ + l->got_plugin_details = TRUE; + } else if (entry != NULL) { + /* Create a blacklist entry for this file to prevent scanning every time */ + plugin_loader_create_blacklist_plugin (l, entry); + l->got_plugin_details = TRUE; + } + + if (entry != NULL) { + g_free (entry->filename); + g_slice_free (PendingPluginEntry, entry); + } + + /* Remove the plugin entry we just loaded */ + cur = l->pending_plugins; + if (cur != NULL) + cur = g_list_delete_link (cur, cur); + l->pending_plugins = cur; + if (cur == NULL) + l->pending_plugins_tail = NULL; + + break; + } + case PACKET_SYNC: + if (l->is_child) { + /* Respond with our reply - also a sync */ + put_packet (l, PACKET_SYNC, tag, NULL, 0); + GST_LOG ("Got SYNC in child - replying"); + } else + l->rx_got_sync = TRUE; + break; + case PACKET_VERSION: + if (l->is_child) { + /* Respond with our reply - a version packet, with the version */ + const gint version_len = + sizeof (guint32) + GST_MAGIC_BINARY_VERSION_LEN; + guint8 version_info[sizeof (guint32) + GST_MAGIC_BINARY_VERSION_LEN]; + memset (version_info, 0, version_len); + GST_WRITE_UINT32_BE (version_info, loader_protocol_version); + memcpy (version_info + sizeof (guint32), GST_MAGIC_BINARY_VERSION_STR, + strlen (GST_MAGIC_BINARY_VERSION_STR)); + put_packet (l, PACKET_VERSION, tag, version_info, version_len); + GST_LOG ("Got VERSION in child - replying %u", loader_protocol_version); + } else { + res = check_protocol_version (l, payload, payload_len); + } + break; + default: + return FALSE; /* Invalid packet -> something is wrong */ + } + + return res; +} + +static gboolean +read_one (GstPluginLoader * l) +{ + guint64 magic; + guint32 to_read, packet_len, tag; + guint8 *in; + gint res; + + to_read = HEADER_SIZE; + in = l->rx_buf; + do { + res = read (l->fd_r.fd, in, to_read); + if (G_UNLIKELY (res < 0)) { + if (errno == EAGAIN || errno == EINTR) + continue; + GST_LOG ("Failed reading packet header"); + return FALSE; + } + to_read -= res; + in += res; + } while (to_read > 0); + + magic = GST_READ_UINT32_BE (l->rx_buf + 8); + if (magic != HEADER_MAGIC) { + GST_WARNING + ("Invalid packet (bad magic number) received from plugin scanner subprocess"); + return FALSE; + } + + packet_len = GST_READ_UINT32_BE (l->rx_buf + 4); + if (packet_len + HEADER_SIZE > BUF_MAX_SIZE) { + GST_WARNING + ("Received excessively large packet for plugin scanner subprocess"); + return FALSE; + } + tag = GST_READ_UINT24_BE (l->rx_buf + 1); + + if (packet_len > 0) { + if (packet_len + HEADER_SIZE >= l->rx_buf_size) { + GST_LOG ("Expanding rx buf from %d to %d", + l->rx_buf_size, packet_len + HEADER_SIZE + BUF_GROW_EXTRA); + l->rx_buf_size = packet_len + HEADER_SIZE + BUF_GROW_EXTRA; + l->rx_buf = g_realloc (l->rx_buf, l->rx_buf_size); + } + + in = l->rx_buf + HEADER_SIZE; + to_read = packet_len; + do { + res = read (l->fd_r.fd, in, to_read); + if (G_UNLIKELY (res < 0)) { + if (errno == EAGAIN || errno == EINTR) + continue; + GST_ERROR ("Packet payload read failed"); + return FALSE; + } + to_read -= res; + in += res; + } while (to_read > 0); + } else { + GST_LOG ("No payload to read for 0 length packet type %d tag %u", + l->rx_buf[0], tag); + } + + return handle_rx_packet (l, l->rx_buf[0], tag, + l->rx_buf + HEADER_SIZE, packet_len); +} + +static gboolean +exchange_packets (GstPluginLoader * l) +{ + gint res; + + /* Wait for activity on our FDs */ + do { + do { + res = gst_poll_wait (l->fdset, GST_SECOND); + } while (res == -1 && (errno == EINTR || errno == EAGAIN)); + + if (res < 0) + return FALSE; + + GST_LOG ("Poll res = %d. %d bytes pending for write", res, + l->tx_buf_write - l->tx_buf_read); + + if (!l->rx_done) { + if (gst_poll_fd_has_error (l->fdset, &l->fd_r) || + gst_poll_fd_has_closed (l->fdset, &l->fd_r)) { + GST_LOG ("read fd %d closed/errored", l->fd_r.fd); + goto fail_and_cleanup; + } + + if (gst_poll_fd_can_read (l->fdset, &l->fd_r)) { + if (!read_one (l)) + goto fail_and_cleanup; + } + } + + if (l->tx_buf_read < l->tx_buf_write) { + if (gst_poll_fd_has_error (l->fdset, &l->fd_w) || + gst_poll_fd_has_closed (l->fdset, &l->fd_r)) { + GST_ERROR ("write fd %d closed/errored", l->fd_w.fd); + goto fail_and_cleanup; + } + if (gst_poll_fd_can_write (l->fdset, &l->fd_w)) { + if (!write_one (l)) + goto fail_and_cleanup; + } + } + } while (l->tx_buf_read < l->tx_buf_write); + + return TRUE; +fail_and_cleanup: + plugin_loader_cleanup_child (l); + return FALSE; +} diff --git a/gst/gstpluginloader.h b/gst/gstpluginloader.h new file mode 100644 index 0000000..b2dbabd --- /dev/null +++ b/gst/gstpluginloader.h @@ -0,0 +1,39 @@ +/* GStreamer + * Copyright (C) 2008 Jan Schmidt <jan.schmidt@sun.com> + * + * gstpluginloader.h: Helper for out-of-process plugin loading. Private header. + * + * 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_PLUGINLOADER_H__ +#define __GST_PLUGINLOADER_H__ + +G_BEGIN_DECLS + +typedef struct _GstPluginLoader GstPluginLoader; + +typedef struct _GstPluginLoaderFuncs { + GstPluginLoader * (*create)(GstRegistry *registry); + gboolean (*destroy)(GstPluginLoader *loader); + gboolean (*load)(GstPluginLoader *loader, const gchar *filename, + off_t file_size, time_t file_mtime); +} GstPluginLoaderFuncs; + +extern const GstPluginLoaderFuncs _priv_gst_plugin_loader_funcs; + +G_END_DECLS + +#endif /* __GST_PLUGINLOADER_H__ */ diff --git a/gst/gstpoll.c b/gst/gstpoll.c new file mode 100644 index 0000000..83c8497 --- /dev/null +++ b/gst/gstpoll.c @@ -0,0 +1,1592 @@ +/* GStreamer + * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu> + * Copyright (C) 2004 Wim Taymans <wim.taymans@gmail.com> + * Copyright (C) 2007 Peter Kjellerstedt <pkj@axis.com> + * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com> + * + * gstpoll.c: File descriptor set + * + * 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:gstpoll + * @short_description: Keep track of file descriptors and make it possible + * to wait on them in a cancelable way + * + * A #GstPoll keeps track of file descriptors much like fd_set (used with + * select()) or a struct pollfd array (used with poll()). Once created with + * gst_poll_new(), the set can be used to wait for file descriptors to be + * readable and/or writeable. It is possible to make this wait be controlled + * by specifying %TRUE for the @controllable flag when creating the set (or + * later calling gst_poll_set_controllable()). + * + * New file descriptors are added to the set using gst_poll_add_fd(), and + * removed using gst_poll_remove_fd(). Controlling which file descriptors + * should be waited for to become readable and/or writeable are done using + * gst_poll_fd_ctl_read() and gst_poll_fd_ctl_write(). + * + * Use gst_poll_wait() to wait for the file descriptors to actually become + * readable and/or writeable, or to timeout if no file descriptor is available + * in time. The wait can be controlled by calling gst_poll_restart() and + * gst_poll_set_flushing(). + * + * Once the file descriptor set has been waited for, one can use + * gst_poll_fd_has_closed() to see if the file descriptor has been closed, + * gst_poll_fd_has_error() to see if it has generated an error, + * gst_poll_fd_can_read() to see if it is possible to read from the file + * descriptor, and gst_poll_fd_can_write() to see if it is possible to + * write to it. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gst_private.h" +#include "glib-compat-private.h" + +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <errno.h> +#include <fcntl.h> + +#include <glib.h> + +#ifdef G_OS_WIN32 +#include <winsock2.h> +#define EINPROGRESS WSAEINPROGRESS +#else +#define _GNU_SOURCE 1 +#include <sys/poll.h> +#include <sys/time.h> +#include <sys/socket.h> +#endif + +/* OS/X needs this because of bad headers */ +#include <string.h> + +/* The poll() emulation on OS/X doesn't handle fds=NULL, nfds=0, + * so we prefer our own poll emulation. + */ +#if defined(BROKEN_POLL) +#undef HAVE_POLL +#endif + +#include "gstpoll.h" + +#define GST_CAT_DEFAULT GST_CAT_POLL + +#ifdef G_OS_WIN32 +typedef struct _WinsockFd WinsockFd; + +struct _WinsockFd +{ + gint fd; + glong event_mask; + WSANETWORKEVENTS events; + glong ignored_event_mask; +}; +#endif + +typedef enum +{ + GST_POLL_MODE_AUTO, + GST_POLL_MODE_SELECT, + GST_POLL_MODE_PSELECT, + GST_POLL_MODE_POLL, + GST_POLL_MODE_PPOLL, + GST_POLL_MODE_WINDOWS +} GstPollMode; + +struct _GstPoll +{ + GstPollMode mode; + + GMutex *lock; + /* array of fds, always written to and read from with lock */ + GArray *fds; + /* array of active fds, only written to from the waiting thread with the + * lock and read from with the lock or without the lock from the waiting + * thread */ + GArray *active_fds; + +#ifndef G_OS_WIN32 + gchar buf[1]; + GstPollFD control_read_fd; + GstPollFD control_write_fd; +#else + GArray *active_fds_ignored; + GArray *events; + GArray *active_events; + + HANDLE wakeup_event; +#endif + + gboolean controllable; + volatile gint waiting; + volatile gint control_pending; + volatile gint flushing; + gboolean timer; + volatile gint rebuild; +}; + +static gboolean gst_poll_fd_ctl_read_unlocked (GstPoll * set, GstPollFD * fd, + gboolean active); +static gboolean gst_poll_add_fd_unlocked (GstPoll * set, GstPollFD * fd); + +#define IS_FLUSHING(s) (g_atomic_int_get(&(s)->flushing)) +#define SET_FLUSHING(s,val) (g_atomic_int_set(&(s)->flushing, (val))) + +#define INC_WAITING(s) (G_ATOMIC_INT_ADD(&(s)->waiting, 1)) +#define DEC_WAITING(s) (G_ATOMIC_INT_ADD(&(s)->waiting, -1)) +#define GET_WAITING(s) (g_atomic_int_get(&(s)->waiting)) + +#define TEST_REBUILD(s) (g_atomic_int_compare_and_exchange(&(s)->rebuild, 1, 0)) +#define MARK_REBUILD(s) (g_atomic_int_set(&(s)->rebuild, 1)) + +#ifndef G_OS_WIN32 +#define WAKE_EVENT(s) (write ((s)->control_write_fd.fd, "W", 1) == 1) +#define RELEASE_EVENT(s) (read ((s)->control_read_fd.fd, (s)->buf, 1) == 1) +#else +#define WAKE_EVENT(s) (SetEvent ((s)->wakeup_event), errno = GetLastError () == NO_ERROR ? 0 : EACCES, errno == 0 ? 1 : 0) +#define RELEASE_EVENT(s) (ResetEvent ((s)->wakeup_event)) +#endif + +/* the poll/select call is also performed on a control socket, that way + * we can send special commands to control it */ +static inline gboolean +raise_wakeup (GstPoll * set) +{ + gboolean result = TRUE; + + if (G_ATOMIC_INT_ADD (&set->control_pending, 1) == 0) { + /* raise when nothing pending */ + GST_LOG ("%p: raise", set); + result = WAKE_EVENT (set); + } + return result; +} + +/* note how bad things can happen when the 2 threads both raise and release the + * wakeup. This is however not a problem because you must always pair a raise + * with a release */ +static inline gboolean +release_wakeup (GstPoll * set) +{ + gboolean result = TRUE; + + if (g_atomic_int_dec_and_test (&set->control_pending)) { + GST_LOG ("%p: release", set); + result = RELEASE_EVENT (set); + } + return result; +} + +static inline gint +release_all_wakeup (GstPoll * set) +{ + gint old; + + while (TRUE) { + if (!(old = g_atomic_int_get (&set->control_pending))) + /* nothing pending, just exit */ + break; + + /* try to remove all pending control messages */ + if (g_atomic_int_compare_and_exchange (&set->control_pending, old, 0)) { + /* we managed to remove all messages, read the control socket */ + if (RELEASE_EVENT (set)) + break; + else + /* retry again until we read it successfully */ + G_ATOMIC_INT_ADD (&set->control_pending, 1); + } + } + return old; +} + +static gint +find_index (GArray * array, GstPollFD * fd) +{ +#ifndef G_OS_WIN32 + struct pollfd *ifd; +#else + WinsockFd *ifd; +#endif + guint i; + + /* start by assuming the index found in the fd is still valid */ + if (fd->idx >= 0 && fd->idx < array->len) { +#ifndef G_OS_WIN32 + ifd = &g_array_index (array, struct pollfd, fd->idx); +#else + ifd = &g_array_index (array, WinsockFd, fd->idx); +#endif + + if (ifd->fd == fd->fd) { + return fd->idx; + } + } + + /* the pollfd array has changed and we need to lookup the fd again */ + for (i = 0; i < array->len; i++) { +#ifndef G_OS_WIN32 + ifd = &g_array_index (array, struct pollfd, i); +#else + ifd = &g_array_index (array, WinsockFd, i); +#endif + + if (ifd->fd == fd->fd) { + fd->idx = (gint) i; + return fd->idx; + } + } + + fd->idx = -1; + return fd->idx; +} + +#if !defined(HAVE_PPOLL) && defined(HAVE_POLL) +/* check if all file descriptors will fit in an fd_set */ +static gboolean +selectable_fds (const GstPoll * set) +{ + guint i; + + g_mutex_lock (set->lock); + for (i = 0; i < set->fds->len; i++) { + struct pollfd *pfd = &g_array_index (set->fds, struct pollfd, i); + + if (pfd->fd >= FD_SETSIZE) + goto too_many; + } + g_mutex_unlock (set->lock); + + return TRUE; + +too_many: + { + g_mutex_unlock (set->lock); + return FALSE; + } +} + +/* check if the timeout will convert to a timeout value used for poll() + * without a loss of precision + */ +static gboolean +pollable_timeout (GstClockTime timeout) +{ + if (timeout == GST_CLOCK_TIME_NONE) + return TRUE; + + /* not a nice multiple of milliseconds */ + if (timeout % 1000000) + return FALSE; + + return TRUE; +} +#endif + +static GstPollMode +choose_mode (const GstPoll * set, GstClockTime timeout) +{ + GstPollMode mode; + + if (set->mode == GST_POLL_MODE_AUTO) { +#ifdef HAVE_PPOLL + mode = GST_POLL_MODE_PPOLL; +#elif defined(HAVE_POLL) + if (!selectable_fds (set) || pollable_timeout (timeout)) { + mode = GST_POLL_MODE_POLL; + } else { +#ifdef HAVE_PSELECT + mode = GST_POLL_MODE_PSELECT; +#else + mode = GST_POLL_MODE_SELECT; +#endif + } +#elif defined(HAVE_PSELECT) + mode = GST_POLL_MODE_PSELECT; +#else + mode = GST_POLL_MODE_SELECT; +#endif + } else { + mode = set->mode; + } + return mode; +} + +#ifndef G_OS_WIN32 +static gint +pollfd_to_fd_set (GstPoll * set, fd_set * readfds, fd_set * writefds, + fd_set * errorfds) +{ + gint max_fd = -1; + guint i; + + FD_ZERO (readfds); + FD_ZERO (writefds); + FD_ZERO (errorfds); + + g_mutex_lock (set->lock); + + for (i = 0; i < set->active_fds->len; i++) { + struct pollfd *pfd = &g_array_index (set->fds, struct pollfd, i); + + if (pfd->fd < FD_SETSIZE) { + if (pfd->events & POLLIN) + FD_SET (pfd->fd, readfds); + if (pfd->events & POLLOUT) + FD_SET (pfd->fd, writefds); + if (pfd->events) + FD_SET (pfd->fd, errorfds); + if (pfd->fd > max_fd && (pfd->events & (POLLIN | POLLOUT))) + max_fd = pfd->fd; + } + } + + g_mutex_unlock (set->lock); + + return max_fd; +} + +static void +fd_set_to_pollfd (GstPoll * set, fd_set * readfds, fd_set * writefds, + fd_set * errorfds) +{ + guint i; + + g_mutex_lock (set->lock); + + for (i = 0; i < set->active_fds->len; i++) { + struct pollfd *pfd = &g_array_index (set->active_fds, struct pollfd, i); + + if (pfd->fd < FD_SETSIZE) { + pfd->revents = 0; + if (FD_ISSET (pfd->fd, readfds)) + pfd->revents |= POLLIN; + if (FD_ISSET (pfd->fd, writefds)) + pfd->revents |= POLLOUT; + if (FD_ISSET (pfd->fd, errorfds)) + pfd->revents |= POLLERR; + } + } + + g_mutex_unlock (set->lock); +} +#else /* G_OS_WIN32 */ +/* + * Translate errors thrown by the Winsock API used by GstPoll: + * WSAEventSelect, WSAWaitForMultipleEvents and WSAEnumNetworkEvents + */ +static gint +gst_poll_winsock_error_to_errno (DWORD last_error) +{ + switch (last_error) { + case WSA_INVALID_HANDLE: + case WSAEINVAL: + case WSAENOTSOCK: + return EBADF; + + case WSA_NOT_ENOUGH_MEMORY: + return ENOMEM; + + /* + * Anything else, including: + * WSA_INVALID_PARAMETER, WSAEFAULT, WSAEINPROGRESS, WSAENETDOWN, + * WSANOTINITIALISED + */ + default: + return EINVAL; + } +} + +static void +gst_poll_free_winsock_event (GstPoll * set, gint idx) +{ + WinsockFd *wfd = &g_array_index (set->fds, WinsockFd, idx); + HANDLE event = g_array_index (set->events, HANDLE, idx); + + WSAEventSelect (wfd->fd, event, 0); + CloseHandle (event); +} + +static void +gst_poll_update_winsock_event_mask (GstPoll * set, gint idx, glong flags, + gboolean active) +{ + WinsockFd *wfd; + + wfd = &g_array_index (set->fds, WinsockFd, idx); + + if (active) + wfd->event_mask |= flags; + else + wfd->event_mask &= ~flags; + + /* reset ignored state if the new mask doesn't overlap at all */ + if ((wfd->ignored_event_mask & wfd->event_mask) == 0) + wfd->ignored_event_mask = 0; +} + +static gboolean +gst_poll_prepare_winsock_active_sets (GstPoll * set) +{ + guint i; + + g_array_set_size (set->active_fds, 0); + g_array_set_size (set->active_fds_ignored, 0); + g_array_set_size (set->active_events, 0); + g_array_append_val (set->active_events, set->wakeup_event); + + for (i = 0; i < set->fds->len; i++) { + WinsockFd *wfd = &g_array_index (set->fds, WinsockFd, i); + HANDLE event = g_array_index (set->events, HANDLE, i); + + if (wfd->ignored_event_mask == 0) { + gint ret; + + g_array_append_val (set->active_fds, *wfd); + g_array_append_val (set->active_events, event); + + ret = WSAEventSelect (wfd->fd, event, wfd->event_mask); + if (G_UNLIKELY (ret != 0)) { + errno = gst_poll_winsock_error_to_errno (WSAGetLastError ()); + return FALSE; + } + } else { + g_array_append_val (set->active_fds_ignored, wfd); + } + } + + return TRUE; +} + +static gint +gst_poll_collect_winsock_events (GstPoll * set) +{ + gint res, i; + + /* + * We need to check which events are signaled, and call + * WSAEnumNetworkEvents for those that are, which resets + * the event and clears the internal network event records. + */ + res = 0; + for (i = 0; i < set->active_fds->len; i++) { + WinsockFd *wfd = &g_array_index (set->active_fds, WinsockFd, i); + HANDLE event = g_array_index (set->active_events, HANDLE, i + 1); + DWORD wait_ret; + + wait_ret = WaitForSingleObject (event, 0); + if (wait_ret == WAIT_OBJECT_0) { + gint enum_ret = WSAEnumNetworkEvents (wfd->fd, event, &wfd->events); + + if (G_UNLIKELY (enum_ret != 0)) { + res = -1; + errno = gst_poll_winsock_error_to_errno (WSAGetLastError ()); + break; + } + + res++; + } else { + /* clear any previously stored result */ + memset (&wfd->events, 0, sizeof (wfd->events)); + } + } + + /* If all went well we also need to reset the ignored fds. */ + if (res >= 0) { + res += set->active_fds_ignored->len; + + for (i = 0; i < set->active_fds_ignored->len; i++) { + WinsockFd *wfd = g_array_index (set->active_fds_ignored, WinsockFd *, i); + + wfd->ignored_event_mask = 0; + } + + g_array_set_size (set->active_fds_ignored, 0); + } + + return res; +} +#endif + +/** + * gst_poll_new: + * @controllable: whether it should be possible to control a wait. + * + * Create a new file descriptor set. If @controllable, it + * is possible to restart or flush a call to gst_poll_wait() with + * gst_poll_restart() and gst_poll_set_flushing() respectively. + * + * Free-function: gst_poll_free + * + * Returns: (transfer full): a new #GstPoll, or %NULL in case of an error. + * Free with gst_poll_free(). + * + * Since: 0.10.18 + */ +GstPoll * +gst_poll_new (gboolean controllable) +{ + GstPoll *nset; + + GST_DEBUG ("controllable : %d", controllable); + + nset = g_slice_new0 (GstPoll); + nset->lock = g_mutex_new (); +#ifndef G_OS_WIN32 + nset->mode = GST_POLL_MODE_AUTO; + nset->fds = g_array_new (FALSE, FALSE, sizeof (struct pollfd)); + nset->active_fds = g_array_new (FALSE, FALSE, sizeof (struct pollfd)); + nset->control_read_fd.fd = -1; + nset->control_write_fd.fd = -1; + { + gint control_sock[2]; + + if (socketpair (PF_UNIX, SOCK_STREAM, 0, control_sock) < 0) + goto no_socket_pair; + + fcntl (control_sock[0], F_SETFL, O_NONBLOCK); + fcntl (control_sock[1], F_SETFL, O_NONBLOCK); + + nset->control_read_fd.fd = control_sock[0]; + nset->control_write_fd.fd = control_sock[1]; + + gst_poll_add_fd_unlocked (nset, &nset->control_read_fd); + gst_poll_fd_ctl_read_unlocked (nset, &nset->control_read_fd, TRUE); + } +#else + nset->mode = GST_POLL_MODE_WINDOWS; + nset->fds = g_array_new (FALSE, FALSE, sizeof (WinsockFd)); + nset->active_fds = g_array_new (FALSE, FALSE, sizeof (WinsockFd)); + nset->active_fds_ignored = g_array_new (FALSE, FALSE, sizeof (WinsockFd *)); + nset->events = g_array_new (FALSE, FALSE, sizeof (HANDLE)); + nset->active_events = g_array_new (FALSE, FALSE, sizeof (HANDLE)); + + nset->wakeup_event = CreateEvent (NULL, TRUE, FALSE, NULL); +#endif + + /* ensure (re)build, though already sneakily set in non-windows case */ + MARK_REBUILD (nset); + + nset->controllable = controllable; + + return nset; + + /* ERRORS */ +#ifndef G_OS_WIN32 +no_socket_pair: + { + GST_WARNING ("%p: can't create socket pair !", nset); + gst_poll_free (nset); + return NULL; + } +#endif +} + +/** + * gst_poll_new_timer: + * + * Create a new poll object that can be used for scheduling cancellable + * timeouts. + * + * A timeout is performed with gst_poll_wait(). Multiple timeouts can be + * performed from different threads. + * + * Free-function: gst_poll_free + * + * Returns: (transfer full): a new #GstPoll, or %NULL in case of an error. + * Free with gst_poll_free(). + * + * Since: 0.10.23 + */ +GstPoll * +gst_poll_new_timer (void) +{ + GstPoll *poll; + + /* make a new controllable poll set */ + if (!(poll = gst_poll_new (TRUE))) + goto done; + + /* we are a timer */ + poll->timer = TRUE; + +done: + return poll; +} + +/** + * gst_poll_free: + * @set: (transfer full): a file descriptor set. + * + * Free a file descriptor set. + * + * Since: 0.10.18 + */ +void +gst_poll_free (GstPoll * set) +{ + g_return_if_fail (set != NULL); + + GST_DEBUG ("%p: freeing", set); + +#ifndef G_OS_WIN32 + if (set->control_write_fd.fd >= 0) + close (set->control_write_fd.fd); + if (set->control_read_fd.fd >= 0) + close (set->control_read_fd.fd); +#else + CloseHandle (set->wakeup_event); + + { + guint i; + + for (i = 0; i < set->events->len; i++) + gst_poll_free_winsock_event (set, i); + } + + g_array_free (set->active_events, TRUE); + g_array_free (set->events, TRUE); + g_array_free (set->active_fds_ignored, TRUE); +#endif + + g_array_free (set->active_fds, TRUE); + g_array_free (set->fds, TRUE); + g_mutex_free (set->lock); + g_slice_free (GstPoll, set); +} + +/** + * gst_poll_get_read_gpollfd: + * @set: a #GstPoll + * @fd: a #GPollFD + * + * Get a GPollFD for the reading part of the control socket. This is useful when + * integrating with a GSource and GMainLoop. + * + * Since: 0.10.32 + */ +void +gst_poll_get_read_gpollfd (GstPoll * set, GPollFD * fd) +{ + g_return_if_fail (set != NULL); + g_return_if_fail (fd != NULL); + +#ifndef G_OS_WIN32 + fd->fd = set->control_read_fd.fd; +#else +#if GLIB_SIZEOF_VOID_P == 8 + fd->fd = (gint64) set->wakeup_event; +#else + fd->fd = (gint) set->wakeup_event; +#endif +#endif + fd->events = G_IO_IN | G_IO_HUP | G_IO_ERR; + fd->revents = 0; +} + +/** + * gst_poll_fd_init: + * @fd: a #GstPollFD + * + * Initializes @fd. Alternatively you can initialize it with + * #GST_POLL_FD_INIT. + * + * Since: 0.10.18 + */ +void +gst_poll_fd_init (GstPollFD * fd) +{ + g_return_if_fail (fd != NULL); + + fd->fd = -1; + fd->idx = -1; +} + +static gboolean +gst_poll_add_fd_unlocked (GstPoll * set, GstPollFD * fd) +{ + gint idx; + + GST_DEBUG ("%p: fd (fd:%d, idx:%d)", set, fd->fd, fd->idx); + + idx = find_index (set->fds, fd); + if (idx < 0) { +#ifndef G_OS_WIN32 + struct pollfd nfd; + + nfd.fd = fd->fd; + nfd.events = POLLERR | POLLNVAL | POLLHUP; + nfd.revents = 0; + + g_array_append_val (set->fds, nfd); + + fd->idx = set->fds->len - 1; +#else + WinsockFd wfd; + HANDLE event; + + wfd.fd = fd->fd; + wfd.event_mask = FD_CLOSE; + memset (&wfd.events, 0, sizeof (wfd.events)); + wfd.ignored_event_mask = 0; + event = WSACreateEvent (); + + g_array_append_val (set->fds, wfd); + g_array_append_val (set->events, event); + + fd->idx = set->fds->len - 1; +#endif + MARK_REBUILD (set); + } else { + GST_WARNING ("%p: couldn't find fd !", set); + } + + return TRUE; +} + +/** + * gst_poll_add_fd: + * @set: a file descriptor set. + * @fd: a file descriptor. + * + * Add a file descriptor to the file descriptor set. + * + * Returns: %TRUE if the file descriptor was successfully added to the set. + * + * Since: 0.10.18 + */ +gboolean +gst_poll_add_fd (GstPoll * set, GstPollFD * fd) +{ + gboolean ret; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (fd != NULL, FALSE); + g_return_val_if_fail (fd->fd >= 0, FALSE); + + g_mutex_lock (set->lock); + + ret = gst_poll_add_fd_unlocked (set, fd); + + g_mutex_unlock (set->lock); + + return ret; +} + +/** + * gst_poll_remove_fd: + * @set: a file descriptor set. + * @fd: a file descriptor. + * + * Remove a file descriptor from the file descriptor set. + * + * Returns: %TRUE if the file descriptor was successfully removed from the set. + * + * Since: 0.10.18 + */ +gboolean +gst_poll_remove_fd (GstPoll * set, GstPollFD * fd) +{ + gint idx; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (fd != NULL, FALSE); + g_return_val_if_fail (fd->fd >= 0, FALSE); + + + GST_DEBUG ("%p: fd (fd:%d, idx:%d)", set, fd->fd, fd->idx); + + g_mutex_lock (set->lock); + + /* get the index, -1 is an fd that is not added */ + idx = find_index (set->fds, fd); + if (idx >= 0) { +#ifdef G_OS_WIN32 + gst_poll_free_winsock_event (set, idx); + g_array_remove_index_fast (set->events, idx); +#endif + + /* remove the fd at index, we use _remove_index_fast, which copies the last + * element of the array to the freed index */ + g_array_remove_index_fast (set->fds, idx); + + /* mark fd as removed by setting the index to -1 */ + fd->idx = -1; + MARK_REBUILD (set); + } else { + GST_WARNING ("%p: couldn't find fd !", set); + } + + g_mutex_unlock (set->lock); + + return idx >= 0; +} + +/** + * gst_poll_fd_ctl_write: + * @set: a file descriptor set. + * @fd: a file descriptor. + * @active: a new status. + * + * Control whether the descriptor @fd in @set will be monitored for + * writability. + * + * Returns: %TRUE if the descriptor was successfully updated. + * + * Since: 0.10.18 + */ +gboolean +gst_poll_fd_ctl_write (GstPoll * set, GstPollFD * fd, gboolean active) +{ + gint idx; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (fd != NULL, FALSE); + g_return_val_if_fail (fd->fd >= 0, FALSE); + + GST_DEBUG ("%p: fd (fd:%d, idx:%d), active : %d", set, + fd->fd, fd->idx, active); + + g_mutex_lock (set->lock); + + idx = find_index (set->fds, fd); + if (idx >= 0) { +#ifndef G_OS_WIN32 + struct pollfd *pfd = &g_array_index (set->fds, struct pollfd, idx); + + if (active) + pfd->events |= POLLOUT; + else + pfd->events &= ~POLLOUT; + + GST_LOG ("pfd->events now %d (POLLOUT:%d)", pfd->events, POLLOUT); +#else + gst_poll_update_winsock_event_mask (set, idx, FD_WRITE | FD_CONNECT, + active); +#endif + MARK_REBUILD (set); + } else { + GST_WARNING ("%p: couldn't find fd !", set); + } + + g_mutex_unlock (set->lock); + + return idx >= 0; +} + +static gboolean +gst_poll_fd_ctl_read_unlocked (GstPoll * set, GstPollFD * fd, gboolean active) +{ + gint idx; + + GST_DEBUG ("%p: fd (fd:%d, idx:%d), active : %d", set, + fd->fd, fd->idx, active); + + idx = find_index (set->fds, fd); + + if (idx >= 0) { +#ifndef G_OS_WIN32 + struct pollfd *pfd = &g_array_index (set->fds, struct pollfd, idx); + + if (active) + pfd->events |= (POLLIN | POLLPRI); + else + pfd->events &= ~(POLLIN | POLLPRI); +#else + gst_poll_update_winsock_event_mask (set, idx, FD_READ | FD_ACCEPT, active); +#endif + MARK_REBUILD (set); + } else { + GST_WARNING ("%p: couldn't find fd !", set); + } + + return idx >= 0; +} + +/** + * gst_poll_fd_ctl_read: + * @set: a file descriptor set. + * @fd: a file descriptor. + * @active: a new status. + * + * Control whether the descriptor @fd in @set will be monitored for + * readability. + * + * Returns: %TRUE if the descriptor was successfully updated. + * + * Since: 0.10.18 + */ +gboolean +gst_poll_fd_ctl_read (GstPoll * set, GstPollFD * fd, gboolean active) +{ + gboolean ret; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (fd != NULL, FALSE); + g_return_val_if_fail (fd->fd >= 0, FALSE); + + g_mutex_lock (set->lock); + + ret = gst_poll_fd_ctl_read_unlocked (set, fd, active); + + g_mutex_unlock (set->lock); + + return ret; +} + +/** + * gst_poll_fd_ignored: + * @set: a file descriptor set. + * @fd: a file descriptor. + * + * Mark @fd as ignored so that the next call to gst_poll_wait() will yield + * the same result for @fd as last time. This function must be called if no + * operation (read/write/recv/send/etc.) will be performed on @fd before + * the next call to gst_poll_wait(). + * + * The reason why this is needed is because the underlying implementation + * might not allow querying the fd more than once between calls to one of + * the re-enabling operations. + * + * Since: 0.10.18 + */ +void +gst_poll_fd_ignored (GstPoll * set, GstPollFD * fd) +{ +#ifdef G_OS_WIN32 + gint idx; + + g_return_if_fail (set != NULL); + g_return_if_fail (fd != NULL); + g_return_if_fail (fd->fd >= 0); + + g_mutex_lock (set->lock); + + idx = find_index (set->fds, fd); + if (idx >= 0) { + WinsockFd *wfd = &g_array_index (set->fds, WinsockFd, idx); + + wfd->ignored_event_mask = wfd->event_mask & (FD_READ | FD_WRITE); + MARK_REBUILD (set); + } + + g_mutex_unlock (set->lock); +#endif +} + +/** + * gst_poll_fd_has_closed: + * @set: a file descriptor set. + * @fd: a file descriptor. + * + * Check if @fd in @set has closed the connection. + * + * Returns: %TRUE if the connection was closed. + * + * Since: 0.10.18 + */ +gboolean +gst_poll_fd_has_closed (const GstPoll * set, GstPollFD * fd) +{ + gboolean res = FALSE; + gint idx; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (fd != NULL, FALSE); + g_return_val_if_fail (fd->fd >= 0, FALSE); + + GST_DEBUG ("%p: fd (fd:%d, idx:%d)", set, fd->fd, fd->idx); + + g_mutex_lock (set->lock); + + idx = find_index (set->active_fds, fd); + if (idx >= 0) { +#ifndef G_OS_WIN32 + struct pollfd *pfd = &g_array_index (set->active_fds, struct pollfd, idx); + + res = (pfd->revents & POLLHUP) != 0; +#else + WinsockFd *wfd = &g_array_index (set->active_fds, WinsockFd, idx); + + res = (wfd->events.lNetworkEvents & FD_CLOSE) != 0; +#endif + } else { + GST_WARNING ("%p: couldn't find fd !", set); + } + + g_mutex_unlock (set->lock); + + return res; +} + +/** + * gst_poll_fd_has_error: + * @set: a file descriptor set. + * @fd: a file descriptor. + * + * Check if @fd in @set has an error. + * + * Returns: %TRUE if the descriptor has an error. + * + * Since: 0.10.18 + */ +gboolean +gst_poll_fd_has_error (const GstPoll * set, GstPollFD * fd) +{ + gboolean res = FALSE; + gint idx; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (fd != NULL, FALSE); + g_return_val_if_fail (fd->fd >= 0, FALSE); + + GST_DEBUG ("%p: fd (fd:%d, idx:%d)", set, fd->fd, fd->idx); + + g_mutex_lock (set->lock); + + idx = find_index (set->active_fds, fd); + if (idx >= 0) { +#ifndef G_OS_WIN32 + struct pollfd *pfd = &g_array_index (set->active_fds, struct pollfd, idx); + + res = (pfd->revents & (POLLERR | POLLNVAL)) != 0; +#else + WinsockFd *wfd = &g_array_index (set->active_fds, WinsockFd, idx); + + res = (wfd->events.iErrorCode[FD_CLOSE_BIT] != 0) || + (wfd->events.iErrorCode[FD_READ_BIT] != 0) || + (wfd->events.iErrorCode[FD_WRITE_BIT] != 0) || + (wfd->events.iErrorCode[FD_ACCEPT_BIT] != 0) || + (wfd->events.iErrorCode[FD_CONNECT_BIT] != 0); +#endif + } else { + GST_WARNING ("%p: couldn't find fd !", set); + } + + g_mutex_unlock (set->lock); + + return res; +} + +static gboolean +gst_poll_fd_can_read_unlocked (const GstPoll * set, GstPollFD * fd) +{ + gboolean res = FALSE; + gint idx; + + GST_DEBUG ("%p: fd (fd:%d, idx:%d)", set, fd->fd, fd->idx); + + idx = find_index (set->active_fds, fd); + if (idx >= 0) { +#ifndef G_OS_WIN32 + struct pollfd *pfd = &g_array_index (set->active_fds, struct pollfd, idx); + + res = (pfd->revents & (POLLIN | POLLPRI)) != 0; +#else + WinsockFd *wfd = &g_array_index (set->active_fds, WinsockFd, idx); + + res = (wfd->events.lNetworkEvents & (FD_READ | FD_ACCEPT)) != 0; +#endif + } else { + GST_WARNING ("%p: couldn't find fd !", set); + } + + return res; +} + +/** + * gst_poll_fd_can_read: + * @set: a file descriptor set. + * @fd: a file descriptor. + * + * Check if @fd in @set has data to be read. + * + * Returns: %TRUE if the descriptor has data to be read. + * + * Since: 0.10.18 + */ +gboolean +gst_poll_fd_can_read (const GstPoll * set, GstPollFD * fd) +{ + gboolean res = FALSE; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (fd != NULL, FALSE); + g_return_val_if_fail (fd->fd >= 0, FALSE); + + g_mutex_lock (set->lock); + + res = gst_poll_fd_can_read_unlocked (set, fd); + + g_mutex_unlock (set->lock); + + return res; +} + +/** + * gst_poll_fd_can_write: + * @set: a file descriptor set. + * @fd: a file descriptor. + * + * Check if @fd in @set can be used for writing. + * + * Returns: %TRUE if the descriptor can be used for writing. + * + * Since: 0.10.18 + */ +gboolean +gst_poll_fd_can_write (const GstPoll * set, GstPollFD * fd) +{ + gboolean res = FALSE; + gint idx; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (fd != NULL, FALSE); + g_return_val_if_fail (fd->fd >= 0, FALSE); + + GST_DEBUG ("%p: fd (fd:%d, idx:%d)", set, fd->fd, fd->idx); + + g_mutex_lock (set->lock); + + idx = find_index (set->active_fds, fd); + if (idx >= 0) { +#ifndef G_OS_WIN32 + struct pollfd *pfd = &g_array_index (set->active_fds, struct pollfd, idx); + + res = (pfd->revents & POLLOUT) != 0; +#else + WinsockFd *wfd = &g_array_index (set->active_fds, WinsockFd, idx); + + res = (wfd->events.lNetworkEvents & FD_WRITE) != 0; +#endif + } else { + GST_WARNING ("%p: couldn't find fd !", set); + } + + g_mutex_unlock (set->lock); + + return res; +} + +/** + * gst_poll_wait: + * @set: a #GstPoll. + * @timeout: a timeout in nanoseconds. + * + * Wait for activity on the file descriptors in @set. This function waits up to + * the specified @timeout. A timeout of #GST_CLOCK_TIME_NONE waits forever. + * + * For #GstPoll objects created with gst_poll_new(), this function can only be + * called from a single thread at a time. If called from multiple threads, + * -1 will be returned with errno set to EPERM. + * + * This is not true for timer #GstPoll objects created with + * gst_poll_new_timer(), where it is allowed to have multiple threads waiting + * simultaneously. + * + * Returns: The number of #GstPollFD in @set that have activity or 0 when no + * activity was detected after @timeout. If an error occurs, -1 is returned + * and errno is set. + * + * Since: 0.10.18 + */ +gint +gst_poll_wait (GstPoll * set, GstClockTime timeout) +{ + gboolean restarting; + gboolean is_timer; + int res; + gint old_waiting; + + g_return_val_if_fail (set != NULL, -1); + + GST_DEBUG ("timeout :%" GST_TIME_FORMAT, GST_TIME_ARGS (timeout)); + + is_timer = set->timer; + + /* add one more waiter */ + old_waiting = INC_WAITING (set); + + /* we cannot wait from multiple threads unless we are a timer */ + if (G_UNLIKELY (old_waiting > 0 && !is_timer)) + goto already_waiting; + + /* flushing, exit immediately */ + if (G_UNLIKELY (IS_FLUSHING (set))) + goto flushing; + + do { + GstPollMode mode; + + res = -1; + restarting = FALSE; + + mode = choose_mode (set, timeout); + + if (TEST_REBUILD (set)) { + g_mutex_lock (set->lock); +#ifndef G_OS_WIN32 + g_array_set_size (set->active_fds, set->fds->len); + memcpy (set->active_fds->data, set->fds->data, + set->fds->len * sizeof (struct pollfd)); +#else + if (!gst_poll_prepare_winsock_active_sets (set)) + goto winsock_error; +#endif + g_mutex_unlock (set->lock); + } + + switch (mode) { + case GST_POLL_MODE_AUTO: + g_assert_not_reached (); + break; + case GST_POLL_MODE_PPOLL: + { +#ifdef HAVE_PPOLL + struct timespec ts; + struct timespec *tsptr; + + if (timeout != GST_CLOCK_TIME_NONE) { + GST_TIME_TO_TIMESPEC (timeout, ts); + tsptr = &ts; + } else { + tsptr = NULL; + } + + res = + ppoll ((struct pollfd *) set->active_fds->data, + set->active_fds->len, tsptr, NULL); +#else + g_assert_not_reached (); + errno = ENOSYS; +#endif + break; + } + case GST_POLL_MODE_POLL: + { +#ifdef HAVE_POLL + gint t; + + if (timeout != GST_CLOCK_TIME_NONE) { + t = GST_TIME_AS_MSECONDS (timeout); + } else { + t = -1; + } + + res = + poll ((struct pollfd *) set->active_fds->data, + set->active_fds->len, t); +#else + g_assert_not_reached (); + errno = ENOSYS; +#endif + break; + } + case GST_POLL_MODE_PSELECT: +#ifndef HAVE_PSELECT + { + g_assert_not_reached (); + errno = ENOSYS; + break; + } +#endif + case GST_POLL_MODE_SELECT: + { +#ifndef G_OS_WIN32 + fd_set readfds; + fd_set writefds; + fd_set errorfds; + gint max_fd; + + max_fd = pollfd_to_fd_set (set, &readfds, &writefds, &errorfds); + + if (mode == GST_POLL_MODE_SELECT) { + struct timeval tv; + struct timeval *tvptr; + + if (timeout != GST_CLOCK_TIME_NONE) { + GST_TIME_TO_TIMEVAL (timeout, tv); + tvptr = &tv; + } else { + tvptr = NULL; + } + + GST_DEBUG ("Calling select"); + res = select (max_fd + 1, &readfds, &writefds, &errorfds, tvptr); + GST_DEBUG ("After select, res:%d", res); + } else { +#ifdef HAVE_PSELECT + struct timespec ts; + struct timespec *tsptr; + + if (timeout != GST_CLOCK_TIME_NONE) { + GST_TIME_TO_TIMESPEC (timeout, ts); + tsptr = &ts; + } else { + tsptr = NULL; + } + + GST_DEBUG ("Calling pselect"); + res = + pselect (max_fd + 1, &readfds, &writefds, &errorfds, tsptr, NULL); + GST_DEBUG ("After pselect, res:%d", res); +#endif + } + + if (res >= 0) { + fd_set_to_pollfd (set, &readfds, &writefds, &errorfds); + } +#else /* G_OS_WIN32 */ + g_assert_not_reached (); + errno = ENOSYS; +#endif + break; + } + case GST_POLL_MODE_WINDOWS: + { +#ifdef G_OS_WIN32 + gint ignore_count = set->active_fds_ignored->len; + DWORD t, wait_ret; + + if (G_LIKELY (ignore_count == 0)) { + if (timeout != GST_CLOCK_TIME_NONE) + t = GST_TIME_AS_MSECONDS (timeout); + else + t = INFINITE; + } else { + /* already one or more ignored fds, so we quickly sweep the others */ + t = 0; + } + + if (set->active_events->len != 0) { + wait_ret = WSAWaitForMultipleEvents (set->active_events->len, + (HANDLE *) set->active_events->data, FALSE, t, FALSE); + } else { + wait_ret = WSA_WAIT_FAILED; + WSASetLastError (WSA_INVALID_PARAMETER); + } + + if (ignore_count == 0 && wait_ret == WSA_WAIT_TIMEOUT) { + res = 0; + } else if (wait_ret == WSA_WAIT_FAILED) { + res = -1; + errno = gst_poll_winsock_error_to_errno (WSAGetLastError ()); + } else { + /* the first entry is the wakeup event */ + if (wait_ret - WSA_WAIT_EVENT_0 >= 1) { + res = gst_poll_collect_winsock_events (set); + } else { + res = 1; /* wakeup event */ + } + } +#else + g_assert_not_reached (); + errno = ENOSYS; +#endif + break; + } + } + + if (!is_timer) { + /* Applications needs to clear the control socket themselves for timer + * polls. + * For other polls, we need to clear the control socket. If there was only + * one socket with activity and it was the control socket, we need to + * restart */ + if (release_all_wakeup (set) > 0 && res == 1) + restarting = TRUE; + } + + /* we got woken up and we are flushing, we need to stop */ + if (G_UNLIKELY (IS_FLUSHING (set))) + goto flushing; + + } while (G_UNLIKELY (restarting)); + + DEC_WAITING (set); + + return res; + + /* ERRORS */ +already_waiting: + { + GST_LOG ("%p: we are already waiting", set); + DEC_WAITING (set); + errno = EPERM; + return -1; + } +flushing: + { + GST_LOG ("%p: we are flushing", set); + DEC_WAITING (set); + errno = EBUSY; + return -1; + } +#ifdef G_OS_WIN32 +winsock_error: + { + GST_LOG ("%p: winsock error", set); + g_mutex_unlock (set->lock); + DEC_WAITING (set); + return -1; + } +#endif +} + +/** + * gst_poll_set_controllable: + * @set: a #GstPoll. + * @controllable: new controllable state. + * + * When @controllable is %TRUE, this function ensures that future calls to + * gst_poll_wait() will be affected by gst_poll_restart() and + * gst_poll_set_flushing(). + * + * Returns: %TRUE if the controllability of @set could be updated. + * + * Since: 0.10.18 + */ +gboolean +gst_poll_set_controllable (GstPoll * set, gboolean controllable) +{ + g_return_val_if_fail (set != NULL, FALSE); + + GST_LOG ("%p: controllable : %d", set, controllable); + + set->controllable = controllable; + + return TRUE; +} + +/** + * gst_poll_restart: + * @set: a #GstPoll. + * + * Restart any gst_poll_wait() that is in progress. This function is typically + * used after adding or removing descriptors to @set. + * + * If @set is not controllable, then this call will have no effect. + * + * Since: 0.10.18 + */ +void +gst_poll_restart (GstPoll * set) +{ + g_return_if_fail (set != NULL); + + if (set->controllable && GET_WAITING (set) > 0) { + /* we are controllable and waiting, wake up the waiter. The socket will be + * cleared by the _wait() thread and the poll will be restarted */ + raise_wakeup (set); + } +} + +/** + * gst_poll_set_flushing: + * @set: a #GstPoll. + * @flushing: new flushing state. + * + * When @flushing is %TRUE, this function ensures that current and future calls + * to gst_poll_wait() will return -1, with errno set to EBUSY. + * + * Unsetting the flushing state will restore normal operation of @set. + * + * Since: 0.10.18 + */ +void +gst_poll_set_flushing (GstPoll * set, gboolean flushing) +{ + g_return_if_fail (set != NULL); + + GST_LOG ("%p: flushing: %d", set, flushing); + + /* update the new state first */ + SET_FLUSHING (set, flushing); + + if (flushing && set->controllable && GET_WAITING (set) > 0) { + /* we are flushing, controllable and waiting, wake up the waiter. When we + * stop the flushing operation we don't clear the wakeup fd here, this will + * happen in the _wait() thread. */ + raise_wakeup (set); + } +} + +/** + * gst_poll_write_control: + * @set: a #GstPoll. + * + * Write a byte to the control socket of the controllable @set. + * This function is mostly useful for timer #GstPoll objects created with + * gst_poll_new_timer(). + * + * It will make any current and future gst_poll_wait() function return with + * 1, meaning the control socket is set. After an equal amount of calls to + * gst_poll_read_control() have been performed, calls to gst_poll_wait() will + * block again until their timeout expired. + * + * Returns: %TRUE on success. %FALSE when @set is not controllable or when the + * byte could not be written. + * + * Since: 0.10.23 + */ +gboolean +gst_poll_write_control (GstPoll * set) +{ + gboolean res; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (set->timer, FALSE); + + res = raise_wakeup (set); + + return res; +} + +/** + * gst_poll_read_control: + * @set: a #GstPoll. + * + * Read a byte from the control socket of the controllable @set. + * This function is mostly useful for timer #GstPoll objects created with + * gst_poll_new_timer(). + * + * Returns: %TRUE on success. %FALSE when @set is not controllable or when there + * was no byte to read. + * + * Since: 0.10.23 + */ +gboolean +gst_poll_read_control (GstPoll * set) +{ + gboolean res; + + g_return_val_if_fail (set != NULL, FALSE); + g_return_val_if_fail (set->timer, FALSE); + + res = release_wakeup (set); + + return res; +} diff --git a/gst/gstpoll.h b/gst/gstpoll.h new file mode 100644 index 0000000..741a79b --- /dev/null +++ b/gst/gstpoll.h @@ -0,0 +1,97 @@ +/* GStreamer + * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu> + * Copyright (C) 2004 Wim Taymans <wim.taymans@gmail.com> + * Copyright (C) 2007 Peter Kjellerstedt <pkj@axis.com> + * + * gstpoll.h: File descriptor set + * + * 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_POLL_H__ +#define __GST_POLL_H__ + +#include <glib.h> +#include <glib-object.h> + +#include <gst/gstclock.h> + +G_BEGIN_DECLS + +/** + * GstPoll: + * + * A set of file/network descriptors. + */ +typedef struct _GstPoll GstPoll; + +/** + * GstPollFD: + * @fd: a file descriptor + * + * A file descriptor object. + */ +typedef struct { + int fd; + + /*< private >*/ + gint idx; +} GstPollFD; + +/** + * GST_POLL_FD_INIT: + * + * A #GstPollFD must be initialized with this macro, before it can be + * used. This macro can used be to initialize a variable, but it cannot + * be assigned to a variable. In that case you have to use + * gst_poll_fd_init(). + * + * Since: 0.10.18 + */ +#define GST_POLL_FD_INIT { -1, -1 } + +GstPoll* gst_poll_new (gboolean controllable); +GstPoll* gst_poll_new_timer (void); +void gst_poll_free (GstPoll *set); + +void gst_poll_get_read_gpollfd (GstPoll *set, GPollFD *fd); + +void gst_poll_fd_init (GstPollFD *fd); + +gboolean gst_poll_add_fd (GstPoll *set, GstPollFD *fd); +gboolean gst_poll_remove_fd (GstPoll *set, GstPollFD *fd); + +gboolean gst_poll_fd_ctl_write (GstPoll *set, GstPollFD *fd, gboolean active); +gboolean gst_poll_fd_ctl_read (GstPoll *set, GstPollFD *fd, gboolean active); +void gst_poll_fd_ignored (GstPoll *set, GstPollFD *fd); + +gboolean gst_poll_fd_has_closed (const GstPoll *set, GstPollFD *fd); +gboolean gst_poll_fd_has_error (const GstPoll *set, GstPollFD *fd); +gboolean gst_poll_fd_can_read (const GstPoll *set, GstPollFD *fd); +gboolean gst_poll_fd_can_write (const GstPoll *set, GstPollFD *fd); + +gint gst_poll_wait (GstPoll *set, GstClockTime timeout); + +gboolean gst_poll_set_controllable (GstPoll *set, gboolean controllable); +void gst_poll_restart (GstPoll *set); +void gst_poll_set_flushing (GstPoll *set, gboolean flushing); + +gboolean gst_poll_write_control (GstPoll *set); +gboolean gst_poll_read_control (GstPoll *set); + +G_END_DECLS + +#endif /* __GST_POLL_H__ */ diff --git a/gst/gstpreset.c b/gst/gstpreset.c new file mode 100644 index 0000000..6280896 --- /dev/null +++ b/gst/gstpreset.c @@ -0,0 +1,1122 @@ +/* GStreamer + * Copyright (C) 2006 Stefan Kost <ensonic@users.sf.net> + * + * gstpreset.c: helper interface for element presets + * + * 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:gstpreset + * @short_description: helper interface for element presets + * + * This interface offers methods to query and manipulate parameter preset sets. + * A preset is a bunch of property settings, together with meta data and a name. + * The name of a preset serves as key for subsequent method calls to manipulate + * single presets. + * All instances of one type will share the list of presets. The list is created + * on demand, if presets are not used, the list is not created. + * + * The interface comes with a default implementation that serves most plugins. + * Wrapper plugins will override most methods to implement support for the + * native preset format of those wrapped plugins. + * One method that is useful to be overridden is gst_preset_get_property_names(). + * With that one can control which properties are saved and in which order. + */ +/* FIXME: + * - non racyness + * - we need to avoid two instances writing the preset file + * -> flock(fileno()), http://www.ecst.csuchico.edu/~beej/guide/ipc/flock.html + * -> open exclusive + * -> better save the new file to a tempfile and then rename? + * - we like to know when any other instance makes changes to the keyfile + * - then ui can be updated + * - and we make sure that we don't lose edits + * -> its the same problem actually, once for inside a process, once system- + * wide + * - can we use a lock inside a names shared memory segment? + * + * - need to add support for GstChildProxy + * we can do this in a next iteration, the format is flexible enough + * http://www.buzztard.org/index.php/Preset_handling_interface + * + * - should there be a 'preset-list' property to get the preset list + * (and to connect a notify:: to to listen for changes) + * we could use gnome_vfs_monitor_add() to monitor the user preset_file. + * + * - should there be a 'preset-name' property so that we can set a preset via + * gst-launch, or should we handle this with special syntax in gst-launch: + * gst-launch element preset:<preset-name> property=value ... + * - this would alloow to hanve preset-bundles too (a preset on bins that + * specifies presets for children + * + * - GstChildProxy suport + * - if we stick with GParamSpec **_list_properties() + * we need to use g_param_spec_set_qdata() to specify the instance on each GParamSpec + * OBJECT_LOCK(obj); // ChildProxy needs GstIterator support + * num=gst_child_proxy_get_children_count(obj); + * for(i=0;i<num;i++) { + * child=gst_child_proxy_get_child_by_index(obj,i); + * // v1 ---- + * g_object_class_list_properties(child,&num); + * // foreach prop + * // g_param_spec_set_qdata(prop, quark, (gpointer)child); + * // add to result + * // v2 ---- + * // children have to implement preset-iface too tag the returned GParamSpec* with the owner + * props=gst_preset_list_properties(child); + * // add props to result + * } + * OBJECT_UNLOCK(obj); + * + */ + +#include "gst_private.h" + +#include "gstpreset.h" +#include "gstinfo.h" +#include "gstvalue.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <glib/gstdio.h> + +#define GST_CAT_DEFAULT preset_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +/* defines for keyfile usage, this group contains the element type name and + * version these presets belong to. */ +#define PRESET_HEADER "_presets_" + +/* keys of the preset header section */ +#define PRESET_HEADER_ELEMENT_NAME "element-name" +#define PRESET_HEADER_VERSION "version" + +static GQuark preset_user_path_quark = 0; +static GQuark preset_system_path_quark = 0; +static GQuark preset_quark = 0; + +/*static GQuark property_list_quark = 0;*/ + +/* default iface implementation */ + +static gboolean gst_preset_default_save_presets_file (GstPreset * preset); + +/* + * preset_get_paths: + * @preset: a #GObject that implements #GstPreset + * @preset_user_path: location for path or %NULL + * @preset_system_path: location for path or %NULL + * + * Fetch the preset_path for user local and system wide settings. Don't free + * after use. + * + * Returns: %FALSE if no paths could be found. + */ +static gboolean +preset_get_paths (GstPreset * preset, const gchar ** preset_user_path, + const gchar ** preset_system_path) +{ + GType type = G_TYPE_FROM_INSTANCE (preset); + gchar *preset_path; + const gchar *element_name; + + /* we use the element name when we must contruct the paths */ + element_name = G_OBJECT_TYPE_NAME (preset); + GST_INFO_OBJECT (preset, "element_name: '%s'", element_name); + + if (preset_user_path) { + /* preset user path requested, see if we have it cached in the qdata */ + if (!(preset_path = g_type_get_qdata (type, preset_user_path_quark))) { + gchar *preset_dir; + + /* user presets go in user's XDG data directory. */ + preset_dir = g_build_filename (g_get_user_data_dir (), + "gstreamer-" GST_MAJORMINOR, "presets", NULL); + GST_INFO_OBJECT (preset, "user_preset_dir: '%s'", preset_dir); + preset_path = + g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.prs", preset_dir, + element_name); + GST_INFO_OBJECT (preset, "user_preset_path: '%s'", preset_path); + /* create dirs */ + g_mkdir_with_parents (preset_dir, 0755); + g_free (preset_dir); + + /* cache the preset path to the type */ + g_type_set_qdata (type, preset_user_path_quark, preset_path); + } + *preset_user_path = preset_path; + } + + if (preset_system_path) { + /* preset system path requested, see if we have it cached in the qdata */ + if (!(preset_path = g_type_get_qdata (type, preset_system_path_quark))) { + gchar *preset_dir; + + /* system presets in '$GST_DATADIR/gstreamer-0.10/presets/GstAudioPanorama.prs' */ + preset_dir = g_build_filename (GST_DATADIR, "gstreamer-" GST_MAJORMINOR, + "presets", NULL); + GST_INFO_OBJECT (preset, "system_preset_dir: '%s'", preset_dir); + preset_path = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.prs", + preset_dir, element_name); + GST_INFO_OBJECT (preset, "system_preset_path: '%s'", preset_path); + /* create dirs */ + g_mkdir_with_parents (preset_dir, 0755); + g_free (preset_dir); + + /* cache the preset path to the type */ + g_type_set_qdata (type, preset_system_path_quark, preset_path); + } + *preset_system_path = preset_path; + } + return TRUE; +} + +static gboolean +preset_skip_property (GParamSpec * property) +{ + if (((property->flags & G_PARAM_READWRITE) != G_PARAM_READWRITE) || + (property->flags & G_PARAM_CONSTRUCT_ONLY)) + return TRUE; + /* FIXME: skip GST_PARAM_NOT_PRESETABLE, see #522205 */ + return FALSE; +} + +/* caller must free @preset_version after use */ +static GKeyFile * +preset_open_and_parse_header (GstPreset * preset, const gchar * preset_path, + gchar ** preset_version) +{ + GKeyFile *in; + GError *error = NULL; + gboolean res; + const gchar *element_name; + gchar *name; + + in = g_key_file_new (); + + res = g_key_file_load_from_file (in, preset_path, + G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error); + if (!res || error != NULL) + goto load_error; + + /* element type name and preset name must match or we are dealing with a wrong + * preset file */ + element_name = G_OBJECT_TYPE_NAME (preset); + name = + g_key_file_get_value (in, PRESET_HEADER, PRESET_HEADER_ELEMENT_NAME, + NULL); + + if (!name || strcmp (name, element_name)) + goto wrong_name; + + g_free (name); + + /* get the version now so that the caller can check it */ + if (preset_version) + *preset_version = + g_key_file_get_value (in, PRESET_HEADER, PRESET_HEADER_VERSION, NULL); + + return in; + + /* ERRORS */ +load_error: + { + GST_WARNING_OBJECT (preset, "Unable to read preset file %s: %s", + preset_path, error->message); + g_error_free (error); + g_key_file_free (in); + return NULL; + } +wrong_name: + { + GST_WARNING_OBJECT (preset, + "Wrong element name in preset file %s. Expected %s, got %s", + preset_path, element_name, GST_STR_NULL (name)); + g_free (name); + g_key_file_free (in); + return NULL; + } +} + +static guint64 +preset_parse_version (const gchar * str_version) +{ + guint major, minor, micro, nano; + gint num; + + major = minor = micro = nano = 0; + + /* parse version (e.g. 0.10.15.1) to guint64 */ + num = sscanf (str_version, "%u.%u.%u.%u", &major, &minor, µ, &nano); + /* make sure we have atleast "major.minor" */ + if (num > 1) { + guint64 version; + + version = ((((major << 8 | minor) << 8) | micro) << 8) | nano; + GST_DEBUG ("version %s -> %" G_GUINT64_FORMAT, str_version, version); + return version; + } + return G_GUINT64_CONSTANT (0); +} + +static void +preset_merge (GKeyFile * system, GKeyFile * user) +{ + gchar *str; + gchar **groups, **keys; + gsize i, j, num_groups, num_keys; + + /* copy file comment if there is any */ + if ((str = g_key_file_get_comment (user, NULL, NULL, NULL))) { + g_key_file_set_comment (system, NULL, NULL, str, NULL); + g_free (str); + } + + /* get groups in user and copy into system */ + groups = g_key_file_get_groups (user, &num_groups); + for (i = 0; i < num_groups; i++) { + /* copy group comment if there is any */ + if ((str = g_key_file_get_comment (user, groups[i], NULL, NULL))) { + g_key_file_set_comment (system, groups[i], NULL, str, NULL); + g_free (str); + } + + /* ignore private groups */ + if (groups[i][0] == '_') + continue; + + /* if group already exists in system, remove and re-add keys from user */ + if (g_key_file_has_group (system, groups[i])) { + g_key_file_remove_group (system, groups[i], NULL); + } + + keys = g_key_file_get_keys (user, groups[i], &num_keys, NULL); + for (j = 0; j < num_keys; j++) { + /* copy key comment if there is any */ + if ((str = g_key_file_get_comment (user, groups[i], keys[j], NULL))) { + g_key_file_set_comment (system, groups[i], keys[j], str, NULL); + g_free (str); + } + str = g_key_file_get_value (user, groups[i], keys[j], NULL); + g_key_file_set_value (system, groups[i], keys[j], str); + g_free (str); + } + g_strfreev (keys); + } + g_strfreev (groups); +} + +/* reads the user and system presets files and merges them together. This + * function caches the GKeyFile on the element type. If there is no existing + * preset file, a new in-memory GKeyFile will be created. */ +static GKeyFile * +preset_get_keyfile (GstPreset * preset) +{ + GKeyFile *presets; + GType type = G_TYPE_FROM_INSTANCE (preset); + + /* first see if the have a cached version for the type */ + if (!(presets = g_type_get_qdata (type, preset_quark))) { + const gchar *preset_user_path, *preset_system_path; + gchar *str_version_user = NULL, *str_version_system = NULL; + gboolean updated_from_system = FALSE; + GKeyFile *in_user, *in_system; + + preset_get_paths (preset, &preset_user_path, &preset_system_path); + + /* try to load the user and system presets, we do this to get the versions + * of both files. */ + in_user = preset_open_and_parse_header (preset, preset_user_path, + &str_version_user); + in_system = preset_open_and_parse_header (preset, preset_system_path, + &str_version_system); + + /* compare version to check for merge */ + if (in_system) { + /* keep system presets if there is no user preset or when the system + * version is higher than the user version. */ + if (!in_user) { + presets = in_system; + } else if (preset_parse_version (str_version_system) > + preset_parse_version (str_version_user)) { + presets = in_system; + updated_from_system = TRUE; + } + } + if (in_user) { + if (updated_from_system) { + /* merge user on top of system presets */ + preset_merge (presets, in_user); + g_key_file_free (in_user); + } else { + /* keep user presets */ + presets = in_user; + } + } + if (!in_user && !in_system) { + /* we did not load a user or system presets file, create a new one */ + presets = g_key_file_new (); + g_key_file_set_string (presets, PRESET_HEADER, PRESET_HEADER_ELEMENT_NAME, + G_OBJECT_TYPE_NAME (preset)); + } + + g_free (str_version_user); + g_free (str_version_system); + + /* attach the preset to the type */ + g_type_set_qdata (type, preset_quark, (gpointer) presets); + + if (updated_from_system) { + gst_preset_default_save_presets_file (preset); + } + } + return presets; +} + +/* get a list of all supported preset names for an element */ +static gchar ** +gst_preset_default_get_preset_names (GstPreset * preset) +{ + GKeyFile *presets; + gsize i, num_groups; + gchar **groups; + + /* get the presets from the type */ + if (!(presets = preset_get_keyfile (preset))) + goto no_presets; + + /* get the groups, which are also the preset names */ + if (!(groups = g_key_file_get_groups (presets, &num_groups))) + goto no_groups; + + /* remove all private group names starting with '_' from the array */ + for (i = 0; i < num_groups; i++) { + if (groups[i][0] == '_') { + /* free private group */ + g_free (groups[i]); + /* move last element of list down */ + num_groups--; + /* move last element into removed element */ + groups[i] = groups[num_groups]; + groups[num_groups] = NULL; + } + } + /* sort the array now */ + g_qsort_with_data (groups, num_groups, sizeof (gchar *), + (GCompareDataFunc) strcmp, NULL); + + return groups; + + /* ERRORS */ +no_presets: + { + GST_WARNING_OBJECT (preset, "Could not load presets"); + return NULL; + } +no_groups: + { + GST_WARNING_OBJECT (preset, "Could not find preset groups"); + return NULL; + } +} + +/* get a list of all property names that are used for presets */ +static gchar ** +gst_preset_default_get_property_names (GstPreset * preset) +{ + GParamSpec **props; + guint i, j, n_props; + GObjectClass *gclass; + gchar **result; + + gclass = G_OBJECT_CLASS (GST_ELEMENT_GET_CLASS (preset)); + + /* get a list of normal properties. + * FIXME, change this for childproxy support. */ + props = g_object_class_list_properties (gclass, &n_props); + if (!props) + goto no_properties; + + /* allocate array big enough to hold the worst case, including a terminating + * NULL pointer. */ + result = g_new (gchar *, n_props + 1); + + /* now filter out the properties that we can use for presets */ + GST_DEBUG_OBJECT (preset, " filtering properties: %u", n_props); + for (i = j = 0; i < n_props; i++) { + if (preset_skip_property (props[i])) + continue; + + /* copy and increment out pointer */ + result[j++] = g_strdup (props[i]->name); + } + result[j] = NULL; + g_free (props); + + return result; + + /* ERRORS */ +no_properties: + { + GST_INFO_OBJECT (preset, "object has no properties"); + return NULL; + } +} + +/* load the presets of @name for the instance @preset. Returns %FALSE if something + * failed. */ +static gboolean +gst_preset_default_load_preset (GstPreset * preset, const gchar * name) +{ + GKeyFile *presets; + gchar **props; + guint i; + GObjectClass *gclass; + + /* get the presets from the type */ + if (!(presets = preset_get_keyfile (preset))) + goto no_presets; + + /* get the preset name */ + if (!g_key_file_has_group (presets, name)) + goto no_group; + + GST_DEBUG_OBJECT (preset, "loading preset : '%s'", name); + + /* get the properties that we can configure in this element */ + if (!(props = gst_preset_get_property_names (preset))) + goto no_properties; + + gclass = G_OBJECT_CLASS (GST_ELEMENT_GET_CLASS (preset)); + + /* for each of the property names, find the preset parameter and try to + * configure the property with its value */ + for (i = 0; props[i]; i++) { + gchar *str; + GValue gvalue = { 0, }; + GParamSpec *property; + + /* check if we have a settings for this element property */ + if (!(str = g_key_file_get_value (presets, name, props[i], NULL))) { + /* the element has a property but the parameter is not in the keyfile */ + GST_WARNING_OBJECT (preset, "parameter '%s' not in preset", props[i]); + continue; + } + + GST_DEBUG_OBJECT (preset, "setting value '%s' for property '%s'", str, + props[i]); + + /* FIXME, change for childproxy to get the property and element. */ + if (!(property = g_object_class_find_property (gclass, props[i]))) { + /* the parameter was in the keyfile, the element said it supported it but + * then the property was not found in the element. This should not happen. */ + GST_WARNING_OBJECT (preset, "property '%s' not in object", props[i]); + g_free (str); + continue; + } + + /* try to deserialize the property value from the keyfile and set it as + * the object property */ + g_value_init (&gvalue, property->value_type); + if (gst_value_deserialize (&gvalue, str)) { + /* FIXME, change for childproxy support */ + g_object_set_property (G_OBJECT (preset), props[i], &gvalue); + } else { + GST_WARNING_OBJECT (preset, + "deserialization of value '%s' for property '%s' failed", str, + props[i]); + } + g_value_unset (&gvalue); + g_free (str); + } + g_strfreev (props); + + return TRUE; + + /* ERRORS */ +no_presets: + { + GST_WARNING_OBJECT (preset, "no presets"); + return FALSE; + } +no_group: + { + GST_WARNING_OBJECT (preset, "no preset named '%s'", name); + return FALSE; + } +no_properties: + { + GST_INFO_OBJECT (preset, "no properties"); + return FALSE; + } +} + +/* save the presets file. A copy of the existing presets file is stored in a + * .bak file */ +static gboolean +gst_preset_default_save_presets_file (GstPreset * preset) +{ + GKeyFile *presets; + const gchar *preset_path; + GError *error = NULL; + gchar *bak_file_name; + gboolean backup = TRUE; + gchar *data; + gsize data_size; + + preset_get_paths (preset, &preset_path, NULL); + + /* get the presets from the type */ + if (!(presets = preset_get_keyfile (preset))) + goto no_presets; + + GST_DEBUG_OBJECT (preset, "saving preset file: '%s'", preset_path); + + /* create backup if possible */ + bak_file_name = g_strdup_printf ("%s.bak", preset_path); + if (g_file_test (bak_file_name, G_FILE_TEST_EXISTS)) { + if (g_unlink (bak_file_name)) { + backup = FALSE; + GST_INFO_OBJECT (preset, "cannot remove old backup file : %s", + bak_file_name); + } + } + if (backup) { + if (g_rename (preset_path, bak_file_name)) { + GST_INFO_OBJECT (preset, "cannot backup file : %s -> %s", preset_path, + bak_file_name); + } + } + g_free (bak_file_name); + + /* update gstreamer version */ + g_key_file_set_string (presets, PRESET_HEADER, PRESET_HEADER_VERSION, + PACKAGE_VERSION); + + /* get new contents, wee need this to save it */ + if (!(data = g_key_file_to_data (presets, &data_size, &error))) + goto convert_failed; + + /* write presets */ + if (!g_file_set_contents (preset_path, data, data_size, &error)) + goto write_failed; + + g_free (data); + + return TRUE; + + /* ERRORS */ +no_presets: + { + GST_WARNING_OBJECT (preset, + "no presets, trying to unlink possibly existing preset file: '%s'", + preset_path); + g_unlink (preset_path); + return FALSE; + } +convert_failed: + { + GST_WARNING_OBJECT (preset, "can not get the keyfile contents: %s", + error->message); + g_error_free (error); + g_free (data); + return FALSE; + } +write_failed: + { + GST_WARNING_OBJECT (preset, "Unable to store preset file %s: %s", + preset_path, error->message); + g_error_free (error); + g_free (data); + return FALSE; + } +} + +/* save the preset with the given name */ +static gboolean +gst_preset_default_save_preset (GstPreset * preset, const gchar * name) +{ + GKeyFile *presets; + gchar **props; + guint i; + GObjectClass *gclass; + + GST_INFO_OBJECT (preset, "saving new preset: %s", name); + + /* get the presets from the type */ + if (!(presets = preset_get_keyfile (preset))) + goto no_presets; + + /* take copies of current gobject properties from preset */ + if (!(props = gst_preset_get_property_names (preset))) + goto no_properties; + + gclass = G_OBJECT_CLASS (GST_ELEMENT_GET_CLASS (preset)); + + /* loop over the object properties and store the property value in the + * keyfile */ + for (i = 0; props[i]; i++) { + GValue gvalue = { 0, }; + gchar *str; + GParamSpec *property; + + /* FIXME, change for childproxy to get the property and element. */ + if (!(property = g_object_class_find_property (gclass, props[i]))) { + /* the element said it supported the property but then it does not have + * that property. This should not happen. */ + GST_WARNING_OBJECT (preset, "property '%s' not in object", props[i]); + continue; + } + + g_value_init (&gvalue, property->value_type); + /* FIXME, change for childproxy */ + g_object_get_property (G_OBJECT (preset), props[i], &gvalue); + + if ((str = gst_value_serialize (&gvalue))) { + g_key_file_set_string (presets, name, props[i], (gpointer) str); + g_free (str); + } else { + GST_WARNING_OBJECT (preset, "serialization for property '%s' failed", + props[i]); + } + g_value_unset (&gvalue); + } + GST_INFO_OBJECT (preset, " saved"); + g_strfreev (props); + + /* save updated version */ + return gst_preset_default_save_presets_file (preset); + + /* ERRORS */ +no_presets: + { + GST_WARNING_OBJECT (preset, "no presets"); + return FALSE; + } +no_properties: + { + GST_INFO_OBJECT (preset, "no properties"); + return FALSE; + } +} + +/* copies all keys and comments from one group to another, deleting the old + * group. */ +static gboolean +gst_preset_default_rename_preset (GstPreset * preset, const gchar * old_name, + const gchar * new_name) +{ + GKeyFile *presets; + gchar *str; + gchar **keys; + gsize i, num_keys; + + /* get the presets from the type */ + if (!(presets = preset_get_keyfile (preset))) + goto no_presets; + + if (!g_key_file_has_group (presets, old_name)) + goto no_group; + + /* copy group comment if there is any */ + if ((str = g_key_file_get_comment (presets, old_name, NULL, NULL))) { + g_key_file_set_comment (presets, new_name, NULL, str, NULL); + g_free (str); + } + + /* get all keys from the old group and copy them in the new group */ + keys = g_key_file_get_keys (presets, old_name, &num_keys, NULL); + for (i = 0; i < num_keys; i++) { + /* copy key comment if there is any */ + if ((str = g_key_file_get_comment (presets, old_name, keys[i], NULL))) { + g_key_file_set_comment (presets, new_name, keys[i], str, NULL); + g_free (str); + } + /* copy key value */ + str = g_key_file_get_value (presets, old_name, keys[i], NULL); + g_key_file_set_value (presets, new_name, keys[i], str); + g_free (str); + } + g_strfreev (keys); + + /* remove old group */ + g_key_file_remove_group (presets, old_name, NULL); + + /* save updated version */ + return gst_preset_default_save_presets_file (preset); + + /* ERRORS */ +no_presets: + { + GST_WARNING_OBJECT (preset, "no presets"); + return FALSE; + } +no_group: + { + GST_WARNING_OBJECT (preset, "no preset named %s", old_name); + return FALSE; + } +} + +/* delete a group from the keyfile */ +static gboolean +gst_preset_default_delete_preset (GstPreset * preset, const gchar * name) +{ + GKeyFile *presets; + + /* get the presets from the type */ + if (!(presets = preset_get_keyfile (preset))) + goto no_presets; + + /* get the group */ + if (!g_key_file_has_group (presets, name)) + goto no_group; + + /* remove the group */ + g_key_file_remove_group (presets, name, NULL); + + /* save updated version */ + return gst_preset_default_save_presets_file (preset); + + /* ERRORS */ +no_presets: + { + GST_WARNING_OBJECT (preset, "no presets"); + return FALSE; + } +no_group: + { + GST_WARNING_OBJECT (preset, "no preset named %s", name); + return FALSE; + } +} + +static gboolean +gst_preset_default_set_meta (GstPreset * preset, const gchar * name, + const gchar * tag, const gchar * value) +{ + GKeyFile *presets; + gchar *key; + + /* get the presets from the type */ + if (!(presets = preset_get_keyfile (preset))) + goto no_presets; + + key = g_strdup_printf ("_meta/%s", tag); + if (value && *value) { + g_key_file_set_value (presets, name, key, value); + } else { + g_key_file_remove_key (presets, name, key, NULL); + } + g_free (key); + + /* save updated keyfile */ + return gst_preset_default_save_presets_file (preset); + + /* ERRORS */ +no_presets: + { + GST_WARNING_OBJECT (preset, "no presets"); + return FALSE; + } +} + +/* the caller must free @value after usage */ +static gboolean +gst_preset_default_get_meta (GstPreset * preset, const gchar * name, + const gchar * tag, gchar ** value) +{ + GKeyFile *presets; + gchar *key; + + /* get the presets from the type */ + if (!(presets = preset_get_keyfile (preset))) + goto no_presets; + + key = g_strdup_printf ("_meta/%s", tag); + *value = g_key_file_get_value (presets, name, key, NULL); + g_free (key); + + return TRUE; + + /* ERRORS */ +no_presets: + { + GST_WARNING_OBJECT (preset, "no presets"); + *value = NULL; + return FALSE; + } +} + +/* wrapper */ + +/** + * gst_preset_get_preset_names: + * @preset: a #GObject that implements #GstPreset + * + * Get a copy of preset names as a NULL terminated string array. + * + * Returns: (transfer full) (array zero-terminated=1) (element-type gchar*): + * list with names, ue g_strfreev() after usage. + * + * Since: 0.10.20 + */ +gchar ** +gst_preset_get_preset_names (GstPreset * preset) +{ + g_return_val_if_fail (GST_IS_PRESET (preset), NULL); + + return (GST_PRESET_GET_INTERFACE (preset)->get_preset_names (preset)); +} + +/** + * gst_preset_get_property_names: + * @preset: a #GObject that implements #GstPreset + * + * Get a the names of the GObject properties that can be used for presets. + * + * Returns: (transfer full) (array zero-terminated=1) (element-type gchar*): an + * array of property names which should be freed with g_strfreev() after use. + * + * Since: 0.10.20 + */ +gchar ** +gst_preset_get_property_names (GstPreset * preset) +{ + g_return_val_if_fail (GST_IS_PRESET (preset), NULL); + + return (GST_PRESET_GET_INTERFACE (preset)->get_property_names (preset)); +} + +/** + * gst_preset_load_preset: + * @preset: a #GObject that implements #GstPreset + * @name: preset name to load + * + * Load the given preset. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with that @name + * + * Since: 0.10.20 + */ +gboolean +gst_preset_load_preset (GstPreset * preset, const gchar * name) +{ + g_return_val_if_fail (GST_IS_PRESET (preset), FALSE); + g_return_val_if_fail (name, FALSE); + + return (GST_PRESET_GET_INTERFACE (preset)->load_preset (preset, name)); +} + +/** + * gst_preset_save_preset: + * @preset: a #GObject that implements #GstPreset + * @name: preset name to save + * + * Save the current object settings as a preset under the given name. If there + * is already a preset by this @name it will be overwritten. + * + * Returns: %TRUE for success, %FALSE + * + * Since: 0.10.20 + */ +gboolean +gst_preset_save_preset (GstPreset * preset, const gchar * name) +{ + g_return_val_if_fail (GST_IS_PRESET (preset), FALSE); + g_return_val_if_fail (name, FALSE); + + return (GST_PRESET_GET_INTERFACE (preset)->save_preset (preset, name)); +} + +/** + * gst_preset_rename_preset: + * @preset: a #GObject that implements #GstPreset + * @old_name: current preset name + * @new_name: new preset name + * + * Renames a preset. If there is already a preset by the @new_name it will be + * overwritten. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with @old_name + * + * Since: 0.10.20 + */ +gboolean +gst_preset_rename_preset (GstPreset * preset, const gchar * old_name, + const gchar * new_name) +{ + g_return_val_if_fail (GST_IS_PRESET (preset), FALSE); + g_return_val_if_fail (old_name, FALSE); + g_return_val_if_fail (new_name, FALSE); + + return (GST_PRESET_GET_INTERFACE (preset)->rename_preset (preset, old_name, + new_name)); +} + +/** + * gst_preset_delete_preset: + * @preset: a #GObject that implements #GstPreset + * @name: preset name to remove + * + * Delete the given preset. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with that @name + * + * Since: 0.10.20 + */ +gboolean +gst_preset_delete_preset (GstPreset * preset, const gchar * name) +{ + g_return_val_if_fail (GST_IS_PRESET (preset), FALSE); + g_return_val_if_fail (name, FALSE); + + return (GST_PRESET_GET_INTERFACE (preset)->delete_preset (preset, name)); +} + +/** + * gst_preset_set_meta: + * @preset: a #GObject that implements #GstPreset + * @name: preset name + * @tag: meta data item name + * @value: new value + * + * Sets a new @value for an existing meta data item or adds a new item. Meta + * data @tag names can be something like e.g. "comment". Supplying %NULL for the + * @value will unset an existing value. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with that @name + * + * Since: 0.10.20 + */ +gboolean +gst_preset_set_meta (GstPreset * preset, const gchar * name, const gchar * tag, + const gchar * value) +{ + g_return_val_if_fail (GST_IS_PRESET (preset), FALSE); + g_return_val_if_fail (name, FALSE); + g_return_val_if_fail (tag, FALSE); + + return GST_PRESET_GET_INTERFACE (preset)->set_meta (preset, name, tag, value); +} + +/** + * gst_preset_get_meta: + * @preset: a #GObject that implements #GstPreset + * @name: preset name + * @tag: meta data item name + * @value: (out callee-allocates): value + * + * Gets the @value for an existing meta data @tag. Meta data @tag names can be + * something like e.g. "comment". Returned values need to be released when done. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with that @name + * or no value for the given @tag + * + * Since: 0.10.20 + */ +gboolean +gst_preset_get_meta (GstPreset * preset, const gchar * name, const gchar * tag, + gchar ** value) +{ + g_return_val_if_fail (GST_IS_PRESET (preset), FALSE); + g_return_val_if_fail (name, FALSE); + g_return_val_if_fail (tag, FALSE); + g_return_val_if_fail (value, FALSE); + + return GST_PRESET_GET_INTERFACE (preset)->get_meta (preset, name, tag, value); +} + +/* class internals */ + +static void +gst_preset_class_init (GstPresetInterface * iface) +{ + iface->get_preset_names = gst_preset_default_get_preset_names; + iface->get_property_names = gst_preset_default_get_property_names; + + iface->load_preset = gst_preset_default_load_preset; + iface->save_preset = gst_preset_default_save_preset; + iface->rename_preset = gst_preset_default_rename_preset; + iface->delete_preset = gst_preset_default_delete_preset; + + iface->set_meta = gst_preset_default_set_meta; + iface->get_meta = gst_preset_default_get_meta; +} + +static void +gst_preset_base_init (gpointer g_class) +{ + static gboolean initialized = FALSE; + + if (!initialized) { + /* init default implementation */ + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "preset", + GST_DEBUG_FG_WHITE | GST_DEBUG_BG_BLACK, "preset interface"); + + /* create quarks for use with g_type_{g,s}et_qdata() */ + preset_quark = g_quark_from_static_string ("GstPreset::presets"); + preset_user_path_quark = + g_quark_from_static_string ("GstPreset::user_path"); + preset_system_path_quark = + g_quark_from_static_string ("GstPreset::system_path"); + +#if 0 + property_list_quark = g_quark_from_static_string ("GstPreset::properties"); + + /* create interface properties, each element would need to override this + * g_object_class_override_property(gobject_class, PROP_PRESET_NAME, "preset-name"); + * and in _set_property() do + * case PROP_PRESET_NAME: { + * gchar *name = g_value_get_string (value); + * if (name) + * gst_preset_load_preset(preset, name); + * } break; + */ + g_object_interface_install_property (g_class, + g_param_spec_string ("preset-name", + "preset-name property", + "load given preset", NULL, G_PARAM_WRITABLE)); +#endif + + initialized = TRUE; + } +} + +GType +gst_preset_get_type (void) +{ + static volatile gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + const GTypeInfo info = { + sizeof (GstPresetInterface), + (GBaseInitFunc) gst_preset_base_init, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gst_preset_class_init, /* class_init */ + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL /* instance_init */ + }; + _type = g_type_register_static (G_TYPE_INTERFACE, "GstPreset", &info, 0); + g_once_init_leave (&type, _type); + } + return type; +} diff --git a/gst/gstpreset.h b/gst/gstpreset.h new file mode 100644 index 0000000..ccf1366 --- /dev/null +++ b/gst/gstpreset.h @@ -0,0 +1,98 @@ +/* GStreamer + * Copyright (C) 2006 Stefan Kost <ensonic@users.sf.net> + * + * gstpreset.h: helper interface header for element presets + * + * 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_PRESET_H__ +#define __GST_PRESET_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GST_TYPE_PRESET (gst_preset_get_type()) +#define GST_PRESET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PRESET, GstPreset)) +#define GST_IS_PRESET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PRESET)) +#define GST_PRESET_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GST_TYPE_PRESET, GstPresetInterface)) + +/** + * GstPreset: + * + * Opaque #GstPreset data structure. + */ +typedef struct _GstPreset GstPreset; /* dummy object */ +typedef struct _GstPresetInterface GstPresetInterface; + +/** + * GstPresetInterface: + * @parent: parent interface type. + * @get_preset_names: virtual method to get list of presets + * @get_property_names: virtual methods to get properties that are persistent + * @load_preset: virtual methods to load a preset into properties + * @save_preset: virtual methods to save properties into a preset + * @rename_preset: virtual methods to rename a preset + * @delete_preset: virtual methods to remove a preset + * @set_meta: virtual methods to set textual meta data to a preset + * @get_meta: virtual methods to get textual meta data from a preset + * + * #GstPreset interface. + */ +struct _GstPresetInterface +{ + GTypeInterface parent; + + /* methods */ + gchar** (*get_preset_names) (GstPreset *preset); + + gchar** (*get_property_names) (GstPreset *preset); + + gboolean (*load_preset) (GstPreset *preset, const gchar *name); + gboolean (*save_preset) (GstPreset *preset, const gchar *name); + gboolean (*rename_preset) (GstPreset *preset, const gchar *old_name, + const gchar *new_name); + gboolean (*delete_preset) (GstPreset *preset, const gchar *name); + + gboolean (*set_meta) (GstPreset *preset, const gchar *name, + const gchar *tag, const gchar *value); + gboolean (*get_meta) (GstPreset *preset, const gchar *name, + const gchar *tag, gchar **value); + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_preset_get_type(void); + +gchar** gst_preset_get_preset_names (GstPreset *preset); + +gchar** gst_preset_get_property_names (GstPreset *preset); + +gboolean gst_preset_load_preset (GstPreset *preset, const gchar *name); +gboolean gst_preset_save_preset (GstPreset *preset, const gchar *name); +gboolean gst_preset_rename_preset (GstPreset *preset, const gchar *old_name, + const gchar *new_name); +gboolean gst_preset_delete_preset (GstPreset *preset, const gchar *name); + +gboolean gst_preset_set_meta (GstPreset *preset, const gchar *name, + const gchar *tag, const gchar *value); +gboolean gst_preset_get_meta (GstPreset *preset, const gchar *name, + const gchar *tag, gchar **value); + +G_END_DECLS + +#endif /* __GST_PRESET_H__ */ diff --git a/gst/gstquark.c b/gst/gstquark.c new file mode 100644 index 0000000..26b3f16 --- /dev/null +++ b/gst/gstquark.c @@ -0,0 +1,74 @@ +/* GStreamer + * Copyright (C) 2006 Jan Schmidt <thaytan@noraisin.net> + * + * gstquark.c: Registered quarks for the _priv_gst_quark_table, private to + * GStreamer + * + * 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. + */ + +#include "gst_private.h" +#include "gstquark.h" + +/* These strings must match order and number declared in the GstQuarkId + * enum in gstquark.h! */ +static const gchar *_quark_strings[] = { + "format", "current", "duration", "rate", + "seekable", "segment-start", "segment-end", + "src_format", "src_value", "dest_format", "dest_value", + "start_format", "start_value", "stop_format", "stop_value", + "gerror", "debug", "buffer-percent", "buffering-mode", + "avg-in-rate", "avg-out-rate", "buffering-left", + "estimated-total", "old-state", "new-state", "pending-state", + "clock", "ready", "position", "reset-time", "live", "min-latency", + "max-latency", "busy", "type", "owner", "update", "applied-rate", + "start", "stop", "minsize", "maxsize", "async", "proportion", + "diff", "timestamp", "flags", "cur-type", "cur", "stop-type", + "latency", "uri", "object", "taglist", "GstEventSegment", + "GstEventBufferSize", "GstEventQOS", "GstEventSeek", "GstEventLatency", + "GstMessageError", "GstMessageWarning", "GstMessageInfo", + "GstMessageBuffering", "GstMessageState", "GstMessageClockProvide", + "GstMessageClockLost", "GstMessageNewClock", "GstMessageStructureChange", + "GstMessageSegmentStart", "GstMessageSegmentDone", "GstMessageDuration", + "GstMessageAsyncDone", "GstMessageRequestState", "GstMessageStreamStatus", + "GstQueryPosition", "GstQueryDuration", "GstQueryLatency", "GstQueryConvert", + "GstQuerySegment", "GstQuerySeeking", "GstQueryFormats", "GstQueryBuffering", + "GstQueryURI", "GstEventStep", "GstMessageStepDone", "amount", "flush", + "intermediate", "GstMessageStepStart", "active", "eos", "sink-message", + "message", "GstMessageQOS", "running-time", "stream-time", "jitter", + "quality", "processed", "dropped", "buffering-ranges", "GstMessageProgress", + "code", "text", "percent", "timeout", "GstBufferPoolConfig", "caps", "size", + "min-buffers", "max-buffers", "prefix", "postfix", "align", "time", + "GstQueryAllocation", "need-pool", "meta", "pool", "GstEventCaps", + "GstEventReconfigure", "segment", "GstQueryScheduling", "pull-mode", + "random-access", "sequential", "allocator", "GstEventFlushStop", "options" +}; + +GQuark _priv_gst_quark_table[GST_QUARK_MAX]; + +void +_priv_gst_quarks_initialize (void) +{ + gint i; + + if (G_N_ELEMENTS (_quark_strings) != GST_QUARK_MAX) + g_warning ("the quark table is not consistent! %d != %d", + (int) G_N_ELEMENTS (_quark_strings), GST_QUARK_MAX); + + for (i = 0; i < GST_QUARK_MAX; i++) { + _priv_gst_quark_table[i] = g_quark_from_static_string (_quark_strings[i]); + } +} diff --git a/gst/gstquark.h b/gst/gstquark.h new file mode 100644 index 0000000..06eb709 --- /dev/null +++ b/gst/gstquark.h @@ -0,0 +1,166 @@ +/* GStreamer + * Copyright (C) 2006 Jan Schmidt <thaytan@noraisin.net> + * + * gstquark.h: Private header for storing quark info + * + * 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_QUARK_H__ +#define __GST_QUARK_H__ + +/* These enums need to match the number and order + * of strings declared in _quark_table, in gstquark.c */ +typedef enum _GstQuarkId +{ + GST_QUARK_FORMAT = 0, + GST_QUARK_CURRENT = 1, + GST_QUARK_DURATION = 2, + GST_QUARK_RATE = 3, + GST_QUARK_SEEKABLE = 4, + GST_QUARK_SEGMENT_START = 5, + GST_QUARK_SEGMENT_END = 6, + GST_QUARK_SRC_FORMAT = 7, + GST_QUARK_SRC_VALUE = 8, + GST_QUARK_DEST_FORMAT = 9, + GST_QUARK_DEST_VALUE = 10, + GST_QUARK_START_FORMAT = 11, + GST_QUARK_START_VALUE = 12, + GST_QUARK_STOP_FORMAT = 13, + GST_QUARK_STOP_VALUE = 14, + GST_QUARK_GERROR = 15, + GST_QUARK_DEBUG = 16, + GST_QUARK_BUFFER_PERCENT = 17, + GST_QUARK_BUFFERING_MODE = 18, + GST_QUARK_AVG_IN_RATE = 19, + GST_QUARK_AVG_OUT_RATE = 20, + GST_QUARK_BUFFERING_LEFT = 21, + GST_QUARK_ESTIMATED_TOTAL = 22, + GST_QUARK_OLD_STATE = 23, + GST_QUARK_NEW_STATE = 24, + GST_QUARK_PENDING_STATE = 25, + GST_QUARK_CLOCK = 26, + GST_QUARK_READY = 27, + GST_QUARK_POSITION = 28, + GST_QUARK_RESET_TIME = 29, + GST_QUARK_LIVE = 30, + GST_QUARK_MIN_LATENCY = 31, + GST_QUARK_MAX_LATENCY = 32, + GST_QUARK_BUSY = 33, + GST_QUARK_TYPE = 34, + GST_QUARK_OWNER = 35, + GST_QUARK_UPDATE = 36, + GST_QUARK_APPLIED_RATE = 37, + GST_QUARK_START = 38, + GST_QUARK_STOP = 39, + GST_QUARK_MINSIZE = 40, + GST_QUARK_MAXSIZE = 41, + GST_QUARK_ASYNC = 42, + GST_QUARK_PROPORTION = 43, + GST_QUARK_DIFF = 44, + GST_QUARK_TIMESTAMP = 45, + GST_QUARK_FLAGS = 46, + GST_QUARK_CUR_TYPE = 47, + GST_QUARK_CUR = 48, + GST_QUARK_STOP_TYPE = 49, + GST_QUARK_LATENCY = 50, + GST_QUARK_URI = 51, + GST_QUARK_OBJECT = 52, + GST_QUARK_TAGLIST = 53, + GST_QUARK_EVENT_SEGMENT = 54, + GST_QUARK_EVENT_BUFFER_SIZE = 55, + GST_QUARK_EVENT_QOS = 56, + GST_QUARK_EVENT_SEEK = 57, + GST_QUARK_EVENT_LATENCY = 58, + GST_QUARK_MESSAGE_ERROR = 59, + GST_QUARK_MESSAGE_WARNING = 60, + GST_QUARK_MESSAGE_INFO = 61, + GST_QUARK_MESSAGE_BUFFERING = 62, + GST_QUARK_MESSAGE_STATE = 63, + GST_QUARK_MESSAGE_CLOCK_PROVIDE = 64, + GST_QUARK_MESSAGE_CLOCK_LOST = 65, + GST_QUARK_MESSAGE_NEW_CLOCK = 66, + GST_QUARK_MESSAGE_STRUCTURE_CHANGE = 67, + GST_QUARK_MESSAGE_SEGMENT_START = 68, + GST_QUARK_MESSAGE_SEGMENT_DONE = 69, + GST_QUARK_MESSAGE_DURATION = 70, + GST_QUARK_MESSAGE_ASYNC_DONE = 71, + GST_QUARK_MESSAGE_REQUEST_STATE = 72, + GST_QUARK_MESSAGE_STREAM_STATUS = 73, + GST_QUARK_QUERY_POSITION = 74, + GST_QUARK_QUERY_DURATION = 75, + GST_QUARK_QUERY_LATENCY = 76, + GST_QUARK_QUERY_CONVERT = 77, + GST_QUARK_QUERY_SEGMENT = 78, + GST_QUARK_QUERY_SEEKING = 79, + GST_QUARK_QUERY_FORMATS = 80, + GST_QUARK_QUERY_BUFFERING = 81, + GST_QUARK_QUERY_URI = 82, + GST_QUARK_EVENT_STEP = 83, + GST_QUARK_MESSAGE_STEP_DONE = 84, + GST_QUARK_AMOUNT = 85, + GST_QUARK_FLUSH = 86, + GST_QUARK_INTERMEDIATE = 87, + GST_QUARK_MESSAGE_STEP_START = 88, + GST_QUARK_ACTIVE = 89, + GST_QUARK_EOS = 90, + GST_QUARK_EVENT_SINK_MESSAGE = 91, + GST_QUARK_MESSAGE = 92, + GST_QUARK_MESSAGE_QOS = 93, + GST_QUARK_RUNNING_TIME = 94, + GST_QUARK_STREAM_TIME = 95, + GST_QUARK_JITTER = 96, + GST_QUARK_QUALITY = 97, + GST_QUARK_PROCESSED = 98, + GST_QUARK_DROPPED = 99, + GST_QUARK_BUFFERING_RANGES = 100, + GST_QUARK_MESSAGE_PROGRESS = 101, + GST_QUARK_CODE = 102, + GST_QUARK_TEXT = 103, + GST_QUARK_PERCENT = 104, + GST_QUARK_TIMEOUT = 105, + GST_QUARK_BUFFER_POOL_CONFIG = 106, + GST_QUARK_CAPS = 107, + GST_QUARK_SIZE = 108, + GST_QUARK_MIN_BUFFERS = 109, + GST_QUARK_MAX_BUFFERS = 110, + GST_QUARK_PREFIX = 111, + GST_QUARK_POSTFIX = 112, + GST_QUARK_ALIGN = 113, + GST_QUARK_TIME = 114, + GST_QUARK_QUERY_ALLOCATION = 115, + GST_QUARK_NEED_POOL = 116, + GST_QUARK_META = 117, + GST_QUARK_POOL = 118, + GST_QUARK_EVENT_CAPS = 119, + GST_QUARK_EVENT_RECONFIGURE = 120, + GST_QUARK_SEGMENT = 121, + GST_QUARK_QUERY_SCHEDULING = 122, + GST_QUARK_PULL_MODE = 123, + GST_QUARK_RANDOM_ACCESS = 124, + GST_QUARK_SEQUENTIAL = 125, + GST_QUARK_ALLOCATOR = 126, + GST_QUARK_EVENT_FLUSH_STOP = 127, + GST_QUARK_OPTIONS = 128, + + GST_QUARK_MAX = 129 +} GstQuarkId; + +extern GQuark _priv_gst_quark_table[GST_QUARK_MAX]; + +#define GST_QUARK(q) _priv_gst_quark_table[GST_QUARK_##q] + +#endif diff --git a/gst/gstquery.c b/gst/gstquery.c new file mode 100644 index 0000000..eb28019 --- /dev/null +++ b/gst/gstquery.c @@ -0,0 +1,2004 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstquery.c: GstQueryType registration and Query parsing/creation + * + * 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:gstquery + * @short_description: Dynamically register new query types. Provide functions + * to create queries, and to set and parse values in them. + * @see_also: #GstPad, #GstElement + * + * GstQuery functions are used to register new query types to the gstreamer + * core and use them. + * Queries can be performed on pads (gst_pad_query()) and elements + * (gst_element_query()). Please note that some queries might need a running + * pipeline to work. + * + * Queries can be created using the gst_query_new_*() functions. + * Query values can be set using gst_query_set_*(), and parsed using + * gst_query_parse_*() helpers. + * + * The following example shows how to query the duration of a pipeline: + * + * <example> + * <title>Query duration on a pipeline</title> + * <programlisting> + * GstQuery *query; + * gboolean res; + * query = gst_query_new_duration (GST_FORMAT_TIME); + * res = gst_element_query (pipeline, query); + * if (res) { + * gint64 duration; + * gst_query_parse_duration (query, NULL, &duration); + * g_print ("duration = %"GST_TIME_FORMAT, GST_TIME_ARGS (duration)); + * } + * else { + * g_print ("duration query failed..."); + * } + * gst_query_unref (query); + * </programlisting> + * </example> + * + * Last reviewed on 2006-02-14 (0.10.4) + */ + +#include "gst_private.h" +#include "gstinfo.h" +#include "gstquery.h" +#include "gstvalue.h" +#include "gstenumtypes.h" +#include "gstquark.h" +#include "gsturi.h" +#include "gstbufferpool.h" + +GST_DEBUG_CATEGORY_STATIC (gst_query_debug); +#define GST_CAT_DEFAULT gst_query_debug + +static GType _gst_query_type = 0; + +typedef struct +{ + GstQuery query; + + GstStructure *structure; +} GstQueryImpl; + +#define GST_QUERY_STRUCTURE(q) (((GstQueryImpl *)(q))->structure) + +static GStaticMutex mutex = G_STATIC_MUTEX_INIT; +static GList *_gst_queries = NULL; +static GHashTable *_nick_to_query = NULL; +static GHashTable *_query_type_to_nick = NULL; +static guint32 _n_values = 1; /* we start from 1 because 0 reserved for NONE */ + +static GstQueryTypeDefinition standard_definitions[] = { + {GST_QUERY_POSITION, "position", "Current position", 0}, + {GST_QUERY_DURATION, "duration", "Total duration", 0}, + {GST_QUERY_LATENCY, "latency", "Latency", 0}, + {GST_QUERY_JITTER, "jitter", "Jitter", 0}, + {GST_QUERY_RATE, "rate", "Configured rate 1000000 = 1", 0}, + {GST_QUERY_SEEKING, "seeking", "Seeking capabilities and parameters", 0}, + {GST_QUERY_SEGMENT, "segment", "currently configured segment", 0}, + {GST_QUERY_CONVERT, "convert", "Converting between formats", 0}, + {GST_QUERY_FORMATS, "formats", "Supported formats for conversion", 0}, + {GST_QUERY_BUFFERING, "buffering", "Buffering status", 0}, + {GST_QUERY_CUSTOM, "custom", "Custom query", 0}, + {GST_QUERY_URI, "uri", "URI of the source or sink", 0}, + {GST_QUERY_ALLOCATION, "allocation", "Allocation properties", 0}, + {GST_QUERY_SCHEDULING, "scheduling", "Scheduling properties", 0}, + {GST_QUERY_NONE, NULL, NULL, 0} +}; + +GST_DEFINE_MINI_OBJECT_TYPE (GstQuery, gst_query); + +void +_priv_gst_query_initialize (void) +{ + GstQueryTypeDefinition *standards = standard_definitions; + + GST_CAT_INFO (GST_CAT_GST_INIT, "init queries"); + + GST_DEBUG_CATEGORY_INIT (gst_query_debug, "query", 0, "query system"); + + g_static_mutex_lock (&mutex); + if (_nick_to_query == NULL) { + _nick_to_query = g_hash_table_new (g_str_hash, g_str_equal); + _query_type_to_nick = g_hash_table_new (NULL, NULL); + } + + while (standards->nick) { + standards->quark = g_quark_from_static_string (standards->nick); + g_hash_table_insert (_nick_to_query, (gpointer) standards->nick, standards); + g_hash_table_insert (_query_type_to_nick, + GINT_TO_POINTER (standards->value), standards); + + _gst_queries = g_list_append (_gst_queries, standards); + standards++; + _n_values++; + } + g_static_mutex_unlock (&mutex); + + _gst_query_type = gst_query_get_type (); +} + +/** + * gst_query_type_get_name: + * @query: the query type + * + * Get a printable name for the given query type. Do not modify or free. + * + * Returns: a reference to the static name of the query. + */ +const gchar * +gst_query_type_get_name (GstQueryType query) +{ + const GstQueryTypeDefinition *def; + + def = gst_query_type_get_details (query); + g_return_val_if_fail (def != NULL, NULL); + + return def->nick; +} + +/** + * gst_query_type_to_quark: + * @query: the query type + * + * Get the unique quark for the given query type. + * + * Returns: the quark associated with the query type + */ +GQuark +gst_query_type_to_quark (GstQueryType query) +{ + const GstQueryTypeDefinition *def; + + def = gst_query_type_get_details (query); + g_return_val_if_fail (def != NULL, 0); + + return def->quark; +} + +/** + * gst_query_type_register: + * @nick: The nick of the new query + * @description: The description of the new query + * + * Create a new GstQueryType based on the nick or return an + * already registered query with that nick + * + * Returns: A new GstQueryType or an already registered query + * with the same nick. + */ +GstQueryType +gst_query_type_register (const gchar * nick, const gchar * description) +{ + GstQueryTypeDefinition *query; + GstQueryType lookup; + + g_return_val_if_fail (nick != NULL, GST_QUERY_NONE); + g_return_val_if_fail (description != NULL, GST_QUERY_NONE); + + lookup = gst_query_type_get_by_nick (nick); + if (lookup != GST_QUERY_NONE) + return lookup; + + query = g_slice_new (GstQueryTypeDefinition); + query->value = (GstQueryType) _n_values; + query->nick = g_strdup (nick); + query->description = g_strdup (description); + query->quark = g_quark_from_static_string (query->nick); + + g_static_mutex_lock (&mutex); + g_hash_table_insert (_nick_to_query, (gpointer) query->nick, query); + g_hash_table_insert (_query_type_to_nick, GINT_TO_POINTER (query->value), + query); + _gst_queries = g_list_append (_gst_queries, query); + _n_values++; + g_static_mutex_unlock (&mutex); + + return query->value; +} + +/** + * gst_query_type_get_by_nick: + * @nick: The nick of the query + * + * Get the query type registered with @nick. + * + * Returns: The query registered with @nick or #GST_QUERY_NONE + * if the query was not registered. + */ +GstQueryType +gst_query_type_get_by_nick (const gchar * nick) +{ + GstQueryTypeDefinition *query; + + g_return_val_if_fail (nick != NULL, GST_QUERY_NONE); + + g_static_mutex_lock (&mutex); + query = g_hash_table_lookup (_nick_to_query, nick); + g_static_mutex_unlock (&mutex); + + if (query != NULL) + return query->value; + else + return GST_QUERY_NONE; +} + +/** + * gst_query_types_contains: + * @types: The query array to search + * @type: the #GstQueryType to find + * + * See if the given #GstQueryType is inside the @types query types array. + * + * Returns: TRUE if the type is found inside the array + */ +gboolean +gst_query_types_contains (const GstQueryType * types, GstQueryType type) +{ + if (!types) + return FALSE; + + while (*types) { + if (*types == type) + return TRUE; + + types++; + } + return FALSE; +} + + +/** + * gst_query_type_get_details: + * @type: a #GstQueryType + * + * Get details about the given #GstQueryType. + * + * Returns: The #GstQueryTypeDefinition for @type or NULL on failure. + */ +const GstQueryTypeDefinition * +gst_query_type_get_details (GstQueryType type) +{ + const GstQueryTypeDefinition *result; + + g_static_mutex_lock (&mutex); + result = g_hash_table_lookup (_query_type_to_nick, GINT_TO_POINTER (type)); + g_static_mutex_unlock (&mutex); + + return result; +} + +/** + * gst_query_type_iterate_definitions: + * + * Get a #GstIterator of all the registered query types. The definitions + * iterated over are read only. + * + * Free-function: gst_iterator_free + * + * Returns: (transfer full): a #GstIterator of #GstQueryTypeDefinition. + */ +GstIterator * +gst_query_type_iterate_definitions (void) +{ + GstIterator *result; + + g_static_mutex_lock (&mutex); + /* FIXME: register a boxed type for GstQueryTypeDefinition */ + result = gst_iterator_new_list (G_TYPE_POINTER, + g_static_mutex_get_mutex (&mutex), &_n_values, &_gst_queries, NULL, NULL); + g_static_mutex_unlock (&mutex); + + return result; +} + +static void +_gst_query_free (GstQuery * query) +{ + GstStructure *s; + + g_return_if_fail (query != NULL); + + s = GST_QUERY_STRUCTURE (query); + if (s) { + gst_structure_set_parent_refcount (s, NULL); + gst_structure_free (s); + } + + g_slice_free1 (GST_MINI_OBJECT_SIZE (query), query); +} + +static GstQuery *gst_query_new (GstQueryType type, GstStructure * structure); + +static GstQuery * +_gst_query_copy (GstQuery * query) +{ + GstQuery *copy; + + copy = gst_query_new (query->type, GST_QUERY_STRUCTURE (query)); + + return copy; +} + +static GstQuery * +gst_query_new (GstQueryType type, GstStructure * structure) +{ + GstQueryImpl *query; + + query = g_slice_new0 (GstQueryImpl); + + gst_mini_object_init (GST_MINI_OBJECT_CAST (query), + _gst_query_type, sizeof (GstQueryImpl)); + + query->query.mini_object.copy = (GstMiniObjectCopyFunction) _gst_query_copy; + query->query.mini_object.free = (GstMiniObjectFreeFunction) _gst_query_free; + + GST_DEBUG ("creating new query %p %d", query, type); + + GST_QUERY_TYPE (query) = type; + query->structure = structure; + + if (structure) + gst_structure_set_parent_refcount (structure, + &query->query.mini_object.refcount); + + return GST_QUERY_CAST (query); +} + +/** + * gst_query_new_position: + * @format: the default #GstFormat for the new query + * + * Constructs a new query stream position query object. Use gst_query_unref() + * when done with it. A position query is used to query the current position + * of playback in the streams, in some format. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + */ +GstQuery * +gst_query_new_position (GstFormat format) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (QUERY_POSITION), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (CURRENT), G_TYPE_INT64, G_GINT64_CONSTANT (-1), NULL); + + query = gst_query_new (GST_QUERY_POSITION, structure); + + return query; +} + +/** + * gst_query_set_position: + * @query: a #GstQuery with query type GST_QUERY_POSITION + * @format: the requested #GstFormat + * @cur: the position to set + * + * Answer a position query by setting the requested value in the given format. + */ +void +gst_query_set_position (GstQuery * query, GstFormat format, gint64 cur) +{ + GstStructure *s; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_POSITION); + + s = GST_QUERY_STRUCTURE (query); + g_return_if_fail (format == g_value_get_enum (gst_structure_id_get_value (s, + GST_QUARK (FORMAT)))); + + gst_structure_id_set (s, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (CURRENT), G_TYPE_INT64, cur, NULL); +} + +/** + * gst_query_parse_position: + * @query: a #GstQuery + * @format: (out) (allow-none): the storage for the #GstFormat of the + * position values (may be NULL) + * @cur: (out) (allow-none): the storage for the current position (may be NULL) + * + * Parse a position query, writing the format into @format, and the position + * into @cur, if the respective parameters are non-NULL. + */ +void +gst_query_parse_position (GstQuery * query, GstFormat * format, gint64 * cur) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_POSITION); + + structure = GST_QUERY_STRUCTURE (query); + if (format) + *format = + (GstFormat) g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (cur) + *cur = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (CURRENT))); +} + + +/** + * gst_query_new_duration: + * @format: the #GstFormat for this duration query + * + * Constructs a new stream duration query object to query in the given format. + * Use gst_query_unref() when done with it. A duration query will give the + * total length of the stream. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + */ +GstQuery * +gst_query_new_duration (GstFormat format) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (QUERY_DURATION), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (DURATION), G_TYPE_INT64, G_GINT64_CONSTANT (-1), NULL); + + query = gst_query_new (GST_QUERY_DURATION, structure); + + return query; +} + +/** + * gst_query_set_duration: + * @query: a #GstQuery + * @format: the #GstFormat for the duration + * @duration: the duration of the stream + * + * Answer a duration query by setting the requested value in the given format. + */ +void +gst_query_set_duration (GstQuery * query, GstFormat format, gint64 duration) +{ + GstStructure *s; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_DURATION); + + s = GST_QUERY_STRUCTURE (query); + g_return_if_fail (format == g_value_get_enum (gst_structure_id_get_value (s, + GST_QUARK (FORMAT)))); + gst_structure_id_set (s, GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (DURATION), G_TYPE_INT64, duration, NULL); +} + +/** + * gst_query_parse_duration: + * @query: a #GstQuery + * @format: (out) (allow-none): the storage for the #GstFormat of the duration + * value, or NULL. + * @duration: (out) (allow-none): the storage for the total duration, or NULL. + * + * Parse a duration query answer. Write the format of the duration into @format, + * and the value into @duration, if the respective variables are non-NULL. + */ +void +gst_query_parse_duration (GstQuery * query, GstFormat * format, + gint64 * duration) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_DURATION); + + structure = GST_QUERY_STRUCTURE (query); + if (format) + *format = + (GstFormat) g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (duration) + *duration = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (DURATION))); +} + +/** + * gst_query_new_latency: + * + * Constructs a new latency query object. + * Use gst_query_unref() when done with it. A latency query is usually performed + * by sinks to compensate for additional latency introduced by elements in the + * pipeline. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a #GstQuery + * + * Since: 0.10.12 + */ +GstQuery * +gst_query_new_latency (void) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (QUERY_LATENCY), + GST_QUARK (LIVE), G_TYPE_BOOLEAN, FALSE, + GST_QUARK (MIN_LATENCY), G_TYPE_UINT64, G_GUINT64_CONSTANT (0), + GST_QUARK (MAX_LATENCY), G_TYPE_UINT64, G_GUINT64_CONSTANT (-1), NULL); + + query = gst_query_new (GST_QUERY_LATENCY, structure); + + return query; +} + +/** + * gst_query_set_latency: + * @query: a #GstQuery + * @live: if there is a live element upstream + * @min_latency: the minimal latency of the live element + * @max_latency: the maximal latency of the live element + * + * Answer a latency query by setting the requested values in the given format. + * + * Since: 0.10.12 + */ +void +gst_query_set_latency (GstQuery * query, gboolean live, + GstClockTime min_latency, GstClockTime max_latency) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_LATENCY); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, + GST_QUARK (LIVE), G_TYPE_BOOLEAN, live, + GST_QUARK (MIN_LATENCY), G_TYPE_UINT64, min_latency, + GST_QUARK (MAX_LATENCY), G_TYPE_UINT64, max_latency, NULL); +} + +/** + * gst_query_parse_latency: + * @query: a #GstQuery + * @live: (out) (allow-none): storage for live or NULL + * @min_latency: (out) (allow-none): the storage for the min latency or NULL + * @max_latency: (out) (allow-none): the storage for the max latency or NULL + * + * Parse a latency query answer. + * + * Since: 0.10.12 + */ +void +gst_query_parse_latency (GstQuery * query, gboolean * live, + GstClockTime * min_latency, GstClockTime * max_latency) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_LATENCY); + + structure = GST_QUERY_STRUCTURE (query); + if (live) + *live = + g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (LIVE))); + if (min_latency) + *min_latency = g_value_get_uint64 (gst_structure_id_get_value (structure, + GST_QUARK (MIN_LATENCY))); + if (max_latency) + *max_latency = g_value_get_uint64 (gst_structure_id_get_value (structure, + GST_QUARK (MAX_LATENCY))); +} + +/** + * gst_query_new_convert: + * @src_format: the source #GstFormat for the new query + * @value: the value to convert + * @dest_format: the target #GstFormat + * + * Constructs a new convert query object. Use gst_query_unref() + * when done with it. A convert query is used to ask for a conversion between + * one format and another. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a #GstQuery + */ +GstQuery * +gst_query_new_convert (GstFormat src_format, gint64 value, + GstFormat dest_format) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (QUERY_CONVERT), + GST_QUARK (SRC_FORMAT), GST_TYPE_FORMAT, src_format, + GST_QUARK (SRC_VALUE), G_TYPE_INT64, value, + GST_QUARK (DEST_FORMAT), GST_TYPE_FORMAT, dest_format, + GST_QUARK (DEST_VALUE), G_TYPE_INT64, G_GINT64_CONSTANT (-1), NULL); + + query = gst_query_new (GST_QUERY_CONVERT, structure); + + return query; +} + +/** + * gst_query_set_convert: + * @query: a #GstQuery + * @src_format: the source #GstFormat + * @src_value: the source value + * @dest_format: the destination #GstFormat + * @dest_value: the destination value + * + * Answer a convert query by setting the requested values. + */ +void +gst_query_set_convert (GstQuery * query, GstFormat src_format, gint64 src_value, + GstFormat dest_format, gint64 dest_value) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_CONVERT); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, + GST_QUARK (SRC_FORMAT), GST_TYPE_FORMAT, src_format, + GST_QUARK (SRC_VALUE), G_TYPE_INT64, src_value, + GST_QUARK (DEST_FORMAT), GST_TYPE_FORMAT, dest_format, + GST_QUARK (DEST_VALUE), G_TYPE_INT64, dest_value, NULL); +} + +/** + * gst_query_parse_convert: + * @query: a #GstQuery + * @src_format: (out) (allow-none): the storage for the #GstFormat of the + * source value, or NULL + * @src_value: (out) (allow-none): the storage for the source value, or NULL + * @dest_format: (out) (allow-none): the storage for the #GstFormat of the + * destination value, or NULL + * @dest_value: (out) (allow-none): the storage for the destination value, + * or NULL + * + * Parse a convert query answer. Any of @src_format, @src_value, @dest_format, + * and @dest_value may be NULL, in which case that value is omitted. + */ +void +gst_query_parse_convert (GstQuery * query, GstFormat * src_format, + gint64 * src_value, GstFormat * dest_format, gint64 * dest_value) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_CONVERT); + + structure = GST_QUERY_STRUCTURE (query); + if (src_format) + *src_format = + (GstFormat) g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (SRC_FORMAT))); + if (src_value) + *src_value = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (SRC_VALUE))); + if (dest_format) + *dest_format = + (GstFormat) g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (DEST_FORMAT))); + if (dest_value) + *dest_value = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (DEST_VALUE))); +} + +/** + * gst_query_new_segment: + * @format: the #GstFormat for the new query + * + * Constructs a new segment query object. Use gst_query_unref() + * when done with it. A segment query is used to discover information about the + * currently configured segment for playback. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + */ +GstQuery * +gst_query_new_segment (GstFormat format) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (QUERY_SEGMENT), + GST_QUARK (RATE), G_TYPE_DOUBLE, (gdouble) 0.0, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (START_VALUE), G_TYPE_INT64, G_GINT64_CONSTANT (-1), + GST_QUARK (STOP_VALUE), G_TYPE_INT64, G_GINT64_CONSTANT (-1), NULL); + + query = gst_query_new (GST_QUERY_SEGMENT, structure); + + return query; +} + +/** + * gst_query_set_segment: + * @query: a #GstQuery + * @rate: the rate of the segment + * @format: the #GstFormat of the segment values (@start_value and @stop_value) + * @start_value: the start value + * @stop_value: the stop value + * + * Answer a segment query by setting the requested values. The normal + * playback segment of a pipeline is 0 to duration at the default rate of + * 1.0. If a seek was performed on the pipeline to play a different + * segment, this query will return the range specified in the last seek. + * + * @start_value and @stop_value will respectively contain the configured + * playback range start and stop values expressed in @format. + * The values are always between 0 and the duration of the media and + * @start_value <= @stop_value. @rate will contain the playback rate. For + * negative rates, playback will actually happen from @stop_value to + * @start_value. + */ +void +gst_query_set_segment (GstQuery * query, gdouble rate, GstFormat format, + gint64 start_value, gint64 stop_value) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_SEGMENT); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, + GST_QUARK (RATE), G_TYPE_DOUBLE, rate, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (START_VALUE), G_TYPE_INT64, start_value, + GST_QUARK (STOP_VALUE), G_TYPE_INT64, stop_value, NULL); +} + +/** + * gst_query_parse_segment: + * @query: a #GstQuery + * @rate: (out) (allow-none): the storage for the rate of the segment, or NULL + * @format: (out) (allow-none): the storage for the #GstFormat of the values, + * or NULL + * @start_value: (out) (allow-none): the storage for the start value, or NULL + * @stop_value: (out) (allow-none): the storage for the stop value, or NULL + * + * Parse a segment query answer. Any of @rate, @format, @start_value, and + * @stop_value may be NULL, which will cause this value to be omitted. + * + * See gst_query_set_segment() for an explanation of the function arguments. + */ +void +gst_query_parse_segment (GstQuery * query, gdouble * rate, GstFormat * format, + gint64 * start_value, gint64 * stop_value) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_SEGMENT); + + structure = GST_QUERY_STRUCTURE (query); + if (rate) + *rate = g_value_get_double (gst_structure_id_get_value (structure, + GST_QUARK (RATE))); + if (format) + *format = + (GstFormat) g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (start_value) + *start_value = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (START_VALUE))); + if (stop_value) + *stop_value = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (STOP_VALUE))); +} + +/** + * gst_query_new_custom: + * @type: the query type + * @structure: a structure for the query + * + * Constructs a new custom query object. Use gst_query_unref() + * when done with it. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + */ +GstQuery * +gst_query_new_custom (GstQueryType type, GstStructure * structure) +{ + g_return_val_if_fail (gst_query_type_get_details (type) != NULL, NULL); + g_return_val_if_fail (structure != NULL, NULL); + + return gst_query_new (type, structure); +} + +/** + * gst_query_get_structure: + * @query: a #GstQuery + * + * Get the structure of a query. + * + * Returns: (transfer none): the #GstStructure of the query. The structure is + * still owned by the query and will therefore be freed when the query + * is unreffed. + */ +const GstStructure * +gst_query_get_structure (GstQuery * query) +{ + g_return_val_if_fail (GST_IS_QUERY (query), NULL); + + return GST_QUERY_STRUCTURE (query); +} + +/** + * gst_query_writable_structure: + * @query: a #GstQuery + * + * Get the structure of a query. + * + * Returns: (transfer none): the #GstStructure of the query. The structure is + * still owned by the query and will therefore be freed when the query + * is unreffed. + */ +GstStructure * +gst_query_writable_structure (GstQuery * query) +{ + g_return_val_if_fail (GST_IS_QUERY (query), NULL); + g_return_val_if_fail (gst_query_is_writable (query), NULL); + + return GST_QUERY_STRUCTURE (query); +} + +/** + * gst_query_new_seeking: + * @format: the default #GstFormat for the new query + * + * Constructs a new query object for querying seeking properties of + * the stream. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + */ +GstQuery * +gst_query_new_seeking (GstFormat format) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (QUERY_SEEKING), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (SEEKABLE), G_TYPE_BOOLEAN, FALSE, + GST_QUARK (SEGMENT_START), G_TYPE_INT64, G_GINT64_CONSTANT (-1), + GST_QUARK (SEGMENT_END), G_TYPE_INT64, G_GINT64_CONSTANT (-1), NULL); + + query = gst_query_new (GST_QUERY_SEEKING, structure); + + return query; +} + +/** + * gst_query_set_seeking: + * @query: a #GstQuery + * @format: the format to set for the @segment_start and @segment_end values + * @seekable: the seekable flag to set + * @segment_start: the segment_start to set + * @segment_end: the segment_end to set + * + * Set the seeking query result fields in @query. + */ +void +gst_query_set_seeking (GstQuery * query, GstFormat format, + gboolean seekable, gint64 segment_start, gint64 segment_end) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_SEEKING); + g_return_if_fail (gst_query_is_writable (query)); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (SEEKABLE), G_TYPE_BOOLEAN, seekable, + GST_QUARK (SEGMENT_START), G_TYPE_INT64, segment_start, + GST_QUARK (SEGMENT_END), G_TYPE_INT64, segment_end, NULL); +} + +/** + * gst_query_parse_seeking: + * @query: a GST_QUERY_SEEKING type query #GstQuery + * @format: (out) (allow-none): the format to set for the @segment_start + * and @segment_end values, or NULL + * @seekable: (out) (allow-none): the seekable flag to set, or NULL + * @segment_start: (out) (allow-none): the segment_start to set, or NULL + * @segment_end: (out) (allow-none): the segment_end to set, or NULL + * + * Parse a seeking query, writing the format into @format, and + * other results into the passed parameters, if the respective parameters + * are non-NULL + */ +void +gst_query_parse_seeking (GstQuery * query, GstFormat * format, + gboolean * seekable, gint64 * segment_start, gint64 * segment_end) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_SEEKING); + + structure = GST_QUERY_STRUCTURE (query); + if (format) + *format = + (GstFormat) g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (seekable) + *seekable = g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (SEEKABLE))); + if (segment_start) + *segment_start = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (SEGMENT_START))); + if (segment_end) + *segment_end = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (SEGMENT_END))); +} + +/** + * gst_query_new_formats: + * + * Constructs a new query object for querying formats of + * the stream. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + * + * Since: 0.10.4 + */ +GstQuery * +gst_query_new_formats (void) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_empty_new (GST_QUARK (QUERY_FORMATS)); + query = gst_query_new (GST_QUERY_FORMATS, structure); + + return query; +} + +static void +gst_query_list_add_format (GValue * list, GstFormat format) +{ + GValue item = { 0, }; + + g_value_init (&item, GST_TYPE_FORMAT); + g_value_set_enum (&item, format); + gst_value_list_append_value (list, &item); + g_value_unset (&item); +} + +/** + * gst_query_set_formats: + * @query: a #GstQuery + * @n_formats: the number of formats to set. + * @...: A number of @GstFormats equal to @n_formats. + * + * Set the formats query result fields in @query. The number of formats passed + * must be equal to @n_formats. + */ +void +gst_query_set_formats (GstQuery * query, gint n_formats, ...) +{ + va_list ap; + GValue list = { 0, }; + gint i; + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_FORMATS); + g_return_if_fail (gst_query_is_writable (query)); + + g_value_init (&list, GST_TYPE_LIST); + + va_start (ap, n_formats); + for (i = 0; i < n_formats; i++) { + gst_query_list_add_format (&list, va_arg (ap, GstFormat)); + } + va_end (ap); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_set_value (structure, "formats", &list); + + g_value_unset (&list); + +} + +/** + * gst_query_set_formatsv: + * @query: a #GstQuery + * @n_formats: the number of formats to set. + * @formats: (in) (array length=n_formats): an array containing @n_formats + * @GstFormat values. + * + * Set the formats query result fields in @query. The number of formats passed + * in the @formats array must be equal to @n_formats. + * + * Since: 0.10.4 + */ +void +gst_query_set_formatsv (GstQuery * query, gint n_formats, + const GstFormat * formats) +{ + GValue list = { 0, }; + gint i; + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_FORMATS); + g_return_if_fail (gst_query_is_writable (query)); + + g_value_init (&list, GST_TYPE_LIST); + for (i = 0; i < n_formats; i++) { + gst_query_list_add_format (&list, formats[i]); + } + structure = GST_QUERY_STRUCTURE (query); + gst_structure_set_value (structure, "formats", &list); + + g_value_unset (&list); +} + +/** + * gst_query_parse_n_formats: + * @query: a #GstQuery + * @n_formats: (out): the number of formats in this query. + * + * Parse the number of formats in the formats @query. + * + * Since: 0.10.4 + */ +void +gst_query_parse_n_formats (GstQuery * query, guint * n_formats) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_FORMATS); + + if (n_formats) { + const GValue *list; + + structure = GST_QUERY_STRUCTURE (query); + list = gst_structure_get_value (structure, "formats"); + if (list == NULL) + *n_formats = 0; + else + *n_formats = gst_value_list_get_size (list); + } +} + +/** + * gst_query_parse_nth_format: + * @query: a #GstQuery + * @nth: (out): the nth format to retrieve. + * @format: (out): a pointer to store the nth format + * + * Parse the format query and retrieve the @nth format from it into + * @format. If the list contains less elements than @nth, @format will be + * set to GST_FORMAT_UNDEFINED. + */ +void +gst_query_parse_nth_format (GstQuery * query, guint nth, GstFormat * format) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_FORMATS); + + if (format) { + const GValue *list; + + structure = GST_QUERY_STRUCTURE (query); + list = gst_structure_get_value (structure, "formats"); + if (list == NULL) { + *format = GST_FORMAT_UNDEFINED; + } else { + if (nth < gst_value_list_get_size (list)) { + *format = + (GstFormat) g_value_get_enum (gst_value_list_get_value (list, nth)); + } else + *format = GST_FORMAT_UNDEFINED; + } + } +} + +/** + * gst_query_new_buffering + * @format: the default #GstFormat for the new query + * + * Constructs a new query object for querying the buffering status of + * a stream. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + * + * Since: 0.10.20 + */ +GstQuery * +gst_query_new_buffering (GstFormat format) +{ + GstQuery *query; + GstStructure *structure; + + /* by default, we configure the answer as no buffering with a 100% buffering + * progress */ + structure = gst_structure_id_new (GST_QUARK (QUERY_BUFFERING), + GST_QUARK (BUSY), G_TYPE_BOOLEAN, FALSE, + GST_QUARK (BUFFER_PERCENT), G_TYPE_INT, 100, + GST_QUARK (BUFFERING_MODE), GST_TYPE_BUFFERING_MODE, GST_BUFFERING_STREAM, + GST_QUARK (AVG_IN_RATE), G_TYPE_INT, -1, + GST_QUARK (AVG_OUT_RATE), G_TYPE_INT, -1, + GST_QUARK (BUFFERING_LEFT), G_TYPE_INT64, G_GINT64_CONSTANT (0), + GST_QUARK (ESTIMATED_TOTAL), G_TYPE_INT64, G_GINT64_CONSTANT (-1), + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (START_VALUE), G_TYPE_INT64, G_GINT64_CONSTANT (-1), + GST_QUARK (STOP_VALUE), G_TYPE_INT64, G_GINT64_CONSTANT (-1), NULL); + + query = gst_query_new (GST_QUERY_BUFFERING, structure); + + return query; +} + +/** + * gst_query_set_buffering_percent + * @query: A valid #GstQuery of type GST_QUERY_BUFFERING. + * @busy: if buffering is busy + * @percent: a buffering percent + * + * Set the percentage of buffered data. This is a value between 0 and 100. + * The @busy indicator is %TRUE when the buffering is in progress. + * + * Since: 0.10.20 + */ +void +gst_query_set_buffering_percent (GstQuery * query, gboolean busy, gint percent) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_BUFFERING); + g_return_if_fail (gst_query_is_writable (query)); + g_return_if_fail (percent >= 0 && percent <= 100); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, + GST_QUARK (BUSY), G_TYPE_BOOLEAN, busy, + GST_QUARK (BUFFER_PERCENT), G_TYPE_INT, percent, NULL); +} + +/** + * gst_query_parse_buffering_percent + * @query: A valid #GstQuery of type GST_QUERY_BUFFERING. + * @busy: (out) (allow-none): if buffering is busy, or NULL + * @percent: (out) (allow-none): a buffering percent, or NULL + * + * Get the percentage of buffered data. This is a value between 0 and 100. + * The @busy indicator is %TRUE when the buffering is in progress. + * + * Since: 0.10.20 + */ +void +gst_query_parse_buffering_percent (GstQuery * query, gboolean * busy, + gint * percent) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_BUFFERING); + + structure = GST_QUERY_STRUCTURE (query); + if (busy) + *busy = g_value_get_boolean (gst_structure_id_get_value (structure, + GST_QUARK (BUSY))); + if (percent) + *percent = g_value_get_int (gst_structure_id_get_value (structure, + GST_QUARK (BUFFER_PERCENT))); +} + +/** + * gst_query_set_buffering_stats: + * @query: A valid #GstQuery of type GST_QUERY_BUFFERING. + * @mode: a buffering mode + * @avg_in: the average input rate + * @avg_out: the average output rate + * @buffering_left: amount of buffering time left + * + * Configures the buffering stats values in @query. + * + * Since: 0.10.20 + */ +void +gst_query_set_buffering_stats (GstQuery * query, GstBufferingMode mode, + gint avg_in, gint avg_out, gint64 buffering_left) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_BUFFERING); + g_return_if_fail (gst_query_is_writable (query)); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, + GST_QUARK (BUFFERING_MODE), GST_TYPE_BUFFERING_MODE, mode, + GST_QUARK (AVG_IN_RATE), G_TYPE_INT, avg_in, + GST_QUARK (AVG_OUT_RATE), G_TYPE_INT, avg_out, + GST_QUARK (BUFFERING_LEFT), G_TYPE_INT64, buffering_left, NULL); +} + +/** + * gst_query_parse_buffering_stats: + * @query: A valid #GstQuery of type GST_QUERY_BUFFERING. + * @mode: (out) (allow-none): a buffering mode, or NULL + * @avg_in: (out) (allow-none): the average input rate, or NULL + * @avg_out: (out) (allow-none): the average output rat, or NULLe + * @buffering_left: (out) (allow-none): amount of buffering time left, or NULL + * + * Extracts the buffering stats values from @query. + * + * Since: 0.10.20 + */ +void +gst_query_parse_buffering_stats (GstQuery * query, + GstBufferingMode * mode, gint * avg_in, gint * avg_out, + gint64 * buffering_left) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_BUFFERING); + + structure = GST_QUERY_STRUCTURE (query); + if (mode) + *mode = (GstBufferingMode) + g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (BUFFERING_MODE))); + if (avg_in) + *avg_in = g_value_get_int (gst_structure_id_get_value (structure, + GST_QUARK (AVG_IN_RATE))); + if (avg_out) + *avg_out = g_value_get_int (gst_structure_id_get_value (structure, + GST_QUARK (AVG_OUT_RATE))); + if (buffering_left) + *buffering_left = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (BUFFERING_LEFT))); +} + + +/** + * gst_query_set_buffering_range: + * @query: a #GstQuery + * @format: the format to set for the @start and @stop values + * @start: the start to set + * @stop: the stop to set + * @estimated_total: estimated total amount of download time + * + * Set the available query result fields in @query. + * + * Since: 0.10.20 + */ +void +gst_query_set_buffering_range (GstQuery * query, GstFormat format, + gint64 start, gint64 stop, gint64 estimated_total) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_BUFFERING); + g_return_if_fail (gst_query_is_writable (query)); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, + GST_QUARK (FORMAT), GST_TYPE_FORMAT, format, + GST_QUARK (START_VALUE), G_TYPE_INT64, start, + GST_QUARK (STOP_VALUE), G_TYPE_INT64, stop, + GST_QUARK (ESTIMATED_TOTAL), G_TYPE_INT64, estimated_total, NULL); +} + +/** + * gst_query_parse_buffering_range: + * @query: a GST_QUERY_BUFFERING type query #GstQuery + * @format: (out) (allow-none): the format to set for the @segment_start + * and @segment_end values, or NULL + * @start: (out) (allow-none): the start to set, or NULL + * @stop: (out) (allow-none): the stop to set, or NULL + * @estimated_total: (out) (allow-none): estimated total amount of download + * time, or NULL + * + * Parse an available query, writing the format into @format, and + * other results into the passed parameters, if the respective parameters + * are non-NULL + * + * Since: 0.10.20 + */ +void +gst_query_parse_buffering_range (GstQuery * query, GstFormat * format, + gint64 * start, gint64 * stop, gint64 * estimated_total) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_BUFFERING); + + structure = GST_QUERY_STRUCTURE (query); + if (format) + *format = + (GstFormat) g_value_get_enum (gst_structure_id_get_value (structure, + GST_QUARK (FORMAT))); + if (start) + *start = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (START_VALUE))); + if (stop) + *stop = g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (STOP_VALUE))); + if (estimated_total) + *estimated_total = + g_value_get_int64 (gst_structure_id_get_value (structure, + GST_QUARK (ESTIMATED_TOTAL))); +} + +/** + * gst_query_add_buffering_range + * @query: a GST_QUERY_BUFFERING type query #GstQuery + * @start: start position of the range + * @stop: stop position of the range + * + * Set the buffering-ranges array field in @query. The current last + * start position of the array should be inferior to @start. + * + * Returns: a #gboolean indicating if the range was added or not. + * + * Since: 0.10.31 + */ +gboolean +gst_query_add_buffering_range (GstQuery * query, gint64 start, gint64 stop) +{ + GValueArray *array; + GValue *last_array_value; + const GValue *value; + GValue range_value = { 0 }; + GstStructure *structure; + + g_return_val_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_BUFFERING, FALSE); + g_return_val_if_fail (gst_query_is_writable (query), FALSE); + + if (G_UNLIKELY (start >= stop)) + return FALSE; + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (BUFFERING_RANGES)); + if (value) { + array = (GValueArray *) g_value_get_boxed (value); + last_array_value = g_value_array_get_nth (array, array->n_values - 1); + if (G_UNLIKELY (start <= gst_value_get_int64_range_min (last_array_value))) + return FALSE; + } else { + GValue new_array_val = { 0, }; + + array = g_value_array_new (0); + + g_value_init (&new_array_val, G_TYPE_VALUE_ARRAY); + g_value_take_boxed (&new_array_val, array); + + /* set the value array only once, so we then modify (append to) the + * existing value array owned by the GstStructure / the field's GValue */ + gst_structure_id_take_value (structure, GST_QUARK (BUFFERING_RANGES), + &new_array_val); + } + + g_value_init (&range_value, GST_TYPE_INT64_RANGE); + gst_value_set_int64_range (&range_value, start, stop); + g_value_array_append (array, &range_value); + /* skip the g_value_unset(&range_value) here, we know it's not needed */ + + return TRUE; +} + +/** + * gst_query_get_n_buffering_ranges + * @query: a GST_QUERY_BUFFERING type query #GstQuery + * + * Retrieve the number of values currently stored in the + * buffered-ranges array of the query's structure. + * + * Returns: the range array size as a #guint. + * + * Since: 0.10.31 + */ +guint +gst_query_get_n_buffering_ranges (GstQuery * query) +{ + GValueArray *array; + const GValue *value; + guint size = 0; + GstStructure *structure; + + g_return_val_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_BUFFERING, 0); + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (BUFFERING_RANGES)); + if (value) { + array = (GValueArray *) g_value_get_boxed (value); + size = array->n_values; + } + return size; +} + + +/** + * gst_query_parse_nth_buffering_range + * @query: a GST_QUERY_BUFFERING type query #GstQuery + * @index: position in the buffered-ranges array to read + * @start: (out) (allow-none): the start position to set, or NULL + * @stop: (out) (allow-none): the stop position to set, or NULL + * + * Parse an available query and get the start and stop values stored + * at the @index of the buffered ranges array. + * + * Returns: a #gboolean indicating if the parsing succeeded. + * + * Since: 0.10.31 + */ +gboolean +gst_query_parse_nth_buffering_range (GstQuery * query, guint index, + gint64 * start, gint64 * stop) +{ + const GValue *value; + GValueArray *ranges; + GValue *range_value; + gboolean ret = FALSE; + GstStructure *structure; + + g_return_val_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_BUFFERING, ret); + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (BUFFERING_RANGES)); + ranges = (GValueArray *) g_value_get_boxed (value); + range_value = g_value_array_get_nth (ranges, index); + if (range_value) { + if (start) + *start = gst_value_get_int64_range_min (range_value); + if (stop) + *stop = gst_value_get_int64_range_max (range_value); + ret = TRUE; + } + + return ret; +} + + +/** + * gst_query_new_uri: + * + * Constructs a new query URI query object. Use gst_query_unref() + * when done with it. An URI query is used to query the current URI + * that is used by the source or sink. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + * + * Since: 0.10.22 + */ +GstQuery * +gst_query_new_uri (void) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (QUERY_URI), + GST_QUARK (URI), G_TYPE_STRING, NULL, NULL); + + query = gst_query_new (GST_QUERY_URI, structure); + + return query; +} + +/** + * gst_query_set_uri: + * @query: a #GstQuery with query type GST_QUERY_URI + * @uri: the URI to set + * + * Answer a URI query by setting the requested URI. + * + * Since: 0.10.22 + */ +void +gst_query_set_uri (GstQuery * query, const gchar * uri) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_URI); + g_return_if_fail (gst_query_is_writable (query)); + g_return_if_fail (gst_uri_is_valid (uri)); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, GST_QUARK (URI), G_TYPE_STRING, uri, NULL); +} + +/** + * gst_query_parse_uri: + * @query: a #GstQuery + * @uri: (out callee-allocates) (allow-none): the storage for the current URI + * (may be NULL) + * + * Parse an URI query, writing the URI into @uri as a newly + * allocated string, if the respective parameters are non-NULL. + * Free the string with g_free() after usage. + * + * Since: 0.10.22 + */ +void +gst_query_parse_uri (GstQuery * query, gchar ** uri) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_URI); + + structure = GST_QUERY_STRUCTURE (query); + if (uri) + *uri = g_value_dup_string (gst_structure_id_get_value (structure, + GST_QUARK (URI))); +} + +/** + * gst_query_new_allocation + * @caps: the negotiated caps + * @need_pool: return a pool + * + * Constructs a new query object for querying the allocation properties. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + */ +GstQuery * +gst_query_new_allocation (GstCaps * caps, gboolean need_pool) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (QUERY_ALLOCATION), + GST_QUARK (CAPS), GST_TYPE_CAPS, caps, + GST_QUARK (NEED_POOL), G_TYPE_BOOLEAN, need_pool, + GST_QUARK (SIZE), G_TYPE_UINT, 0, + GST_QUARK (MIN_BUFFERS), G_TYPE_UINT, 0, + GST_QUARK (MAX_BUFFERS), G_TYPE_UINT, 0, + GST_QUARK (PREFIX), G_TYPE_UINT, 0, + GST_QUARK (ALIGN), G_TYPE_UINT, 0, + GST_QUARK (POOL), GST_TYPE_BUFFER_POOL, NULL, NULL); + + query = gst_query_new (GST_QUERY_ALLOCATION, structure); + + return query; +} + +void +gst_query_parse_allocation (GstQuery * query, GstCaps ** caps, + gboolean * need_pool) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_get (structure, + GST_QUARK (CAPS), GST_TYPE_CAPS, caps, + GST_QUARK (NEED_POOL), G_TYPE_BOOLEAN, need_pool, NULL); +} + +/** + * gst_query_set_allocation_params + * @query: A valid #GstQuery of type GST_QUERY_ALLOCATION. + * @size: the size + * @min_buffers: the min buffers + * @max_buffers: the max buffers + * @prefix: the prefix + * @alignment: the alignment + * @pool: the #GstBufferPool + * + * Set the allocation parameters in @query. + */ +void +gst_query_set_allocation_params (GstQuery * query, guint size, + guint min_buffers, guint max_buffers, guint prefix, + guint alignment, GstBufferPool * pool) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION); + g_return_if_fail (gst_query_is_writable (query)); + g_return_if_fail (((alignment + 1) & alignment) == 0); + g_return_if_fail (size != 0 || pool == NULL); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, + GST_QUARK (SIZE), G_TYPE_UINT, size, + GST_QUARK (MIN_BUFFERS), G_TYPE_UINT, min_buffers, + GST_QUARK (MAX_BUFFERS), G_TYPE_UINT, max_buffers, + GST_QUARK (PREFIX), G_TYPE_UINT, prefix, + GST_QUARK (ALIGN), G_TYPE_UINT, alignment, + GST_QUARK (POOL), GST_TYPE_BUFFER_POOL, pool, NULL); +} + +/** + * gst_query_parse_allocation_params + * @query: A valid #GstQuery of type GST_QUERY_ALLOCATION. + * @size: the size + * @min_buffers: the min buffers + * @max_buffers: the max buffers + * @prefix: the prefix + * @alignment: the alignment + * @pool: the #GstBufferPool + * + * Get the allocation parameters in @query. + */ +void +gst_query_parse_allocation_params (GstQuery * query, guint * size, + guint * min_buffers, guint * max_buffers, guint * prefix, + guint * alignment, GstBufferPool ** pool) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_get (structure, + GST_QUARK (SIZE), G_TYPE_UINT, size, + GST_QUARK (MIN_BUFFERS), G_TYPE_UINT, min_buffers, + GST_QUARK (MAX_BUFFERS), G_TYPE_UINT, max_buffers, + GST_QUARK (PREFIX), G_TYPE_UINT, prefix, + GST_QUARK (ALIGN), G_TYPE_UINT, alignment, + GST_QUARK (POOL), GST_TYPE_BUFFER_POOL, pool, NULL); +} + +/** + * gst_query_add_allocation_meta + * @query: a GST_QUERY_ALLOCATION type query #GstQuery + * @api: the metadata API + * + * Add @api as aone of the supported metadata API to @query. + */ +void +gst_query_add_allocation_meta (GstQuery * query, const gchar * api) +{ + GValueArray *array; + const GValue *value; + GValue api_value = { 0 }; + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION); + g_return_if_fail (api != NULL); + g_return_if_fail (gst_query_is_writable (query)); + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (META)); + if (value) { + array = (GValueArray *) g_value_get_boxed (value); + } else { + GValue new_array_val = { 0, }; + + array = g_value_array_new (0); + + g_value_init (&new_array_val, G_TYPE_VALUE_ARRAY); + g_value_take_boxed (&new_array_val, array); + + gst_structure_id_take_value (structure, GST_QUARK (META), &new_array_val); + } + + g_value_init (&api_value, G_TYPE_STRING); + g_value_set_string (&api_value, api); + g_value_array_append (array, &api_value); + g_value_unset (&api_value); +} + +/** + * gst_query_get_n_allocation_metas: + * @query: a GST_QUERY_ALLOCATION type query #GstQuery + * + * Retrieve the number of values currently stored in the + * meta API array of the query's structure. + * + * Returns: the metadata API array size as a #guint. + */ +guint +gst_query_get_n_allocation_metas (GstQuery * query) +{ + GValueArray *array; + const GValue *value; + guint size = 0; + GstStructure *structure; + + g_return_val_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION, 0); + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (META)); + if (value) { + array = (GValueArray *) g_value_get_boxed (value); + size = array->n_values; + } + return size; +} + +/** + * gst_query_parse_nth_allocation_meta + * @query: a GST_QUERY_ALLOCATION type query #GstQuery + * @index: position in the metadata API array to read + * + * Parse an available query and get the metadata API + * at @index of the metadata API array. + * + * Returns: a #gchar of the metadata API at @index. + */ +const gchar * +gst_query_parse_nth_allocation_meta (GstQuery * query, guint index) +{ + const GValue *value; + const gchar *ret = NULL; + GstStructure *structure; + + g_return_val_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION, NULL); + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (META)); + if (value) { + GValueArray *meta; + GValue *api_value; + + meta = (GValueArray *) g_value_get_boxed (value); + api_value = g_value_array_get_nth (meta, index); + + if (api_value) + ret = g_value_get_string (api_value); + } + return ret; +} + +/** + * gst_query_has_allocation_meta + * @query: a GST_QUERY_ALLOCATION type query #GstQuery + * @api: the metadata API + * + * Check if @query has metadata @api set. + * + * Returns: TRUE when @api is in the list of metadata. + */ +gboolean +gst_query_has_allocation_meta (GstQuery * query, const gchar * api) +{ + const GValue *value; + GstStructure *structure; + + g_return_val_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION, FALSE); + g_return_val_if_fail (api != NULL, FALSE); + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (META)); + if (value) { + GValueArray *array; + GValue *api_value; + guint i; + + array = (GValueArray *) g_value_get_boxed (value); + for (i = 0; i < array->n_values; i++) { + api_value = g_value_array_get_nth (array, i); + if (!strcmp (api, g_value_get_string (api_value))) + return TRUE; + } + } + return FALSE; +} + +/** + * gst_query_add_allocation_memory + * @query: a GST_QUERY_ALLOCATION type query #GstQuery + * @alloc: the memory allocator + * + * Add @alloc as a supported memory allocator. + */ +void +gst_query_add_allocation_memory (GstQuery * query, const gchar * alloc) +{ + GValueArray *array; + const GValue *value; + GValue alloc_value = { 0 }; + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION); + g_return_if_fail (gst_query_is_writable (query)); + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (ALLOCATOR)); + if (value) { + array = (GValueArray *) g_value_get_boxed (value); + } else { + GValue new_array_val = { 0, }; + + array = g_value_array_new (0); + + g_value_init (&new_array_val, G_TYPE_VALUE_ARRAY); + g_value_take_boxed (&new_array_val, array); + + gst_structure_id_take_value (structure, GST_QUARK (ALLOCATOR), + &new_array_val); + } + + g_value_init (&alloc_value, G_TYPE_STRING); + g_value_set_string (&alloc_value, alloc); + g_value_array_append (array, &alloc_value); + g_value_unset (&alloc_value); +} + +/** + * gst_query_get_n_allocation_memories: + * @query: a GST_QUERY_ALLOCATION type query #GstQuery + * + * Retrieve the number of values currently stored in the + * allocator array of the query's structure. + * + * If no memory allocator is specified, the downstream element can handle + * the default memory allocator. + * + * Returns: the allocator array size as a #guint. + */ +guint +gst_query_get_n_allocation_memories (GstQuery * query) +{ + GValueArray *array; + const GValue *value; + guint size = 0; + GstStructure *structure; + + g_return_val_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION, 0); + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (ALLOCATOR)); + if (value) { + array = (GValueArray *) g_value_get_boxed (value); + size = array->n_values; + } + return size; +} + +/** + * gst_query_parse_nth_allocation_memory + * @query: a GST_QUERY_ALLOCATION type query #GstQuery + * @index: position in the allocator array to read + * + * Parse an available query and get the alloctor + * at @index of the allocator array. + * + * Returns: the name of the allocator at @index. + */ +const gchar * +gst_query_parse_nth_allocation_memory (GstQuery * query, guint index) +{ + const GValue *value; + const gchar *ret = NULL; + GstStructure *structure; + + g_return_val_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION, NULL); + + structure = GST_QUERY_STRUCTURE (query); + value = gst_structure_id_get_value (structure, GST_QUARK (ALLOCATOR)); + if (value) { + GValueArray *memory; + GValue *alloc_value; + + memory = (GValueArray *) g_value_get_boxed (value); + alloc_value = g_value_array_get_nth (memory, index); + + if (alloc_value) + ret = g_value_get_string (alloc_value); + } + return ret; +} + +/** + * gst_query_new_scheduling + * + * Constructs a new query object for querying the scheduling properties. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new #GstQuery + */ +GstQuery * +gst_query_new_scheduling (void) +{ + GstQuery *query; + GstStructure *structure; + + structure = gst_structure_id_new (GST_QUARK (QUERY_SCHEDULING), + GST_QUARK (PULL_MODE), G_TYPE_BOOLEAN, FALSE, + GST_QUARK (RANDOM_ACCESS), G_TYPE_BOOLEAN, FALSE, + GST_QUARK (SEQUENTIAL), G_TYPE_BOOLEAN, TRUE, + GST_QUARK (MINSIZE), G_TYPE_INT, 1, + GST_QUARK (MAXSIZE), G_TYPE_INT, -1, + GST_QUARK (ALIGN), G_TYPE_INT, 1, NULL); + query = gst_query_new (GST_QUERY_SCHEDULING, structure); + + return query; +} + +/** + * gst_query_set_scheduling + * @query: A valid #GstQuery of type GST_QUERY_SCHEDULING. + * @pull_mode: if pull mode scheduling is supported + * @random_access: if random access is possible + * @sequential: if sequential access is recommended + * @minsize: the suggested minimum size of pull requests + * @maxsize: the suggested maximum size of pull requests: + * @align: the suggested alignment of pull requests + * + * Set the scheduling properties. + */ +void +gst_query_set_scheduling (GstQuery * query, gboolean pull_mode, + gboolean random_access, gboolean sequential, + gint minsize, gint maxsize, gint align) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_SCHEDULING); + g_return_if_fail (gst_query_is_writable (query)); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_set (structure, + GST_QUARK (PULL_MODE), G_TYPE_BOOLEAN, pull_mode, + GST_QUARK (RANDOM_ACCESS), G_TYPE_BOOLEAN, random_access, + GST_QUARK (SEQUENTIAL), G_TYPE_BOOLEAN, sequential, + GST_QUARK (MINSIZE), G_TYPE_INT, minsize, + GST_QUARK (MAXSIZE), G_TYPE_INT, maxsize, + GST_QUARK (ALIGN), G_TYPE_INT, align, NULL); +} + +/** + * gst_query_parse_scheduling + * @query: A valid #GstQuery of type GST_QUERY_SCHEDULING. + * @pull_mode: if pull mode scheduling is supported + * @random_access: if random access is possible + * @sequential: if sequential access is recommended + * @minsize: the suggested minimum size of pull requests + * @maxsize: the suggested maximum size of pull requests: + * @align: the suggested alignment of pull requests + * + * Set the scheduling properties. + */ +void +gst_query_parse_scheduling (GstQuery * query, gboolean * pull_mode, + gboolean * random_access, gboolean * sequential, + gint * minsize, gint * maxsize, gint * align) +{ + GstStructure *structure; + + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_SCHEDULING); + + structure = GST_QUERY_STRUCTURE (query); + gst_structure_id_get (structure, + GST_QUARK (PULL_MODE), G_TYPE_BOOLEAN, pull_mode, + GST_QUARK (RANDOM_ACCESS), G_TYPE_BOOLEAN, random_access, + GST_QUARK (SEQUENTIAL), G_TYPE_BOOLEAN, sequential, + GST_QUARK (MINSIZE), G_TYPE_INT, minsize, + GST_QUARK (MAXSIZE), G_TYPE_INT, maxsize, + GST_QUARK (ALIGN), G_TYPE_INT, align, NULL); +} diff --git a/gst/gstquery.h b/gst/gstquery.h new file mode 100644 index 0000000..7b83cbc --- /dev/null +++ b/gst/gstquery.h @@ -0,0 +1,385 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * 2011 Wim Taymans <wim.taymans@gmail.com> + * + * gstquery.h: GstQuery API declaration + * + * 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_QUERY_H__ +#define __GST_QUERY_H__ + +#include <glib.h> + +#include <gst/gstiterator.h> +#include <gst/gstminiobject.h> +#include <gst/gststructure.h> +#include <gst/gstformat.h> +#include <gst/gstpad.h> + +G_BEGIN_DECLS + +/** + * GstQueryType: + * @GST_QUERY_NONE: invalid query type + * @GST_QUERY_POSITION: current position in stream + * @GST_QUERY_DURATION: total duration of the stream + * @GST_QUERY_LATENCY: latency of stream + * @GST_QUERY_JITTER: current jitter of stream + * @GST_QUERY_RATE: current rate of the stream + * @GST_QUERY_SEEKING: seeking capabilities + * @GST_QUERY_SEGMENT: segment start/stop positions + * @GST_QUERY_CONVERT: convert values between formats + * @GST_QUERY_FORMATS: query supported formats for convert + * @GST_QUERY_BUFFERING: query available media for efficient seeking. Since + * 0.10.20. + * @GST_QUERY_CUSTOM: a custom application or element defined query. Since + * 0.10.22. + * @GST_QUERY_URI: query the URI of the source or sink. Since 0.10.22. + * @GST_QUERY_ALLOCATION: the buffer allocation properties + * @GST_QUERY_SCHEDULING: the scheduling properties + * + * Standard predefined Query types + */ +/* NOTE: don't forget to update the table in gstquery.c when changing + * this enum */ +typedef enum { + GST_QUERY_NONE = 0, + GST_QUERY_POSITION, + GST_QUERY_DURATION, + GST_QUERY_LATENCY, + GST_QUERY_JITTER, /* not in draft-query, necessary? */ + GST_QUERY_RATE, + GST_QUERY_SEEKING, + GST_QUERY_SEGMENT, + GST_QUERY_CONVERT, + GST_QUERY_FORMATS, + GST_QUERY_BUFFERING, + GST_QUERY_CUSTOM, + GST_QUERY_URI, + GST_QUERY_ALLOCATION, + GST_QUERY_SCHEDULING +} GstQueryType; + +/** + * GstBufferingMode: + * @GST_BUFFERING_STREAM: a small amount of data is buffered + * @GST_BUFFERING_DOWNLOAD: the stream is being downloaded + * @GST_BUFFERING_TIMESHIFT: the stream is being downloaded in a ringbuffer + * @GST_BUFFERING_LIVE: the stream is a live stream + * + * The different types of buffering methods. + */ +typedef enum { + GST_BUFFERING_STREAM, + GST_BUFFERING_DOWNLOAD, + GST_BUFFERING_TIMESHIFT, + GST_BUFFERING_LIVE +} GstBufferingMode; + +typedef struct _GstQueryTypeDefinition GstQueryTypeDefinition; +typedef struct _GstQuery GstQuery; + +/** + * GstQueryTypeDefinition: + * @value: the unique id of the Query type + * @nick: a short nick + * @description: a longer description of the query type + * @quark: the quark for the nick + * + * A Query Type definition + */ +struct _GstQueryTypeDefinition +{ + GstQueryType value; + const gchar *nick; + const gchar *description; + GQuark quark; +}; + +#define GST_TYPE_QUERY (gst_query_get_type()) +#define GST_IS_QUERY(obj) (GST_IS_MINI_OBJECT_TYPE (obj, GST_TYPE_QUERY)) +#define GST_QUERY_CAST(obj) ((GstQuery*)(obj)) +#define GST_QUERY(obj) (GST_QUERY_CAST(obj)) + + +/** + * GST_QUERY_TYPE: + * @query: the query to query + * + * Get the #GstQueryType of the query. + */ +#define GST_QUERY_TYPE(query) (((GstQuery*)(query))->type) + +/** + * GST_QUERY_TYPE_NAME: + * @query: the query to query + * + * Get a constant string representation of the #GstQueryType of the query. + * + * Since: 0.10.4 + */ +#define GST_QUERY_TYPE_NAME(query) (gst_query_type_get_name(GST_QUERY_TYPE(query))) + + +/** + * GstQuery: + * @mini_object: The parent #GstMiniObject type + * @type: the #GstQueryType + * + * The #GstQuery structure. + */ +struct _GstQuery +{ + GstMiniObject mini_object; + + /*< public > *//* with COW */ + GstQueryType type; +}; + +const gchar* gst_query_type_get_name (GstQueryType query); +GQuark gst_query_type_to_quark (GstQueryType query); + +GType gst_query_get_type (void); + +/* register a new query */ +GstQueryType gst_query_type_register (const gchar *nick, + const gchar *description); +GstQueryType gst_query_type_get_by_nick (const gchar *nick); + +/* check if a query is in an array of querys */ +gboolean gst_query_types_contains (const GstQueryType *types, + GstQueryType type); + +/* query for query details */ + +const GstQueryTypeDefinition* + gst_query_type_get_details (GstQueryType type); +GstIterator* gst_query_type_iterate_definitions (void); + +/* refcounting */ +/** + * gst_query_ref: + * @q: a #GstQuery to increase the refcount of. + * + * Increases the refcount of the given query by one. + * + * Returns: @q + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstQuery * gst_query_ref (GstQuery * q); +#endif + +static inline GstQuery * +gst_query_ref (GstQuery * q) +{ + return GST_QUERY_CAST (gst_mini_object_ref (GST_MINI_OBJECT_CAST (q))); +} + +/** + * gst_query_unref: + * @q: a #GstQuery to decrease the refcount of. + * + * Decreases the refcount of the query. If the refcount reaches 0, the query + * will be freed. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void gst_query_unref (GstQuery * q); +#endif + +static inline void +gst_query_unref (GstQuery * q) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (q)); +} + +/* copy query */ +/** + * gst_query_copy: + * @q: a #GstQuery to copy. + * + * Copies the given query using the copy function of the parent #GstStructure. + * + * Free-function: gst_query_unref + * + * Returns: (transfer full): a new copy of @q. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstQuery * gst_query_copy (const GstQuery * q); +#endif + +static inline GstQuery * +gst_query_copy (const GstQuery * q) +{ + return GST_QUERY_CAST (gst_mini_object_copy (GST_MINI_OBJECT_CONST_CAST (q))); +} + +/** + * gst_query_is_writable: + * @q: a #GstQuery + * + * Tests if you can safely write data into a query's structure. + */ +#define gst_query_is_writable(q) gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (q)) +/** + * gst_query_make_writable: + * @q: (transfer full): a #GstQuery to make writable + * + * Makes a writable query from the given query. + * + * Returns: (transfer full): a new writable query (possibly same as @q) + */ +#define gst_query_make_writable(q) GST_QUERY_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (q))) +/** + * gst_query_replace: + * @old_query: (inout) (transfer full): pointer to a pointer to a #GstQuery + * to be replaced. + * @new_query: (allow-none) (transfer none): pointer to a #GstQuery that will + * replace the query pointed to by @old_query. + * + * Modifies a pointer to a #GstQuery to point to a different #GstQuery. The + * modification is done atomically (so this is useful for ensuring thread safety + * in some cases), and the reference counts are updated appropriately (the old + * query is unreffed, the new one is reffed). + * + * Either @new_query or the #GstQuery pointed to by @old_query may be NULL. + */ +#define gst_query_replace(old_query,new_query) \ + gst_mini_object_replace ((GstMiniObject **)(old_query), GST_MINI_OBJECT_CAST (new_query)) + + +/* application specific query */ +GstQuery * gst_query_new_custom (GstQueryType type, GstStructure *structure); +const GstStructure * + gst_query_get_structure (GstQuery *query); +GstStructure * gst_query_writable_structure (GstQuery *query); + +/* position query */ +GstQuery* gst_query_new_position (GstFormat format); +void gst_query_set_position (GstQuery *query, GstFormat format, gint64 cur); +void gst_query_parse_position (GstQuery *query, GstFormat *format, gint64 *cur); + +/* duration query */ +GstQuery* gst_query_new_duration (GstFormat format); +void gst_query_set_duration (GstQuery *query, GstFormat format, gint64 duration); +void gst_query_parse_duration (GstQuery *query, GstFormat *format, gint64 *duration); + +/* latency query */ +GstQuery* gst_query_new_latency (void); +void gst_query_set_latency (GstQuery *query, gboolean live, GstClockTime min_latency, + GstClockTime max_latency); +void gst_query_parse_latency (GstQuery *query, gboolean *live, GstClockTime *min_latency, + GstClockTime *max_latency); + +/* convert query */ +GstQuery* gst_query_new_convert (GstFormat src_format, gint64 value, GstFormat dest_format); +void gst_query_set_convert (GstQuery *query, GstFormat src_format, gint64 src_value, + GstFormat dest_format, gint64 dest_value); +void gst_query_parse_convert (GstQuery *query, GstFormat *src_format, gint64 *src_value, + GstFormat *dest_format, gint64 *dest_value); +/* segment query */ +GstQuery* gst_query_new_segment (GstFormat format); +void gst_query_set_segment (GstQuery *query, gdouble rate, GstFormat format, + gint64 start_value, gint64 stop_value); +void gst_query_parse_segment (GstQuery *query, gdouble *rate, GstFormat *format, + gint64 *start_value, gint64 *stop_value); + +/* seeking query */ +GstQuery* gst_query_new_seeking (GstFormat format); +void gst_query_set_seeking (GstQuery *query, GstFormat format, + gboolean seekable, + gint64 segment_start, + gint64 segment_end); +void gst_query_parse_seeking (GstQuery *query, GstFormat *format, + gboolean *seekable, + gint64 *segment_start, + gint64 *segment_end); +/* formats query */ +GstQuery* gst_query_new_formats (void); +void gst_query_set_formats (GstQuery *query, gint n_formats, ...); +void gst_query_set_formatsv (GstQuery *query, gint n_formats, const GstFormat *formats); +void gst_query_parse_n_formats (GstQuery *query, guint *n_formats); +void gst_query_parse_nth_format (GstQuery *query, guint nth, GstFormat *format); + +/* buffering query */ +GstQuery* gst_query_new_buffering (GstFormat format); +void gst_query_set_buffering_percent (GstQuery *query, gboolean busy, gint percent); +void gst_query_parse_buffering_percent (GstQuery *query, gboolean *busy, gint *percent); + +void gst_query_set_buffering_stats (GstQuery *query, GstBufferingMode mode, + gint avg_in, gint avg_out, + gint64 buffering_left); +void gst_query_parse_buffering_stats (GstQuery *query, GstBufferingMode *mode, + gint *avg_in, gint *avg_out, + gint64 *buffering_left); + +void gst_query_set_buffering_range (GstQuery *query, GstFormat format, + gint64 start, gint64 stop, + gint64 estimated_total); +void gst_query_parse_buffering_range (GstQuery *query, GstFormat *format, + gint64 *start, gint64 *stop, + gint64 *estimated_total); + +gboolean gst_query_add_buffering_range (GstQuery *query, + gint64 start, gint64 stop); +guint gst_query_get_n_buffering_ranges (GstQuery *query); +gboolean gst_query_parse_nth_buffering_range (GstQuery *query, + guint index, gint64 *start, + gint64 *stop); + +/* URI query */ +GstQuery * gst_query_new_uri (void); +void gst_query_parse_uri (GstQuery *query, gchar **uri); +void gst_query_set_uri (GstQuery *query, const gchar *uri); + +/* allocation query */ +GstQuery * gst_query_new_allocation (GstCaps *caps, gboolean need_pool); +void gst_query_parse_allocation (GstQuery *query, GstCaps **caps, gboolean *need_pool); + +void gst_query_set_allocation_params (GstQuery *query, guint size, guint min_buffers, + guint max_buffers, guint prefix, guint alignment, + GstBufferPool *pool); +void gst_query_parse_allocation_params (GstQuery *query, guint *size, guint *min_buffers, + guint *max_buffers, guint *prefix, guint *alignment, + GstBufferPool **pool); + +void gst_query_add_allocation_meta (GstQuery *query, const gchar *api); +guint gst_query_get_n_allocation_metas (GstQuery *query); +const gchar * gst_query_parse_nth_allocation_meta (GstQuery *query, guint index); +gboolean gst_query_has_allocation_meta (GstQuery *query, const gchar *api); + +void gst_query_add_allocation_memory (GstQuery *query, const gchar *alloc); +guint gst_query_get_n_allocation_memories (GstQuery *query); +const gchar * gst_query_parse_nth_allocation_memory (GstQuery *query, guint index); + +/* scheduling query */ +GstQuery * gst_query_new_scheduling (void); + +void gst_query_set_scheduling (GstQuery *query, gboolean pull_mode, + gboolean random_access, gboolean sequential, + gint minsize, gint maxsize, gint align); +void gst_query_parse_scheduling (GstQuery *query, gboolean *pull_mode, + gboolean *random_access, gboolean *sequential, + gint *minsize, gint *maxsize, gint *align); + +G_END_DECLS + +#endif /* __GST_QUERY_H__ */ + diff --git a/gst/gstregistry.c b/gst/gstregistry.c new file mode 100644 index 0000000..345068e --- /dev/null +++ b/gst/gstregistry.c @@ -0,0 +1,1745 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2005 David A. Schleef <ds@schleef.org> + * + * gstregistry.c: handle registry + * + * 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:gstregistry + * @short_description: Abstract base class for management of #GstPlugin objects + * @see_also: #GstPlugin, #GstPluginFeature + * + * One registry holds the metadata of a set of plugins. + * + * <emphasis role="bold">Design:</emphasis> + * + * The #GstRegistry object is a list of plugins and some functions for dealing + * with them. Each #GstPlugin is matched 1-1 with a file on disk, and may or may + * not be loaded at a given time. There may be multiple #GstRegistry objects, + * but the "default registry" is the only object that has any meaning to the + * core. + * + * The registry file is actually a cache of plugin information. This is + * unlike versions prior to 0.10, where the registry file was the primary source + * of plugin information, and was created by the gst-register command. + * + * The primary source, at all times, of plugin information is each plugin file + * itself. Thus, if an application wants information about a particular plugin, + * or wants to search for a feature that satisfies given criteria, the primary + * means of doing so is to load every plugin and look at the resulting + * information that is gathered in the default registry. Clearly, this is a time + * consuming process, so we cache information in the registry file. The format + * and location of the cache file is internal to gstreamer. + * + * On startup, plugins are searched for in the plugin search path. The following + * locations are checked in this order: + * <itemizedlist> + * <listitem> + * <para>location from --gst-plugin-path commandline option.</para> + * </listitem> + * <listitem> + * <para>the GST_PLUGIN_PATH environment variable.</para> + * </listitem> + * <listitem> + * <para>the GST_PLUGIN_SYSTEM_PATH environment variable.</para> + * </listitem> + * <listitem> + * <para>default locations (if GST_PLUGIN_SYSTEM_PATH is not set). Those + * default locations are: + * <filename>~/.gstreamer-$GST_MAJORMINOR/plugins/</filename> + * and <filename>$prefix/libs/gstreamer-$GST_MAJORMINOR/</filename>. + * </para> + * </listitem> + * </itemizedlist> + * The registry cache file is loaded from + * <filename>~/.gstreamer-$GST_MAJORMINOR/registry-$ARCH.bin</filename> or the + * file listed in the GST_REGISTRY env var. One reason to change the registry + * location is for testing. + * + * For each plugin that is found in the plugin search path, there could be 3 + * possibilities for cached information: + * <itemizedlist> + * <listitem> + * <para>the cache may not contain information about a given file.</para> + * </listitem> + * <listitem> + * <para>the cache may have stale information.</para> + * </listitem> + * <listitem> + * <para>the cache may have current information.</para> + * </listitem> + * </itemizedlist> + * + * In the first two cases, the plugin is loaded and the cache updated. In + * addition to these cases, the cache may have entries for plugins that are not + * relevant to the current process. These are marked as not available to the + * current process. If the cache is updated for whatever reason, it is marked + * dirty. + * + * A dirty cache is written out at the end of initialization. Each entry is + * checked to make sure the information is minimally valid. If not, the entry is + * simply dropped. + * + * <emphasis role="bold">Implementation notes:</emphasis> + * + * The "cache" and "default registry" are different concepts and can represent + * different sets of plugins. For various reasons, at init time, the cache is + * stored in the default registry, and plugins not relevant to the current + * process are marked with the %GST_PLUGIN_FLAG_CACHED bit. These plugins are + * removed at the end of initialization. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gstconfig.h" +#include "gst_private.h" +#include <glib.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> +#include <stdio.h> +#include <string.h> + +/* For g_stat () */ +#include <glib/gstdio.h> + +#include "gstinfo.h" +#include "gsterror.h" +#include "gstregistry.h" +#include "gstmarshal.h" +#include "gstfilter.h" + +#include "gstpluginloader.h" + +#include "gst-i18n-lib.h" + +#include "gst.h" +#include "glib-compat-private.h" + +#ifdef G_OS_WIN32 +#include <windows.h> +extern HMODULE _priv_gst_dll_handle; +#endif + +#define GST_CAT_DEFAULT GST_CAT_REGISTRY + +struct _GstRegistryPrivate +{ + /* updated whenever the feature list changes */ + guint32 cookie; + /* speedup for searching features */ + GList *element_factory_list; + guint32 efl_cookie; + GList *typefind_factory_list; + guint32 tfl_cookie; +}; + +/* the one instance of the default registry and the mutex protecting the + * variable. */ +static GStaticMutex _gst_registry_mutex = G_STATIC_MUTEX_INIT; +static GstRegistry *_gst_registry_default = NULL; + +/* defaults */ +#define DEFAULT_FORK TRUE + +/* control the behaviour of registry rebuild */ +static gboolean _gst_enable_registry_fork = DEFAULT_FORK; +/* List of plugins that need preloading/reloading after scanning registry */ +extern GSList *_priv_gst_preload_plugins; + +#ifndef GST_DISABLE_REGISTRY +/*set to TRUE when registry needn't to be updated */ +gboolean _priv_gst_disable_registry_update = FALSE; +extern GList *_priv_gst_plugin_paths; + +/* Set to TRUE when the registry cache should be disabled */ +gboolean _gst_disable_registry_cache = FALSE; +#endif + +/* Element signals and args */ +enum +{ + PLUGIN_ADDED, + FEATURE_ADDED, + LAST_SIGNAL +}; + +static void gst_registry_finalize (GObject * object); + +static guint gst_registry_signals[LAST_SIGNAL] = { 0 }; + +static GstPluginFeature *gst_registry_lookup_feature_locked (GstRegistry * + registry, const char *name); +static GstPlugin *gst_registry_lookup_bn_locked (GstRegistry * registry, + const char *basename); + +G_DEFINE_TYPE (GstRegistry, gst_registry, GST_TYPE_OBJECT); +static GstObjectClass *parent_class = NULL; + +static void +gst_registry_class_init (GstRegistryClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (klass, sizeof (GstRegistryPrivate)); + + /** + * GstRegistry::plugin-added: + * @registry: the registry that emitted the signal + * @plugin: the plugin that has been added + * + * Signals that a plugin has been added to the registry (possibly + * replacing a previously-added one by the same name) + */ + gst_registry_signals[PLUGIN_ADDED] = + g_signal_new ("plugin-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRegistryClass, plugin_added), NULL, + NULL, gst_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * GstRegistry::feature-added: + * @registry: the registry that emitted the signal + * @feature: the feature that has been added + * + * Signals that a feature has been added to the registry (possibly + * replacing a previously-added one by the same name) + */ + gst_registry_signals[FEATURE_ADDED] = + g_signal_new ("feature-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRegistryClass, feature_added), + NULL, NULL, gst_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); + + gobject_class->finalize = gst_registry_finalize; +} + +static void +gst_registry_init (GstRegistry * registry) +{ + registry->feature_hash = g_hash_table_new (g_str_hash, g_str_equal); + registry->basename_hash = g_hash_table_new (g_str_hash, g_str_equal); + registry->priv = + G_TYPE_INSTANCE_GET_PRIVATE (registry, GST_TYPE_REGISTRY, + GstRegistryPrivate); +} + +static void +gst_registry_finalize (GObject * object) +{ + GstRegistry *registry = GST_REGISTRY (object); + GList *plugins, *p; + GList *features, *f; + + plugins = registry->plugins; + registry->plugins = NULL; + + GST_DEBUG_OBJECT (registry, "registry finalize"); + p = plugins; + while (p) { + GstPlugin *plugin = p->data; + + if (plugin) { + GST_LOG_OBJECT (registry, "removing plugin %s", + gst_plugin_get_name (plugin)); + gst_object_unref (plugin); + } + p = g_list_next (p); + } + g_list_free (plugins); + + features = registry->features; + registry->features = NULL; + + f = features; + while (f) { + GstPluginFeature *feature = f->data; + + if (feature) { + GST_LOG_OBJECT (registry, "removing feature %p (%s)", + feature, gst_plugin_feature_get_name (feature)); + gst_object_unparent (GST_OBJECT_CAST (feature)); + } + f = g_list_next (f); + } + g_list_free (features); + + g_hash_table_destroy (registry->feature_hash); + registry->feature_hash = NULL; + g_hash_table_destroy (registry->basename_hash); + registry->basename_hash = NULL; + + if (registry->priv->element_factory_list) { + GST_DEBUG_OBJECT (registry, "Cleaning up cached element factory list"); + gst_plugin_feature_list_free (registry->priv->element_factory_list); + } + + if (registry->priv->typefind_factory_list) { + GST_DEBUG_OBJECT (registry, "Cleaning up cached typefind factory list"); + gst_plugin_feature_list_free (registry->priv->typefind_factory_list); + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/** + * gst_registry_get_default: + * + * Retrieves the default registry. The caller does not own a reference on the + * registry, as it is alive as long as GStreamer is initialized. + * + * Returns: (transfer none): The default #GstRegistry. + */ +GstRegistry * +gst_registry_get_default (void) +{ + GstRegistry *registry; + + g_static_mutex_lock (&_gst_registry_mutex); + if (G_UNLIKELY (!_gst_registry_default)) { + _gst_registry_default = g_object_newv (GST_TYPE_REGISTRY, 0, NULL); + gst_object_ref_sink (GST_OBJECT_CAST (_gst_registry_default)); + } + registry = _gst_registry_default; + g_static_mutex_unlock (&_gst_registry_mutex); + + return registry; +} + +/** + * gst_registry_add_path: + * @registry: the registry to add the path to + * @path: the path to add to the registry + * + * Add the given path to the registry. The syntax of the + * path is specific to the registry. If the path has already been + * added, do nothing. + */ +void +gst_registry_add_path (GstRegistry * registry, const gchar * path) +{ + g_return_if_fail (GST_IS_REGISTRY (registry)); + g_return_if_fail (path != NULL); + + if (strlen (path) == 0) + goto empty_path; + + GST_OBJECT_LOCK (registry); + if (g_list_find_custom (registry->paths, path, (GCompareFunc) strcmp)) + goto was_added; + + GST_INFO ("Adding plugin path: \"%s\"", path); + registry->paths = g_list_append (registry->paths, g_strdup (path)); + GST_OBJECT_UNLOCK (registry); + + return; + +empty_path: + { + GST_INFO ("Ignoring empty plugin path"); + return; + } +was_added: + { + g_warning ("path %s already added to registry", path); + GST_OBJECT_UNLOCK (registry); + return; + } +} + +/** + * gst_registry_get_path_list: + * @registry: the registry to get the pathlist of + * + * Get the list of paths for the given registry. + * + * Returns: (transfer container) (element-type char*): A #GList of paths as + * strings. g_list_free after use. + * + * MT safe. + */ +GList * +gst_registry_get_path_list (GstRegistry * registry) +{ + GList *list; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + + GST_OBJECT_LOCK (registry); + /* We don't need to copy the strings, because they won't be deleted + * as long as the GstRegistry is around */ + list = g_list_copy (registry->paths); + GST_OBJECT_UNLOCK (registry); + + return list; +} + + +/** + * gst_registry_add_plugin: + * @registry: the registry to add the plugin to + * @plugin: (transfer full): the plugin to add + * + * Add the plugin to the registry. The plugin-added signal will be emitted. + * This function will sink @plugin. + * + * Returns: TRUE on success. + * + * MT safe. + */ +gboolean +gst_registry_add_plugin (GstRegistry * registry, GstPlugin * plugin) +{ + GstPlugin *existing_plugin; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), FALSE); + g_return_val_if_fail (GST_IS_PLUGIN (plugin), FALSE); + + GST_OBJECT_LOCK (registry); + if (G_LIKELY (plugin->basename)) { + /* we have a basename, see if we find the plugin */ + existing_plugin = + gst_registry_lookup_bn_locked (registry, plugin->basename); + if (existing_plugin) { + GST_DEBUG_OBJECT (registry, + "Replacing existing plugin \"%s\" %p with new plugin %p for filename \"%s\"", + GST_STR_NULL (existing_plugin->filename), existing_plugin, plugin, + GST_STR_NULL (plugin->filename)); + /* If the new plugin is blacklisted and the existing one isn't cached, do not + * accept if it's from a different location than the existing one */ + if ((plugin->flags & GST_PLUGIN_FLAG_BLACKLISTED) && + strcmp (plugin->filename, existing_plugin->filename)) { + GST_WARNING_OBJECT (registry, + "Not replacing plugin because new one (%s) is blacklisted but for a different location than existing one (%s)", + plugin->filename, existing_plugin->filename); + gst_object_unref (plugin); + GST_OBJECT_UNLOCK (registry); + return FALSE; + } + registry->plugins = g_list_remove (registry->plugins, existing_plugin); + if (G_LIKELY (existing_plugin->basename)) + g_hash_table_remove (registry->basename_hash, + existing_plugin->basename); + gst_object_unref (existing_plugin); + } + } + + GST_DEBUG_OBJECT (registry, "adding plugin %p for filename \"%s\"", + plugin, GST_STR_NULL (plugin->filename)); + + registry->plugins = g_list_prepend (registry->plugins, plugin); + if (G_LIKELY (plugin->basename)) + g_hash_table_replace (registry->basename_hash, plugin->basename, plugin); + + gst_object_ref_sink (plugin); + GST_OBJECT_UNLOCK (registry); + + GST_LOG_OBJECT (registry, "emitting plugin-added for filename \"%s\"", + GST_STR_NULL (plugin->filename)); + g_signal_emit (registry, gst_registry_signals[PLUGIN_ADDED], 0, plugin); + + return TRUE; +} + +static void +gst_registry_remove_features_for_plugin_unlocked (GstRegistry * registry, + GstPlugin * plugin) +{ + GList *f; + + g_return_if_fail (GST_IS_REGISTRY (registry)); + g_return_if_fail (GST_IS_PLUGIN (plugin)); + + /* Remove all features for this plugin */ + f = registry->features; + while (f != NULL) { + GList *next = g_list_next (f); + GstPluginFeature *feature = f->data; + + if (G_UNLIKELY (feature && feature->plugin == plugin)) { + GST_DEBUG_OBJECT (registry, "removing feature %p (%s) for plugin %p (%s)", + feature, gst_plugin_feature_get_name (feature), plugin, + plugin->desc.name); + + registry->features = g_list_delete_link (registry->features, f); + g_hash_table_remove (registry->feature_hash, GST_OBJECT_NAME (feature)); + gst_object_unparent (GST_OBJECT_CAST (feature)); + } + f = next; + } + registry->priv->cookie++; +} + +/** + * gst_registry_remove_plugin: + * @registry: the registry to remove the plugin from + * @plugin: (transfer none): the plugin to remove + * + * Remove the plugin from the registry. + * + * MT safe. + */ +void +gst_registry_remove_plugin (GstRegistry * registry, GstPlugin * plugin) +{ + g_return_if_fail (GST_IS_REGISTRY (registry)); + g_return_if_fail (GST_IS_PLUGIN (plugin)); + + GST_DEBUG_OBJECT (registry, "removing plugin %p (%s)", + plugin, gst_plugin_get_name (plugin)); + + GST_OBJECT_LOCK (registry); + registry->plugins = g_list_remove (registry->plugins, plugin); + if (G_LIKELY (plugin->basename)) + g_hash_table_remove (registry->basename_hash, plugin->basename); + gst_registry_remove_features_for_plugin_unlocked (registry, plugin); + GST_OBJECT_UNLOCK (registry); + gst_object_unref (plugin); +} + +/** + * gst_registry_add_feature: + * @registry: the registry to add the plugin to + * @feature: (transfer full): the feature to add + * + * Add the feature to the registry. The feature-added signal will be emitted. + * This function sinks @feature. + * + * Returns: TRUE on success. + * + * MT safe. + */ +gboolean +gst_registry_add_feature (GstRegistry * registry, GstPluginFeature * feature) +{ + GstPluginFeature *existing_feature; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), FALSE); + g_return_val_if_fail (GST_IS_PLUGIN_FEATURE (feature), FALSE); + g_return_val_if_fail (GST_OBJECT_NAME (feature) != NULL, FALSE); + g_return_val_if_fail (feature->plugin_name != NULL, FALSE); + + GST_OBJECT_LOCK (registry); + existing_feature = gst_registry_lookup_feature_locked (registry, + GST_OBJECT_NAME (feature)); + if (G_UNLIKELY (existing_feature)) { + GST_DEBUG_OBJECT (registry, "replacing existing feature %p (%s)", + existing_feature, GST_OBJECT_NAME (feature)); + /* Remove the existing feature from the list now, before we insert the new + * one, but don't unref yet because the hash is still storing a reference to + * it. */ + registry->features = g_list_remove (registry->features, existing_feature); + } + + GST_DEBUG_OBJECT (registry, "adding feature %p (%s)", feature, + GST_OBJECT_NAME (feature)); + + registry->features = g_list_prepend (registry->features, feature); + g_hash_table_replace (registry->feature_hash, GST_OBJECT_NAME (feature), + feature); + + if (G_UNLIKELY (existing_feature)) { + /* We unref now. No need to remove the feature name from the hash table, it + * got replaced by the new feature */ + gst_object_unparent (GST_OBJECT_CAST (existing_feature)); + } + + gst_object_set_parent (GST_OBJECT_CAST (feature), GST_OBJECT_CAST (registry)); + + registry->priv->cookie++; + GST_OBJECT_UNLOCK (registry); + + GST_LOG_OBJECT (registry, "emitting feature-added for %s", + GST_OBJECT_NAME (feature)); + g_signal_emit (registry, gst_registry_signals[FEATURE_ADDED], 0, feature); + + return TRUE; +} + +/** + * gst_registry_remove_feature: + * @registry: the registry to remove the feature from + * @feature: (transfer none): the feature to remove + * + * Remove the feature from the registry. + * + * MT safe. + */ +void +gst_registry_remove_feature (GstRegistry * registry, GstPluginFeature * feature) +{ + g_return_if_fail (GST_IS_REGISTRY (registry)); + g_return_if_fail (GST_IS_PLUGIN_FEATURE (feature)); + + GST_DEBUG_OBJECT (registry, "removing feature %p (%s)", + feature, gst_plugin_feature_get_name (feature)); + + GST_OBJECT_LOCK (registry); + registry->features = g_list_remove (registry->features, feature); + g_hash_table_remove (registry->feature_hash, GST_OBJECT_NAME (feature)); + registry->priv->cookie++; + GST_OBJECT_UNLOCK (registry); + + gst_object_unparent ((GstObject *) feature); +} + +/** + * gst_registry_plugin_filter: + * @registry: registry to query + * @filter: (scope call): the filter to use + * @first: only return first match + * @user_data: (closure): user data passed to the filter function + * + * Runs a filter against all plugins in the registry and returns a #GList with + * the results. If the first flag is set, only the first match is + * returned (as a list with a single object). + * Every plugin is reffed; use gst_plugin_list_free() after use, which + * will unref again. + * + * Returns: (transfer full) (element-type Gst.Plugin): a #GList of #GstPlugin. + * Use gst_plugin_list_free() after usage. + * + * MT safe. + */ +GList * +gst_registry_plugin_filter (GstRegistry * registry, + GstPluginFilter filter, gboolean first, gpointer user_data) +{ + GList *list; + GList *g; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + + GST_OBJECT_LOCK (registry); + list = gst_filter_run (registry->plugins, (GstFilterFunc) filter, first, + user_data); + for (g = list; g; g = g->next) { + gst_object_ref (GST_PLUGIN_CAST (g->data)); + } + GST_OBJECT_UNLOCK (registry); + + return list; +} + +/* returns TRUE if the list was changed + * + * Must be called with the object lock taken */ +static gboolean +gst_registry_get_feature_list_or_create (GstRegistry * registry, + GList ** previous, guint32 * cookie, GType type) +{ + gboolean res = FALSE; + GstRegistryPrivate *priv = registry->priv; + + if (G_UNLIKELY (!*previous || priv->cookie != *cookie)) { + GstTypeNameData data; + + if (*previous) + gst_plugin_feature_list_free (*previous); + + data.type = type; + data.name = NULL; + *previous = + gst_filter_run (registry->features, + (GstFilterFunc) gst_plugin_feature_type_name_filter, FALSE, &data); + g_list_foreach (*previous, (GFunc) gst_object_ref, NULL); + *cookie = priv->cookie; + res = TRUE; + } + + return res; +} + +static gint +type_find_factory_rank_cmp (const GstPluginFeature * fac1, + const GstPluginFeature * fac2) +{ + if (G_LIKELY (fac1->rank != fac2->rank)) + return fac2->rank - fac1->rank; + + /* to make the order in which things happen more deterministic, + * sort by name when the ranks are the same. */ + return strcmp (GST_OBJECT_NAME (fac1), GST_OBJECT_NAME (fac2)); +} + +static GList * +gst_registry_get_element_factory_list (GstRegistry * registry) +{ + GList *list; + + GST_OBJECT_LOCK (registry); + + gst_registry_get_feature_list_or_create (registry, + ®istry->priv->element_factory_list, ®istry->priv->efl_cookie, + GST_TYPE_ELEMENT_FACTORY); + + /* Return reffed copy */ + list = gst_plugin_feature_list_copy (registry->priv->element_factory_list); + + GST_OBJECT_UNLOCK (registry); + + return list; +} + +static GList * +gst_registry_get_typefind_factory_list (GstRegistry * registry) +{ + GList *list; + + GST_OBJECT_LOCK (registry); + + if (G_UNLIKELY (gst_registry_get_feature_list_or_create (registry, + ®istry->priv->typefind_factory_list, + ®istry->priv->tfl_cookie, GST_TYPE_TYPE_FIND_FACTORY))) + registry->priv->typefind_factory_list = + g_list_sort (registry->priv->typefind_factory_list, + (GCompareFunc) type_find_factory_rank_cmp); + + /* Return reffed copy */ + list = gst_plugin_feature_list_copy (registry->priv->typefind_factory_list); + + GST_OBJECT_UNLOCK (registry); + + return list; +} + +/** + * gst_registry_feature_filter: + * @registry: registry to query + * @filter: (scope call): the filter to use + * @first: only return first match + * @user_data: (closure): user data passed to the filter function + * + * Runs a filter against all features of the plugins in the registry + * and returns a GList with the results. + * If the first flag is set, only the first match is + * returned (as a list with a single object). + * + * Returns: (transfer full) (element-type Gst.PluginFeature): a #GList of + * #GstPluginFeature. Use gst_plugin_feature_list_free() after usage. + * + * MT safe. + */ +GList * +gst_registry_feature_filter (GstRegistry * registry, + GstPluginFeatureFilter filter, gboolean first, gpointer user_data) +{ + GList *list; + GList *g; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + + GST_OBJECT_LOCK (registry); + list = gst_filter_run (registry->features, (GstFilterFunc) filter, first, + user_data); + for (g = list; g; g = g->next) { + gst_object_ref (GST_PLUGIN_FEATURE_CAST (g->data)); + } + GST_OBJECT_UNLOCK (registry); + + return list; +} + +/** + * gst_registry_find_plugin: + * @registry: the registry to search + * @name: the plugin name to find + * + * Find the plugin with the given name in the registry. + * The plugin will be reffed; caller is responsible for unreffing. + * + * Returns: (transfer full): the plugin with the given name or NULL if the + * plugin was not found. gst_object_unref() after usage. + * + * MT safe. + */ +GstPlugin * +gst_registry_find_plugin (GstRegistry * registry, const gchar * name) +{ + GList *walk; + GstPlugin *result = NULL; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + g_return_val_if_fail (name != NULL, NULL); + + walk = gst_registry_plugin_filter (registry, + (GstPluginFilter) gst_plugin_name_filter, TRUE, (gpointer) name); + if (walk) { + result = GST_PLUGIN_CAST (walk->data); + + gst_object_ref (result); + gst_plugin_list_free (walk); + } + + return result; +} + +/** + * gst_registry_find_feature: + * @registry: the registry to search + * @name: the pluginfeature name to find + * @type: the pluginfeature type to find + * + * Find the pluginfeature with the given name and type in the registry. + * + * Returns: (transfer full): the pluginfeature with the given name and type + * or NULL if the plugin was not found. gst_object_unref() after usage. + * + * MT safe. + */ +GstPluginFeature * +gst_registry_find_feature (GstRegistry * registry, const gchar * name, + GType type) +{ + GstPluginFeature *feature = NULL; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (g_type_is_a (type, GST_TYPE_PLUGIN_FEATURE), NULL); + + feature = gst_registry_lookup_feature (registry, name); + if (feature && !g_type_is_a (G_TYPE_FROM_INSTANCE (feature), type)) { + gst_object_unref (feature); + feature = NULL; + } + + return feature; +} + +/** + * gst_registry_get_feature_list: + * @registry: a #GstRegistry + * @type: a #GType. + * + * Retrieves a #GList of #GstPluginFeature of @type. + * + * Returns: (transfer full) (element-type Gst.PluginFeature): a #GList of + * #GstPluginFeature of @type. Use gst_plugin_feature_list_free() after use + * + * MT safe. + */ +GList * +gst_registry_get_feature_list (GstRegistry * registry, GType type) +{ + GstTypeNameData data; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + g_return_val_if_fail (g_type_is_a (type, GST_TYPE_PLUGIN_FEATURE), NULL); + + /* Speed up */ + if (type == GST_TYPE_ELEMENT_FACTORY) + return gst_registry_get_element_factory_list (registry); + else if (type == GST_TYPE_TYPE_FIND_FACTORY) + return gst_registry_get_typefind_factory_list (registry); + + data.type = type; + data.name = NULL; + + return gst_registry_feature_filter (registry, + (GstPluginFeatureFilter) gst_plugin_feature_type_name_filter, + FALSE, &data); +} + +/** + * gst_registry_get_plugin_list: + * @registry: the registry to search + * + * Get a copy of all plugins registered in the given registry. The refcount + * of each element in the list in incremented. + * + * Returns: (transfer full) (element-type Gst.Plugin): a #GList of #GstPlugin. + * Use gst_plugin_list_free() after usage. + * + * MT safe. + */ +GList * +gst_registry_get_plugin_list (GstRegistry * registry) +{ + GList *list; + GList *g; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + + GST_OBJECT_LOCK (registry); + list = g_list_copy (registry->plugins); + for (g = list; g; g = g->next) { + gst_object_ref (GST_PLUGIN_CAST (g->data)); + } + GST_OBJECT_UNLOCK (registry); + + return list; +} + +static GstPluginFeature * +gst_registry_lookup_feature_locked (GstRegistry * registry, const char *name) +{ + return g_hash_table_lookup (registry->feature_hash, name); +} + +/** + * gst_registry_lookup_feature: + * @registry: a #GstRegistry + * @name: a #GstPluginFeature name + * + * Find a #GstPluginFeature with @name in @registry. + * + * Returns: (transfer full): a #GstPluginFeature with its refcount incremented, + * use gst_object_unref() after usage. + * + * MT safe. + */ +GstPluginFeature * +gst_registry_lookup_feature (GstRegistry * registry, const char *name) +{ + GstPluginFeature *feature; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + g_return_val_if_fail (name != NULL, NULL); + + GST_OBJECT_LOCK (registry); + feature = gst_registry_lookup_feature_locked (registry, name); + if (feature) + gst_object_ref (feature); + GST_OBJECT_UNLOCK (registry); + + return feature; +} + +static GstPlugin * +gst_registry_lookup_bn_locked (GstRegistry * registry, const char *basename) +{ + return g_hash_table_lookup (registry->basename_hash, basename); +} + +static GstPlugin * +gst_registry_lookup_bn (GstRegistry * registry, const char *basename) +{ + GstPlugin *plugin; + + GST_OBJECT_LOCK (registry); + plugin = gst_registry_lookup_bn_locked (registry, basename); + if (plugin) + gst_object_ref (plugin); + GST_OBJECT_UNLOCK (registry); + + return plugin; +} + +/** + * gst_registry_lookup: + * @registry: the registry to look up in + * @filename: the name of the file to look up + * + * Look up a plugin in the given registry with the given filename. + * If found, plugin is reffed. + * + * Returns: (transfer full): the #GstPlugin if found, or NULL if not. + * gst_object_unref() after usage. + */ +GstPlugin * +gst_registry_lookup (GstRegistry * registry, const char *filename) +{ + GstPlugin *plugin; + gchar *basename; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + g_return_val_if_fail (filename != NULL, NULL); + + basename = g_path_get_basename (filename); + if (G_UNLIKELY (basename == NULL)) + return NULL; + + plugin = gst_registry_lookup_bn (registry, basename); + + g_free (basename); + + return plugin; +} + +typedef enum +{ + REGISTRY_SCAN_HELPER_NOT_STARTED = 0, + REGISTRY_SCAN_HELPER_DISABLED, + REGISTRY_SCAN_HELPER_RUNNING +} GstRegistryScanHelperState; + +typedef struct +{ + GstRegistry *registry; + GstRegistryScanHelperState helper_state; + GstPluginLoader *helper; + gboolean changed; +} GstRegistryScanContext; + +static void +init_scan_context (GstRegistryScanContext * context, GstRegistry * registry) +{ + gboolean do_fork; + + context->registry = registry; + + /* see if forking is enabled and set up the scan helper state accordingly */ + do_fork = _gst_enable_registry_fork; + if (do_fork) { + const gchar *fork_env; + + /* forking enabled, see if it is disabled with an env var */ + if ((fork_env = g_getenv ("GST_REGISTRY_FORK"))) { + /* fork enabled for any value different from "no" */ + do_fork = strcmp (fork_env, "no") != 0; + } + } + + if (do_fork) + context->helper_state = REGISTRY_SCAN_HELPER_NOT_STARTED; + else + context->helper_state = REGISTRY_SCAN_HELPER_DISABLED; + + context->helper = NULL; + context->changed = FALSE; +} + +static void +clear_scan_context (GstRegistryScanContext * context) +{ + if (context->helper) { + context->changed |= _priv_gst_plugin_loader_funcs.destroy (context->helper); + context->helper = NULL; + } +} + +static gboolean +gst_registry_scan_plugin_file (GstRegistryScanContext * context, + const gchar * filename, off_t file_size, time_t file_mtime) +{ + gboolean changed = FALSE; + GstPlugin *newplugin = NULL; + +#ifdef G_OS_WIN32 + /* Disable external plugin loader on Windows until it is ported properly. */ + context->helper_state = REGISTRY_SCAN_HELPER_DISABLED; +#endif + + + /* Have a plugin to load - see if the scan-helper needs starting */ + if (context->helper_state == REGISTRY_SCAN_HELPER_NOT_STARTED) { + GST_DEBUG ("Starting plugin scanner for file %s", filename); + context->helper = _priv_gst_plugin_loader_funcs.create (context->registry); + if (context->helper != NULL) + context->helper_state = REGISTRY_SCAN_HELPER_RUNNING; + else { + GST_WARNING ("Failed starting plugin scanner. Scanning in-process"); + context->helper_state = REGISTRY_SCAN_HELPER_DISABLED; + } + } + + if (context->helper_state == REGISTRY_SCAN_HELPER_RUNNING) { + GST_DEBUG ("Using scan-helper to load plugin %s", filename); + if (!_priv_gst_plugin_loader_funcs.load (context->helper, + filename, file_size, file_mtime)) { + g_warning ("External plugin loader failed. This most likely means that " + "the plugin loader helper binary was not found or could not be run. " + "%s", (g_getenv ("GST_PLUGIN_PATH") != NULL) ? + "If you are running an uninstalled GStreamer setup, you might need " + "to update your gst-uninstalled script so that the " + "GST_PLUGIN_SCANNER environment variable gets set." : ""); + context->helper_state = REGISTRY_SCAN_HELPER_DISABLED; + } + } + + /* Check if the helper is disabled (or just got disabled above) */ + if (context->helper_state == REGISTRY_SCAN_HELPER_DISABLED) { + /* Load plugin the old fashioned way... */ + + /* We don't use a GError here because a failure to load some shared + * objects as plugins is normal (particularly in the uninstalled case) + */ + newplugin = gst_plugin_load_file (filename, NULL); + } + + if (newplugin) { + GST_DEBUG_OBJECT (context->registry, "marking new plugin %p as registered", + newplugin); + newplugin->registered = TRUE; + gst_object_unref (newplugin); + changed = TRUE; + } + + return changed; +} + +static gboolean +is_blacklisted_hidden_directory (const gchar * dirent) +{ + if (G_LIKELY (dirent[0] != '.')) + return FALSE; + + /* skip the .debug directory, these contain elf files that are not + * useful or worse, can crash dlopen () */ + if (strcmp (dirent, ".debug") == 0) + return TRUE; + + /* can also skip .git and .deps dirs, those won't contain useful files. + * This speeds up scanning a bit in uninstalled setups. */ + if (strcmp (dirent, ".git") == 0 || strcmp (dirent, ".deps") == 0) + return TRUE; + + return FALSE; +} + +static gboolean +gst_registry_scan_path_level (GstRegistryScanContext * context, + const gchar * path, int level) +{ + GDir *dir; + const gchar *dirent; + gchar *filename; + GstPlugin *plugin; + gboolean changed = FALSE; + + dir = g_dir_open (path, 0, NULL); + if (!dir) + return FALSE; + + while ((dirent = g_dir_read_name (dir))) { + GStatBuf file_status; + + filename = g_build_filename (path, dirent, NULL); + if (g_stat (filename, &file_status) < 0) { + /* Plugin will be removed from cache after the scan completes if it + * is still marked 'cached' */ + g_free (filename); + continue; + } + + if (file_status.st_mode & S_IFDIR) { + if (G_UNLIKELY (is_blacklisted_hidden_directory (dirent))) { + GST_TRACE_OBJECT (context->registry, "ignoring %s directory", dirent); + g_free (filename); + continue; + } + /* FIXME 0.11: Don't recurse into directories, this behaviour + * is inconsistent with other PATH environment variables + */ + if (level > 0) { + GST_LOG_OBJECT (context->registry, "recursing into directory %s", + filename); + changed |= gst_registry_scan_path_level (context, filename, level - 1); + } else { + GST_LOG_OBJECT (context->registry, "not recursing into directory %s, " + "recursion level too deep", filename); + } + g_free (filename); + continue; + } + if (!(file_status.st_mode & S_IFREG)) { + GST_TRACE_OBJECT (context->registry, "%s is not a regular file, ignoring", + filename); + g_free (filename); + continue; + } + if (!g_str_has_suffix (dirent, G_MODULE_SUFFIX) +#ifdef GST_EXTRA_MODULE_SUFFIX + && !g_str_has_suffix (dirent, GST_EXTRA_MODULE_SUFFIX) +#endif + ) { + GST_TRACE_OBJECT (context->registry, + "extension is not recognized as module file, ignoring file %s", + filename); + g_free (filename); + continue; + } + + GST_LOG_OBJECT (context->registry, "file %s looks like a possible module", + filename); + + /* try to avoid unnecessary plugin-move pain */ + if (g_str_has_prefix (dirent, "libgstvalve") || + g_str_has_prefix (dirent, "libgstselector")) { + GST_WARNING_OBJECT (context->registry, "ignoring old plugin %s which " + "has been merged into the corelements plugin", filename); + /* Plugin will be removed from cache after the scan completes if it + * is still marked 'cached' */ + g_free (filename); + continue; + } + + /* plug-ins are considered unique by basename; if the given name + * was already seen by the registry, we ignore it */ + plugin = gst_registry_lookup_bn (context->registry, dirent); + if (plugin) { + gboolean env_vars_changed, deps_changed = FALSE; + + if (plugin->registered) { + GST_DEBUG_OBJECT (context->registry, + "plugin already registered from path \"%s\"", + GST_STR_NULL (plugin->filename)); + g_free (filename); + gst_object_unref (plugin); + continue; + } + + env_vars_changed = _priv_plugin_deps_env_vars_changed (plugin); + + /* If a file with a certain basename is seen on a different path, + * update the plugin to ensure the registry cache will reflect up + * to date information */ + + if (plugin->file_mtime == file_status.st_mtime && + plugin->file_size == file_status.st_size && !env_vars_changed && + !(deps_changed = _priv_plugin_deps_files_changed (plugin)) && + !strcmp (plugin->filename, filename)) { + GST_LOG_OBJECT (context->registry, "file %s cached", filename); + plugin->flags &= ~GST_PLUGIN_FLAG_CACHED; + GST_LOG_OBJECT (context->registry, + "marking plugin %p as registered as %s", plugin, filename); + plugin->registered = TRUE; + } else { + GST_INFO_OBJECT (context->registry, "cached info for %s is stale", + filename); + GST_DEBUG_OBJECT (context->registry, "mtime %" G_GINT64_FORMAT " != %" + G_GINT64_FORMAT " or size %" G_GINT64_FORMAT " != %" + G_GINT64_FORMAT " or external dependency env_vars changed: %d or" + " external dependencies changed: %d or old path %s != new path %s", + (gint64) plugin->file_mtime, (gint64) file_status.st_mtime, + (gint64) plugin->file_size, (gint64) file_status.st_size, + env_vars_changed, deps_changed, plugin->filename, filename); + gst_registry_remove_plugin (context->registry, plugin); + changed |= gst_registry_scan_plugin_file (context, filename, + file_status.st_size, file_status.st_mtime); + } + gst_object_unref (plugin); + + } else { + GST_DEBUG_OBJECT (context->registry, "file %s not yet in registry", + filename); + changed |= gst_registry_scan_plugin_file (context, filename, + file_status.st_size, file_status.st_mtime); + } + + g_free (filename); + } + + g_dir_close (dir); + + return changed; +} + +static gboolean +gst_registry_scan_path_internal (GstRegistryScanContext * context, + const gchar * path) +{ + gboolean changed; + + GST_DEBUG_OBJECT (context->registry, "scanning path %s", path); + changed = gst_registry_scan_path_level (context, path, 10); + + GST_DEBUG_OBJECT (context->registry, "registry changed in path %s: %d", path, + changed); + return changed; +} + +/** + * gst_registry_scan_path: + * @registry: the registry to add found plugins to + * @path: the path to scan + * + * Scan the given path for plugins to add to the registry. The syntax of the + * path is specific to the registry. + * + * Returns: %TRUE if registry changed + */ +gboolean +gst_registry_scan_path (GstRegistry * registry, const gchar * path) +{ + GstRegistryScanContext context; + gboolean result; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + init_scan_context (&context, registry); + + result = gst_registry_scan_path_internal (&context, path); + + clear_scan_context (&context); + result |= context.changed; + + return result; +} + +static gboolean +_gst_plugin_feature_filter_plugin_name (GstPluginFeature * feature, + gpointer user_data) +{ + return (strcmp (feature->plugin_name, (gchar *) user_data) == 0); +} + +/** + * gst_registry_get_feature_list_by_plugin: + * @registry: a #GstRegistry. + * @name: a plugin name. + * + * Retrieves a #GList of features of the plugin with name @name. + * + * Returns: (transfer full) (element-type Gst.PluginFeature): a #GList of + * #GstPluginFeature. Use gst_plugin_feature_list_free() after usage. + */ +GList * +gst_registry_get_feature_list_by_plugin (GstRegistry * registry, + const gchar * name) +{ + g_return_val_if_fail (GST_IS_REGISTRY (registry), NULL); + g_return_val_if_fail (name != NULL, NULL); + + return gst_registry_feature_filter (registry, + _gst_plugin_feature_filter_plugin_name, FALSE, (gpointer) name); +} + +/* Unref and delete the default registry */ +void +_priv_gst_registry_cleanup (void) +{ + GstRegistry *registry; + + g_static_mutex_lock (&_gst_registry_mutex); + if ((registry = _gst_registry_default) != NULL) { + _gst_registry_default = NULL; + } + g_static_mutex_unlock (&_gst_registry_mutex); + + /* unref outside of the lock because we can. */ + if (registry) + gst_object_unref (registry); +} + +/** + * gst_default_registry_check_feature_version: + * @feature_name: the name of the feature (e.g. "oggdemux") + * @min_major: the minimum major version number + * @min_minor: the minimum minor version number + * @min_micro: the minimum micro version number + * + * Checks whether a plugin feature by the given name exists in the + * default registry and whether its version is at least the + * version required. + * + * Returns: #TRUE if the feature could be found and the version is + * the same as the required version or newer, and #FALSE otherwise. + */ +gboolean +gst_default_registry_check_feature_version (const gchar * feature_name, + guint min_major, guint min_minor, guint min_micro) +{ + GstPluginFeature *feature; + GstRegistry *registry; + gboolean ret = FALSE; + + g_return_val_if_fail (feature_name != NULL, FALSE); + + GST_DEBUG ("Looking up plugin feature '%s'", feature_name); + + registry = gst_registry_get_default (); + feature = gst_registry_lookup_feature (registry, feature_name); + if (feature) { + ret = gst_plugin_feature_check_version (feature, min_major, min_minor, + min_micro); + gst_object_unref (feature); + } else { + GST_DEBUG ("Could not find plugin feature '%s'", feature_name); + } + + return ret; +} + +static void +load_plugin_func (gpointer data, gpointer user_data) +{ + GstPlugin *plugin; + const gchar *filename; + GError *err = NULL; + + filename = (const gchar *) data; + GST_DEBUG ("Pre-loading plugin %s", filename); + + plugin = gst_plugin_load_file (filename, &err); + + if (plugin) { + GST_INFO ("Loaded plugin: \"%s\"", filename); + + gst_default_registry_add_plugin (plugin); + } else { + if (err) { + /* Report error to user, and free error */ + GST_ERROR ("Failed to load plugin: %s", err->message); + g_error_free (err); + } else { + GST_WARNING ("Failed to load plugin: \"%s\"", filename); + } + } +} + +#ifndef GST_DISABLE_REGISTRY +/* Unref all plugins marked 'cached', to clear old plugins that no + * longer exist. Returns TRUE if any plugins were removed */ +static gboolean +gst_registry_remove_cache_plugins (GstRegistry * registry) +{ + GList *g; + GList *g_next; + GstPlugin *plugin; + gboolean changed = FALSE; + + g_return_val_if_fail (GST_IS_REGISTRY (registry), FALSE); + + GST_OBJECT_LOCK (registry); + + GST_DEBUG_OBJECT (registry, "removing cached plugins"); + g = registry->plugins; + while (g) { + g_next = g->next; + plugin = g->data; + if (plugin->flags & GST_PLUGIN_FLAG_CACHED) { + GST_DEBUG_OBJECT (registry, "removing cached plugin \"%s\"", + GST_STR_NULL (plugin->filename)); + registry->plugins = g_list_delete_link (registry->plugins, g); + if (G_LIKELY (plugin->basename)) + g_hash_table_remove (registry->basename_hash, plugin->basename); + gst_registry_remove_features_for_plugin_unlocked (registry, plugin); + gst_object_unref (plugin); + changed = TRUE; + } + g = g_next; + } + + GST_OBJECT_UNLOCK (registry); + + return changed; +} + +typedef enum +{ + REGISTRY_SCAN_AND_UPDATE_FAILURE = 0, + REGISTRY_SCAN_AND_UPDATE_SUCCESS_NOT_CHANGED, + REGISTRY_SCAN_AND_UPDATE_SUCCESS_UPDATED +} GstRegistryScanAndUpdateResult; + +/* + * scan_and_update_registry: + * @default_registry: the #GstRegistry + * @registry_file: registry filename + * @write_changes: write registry if it has changed? + * + * Scans for registry changes and eventually updates the registry cache. + * + * Return: %REGISTRY_SCAN_AND_UPDATE_FAILURE if the registry could not scanned + * or updated, %REGISTRY_SCAN_AND_UPDATE_SUCCESS_NOT_CHANGED if the + * registry is clean and %REGISTRY_SCAN_AND_UPDATE_SUCCESS_UPDATED if + * it has been updated and the cache needs to be re-read. + */ +static GstRegistryScanAndUpdateResult +scan_and_update_registry (GstRegistry * default_registry, + const gchar * registry_file, gboolean write_changes, GError ** error) +{ + const gchar *plugin_path; + gboolean changed = FALSE; + GList *l; + GstRegistryScanContext context; + + GST_INFO ("Validating plugins from registry cache: %s", registry_file); + + init_scan_context (&context, default_registry); + + /* It sounds tempting to just compare the mtime of directories with the mtime + * of the registry cache, but it does not work. It would not catch updated + * plugins, which might bring more or less features. + */ + + /* scan paths specified via --gst-plugin-path */ + GST_DEBUG ("scanning paths added via --gst-plugin-path"); + for (l = _priv_gst_plugin_paths; l != NULL; l = l->next) { + GST_INFO ("Scanning plugin path: \"%s\"", (gchar *) l->data); + changed |= gst_registry_scan_path_internal (&context, (gchar *) l->data); + } + /* keep plugin_paths around in case a re-scan is forced later on */ + + /* GST_PLUGIN_PATH specifies a list of directories to scan for + * additional plugins. These take precedence over the system plugins */ + plugin_path = g_getenv ("GST_PLUGIN_PATH"); + if (plugin_path) { + char **list; + int i; + + GST_DEBUG ("GST_PLUGIN_PATH set to %s", plugin_path); + list = g_strsplit (plugin_path, G_SEARCHPATH_SEPARATOR_S, 0); + for (i = 0; list[i]; i++) { + changed |= gst_registry_scan_path_internal (&context, list[i]); + } + g_strfreev (list); + } else { + GST_DEBUG ("GST_PLUGIN_PATH not set"); + } + + /* GST_PLUGIN_SYSTEM_PATH specifies a list of plugins that are always + * loaded by default. If not set, this defaults to the system-installed + * path, and the plugins installed in the user's home directory */ + plugin_path = g_getenv ("GST_PLUGIN_SYSTEM_PATH"); + if (plugin_path == NULL) { + char *home_plugins; + + GST_DEBUG ("GST_PLUGIN_SYSTEM_PATH not set"); + + /* plugins in the user's home directory take precedence over + * system-installed ones */ + home_plugins = g_build_filename (g_get_user_data_dir (), + "gstreamer-" GST_MAJORMINOR, "plugins", NULL); + + GST_DEBUG ("scanning home plugins %s", home_plugins); + changed |= gst_registry_scan_path_internal (&context, home_plugins); + g_free (home_plugins); + + /* add the main (installed) library path */ + GST_DEBUG ("scanning main plugins %s", PLUGINDIR); + changed |= gst_registry_scan_path_internal (&context, PLUGINDIR); + +#ifdef G_OS_WIN32 + { + char *base_dir; + char *dir; + + base_dir = + g_win32_get_package_installation_directory_of_module + (_priv_gst_dll_handle); + + dir = g_build_filename (base_dir, "lib", "gstreamer-0.10", NULL); + GST_DEBUG ("scanning DLL dir %s", dir); + + changed |= gst_registry_scan_path_internal (&context, dir); + + g_free (dir); + g_free (base_dir); + } +#endif + } else { + gchar **list; + gint i; + + GST_DEBUG ("GST_PLUGIN_SYSTEM_PATH set to %s", plugin_path); + list = g_strsplit (plugin_path, G_SEARCHPATH_SEPARATOR_S, 0); + for (i = 0; list[i]; i++) { + changed |= gst_registry_scan_path_internal (&context, list[i]); + } + g_strfreev (list); + } + + clear_scan_context (&context); + changed |= context.changed; + + /* Remove cached plugins so stale info is cleared. */ + changed |= gst_registry_remove_cache_plugins (default_registry); + + if (!changed) { + GST_INFO ("Registry cache has not changed"); + return REGISTRY_SCAN_AND_UPDATE_SUCCESS_NOT_CHANGED; + } + + if (!write_changes) { + GST_INFO ("Registry cache changed, but writing is disabled. Not writing."); + return REGISTRY_SCAN_AND_UPDATE_FAILURE; + } + + GST_INFO ("Registry cache changed. Writing new registry cache"); + if (!priv_gst_registry_binary_write_cache (default_registry, registry_file)) { + g_set_error (error, GST_CORE_ERROR, GST_CORE_ERROR_FAILED, + _("Error writing registry cache to %s: %s"), + registry_file, g_strerror (errno)); + return REGISTRY_SCAN_AND_UPDATE_FAILURE; + } + + GST_INFO ("Registry cache written successfully"); + return REGISTRY_SCAN_AND_UPDATE_SUCCESS_UPDATED; +} + +static gboolean +ensure_current_registry (GError ** error) +{ + gchar *registry_file; + GstRegistry *default_registry; + gboolean ret = TRUE; + gboolean do_update = TRUE; + gboolean have_cache = TRUE; + + default_registry = gst_registry_get_default (); + registry_file = g_strdup (g_getenv ("GST_REGISTRY")); + if (registry_file == NULL) { + registry_file = g_build_filename (g_get_user_cache_dir (), + "gstreamer-" GST_MAJORMINOR, "registry." HOST_CPU ".bin", NULL); + } + + if (!_gst_disable_registry_cache) { + GST_INFO ("reading registry cache: %s", registry_file); + have_cache = priv_gst_registry_binary_read_cache (default_registry, + registry_file); + /* Only ever read the registry cache once, then disable it for + * subsequent updates during the program lifetime */ + _gst_disable_registry_cache = TRUE; + } + + if (have_cache) { + do_update = !_priv_gst_disable_registry_update; + if (do_update) { + const gchar *update_env; + + if ((update_env = g_getenv ("GST_REGISTRY_UPDATE"))) { + /* do update for any value different from "no" */ + do_update = (strcmp (update_env, "no") != 0); + } + } + } + + if (do_update) { + /* now check registry */ + GST_DEBUG ("Updating registry cache"); + scan_and_update_registry (default_registry, registry_file, TRUE, error); + } else { + GST_DEBUG ("Not updating registry cache (disabled)"); + } + + g_free (registry_file); + GST_INFO ("registry reading and updating done, result = %d", ret); + + return ret; +} +#endif /* GST_DISABLE_REGISTRY */ + +/** + * gst_registry_fork_is_enabled: + * + * By default GStreamer will perform scanning and rebuilding of the + * registry file using a helper child process. + * + * Applications might want to disable this behaviour with the + * gst_registry_fork_set_enabled() function, in which case new plugins + * are scanned (and loaded) into the application process. + * + * Returns: %TRUE if GStreamer will use the child helper process when + * rebuilding the registry. + * + * Since: 0.10.10 + */ +gboolean +gst_registry_fork_is_enabled (void) +{ + return _gst_enable_registry_fork; +} + +/** + * gst_registry_fork_set_enabled: + * @enabled: whether rebuilding the registry can use a temporary child helper process. + * + * Applications might want to disable/enable spawning of a child helper process + * when rebuilding the registry. See gst_registry_fork_is_enabled() for more + * information. + * + * Since: 0.10.10 + */ +void +gst_registry_fork_set_enabled (gboolean enabled) +{ + _gst_enable_registry_fork = enabled; +} + +/** + * gst_update_registry: + * + * Forces GStreamer to re-scan its plugin paths and update the default + * plugin registry. + * + * Applications will almost never need to call this function, it is only + * useful if the application knows new plugins have been installed (or old + * ones removed) since the start of the application (or, to be precise, the + * first call to gst_init()) and the application wants to make use of any + * newly-installed plugins without restarting the application. + * + * Applications should assume that the registry update is neither atomic nor + * thread-safe and should therefore not have any dynamic pipelines running + * (including the playbin and decodebin elements) and should also not create + * any elements or access the GStreamer registry while the update is in + * progress. + * + * Note that this function may block for a significant amount of time. + * + * Returns: %TRUE if the registry has been updated successfully (does not + * imply that there were changes), otherwise %FALSE. + * + * Since: 0.10.12 + */ +gboolean +gst_update_registry (void) +{ + gboolean res; + +#ifndef GST_DISABLE_REGISTRY + GError *err = NULL; + + res = ensure_current_registry (&err); + if (err) { + GST_WARNING ("registry update failed: %s", err->message); + g_error_free (err); + } else { + GST_LOG ("registry update succeeded"); + } + +#else + GST_WARNING ("registry update failed: %s", "registry disabled"); + res = TRUE; +#endif /* GST_DISABLE_REGISTRY */ + + if (_priv_gst_preload_plugins) { + GST_DEBUG ("Preloading indicated plugins..."); + g_slist_foreach (_priv_gst_preload_plugins, load_plugin_func, NULL); + } + + return res; +} + +/** + * gst_registry_get_feature_list_cookie: + * @registry: the registry + * + * Returns the registrys feature list cookie. This changes + * every time a feature is added or removed from the registry. + * + * Returns: the feature list cookie. + * + * Since: 0.10.26 + */ +guint32 +gst_registry_get_feature_list_cookie (GstRegistry * registry) +{ + g_return_val_if_fail (GST_IS_REGISTRY (registry), 0); + + return registry->priv->cookie; +} diff --git a/gst/gstregistry.h b/gst/gstregistry.h new file mode 100644 index 0000000..f2d07c1 --- /dev/null +++ b/gst/gstregistry.h @@ -0,0 +1,229 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * + * gstregistry.h: Header for registry handling + * + * 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_REGISTRY_H__ +#define __GST_REGISTRY_H__ + +#include <gst/gstconfig.h> +#include <gst/gstplugin.h> +#include <gst/gstpluginfeature.h> + +G_BEGIN_DECLS + +#define GST_TYPE_REGISTRY (gst_registry_get_type ()) +#define GST_REGISTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_REGISTRY, GstRegistry)) +#define GST_IS_REGISTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_REGISTRY)) +#define GST_REGISTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_REGISTRY, GstRegistryClass)) +#define GST_IS_REGISTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_REGISTRY)) +#define GST_REGISTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_REGISTRY, GstRegistryClass)) + +typedef struct _GstRegistry GstRegistry; +typedef struct _GstRegistryClass GstRegistryClass; +typedef struct _GstRegistryPrivate GstRegistryPrivate; + +/** + * GstRegistry: + * + * Opaque #GstRegistry structure. + */ +struct _GstRegistry { + GstObject object; + + /*< private >*/ + GList *plugins; + GList *features; + + GList *paths; + + /* FIXME move these elsewhere */ + int cache_file; + + /* hash to speedup _lookup_feature_locked() */ + GHashTable *feature_hash; + /* hash to speedup _lookup */ + GHashTable *basename_hash; + + GstRegistryPrivate *priv; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstRegistryClass { + GstObjectClass parent_class; + + /* signals */ + void (*plugin_added) (GstRegistry *registry, GstPlugin *plugin); + void (*feature_added) (GstRegistry *registry, GstPluginFeature *feature); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + + +/* normal GObject stuff */ +GType gst_registry_get_type (void); + +GstRegistry * gst_registry_get_default (void); + +gboolean gst_registry_scan_path (GstRegistry *registry, const gchar *path); +void gst_registry_add_path (GstRegistry * registry, const gchar * path); +GList* gst_registry_get_path_list (GstRegistry *registry); + +gboolean gst_registry_add_plugin (GstRegistry *registry, GstPlugin *plugin); +void gst_registry_remove_plugin (GstRegistry *registry, GstPlugin *plugin); +gboolean gst_registry_add_feature (GstRegistry * registry, GstPluginFeature * feature); +void gst_registry_remove_feature (GstRegistry * registry, GstPluginFeature * feature); + +GList* gst_registry_get_plugin_list (GstRegistry *registry); +GList* gst_registry_plugin_filter (GstRegistry *registry, + GstPluginFilter filter, + gboolean first, + gpointer user_data); +GList* gst_registry_feature_filter (GstRegistry *registry, + GstPluginFeatureFilter filter, + gboolean first, + gpointer user_data); +GList * gst_registry_get_feature_list (GstRegistry *registry, + GType type); +GList * gst_registry_get_feature_list_by_plugin (GstRegistry *registry, const gchar *name); +guint32 gst_registry_get_feature_list_cookie (GstRegistry *registry); + +GstPlugin* gst_registry_find_plugin (GstRegistry *registry, const gchar *name); +GstPluginFeature* gst_registry_find_feature (GstRegistry *registry, const gchar *name, GType type); + +GstPlugin * gst_registry_lookup (GstRegistry *registry, const char *filename); +GstPluginFeature * gst_registry_lookup_feature (GstRegistry *registry, const char *name); + +/* convinience defines for the default registry */ + +/** + * gst_default_registry_add_plugin: + * @plugin: (transfer full): the plugin to add + * + * Add the plugin to the default registry. + * The plugin-added signal will be emitted. + * + * Returns: TRUE on success. + */ +#define gst_default_registry_add_plugin(plugin) \ + gst_registry_add_plugin (gst_registry_get_default(), plugin) + +/** + * gst_default_registry_add_path: + * @path: the path to add to the registry + * + * Add the given path to the default registry. The syntax of the + * path is specific to the registry. If the path has already been + * added, do nothing. + */ +#define gst_default_registry_add_path(path) \ + gst_registry_add_path (gst_registry_get_default(), path) + +/** + * gst_default_registry_get_path_list: + * + * Get the list of paths for the default registry. + * + * Returns: (transfer container) (element-type char*): a #GList of paths as + * strings. g_list_free() after use. + */ +#define gst_default_registry_get_path_list() \ + gst_registry_get_path_list (gst_registry_get_default()) + +/** + * gst_default_registry_get_plugin_list: + * + * Get a copy of all plugins registered in the default registry. + * + * Returns: (transfer full) (element-type Gst.Plugin): a copy of the list. + * Free after use. + */ +#define gst_default_registry_get_plugin_list() \ + gst_registry_get_plugin_list (gst_registry_get_default()) + +/** + * gst_default_registry_find_feature: + * @name: the pluginfeature name to find + * @type: the pluginfeature type to find + * + * Find the pluginfeature with the given name and type in the default registry. + * + * Returns: (transfer full): the pluginfeature with the given name and type or + * NULL if the plugin was not found. + */ +#define gst_default_registry_find_feature(name,type) \ + gst_registry_find_feature (gst_registry_get_default(),name,type) + +/** + * gst_default_registry_find_plugin: + * @name: the plugin name to find + * + * Find the plugin with the given name in the default registry. + * The plugin will be reffed; caller is responsible for unreffing. + * + * Returns: (transfer full): The plugin with the given name or NULL if the + * plugin was not found. + */ +#define gst_default_registry_find_plugin(name) \ + gst_registry_find_plugin (gst_registry_get_default(),name) + +/** + * gst_default_registry_feature_filter: + * @filter: the filter to use + * @first: only return first match + * @user_data: user data passed to the filter function + * + * Runs a filter against all features of the plugins in the default registry + * and returns a GList with the results. + * If the first flag is set, only the first match is + * returned (as a list with a single object). + * + * Returns: (transfer full) (element-type Gst.PluginFeature): a #GList of + * plugin features, gst_plugin_feature_list_free after use. + */ +#define gst_default_registry_feature_filter(filter,first,user_data) \ + gst_registry_feature_filter (gst_registry_get_default(),filter,first,user_data) + +/** + * gst_default_registry_get_feature_list_cookie: + * + * Returns the default registrys feature list cookie. This changes + * every time a feature is added or removed from the registry. + * + * Returns: the feature list cookie. + * + * Since: 0.10.26 + */ +#define gst_default_registry_get_feature_list_cookie() \ + gst_registry_get_feature_list_cookie (gst_registry_get_default()) + +gboolean gst_default_registry_check_feature_version (const gchar *feature_name, + guint min_major, + guint min_minor, + guint min_micro); + +G_END_DECLS + +#endif /* __GST_REGISTRY_H__ */ + diff --git a/gst/gstregistrybinary.c b/gst/gstregistrybinary.c new file mode 100644 index 0000000..85648be --- /dev/null +++ b/gst/gstregistrybinary.c @@ -0,0 +1,631 @@ +/* GStreamer + * Copyright (C) 2006 Josep Torra <josep@fluendo.com> + * 2006 Mathieu Garcia <matthieu@fluendo.com> + * 2006,2007 Stefan Kost <ensonic@users.sf.net> + * 2008 Sebastian Dröge <slomo@circular-chaos.org> + * + * gstregistrybinary.c: GstRegistryBinary object, support routines + * + * 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. + */ + +/* FIXME: + * - keep registry binary blob and reference strings + * - don't free/unmmap contents when leaving gst_registry_binary_read_cache() + * - free at gst_deinit() / _priv_gst_registry_cleanup() ? + * - GstPlugin: + * - GST_PLUGIN_FLAG_CONST + * - GstPluginFeature, GstIndexFactory, GstElementFactory + * - needs Flags (GST_PLUGIN_FEATURE_FLAG_CONST) + * - can we turn loaded into flag? + * - why do we collect a list of binary chunks and not write immediately + * - because we need to process subchunks, before we can set e.g. nr_of_items + * in parent chunk + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <errno.h> +#include <stdio.h> + +#if defined (_MSC_VER) && _MSC_VER >= 1400 +#include <io.h> +#endif + +#include <gst/gst_private.h> +#include <gst/gstconfig.h> +#include <gst/gstelement.h> +#include <gst/gsttypefind.h> +#include <gst/gsttypefindfactory.h> +#include <gst/gsturi.h> +#include <gst/gstinfo.h> +#include <gst/gstenumtypes.h> +#include <gst/gstpadtemplate.h> + +#include <gst/gstregistrychunks.h> +#include <gst/gstregistrybinary.h> + +#include <glib/gstdio.h> /* for g_stat(), g_mapped_file(), ... */ + +#include "glib-compat-private.h" + + +#define GST_CAT_DEFAULT GST_CAT_REGISTRY + +/* reading macros */ +#define unpack_element(inptr, outptr, element, endptr, error_label) G_STMT_START{ \ + if (inptr + sizeof(element) >= endptr) \ + goto error_label; \ + outptr = (element *) inptr; \ + inptr += sizeof (element); \ +}G_STMT_END + +#define ALIGNMENT (sizeof (void *)) +#define alignment(_address) (gsize)_address%ALIGNMENT +#define align(_ptr) _ptr += (( alignment(_ptr) == 0) ? 0 : ALIGNMENT-alignment(_ptr)) + +/* Registry saving */ + +#ifdef G_OS_WIN32 +/* On win32, we can't use g_mkstmp(), because of cross-DLL file I/O problems. + * So, we just create the entire binary registry in memory, then write it out + * with g_file_set_contents(), which creates a temporary file internally + */ + +typedef struct BinaryRegistryCache +{ + const char *location; + guint8 *mem; + gssize len; +} BinaryRegistryCache; + +static BinaryRegistryCache * +gst_registry_binary_cache_init (GstRegistry * registry, const char *location) +{ + BinaryRegistryCache *cache = g_slice_new0 (BinaryRegistryCache); + cache->location = location; + return cache; +} + +static int +gst_registry_binary_cache_write (BinaryRegistryCache * cache, + unsigned long offset, const void *data, int length) +{ + cache->len = MAX (offset + length, cache->len); + cache->mem = g_realloc (cache->mem, cache->len); + + memcpy (cache->mem + offset, data, length); + + return length; +} + +static gboolean +gst_registry_binary_cache_finish (BinaryRegistryCache * cache, gboolean success) +{ + gboolean ret = TRUE; + GError *error = NULL; + if (!g_file_set_contents (cache->location, (const gchar *) cache->mem, + cache->len, &error)) { + /* Probably the directory didn't exist; create it */ + gchar *dir; + dir = g_path_get_dirname (cache->location); + g_mkdir_with_parents (dir, 0777); + g_free (dir); + + g_error_free (error); + error = NULL; + + if (!g_file_set_contents (cache->location, (const gchar *) cache->mem, + cache->len, &error)) { + /* Probably the directory didn't exist; create it */ + gchar *dir; + dir = g_path_get_dirname (cache->location); + g_mkdir_with_parents (dir, 0777); + g_free (dir); + + g_error_free (error); + error = NULL; + + if (!g_file_set_contents (cache->location, (const gchar *) cache->mem, + cache->len, &error)) { + GST_ERROR ("Failed to write to cache file: %s", error->message); + g_error_free (error); + ret = FALSE; + } + } + } + + g_free (cache->mem); + g_slice_free (BinaryRegistryCache, cache); + return ret; +} + +#else +typedef struct BinaryRegistryCache +{ + const char *location; + char *tmp_location; + unsigned long currentoffset; + int cache_fd; +} BinaryRegistryCache; + +static BinaryRegistryCache * +gst_registry_binary_cache_init (GstRegistry * registry, const char *location) +{ + BinaryRegistryCache *cache = g_slice_new0 (BinaryRegistryCache); + + cache->location = location; + cache->tmp_location = g_strconcat (location, ".tmpXXXXXX", NULL); + cache->cache_fd = g_mkstemp (cache->tmp_location); + if (cache->cache_fd == -1) { + int ret; + GStatBuf statbuf; + gchar *dir; + + /* oops, I bet the directory doesn't exist */ + dir = g_path_get_dirname (location); + g_mkdir_with_parents (dir, 0777); + + ret = g_stat (dir, &statbuf); + if (ret != -1 && (statbuf.st_mode & 0700) != 0700) { + g_chmod (dir, 0700); + } + + g_free (dir); + + /* the previous g_mkstemp call overwrote the XXXXXX placeholder ... */ + g_free (cache->tmp_location); + cache->tmp_location = g_strconcat (location, ".tmpXXXXXX", NULL); + cache->cache_fd = g_mkstemp (cache->tmp_location); + + if (cache->cache_fd == -1) { + GST_DEBUG ("g_mkstemp() failed: %s", g_strerror (errno)); + g_free (cache->tmp_location); + g_slice_free (BinaryRegistryCache, cache); + return NULL; + } + + ret = g_stat (cache->tmp_location, &statbuf); + if (ret != -1 && (statbuf.st_mode & 0600) != 0600) { + g_chmod (cache->tmp_location, 0600); + } + } + + return cache; +} + +static int +gst_registry_binary_cache_write (BinaryRegistryCache * cache, + unsigned long offset, const void *data, int length) +{ + long written; + if (offset != cache->currentoffset) { + if (lseek (cache->cache_fd, offset, SEEK_SET) != 0) { + GST_ERROR ("Seeking to new offset failed"); + return FALSE; + } + cache->currentoffset = offset; + } + + written = write (cache->cache_fd, data, length); + if (written != length) { + GST_ERROR ("Failed to write to cache file"); + } + cache->currentoffset += written; + + return written; +} + +static gboolean +gst_registry_binary_cache_finish (BinaryRegistryCache * cache, gboolean success) +{ + /* only fsync if we're actually going to use and rename the file below */ + if (success && fsync (cache->cache_fd) < 0) + goto fsync_failed; + + if (close (cache->cache_fd) < 0) + goto close_failed; + + if (success) { + /* Only do the rename if we wrote the entire file successfully */ + if (g_rename (cache->tmp_location, cache->location) < 0) { + GST_ERROR ("g_rename() failed: %s", g_strerror (errno)); + goto rename_failed; + } + } + + g_free (cache->tmp_location); + g_slice_free (BinaryRegistryCache, cache); + GST_INFO ("Wrote binary registry cache"); + return TRUE; + +/* ERRORS */ +fail_after_close: + { + g_unlink (cache->tmp_location); + g_free (cache->tmp_location); + g_slice_free (BinaryRegistryCache, cache); + return FALSE; + } +fsync_failed: + { + GST_ERROR ("fsync() failed: %s", g_strerror (errno)); + goto fail_after_close; + } +close_failed: + { + GST_ERROR ("close() failed: %s", g_strerror (errno)); + goto fail_after_close; + } +rename_failed: + { + GST_ERROR ("g_rename() failed: %s", g_strerror (errno)); + goto fail_after_close; + } +} +#endif + +/* + * gst_registry_binary_write_chunk: + * + * Write from a memory location to the registry cache file + * + * Returns: %TRUE for success + */ +inline static gboolean +gst_registry_binary_write_chunk (BinaryRegistryCache * cache, + GstRegistryChunk * chunk, unsigned long *file_position) +{ + gchar padder[ALIGNMENT] = { 0, }; + int padsize = 0; + + /* Padding to insert the struct that requiere word alignment */ + if ((chunk->align) && (alignment (*file_position) != 0)) { + padsize = ALIGNMENT - alignment (*file_position); + if (gst_registry_binary_cache_write (cache, *file_position, + padder, padsize) != padsize) { + GST_ERROR ("Failed to write binary registry padder"); + return FALSE; + } + *file_position += padsize; + } + + if (gst_registry_binary_cache_write (cache, *file_position, + chunk->data, chunk->size) != chunk->size) { + GST_ERROR ("Failed to write binary registry element"); + return FALSE; + } + + *file_position += chunk->size; + + return TRUE; +} + + +/* + * gst_registry_binary_initialize_magic: + * + * Initialize the GstBinaryRegistryMagic, setting both our magic number and + * gstreamer major/minor version + */ +inline static gboolean +gst_registry_binary_initialize_magic (GstBinaryRegistryMagic * m) +{ + memset (m, 0, sizeof (GstBinaryRegistryMagic)); + + if (!strncpy (m->magic, GST_MAGIC_BINARY_REGISTRY_STR, + GST_MAGIC_BINARY_REGISTRY_LEN) + || !strncpy (m->version, GST_MAGIC_BINARY_VERSION_STR, + GST_MAGIC_BINARY_VERSION_LEN)) { + GST_ERROR ("Failed to write magic to the registry magic structure"); + return FALSE; + } + + return TRUE; +} + +/** + * gst_registry_binary_write_cache: + * @registry: a #GstRegistry + * @location: a filename + * + * Write the @registry to a cache to file at given @location. + * + * Returns: %TRUE on success. + */ +gboolean +priv_gst_registry_binary_write_cache (GstRegistry * registry, + const char *location) +{ + GList *walk; + GstBinaryRegistryMagic magic; + GList *to_write = NULL; + unsigned long file_position = 0; + BinaryRegistryCache *cache; + + GST_INFO ("Building binary registry cache image"); + + g_return_val_if_fail (GST_IS_REGISTRY (registry), FALSE); + + if (!gst_registry_binary_initialize_magic (&magic)) + goto fail; + + /* iterate trough the list of plugins and fit them into binary structures */ + for (walk = registry->plugins; walk; walk = g_list_next (walk)) { + GstPlugin *plugin = GST_PLUGIN (walk->data); + + if (!plugin->filename) + continue; + + if (plugin->flags & GST_PLUGIN_FLAG_CACHED) { + GStatBuf statbuf; + + if (g_stat (plugin->filename, &statbuf) < 0 || + plugin->file_mtime != statbuf.st_mtime || + plugin->file_size != statbuf.st_size) + continue; + } + + if (!_priv_gst_registry_chunks_save_plugin (&to_write, registry, plugin)) { + GST_ERROR ("Can't write binary plugin information for \"%s\"", + plugin->filename); + } + } + + _priv_gst_registry_chunks_save_global_header (&to_write, registry, + priv_gst_plugin_loading_get_whitelist_hash ()); + + GST_INFO ("Writing binary registry cache"); + + cache = gst_registry_binary_cache_init (registry, location); + if (!cache) + goto fail_free_list; + + /* write magic */ + if (gst_registry_binary_cache_write (cache, file_position, + &magic, sizeof (GstBinaryRegistryMagic)) != + sizeof (GstBinaryRegistryMagic)) { + GST_ERROR ("Failed to write binary registry magic"); + goto fail_free_list; + } + file_position += sizeof (GstBinaryRegistryMagic); + + /* write out data chunks */ + for (walk = to_write; walk; walk = g_list_next (walk)) { + GstRegistryChunk *cur = walk->data; + gboolean res; + + res = gst_registry_binary_write_chunk (cache, cur, &file_position); + + _priv_gst_registry_chunk_free (cur); + walk->data = NULL; + if (!res) + goto fail_free_list; + } + g_list_free (to_write); + + if (!gst_registry_binary_cache_finish (cache, TRUE)) + return FALSE; + + return TRUE; + + /* Errors */ +fail_free_list: + { + for (walk = to_write; walk; walk = g_list_next (walk)) { + GstRegistryChunk *cur = walk->data; + + if (cur) + _priv_gst_registry_chunk_free (cur); + } + g_list_free (to_write); + + if (cache) + (void) gst_registry_binary_cache_finish (cache, FALSE); + /* fall through */ + } +fail: + { + return FALSE; + } +} + + +/* Registry loading */ + +/* + * gst_registry_binary_check_magic: + * + * Check GstBinaryRegistryMagic validity. + * Return < 0 if something is wrong, -2 means + * that just the version of the registry is out of + * date, -1 is a general failure. + */ +static gint +gst_registry_binary_check_magic (gchar ** in, gsize size) +{ + GstBinaryRegistryMagic *m; + + align (*in); + GST_DEBUG ("Reading/casting for GstBinaryRegistryMagic at address %p", *in); + unpack_element (*in, m, GstBinaryRegistryMagic, (*in + size), fail); + + if (strncmp (m->magic, GST_MAGIC_BINARY_REGISTRY_STR, + GST_MAGIC_BINARY_REGISTRY_LEN) != 0) { + GST_WARNING + ("Binary registry magic is different : %02x%02x%02x%02x != %02x%02x%02x%02x", + GST_MAGIC_BINARY_REGISTRY_STR[0] & 0xff, + GST_MAGIC_BINARY_REGISTRY_STR[1] & 0xff, + GST_MAGIC_BINARY_REGISTRY_STR[2] & 0xff, + GST_MAGIC_BINARY_REGISTRY_STR[3] & 0xff, m->magic[0] & 0xff, + m->magic[1] & 0xff, m->magic[2] & 0xff, m->magic[3] & 0xff); + return -1; + } + if (strncmp (m->version, GST_MAGIC_BINARY_VERSION_STR, + GST_MAGIC_BINARY_VERSION_LEN)) { + GST_WARNING ("Binary registry magic version is different : %s != %s", + GST_MAGIC_BINARY_VERSION_STR, m->version); + return -2; + } + + return 0; + +fail: + GST_WARNING ("Not enough data for binary registry magic structure"); + return -1; +} + +/** + * gst_registry_binary_read_cache: + * @registry: a #GstRegistry + * @location: a filename + * + * Read the contents of the binary cache file at @location into @registry. + * + * Returns: %TRUE on success. + */ +gboolean +priv_gst_registry_binary_read_cache (GstRegistry * registry, + const char *location) +{ + GMappedFile *mapped = NULL; + gchar *contents = NULL; + gchar *in = NULL; + gsize size; + GError *err = NULL; + gboolean res = FALSE; + guint32 filter_env_hash = 0; + gint check_magic_result; +#ifndef GST_DISABLE_GST_DEBUG + GTimer *timer = NULL; + gdouble seconds; +#endif + + /* make sure these types exist */ + GST_TYPE_ELEMENT_FACTORY; + GST_TYPE_TYPE_FIND_FACTORY; + GST_TYPE_INDEX_FACTORY; + +#ifndef GST_DISABLE_GST_DEBUG + timer = g_timer_new (); +#endif + + mapped = g_mapped_file_new (location, FALSE, &err); + if (G_UNLIKELY (err != NULL)) { + GST_INFO ("Unable to mmap file %s : %s", location, err->message); + g_error_free (err); + err = NULL; + } + + if (mapped == NULL) { + /* Error mmap-ing the cache, try a plain memory read */ + + g_file_get_contents (location, &contents, &size, &err); + if (err != NULL) { + GST_INFO ("Unable to read file %s : %s", location, err->message); +#ifndef GST_DISABLE_GST_DEBUG + g_timer_destroy (timer); +#endif + g_error_free (err); + return FALSE; + } + } else { + /* This can't fail if g_mapped_file_new() succeeded */ + contents = g_mapped_file_get_contents (mapped); + size = g_mapped_file_get_length (mapped); + } + + /* in is a cursor pointer, we initialize it with the begin of registry and is updated on each read */ + in = contents; + GST_DEBUG ("File data at address %p", in); + if (G_UNLIKELY (size < sizeof (GstBinaryRegistryMagic))) { + GST_ERROR ("No or broken registry header for file at %s", location); + goto Error; + } + + /* check if header is valid */ + if (G_UNLIKELY ((check_magic_result = + gst_registry_binary_check_magic (&in, size)) < 0)) { + + if (check_magic_result == -1) + GST_ERROR + ("Binary registry type not recognized (invalid magic) for file at %s", + location); + goto Error; + } + + if (!_priv_gst_registry_chunks_load_global_header (registry, &in, + contents + size, &filter_env_hash)) { + GST_ERROR ("Couldn't read global header chunk"); + goto Error; + } + + if (filter_env_hash != priv_gst_plugin_loading_get_whitelist_hash ()) { + GST_INFO_OBJECT (registry, "Plugin loading filter environment changed, " + "ignoring plugin cache to force update with new filter environment"); + goto done; + } + + /* check if there are plugins in the file */ + if (G_UNLIKELY (!(((gsize) in + sizeof (GstRegistryChunkPluginElement)) < + (gsize) contents + size))) { + GST_INFO ("No binary plugins structure to read"); + /* empty file, this is not an error */ + } else { + gchar *end = contents + size; + /* read as long as we still have space for a GstRegistryChunkPluginElement */ + for (; + ((gsize) in + sizeof (GstRegistryChunkPluginElement)) < + (gsize) contents + size;) { + GST_DEBUG ("reading binary registry %" G_GSIZE_FORMAT "(%x)/%" + G_GSIZE_FORMAT, (gsize) in - (gsize) contents, + (guint) ((gsize) in - (gsize) contents), size); + if (!_priv_gst_registry_chunks_load_plugin (registry, &in, end, NULL)) { + GST_ERROR ("Problem while reading binary registry %s", location); + goto Error; + } + } + } + +done: + +#ifndef GST_DISABLE_GST_DEBUG + g_timer_stop (timer); + seconds = g_timer_elapsed (timer, NULL); +#endif + + GST_INFO ("loaded %s in %lf seconds", location, seconds); + + res = TRUE; + /* TODO: once we re-use the pointers to registry contents, return here */ + +Error: +#ifndef GST_DISABLE_GST_DEBUG + g_timer_destroy (timer); +#endif + if (mapped) { + g_mapped_file_unref (mapped); + } else { + g_free (contents); + } + return res; +} diff --git a/gst/gstregistrybinary.h b/gst/gstregistrybinary.h new file mode 100644 index 0000000..aa2c6d3 --- /dev/null +++ b/gst/gstregistrybinary.h @@ -0,0 +1,76 @@ +/* GStreamer + * Copyright (C) 2006 Josep Torra <josep@fluendo.com> + * Copyright (C) 2006 Mathieu Garcia <matthieu@fluendo.com> + * Copyright (C) 2006 Stefan Kost <ensonic@sonicpulse.de> + * + * gstregistrybinary.h: Header for registry handling + * + * 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. + */ + +/* SUGGESTIONS AND TODO : +** ==================== +** - Use a compressed registry, but would induce performance loss +** - Encrypt the registry, for security purpose, but would also reduce performances +*/ + +#ifndef __GST_REGISTRYBINARY_H__ +#define __GST_REGISTRYBINARY_H__ + +#include <gst/gstpad.h> +#include <gst/gstregistry.h> + +G_BEGIN_DECLS + +/* + * GST_MAGIC_BINARY_REGISTRY_STR: + * + * A tag, written at the beginning of the file + */ +#define GST_MAGIC_BINARY_REGISTRY_STR "\xc0\xde\xf0\x0d" +/* + * GST_MAGIC_BINARY_REGISTRY_LEN: + * + * length of the header tag. + */ +#define GST_MAGIC_BINARY_REGISTRY_LEN (4) + +/* + * GST_MAGIC_BINARY_VERSION_STR: + * + * The current version of the binary registry format. + * This _must_ be updated whenever the registry format changes, + * we currently use the core version where this change happened. + */ +#define GST_MAGIC_BINARY_VERSION_STR "0.10.30.1" + +/* + * GST_MAGIC_BINARY_VERSION_LEN: + * + * Maximum length of the version string in the header. + */ +#define GST_MAGIC_BINARY_VERSION_LEN (64) + +typedef struct _GstBinaryRegistryMagic +{ + gchar magic[GST_MAGIC_BINARY_REGISTRY_LEN]; + gchar version[GST_MAGIC_BINARY_VERSION_LEN]; +} GstBinaryRegistryMagic; + +G_END_DECLS + +#endif /* !__GST_REGISTRYBINARY_H__ */ + diff --git a/gst/gstregistrychunks.c b/gst/gstregistrychunks.c new file mode 100644 index 0000000..bc1ca6b --- /dev/null +++ b/gst/gstregistrychunks.c @@ -0,0 +1,904 @@ +/* GStreamer + * Copyright (C) 2006 Josep Torra <josep@fluendo.com> + * 2006 Mathieu Garcia <matthieu@fluendo.com> + * 2006,2007 Stefan Kost <ensonic@users.sf.net> + * 2008 Sebastian Dröge <slomo@circular-chaos.org> + * 2008 Jan Schmidt <jan.schmidt@sun.com> + * + * gstregistrychunks.c: GstRegistryChunk helper for serialising/deserialising + * plugin entries and features. + * + * 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_private.h> +#include <gst/gstconfig.h> +#include <gst/gstelement.h> +#include <gst/gsttypefind.h> +#include <gst/gsttypefindfactory.h> +#include <gst/gsturi.h> +#include <gst/gstinfo.h> +#include <gst/gstenumtypes.h> +#include <gst/gstpadtemplate.h> + +#include <gst/gstregistrychunks.h> + +#define GST_CAT_DEFAULT GST_CAT_REGISTRY + +/* count string length, but return -1 if we hit the eof */ +static gint +_strnlen (const gchar * str, gint maxlen) +{ + gint len = 0; + + while (G_LIKELY (len < maxlen)) { + if (G_UNLIKELY (str[len] == '\0')) + return len; + len++; + } + return -1; +} + +/* Macros */ +#define unpack_element(inptr, outptr, element, endptr, error_label) G_STMT_START{ \ + if (inptr + sizeof(element) > endptr) { \ + GST_ERROR ("Failed reading element " G_STRINGIFY (element) \ + ". Have %d bytes need %" G_GSSIZE_FORMAT, \ + (int) (endptr - inptr), sizeof(element)); \ + goto error_label; \ + } \ + outptr = (element *) inptr; \ + inptr += sizeof (element); \ +}G_STMT_END + +#define unpack_const_string(inptr, outptr, endptr, error_label) G_STMT_START{\ + gint _len = _strnlen (inptr, (endptr-inptr)); \ + if (_len == -1) \ + goto error_label; \ + outptr = g_intern_string ((const gchar *)inptr); \ + inptr += _len + 1; \ +}G_STMT_END + +#define unpack_string(inptr, outptr, endptr, error_label) G_STMT_START{\ + gint _len = _strnlen (inptr, (endptr-inptr)); \ + if (_len == -1) \ + goto error_label; \ + outptr = g_memdup ((gconstpointer)inptr, _len + 1); \ + inptr += _len + 1; \ +}G_STMT_END + +#define unpack_string_nocopy(inptr, outptr, endptr, error_label) G_STMT_START{\ + gint _len = _strnlen (inptr, (endptr-inptr)); \ + if (_len == -1) \ + goto error_label; \ + outptr = (const gchar *)inptr; \ + inptr += _len + 1; \ +}G_STMT_END + +#define ALIGNMENT (sizeof (void *)) +#define alignment(_address) (gsize)_address%ALIGNMENT +#define align(_ptr) _ptr += (( alignment(_ptr) == 0) ? 0 : ALIGNMENT-alignment(_ptr)) + +void +_priv_gst_registry_chunk_free (GstRegistryChunk * chunk) +{ + if (!(chunk->flags & GST_REGISTRY_CHUNK_FLAG_CONST)) { + if ((chunk->flags & GST_REGISTRY_CHUNK_FLAG_MALLOC)) + g_free (chunk->data); + else + g_slice_free1 (chunk->size, chunk->data); + } + g_slice_free (GstRegistryChunk, chunk); +} + +/* + * gst_registry_chunks_save_const_string: + * + * Store a const string in a binary chunk. + * + * Returns: %TRUE for success + */ +inline static gboolean +gst_registry_chunks_save_const_string (GList ** list, const gchar * str) +{ + GstRegistryChunk *chunk; + + if (G_UNLIKELY (str == NULL)) { + GST_ERROR ("unexpected NULL string in plugin or plugin feature data"); + str = ""; + } + + chunk = g_slice_new (GstRegistryChunk); + chunk->data = (gpointer) str; + chunk->size = strlen ((gchar *) chunk->data) + 1; + chunk->flags = GST_REGISTRY_CHUNK_FLAG_CONST; + chunk->align = FALSE; + *list = g_list_prepend (*list, chunk); + return TRUE; +} + +/* + * gst_registry_chunks_save_string: + * + * Store a string in a binary chunk. + * + * Returns: %TRUE for success + */ +inline static gboolean +gst_registry_chunks_save_string (GList ** list, gchar * str) +{ + GstRegistryChunk *chunk; + + chunk = g_slice_new (GstRegistryChunk); + chunk->data = str; + chunk->size = strlen ((gchar *) chunk->data) + 1; + chunk->flags = GST_REGISTRY_CHUNK_FLAG_MALLOC; + chunk->align = FALSE; + *list = g_list_prepend (*list, chunk); + return TRUE; +} + +/* + * gst_registry_chunks_save_data: + * + * Store some data in a binary chunk. + * + * Returns: the initialized chunk + */ +inline static GstRegistryChunk * +gst_registry_chunks_make_data (gpointer data, gulong size) +{ + GstRegistryChunk *chunk; + + chunk = g_slice_new (GstRegistryChunk); + chunk->data = data; + chunk->size = size; + chunk->flags = GST_REGISTRY_CHUNK_FLAG_NONE; + chunk->align = TRUE; + return chunk; +} + + +/* + * gst_registry_chunks_save_pad_template: + * + * Store pad_templates in binary chunks. + * + * Returns: %TRUE for success + */ +static gboolean +gst_registry_chunks_save_pad_template (GList ** list, + GstStaticPadTemplate * template) +{ + GstRegistryChunkPadTemplate *pt; + GstRegistryChunk *chk; + + pt = g_slice_new (GstRegistryChunkPadTemplate); + chk = + gst_registry_chunks_make_data (pt, sizeof (GstRegistryChunkPadTemplate)); + + pt->presence = template->presence; + pt->direction = template->direction; + + /* pack pad template strings */ + gst_registry_chunks_save_const_string (list, + (gchar *) (template->static_caps.string)); + gst_registry_chunks_save_const_string (list, template->name_template); + + *list = g_list_prepend (*list, chk); + + return TRUE; +} + +#define VALIDATE_UTF8(__details, __entry) \ +G_STMT_START { \ + if (!g_utf8_validate (__details->__entry, -1, NULL)) { \ + g_warning ("Invalid UTF-8 in " G_STRINGIFY (__entry) ": %s", \ + __details->__entry); \ + g_free (__details->__entry); \ + __details->__entry = g_strdup ("[ERROR: invalid UTF-8]"); \ + } \ +} G_STMT_END + +/* + * gst_registry_chunks_save_feature: + * + * Store features in binary chunks. + * + * Returns: %TRUE for success + */ +static gboolean +gst_registry_chunks_save_feature (GList ** list, GstPluginFeature * feature) +{ + const gchar *type_name = g_type_name (G_OBJECT_TYPE (feature)); + GstRegistryChunkPluginFeature *pf = NULL; + GstRegistryChunk *chk = NULL; + GList *walk; + + if (!type_name) { + GST_ERROR ("NULL feature type_name, aborting."); + return FALSE; + } + + if (GST_IS_ELEMENT_FACTORY (feature)) { + GstRegistryChunkElementFactory *ef; + GstElementFactory *factory = GST_ELEMENT_FACTORY (feature); + + /* Initialize with zeroes because of struct padding and + * valgrind complaining about copying unitialized memory + */ + ef = g_slice_new0 (GstRegistryChunkElementFactory); + chk = + gst_registry_chunks_make_data (ef, + sizeof (GstRegistryChunkElementFactory)); + ef->npadtemplates = ef->ninterfaces = ef->nuriprotocols = 0; + pf = (GstRegistryChunkPluginFeature *) ef; + + /* save interfaces */ + for (walk = factory->interfaces; walk; + walk = g_list_next (walk), ef->ninterfaces++) { + gst_registry_chunks_save_const_string (list, (gchar *) walk->data); + } + GST_DEBUG ("Feature %s: saved %d interfaces %d pad templates", + GST_OBJECT_NAME (feature), ef->ninterfaces, ef->npadtemplates); + + /* save uritypes */ + if (GST_URI_TYPE_IS_VALID (factory->uri_type)) { + if (factory->uri_protocols && *factory->uri_protocols) { + GstRegistryChunk *subchk; + gchar **protocol; + + subchk = + gst_registry_chunks_make_data (&factory->uri_type, + sizeof (factory->uri_type)); + subchk->flags = GST_REGISTRY_CHUNK_FLAG_CONST; + + protocol = factory->uri_protocols; + while (*protocol) { + gst_registry_chunks_save_const_string (list, *protocol++); + ef->nuriprotocols++; + } + *list = g_list_prepend (*list, subchk); + GST_DEBUG ("Saved %d UriTypes", ef->nuriprotocols); + } else { + g_warning ("GStreamer feature '%s' is URI handler but does not provide" + " any protocols it can handle", GST_OBJECT_NAME (feature)); + } + } + + /* save pad-templates */ + for (walk = factory->staticpadtemplates; walk; + walk = g_list_next (walk), ef->npadtemplates++) { + GstStaticPadTemplate *template = walk->data; + + if (!gst_registry_chunks_save_pad_template (list, template)) { + GST_ERROR ("Can't fill pad template, aborting."); + goto fail; + } + } + + /* pack element metadata strings */ + gst_registry_chunks_save_string (list, + gst_structure_to_string (factory->metadata)); + } else if (GST_IS_TYPE_FIND_FACTORY (feature)) { + GstRegistryChunkTypeFindFactory *tff; + GstTypeFindFactory *factory = GST_TYPE_FIND_FACTORY (feature); + gchar *str; + + /* Initialize with zeroes because of struct padding and + * valgrind complaining about copying unitialized memory + */ + tff = g_slice_new0 (GstRegistryChunkTypeFindFactory); + chk = + gst_registry_chunks_make_data (tff, + sizeof (GstRegistryChunkTypeFindFactory)); + tff->nextensions = 0; + pf = (GstRegistryChunkPluginFeature *) tff; + + /* save extensions */ + if (factory->extensions) { + while (factory->extensions[tff->nextensions]) { + gst_registry_chunks_save_const_string (list, + factory->extensions[tff->nextensions++]); + } + } + /* save caps */ + if (factory->caps) { + /* we copy the caps here so we can simplify them before saving. This + * is a lot faster when loading them later on */ + GstCaps *copy = gst_caps_copy (factory->caps); + + gst_caps_do_simplify (copy); + str = gst_caps_to_string (copy); + gst_caps_unref (copy); + gst_registry_chunks_save_string (list, str); + } else { + gst_registry_chunks_save_const_string (list, ""); + } + } else if (GST_IS_INDEX_FACTORY (feature)) { + GstIndexFactory *factory = GST_INDEX_FACTORY (feature); + + pf = g_slice_new (GstRegistryChunkPluginFeature); + chk = + gst_registry_chunks_make_data (pf, + sizeof (GstRegistryChunkPluginFeature)); + + /* pack element factory strings */ + gst_registry_chunks_save_const_string (list, factory->longdesc); + } else { + GST_WARNING ("unhandled feature type '%s'", type_name); + } + + if (pf) { + pf->rank = feature->rank; + *list = g_list_prepend (*list, chk); + + /* pack plugin feature strings */ + gst_registry_chunks_save_const_string (list, GST_OBJECT_NAME (feature)); + gst_registry_chunks_save_const_string (list, (gchar *) type_name); + + return TRUE; + } + + /* Errors */ +fail: + g_free (chk); + g_free (pf); + return FALSE; +} + +static gboolean +gst_registry_chunks_save_plugin_dep (GList ** list, GstPluginDep * dep) +{ + GstRegistryChunkDep *ed; + GstRegistryChunk *chk; + gchar **s; + + ed = g_slice_new (GstRegistryChunkDep); + chk = gst_registry_chunks_make_data (ed, sizeof (GstRegistryChunkDep)); + + ed->flags = dep->flags; + ed->n_env_vars = 0; + ed->n_paths = 0; + ed->n_names = 0; + + ed->env_hash = dep->env_hash; + ed->stat_hash = dep->stat_hash; + + for (s = dep->env_vars; s != NULL && *s != NULL; ++s, ++ed->n_env_vars) + gst_registry_chunks_save_string (list, g_strdup (*s)); + + for (s = dep->paths; s != NULL && *s != NULL; ++s, ++ed->n_paths) + gst_registry_chunks_save_string (list, g_strdup (*s)); + + for (s = dep->names; s != NULL && *s != NULL; ++s, ++ed->n_names) + gst_registry_chunks_save_string (list, g_strdup (*s)); + + *list = g_list_prepend (*list, chk); + + GST_LOG ("Saved external plugin dependency"); + return TRUE; +} + +/* + * _priv_gst_registry_chunks_save_plugin: + * + * Adapt a GstPlugin to our GstRegistryChunkPluginElement structure, and + * prepend it as a GstRegistryChunk in the provided list. + * + */ +gboolean +_priv_gst_registry_chunks_save_plugin (GList ** list, GstRegistry * registry, + GstPlugin * plugin) +{ + GstRegistryChunkPluginElement *pe; + GstRegistryChunk *chk; + GList *plugin_features = NULL; + GList *walk; + + pe = g_slice_new (GstRegistryChunkPluginElement); + chk = + gst_registry_chunks_make_data (pe, + sizeof (GstRegistryChunkPluginElement)); + + pe->file_size = plugin->file_size; + pe->file_mtime = plugin->file_mtime; + pe->nfeatures = 0; + pe->n_deps = 0; + + /* pack external deps */ + for (walk = plugin->priv->deps; walk != NULL; walk = walk->next) { + if (!gst_registry_chunks_save_plugin_dep (list, walk->data)) { + GST_ERROR ("Could not save external plugin dependency, aborting."); + goto fail; + } + ++pe->n_deps; + } + + /* pack plugin features */ + plugin_features = + gst_registry_get_feature_list_by_plugin (registry, plugin->desc.name); + for (walk = plugin_features; walk; walk = g_list_next (walk), pe->nfeatures++) { + GstPluginFeature *feature = GST_PLUGIN_FEATURE (walk->data); + + if (!gst_registry_chunks_save_feature (list, feature)) { + GST_ERROR ("Can't fill plugin feature, aborting."); + goto fail; + } + } + + gst_plugin_feature_list_free (plugin_features); + + /* pack cache data */ + if (plugin->priv->cache_data) { + gchar *cache_str = gst_structure_to_string (plugin->priv->cache_data); + gst_registry_chunks_save_string (list, cache_str); + } else { + gst_registry_chunks_save_const_string (list, ""); + } + + /* pack plugin element strings */ + gst_registry_chunks_save_const_string (list, + (plugin->desc.release_datetime) ? plugin->desc.release_datetime : ""); + gst_registry_chunks_save_const_string (list, plugin->desc.origin); + gst_registry_chunks_save_const_string (list, plugin->desc.package); + gst_registry_chunks_save_const_string (list, plugin->desc.source); + gst_registry_chunks_save_const_string (list, plugin->desc.license); + gst_registry_chunks_save_const_string (list, plugin->desc.version); + gst_registry_chunks_save_const_string (list, plugin->filename); + gst_registry_chunks_save_const_string (list, plugin->desc.description); + gst_registry_chunks_save_const_string (list, plugin->desc.name); + + *list = g_list_prepend (*list, chk); + + GST_DEBUG ("Found %d features in plugin \"%s\"", pe->nfeatures, + plugin->desc.name); + return TRUE; + + /* Errors */ +fail: + gst_plugin_feature_list_free (plugin_features); + g_free (chk); + g_free (pe); + return FALSE; +} + +/* + * gst_registry_chunks_load_pad_template: + * + * Make a new GstStaticPadTemplate from current GstRegistryChunkPadTemplate + * structure. + * + * Returns: new GstStaticPadTemplate + */ +static gboolean +gst_registry_chunks_load_pad_template (GstElementFactory * factory, gchar ** in, + gchar * end) +{ + GstRegistryChunkPadTemplate *pt; + GstStaticPadTemplate *template = NULL; + + align (*in); + GST_DEBUG ("Reading/casting for GstRegistryChunkPadTemplate at address %p", + *in); + unpack_element (*in, pt, GstRegistryChunkPadTemplate, end, fail); + + template = g_slice_new (GstStaticPadTemplate); + template->presence = pt->presence; + template->direction = (GstPadDirection) pt->direction; + template->static_caps.caps.mini_object.refcount = 0; + + /* unpack pad template strings */ + unpack_const_string (*in, template->name_template, end, fail); + unpack_const_string (*in, template->static_caps.string, end, fail); + + __gst_element_factory_add_static_pad_template (factory, template); + GST_DEBUG ("Added pad_template %s", template->name_template); + + return TRUE; +fail: + GST_INFO ("Reading pad template failed"); + if (template) + g_slice_free (GstStaticPadTemplate, template); + return FALSE; +} + +/* + * gst_registry_chunks_load_feature: + * + * Make a new GstPluginFeature from current binary plugin feature structure + * + * Returns: new GstPluginFeature + */ +static gboolean +gst_registry_chunks_load_feature (GstRegistry * registry, gchar ** in, + gchar * end, GstPlugin * plugin) +{ + GstRegistryChunkPluginFeature *pf = NULL; + GstPluginFeature *feature = NULL; + const gchar *const_str, *type_name; + const gchar *feature_name; + const gchar *plugin_name; + gchar *str; + GType type; + guint i; + + plugin_name = plugin->desc.name; + + /* unpack plugin feature strings */ + unpack_string_nocopy (*in, type_name, end, fail); + + if (G_UNLIKELY (!type_name)) { + GST_ERROR ("No feature type name"); + return FALSE; + } + + /* unpack more plugin feature strings */ + unpack_string_nocopy (*in, feature_name, end, fail); + + GST_DEBUG ("Plugin '%s' feature '%s' typename : '%s'", plugin_name, + feature_name, type_name); + + if (G_UNLIKELY (!(type = g_type_from_name (type_name)))) { + GST_ERROR ("Unknown type from typename '%s' for plugin '%s'", type_name, + plugin_name); + return FALSE; + } + if (G_UNLIKELY ((feature = g_object_newv (type, 0, NULL)) == NULL)) { + GST_ERROR ("Can't create feature from type"); + return FALSE; + } + gst_plugin_feature_set_name (feature, feature_name); + + if (G_UNLIKELY (!GST_IS_PLUGIN_FEATURE (feature))) { + GST_ERROR ("typename : '%s' is not a plugin feature", type_name); + goto fail; + } + + if (GST_IS_ELEMENT_FACTORY (feature)) { + GstRegistryChunkElementFactory *ef; + guint n; + GstElementFactory *factory = GST_ELEMENT_FACTORY_CAST (feature); + gchar *str; + const gchar *meta_data_str; + + align (*in); + GST_LOG ("Reading/casting for GstRegistryChunkElementFactory at address %p", + *in); + unpack_element (*in, ef, GstRegistryChunkElementFactory, end, fail); + pf = (GstRegistryChunkPluginFeature *) ef; + + /* unpack element factory strings */ + unpack_string_nocopy (*in, meta_data_str, end, fail); + if (meta_data_str && *meta_data_str) { + factory->metadata = gst_structure_from_string (meta_data_str, NULL); + if (!factory->metadata) { + GST_ERROR + ("Error when trying to deserialize structure for metadata '%s'", + meta_data_str); + goto fail; + } + } + n = ef->npadtemplates; + GST_DEBUG ("Element factory : npadtemplates=%d", n); + + /* load pad templates */ + for (i = 0; i < n; i++) { + if (G_UNLIKELY (!gst_registry_chunks_load_pad_template (factory, in, + end))) { + GST_ERROR ("Error while loading binary pad template"); + goto fail; + } + } + + /* load uritypes */ + if (G_UNLIKELY ((n = ef->nuriprotocols))) { + GST_DEBUG ("Reading %d UriTypes at address %p", n, *in); + + align (*in); + factory->uri_type = *((guint *) * in); + *in += sizeof (factory->uri_type); + /*unpack_element(*in, &factory->uri_type, factory->uri_type, end, fail); */ + + factory->uri_protocols = g_new0 (gchar *, n + 1); + for (i = 0; i < n; i++) { + unpack_string (*in, str, end, fail); + factory->uri_protocols[i] = str; + } + } + /* load interfaces */ + if (G_UNLIKELY ((n = ef->ninterfaces))) { + GST_DEBUG ("Reading %d Interfaces at address %p", n, *in); + for (i = 0; i < n; i++) { + unpack_string_nocopy (*in, const_str, end, fail); + __gst_element_factory_add_interface (factory, const_str); + } + } + } else if (GST_IS_TYPE_FIND_FACTORY (feature)) { + GstRegistryChunkTypeFindFactory *tff; + GstTypeFindFactory *factory = GST_TYPE_FIND_FACTORY (feature); + + align (*in); + GST_DEBUG + ("Reading/casting for GstRegistryChunkPluginFeature at address %p", + *in); + unpack_element (*in, tff, GstRegistryChunkTypeFindFactory, end, fail); + pf = (GstRegistryChunkPluginFeature *) tff; + + /* load typefinder caps */ + unpack_string_nocopy (*in, const_str, end, fail); + if (const_str != NULL && *const_str != '\0') + factory->caps = gst_caps_from_string (const_str); + else + factory->caps = NULL; + + /* load extensions */ + if (tff->nextensions) { + GST_DEBUG ("Reading %d Typefind extensions at address %p", + tff->nextensions, *in); + factory->extensions = g_new0 (gchar *, tff->nextensions + 1); + /* unpack in reverse order to maintain the correct order */ + for (i = tff->nextensions; i > 0; i--) { + unpack_string (*in, str, end, fail); + factory->extensions[i - 1] = str; + } + } + } else if (GST_IS_INDEX_FACTORY (feature)) { + GstIndexFactory *factory = GST_INDEX_FACTORY (feature); + + align (*in); + GST_DEBUG + ("Reading/casting for GstRegistryChunkPluginFeature at address %p", + *in); + unpack_element (*in, pf, GstRegistryChunkPluginFeature, end, fail); + + /* unpack index factory strings */ + unpack_string (*in, factory->longdesc, end, fail); + } else { + GST_WARNING ("unhandled factory type : %s", G_OBJECT_TYPE_NAME (feature)); + goto fail; + } + + feature->rank = pf->rank; + + feature->plugin_name = plugin_name; + feature->plugin = plugin; + g_object_add_weak_pointer ((GObject *) plugin, + (gpointer *) & feature->plugin); + + gst_registry_add_feature (registry, feature); + GST_DEBUG ("Added feature %s, plugin %p %s", GST_OBJECT_NAME (feature), + plugin, plugin_name); + + return TRUE; + + /* Errors */ +fail: + GST_INFO ("Reading plugin feature failed"); + if (feature) { + if (GST_IS_OBJECT (feature)) + gst_object_unref (feature); + else + g_object_unref (feature); + } + return FALSE; +} + +static gchar ** +gst_registry_chunks_load_plugin_dep_strv (gchar ** in, gchar * end, guint n) +{ + gchar **arr; + + if (n == 0) + return NULL; + + arr = g_new0 (gchar *, n + 1); + while (n > 0) { + unpack_string (*in, arr[n - 1], end, fail); + --n; + } + return arr; +fail: + GST_INFO ("Reading plugin dependency strings failed"); + return NULL; +} + +static gboolean +gst_registry_chunks_load_plugin_dep (GstPlugin * plugin, gchar ** in, + gchar * end) +{ + GstPluginDep *dep; + GstRegistryChunkDep *d; + gchar **s; + + align (*in); + GST_LOG_OBJECT (plugin, "Unpacking GstRegistryChunkDep from %p", *in); + unpack_element (*in, d, GstRegistryChunkDep, end, fail); + + dep = g_slice_new (GstPluginDep); + + dep->env_hash = d->env_hash; + dep->stat_hash = d->stat_hash; + + dep->flags = (GstPluginDependencyFlags) d->flags; + + dep->names = gst_registry_chunks_load_plugin_dep_strv (in, end, d->n_names); + dep->paths = gst_registry_chunks_load_plugin_dep_strv (in, end, d->n_paths); + dep->env_vars = + gst_registry_chunks_load_plugin_dep_strv (in, end, d->n_env_vars); + + plugin->priv->deps = g_list_append (plugin->priv->deps, dep); + + GST_DEBUG_OBJECT (plugin, "Loaded external plugin dependency from registry: " + "env_hash: %08x, stat_hash: %08x", dep->env_hash, dep->stat_hash); + for (s = dep->env_vars; s != NULL && *s != NULL; ++s) + GST_LOG_OBJECT (plugin, " evar: %s", *s); + for (s = dep->paths; s != NULL && *s != NULL; ++s) + GST_LOG_OBJECT (plugin, " path: %s", *s); + for (s = dep->names; s != NULL && *s != NULL; ++s) + GST_LOG_OBJECT (plugin, " name: %s", *s); + + return TRUE; +fail: + GST_INFO ("Reading plugin dependency failed"); + return FALSE; +} + + +/* + * _priv_gst_registry_chunks_load_plugin: + * + * Make a new GstPlugin from current GstRegistryChunkPluginElement structure + * and add it to the GstRegistry. Return an offset to the next + * GstRegistryChunkPluginElement structure. + */ +gboolean +_priv_gst_registry_chunks_load_plugin (GstRegistry * registry, gchar ** in, + gchar * end, GstPlugin ** out_plugin) +{ +#ifndef GST_DISABLE_GST_DEBUG + gchar *start = *in; +#endif + GstRegistryChunkPluginElement *pe; + const gchar *cache_str = NULL; + GstPlugin *plugin = NULL; + guint i, n; + + align (*in); + GST_LOG ("Reading/casting for GstRegistryChunkPluginElement at address %p", + *in); + unpack_element (*in, pe, GstRegistryChunkPluginElement, end, fail); + + plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL); + + /* TODO: also set GST_PLUGIN_FLAG_CONST */ + plugin->flags |= GST_PLUGIN_FLAG_CACHED; + plugin->file_mtime = pe->file_mtime; + plugin->file_size = pe->file_size; + + /* unpack plugin element strings */ + unpack_const_string (*in, plugin->desc.name, end, fail); + unpack_const_string (*in, plugin->desc.description, end, fail); + unpack_string (*in, plugin->filename, end, fail); + unpack_const_string (*in, plugin->desc.version, end, fail); + unpack_const_string (*in, plugin->desc.license, end, fail); + unpack_const_string (*in, plugin->desc.source, end, fail); + unpack_const_string (*in, plugin->desc.package, end, fail); + unpack_const_string (*in, plugin->desc.origin, end, fail); + unpack_const_string (*in, plugin->desc.release_datetime, end, fail); + + GST_LOG ("read strings for name='%s'", plugin->desc.name); + GST_LOG (" desc.description='%s'", plugin->desc.description); + GST_LOG (" filename='%s'", plugin->filename); + GST_LOG (" desc.version='%s'", plugin->desc.version); + GST_LOG (" desc.license='%s'", plugin->desc.license); + GST_LOG (" desc.source='%s'", plugin->desc.source); + GST_LOG (" desc.package='%s'", plugin->desc.package); + GST_LOG (" desc.origin='%s'", plugin->desc.origin); + GST_LOG (" desc.datetime=%s", plugin->desc.release_datetime); + + if (plugin->desc.release_datetime[0] == '\0') + plugin->desc.release_datetime = NULL; + + /* unpack cache data */ + unpack_string_nocopy (*in, cache_str, end, fail); + if (cache_str != NULL && *cache_str != '\0') + plugin->priv->cache_data = gst_structure_from_string (cache_str, NULL); + + /* If the license string is 'BLACKLIST', mark this as a blacklisted + * plugin */ + if (strcmp (plugin->desc.license, "BLACKLIST") == 0) + plugin->flags |= GST_PLUGIN_FLAG_BLACKLISTED; + + plugin->basename = g_path_get_basename (plugin->filename); + + /* Takes ownership of plugin */ + gst_registry_add_plugin (registry, plugin); + n = pe->nfeatures; + GST_DEBUG ("Added plugin '%s' plugin with %d features from binary registry", + plugin->desc.name, n); + + /* Load plugin features */ + for (i = 0; i < n; i++) { + if (G_UNLIKELY (!gst_registry_chunks_load_feature (registry, in, end, + plugin))) { + GST_ERROR ("Error while loading binary feature for plugin '%s'", + GST_STR_NULL (plugin->desc.name)); + gst_registry_remove_plugin (registry, plugin); + goto fail; + } + } + + /* Load external plugin dependencies */ + for (i = 0; i < pe->n_deps; ++i) { + if (G_UNLIKELY (!gst_registry_chunks_load_plugin_dep (plugin, in, end))) { + GST_ERROR_OBJECT (plugin, "Could not read external plugin dependency " + "for plugin '%s'", GST_STR_NULL (plugin->desc.name)); + gst_registry_remove_plugin (registry, plugin); + goto fail; + } + } + + if (out_plugin) + *out_plugin = plugin; + + return TRUE; + + /* Errors */ +fail: + GST_INFO ("Reading plugin failed after %u bytes", (guint) (end - start)); + return FALSE; +} + +void +_priv_gst_registry_chunks_save_global_header (GList ** list, + GstRegistry * registry, guint32 filter_env_hash) +{ + GstRegistryChunkGlobalHeader *hdr; + GstRegistryChunk *chk; + + hdr = g_slice_new (GstRegistryChunkGlobalHeader); + chk = gst_registry_chunks_make_data (hdr, + sizeof (GstRegistryChunkGlobalHeader)); + + hdr->filter_env_hash = filter_env_hash; + + *list = g_list_prepend (*list, chk); + + GST_LOG ("Saved global header (filter_env_hash=0x%08x)", filter_env_hash); +} + +gboolean +_priv_gst_registry_chunks_load_global_header (GstRegistry * registry, + gchar ** in, gchar * end, guint32 * filter_env_hash) +{ + GstRegistryChunkGlobalHeader *hdr; + + align (*in); + GST_LOG ("Reading/casting for GstRegistryChunkGlobalHeader at %p", *in); + unpack_element (*in, hdr, GstRegistryChunkGlobalHeader, end, fail); + *filter_env_hash = hdr->filter_env_hash; + return TRUE; + + /* Errors */ +fail: + GST_WARNING ("Reading global header failed"); + return FALSE; +} diff --git a/gst/gstregistrychunks.h b/gst/gstregistrychunks.h new file mode 100644 index 0000000..fdbac5e --- /dev/null +++ b/gst/gstregistrychunks.h @@ -0,0 +1,168 @@ +/* GStreamer + * Copyright (C) 2006 Josep Torra <josep@fluendo.com> + * Copyright (C) 2006 Mathieu Garcia <matthieu@fluendo.com> + * Copyright (C) 2006 Stefan Kost <ensonic@sonicpulse.de> + * + * gstregistrybinary.h: Header for registry handling + * + * 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_REGISTRYCHUNKS_H__ +#define __GST_REGISTRYCHUNKS_H__ + +#include <gst/gstpad.h> +#include <gst/gstregistry.h> + +/* + * we reference strings directly from the plugins and in this case set CONST to + * avoid freeing them. If g_free() should be used, the MALLOC flag is set, + * otherwise g_slice_free1() will be used! + */ +enum { + GST_REGISTRY_CHUNK_FLAG_NONE = 0, + GST_REGISTRY_CHUNK_FLAG_CONST = 1, + GST_REGISTRY_CHUNK_FLAG_MALLOC = 2, +}; + +/* + * GstRegistryChunk: + * + * Header for binary blobs + */ +typedef struct _GstRegistryChunk +{ + gpointer data; + guint size; + guint flags; + gboolean align; +} GstRegistryChunk; + +typedef struct _GstRegistryChunkGlobalHeader +{ + guint32 filter_env_hash; +} GstRegistryChunkGlobalHeader; + +/* + * GstRegistryChunkPluginElement: + * + * @n_deps: Says how many dependency structures follows. + * + * @nfeatures: says how many binary plugin feature structures we will have + * right after the structure itself. + * + * A structure containing (staticely) every information needed for a plugin + */ + +typedef struct _GstRegistryChunkPluginElement +{ + gulong file_size; + gulong file_mtime; + + guint n_deps; + + guint nfeatures; +} GstRegistryChunkPluginElement; + +/* GstRegistryChunkDep: + */ +typedef struct _GstRegistryChunkDep +{ + guint flags; + guint n_env_vars; + guint n_paths; + guint n_names; + + guint env_hash; + guint stat_hash; +} GstRegistryChunkDep; + +/* + * GstRegistryChunkPluginFeature: + * @rank: rank of the feature + * + * A structure containing the plugin features + */ +typedef struct _GstRegistryChunkPluginFeature +{ + gulong rank; +} GstRegistryChunkPluginFeature; + +/* + * GstRegistryChunkElementFactory: + * @npadtemplates: stores the number of GstRegistryChunkPadTemplate structures + * following the structure + * @ninterfaces: stores the number of interface names following the structure + * @nuriprotocols: stores the number of protocol strings following the structure + * + * A structure containing the element factory fields + */ +typedef struct _GstRegistryChunkElementFactory +{ + GstRegistryChunkPluginFeature plugin_feature; + + guint npadtemplates; + guint ninterfaces; + guint nuriprotocols; +} GstRegistryChunkElementFactory; + +/* + * GstRegistryChunkTypeFindFactory: + * @nextensions: stores the number of typefind extensions + * + * A structure containing the element factory fields + */ +typedef struct _GstRegistryChunkTypeFindFactory +{ + GstRegistryChunkPluginFeature plugin_feature; + + guint nextensions; +} GstRegistryChunkTypeFindFactory; + +/* + * GstRegistryChunkPadTemplate: + * + * A structure containing the static pad templates of a plugin feature + */ +typedef struct _GstRegistryChunkPadTemplate +{ + guint direction; /* Either 0:"sink" or 1:"src" */ + GstPadPresence presence; +} GstRegistryChunkPadTemplate; + +G_BEGIN_DECLS + +gboolean +_priv_gst_registry_chunks_save_plugin (GList ** list, GstRegistry * registry, + GstPlugin * plugin); + +gboolean +_priv_gst_registry_chunks_load_plugin (GstRegistry * registry, gchar ** in, + gchar *end, GstPlugin **out_plugin); + +void +_priv_gst_registry_chunks_save_global_header (GList ** list, + GstRegistry * registry, guint32 filter_env_hash); + +gboolean +_priv_gst_registry_chunks_load_global_header (GstRegistry * registry, + gchar ** in, gchar *end, guint32 * filter_env_hash); + +void +_priv_gst_registry_chunk_free (GstRegistryChunk *chunk); + +G_END_DECLS + +#endif /* __GST_REGISTRYCHUNKS_H__ */ diff --git a/gst/gstsegment.c b/gst/gstsegment.c new file mode 100644 index 0000000..456bed5 --- /dev/null +++ b/gst/gstsegment.c @@ -0,0 +1,720 @@ +/* GStreamer + * Copyright (C) 2005 Wim Taymans <wim@fluendo.com> + * + * gstsegment.c: GstSegment subsystem + * + * 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. + */ + +#include "gst_private.h" + +#include <math.h> + +#include "gstutils.h" +#include "gstsegment.h" + +/** + * SECTION:gstsegment + * @short_description: Structure describing the configured region of interest + * in a media file. + * @see_also: #GstEvent + * + * This helper structure holds the relevant values for tracking the region of + * interest in a media file, called a segment. + * + * The structure can be used for two purposes: + * <itemizedlist> + * <listitem><para>performing seeks (handling seek events)</para></listitem> + * <listitem><para>tracking playback regions (handling newsegment events)</para></listitem> + * </itemizedlist> + * + * The segment is usually configured by the application with a seek event which + * is propagated upstream and eventually handled by an element that performs the seek. + * + * The configured segment is then propagated back downstream with a newsegment event. + * This information is then used to clip media to the segment boundaries. + * + * A segment structure is initialized with gst_segment_init(), which takes a #GstFormat + * that will be used as the format of the segment values. The segment will be configured + * with a start value of 0 and a stop/duration of -1, which is undefined. The default + * rate and applied_rate is 1.0. + * + * If the segment is used for managing seeks, the segment duration should be set with + * gst_segment_set_duration(). The public duration field contains the duration of the + * segment. When using the segment for seeking, the start and time members should + * normally be left to their default 0 value. The stop position is left to -1 unless + * explicitly configured to a different value after a seek event. + * + * The current position in the segment should be set with the gst_segment_set_last_stop(). + * The public last_stop field contains the last set stop position in the segment. + * + * For elements that perform seeks, the current segment should be updated with the + * gst_segment_set_seek() and the values from the seek event. This method will update + * all the segment fields. The last_stop field will contain the new playback position. + * If the cur_type was different from GST_SEEK_TYPE_NONE, playback continues from + * the last_stop position, possibly with updated flags or rate. + * + * For elements that want to use #GstSegment to track the playback region, use + * gst_segment_set_newsegment() to update the segment fields with the information from + * the newsegment event. The gst_segment_clip() method can be used to check and clip + * the media data to the segment boundaries. + * + * For elements that want to synchronize to the pipeline clock, gst_segment_to_running_time() + * can be used to convert a timestamp to a value that can be used to synchronize + * to the clock. This function takes into account all accumulated segments as well as + * any rate or applied_rate conversions. + * + * For elements that need to perform operations on media data in stream_time, + * gst_segment_to_stream_time() can be used to convert a timestamp and the segment + * info to stream time (which is always between 0 and the duration of the stream). + * + * Last reviewed on 2007-05-17 (0.10.13) + */ + +/** + * gst_segment_copy: + * @segment: (transfer none): a #GstSegment + * + * Create a copy of given @segment. + * + * Free-function: gst_segment_free + * + * Returns: (transfer full): a new #GstSegment, free with gst_segment_free(). + * + * Since: 0.10.20 + */ +GstSegment * +gst_segment_copy (const GstSegment * segment) +{ + GstSegment *result = NULL; + + if (segment) { + result = (GstSegment *) g_slice_copy (sizeof (GstSegment), segment); + } + return result; +} + +void +gst_segment_copy_into (const GstSegment * src, GstSegment * dest) +{ + memcpy (dest, src, sizeof (GstSegment)); +} + +GType +gst_segment_get_type (void) +{ + static GType gst_segment_type = 0; + + if (G_UNLIKELY (gst_segment_type == 0)) { + gst_segment_type = g_boxed_type_register_static ("GstSegment", + (GBoxedCopyFunc) gst_segment_copy, (GBoxedFreeFunc) gst_segment_free); + } + + return gst_segment_type; +} + +/** + * gst_segment_new: + * + * Allocate a new #GstSegment structure and initialize it using + * gst_segment_init(). + * + * Free-function: gst_segment_free + * + * Returns: (transfer full): a new #GstSegment, free with gst_segment_free(). + */ +GstSegment * +gst_segment_new (void) +{ + GstSegment *result; + + result = g_slice_new0 (GstSegment); + gst_segment_init (result, GST_FORMAT_UNDEFINED); + + return result; +} + +/** + * gst_segment_free: + * @segment: (in) (transfer full): a #GstSegment + * + * Free the allocated segment @segment. + */ +void +gst_segment_free (GstSegment * segment) +{ + g_slice_free (GstSegment, segment); +} + +/** + * gst_segment_init: + * @segment: a #GstSegment structure. + * @format: the format of the segment. + * + * The start/last_stop positions are set to 0 and the stop/duration + * fields are set to -1 (unknown). The default rate of 1.0 and no + * flags are set. + * + * Initialize @segment to its default values. + */ +void +gst_segment_init (GstSegment * segment, GstFormat format) +{ + g_return_if_fail (segment != NULL); + + segment->flags = GST_SEEK_FLAG_NONE; + segment->rate = 1.0; + segment->applied_rate = 1.0; + segment->format = format; + segment->base = 0; + segment->start = 0; + segment->stop = -1; + segment->time = 0; + segment->position = 0; + segment->duration = -1; +} + +/** + * gst_segment_do_seek: + * @segment: a #GstSegment structure. + * @rate: the rate of the segment. + * @format: the format of the segment. + * @flags: the segment flags for the segment + * @start_type: the seek method + * @start: the seek start value + * @stop_type: the seek method + * @stop: the seek stop value + * @update: boolean holding whether position was updated. + * + * Update the segment structure with the field values of a seek event (see + * gst_event_new_seek()). + * + * After calling this method, the segment field position and time will + * contain the requested new position in the segment. The new requested + * position in the segment depends on @rate and @start_type and @stop_type. + * + * For positive @rate, the new position in the segment is the new @segment + * start field when it was updated with a @start_type different from + * #GST_SEEK_TYPE_NONE. If no update was performed on @segment start position + * (#GST_SEEK_TYPE_NONE), @start is ignored and @segment position is + * unmodified. + * + * For negative @rate, the new position in the segment is the new @segment + * stop field when it was updated with a @stop_type different from + * #GST_SEEK_TYPE_NONE. If no stop was previously configured in the segment, the + * duration of the segment will be used to update the stop position. + * If no update was performed on @segment stop position (#GST_SEEK_TYPE_NONE), + * @stop is ignored and @segment position is unmodified. + * + * The applied rate of the segment will be set to 1.0 by default. + * If the caller can apply a rate change, it should update @segment + * rate and applied_rate after calling this function. + * + * @update will be set to TRUE if a seek should be performed to the segment + * position field. This field can be FALSE if, for example, only the @rate + * has been changed but not the playback position. + * + * Returns: %TRUE if the seek could be performed. + */ +gboolean +gst_segment_do_seek (GstSegment * segment, gdouble rate, + GstFormat format, GstSeekFlags flags, + GstSeekType start_type, guint64 start, + GstSeekType stop_type, guint64 stop, gboolean * update) +{ + gboolean update_stop, update_start; + guint64 position, base; + + g_return_val_if_fail (rate != 0.0, FALSE); + g_return_val_if_fail (segment != NULL, FALSE); + g_return_val_if_fail (segment->format == format, FALSE); + + update_start = update_stop = TRUE; + + position = segment->position; + + if (flags & GST_SEEK_FLAG_FLUSH) { + /* flush resets the running_time */ + base = 0; + } else { + base = gst_segment_to_running_time (segment, format, position); + } + + /* segment->start is never invalid */ + switch (start_type) { + case GST_SEEK_TYPE_NONE: + /* no update to segment, take previous start */ + start = segment->start; + update_start = FALSE; + break; + case GST_SEEK_TYPE_SET: + /* start holds desired position, map -1 to the start */ + if (start == -1) + start = 0; + break; + case GST_SEEK_TYPE_CUR: + { + gint64 sstart = (gint64) start; + /* add start to currently configured segment */ + if (sstart > 0 || segment->start > -sstart) + start = segment->start + start; + else + start = 0; + break; + } + case GST_SEEK_TYPE_END: + if (segment->duration != -1) { + /* add start to total length */ + start = segment->duration + start; + } else { + /* no update if duration unknown */ + start = segment->start; + update_start = FALSE; + } + break; + } + /* bring in sane range */ + if (segment->duration != -1) + start = MIN (start, segment->duration); + else + start = MAX (start, 0); + + /* stop can be -1 if we have not configured a stop. */ + switch (stop_type) { + case GST_SEEK_TYPE_NONE: + stop = segment->stop; + update_stop = FALSE; + break; + case GST_SEEK_TYPE_SET: + /* stop holds required value */ + break; + case GST_SEEK_TYPE_CUR: + if (segment->stop != -1) { + gint64 sstop = (gint64) stop; + if (sstop > 0 || segment->stop > -sstop) + stop = segment->stop + stop; + else + stop = 0; + } else + stop = -1; + break; + case GST_SEEK_TYPE_END: + if (segment->duration != -1) { + stop = segment->duration + stop; + } else { + stop = segment->stop; + update_stop = FALSE; + } + break; + } + + /* if we have a valid stop time, make sure it is clipped */ + if (stop != -1) { + if (segment->duration != -1) + stop = CLAMP (stop, 0, segment->duration); + else + stop = MAX (stop, 0); + } + + /* we can't have stop before start */ + if (stop != -1) { + if (start > stop) { + g_return_val_if_fail (start <= stop, FALSE); + return FALSE; + } + } + + segment->rate = rate; + segment->applied_rate = 1.0; + segment->base = base; + segment->flags = (GstSegmentFlags) flags; + segment->start = start; + segment->stop = stop; + segment->time = start; + + if (update_start && rate > 0.0) { + position = start; + } + if (update_stop && rate < 0.0) { + if (stop != -1) + position = stop; + else { + if (segment->duration != -1) + position = segment->duration; + else + position = 0; + } + } + /* set update arg to reflect update of position */ + if (update) + *update = position != segment->position; + + /* update new position */ + segment->position = position; + + return TRUE; +} + +/** + * gst_segment_to_stream_time: + * @segment: a #GstSegment structure. + * @format: the format of the segment. + * @position: the position in the segment + * + * Translate @position to stream time using the currently configured + * segment. The @position value must be between @segment start and + * stop value. + * + * This function is typically used by elements that need to operate on + * the stream time of the buffers it receives, such as effect plugins. + * In those use cases, @position is typically the buffer timestamp or + * clock time that one wants to convert to the stream time. + * The stream time is always between 0 and the total duration of the + * media stream. + * + * Returns: the position in stream_time or -1 when an invalid position + * was given. + */ +guint64 +gst_segment_to_stream_time (const GstSegment * segment, GstFormat format, + guint64 position) +{ + guint64 result, start, stop, time; + gdouble abs_applied_rate; + + /* format does not matter for -1 */ + if (G_UNLIKELY (position == -1)) + return -1; + + g_return_val_if_fail (segment != NULL, -1); + g_return_val_if_fail (segment->format == format, -1); + + /* if we have the position for the same format as the segment, we can compare + * the start and stop values, otherwise we assume 0 and -1 */ + if (G_LIKELY (segment->format == format)) { + start = segment->start; + stop = segment->stop; + time = segment->time; + } else { + start = 0; + stop = -1; + time = 0; + } + + /* outside of the segment boundary stop */ + if (G_UNLIKELY (stop != -1 && position > stop)) + return -1; + + /* before the segment boundary */ + if (G_UNLIKELY (position < start)) + return -1; + + /* time must be known */ + if (G_UNLIKELY (time == -1)) + return -1; + + /* bring to uncorrected position in segment */ + result = position - start; + + abs_applied_rate = ABS (segment->applied_rate); + + /* correct for applied rate if needed */ + if (G_UNLIKELY (abs_applied_rate != 1.0)) + result *= abs_applied_rate; + + /* add or subtract from segment time based on applied rate */ + if (G_LIKELY (segment->applied_rate > 0.0)) { + /* correct for segment time */ + result += time; + } else { + /* correct for segment time, clamp at 0. Streams with a negative + * applied_rate have timestamps between start and stop, as usual, but have + * the time member starting high and going backwards. */ + if (G_LIKELY (time > result)) + result = time - result; + else + result = 0; + } + + return result; +} + +/** + * gst_segment_to_running_time: + * @segment: a #GstSegment structure. + * @format: the format of the segment. + * @position: the position in the segment + * + * Translate @position to the total running time using the currently configured + * and previously accumulated segments. Position is a value between @segment + * start and stop time. + * + * This function is typically used by elements that need to synchronize to the + * global clock in a pipeline. The runnning time is a constantly increasing value + * starting from 0. When gst_segment_init() is called, this value will reset to + * 0. + * + * This function returns -1 if the position is outside of @segment start and stop. + * + * Returns: the position as the total running time or -1 when an invalid position + * was given. + */ +guint64 +gst_segment_to_running_time (const GstSegment * segment, GstFormat format, + guint64 position) +{ + guint64 result; + guint64 start, stop, base; + gdouble abs_rate; + + if (G_UNLIKELY (position == -1)) + return -1; + + g_return_val_if_fail (segment != NULL, -1); + g_return_val_if_fail (segment->format == format, -1); + + /* if we have the position for the same format as the segment, we can compare + * the start and stop values, otherwise we assume 0 and -1 */ + if (G_LIKELY (segment->format == format)) { + start = segment->start; + stop = segment->stop; + base = segment->base; + } else { + start = 0; + stop = -1; + base = 0; + } + + /* before the segment boundary */ + if (G_UNLIKELY (position < start)) + return -1; + + if (G_LIKELY (segment->rate > 0.0)) { + /* outside of the segment boundary stop */ + if (G_UNLIKELY (stop != -1 && position > stop)) + return -1; + + /* bring to uncorrected position in segment */ + result = position - start; + } else { + /* cannot continue if no stop position set or outside of + * the segment. */ + if (G_UNLIKELY (stop == -1 || position > stop)) + return -1; + + /* bring to uncorrected position in segment */ + result = stop - position; + } + + /* scale based on the rate, avoid division by and conversion to + * float when not needed */ + abs_rate = ABS (segment->rate); + if (G_UNLIKELY (abs_rate != 1.0)) + result /= abs_rate; + + /* correct for base of the segment */ + result += base; + + return result; +} + +/** + * gst_segment_clip: + * @segment: a #GstSegment structure. + * @format: the format of the segment. + * @start: the start position in the segment + * @stop: the stop position in the segment + * @clip_start: (out) (allow-none): the clipped start position in the segment + * @clip_stop: (out) (allow-none): the clipped stop position in the segment + * + * Clip the given @start and @stop values to the segment boundaries given + * in @segment. @start and @stop are compared and clipped to @segment + * start and stop values. + * + * If the function returns FALSE, @start and @stop are known to fall + * outside of @segment and @clip_start and @clip_stop are not updated. + * + * When the function returns TRUE, @clip_start and @clip_stop will be + * updated. If @clip_start or @clip_stop are different from @start or @stop + * respectively, the region fell partially in the segment. + * + * Note that when @stop is -1, @clip_stop will be set to the end of the + * segment. Depending on the use case, this may or may not be what you want. + * + * Returns: TRUE if the given @start and @stop times fall partially or + * completely in @segment, FALSE if the values are completely outside + * of the segment. + */ +gboolean +gst_segment_clip (const GstSegment * segment, GstFormat format, guint64 start, + guint64 stop, guint64 * clip_start, guint64 * clip_stop) +{ + g_return_val_if_fail (segment != NULL, FALSE); + g_return_val_if_fail (segment->format == format, FALSE); + + /* if we have a stop position and a valid start and start is bigger, + * we're outside of the segment */ + if (G_UNLIKELY (segment->stop != -1 && start != -1 && start >= segment->stop)) + return FALSE; + + /* if a stop position is given and is before the segment start, + * we're outside of the segment. Special case is were start + * and stop are equal to the segment start. In that case we + * are inside the segment. */ + if (G_UNLIKELY (stop != -1 && (stop < segment->start || (start != stop + && stop == segment->start)))) + return FALSE; + + if (clip_start) { + if (start == -1) + *clip_start = -1; + else + *clip_start = MAX (start, segment->start); + } + + if (clip_stop) { + if (stop == -1) + *clip_stop = segment->stop; + else if (segment->stop == -1) + *clip_stop = stop; + else + *clip_stop = MIN (stop, segment->stop); + + if (segment->duration != -1 && *clip_stop != -1) + *clip_stop = MIN (*clip_stop, segment->duration); + } + + return TRUE; +} + +/** + * gst_segment_to_position: + * @segment: a #GstSegment structure. + * @format: the format of the segment. + * @running_time: the running_time in the segment + * + * Convert @running_time into a position in the segment so that + * gst_segment_to_running_time() with that position returns @running_time. + * + * Returns: the position in the segment for @running_time. This function returns + * -1 when @running_time is -1 or when it is not inside @segment. + * + * Since: 0.10.24 + */ +guint64 +gst_segment_to_position (const GstSegment * segment, GstFormat format, + guint64 running_time) +{ + guint64 result; + guint64 start, stop, base; + gdouble abs_rate; + + if (G_UNLIKELY (running_time == -1)) + return -1; + + g_return_val_if_fail (segment != NULL, -1); + g_return_val_if_fail (segment->format == format, FALSE); + + /* if we have the position for the same format as the segment, we can compare + * the start and stop values, otherwise we assume 0 and -1 */ + if (G_LIKELY (segment->format == format)) { + start = segment->start; + stop = segment->stop; + base = segment->base; + } else { + start = 0; + stop = -1; + base = 0; + } + + /* this running_time was for a previous segment */ + if (running_time < base) + return -1; + + /* start by subtracting the base time */ + result = running_time - base; + + /* move into the segment at the right rate */ + abs_rate = ABS (segment->rate); + if (G_UNLIKELY (abs_rate != 1.0)) + result = ceil (result * abs_rate); + + if (G_LIKELY (segment->rate > 0.0)) { + /* bring to corrected position in segment */ + result += start; + + /* outside of the segment boundary stop */ + if (G_UNLIKELY (stop != -1 && result > stop)) + return -1; + } else { + /* cannot continue if no stop position set or outside of + * the segment. */ + if (G_UNLIKELY (stop == -1 || result + start > stop)) + return -1; + + /* bring to corrected position in segment */ + result = stop - result; + } + return result; +} + + +/** + * gst_segment_set_running_time: + * @segment: a #GstSegment structure. + * @format: the format of the segment. + * @running_time: the running_time in the segment + * + * Adjust the start/stop and base values of @segment such that the next valid + * buffer will be one with @running_time. + * + * Returns: %TRUE if the segment could be updated successfully. If %FALSE is + * returned, @running_time is -1 or not in @segment. + * + * Since: 0.10.24 + */ +gboolean +gst_segment_set_running_time (GstSegment * segment, GstFormat format, + guint64 running_time) +{ + guint64 position; + guint64 start, stop; + + /* start by bringing the running_time into the segment position */ + position = gst_segment_to_position (segment, format, running_time); + + /* we must have a valid position now */ + if (G_UNLIKELY (position == -1)) + return FALSE; + + start = segment->start; + stop = segment->stop; + + if (G_LIKELY (segment->rate > 0.0)) { + /* update the start and time values */ + start = position; + } else { + /* reverse, update stop */ + stop = position; + } + /* and base time is exactly the running time */ + segment->time = gst_segment_to_stream_time (segment, format, start); + segment->start = start; + segment->stop = stop; + segment->base = running_time; + + return TRUE; +} diff --git a/gst/gstsegment.h b/gst/gstsegment.h new file mode 100644 index 0000000..db5e1ad --- /dev/null +++ b/gst/gstsegment.h @@ -0,0 +1,178 @@ +/* GStreamer + * Copyright (C) 2005 Wim Taymans <wim@fluendo.com> + * + * gstsegment.h: Header for GstSegment subsystem + * + * 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_SEGMENT_H__ +#define __GST_SEGMENT_H__ + +#include <gst/gstformat.h> + +G_BEGIN_DECLS + +#define GST_TYPE_SEGMENT (gst_segment_get_type()) + +typedef struct _GstSegment GstSegment; + +/** + * GstSeekType: + * @GST_SEEK_TYPE_NONE: no change in position is required + * @GST_SEEK_TYPE_CUR: change relative to currently configured segment. This + * can't be used to seek relative to the current playback position - do a + * position query, calculate the desired position and then do an absolute + * position seek instead if that's what you want to do. + * @GST_SEEK_TYPE_SET: absolute position is requested + * @GST_SEEK_TYPE_END: relative position to duration is requested + * + * The different types of seek events. When constructing a seek event with + * gst_event_new_seek() or when doing gst_segment_do_seek (). + */ +typedef enum { + /* one of these */ + GST_SEEK_TYPE_NONE = 0, + GST_SEEK_TYPE_CUR = 1, + GST_SEEK_TYPE_SET = 2, + GST_SEEK_TYPE_END = 3 +} GstSeekType; + +/** + * GstSeekFlags: + * @GST_SEEK_FLAG_NONE: no flag + * @GST_SEEK_FLAG_FLUSH: flush pipeline + * @GST_SEEK_FLAG_ACCURATE: accurate position is requested, this might + * be considerably slower for some formats. + * @GST_SEEK_FLAG_KEY_UNIT: seek to the nearest keyframe. This might be + * faster but less accurate. + * @GST_SEEK_FLAG_SEGMENT: perform a segment seek. + * @GST_SEEK_FLAG_SKIP: when doing fast foward or fast reverse playback, allow + * elements to skip frames instead of generating all + * frames. Since 0.10.22. + * + * Flags to be used with gst_element_seek() or gst_event_new_seek(). All flags + * can be used together. + * + * A non flushing seek might take some time to perform as the currently + * playing data in the pipeline will not be cleared. + * + * An accurate seek might be slower for formats that don't have any indexes + * or timestamp markers in the stream. Specifying this flag might require a + * complete scan of the file in those cases. + * + * When performing a segment seek: after the playback of the segment completes, + * no EOS will be emmited by the element that performed the seek, but a + * #GST_MESSAGE_SEGMENT_DONE message will be posted on the bus by the element. + * When this message is posted, it is possible to send a new seek event to + * continue playback. With this seek method it is possible to perform seamless + * looping or simple linear editing. + * + * When doing fast forward (rate > 1.0) or fast reverse (rate < -1.0) trickmode + * playback, the @GST_SEEK_FLAG_SKIP flag can be used to instruct decoders + * and demuxers to adjust the playback rate by skipping frames. This can improve + * performance and decrease CPU usage because not all frames need to be decoded. + * + * Also see part-seeking.txt in the GStreamer design documentation for more + * details on the meaning of these flags and the behaviour expected of + * elements that handle them. + */ +typedef enum { + GST_SEEK_FLAG_NONE = 0, + GST_SEEK_FLAG_FLUSH = (1 << 0), + GST_SEEK_FLAG_ACCURATE = (1 << 1), + GST_SEEK_FLAG_KEY_UNIT = (1 << 2), + GST_SEEK_FLAG_SEGMENT = (1 << 3), + GST_SEEK_FLAG_SKIP = (1 << 4) +} GstSeekFlags; + +/** + * GstSegmentFlags: + * @GST_SEGMENT_FLAG_NONE: no flags + * @GST_SEGMENT_FLAG_RESET: reset the pipeline running_time to the segment + * running_time + * @GST_SEGMENT_FLAG_SKIP: perform skip playback + * + * Flags for the GstSegment structure. Currently mapped to the corresponding + * values of the seek flags. + */ +typedef enum { + GST_SEGMENT_FLAG_NONE = GST_SEEK_FLAG_NONE, + GST_SEGMENT_FLAG_RESET = GST_SEEK_FLAG_FLUSH, + GST_SEGMENT_FLAG_SKIP = GST_SEEK_FLAG_SKIP +} GstSegmentFlags; + +/** + * GstSegment: + * @flags: flags for this segment + * @rate: the rate of the segment + * @applied_rate: the already applied rate to the segment + * @format: the format of the segment values + * @base: the base time of the segment + * @start: the start of the segment + * @stop: the stop of the segment + * @time: the stream time of the segment + * @position: the position in the segment + * @duration: the duration of the segment + * + * A helper structure that holds the configured region of + * interest in a media file. + */ +struct _GstSegment { + /*< public >*/ + GstSegmentFlags flags; + + gdouble rate; + gdouble applied_rate; + + GstFormat format; + guint64 base; + guint64 start; + guint64 stop; + guint64 time; + + guint64 position; + guint64 duration; +}; + +GType gst_segment_get_type (void); + +GstSegment * gst_segment_new (void); +GstSegment * gst_segment_copy (const GstSegment *segment); +void gst_segment_copy_into (const GstSegment *src, GstSegment *dest); +void gst_segment_free (GstSegment *segment); + +void gst_segment_init (GstSegment *segment, GstFormat format); + +guint64 gst_segment_to_stream_time (const GstSegment *segment, GstFormat format, guint64 position); +guint64 gst_segment_to_running_time (const GstSegment *segment, GstFormat format, guint64 position); +guint64 gst_segment_to_position (const GstSegment *segment, GstFormat format, guint64 running_time); + +gboolean gst_segment_set_running_time (GstSegment *segment, GstFormat format, guint64 running_time); + +gboolean gst_segment_clip (const GstSegment *segment, GstFormat format, guint64 start, + guint64 stop, guint64 *clip_start, guint64 *clip_stop); + + +gboolean gst_segment_do_seek (GstSegment * segment, gdouble rate, + GstFormat format, GstSeekFlags flags, + GstSeekType start_type, guint64 start, + GstSeekType stop_type, guint64 stop, gboolean * update); + +G_END_DECLS + +#endif /* __GST_SEGMENT_H__ */ diff --git a/gst/gststructure.c b/gst/gststructure.c new file mode 100644 index 0000000..ca16622 --- /dev/null +++ b/gst/gststructure.c @@ -0,0 +1,3205 @@ +/* GStreamer + * Copyright (C) 2003 David A. Schleef <ds@schleef.org> + * + * gststructure.c: lists of { GQuark, GValue } tuples + * + * 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:gststructure + * @short_description: Generic structure containing fields of names and values + * @see_also: #GstCaps, #GstMessage, #GstEvent, #GstQuery + * + * A #GstStructure is a collection of key/value pairs. The keys are expressed + * as GQuarks and the values can be of any GType. + * + * In addition to the key/value pairs, a #GstStructure also has a name. The name + * starts with a letter and can be folled by letters, numbers and any of "/-_.:". + * + * #GstStructure is used by various GStreamer subsystems to store information + * in a flexible and extensible way. A #GstStructure does not have a refcount + * because it usually is part of a higher level object such as #GstCaps. It + * provides a means to enforce mutability using the refcount of the parent + * with the gst_structure_set_parent_refcount() method. + * + * A #GstStructure can be created with gst_structure_empty_new() or + * gst_structure_new(), which both take a name and an optional set of + * key/value pairs along with the types of the values. + * + * Field values can be changed with gst_structure_set_value() or + * gst_structure_set(). + * + * Field values can be retrieved with gst_structure_get_value() or the more + * convenient gst_structure_get_*() functions. + * + * Fields can be removed with gst_structure_remove_field() or + * gst_structure_remove_fields(). + * + * Strings in structures must be ASCII or UTF-8 encoded. Other encodings are + * not allowed. Strings must not be empty either, but may be NULL. + * + * Last reviewed on 2009-06-08 (0.10.23) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include "gst_private.h" +#include "gstquark.h" +#include <gst/gst.h> +#include <gobject/gvaluecollector.h> + +typedef struct _GstStructureField GstStructureField; + +struct _GstStructureField +{ + GQuark name; + GValue value; +}; + +typedef struct +{ + GstStructure s; + + /* owned by parent structure, NULL if no parent */ + gint *parent_refcount; + + GArray *fields; +} GstStructureImpl; + +#define GST_STRUCTURE_REFCOUNT(s) (((GstStructureImpl*)(s))->parent_refcount) +#define GST_STRUCTURE_FIELDS(s) (((GstStructureImpl*)(s))->fields) + +#define GST_STRUCTURE_FIELD(structure, index) \ + &g_array_index(GST_STRUCTURE_FIELDS(structure), GstStructureField, (index)) + +#define IS_MUTABLE(structure) \ + (!GST_STRUCTURE_REFCOUNT(structure) || \ + g_atomic_int_get (GST_STRUCTURE_REFCOUNT(structure)) == 1) + +#define IS_TAGLIST(structure) \ + (structure->name == GST_QUARK (TAGLIST)) + +static void gst_structure_set_field (GstStructure * structure, + GstStructureField * field); +static GstStructureField *gst_structure_get_field (const GstStructure * + structure, const gchar * fieldname); +static GstStructureField *gst_structure_id_get_field (const GstStructure * + structure, GQuark field); +static void gst_structure_transform_to_string (const GValue * src_value, + GValue * dest_value); +static GstStructure *gst_structure_copy_conditional (const GstStructure * + structure); +static gboolean gst_structure_parse_value (gchar * str, gchar ** after, + GValue * value, GType default_type); +static gboolean gst_structure_parse_simple_string (gchar * s, gchar ** end); + +GType _gst_structure_type = 0; + +void +_priv_gst_structure_initialize (void) +{ + _gst_structure_type = g_boxed_type_register_static ("GstStructure", + (GBoxedCopyFunc) gst_structure_copy_conditional, + (GBoxedFreeFunc) gst_structure_free); + + g_value_register_transform_func (_gst_structure_type, G_TYPE_STRING, + gst_structure_transform_to_string); +} + +static GstStructure * +gst_structure_id_empty_new_with_size (GQuark quark, guint prealloc) +{ + GstStructureImpl *structure; + + structure = g_slice_new (GstStructureImpl); + ((GstStructure *) structure)->type = _gst_structure_type; + ((GstStructure *) structure)->name = quark; + GST_STRUCTURE_REFCOUNT (structure) = NULL; + GST_STRUCTURE_FIELDS (structure) = + g_array_sized_new (FALSE, FALSE, sizeof (GstStructureField), prealloc); + + return GST_STRUCTURE_CAST (structure); +} + +/** + * gst_structure_id_empty_new: + * @quark: name of new structure + * + * Creates a new, empty #GstStructure with the given name as a GQuark. + * + * Free-function: gst_structure_free + * + * Returns: (transfer full): a new, empty #GstStructure + */ +GstStructure * +gst_structure_id_empty_new (GQuark quark) +{ + g_return_val_if_fail (quark != 0, NULL); + + return gst_structure_id_empty_new_with_size (quark, 0); +} + +#ifndef G_DISABLE_CHECKS +static gboolean +gst_structure_validate_name (const gchar * name) +{ + const gchar *s; + + g_return_val_if_fail (name != NULL, FALSE); + + if (G_UNLIKELY (!g_ascii_isalpha (*name))) { + GST_WARNING ("Invalid character '%c' at offset 0 in structure name: %s", + *name, name); + return FALSE; + } + + /* FIXME: test name string more */ + s = &name[1]; + while (*s && (g_ascii_isalnum (*s) || strchr ("/-_.:+", *s) != NULL)) + s++; + if (G_UNLIKELY (*s != '\0')) { + GST_WARNING ("Invalid character '%c' at offset %" G_GUINTPTR_FORMAT " in" + " structure name: %s", *s, ((guintptr) s - (guintptr) name), name); + return FALSE; + } + + return TRUE; +} +#endif + +/** + * gst_structure_empty_new: + * @name: name of new structure + * + * Creates a new, empty #GstStructure with the given @name. + * + * See gst_structure_set_name() for constraints on the @name parameter. + * + * Free-function: gst_structure_free + * + * Returns: (transfer full): a new, empty #GstStructure + */ +GstStructure * +gst_structure_empty_new (const gchar * name) +{ + g_return_val_if_fail (gst_structure_validate_name (name), NULL); + + return gst_structure_id_empty_new_with_size (g_quark_from_string (name), 0); +} + +/** + * gst_structure_new: + * @name: name of new structure + * @firstfield: name of first field to set + * @...: additional arguments + * + * Creates a new #GstStructure with the given name. Parses the + * list of variable arguments and sets fields to the values listed. + * Variable arguments should be passed as field name, field type, + * and value. Last variable argument should be NULL. + * + * Free-function: gst_structure_free + * + * Returns: (transfer full): a new #GstStructure + */ +GstStructure * +gst_structure_new (const gchar * name, const gchar * firstfield, ...) +{ + GstStructure *structure; + va_list varargs; + + va_start (varargs, firstfield); + structure = gst_structure_new_valist (name, firstfield, varargs); + va_end (varargs); + + return structure; +} + +/** + * gst_structure_new_valist: + * @name: name of new structure + * @firstfield: name of first field to set + * @varargs: variable argument list + * + * Creates a new #GstStructure with the given @name. Structure fields + * are set according to the varargs in a manner similar to + * gst_structure_new(). + * + * See gst_structure_set_name() for constraints on the @name parameter. + * + * Free-function: gst_structure_free + * + * Returns: (transfer full): a new #GstStructure + */ +GstStructure * +gst_structure_new_valist (const gchar * name, + const gchar * firstfield, va_list varargs) +{ + GstStructure *structure; + + structure = gst_structure_empty_new (name); + + if (structure) + gst_structure_set_valist (structure, firstfield, varargs); + + return structure; +} + +/** + * gst_structure_set_parent_refcount: + * @structure: a #GstStructure + * @refcount: (in): a pointer to the parent's refcount + * + * Sets the parent_refcount field of #GstStructure. This field is used to + * determine whether a structure is mutable or not. This function should only be + * called by code implementing parent objects of #GstStructure, as described in + * the MT Refcounting section of the design documents. + * + * Returns: %TRUE if the parent refcount could be set. + */ +gboolean +gst_structure_set_parent_refcount (GstStructure * structure, gint * refcount) +{ + g_return_val_if_fail (structure != NULL, FALSE); + + /* if we have a parent_refcount already, we can only clear + * if with a NULL refcount */ + if (GST_STRUCTURE_REFCOUNT (structure)) { + if (refcount != NULL) { + g_return_val_if_fail (refcount == NULL, FALSE); + return FALSE; + } + } else { + if (refcount == NULL) { + g_return_val_if_fail (refcount != NULL, FALSE); + return FALSE; + } + } + + GST_STRUCTURE_REFCOUNT (structure) = refcount; + + return TRUE; +} + +/** + * gst_structure_copy: + * @structure: a #GstStructure to duplicate + * + * Duplicates a #GstStructure and all its fields and values. + * + * Free-function: gst_structure_free + * + * Returns: (transfer none): a new #GstStructure. + */ +GstStructure * +gst_structure_copy (const GstStructure * structure) +{ + GstStructure *new_structure; + GstStructureField *field; + guint i, len; + + g_return_val_if_fail (structure != NULL, NULL); + + len = GST_STRUCTURE_FIELDS (structure)->len; + new_structure = gst_structure_id_empty_new_with_size (structure->name, len); + + for (i = 0; i < len; i++) { + GstStructureField new_field = { 0 }; + + field = GST_STRUCTURE_FIELD (structure, i); + + new_field.name = field->name; + gst_value_init_and_copy (&new_field.value, &field->value); + g_array_append_val (GST_STRUCTURE_FIELDS (new_structure), new_field); + } + + return new_structure; +} + +/** + * gst_structure_free: + * @structure: (in) (transfer full): the #GstStructure to free + * + * Frees a #GstStructure and all its fields and values. The structure must not + * have a parent when this function is called. + */ +void +gst_structure_free (GstStructure * structure) +{ + GstStructureField *field; + guint i, len; + + g_return_if_fail (structure != NULL); + g_return_if_fail (GST_STRUCTURE_REFCOUNT (structure) == NULL); + + len = GST_STRUCTURE_FIELDS (structure)->len; + for (i = 0; i < len; i++) { + field = GST_STRUCTURE_FIELD (structure, i); + + if (G_IS_VALUE (&field->value)) { + g_value_unset (&field->value); + } + } + g_array_free (GST_STRUCTURE_FIELDS (structure), TRUE); +#ifdef USE_POISONING + memset (structure, 0xff, sizeof (GstStructure)); +#endif + g_slice_free1 (sizeof (GstStructureImpl), structure); +} + +/** + * gst_structure_get_name: + * @structure: a #GstStructure + * + * Get the name of @structure as a string. + * + * Returns: the name of the structure. + */ +const gchar * +gst_structure_get_name (const GstStructure * structure) +{ + g_return_val_if_fail (structure != NULL, NULL); + + return g_quark_to_string (structure->name); +} + +/** + * gst_structure_has_name: + * @structure: a #GstStructure + * @name: structure name to check for + * + * Checks if the structure has the given name + * + * Returns: TRUE if @name matches the name of the structure. + */ +gboolean +gst_structure_has_name (const GstStructure * structure, const gchar * name) +{ + const gchar *structure_name; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + /* getting the string is cheap and comparing short strings is too + * should be faster than getting the quark for name and comparing the quarks + */ + structure_name = g_quark_to_string (structure->name); + + return (structure_name && strcmp (structure_name, name) == 0); +} + +/** + * gst_structure_get_name_id: + * @structure: a #GstStructure + * + * Get the name of @structure as a GQuark. + * + * Returns: the quark representing the name of the structure. + */ +GQuark +gst_structure_get_name_id (const GstStructure * structure) +{ + g_return_val_if_fail (structure != NULL, 0); + + return structure->name; +} + +/** + * gst_structure_set_name: + * @structure: a #GstStructure + * @name: the new name of the structure + * + * Sets the name of the structure to the given @name. The string + * provided is copied before being used. It must not be empty, start with a + * letter and can be followed by letters, numbers and any of "/-_.:". + */ +void +gst_structure_set_name (GstStructure * structure, const gchar * name) +{ + g_return_if_fail (structure != NULL); + g_return_if_fail (IS_MUTABLE (structure)); + g_return_if_fail (gst_structure_validate_name (name)); + + structure->name = g_quark_from_string (name); +} + +static inline void +gst_structure_id_set_value_internal (GstStructure * structure, GQuark field, + const GValue * value) +{ + GstStructureField gsfield = { 0, {0,} }; + + gsfield.name = field; + gst_value_init_and_copy (&gsfield.value, value); + + gst_structure_set_field (structure, &gsfield); +} + +/** + * gst_structure_id_set_value: + * @structure: a #GstStructure + * @field: a #GQuark representing a field + * @value: the new value of the field + * + * Sets the field with the given GQuark @field to @value. If the field + * does not exist, it is created. If the field exists, the previous + * value is replaced and freed. + */ +void +gst_structure_id_set_value (GstStructure * structure, + GQuark field, const GValue * value) +{ + + g_return_if_fail (structure != NULL); + g_return_if_fail (G_IS_VALUE (value)); + g_return_if_fail (IS_MUTABLE (structure)); + + gst_structure_id_set_value_internal (structure, field, value); +} + +/** + * gst_structure_set_value: + * @structure: a #GstStructure + * @fieldname: the name of the field to set + * @value: the new value of the field + * + * Sets the field with the given name @field to @value. If the field + * does not exist, it is created. If the field exists, the previous + * value is replaced and freed. + */ +void +gst_structure_set_value (GstStructure * structure, + const gchar * fieldname, const GValue * value) +{ + g_return_if_fail (structure != NULL); + g_return_if_fail (fieldname != NULL); + g_return_if_fail (G_IS_VALUE (value)); + g_return_if_fail (IS_MUTABLE (structure)); + + gst_structure_id_set_value_internal (structure, + g_quark_from_string (fieldname), value); +} + +static inline void +gst_structure_id_take_value_internal (GstStructure * structure, GQuark field, + GValue * value) +{ + GstStructureField gsfield = { 0, {0,} }; + + gsfield.name = field; + gsfield.value = *value; + + gst_structure_set_field (structure, &gsfield); + + /* we took ownership */ +#ifdef USE_POISONING + memset (value, 0, sizeof (GValue)); +#else + value->g_type = G_TYPE_INVALID; +#endif +} + +/** + * gst_structure_id_take_value: + * @structure: a #GstStructure + * @field: a #GQuark representing a field + * @value: (transfer full): the new value of the field + * + * Sets the field with the given GQuark @field to @value. If the field + * does not exist, it is created. If the field exists, the previous + * value is replaced and freed. + * + * Since: 0.10.31 + */ +void +gst_structure_id_take_value (GstStructure * structure, GQuark field, + GValue * value) +{ + g_return_if_fail (structure != NULL); + g_return_if_fail (G_IS_VALUE (value)); + g_return_if_fail (IS_MUTABLE (structure)); + + gst_structure_id_take_value_internal (structure, field, value); +} + +/** + * gst_structure_take_value: + * @structure: a #GstStructure + * @fieldname: the name of the field to set + * @value: (transfer full): the new value of the field + * + * Sets the field with the given name @field to @value. If the field + * does not exist, it is created. If the field exists, the previous + * value is replaced and freed. The function will take ownership of @value. + * + * Since: 0.10.31 + */ +void +gst_structure_take_value (GstStructure * structure, const gchar * fieldname, + GValue * value) +{ + g_return_if_fail (structure != NULL); + g_return_if_fail (fieldname != NULL); + g_return_if_fail (G_IS_VALUE (value)); + g_return_if_fail (IS_MUTABLE (structure)); + + gst_structure_id_take_value_internal (structure, + g_quark_from_string (fieldname), value); +} + +static void +gst_structure_set_valist_internal (GstStructure * structure, + const gchar * fieldname, va_list varargs) +{ + gchar *err = NULL; + GType type; + + while (fieldname) { + GstStructureField field = { 0 }; + + field.name = g_quark_from_string (fieldname); + + type = va_arg (varargs, GType); + + if (G_UNLIKELY (type == G_TYPE_DATE)) { + g_warning ("Don't use G_TYPE_DATE, use GST_TYPE_DATE instead\n"); + type = GST_TYPE_DATE; + } + G_VALUE_COLLECT_INIT (&field.value, type, varargs, 0, &err); + if (G_UNLIKELY (err)) { + g_critical ("%s", err); + return; + } + gst_structure_set_field (structure, &field); + + fieldname = va_arg (varargs, gchar *); + } +} + +/** + * gst_structure_set: + * @structure: a #GstStructure + * @fieldname: the name of the field to set + * @...: variable arguments + * + * Parses the variable arguments and sets fields accordingly. + * Variable arguments should be in the form field name, field type + * (as a GType), value(s). The last variable argument should be NULL. + */ +void +gst_structure_set (GstStructure * structure, const gchar * field, ...) +{ + va_list varargs; + + g_return_if_fail (structure != NULL); + g_return_if_fail (IS_MUTABLE (structure) || field == NULL); + + va_start (varargs, field); + gst_structure_set_valist_internal (structure, field, varargs); + va_end (varargs); +} + +/** + * gst_structure_set_valist: + * @structure: a #GstStructure + * @fieldname: the name of the field to set + * @varargs: variable arguments + * + * va_list form of gst_structure_set(). + */ +void +gst_structure_set_valist (GstStructure * structure, + const gchar * fieldname, va_list varargs) +{ + g_return_if_fail (structure != NULL); + g_return_if_fail (IS_MUTABLE (structure)); + + gst_structure_set_valist_internal (structure, fieldname, varargs); +} + +static void +gst_structure_id_set_valist_internal (GstStructure * structure, + GQuark fieldname, va_list varargs) +{ + gchar *err = NULL; + GType type; + + while (fieldname) { + GstStructureField field = { 0 }; + + field.name = fieldname; + + type = va_arg (varargs, GType); + + if (G_UNLIKELY (type == G_TYPE_DATE)) { + g_warning ("Don't use G_TYPE_DATE, use GST_TYPE_DATE instead\n"); + type = GST_TYPE_DATE; + } +#ifndef G_VALUE_COLLECT_INIT + g_value_init (&field.value, type); + G_VALUE_COLLECT (&field.value, varargs, 0, &err); +#else + G_VALUE_COLLECT_INIT (&field.value, type, varargs, 0, &err); +#endif + if (G_UNLIKELY (err)) { + g_critical ("%s", err); + return; + } + gst_structure_set_field (structure, &field); + + fieldname = va_arg (varargs, GQuark); + } +} + +/** + * gst_structure_id_set: + * @structure: a #GstStructure + * @fieldname: the GQuark for the name of the field to set + * @...: variable arguments + * + * Identical to gst_structure_set, except that field names are + * passed using the GQuark for the field name. This allows more efficient + * setting of the structure if the caller already knows the associated + * quark values. + * The last variable argument must be NULL. + * + * Since: 0.10.10 + */ +void +gst_structure_id_set (GstStructure * structure, GQuark field, ...) +{ + va_list varargs; + + g_return_if_fail (structure != NULL); + + va_start (varargs, field); + gst_structure_id_set_valist_internal (structure, field, varargs); + va_end (varargs); +} + +/** + * gst_structure_id_set_valist: + * @structure: a #GstStructure + * @fieldname: the name of the field to set + * @varargs: variable arguments + * + * va_list form of gst_structure_id_set(). + * + * Since: 0.10.10 + */ +void +gst_structure_id_set_valist (GstStructure * structure, + GQuark fieldname, va_list varargs) +{ + g_return_if_fail (structure != NULL); + g_return_if_fail (IS_MUTABLE (structure)); + + gst_structure_id_set_valist_internal (structure, fieldname, varargs); +} + +/** + * gst_structure_id_new: + * @name_quark: name of new structure + * @field_quark: the GQuark for the name of the field to set + * @...: variable arguments + * + * Creates a new #GstStructure with the given name as a GQuark, followed by + * fieldname quark, GType, argument(s) "triplets" in the same format as + * gst_structure_id_set(). Basically a convenience wrapper around + * gst_structure_id_empty_new() and gst_structure_id_set(). + * + * The last variable argument must be NULL (or 0). + * + * Free-function: gst_structure_free + * + * Returns: (transfer full): a new #GstStructure + * + * Since: 0.10.24 + */ +GstStructure * +gst_structure_id_new (GQuark name_quark, GQuark field_quark, ...) +{ + GstStructure *s; + va_list varargs; + + g_return_val_if_fail (name_quark != 0, NULL); + g_return_val_if_fail (field_quark != 0, NULL); + + s = gst_structure_id_empty_new (name_quark); + + va_start (varargs, field_quark); + gst_structure_id_set_valist_internal (s, field_quark, varargs); + va_end (varargs); + + return s; +} + +#if GST_VERSION_NANO == 1 +#define GIT_G_WARNING g_warning +#else +#define GIT_G_WARNING GST_WARNING +#endif + +/* If the structure currently contains a field with the same name, it is + * replaced with the provided field. Otherwise, the field is added to the + * structure. The field's value is not deeply copied. + */ +static void +gst_structure_set_field (GstStructure * structure, GstStructureField * field) +{ + GstStructureField *f; + guint i, len = GST_STRUCTURE_FIELDS (structure)->len; + + if (G_UNLIKELY (G_VALUE_HOLDS_STRING (&field->value))) { + const gchar *s; + + s = g_value_get_string (&field->value); + /* only check for NULL strings in taglists, as they are allowed in message + * structs, e.g. error message debug strings */ + if (G_UNLIKELY (IS_TAGLIST (structure) && (s == NULL || *s == '\0'))) { + if (s == NULL) { + GIT_G_WARNING ("Trying to set NULL string on field '%s' on taglist. " + "Please file a bug.", g_quark_to_string (field->name)); + g_value_unset (&field->value); + return; + } else { + /* empty strings never make sense */ + GIT_G_WARNING ("Trying to set empty string on taglist field '%s'. " + "Please file a bug.", g_quark_to_string (field->name)); + g_value_unset (&field->value); + return; + } + } else if (G_UNLIKELY (s != NULL && !g_utf8_validate (s, -1, NULL))) { + g_warning ("Trying to set string on %s field '%s', but string is not " + "valid UTF-8. Please file a bug.", + IS_TAGLIST (structure) ? "taglist" : "structure", + g_quark_to_string (field->name)); + g_value_unset (&field->value); + return; + } + } else if (G_UNLIKELY (GST_VALUE_HOLDS_DATE (&field->value))) { + const GDate *d; + + d = gst_value_get_date (&field->value); + /* only check for NULL GDates in taglists, as they might make sense + * in other, generic structs */ + if (G_UNLIKELY ((IS_TAGLIST (structure) && d == NULL))) { + GIT_G_WARNING ("Trying to set NULL GDate on field '%s' on taglist. " + "Please file a bug.", g_quark_to_string (field->name)); + g_value_unset (&field->value); + return; + } else if (G_UNLIKELY (d != NULL && !g_date_valid (d))) { + g_warning + ("Trying to set invalid GDate on %s field '%s'. Please file a bug.", + IS_TAGLIST (structure) ? "taglist" : "structure", + g_quark_to_string (field->name)); + g_value_unset (&field->value); + return; + } + } + + for (i = 0; i < len; i++) { + f = GST_STRUCTURE_FIELD (structure, i); + + if (G_UNLIKELY (f->name == field->name)) { + g_value_unset (&f->value); + memcpy (f, field, sizeof (GstStructureField)); + return; + } + } + + g_array_append_val (GST_STRUCTURE_FIELDS (structure), *field); +} + +/* If there is no field with the given ID, NULL is returned. + */ +static GstStructureField * +gst_structure_id_get_field (const GstStructure * structure, GQuark field_id) +{ + GstStructureField *field; + guint i, len; + + len = GST_STRUCTURE_FIELDS (structure)->len; + + for (i = 0; i < len; i++) { + field = GST_STRUCTURE_FIELD (structure, i); + + if (G_UNLIKELY (field->name == field_id)) + return field; + } + + return NULL; +} + +/* If there is no field with the given ID, NULL is returned. + */ +static GstStructureField * +gst_structure_get_field (const GstStructure * structure, + const gchar * fieldname) +{ + g_return_val_if_fail (structure != NULL, NULL); + g_return_val_if_fail (fieldname != NULL, NULL); + + return gst_structure_id_get_field (structure, + g_quark_from_string (fieldname)); +} + +/** + * gst_structure_get_value: + * @structure: a #GstStructure + * @fieldname: the name of the field to get + * + * Get the value of the field with name @fieldname. + * + * Returns: the #GValue corresponding to the field with the given name. + */ +const GValue * +gst_structure_get_value (const GstStructure * structure, + const gchar * fieldname) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, NULL); + g_return_val_if_fail (fieldname != NULL, NULL); + + field = gst_structure_get_field (structure, fieldname); + if (field == NULL) + return NULL; + + return &field->value; +} + +/** + * gst_structure_id_get_value: + * @structure: a #GstStructure + * @field: the #GQuark of the field to get + * + * Get the value of the field with GQuark @field. + * + * Returns: the #GValue corresponding to the field with the given name + * identifier. + */ +const GValue * +gst_structure_id_get_value (const GstStructure * structure, GQuark field) +{ + GstStructureField *gsfield; + + g_return_val_if_fail (structure != NULL, NULL); + + gsfield = gst_structure_id_get_field (structure, field); + if (gsfield == NULL) + return NULL; + + return &gsfield->value; +} + +/** + * gst_structure_remove_field: + * @structure: a #GstStructure + * @fieldname: the name of the field to remove + * + * Removes the field with the given name. If the field with the given + * name does not exist, the structure is unchanged. + */ +void +gst_structure_remove_field (GstStructure * structure, const gchar * fieldname) +{ + GstStructureField *field; + GQuark id; + guint i, len; + + g_return_if_fail (structure != NULL); + g_return_if_fail (fieldname != NULL); + g_return_if_fail (IS_MUTABLE (structure)); + + id = g_quark_from_string (fieldname); + len = GST_STRUCTURE_FIELDS (structure)->len; + + for (i = 0; i < len; i++) { + field = GST_STRUCTURE_FIELD (structure, i); + + if (field->name == id) { + if (G_IS_VALUE (&field->value)) { + g_value_unset (&field->value); + } + GST_STRUCTURE_FIELDS (structure) = + g_array_remove_index (GST_STRUCTURE_FIELDS (structure), i); + return; + } + } +} + +/** + * gst_structure_remove_fields: + * @structure: a #GstStructure + * @fieldname: the name of the field to remove + * @...: NULL-terminated list of more fieldnames to remove + * + * Removes the fields with the given names. If a field does not exist, the + * argument is ignored. + */ +void +gst_structure_remove_fields (GstStructure * structure, + const gchar * fieldname, ...) +{ + va_list varargs; + + g_return_if_fail (structure != NULL); + g_return_if_fail (fieldname != NULL); + /* mutability checked in remove_field */ + + va_start (varargs, fieldname); + gst_structure_remove_fields_valist (structure, fieldname, varargs); + va_end (varargs); +} + +/** + * gst_structure_remove_fields_valist: + * @structure: a #GstStructure + * @fieldname: the name of the field to remove + * @varargs: NULL-terminated list of more fieldnames to remove + * + * va_list form of gst_structure_remove_fields(). + */ +void +gst_structure_remove_fields_valist (GstStructure * structure, + const gchar * fieldname, va_list varargs) +{ + gchar *field = (gchar *) fieldname; + + g_return_if_fail (structure != NULL); + g_return_if_fail (fieldname != NULL); + /* mutability checked in remove_field */ + + while (field) { + gst_structure_remove_field (structure, field); + field = va_arg (varargs, char *); + } +} + +/** + * gst_structure_remove_all_fields: + * @structure: a #GstStructure + * + * Removes all fields in a GstStructure. + */ +void +gst_structure_remove_all_fields (GstStructure * structure) +{ + GstStructureField *field; + int i; + + g_return_if_fail (structure != NULL); + g_return_if_fail (IS_MUTABLE (structure)); + + for (i = GST_STRUCTURE_FIELDS (structure)->len - 1; i >= 0; i--) { + field = GST_STRUCTURE_FIELD (structure, i); + + if (G_IS_VALUE (&field->value)) { + g_value_unset (&field->value); + } + GST_STRUCTURE_FIELDS (structure) = + g_array_remove_index (GST_STRUCTURE_FIELDS (structure), i); + } +} + +/** + * gst_structure_get_field_type: + * @structure: a #GstStructure + * @fieldname: the name of the field + * + * Finds the field with the given name, and returns the type of the + * value it contains. If the field is not found, G_TYPE_INVALID is + * returned. + * + * Returns: the #GValue of the field + */ +GType +gst_structure_get_field_type (const GstStructure * structure, + const gchar * fieldname) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, G_TYPE_INVALID); + g_return_val_if_fail (fieldname != NULL, G_TYPE_INVALID); + + field = gst_structure_get_field (structure, fieldname); + if (field == NULL) + return G_TYPE_INVALID; + + return G_VALUE_TYPE (&field->value); +} + +/** + * gst_structure_n_fields: + * @structure: a #GstStructure + * + * Get the number of fields in the structure. + * + * Returns: the number of fields in the structure + */ +gint +gst_structure_n_fields (const GstStructure * structure) +{ + g_return_val_if_fail (structure != NULL, 0); + + return GST_STRUCTURE_FIELDS (structure)->len; +} + +/** + * gst_structure_nth_field_name: + * @structure: a #GstStructure + * @index: the index to get the name of + * + * Get the name of the given field number, counting from 0 onwards. + * + * Returns: the name of the given field number + */ +const gchar * +gst_structure_nth_field_name (const GstStructure * structure, guint index) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, NULL); + g_return_val_if_fail (index < GST_STRUCTURE_FIELDS (structure)->len, NULL); + + field = GST_STRUCTURE_FIELD (structure, index); + + return g_quark_to_string (field->name); +} + +/** + * gst_structure_foreach: + * @structure: a #GstStructure + * @func: (scope call): a function to call for each field + * @user_data: (closure): private data + * + * Calls the provided function once for each field in the #GstStructure. The + * function must not modify the fields. Also see gst_structure_map_in_place(). + * + * Returns: TRUE if the supplied function returns TRUE For each of the fields, + * FALSE otherwise. + */ +gboolean +gst_structure_foreach (const GstStructure * structure, + GstStructureForeachFunc func, gpointer user_data) +{ + guint i, len; + GstStructureField *field; + gboolean ret; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (func != NULL, FALSE); + + len = GST_STRUCTURE_FIELDS (structure)->len; + + for (i = 0; i < len; i++) { + field = GST_STRUCTURE_FIELD (structure, i); + + ret = func (field->name, &field->value, user_data); + if (G_UNLIKELY (!ret)) + return FALSE; + } + + return TRUE; +} + +/** + * gst_structure_map_in_place: + * @structure: a #GstStructure + * @func: (scope call): a function to call for each field + * @user_data: (closure): private data + * + * Calls the provided function once for each field in the #GstStructure. In + * contrast to gst_structure_foreach(), the function may modify but not delete the + * fields. The structure must be mutable. + * + * Returns: TRUE if the supplied function returns TRUE For each of the fields, + * FALSE otherwise. + */ +gboolean +gst_structure_map_in_place (GstStructure * structure, + GstStructureMapFunc func, gpointer user_data) +{ + guint i, len; + GstStructureField *field; + gboolean ret; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (IS_MUTABLE (structure), FALSE); + g_return_val_if_fail (func != NULL, FALSE); + len = GST_STRUCTURE_FIELDS (structure)->len; + + for (i = 0; i < len; i++) { + field = GST_STRUCTURE_FIELD (structure, i); + + ret = func (field->name, &field->value, user_data); + if (!ret) + return FALSE; + } + + return TRUE; +} + +/** + * gst_structure_id_has_field: + * @structure: a #GstStructure + * @field: #GQuark of the field name + * + * Check if @structure contains a field named @field. + * + * Returns: TRUE if the structure contains a field with the given name + * + * Since: 0.10.26 + */ +gboolean +gst_structure_id_has_field (const GstStructure * structure, GQuark field) +{ + GstStructureField *f; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (field != 0, FALSE); + + f = gst_structure_id_get_field (structure, field); + + return (f != NULL); +} + +/** + * gst_structure_has_field: + * @structure: a #GstStructure + * @fieldname: the name of a field + * + * Check if @structure contains a field named @fieldname. + * + * Returns: TRUE if the structure contains a field with the given name + */ +gboolean +gst_structure_has_field (const GstStructure * structure, + const gchar * fieldname) +{ + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + + return gst_structure_id_has_field (structure, + g_quark_from_string (fieldname)); +} + +/** + * gst_structure_id_has_field_typed: + * @structure: a #GstStructure + * @field: #GQuark of the field name + * @type: the type of a value + * + * Check if @structure contains a field named @field and with GType @type. + * + * Returns: TRUE if the structure contains a field with the given name and type + * + * Since: 0.10.26 + */ +gboolean +gst_structure_id_has_field_typed (const GstStructure * structure, + GQuark field, GType type) +{ + GstStructureField *f; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (field != 0, FALSE); + + f = gst_structure_id_get_field (structure, field); + if (f == NULL) + return FALSE; + + return (G_VALUE_TYPE (&f->value) == type); +} + +/** + * gst_structure_has_field_typed: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @type: the type of a value + * + * Check if @structure contains a field named @fieldname and with GType @type. + * + * Returns: TRUE if the structure contains a field with the given name and type + */ +gboolean +gst_structure_has_field_typed (const GstStructure * structure, + const gchar * fieldname, GType type) +{ + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + + return gst_structure_id_has_field_typed (structure, + g_quark_from_string (fieldname), type); +} + +/* utility functions */ + +/** + * gst_structure_get_boolean: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @value: (out): a pointer to a #gboolean to set + * + * Sets the boolean pointed to by @value corresponding to the value of the + * given field. Caller is responsible for making sure the field exists + * and has the correct type. + * + * Returns: TRUE if the value could be set correctly. If there was no field + * with @fieldname or the existing field did not contain a boolean, this + * function returns FALSE. + */ +gboolean +gst_structure_get_boolean (const GstStructure * structure, + const gchar * fieldname, gboolean * value) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return FALSE; + if (!G_VALUE_HOLDS_BOOLEAN (&field->value)) + return FALSE; + + *value = gst_g_value_get_boolean_unchecked (&field->value); + + return TRUE; +} + +/** + * gst_structure_get_int: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @value: (out): a pointer to an int to set + * + * Sets the int pointed to by @value corresponding to the value of the + * given field. Caller is responsible for making sure the field exists + * and has the correct type. + * + * Returns: %TRUE if the value could be set correctly. If there was no field + * with @fieldname or the existing field did not contain an int, this function + * returns %FALSE. + */ +gboolean +gst_structure_get_int (const GstStructure * structure, + const gchar * fieldname, gint * value) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return FALSE; + if (!G_VALUE_HOLDS_INT (&field->value)) + return FALSE; + + *value = gst_g_value_get_int_unchecked (&field->value); + + return TRUE; +} + +/** + * gst_structure_get_uint: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @value: (out): a pointer to a uint to set + * + * Sets the uint pointed to by @value corresponding to the value of the + * given field. Caller is responsible for making sure the field exists + * and has the correct type. + * + * Returns: %TRUE if the value could be set correctly. If there was no field + * with @fieldname or the existing field did not contain a uint, this function + * returns %FALSE. + * + * Since: 0.10.15 + */ +gboolean +gst_structure_get_uint (const GstStructure * structure, + const gchar * fieldname, guint * value) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return FALSE; + if (!G_VALUE_HOLDS_UINT (&field->value)) + return FALSE; + + *value = gst_g_value_get_uint_unchecked (&field->value); + + return TRUE; +} + +/** + * gst_structure_get_date: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @value: (out callee-allocates): a pointer to a #GDate to set + * + * Sets the date pointed to by @value corresponding to the date of the + * given field. Caller is responsible for making sure the field exists + * and has the correct type. + * + * On success @value will point to a newly-allocated copy of the date which + * should be freed with g_date_free() when no longer needed (note: this is + * inconsistent with e.g. gst_structure_get_string() which doesn't return a + * copy of the string). + * + * Returns: TRUE if the value could be set correctly. If there was no field + * with @fieldname or the existing field did not contain a data, this function + * returns FALSE. + */ +gboolean +gst_structure_get_date (const GstStructure * structure, const gchar * fieldname, + GDate ** value) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return FALSE; + if (!GST_VALUE_HOLDS_DATE (&field->value)) + return FALSE; + + /* FIXME: 0.11 g_value_dup_boxed() -> g_value_get_boxed() */ + *value = g_value_dup_boxed (&field->value); + + return TRUE; +} + +/** + * gst_structure_get_date_time: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @value: (out callee-allocates): a pointer to a #GstDateTime to set + * + * Sets the datetime pointed to by @value corresponding to the datetime of the + * given field. Caller is responsible for making sure the field exists + * and has the correct type. + * + * On success @value will point to a reference of the datetime which + * should be unreffed with gst_date_time_unref() when no longer needed + * (note: this is inconsistent with e.g. gst_structure_get_string() + * which doesn't return a copy of the string). + * + * Returns: TRUE if the value could be set correctly. If there was no field + * with @fieldname or the existing field did not contain a data, this function + * returns FALSE. + * + * Since: 0.10.31 + */ +gboolean +gst_structure_get_date_time (const GstStructure * structure, + const gchar * fieldname, GstDateTime ** value) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return FALSE; + if (!GST_VALUE_HOLDS_DATE_TIME (&field->value)) + return FALSE; + + /* FIXME: 0.11 g_value_dup_boxed() -> g_value_get_boxed() */ + *value = g_value_dup_boxed (&field->value); + + return TRUE; +} + +/** + * gst_structure_get_clock_time: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @value: (out): a pointer to a #GstClockTime to set + * + * Sets the clock time pointed to by @value corresponding to the clock time + * of the given field. Caller is responsible for making sure the field exists + * and has the correct type. + * + * Returns: TRUE if the value could be set correctly. If there was no field + * with @fieldname or the existing field did not contain a #GstClockTime, this + * function returns FALSE. + */ +gboolean +gst_structure_get_clock_time (const GstStructure * structure, + const gchar * fieldname, GstClockTime * value) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return FALSE; + if (!G_VALUE_HOLDS_UINT64 (&field->value)) + return FALSE; + + *value = gst_g_value_get_uint64_unchecked (&field->value); + + return TRUE; +} + +/** + * gst_structure_get_double: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @value: (out): a pointer to a gdouble to set + * + * Sets the double pointed to by @value corresponding to the value of the + * given field. Caller is responsible for making sure the field exists + * and has the correct type. + * + * Returns: TRUE if the value could be set correctly. If there was no field + * with @fieldname or the existing field did not contain a double, this + * function returns FALSE. + */ +gboolean +gst_structure_get_double (const GstStructure * structure, + const gchar * fieldname, gdouble * value) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return FALSE; + if (!G_VALUE_HOLDS_DOUBLE (&field->value)) + return FALSE; + + *value = gst_g_value_get_double_unchecked (&field->value); + + return TRUE; +} + +/** + * gst_structure_get_string: + * @structure: a #GstStructure + * @fieldname: the name of a field + * + * Finds the field corresponding to @fieldname, and returns the string + * contained in the field's value. Caller is responsible for making + * sure the field exists and has the correct type. + * + * The string should not be modified, and remains valid until the next + * call to a gst_structure_*() function with the given structure. + * + * Returns: a pointer to the string or NULL when the field did not exist + * or did not contain a string. + */ +const gchar * +gst_structure_get_string (const GstStructure * structure, + const gchar * fieldname) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, NULL); + g_return_val_if_fail (fieldname != NULL, NULL); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return NULL; + if (!G_VALUE_HOLDS_STRING (&field->value)) + return NULL; + + return gst_g_value_get_string_unchecked (&field->value); +} + +/** + * gst_structure_get_enum: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @enumtype: the enum type of a field + * @value: (out): a pointer to an int to set + * + * Sets the int pointed to by @value corresponding to the value of the + * given field. Caller is responsible for making sure the field exists, + * has the correct type and that the enumtype is correct. + * + * Returns: TRUE if the value could be set correctly. If there was no field + * with @fieldname or the existing field did not contain an enum of the given + * type, this function returns FALSE. + */ +gboolean +gst_structure_get_enum (const GstStructure * structure, + const gchar * fieldname, GType enumtype, gint * value) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + g_return_val_if_fail (enumtype != G_TYPE_INVALID, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return FALSE; + if (!G_TYPE_CHECK_VALUE_TYPE (&field->value, enumtype)) + return FALSE; + + *value = g_value_get_enum (&field->value); + + return TRUE; +} + +/** + * gst_structure_get_fraction: + * @structure: a #GstStructure + * @fieldname: the name of a field + * @value_numerator: (out): a pointer to an int to set + * @value_denominator: (out): a pointer to an int to set + * + * Sets the integers pointed to by @value_numerator and @value_denominator + * corresponding to the value of the given field. Caller is responsible + * for making sure the field exists and has the correct type. + * + * Returns: TRUE if the values could be set correctly. If there was no field + * with @fieldname or the existing field did not contain a GstFraction, this + * function returns FALSE. + */ +gboolean +gst_structure_get_fraction (const GstStructure * structure, + const gchar * fieldname, gint * value_numerator, gint * value_denominator) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (fieldname != NULL, FALSE); + g_return_val_if_fail (value_numerator != NULL, FALSE); + g_return_val_if_fail (value_denominator != NULL, FALSE); + + field = gst_structure_get_field (structure, fieldname); + + if (field == NULL) + return FALSE; + if (!GST_VALUE_HOLDS_FRACTION (&field->value)) + return FALSE; + + *value_numerator = gst_value_get_fraction_numerator (&field->value); + *value_denominator = gst_value_get_fraction_denominator (&field->value); + + return TRUE; +} + +typedef struct _GstStructureAbbreviation +{ + const gchar *type_name; + GType type; +} +GstStructureAbbreviation; + +/* return a copy of an array of GstStructureAbbreviation containing all the + * known type_string, GType maps, including abbreviations for common types */ +static GstStructureAbbreviation * +gst_structure_get_abbrs (gint * n_abbrs) +{ + static GstStructureAbbreviation *abbrs = NULL; + static volatile gsize num = 0; + + if (g_once_init_enter (&num)) { + /* dynamically generate the array */ + gsize _num; + GstStructureAbbreviation dyn_abbrs[] = { + {"int", G_TYPE_INT} + , + {"i", G_TYPE_INT} + , + {"uint", G_TYPE_UINT} + , + {"u", G_TYPE_UINT} + , + {"float", G_TYPE_FLOAT} + , + {"f", G_TYPE_FLOAT} + , + {"double", G_TYPE_DOUBLE} + , + {"d", G_TYPE_DOUBLE} + , + {"buffer", GST_TYPE_BUFFER} + , + {"fraction", GST_TYPE_FRACTION} + , + {"boolean", G_TYPE_BOOLEAN} + , + {"bool", G_TYPE_BOOLEAN} + , + {"b", G_TYPE_BOOLEAN} + , + {"string", G_TYPE_STRING} + , + {"str", G_TYPE_STRING} + , + {"s", G_TYPE_STRING} + , + {"structure", GST_TYPE_STRUCTURE} + , + {"date", GST_TYPE_DATE} + , + {"datetime", GST_TYPE_DATE_TIME} + }; + _num = G_N_ELEMENTS (dyn_abbrs); + /* permanently allocate and copy the array now */ + abbrs = g_new0 (GstStructureAbbreviation, _num); + memcpy (abbrs, dyn_abbrs, sizeof (GstStructureAbbreviation) * _num); + g_once_init_leave (&num, _num); + } + *n_abbrs = num; + + return abbrs; +} + +/* given a type_name that could be a type abbreviation or a registered GType, + * return a matching GType */ +static GType +gst_structure_gtype_from_abbr (const char *type_name) +{ + int i; + GstStructureAbbreviation *abbrs; + gint n_abbrs; + + g_return_val_if_fail (type_name != NULL, G_TYPE_INVALID); + + abbrs = gst_structure_get_abbrs (&n_abbrs); + + for (i = 0; i < n_abbrs; i++) { + if (strcmp (type_name, abbrs[i].type_name) == 0) { + return abbrs[i].type; + } + } + + /* this is the fallback */ + return g_type_from_name (type_name); +} + +static const char * +gst_structure_to_abbr (GType type) +{ + int i; + GstStructureAbbreviation *abbrs; + gint n_abbrs; + + g_return_val_if_fail (type != G_TYPE_INVALID, NULL); + + abbrs = gst_structure_get_abbrs (&n_abbrs); + + for (i = 0; i < n_abbrs; i++) { + if (type == abbrs[i].type) { + return abbrs[i].type_name; + } + } + + return g_type_name (type); +} + +static GType +gst_structure_value_get_generic_type (GValue * val) +{ + if (G_VALUE_TYPE (val) == GST_TYPE_LIST + || G_VALUE_TYPE (val) == GST_TYPE_ARRAY) { + GArray *array = g_value_peek_pointer (val); + + if (array->len > 0) { + GValue *value = &g_array_index (array, GValue, 0); + + return gst_structure_value_get_generic_type (value); + } else { + return G_TYPE_INT; + } + } else if (G_VALUE_TYPE (val) == GST_TYPE_INT_RANGE) { + return G_TYPE_INT; + } else if (G_VALUE_TYPE (val) == GST_TYPE_INT64_RANGE) { + return G_TYPE_INT64; + } else if (G_VALUE_TYPE (val) == GST_TYPE_DOUBLE_RANGE) { + return G_TYPE_DOUBLE; + } else if (G_VALUE_TYPE (val) == GST_TYPE_FRACTION_RANGE) { + return GST_TYPE_FRACTION; + } + return G_VALUE_TYPE (val); +} + +gboolean +priv_gst_structure_append_to_gstring (const GstStructure * structure, + GString * s) +{ + GstStructureField *field; + guint i, len; + + g_return_val_if_fail (s != NULL, FALSE); + + g_string_append (s, g_quark_to_string (structure->name)); + len = GST_STRUCTURE_FIELDS (structure)->len; + for (i = 0; i < len; i++) { + char *t; + GType type; + + field = GST_STRUCTURE_FIELD (structure, i); + + t = gst_value_serialize (&field->value); + type = gst_structure_value_get_generic_type (&field->value); + + g_string_append_len (s, ", ", 2); + /* FIXME: do we need to escape fieldnames? */ + g_string_append (s, g_quark_to_string (field->name)); + g_string_append_len (s, "=(", 2); + g_string_append (s, gst_structure_to_abbr (type)); + g_string_append_c (s, ')'); + g_string_append (s, t == NULL ? "NULL" : t); + g_free (t); + } + + g_string_append_c (s, ';'); + return TRUE; +} + +/** + * gst_structure_to_string: + * @structure: a #GstStructure + * + * Converts @structure to a human-readable string representation. + * + * For debugging purposes its easier to do something like this: + * |[ + * GST_LOG ("structure is %" GST_PTR_FORMAT, structure); + * ]| + * This prints the structure in human readble form. + * + * Free-function: g_free + * + * Returns: (transfer full)L a pointer to string allocated by g_malloc(). + * g_free() after usage. + */ +gchar * +gst_structure_to_string (const GstStructure * structure) +{ + GString *s; + + /* NOTE: This function is potentially called by the debug system, + * so any calls to gst_log() (and GST_DEBUG(), GST_LOG(), etc.) + * should be careful to avoid recursion. This includes any functions + * called by gst_structure_to_string. In particular, calls should + * not use the GST_PTR_FORMAT extension. */ + + g_return_val_if_fail (structure != NULL, NULL); + + /* we estimate a minimum size based on the number of fields in order to + * avoid unnecessary reallocs within GString */ + s = g_string_sized_new (STRUCTURE_ESTIMATED_STRING_LEN (structure)); + priv_gst_structure_append_to_gstring (structure, s); + return g_string_free (s, FALSE); +} + +/* + * r will still point to the string. if end == next, the string will not be + * null-terminated. In all other cases it will be. + * end = pointer to char behind end of string, next = pointer to start of + * unread data. + * THIS FUNCTION MODIFIES THE STRING AND DETECTS INSIDE A NONTERMINATED STRING + */ +static gboolean +gst_structure_parse_string (gchar * s, gchar ** end, gchar ** next, + gboolean unescape) +{ + gchar *w; + + if (*s == 0) + return FALSE; + + if (*s != '"') { + int ret; + + ret = gst_structure_parse_simple_string (s, end); + *next = *end; + + return ret; + } + + if (unescape) { + w = s; + s++; + while (*s != '"') { + if (G_UNLIKELY (*s == 0)) + return FALSE; + if (G_UNLIKELY (*s == '\\')) + s++; + *w = *s; + w++; + s++; + } + s++; + } else { + /* Find the closing quotes */ + s++; + while (*s != '"') { + if (G_UNLIKELY (*s == 0)) + return FALSE; + if (G_UNLIKELY (*s == '\\')) + s++; + s++; + } + s++; + w = s; + } + + *end = w; + *next = s; + + return TRUE; +} + +static gboolean +gst_structure_parse_range (gchar * s, gchar ** after, GValue * value, + GType type) +{ + GValue value1 = { 0 }; + GValue value2 = { 0 }; + GType range_type; + gboolean ret; + + if (*s != '[') + return FALSE; + s++; + + ret = gst_structure_parse_value (s, &s, &value1, type); + if (ret == FALSE) + return FALSE; + + while (g_ascii_isspace (*s)) + s++; + + if (*s != ',') + return FALSE; + s++; + + while (g_ascii_isspace (*s)) + s++; + + ret = gst_structure_parse_value (s, &s, &value2, type); + if (ret == FALSE) + return FALSE; + + while (g_ascii_isspace (*s)) + s++; + + if (*s != ']') + return FALSE; + s++; + + if (G_VALUE_TYPE (&value1) != G_VALUE_TYPE (&value2)) + return FALSE; + + if (G_VALUE_TYPE (&value1) == G_TYPE_DOUBLE) { + range_type = GST_TYPE_DOUBLE_RANGE; + g_value_init (value, range_type); + gst_value_set_double_range (value, + gst_g_value_get_double_unchecked (&value1), + gst_g_value_get_double_unchecked (&value2)); + } else if (G_VALUE_TYPE (&value1) == G_TYPE_INT) { + range_type = GST_TYPE_INT_RANGE; + g_value_init (value, range_type); + gst_value_set_int_range (value, gst_g_value_get_int_unchecked (&value1), + gst_g_value_get_int_unchecked (&value2)); + } else if (G_VALUE_TYPE (&value1) == G_TYPE_INT64) { + range_type = GST_TYPE_INT64_RANGE; + g_value_init (value, range_type); + gst_value_set_int64_range (value, gst_g_value_get_int64_unchecked (&value1), + gst_g_value_get_int64_unchecked (&value2)); + } else if (G_VALUE_TYPE (&value1) == GST_TYPE_FRACTION) { + range_type = GST_TYPE_FRACTION_RANGE; + g_value_init (value, range_type); + gst_value_set_fraction_range (value, &value1, &value2); + } else { + return FALSE; + } + + *after = s; + return TRUE; +} + +static gboolean +gst_structure_parse_any_list (gchar * s, gchar ** after, GValue * value, + GType type, GType list_type, char begin, char end) +{ + GValue list_value = { 0 }; + gboolean ret; + GArray *array; + + g_value_init (value, list_type); + array = g_value_peek_pointer (value); + + if (*s != begin) + return FALSE; + s++; + + while (g_ascii_isspace (*s)) + s++; + if (*s == end) { + s++; + *after = s; + return TRUE; + } + + ret = gst_structure_parse_value (s, &s, &list_value, type); + if (ret == FALSE) + return FALSE; + + g_array_append_val (array, list_value); + + while (g_ascii_isspace (*s)) + s++; + + while (*s != end) { + if (*s != ',') + return FALSE; + s++; + + while (g_ascii_isspace (*s)) + s++; + + memset (&list_value, 0, sizeof (list_value)); + ret = gst_structure_parse_value (s, &s, &list_value, type); + if (ret == FALSE) + return FALSE; + + g_array_append_val (array, list_value); + while (g_ascii_isspace (*s)) + s++; + } + + s++; + + *after = s; + return TRUE; +} + +static gboolean +gst_structure_parse_list (gchar * s, gchar ** after, GValue * value, GType type) +{ + return gst_structure_parse_any_list (s, after, value, type, GST_TYPE_LIST, + '{', '}'); +} + +static gboolean +gst_structure_parse_array (gchar * s, gchar ** after, GValue * value, + GType type) +{ + return gst_structure_parse_any_list (s, after, value, type, + GST_TYPE_ARRAY, '<', '>'); +} + +static gboolean +gst_structure_parse_simple_string (gchar * str, gchar ** end) +{ + char *s = str; + + while (G_LIKELY (GST_ASCII_IS_STRING (*s))) { + s++; + } + + *end = s; + + return (s != str); +} + +static gboolean +gst_structure_parse_field (gchar * str, + gchar ** after, GstStructureField * field) +{ + gchar *name; + gchar *name_end; + gchar *s; + gchar c; + + s = str; + + while (g_ascii_isspace (*s) || (s[0] == '\\' && g_ascii_isspace (s[1]))) + s++; + name = s; + if (G_UNLIKELY (!gst_structure_parse_simple_string (s, &name_end))) { + GST_WARNING ("failed to parse simple string, str=%s", str); + return FALSE; + } + + s = name_end; + while (g_ascii_isspace (*s) || (s[0] == '\\' && g_ascii_isspace (s[1]))) + s++; + + if (G_UNLIKELY (*s != '=')) { + GST_WARNING ("missing assignment operator in the field, str=%s", str); + return FALSE; + } + s++; + + c = *name_end; + *name_end = '\0'; + field->name = g_quark_from_string (name); + GST_DEBUG ("trying field name '%s'", name); + *name_end = c; + + if (G_UNLIKELY (!gst_structure_parse_value (s, &s, &field->value, + G_TYPE_INVALID))) { + GST_WARNING ("failed to parse value %s", str); + return FALSE; + } + + *after = s; + return TRUE; +} + +static gboolean +gst_structure_parse_value (gchar * str, + gchar ** after, GValue * value, GType default_type) +{ + gchar *type_name; + gchar *type_end; + gchar *value_s; + gchar *value_end; + gchar *s; + gchar c; + int ret = 0; + GType type = default_type; + + s = str; + while (g_ascii_isspace (*s)) + s++; + + /* check if there's a (type_name) 'cast' */ + type_name = NULL; + if (*s == '(') { + s++; + while (g_ascii_isspace (*s)) + s++; + type_name = s; + if (G_UNLIKELY (!gst_structure_parse_simple_string (s, &type_end))) + return FALSE; + s = type_end; + while (g_ascii_isspace (*s)) + s++; + if (G_UNLIKELY (*s != ')')) + return FALSE; + s++; + while (g_ascii_isspace (*s)) + s++; + + c = *type_end; + *type_end = 0; + type = gst_structure_gtype_from_abbr (type_name); + GST_DEBUG ("trying type name '%s'", type_name); + *type_end = c; + + if (G_UNLIKELY (type == G_TYPE_INVALID)) { + GST_WARNING ("invalid type"); + return FALSE; + } + } + + while (g_ascii_isspace (*s)) + s++; + if (*s == '[') { + ret = gst_structure_parse_range (s, &s, value, type); + } else if (*s == '{') { + ret = gst_structure_parse_list (s, &s, value, type); + } else if (*s == '<') { + ret = gst_structure_parse_array (s, &s, value, type); + } else { + value_s = s; + + if (G_UNLIKELY (type == G_TYPE_INVALID)) { + GType try_types[] = + { G_TYPE_INT, G_TYPE_DOUBLE, GST_TYPE_FRACTION, G_TYPE_BOOLEAN, + G_TYPE_STRING + }; + int i; + + if (G_UNLIKELY (!gst_structure_parse_string (s, &value_end, &s, TRUE))) + return FALSE; + /* Set NULL terminator for deserialization */ + c = *value_end; + *value_end = '\0'; + + for (i = 0; i < G_N_ELEMENTS (try_types); i++) { + g_value_init (value, try_types[i]); + ret = gst_value_deserialize (value, value_s); + if (ret) + break; + g_value_unset (value); + } + } else { + g_value_init (value, type); + + if (G_UNLIKELY (!gst_structure_parse_string (s, &value_end, &s, + (type != G_TYPE_STRING)))) + return FALSE; + /* Set NULL terminator for deserialization */ + c = *value_end; + *value_end = '\0'; + + ret = gst_value_deserialize (value, value_s); + if (G_UNLIKELY (!ret)) + g_value_unset (value); + } + *value_end = c; + } + + *after = s; + + return ret; +} + +/** + * gst_structure_from_string: + * @string: a string representation of a #GstStructure. + * @end: (out) (allow-none): pointer to store the end of the string in. + * + * Creates a #GstStructure from a string representation. + * If end is not NULL, a pointer to the place inside the given string + * where parsing ended will be returned. + * + * Free-function: gst_structure_free + * + * Returns: (transfer full): a new #GstStructure or NULL when the string could + * not be parsed. Free with gst_structure_free() after use. + */ +GstStructure * +gst_structure_from_string (const gchar * string, gchar ** end) +{ + char *name; + char *copy; + char *w; + char *r; + char save; + GstStructure *structure = NULL; + GstStructureField field; + + g_return_val_if_fail (string != NULL, NULL); + + copy = g_strdup (string); + r = copy; + + /* skip spaces (FIXME: _isspace treats tabs and newlines as space!) */ + while (*r && (g_ascii_isspace (*r) || (r[0] == '\\' + && g_ascii_isspace (r[1])))) + r++; + + name = r; + if (G_UNLIKELY (!gst_structure_parse_string (r, &w, &r, TRUE))) { + GST_WARNING ("Failed to parse structure string '%s'", string); + goto error; + } + + save = *w; + *w = '\0'; + structure = gst_structure_empty_new (name); + *w = save; + + if (G_UNLIKELY (structure == NULL)) + goto error; + + do { + while (*r && (g_ascii_isspace (*r) || (r[0] == '\\' + && g_ascii_isspace (r[1])))) + r++; + if (*r == ';') { + /* end of structure, get the next char and finish */ + r++; + break; + } + if (*r == '\0') { + /* accept \0 as end delimiter */ + break; + } + if (G_UNLIKELY (*r != ',')) { + GST_WARNING ("Failed to find delimiter, r=%s", r); + goto error; + } + r++; + while (*r && (g_ascii_isspace (*r) || (r[0] == '\\' + && g_ascii_isspace (r[1])))) + r++; + + memset (&field, 0, sizeof (field)); + if (G_UNLIKELY (!gst_structure_parse_field (r, &r, &field))) { + GST_WARNING ("Failed to parse field, r=%s", r); + goto error; + } + gst_structure_set_field (structure, &field); + } while (TRUE); + + if (end) + *end = (char *) string + (r - copy); + else if (*r) + g_warning ("gst_structure_from_string did not consume whole string," + " but caller did not provide end pointer (\"%s\")", string); + + g_free (copy); + return structure; + +error: + if (structure) + gst_structure_free (structure); + g_free (copy); + return NULL; +} + +static void +gst_structure_transform_to_string (const GValue * src_value, + GValue * dest_value) +{ + g_return_if_fail (src_value != NULL); + g_return_if_fail (dest_value != NULL); + + dest_value->data[0].v_pointer = + gst_structure_to_string (src_value->data[0].v_pointer); +} + +static GstStructure * +gst_structure_copy_conditional (const GstStructure * structure) +{ + if (structure) + return gst_structure_copy (structure); + return NULL; +} + +/* fixate utility functions */ + +/** + * gst_structure_fixate_field_nearest_int: + * @structure: a #GstStructure + * @field_name: a field in @structure + * @target: the target value of the fixation + * + * Fixates a #GstStructure by changing the given field to the nearest + * integer to @target that is a subset of the existing field. + * + * Returns: TRUE if the structure could be fixated + */ +gboolean +gst_structure_fixate_field_nearest_int (GstStructure * structure, + const char *field_name, int target) +{ + const GValue *value; + + g_return_val_if_fail (gst_structure_has_field (structure, field_name), FALSE); + g_return_val_if_fail (IS_MUTABLE (structure), FALSE); + + value = gst_structure_get_value (structure, field_name); + + if (G_VALUE_TYPE (value) == G_TYPE_INT) { + /* already fixed */ + return FALSE; + } else if (G_VALUE_TYPE (value) == GST_TYPE_INT_RANGE) { + int x; + + x = gst_value_get_int_range_min (value); + if (target < x) + target = x; + x = gst_value_get_int_range_max (value); + if (target > x) + target = x; + gst_structure_set (structure, field_name, G_TYPE_INT, target, NULL); + return TRUE; + } else if (G_VALUE_TYPE (value) == GST_TYPE_LIST) { + const GValue *list_value; + int i, n; + int best = 0; + int best_index = -1; + + n = gst_value_list_get_size (value); + for (i = 0; i < n; i++) { + list_value = gst_value_list_get_value (value, i); + if (G_VALUE_TYPE (list_value) == G_TYPE_INT) { + int x = gst_g_value_get_int_unchecked (list_value); + + if (best_index == -1 || (ABS (target - x) < ABS (target - best))) { + best_index = i; + best = x; + } + } + } + if (best_index != -1) { + gst_structure_set (structure, field_name, G_TYPE_INT, best, NULL); + return TRUE; + } + return FALSE; + } + + return FALSE; +} + +/** + * gst_structure_fixate_field_nearest_double: + * @structure: a #GstStructure + * @field_name: a field in @structure + * @target: the target value of the fixation + * + * Fixates a #GstStructure by changing the given field to the nearest + * double to @target that is a subset of the existing field. + * + * Returns: TRUE if the structure could be fixated + */ +gboolean +gst_structure_fixate_field_nearest_double (GstStructure * structure, + const char *field_name, double target) +{ + const GValue *value; + + g_return_val_if_fail (gst_structure_has_field (structure, field_name), FALSE); + g_return_val_if_fail (IS_MUTABLE (structure), FALSE); + + value = gst_structure_get_value (structure, field_name); + + if (G_VALUE_TYPE (value) == G_TYPE_DOUBLE) { + /* already fixed */ + return FALSE; + } else if (G_VALUE_TYPE (value) == GST_TYPE_DOUBLE_RANGE) { + double x; + + x = gst_value_get_double_range_min (value); + if (target < x) + target = x; + x = gst_value_get_double_range_max (value); + if (target > x) + target = x; + gst_structure_set (structure, field_name, G_TYPE_DOUBLE, target, NULL); + return TRUE; + } else if (G_VALUE_TYPE (value) == GST_TYPE_LIST) { + const GValue *list_value; + int i, n; + double best = 0; + int best_index = -1; + + n = gst_value_list_get_size (value); + for (i = 0; i < n; i++) { + list_value = gst_value_list_get_value (value, i); + if (G_VALUE_TYPE (list_value) == G_TYPE_DOUBLE) { + double x = gst_g_value_get_double_unchecked (list_value); + + if (best_index == -1 || (ABS (target - x) < ABS (target - best))) { + best_index = i; + best = x; + } + } + } + if (best_index != -1) { + gst_structure_set (structure, field_name, G_TYPE_DOUBLE, best, NULL); + return TRUE; + } + return FALSE; + } + + return FALSE; + +} + +/** + * gst_structure_fixate_field_boolean: + * @structure: a #GstStructure + * @field_name: a field in @structure + * @target: the target value of the fixation + * + * Fixates a #GstStructure by changing the given @field_name field to the given + * @target boolean if that field is not fixed yet. + * + * Returns: TRUE if the structure could be fixated + */ +gboolean +gst_structure_fixate_field_boolean (GstStructure * structure, + const char *field_name, gboolean target) +{ + const GValue *value; + + g_return_val_if_fail (gst_structure_has_field (structure, field_name), FALSE); + g_return_val_if_fail (IS_MUTABLE (structure), FALSE); + + value = gst_structure_get_value (structure, field_name); + + if (G_VALUE_TYPE (value) == G_TYPE_BOOLEAN) { + /* already fixed */ + return FALSE; + } else if (G_VALUE_TYPE (value) == GST_TYPE_LIST) { + const GValue *list_value; + int i, n; + int best = 0; + int best_index = -1; + + n = gst_value_list_get_size (value); + for (i = 0; i < n; i++) { + list_value = gst_value_list_get_value (value, i); + if (G_VALUE_TYPE (list_value) == G_TYPE_BOOLEAN) { + gboolean x = gst_g_value_get_boolean_unchecked (list_value); + + if (best_index == -1 || x == target) { + best_index = i; + best = x; + } + } + } + if (best_index != -1) { + gst_structure_set (structure, field_name, G_TYPE_BOOLEAN, best, NULL); + return TRUE; + } + return FALSE; + } + + return FALSE; +} + +/** + * gst_structure_fixate_field_string: + * @structure: a #GstStructure + * @field_name: a field in @structure + * @target: the target value of the fixation + * + * Fixates a #GstStructure by changing the given @field_name field to the given + * @target string if that field is not fixed yet. + * + * Returns: TRUE if the structure could be fixated + * + * Since: 0.10.30 + */ +gboolean +gst_structure_fixate_field_string (GstStructure * structure, + const gchar * field_name, const gchar * target) +{ + const GValue *value; + + g_return_val_if_fail (gst_structure_has_field (structure, field_name), FALSE); + g_return_val_if_fail (IS_MUTABLE (structure), FALSE); + + value = gst_structure_get_value (structure, field_name); + + if (G_VALUE_TYPE (value) == G_TYPE_STRING) { + /* already fixed */ + return FALSE; + } else if (G_VALUE_TYPE (value) == GST_TYPE_LIST) { + const GValue *list_value; + int i, n; + const gchar *best = NULL; + int best_index = -1; + + n = gst_value_list_get_size (value); + for (i = 0; i < n; i++) { + list_value = gst_value_list_get_value (value, i); + if (G_VALUE_TYPE (list_value) == G_TYPE_STRING) { + const gchar *x = g_value_get_string (list_value); + + if (best_index == -1 || g_str_equal (x, target)) { + best_index = i; + best = x; + } + } + } + if (best_index != -1) { + gst_structure_set (structure, field_name, G_TYPE_STRING, best, NULL); + return TRUE; + } + return FALSE; + } + + return FALSE; +} + +/** + * gst_structure_fixate_field_nearest_fraction: + * @structure: a #GstStructure + * @field_name: a field in @structure + * @target_numerator: The numerator of the target value of the fixation + * @target_denominator: The denominator of the target value of the fixation + * + * Fixates a #GstStructure by changing the given field to the nearest + * fraction to @target_numerator/@target_denominator that is a subset + * of the existing field. + * + * Returns: TRUE if the structure could be fixated + */ +gboolean +gst_structure_fixate_field_nearest_fraction (GstStructure * structure, + const char *field_name, const gint target_numerator, + const gint target_denominator) +{ + const GValue *value; + + g_return_val_if_fail (gst_structure_has_field (structure, field_name), FALSE); + g_return_val_if_fail (IS_MUTABLE (structure), FALSE); + + value = gst_structure_get_value (structure, field_name); + + if (G_VALUE_TYPE (value) == GST_TYPE_FRACTION) { + /* already fixed */ + return FALSE; + } else if (G_VALUE_TYPE (value) == GST_TYPE_FRACTION_RANGE) { + const GValue *x, *new_value; + GValue target = { 0 }; + g_value_init (&target, GST_TYPE_FRACTION); + gst_value_set_fraction (&target, target_numerator, target_denominator); + + new_value = ⌖ + x = gst_value_get_fraction_range_min (value); + if (gst_value_compare (&target, x) == GST_VALUE_LESS_THAN) + new_value = x; + x = gst_value_get_fraction_range_max (value); + if (gst_value_compare (&target, x) == GST_VALUE_GREATER_THAN) + new_value = x; + + gst_structure_set_value (structure, field_name, new_value); + g_value_unset (&target); + return TRUE; + } else if (G_VALUE_TYPE (value) == GST_TYPE_LIST) { + const GValue *list_value; + int i, n; + const GValue *best = NULL; + gdouble target; + gdouble cur_diff; + gdouble best_diff = G_MAXDOUBLE; + + target = (gdouble) target_numerator / (gdouble) target_denominator; + + GST_DEBUG ("target %g, best %g", target, best_diff); + + best = NULL; + + n = gst_value_list_get_size (value); + for (i = 0; i < n; i++) { + list_value = gst_value_list_get_value (value, i); + if (G_VALUE_TYPE (list_value) == GST_TYPE_FRACTION) { + gint num, denom; + gdouble list_double; + + num = gst_value_get_fraction_numerator (list_value); + denom = gst_value_get_fraction_denominator (list_value); + + list_double = ((gdouble) num / (gdouble) denom); + cur_diff = target - list_double; + + GST_DEBUG ("curr diff %g, list %g", cur_diff, list_double); + + if (cur_diff < 0) + cur_diff = -cur_diff; + + if (!best || cur_diff < best_diff) { + GST_DEBUG ("new best %g", list_double); + best = list_value; + best_diff = cur_diff; + } + } + } + if (best != NULL) { + gst_structure_set_value (structure, field_name, best); + return TRUE; + } + } + + return FALSE; +} + +static gboolean +default_fixate (GQuark field_id, const GValue * value, gpointer data) +{ + GstStructure *s = data; + GValue v = { 0 }; + + if (gst_value_fixate (&v, value)) { + gst_structure_id_set_value (s, field_id, &v); + g_value_unset (&v); + } + return TRUE; +} + +/** + * gst_structure_fixate_field: + * @structure: a #GstStructure + * @field_name: a field in @structure + * + * Fixates a #GstStructure by changing the given field with its fixated value. + * + * Returns: TRUE if the structure field could be fixated + */ +gboolean +gst_structure_fixate_field (GstStructure * structure, const char *field_name) +{ + GstStructureField *field; + + g_return_val_if_fail (structure != NULL, FALSE); + g_return_val_if_fail (IS_MUTABLE (structure), FALSE); + + if (!(field = gst_structure_get_field (structure, field_name))) + return FALSE; + + return default_fixate (field->name, &field->value, structure); +} + +/* our very own version of G_VALUE_LCOPY that allows NULL return locations + * (useful for message parsing functions where the return location is user + * supplied and the user may pass NULL if the value isn't of interest) */ +#define GST_VALUE_LCOPY(value, var_args, flags, __error, fieldname) \ +G_STMT_START { \ + const GValue *_value = (value); \ + guint _flags = (flags); \ + GType _value_type = G_VALUE_TYPE (_value); \ + GTypeValueTable *_vtable = g_type_value_table_peek (_value_type); \ + gchar *_lcopy_format = _vtable->lcopy_format; \ + GTypeCValue _cvalues[G_VALUE_COLLECT_FORMAT_MAX_LENGTH] = { { 0, }, }; \ + guint _n_values = 0; \ + \ + while (*_lcopy_format != '\0') { \ + g_assert (*_lcopy_format == G_VALUE_COLLECT_POINTER); \ + _cvalues[_n_values++].v_pointer = va_arg ((var_args), gpointer); \ + _lcopy_format++; \ + } \ + if (_n_values == 2 && !!_cvalues[0].v_pointer != !!_cvalues[1].v_pointer) { \ + *(__error) = g_strdup_printf ("either all or none of the return " \ + "locations for field '%s' need to be NULL", fieldname); \ + } else if (_cvalues[0].v_pointer != NULL) { \ + *(__error) = _vtable->lcopy_value (_value, _n_values, _cvalues, _flags); \ + } \ +} G_STMT_END + +/** + * gst_structure_get_valist: + * @structure: a #GstStructure + * @first_fieldname: the name of the first field to read + * @args: variable arguments + * + * Parses the variable arguments and reads fields from @structure accordingly. + * valist-variant of gst_structure_get(). Look at the documentation of + * gst_structure_get() for more details. + * + * Returns: TRUE, or FALSE if there was a problem reading any of the fields + * + * Since: 0.10.24 + */ +gboolean +gst_structure_get_valist (const GstStructure * structure, + const char *first_fieldname, va_list args) +{ + const char *field_name; + GType expected_type = G_TYPE_INVALID; + + g_return_val_if_fail (GST_IS_STRUCTURE (structure), FALSE); + g_return_val_if_fail (first_fieldname != NULL, FALSE); + + field_name = first_fieldname; + while (field_name) { + const GValue *val = NULL; + gchar *err = NULL; + + expected_type = va_arg (args, GType); + + val = gst_structure_get_value (structure, field_name); + + if (val == NULL) + goto no_such_field; + + if (G_VALUE_TYPE (val) != expected_type) + goto wrong_type; + + GST_VALUE_LCOPY (val, args, 0, &err, field_name); + if (err) { + g_warning ("%s: %s", G_STRFUNC, err); + g_free (err); + return FALSE; + } + + field_name = va_arg (args, const gchar *); + } + + return TRUE; + +/* ERRORS */ +no_such_field: + { + GST_WARNING ("Expected field '%s' in structure: %" GST_PTR_FORMAT, + field_name, structure); + return FALSE; + } +wrong_type: + { + GST_WARNING ("Expected field '%s' in structure to be of type '%s', but " + "field was of type '%s': %" GST_PTR_FORMAT, field_name, + GST_STR_NULL (g_type_name (expected_type)), + G_VALUE_TYPE_NAME (gst_structure_get_value (structure, field_name)), + structure); + return FALSE; + } +} + +/** + * gst_structure_id_get_valist: + * @structure: a #GstStructure + * @first_field_id: the quark of the first field to read + * @args: variable arguments + * + * Parses the variable arguments and reads fields from @structure accordingly. + * valist-variant of gst_structure_id_get(). Look at the documentation of + * gst_structure_id_get() for more details. + * + * Returns: TRUE, or FALSE if there was a problem reading any of the fields + * + * Since: 0.10.24 + */ +gboolean +gst_structure_id_get_valist (const GstStructure * structure, + GQuark first_field_id, va_list args) +{ + GQuark field_id; + GType expected_type = G_TYPE_INVALID; + + g_return_val_if_fail (GST_IS_STRUCTURE (structure), FALSE); + g_return_val_if_fail (first_field_id != 0, FALSE); + + field_id = first_field_id; + while (field_id) { + const GValue *val = NULL; + gchar *err = NULL; + + expected_type = va_arg (args, GType); + + val = gst_structure_id_get_value (structure, field_id); + + if (val == NULL) + goto no_such_field; + + if (G_VALUE_TYPE (val) != expected_type) + goto wrong_type; + + GST_VALUE_LCOPY (val, args, 0, &err, g_quark_to_string (field_id)); + if (err) { + g_warning ("%s: %s", G_STRFUNC, err); + g_free (err); + return FALSE; + } + + field_id = va_arg (args, GQuark); + } + + return TRUE; + +/* ERRORS */ +no_such_field: + { + GST_WARNING ("Expected field '%s' in structure: %" GST_PTR_FORMAT, + GST_STR_NULL (g_quark_to_string (field_id)), structure); + return FALSE; + } +wrong_type: + { + GST_WARNING ("Expected field '%s' in structure to be of type '%s', but " + "field was of type '%s': %" GST_PTR_FORMAT, + g_quark_to_string (field_id), + GST_STR_NULL (g_type_name (expected_type)), + G_VALUE_TYPE_NAME (gst_structure_id_get_value (structure, field_id)), + structure); + return FALSE; + } +} + +/** + * gst_structure_get: + * @structure: a #GstStructure + * @first_fieldname: the name of the first field to read + * @...: variable arguments + * + * Parses the variable arguments and reads fields from @structure accordingly. + * Variable arguments should be in the form field name, field type + * (as a GType), pointer(s) to a variable(s) to hold the return value(s). + * The last variable argument should be NULL. + * + * For refcounted (mini)objects you will acquire your own reference which + * you must release with a suitable _unref() when no longer needed. For + * strings and boxed types you will acquire a copy which you will need to + * release with either g_free() or the suitable function for the boxed type. + * + * Returns: FALSE if there was a problem reading any of the fields (e.g. + * because the field requested did not exist, or was of a type other + * than the type specified), otherwise TRUE. + * + * Since: 0.10.24 + */ +gboolean +gst_structure_get (const GstStructure * structure, const char *first_fieldname, + ...) +{ + gboolean ret; + va_list args; + + g_return_val_if_fail (GST_IS_STRUCTURE (structure), FALSE); + g_return_val_if_fail (first_fieldname != NULL, FALSE); + + va_start (args, first_fieldname); + ret = gst_structure_get_valist (structure, first_fieldname, args); + va_end (args); + + return ret; +} + +/** + * gst_structure_id_get: + * @structure: a #GstStructure + * @first_field_id: the quark of the first field to read + * @...: variable arguments + * + * Parses the variable arguments and reads fields from @structure accordingly. + * Variable arguments should be in the form field id quark, field type + * (as a GType), pointer(s) to a variable(s) to hold the return value(s). + * The last variable argument should be NULL (technically it should be a + * 0 quark, but we require NULL so compilers that support it can check for + * the NULL terminator and warn if it's not there). + * + * This function is just like gst_structure_get() only that it is slightly + * more efficient since it saves the string-to-quark lookup in the global + * quark hashtable. + * + * For refcounted (mini)objects you will acquire your own reference which + * you must release with a suitable _unref() when no longer needed. For + * strings and boxed types you will acquire a copy which you will need to + * release with either g_free() or the suitable function for the boxed type. + * + * Returns: FALSE if there was a problem reading any of the fields (e.g. + * because the field requested did not exist, or was of a type other + * than the type specified), otherwise TRUE. + * + * Since: 0.10.24 + */ +gboolean +gst_structure_id_get (const GstStructure * structure, GQuark first_field_id, + ...) +{ + gboolean ret; + va_list args; + + g_return_val_if_fail (GST_IS_STRUCTURE (structure), FALSE); + g_return_val_if_fail (first_field_id != 0, FALSE); + + va_start (args, first_field_id); + ret = gst_structure_id_get_valist (structure, first_field_id, args); + va_end (args); + + return ret; +} + +static gboolean +gst_structure_is_equal_foreach (GQuark field_id, const GValue * val2, + gpointer data) +{ + const GstStructure *struct1 = (const GstStructure *) data; + const GValue *val1 = gst_structure_id_get_value (struct1, field_id); + + if (G_UNLIKELY (val1 == NULL)) + return FALSE; + if (gst_value_compare (val1, val2) == GST_VALUE_EQUAL) { + return TRUE; + } + + return FALSE; +} + +/** + * gst_structure_is_equal: + * @structure1: a #GstStructure. + * @structure2: a #GstStructure. + * + * Tests if the two #GstStructure are equal. + * + * Returns: TRUE if the two structures have the same name and field. + * + * Since: 0.10.35 + **/ +gboolean +gst_structure_is_equal (const GstStructure * structure1, + const GstStructure * structure2) +{ + g_return_val_if_fail (GST_IS_STRUCTURE (structure1), FALSE); + g_return_val_if_fail (GST_IS_STRUCTURE (structure2), FALSE); + + if (structure1->name != structure2->name) { + return FALSE; + } + if (GST_STRUCTURE_FIELDS (structure1)->len != + GST_STRUCTURE_FIELDS (structure2)->len) { + return FALSE; + } + + return gst_structure_foreach (structure1, gst_structure_is_equal_foreach, + (gpointer) structure2); +} + + +typedef struct +{ + GstStructure *dest; + const GstStructure *intersect; +} +IntersectData; + +static gboolean +gst_structure_intersect_field1 (GQuark id, const GValue * val1, gpointer data) +{ + IntersectData *idata = (IntersectData *) data; + const GValue *val2 = gst_structure_id_get_value (idata->intersect, id); + + if (G_UNLIKELY (val2 == NULL)) { + gst_structure_id_set_value (idata->dest, id, val1); + } else { + GValue dest_value = { 0 }; + if (gst_value_intersect (&dest_value, val1, val2)) { + gst_structure_id_set_value (idata->dest, id, &dest_value); + g_value_unset (&dest_value); + } else { + return FALSE; + } + } + return TRUE; +} + +static gboolean +gst_structure_intersect_field2 (GQuark id, const GValue * val1, gpointer data) +{ + IntersectData *idata = (IntersectData *) data; + const GValue *val2 = gst_structure_id_get_value (idata->intersect, id); + + if (G_UNLIKELY (val2 == NULL)) { + gst_structure_id_set_value (idata->dest, id, val1); + } + return TRUE; +} + +/** + * gst_structure_intersect: + * @struct1: a #GstStructure + * @struct2: a #GstStructure + * + * Interesects @struct1 and @struct2 and returns the intersection. + * + * Returns: Intersection of @struct1 and @struct2 + * + * Since: 0.10.35 + */ +GstStructure * +gst_structure_intersect (const GstStructure * struct1, + const GstStructure * struct2) +{ + IntersectData data; + + g_assert (struct1 != NULL); + g_assert (struct2 != NULL); + + if (G_UNLIKELY (struct1->name != struct2->name)) + return NULL; + + /* copy fields from struct1 which we have not in struct2 to target + * intersect if we have the field in both */ + data.dest = gst_structure_id_empty_new (struct1->name); + data.intersect = struct2; + if (G_UNLIKELY (!gst_structure_foreach ((GstStructure *) struct1, + gst_structure_intersect_field1, &data))) + goto error; + + /* copy fields from struct2 which we have not in struct1 to target */ + data.intersect = struct1; + if (G_UNLIKELY (!gst_structure_foreach ((GstStructure *) struct2, + gst_structure_intersect_field2, &data))) + goto error; + + return data.dest; + +error: + gst_structure_free (data.dest); + return NULL; +} + +static gboolean +gst_caps_structure_can_intersect_field (GQuark id, const GValue * val1, + gpointer data) +{ + GstStructure *other = (GstStructure *) data; + const GValue *val2 = gst_structure_id_get_value (other, id); + + if (G_LIKELY (val2)) { + if (!gst_value_can_intersect (val1, val2)) { + return FALSE; + } else { + gint eq = gst_value_compare (val1, val2); + + if (eq == GST_VALUE_UNORDERED) { + /* we need to try interseting */ + GValue dest_value = { 0 }; + if (gst_value_intersect (&dest_value, val1, val2)) { + g_value_unset (&dest_value); + } else { + return FALSE; + } + } else if (eq != GST_VALUE_EQUAL) { + return FALSE; + } + } + } + return TRUE; +} + +/** + * gst_structure_can_intersect: + * @struct1: a #GstStructure + * @struct2: a #GstStructure + * + * Tries intersecting @struct1 and @struct2 and reports whether the result + * would not be empty. + * + * Returns: %TRUE if intersection would not be empty + * + * Since: 0.10.35 + */ +gboolean +gst_structure_can_intersect (const GstStructure * struct1, + const GstStructure * struct2) +{ + g_return_val_if_fail (GST_IS_STRUCTURE (struct1), FALSE); + g_return_val_if_fail (GST_IS_STRUCTURE (struct2), FALSE); + + if (G_UNLIKELY (struct1->name != struct2->name)) + return FALSE; + + /* tries to intersect if we have the field in both */ + if (G_UNLIKELY (!gst_structure_foreach ((GstStructure *) struct1, + gst_caps_structure_can_intersect_field, (gpointer) struct2))) + return FALSE; + + return TRUE; +} + +static gboolean +gst_caps_structure_is_subset_field (GQuark field_id, const GValue * value, + gpointer user_data) +{ + GstStructure *superset = user_data; + GValue subtraction = { 0, }; + const GValue *other; + + if (!(other = gst_structure_id_get_value (superset, field_id))) + /* field is missing in the superset => is subset */ + return TRUE; + + /* equal values are subset */ + if (gst_value_compare (other, value) == GST_VALUE_EQUAL) + return TRUE; + + /* + * 1 - [1,2] = empty + * -> !subset + * + * [1,2] - 1 = 2 + * -> 1 - [1,2] = empty + * -> subset + * + * [1,3] - [1,2] = 3 + * -> [1,2] - [1,3] = empty + * -> subset + * + * {1,2} - {1,3} = 2 + * -> {1,3} - {1,2} = 3 + * -> !subset + * + * First caps subtraction needs to return a non-empty set, second + * subtractions needs to give en empty set. + */ + if (gst_value_subtract (&subtraction, other, value)) { + g_value_unset (&subtraction); + /* !empty result, swapping must be empty */ + if (!gst_value_subtract (&subtraction, value, other)) + return TRUE; + + g_value_unset (&subtraction); + } + return FALSE; +} + +/** + * gst_structure_is_subset: + * @subset: a #GstStructure + * @superset: a potentially greater #GstStructure + * + * Checks if @subset is a subset of @superset, i.e. has the same + * structure name and for all fields that are existing in @superset, + * @subset has a value that is a subset of the value in @superset. + * + * Returns: %TRUE if @subset is a subset of @superset + * + * Since: 0.10.35 + */ +gboolean +gst_structure_is_subset (const GstStructure * subset, + const GstStructure * superset) +{ + if ((superset->name != subset->name) || + (gst_structure_n_fields (superset) > gst_structure_n_fields (subset))) + return FALSE; + + return gst_structure_foreach ((GstStructure *) subset, + gst_caps_structure_is_subset_field, (gpointer) superset); +} + + +/** + * gst_structure_fixate: + * @structure: a #GstStructure + * + * Fixate all values in @structure using gst_value_fixate(). + * @structure will be modified in-place and should be writable. + */ +void +gst_structure_fixate (GstStructure * structure) +{ + g_return_if_fail (GST_IS_STRUCTURE (structure)); + + gst_structure_foreach (structure, default_fixate, structure); +} diff --git a/gst/gststructure.h b/gst/gststructure.h new file mode 100644 index 0000000..61e4253 --- /dev/null +++ b/gst/gststructure.h @@ -0,0 +1,258 @@ +/* GStreamer + * Copyright (C) 2003 David A. Schleef <ds@schleef.org> + * + * 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_STRUCTURE_H__ +#define __GST_STRUCTURE_H__ + +#include <gst/gstconfig.h> +#include <glib-object.h> +#include <gst/gstclock.h> +#include <gst/gstdatetime.h> +#include <gst/glib-compat.h> + +G_BEGIN_DECLS + +extern GType _gst_structure_type; + +typedef struct _GstStructure GstStructure; + +#define GST_TYPE_STRUCTURE (_gst_structure_type) +#define GST_IS_STRUCTURE(object) ((object) && (GST_STRUCTURE(object)->type == GST_TYPE_STRUCTURE)) +#define GST_STRUCTURE_CAST(object) ((GstStructure *)(object)) +#define GST_STRUCTURE(object) (GST_STRUCTURE_CAST(object)) + + +/** + * GstStructureForeachFunc: + * @field_id: the #GQuark of the field name + * @value: the #GValue of the field + * @user_data: user data + * + * A function that will be called in gst_structure_foreach(). The function may + * not modify @value. + * + * Returns: TRUE if the foreach operation should continue, FALSE if + * the foreach operation should stop with FALSE. + */ +typedef gboolean (*GstStructureForeachFunc) (GQuark field_id, + const GValue * value, + gpointer user_data); + +/** + * GstStructureMapFunc: + * @field_id: the #GQuark of the field name + * @value: the #GValue of the field + * @user_data: user data + * + * A function that will be called in gst_structure_map_in_place(). The function + * may modify @value. + * + * Returns: TRUE if the map operation should continue, FALSE if + * the map operation should stop with FALSE. + */ +typedef gboolean (*GstStructureMapFunc) (GQuark field_id, + GValue * value, + gpointer user_data); + +/** + * GstStructure: + * @type: the GType of a structure + * + * The GstStructure object. Most fields are private. + */ +struct _GstStructure { + GType type; + + /*< private >*/ + GQuark name; +}; + +GstStructure * gst_structure_empty_new (const gchar * name); +GstStructure * gst_structure_id_empty_new (GQuark quark); +GstStructure * gst_structure_new (const gchar * name, + const gchar * firstfield, + ...); +GstStructure * gst_structure_new_valist (const gchar * name, + const gchar * firstfield, + va_list varargs); +GstStructure * gst_structure_id_new (GQuark name_quark, + GQuark field_quark, + ...); +GstStructure * gst_structure_copy (const GstStructure *structure); +gboolean gst_structure_set_parent_refcount (GstStructure *structure, + gint *refcount); +void gst_structure_free (GstStructure *structure); + +const gchar * gst_structure_get_name (const GstStructure *structure); +GQuark gst_structure_get_name_id (const GstStructure *structure); +gboolean gst_structure_has_name (const GstStructure *structure, + const gchar *name); +void gst_structure_set_name (GstStructure *structure, + const gchar *name); + +void gst_structure_id_set_value (GstStructure *structure, + GQuark field, + const GValue *value); +void gst_structure_set_value (GstStructure *structure, + const gchar *fieldname, + const GValue *value); +void gst_structure_id_take_value (GstStructure *structure, + GQuark field, + GValue *value); +void gst_structure_take_value (GstStructure *structure, + const gchar *fieldname, + GValue *value); +void gst_structure_set (GstStructure *structure, + const gchar *fieldname, + ...) G_GNUC_NULL_TERMINATED; + +void gst_structure_set_valist (GstStructure *structure, + const gchar *fieldname, + va_list varargs); + +void gst_structure_id_set (GstStructure *structure, + GQuark fieldname, + ...) G_GNUC_NULL_TERMINATED; + +void gst_structure_id_set_valist (GstStructure *structure, + GQuark fieldname, + va_list varargs); + +gboolean gst_structure_get_valist (const GstStructure *structure, + const char *first_fieldname, + va_list args); + +gboolean gst_structure_get (const GstStructure *structure, + const char *first_fieldname, + ...) G_GNUC_NULL_TERMINATED; + +gboolean gst_structure_id_get_valist (const GstStructure *structure, + GQuark first_field_id, + va_list args); + +gboolean gst_structure_id_get (const GstStructure *structure, + GQuark first_field_id, + ...) G_GNUC_NULL_TERMINATED; + +const GValue * gst_structure_id_get_value (const GstStructure *structure, + GQuark field); +const GValue * gst_structure_get_value (const GstStructure *structure, + const gchar *fieldname); +void gst_structure_remove_field (GstStructure *structure, + const gchar *fieldname); +void gst_structure_remove_fields (GstStructure *structure, + const gchar *fieldname, + ...) G_GNUC_NULL_TERMINATED; +void gst_structure_remove_fields_valist (GstStructure *structure, + const gchar *fieldname, + va_list varargs); +void gst_structure_remove_all_fields (GstStructure *structure); + +GType gst_structure_get_field_type (const GstStructure *structure, + const gchar *fieldname); +gboolean gst_structure_foreach (const GstStructure *structure, + GstStructureForeachFunc func, + gpointer user_data); +gboolean gst_structure_map_in_place (GstStructure *structure, + GstStructureMapFunc func, + gpointer user_data); +gint gst_structure_n_fields (const GstStructure *structure); +const gchar * gst_structure_nth_field_name (const GstStructure *structure, guint index); +gboolean gst_structure_id_has_field (const GstStructure *structure, + GQuark field); +gboolean gst_structure_id_has_field_typed (const GstStructure *structure, + GQuark field, + GType type); +gboolean gst_structure_has_field (const GstStructure *structure, + const gchar *fieldname); +gboolean gst_structure_has_field_typed (const GstStructure *structure, + const gchar *fieldname, + GType type); + +/* utility functions */ +gboolean gst_structure_get_boolean (const GstStructure *structure, + const gchar *fieldname, + gboolean *value); +gboolean gst_structure_get_int (const GstStructure *structure, + const gchar *fieldname, + gint *value); +gboolean gst_structure_get_uint (const GstStructure *structure, + const gchar *fieldname, + guint *value); +gboolean gst_structure_get_double (const GstStructure *structure, + const gchar *fieldname, + gdouble *value); +gboolean gst_structure_get_date (const GstStructure *structure, + const gchar *fieldname, + GDate **value); +gboolean gst_structure_get_date_time (const GstStructure *structure, + const gchar *fieldname, + GstDateTime **value); +gboolean gst_structure_get_clock_time (const GstStructure *structure, + const gchar *fieldname, + GstClockTime *value); +const gchar * gst_structure_get_string (const GstStructure *structure, + const gchar *fieldname); +gboolean gst_structure_get_enum (const GstStructure *structure, + const gchar *fieldname, + GType enumtype, + gint *value); +gboolean gst_structure_get_fraction (const GstStructure *structure, + const gchar *fieldname, + gint *value_numerator, + gint *value_denominator); + +gchar * gst_structure_to_string (const GstStructure *structure); +GstStructure * gst_structure_from_string (const gchar *string, + gchar **end); + +gboolean gst_structure_fixate_field_nearest_int (GstStructure *structure, + const char *field_name, + int target); +gboolean gst_structure_fixate_field_nearest_double (GstStructure *structure, + const char *field_name, + double target); + +gboolean gst_structure_fixate_field_boolean (GstStructure *structure, + const char *field_name, + gboolean target); +gboolean gst_structure_fixate_field_string (GstStructure *structure, + const char *field_name, + const gchar *target); +gboolean gst_structure_fixate_field_nearest_fraction (GstStructure *structure, + const char *field_name, + const gint target_numerator, + const gint target_denominator); +gboolean gst_structure_fixate_field (GstStructure *structure, + const char *field_name); + +gboolean gst_structure_is_equal(const GstStructure *structure1, + const GstStructure *structure2); +gboolean gst_structure_is_subset(const GstStructure *subset, + const GstStructure *superset); +gboolean gst_structure_can_intersect(const GstStructure *struct1, + const GstStructure *struct2); +GstStructure* gst_structure_intersect (const GstStructure *struct1, + const GstStructure *struct2); +void gst_structure_fixate (GstStructure *structure); + +G_END_DECLS + +#endif + diff --git a/gst/gstsystemclock.c b/gst/gstsystemclock.c new file mode 100644 index 0000000..b592813 --- /dev/null +++ b/gst/gstsystemclock.c @@ -0,0 +1,875 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2004 Wim Taymans <wim@fluendo.com> + * + * gstsystemclock.c: Default clock, uses the system clock + * + * 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:gstsystemclock + * @short_description: Default clock that uses the current system time + * @see_also: #GstClock + * + * The GStreamer core provides a GstSystemClock based on the system time. + * Asynchronous callbacks are scheduled from an internal thread. + * + * Clock implementors are encouraged to subclass this systemclock as it + * implements the async notification. + * + * Subclasses can however override all of the important methods for sync and + * async notifications to implement their own callback methods or blocking + * wait operations. + * + * Last reviewed on 2006-03-08 (0.10.4) + */ + +#include "gst_private.h" +#include "gstinfo.h" +#include "gstsystemclock.h" +#include "gstenumtypes.h" +#include "gstpoll.h" +#include "gstutils.h" +#include "glib-compat-private.h" + +#include <errno.h> + +#ifdef G_OS_WIN32 +# define WIN32_LEAN_AND_MEAN /* prevents from including too many things */ +# include <windows.h> /* QueryPerformance* stuff */ +# undef WIN32_LEAN_AND_MEAN +# define EWOULDBLOCK EAGAIN /* This is just to placate gcc */ +#endif /* G_OS_WIN32 */ + +#define GET_ENTRY_STATUS(e) ((GstClockReturn) g_atomic_int_get(&GST_CLOCK_ENTRY_STATUS(e))) +#define SET_ENTRY_STATUS(e,val) (g_atomic_int_set(&GST_CLOCK_ENTRY_STATUS(e),(val))) +#define CAS_ENTRY_STATUS(e,old,val) (G_ATOMIC_INT_COMPARE_AND_EXCHANGE(\ + (&GST_CLOCK_ENTRY_STATUS(e)), (old), (val))) + +/* Define this to get some extra debug about jitter from each clock_wait */ +#undef WAIT_DEBUGGING + +struct _GstSystemClockPrivate +{ + GstClockType clock_type; + GstPoll *timer; + gint wakeup_count; /* the number of entries with a pending wakeup */ + gboolean async_wakeup; /* if the wakeup was because of a async list change */ + +#ifdef G_OS_WIN32 + LARGE_INTEGER start; + LARGE_INTEGER frequency; +#endif /* G_OS_WIN32 */ +}; + +#define GST_SYSTEM_CLOCK_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_SYSTEM_CLOCK, \ + GstSystemClockPrivate)) + +#ifdef HAVE_POSIX_TIMERS +# ifdef HAVE_MONOTONIC_CLOCK +# define DEFAULT_CLOCK_TYPE GST_CLOCK_TYPE_MONOTONIC +# else +# define DEFAULT_CLOCK_TYPE GST_CLOCK_TYPE_REALTIME +# endif +#else +#define DEFAULT_CLOCK_TYPE GST_CLOCK_TYPE_REALTIME +#endif + +enum +{ + PROP_0, + PROP_CLOCK_TYPE, + /* FILL ME */ +}; + +/* the one instance of the systemclock */ +static GstClock *_the_system_clock = NULL; + +static void gst_system_clock_dispose (GObject * object); +static void gst_system_clock_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_system_clock_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstClockTime gst_system_clock_get_internal_time (GstClock * clock); +static guint64 gst_system_clock_get_resolution (GstClock * clock); +static GstClockReturn gst_system_clock_id_wait_jitter (GstClock * clock, + GstClockEntry * entry, GstClockTimeDiff * jitter); +static GstClockReturn gst_system_clock_id_wait_jitter_unlocked + (GstClock * clock, GstClockEntry * entry, GstClockTimeDiff * jitter, + gboolean restart); +static GstClockReturn gst_system_clock_id_wait_async (GstClock * clock, + GstClockEntry * entry); +static void gst_system_clock_id_unschedule (GstClock * clock, + GstClockEntry * entry); +static void gst_system_clock_async_thread (GstClock * clock); +static gboolean gst_system_clock_start_async (GstSystemClock * clock); +static void gst_system_clock_add_wakeup (GstSystemClock * sysclock); + +static GStaticMutex _gst_sysclock_mutex = G_STATIC_MUTEX_INIT; + +static GstClockClass *parent_class = NULL; + +/* static guint gst_system_clock_signals[LAST_SIGNAL] = { 0 }; */ + +G_DEFINE_TYPE (GstSystemClock, gst_system_clock, GST_TYPE_CLOCK); + +static void +gst_system_clock_class_init (GstSystemClockClass * klass) +{ + GObjectClass *gobject_class; + GstClockClass *gstclock_class; + + gobject_class = (GObjectClass *) klass; + gstclock_class = (GstClockClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + g_type_class_add_private (klass, sizeof (GstSystemClockPrivate)); + + gobject_class->dispose = gst_system_clock_dispose; + gobject_class->set_property = gst_system_clock_set_property; + gobject_class->get_property = gst_system_clock_get_property; + + g_object_class_install_property (gobject_class, PROP_CLOCK_TYPE, + g_param_spec_enum ("clock-type", "Clock type", + "The type of underlying clock implementation used", + GST_TYPE_CLOCK_TYPE, DEFAULT_CLOCK_TYPE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstclock_class->get_internal_time = gst_system_clock_get_internal_time; + gstclock_class->get_resolution = gst_system_clock_get_resolution; + gstclock_class->wait = gst_system_clock_id_wait_jitter; + gstclock_class->wait_async = gst_system_clock_id_wait_async; + gstclock_class->unschedule = gst_system_clock_id_unschedule; +} + +static void +gst_system_clock_init (GstSystemClock * clock) +{ + GST_OBJECT_FLAG_SET (clock, + GST_CLOCK_FLAG_CAN_DO_SINGLE_SYNC | + GST_CLOCK_FLAG_CAN_DO_SINGLE_ASYNC | + GST_CLOCK_FLAG_CAN_DO_PERIODIC_SYNC | + GST_CLOCK_FLAG_CAN_DO_PERIODIC_ASYNC); + + clock->priv = GST_SYSTEM_CLOCK_GET_PRIVATE (clock); + + clock->priv->clock_type = DEFAULT_CLOCK_TYPE; + clock->priv->timer = gst_poll_new_timer (); + +#ifdef G_OS_WIN32 + QueryPerformanceFrequency (&clock->priv->frequency); + /* can be 0 if the hardware does not have hardware support */ + if (clock->priv->frequency.QuadPart != 0) + /* we take a base time so that time starts from 0 to ease debugging */ + QueryPerformanceCounter (&clock->priv->start); +#endif /* G_OS_WIN32 */ + +#if 0 + /* Uncomment this to start the async clock thread straight away */ + GST_OBJECT_LOCK (clock); + gst_system_clock_start_async (clock); + GST_OBJECT_UNLOCK (clock); +#endif +} + +static void +gst_system_clock_dispose (GObject * object) +{ + GstClock *clock = (GstClock *) object; + GstSystemClock *sysclock = GST_SYSTEM_CLOCK_CAST (clock); + GList *entries; + + /* else we have to stop the thread */ + GST_OBJECT_LOCK (clock); + sysclock->stopping = TRUE; + /* unschedule all entries */ + for (entries = clock->entries; entries; entries = g_list_next (entries)) { + GstClockEntry *entry = (GstClockEntry *) entries->data; + + GST_CAT_DEBUG (GST_CAT_CLOCK, "unscheduling entry %p", entry); + SET_ENTRY_STATUS (entry, GST_CLOCK_UNSCHEDULED); + } + GST_CLOCK_BROADCAST (clock); + gst_system_clock_add_wakeup (sysclock); + GST_OBJECT_UNLOCK (clock); + + if (sysclock->thread) + g_thread_join (sysclock->thread); + sysclock->thread = NULL; + GST_CAT_DEBUG (GST_CAT_CLOCK, "joined thread"); + + g_list_foreach (clock->entries, (GFunc) gst_clock_id_unref, NULL); + g_list_free (clock->entries); + clock->entries = NULL; + + gst_poll_free (sysclock->priv->timer); + + G_OBJECT_CLASS (parent_class)->dispose (object); + + if (_the_system_clock == clock) { + _the_system_clock = NULL; + GST_CAT_DEBUG (GST_CAT_CLOCK, "disposed system clock"); + } +} + +static void +gst_system_clock_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstSystemClock *sysclock = GST_SYSTEM_CLOCK (object); + + switch (prop_id) { + case PROP_CLOCK_TYPE: + sysclock->priv->clock_type = (GstClockType) g_value_get_enum (value); + GST_CAT_DEBUG (GST_CAT_CLOCK, "clock-type set to %d", + sysclock->priv->clock_type); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_system_clock_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstSystemClock *sysclock = GST_SYSTEM_CLOCK (object); + + switch (prop_id) { + case PROP_CLOCK_TYPE: + g_value_set_enum (value, sysclock->priv->clock_type); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/** + * gst_system_clock_obtain: + * + * Get a handle to the default system clock. The refcount of the + * clock will be increased so you need to unref the clock after + * usage. + * + * Returns: (transfer full): the default clock. + * + * MT safe. + */ +GstClock * +gst_system_clock_obtain (void) +{ + GstClock *clock; + + g_static_mutex_lock (&_gst_sysclock_mutex); + clock = _the_system_clock; + + if (clock == NULL) { + GST_CAT_DEBUG (GST_CAT_CLOCK, "creating new static system clock"); + clock = g_object_new (GST_TYPE_SYSTEM_CLOCK, + "name", "GstSystemClock", NULL); + + /* we created the global clock; take ownership so + * we can hand out instances later */ + gst_object_ref_sink (clock); + + _the_system_clock = clock; + g_static_mutex_unlock (&_gst_sysclock_mutex); + } else { + g_static_mutex_unlock (&_gst_sysclock_mutex); + GST_CAT_DEBUG (GST_CAT_CLOCK, "returning static system clock"); + } + + /* we ref it since we are a clock factory. */ + gst_object_ref (clock); + return clock; +} + +static void +gst_system_clock_remove_wakeup (GstSystemClock * sysclock) +{ + g_return_if_fail (sysclock->priv->wakeup_count > 0); + + sysclock->priv->wakeup_count--; + if (sysclock->priv->wakeup_count == 0) { + /* read the control socket byte when we removed the last wakeup count */ + GST_CAT_DEBUG (GST_CAT_CLOCK, "reading control"); + while (!gst_poll_read_control (sysclock->priv->timer)) { + g_warning ("gstsystemclock: read control failed, trying again\n"); + } + GST_CLOCK_BROADCAST (sysclock); + } + GST_CAT_DEBUG (GST_CAT_CLOCK, "wakeup count %d", + sysclock->priv->wakeup_count); +} + +static void +gst_system_clock_add_wakeup (GstSystemClock * sysclock) +{ + /* only write the control socket for the first wakeup */ + if (sysclock->priv->wakeup_count == 0) { + GST_CAT_DEBUG (GST_CAT_CLOCK, "writing control"); + while (!gst_poll_write_control (sysclock->priv->timer)) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + g_warning + ("gstsystemclock: write control failed in wakeup_async, trying again: %d:%s\n", + errno, g_strerror (errno)); + } else { + g_critical + ("gstsystemclock: write control failed in wakeup_async: %d:%s\n", + errno, g_strerror (errno)); + return; + } + } + } + sysclock->priv->wakeup_count++; + GST_CAT_DEBUG (GST_CAT_CLOCK, "wakeup count %d", + sysclock->priv->wakeup_count); +} + +static void +gst_system_clock_wait_wakeup (GstSystemClock * sysclock) +{ + while (sysclock->priv->wakeup_count > 0) { + GST_CLOCK_WAIT (sysclock); + } +} + +/* this thread reads the sorted clock entries from the queue. + * + * It waits on each of them and fires the callback when the timeout occurs. + * + * When an entry in the queue was canceled before we wait for it, it is + * simply skipped. + * + * When waiting for an entry, it can become canceled, in that case we don't + * call the callback but move to the next item in the queue. + * + * MT safe. + */ +static void +gst_system_clock_async_thread (GstClock * clock) +{ + GstSystemClock *sysclock = GST_SYSTEM_CLOCK_CAST (clock); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "enter system clock thread"); + GST_OBJECT_LOCK (clock); + /* signal spinup */ + GST_CLOCK_BROADCAST (clock); + /* now enter our (almost) infinite loop */ + while (!sysclock->stopping) { + GstClockEntry *entry; + GstClockTime requested; + GstClockReturn res; + + /* check if something to be done */ + while (clock->entries == NULL) { + GST_CAT_DEBUG (GST_CAT_CLOCK, "no clock entries, waiting.."); + /* wait for work to do */ + GST_CLOCK_WAIT (clock); + GST_CAT_DEBUG (GST_CAT_CLOCK, "got signal"); + /* clock was stopping, exit */ + if (sysclock->stopping) + goto exit; + } + + /* see if we have a pending wakeup because the order of the list + * changed. */ + if (sysclock->priv->async_wakeup) { + GST_CAT_DEBUG (GST_CAT_CLOCK, "clear async wakeup"); + gst_system_clock_remove_wakeup (sysclock); + sysclock->priv->async_wakeup = FALSE; + } + + /* pick the next entry */ + entry = clock->entries->data; + GST_OBJECT_UNLOCK (clock); + + requested = entry->time; + + /* now wait for the entry, we already hold the lock */ + res = + gst_system_clock_id_wait_jitter_unlocked (clock, (GstClockID) entry, + NULL, FALSE); + + GST_OBJECT_LOCK (clock); + + switch (res) { + case GST_CLOCK_UNSCHEDULED: + /* entry was unscheduled, move to the next */ + GST_CAT_DEBUG (GST_CAT_CLOCK, "async entry %p unscheduled", entry); + goto next_entry; + case GST_CLOCK_OK: + case GST_CLOCK_EARLY: + { + /* entry timed out normally, fire the callback and move to the next + * entry */ + GST_CAT_DEBUG (GST_CAT_CLOCK, "async entry %p timed out", entry); + if (entry->func) { + /* unlock before firing the callback */ + GST_OBJECT_UNLOCK (clock); + entry->func (clock, entry->time, (GstClockID) entry, + entry->user_data); + GST_OBJECT_LOCK (clock); + } + if (entry->type == GST_CLOCK_ENTRY_PERIODIC) { + GST_CAT_DEBUG (GST_CAT_CLOCK, "updating periodic entry %p", entry); + /* adjust time now */ + entry->time = requested + entry->interval; + /* and resort the list now */ + clock->entries = + g_list_sort (clock->entries, gst_clock_id_compare_func); + /* and restart */ + continue; + } else { + GST_CAT_DEBUG (GST_CAT_CLOCK, "moving to next entry"); + goto next_entry; + } + } + case GST_CLOCK_BUSY: + /* somebody unlocked the entry but is was not canceled, This means that + * either a new entry was added in front of the queue or some other entry + * was canceled. Whatever it is, pick the head entry of the list and + * continue waiting. */ + GST_CAT_DEBUG (GST_CAT_CLOCK, "async entry %p needs restart", entry); + + /* we set the entry back to the OK state. This is needed so that the + * _unschedule() code can see if an entry is currently being waited + * on (when its state is BUSY). */ + SET_ENTRY_STATUS (entry, GST_CLOCK_OK); + continue; + default: + GST_CAT_DEBUG (GST_CAT_CLOCK, + "strange result %d waiting for %p, skipping", res, entry); + g_warning ("%s: strange result %d waiting for %p, skipping", + GST_OBJECT_NAME (clock), res, entry); + goto next_entry; + } + next_entry: + /* we remove the current entry and unref it */ + clock->entries = g_list_remove (clock->entries, entry); + gst_clock_id_unref ((GstClockID) entry); + } +exit: + /* signal exit */ + GST_CLOCK_BROADCAST (clock); + GST_OBJECT_UNLOCK (clock); + GST_CAT_DEBUG (GST_CAT_CLOCK, "exit system clock thread"); +} + +#ifdef HAVE_POSIX_TIMERS +static inline clockid_t +clock_type_to_posix_id (GstClockType clock_type) +{ +#ifdef HAVE_MONOTONIC_CLOCK + if (clock_type == GST_CLOCK_TYPE_MONOTONIC) + return CLOCK_MONOTONIC; + else +#endif + return CLOCK_REALTIME; +} +#endif + +/* MT safe */ +static GstClockTime +gst_system_clock_get_internal_time (GstClock * clock) +{ +#ifdef G_OS_WIN32 + GstSystemClock *sysclock = GST_SYSTEM_CLOCK_CAST (clock); + + if (sysclock->priv->frequency.QuadPart != 0) { + LARGE_INTEGER now; + + /* we prefer the highly accurate performance counters on windows */ + QueryPerformanceCounter (&now); + + return gst_util_uint64_scale (now.QuadPart - sysclock->priv->start.QuadPart, + GST_SECOND, sysclock->priv->frequency.QuadPart); + } else +#endif /* G_OS_WIN32 */ +#if !defined HAVE_POSIX_TIMERS + { + GTimeVal timeval; + + g_get_current_time (&timeval); + + return GST_TIMEVAL_TO_TIME (timeval); + } +#else + { + GstSystemClock *sysclock = GST_SYSTEM_CLOCK_CAST (clock); + clockid_t ptype; + struct timespec ts; + + ptype = clock_type_to_posix_id (sysclock->priv->clock_type); + + if (G_UNLIKELY (clock_gettime (ptype, &ts))) + return GST_CLOCK_TIME_NONE; + + return GST_TIMESPEC_TO_TIME (ts); + } +#endif +} + +static guint64 +gst_system_clock_get_resolution (GstClock * clock) +{ +#ifdef G_OS_WIN32 + GstSystemClock *sysclock = GST_SYSTEM_CLOCK_CAST (clock); + + if (sysclock->priv->frequency.QuadPart != 0) { + return GST_SECOND / sysclock->priv->frequency.QuadPart; + } else +#endif /* G_OS_WIN32 */ +#ifdef HAVE_POSIX_TIMERS + { + GstSystemClock *sysclock = GST_SYSTEM_CLOCK_CAST (clock); + clockid_t ptype; + struct timespec ts; + + ptype = clock_type_to_posix_id (sysclock->priv->clock_type); + + if (G_UNLIKELY (clock_getres (ptype, &ts))) + return GST_CLOCK_TIME_NONE; + + return GST_TIMESPEC_TO_TIME (ts); + } +#else + { + return 1 * GST_USECOND; + } +#endif +} + +/* synchronously wait on the given GstClockEntry. + * + * We do this by blocking on the global GstPoll timer with + * the requested timeout. This allows us to unblock the + * entry by writing on the control fd. + * + * Note that writing the global GstPoll unlocks all waiting entries. So + * we need to check if an unlocked entry has changed when it unlocks. + * + * Entries that arrive too late are simply not waited on and a + * GST_CLOCK_EARLY result is returned. + * + * MT safe. + */ +static GstClockReturn +gst_system_clock_id_wait_jitter_unlocked (GstClock * clock, + GstClockEntry * entry, GstClockTimeDiff * jitter, gboolean restart) +{ + GstSystemClock *sysclock = GST_SYSTEM_CLOCK_CAST (clock); + GstClockTime entryt, now; + GstClockTimeDiff diff; + GstClockReturn status; + + if (G_UNLIKELY (GET_ENTRY_STATUS (entry) == GST_CLOCK_UNSCHEDULED)) + return GST_CLOCK_UNSCHEDULED; + + /* need to call the overridden method because we want to sync against the time + * of the clock, whatever the subclass uses as a clock. */ + now = gst_clock_get_time (clock); + + /* get the time of the entry */ + entryt = GST_CLOCK_ENTRY_TIME (entry); + + /* the diff of the entry with the clock is the amount of time we have to + * wait */ + diff = GST_CLOCK_DIFF (now, entryt); + if (G_LIKELY (jitter)) + *jitter = -diff; + + GST_CAT_DEBUG (GST_CAT_CLOCK, "entry %p" + " time %" GST_TIME_FORMAT + " now %" GST_TIME_FORMAT + " diff (time-now) %" G_GINT64_FORMAT, + entry, GST_TIME_ARGS (entryt), GST_TIME_ARGS (now), diff); + + if (G_LIKELY (diff > 0)) { +#ifdef WAIT_DEBUGGING + GstClockTime final; +#endif + + while (TRUE) { + gint pollret; + + do { + status = GET_ENTRY_STATUS (entry); + + /* stop when we are unscheduled */ + if (G_UNLIKELY (status == GST_CLOCK_UNSCHEDULED)) + goto done; + + /* mark the entry as busy but watch out for intermediate unscheduled + * statuses */ + } while (G_UNLIKELY (!CAS_ENTRY_STATUS (entry, status, GST_CLOCK_BUSY))); + + /* now wait on the entry, it either times out or the fd is written. The + * status of the entry is only BUSY around the poll. */ + pollret = gst_poll_wait (sysclock->priv->timer, diff); + + /* get the new status, mark as DONE. We do this so that the unschedule + * function knows when we left the poll and doesn't need to wakeup the + * poll anymore. */ + do { + status = GET_ENTRY_STATUS (entry); + /* we were unscheduled, exit immediately */ + if (G_UNLIKELY (status == GST_CLOCK_UNSCHEDULED)) + break; + } while (G_UNLIKELY (!CAS_ENTRY_STATUS (entry, status, GST_CLOCK_DONE))); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "entry %p unlocked, status %d, ret %d", + entry, status, pollret); + + if (G_UNLIKELY (status == GST_CLOCK_UNSCHEDULED)) { + /* try to clean up The unschedule function managed to set the status to + * unscheduled. We now take the lock and mark the entry as unscheduled. + * This makes sure that the unschedule function doesn't perform a + * wakeup anymore. If the unschedule function has a change to perform + * the wakeup before us, we clean up here */ + GST_OBJECT_LOCK (sysclock); + entry->unscheduled = TRUE; + if (entry->woken_up) { + gst_system_clock_remove_wakeup (sysclock); + } + GST_OBJECT_UNLOCK (sysclock); + goto done; + } else { + if (G_UNLIKELY (pollret != 0)) { + /* some other id got unlocked */ + if (!restart) { + /* this can happen if the entry got unlocked because of an async + * entry was added to the head of the async queue. */ + GST_CAT_DEBUG (GST_CAT_CLOCK, "wakeup waiting for entry %p", entry); + goto done; + } + + /* wait till all the entries got woken up */ + GST_OBJECT_LOCK (sysclock); + gst_system_clock_wait_wakeup (sysclock); + GST_OBJECT_UNLOCK (sysclock); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "entry %p needs to be restarted", + entry); + } else { + GST_CAT_DEBUG (GST_CAT_CLOCK, "entry %p unlocked after timeout", + entry); + } + + /* reschedule if gst_poll_wait returned early or we have to reschedule after + * an unlock*/ + now = gst_clock_get_time (clock); + diff = GST_CLOCK_DIFF (now, entryt); + + if (diff <= 0) { + /* timeout, this is fine, we can report success now */ + status = GST_CLOCK_OK; + SET_ENTRY_STATUS (entry, status); + + GST_CAT_DEBUG (GST_CAT_CLOCK, + "entry %p finished, diff %" G_GINT64_FORMAT, entry, diff); + +#ifdef WAIT_DEBUGGING + final = gst_system_clock_get_internal_time (clock); + GST_CAT_DEBUG (GST_CAT_CLOCK, "Waited for %" G_GINT64_FORMAT + " got %" G_GINT64_FORMAT " diff %" G_GINT64_FORMAT + " %g target-offset %" G_GINT64_FORMAT " %g", entryt, now, + now - entryt, + (double) (GstClockTimeDiff) (now - entryt) / GST_SECOND, + (final - target), + ((double) (GstClockTimeDiff) (final - target)) / GST_SECOND); +#endif + goto done; + } else { + GST_CAT_DEBUG (GST_CAT_CLOCK, + "entry %p restart, diff %" G_GINT64_FORMAT, entry, diff); + } + } + } + } else { + /* we are right on time or too late */ + if (G_UNLIKELY (diff == 0)) + status = GST_CLOCK_OK; + else + status = GST_CLOCK_EARLY; + + SET_ENTRY_STATUS (entry, status); + } +done: + return status; +} + +static GstClockReturn +gst_system_clock_id_wait_jitter (GstClock * clock, GstClockEntry * entry, + GstClockTimeDiff * jitter) +{ + return gst_system_clock_id_wait_jitter_unlocked (clock, entry, jitter, TRUE); +} + +/* Start the async clock thread. Must be called with the object lock + * held */ +static gboolean +gst_system_clock_start_async (GstSystemClock * clock) +{ + GError *error = NULL; + + if (G_LIKELY (clock->thread != NULL)) + return TRUE; /* Thread already running. Nothing to do */ + + clock->thread = g_thread_create ((GThreadFunc) gst_system_clock_async_thread, + clock, TRUE, &error); + if (G_UNLIKELY (error)) + goto no_thread; + + /* wait for it to spin up */ + GST_CLOCK_WAIT (clock); + + return TRUE; + + /* ERRORS */ +no_thread: + { + g_warning ("could not create async clock thread: %s", error->message); + g_error_free (error); + } + return FALSE; +} + +/* Add an entry to the list of pending async waits. The entry is inserted + * in sorted order. If we inserted the entry at the head of the list, we + * need to signal the thread as it might either be waiting on it or waiting + * for a new entry. + * + * MT safe. + */ +static GstClockReturn +gst_system_clock_id_wait_async (GstClock * clock, GstClockEntry * entry) +{ + GstSystemClock *sysclock; + GstClockEntry *head; + + sysclock = GST_SYSTEM_CLOCK_CAST (clock); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "adding async entry %p", entry); + + GST_OBJECT_LOCK (clock); + /* Start the clock async thread if needed */ + if (G_UNLIKELY (!gst_system_clock_start_async (sysclock))) + goto thread_error; + + if (G_UNLIKELY (GET_ENTRY_STATUS (entry) == GST_CLOCK_UNSCHEDULED)) + goto was_unscheduled; + + if (clock->entries) + head = clock->entries->data; + else + head = NULL; + + /* need to take a ref */ + gst_clock_id_ref ((GstClockID) entry); + /* insert the entry in sorted order */ + clock->entries = g_list_insert_sorted (clock->entries, entry, + gst_clock_id_compare_func); + + /* only need to send the signal if the entry was added to the + * front, else the thread is just waiting for another entry and + * will get to this entry automatically. */ + if (clock->entries->data == entry) { + GST_CAT_DEBUG (GST_CAT_CLOCK, "async entry added to head %p", head); + if (head == NULL) { + /* the list was empty before, signal the cond so that the async thread can + * start taking a look at the queue */ + GST_CAT_DEBUG (GST_CAT_CLOCK, "first entry, sending signal"); + GST_CLOCK_BROADCAST (clock); + } else { + GstClockReturn status; + + status = GET_ENTRY_STATUS (head); + GST_CAT_DEBUG (GST_CAT_CLOCK, "head entry %p status %d", head, status); + + if (status == GST_CLOCK_BUSY) { + GST_CAT_DEBUG (GST_CAT_CLOCK, "head entry is busy"); + /* the async thread was waiting for an entry, unlock the wait so that it + * looks at the new head entry instead, we only need to do this once */ + if (!sysclock->priv->async_wakeup) { + GST_CAT_DEBUG (GST_CAT_CLOCK, "wakeup async thread"); + sysclock->priv->async_wakeup = TRUE; + gst_system_clock_add_wakeup (sysclock); + } + } + } + } + GST_OBJECT_UNLOCK (clock); + + return GST_CLOCK_OK; + + /* ERRORS */ +thread_error: + { + /* Could not start the async clock thread */ + GST_OBJECT_UNLOCK (clock); + return GST_CLOCK_ERROR; + } +was_unscheduled: + { + GST_OBJECT_UNLOCK (clock); + return GST_CLOCK_UNSCHEDULED; + } +} + +/* unschedule an entry. This will set the state of the entry to GST_CLOCK_UNSCHEDULED + * and will signal any thread waiting for entries to recheck their entry. + * We cannot really decide if the signal is needed or not because the entry + * could be waited on in async or sync mode. + * + * MT safe. + */ +static void +gst_system_clock_id_unschedule (GstClock * clock, GstClockEntry * entry) +{ + GstSystemClock *sysclock; + GstClockReturn status; + + sysclock = GST_SYSTEM_CLOCK_CAST (clock); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "unscheduling entry %p", entry); + + GST_OBJECT_LOCK (clock); + /* change the entry status to unscheduled */ + do { + status = GET_ENTRY_STATUS (entry); + } while (G_UNLIKELY (!CAS_ENTRY_STATUS (entry, status, + GST_CLOCK_UNSCHEDULED))); + + if (G_LIKELY (status == GST_CLOCK_BUSY)) { + /* the entry was being busy, wake up all entries so that they recheck their + * status. We cannot wake up just one entry because allocating such a + * datastructure for each entry would be too heavy and unlocking an entry + * is usually done when shutting down or some other exceptional case. */ + GST_CAT_DEBUG (GST_CAT_CLOCK, "entry was BUSY, doing wakeup"); + if (!entry->unscheduled && !entry->woken_up) { + gst_system_clock_add_wakeup (sysclock); + entry->woken_up = TRUE; + } + } + GST_OBJECT_UNLOCK (clock); +} diff --git a/gst/gstsystemclock.h b/gst/gstsystemclock.h new file mode 100644 index 0000000..490f700 --- /dev/null +++ b/gst/gstsystemclock.h @@ -0,0 +1,89 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstsystemclock.h: A clock implementation based on system time + * + * 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_SYSTEM_CLOCK_H__ +#define __GST_SYSTEM_CLOCK_H__ + +#include <gst/gstclock.h> + +G_BEGIN_DECLS + +#define GST_TYPE_SYSTEM_CLOCK (gst_system_clock_get_type ()) +#define GST_SYSTEM_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_SYSTEM_CLOCK, GstSystemClock)) +#define GST_SYSTEM_CLOCK_CAST(obj) ((GstSystemClock *)(obj)) +#define GST_IS_SYSTEM_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_SYSTEM_CLOCK)) +#define GST_SYSTEM_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_SYSTEM_CLOCK, GstSystemClockClass)) +#define GST_IS_SYSTEM_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_SYSTEM_CLOCK)) +#define GST_SYSTEM_CLOCK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_SYSTEM_CLOCK, GstSystemClockClass)) + + +typedef struct _GstSystemClock GstSystemClock; +typedef struct _GstSystemClockClass GstSystemClockClass; +typedef struct _GstSystemClockPrivate GstSystemClockPrivate; + +/** + * GstClockType: + * @GST_CLOCK_TYPE_REALTIME: time since Epoch + * @GST_CLOCK_TYPE_MONOTONIC: monotonic time since some unspecified starting + * point + * + * The different kind of clocks. + */ +typedef enum { + GST_CLOCK_TYPE_REALTIME = 0, + GST_CLOCK_TYPE_MONOTONIC = 1 +} GstClockType; + +/** + * GstSystemClock: + * + * The default implementation of a #GstClock that uses the system time. + */ +struct _GstSystemClock { + GstClock clock; + + /*< private >*/ + GThread *thread; /* thread for async notify */ + gboolean stopping; + + /* ABI added */ + GstSystemClockPrivate *priv; + + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstSystemClockClass { + GstClockClass parent_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_system_clock_get_type (void); + +GstClock* gst_system_clock_obtain (void); + +G_END_DECLS + +#endif /* __GST_SYSTEM_CLOCK_H__ */ diff --git a/gst/gsttaglist.c b/gst/gsttaglist.c new file mode 100644 index 0000000..b62fbc7 --- /dev/null +++ b/gst/gsttaglist.c @@ -0,0 +1,1885 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gsttaglist.c: tag support (aka metadata) + * + * 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:gsttaglist + * @short_description: List of tags and values used to describe media metadata + * + * List of tags and values used to describe media metadata. + * + * Strings must be in ASCII or UTF-8 encoding. No other encodings are allowed. + * + * Last reviewed on 2009-06-09 (0.10.23) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst_private.h" +#include "gst-i18n-lib.h" +#include "gsttaglist.h" +#include "gstinfo.h" +#include "gstvalue.h" +#include "gstbuffer.h" +#include "gstquark.h" + +#include <gobject/gvaluecollector.h> +#include <string.h> + +#define GST_TAG_IS_VALID(tag) (gst_tag_get_info (tag) != NULL) + +/* FIXME 0.11: use GParamSpecs or something similar for tag registrations, + * possibly even gst_tag_register(). Especially value ranges might be + * useful for some tags. */ + +typedef struct +{ + GType type; /* type the data is in */ + + gchar *nick; /* translated name */ + gchar *blurb; /* translated description of type */ + + GstTagMergeFunc merge_func; /* functions to merge the values */ + GstTagFlag flag; /* type of tag */ +} +GstTagInfo; + +static GMutex *__tag_mutex; + +static GHashTable *__tags; + +#define TAG_LOCK g_mutex_lock (__tag_mutex) +#define TAG_UNLOCK g_mutex_unlock (__tag_mutex) + +GType +gst_tag_list_get_type (void) +{ + static GType _gst_tag_list_type = 0; + + if (G_UNLIKELY (_gst_tag_list_type == 0)) { + _gst_tag_list_type = g_boxed_type_register_static ("GstTagList", + (GBoxedCopyFunc) gst_tag_list_copy, (GBoxedFreeFunc) gst_tag_list_free); + +#if 0 + g_value_register_transform_func (_gst_tag_list_type, G_TYPE_STRING, + _gst_structure_transform_to_string); +#endif + } + + return _gst_tag_list_type; +} + +void +_priv_gst_tag_initialize (void) +{ + __tag_mutex = g_mutex_new (); + __tags = g_hash_table_new (g_direct_hash, g_direct_equal); + gst_tag_register (GST_TAG_TITLE, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("title"), _("commonly used title"), gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_TITLE_SORTNAME, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("title sortname"), _("commonly used title for sorting purposes"), NULL); + gst_tag_register (GST_TAG_ARTIST, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("artist"), + _("person(s) responsible for the recording"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_ARTIST_SORTNAME, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("artist sortname"), + _("person(s) responsible for the recording for sorting purposes"), NULL); + gst_tag_register (GST_TAG_ALBUM, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("album"), + _("album containing this data"), gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_ALBUM_SORTNAME, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("album sortname"), + _("album containing this data for sorting purposes"), NULL); + gst_tag_register (GST_TAG_ALBUM_ARTIST, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("album artist"), + _("The artist of the entire album, as it should be displayed"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_ALBUM_ARTIST_SORTNAME, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("album artist sortname"), + _("The artist of the entire album, as it should be sorted"), NULL); + gst_tag_register (GST_TAG_DATE, GST_TAG_FLAG_META, GST_TYPE_DATE, + _("date"), _("date the data was created (as a GDate structure)"), NULL); + gst_tag_register (GST_TAG_DATE_TIME, GST_TAG_FLAG_META, GST_TYPE_DATE_TIME, + _("datetime"), + _("date and time the data was created (as a GstDateTime structure)"), + NULL); + gst_tag_register (GST_TAG_GENRE, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("genre"), + _("genre this data belongs to"), gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_COMMENT, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("comment"), + _("free text commenting the data"), gst_tag_merge_use_first); + gst_tag_register (GST_TAG_EXTENDED_COMMENT, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("extended comment"), + _("free text commenting the data in key=value or key[en]=comment form"), + gst_tag_merge_use_first); + gst_tag_register (GST_TAG_TRACK_NUMBER, GST_TAG_FLAG_META, + G_TYPE_UINT, + _("track number"), + _("track number inside a collection"), gst_tag_merge_use_first); + gst_tag_register (GST_TAG_TRACK_COUNT, GST_TAG_FLAG_META, + G_TYPE_UINT, + _("track count"), + _("count of tracks inside collection this track belongs to"), + gst_tag_merge_use_first); + gst_tag_register (GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_FLAG_META, + G_TYPE_UINT, + _("disc number"), + _("disc number inside a collection"), gst_tag_merge_use_first); + gst_tag_register (GST_TAG_ALBUM_VOLUME_COUNT, GST_TAG_FLAG_META, + G_TYPE_UINT, + _("disc count"), + _("count of discs inside collection this disc belongs to"), + gst_tag_merge_use_first); + gst_tag_register (GST_TAG_LOCATION, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("location"), _("Origin of media as a URI (location, where the " + "original of the file or stream is hosted)"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_HOMEPAGE, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("homepage"), + _("Homepage for this media (i.e. artist or movie homepage)"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_DESCRIPTION, GST_TAG_FLAG_META, G_TYPE_STRING, + _("description"), _("short text describing the content of the data"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_VERSION, GST_TAG_FLAG_META, G_TYPE_STRING, + _("version"), _("version of this data"), NULL); + gst_tag_register (GST_TAG_ISRC, GST_TAG_FLAG_META, G_TYPE_STRING, _("ISRC"), + _ + ("International Standard Recording Code - see http://www.ifpi.org/isrc/"), + NULL); + /* FIXME: organization (fix what? tpm) */ + gst_tag_register (GST_TAG_ORGANIZATION, GST_TAG_FLAG_META, G_TYPE_STRING, + _("organization"), _("organization"), gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_COPYRIGHT, GST_TAG_FLAG_META, + G_TYPE_STRING, _("copyright"), _("copyright notice of the data"), NULL); + gst_tag_register (GST_TAG_COPYRIGHT_URI, GST_TAG_FLAG_META, + G_TYPE_STRING, _("copyright uri"), + _("URI to the copyright notice of the data"), NULL); + gst_tag_register (GST_TAG_ENCODED_BY, GST_TAG_FLAG_META, G_TYPE_STRING, + _("encoded by"), _("name of the encoding person or organization"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_CONTACT, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("contact"), _("contact information"), gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_LICENSE, GST_TAG_FLAG_META, + G_TYPE_STRING, _("license"), _("license of data"), NULL); + gst_tag_register (GST_TAG_LICENSE_URI, GST_TAG_FLAG_META, + G_TYPE_STRING, _("license uri"), + _("URI to the license of the data"), NULL); + gst_tag_register (GST_TAG_PERFORMER, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("performer"), + _("person(s) performing"), gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_COMPOSER, GST_TAG_FLAG_META, + G_TYPE_STRING, + _("composer"), + _("person(s) who composed the recording"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_DURATION, GST_TAG_FLAG_DECODED, + G_TYPE_UINT64, + _("duration"), _("length in GStreamer time units (nanoseconds)"), NULL); + gst_tag_register (GST_TAG_CODEC, GST_TAG_FLAG_ENCODED, + G_TYPE_STRING, + _("codec"), + _("codec the data is stored in"), gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_VIDEO_CODEC, GST_TAG_FLAG_ENCODED, + G_TYPE_STRING, + _("video codec"), _("codec the video data is stored in"), NULL); + gst_tag_register (GST_TAG_AUDIO_CODEC, GST_TAG_FLAG_ENCODED, + G_TYPE_STRING, + _("audio codec"), _("codec the audio data is stored in"), NULL); + gst_tag_register (GST_TAG_SUBTITLE_CODEC, GST_TAG_FLAG_ENCODED, + G_TYPE_STRING, + _("subtitle codec"), _("codec the subtitle data is stored in"), NULL); + gst_tag_register (GST_TAG_CONTAINER_FORMAT, GST_TAG_FLAG_ENCODED, + G_TYPE_STRING, _("container format"), + _("container format the data is stored in"), NULL); + gst_tag_register (GST_TAG_BITRATE, GST_TAG_FLAG_ENCODED, + G_TYPE_UINT, _("bitrate"), _("exact or average bitrate in bits/s"), NULL); + gst_tag_register (GST_TAG_NOMINAL_BITRATE, GST_TAG_FLAG_ENCODED, + G_TYPE_UINT, _("nominal bitrate"), _("nominal bitrate in bits/s"), NULL); + gst_tag_register (GST_TAG_MINIMUM_BITRATE, GST_TAG_FLAG_ENCODED, + G_TYPE_UINT, _("minimum bitrate"), _("minimum bitrate in bits/s"), NULL); + gst_tag_register (GST_TAG_MAXIMUM_BITRATE, GST_TAG_FLAG_ENCODED, + G_TYPE_UINT, _("maximum bitrate"), _("maximum bitrate in bits/s"), NULL); + gst_tag_register (GST_TAG_ENCODER, GST_TAG_FLAG_ENCODED, + G_TYPE_STRING, + _("encoder"), _("encoder used to encode this stream"), NULL); + gst_tag_register (GST_TAG_ENCODER_VERSION, GST_TAG_FLAG_ENCODED, + G_TYPE_UINT, + _("encoder version"), + _("version of the encoder used to encode this stream"), NULL); + gst_tag_register (GST_TAG_SERIAL, GST_TAG_FLAG_ENCODED, + G_TYPE_UINT, _("serial"), _("serial number of track"), NULL); + gst_tag_register (GST_TAG_TRACK_GAIN, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("replaygain track gain"), _("track gain in db"), NULL); + gst_tag_register (GST_TAG_TRACK_PEAK, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("replaygain track peak"), _("peak of the track"), NULL); + gst_tag_register (GST_TAG_ALBUM_GAIN, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("replaygain album gain"), _("album gain in db"), NULL); + gst_tag_register (GST_TAG_ALBUM_PEAK, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("replaygain album peak"), _("peak of the album"), NULL); + gst_tag_register (GST_TAG_REFERENCE_LEVEL, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("replaygain reference level"), + _("reference level of track and album gain values"), NULL); + gst_tag_register (GST_TAG_LANGUAGE_CODE, GST_TAG_FLAG_META, G_TYPE_STRING, + _("language code"), + _("language code for this stream, conforming to ISO-639-1"), NULL); + gst_tag_register (GST_TAG_IMAGE, GST_TAG_FLAG_META, GST_TYPE_BUFFER, + _("image"), _("image related to this stream"), gst_tag_merge_use_first); + gst_tag_register (GST_TAG_PREVIEW_IMAGE, GST_TAG_FLAG_META, GST_TYPE_BUFFER, + /* TRANSLATORS: 'preview image' = image that shows a preview of the full image */ + _("preview image"), _("preview image related to this stream"), NULL); + gst_tag_register (GST_TAG_ATTACHMENT, GST_TAG_FLAG_META, GST_TYPE_BUFFER, + _("attachment"), _("file attached to this stream"), + gst_tag_merge_use_first); + gst_tag_register (GST_TAG_BEATS_PER_MINUTE, GST_TAG_FLAG_META, G_TYPE_DOUBLE, + _("beats per minute"), _("number of beats per minute in audio"), NULL); + gst_tag_register (GST_TAG_KEYWORDS, GST_TAG_FLAG_META, G_TYPE_STRING, + _("keywords"), _("comma separated keywords describing the content"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_GEO_LOCATION_NAME, GST_TAG_FLAG_META, G_TYPE_STRING, + _("geo location name"), _("human readable descriptive location of where " + "the media has been recorded or produced"), NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_LATITUDE, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("geo location latitude"), + _("geo latitude location of where the media has been recorded or " + "produced in degrees according to WGS84 (zero at the equator, " + "negative values for southern latitudes)"), NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_LONGITUDE, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("geo location longitude"), + _("geo longitude location of where the media has been recorded or " + "produced in degrees according to WGS84 (zero at the prime meridian " + "in Greenwich/UK, negative values for western longitudes)"), NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_ELEVATION, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("geo location elevation"), + _("geo elevation of where the media has been recorded or produced in " + "meters according to WGS84 (zero is average sea level)"), NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_COUNTRY, GST_TAG_FLAG_META, + G_TYPE_STRING, _("geo location country"), + _("country (english name) where the media has been recorded " + "or produced"), NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_CITY, GST_TAG_FLAG_META, + G_TYPE_STRING, _("geo location city"), + _("city (english name) where the media has been recorded " + "or produced"), NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_SUBLOCATION, GST_TAG_FLAG_META, + G_TYPE_STRING, _("geo location sublocation"), + _("a location whithin a city where the media has been produced " + "or created (e.g. the neighborhood)"), NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_HORIZONTAL_ERROR, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("geo location horizontal error"), + _("expected error of the horizontal positioning measures (in meters)"), + NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("geo location movement speed"), + _("movement speed of the capturing device while performing the capture " + "in m/s"), NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("geo location movement direction"), + _("indicates the movement direction of the device performing the capture" + " of a media. It is represented as degrees in floating point " + "representation, 0 means the geographic north, and increases " + "clockwise"), NULL); + gst_tag_register (GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION, GST_TAG_FLAG_META, + G_TYPE_DOUBLE, _("geo location capture direction"), + _("indicates the direction the device is pointing to when capturing " + " a media. It is represented as degrees in floating point " + " representation, 0 means the geographic north, and increases " + "clockwise"), NULL); + gst_tag_register (GST_TAG_SHOW_NAME, GST_TAG_FLAG_META, G_TYPE_STRING, + /* TRANSLATORS: 'show name' = 'TV/radio/podcast show name' here */ + _("show name"), + _("Name of the tv/podcast/series show the media is from"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_SHOW_SORTNAME, GST_TAG_FLAG_META, G_TYPE_STRING, + /* TRANSLATORS: 'show sortname' = 'TV/radio/podcast show name as used for sorting purposes' here */ + _("show sortname"), + _("Name of the tv/podcast/series show the media is from, for sorting " + "purposes"), NULL); + gst_tag_register (GST_TAG_SHOW_EPISODE_NUMBER, GST_TAG_FLAG_META, G_TYPE_UINT, + _("episode number"), + _("The episode number in the season the media is part of"), + gst_tag_merge_use_first); + gst_tag_register (GST_TAG_SHOW_SEASON_NUMBER, GST_TAG_FLAG_META, G_TYPE_UINT, + _("season number"), + _("The season number of the show the media is part of"), + gst_tag_merge_use_first); + gst_tag_register (GST_TAG_LYRICS, GST_TAG_FLAG_META, G_TYPE_STRING, + _("lyrics"), _("The lyrics of the media, commonly used for songs"), + gst_tag_merge_strings_with_comma); + gst_tag_register (GST_TAG_COMPOSER_SORTNAME, GST_TAG_FLAG_META, G_TYPE_STRING, + _("composer sortname"), + _("person(s) who composed the recording, for sorting purposes"), NULL); + gst_tag_register (GST_TAG_GROUPING, GST_TAG_FLAG_META, G_TYPE_STRING, + _("grouping"), + _("Groups related media that spans multiple tracks, like the different " + "pieces of a concerto. It is a higher level than a track, " + "but lower than an album"), NULL); + gst_tag_register (GST_TAG_USER_RATING, GST_TAG_FLAG_META, G_TYPE_UINT, + _("user rating"), + _("Rating attributed by a user. The higher the rank, " + "the more the user likes this media"), NULL); + gst_tag_register (GST_TAG_DEVICE_MANUFACTURER, GST_TAG_FLAG_META, + G_TYPE_STRING, _("device manufacturer"), + _("Manufacturer of the device used to create this media"), NULL); + gst_tag_register (GST_TAG_DEVICE_MODEL, GST_TAG_FLAG_META, G_TYPE_STRING, + _("device model"), + _("Model of the device used to create this media"), NULL); + gst_tag_register (GST_TAG_APPLICATION_NAME, GST_TAG_FLAG_META, G_TYPE_STRING, + _("application name"), _("Application used to create the media"), NULL); + gst_tag_register (GST_TAG_APPLICATION_DATA, GST_TAG_FLAG_META, + GST_TYPE_BUFFER, _("application data"), + _("Arbitrary application data to be serialized into the media"), NULL); + gst_tag_register (GST_TAG_IMAGE_ORIENTATION, GST_TAG_FLAG_META, G_TYPE_STRING, + _("image orientation"), + _("How the image should be rotated or flipped before display"), NULL); +} + +/** + * gst_tag_merge_use_first: + * @dest: (out caller-allocates): uninitialized GValue to store result in + * @src: GValue to copy from + * + * This is a convenience function for the func argument of gst_tag_register(). + * It creates a copy of the first value from the list. + */ +void +gst_tag_merge_use_first (GValue * dest, const GValue * src) +{ + const GValue *ret = gst_value_list_get_value (src, 0); + + g_value_init (dest, G_VALUE_TYPE (ret)); + g_value_copy (ret, dest); +} + +/** + * gst_tag_merge_strings_with_comma: + * @dest: (out caller-allocates): uninitialized GValue to store result in + * @src: GValue to copy from + * + * This is a convenience function for the func argument of gst_tag_register(). + * It concatenates all given strings using a comma. The tag must be registered + * as a G_TYPE_STRING or this function will fail. + */ +void +gst_tag_merge_strings_with_comma (GValue * dest, const GValue * src) +{ + GString *str; + gint i, count; + + count = gst_value_list_get_size (src); + str = g_string_new (g_value_get_string (gst_value_list_get_value (src, 0))); + for (i = 1; i < count; i++) { + /* separator between two strings */ + g_string_append (str, _(", ")); + g_string_append (str, + g_value_get_string (gst_value_list_get_value (src, i))); + } + + g_value_init (dest, G_TYPE_STRING); + g_value_take_string (dest, str->str); + g_string_free (str, FALSE); +} + +static GstTagInfo * +gst_tag_lookup (GQuark entry) +{ + GstTagInfo *ret; + + TAG_LOCK; + ret = g_hash_table_lookup (__tags, GUINT_TO_POINTER (entry)); + TAG_UNLOCK; + + return ret; +} + +/** + * gst_tag_register: + * @name: the name or identifier string + * @flag: a flag describing the type of tag info + * @type: the type this data is in + * @nick: human-readable name + * @blurb: a human-readable description about this tag + * @func: function for merging multiple values of this tag, or NULL + * + * Registers a new tag type for the use with GStreamer's type system. If a type + * with that name is already registered, that one is used. + * The old registration may have used a different type however. So don't rely + * on your supplied values. + * + * Important: if you do not supply a merge function the implication will be + * that there can only be one single value for this tag in a tag list and + * any additional values will silenty be discarded when being added (unless + * #GST_TAG_MERGE_REPLACE, #GST_TAG_MERGE_REPLACE_ALL, or + * #GST_TAG_MERGE_PREPEND is used as merge mode, in which case the new + * value will replace the old one in the list). + * + * The merge function will be called from gst_tag_list_copy_value() when + * it is required that one or more values for a tag be condensed into + * one single value. This may happen from gst_tag_list_get_string(), + * gst_tag_list_get_int(), gst_tag_list_get_double() etc. What will happen + * exactly in that case depends on how the tag was registered and if a + * merge function was supplied and if so which one. + * + * Two default merge functions are provided: gst_tag_merge_use_first() and + * gst_tag_merge_strings_with_comma(). + */ +void +gst_tag_register (const gchar * name, GstTagFlag flag, GType type, + const gchar * nick, const gchar * blurb, GstTagMergeFunc func) +{ + GQuark key; + GstTagInfo *info; + + g_return_if_fail (name != NULL); + g_return_if_fail (nick != NULL); + g_return_if_fail (blurb != NULL); + g_return_if_fail (type != 0 && type != GST_TYPE_LIST); + + key = g_quark_from_string (name); + info = gst_tag_lookup (key); + + if (info) { + g_return_if_fail (info->type == type); + return; + } + + info = g_slice_new (GstTagInfo); + info->flag = flag; + info->type = type; + info->nick = g_strdup (nick); + info->blurb = g_strdup (blurb); + info->merge_func = func; + + TAG_LOCK; + g_hash_table_insert (__tags, GUINT_TO_POINTER (key), info); + TAG_UNLOCK; +} + +/** + * gst_tag_exists: + * @tag: name of the tag + * + * Checks if the given type is already registered. + * + * Returns: TRUE if the type is already registered + */ +gboolean +gst_tag_exists (const gchar * tag) +{ + g_return_val_if_fail (tag != NULL, FALSE); + + return gst_tag_lookup (g_quark_from_string (tag)) != NULL; +} + +/** + * gst_tag_get_type: + * @tag: the tag + * + * Gets the #GType used for this tag. + * + * Returns: the #GType of this tag + */ +GType +gst_tag_get_type (const gchar * tag) +{ + GstTagInfo *info; + + g_return_val_if_fail (tag != NULL, 0); + info = gst_tag_lookup (g_quark_from_string (tag)); + g_return_val_if_fail (info != NULL, 0); + + return info->type; +} + +/** + * gst_tag_get_nick + * @tag: the tag + * + * Returns the human-readable name of this tag, You must not change or free + * this string. + * + * Returns: the human-readable name of this tag + */ +const gchar * +gst_tag_get_nick (const gchar * tag) +{ + GstTagInfo *info; + + g_return_val_if_fail (tag != NULL, NULL); + info = gst_tag_lookup (g_quark_from_string (tag)); + g_return_val_if_fail (info != NULL, NULL); + + return info->nick; +} + +/** + * gst_tag_get_description: + * @tag: the tag + * + * Returns the human-readable description of this tag, You must not change or + * free this string. + * + * Returns: the human-readable description of this tag + */ +const gchar * +gst_tag_get_description (const gchar * tag) +{ + GstTagInfo *info; + + g_return_val_if_fail (tag != NULL, NULL); + info = gst_tag_lookup (g_quark_from_string (tag)); + g_return_val_if_fail (info != NULL, NULL); + + return info->blurb; +} + +/** + * gst_tag_get_flag: + * @tag: the tag + * + * Gets the flag of @tag. + * + * Returns: the flag of this tag. + */ +GstTagFlag +gst_tag_get_flag (const gchar * tag) +{ + GstTagInfo *info; + + g_return_val_if_fail (tag != NULL, GST_TAG_FLAG_UNDEFINED); + info = gst_tag_lookup (g_quark_from_string (tag)); + g_return_val_if_fail (info != NULL, GST_TAG_FLAG_UNDEFINED); + + return info->flag; +} + +/** + * gst_tag_is_fixed: + * @tag: tag to check + * + * Checks if the given tag is fixed. A fixed tag can only contain one value. + * Unfixed tags can contain lists of values. + * + * Returns: TRUE, if the given tag is fixed. + */ +gboolean +gst_tag_is_fixed (const gchar * tag) +{ + GstTagInfo *info; + + g_return_val_if_fail (tag != NULL, FALSE); + info = gst_tag_lookup (g_quark_from_string (tag)); + g_return_val_if_fail (info != NULL, FALSE); + + return info->merge_func == NULL; +} + +/** + * gst_tag_list_new: + * + * Creates a new empty GstTagList. + * + * Free-function: gst_tag_list_free + * + * Returns: (transfer full): An empty tag list + */ +GstTagList * +gst_tag_list_new (void) +{ + return GST_TAG_LIST (gst_structure_id_empty_new (GST_QUARK (TAGLIST))); +} + +/** + * gst_tag_list_new_full: + * @tag: tag + * @...: NULL-terminated list of values to set + * + * Creates a new taglist and appends the values for the given tags. It expects + * tag-value pairs like gst_tag_list_add(), and a NULL terminator after the + * last pair. The type of the values is implicit and is documented in the API + * reference, but can also be queried at runtime with gst_tag_get_type(). It + * is an error to pass a value of a type not matching the tag type into this + * function. The tag list will make copies of any arguments passed + * (e.g. strings, buffers). + * + * Free-function: gst_tag_list_free + * + * Returns: (transfer full): a new #GstTagList. Free with gst_tag_list_free() + * when no longer needed. + * + * Since: 0.10.24 + */ +/* FIXME 0.11: rename gst_tag_list_new_full to _new and _new to _new_empty */ +GstTagList * +gst_tag_list_new_full (const gchar * tag, ...) +{ + GstTagList *list; + va_list args; + + g_return_val_if_fail (tag != NULL, NULL); + + list = gst_tag_list_new (); + va_start (args, tag); + gst_tag_list_add_valist (list, GST_TAG_MERGE_APPEND, tag, args); + va_end (args); + + return list; +} + +/** + * gst_tag_list_new_full_valist: + * @var_args: tag / value pairs to set + * + * Just like gst_tag_list_new_full(), only that it takes a va_list argument. + * Useful mostly for language bindings. + * + * Free-function: gst_tag_list_free + * + * Returns: (transfer full): a new #GstTagList. Free with gst_tag_list_free() + * when no longer needed. + * + * Since: 0.10.24 + */ +GstTagList * +gst_tag_list_new_full_valist (va_list var_args) +{ + GstTagList *list; + const gchar *tag; + + list = gst_tag_list_new (); + + tag = va_arg (var_args, gchar *); + gst_tag_list_add_valist (list, GST_TAG_MERGE_APPEND, tag, var_args); + + return list; +} + +/** + * gst_tag_list_is_empty: + * @list: A #GstTagList. + * + * Checks if the given taglist is empty. + * + * Returns: TRUE if the taglist is empty, otherwise FALSE. + * + * Since: 0.10.11 + */ +gboolean +gst_tag_list_is_empty (const GstTagList * list) +{ + g_return_val_if_fail (list != NULL, FALSE); + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); + + return (gst_structure_n_fields ((GstStructure *) list) == 0); +} + +/** + * gst_is_tag_list: + * @p: Object that might be a taglist + * + * Checks if the given pointer is a taglist. + * + * Returns: TRUE, if the given pointer is a taglist + */ +gboolean +gst_is_tag_list (gconstpointer p) +{ + GstStructure *s = (GstStructure *) p; + + g_return_val_if_fail (p != NULL, FALSE); + + return (GST_IS_STRUCTURE (s) && s->name == GST_QUARK (TAGLIST)); +} + +typedef struct +{ + GstStructure *list; + GstTagMergeMode mode; +} +GstTagCopyData; + +static void +gst_tag_list_add_value_internal (GstStructure * list, GstTagMergeMode mode, + GQuark tag, const GValue * value, GstTagInfo * info) +{ + const GValue *value2; + + if (info == NULL) { + info = gst_tag_lookup (tag); + if (G_UNLIKELY (info == NULL)) { + g_warning ("unknown tag '%s'", g_quark_to_string (tag)); + return; + } + } + + if (info->merge_func + && (value2 = gst_structure_id_get_value (list, tag)) != NULL) { + GValue dest = { 0, }; + + switch (mode) { + case GST_TAG_MERGE_REPLACE_ALL: + case GST_TAG_MERGE_REPLACE: + gst_structure_id_set_value (list, tag, value); + break; + case GST_TAG_MERGE_PREPEND: + gst_value_list_merge (&dest, value, value2); + gst_structure_id_set_value (list, tag, &dest); + g_value_unset (&dest); + break; + case GST_TAG_MERGE_APPEND: + gst_value_list_merge (&dest, value2, value); + gst_structure_id_set_value (list, tag, &dest); + g_value_unset (&dest); + break; + case GST_TAG_MERGE_KEEP: + case GST_TAG_MERGE_KEEP_ALL: + break; + default: + g_assert_not_reached (); + break; + } + } else { + switch (mode) { + case GST_TAG_MERGE_APPEND: + case GST_TAG_MERGE_KEEP: + if (gst_structure_id_get_value (list, tag) != NULL) + break; + /* fall through */ + case GST_TAG_MERGE_REPLACE_ALL: + case GST_TAG_MERGE_REPLACE: + case GST_TAG_MERGE_PREPEND: + gst_structure_id_set_value (list, tag, value); + break; + case GST_TAG_MERGE_KEEP_ALL: + break; + default: + g_assert_not_reached (); + break; + } + } +} + +static gboolean +gst_tag_list_copy_foreach (GQuark tag, const GValue * value, gpointer user_data) +{ + GstTagCopyData *copy = (GstTagCopyData *) user_data; + + gst_tag_list_add_value_internal (copy->list, copy->mode, tag, value, NULL); + + return TRUE; +} + +/** + * gst_tag_list_insert: + * @into: list to merge into + * @from: list to merge from + * @mode: the mode to use + * + * Inserts the tags of the @from list into the first list using the given mode. + */ +void +gst_tag_list_insert (GstTagList * into, const GstTagList * from, + GstTagMergeMode mode) +{ + GstTagCopyData data; + + g_return_if_fail (GST_IS_TAG_LIST (into)); + g_return_if_fail (GST_IS_TAG_LIST (from)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + + data.list = (GstStructure *) into; + data.mode = mode; + if (mode == GST_TAG_MERGE_REPLACE_ALL) { + gst_structure_remove_all_fields (data.list); + } + gst_structure_foreach ((GstStructure *) from, gst_tag_list_copy_foreach, + &data); +} + +/** + * gst_tag_list_copy: + * @list: list to copy + * + * Copies a given #GstTagList. + * + * Free-function: gst_tag_list_free + * + * Returns: (transfer full): copy of the given list + */ +GstTagList * +gst_tag_list_copy (const GstTagList * list) +{ + g_return_val_if_fail (GST_IS_TAG_LIST (list), NULL); + + return GST_TAG_LIST (gst_structure_copy ((GstStructure *) list)); +} + +/** + * gst_tag_list_merge: + * @list1: first list to merge + * @list2: second list to merge + * @mode: the mode to use + * + * Merges the two given lists into a new list. If one of the lists is NULL, a + * copy of the other is returned. If both lists are NULL, NULL is returned. + * + * Free-function: gst_tag_list_free + * + * Returns: (transfer full): the new list + */ +GstTagList * +gst_tag_list_merge (const GstTagList * list1, const GstTagList * list2, + GstTagMergeMode mode) +{ + GstTagList *list1_cp; + const GstTagList *list2_cp; + + g_return_val_if_fail (list1 == NULL || GST_IS_TAG_LIST (list1), NULL); + g_return_val_if_fail (list2 == NULL || GST_IS_TAG_LIST (list2), NULL); + g_return_val_if_fail (GST_TAG_MODE_IS_VALID (mode), NULL); + + /* nothing to merge */ + if (!list1 && !list2) { + return NULL; + } + + /* create empty list, we need to do this to correctly handling merge modes */ + list1_cp = (list1) ? gst_tag_list_copy (list1) : gst_tag_list_new (); + list2_cp = (list2) ? list2 : gst_tag_list_new (); + + gst_tag_list_insert (list1_cp, list2_cp, mode); + + if (!list2) + gst_tag_list_free ((GstTagList *) list2_cp); + + return list1_cp; +} + +/** + * gst_tag_list_free: + * @list: (in) (transfer full): the list to free + * + * Frees the given list and all associated values. + */ +void +gst_tag_list_free (GstTagList * list) +{ + g_return_if_fail (GST_IS_TAG_LIST (list)); + gst_structure_free ((GstStructure *) list); +} + +/** + * gst_tag_list_get_tag_size: + * @list: a taglist + * @tag: the tag to query + * + * Checks how many value are stored in this tag list for the given tag. + * + * Returns: The number of tags stored + */ +guint +gst_tag_list_get_tag_size (const GstTagList * list, const gchar * tag) +{ + const GValue *value; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), 0); + + value = gst_structure_get_value ((GstStructure *) list, tag); + if (value == NULL) + return 0; + if (G_VALUE_TYPE (value) != GST_TYPE_LIST) + return 1; + + return gst_value_list_get_size (value); +} + +/** + * gst_tag_list_add: + * @list: list to set tags in + * @mode: the mode to use + * @tag: tag + * @...: NULL-terminated list of values to set + * + * Sets the values for the given tags using the specified mode. + */ +void +gst_tag_list_add (GstTagList * list, GstTagMergeMode mode, const gchar * tag, + ...) +{ + va_list args; + + g_return_if_fail (GST_IS_TAG_LIST (list)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + g_return_if_fail (tag != NULL); + + va_start (args, tag); + gst_tag_list_add_valist (list, mode, tag, args); + va_end (args); +} + +/** + * gst_tag_list_add_values: + * @list: list to set tags in + * @mode: the mode to use + * @tag: tag + * @...: GValues to set + * + * Sets the GValues for the given tags using the specified mode. + */ +void +gst_tag_list_add_values (GstTagList * list, GstTagMergeMode mode, + const gchar * tag, ...) +{ + va_list args; + + g_return_if_fail (GST_IS_TAG_LIST (list)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + g_return_if_fail (tag != NULL); + + va_start (args, tag); + gst_tag_list_add_valist_values (list, mode, tag, args); + va_end (args); +} + +/** + * gst_tag_list_add_valist: + * @list: list to set tags in + * @mode: the mode to use + * @tag: tag + * @var_args: tag / value pairs to set + * + * Sets the values for the given tags using the specified mode. + */ +void +gst_tag_list_add_valist (GstTagList * list, GstTagMergeMode mode, + const gchar * tag, va_list var_args) +{ + GstTagInfo *info; + GQuark quark; + gchar *error = NULL; + + g_return_if_fail (GST_IS_TAG_LIST (list)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + g_return_if_fail (tag != NULL); + + if (mode == GST_TAG_MERGE_REPLACE_ALL) { + gst_structure_remove_all_fields (list); + } + + while (tag != NULL) { + GValue value = { 0, }; + + quark = g_quark_from_string (tag); + info = gst_tag_lookup (quark); + if (G_UNLIKELY (info == NULL)) { + g_warning ("unknown tag '%s'", tag); + return; + } + G_VALUE_COLLECT_INIT (&value, info->type, var_args, 0, &error); + if (error) { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + /* we purposely leak the value here, it might not be + * in a sane state if an error condition occoured + */ + return; + } + gst_tag_list_add_value_internal (list, mode, quark, &value, info); + g_value_unset (&value); + tag = va_arg (var_args, gchar *); + } +} + +/** + * gst_tag_list_add_valist_values: + * @list: list to set tags in + * @mode: the mode to use + * @tag: tag + * @var_args: tag / GValue pairs to set + * + * Sets the GValues for the given tags using the specified mode. + */ +void +gst_tag_list_add_valist_values (GstTagList * list, GstTagMergeMode mode, + const gchar * tag, va_list var_args) +{ + GQuark quark; + + g_return_if_fail (GST_IS_TAG_LIST (list)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + g_return_if_fail (tag != NULL); + + if (mode == GST_TAG_MERGE_REPLACE_ALL) { + gst_structure_remove_all_fields (list); + } + + while (tag != NULL) { + quark = g_quark_from_string (tag); + g_return_if_fail (gst_tag_lookup (quark) != NULL); + gst_tag_list_add_value_internal (list, mode, quark, va_arg (var_args, + GValue *), NULL); + tag = va_arg (var_args, gchar *); + } +} + +/** + * gst_tag_list_add_value: + * @list: list to set tags in + * @mode: the mode to use + * @tag: tag + * @value: GValue for this tag + * + * Sets the GValue for a given tag using the specified mode. + * + * Since: 0.10.24 + */ +void +gst_tag_list_add_value (GstTagList * list, GstTagMergeMode mode, + const gchar * tag, const GValue * value) +{ + g_return_if_fail (GST_IS_TAG_LIST (list)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + g_return_if_fail (tag != NULL); + + gst_tag_list_add_value_internal (list, mode, g_quark_from_string (tag), + value, NULL); +} + +/** + * gst_tag_list_remove_tag: + * @list: list to remove tag from + * @tag: tag to remove + * + * Removes the given tag from the taglist. + */ +void +gst_tag_list_remove_tag (GstTagList * list, const gchar * tag) +{ + g_return_if_fail (GST_IS_TAG_LIST (list)); + g_return_if_fail (tag != NULL); + + gst_structure_remove_field ((GstStructure *) list, tag); +} + +typedef struct +{ + GstTagForeachFunc func; + const GstTagList *tag_list; + gpointer data; +} +TagForeachData; + +static int +structure_foreach_wrapper (GQuark field_id, const GValue * value, + gpointer user_data) +{ + TagForeachData *data = (TagForeachData *) user_data; + + data->func (data->tag_list, g_quark_to_string (field_id), data->data); + return TRUE; +} + +/** + * gst_tag_list_foreach: + * @list: list to iterate over + * @func: (scope call): function to be called for each tag + * @user_data: (closure): user specified data + * + * Calls the given function for each tag inside the tag list. Note that if there + * is no tag, the function won't be called at all. + */ +void +gst_tag_list_foreach (const GstTagList * list, GstTagForeachFunc func, + gpointer user_data) +{ + TagForeachData data; + + g_return_if_fail (GST_IS_TAG_LIST (list)); + g_return_if_fail (func != NULL); + + data.func = func; + data.tag_list = list; + data.data = user_data; + gst_structure_foreach ((GstStructure *) list, structure_foreach_wrapper, + &data); +} + +/** + * gst_tag_list_get_value_index: + * @list: a #GstTagList + * @tag: tag to read out + * @index: number of entry to read out + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: (transfer none): The GValue for the specified entry or NULL if the + * tag wasn't available or the tag doesn't have as many entries + */ +const GValue * +gst_tag_list_get_value_index (const GstTagList * list, const gchar * tag, + guint index) +{ + const GValue *value; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), NULL); + g_return_val_if_fail (tag != NULL, NULL); + + value = gst_structure_get_value ((GstStructure *) list, tag); + if (value == NULL) + return NULL; + + if (GST_VALUE_HOLDS_LIST (value)) { + if (index >= gst_value_list_get_size (value)) + return NULL; + return gst_value_list_get_value (value, index); + } else { + if (index > 0) + return NULL; + return value; + } +} + +/** + * gst_tag_list_copy_value: + * @dest: (out caller-allocates): uninitialized #GValue to copy into + * @list: list to get the tag from + * @tag: tag to read out + * + * Copies the contents for the given tag into the value, + * merging multiple values into one if multiple values are associated + * with the tag. + * You must g_value_unset() the value after use. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +gboolean +gst_tag_list_copy_value (GValue * dest, const GstTagList * list, + const gchar * tag) +{ + const GValue *src; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (dest != NULL, FALSE); + g_return_val_if_fail (G_VALUE_TYPE (dest) == 0, FALSE); + + src = gst_structure_get_value ((GstStructure *) list, tag); + if (!src) + return FALSE; + + if (G_VALUE_TYPE (src) == GST_TYPE_LIST) { + GstTagInfo *info = gst_tag_lookup (g_quark_from_string (tag)); + + if (!info) + return FALSE; + + /* must be there or lists aren't allowed */ + g_assert (info->merge_func); + info->merge_func (dest, src); + } else { + g_value_init (dest, G_VALUE_TYPE (src)); + g_value_copy (src, dest); + } + return TRUE; +} + +/* FIXME 0.11: this whole merge function business is overdesigned, and the + * _get_foo() API is misleading as well - how many application developers will + * expect gst_tag_list_get_string (list, GST_TAG_ARTIST, &val) might return a + * string with multiple comma-separated artists? _get_foo() should just be + * a convenience wrapper around _get_foo_index (list, tag, 0, &val), + * supplemented by a special _tag_list_get_string_merged() function if needed + * (unless someone can actually think of real use cases where the merge + * function is not 'use first' for non-strings and merge for strings) */ + +/***** evil macros to get all the gst_tag_list_get_*() functions right *****/ + +#define TAG_MERGE_FUNCS(name,type,ret) \ +gboolean \ +gst_tag_list_get_ ## name (const GstTagList *list, const gchar *tag, \ + type *value) \ +{ \ + GValue v = { 0, }; \ + \ + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); \ + g_return_val_if_fail (tag != NULL, FALSE); \ + g_return_val_if_fail (value != NULL, FALSE); \ + \ + if (!gst_tag_list_copy_value (&v, list, tag)) \ + return FALSE; \ + *value = COPY_FUNC (g_value_get_ ## name (&v)); \ + g_value_unset (&v); \ + return ret; \ +} \ + \ +gboolean \ +gst_tag_list_get_ ## name ## _index (const GstTagList *list, \ + const gchar *tag, \ + guint index, type *value) \ +{ \ + const GValue *v; \ + \ + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); \ + g_return_val_if_fail (tag != NULL, FALSE); \ + g_return_val_if_fail (value != NULL, FALSE); \ + \ + if ((v = gst_tag_list_get_value_index (list, tag, index)) == NULL) \ + return FALSE; \ + *value = COPY_FUNC (g_value_get_ ## name (v)); \ + return ret; \ +} + +/* FIXME 0.11: maybe get rid of _get_char*(), _get_uchar*(), _get_long*(), + * _get_ulong*() and _get_pointer*()? - they are not really useful/common + * enough to warrant convenience accessor functions */ + +#define COPY_FUNC /**/ +/** + * gst_tag_list_get_char: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_char_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (char, gchar, TRUE); +/** + * gst_tag_list_get_uchar: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_uchar_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (uchar, guchar, TRUE); +/** + * gst_tag_list_get_boolean: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_boolean_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (boolean, gboolean, TRUE); +/** + * gst_tag_list_get_int: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_int_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (int, gint, TRUE); +/** + * gst_tag_list_get_uint: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_uint_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (uint, guint, TRUE); +/** + * gst_tag_list_get_long: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_long_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (long, glong, TRUE); +/** + * gst_tag_list_get_ulong: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_ulong_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (ulong, gulong, TRUE); +/** + * gst_tag_list_get_int64: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_int64_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (int64, gint64, TRUE); +/** + * gst_tag_list_get_uint64: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_uint64_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (uint64, guint64, TRUE); +/** + * gst_tag_list_get_float: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_float_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (float, gfloat, TRUE); +/** + * gst_tag_list_get_double: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_double_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (double, gdouble, TRUE); +/** + * gst_tag_list_get_pointer: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out) (transfer none): location for the result + * + * Copies the contents for the given tag into the value, merging multiple values + * into one if multiple values are associated with the tag. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_pointer_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out) (transfer none): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (pointer, gpointer, (*value != NULL)); + +static inline gchar * +_gst_strdup0 (const gchar * s) +{ + if (s == NULL || *s == '\0') + return NULL; + + return g_strdup (s); +} + +#undef COPY_FUNC +#define COPY_FUNC _gst_strdup0 + +/** + * gst_tag_list_get_string: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out callee-allocates) (transfer full): location for the result + * + * Copies the contents for the given tag into the value, possibly merging + * multiple values into one if multiple values are associated with the tag. + * + * Use gst_tag_list_get_string_index (list, tag, 0, value) if you want + * to retrieve the first string associated with this tag unmodified. + * + * The resulting string in @value will be in UTF-8 encoding and should be + * freed by the caller using g_free when no longer needed. Since 0.10.24 the + * returned string is also guaranteed to be non-NULL and non-empty. + * + * Free-function: g_free + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +/** + * gst_tag_list_get_string_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out callee-allocates) (transfer full): location for the result + * + * Gets the value that is at the given index for the given tag in the given + * list. + * + * The resulting string in @value will be in UTF-8 encoding and should be + * freed by the caller using g_free when no longer needed. Since 0.10.24 the + * returned string is also guaranteed to be non-NULL and non-empty. + * + * Free-function: g_free + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list. + */ +TAG_MERGE_FUNCS (string, gchar *, (*value != NULL)); + +/* + *FIXME 0.11: Instead of _peek (non-copy) and _get (copy), we could have + * _get (non-copy) and _dup (copy) for strings, seems more + * widely used + */ +/** + * gst_tag_list_peek_string_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out) (transfer none): location for the result + * + * Peeks at the value that is at the given index for the given tag in the given + * list. + * + * The resulting string in @value will be in UTF-8 encoding and doesn't need + * to be freed by the caller. The returned string is also guaranteed to + * be non-NULL and non-empty. + * + * Returns: TRUE, if a value was set, FALSE if the tag didn't exist in the + * given list. + */ +gboolean +gst_tag_list_peek_string_index (const GstTagList * list, + const gchar * tag, guint index, const gchar ** value) +{ + const GValue *v; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if ((v = gst_tag_list_get_value_index (list, tag, index)) == NULL) + return FALSE; + *value = g_value_get_string (v); + return *value != NULL && **value != '\0'; +} + +/** + * gst_tag_list_get_date: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out callee-allocates) (transfer full): address of a GDate pointer + * variable to store the result into + * + * Copies the first date for the given tag in the taglist into the variable + * pointed to by @value. Free the date with g_date_free() when it is no longer + * needed. + * + * Free-function: g_date_free + * + * Returns: TRUE, if a date was copied, FALSE if the tag didn't exist in the + * given list or if it was #NULL. + */ +gboolean +gst_tag_list_get_date (const GstTagList * list, const gchar * tag, + GDate ** value) +{ + GValue v = { 0, }; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if (!gst_tag_list_copy_value (&v, list, tag)) + return FALSE; + *value = (GDate *) g_value_dup_boxed (&v); + g_value_unset (&v); + return (*value != NULL); +} + +/** + * gst_tag_list_get_date_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out callee-allocates) (transfer full): location for the result + * + * Gets the date that is at the given index for the given tag in the given + * list and copies it into the variable pointed to by @value. Free the date + * with g_date_free() when it is no longer needed. + * + * Free-function: g_date_free + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list or if it was #NULL. + */ +gboolean +gst_tag_list_get_date_index (const GstTagList * list, + const gchar * tag, guint index, GDate ** value) +{ + const GValue *v; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if ((v = gst_tag_list_get_value_index (list, tag, index)) == NULL) + return FALSE; + *value = (GDate *) g_value_dup_boxed (v); + return (*value != NULL); +} + +/** + * gst_tag_list_get_date_time: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out callee-allocates) (transfer full): address of a #GstDateTime + * pointer variable to store the result into + * + * Copies the first datetime for the given tag in the taglist into the variable + * pointed to by @value. Unref the date with gst_date_time_unref() when + * it is no longer needed. + * + * Free-function: gst_date_time_unref + * + * Returns: TRUE, if a datetime was copied, FALSE if the tag didn't exist in + * thegiven list or if it was #NULL. + * + * Since: 0.10.31 + */ +gboolean +gst_tag_list_get_date_time (const GstTagList * list, const gchar * tag, + GstDateTime ** value) +{ + GValue v = { 0, }; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if (!gst_tag_list_copy_value (&v, list, tag)) + return FALSE; + + g_return_val_if_fail (GST_VALUE_HOLDS_DATE_TIME (&v), FALSE); + + *value = (GstDateTime *) g_value_dup_boxed (&v); + g_value_unset (&v); + return (*value != NULL); +} + +/** + * gst_tag_list_get_date_time_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out callee-allocates) (transfer full): location for the result + * + * Gets the datetime that is at the given index for the given tag in the given + * list and copies it into the variable pointed to by @value. Unref the datetime + * with gst_date_time_unref() when it is no longer needed. + * + * Free-function: gst_date_time_unref + * + * Returns: TRUE, if a value was copied, FALSE if the tag didn't exist in the + * given list or if it was #NULL. + * + * Since: 0.10.31 + */ +gboolean +gst_tag_list_get_date_time_index (const GstTagList * list, + const gchar * tag, guint index, GstDateTime ** value) +{ + const GValue *v; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if ((v = gst_tag_list_get_value_index (list, tag, index)) == NULL) + return FALSE; + *value = (GstDateTime *) g_value_dup_boxed (v); + return (*value != NULL); +} + +/** + * gst_tag_list_get_buffer: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @value: (out callee-allocates) (transfer full): address of a GstBuffer + * pointer variable to store the result into + * + * Copies the first buffer for the given tag in the taglist into the variable + * pointed to by @value. Free the buffer with gst_buffer_unref() when it is + * no longer needed. + * + * Free-function: gst_buffer_unref + * + * Returns: TRUE, if a buffer was copied, FALSE if the tag didn't exist in the + * given list or if it was #NULL. + * + * Since: 0.10.23 + */ +gboolean +gst_tag_list_get_buffer (const GstTagList * list, const gchar * tag, + GstBuffer ** value) +{ + GValue v = { 0, }; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if (!gst_tag_list_copy_value (&v, list, tag)) + return FALSE; + *value = g_value_dup_boxed (&v); + g_value_unset (&v); + return (*value != NULL); +} + +/** + * gst_tag_list_get_buffer_index: + * @list: a #GstTagList to get the tag from + * @tag: tag to read out + * @index: number of entry to read out + * @value: (out callee-allocates) (transfer full): address of a GstBuffer + * pointer variable to store the result into + * + * Gets the buffer that is at the given index for the given tag in the given + * list and copies it into the variable pointed to by @value. Free the buffer + * with gst_buffer_unref() when it is no longer needed. + * + * Free-function: gst_buffer_unref + * + * Returns: TRUE, if a buffer was copied, FALSE if the tag didn't exist in the + * given list or if it was #NULL. + * + * Since: 0.10.23 + */ +gboolean +gst_tag_list_get_buffer_index (const GstTagList * list, + const gchar * tag, guint index, GstBuffer ** value) +{ + const GValue *v; + + g_return_val_if_fail (GST_IS_TAG_LIST (list), FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if ((v = gst_tag_list_get_value_index (list, tag, index)) == NULL) + return FALSE; + *value = g_value_dup_boxed (v); + return (*value != NULL); +} diff --git a/gst/gsttaglist.h b/gst/gsttaglist.h new file mode 100644 index 0000000..e40d52a --- /dev/null +++ b/gst/gsttaglist.h @@ -0,0 +1,1034 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gsttaglist.h: Header for tag support + * + * 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_TAGLIST_H__ +#define __GST_TAGLIST_H__ + +#include <gst/gstdatetime.h> +#include <gst/gstbuffer.h> +#include <gst/gststructure.h> +#include <gst/glib-compat.h> + +G_BEGIN_DECLS + +/** + * GstTagMergeMode: + * @GST_TAG_MERGE_UNDEFINED: undefined merge mode + * @GST_TAG_MERGE_REPLACE_ALL: replace all tags (clear list and append) + * @GST_TAG_MERGE_REPLACE: replace tags + * @GST_TAG_MERGE_APPEND: append tags + * @GST_TAG_MERGE_PREPEND: prepend tags + * @GST_TAG_MERGE_KEEP: keep existing tags + * @GST_TAG_MERGE_KEEP_ALL: keep all existing tags + * @GST_TAG_MERGE_COUNT: the number of merge modes + * + * The different tag merging modes are basically replace, overwrite and append, + * but they can be seen from two directions. Given two taglists: (A) the tags + * already in the element and (B) the ones that are supplied to the element ( + * e.g. via gst_tag_setter_merge_tags() / gst_tag_setter_add_tags() or a + * %GST_EVENT_TAG), how are these tags merged? + * In the table below this is shown for the cases that a tag exists in the list + * (A) or does not exists (!A) and combinations thereof. + * + * <table frame="all" colsep="1" rowsep="1"> + * <title>merge mode</title> + * <tgroup cols='5' align='left'> + * <thead> + * <row> + * <entry>merge mode</entry> + * <entry>A + B</entry> + * <entry>A + !B</entry> + * <entry>!A + B</entry> + * <entry>!A + !B</entry> + * </row> + * </thead> + * <tbody> + * <row> + * <entry>REPLACE_ALL</entry> + * <entry>B</entry> + * <entry>-</entry> + * <entry>B</entry> + * <entry>-</entry> + * </row> + * <row> + * <entry>REPLACE</entry> + * <entry>B</entry> + * <entry>A</entry> + * <entry>B</entry> + * <entry>-</entry> + * </row> + * <row> + * <entry>APPEND</entry> + * <entry>A, B</entry> + * <entry>A</entry> + * <entry>B</entry> + * <entry>-</entry> + * </row> + * <row> + * <entry>PREPEND</entry> + * <entry>B, A</entry> + * <entry>A</entry> + * <entry>B</entry> + * <entry>-</entry> + * </row> + * <row> + * <entry>KEEP</entry> + * <entry>A</entry> + * <entry>A</entry> + * <entry>B</entry> + * <entry>-</entry> + * </row> + * <row> + * <entry>KEEP_ALL</entry> + * <entry>A</entry> + * <entry>A</entry> + * <entry>-</entry> + * <entry>-</entry> + * </row> + * </tbody> + * </tgroup> + * </table> + */ +typedef enum { + GST_TAG_MERGE_UNDEFINED, + GST_TAG_MERGE_REPLACE_ALL, + GST_TAG_MERGE_REPLACE, + GST_TAG_MERGE_APPEND, + GST_TAG_MERGE_PREPEND, + GST_TAG_MERGE_KEEP, + GST_TAG_MERGE_KEEP_ALL, + /* add more */ + GST_TAG_MERGE_COUNT +} GstTagMergeMode; + +#define GST_TAG_MODE_IS_VALID(mode) (((mode) > GST_TAG_MERGE_UNDEFINED) && ((mode) < GST_TAG_MERGE_COUNT)) + +/** + * GstTagFlag: + * @GST_TAG_FLAG_UNDEFINED: undefined flag + * @GST_TAG_FLAG_META: tag is meta data + * @GST_TAG_FLAG_ENCODED: tag is encoded + * @GST_TAG_FLAG_DECODED: tag is decoded + * @GST_TAG_FLAG_COUNT: number of tag flags + * + * Extra tag flags used when registering tags. + */ +typedef enum { + GST_TAG_FLAG_UNDEFINED, + GST_TAG_FLAG_META, + GST_TAG_FLAG_ENCODED, + GST_TAG_FLAG_DECODED, + GST_TAG_FLAG_COUNT +} GstTagFlag; + +#define GST_TAG_FLAG_IS_VALID(flag) (((flag) > GST_TAG_FLAG_UNDEFINED) && ((flag) < GST_TAG_FLAG_COUNT)) + +/* FIXME 0.11: Don't typedef GstTagList to be a GstStructure, they're + * internally the same but not from an API point of view. + * See bug #518934. + */ +/** + * GstTagList: + * + * Opaque #GstTagList data structure. + */ +#ifdef _FOOL_GTK_DOC_ +typedef struct _GstTagList GstTagList; +#else +#ifdef IN_GOBJECT_INTROSPECTION +typedef struct _GstTagList GstTagList; +#else +typedef GstStructure GstTagList; +#endif +#endif + +#define GST_TAG_LIST(x) ((GstTagList *) (x)) +#define GST_IS_TAG_LIST(x) ((x) != NULL && gst_is_tag_list (GST_TAG_LIST (x))) +#define GST_TYPE_TAG_LIST (gst_tag_list_get_type ()) + +/** + * GstTagForeachFunc: + * @list: the #GstTagList + * @tag: a name of a tag in @list + * @user_data: user data + * + * A function that will be called in gst_tag_list_foreach(). The function may + * not modify the tag list. + */ +typedef void (*GstTagForeachFunc) (const GstTagList * list, + const gchar * tag, + gpointer user_data); + +/** + * GstTagMergeFunc: + * @dest: the destination #GValue + * @src: the source #GValue + * + * A function for merging multiple values of a tag used when registering + * tags. + */ +typedef void (* GstTagMergeFunc) (GValue *dest, const GValue *src); + +GType gst_tag_list_get_type (void); + +/* tag registration */ +void gst_tag_register (const gchar * name, + GstTagFlag flag, + GType type, + const gchar * nick, + const gchar * blurb, + GstTagMergeFunc func); + +/* some default merging functions */ +void gst_tag_merge_use_first (GValue * dest, const GValue * src); +void gst_tag_merge_strings_with_comma (GValue * dest, const GValue * src); + +/* basic tag support */ +gboolean gst_tag_exists (const gchar * tag); +GType gst_tag_get_type (const gchar * tag); +const gchar * gst_tag_get_nick (const gchar * tag); +const gchar * gst_tag_get_description (const gchar * tag); +GstTagFlag gst_tag_get_flag (const gchar * tag); +gboolean gst_tag_is_fixed (const gchar * tag); + +/* tag lists */ +GstTagList * gst_tag_list_new (void); +GstTagList * gst_tag_list_new_full (const gchar * tag, ...); +GstTagList * gst_tag_list_new_full_valist (va_list var_args); + +gboolean gst_is_tag_list (gconstpointer p); +GstTagList * gst_tag_list_copy (const GstTagList * list); +gboolean gst_tag_list_is_empty (const GstTagList * list); +void gst_tag_list_insert (GstTagList * into, + const GstTagList * from, + GstTagMergeMode mode); +GstTagList * gst_tag_list_merge (const GstTagList * list1, + const GstTagList * list2, + GstTagMergeMode mode); +void gst_tag_list_free (GstTagList * list); +guint gst_tag_list_get_tag_size (const GstTagList * list, + const gchar * tag); +void gst_tag_list_add (GstTagList * list, + GstTagMergeMode mode, + const gchar * tag, + ...) G_GNUC_NULL_TERMINATED; +void gst_tag_list_add_values (GstTagList * list, + GstTagMergeMode mode, + const gchar * tag, + ...) G_GNUC_NULL_TERMINATED; +void gst_tag_list_add_valist (GstTagList * list, + GstTagMergeMode mode, + const gchar * tag, + va_list var_args); +void gst_tag_list_add_valist_values (GstTagList * list, + GstTagMergeMode mode, + const gchar * tag, + va_list var_args); +void gst_tag_list_add_value (GstTagList * list, + GstTagMergeMode mode, + const gchar * tag, + const GValue * value); +void gst_tag_list_remove_tag (GstTagList * list, + const gchar * tag); +void gst_tag_list_foreach (const GstTagList * list, + GstTagForeachFunc func, + gpointer user_data); + +const GValue * + gst_tag_list_get_value_index (const GstTagList * list, + const gchar * tag, + guint index); +gboolean gst_tag_list_copy_value (GValue * dest, + const GstTagList * list, + const gchar * tag); + +/* simplifications (FIXME: do we want them?) */ +gboolean gst_tag_list_get_char (const GstTagList * list, + const gchar * tag, + gchar * value); +gboolean gst_tag_list_get_char_index (const GstTagList * list, + const gchar * tag, + guint index, + gchar * value); +gboolean gst_tag_list_get_uchar (const GstTagList * list, + const gchar * tag, + guchar * value); +gboolean gst_tag_list_get_uchar_index (const GstTagList * list, + const gchar * tag, + guint index, + guchar * value); +gboolean gst_tag_list_get_boolean (const GstTagList * list, + const gchar * tag, + gboolean * value); +gboolean gst_tag_list_get_boolean_index (const GstTagList * list, + const gchar * tag, + guint index, + gboolean * value); +gboolean gst_tag_list_get_int (const GstTagList * list, + const gchar * tag, + gint * value); +gboolean gst_tag_list_get_int_index (const GstTagList * list, + const gchar * tag, + guint index, + gint * value); +gboolean gst_tag_list_get_uint (const GstTagList * list, + const gchar * tag, + guint * value); +gboolean gst_tag_list_get_uint_index (const GstTagList * list, + const gchar * tag, + guint index, + guint * value); +gboolean gst_tag_list_get_long (const GstTagList * list, + const gchar * tag, + glong * value); +gboolean gst_tag_list_get_long_index (const GstTagList * list, + const gchar * tag, + guint index, + glong * value); +gboolean gst_tag_list_get_ulong (const GstTagList * list, + const gchar * tag, + gulong * value); +gboolean gst_tag_list_get_ulong_index (const GstTagList * list, + const gchar * tag, + guint index, + gulong * value); +gboolean gst_tag_list_get_int64 (const GstTagList * list, + const gchar * tag, + gint64 * value); +gboolean gst_tag_list_get_int64_index (const GstTagList * list, + const gchar * tag, + guint index, + gint64 * value); +gboolean gst_tag_list_get_uint64 (const GstTagList * list, + const gchar * tag, + guint64 * value); +gboolean gst_tag_list_get_uint64_index (const GstTagList * list, + const gchar * tag, + guint index, + guint64 * value); +gboolean gst_tag_list_get_float (const GstTagList * list, + const gchar * tag, + gfloat * value); +gboolean gst_tag_list_get_float_index (const GstTagList * list, + const gchar * tag, + guint index, + gfloat * value); +gboolean gst_tag_list_get_double (const GstTagList * list, + const gchar * tag, + gdouble * value); +gboolean gst_tag_list_get_double_index (const GstTagList * list, + const gchar * tag, + guint index, + gdouble * value); +gboolean gst_tag_list_get_string (const GstTagList * list, + const gchar * tag, + gchar ** value); +gboolean gst_tag_list_get_string_index (const GstTagList * list, + const gchar * tag, + guint index, + gchar ** value); +gboolean gst_tag_list_peek_string_index (const GstTagList * list, + const gchar * tag, + guint index, + const gchar ** value); +gboolean gst_tag_list_get_pointer (const GstTagList * list, + const gchar * tag, + gpointer * value); +gboolean gst_tag_list_get_pointer_index (const GstTagList * list, + const gchar * tag, + guint index, + gpointer * value); +gboolean gst_tag_list_get_date (const GstTagList * list, + const gchar * tag, + GDate ** value); +gboolean gst_tag_list_get_date_index (const GstTagList * list, + const gchar * tag, + guint index, + GDate ** value); +gboolean gst_tag_list_get_date_time (const GstTagList * list, + const gchar * tag, + GstDateTime ** value); +gboolean gst_tag_list_get_date_time_index (const GstTagList * list, + const gchar * tag, + guint index, + GstDateTime ** value); +gboolean gst_tag_list_get_buffer (const GstTagList * list, + const gchar * tag, + GstBuffer ** value); +gboolean gst_tag_list_get_buffer_index (const GstTagList * list, + const gchar * tag, + guint index, + GstBuffer ** value); + +/* GStreamer core tags */ +/** + * GST_TAG_TITLE: + * + * commonly used title (string) + * + * The title as it should be displayed, e.g. 'The Doll House' + */ +#define GST_TAG_TITLE "title" +/** + * GST_TAG_TITLE_SORTNAME: + * + * commonly used title, as used for sorting (string) + * + * The title as it should be sorted, e.g. 'Doll House, The' + * + * Since: 0.10.15 + */ +#define GST_TAG_TITLE_SORTNAME "title-sortname" +/** + * GST_TAG_ARTIST: + * + * person(s) responsible for the recording (string) + * + * The artist name as it should be displayed, e.g. 'Jimi Hendrix' or + * 'The Guitar Heroes' + */ +#define GST_TAG_ARTIST "artist" +/** + * GST_TAG_ARTIST_SORTNAME: + * + * person(s) responsible for the recording, as used for sorting (string) + * + * The artist name as it should be sorted, e.g. 'Hendrix, Jimi' or + * 'Guitar Heroes, The' + * + * Since: 0.10.15 + */ +/* FIXME 0.11: change to "artist-sortname" */ +#define GST_TAG_ARTIST_SORTNAME "musicbrainz-sortname" +/** + * GST_TAG_ALBUM: + * + * album containing this data (string) + * + * The album name as it should be displayed, e.g. 'The Jazz Guitar' + */ +#define GST_TAG_ALBUM "album" +/** + * GST_TAG_ALBUM_SORTNAME: + * + * album containing this data, as used for sorting (string) + * + * The album name as it should be sorted, e.g. 'Jazz Guitar, The' + * + * Since: 0.10.15 + */ +#define GST_TAG_ALBUM_SORTNAME "album-sortname" +/** + * GST_TAG_ALBUM_ARTIST: + * + * The artist of the entire album, as it should be displayed. + * + * Since: 0.10.25 + */ +#define GST_TAG_ALBUM_ARTIST "album-artist" +/** + * GST_TAG_ALBUM_ARTIST_SORTNAME: + * + * The artist of the entire album, as it should be sorted. + * + * Since: 0.10.25 + */ +#define GST_TAG_ALBUM_ARTIST_SORTNAME "album-artist-sortname" +/** + * GST_TAG_COMPOSER: + * + * person(s) who composed the recording (string) + * + * Since: 0.10.15 + */ +#define GST_TAG_COMPOSER "composer" +/** + * GST_TAG_DATE: + * + * date the data was created (#GDate structure) + */ +#define GST_TAG_DATE "date" +/** + * GST_TAG_DATE_TIME: + * + * date and time the data was created (#GstDateTime structure) + * + * Since: 0.10.31 + */ +#define GST_TAG_DATE_TIME "datetime" +/** + * GST_TAG_GENRE: + * + * genre this data belongs to (string) + */ +#define GST_TAG_GENRE "genre" +/** + * GST_TAG_COMMENT: + * + * free text commenting the data (string) + */ +#define GST_TAG_COMMENT "comment" +/** + * GST_TAG_EXTENDED_COMMENT: + * + * key/value text commenting the data (string) + * + * Must be in the form of 'key=comment' or + * 'key[lc]=comment' where 'lc' is an ISO-639 + * language code. + * + * This tag is used for unknown Vorbis comment tags, + * unknown APE tags and certain ID3v2 comment fields. + * + * Since: 0.10.10 + */ +#define GST_TAG_EXTENDED_COMMENT "extended-comment" +/** + * GST_TAG_TRACK_NUMBER: + * + * track number inside a collection (unsigned integer) + */ +#define GST_TAG_TRACK_NUMBER "track-number" +/** + * GST_TAG_TRACK_COUNT: + * + * count of tracks inside collection this track belongs to (unsigned integer) + */ +#define GST_TAG_TRACK_COUNT "track-count" +/** + * GST_TAG_ALBUM_VOLUME_NUMBER: + * + * disc number inside a collection (unsigned integer) + */ +#define GST_TAG_ALBUM_VOLUME_NUMBER "album-disc-number" +/** + * GST_TAG_ALBUM_VOLUME_COUNT: + * + * count of discs inside collection this disc belongs to (unsigned integer) + */ +#define GST_TAG_ALBUM_VOLUME_COUNT "album-disc-count" +/** + * GST_TAG_LOCATION: + * + * Origin of media as a URI (location, where the original of the file or stream + * is hosted) (string) + */ +#define GST_TAG_LOCATION "location" +/** + * GST_TAG_HOMEPAGE: + * + * Homepage for this media (i.e. artist or movie homepage) (string) + * + * Since: 0.10.23 + */ +#define GST_TAG_HOMEPAGE "homepage" +/** + * GST_TAG_DESCRIPTION: + * + * short text describing the content of the data (string) + */ +#define GST_TAG_DESCRIPTION "description" +/** + * GST_TAG_VERSION: + * + * version of this data (string) + */ +#define GST_TAG_VERSION "version" +/** + * GST_TAG_ISRC: + * + * International Standard Recording Code - see http://www.ifpi.org/isrc/ (string) + */ +#define GST_TAG_ISRC "isrc" +/** + * GST_TAG_ORGANIZATION: + * + * organization (string) + */ +#define GST_TAG_ORGANIZATION "organization" +/** + * GST_TAG_COPYRIGHT: + * + * copyright notice of the data (string) + */ +#define GST_TAG_COPYRIGHT "copyright" +/** + * GST_TAG_COPYRIGHT_URI: + * + * URI to location where copyright details can be found (string) + * + * Since: 0.10.14 + */ +#define GST_TAG_COPYRIGHT_URI "copyright-uri" +/** + * GST_TAG_ENCODED_BY: + * + * name of the person or organisation that encoded the file. May contain a + * copyright message if the person or organisation also holds the copyright + * (string) + * + * Note: do not use this field to describe the encoding application. Use + * #GST_TAG_APPLICATION_NAME or #GST_TAG_COMMENT for that. + * + * Since: 0.10.33 + */ +#define GST_TAG_ENCODED_BY "encoded-by" +/** + * GST_TAG_CONTACT: + * + * contact information (string) + */ +#define GST_TAG_CONTACT "contact" +/** + * GST_TAG_LICENSE: + * + * license of data (string) + */ +#define GST_TAG_LICENSE "license" +/** + * GST_TAG_LICENSE_URI: + * + * URI to location where license details can be found (string) + * + * Since: 0.10.14 + */ +#define GST_TAG_LICENSE_URI "license-uri" +/** + * GST_TAG_PERFORMER: + * + * person(s) performing (string) + */ +#define GST_TAG_PERFORMER "performer" +/** + * GST_TAG_DURATION: + * + * length in GStreamer time units (nanoseconds) (unsigned 64-bit integer) + */ +#define GST_TAG_DURATION "duration" +/** + * GST_TAG_CODEC: + * + * codec the data is stored in (string) + */ +#define GST_TAG_CODEC "codec" +/** + * GST_TAG_VIDEO_CODEC: + * + * codec the video data is stored in (string) + */ +#define GST_TAG_VIDEO_CODEC "video-codec" +/** + * GST_TAG_AUDIO_CODEC: + * + * codec the audio data is stored in (string) + */ +#define GST_TAG_AUDIO_CODEC "audio-codec" +/** + * GST_TAG_SUBTITLE_CODEC: + * + * codec/format the subtitle data is stored in (string) + * + * Since: 0.10.23 + */ +#define GST_TAG_SUBTITLE_CODEC "subtitle-codec" +/** + * GST_TAG_CONTAINER_FORMAT: + * + * container format the data is stored in (string) + * + * Since: 0.10.24 + */ +#define GST_TAG_CONTAINER_FORMAT "container-format" +/** + * GST_TAG_BITRATE: + * + * exact or average bitrate in bits/s (unsigned integer) + */ +#define GST_TAG_BITRATE "bitrate" +/** + * GST_TAG_NOMINAL_BITRATE: + * + * nominal bitrate in bits/s (unsigned integer). The actual bitrate might be + * different from this target bitrate. + */ +#define GST_TAG_NOMINAL_BITRATE "nominal-bitrate" +/** + * GST_TAG_MINIMUM_BITRATE: + * + * minimum bitrate in bits/s (unsigned integer) + */ +#define GST_TAG_MINIMUM_BITRATE "minimum-bitrate" +/** + * GST_TAG_MAXIMUM_BITRATE: + * + * maximum bitrate in bits/s (unsigned integer) + */ +#define GST_TAG_MAXIMUM_BITRATE "maximum-bitrate" +/** + * GST_TAG_SERIAL: + * + * serial number of track (unsigned integer) + */ +#define GST_TAG_SERIAL "serial" +/** + * GST_TAG_ENCODER: + * + * encoder used to encode this stream (string) + */ +#define GST_TAG_ENCODER "encoder" +/** + * GST_TAG_ENCODER_VERSION: + * + * version of the encoder used to encode this stream (unsigned integer) + */ +#define GST_TAG_ENCODER_VERSION "encoder-version" +/** + * GST_TAG_TRACK_GAIN: + * + * track gain in db (double) + */ +#define GST_TAG_TRACK_GAIN "replaygain-track-gain" +/** + * GST_TAG_TRACK_PEAK: + * + * peak of the track (double) + */ +#define GST_TAG_TRACK_PEAK "replaygain-track-peak" +/** + * GST_TAG_ALBUM_GAIN: + * + * album gain in db (double) + */ +#define GST_TAG_ALBUM_GAIN "replaygain-album-gain" +/** + * GST_TAG_ALBUM_PEAK: + * + * peak of the album (double) + */ +#define GST_TAG_ALBUM_PEAK "replaygain-album-peak" +/** + * GST_TAG_REFERENCE_LEVEL: + * + * reference level of track and album gain values (double) + * + * Since: 0.10.12 + */ +#define GST_TAG_REFERENCE_LEVEL "replaygain-reference-level" +/** + * GST_TAG_LANGUAGE_CODE: + * + * Language code (ISO-639-1) (string) of the content + */ +#define GST_TAG_LANGUAGE_CODE "language-code" +/** + * GST_TAG_IMAGE: + * + * image (buffer) (buffer caps should specify the content type and preferably + * also set "image-type" field as #GstTagImageType) + * + * Since: 0.10.6 + */ +#define GST_TAG_IMAGE "image" +/** + * GST_TAG_PREVIEW_IMAGE: + * + * image that is meant for preview purposes, e.g. small icon-sized version + * (buffer) (buffer caps should specify the content type) + * + * Since: 0.10.7 + */ +#define GST_TAG_PREVIEW_IMAGE "preview-image" + +/** + * GST_TAG_ATTACHMENT: + * + * generic file attachment (buffer) (buffer caps should specify the content + * type and if possible set "filename" to the file name of the + * attachment) + * + * Since: 0.10.21 + */ +#define GST_TAG_ATTACHMENT "attachment" + +/** + * GST_TAG_BEATS_PER_MINUTE: + * + * number of beats per minute in audio (double) + * + * Since: 0.10.12 + */ +#define GST_TAG_BEATS_PER_MINUTE "beats-per-minute" + +/** + * GST_TAG_KEYWORDS: + * + * comma separated keywords describing the content (string). + * + * Since: 0.10.21 + */ +#define GST_TAG_KEYWORDS "keywords" + +/** + * GST_TAG_GEO_LOCATION_NAME: + * + * human readable descriptive location of where the media has been recorded or + * produced. (string). + * + * Since: 0.10.21 + */ +#define GST_TAG_GEO_LOCATION_NAME "geo-location-name" + +/** + * GST_TAG_GEO_LOCATION_LATITUDE: + * + * geo latitude location of where the media has been recorded or produced in + * degrees according to WGS84 (zero at the equator, negative values for southern + * latitudes) (double). + * + * Since: 0.10.21 + */ +#define GST_TAG_GEO_LOCATION_LATITUDE "geo-location-latitude" + +/** + * GST_TAG_GEO_LOCATION_LONGITUDE: + * + * geo longitude location of where the media has been recorded or produced in + * degrees according to WGS84 (zero at the prime meridian in Greenwich/UK, + * negative values for western longitudes). (double). + * + * Since: 0.10.21 + */ +#define GST_TAG_GEO_LOCATION_LONGITUDE "geo-location-longitude" + +/** + * GST_TAG_GEO_LOCATION_ELEVATION: + * + * geo elevation of where the media has been recorded or produced in meters + * according to WGS84 (zero is average sea level) (double). + * + * Since: 0.10.21 + */ +#define GST_TAG_GEO_LOCATION_ELEVATION "geo-location-elevation" +/** + * GST_TAG_GEO_LOCATION_COUNTRY: + * + * The country (english name) where the media has been produced (string). + * + * Since: 0.10.29 + */ +#define GST_TAG_GEO_LOCATION_COUNTRY "geo-location-country" +/** + * GST_TAG_GEO_LOCATION_CITY: + * + * The city (english name) where the media has been produced (string). + * + * Since: 0.10.29 + */ +#define GST_TAG_GEO_LOCATION_CITY "geo-location-city" +/** + * GST_TAG_GEO_LOCATION_SUBLOCATION: + * + * A location 'smaller' than GST_TAG_GEO_LOCATION_CITY that specifies better + * where the media has been produced. (e.g. the neighborhood) (string). + * + * This tag has been added as this is how it is handled/named in XMP's + * Iptc4xmpcore schema. + * + * Since: 0.10.29 + */ +#define GST_TAG_GEO_LOCATION_SUBLOCATION "geo-location-sublocation" +/** + * GST_TAG_GEO_LOCATION_HORIZONTAL_ERROR: + * + * Represents the expected error on the horizontal positioning in + * meters (double). + * + * Since: 0.10.31 + */ +#define GST_TAG_GEO_LOCATION_HORIZONTAL_ERROR "geo-location-horizontal-error" +/** + * GST_TAG_GEO_LOCATION_MOVEMENT_SPEED: + * + * Speed of the capturing device when performing the capture. + * Represented in m/s. (double) + * + * See also #GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION + * + * Since 0.10.30 + */ +#define GST_TAG_GEO_LOCATION_MOVEMENT_SPEED "geo-location-movement-speed" +/** + * GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION: + * + * Indicates the movement direction of the device performing the capture + * of a media. It is represented as degrees in floating point representation, + * 0 means the geographic north, and increases clockwise (double from 0 to 360) + * + * See also #GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION + * + * Since: 0.10.30 + */ +#define GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION "geo-location-movement-direction" +/** + * GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION: + * + * Indicates the direction the device is pointing to when capturing + * a media. It is represented as degrees in floating point representation, + * 0 means the geographic north, and increases clockwise (double from 0 to 360) + * + * See also #GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION + * + * Since: 0.10.30 + */ +#define GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION "geo-location-capture-direction" +/** + * GST_TAG_SHOW_NAME: + * + * Name of the show, used for displaying (string) + * + * Since: 0.10.26 + */ +#define GST_TAG_SHOW_NAME "show-name" +/** + * GST_TAG_SHOW_SORTNAME: + * + * Name of the show, used for sorting (string) + * + * Since: 0.10.26 + */ +#define GST_TAG_SHOW_SORTNAME "show-sortname" +/** + * GST_TAG_SHOW_EPISODE_NUMBER: + * + * Number of the episode within a season/show (unsigned integer) + * + * Since: 0.10.26 + */ +#define GST_TAG_SHOW_EPISODE_NUMBER "show-episode-number" +/** + * GST_TAG_SHOW_SEASON_NUMBER: + * + * Number of the season of a show/series (unsigned integer) + * + * Since: 0.10.26 + */ +#define GST_TAG_SHOW_SEASON_NUMBER "show-season-number" +/** + * GST_TAG_LYRICS: + * + * The lyrics of the media (string) + * + * Since: 0.10.26 + */ +#define GST_TAG_LYRICS "lyrics" +/** + * GST_TAG_COMPOSER_SORTNAME: + * + * The composer's name, used for sorting (string) + * + * Since: 0.10.26 + */ +#define GST_TAG_COMPOSER_SORTNAME "composer-sortname" +/** + * GST_TAG_GROUPING: + * + * Groups together media that are related and spans multiple tracks. An + * example are multiple pieces of a concerto. (string) + * + * Since: 0.10.26 + */ +#define GST_TAG_GROUPING "grouping" +/** + * GST_TAG_USER_RATING: + * + * Rating attributed by a person (likely the application user). + * The higher the value, the more the user likes this media + * (unsigned int from 0 to 100) + * + * Since: 0.10.29 + */ +#define GST_TAG_USER_RATING "user-rating" +/** + * GST_TAG_DEVICE_MANUFACTURER: + * + * Manufacturer of the device used to create the media (string) + * + * Since: 0.10.30 + */ +#define GST_TAG_DEVICE_MANUFACTURER "device-manufacturer" +/** + * GST_TAG_DEVICE_MODEL: + * + * Model of the device used to create the media (string) + * + * Since: 0.10.30 + */ +#define GST_TAG_DEVICE_MODEL "device-model" +/** + * GST_TAG_APPLICATION_NAME: + * + * Name of the application used to create the media (string) + * + * Since: 0.10.31 + */ +#define GST_TAG_APPLICATION_NAME "application-name" +/** + * GST_TAG_APPLICATION_DATA: + * + * Arbitrary application data (buffer) + * + * Some formats allow application's to add their own arbitrary data + * into files. This data is application's dependent. + * + * Since: 0.10.31 + */ +#define GST_TAG_APPLICATION_DATA "application-data" +/** + * GST_TAG_IMAGE_ORIENTATION: + * + * Represents the 'Orientation' tag from EXIF. Defines how the image + * should be rotated and mirrored for display. (string) + * + * This tag has a predefined set of allowed values: + * "rotate-0" + * "rotate-90" + * "rotate-180" + * "rotate-270" + * "flip-rotate-0" + * "flip-rotate-90" + * "flip-rotate-180" + * "flip-rotate-270" + * + * The naming is adopted according to a possible transformation to perform + * on the image to fix its orientation, obviously equivalent operations will + * yield the same result. + * + * Rotations indicated by the values are in clockwise direction and + * 'flip' means an horizontal mirroring. + * + * Since: 0.10.30 + */ +#define GST_TAG_IMAGE_ORIENTATION "image-orientation" + +G_END_DECLS + +#endif /* __GST_TAGLIST_H__ */ diff --git a/gst/gsttagsetter.c b/gst/gsttagsetter.c new file mode 100644 index 0000000..d08d7e8 --- /dev/null +++ b/gst/gsttagsetter.c @@ -0,0 +1,424 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gsttagsetter.c: interface for tag setting on elements + * + * 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:gsttagsetter + * @short_description: Element interface that allows setting and retrieval + * of media metadata + * + * Element interface that allows setting of media metadata. + * + * Elements that support changing a stream's metadata will implement this + * interface. Examples of such elements are 'vorbisenc', 'theoraenc' and + * 'id3v2mux'. + * + * If you just want to retrieve metadata in your application then all you + * need to do is watch for tag messages on your pipeline's bus. This + * interface is only for setting metadata, not for extracting it. To set tags + * from the application, find tagsetter elements and set tags using e.g. + * gst_tag_setter_merge_tags() or gst_tag_setter_add_tags(). Also consider + * setting the #GstTagMergeMode that is used for tag events that arrive at the + * tagsetter element (default mode is to keep existing tags). + * The application should do that before the element goes to %GST_STATE_PAUSED. + * + * Elements implementing the #GstTagSetter interface often have to merge + * any tags received from upstream and the tags set by the application via + * the interface. This can be done like this: + * + * |[ + * GstTagMergeMode merge_mode; + * const GstTagList *application_tags; + * const GstTagList *event_tags; + * GstTagSetter *tagsetter; + * GstTagList *result; + * + * tagsetter = GST_TAG_SETTER (element); + * + * merge_mode = gst_tag_setter_get_tag_merge_mode (tagsetter); + * application_tags = gst_tag_setter_get_tag_list (tagsetter); + * event_tags = (const GstTagList *) element->event_tags; + * + * GST_LOG_OBJECT (tagsetter, "merging tags, merge mode = %d", merge_mode); + * GST_LOG_OBJECT (tagsetter, "event tags: %" GST_PTR_FORMAT, event_tags); + * GST_LOG_OBJECT (tagsetter, "set tags: %" GST_PTR_FORMAT, application_tags); + * + * result = gst_tag_list_merge (application_tags, event_tags, merge_mode); + * + * GST_LOG_OBJECT (tagsetter, "final tags: %" GST_PTR_FORMAT, result); + * ]| + * + * Last reviewed on 2006-05-18 (0.10.6) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst_private.h" +#include "gsttagsetter.h" +#include <gobject/gvaluecollector.h> +#include <string.h> + +static GQuark gst_tag_key; + +typedef struct +{ + GstTagMergeMode mode; + GstTagList *list; + GStaticMutex lock; +} GstTagData; + +GType +gst_tag_setter_get_type (void) +{ + static volatile gsize tag_setter_type = 0; + + if (g_once_init_enter (&tag_setter_type)) { + GType _type; + static const GTypeInfo tag_setter_info = { + sizeof (GstTagSetterIFace), /* class_size */ + NULL, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, + NULL + }; + + _type = g_type_register_static (G_TYPE_INTERFACE, "GstTagSetter", + &tag_setter_info, 0); + + g_type_interface_add_prerequisite (_type, GST_TYPE_ELEMENT); + + gst_tag_key = g_quark_from_static_string ("GST_TAG_SETTER"); + g_once_init_leave (&tag_setter_type, _type); + } + + return tag_setter_type; +} + +static void +gst_tag_data_free (gpointer p) +{ + GstTagData *data = (GstTagData *) p; + + if (data->list) + gst_tag_list_free (data->list); + + g_static_mutex_free (&data->lock); + + g_slice_free (GstTagData, data); +} + +static GstTagData * +gst_tag_setter_get_data (GstTagSetter * setter) +{ + GstTagData *data; + + data = g_object_get_qdata (G_OBJECT (setter), gst_tag_key); + if (!data) { + static GStaticMutex create_mutex = G_STATIC_MUTEX_INIT; + + /* make sure no other thread is creating a GstTagData at the same time */ + g_static_mutex_lock (&create_mutex); + data = g_object_get_qdata (G_OBJECT (setter), gst_tag_key); + if (!data) { + data = g_slice_new (GstTagData); + g_static_mutex_init (&data->lock); + data->list = NULL; + data->mode = GST_TAG_MERGE_KEEP; + g_object_set_qdata_full (G_OBJECT (setter), gst_tag_key, data, + gst_tag_data_free); + } + g_static_mutex_unlock (&create_mutex); + } + + return data; +} + +/** + * gst_tag_setter_reset_tags: + * @setter: a #GstTagSetter + * + * Reset the internal taglist. Elements should call this from within the + * state-change handler. + * + * Since: 0.10.22 + */ +void +gst_tag_setter_reset_tags (GstTagSetter * setter) +{ + GstTagData *data; + + g_return_if_fail (GST_IS_TAG_SETTER (setter)); + + data = gst_tag_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + if (data->list) { + gst_tag_list_free (data->list); + data->list = NULL; + } + g_static_mutex_unlock (&data->lock); +} + +/** + * gst_tag_setter_merge_tags: + * @setter: a #GstTagSetter + * @list: a tag list to merge from + * @mode: the mode to merge with + * + * Merges the given list into the setter's list using the given mode. + */ +void +gst_tag_setter_merge_tags (GstTagSetter * setter, const GstTagList * list, + GstTagMergeMode mode) +{ + GstTagData *data; + + g_return_if_fail (GST_IS_TAG_SETTER (setter)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + g_return_if_fail (GST_IS_TAG_LIST (list)); + + data = gst_tag_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + if (data->list == NULL) { + if (mode != GST_TAG_MERGE_KEEP_ALL) + data->list = gst_tag_list_copy (list); + } else { + gst_tag_list_insert (data->list, list, mode); + } + g_static_mutex_unlock (&data->lock); +} + +/** + * gst_tag_setter_add_tags: + * @setter: a #GstTagSetter + * @mode: the mode to use + * @tag: tag to set + * @...: more tag / value pairs to set + * + * Adds the given tag / value pairs on the setter using the given merge mode. + * The list must be terminated with NULL. + */ +void +gst_tag_setter_add_tags (GstTagSetter * setter, GstTagMergeMode mode, + const gchar * tag, ...) +{ + va_list args; + + g_return_if_fail (GST_IS_TAG_SETTER (setter)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + + va_start (args, tag); + gst_tag_setter_add_tag_valist (setter, mode, tag, args); + va_end (args); +} + +/** + * gst_tag_setter_add_tag_values: + * @setter: a #GstTagSetter + * @mode: the mode to use + * @tag: tag to set + * @...: more tag / GValue pairs to set + * + * Adds the given tag / GValue pairs on the setter using the given merge mode. + * The list must be terminated with NULL. + */ +void +gst_tag_setter_add_tag_values (GstTagSetter * setter, GstTagMergeMode mode, + const gchar * tag, ...) +{ + va_list args; + + g_return_if_fail (GST_IS_TAG_SETTER (setter)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + + va_start (args, tag); + gst_tag_setter_add_tag_valist_values (setter, mode, tag, args); + va_end (args); +} + +/** + * gst_tag_setter_add_tag_valist: + * @setter: a #GstTagSetter + * @mode: the mode to use + * @tag: tag to set + * @var_args: tag / value pairs to set + * + * Adds the given tag / value pairs on the setter using the given merge mode. + * The list must be terminated with NULL. + */ +void +gst_tag_setter_add_tag_valist (GstTagSetter * setter, GstTagMergeMode mode, + const gchar * tag, va_list var_args) +{ + GstTagData *data; + + g_return_if_fail (GST_IS_TAG_SETTER (setter)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + + data = gst_tag_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + if (!data->list) + data->list = gst_tag_list_new (); + + gst_tag_list_add_valist (data->list, mode, tag, var_args); + + g_static_mutex_unlock (&data->lock); +} + +/** + * gst_tag_setter_add_tag_valist_values: + * @setter: a #GstTagSetter + * @mode: the mode to use + * @tag: tag to set + * @var_args: tag / GValue pairs to set + * + * Adds the given tag / GValue pairs on the setter using the given merge mode. + * The list must be terminated with NULL. + */ +void +gst_tag_setter_add_tag_valist_values (GstTagSetter * setter, + GstTagMergeMode mode, const gchar * tag, va_list var_args) +{ + GstTagData *data; + + g_return_if_fail (GST_IS_TAG_SETTER (setter)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + + data = gst_tag_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + + if (!data->list) + data->list = gst_tag_list_new (); + + gst_tag_list_add_valist_values (data->list, mode, tag, var_args); + + g_static_mutex_unlock (&data->lock); +} + +/** + * gst_tag_setter_add_tag_value: + * @setter: a #GstTagSetter + * @mode: the mode to use + * @tag: tag to set + * @value: GValue to set for the tag + * + * Adds the given tag / GValue pair on the setter using the given merge mode. + * + * Since: 0.10.24 + */ +void +gst_tag_setter_add_tag_value (GstTagSetter * setter, + GstTagMergeMode mode, const gchar * tag, const GValue * value) +{ + GstTagData *data; + + g_return_if_fail (GST_IS_TAG_SETTER (setter)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + + data = gst_tag_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + + if (!data->list) + data->list = gst_tag_list_new (); + + gst_tag_list_add_value (data->list, mode, tag, value); + + g_static_mutex_unlock (&data->lock); +} + +/** + * gst_tag_setter_get_tag_list: + * @setter: a #GstTagSetter + * + * Returns the current list of tags the setter uses. The list should not be + * modified or freed. + * + * This function is not thread-safe. + * + * Returns: (transfer none): a current snapshot of the taglist used in the + * setter or NULL if none is used. + */ +const GstTagList * +gst_tag_setter_get_tag_list (GstTagSetter * setter) +{ + g_return_val_if_fail (GST_IS_TAG_SETTER (setter), NULL); + + return gst_tag_setter_get_data (setter)->list; +} + +/** + * gst_tag_setter_set_tag_merge_mode: + * @setter: a #GstTagSetter + * @mode: The mode with which tags are added + * + * Sets the given merge mode that is used for adding tags from events to tags + * specified by this interface. The default is #GST_TAG_MERGE_KEEP, which keeps + * the tags set with this interface and discards tags from events. + */ +void +gst_tag_setter_set_tag_merge_mode (GstTagSetter * setter, GstTagMergeMode mode) +{ + GstTagData *data; + + g_return_if_fail (GST_IS_TAG_SETTER (setter)); + g_return_if_fail (GST_TAG_MODE_IS_VALID (mode)); + + data = gst_tag_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + data->mode = mode; + g_static_mutex_unlock (&data->lock); +} + +/** + * gst_tag_setter_get_tag_merge_mode: + * @setter: a #GstTagSetter + * + * Queries the mode by which tags inside the setter are overwritten by tags + * from events + * + * Returns: the merge mode used inside the element. + */ +GstTagMergeMode +gst_tag_setter_get_tag_merge_mode (GstTagSetter * setter) +{ + GstTagMergeMode mode; + GstTagData *data; + + g_return_val_if_fail (GST_IS_TAG_SETTER (setter), GST_TAG_MERGE_UNDEFINED); + + data = gst_tag_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + mode = data->mode; + g_static_mutex_unlock (&data->lock); + + return mode; +} diff --git a/gst/gsttagsetter.h b/gst/gsttagsetter.h new file mode 100644 index 0000000..44c794b --- /dev/null +++ b/gst/gsttagsetter.h @@ -0,0 +1,100 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gsttagsetter.h: Interfaces for tagging + * + * 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_TAG_SETTER_H__ +#define __GST_TAG_SETTER_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define GST_TYPE_TAG_SETTER (gst_tag_setter_get_type ()) +#define GST_TAG_SETTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TAG_SETTER, GstTagSetter)) +#define GST_IS_TAG_SETTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TAG_SETTER)) +#define GST_TAG_SETTER_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GST_TYPE_TAG_SETTER, GstTagSetterIFace)) + +/** + * GstTagSetter: + * + * Opaque #GstTagSetter data structure. + */ +typedef struct _GstTagSetter GstTagSetter; /* Dummy typedef */ +typedef struct _GstTagSetterIFace GstTagSetterIFace; + +/** + * GstTagSetterIFace: + * @g_iface: parent interface type. + * + * #GstTagSetterIFace interface. + */ +/* use an empty interface here to allow detection of elements using user-set + tags */ +struct _GstTagSetterIFace +{ + GTypeInterface g_iface; + + /* signals */ + + /* virtual table */ +}; + +GType gst_tag_setter_get_type (void); + +void gst_tag_setter_reset_tags (GstTagSetter * setter); + +void gst_tag_setter_merge_tags (GstTagSetter * setter, + const GstTagList * list, + GstTagMergeMode mode); +void gst_tag_setter_add_tags (GstTagSetter * setter, + GstTagMergeMode mode, + const gchar * tag, + ...) G_GNUC_NULL_TERMINATED; + +void gst_tag_setter_add_tag_values (GstTagSetter * setter, + GstTagMergeMode mode, + const gchar * tag, + ...) G_GNUC_NULL_TERMINATED; + +void gst_tag_setter_add_tag_valist (GstTagSetter * setter, + GstTagMergeMode mode, + const gchar * tag, + va_list var_args); + +void gst_tag_setter_add_tag_valist_values(GstTagSetter * setter, + GstTagMergeMode mode, + const gchar * tag, + va_list var_args); + +void gst_tag_setter_add_tag_value (GstTagSetter * setter, + GstTagMergeMode mode, + const gchar * tag, + const GValue * value); + +const GstTagList * + gst_tag_setter_get_tag_list (GstTagSetter * setter); + +void gst_tag_setter_set_tag_merge_mode (GstTagSetter * setter, + GstTagMergeMode mode); +GstTagMergeMode gst_tag_setter_get_tag_merge_mode (GstTagSetter * setter); + +G_END_DECLS + +#endif /* __GST_TAG_SETTER_H__ */ diff --git a/gst/gsttask.c b/gst/gsttask.c new file mode 100644 index 0000000..aac6b7a --- /dev/null +++ b/gst/gsttask.c @@ -0,0 +1,855 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gsttask.c: Streaming tasks + * + * 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:gsttask + * @short_description: Abstraction of GStreamer streaming threads. + * @see_also: #GstElement, #GstPad + * + * #GstTask is used by #GstElement and #GstPad to provide the data passing + * threads in a #GstPipeline. + * + * A #GstPad will typically start a #GstTask to push or pull data to/from the + * peer pads. Most source elements start a #GstTask to push data. In some cases + * a demuxer element can start a #GstTask to pull data from a peer element. This + * is typically done when the demuxer can perform random access on the upstream + * peer element for improved performance. + * + * Although convenience functions exist on #GstPad to start/pause/stop tasks, it + * might sometimes be needed to create a #GstTask manually if it is not related to + * a #GstPad. + * + * Before the #GstTask can be run, it needs a #GStaticRecMutex that can be set with + * gst_task_set_lock(). + * + * The task can be started, paused and stopped with gst_task_start(), gst_task_pause() + * and gst_task_stop() respectively or with the gst_task_set_state() function. + * + * A #GstTask will repeatedly call the #GstTaskFunction with the user data + * that was provided when creating the task with gst_task_create(). While calling + * the function it will acquire the provided lock. The provided lock is released + * when the task pauses or stops. + * + * Stopping a task with gst_task_stop() will not immediately make sure the task is + * not running anymore. Use gst_task_join() to make sure the task is completely + * stopped and the thread is stopped. + * + * After creating a #GstTask, use gst_object_unref() to free its resources. This can + * only be done it the task is not running anymore. + * + * Task functions can send a #GstMessage to send out-of-band data to the + * application. The application can receive messages from the #GstBus in its + * mainloop. + * + * For debugging perposes, the task will configure its object name as the thread + * name on Linux. Please note that the object name should be configured before the + * task is started; changing the object name after the task has been started, has + * no effect on the thread name. + * + * Last reviewed on 2010-03-15 (0.10.29) + */ + +#include "gst_private.h" + +#include "gstinfo.h" +#include "gsttask.h" + +#include <stdio.h> + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +GST_DEBUG_CATEGORY_STATIC (task_debug); +#define GST_CAT_DEFAULT (task_debug) + +#define SET_TASK_STATE(t,s) (g_atomic_int_set (&GST_TASK_STATE(t), (s))) +#define GET_TASK_STATE(t) ((GstTaskState) g_atomic_int_get (&GST_TASK_STATE(t))) + +#define GST_TASK_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_TASK, GstTaskPrivate)) + +struct _GstTaskPrivate +{ + /* callbacks for managing the thread of this task */ + GstTaskThreadCallbacks thr_callbacks; + gpointer thr_user_data; + GDestroyNotify thr_notify; + + gboolean prio_set; + GThreadPriority priority; + + /* configured pool */ + GstTaskPool *pool; + + /* remember the pool and id that is currently running. */ + gpointer id; + GstTaskPool *pool_id; +}; + +#ifdef _MSC_VER +#include <windows.h> + +struct _THREADNAME_INFO +{ + DWORD dwType; // must be 0x1000 + LPCSTR szName; // pointer to name (in user addr space) + DWORD dwThreadID; // thread ID (-1=caller thread) + DWORD dwFlags; // reserved for future use, must be zero +}; +typedef struct _THREADNAME_INFO THREADNAME_INFO; + +void +SetThreadName (DWORD dwThreadID, LPCSTR szThreadName) +{ + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = szThreadName; + info.dwThreadID = dwThreadID; + info.dwFlags = 0; + + __try { + RaiseException (0x406D1388, 0, sizeof (info) / sizeof (DWORD), + (DWORD *) & info); + } + __except (EXCEPTION_CONTINUE_EXECUTION) { + } +} +#endif + +static void gst_task_finalize (GObject * object); + +static void gst_task_func (GstTask * task); + +static GStaticMutex pool_lock = G_STATIC_MUTEX_INIT; + +#define _do_init \ +{ \ + GST_DEBUG_CATEGORY_INIT (task_debug, "task", 0, "Processing tasks"); \ +} + +G_DEFINE_TYPE_WITH_CODE (GstTask, gst_task, GST_TYPE_OBJECT, _do_init); + +static void +init_klass_pool (GstTaskClass * klass) +{ + g_static_mutex_lock (&pool_lock); + if (klass->pool) { + gst_task_pool_cleanup (klass->pool); + gst_object_unref (klass->pool); + } + klass->pool = gst_task_pool_new (); + gst_task_pool_prepare (klass->pool, NULL); + g_static_mutex_unlock (&pool_lock); +} + +static void +gst_task_class_init (GstTaskClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstTaskPrivate)); + + gobject_class->finalize = gst_task_finalize; + + init_klass_pool (klass); +} + +static void +gst_task_init (GstTask * task) +{ + GstTaskClass *klass; + + klass = GST_TASK_GET_CLASS (task); + + task->priv = GST_TASK_GET_PRIVATE (task); + task->running = FALSE; + task->thread = NULL; + task->lock = NULL; + task->cond = g_cond_new (); + SET_TASK_STATE (task, GST_TASK_STOPPED); + task->priv->prio_set = FALSE; + + /* use the default klass pool for this task, users can + * override this later */ + g_static_mutex_lock (&pool_lock); + task->priv->pool = gst_object_ref (klass->pool); + g_static_mutex_unlock (&pool_lock); +} + +static void +gst_task_finalize (GObject * object) +{ + GstTask *task = GST_TASK (object); + GstTaskPrivate *priv = task->priv; + + GST_DEBUG ("task %p finalize", task); + + if (priv->thr_notify) + priv->thr_notify (priv->thr_user_data); + priv->thr_notify = NULL; + priv->thr_user_data = NULL; + + gst_object_unref (priv->pool); + + /* task thread cannot be running here since it holds a ref + * to the task so that the finalize could not have happened */ + g_cond_free (task->cond); + task->cond = NULL; + + G_OBJECT_CLASS (gst_task_parent_class)->finalize (object); +} + +/* should be called with the object LOCK */ +static void +gst_task_configure_name (GstTask * task) +{ +#if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_NAME) + const gchar *name; + gchar thread_name[17] = { 0, }; + + GST_OBJECT_LOCK (task); + name = GST_OBJECT_NAME (task); + + /* set the thread name to something easily identifiable */ + if (!snprintf (thread_name, 17, "%s", GST_STR_NULL (name))) { + GST_DEBUG_OBJECT (task, "Could not create thread name for '%s'", name); + } else { + GST_DEBUG_OBJECT (task, "Setting thread name to '%s'", thread_name); + if (prctl (PR_SET_NAME, (unsigned long int) thread_name, 0, 0, 0)) + GST_DEBUG_OBJECT (task, "Failed to set thread name"); + } + GST_OBJECT_UNLOCK (task); +#endif +#ifdef _MSC_VER + const gchar *name; + name = GST_OBJECT_NAME (task); + + /* set the thread name to something easily identifiable */ + GST_DEBUG_OBJECT (task, "Setting thread name to '%s'", name); + SetThreadName (-1, name); +#endif +} + +static void +gst_task_func (GstTask * task) +{ + GStaticRecMutex *lock; + GThread *tself; + GstTaskPrivate *priv; + + priv = task->priv; + + tself = g_thread_self (); + + GST_DEBUG ("Entering task %p, thread %p", task, tself); + + /* we have to grab the lock to get the mutex. We also + * mark our state running so that nobody can mess with + * the mutex. */ + GST_OBJECT_LOCK (task); + if (GET_TASK_STATE (task) == GST_TASK_STOPPED) + goto exit; + lock = GST_TASK_GET_LOCK (task); + if (G_UNLIKELY (lock == NULL)) + goto no_lock; + task->thread = tself; + /* only update the priority when it was changed */ + if (priv->prio_set) + g_thread_set_priority (tself, priv->priority); + GST_OBJECT_UNLOCK (task); + + /* fire the enter_thread callback when we need to */ + if (priv->thr_callbacks.enter_thread) + priv->thr_callbacks.enter_thread (task, tself, priv->thr_user_data); + + /* locking order is TASK_LOCK, LOCK */ + g_static_rec_mutex_lock (lock); + /* configure the thread name now */ + gst_task_configure_name (task); + + while (G_LIKELY (GET_TASK_STATE (task) != GST_TASK_STOPPED)) { + if (G_UNLIKELY (GET_TASK_STATE (task) == GST_TASK_PAUSED)) { + GST_OBJECT_LOCK (task); + while (G_UNLIKELY (GST_TASK_STATE (task) == GST_TASK_PAUSED)) { + gint t; + + t = g_static_rec_mutex_unlock_full (lock); + if (t <= 0) { + g_warning ("wrong STREAM_LOCK count %d", t); + } + GST_TASK_SIGNAL (task); + GST_TASK_WAIT (task); + GST_OBJECT_UNLOCK (task); + /* locking order.. */ + if (t > 0) + g_static_rec_mutex_lock_full (lock, t); + + GST_OBJECT_LOCK (task); + if (G_UNLIKELY (GET_TASK_STATE (task) == GST_TASK_STOPPED)) { + GST_OBJECT_UNLOCK (task); + goto done; + } + } + GST_OBJECT_UNLOCK (task); + } + + task->func (task->data); + } +done: + g_static_rec_mutex_unlock (lock); + + GST_OBJECT_LOCK (task); + task->thread = NULL; + +exit: + if (priv->thr_callbacks.leave_thread) { + /* fire the leave_thread callback when we need to. We need to do this before + * we signal the task and with the task lock released. */ + GST_OBJECT_UNLOCK (task); + priv->thr_callbacks.leave_thread (task, tself, priv->thr_user_data); + GST_OBJECT_LOCK (task); + } else { + /* restore normal priority when releasing back into the pool, we will not + * touch the priority when a custom callback has been installed. */ + g_thread_set_priority (tself, G_THREAD_PRIORITY_NORMAL); + } + /* now we allow messing with the lock again by setting the running flag to + * FALSE. Together with the SIGNAL this is the sign for the _join() to + * complete. + * Note that we still have not dropped the final ref on the task. We could + * check here if there is a pending join() going on and drop the last ref + * before releasing the lock as we can be sure that a ref is held by the + * caller of the join(). */ + task->running = FALSE; + GST_TASK_SIGNAL (task); + GST_OBJECT_UNLOCK (task); + + GST_DEBUG ("Exit task %p, thread %p", task, g_thread_self ()); + + gst_object_unref (task); + return; + +no_lock: + { + g_warning ("starting task without a lock"); + goto exit; + } +} + +/** + * gst_task_cleanup_all: + * + * Wait for all tasks to be stopped. This is mainly used internally + * to ensure proper cleanup of internal data structures in test suites. + * + * MT safe. + */ +void +gst_task_cleanup_all (void) +{ + GstTaskClass *klass; + + if ((klass = g_type_class_peek (GST_TYPE_TASK))) { + init_klass_pool (klass); + } +} + +/** + * gst_task_create: + * @func: The #GstTaskFunction to use + * @data: (closure): User data to pass to @func + * + * Create a new Task that will repeatedly call the provided @func + * with @data as a parameter. Typically the task will run in + * a new thread. + * + * The function cannot be changed after the task has been created. You + * must create a new #GstTask to change the function. + * + * This function will not yet create and start a thread. Use gst_task_start() or + * gst_task_pause() to create and start the GThread. + * + * Before the task can be used, a #GStaticRecMutex must be configured using the + * gst_task_set_lock() function. This lock will always be acquired while + * @func is called. + * + * Returns: (transfer full): A new #GstTask. + * + * MT safe. + */ +GstTask * +gst_task_create (GstTaskFunction func, gpointer data) +{ + GstTask *task; + + task = g_object_newv (GST_TYPE_TASK, 0, NULL); + task->func = func; + task->data = data; + + GST_DEBUG ("Created task %p", task); + + return task; +} + +/** + * gst_task_set_lock: + * @task: The #GstTask to use + * @mutex: The #GMutex to use + * + * Set the mutex used by the task. The mutex will be acquired before + * calling the #GstTaskFunction. + * + * This function has to be called before calling gst_task_pause() or + * gst_task_start(). + * + * MT safe. + */ +void +gst_task_set_lock (GstTask * task, GStaticRecMutex * mutex) +{ + GST_OBJECT_LOCK (task); + if (G_UNLIKELY (task->running)) + goto is_running; + GST_TASK_GET_LOCK (task) = mutex; + GST_OBJECT_UNLOCK (task); + + return; + + /* ERRORS */ +is_running: + { + GST_OBJECT_UNLOCK (task); + g_warning ("cannot call set_lock on a running task"); + } +} + +/** + * gst_task_set_priority: + * @task: a #GstTask + * @priority: a new priority for @task + * + * Changes the priority of @task to @priority. + * + * Note: try not to depend on task priorities. + * + * MT safe. + * + * Since: 0.10.24 + */ +void +gst_task_set_priority (GstTask * task, GThreadPriority priority) +{ + GstTaskPrivate *priv; + GThread *thread; + + g_return_if_fail (GST_IS_TASK (task)); + + priv = task->priv; + + GST_OBJECT_LOCK (task); + priv->prio_set = TRUE; + priv->priority = priority; + thread = task->thread; + if (thread != NULL) { + /* if this task already has a thread, we can configure the priority right + * away, else we do that when we assign a thread to the task. */ + g_thread_set_priority (thread, priority); + } + GST_OBJECT_UNLOCK (task); +} + +/** + * gst_task_get_pool: + * @task: a #GstTask + * + * Get the #GstTaskPool that this task will use for its streaming + * threads. + * + * MT safe. + * + * Returns: (transfer full): the #GstTaskPool used by @task. gst_object_unref() + * after usage. + * + * Since: 0.10.24 + */ +GstTaskPool * +gst_task_get_pool (GstTask * task) +{ + GstTaskPool *result; + GstTaskPrivate *priv; + + g_return_val_if_fail (GST_IS_TASK (task), NULL); + + priv = task->priv; + + GST_OBJECT_LOCK (task); + result = gst_object_ref (priv->pool); + GST_OBJECT_UNLOCK (task); + + return result; +} + +/** + * gst_task_set_pool: + * @task: a #GstTask + * @pool: (transfer none): a #GstTaskPool + * + * Set @pool as the new GstTaskPool for @task. Any new streaming threads that + * will be created by @task will now use @pool. + * + * MT safe. + * + * Since: 0.10.24 + */ +void +gst_task_set_pool (GstTask * task, GstTaskPool * pool) +{ + GstTaskPool *old; + GstTaskPrivate *priv; + + g_return_if_fail (GST_IS_TASK (task)); + g_return_if_fail (GST_IS_TASK_POOL (pool)); + + priv = task->priv; + + GST_OBJECT_LOCK (task); + if (priv->pool != pool) { + old = priv->pool; + priv->pool = gst_object_ref (pool); + } else + old = NULL; + GST_OBJECT_UNLOCK (task); + + if (old) + gst_object_unref (old); +} + + +/** + * gst_task_set_thread_callbacks: + * @task: The #GstTask to use + * @callbacks: (in): a #GstTaskThreadCallbacks pointer + * @user_data: (closure): user data passed to the callbacks + * @notify: called when @user_data is no longer referenced + * + * Set callbacks which will be executed when a new thread is needed, the thread + * function is entered and left and when the thread is joined. + * + * By default a thread for @task will be created from a default thread pool. + * + * Objects can use custom GThreads or can perform additional configuration of + * the threads (such as changing the thread priority) by installing callbacks. + * + * MT safe. + * + * Since: 0.10.24 + */ +void +gst_task_set_thread_callbacks (GstTask * task, + GstTaskThreadCallbacks * callbacks, gpointer user_data, + GDestroyNotify notify) +{ + GDestroyNotify old_notify; + + g_return_if_fail (task != NULL); + g_return_if_fail (GST_IS_TASK (task)); + g_return_if_fail (callbacks != NULL); + + GST_OBJECT_LOCK (task); + old_notify = task->priv->thr_notify; + + if (old_notify) { + gpointer old_data; + + old_data = task->priv->thr_user_data; + + task->priv->thr_user_data = NULL; + task->priv->thr_notify = NULL; + GST_OBJECT_UNLOCK (task); + + old_notify (old_data); + + GST_OBJECT_LOCK (task); + } + task->priv->thr_callbacks = *callbacks; + task->priv->thr_user_data = user_data; + task->priv->thr_notify = notify; + GST_OBJECT_UNLOCK (task); +} + +/** + * gst_task_get_state: + * @task: The #GstTask to query + * + * Get the current state of the task. + * + * Returns: The #GstTaskState of the task + * + * MT safe. + */ +GstTaskState +gst_task_get_state (GstTask * task) +{ + GstTaskState result; + + g_return_val_if_fail (GST_IS_TASK (task), GST_TASK_STOPPED); + + result = GET_TASK_STATE (task); + + return result; +} + +/* make sure the task is running and start a thread if it's not. + * This function must be called with the task LOCK. */ +static gboolean +start_task (GstTask * task) +{ + gboolean res = TRUE; + GError *error = NULL; + GstTaskPrivate *priv; + + priv = task->priv; + + /* new task, We ref before so that it remains alive while + * the thread is running. */ + gst_object_ref (task); + /* mark task as running so that a join will wait until we schedule + * and exit the task function. */ + task->running = TRUE; + + /* push on the thread pool, we remember the original pool because the user + * could change it later on and then we join to the wrong pool. */ + priv->pool_id = gst_object_ref (priv->pool); + priv->id = + gst_task_pool_push (priv->pool_id, (GstTaskPoolFunction) gst_task_func, + task, &error); + + if (error != NULL) { + g_warning ("failed to create thread: %s", error->message); + g_error_free (error); + res = FALSE; + } + return res; +} + + +/** + * gst_task_set_state: + * @task: a #GstTask + * @state: the new task state + * + * Sets the state of @task to @state. + * + * The @task must have a lock associated with it using + * gst_task_set_lock() when going to GST_TASK_STARTED or GST_TASK_PAUSED or + * this function will return %FALSE. + * + * MT safe. + * + * Returns: %TRUE if the state could be changed. + * + * Since: 0.10.24 + */ +gboolean +gst_task_set_state (GstTask * task, GstTaskState state) +{ + GstTaskState old; + gboolean res = TRUE; + + g_return_val_if_fail (GST_IS_TASK (task), FALSE); + + GST_DEBUG_OBJECT (task, "Changing task %p to state %d", task, state); + + GST_OBJECT_LOCK (task); + if (state != GST_TASK_STOPPED) + if (G_UNLIKELY (GST_TASK_GET_LOCK (task) == NULL)) + goto no_lock; + + /* if the state changed, do our thing */ + old = GET_TASK_STATE (task); + if (old != state) { + SET_TASK_STATE (task, state); + switch (old) { + case GST_TASK_STOPPED: + /* If the task already has a thread scheduled we don't have to do + * anything. */ + if (G_UNLIKELY (!task->running)) + res = start_task (task); + break; + case GST_TASK_PAUSED: + /* when we are paused, signal to go to the new state */ + GST_TASK_SIGNAL (task); + break; + case GST_TASK_STARTED: + /* if we were started, we'll go to the new state after the next + * iteration. */ + break; + } + } + GST_OBJECT_UNLOCK (task); + + return res; + + /* ERRORS */ +no_lock: + { + GST_WARNING_OBJECT (task, "state %d set on task without a lock", state); + GST_OBJECT_UNLOCK (task); + g_warning ("task without a lock can't be set to state %d", state); + return FALSE; + } +} + +/** + * gst_task_start: + * @task: The #GstTask to start + * + * Starts @task. The @task must have a lock associated with it using + * gst_task_set_lock() or this function will return %FALSE. + * + * Returns: %TRUE if the task could be started. + * + * MT safe. + */ +gboolean +gst_task_start (GstTask * task) +{ + return gst_task_set_state (task, GST_TASK_STARTED); +} + +/** + * gst_task_stop: + * @task: The #GstTask to stop + * + * Stops @task. This method merely schedules the task to stop and + * will not wait for the task to have completely stopped. Use + * gst_task_join() to stop and wait for completion. + * + * Returns: %TRUE if the task could be stopped. + * + * MT safe. + */ +gboolean +gst_task_stop (GstTask * task) +{ + return gst_task_set_state (task, GST_TASK_STOPPED); +} + +/** + * gst_task_pause: + * @task: The #GstTask to pause + * + * Pauses @task. This method can also be called on a task in the + * stopped state, in which case a thread will be started and will remain + * in the paused state. This function does not wait for the task to complete + * the paused state. + * + * Returns: %TRUE if the task could be paused. + * + * MT safe. + */ +gboolean +gst_task_pause (GstTask * task) +{ + return gst_task_set_state (task, GST_TASK_PAUSED); +} + +/** + * gst_task_join: + * @task: The #GstTask to join + * + * Joins @task. After this call, it is safe to unref the task + * and clean up the lock set with gst_task_set_lock(). + * + * The task will automatically be stopped with this call. + * + * This function cannot be called from within a task function as this + * would cause a deadlock. The function will detect this and print a + * g_warning. + * + * Returns: %TRUE if the task could be joined. + * + * MT safe. + */ +gboolean +gst_task_join (GstTask * task) +{ + GThread *tself; + GstTaskPrivate *priv; + gpointer id; + GstTaskPool *pool = NULL; + + priv = task->priv; + + g_return_val_if_fail (GST_IS_TASK (task), FALSE); + + tself = g_thread_self (); + + GST_DEBUG_OBJECT (task, "Joining task %p, thread %p", task, tself); + + /* we don't use a real thread join here because we are using + * thread pools */ + GST_OBJECT_LOCK (task); + if (G_UNLIKELY (tself == task->thread)) + goto joining_self; + SET_TASK_STATE (task, GST_TASK_STOPPED); + /* signal the state change for when it was blocked in PAUSED. */ + GST_TASK_SIGNAL (task); + /* we set the running flag when pushing the task on the thread pool. + * This means that the task function might not be called when we try + * to join it here. */ + while (G_LIKELY (task->running)) + GST_TASK_WAIT (task); + /* clean the thread */ + task->thread = NULL; + /* get the id and pool to join */ + pool = priv->pool_id; + id = priv->id; + priv->pool_id = NULL; + priv->id = NULL; + GST_OBJECT_UNLOCK (task); + + if (pool) { + if (id) + gst_task_pool_join (pool, id); + gst_object_unref (pool); + } + + GST_DEBUG_OBJECT (task, "Joined task %p", task); + + return TRUE; + + /* ERRORS */ +joining_self: + { + GST_WARNING_OBJECT (task, "trying to join task from its thread"); + GST_OBJECT_UNLOCK (task); + g_warning ("\nTrying to join task %p from its thread would deadlock.\n" + "You cannot change the state of an element from its streaming\n" + "thread. Use g_idle_add() or post a GstMessage on the bus to\n" + "schedule the state change from the main thread.\n", task); + return FALSE; + } +} diff --git a/gst/gsttask.h b/gst/gsttask.h new file mode 100644 index 0000000..21784b7 --- /dev/null +++ b/gst/gsttask.h @@ -0,0 +1,201 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * <2005> Wim Taymans <wim@fluendo.com> + * + * gsttask.h: Streaming tasks + * + * 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_TASK_H__ +#define __GST_TASK_H__ + +#include <gst/gstobject.h> +#include <gst/gsttaskpool.h> + +G_BEGIN_DECLS + +/** + * GstTaskFunction: + * @data: user data passed to the function + * + * A function that will repeatedly be called in the thread created by + * a #GstTask. + */ +typedef void (*GstTaskFunction) (void *data); + +/* --- standard type macros --- */ +#define GST_TYPE_TASK (gst_task_get_type ()) +#define GST_TASK(task) (G_TYPE_CHECK_INSTANCE_CAST ((task), GST_TYPE_TASK, GstTask)) +#define GST_IS_TASK(task) (G_TYPE_CHECK_INSTANCE_TYPE ((task), GST_TYPE_TASK)) +#define GST_TASK_CLASS(tclass) (G_TYPE_CHECK_CLASS_CAST ((tclass), GST_TYPE_TASK, GstTaskClass)) +#define GST_IS_TASK_CLASS(tclass) (G_TYPE_CHECK_CLASS_TYPE ((tclass), GST_TYPE_TASK)) +#define GST_TASK_GET_CLASS(task) (G_TYPE_INSTANCE_GET_CLASS ((task), GST_TYPE_TASK, GstTaskClass)) +#define GST_TASK_CAST(task) ((GstTask*)(task)) + +typedef struct _GstTask GstTask; +typedef struct _GstTaskClass GstTaskClass; +typedef struct _GstTaskPrivate GstTaskPrivate; + +/** + * GstTaskState: + * @GST_TASK_STARTED: the task is started and running + * @GST_TASK_STOPPED: the task is stopped + * @GST_TASK_PAUSED: the task is paused + * + * The different states a task can be in + */ +typedef enum { + GST_TASK_STARTED, + GST_TASK_STOPPED, + GST_TASK_PAUSED +} GstTaskState; + +/** + * GST_TASK_STATE: + * @task: Task to get the state of + * + * Get access to the state of the task. + */ +#define GST_TASK_STATE(task) (GST_TASK_CAST(task)->state) + +/** + * GST_TASK_GET_COND: + * @task: Task to get the cond of + * + * Get access to the cond of the task. + */ +#define GST_TASK_GET_COND(task) (GST_TASK_CAST(task)->cond) +/** + * GST_TASK_WAIT: + * @task: Task to wait for + * + * Wait for the task cond to be signalled + */ +#define GST_TASK_WAIT(task) g_cond_wait(GST_TASK_GET_COND (task), GST_OBJECT_GET_LOCK (task)) +/** + * GST_TASK_SIGNAL: + * @task: Task to signal + * + * Signal the task cond + */ +#define GST_TASK_SIGNAL(task) g_cond_signal(GST_TASK_GET_COND (task)) +/** + * GST_TASK_BROADCAST: + * @task: Task to broadcast + * + * Send a broadcast signal to all waiting task conds + */ +#define GST_TASK_BROADCAST(task) g_cond_broadcast(GST_TASK_GET_COND (task)) + +/** + * GST_TASK_GET_LOCK: + * @task: Task to get the lock of + * + * Get access to the task lock. + */ +#define GST_TASK_GET_LOCK(task) (GST_TASK_CAST(task)->lock) + +/** + * GstTaskThreadCallbacks: + * @enter_thread: a thread is entered, this callback is called when the new + * thread enters its function. + * @leave_thread: a thread is exiting, this is called when the thread is about + * to leave its function + * + * Custom GstTask thread callback functions that can be installed. + * + * Since: 0.10.24 + */ +typedef struct { + /* manage the lifetime of the thread */ + void (*enter_thread) (GstTask *task, GThread *thread, gpointer user_data); + void (*leave_thread) (GstTask *task, GThread *thread, gpointer user_data); + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +} GstTaskThreadCallbacks; + +/** + * GstTask: + * @state: the state of the task + * @cond: used to pause/resume the task + * @lock: The lock taken when iterating the task function + * @func: the function executed by this task + * @data: data passed to the task function + * @running: a flag indicating that the task is running + * + * The #GstTask object. + */ +struct _GstTask { + GstObject object; + + /*< public >*/ /* with LOCK */ + GstTaskState state; + GCond *cond; + + GStaticRecMutex *lock; + + GstTaskFunction func; + gpointer data; + + gboolean running; + + /*< private >*/ + GThread *thread; + + GstTaskPrivate *priv; + + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstTaskClass { + GstObjectClass parent_class; + + /*< private >*/ + GstTaskPool *pool; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +void gst_task_cleanup_all (void); + +GType gst_task_get_type (void); + +GstTask* gst_task_create (GstTaskFunction func, gpointer data); +void gst_task_set_lock (GstTask *task, GStaticRecMutex *mutex); +void gst_task_set_priority (GstTask *task, GThreadPriority priority); + +GstTaskPool * gst_task_get_pool (GstTask *task); +void gst_task_set_pool (GstTask *task, GstTaskPool *pool); + +void gst_task_set_thread_callbacks (GstTask *task, + GstTaskThreadCallbacks *callbacks, + gpointer user_data, + GDestroyNotify notify); + +GstTaskState gst_task_get_state (GstTask *task); +gboolean gst_task_set_state (GstTask *task, GstTaskState state); + +gboolean gst_task_start (GstTask *task); +gboolean gst_task_stop (GstTask *task); +gboolean gst_task_pause (GstTask *task); + +gboolean gst_task_join (GstTask *task); + +G_END_DECLS + +#endif /* __GST_TASK_H__ */ diff --git a/gst/gsttaskpool.c b/gst/gsttaskpool.c new file mode 100644 index 0000000..d5981e6 --- /dev/null +++ b/gst/gsttaskpool.c @@ -0,0 +1,282 @@ +/* GStreamer + * Copyright (C) 2009 Wim Taymans <wim.taymans@gmail.com> + * + * gsttaskpool.c: Pool for streaming threads + * + * 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:gsttaskpool + * @short_description: Pool of GStreamer streaming threads + * @see_also: #GstTask, #GstPad + * + * This object provides an abstraction for creating threads. The default + * implementation uses a regular GThreadPool to start tasks. + * + * Subclasses can be made to create custom threads. + * + * Last reviewed on 2009-04-23 (0.10.24) + */ + +#include "gst_private.h" + +#include "gstinfo.h" +#include "gsttaskpool.h" + +GST_DEBUG_CATEGORY_STATIC (taskpool_debug); +#define GST_CAT_DEFAULT (taskpool_debug) + +#ifndef GST_DISABLE_GST_DEBUG +static void gst_task_pool_finalize (GObject * object); +#endif + +#define _do_init \ +{ \ + GST_DEBUG_CATEGORY_INIT (taskpool_debug, "taskpool", 0, "Thread pool"); \ +} + +G_DEFINE_TYPE_WITH_CODE (GstTaskPool, gst_task_pool, GST_TYPE_OBJECT, _do_init); + +typedef struct +{ + GstTaskPoolFunction func; + gpointer user_data; +} TaskData; + +static void +default_func (TaskData * tdata, GstTaskPool * pool) +{ + GstTaskPoolFunction func; + gpointer user_data; + + func = tdata->func; + user_data = tdata->user_data; + g_slice_free (TaskData, tdata); + + func (user_data); +} + +static void +default_prepare (GstTaskPool * pool, GError ** error) +{ + GST_OBJECT_LOCK (pool); + pool->pool = g_thread_pool_new ((GFunc) default_func, pool, -1, FALSE, NULL); + GST_OBJECT_UNLOCK (pool); +} + +static void +default_cleanup (GstTaskPool * pool) +{ + GST_OBJECT_LOCK (pool); + if (pool->pool) { + /* Shut down all the threads, we still process the ones scheduled + * because the unref happens in the thread function. + * Also wait for currently running ones to finish. */ + g_thread_pool_free (pool->pool, FALSE, TRUE); + pool->pool = NULL; + } + GST_OBJECT_UNLOCK (pool); +} + +static gpointer +default_push (GstTaskPool * pool, GstTaskPoolFunction func, + gpointer user_data, GError ** error) +{ + TaskData *tdata; + + tdata = g_slice_new (TaskData); + tdata->func = func; + tdata->user_data = user_data; + + GST_OBJECT_LOCK (pool); + if (pool->pool) + g_thread_pool_push (pool->pool, tdata, error); + else { + g_slice_free (TaskData, tdata); + } + GST_OBJECT_UNLOCK (pool); + + return NULL; +} + +static void +default_join (GstTaskPool * pool, gpointer id) +{ + /* we do nothing here, we can't join from the pools */ +} + +static void +gst_task_pool_class_init (GstTaskPoolClass * klass) +{ + GObjectClass *gobject_class; + GstTaskPoolClass *gsttaskpool_class; + + gobject_class = (GObjectClass *) klass; + gsttaskpool_class = (GstTaskPoolClass *) klass; + +#ifndef GST_DISABLE_GST_DEBUG + gobject_class->finalize = gst_task_pool_finalize; +#endif + + gsttaskpool_class->prepare = default_prepare; + gsttaskpool_class->cleanup = default_cleanup; + gsttaskpool_class->push = default_push; + gsttaskpool_class->join = default_join; +} + +static void +gst_task_pool_init (GstTaskPool * pool) +{ +} + +#ifndef GST_DISABLE_GST_DEBUG +static void +gst_task_pool_finalize (GObject * object) +{ + GST_DEBUG ("taskpool %p finalize", object); + + G_OBJECT_CLASS (gst_task_pool_parent_class)->finalize (object); +} +#endif +/** + * gst_task_pool_new: + * + * Create a new default task pool. The default task pool will use a regular + * GThreadPool for threads. + * + * Returns: (transfer full): a new #GstTaskPool. gst_object_unref() after usage. + * + * Since: 0.10.24 + */ +GstTaskPool * +gst_task_pool_new (void) +{ + GstTaskPool *pool; + + pool = g_object_newv (GST_TYPE_TASK_POOL, 0, NULL); + + return pool; +} + +/** + * gst_task_pool_prepare: + * @pool: a #GstTaskPool + * @error: an error return location + * + * Prepare the taskpool for accepting gst_task_pool_push() operations. + * + * MT safe. + * + * Since: 0.10.24 + */ +void +gst_task_pool_prepare (GstTaskPool * pool, GError ** error) +{ + GstTaskPoolClass *klass; + + g_return_if_fail (GST_IS_TASK_POOL (pool)); + + klass = GST_TASK_POOL_GET_CLASS (pool); + + if (klass->prepare) + klass->prepare (pool, error); +} + +/** + * gst_task_pool_cleanup: + * @pool: a #GstTaskPool + * + * Wait for all tasks to be stopped. This is mainly used internally + * to ensure proper cleanup of internal data structures in test suites. + * + * MT safe. + * + * Since: 0.10.24 + */ +void +gst_task_pool_cleanup (GstTaskPool * pool) +{ + GstTaskPoolClass *klass; + + g_return_if_fail (GST_IS_TASK_POOL (pool)); + + klass = GST_TASK_POOL_GET_CLASS (pool); + + if (klass->cleanup) + klass->cleanup (pool); +} + +/** + * gst_task_pool_push: + * @pool: a #GstTaskPool + * @func: the function to call + * @user_data: (closure): data to pass to @func + * @error: return location for an error + * + * Start the execution of a new thread from @pool. + * + * Returns: a pointer that should be used for the gst_task_pool_join + * function. This pointer can be NULL, you must check @error to detect + * errors. + * + * Since: 0.10.24 + */ +gpointer +gst_task_pool_push (GstTaskPool * pool, GstTaskPoolFunction func, + gpointer user_data, GError ** error) +{ + GstTaskPoolClass *klass; + + g_return_val_if_fail (GST_IS_TASK_POOL (pool), NULL); + + klass = GST_TASK_POOL_GET_CLASS (pool); + + if (klass->push == NULL) + goto not_supported; + + return klass->push (pool, func, user_data, error); + + /* ERRORS */ +not_supported: + { + g_warning ("pushing tasks on pool %p is not supported", pool); + return NULL; + } +} + +/** + * gst_task_pool_join: + * @pool: a #GstTaskPool + * @id: the id + * + * Join a task and/or return it to the pool. @id is the id obtained from + * gst_task_pool_push(). + * + * Since: 0.10.24 + */ +void +gst_task_pool_join (GstTaskPool * pool, gpointer id) +{ + GstTaskPoolClass *klass; + + g_return_if_fail (GST_IS_TASK_POOL (pool)); + + klass = GST_TASK_POOL_GET_CLASS (pool); + + if (klass->join) + klass->join (pool, id); +} diff --git a/gst/gsttaskpool.h b/gst/gsttaskpool.h new file mode 100644 index 0000000..912015e --- /dev/null +++ b/gst/gsttaskpool.h @@ -0,0 +1,103 @@ +/* GStreamer + * Copyright (C) <2009> Wim Taymans <wim.taymans@gmail.com> + * + * gsttaskpool.h: Pool for creating streaming threads + * + * 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_TASK_POOL_H__ +#define __GST_TASK_POOL_H__ + +#include <gst/gstobject.h> + +G_BEGIN_DECLS + +/* --- standard type macros --- */ +#define GST_TYPE_TASK_POOL (gst_task_pool_get_type ()) +#define GST_TASK_POOL(pool) (G_TYPE_CHECK_INSTANCE_CAST ((pool), GST_TYPE_TASK_POOL, GstTaskPool)) +#define GST_IS_TASK_POOL(pool) (G_TYPE_CHECK_INSTANCE_TYPE ((pool), GST_TYPE_TASK_POOL)) +#define GST_TASK_POOL_CLASS(pclass) (G_TYPE_CHECK_CLASS_CAST ((pclass), GST_TYPE_TASK_POOL, GstTaskPoolClass)) +#define GST_IS_TASK_POOL_CLASS(pclass) (G_TYPE_CHECK_CLASS_TYPE ((pclass), GST_TYPE_TASK_POOL)) +#define GST_TASK_POOL_GET_CLASS(pool) (G_TYPE_INSTANCE_GET_CLASS ((pool), GST_TYPE_TASK_POOL, GstTaskPoolClass)) +#define GST_TASK_POOL_CAST(pool) ((GstTaskPool*)(pool)) + +typedef struct _GstTaskPool GstTaskPool; +typedef struct _GstTaskPoolClass GstTaskPoolClass; + +/** + * GstTaskPoolFunction: + * @data: user data for the task function + * + * Task function, see gst_task_pool_push(). + * + * Since: 0.10.24 + */ +typedef void (*GstTaskPoolFunction) (void *data); + +/** + * GstTaskPool: + * + * The #GstTaskPool object. + */ +struct _GstTaskPool { + GstObject object; + + /*< private >*/ + GThreadPool *pool; + + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstTaskPoolClass: + * @parent_class: the parent class structure + * @prepare: prepare the threadpool + * @cleanup: make sure all threads are stopped + * @push: start a new thread + * @join: join a thread + * + * The #GstTaskPoolClass object. + */ +struct _GstTaskPoolClass { + GstObjectClass parent_class; + + /*< public >*/ + void (*prepare) (GstTaskPool *pool, GError **error); + void (*cleanup) (GstTaskPool *pool); + + gpointer (*push) (GstTaskPool *pool, GstTaskPoolFunction func, + gpointer user_data, GError **error); + void (*join) (GstTaskPool *pool, gpointer id); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_task_pool_get_type (void); + +GstTaskPool * gst_task_pool_new (void); +void gst_task_pool_prepare (GstTaskPool *pool, GError **error); + +gpointer gst_task_pool_push (GstTaskPool *pool, GstTaskPoolFunction func, + gpointer user_data, GError **error); +void gst_task_pool_join (GstTaskPool *pool, gpointer id); + +void gst_task_pool_cleanup (GstTaskPool *pool); + +G_END_DECLS + +#endif /* __GST_TASK_POOL_H__ */ diff --git a/gst/gsttrace.c b/gst/gsttrace.c new file mode 100644 index 0000000..b857458 --- /dev/null +++ b/gst/gsttrace.c @@ -0,0 +1,515 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gsttrace.c: Tracing functions (deprecated) + * + * 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:gsttrace + * @short_description: Tracing functionality + * + * Traces allows to track object allocation. They provide a instance counter per + * #GType. The counter is incremented for each object allocated and decremented + * it when it's freed. + * + * <example> + * <title>Tracing object instances</title> + * <programlisting> + * // trace un-freed object instances + * gst_alloc_trace_set_flags_all (GST_ALLOC_TRACE_LIVE); + * if (!gst_alloc_trace_available ()) { + * g_warning ("Trace not available (recompile with trace enabled)."); + * } + * gst_alloc_trace_print_live (); + * // do something here + * gst_alloc_trace_print_live (); + * </programlisting> + * </example> + * + * Last reviewed on 2005-11-21 (0.9.5) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> + +#if defined (_MSC_VER) && _MSC_VER >= 1400 +# include <io.h> +#endif + +#include "gst_private.h" +#include "gstinfo.h" + +#include "gsttrace.h" + +GStaticMutex _gst_trace_mutex = G_STATIC_MUTEX_INIT; + +static +#ifdef __inline__ + __inline__ +#endif + void +read_tsc (gint64 * dst) +{ +#if defined(HAVE_RDTSC) && defined(__GNUC__) + guint64 tsc; + __asm__ __volatile__ ("rdtsc":"=A" (tsc)); + + *dst = tsc; +#else + *dst = 0; +#endif +} + +/** + * gst_trace_read_tsc: + * @dst: (out) pointer to hold the result. + * + * Read a platform independent timer value that can be used in + * benchmarks. + */ +void +gst_trace_read_tsc (gint64 * dst) +{ + read_tsc (dst); +} + +static GstTrace *_gst_trace_default = NULL; +gint _gst_trace_on = 1; + +/** + * gst_trace_new: + * @filename: a filename + * @size: the max size of the file + * + * Create a ringbuffer of @size in the file with @filename to + * store trace results in. + * + * Free-function: gst_trace_destroy + * + * Returns: (transfer full): a new #GstTrace. + */ +GstTrace * +gst_trace_new (const gchar * filename, gint size) +{ + GstTrace *trace = g_slice_new (GstTrace); + + g_return_val_if_fail (trace != NULL, NULL); + trace->filename = g_strdup (filename); + GST_DEBUG ("opening '%s'", trace->filename); +#ifndef S_IWUSR +#define S_IWUSR S_IWRITE +#endif +#ifndef S_IRUSR +#define S_IRUSR S_IREAD +#endif + trace->fd = + open (trace->filename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + perror ("opening trace file"); + g_return_val_if_fail (trace->fd > 0, NULL); + trace->buf = g_malloc (size * sizeof (GstTraceEntry)); + g_return_val_if_fail (trace->buf != NULL, NULL); + trace->bufsize = size; + trace->bufoffset = 0; + + return trace; +} + +/** + * gst_trace_destroy: + * @trace: (in) (transfer full): the #GstTrace to destroy + * + * Flush an close the previously allocated @trace. + */ +void +gst_trace_destroy (GstTrace * trace) +{ + g_return_if_fail (trace != NULL); + g_return_if_fail (trace->buf != NULL); + + if (gst_trace_get_remaining (trace) > 0) + gst_trace_flush (trace); + close (trace->fd); + g_free (trace->buf); + g_slice_free (GstTrace, trace); +} + +/** + * gst_trace_flush: + * @trace: the #GstTrace to flush. + * + * Flush any pending trace entries in @trace to the trace file. + * @trace can be NULL in which case the default #GstTrace will be + * flushed. + */ +void +gst_trace_flush (GstTrace * trace) +{ + int res, buf_len; + + if (!trace) { + trace = _gst_trace_default; + if (!trace) + return; + } + + buf_len = trace->bufoffset * sizeof (GstTraceEntry); + res = write (trace->fd, trace->buf, buf_len); + if (res < 0) { + g_warning ("Failed to write trace: %s", g_strerror (errno)); + return; + } else if (res < buf_len) { + g_warning ("Failed to write trace: only wrote %d/%d bytes", res, buf_len); + return; + } + trace->bufoffset = 0; +} + +/** + * gst_trace_text_flush: + * @trace: the #GstTrace to flush. + * + * Flush any pending trace entries in @trace to the trace file, + * formatted as a text line with timestamp and sequence numbers. + * @trace can be NULL in which case the default #GstTrace will be + * flushed. + */ +void +gst_trace_text_flush (GstTrace * trace) +{ + int i; + +#define STRSIZE (20 + 1 + 10 + 1 + 10 + 1 + 112 + 1 + 1) + char str[STRSIZE]; + + if (!trace) { + trace = _gst_trace_default; + if (!trace) + return; + } + + for (i = 0; i < trace->bufoffset; i++) { + g_snprintf (str, STRSIZE, "%20" G_GINT64_FORMAT " %10d %10d %s\n", + trace->buf[i].timestamp, + trace->buf[i].sequence, trace->buf[i].data, trace->buf[i].message); + if (write (trace->fd, str, strlen (str)) < 0) { + g_warning ("Failed to write trace %d: %s", i, g_strerror (errno)); + return; + } + } + trace->bufoffset = 0; +#undef STRSIZE +} + +/** + * gst_trace_set_default: + * @trace: the #GstTrace to set as the default. + * + * Set the default #GstTrace to @trace. + */ +void +gst_trace_set_default (GstTrace * trace) +{ + g_return_if_fail (trace != NULL); + _gst_trace_default = trace; +} + +void +_gst_trace_add_entry (GstTrace * trace, guint32 seq, guint32 data, gchar * msg) +{ + GstTraceEntry *entry; + + if (!trace) { + trace = _gst_trace_default; + if (!trace) + return; + } + + entry = trace->buf + trace->bufoffset; + read_tsc (&(entry->timestamp)); + entry->sequence = seq; + entry->data = data; + strncpy (entry->message, msg, 112); + entry->message[111] = '\0'; + trace->bufoffset++; + + gst_trace_flush (trace); +} + + +/* global flags */ +static GstAllocTraceFlags _gst_trace_flags = GST_ALLOC_TRACE_NONE; + +/* list of registered tracers */ +static GList *_gst_alloc_tracers = NULL; + +/** + * gst_alloc_trace_available: + * + * Check if alloc tracing was compiled into the core + * + * Returns: TRUE if the core was compiled with alloc + * tracing enabled. + */ +gboolean +gst_alloc_trace_available (void) +{ +#ifdef GST_DISABLE_ALLOC_TRACE + return FALSE; +#else + return TRUE; +#endif +} + +/** + * _gst_alloc_trace_register: + * @name: the name of the new alloc trace object. + * + * Register an get a handle to a GstAllocTrace object that + * can be used to trace memory allocations. + * + * Returns: A handle to a GstAllocTrace. + */ +GstAllocTrace * +_gst_alloc_trace_register (const gchar * name) +{ + GstAllocTrace *trace; + + g_return_val_if_fail (name, NULL); + + trace = g_slice_new (GstAllocTrace); + trace->name = g_strdup (name); + trace->live = 0; + trace->mem_live = NULL; + trace->flags = _gst_trace_flags; + + _gst_alloc_tracers = g_list_prepend (_gst_alloc_tracers, trace); + + return trace; +} + +/** + * gst_alloc_trace_list: + * + * Get a list of all registered alloc trace objects. + * + * Returns: a GList of GstAllocTrace objects. + */ +const GList * +gst_alloc_trace_list (void) +{ + return _gst_alloc_tracers; +} + +/** + * gst_alloc_trace_live_all: + * + * Get the total number of live registered alloc trace objects. + * + * Returns: the total number of live registered alloc trace objects. + */ +int +gst_alloc_trace_live_all (void) +{ + GList *walk = _gst_alloc_tracers; + int num = 0; + + while (walk) { + GstAllocTrace *trace = (GstAllocTrace *) walk->data; + + num += trace->live; + + walk = g_list_next (walk); + } + + return num; +} + +static gint +compare_func (GstAllocTrace * a, GstAllocTrace * b) +{ + return strcmp (a->name, b->name); +} + +static GList * +gst_alloc_trace_list_sorted (void) +{ + GList *ret; + + ret = g_list_sort (g_list_copy (_gst_alloc_tracers), + (GCompareFunc) compare_func); + + return ret; +} + +/** + * gst_alloc_trace_print_all: + * + * Print the status of all registered alloc trace objects. + */ +void +gst_alloc_trace_print_all (void) +{ + GList *orig, *walk; + + orig = walk = gst_alloc_trace_list_sorted (); + + while (walk) { + GstAllocTrace *trace = (GstAllocTrace *) walk->data; + + gst_alloc_trace_print (trace); + + walk = g_list_next (walk); + } + + g_list_free (orig); +} + +/** + * gst_alloc_trace_print_live: + * + * Print the status of all registered alloc trace objects, ignoring those + * without live objects. + */ +void +gst_alloc_trace_print_live (void) +{ + GList *orig, *walk; + + orig = walk = gst_alloc_trace_list_sorted (); + + while (walk) { + GstAllocTrace *trace = (GstAllocTrace *) walk->data; + + if (trace->live) + gst_alloc_trace_print (trace); + + walk = g_list_next (walk); + } + + g_list_free (orig); +} + +/** + * gst_alloc_trace_set_flags_all: + * @flags: the options to enable + * + * Enable the specified options on all registered alloc trace + * objects. + */ +void +gst_alloc_trace_set_flags_all (GstAllocTraceFlags flags) +{ + GList *walk = _gst_alloc_tracers; + + while (walk) { + GstAllocTrace *trace = (GstAllocTrace *) walk->data; + + GST_DEBUG ("setting flags %d on %p", (gint) flags, trace); + gst_alloc_trace_set_flags (trace, flags); + + walk = g_list_next (walk); + } + _gst_trace_flags = flags; +} + +/** + * gst_alloc_trace_get: + * @name: the name of the alloc trace object + * + * Get the named alloc trace object. + * + * Returns: a GstAllocTrace with the given name or NULL when + * no alloc tracer was registered with that name. + */ +GstAllocTrace * +gst_alloc_trace_get (const gchar * name) +{ + GList *walk = _gst_alloc_tracers; + + g_return_val_if_fail (name, NULL); + + while (walk) { + GstAllocTrace *trace = (GstAllocTrace *) walk->data; + + if (!strcmp (trace->name, name)) + return trace; + + walk = g_list_next (walk); + } + return NULL; +} + +/** + * gst_alloc_trace_print: + * @trace: the GstAllocTrace to print + * + * Print the status of the given GstAllocTrace. + */ +void +gst_alloc_trace_print (const GstAllocTrace * trace) +{ + GSList *mem_live; + + g_return_if_fail (trace != NULL); + + if (trace->flags & GST_ALLOC_TRACE_LIVE) { + g_print ("%-22.22s : %d\n", trace->name, trace->live); + } else { + g_print ("%-22.22s : (no live count)\n", trace->name); + } + + if (trace->flags & GST_ALLOC_TRACE_MEM_LIVE) { + mem_live = trace->mem_live; + + while (mem_live) { + gpointer data = mem_live->data; + + if (G_IS_OBJECT (data)) { + g_print ("%-22.22s : %p\n", g_type_name (G_OBJECT_TYPE (data)), data); + } else { + g_print ("%-22.22s : %p\n", "", data); + } + mem_live = mem_live->next; + } + } +} + +/** + * gst_alloc_trace_set_flags: + * @trace: the GstAllocTrace + * @flags: flags to set + * + * Enable the given features on the given GstAllocTrace object. + */ +void +gst_alloc_trace_set_flags (GstAllocTrace * trace, GstAllocTraceFlags flags) +{ + g_return_if_fail (trace != NULL); + + trace->flags = flags; +} diff --git a/gst/gsttrace.h b/gst/gsttrace.h new file mode 100644 index 0000000..817ae1f --- /dev/null +++ b/gst/gsttrace.h @@ -0,0 +1,253 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gsttrace.h: Header for tracing functions (deprecated) + * + * 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_TRACE_H__ +#define __GST_TRACE_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +/** + * GstAllocTraceFlags: + * @GST_ALLOC_TRACE_NONE: No tracing specified or desired. Since 0.10.36. + * @GST_ALLOC_TRACE_LIVE: Trace number of non-freed memory. + * @GST_ALLOC_TRACE_MEM_LIVE: Trace pointers of unfreed memory. + * + * Flags indicating which tracing feature to enable. + */ +typedef enum { + GST_ALLOC_TRACE_NONE = 0, + GST_ALLOC_TRACE_LIVE = (1 << 0), + GST_ALLOC_TRACE_MEM_LIVE = (1 << 1) +} GstAllocTraceFlags; + +typedef struct _GstAllocTrace GstAllocTrace; + +/** + * GstAllocTrace: + * @name: The name of the tracing object + * @flags: Flags for this object + * @live: counter for live memory + * @mem_live: list with pointers to unfreed memory + * + * The main tracing object + */ +struct _GstAllocTrace { + gchar *name; + gint flags; + + gint live; + GSList *mem_live; +}; + +#ifndef GST_DISABLE_TRACE + +typedef struct _GstTrace GstTrace; +typedef struct _GstTraceEntry GstTraceEntry; + +/** + * GstTrace: + * + * Opaque #GstTrace structure. + */ +struct _GstTrace { + /*< private >*/ + /* where this trace is going */ + gchar *filename; + int fd; + + /* current buffer, size, head offset */ + GstTraceEntry *buf; + gint bufsize; + gint bufoffset; +}; + +struct _GstTraceEntry { + gint64 timestamp; + guint32 sequence; + guint32 data; + gchar message[112]; +}; + +GstTrace* gst_trace_new (const gchar *filename, gint size); + +void gst_trace_destroy (GstTrace *trace); +void gst_trace_flush (GstTrace *trace); +void gst_trace_text_flush (GstTrace *trace); +/** + * gst_trace_get_size: + * @trace: a #GstTrace + * + * Retrieve the buffer size of @trace. + */ +#define gst_trace_get_size(trace) ((trace)->bufsize) +/** + * gst_trace_get_offset: + * @trace: a #GstTrace + * + * Retrieve the current buffer offset of @trace. + */ +#define gst_trace_get_offset(trace) ((trace)->bufoffset) +/** + * gst_trace_get_remaining: + * @trace: a #GstTrace + * + * Retrieve the remaining size in the @trace buffer. + */ +#define gst_trace_get_remaining(trace) ((trace)->bufsize - (trace)->bufoffset) +void gst_trace_set_default (GstTrace *trace); + +void _gst_trace_add_entry (GstTrace *trace, guint32 seq, + guint32 data, gchar *msg); + +void gst_trace_read_tsc (gint64 *dst); + + +extern GStaticMutex _gst_trace_mutex; + +gboolean gst_alloc_trace_available (void); +const GList* gst_alloc_trace_list (void); +GstAllocTrace* _gst_alloc_trace_register (const gchar *name); + +int gst_alloc_trace_live_all (void); +void gst_alloc_trace_print_all (void); +void gst_alloc_trace_print_live (void); +void gst_alloc_trace_set_flags_all (GstAllocTraceFlags flags); + +GstAllocTrace* gst_alloc_trace_get (const gchar *name); +void gst_alloc_trace_print (const GstAllocTrace *trace); +void gst_alloc_trace_set_flags (GstAllocTrace *trace, GstAllocTraceFlags flags); + + +#ifndef GST_DISABLE_ALLOC_TRACE +/** + * gst_alloc_trace_register: + * @name: The name of the tracer object + * + * Register a new alloc tracer with the given name + */ +#define gst_alloc_trace_register(name) _gst_alloc_trace_register (name); + +/** + * gst_alloc_trace_new: + * @trace: The tracer to use + * @mem: The memory allocated + * + * Use the tracer to trace a new memory allocation + */ +#define gst_alloc_trace_new(trace, mem) \ +G_STMT_START { \ + if (G_UNLIKELY ((trace)->flags)) { \ + g_static_mutex_lock (&_gst_trace_mutex); \ + if ((trace)->flags & GST_ALLOC_TRACE_LIVE) \ + (trace)->live++; \ + if ((trace)->flags & GST_ALLOC_TRACE_MEM_LIVE) \ + (trace)->mem_live = \ + g_slist_prepend ((trace)->mem_live, mem); \ + g_static_mutex_unlock (&_gst_trace_mutex); \ + } \ +} G_STMT_END + +/** + * gst_alloc_trace_free: + * @trace: The tracer to use + * @mem: The memory that is freed + * + * Trace a memory free operation + */ +#define gst_alloc_trace_free(trace, mem) \ +G_STMT_START { \ + if (G_UNLIKELY ((trace)->flags)) { \ + g_static_mutex_lock (&_gst_trace_mutex); \ + if ((trace)->flags & GST_ALLOC_TRACE_LIVE) \ + (trace)->live--; \ + if ((trace)->flags & GST_ALLOC_TRACE_MEM_LIVE) \ + (trace)->mem_live = \ + g_slist_remove ((trace)->mem_live, mem); \ + g_static_mutex_unlock (&_gst_trace_mutex); \ + } \ +} G_STMT_END + +#else +#define gst_alloc_trace_register(name) (NULL) +#define gst_alloc_trace_new(trace, mem) +#define gst_alloc_trace_free(trace, mem) +#endif + + +extern gint _gst_trace_on; +/** + * gst_trace_add_entry: + * @trace: a #GstTrace + * @seq: a sequence number + * @data: the data to trace + * @msg: the trace message + * + * Add an entry to @trace with sequence number @seq, @data and @msg. + * If @trace is NULL, the entry will be added to the default #GstTrace. + */ +#define gst_trace_add_entry(trace,seq,data,msg) \ + if (_gst_trace_on) { \ + _gst_trace_add_entry(trace,(guint32)seq,(guint32)data,msg); \ + } + +#else /* GST_DISABLE_TRACE */ + +#if defined __GNUC__ && __GNUC__ >= 3 +#pragma GCC poison gst_trace_new +#pragma GCC poison gst_trace_destroy +#pragma GCC poison gst_trace_flush +#pragma GCC poison gst_trace_text_flush +#pragma GCC poison gst_trace_get_size +#pragma GCC poison gst_trace_get_offset +#pragma GCC poison gst_trace_get_remaining +#pragma GCC poison gst_trace_set_default +#pragma GCC poison _gst_trace_add_entry +#pragma GCC poison gst_trace_read_tsc +#endif + +#define gst_alloc_trace_register(name) (NULL) +#define gst_alloc_trace_new(trace, mem) +#define gst_alloc_trace_free(trace, mem) + +#define gst_alloc_trace_available() (FALSE) +#define gst_alloc_trace_list() (NULL) +#define _gst_alloc_trace_register(name) (NULL) + +#define gst_alloc_trace_live_all() (0) +#define gst_alloc_trace_print_all() +#define gst_alloc_trace_print_live() +#define gst_alloc_trace_set_flags_all(flags) + +#define gst_alloc_trace_get(name) (NULL) +#define gst_alloc_trace_print(trace) +#define gst_alloc_trace_set_flags(trace,flags) + +#define gst_trace_add_entry(trace,seq,data,msg) + +#endif /* GST_DISABLE_TRACE */ + +G_END_DECLS + +#endif /* __GST_TRACE_H__ */ diff --git a/gst/gsttypefind.c b/gst/gsttypefind.c new file mode 100644 index 0000000..1f0b16d --- /dev/null +++ b/gst/gsttypefind.c @@ -0,0 +1,231 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gsttypefind.c: typefinding subsystem + * + * 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:gsttypefind + * @short_description: Stream type detection + * + * The following functions allow you to detect the media type of an unknown + * stream. + * + * Last reviewed on 2005-11-09 (0.9.4) + */ + +#include "gst_private.h" +#include "gstinfo.h" +#include "gsttypefind.h" +#include "gstregistry.h" +#include "gsttypefindfactory.h" + +GST_DEBUG_CATEGORY_EXTERN (type_find_debug); +#define GST_CAT_DEFAULT type_find_debug + +GType +gst_type_find_get_type (void) +{ + static GType typefind_type = 0; + + if (G_UNLIKELY (typefind_type == 0)) { + typefind_type = g_pointer_type_register_static ("GstTypeFind"); + } + return typefind_type; +} + +/** + * gst_type_find_register: + * @plugin: A #GstPlugin, or NULL for a static typefind function (note that + * passing NULL only works in GStreamer 0.10.16 and later) + * @name: The name for registering + * @rank: The rank (or importance) of this typefind function + * @func: The #GstTypeFindFunction to use + * @extensions: (transfer none) (array zero-terminated=1) (element-type utf8): + * Optional extensions that could belong to this type + * @possible_caps: Optionally the caps that could be returned when typefinding + * succeeds + * @data: Optional user data. This user data must be available until the plugin + * is unloaded. + * @data_notify: a #GDestroyNotify that will be called on @data when the plugin + * is unloaded. + * + * Registers a new typefind function to be used for typefinding. After + * registering this function will be available for typefinding. + * This function is typically called during an element's plugin initialization. + * + * Returns: TRUE on success, FALSE otherwise + */ +gboolean +gst_type_find_register (GstPlugin * plugin, const gchar * name, guint rank, + GstTypeFindFunction func, gchar ** extensions, + const GstCaps * possible_caps, gpointer data, GDestroyNotify data_notify) +{ + GstTypeFindFactory *factory; + + g_return_val_if_fail (name != NULL, FALSE); + + GST_INFO ("registering typefind function for %s", name); + + factory = g_object_newv (GST_TYPE_TYPE_FIND_FACTORY, 0, NULL); + GST_DEBUG_OBJECT (factory, "using new typefind factory for %s", name); + g_assert (GST_IS_TYPE_FIND_FACTORY (factory)); + + gst_plugin_feature_set_name (GST_PLUGIN_FEATURE_CAST (factory), name); + gst_plugin_feature_set_rank (GST_PLUGIN_FEATURE_CAST (factory), rank); + + if (factory->extensions) + g_strfreev (factory->extensions); + factory->extensions = g_strdupv (extensions); + + gst_caps_replace (&factory->caps, (GstCaps *) possible_caps); + factory->function = func; + factory->user_data = data; + factory->user_data_notify = data_notify; + if (plugin && plugin->desc.name) { + GST_PLUGIN_FEATURE_CAST (factory)->plugin_name = plugin->desc.name; /* interned string */ + GST_PLUGIN_FEATURE_CAST (factory)->plugin = plugin; + g_object_add_weak_pointer ((GObject *) plugin, + (gpointer *) & GST_PLUGIN_FEATURE_CAST (factory)->plugin); + } else { + GST_PLUGIN_FEATURE_CAST (factory)->plugin_name = "NULL"; + GST_PLUGIN_FEATURE_CAST (factory)->plugin = NULL; + } + GST_PLUGIN_FEATURE_CAST (factory)->loaded = TRUE; + + gst_registry_add_feature (gst_registry_get_default (), + GST_PLUGIN_FEATURE_CAST (factory)); + + return TRUE; +} + +/*** typefind function interface **********************************************/ + +/** + * gst_type_find_peek: + * @find: The #GstTypeFind object the function was called with + * @offset: The offset + * @size: The number of bytes to return + * + * Returns the @size bytes of the stream to identify beginning at offset. If + * offset is a positive number, the offset is relative to the beginning of the + * stream, if offset is a negative number the offset is relative to the end of + * the stream. The returned memory is valid until the typefinding function + * returns and must not be freed. + * + * Returns: (transfer none) (array length=size): the requested data, or NULL + * if that data is not available. + */ +const guint8 * +gst_type_find_peek (GstTypeFind * find, gint64 offset, guint size) +{ + g_return_val_if_fail (find->peek != NULL, NULL); + + return find->peek (find->data, offset, size); +} + +/** + * gst_type_find_suggest: + * @find: The #GstTypeFind object the function was called with + * @probability: The probability in percent that the suggestion is right + * @caps: The fixed #GstCaps to suggest + * + * If a #GstTypeFindFunction calls this function it suggests the caps with the + * given probability. A #GstTypeFindFunction may supply different suggestions + * in one call. + * It is up to the caller of the #GstTypeFindFunction to interpret these values. + */ +void +gst_type_find_suggest (GstTypeFind * find, guint probability, + const GstCaps * caps) +{ + g_return_if_fail (find->suggest != NULL); + g_return_if_fail (probability <= 100); + g_return_if_fail (caps != NULL); + g_return_if_fail (gst_caps_is_fixed (caps)); + + find->suggest (find->data, probability, caps); +} + +/** + * gst_type_find_suggest_simple: + * @find: The #GstTypeFind object the function was called with + * @probability: The probability in percent that the suggestion is right + * @media_type: the media type of the suggested caps + * @fieldname: first field of the suggested caps, or NULL + * @...: additional arguments to the suggested caps in the same format as the + * arguments passed to gst_structure_new() (ie. triplets of field name, + * field GType and field value) + * + * If a #GstTypeFindFunction calls this function it suggests the caps with the + * given probability. A #GstTypeFindFunction may supply different suggestions + * in one call. It is up to the caller of the #GstTypeFindFunction to interpret + * these values. + * + * This function is similar to gst_type_find_suggest(), only that instead of + * passing a #GstCaps argument you can create the caps on the fly in the same + * way as you can with gst_caps_new_simple(). + * + * Make sure you terminate the list of arguments with a NULL argument and that + * the values passed have the correct type (in terms of width in bytes when + * passed to the vararg function - this applies particularly to gdouble and + * guint64 arguments). + * + * Since: 0.10.20 + */ +void +gst_type_find_suggest_simple (GstTypeFind * find, guint probability, + const char *media_type, const char *fieldname, ...) +{ + GstStructure *structure; + va_list var_args; + GstCaps *caps; + + g_return_if_fail (find->suggest != NULL); + g_return_if_fail (probability <= 100); + g_return_if_fail (media_type != NULL); + + caps = gst_caps_new_empty (); + + va_start (var_args, fieldname); + structure = gst_structure_new_valist (media_type, fieldname, var_args); + va_end (var_args); + + gst_caps_append_structure (caps, structure); + g_return_if_fail (gst_caps_is_fixed (caps)); + + find->suggest (find->data, probability, caps); + gst_caps_unref (caps); +} + +/** + * gst_type_find_get_length: + * @find: The #GstTypeFind the function was called with + * + * Get the length of the data stream. + * + * Returns: The length of the data stream, or 0 if it is not available. + */ +guint64 +gst_type_find_get_length (GstTypeFind * find) +{ + if (find->get_length == NULL) + return 0; + + return find->get_length (find->data); +} diff --git a/gst/gsttypefind.h b/gst/gsttypefind.h new file mode 100644 index 0000000..27c7e32 --- /dev/null +++ b/gst/gsttypefind.h @@ -0,0 +1,124 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gsttypefind.h: typefinding subsystem + * + * 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_TYPE_FIND_H__ +#define __GST_TYPE_FIND_H__ + +#include <gst/gstcaps.h> +#include <gst/gstplugin.h> +#include <gst/gstpluginfeature.h> + +G_BEGIN_DECLS + +#define GST_TYPE_TYPE_FIND (gst_type_find_get_type()) + +typedef struct _GstTypeFind GstTypeFind; + +/** + * GstTypeFindFunction: + * @find: A #GstTypeFind structure + * @data: optionnal data to pass to the function + * + * A function that will be called by typefinding. + */ +typedef void (* GstTypeFindFunction) (GstTypeFind *find, gpointer data); + +/** + * GstTypeFindProbability: + * @GST_TYPE_FIND_NONE: type undetected. Since 0.10.36. + * @GST_TYPE_FIND_MINIMUM: unlikely typefind. + * @GST_TYPE_FIND_POSSIBLE: possible type detected. + * @GST_TYPE_FIND_LIKELY: likely a type was detected. + * @GST_TYPE_FIND_NEARLY_CERTAIN: nearly certain that a type was detected. + * @GST_TYPE_FIND_MAXIMUM: very certain a type was detected. + * + * The probability of the typefind function. Higher values have more certainty + * in doing a reliable typefind. + */ +typedef enum { + GST_TYPE_FIND_NONE = 0, + GST_TYPE_FIND_MINIMUM = 1, + GST_TYPE_FIND_POSSIBLE = 50, + GST_TYPE_FIND_LIKELY = 80, + GST_TYPE_FIND_NEARLY_CERTAIN = 99, + GST_TYPE_FIND_MAXIMUM = 100 +} GstTypeFindProbability; + +/** + * GstTypeFind: + * @peek: Method to peek data. + * @suggest: Method to suggest #GstCaps with a given probability. + * @data: The data used by the caller of the typefinding function. + * @get_length: Returns the length of current data. + * + * Object that stores typefind callbacks. To use with #GstTypeFindFactory. + */ +struct _GstTypeFind { + /* private to the caller of the typefind function */ + const guint8 * (* peek) (gpointer data, + gint64 offset, + guint size); + + void (* suggest) (gpointer data, + guint probability, + const GstCaps * caps); + + gpointer data; + + /* optional */ + guint64 (* get_length) (gpointer data); + + /* <private> */ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_type_find_get_type (void); + +/* typefind function interface */ +const guint8 * gst_type_find_peek (GstTypeFind * find, + gint64 offset, + guint size); + +void gst_type_find_suggest (GstTypeFind * find, + guint probability, + const GstCaps * caps); + +void gst_type_find_suggest_simple (GstTypeFind * find, + guint probability, + const char * media_type, + const char * fieldname, ...); + +guint64 gst_type_find_get_length (GstTypeFind * find); + +/* registration interface */ +gboolean gst_type_find_register (GstPlugin * plugin, + const gchar * name, + guint rank, + GstTypeFindFunction func, + gchar ** extensions, + const GstCaps * possible_caps, + gpointer data, + GDestroyNotify data_notify); + +G_END_DECLS + +#endif /* __GST_TYPE_FIND_H__ */ diff --git a/gst/gsttypefindfactory.c b/gst/gsttypefindfactory.c new file mode 100644 index 0000000..26cbfb5 --- /dev/null +++ b/gst/gsttypefindfactory.c @@ -0,0 +1,227 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gsttypefindfactory.c: typefinding subsystem + * + * 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:gsttypefindfactory + * @short_description: Information about registered typefind functions + * + * These functions allow querying informations about registered typefind + * functions. How to create and register these functions is described in + * the section <link linkend="gstreamer-Writing-typefind-functions"> + * "Writing typefind functions"</link>. + * + * <example> + * <title>how to write a simple typefinder</title> + * <programlisting> + * typedef struct { + * guint8 *data; + * guint size; + * guint probability; + * GstCaps *data; + * } MyTypeFind; + * static void + * my_peek (gpointer data, gint64 offset, guint size) + * { + * MyTypeFind *find = (MyTypeFind *) data; + * if (offset >= 0 && offset + size <= find->size) { + * return find->data + offset; + * } + * return NULL; + * } + * static void + * my_suggest (gpointer data, guint probability, GstCaps *caps) + * { + * MyTypeFind *find = (MyTypeFind *) data; + * if (probability > find->probability) { + * find->probability = probability; + * gst_caps_replace (&find->caps, caps); + * } + * } + * static GstCaps * + * find_type (guint8 *data, guint size) + * { + * GList *walk, *type_list; + * MyTypeFind find = {data, size, 0, NULL}; + * GstTypeFind gst_find = {my_peek, my_suggest, &find, }; + * walk = type_list = gst_type_find_factory_get_list (); + * while (walk) { + * GstTypeFindFactory *factory = GST_TYPE_FIND_FACTORY (walk->data); + * walk = g_list_next (walk) + * gst_type_find_factory_call_function (factory, &gst_find); + * } + * g_list_free (type_list); + * return find.caps; + * }; + * </programlisting> + * </example> + * + * The above example shows how to write a very simple typefinder that + * identifies the given data. You can get quite a bit more complicated than + * that though. + * + * Last reviewed on 2005-11-09 (0.9.4) + */ + +#include "gst_private.h" +#include "gstinfo.h" +#include "gsttypefind.h" +#include "gsttypefindfactory.h" +#include "gstregistry.h" + +GST_DEBUG_CATEGORY (type_find_debug); +#define GST_CAT_DEFAULT type_find_debug + +static void gst_type_find_factory_dispose (GObject * object); + +static GstPluginFeatureClass *parent_class = NULL; + +#define _do_init \ +{ \ + GST_DEBUG_CATEGORY_INIT (type_find_debug, "GST_TYPEFIND", \ + GST_DEBUG_FG_GREEN, "typefinding subsystem"); \ +} + +G_DEFINE_TYPE_WITH_CODE (GstTypeFindFactory, gst_type_find_factory, + GST_TYPE_PLUGIN_FEATURE, _do_init); + +static void +gst_type_find_factory_class_init (GstTypeFindFactoryClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + object_class->dispose = gst_type_find_factory_dispose; +} + +static void +gst_type_find_factory_init (GstTypeFindFactory * factory) +{ + factory->user_data = factory; + factory->user_data_notify = NULL; +} + +static void +gst_type_find_factory_dispose (GObject * object) +{ + GstTypeFindFactory *factory = GST_TYPE_FIND_FACTORY (object); + + if (factory->caps) { + gst_caps_unref (factory->caps); + factory->caps = NULL; + } + if (factory->extensions) { + g_strfreev (factory->extensions); + factory->extensions = NULL; + } + if (factory->user_data_notify && factory->user_data) { + factory->user_data_notify (factory->user_data); + factory->user_data = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/** + * gst_type_find_factory_get_list: + * + * Gets the list of all registered typefind factories. You must free the + * list using gst_plugin_feature_list_free(). + * + * The returned factories are sorted by highest rank first, and then by + * factory name. (behaviour change since 0.10.26) + * + * Free-function: gst_plugin_feature_list_free + * + * Returns: (transfer full) (element-type Gst.TypeFindFactory): the list of all + * registered #GstTypeFindFactory. + */ +GList * +gst_type_find_factory_get_list (void) +{ + return gst_registry_get_feature_list (gst_registry_get_default (), + GST_TYPE_TYPE_FIND_FACTORY); +} + +/** + * gst_type_find_factory_get_caps: + * @factory: A #GstTypeFindFactory + * + * Gets the #GstCaps associated with a typefind factory. + * + * Returns: (transfer none): the #GstCaps associated with this factory + */ +GstCaps * +gst_type_find_factory_get_caps (GstTypeFindFactory * factory) +{ + g_return_val_if_fail (GST_IS_TYPE_FIND_FACTORY (factory), NULL); + + return factory->caps; +} + +/** + * gst_type_find_factory_get_extensions: + * @factory: A #GstTypeFindFactory + * + * Gets the extensions associated with a #GstTypeFindFactory. The returned + * array should not be changed. If you need to change stuff in it, you should + * copy it using g_strdupv(). This function may return NULL to indicate + * a 0-length list. + * + * Returns: (transfer none) (array zero-terminated=1) (element-type utf8): a + * NULL-terminated array of extensions associated with this factory + */ +gchar ** +gst_type_find_factory_get_extensions (GstTypeFindFactory * factory) +{ + g_return_val_if_fail (GST_IS_TYPE_FIND_FACTORY (factory), NULL); + + return factory->extensions; +} + +/** + * gst_type_find_factory_call_function: + * @factory: A #GstTypeFindFactory + * @find: (transfer none): a properly setup #GstTypeFind entry. The get_data + * and suggest_type members must be set. + * + * Calls the #GstTypeFindFunction associated with this factory. + */ +void +gst_type_find_factory_call_function (GstTypeFindFactory * factory, + GstTypeFind * find) +{ + GstTypeFindFactory *new_factory; + + g_return_if_fail (GST_IS_TYPE_FIND_FACTORY (factory)); + g_return_if_fail (find != NULL); + g_return_if_fail (find->peek != NULL); + g_return_if_fail (find->suggest != NULL); + + new_factory = + GST_TYPE_FIND_FACTORY (gst_plugin_feature_load (GST_PLUGIN_FEATURE + (factory))); + if (new_factory) { + if (new_factory->function) + new_factory->function (find, new_factory->user_data); + gst_object_unref (new_factory); + } +} diff --git a/gst/gsttypefindfactory.h b/gst/gsttypefindfactory.h new file mode 100644 index 0000000..19792ee --- /dev/null +++ b/gst/gsttypefindfactory.h @@ -0,0 +1,81 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * + * gsttypefindfactory.h: typefinding subsystem + * + * 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_TYPE_FIND_FACTORY_H__ +#define __GST_TYPE_FIND_FACTORY_H__ + +#include <gst/gstcaps.h> +#include <gst/gstplugin.h> +#include <gst/gstpluginfeature.h> +#include <gst/gsttypefind.h> + +G_BEGIN_DECLS + +#define GST_TYPE_TYPE_FIND_FACTORY (gst_type_find_factory_get_type()) +#define GST_TYPE_FIND_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TYPE_FIND_FACTORY, GstTypeFindFactory)) +#define GST_IS_TYPE_FIND_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TYPE_FIND_FACTORY)) +#define GST_TYPE_FIND_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_TYPE_FIND_FACTORY, GstTypeFindFactoryClass)) +#define GST_IS_TYPE_FIND_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_TYPE_FIND_FACTORY)) +#define GST_TYPE_FIND_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_TYPE_FIND_FACTORY, GstTypeFindFactoryClass)) + +typedef struct _GstTypeFindFactory GstTypeFindFactory; +typedef struct _GstTypeFindFactoryClass GstTypeFindFactoryClass; + +/** + * GstTypeFindFactory: + * + * Object that stores information about a typefind function. + */ +struct _GstTypeFindFactory { + GstPluginFeature feature; + /* <private> */ + + GstTypeFindFunction function; + gchar ** extensions; + GstCaps * caps; /* FIXME: not yet saved in registry */ + + gpointer user_data; + GDestroyNotify user_data_notify; + + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstTypeFindFactoryClass { + GstPluginFeatureClass parent; + /* <private> */ + + gpointer _gst_reserved[GST_PADDING]; +}; + +/* typefinding interface */ + +GType gst_type_find_factory_get_type (void); + +GList * gst_type_find_factory_get_list (void); + +gchar ** gst_type_find_factory_get_extensions (GstTypeFindFactory *factory); +GstCaps * gst_type_find_factory_get_caps (GstTypeFindFactory *factory); +void gst_type_find_factory_call_function (GstTypeFindFactory *factory, + GstTypeFind *find); + +G_END_DECLS + +#endif /* __GST_TYPE_FIND_FACTORY_H__ */ diff --git a/gst/gsturi.c b/gst/gsturi.c new file mode 100644 index 0000000..66ab40c --- /dev/null +++ b/gst/gsturi.c @@ -0,0 +1,888 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net> + * + * gsturi.c: register URI handlers + * + * 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:gsturihandler + * @short_description: Interface to ease URI handling in plugins. + * + * The URIHandler is an interface that is implemented by Source and Sink + * #GstElement to simplify then handling of URI. + * + * An application can use the following functions to quickly get an element + * that handles the given URI for reading or writing + * (gst_element_make_from_uri()). + * + * Source and Sink plugins should implement this interface when possible. + * + * Last reviewed on 2005-11-09 (0.9.4) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst_private.h" +#include "gsturi.h" +#include "gstinfo.h" +#include "gstmarshal.h" +#include "gstregistry.h" + +#include <string.h> + +GST_DEBUG_CATEGORY_STATIC (gst_uri_handler_debug); +#define GST_CAT_DEFAULT gst_uri_handler_debug + +enum +{ + NEW_URI, + LAST_SIGNAL +}; + +static guint gst_uri_handler_signals[LAST_SIGNAL] = { 0 }; + +static void gst_uri_handler_base_init (gpointer g_class); + +GType +gst_uri_handler_get_type (void) +{ + static volatile gsize urihandler_type = 0; + + if (g_once_init_enter (&urihandler_type)) { + GType _type; + static const GTypeInfo urihandler_info = { + sizeof (GstURIHandlerInterface), + gst_uri_handler_base_init, + NULL, + NULL, + NULL, + NULL, + 0, + 0, + NULL, + NULL + }; + + _type = g_type_register_static (G_TYPE_INTERFACE, + "GstURIHandler", &urihandler_info, 0); + + GST_DEBUG_CATEGORY_INIT (gst_uri_handler_debug, "GST_URI", GST_DEBUG_BOLD, + "handling of URIs"); + g_once_init_leave (&urihandler_type, _type); + } + return urihandler_type; +} + +static void +gst_uri_handler_base_init (gpointer g_class) +{ + static gboolean initialized = FALSE; + + if (G_UNLIKELY (!initialized)) { + + /** + * GstURIHandler::new-uri: + * @handler: The #GstURIHandler which emitted the signal + * @uri: (transfer none): The new URI, or NULL if the URI was removed + * + * The URI of the given @handler has changed. + */ + + gst_uri_handler_signals[NEW_URI] = + g_signal_new ("new-uri", GST_TYPE_URI_HANDLER, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstURIHandlerInterface, new_uri), NULL, NULL, + gst_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); + initialized = TRUE; + } +} + +static const guchar acceptable[96] = { /* X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 XA XB XC XD XE XF */ + 0x00, 0x3F, 0x20, 0x20, 0x20, 0x00, 0x2C, 0x3F, 0x3F, 0x3F, 0x3F, 0x22, 0x20, 0x3F, 0x3F, 0x1C, /* 2X !"#$%&'()*+,-./ */ + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x38, 0x20, 0x20, 0x2C, 0x20, 0x2C, /* 3X 0123456789:;<=>? */ + 0x30, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 4X @ABCDEFGHIJKLMNO */ + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x20, 0x3F, /* 5X PQRSTUVWXYZ[\]^_ */ + 0x20, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 6X `abcdefghijklmno */ + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x3F, 0x20 /* 7X pqrstuvwxyz{|}~DEL */ +}; + +typedef enum +{ + UNSAFE_ALL = 0x1, /* Escape all unsafe characters */ + UNSAFE_ALLOW_PLUS = 0x2, /* Allows '+' */ + UNSAFE_PATH = 0x4, /* Allows '/' and '?' and '&' and '=' */ + UNSAFE_DOS_PATH = 0x8, /* Allows '/' and '?' and '&' and '=' and ':' */ + UNSAFE_HOST = 0x10, /* Allows '/' and ':' and '@' */ + UNSAFE_SLASHES = 0x20 /* Allows all characters except for '/' and '%' */ +} UnsafeCharacterSet; + +#define HEX_ESCAPE '%' + +/* Escape undesirable characters using % + * ------------------------------------- + * + * This function takes a pointer to a string in which + * some characters may be unacceptable unescaped. + * It returns a string which has these characters + * represented by a '%' character followed by two hex digits. + * + * This routine returns a g_malloced string. + */ + +static const gchar hex[16] = "0123456789ABCDEF"; + +static gchar * +escape_string_internal (const gchar * string, UnsafeCharacterSet mask) +{ +#define ACCEPTABLE_CHAR(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask)) + + const gchar *p; + gchar *q; + gchar *result; + guchar c; + gint unacceptable; + UnsafeCharacterSet use_mask; + + g_return_val_if_fail (mask == UNSAFE_ALL + || mask == UNSAFE_ALLOW_PLUS + || mask == UNSAFE_PATH + || mask == UNSAFE_DOS_PATH + || mask == UNSAFE_HOST || mask == UNSAFE_SLASHES, NULL); + + if (string == NULL) { + return NULL; + } + + unacceptable = 0; + use_mask = mask; + for (p = string; *p != '\0'; p++) { + c = *p; + if (!ACCEPTABLE_CHAR (c)) { + unacceptable++; + } + if ((use_mask == UNSAFE_HOST) && (unacceptable || (c == '/'))) { + /* when escaping a host, if we hit something that needs to be escaped, or we finally + * hit a path separator, revert to path mode (the host segment of the url is over). + */ + use_mask = UNSAFE_PATH; + } + } + + result = g_malloc (p - string + unacceptable * 2 + 1); + + use_mask = mask; + for (q = result, p = string; *p != '\0'; p++) { + c = *p; + + if (!ACCEPTABLE_CHAR (c)) { + *q++ = HEX_ESCAPE; /* means hex coming */ + *q++ = hex[c >> 4]; + *q++ = hex[c & 15]; + } else { + *q++ = c; + } + if ((use_mask == UNSAFE_HOST) && (!ACCEPTABLE_CHAR (c) || (c == '/'))) { + use_mask = UNSAFE_PATH; + } + } + + *q = '\0'; + + return result; +} + +/* escape_string: + * @string: string to be escaped + * + * Escapes @string, replacing any and all special characters + * with equivalent escape sequences. + * + * Return value: a newly allocated string equivalent to @string + * but with all special characters escaped + **/ +static gchar * +escape_string (const gchar * string) +{ + return escape_string_internal (string, UNSAFE_ALL); +} + +static int +hex_to_int (gchar c) +{ + return c >= '0' && c <= '9' ? c - '0' + : c >= 'A' && c <= 'F' ? c - 'A' + 10 + : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1; +} + +static int +unescape_character (const char *scanner) +{ + int first_digit; + int second_digit; + + first_digit = hex_to_int (*scanner++); + if (first_digit < 0) { + return -1; + } + + second_digit = hex_to_int (*scanner); + if (second_digit < 0) { + return -1; + } + + return (first_digit << 4) | second_digit; +} + +/* unescape_string: + * @escaped_string: an escaped URI, path, or other string + * @illegal_characters: a string containing a sequence of characters + * considered "illegal", '\0' is automatically in this list. + * + * Decodes escaped characters (i.e. PERCENTxx sequences) in @escaped_string. + * Characters are encoded in PERCENTxy form, where xy is the ASCII hex code + * for character 16x+y. + * + * Return value: a newly allocated string with the unescaped equivalents, + * or %NULL if @escaped_string contained one of the characters + * in @illegal_characters. + **/ +static char * +unescape_string (const gchar * escaped_string, const gchar * illegal_characters) +{ + const gchar *in; + gchar *out, *result; + gint character; + + if (escaped_string == NULL) { + return NULL; + } + + result = g_malloc (strlen (escaped_string) + 1); + + out = result; + for (in = escaped_string; *in != '\0'; in++) { + character = *in; + if (*in == HEX_ESCAPE) { + character = unescape_character (in + 1); + + /* Check for an illegal character. We consider '\0' illegal here. */ + if (character <= 0 + || (illegal_characters != NULL + && strchr (illegal_characters, (char) character) != NULL)) { + g_free (result); + return NULL; + } + in += 2; + } + *out++ = (char) character; + } + + *out = '\0'; + g_assert ((gsize) (out - result) <= strlen (escaped_string)); + return result; + +} + + +static void +gst_uri_protocol_check_internal (const gchar * uri, gchar ** endptr) +{ + gchar *check = (gchar *) uri; + + g_assert (uri != NULL); + g_assert (endptr != NULL); + + if (g_ascii_isalpha (*check)) { + check++; + while (g_ascii_isalnum (*check) || *check == '+' + || *check == '-' || *check == '.') + check++; + } + + *endptr = check; +} + +/** + * gst_uri_protocol_is_valid: + * @protocol: A string + * + * Tests if the given string is a valid protocol identifier. Protocols + * must consist of alphanumeric characters, '+', '-' and '.' and must + * start with a alphabetic character. See RFC 3986 Section 3.1. + * + * Returns: TRUE if the string is a valid protocol identifier, FALSE otherwise. + */ +gboolean +gst_uri_protocol_is_valid (const gchar * protocol) +{ + gchar *endptr; + + g_return_val_if_fail (protocol != NULL, FALSE); + + gst_uri_protocol_check_internal (protocol, &endptr); + + return *endptr == '\0' && endptr != protocol; +} + +/** + * gst_uri_is_valid: + * @uri: A URI string + * + * Tests if the given string is a valid URI identifier. URIs start with a valid + * scheme followed by ":" and maybe a string identifying the location. + * + * Returns: TRUE if the string is a valid URI + */ +gboolean +gst_uri_is_valid (const gchar * uri) +{ + gchar *endptr; + + g_return_val_if_fail (uri != NULL, FALSE); + + gst_uri_protocol_check_internal (uri, &endptr); + + return *endptr == ':'; +} + +/** + * gst_uri_get_protocol: + * @uri: A URI string + * + * Extracts the protocol out of a given valid URI. The returned string must be + * freed using g_free(). + * + * Returns: The protocol for this URI. + */ +gchar * +gst_uri_get_protocol (const gchar * uri) +{ + gchar *colon; + + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (gst_uri_is_valid (uri), NULL); + + colon = strstr (uri, ":"); + + return g_ascii_strdown (uri, colon - uri); +} + +/** + * gst_uri_has_protocol: + * @uri: a URI string + * @protocol: a protocol string (e.g. "http") + * + * Checks if the protocol of a given valid URI matches @protocol. + * + * Returns: %TRUE if the protocol matches. + * + * Since: 0.10.4 + */ +gboolean +gst_uri_has_protocol (const gchar * uri, const gchar * protocol) +{ + gchar *colon; + + g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (protocol != NULL, FALSE); + g_return_val_if_fail (gst_uri_is_valid (uri), FALSE); + + colon = strstr (uri, ":"); + + if (colon == NULL) + return FALSE; + + return (g_ascii_strncasecmp (uri, protocol, (gsize) (colon - uri)) == 0); +} + +/** + * gst_uri_get_location: + * @uri: A URI string + * + * Extracts the location out of a given valid URI, ie. the protocol and "://" + * are stripped from the URI, which means that the location returned includes + * the hostname if one is specified. The returned string must be freed using + * g_free(). + * + * Free-function: g_free + * + * Returns: (transfer full) (array zero-terminated=1): the location for this + * URI. Returns NULL if the URI isn't valid. If the URI does not contain + * a location, an empty string is returned. + */ +gchar * +gst_uri_get_location (const gchar * uri) +{ + const gchar *colon; + gchar *unescaped = NULL; + + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (gst_uri_is_valid (uri), NULL); + + colon = strstr (uri, "://"); + if (!colon) + return NULL; + + unescaped = unescape_string (colon + 3, "/"); + + /* On Windows an URI might look like file:///c:/foo/bar.txt or + * file:///c|/foo/bar.txt (some Netscape versions) and we want to + * return c:/foo/bar.txt as location rather than /c:/foo/bar.txt. + * Can't use g_filename_from_uri() here because it will only handle the + * file:// protocol */ +#ifdef G_OS_WIN32 + if (unescaped != NULL && unescaped[0] == '/' && + g_ascii_isalpha (unescaped[1]) && + (unescaped[2] == ':' || unescaped[2] == '|')) { + unescaped[2] = ':'; + g_memmove (unescaped, unescaped + 1, strlen (unescaped + 1) + 1); + } +#endif + + GST_LOG ("extracted location '%s' from URI '%s'", GST_STR_NULL (unescaped), + uri); + return unescaped; +} + +/** + * gst_uri_construct: + * @protocol: Protocol for URI + * @location: (array zero-terminated=1) (transfer none): Location for URI + * + * Constructs a URI for a given valid protocol and location. + * + * Free-function: g_free + * + * Returns: (transfer full) (array zero-terminated=1): a new string for this + * URI. Returns NULL if the given URI protocol is not valid, or the given + * location is NULL. + */ +gchar * +gst_uri_construct (const gchar * protocol, const gchar * location) +{ + char *escaped, *proto_lowercase; + char *retval; + + g_return_val_if_fail (gst_uri_protocol_is_valid (protocol), NULL); + g_return_val_if_fail (location != NULL, NULL); + + proto_lowercase = g_ascii_strdown (protocol, -1); + escaped = escape_string (location); + retval = g_strdup_printf ("%s://%s", proto_lowercase, escaped); + g_free (escaped); + g_free (proto_lowercase); + + return retval; +} + +typedef struct +{ + GstURIType type; + const gchar *protocol; +} +SearchEntry; + +static gboolean +search_by_entry (GstPluginFeature * feature, gpointer search_entry) +{ + gchar **protocols; + GstElementFactory *factory; + SearchEntry *entry = (SearchEntry *) search_entry; + + if (!GST_IS_ELEMENT_FACTORY (feature)) + return FALSE; + factory = GST_ELEMENT_FACTORY_CAST (feature); + + if (factory->uri_type != entry->type) + return FALSE; + + protocols = gst_element_factory_get_uri_protocols (factory); + + if (protocols == NULL) { + g_warning ("Factory '%s' implements GstUriHandler interface but returned " + "no supported protocols!", gst_plugin_feature_get_name (feature)); + return FALSE; + } + + while (*protocols != NULL) { + if (g_ascii_strcasecmp (*protocols, entry->protocol) == 0) + return TRUE; + protocols++; + } + return FALSE; +} + +static gint +sort_by_rank (GstPluginFeature * first, GstPluginFeature * second) +{ + return gst_plugin_feature_get_rank (second) - + gst_plugin_feature_get_rank (first); +} + +static GList * +get_element_factories_from_uri_protocol (const GstURIType type, + const gchar * protocol) +{ + GList *possibilities; + SearchEntry entry; + + g_return_val_if_fail (protocol, NULL); + + entry.type = type; + entry.protocol = protocol; + possibilities = gst_registry_feature_filter (gst_registry_get_default (), + search_by_entry, FALSE, &entry); + + return possibilities; +} + +/** + * gst_uri_protocol_is_supported: + * @type: Whether to check for a source or a sink + * @protocol: Protocol that should be checked for (e.g. "http" or "smb") + * + * Checks if an element exists that supports the given URI protocol. Note + * that a positive return value does not imply that a subsequent call to + * gst_element_make_from_uri() is guaranteed to work. + * + * Returns: TRUE + * + * Since: 0.10.13 +*/ +gboolean +gst_uri_protocol_is_supported (const GstURIType type, const gchar * protocol) +{ + GList *possibilities; + + g_return_val_if_fail (protocol, FALSE); + + possibilities = get_element_factories_from_uri_protocol (type, protocol); + + if (possibilities) { + g_list_free (possibilities); + return TRUE; + } else + return FALSE; +} + +/** + * gst_element_make_from_uri: + * @type: Whether to create a source or a sink + * @uri: URI to create an element for + * @elementname: (allow-none): Name of created element, can be NULL. + * + * Creates an element for handling the given URI. + * + * Returns: (transfer full): a new element or NULL if none could be created + */ +GstElement * +gst_element_make_from_uri (const GstURIType type, const gchar * uri, + const gchar * elementname) +{ + GList *possibilities, *walk; + gchar *protocol; + GstElement *ret = NULL; + + g_return_val_if_fail (GST_URI_TYPE_IS_VALID (type), NULL); + g_return_val_if_fail (gst_uri_is_valid (uri), NULL); + + protocol = gst_uri_get_protocol (uri); + possibilities = get_element_factories_from_uri_protocol (type, protocol); + g_free (protocol); + + if (!possibilities) { + GST_DEBUG ("No %s for URI '%s'", type == GST_URI_SINK ? "sink" : "source", + uri); + return NULL; + } + + possibilities = g_list_sort (possibilities, (GCompareFunc) sort_by_rank); + walk = possibilities; + while (walk) { + if ((ret = + gst_element_factory_create (GST_ELEMENT_FACTORY_CAST (walk->data), + elementname)) != NULL) { + GstURIHandler *handler = GST_URI_HANDLER (ret); + + if (gst_uri_handler_set_uri (handler, uri)) + break; + gst_object_unref (ret); + ret = NULL; + } + walk = walk->next; + } + gst_plugin_feature_list_free (possibilities); + + GST_LOG_OBJECT (ret, "created %s for URL '%s'", + type == GST_URI_SINK ? "sink" : "source", uri); + return ret; +} + +/** + * gst_uri_handler_get_uri_type: + * @handler: A #GstURIHandler. + * + * Gets the type of the given URI handler + * + * Returns: the #GstURIType of the URI handler. + * Returns #GST_URI_UNKNOWN if the @handler isn't implemented correctly. + */ +guint +gst_uri_handler_get_uri_type (GstURIHandler * handler) +{ + GstURIHandlerInterface *iface; + guint ret; + + g_return_val_if_fail (GST_IS_URI_HANDLER (handler), GST_URI_UNKNOWN); + + iface = GST_URI_HANDLER_GET_INTERFACE (handler); + g_return_val_if_fail (iface != NULL, GST_URI_UNKNOWN); + g_return_val_if_fail (iface->get_type != NULL, GST_URI_UNKNOWN); + + ret = iface->get_type (G_OBJECT_TYPE (handler)); + g_return_val_if_fail (GST_URI_TYPE_IS_VALID (ret), GST_URI_UNKNOWN); + + return ret; +} + +/** + * gst_uri_handler_get_protocols: + * @handler: A #GstURIHandler. + * + * Gets the list of protocols supported by @handler. This list may not be + * modified. + * + * Returns: (transfer none) (array zero-terminated=1) (element-type utf8): the + * supported protocols. Returns NULL if the @handler isn't implemented + * properly, or the @handler doesn't support any protocols. + */ +gchar ** +gst_uri_handler_get_protocols (GstURIHandler * handler) +{ + GstURIHandlerInterface *iface; + gchar **ret; + + g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL); + + iface = GST_URI_HANDLER_GET_INTERFACE (handler); + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (iface->get_protocols != NULL, NULL); + + ret = iface->get_protocols (G_OBJECT_TYPE (handler)); + g_return_val_if_fail (ret != NULL, NULL); + + return ret; +} + +/** + * gst_uri_handler_get_uri: + * @handler: A #GstURIHandler + * + * Gets the currently handled URI. + * + * Returns: (transfer none): the URI currently handled by the @handler. + * Returns NULL if there are no URI currently handled. The + * returned string must not be modified or freed. + */ +const gchar * +gst_uri_handler_get_uri (GstURIHandler * handler) +{ + GstURIHandlerInterface *iface; + const gchar *ret; + + g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL); + + iface = GST_URI_HANDLER_GET_INTERFACE (handler); + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (iface->get_uri != NULL, NULL); + ret = iface->get_uri (handler); + if (ret != NULL) + g_return_val_if_fail (gst_uri_is_valid (ret), NULL); + + return ret; +} + +/** + * gst_uri_handler_set_uri: + * @handler: A #GstURIHandler + * @uri: URI to set + * + * Tries to set the URI of the given handler. + * + * Returns: TRUE if the URI was set successfully, else FALSE. + */ +gboolean +gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri) +{ + GstURIHandlerInterface *iface; + gboolean ret; + gchar *new_uri, *protocol, *location, *colon; + + g_return_val_if_fail (GST_IS_URI_HANDLER (handler), FALSE); + g_return_val_if_fail (gst_uri_is_valid (uri), FALSE); + + iface = GST_URI_HANDLER_GET_INTERFACE (handler); + g_return_val_if_fail (iface != NULL, FALSE); + g_return_val_if_fail (iface->set_uri != NULL, FALSE); + + protocol = gst_uri_get_protocol (uri); + + colon = strstr (uri, ":"); + location = g_strdup (colon); + + new_uri = g_strdup_printf ("%s%s", protocol, location); + + ret = iface->set_uri (handler, uri); + + g_free (new_uri); + g_free (location); + g_free (protocol); + + return ret; +} + +/** + * gst_uri_handler_new_uri: + * @handler: A #GstURIHandler + * @uri: new URI or NULL if it was unset + * + * Emits the new-uri signal for a given handler, when that handler has a new URI. + * This function should only be called by URI handlers themselves. + */ +void +gst_uri_handler_new_uri (GstURIHandler * handler, const gchar * uri) +{ + g_return_if_fail (GST_IS_URI_HANDLER (handler)); + + g_signal_emit (handler, gst_uri_handler_signals[NEW_URI], 0, uri); +} + +static gchar * +gst_file_utils_canonicalise_path (const gchar * path) +{ + gchar **parts, **p, *clean_path; + +#ifdef G_OS_WIN32 + { + GST_WARNING ("FIXME: canonicalise win32 path"); + return g_strdup (path); + } +#endif + + parts = g_strsplit (path, "/", -1); + + p = parts; + while (*p != NULL) { + if (strcmp (*p, ".") == 0) { + /* just move all following parts on top of this, incl. NUL terminator */ + g_free (*p); + g_memmove (p, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *)); + /* re-check the new current part again in the next iteration */ + continue; + } else if (strcmp (*p, "..") == 0 && p > parts) { + /* just move all following parts on top of the previous part, incl. + * NUL terminator */ + g_free (*(p - 1)); + g_free (*p); + g_memmove (p - 1, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *)); + /* re-check the new current part again in the next iteration */ + --p; + continue; + } + ++p; + } + if (*path == '/') { + guint num_parts; + + num_parts = g_strv_length (parts) + 1; /* incl. terminator */ + parts = g_renew (gchar *, parts, num_parts + 1); + g_memmove (parts + 1, parts, num_parts * sizeof (gchar *)); + parts[0] = g_strdup ("/"); + } + + clean_path = g_build_filenamev (parts); + g_strfreev (parts); + return clean_path; +} + +static gboolean +file_path_contains_relatives (const gchar * path) +{ + return (strstr (path, "/./") != NULL || strstr (path, "/../") != NULL || + strstr (path, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) != NULL || + strstr (path, G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) != NULL); +} + +/** + * gst_filename_to_uri: + * @filename: absolute or relative file name path + * @error: pointer to error, or NULL + * + * Similar to g_filename_to_uri(), but attempts to handle relative file paths + * as well. Before converting @filename into an URI, it will be prefixed by + * the current working directory if it is a relative path, and then the path + * will be canonicalised so that it doesn't contain any './' or '../' segments. + * + * On Windows #filename should be in UTF-8 encoding. + * + * Since: 0.10.33 + */ +gchar * +gst_filename_to_uri (const gchar * filename, GError ** error) +{ + gchar *abs_location = NULL; + gchar *uri, *abs_clean; + + g_return_val_if_fail (filename != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (g_path_is_absolute (filename)) { + if (!file_path_contains_relatives (filename)) { + uri = g_filename_to_uri (filename, NULL, error); + goto beach; + } + + abs_location = g_strdup (filename); + } else { + gchar *cwd; + + cwd = g_get_current_dir (); + abs_location = g_build_filename (cwd, filename, NULL); + g_free (cwd); + + if (!file_path_contains_relatives (abs_location)) { + uri = g_filename_to_uri (abs_location, NULL, error); + goto beach; + } + } + + /* path is now absolute, but contains '.' or '..' */ + abs_clean = gst_file_utils_canonicalise_path (abs_location); + GST_LOG ("'%s' -> '%s' -> '%s'", filename, abs_location, abs_clean); + uri = g_filename_to_uri (abs_clean, NULL, error); + g_free (abs_clean); + +beach: + + g_free (abs_location); + GST_DEBUG ("'%s' -> '%s'", filename, uri); + return uri; +} diff --git a/gst/gsturi.h b/gst/gsturi.h new file mode 100644 index 0000000..176d70e --- /dev/null +++ b/gst/gsturi.h @@ -0,0 +1,145 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gsturi.h: Header for uri to element mappings + * + * 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_URI_H__ +#define __GST_URI_H__ + +#include <glib.h> +#include <gst/gstelement.h> +#include <gst/gstpluginfeature.h> + +G_BEGIN_DECLS + +/** + * GstURIType: + * @GST_URI_UNKNOWN : The URI direction is unknown + * @GST_URI_SINK : The URI is a consumer. + * @GST_URI_SRC : The URI is a producer. + * + * The different types of URI direction. + */ + +typedef enum { + GST_URI_UNKNOWN, + GST_URI_SINK, + GST_URI_SRC +} GstURIType; + +/** + * GST_URI_TYPE_IS_VALID: + * @type: A #GstURIType + * + * Tests if the type direction is valid. + */ +#define GST_URI_TYPE_IS_VALID(type) ((type) == GST_URI_SRC || (type) == GST_URI_SINK) + +/* uri handler functions */ +#define GST_TYPE_URI_HANDLER (gst_uri_handler_get_type ()) +#define GST_URI_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_URI_HANDLER, GstURIHandler)) +#define GST_IS_URI_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_URI_HANDLER)) +#define GST_URI_HANDLER_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GST_TYPE_URI_HANDLER, GstURIHandlerInterface)) + +/** + * GstURIHandler: + * + * Opaque #GstURIHandler structure. + */ +typedef struct _GstURIHandler GstURIHandler; +typedef struct _GstURIHandlerInterface GstURIHandlerInterface; + +/** + * GstURIHandlerInterface: + * @parent: The parent interface type + * @get_type: Method to tell whether the element handles source or sink URI. + * @get_protocols: Method to return the list of protocols handled by the element. + * @get_uri: Method to return the URI currently handled by the element. + * @set_uri: Method to set a new URI. + * + * Any #GstElement using this interface should implement these methods. + */ +struct _GstURIHandlerInterface { + GTypeInterface parent; + + /*< private >*/ + /* signals */ + void (* new_uri) (GstURIHandler * handler, + const gchar * uri); + /* idea for the future ? + gboolean (* require_password) (GstURIHandler * handler, + gchar ** username, + gchar ** password); + */ + + /* vtable */ + + /*< public >*/ + /* querying capabilities */ + GstURIType (* get_type) (GType type); + gchar ** (* get_protocols) (GType type); + + /* using the interface */ + const gchar * (* get_uri) (GstURIHandler * handler); + gboolean (* set_uri) (GstURIHandler * handler, + const gchar * uri); + + /*< private >*/ + /* we might want to add functions here to query features, + * someone with gnome-vfs knowledge go ahead */ + + gpointer _gst_reserved[GST_PADDING]; +}; + +/* general URI functions */ + +gboolean gst_uri_protocol_is_valid (const gchar * protocol); +gboolean gst_uri_protocol_is_supported (const GstURIType type, + const gchar *protocol); +gboolean gst_uri_is_valid (const gchar * uri); +gchar * gst_uri_get_protocol (const gchar * uri); +gboolean gst_uri_has_protocol (const gchar * uri, + const gchar * protocol); +gchar * gst_uri_get_location (const gchar * uri); +gchar * gst_uri_construct (const gchar * protocol, + const gchar * location); + +gchar * gst_filename_to_uri (const gchar * filename, + GError ** error); + +GstElement * gst_element_make_from_uri (const GstURIType type, + const gchar * uri, + const gchar * elementname); + +/* accessing the interface */ +GType gst_uri_handler_get_type (void); + +guint gst_uri_handler_get_uri_type (GstURIHandler * handler); +gchar ** gst_uri_handler_get_protocols (GstURIHandler * handler); +const gchar * gst_uri_handler_get_uri (GstURIHandler * handler); +gboolean gst_uri_handler_set_uri (GstURIHandler * handler, + const gchar * uri); +void gst_uri_handler_new_uri (GstURIHandler * handler, + const gchar * uri); + +G_END_DECLS + +#endif /* __GST_URI_H__ */ diff --git a/gst/gstutils.c b/gst/gstutils.c new file mode 100644 index 0000000..07eac6b --- /dev/null +++ b/gst/gstutils.c @@ -0,0 +1,3813 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2002 Thomas Vander Stichele <thomas@apestaart.org> + * + * gstutils.c: Utility functions + * + * 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:gstutils + * @short_description: Various utility functions + * + */ + +#include "gst_private.h" +#include <stdio.h> +#include <string.h> + +#include "gstghostpad.h" +#include "gstutils.h" +#include "gsterror.h" +#include "gstinfo.h" +#include "gstparse.h" +#include "gstvalue.h" +#include "gst-i18n-lib.h" +#include "glib-compat-private.h" +#include <math.h> + +/** + * gst_util_dump_mem: + * @mem: a pointer to the memory to dump + * @size: the size of the memory block to dump + * + * Dumps the memory block into a hex representation. Useful for debugging. + */ +void +gst_util_dump_mem (const guchar * mem, guint size) +{ + guint i, j; + GString *string = g_string_sized_new (50); + GString *chars = g_string_sized_new (18); + + i = j = 0; + while (i < size) { + if (g_ascii_isprint (mem[i])) + g_string_append_c (chars, mem[i]); + else + g_string_append_c (chars, '.'); + + g_string_append_printf (string, "%02x ", mem[i]); + + j++; + i++; + + if (j == 16 || i == size) { + g_print ("%08x (%p): %-48.48s %-16.16s\n", i - j, mem + i - j, + string->str, chars->str); + g_string_set_size (string, 0); + g_string_set_size (chars, 0); + j = 0; + } + } + g_string_free (string, TRUE); + g_string_free (chars, TRUE); +} + + +/** + * gst_util_set_value_from_string: + * @value: (out caller-allocates): the value to set + * @value_str: the string to get the value from + * + * Converts the string to the type of the value and + * sets the value with it. + * + * Note that this function is dangerous as it does not return any indication + * if the conversion worked or not. + */ +void +gst_util_set_value_from_string (GValue * value, const gchar * value_str) +{ + gboolean res; + + g_return_if_fail (value != NULL); + g_return_if_fail (value_str != NULL); + + GST_CAT_DEBUG (GST_CAT_PARAMS, "parsing '%s' to type %s", value_str, + g_type_name (G_VALUE_TYPE (value))); + + res = gst_value_deserialize (value, value_str); + if (!res && G_VALUE_TYPE (value) == G_TYPE_BOOLEAN) { + /* backwards compat, all booleans that fail to parse are false */ + g_value_set_boolean (value, FALSE); + res = TRUE; + } + g_return_if_fail (res); +} + +/** + * gst_util_set_object_arg: + * @object: the object to set the argument of + * @name: the name of the argument to set + * @value: the string value to set + * + * Convertes the string value to the type of the objects argument and + * sets the argument with it. + * + * Note that this function silently returns if @object has no property named + * @name or when @value cannot be converted to the type of the property. + */ +void +gst_util_set_object_arg (GObject * object, const gchar * name, + const gchar * value) +{ + GParamSpec *pspec; + GType value_type; + GValue v = { 0, }; + + g_return_if_fail (G_IS_OBJECT (object)); + g_return_if_fail (name != NULL); + g_return_if_fail (value != NULL); + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), name); + if (!pspec) + return; + + value_type = pspec->value_type; + + GST_DEBUG ("pspec->flags is %d, pspec->value_type is %s", + pspec->flags, g_type_name (value_type)); + + if (!(pspec->flags & G_PARAM_WRITABLE)) + return; + + g_value_init (&v, value_type); + + /* special case for element <-> xml (de)serialisation */ + if (GST_VALUE_HOLDS_STRUCTURE (&v) && strcmp (value, "NULL") == 0) { + g_value_set_boxed (&v, NULL); + goto done; + } + + if (!gst_value_deserialize (&v, value)) + return; + +done: + + g_object_set_property (object, pspec->name, &v); + g_value_unset (&v); +} + +/* work around error C2520: conversion from unsigned __int64 to double + * not implemented, use signed __int64 + * + * These are implemented as functions because on some platforms a 64bit int to + * double conversion is not defined/implemented. + */ + +gdouble +gst_util_guint64_to_gdouble (guint64 value) +{ + if (value & G_GINT64_CONSTANT (0x8000000000000000)) + return (gdouble) ((gint64) value) + (gdouble) 18446744073709551616.; + else + return (gdouble) ((gint64) value); +} + +guint64 +gst_util_gdouble_to_guint64 (gdouble value) +{ + if (value < (gdouble) 9223372036854775808.) /* 1 << 63 */ + return ((guint64) ((gint64) value)); + + value -= (gdouble) 18446744073709551616.; + return ((guint64) ((gint64) value)); +} + +#ifndef HAVE_UINT128_T +/* convenience struct for getting high and low uint32 parts of + * a guint64 */ +typedef union +{ + guint64 ll; + struct + { +#if G_BYTE_ORDER == G_BIG_ENDIAN + guint32 high, low; +#else + guint32 low, high; +#endif + } l; +} GstUInt64; + +#if defined (__x86_64__) && defined (__GNUC__) +static inline void +gst_util_uint64_mul_uint64 (GstUInt64 * c1, GstUInt64 * c0, guint64 arg1, + guint64 arg2) +{ + __asm__ __volatile__ ("mulq %3":"=a" (c0->ll), "=d" (c1->ll) + :"a" (arg1), "g" (arg2) + ); +} +#else /* defined (__x86_64__) */ +/* multiply two 64-bit unsigned ints into a 128-bit unsigned int. the high + * and low 64 bits of the product are placed in c1 and c0 respectively. + * this operation cannot overflow. */ +static inline void +gst_util_uint64_mul_uint64 (GstUInt64 * c1, GstUInt64 * c0, guint64 arg1, + guint64 arg2) +{ + GstUInt64 a1, b0; + GstUInt64 v, n; + + /* prepare input */ + v.ll = arg1; + n.ll = arg2; + + /* do 128 bits multiply + * nh nl + * * vh vl + * ---------- + * a0 = vl * nl + * a1 = vl * nh + * b0 = vh * nl + * b1 = + vh * nh + * ------------------- + * c1h c1l c0h c0l + * + * "a0" is optimized away, result is stored directly in c0. "b1" is + * optimized away, result is stored directly in c1. + */ + c0->ll = (guint64) v.l.low * n.l.low; + a1.ll = (guint64) v.l.low * n.l.high; + b0.ll = (guint64) v.l.high * n.l.low; + + /* add the high word of a0 to the low words of a1 and b0 using c1 as + * scrach space to capture the carry. the low word of the result becomes + * the final high word of c0 */ + c1->ll = (guint64) c0->l.high + a1.l.low + b0.l.low; + c0->l.high = c1->l.low; + + /* add the carry from the result above (found in the high word of c1) and + * the high words of a1 and b0 to b1, the result is c1. */ + c1->ll = (guint64) v.l.high * n.l.high + c1->l.high + a1.l.high + b0.l.high; +} +#endif /* defined (__x86_64__) */ + +#if defined (__x86_64__) && defined (__GNUC__) +static inline guint64 +gst_util_div128_64 (GstUInt64 c1, GstUInt64 c0, guint64 denom) +{ + guint64 res; + + __asm__ __volatile__ ("divq %3":"=a" (res) + :"d" (c1.ll), "a" (c0.ll), "g" (denom) + ); + + return res; +} +#else +/* count leading zeros */ +static inline guint +gst_util_clz (guint32 val) +{ + guint s; + + s = val | (val >> 1); + s |= (s >> 2); + s |= (s >> 4); + s |= (s >> 8); + s = ~(s | (s >> 16)); + s = s - ((s >> 1) & 0x55555555); + s = (s & 0x33333333) + ((s >> 2) & 0x33333333); + s = (s + (s >> 4)) & 0x0f0f0f0f; + s += (s >> 8); + s = (s + (s >> 16)) & 0x3f; + + return s; +} + +/* based on Hacker's Delight p152 */ +static inline guint64 +gst_util_div128_64 (GstUInt64 c1, GstUInt64 c0, guint64 denom) +{ + GstUInt64 q1, q0, rhat; + GstUInt64 v, cmp1, cmp2; + guint s; + + v.ll = denom; + + /* count number of leading zeroes, we know they must be in the high + * part of denom since denom > G_MAXUINT32. */ + s = gst_util_clz (v.l.high); + + if (s > 0) { + /* normalize divisor and dividend */ + v.ll <<= s; + c1.ll = (c1.ll << s) | (c0.l.high >> (32 - s)); + c0.ll <<= s; + } + + q1.ll = c1.ll / v.l.high; + rhat.ll = c1.ll - q1.ll * v.l.high; + + cmp1.l.high = rhat.l.low; + cmp1.l.low = c0.l.high; + cmp2.ll = q1.ll * v.l.low; + + while (q1.l.high || cmp2.ll > cmp1.ll) { + q1.ll--; + rhat.ll += v.l.high; + if (rhat.l.high) + break; + cmp1.l.high = rhat.l.low; + cmp2.ll -= v.l.low; + } + c1.l.high = c1.l.low; + c1.l.low = c0.l.high; + c1.ll -= q1.ll * v.ll; + q0.ll = c1.ll / v.l.high; + rhat.ll = c1.ll - q0.ll * v.l.high; + + cmp1.l.high = rhat.l.low; + cmp1.l.low = c0.l.low; + cmp2.ll = q0.ll * v.l.low; + + while (q0.l.high || cmp2.ll > cmp1.ll) { + q0.ll--; + rhat.ll += v.l.high; + if (rhat.l.high) + break; + cmp1.l.high = rhat.l.low; + cmp2.ll -= v.l.low; + } + q0.l.high += q1.l.low; + + return q0.ll; +} +#endif /* defined (__GNUC__) */ + +/* This always gives the correct result because: + * a) val <= G_MAXUINT64-1 + * b) (c0,c1) <= G_MAXUINT64 * (G_MAXUINT64-1) + * or + * (c0,c1) == G_MAXUINT64 * G_MAXUINT64 and denom < G_MAXUINT64 + * (note: num==denom case is handled by short path) + * This means that (c0,c1) either has enough space for val + * or that the overall result will overflow anyway. + */ + +/* add correction with carry */ +#define CORRECT(c0,c1,val) \ + if (val) { \ + if (G_MAXUINT64 - c0.ll < val) { \ + if (G_UNLIKELY (c1.ll == G_MAXUINT64)) \ + /* overflow */ \ + return G_MAXUINT64; \ + c1.ll++; \ + } \ + c0.ll += val; \ + } + +static guint64 +gst_util_uint64_scale_uint64_unchecked (guint64 val, guint64 num, + guint64 denom, guint64 correct) +{ + GstUInt64 c1, c0; + + /* compute 128-bit numerator product */ + gst_util_uint64_mul_uint64 (&c1, &c0, val, num); + + /* perform rounding correction */ + CORRECT (c0, c1, correct); + + /* high word as big as or bigger than denom --> overflow */ + if (G_UNLIKELY (c1.ll >= denom)) + return G_MAXUINT64; + + /* compute quotient, fits in 64 bits */ + return gst_util_div128_64 (c1, c0, denom); +} +#else + +#define GST_MAXUINT128 ((__uint128_t) -1) +static guint64 +gst_util_uint64_scale_uint64_unchecked (guint64 val, guint64 num, + guint64 denom, guint64 correct) +{ + __uint128_t tmp; + + /* Calculate val * num */ + tmp = ((__uint128_t) val) * ((__uint128_t) num); + + /* overflow checks */ + if (G_UNLIKELY (GST_MAXUINT128 - correct < tmp)) + return G_MAXUINT64; + + /* perform rounding correction */ + tmp += correct; + + /* Divide by denom */ + tmp /= denom; + + /* if larger than G_MAXUINT64 --> overflow */ + if (G_UNLIKELY (tmp > G_MAXUINT64)) + return G_MAXUINT64; + + /* compute quotient, fits in 64 bits */ + return (guint64) tmp; +} + +#endif + +#if !defined (__x86_64__) && !defined (HAVE_UINT128_T) +static inline void +gst_util_uint64_mul_uint32 (GstUInt64 * c1, GstUInt64 * c0, guint64 arg1, + guint32 arg2) +{ + GstUInt64 a; + + a.ll = arg1; + + c0->ll = (guint64) a.l.low * arg2; + c1->ll = (guint64) a.l.high * arg2 + c0->l.high; + c0->l.high = 0; +} + +/* divide a 96-bit unsigned int by a 32-bit unsigned int when we know the + * quotient fits into 64 bits. the high 64 bits and low 32 bits of the + * numerator are expected in c1 and c0 respectively. */ +static inline guint64 +gst_util_div96_32 (guint64 c1, guint64 c0, guint32 denom) +{ + c0 += (c1 % denom) << 32; + return ((c1 / denom) << 32) + (c0 / denom); +} + +static inline guint64 +gst_util_uint64_scale_uint32_unchecked (guint64 val, guint32 num, + guint32 denom, guint32 correct) +{ + GstUInt64 c1, c0; + + /* compute 96-bit numerator product */ + gst_util_uint64_mul_uint32 (&c1, &c0, val, num); + + /* condition numerator based on rounding mode */ + CORRECT (c0, c1, correct); + + /* high 32 bits as big as or bigger than denom --> overflow */ + if (G_UNLIKELY (c1.l.high >= denom)) + return G_MAXUINT64; + + /* compute quotient, fits in 64 bits */ + return gst_util_div96_32 (c1.ll, c0.ll, denom); +} +#endif + +/* the guts of the gst_util_uint64_scale() variants */ +static guint64 +_gst_util_uint64_scale (guint64 val, guint64 num, guint64 denom, + guint64 correct) +{ + g_return_val_if_fail (denom != 0, G_MAXUINT64); + + if (G_UNLIKELY (num == 0)) + return 0; + + if (G_UNLIKELY (num == denom)) + return val; + + /* on 64bits we always use a full 128bits multiply/division */ +#if !defined (__x86_64__) && !defined (HAVE_UINT128_T) + /* denom is low --> try to use 96 bit muldiv */ + if (G_LIKELY (denom <= G_MAXUINT32)) { + /* num is low --> use 96 bit muldiv */ + if (G_LIKELY (num <= G_MAXUINT32)) + return gst_util_uint64_scale_uint32_unchecked (val, (guint32) num, + (guint32) denom, correct); + + /* num is high but val is low --> swap and use 96-bit muldiv */ + if (G_LIKELY (val <= G_MAXUINT32)) + return gst_util_uint64_scale_uint32_unchecked (num, (guint32) val, + (guint32) denom, correct); + } +#endif /* !defined (__x86_64__) && !defined (HAVE_UINT128_T) */ + + /* val is high and num is high --> use 128-bit muldiv */ + return gst_util_uint64_scale_uint64_unchecked (val, num, denom, correct); +} + +/** + * gst_util_uint64_scale: + * @val: the number to scale + * @num: the numerator of the scale ratio + * @denom: the denominator of the scale ratio + * + * Scale @val by the rational number @num / @denom, avoiding overflows and + * underflows and without loss of precision. + * + * This function can potentially be very slow if val and num are both + * greater than G_MAXUINT32. + * + * Returns: @val * @num / @denom. In the case of an overflow, this + * function returns G_MAXUINT64. If the result is not exactly + * representable as an integer it is truncated. See also + * gst_util_uint64_scale_round(), gst_util_uint64_scale_ceil(), + * gst_util_uint64_scale_int(), gst_util_uint64_scale_int_round(), + * gst_util_uint64_scale_int_ceil(). + */ +guint64 +gst_util_uint64_scale (guint64 val, guint64 num, guint64 denom) +{ + return _gst_util_uint64_scale (val, num, denom, 0); +} + +/** + * gst_util_uint64_scale_round: + * @val: the number to scale + * @num: the numerator of the scale ratio + * @denom: the denominator of the scale ratio + * + * Scale @val by the rational number @num / @denom, avoiding overflows and + * underflows and without loss of precision. + * + * This function can potentially be very slow if val and num are both + * greater than G_MAXUINT32. + * + * Returns: @val * @num / @denom. In the case of an overflow, this + * function returns G_MAXUINT64. If the result is not exactly + * representable as an integer, it is rounded to the nearest integer + * (half-way cases are rounded up). See also gst_util_uint64_scale(), + * gst_util_uint64_scale_ceil(), gst_util_uint64_scale_int(), + * gst_util_uint64_scale_int_round(), gst_util_uint64_scale_int_ceil(). + */ +guint64 +gst_util_uint64_scale_round (guint64 val, guint64 num, guint64 denom) +{ + return _gst_util_uint64_scale (val, num, denom, denom >> 1); +} + +/** + * gst_util_uint64_scale_ceil: + * @val: the number to scale + * @num: the numerator of the scale ratio + * @denom: the denominator of the scale ratio + * + * Scale @val by the rational number @num / @denom, avoiding overflows and + * underflows and without loss of precision. + * + * This function can potentially be very slow if val and num are both + * greater than G_MAXUINT32. + * + * Returns: @val * @num / @denom. In the case of an overflow, this + * function returns G_MAXUINT64. If the result is not exactly + * representable as an integer, it is rounded up. See also + * gst_util_uint64_scale(), gst_util_uint64_scale_round(), + * gst_util_uint64_scale_int(), gst_util_uint64_scale_int_round(), + * gst_util_uint64_scale_int_ceil(). + */ +guint64 +gst_util_uint64_scale_ceil (guint64 val, guint64 num, guint64 denom) +{ + return _gst_util_uint64_scale (val, num, denom, denom - 1); +} + +/* the guts of the gst_util_uint64_scale_int() variants */ +static guint64 +_gst_util_uint64_scale_int (guint64 val, gint num, gint denom, gint correct) +{ + g_return_val_if_fail (denom > 0, G_MAXUINT64); + g_return_val_if_fail (num >= 0, G_MAXUINT64); + + if (G_UNLIKELY (num == 0)) + return 0; + + if (G_UNLIKELY (num == denom)) + return val; + + if (val <= G_MAXUINT32) { + /* simple case. num and denom are not negative so casts are OK. when + * not truncating, the additions to the numerator cannot overflow + * because val*num <= G_MAXUINT32 * G_MAXINT32 < G_MAXUINT64 - + * G_MAXINT32, so there's room to add another gint32. */ + val *= (guint64) num; + /* add rounding correction */ + val += correct; + + return val / (guint64) denom; + } +#if !defined (__x86_64__) && !defined (HAVE_UINT128_T) + /* num and denom are not negative so casts are OK */ + return gst_util_uint64_scale_uint32_unchecked (val, (guint32) num, + (guint32) denom, (guint32) correct); +#else + /* always use full 128bits scale */ + return gst_util_uint64_scale_uint64_unchecked (val, num, denom, correct); +#endif +} + +/** + * gst_util_uint64_scale_int: + * @val: guint64 (such as a #GstClockTime) to scale. + * @num: numerator of the scale factor. + * @denom: denominator of the scale factor. + * + * Scale @val by the rational number @num / @denom, avoiding overflows and + * underflows and without loss of precision. @num must be non-negative and + * @denom must be positive. + * + * Returns: @val * @num / @denom. In the case of an overflow, this + * function returns G_MAXUINT64. If the result is not exactly + * representable as an integer, it is truncated. See also + * gst_util_uint64_scale_int_round(), gst_util_uint64_scale_int_ceil(), + * gst_util_uint64_scale(), gst_util_uint64_scale_round(), + * gst_util_uint64_scale_ceil(). + */ +guint64 +gst_util_uint64_scale_int (guint64 val, gint num, gint denom) +{ + return _gst_util_uint64_scale_int (val, num, denom, 0); +} + +/** + * gst_util_uint64_scale_int_round: + * @val: guint64 (such as a #GstClockTime) to scale. + * @num: numerator of the scale factor. + * @denom: denominator of the scale factor. + * + * Scale @val by the rational number @num / @denom, avoiding overflows and + * underflows and without loss of precision. @num must be non-negative and + * @denom must be positive. + * + * Returns: @val * @num / @denom. In the case of an overflow, this + * function returns G_MAXUINT64. If the result is not exactly + * representable as an integer, it is rounded to the nearest integer + * (half-way cases are rounded up). See also gst_util_uint64_scale_int(), + * gst_util_uint64_scale_int_ceil(), gst_util_uint64_scale(), + * gst_util_uint64_scale_round(), gst_util_uint64_scale_ceil(). + */ +guint64 +gst_util_uint64_scale_int_round (guint64 val, gint num, gint denom) +{ + /* we can use a shift to divide by 2 because denom is required to be + * positive. */ + return _gst_util_uint64_scale_int (val, num, denom, denom >> 1); +} + +/** + * gst_util_uint64_scale_int_ceil: + * @val: guint64 (such as a #GstClockTime) to scale. + * @num: numerator of the scale factor. + * @denom: denominator of the scale factor. + * + * Scale @val by the rational number @num / @denom, avoiding overflows and + * underflows and without loss of precision. @num must be non-negative and + * @denom must be positive. + * + * Returns: @val * @num / @denom. In the case of an overflow, this + * function returns G_MAXUINT64. If the result is not exactly + * representable as an integer, it is rounded up. See also + * gst_util_uint64_scale_int(), gst_util_uint64_scale_int_round(), + * gst_util_uint64_scale(), gst_util_uint64_scale_round(), + * gst_util_uint64_scale_ceil(). + */ +guint64 +gst_util_uint64_scale_int_ceil (guint64 val, gint num, gint denom) +{ + return _gst_util_uint64_scale_int (val, num, denom, denom - 1); +} + +/** + * gst_util_seqnum_next: + * + * Return a constantly incrementing sequence number. + * + * This function is used internally to GStreamer to be able to determine which + * events and messages are "the same". For example, elements may set the seqnum + * on a segment-done message to be the same as that of the last seek event, to + * indicate that event and the message correspond to the same segment. + * + * Returns: A constantly incrementing 32-bit unsigned integer, which might + * overflow back to 0 at some point. Use gst_util_seqnum_compare() to make sure + * you handle wraparound correctly. + * + * Since: 0.10.22 + */ +guint32 +gst_util_seqnum_next (void) +{ + static gint counter = 0; + return G_ATOMIC_INT_ADD (&counter, 1); +} + +/** + * gst_util_seqnum_compare: + * @s1: A sequence number. + * @s2: Another sequence number. + * + * Compare two sequence numbers, handling wraparound. + * + * The current implementation just returns (gint32)(@s1 - @s2). + * + * Returns: A negative number if @s1 is before @s2, 0 if they are equal, or a + * positive number if @s1 is after @s2. + * + * Since: 0.10.22 + */ +gint32 +gst_util_seqnum_compare (guint32 s1, guint32 s2) +{ + return (gint32) (s1 - s2); +} + +/* ----------------------------------------------------- + * + * The following code will be moved out of the main + * gstreamer library someday. + */ + +#include "gstpad.h" + +static void +string_append_indent (GString * str, gint count) +{ + gint xx; + + for (xx = 0; xx < count; xx++) + g_string_append_c (str, ' '); +} + +/** + * gst_print_pad_caps: + * @buf: the buffer to print the caps in + * @indent: initial indentation + * @pad: (transfer none): the pad to print the caps from + * + * Write the pad capabilities in a human readable format into + * the given GString. + */ +void +gst_print_pad_caps (GString * buf, gint indent, GstPad * pad) +{ + GstCaps *caps; + + caps = gst_pad_get_current_caps (pad); + + if (!caps) { + string_append_indent (buf, indent); + g_string_printf (buf, "%s:%s has no capabilities", + GST_DEBUG_PAD_NAME (pad)); + } else { + char *s; + + s = gst_caps_to_string (caps); + g_string_append (buf, s); + g_free (s); + + gst_caps_unref (caps); + } +} + +/** + * gst_print_element_args: + * @buf: the buffer to print the args in + * @indent: initial indentation + * @element: (transfer none): the element to print the args of + * + * Print the element argument in a human readable format in the given + * GString. + */ +void +gst_print_element_args (GString * buf, gint indent, GstElement * element) +{ + guint width; + GValue value = { 0, }; /* the important thing is that value.type = 0 */ + gchar *str = NULL; + GParamSpec *spec, **specs, **walk; + + specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (element), NULL); + + width = 0; + for (walk = specs; *walk; walk++) { + spec = *walk; + if (width < strlen (spec->name)) + width = strlen (spec->name); + } + + for (walk = specs; *walk; walk++) { + spec = *walk; + + if (spec->flags & G_PARAM_READABLE) { + g_value_init (&value, spec->value_type); + g_object_get_property (G_OBJECT (element), spec->name, &value); + str = g_strdup_value_contents (&value); + g_value_unset (&value); + } else { + str = g_strdup ("Parameter not readable."); + } + + string_append_indent (buf, indent); + g_string_append (buf, spec->name); + string_append_indent (buf, 2 + width - strlen (spec->name)); + g_string_append (buf, str); + g_string_append_c (buf, '\n'); + + g_free (str); + } + + g_free (specs); +} + +/** + * gst_element_create_all_pads: + * @element: (transfer none): a #GstElement to create pads for + * + * Creates a pad for each pad template that is always available. + * This function is only useful during object initialization of + * subclasses of #GstElement. + */ +void +gst_element_create_all_pads (GstElement * element) +{ + GList *padlist; + + /* FIXME: lock element */ + + padlist = + gst_element_class_get_pad_template_list (GST_ELEMENT_CLASS + (G_OBJECT_GET_CLASS (element))); + + while (padlist) { + GstPadTemplate *padtempl = (GstPadTemplate *) padlist->data; + + if (padtempl->presence == GST_PAD_ALWAYS) { + GstPad *pad; + + pad = gst_pad_new_from_template (padtempl, padtempl->name_template); + + gst_element_add_pad (element, pad); + } + padlist = padlist->next; + } +} + +/** + * gst_element_get_compatible_pad_template: + * @element: (transfer none): a #GstElement to get a compatible pad template for + * @compattempl: (transfer none): the #GstPadTemplate to find a compatible + * template for + * + * Retrieves a pad template from @element that is compatible with @compattempl. + * Pads from compatible templates can be linked together. + * + * Returns: (transfer none): a compatible #GstPadTemplate, or NULL if none + * was found. No unreferencing is necessary. + */ +GstPadTemplate * +gst_element_get_compatible_pad_template (GstElement * element, + GstPadTemplate * compattempl) +{ + GstPadTemplate *newtempl = NULL; + GList *padlist; + GstElementClass *class; + gboolean compatible; + + g_return_val_if_fail (element != NULL, NULL); + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + g_return_val_if_fail (compattempl != NULL, NULL); + + class = GST_ELEMENT_GET_CLASS (element); + + padlist = gst_element_class_get_pad_template_list (class); + + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "Looking for a suitable pad template in %s out of %d templates...", + GST_ELEMENT_NAME (element), g_list_length (padlist)); + + while (padlist) { + GstPadTemplate *padtempl = (GstPadTemplate *) padlist->data; + + /* Ignore name + * Ignore presence + * Check direction (must be opposite) + * Check caps + */ + GST_CAT_LOG (GST_CAT_CAPS, + "checking pad template %s", padtempl->name_template); + if (padtempl->direction != compattempl->direction) { + GST_CAT_DEBUG (GST_CAT_CAPS, + "compatible direction: found %s pad template \"%s\"", + padtempl->direction == GST_PAD_SRC ? "src" : "sink", + padtempl->name_template); + + GST_CAT_DEBUG (GST_CAT_CAPS, + "intersecting %" GST_PTR_FORMAT, GST_PAD_TEMPLATE_CAPS (compattempl)); + GST_CAT_DEBUG (GST_CAT_CAPS, + "..and %" GST_PTR_FORMAT, GST_PAD_TEMPLATE_CAPS (padtempl)); + + compatible = gst_caps_can_intersect (GST_PAD_TEMPLATE_CAPS (compattempl), + GST_PAD_TEMPLATE_CAPS (padtempl)); + + GST_CAT_DEBUG (GST_CAT_CAPS, "caps are %scompatible", + (compatible ? "" : "not ")); + + if (compatible) { + newtempl = padtempl; + break; + } + } + + padlist = g_list_next (padlist); + } + if (newtempl) + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "Returning new pad template %p", newtempl); + else + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "No compatible pad template found"); + + return newtempl; +} + +/** + * gst_element_get_pad_from_template: + * @element: (transfer none): a #GstElement. + * @templ: (transfer none): a #GstPadTemplate belonging to @element. + * + * Gets a pad from @element described by @templ. If the presence of @templ is + * #GST_PAD_REQUEST, requests a new pad. Can return %NULL for #GST_PAD_SOMETIMES + * templates. + * + * Returns: (transfer full): the #GstPad, or NULL if one could not be found + * or created. + */ +static GstPad * +gst_element_get_pad_from_template (GstElement * element, GstPadTemplate * templ) +{ + GstPad *ret = NULL; + GstPadPresence presence; + + /* If this function is ever exported, we need check the validity of `element' + * and `templ', and to make sure the template actually belongs to the + * element. */ + + presence = GST_PAD_TEMPLATE_PRESENCE (templ); + + switch (presence) { + case GST_PAD_ALWAYS: + case GST_PAD_SOMETIMES: + ret = gst_element_get_static_pad (element, templ->name_template); + if (!ret && presence == GST_PAD_ALWAYS) + g_warning + ("Element %s has an ALWAYS template %s, but no pad of the same name", + GST_OBJECT_NAME (element), templ->name_template); + break; + + case GST_PAD_REQUEST: + ret = gst_element_request_pad (element, templ, NULL, NULL); + break; + } + + return ret; +} + +/* + * gst_element_request_compatible_pad: + * @element: a #GstElement. + * @templ: the #GstPadTemplate to which the new pad should be able to link. + * + * Requests a pad from @element. The returned pad should be unlinked and + * compatible with @templ. Might return an existing pad, or request a new one. + * + * Returns: a #GstPad, or %NULL if one could not be found or created. + */ +static GstPad * +gst_element_request_compatible_pad (GstElement * element, + GstPadTemplate * templ) +{ + GstPadTemplate *templ_new; + GstPad *pad = NULL; + + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + g_return_val_if_fail (GST_IS_PAD_TEMPLATE (templ), NULL); + + /* FIXME: should really loop through the templates, testing each for + * compatibility and pad availability. */ + templ_new = gst_element_get_compatible_pad_template (element, templ); + if (templ_new) + pad = gst_element_get_pad_from_template (element, templ_new); + + /* This can happen for non-request pads. No need to unref. */ + if (pad && GST_PAD_PEER (pad)) + pad = NULL; + + return pad; +} + +/* + * Checks if the source pad and the sink pad can be linked. + * Both @srcpad and @sinkpad must be unlinked and have a parent. + */ +static gboolean +gst_pad_check_link (GstPad * srcpad, GstPad * sinkpad) +{ + /* FIXME This function is gross. It's almost a direct copy of + * gst_pad_link_filtered(). Any decent programmer would attempt + * to merge the two functions, which I will do some day. --ds + */ + + /* generic checks */ + g_return_val_if_fail (GST_IS_PAD (srcpad), FALSE); + g_return_val_if_fail (GST_IS_PAD (sinkpad), FALSE); + + GST_CAT_INFO (GST_CAT_PADS, "trying to link %s:%s and %s:%s", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad)); + + /* FIXME: shouldn't we convert this to g_return_val_if_fail? */ + if (GST_PAD_PEER (srcpad) != NULL) { + GST_CAT_INFO (GST_CAT_PADS, "Source pad %s:%s has a peer, failed", + GST_DEBUG_PAD_NAME (srcpad)); + return FALSE; + } + if (GST_PAD_PEER (sinkpad) != NULL) { + GST_CAT_INFO (GST_CAT_PADS, "Sink pad %s:%s has a peer, failed", + GST_DEBUG_PAD_NAME (sinkpad)); + return FALSE; + } + if (!GST_PAD_IS_SRC (srcpad)) { + GST_CAT_INFO (GST_CAT_PADS, "Src pad %s:%s is not source pad, failed", + GST_DEBUG_PAD_NAME (srcpad)); + return FALSE; + } + if (!GST_PAD_IS_SINK (sinkpad)) { + GST_CAT_INFO (GST_CAT_PADS, "Sink pad %s:%s is not sink pad, failed", + GST_DEBUG_PAD_NAME (sinkpad)); + return FALSE; + } + if (GST_PAD_PARENT (srcpad) == NULL) { + GST_CAT_INFO (GST_CAT_PADS, "Src pad %s:%s has no parent, failed", + GST_DEBUG_PAD_NAME (srcpad)); + return FALSE; + } + if (GST_PAD_PARENT (sinkpad) == NULL) { + GST_CAT_INFO (GST_CAT_PADS, "Sink pad %s:%s has no parent, failed", + GST_DEBUG_PAD_NAME (srcpad)); + return FALSE; + } + + return TRUE; +} + +/** + * gst_element_get_compatible_pad: + * @element: (transfer none): a #GstElement in which the pad should be found. + * @pad: (transfer none): the #GstPad to find a compatible one for. + * @caps: the #GstCaps to use as a filter. + * + * Looks for an unlinked pad to which the given pad can link. It is not + * guaranteed that linking the pads will work, though it should work in most + * cases. + * + * This function will first attempt to find a compatible unlinked ALWAYS pad, + * and if none can be found, it will request a compatible REQUEST pad by looking + * at the templates of @element. + * + * Returns: (transfer full): the #GstPad to which a link can be made, or %NULL + * if one cannot be found. gst_object_unref() after usage. + */ +GstPad * +gst_element_get_compatible_pad (GstElement * element, GstPad * pad, + const GstCaps * caps) +{ + GstIterator *pads; + GstPadTemplate *templ; + GstCaps *templcaps; + GstPad *foundpad = NULL; + gboolean done; + GValue padptr = { 0, }; + + g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "finding pad in %s compatible with %s:%s", + GST_ELEMENT_NAME (element), GST_DEBUG_PAD_NAME (pad)); + + g_return_val_if_fail (GST_PAD_PEER (pad) == NULL, NULL); + + done = FALSE; + + /* try to get an existing unlinked pad */ + if (GST_PAD_IS_SRC (pad)) { + pads = gst_element_iterate_sink_pads (element); + } else if (GST_PAD_IS_SINK (pad)) { + pads = gst_element_iterate_src_pads (element); + } else { + pads = gst_element_iterate_pads (element); + } + + while (!done) { + switch (gst_iterator_next (pads, &padptr)) { + case GST_ITERATOR_OK: + { + GstPad *peer; + GstPad *current; + GstPad *srcpad; + GstPad *sinkpad; + + current = g_value_get_object (&padptr); + + GST_CAT_LOG (GST_CAT_ELEMENT_PADS, "examining pad %s:%s", + GST_DEBUG_PAD_NAME (current)); + + if (GST_PAD_IS_SRC (current)) { + srcpad = current; + sinkpad = pad; + } else { + srcpad = pad; + sinkpad = current; + } + peer = gst_pad_get_peer (current); + + if (peer == NULL && gst_pad_check_link (srcpad, sinkpad)) { + GstCaps *temp, *intersection; + gboolean compatible; + + /* Now check if the two pads' caps are compatible */ + temp = gst_pad_get_caps (pad, NULL); + if (caps) { + intersection = gst_caps_intersect (temp, caps); + gst_caps_unref (temp); + } else { + intersection = temp; + } + + temp = gst_pad_get_caps (current, NULL); + compatible = gst_caps_can_intersect (temp, intersection); + gst_caps_unref (temp); + gst_caps_unref (intersection); + + if (compatible) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "found existing unlinked compatible pad %s:%s", + GST_DEBUG_PAD_NAME (current)); + gst_iterator_free (pads); + + current = gst_object_ref (current); + g_value_unset (&padptr); + + return current; + } else { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "incompatible pads"); + } + } else { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "already linked or cannot be linked (peer = %p)", peer); + } + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "unreffing pads"); + + g_value_reset (&padptr); + if (peer) + gst_object_unref (peer); + break; + } + case GST_ITERATOR_DONE: + done = TRUE; + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + break; + case GST_ITERATOR_ERROR: + g_assert_not_reached (); + break; + } + } + g_value_unset (&padptr); + gst_iterator_free (pads); + + GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element, + "Could not find a compatible unlinked always pad to link to %s:%s, now checking request pads", + GST_DEBUG_PAD_NAME (pad)); + + /* try to create a new one */ + /* requesting is a little crazy, we need a template. Let's create one */ + /* FIXME: why not gst_pad_get_pad_template (pad); */ + templcaps = gst_pad_get_caps (pad, NULL); + templ = gst_pad_template_new ((gchar *) GST_PAD_NAME (pad), + GST_PAD_DIRECTION (pad), GST_PAD_ALWAYS, templcaps); + gst_caps_unref (templcaps); + + foundpad = gst_element_request_compatible_pad (element, templ); + gst_object_unref (templ); + + if (foundpad) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "found existing request pad %s:%s", GST_DEBUG_PAD_NAME (foundpad)); + return foundpad; + } + + GST_CAT_INFO_OBJECT (GST_CAT_ELEMENT_PADS, element, + "Could not find a compatible pad to link to %s:%s", + GST_DEBUG_PAD_NAME (pad)); + return NULL; +} + +/** + * gst_element_state_get_name: + * @state: a #GstState to get the name of. + * + * Gets a string representing the given state. + * + * Returns: (transfer none): a string with the name of the state. + */ +const gchar * +gst_element_state_get_name (GstState state) +{ + switch (state) { + case GST_STATE_VOID_PENDING: + return "VOID_PENDING"; + case GST_STATE_NULL: + return "NULL"; + case GST_STATE_READY: + return "READY"; + case GST_STATE_PLAYING: + return "PLAYING"; + case GST_STATE_PAUSED: + return "PAUSED"; + default: + /* This is a memory leak */ + return g_strdup_printf ("UNKNOWN!(%d)", state); + } +} + +/** + * gst_element_state_change_return_get_name: + * @state_ret: a #GstStateChangeReturn to get the name of. + * + * Gets a string representing the given state change result. + * + * Returns: (transfer none): a string with the name of the state + * result. + * + * Since: 0.10.11 + */ +const gchar * +gst_element_state_change_return_get_name (GstStateChangeReturn state_ret) +{ + switch (state_ret) { + case GST_STATE_CHANGE_FAILURE: + return "FAILURE"; + case GST_STATE_CHANGE_SUCCESS: + return "SUCCESS"; + case GST_STATE_CHANGE_ASYNC: + return "ASYNC"; + case GST_STATE_CHANGE_NO_PREROLL: + return "NO PREROLL"; + default: + /* This is a memory leak */ + return g_strdup_printf ("UNKNOWN!(%d)", state_ret); + } +} + + +static gboolean +gst_element_factory_can_accept_all_caps_in_direction (GstElementFactory * + factory, const GstCaps * caps, GstPadDirection direction) +{ + GList *templates; + + g_return_val_if_fail (factory != NULL, FALSE); + g_return_val_if_fail (caps != NULL, FALSE); + + templates = factory->staticpadtemplates; + + while (templates) { + GstStaticPadTemplate *template = (GstStaticPadTemplate *) templates->data; + + if (template->direction == direction) { + GstCaps *templcaps = gst_static_caps_get (&template->static_caps); + + if (gst_caps_is_always_compatible (caps, templcaps)) { + gst_caps_unref (templcaps); + return TRUE; + } + gst_caps_unref (templcaps); + } + templates = g_list_next (templates); + } + + return FALSE; +} + +static gboolean +gst_element_factory_can_accept_any_caps_in_direction (GstElementFactory * + factory, const GstCaps * caps, GstPadDirection direction) +{ + GList *templates; + + g_return_val_if_fail (factory != NULL, FALSE); + g_return_val_if_fail (caps != NULL, FALSE); + + templates = factory->staticpadtemplates; + + while (templates) { + GstStaticPadTemplate *template = (GstStaticPadTemplate *) templates->data; + + if (template->direction == direction) { + GstCaps *templcaps = gst_static_caps_get (&template->static_caps); + + if (gst_caps_can_intersect (caps, templcaps)) { + gst_caps_unref (templcaps); + return TRUE; + } + gst_caps_unref (templcaps); + } + templates = g_list_next (templates); + } + + return FALSE; +} + +/** + * gst_element_factory_can_sink_all_caps: + * @factory: factory to query + * @caps: the caps to check + * + * Checks if the factory can sink all possible capabilities. + * + * Returns: %TRUE if the caps are fully compatible. + * + * Since: 0.10.33 + */ +gboolean +gst_element_factory_can_sink_all_caps (GstElementFactory * factory, + const GstCaps * caps) +{ + return gst_element_factory_can_accept_all_caps_in_direction (factory, caps, + GST_PAD_SINK); +} + +/** + * gst_element_factory_can_src_all_caps: + * @factory: factory to query + * @caps: the caps to check + * + * Checks if the factory can src all possible capabilities. + * + * Returns: %TRUE if the caps are fully compatible. + * + * Since: 0.10.33 + */ +gboolean +gst_element_factory_can_src_all_caps (GstElementFactory * factory, + const GstCaps * caps) +{ + return gst_element_factory_can_accept_all_caps_in_direction (factory, caps, + GST_PAD_SRC); +} + +/** + * gst_element_factory_can_sink_any_caps: + * @factory: factory to query + * @caps: the caps to check + * + * Checks if the factory can sink any possible capability. + * + * Returns: %TRUE if the caps have a common subset. + * + * Since: 0.10.33 + */ +gboolean +gst_element_factory_can_sink_any_caps (GstElementFactory * factory, + const GstCaps * caps) +{ + return gst_element_factory_can_accept_any_caps_in_direction (factory, caps, + GST_PAD_SINK); +} + +/** + * gst_element_factory_can_src_any_caps: + * @factory: factory to query + * @caps: the caps to check + * + * Checks if the factory can src any possible capability. + * + * Returns: %TRUE if the caps have a common subset. + * + * Since: 0.10.33 + */ +gboolean +gst_element_factory_can_src_any_caps (GstElementFactory * factory, + const GstCaps * caps) +{ + return gst_element_factory_can_accept_any_caps_in_direction (factory, caps, + GST_PAD_SRC); +} + +/* if return val is true, *direct_child is a caller-owned ref on the direct + * child of ancestor that is part of object's ancestry */ +static gboolean +object_has_ancestor (GstObject * object, GstObject * ancestor, + GstObject ** direct_child) +{ + GstObject *child, *parent; + + if (direct_child) + *direct_child = NULL; + + child = gst_object_ref (object); + parent = gst_object_get_parent (object); + + while (parent) { + if (ancestor == parent) { + if (direct_child) + *direct_child = child; + else + gst_object_unref (child); + gst_object_unref (parent); + return TRUE; + } + + gst_object_unref (child); + child = parent; + parent = gst_object_get_parent (parent); + } + + gst_object_unref (child); + + return FALSE; +} + +/* caller owns return */ +static GstObject * +find_common_root (GstObject * o1, GstObject * o2) +{ + GstObject *top = o1; + GstObject *kid1, *kid2; + GstObject *root = NULL; + + while (GST_OBJECT_PARENT (top)) + top = GST_OBJECT_PARENT (top); + + /* the itsy-bitsy spider... */ + + if (!object_has_ancestor (o2, top, &kid2)) + return NULL; + + root = gst_object_ref (top); + while (TRUE) { + if (!object_has_ancestor (o1, kid2, &kid1)) { + gst_object_unref (kid2); + return root; + } + root = kid2; + if (!object_has_ancestor (o2, kid1, &kid2)) { + gst_object_unref (kid1); + return root; + } + root = kid1; + } +} + +/* caller does not own return */ +static GstPad * +ghost_up (GstElement * e, GstPad * pad) +{ + static gint ghost_pad_index = 0; + GstPad *gpad; + gchar *name; + GstState current; + GstState next; + GstObject *parent = GST_OBJECT_PARENT (e); + + name = g_strdup_printf ("ghost%d", ghost_pad_index++); + gpad = gst_ghost_pad_new (name, pad); + g_free (name); + + GST_STATE_LOCK (e); + gst_element_get_state (e, ¤t, &next, 0); + + if (current > GST_STATE_READY || next == GST_STATE_PAUSED) + gst_pad_set_active (gpad, TRUE); + + if (!gst_element_add_pad ((GstElement *) parent, gpad)) { + g_warning ("Pad named %s already exists in element %s\n", + GST_OBJECT_NAME (gpad), GST_OBJECT_NAME (parent)); + gst_object_unref ((GstObject *) gpad); + GST_STATE_UNLOCK (e); + return NULL; + } + GST_STATE_UNLOCK (e); + + return gpad; +} + +static void +remove_pad (gpointer ppad, gpointer unused) +{ + GstPad *pad = ppad; + + if (!gst_element_remove_pad ((GstElement *) GST_OBJECT_PARENT (pad), pad)) + g_warning ("Couldn't remove pad %s from element %s", + GST_OBJECT_NAME (pad), GST_OBJECT_NAME (GST_OBJECT_PARENT (pad))); +} + +static gboolean +prepare_link_maybe_ghosting (GstPad ** src, GstPad ** sink, + GSList ** pads_created) +{ + GstObject *root; + GstObject *e1, *e2; + GSList *pads_created_local = NULL; + + g_assert (pads_created); + + e1 = GST_OBJECT_PARENT (*src); + e2 = GST_OBJECT_PARENT (*sink); + + if (G_UNLIKELY (e1 == NULL)) { + GST_WARNING ("Trying to ghost a pad that doesn't have a parent: %" + GST_PTR_FORMAT, *src); + return FALSE; + } + if (G_UNLIKELY (e2 == NULL)) { + GST_WARNING ("Trying to ghost a pad that doesn't have a parent: %" + GST_PTR_FORMAT, *sink); + return FALSE; + } + + if (GST_OBJECT_PARENT (e1) == GST_OBJECT_PARENT (e2)) { + GST_CAT_INFO (GST_CAT_PADS, "%s and %s in same bin, no need for ghost pads", + GST_OBJECT_NAME (e1), GST_OBJECT_NAME (e2)); + return TRUE; + } + + GST_CAT_INFO (GST_CAT_PADS, "%s and %s not in same bin, making ghost pads", + GST_OBJECT_NAME (e1), GST_OBJECT_NAME (e2)); + + /* we need to setup some ghost pads */ + root = find_common_root (e1, e2); + if (!root) { + g_warning ("Trying to connect elements that don't share a common " + "ancestor: %s and %s", GST_ELEMENT_NAME (e1), GST_ELEMENT_NAME (e2)); + return FALSE; + } + + while (GST_OBJECT_PARENT (e1) != root) { + *src = ghost_up ((GstElement *) e1, *src); + if (!*src) + goto cleanup_fail; + e1 = GST_OBJECT_PARENT (*src); + pads_created_local = g_slist_prepend (pads_created_local, *src); + } + while (GST_OBJECT_PARENT (e2) != root) { + *sink = ghost_up ((GstElement *) e2, *sink); + if (!*sink) + goto cleanup_fail; + e2 = GST_OBJECT_PARENT (*sink); + pads_created_local = g_slist_prepend (pads_created_local, *sink); + } + + gst_object_unref (root); + *pads_created = g_slist_concat (*pads_created, pads_created_local); + return TRUE; + +cleanup_fail: + gst_object_unref (root); + g_slist_foreach (pads_created_local, remove_pad, NULL); + g_slist_free (pads_created_local); + return FALSE; +} + +static gboolean +pad_link_maybe_ghosting (GstPad * src, GstPad * sink, GstPadLinkCheck flags) +{ + GSList *pads_created = NULL; + gboolean ret; + + if (!prepare_link_maybe_ghosting (&src, &sink, &pads_created)) { + ret = FALSE; + } else { + ret = (gst_pad_link_full (src, sink, flags) == GST_PAD_LINK_OK); + } + + if (!ret) { + g_slist_foreach (pads_created, remove_pad, NULL); + } + g_slist_free (pads_created); + + return ret; +} + +/** + * gst_element_link_pads_full: + * @src: a #GstElement containing the source pad. + * @srcpadname: (allow-none): the name of the #GstPad in source element + * or NULL for any pad. + * @dest: (transfer none): the #GstElement containing the destination pad. + * @destpadname: (allow-none): the name of the #GstPad in destination element, + * or NULL for any pad. + * @flags: the #GstPadLinkCheck to be performed when linking pads. + * + * Links the two named pads of the source and destination elements. + * Side effect is that if one of the pads has no parent, it becomes a + * child of the parent of the other element. If they have different + * parents, the link fails. + * + * Calling gst_element_link_pads_full() with @flags == %GST_PAD_LINK_CHECK_DEFAULT + * is the same as calling gst_element_link_pads() and the recommended way of + * linking pads with safety checks applied. + * + * This is a convenience function for gst_pad_link_full(). + * + * Returns: TRUE if the pads could be linked, FALSE otherwise. + * + * Since: 0.10.30 + */ +gboolean +gst_element_link_pads_full (GstElement * src, const gchar * srcpadname, + GstElement * dest, const gchar * destpadname, GstPadLinkCheck flags) +{ + const GList *srcpads, *destpads, *srctempls, *desttempls, *l; + GstPad *srcpad, *destpad; + GstPadTemplate *srctempl, *desttempl; + GstElementClass *srcclass, *destclass; + + /* checks */ + g_return_val_if_fail (GST_IS_ELEMENT (src), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (dest), FALSE); + + GST_CAT_INFO (GST_CAT_ELEMENT_PADS, + "trying to link element %s:%s to element %s:%s", GST_ELEMENT_NAME (src), + srcpadname ? srcpadname : "(any)", GST_ELEMENT_NAME (dest), + destpadname ? destpadname : "(any)"); + + /* get a src pad */ + if (srcpadname) { + /* name specified, look it up */ + if (!(srcpad = gst_element_get_static_pad (src, srcpadname))) + srcpad = gst_element_get_request_pad (src, srcpadname); + if (!srcpad) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "no pad %s:%s", + GST_ELEMENT_NAME (src), srcpadname); + return FALSE; + } else { + if (!(GST_PAD_DIRECTION (srcpad) == GST_PAD_SRC)) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "pad %s:%s is no src pad", + GST_DEBUG_PAD_NAME (srcpad)); + gst_object_unref (srcpad); + return FALSE; + } + if (GST_PAD_PEER (srcpad) != NULL) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "pad %s:%s is already linked to %s:%s", GST_DEBUG_PAD_NAME (srcpad), + GST_DEBUG_PAD_NAME (GST_PAD_PEER (srcpad))); + gst_object_unref (srcpad); + return FALSE; + } + } + srcpads = NULL; + } else { + /* no name given, get the first available pad */ + GST_OBJECT_LOCK (src); + srcpads = GST_ELEMENT_PADS (src); + srcpad = srcpads ? GST_PAD_CAST (srcpads->data) : NULL; + if (srcpad) + gst_object_ref (srcpad); + GST_OBJECT_UNLOCK (src); + } + + /* get a destination pad */ + if (destpadname) { + /* name specified, look it up */ + if (!(destpad = gst_element_get_static_pad (dest, destpadname))) + destpad = gst_element_get_request_pad (dest, destpadname); + if (!destpad) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "no pad %s:%s", + GST_ELEMENT_NAME (dest), destpadname); + return FALSE; + } else { + if (!(GST_PAD_DIRECTION (destpad) == GST_PAD_SINK)) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "pad %s:%s is no sink pad", + GST_DEBUG_PAD_NAME (destpad)); + gst_object_unref (destpad); + return FALSE; + } + if (GST_PAD_PEER (destpad) != NULL) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "pad %s:%s is already linked to %s:%s", + GST_DEBUG_PAD_NAME (destpad), + GST_DEBUG_PAD_NAME (GST_PAD_PEER (destpad))); + gst_object_unref (destpad); + return FALSE; + } + } + destpads = NULL; + } else { + /* no name given, get the first available pad */ + GST_OBJECT_LOCK (dest); + destpads = GST_ELEMENT_PADS (dest); + destpad = destpads ? GST_PAD_CAST (destpads->data) : NULL; + if (destpad) + gst_object_ref (destpad); + GST_OBJECT_UNLOCK (dest); + } + + if (srcpadname && destpadname) { + gboolean result; + + /* two explicitly specified pads */ + result = pad_link_maybe_ghosting (srcpad, destpad, flags); + + gst_object_unref (srcpad); + gst_object_unref (destpad); + + return result; + } + + if (srcpad) { + /* loop through the allowed pads in the source, trying to find a + * compatible destination pad */ + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "looping through allowed src and dest pads"); + do { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "trying src pad %s:%s", + GST_DEBUG_PAD_NAME (srcpad)); + if ((GST_PAD_DIRECTION (srcpad) == GST_PAD_SRC) && + (GST_PAD_PEER (srcpad) == NULL)) { + GstPad *temp; + + if (destpadname) { + temp = destpad; + gst_object_ref (temp); + } else { + temp = gst_element_get_compatible_pad (dest, srcpad, NULL); + } + + if (temp && pad_link_maybe_ghosting (srcpad, temp, flags)) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "linked pad %s:%s to pad %s:%s", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (temp)); + if (destpad) + gst_object_unref (destpad); + gst_object_unref (srcpad); + gst_object_unref (temp); + return TRUE; + } + + if (temp) { + gst_object_unref (temp); + } + } + /* find a better way for this mess */ + if (srcpads) { + srcpads = g_list_next (srcpads); + if (srcpads) { + gst_object_unref (srcpad); + srcpad = GST_PAD_CAST (srcpads->data); + gst_object_ref (srcpad); + } + } + } while (srcpads); + } + if (srcpadname) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "no link possible from %s:%s to %s", + GST_DEBUG_PAD_NAME (srcpad), GST_ELEMENT_NAME (dest)); + if (destpad) + gst_object_unref (destpad); + destpad = NULL; + } + if (srcpad) + gst_object_unref (srcpad); + srcpad = NULL; + + if (destpad) { + /* loop through the existing pads in the destination */ + do { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "trying dest pad %s:%s", + GST_DEBUG_PAD_NAME (destpad)); + if ((GST_PAD_DIRECTION (destpad) == GST_PAD_SINK) && + (GST_PAD_PEER (destpad) == NULL)) { + GstPad *temp = gst_element_get_compatible_pad (src, destpad, NULL); + + if (temp && pad_link_maybe_ghosting (temp, destpad, flags)) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "linked pad %s:%s to pad %s:%s", + GST_DEBUG_PAD_NAME (temp), GST_DEBUG_PAD_NAME (destpad)); + gst_object_unref (temp); + gst_object_unref (destpad); + return TRUE; + } + if (temp) { + gst_object_unref (temp); + } + } + if (destpads) { + destpads = g_list_next (destpads); + if (destpads) { + gst_object_unref (destpad); + destpad = GST_PAD_CAST (destpads->data); + gst_object_ref (destpad); + } + } + } while (destpads); + } + + if (destpadname) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "no link possible from %s to %s:%s", + GST_ELEMENT_NAME (src), GST_DEBUG_PAD_NAME (destpad)); + gst_object_unref (destpad); + return FALSE; + } else { + if (destpad) + gst_object_unref (destpad); + destpad = NULL; + } + + srcclass = GST_ELEMENT_GET_CLASS (src); + destclass = GST_ELEMENT_GET_CLASS (dest); + + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "we might have request pads on both sides, checking..."); + srctempls = gst_element_class_get_pad_template_list (srcclass); + desttempls = gst_element_class_get_pad_template_list (destclass); + + if (srctempls && desttempls) { + while (srctempls) { + srctempl = (GstPadTemplate *) srctempls->data; + if (srctempl->presence == GST_PAD_REQUEST) { + for (l = desttempls; l; l = l->next) { + desttempl = (GstPadTemplate *) l->data; + if (desttempl->presence == GST_PAD_REQUEST && + desttempl->direction != srctempl->direction) { + GstCaps *srccaps, *destcaps; + + srccaps = gst_pad_template_get_caps (srctempl); + destcaps = gst_pad_template_get_caps (desttempl); + if (gst_caps_is_always_compatible (srccaps, destcaps)) { + srcpad = + gst_element_request_pad (src, srctempl, + srctempl->name_template, NULL); + destpad = + gst_element_request_pad (dest, desttempl, + desttempl->name_template, NULL); + if (srcpad && destpad + && pad_link_maybe_ghosting (srcpad, destpad, flags)) { + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "linked pad %s:%s to pad %s:%s", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (destpad)); + gst_object_unref (srcpad); + gst_object_unref (destpad); + gst_caps_unref (srccaps); + gst_caps_unref (destcaps); + return TRUE; + } + /* it failed, so we release the request pads */ + if (srcpad) + gst_element_release_request_pad (src, srcpad); + if (destpad) + gst_element_release_request_pad (dest, destpad); + } + gst_caps_unref (srccaps); + gst_caps_unref (destcaps); + } + } + } + srctempls = srctempls->next; + } + } + + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "no link possible from %s to %s", + GST_ELEMENT_NAME (src), GST_ELEMENT_NAME (dest)); + return FALSE; +} + +/** + * gst_element_link_pads: + * @src: a #GstElement containing the source pad. + * @srcpadname: (allow-none): the name of the #GstPad in source element + * or NULL for any pad. + * @dest: (transfer none): the #GstElement containing the destination pad. + * @destpadname: (allow-none): the name of the #GstPad in destination element, + * or NULL for any pad. + * + * Links the two named pads of the source and destination elements. + * Side effect is that if one of the pads has no parent, it becomes a + * child of the parent of the other element. If they have different + * parents, the link fails. + * + * Returns: TRUE if the pads could be linked, FALSE otherwise. + */ +gboolean +gst_element_link_pads (GstElement * src, const gchar * srcpadname, + GstElement * dest, const gchar * destpadname) +{ + return gst_element_link_pads_full (src, srcpadname, dest, destpadname, + GST_PAD_LINK_CHECK_DEFAULT); +} + +/** + * gst_element_link_pads_filtered: + * @src: a #GstElement containing the source pad. + * @srcpadname: (allow-none): the name of the #GstPad in source element + * or NULL for any pad. + * @dest: (transfer none): the #GstElement containing the destination pad. + * @destpadname: (allow-none): the name of the #GstPad in destination element + * or NULL for any pad. + * @filter: (transfer none) (allow-none): the #GstCaps to filter the link, + * or #NULL for no filter. + * + * Links the two named pads of the source and destination elements. Side effect + * is that if one of the pads has no parent, it becomes a child of the parent of + * the other element. If they have different parents, the link fails. If @caps + * is not #NULL, makes sure that the caps of the link is a subset of @caps. + * + * Returns: TRUE if the pads could be linked, FALSE otherwise. + */ +gboolean +gst_element_link_pads_filtered (GstElement * src, const gchar * srcpadname, + GstElement * dest, const gchar * destpadname, GstCaps * filter) +{ + /* checks */ + g_return_val_if_fail (GST_IS_ELEMENT (src), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (dest), FALSE); + g_return_val_if_fail (filter == NULL || GST_IS_CAPS (filter), FALSE); + + if (filter) { + GstElement *capsfilter; + GstObject *parent; + GstState state, pending; + gboolean lr1, lr2; + + capsfilter = gst_element_factory_make ("capsfilter", NULL); + if (!capsfilter) { + GST_ERROR ("Could not make a capsfilter"); + return FALSE; + } + + parent = gst_object_get_parent (GST_OBJECT (src)); + g_return_val_if_fail (GST_IS_BIN (parent), FALSE); + + gst_element_get_state (GST_ELEMENT_CAST (parent), &state, &pending, 0); + + if (!gst_bin_add (GST_BIN (parent), capsfilter)) { + GST_ERROR ("Could not add capsfilter"); + gst_object_unref (capsfilter); + gst_object_unref (parent); + return FALSE; + } + + if (pending != GST_STATE_VOID_PENDING) + state = pending; + + gst_element_set_state (capsfilter, state); + + gst_object_unref (parent); + + g_object_set (capsfilter, "caps", filter, NULL); + + lr1 = gst_element_link_pads (src, srcpadname, capsfilter, "sink"); + lr2 = gst_element_link_pads (capsfilter, "src", dest, destpadname); + if (lr1 && lr2) { + return TRUE; + } else { + if (!lr1) { + GST_INFO ("Could not link pads: %s:%s - capsfilter:sink", + GST_ELEMENT_NAME (src), srcpadname); + } else { + GST_INFO ("Could not link pads: capsfilter:src - %s:%s", + GST_ELEMENT_NAME (dest), destpadname); + } + gst_element_set_state (capsfilter, GST_STATE_NULL); + /* this will unlink and unref as appropriate */ + gst_bin_remove (GST_BIN (GST_OBJECT_PARENT (capsfilter)), capsfilter); + return FALSE; + } + } else { + if (gst_element_link_pads (src, srcpadname, dest, destpadname)) { + return TRUE; + } else { + GST_INFO ("Could not link pads: %s:%s - %s:%s", GST_ELEMENT_NAME (src), + srcpadname, GST_ELEMENT_NAME (dest), destpadname); + return FALSE; + } + } +} + +/** + * gst_element_link: + * @src: (transfer none): a #GstElement containing the source pad. + * @dest: (transfer none): the #GstElement containing the destination pad. + * + * Links @src to @dest. The link must be from source to + * destination; the other direction will not be tried. The function looks for + * existing pads that aren't linked yet. It will request new pads if necessary. + * Such pads need to be released manually when unlinking. + * If multiple links are possible, only one is established. + * + * Make sure you have added your elements to a bin or pipeline with + * gst_bin_add() before trying to link them. + * + * Returns: TRUE if the elements could be linked, FALSE otherwise. + */ +gboolean +gst_element_link (GstElement * src, GstElement * dest) +{ + return gst_element_link_pads (src, NULL, dest, NULL); +} + +/** + * gst_element_link_many: + * @element_1: (transfer none): the first #GstElement in the link chain. + * @element_2: (transfer none): the second #GstElement in the link chain. + * @...: the NULL-terminated list of elements to link in order. + * + * Chain together a series of elements. Uses gst_element_link(). + * Make sure you have added your elements to a bin or pipeline with + * gst_bin_add() before trying to link them. + * + * Returns: TRUE on success, FALSE otherwise. + */ +gboolean +gst_element_link_many (GstElement * element_1, GstElement * element_2, ...) +{ + gboolean res = TRUE; + va_list args; + + g_return_val_if_fail (GST_IS_ELEMENT (element_1), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (element_2), FALSE); + + va_start (args, element_2); + + while (element_2) { + if (!gst_element_link (element_1, element_2)) { + res = FALSE; + break; + } + + element_1 = element_2; + element_2 = va_arg (args, GstElement *); + } + + va_end (args); + + return res; +} + +/** + * gst_element_link_filtered: + * @src: a #GstElement containing the source pad. + * @dest: (transfer none): the #GstElement containing the destination pad. + * @filter: (transfer none) (allow-none): the #GstCaps to filter the link, + * or #NULL for no filter. + * + * Links @src to @dest using the given caps as filtercaps. + * The link must be from source to + * destination; the other direction will not be tried. The function looks for + * existing pads that aren't linked yet. It will request new pads if necessary. + * If multiple links are possible, only one is established. + * + * Make sure you have added your elements to a bin or pipeline with + * gst_bin_add() before trying to link them. + * + * Returns: TRUE if the pads could be linked, FALSE otherwise. + */ +gboolean +gst_element_link_filtered (GstElement * src, GstElement * dest, + GstCaps * filter) +{ + return gst_element_link_pads_filtered (src, NULL, dest, NULL, filter); +} + +/** + * gst_element_unlink_pads: + * @src: a (transfer none): #GstElement containing the source pad. + * @srcpadname: the name of the #GstPad in source element. + * @dest: (transfer none): a #GstElement containing the destination pad. + * @destpadname: the name of the #GstPad in destination element. + * + * Unlinks the two named pads of the source and destination elements. + * + * This is a convenience function for gst_pad_unlink(). + */ +void +gst_element_unlink_pads (GstElement * src, const gchar * srcpadname, + GstElement * dest, const gchar * destpadname) +{ + GstPad *srcpad, *destpad; + gboolean srcrequest, destrequest; + + srcrequest = destrequest = FALSE; + + g_return_if_fail (src != NULL); + g_return_if_fail (GST_IS_ELEMENT (src)); + g_return_if_fail (srcpadname != NULL); + g_return_if_fail (dest != NULL); + g_return_if_fail (GST_IS_ELEMENT (dest)); + g_return_if_fail (destpadname != NULL); + + /* obtain the pads requested */ + if (!(srcpad = gst_element_get_static_pad (src, srcpadname))) + if ((srcpad = gst_element_get_request_pad (src, srcpadname))) + srcrequest = TRUE; + if (srcpad == NULL) { + GST_WARNING_OBJECT (src, "source element has no pad \"%s\"", srcpadname); + return; + } + if (!(destpad = gst_element_get_static_pad (dest, destpadname))) + if ((destpad = gst_element_get_request_pad (dest, destpadname))) + destrequest = TRUE; + if (destpad == NULL) { + GST_WARNING_OBJECT (dest, "destination element has no pad \"%s\"", + destpadname); + goto free_src; + } + + /* we're satisfied they can be unlinked, let's do it */ + gst_pad_unlink (srcpad, destpad); + + if (destrequest) + gst_element_release_request_pad (dest, destpad); + gst_object_unref (destpad); + +free_src: + if (srcrequest) + gst_element_release_request_pad (src, srcpad); + gst_object_unref (srcpad); +} + +/** + * gst_element_unlink_many: + * @element_1: (transfer none): the first #GstElement in the link chain. + * @element_2: (transfer none): the second #GstElement in the link chain. + * @...: the NULL-terminated list of elements to unlink in order. + * + * Unlinks a series of elements. Uses gst_element_unlink(). + */ +void +gst_element_unlink_many (GstElement * element_1, GstElement * element_2, ...) +{ + va_list args; + + g_return_if_fail (element_1 != NULL && element_2 != NULL); + g_return_if_fail (GST_IS_ELEMENT (element_1) && GST_IS_ELEMENT (element_2)); + + va_start (args, element_2); + + while (element_2) { + gst_element_unlink (element_1, element_2); + + element_1 = element_2; + element_2 = va_arg (args, GstElement *); + } + + va_end (args); +} + +/** + * gst_element_unlink: + * @src: (transfer none): the source #GstElement to unlink. + * @dest: (transfer none): the sink #GstElement to unlink. + * + * Unlinks all source pads of the source element with all sink pads + * of the sink element to which they are linked. + * + * If the link has been made using gst_element_link(), it could have created an + * requestpad, which has to be released using gst_element_release_request_pad(). + */ +void +gst_element_unlink (GstElement * src, GstElement * dest) +{ + GstIterator *pads; + gboolean done = FALSE; + GValue data = { 0, }; + + g_return_if_fail (GST_IS_ELEMENT (src)); + g_return_if_fail (GST_IS_ELEMENT (dest)); + + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "unlinking \"%s\" and \"%s\"", + GST_ELEMENT_NAME (src), GST_ELEMENT_NAME (dest)); + + pads = gst_element_iterate_pads (src); + while (!done) { + switch (gst_iterator_next (pads, &data)) { + case GST_ITERATOR_OK: + { + GstPad *pad = g_value_get_object (&data); + + if (GST_PAD_IS_SRC (pad)) { + GstPad *peerpad = gst_pad_get_peer (pad); + + /* see if the pad is linked and is really a pad of dest */ + if (peerpad) { + GstElement *peerelem; + + peerelem = gst_pad_get_parent_element (peerpad); + + if (peerelem == dest) { + gst_pad_unlink (pad, peerpad); + } + if (peerelem) + gst_object_unref (peerelem); + + gst_object_unref (peerpad); + } + } + g_value_reset (&data); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + default: + g_assert_not_reached (); + break; + } + } + g_value_unset (&data); + gst_iterator_free (pads); +} + +/** + * gst_element_query_position: + * @element: a #GstElement to invoke the position query on. + * @format: the #GstFormat requested + * @cur: (out) (allow-none): a location in which to store the current + * position, or NULL. + * + * Queries an element for the stream position. If one repeatedly calls this + * function one can also create and reuse it in gst_element_query(). + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_element_query_position (GstElement * element, GstFormat format, + gint64 * cur) +{ + GstQuery *query; + gboolean ret; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (format != GST_FORMAT_UNDEFINED, FALSE); + + query = gst_query_new_position (format); + ret = gst_element_query (element, query); + + if (ret) + gst_query_parse_position (query, NULL, cur); + + gst_query_unref (query); + + return ret; +} + +/** + * gst_element_query_duration: + * @element: a #GstElement to invoke the duration query on. + * @format: the #GstFormat requested + * @duration: (out): A location in which to store the total duration, or NULL. + * + * Queries an element for the total stream duration. + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_element_query_duration (GstElement * element, GstFormat format, + gint64 * duration) +{ + GstQuery *query; + gboolean ret; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (format != GST_FORMAT_UNDEFINED, FALSE); + + query = gst_query_new_duration (format); + ret = gst_element_query (element, query); + + if (ret) + gst_query_parse_duration (query, NULL, duration); + + gst_query_unref (query); + + return ret; +} + +/** + * gst_element_query_convert: + * @element: a #GstElement to invoke the convert query on. + * @src_format: (inout): a #GstFormat to convert from. + * @src_val: a value to convert. + * @dest_format: the #GstFormat to convert to. + * @dest_val: (out): a pointer to the result. + * + * Queries an element to convert @src_val in @src_format to @dest_format. + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_element_query_convert (GstElement * element, GstFormat src_format, + gint64 src_val, GstFormat dest_format, gint64 * dest_val) +{ + GstQuery *query; + gboolean ret; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (dest_format != GST_FORMAT_UNDEFINED, FALSE); + g_return_val_if_fail (dest_val != NULL, FALSE); + + if (dest_format == src_format || src_val == -1) { + *dest_val = src_val; + return TRUE; + } + + query = gst_query_new_convert (src_format, src_val, dest_format); + ret = gst_element_query (element, query); + + if (ret) + gst_query_parse_convert (query, NULL, NULL, NULL, dest_val); + + gst_query_unref (query); + + return ret; +} + +/** + * gst_element_seek_simple + * @element: a #GstElement to seek on + * @format: a #GstFormat to execute the seek in, such as #GST_FORMAT_TIME + * @seek_flags: seek options; playback applications will usually want to use + * GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT here + * @seek_pos: position to seek to (relative to the start); if you are doing + * a seek in #GST_FORMAT_TIME this value is in nanoseconds - + * multiply with #GST_SECOND to convert seconds to nanoseconds or + * with #GST_MSECOND to convert milliseconds to nanoseconds. + * + * Simple API to perform a seek on the given element, meaning it just seeks + * to the given position relative to the start of the stream. For more complex + * operations like segment seeks (e.g. for looping) or changing the playback + * rate or seeking relative to the last configured playback segment you should + * use gst_element_seek(). + * + * In a completely prerolled PAUSED or PLAYING pipeline, seeking is always + * guaranteed to return %TRUE on a seekable media type or %FALSE when the media + * type is certainly not seekable (such as a live stream). + * + * Some elements allow for seeking in the READY state, in this + * case they will store the seek event and execute it when they are put to + * PAUSED. If the element supports seek in READY, it will always return %TRUE when + * it receives the event in the READY state. + * + * Returns: %TRUE if the seek operation succeeded (the seek might not always be + * executed instantly though) + * + * Since: 0.10.7 + */ +gboolean +gst_element_seek_simple (GstElement * element, GstFormat format, + GstSeekFlags seek_flags, gint64 seek_pos) +{ + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (seek_pos >= 0, FALSE); + + return gst_element_seek (element, 1.0, format, seek_flags, + GST_SEEK_TYPE_SET, seek_pos, GST_SEEK_TYPE_NONE, 0); +} + +/** + * gst_pad_use_fixed_caps: + * @pad: the pad to use + * + * A helper function you can use that sets the FIXED_CAPS flag + * This way the default getcaps function will always return the negotiated caps + * or in case the pad is not negotiated, the padtemplate caps. + * + * Use this function on a pad that, once gst_pad_set_caps() has been called + * on it, cannot be renegotiated to something else. + */ +void +gst_pad_use_fixed_caps (GstPad * pad) +{ + GST_OBJECT_FLAG_SET (pad, GST_PAD_FIXED_CAPS); +} + +/** + * gst_pad_get_parent_element: + * @pad: a pad + * + * Gets the parent of @pad, cast to a #GstElement. If a @pad has no parent or + * its parent is not an element, return NULL. + * + * Returns: (transfer full): the parent of the pad. The caller has a + * reference on the parent, so unref when you're finished with it. + * + * MT safe. + */ +GstElement * +gst_pad_get_parent_element (GstPad * pad) +{ + GstObject *p; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + p = gst_object_get_parent (GST_OBJECT_CAST (pad)); + + if (p && !GST_IS_ELEMENT (p)) { + gst_object_unref (p); + p = NULL; + } + return GST_ELEMENT_CAST (p); +} + +/** + * gst_object_default_error: + * @source: the #GstObject that initiated the error. + * @error: (in): the GError. + * @debug: (in) (allow-none): an additional debug information string, or NULL + * + * A default error function that uses g_printerr() to display the error message + * and the optional debug sting.. + * + * The default handler will simply print the error string using g_print. + */ +void +gst_object_default_error (GstObject * source, const GError * error, + const gchar * debug) +{ + gchar *name = gst_object_get_path_string (source); + + g_printerr (_("ERROR: from element %s: %s\n"), name, error->message); + if (debug) + g_printerr (_("Additional debug info:\n%s\n"), debug); + + g_free (name); +} + +/** + * gst_bin_add_many: + * @bin: a #GstBin + * @element_1: (transfer full): the #GstElement element to add to the bin + * @...: (transfer full): additional elements to add to the bin + * + * Adds a NULL-terminated list of elements to a bin. This function is + * equivalent to calling gst_bin_add() for each member of the list. The return + * value of each gst_bin_add() is ignored. + */ +void +gst_bin_add_many (GstBin * bin, GstElement * element_1, ...) +{ + va_list args; + + g_return_if_fail (GST_IS_BIN (bin)); + g_return_if_fail (GST_IS_ELEMENT (element_1)); + + va_start (args, element_1); + + while (element_1) { + gst_bin_add (bin, element_1); + + element_1 = va_arg (args, GstElement *); + } + + va_end (args); +} + +/** + * gst_bin_remove_many: + * @bin: a #GstBin + * @element_1: (transfer none): the first #GstElement to remove from the bin + * @...: (transfer none): NULL-terminated list of elements to remove from the bin + * + * Remove a list of elements from a bin. This function is equivalent + * to calling gst_bin_remove() with each member of the list. + */ +void +gst_bin_remove_many (GstBin * bin, GstElement * element_1, ...) +{ + va_list args; + + g_return_if_fail (GST_IS_BIN (bin)); + g_return_if_fail (GST_IS_ELEMENT (element_1)); + + va_start (args, element_1); + + while (element_1) { + gst_bin_remove (bin, element_1); + + element_1 = va_arg (args, GstElement *); + } + + va_end (args); +} + +static void +gst_element_populate_std_props (GObjectClass * klass, const gchar * prop_name, + guint arg_id, GParamFlags flags) +{ + GQuark prop_id = g_quark_from_string (prop_name); + GParamSpec *pspec; + + static GQuark fd_id = 0; + static GQuark blocksize_id; + static GQuark bytesperread_id; + static GQuark dump_id; + static GQuark filesize_id; + static GQuark mmapsize_id; + static GQuark location_id; + static GQuark offset_id; + static GQuark silent_id; + static GQuark touch_id; + + flags |= G_PARAM_STATIC_STRINGS; + + if (!fd_id) { + fd_id = g_quark_from_static_string ("fd"); + blocksize_id = g_quark_from_static_string ("blocksize"); + bytesperread_id = g_quark_from_static_string ("bytesperread"); + dump_id = g_quark_from_static_string ("dump"); + filesize_id = g_quark_from_static_string ("filesize"); + mmapsize_id = g_quark_from_static_string ("mmapsize"); + location_id = g_quark_from_static_string ("location"); + offset_id = g_quark_from_static_string ("offset"); + silent_id = g_quark_from_static_string ("silent"); + touch_id = g_quark_from_static_string ("touch"); + } + + if (prop_id == fd_id) { + pspec = g_param_spec_int ("fd", "File-descriptor", + "File-descriptor for the file being read", 0, G_MAXINT, 0, flags); + } else if (prop_id == blocksize_id) { + pspec = g_param_spec_ulong ("blocksize", "Block Size", + "Block size to read per buffer", 0, G_MAXULONG, 4096, flags); + + } else if (prop_id == bytesperread_id) { + pspec = g_param_spec_int ("bytesperread", "Bytes per read", + "Number of bytes to read per buffer", G_MININT, G_MAXINT, 0, flags); + + } else if (prop_id == dump_id) { + pspec = g_param_spec_boolean ("dump", "Dump", + "Dump bytes to stdout", FALSE, flags); + + } else if (prop_id == filesize_id) { + pspec = g_param_spec_int64 ("filesize", "File Size", + "Size of the file being read", 0, G_MAXINT64, 0, flags); + + } else if (prop_id == mmapsize_id) { + pspec = g_param_spec_ulong ("mmapsize", "mmap() Block Size", + "Size in bytes of mmap()d regions", 0, G_MAXULONG, 4 * 1048576, flags); + + } else if (prop_id == location_id) { + pspec = g_param_spec_string ("location", "File Location", + "Location of the file to read", NULL, flags); + + } else if (prop_id == offset_id) { + pspec = g_param_spec_int64 ("offset", "File Offset", + "Byte offset of current read pointer", 0, G_MAXINT64, 0, flags); + + } else if (prop_id == silent_id) { + pspec = g_param_spec_boolean ("silent", "Silent", "Don't produce events", + FALSE, flags); + + } else if (prop_id == touch_id) { + pspec = g_param_spec_boolean ("touch", "Touch read data", + "Touch data to force disk read before " "push ()", TRUE, flags); + } else { + g_warning ("Unknown - 'standard' property '%s' id %d from klass %s", + prop_name, arg_id, g_type_name (G_OBJECT_CLASS_TYPE (klass))); + pspec = NULL; + } + + if (pspec) { + g_object_class_install_property (klass, arg_id, pspec); + } +} + +/** + * gst_element_class_install_std_props: + * @klass: the #GstElementClass to add the properties to. + * @first_name: the name of the first property. + * in a NULL terminated + * @...: the id and flags of the first property, followed by + * further 'name', 'id', 'flags' triplets and terminated by NULL. + * + * Adds a list of standardized properties with types to the @klass. + * the id is for the property switch in your get_prop method, and + * the flags determine readability / writeability. + **/ +void +gst_element_class_install_std_props (GstElementClass * klass, + const gchar * first_name, ...) +{ + const char *name; + + va_list args; + + g_return_if_fail (GST_IS_ELEMENT_CLASS (klass)); + + va_start (args, first_name); + + name = first_name; + + while (name) { + int arg_id = va_arg (args, int); + GParamFlags flags = (GParamFlags) va_arg (args, int); + + gst_element_populate_std_props ((GObjectClass *) klass, name, arg_id, + flags); + + name = va_arg (args, char *); + } + + va_end (args); +} + + +/** + * gst_buffer_merge: + * @buf1: (transfer none): the first source #GstBuffer to merge. + * @buf2: (transfer none): the second source #GstBuffer to merge. + * + * Create a new buffer that is the concatenation of the two source + * buffers. The original source buffers will not be modified or + * unref'd. Make sure you unref the source buffers if they are not used + * anymore afterwards. + * + * If the buffers point to contiguous areas of memory, the buffer + * is created without copying the data. + * + * Free-function: gst_buffer_unref + * + * Returns: (transfer full): the new #GstBuffer which is the concatenation + * of the source buffers. + */ +GstBuffer * +gst_buffer_merge (GstBuffer * buf1, GstBuffer * buf2) +{ + GstBuffer *result; + gsize size1, size2; + + size1 = gst_buffer_get_size (buf1); + size2 = gst_buffer_get_size (buf2); + + /* we're just a specific case of the more general gst_buffer_span() */ + result = gst_buffer_span (buf1, 0, buf2, size1 + size2); + + return result; +} + +/** + * gst_buffer_join: + * @buf1: the first source #GstBuffer. + * @buf2: the second source #GstBuffer. + * + * Create a new buffer that is the concatenation of the two source + * buffers, and unrefs the original source buffers. + * + * If the buffers point to contiguous areas of memory, the buffer + * is created without copying the data. + * + * This is a convenience function for C programmers. See also + * gst_buffer_merge(), which does the same thing without + * unreffing the input parameters. Language bindings without + * explicit reference counting should not wrap this function. + * + * Returns: (transfer full): the new #GstBuffer which is the concatenation of + * the source buffers. + */ +GstBuffer * +gst_buffer_join (GstBuffer * buf1, GstBuffer * buf2) +{ + GstBuffer *result; + gsize size1, size2; + + size1 = gst_buffer_get_size (buf1); + size2 = gst_buffer_get_size (buf2); + + result = gst_buffer_span (buf1, 0, buf2, size1 + size2); + gst_buffer_unref (buf1); + gst_buffer_unref (buf2); + + return result; +} + +static gboolean +getcaps_fold_func (const GValue * vpad, GValue * ret, GstCaps * filter) +{ + GstPad *pad = g_value_get_object (vpad); + gboolean empty = FALSE; + GstCaps *peercaps, *existing; + + existing = g_value_get_pointer (ret); + peercaps = gst_pad_peer_get_caps (pad, filter); + if (G_LIKELY (peercaps)) { + GstCaps *intersection = gst_caps_intersect (existing, peercaps); + + empty = gst_caps_is_empty (intersection); + + g_value_set_pointer (ret, intersection); + gst_caps_unref (existing); + gst_caps_unref (peercaps); + } + return !empty; +} + +/** + * gst_pad_proxy_getcaps: + * @pad: a #GstPad to proxy. + * @filter: a #GstCaps filter. + * + * Calls gst_pad_get_allowed_caps() for every other pad belonging to the + * same element as @pad, and returns the intersection of the results. + * + * This function is useful as a default getcaps function for an element + * that can handle any stream format, but requires all its pads to have + * the same caps. Two such elements are tee and adder. + * + * Free-function: gst_caps_unref + * + * Returns: (transfer full): the intersection of the other pads' allowed caps. + */ +GstCaps * +gst_pad_proxy_getcaps (GstPad * pad, GstCaps * filter) +{ + GstElement *element; + GstCaps *caps, *intersected; + GstIterator *iter; + GstIteratorResult res; + GValue ret = { 0, }; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + GST_CAT_DEBUG (GST_CAT_PADS, "proxying getcaps for %s:%s", + GST_DEBUG_PAD_NAME (pad)); + + element = gst_pad_get_parent_element (pad); + if (element == NULL) + goto no_parent; + + /* value to hold the return, by default it holds ANY, the ref is taken by + * the GValue. */ + g_value_init (&ret, G_TYPE_POINTER); + g_value_set_pointer (&ret, gst_caps_new_any ()); + + /* only iterate the pads in the oposite direction */ + if (GST_PAD_IS_SRC (pad)) + iter = gst_element_iterate_sink_pads (element); + else + iter = gst_element_iterate_src_pads (element); + + while (1) { + res = + gst_iterator_fold (iter, (GstIteratorFoldFunction) getcaps_fold_func, + &ret, filter); + switch (res) { + case GST_ITERATOR_RESYNC: + /* unref any value stored */ + if ((caps = g_value_get_pointer (&ret))) + gst_caps_unref (caps); + /* need to reset the result again to ANY */ + g_value_set_pointer (&ret, gst_caps_new_any ()); + gst_iterator_resync (iter); + break; + case GST_ITERATOR_DONE: + /* all pads iterated, return collected value */ + goto done; + case GST_ITERATOR_OK: + /* premature exit (happens if caps intersection is empty) */ + goto done; + default: + /* iterator returned _ERROR, mark an error and exit */ + if ((caps = g_value_get_pointer (&ret))) + gst_caps_unref (caps); + g_value_set_pointer (&ret, NULL); + goto error; + } + } +done: + gst_iterator_free (iter); + + gst_object_unref (element); + + caps = g_value_get_pointer (&ret); + g_value_unset (&ret); + + if (caps) { + intersected = + gst_caps_intersect (caps, gst_pad_get_pad_template_caps (pad)); + gst_caps_unref (caps); + } else { + intersected = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + } + + return intersected; + + /* ERRORS */ +no_parent: + { + GST_DEBUG_OBJECT (pad, "no parent"); + return gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + } +error: + { + g_warning ("Pad list returned error on element %s", + GST_ELEMENT_NAME (element)); + gst_iterator_free (iter); + gst_object_unref (element); + return gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + } +} + +/** + * gst_pad_query_position: + * @pad: a #GstPad to invoke the position query on. + * @format: the #GstFormat requested + * @cur: (out): A location in which to store the current position, or NULL. + * + * Queries a pad for the stream position. + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_pad_query_position (GstPad * pad, GstFormat format, gint64 * cur) +{ + GstQuery *query; + gboolean ret; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (format != GST_FORMAT_UNDEFINED, FALSE); + + query = gst_query_new_position (format); + ret = gst_pad_query (pad, query); + + if (ret) + gst_query_parse_position (query, NULL, cur); + + gst_query_unref (query); + + return ret; +} + +/** + * gst_pad_query_peer_position: + * @pad: a #GstPad on whose peer to invoke the position query on. + * Must be a sink pad. + * @format: the #GstFormat requested + * @cur: (out) (allow-none): a location in which to store the current + * position, or NULL. + * + * Queries the peer of a given sink pad for the stream position. + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_pad_query_peer_position (GstPad * pad, GstFormat format, gint64 * cur) +{ + gboolean ret = FALSE; + GstPad *peer; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (GST_PAD_IS_SINK (pad), FALSE); + g_return_val_if_fail (format != GST_FORMAT_UNDEFINED, FALSE); + + peer = gst_pad_get_peer (pad); + if (peer) { + ret = gst_pad_query_position (peer, format, cur); + gst_object_unref (peer); + } + + return ret; +} + +/** + * gst_pad_query_duration: + * @pad: a #GstPad to invoke the duration query on. + * @format: the #GstFormat requested + * @duration: (out) (allow-none): a location in which to store the total + * duration, or NULL. + * + * Queries a pad for the total stream duration. + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_pad_query_duration (GstPad * pad, GstFormat format, gint64 * duration) +{ + GstQuery *query; + gboolean ret; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (format != GST_FORMAT_UNDEFINED, FALSE); + + query = gst_query_new_duration (format); + ret = gst_pad_query (pad, query); + + if (ret) + gst_query_parse_duration (query, NULL, duration); + + gst_query_unref (query); + + return ret; +} + +/** + * gst_pad_query_peer_duration: + * @pad: a #GstPad on whose peer pad to invoke the duration query on. + * Must be a sink pad. + * @format: the #GstFormat requested + * @duration: (out) (allow-none): a location in which to store the total + * duration, or NULL. + * + * Queries the peer pad of a given sink pad for the total stream duration. + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_pad_query_peer_duration (GstPad * pad, GstFormat format, gint64 * duration) +{ + gboolean ret = FALSE; + GstPad *peer; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (GST_PAD_IS_SINK (pad), FALSE); + g_return_val_if_fail (format != GST_FORMAT_UNDEFINED, FALSE); + + peer = gst_pad_get_peer (pad); + if (peer) { + ret = gst_pad_query_duration (peer, format, duration); + gst_object_unref (peer); + } + + return ret; +} + +/** + * gst_pad_query_convert: + * @pad: a #GstPad to invoke the convert query on. + * @src_format: a #GstFormat to convert from. + * @src_val: a value to convert. + * @dest_format: the #GstFormat to convert to. + * @dest_val: (out): a pointer to the result. + * + * Queries a pad to convert @src_val in @src_format to @dest_format. + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_pad_query_convert (GstPad * pad, GstFormat src_format, gint64 src_val, + GstFormat dest_format, gint64 * dest_val) +{ + GstQuery *query; + gboolean ret; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (dest_format != GST_FORMAT_UNDEFINED, FALSE); + g_return_val_if_fail (dest_val != NULL, FALSE); + + if (dest_format == src_format || src_val == -1) { + *dest_val = src_val; + return TRUE; + } + + query = gst_query_new_convert (src_format, src_val, dest_format); + ret = gst_pad_query (pad, query); + + if (ret) + gst_query_parse_convert (query, NULL, NULL, NULL, dest_val); + + gst_query_unref (query); + + return ret; +} + +/** + * gst_pad_query_peer_convert: + * @pad: a #GstPad, on whose peer pad to invoke the convert query on. + * Must be a sink pad. + * @src_format: a #GstFormat to convert from. + * @src_val: a value to convert. + * @dest_format: the #GstFormat to convert to. + * @dest_val: (out): a pointer to the result. + * + * Queries the peer pad of a given sink pad to convert @src_val in @src_format + * to @dest_format. + * + * Returns: TRUE if the query could be performed. + */ +gboolean +gst_pad_query_peer_convert (GstPad * pad, GstFormat src_format, gint64 src_val, + GstFormat dest_format, gint64 * dest_val) +{ + gboolean ret = FALSE; + GstPad *peer; + + g_return_val_if_fail (GST_IS_PAD (pad), FALSE); + g_return_val_if_fail (GST_PAD_IS_SINK (pad), FALSE); + g_return_val_if_fail (dest_format != GST_FORMAT_UNDEFINED, FALSE); + g_return_val_if_fail (dest_val != NULL, FALSE); + + peer = gst_pad_get_peer (pad); + if (peer) { + ret = gst_pad_query_convert (peer, src_format, src_val, dest_format, + dest_val); + gst_object_unref (peer); + } + + return ret; +} + +/** + * gst_element_found_tags_for_pad: + * @element: element for which to post taglist to bus. + * @pad: (transfer none): pad on which to push tag-event + * @list: (transfer full): the taglist to post on the bus and create event from + * + * Posts a message to the bus that new tags were found and pushes the + * tags as event. Takes ownership of the @list. + * + * This is a utility method for elements. Applications should use the + * #GstTagSetter interface. + */ +void +gst_element_found_tags_for_pad (GstElement * element, + GstPad * pad, GstTagList * list) +{ + g_return_if_fail (element != NULL); + g_return_if_fail (pad != NULL); + g_return_if_fail (list != NULL); + + gst_pad_push_event (pad, gst_event_new_tag (gst_tag_list_copy (list))); +} + +static void +push_and_ref (const GValue * vpad, GstEvent * event) +{ + GstPad *pad = g_value_get_object (vpad); + + gst_pad_push_event (pad, gst_event_ref (event)); +} + +/** + * gst_element_found_tags: + * @element: element for which we found the tags. + * @list: (transfer full): list of tags. + * + * Posts a message to the bus that new tags were found, and pushes an event + * to all sourcepads. Takes ownership of the @list. + * + * This is a utility method for elements. Applications should use the + * #GstTagSetter interface. + */ +void +gst_element_found_tags (GstElement * element, GstTagList * list) +{ + GstIterator *iter; + GstEvent *event; + + g_return_if_fail (element != NULL); + g_return_if_fail (list != NULL); + + iter = gst_element_iterate_src_pads (element); + event = gst_event_new_tag (gst_tag_list_copy (list)); + gst_iterator_foreach (iter, (GstIteratorForeachFunction) push_and_ref, event); + gst_iterator_free (iter); + gst_event_unref (event); +} + +static GstPad * +element_find_unlinked_pad (GstElement * element, GstPadDirection direction) +{ + GstIterator *iter; + GstPad *unlinked_pad = NULL; + gboolean done; + GValue data = { 0, }; + + switch (direction) { + case GST_PAD_SRC: + iter = gst_element_iterate_src_pads (element); + break; + case GST_PAD_SINK: + iter = gst_element_iterate_sink_pads (element); + break; + default: + g_return_val_if_reached (NULL); + } + + done = FALSE; + while (!done) { + switch (gst_iterator_next (iter, &data)) { + case GST_ITERATOR_OK:{ + GstPad *peer; + GstPad *pad = g_value_get_object (&data); + + GST_CAT_LOG (GST_CAT_ELEMENT_PADS, "examining pad %s:%s", + GST_DEBUG_PAD_NAME (pad)); + + peer = gst_pad_get_peer (pad); + if (peer == NULL) { + unlinked_pad = gst_object_ref (pad); + done = TRUE; + GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, + "found existing unlinked pad %s:%s", + GST_DEBUG_PAD_NAME (unlinked_pad)); + } else { + gst_object_unref (peer); + } + g_value_reset (&data); + break; + } + case GST_ITERATOR_DONE: + done = TRUE; + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + g_return_val_if_reached (NULL); + break; + } + } + g_value_unset (&data); + gst_iterator_free (iter); + + return unlinked_pad; +} + +/** + * gst_bin_find_unlinked_pad: + * @bin: bin in which to look for elements with unlinked pads + * @direction: whether to look for an unlinked source or sink pad + * + * Recursively looks for elements with an unlinked pad of the given + * direction within the specified bin and returns an unlinked pad + * if one is found, or NULL otherwise. If a pad is found, the caller + * owns a reference to it and should use gst_object_unref() on the + * pad when it is not needed any longer. + * + * Returns: (transfer full): unlinked pad of the given direction, or NULL. + * + * Since: 0.10.20 + */ +GstPad * +gst_bin_find_unlinked_pad (GstBin * bin, GstPadDirection direction) +{ + GstIterator *iter; + gboolean done; + GstPad *pad = NULL; + GValue data = { 0, }; + + g_return_val_if_fail (GST_IS_BIN (bin), NULL); + g_return_val_if_fail (direction != GST_PAD_UNKNOWN, NULL); + + done = FALSE; + iter = gst_bin_iterate_recurse (bin); + while (!done) { + switch (gst_iterator_next (iter, &data)) { + case GST_ITERATOR_OK:{ + GstElement *element = g_value_get_object (&data); + + pad = element_find_unlinked_pad (element, direction); + if (pad != NULL) + done = TRUE; + g_value_reset (&data); + break; + } + case GST_ITERATOR_DONE: + done = TRUE; + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + g_return_val_if_reached (NULL); + break; + } + } + g_value_unset (&data); + gst_iterator_free (iter); + + return pad; +} + +/** + * gst_parse_bin_from_description: + * @bin_description: command line describing the bin + * @ghost_unlinked_pads: whether to automatically create ghost pads + * for unlinked source or sink pads within the bin + * @err: where to store the error message in case of an error, or NULL + * + * This is a convenience wrapper around gst_parse_launch() to create a + * #GstBin from a gst-launch-style pipeline description. See + * gst_parse_launch() and the gst-launch man page for details about the + * syntax. Ghost pads on the bin for unlinked source or sink pads + * within the bin can automatically be created (but only a maximum of + * one ghost pad for each direction will be created; if you expect + * multiple unlinked source pads or multiple unlinked sink pads + * and want them all ghosted, you will have to create the ghost pads + * yourself). + * + * Returns: (transfer full): a newly-created bin, or NULL if an error occurred. + * + * Since: 0.10.3 + */ +GstElement * +gst_parse_bin_from_description (const gchar * bin_description, + gboolean ghost_unlinked_pads, GError ** err) +{ + return gst_parse_bin_from_description_full (bin_description, + ghost_unlinked_pads, NULL, GST_PARSE_FLAG_NONE, err); +} + +/** + * gst_parse_bin_from_description_full: + * @bin_description: command line describing the bin + * @ghost_unlinked_pads: whether to automatically create ghost pads + * for unlinked source or sink pads within the bin + * @context: (transfer none) (allow-none): a parse context allocated with + * gst_parse_context_new(), or %NULL + * @flags: parsing options, or #GST_PARSE_FLAG_NONE + * @err: where to store the error message in case of an error, or NULL + * + * This is a convenience wrapper around gst_parse_launch() to create a + * #GstBin from a gst-launch-style pipeline description. See + * gst_parse_launch() and the gst-launch man page for details about the + * syntax. Ghost pads on the bin for unlinked source or sink pads + * within the bin can automatically be created (but only a maximum of + * one ghost pad for each direction will be created; if you expect + * multiple unlinked source pads or multiple unlinked sink pads + * and want them all ghosted, you will have to create the ghost pads + * yourself). + * + * Returns: (transfer full): a newly-created bin, or NULL if an error occurred. + * + * Since: 0.10.20 + */ +GstElement * +gst_parse_bin_from_description_full (const gchar * bin_description, + gboolean ghost_unlinked_pads, GstParseContext * context, + GstParseFlags flags, GError ** err) +{ +#ifndef GST_DISABLE_PARSE + GstPad *pad = NULL; + GstBin *bin; + gchar *desc; + + g_return_val_if_fail (bin_description != NULL, NULL); + g_return_val_if_fail (err == NULL || *err == NULL, NULL); + + GST_DEBUG ("Making bin from description '%s'", bin_description); + + /* parse the pipeline to a bin */ + desc = g_strdup_printf ("bin.( %s )", bin_description); + bin = (GstBin *) gst_parse_launch_full (desc, context, flags, err); + g_free (desc); + + if (bin == NULL || (err && *err != NULL)) { + if (bin) + gst_object_unref (bin); + return NULL; + } + + /* find pads and ghost them if necessary */ + if (ghost_unlinked_pads) { + if ((pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SRC))) { + gst_element_add_pad (GST_ELEMENT (bin), gst_ghost_pad_new ("src", pad)); + gst_object_unref (pad); + } + if ((pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SINK))) { + gst_element_add_pad (GST_ELEMENT (bin), gst_ghost_pad_new ("sink", pad)); + gst_object_unref (pad); + } + } + + return GST_ELEMENT (bin); +#else + gchar *msg; + + GST_WARNING ("Disabled API called"); + + msg = gst_error_get_message (GST_CORE_ERROR, GST_CORE_ERROR_DISABLED); + g_set_error (err, GST_CORE_ERROR, GST_CORE_ERROR_DISABLED, "%s", msg); + g_free (msg); + + return NULL; +#endif +} + +/** + * gst_type_register_static_full: + * @parent_type: The GType of the parent type the newly registered type will + * derive from + * @type_name: NULL-terminated string used as the name of the new type + * @class_size: Size of the class structure. + * @base_init: Location of the base initialization function (optional). + * @base_finalize: Location of the base finalization function (optional). + * @class_init: Location of the class initialization function for class types + * Location of the default vtable initialization function for interface + * types. (optional) + * @class_finalize: Location of the class finalization function for class types. + * Location of the default vtable finalization function for interface types. + * (optional) + * @class_data: User-supplied data passed to the class init/finalize functions. + * @instance_size: Size of the instance (object) structure (required for + * instantiatable types only). + * @n_preallocs: The number of pre-allocated (cached) instances to reserve + * memory for (0 indicates no caching). Ignored on recent GLib's. + * @instance_init: Location of the instance initialization function (optional, + * for instantiatable types only). + * @value_table: A GTypeValueTable function table for generic handling of + * GValues of this type (usually only useful for fundamental types). + * @flags: #GTypeFlags for this GType. E.g: G_TYPE_FLAG_ABSTRACT + * + * Helper function which constructs a #GTypeInfo structure and registers a + * GType, but which generates less linker overhead than a static const + * #GTypeInfo structure. For further details of the parameters, please see + * #GTypeInfo in the GLib documentation. + * + * Registers type_name as the name of a new static type derived from + * parent_type. The value of flags determines the nature (e.g. abstract or + * not) of the type. It works by filling a GTypeInfo struct and calling + * g_type_register_static(). + * + * Returns: A #GType for the newly-registered type. + * + * Since: 0.10.14 + */ +GType +gst_type_register_static_full (GType parent_type, + const gchar * type_name, + guint class_size, + GBaseInitFunc base_init, + GBaseFinalizeFunc base_finalize, + GClassInitFunc class_init, + GClassFinalizeFunc class_finalize, + gconstpointer class_data, + guint instance_size, + guint16 n_preallocs, + GInstanceInitFunc instance_init, + const GTypeValueTable * value_table, GTypeFlags flags) +{ + GTypeInfo info; + + info.class_size = class_size; + info.base_init = base_init; + info.base_finalize = base_finalize; + info.class_init = class_init; + info.class_finalize = class_finalize; + info.class_data = class_data; + info.instance_size = instance_size; + info.n_preallocs = n_preallocs; + info.instance_init = instance_init; + info.value_table = value_table; + + return g_type_register_static (parent_type, type_name, &info, flags); +} + + +/** + * gst_util_get_timestamp: + * + * Get a timestamp as GstClockTime to be used for interval measurements. + * The timestamp should not be interpreted in any other way. + * + * Returns: the timestamp + * + * Since: 0.10.16 + */ +GstClockTime +gst_util_get_timestamp (void) +{ +#if defined (HAVE_POSIX_TIMERS) && defined(HAVE_MONOTONIC_CLOCK) + struct timespec now; + + clock_gettime (CLOCK_MONOTONIC, &now); + return GST_TIMESPEC_TO_TIME (now); +#else + GTimeVal now; + + g_get_current_time (&now); + return GST_TIMEVAL_TO_TIME (now); +#endif +} + +/** + * gst_util_array_binary_search: + * @array: the sorted input array + * @num_elements: number of elements in the array + * @element_size: size of every element in bytes + * @search_func: (scope call): function to compare two elements, @search_data will always be passed as second argument + * @mode: search mode that should be used + * @search_data: element that should be found + * @user_data: (closure): data to pass to @search_func + * + * Searches inside @array for @search_data by using the comparison function + * @search_func. @array must be sorted ascending. + * + * As @search_data is always passed as second argument to @search_func it's + * not required that @search_data has the same type as the array elements. + * + * The complexity of this search function is O(log (num_elements)). + * + * Returns: (transfer none): The address of the found element or %NULL if nothing was found + * + * Since: 0.10.23 + */ +gpointer +gst_util_array_binary_search (gpointer array, guint num_elements, + gsize element_size, GCompareDataFunc search_func, GstSearchMode mode, + gconstpointer search_data, gpointer user_data) +{ + glong left = 0, right = num_elements - 1, m; + gint ret; + guint8 *data = (guint8 *) array; + + g_return_val_if_fail (array != NULL, NULL); + g_return_val_if_fail (element_size > 0, NULL); + g_return_val_if_fail (search_func != NULL, NULL); + + /* 0. No elements => return NULL */ + if (num_elements == 0) + return NULL; + + /* 1. If search_data is before the 0th element return the 0th element */ + ret = search_func (data, search_data, user_data); + if ((ret >= 0 && mode == GST_SEARCH_MODE_AFTER) || ret == 0) + return data; + else if (ret > 0) + return NULL; + + /* 2. If search_data is after the last element return the last element */ + ret = + search_func (data + (num_elements - 1) * element_size, search_data, + user_data); + if ((ret <= 0 && mode == GST_SEARCH_MODE_BEFORE) || ret == 0) + return data + (num_elements - 1) * element_size; + else if (ret < 0) + return NULL; + + /* 3. else binary search */ + while (TRUE) { + m = left + (right - left) / 2; + + ret = search_func (data + m * element_size, search_data, user_data); + + if (ret == 0) { + return data + m * element_size; + } else if (ret < 0) { + left = m + 1; + } else { + right = m - 1; + } + + /* No exact match found */ + if (right < left) { + if (mode == GST_SEARCH_MODE_EXACT) { + return NULL; + } else if (mode == GST_SEARCH_MODE_AFTER) { + if (ret < 0) + return (m < num_elements) ? data + (m + 1) * element_size : NULL; + else + return data + m * element_size; + } else { + if (ret < 0) + return data + m * element_size; + else + return (m > 0) ? data + (m - 1) * element_size : NULL; + } + } + } +} + +/* Finds the greatest common divisor. + * Returns 1 if none other found. + * This is Euclid's algorithm. */ + +/** + * gst_util_greatest_common_divisor: + * @a: First value as #gint + * @b: Second value as #gint + * + * Calculates the greatest common divisor of @a + * and @b. + * + * Returns: Greatest common divisor of @a and @b + * + * Since: 0.10.26 + */ +gint +gst_util_greatest_common_divisor (gint a, gint b) +{ + while (b != 0) { + int temp = a; + + a = b; + b = temp % b; + } + + return ABS (a); +} + +/** + * gst_util_fraction_to_double: + * @src_n: Fraction numerator as #gint + * @src_d: Fraction denominator #gint + * @dest: (out): pointer to a #gdouble for the result + * + * Transforms a fraction to a #gdouble. + * + * Since: 0.10.26 + */ +void +gst_util_fraction_to_double (gint src_n, gint src_d, gdouble * dest) +{ + g_return_if_fail (dest != NULL); + g_return_if_fail (src_d != 0); + + *dest = ((gdouble) src_n) / ((gdouble) src_d); +} + +#define MAX_TERMS 30 +#define MIN_DIVISOR 1.0e-10 +#define MAX_ERROR 1.0e-20 + +/* use continued fractions to transform a double into a fraction, + * see http://mathforum.org/dr.math/faq/faq.fractions.html#decfrac. + * This algorithm takes care of overflows. + */ + +/** + * gst_util_double_to_fraction: + * @src: #gdouble to transform + * @dest_n: (out): pointer to a #gint to hold the result numerator + * @dest_d: (out): pointer to a #gint to hold the result denominator + * + * Transforms a #gdouble to a fraction and simplifies + * the result. + * + * Since: 0.10.26 + */ +void +gst_util_double_to_fraction (gdouble src, gint * dest_n, gint * dest_d) +{ + + gdouble V, F; /* double being converted */ + gint N, D; /* will contain the result */ + gint A; /* current term in continued fraction */ + gint64 N1, D1; /* numerator, denominator of last approx */ + gint64 N2, D2; /* numerator, denominator of previous approx */ + gint i; + gint gcd; + gboolean negative = FALSE; + + g_return_if_fail (dest_n != NULL); + g_return_if_fail (dest_d != NULL); + + /* initialize fraction being converted */ + F = src; + if (F < 0.0) { + F = -F; + negative = TRUE; + } + + V = F; + /* initialize fractions with 1/0, 0/1 */ + N1 = 1; + D1 = 0; + N2 = 0; + D2 = 1; + N = 1; + D = 1; + + for (i = 0; i < MAX_TERMS; i++) { + /* get next term */ + A = (gint) F; /* no floor() needed, F is always >= 0 */ + /* get new divisor */ + F = F - A; + + /* calculate new fraction in temp */ + N2 = N1 * A + N2; + D2 = D1 * A + D2; + + /* guard against overflow */ + if (N2 > G_MAXINT || D2 > G_MAXINT) { + break; + } + + N = N2; + D = D2; + + /* save last two fractions */ + N2 = N1; + D2 = D1; + N1 = N; + D1 = D; + + /* quit if dividing by zero or close enough to target */ + if (F < MIN_DIVISOR || fabs (V - ((gdouble) N) / D) < MAX_ERROR) { + break; + } + + /* Take reciprocal */ + F = 1 / F; + } + /* fix for overflow */ + if (D == 0) { + N = G_MAXINT; + D = 1; + } + /* fix for negative */ + if (negative) + N = -N; + + /* simplify */ + gcd = gst_util_greatest_common_divisor (N, D); + if (gcd) { + N /= gcd; + D /= gcd; + } + + /* set results */ + *dest_n = N; + *dest_d = D; +} + +/** + * gst_util_fraction_multiply: + * @a_n: Numerator of first value + * @a_d: Denominator of first value + * @b_n: Numerator of second value + * @b_d: Denominator of second value + * @res_n: (out): Pointer to #gint to hold the result numerator + * @res_d: (out): Pointer to #gint to hold the result denominator + * + * Multiplies the fractions @a_n/@a_d and @b_n/@b_d and stores + * the result in @res_n and @res_d. + * + * Returns: %FALSE on overflow, %TRUE otherwise. + * + * Since: 0.10.26 + */ +gboolean +gst_util_fraction_multiply (gint a_n, gint a_d, gint b_n, gint b_d, + gint * res_n, gint * res_d) +{ + gint gcd; + + g_return_val_if_fail (res_n != NULL, FALSE); + g_return_val_if_fail (res_d != NULL, FALSE); + g_return_val_if_fail (a_d != 0, FALSE); + g_return_val_if_fail (b_d != 0, FALSE); + + gcd = gst_util_greatest_common_divisor (a_n, a_d); + a_n /= gcd; + a_d /= gcd; + + gcd = gst_util_greatest_common_divisor (b_n, b_d); + b_n /= gcd; + b_d /= gcd; + + gcd = gst_util_greatest_common_divisor (a_n, b_d); + a_n /= gcd; + b_d /= gcd; + + gcd = gst_util_greatest_common_divisor (a_d, b_n); + a_d /= gcd; + b_n /= gcd; + + /* This would result in overflow */ + if (a_n != 0 && G_MAXINT / ABS (a_n) < ABS (b_n)) + return FALSE; + if (G_MAXINT / ABS (a_d) < ABS (b_d)) + return FALSE; + + *res_n = a_n * b_n; + *res_d = a_d * b_d; + + gcd = gst_util_greatest_common_divisor (*res_n, *res_d); + *res_n /= gcd; + *res_d /= gcd; + + return TRUE; +} + +/** + * gst_util_fraction_add: + * @a_n: Numerator of first value + * @a_d: Denominator of first value + * @b_n: Numerator of second value + * @b_d: Denominator of second value + * @res_n: (out): Pointer to #gint to hold the result numerator + * @res_d: (out): Pointer to #gint to hold the result denominator + * + * Adds the fractions @a_n/@a_d and @b_n/@b_d and stores + * the result in @res_n and @res_d. + * + * Returns: %FALSE on overflow, %TRUE otherwise. + * + * Since: 0.10.26 + */ +gboolean +gst_util_fraction_add (gint a_n, gint a_d, gint b_n, gint b_d, gint * res_n, + gint * res_d) +{ + gint gcd; + + g_return_val_if_fail (res_n != NULL, FALSE); + g_return_val_if_fail (res_d != NULL, FALSE); + g_return_val_if_fail (a_d != 0, FALSE); + g_return_val_if_fail (b_d != 0, FALSE); + + gcd = gst_util_greatest_common_divisor (a_n, a_d); + a_n /= gcd; + a_d /= gcd; + + gcd = gst_util_greatest_common_divisor (b_n, b_d); + b_n /= gcd; + b_d /= gcd; + + if (a_n == 0) { + *res_n = b_n; + *res_d = b_d; + return TRUE; + } + if (b_n == 0) { + *res_n = a_n; + *res_d = a_d; + return TRUE; + } + + /* This would result in overflow */ + if (G_MAXINT / ABS (a_n) < ABS (b_n)) + return FALSE; + if (G_MAXINT / ABS (a_d) < ABS (b_d)) + return FALSE; + if (G_MAXINT / ABS (a_d) < ABS (b_d)) + return FALSE; + + *res_n = (a_n * b_d) + (a_d * b_n); + *res_d = a_d * b_d; + + gcd = gst_util_greatest_common_divisor (*res_n, *res_d); + if (gcd) { + *res_n /= gcd; + *res_d /= gcd; + } else { + /* res_n == 0 */ + *res_d = 1; + } + + return TRUE; +} + +/** + * gst_util_fraction_compare: + * @a_n: Numerator of first value + * @a_d: Denominator of first value + * @b_n: Numerator of second value + * @b_d: Denominator of second value + * + * Compares the fractions @a_n/@a_d and @b_n/@b_d and returns + * -1 if a < b, 0 if a = b and 1 if a > b. + * + * Returns: -1 if a < b; 0 if a = b; 1 if a > b. + * + * Since: 0.10.31 + */ +gint +gst_util_fraction_compare (gint a_n, gint a_d, gint b_n, gint b_d) +{ + gint64 new_num_1; + gint64 new_num_2; + gint gcd; + + g_return_val_if_fail (a_d != 0 && b_d != 0, 0); + + /* Simplify */ + gcd = gst_util_greatest_common_divisor (a_n, a_d); + a_n /= gcd; + a_d /= gcd; + + gcd = gst_util_greatest_common_divisor (b_n, b_d); + b_n /= gcd; + b_d /= gcd; + + /* fractions are reduced when set, so we can quickly see if they're equal */ + if (a_n == b_n && a_d == b_d) + return 0; + + /* extend to 64 bits */ + new_num_1 = ((gint64) a_n) * b_d; + new_num_2 = ((gint64) b_n) * a_d; + if (new_num_1 < new_num_2) + return -1; + if (new_num_1 > new_num_2) + return 1; + + /* Should not happen because a_d and b_d are not 0 */ + g_return_val_if_reached (0); +} diff --git a/gst/gstutils.h b/gst/gstutils.h new file mode 100644 index 0000000..8f1445a --- /dev/null +++ b/gst/gstutils.h @@ -0,0 +1,983 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2002 Thomas Vander Stichele <thomas@apestaart.org> + * + * gstutils.h: Header for various utility functions + * + * 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_UTILS_H__ +#define __GST_UTILS_H__ + +#include <glib.h> +#include <gst/gstbin.h> +#include <gst/gstparse.h> + +G_BEGIN_DECLS + +void gst_util_set_value_from_string (GValue *value, const gchar *value_str); +void gst_util_set_object_arg (GObject *object, const gchar *name, const gchar *value); +void gst_util_dump_mem (const guchar *mem, guint size); + +guint64 gst_util_gdouble_to_guint64 (gdouble value) G_GNUC_CONST; +gdouble gst_util_guint64_to_gdouble (guint64 value) G_GNUC_CONST; + +/** + * gst_guint64_to_gdouble: + * @value: the #guint64 value to convert + * + * Convert @value to a gdouble. + * + * Returns: @value converted to a #gdouble. + */ + +/** + * gst_gdouble_to_guint64: + * @value: the #gdouble value to convert + * + * Convert @value to a guint64. + * + * Returns: @value converted to a #guint64. + */ +#ifdef WIN32 +#define gst_gdouble_to_guint64(value) gst_util_gdouble_to_guint64(value) +#define gst_guint64_to_gdouble(value) gst_util_guint64_to_gdouble(value) +#else +#define gst_gdouble_to_guint64(value) ((guint64) (value)) +#define gst_guint64_to_gdouble(value) ((gdouble) (value)) +#endif + +guint64 gst_util_uint64_scale (guint64 val, guint64 num, guint64 denom); +guint64 gst_util_uint64_scale_round (guint64 val, guint64 num, guint64 denom); +guint64 gst_util_uint64_scale_ceil (guint64 val, guint64 num, guint64 denom); + +guint64 gst_util_uint64_scale_int (guint64 val, gint num, gint denom); +guint64 gst_util_uint64_scale_int_round (guint64 val, gint num, gint denom); +guint64 gst_util_uint64_scale_int_ceil (guint64 val, gint num, gint denom); + +guint32 gst_util_seqnum_next (void); +gint32 gst_util_seqnum_compare (guint32 s1, guint32 s2); + +void gst_print_pad_caps (GString *buf, gint indent, GstPad *pad); +void gst_print_element_args (GString *buf, gint indent, GstElement *element); + + +GType gst_type_register_static_full (GType parent_type, + const gchar *type_name, + guint class_size, + GBaseInitFunc base_init, + GBaseFinalizeFunc base_finalize, + GClassInitFunc class_init, + GClassFinalizeFunc class_finalize, + gconstpointer class_data, + guint instance_size, + guint16 n_preallocs, + GInstanceInitFunc instance_init, + const GTypeValueTable *value_table, + GTypeFlags flags); + +/** + * GST_CALL_PARENT: + * @parent_class_cast: the name of the class cast macro for the parent type + * @name: name of the function to call + * @args: arguments enclosed in '( )' + * + * Just call the parent handler. This assumes that there is a variable + * named parent_class that points to the (duh!) parent class. Note that + * this macro is not to be used with things that return something, use + * the _WITH_DEFAULT version for that + */ +#define GST_CALL_PARENT(parent_class_cast, name, args) \ + ((parent_class_cast(parent_class)->name != NULL) ? \ + parent_class_cast(parent_class)->name args : (void) 0) + +/** + * GST_CALL_PARENT_WITH_DEFAULT: + * @parent_class_cast: the name of the class cast macro for the parent type + * @name: name of the function to call + * @args: arguments enclosed in '( )' + * @def_return: default result + * + * Same as GST_CALL_PARENT(), but in case there is no implementation, it + * evaluates to @def_return. + */ +#define GST_CALL_PARENT_WITH_DEFAULT(parent_class_cast, name, args, def_return)\ + ((parent_class_cast(parent_class)->name != NULL) ? \ + parent_class_cast(parent_class)->name args : def_return) + +/* Define PUT and GET functions for unaligned memory */ +#define _GST_GET(__data, __idx, __size, __shift) \ + (((guint##__size) (((const guint8 *) (__data))[__idx])) << (__shift)) + +#define _GST_PUT(__data, __idx, __size, __shift, __num) \ + (((guint8 *) (__data))[__idx] = (((guint##__size) (__num)) >> (__shift)) & 0xff) + +/** + * GST_READ_UINT64_BE: + * @data: memory location + * + * Read a 64 bit unsigned integer value in big endian format from the memory buffer. + */ +#define GST_READ_UINT64_BE(data) (_GST_GET (data, 0, 64, 56) | \ + _GST_GET (data, 1, 64, 48) | \ + _GST_GET (data, 2, 64, 40) | \ + _GST_GET (data, 3, 64, 32) | \ + _GST_GET (data, 4, 64, 24) | \ + _GST_GET (data, 5, 64, 16) | \ + _GST_GET (data, 6, 64, 8) | \ + _GST_GET (data, 7, 64, 0)) + +/** + * GST_READ_UINT64_LE: + * @data: memory location + * + * Read a 64 bit unsigned integer value in little endian format from the memory buffer. + */ +#define GST_READ_UINT64_LE(data) (_GST_GET (data, 7, 64, 56) | \ + _GST_GET (data, 6, 64, 48) | \ + _GST_GET (data, 5, 64, 40) | \ + _GST_GET (data, 4, 64, 32) | \ + _GST_GET (data, 3, 64, 24) | \ + _GST_GET (data, 2, 64, 16) | \ + _GST_GET (data, 1, 64, 8) | \ + _GST_GET (data, 0, 64, 0)) + +/** + * GST_READ_UINT32_BE: + * @data: memory location + * + * Read a 32 bit unsigned integer value in big endian format from the memory buffer. + */ +#define GST_READ_UINT32_BE(data) (_GST_GET (data, 0, 32, 24) | \ + _GST_GET (data, 1, 32, 16) | \ + _GST_GET (data, 2, 32, 8) | \ + _GST_GET (data, 3, 32, 0)) + +/** + * GST_READ_UINT32_LE: + * @data: memory location + * + * Read a 32 bit unsigned integer value in little endian format from the memory buffer. + */ +#define GST_READ_UINT32_LE(data) (_GST_GET (data, 3, 32, 24) | \ + _GST_GET (data, 2, 32, 16) | \ + _GST_GET (data, 1, 32, 8) | \ + _GST_GET (data, 0, 32, 0)) + +/** + * GST_READ_UINT24_BE: + * @data: memory location + * + * Read a 24 bit unsigned integer value in big endian format from the memory buffer. + * + * Since: 0.10.22 + */ +#define GST_READ_UINT24_BE(data) (_GST_GET (data, 0, 32, 16) | \ + _GST_GET (data, 1, 32, 8) | \ + _GST_GET (data, 2, 32, 0)) + +/** + * GST_READ_UINT24_LE: + * @data: memory location + * + * Read a 24 bit unsigned integer value in little endian format from the memory buffer. + * + * Since: 0.10.22 + */ +#define GST_READ_UINT24_LE(data) (_GST_GET (data, 2, 32, 16) | \ + _GST_GET (data, 1, 32, 8) | \ + _GST_GET (data, 0, 32, 0)) + +/** + * GST_READ_UINT16_BE: + * @data: memory location + * + * Read a 16 bit unsigned integer value in big endian format from the memory buffer. + */ +#define GST_READ_UINT16_BE(data) (_GST_GET (data, 0, 16, 8) | \ + _GST_GET (data, 1, 16, 0)) + +/** + * GST_READ_UINT16_LE: + * @data: memory location + * + * Read a 16 bit unsigned integer value in little endian format from the memory buffer. + */ +#define GST_READ_UINT16_LE(data) (_GST_GET (data, 1, 16, 8) | \ + _GST_GET (data, 0, 16, 0)) + +/** + * GST_READ_UINT8: + * @data: memory location + * + * Read an 8 bit unsigned integer value from the memory buffer. + */ +#define GST_READ_UINT8(data) (_GST_GET (data, 0, 8, 0)) + +/** + * GST_WRITE_UINT64_BE: + * @data: memory location + * @num: value to store + * + * Store a 64 bit unsigned integer value in big endian format into the memory buffer. + */ +#define GST_WRITE_UINT64_BE(data, num) do { \ + _GST_PUT (data, 0, 64, 56, num); \ + _GST_PUT (data, 1, 64, 48, num); \ + _GST_PUT (data, 2, 64, 40, num); \ + _GST_PUT (data, 3, 64, 32, num); \ + _GST_PUT (data, 4, 64, 24, num); \ + _GST_PUT (data, 5, 64, 16, num); \ + _GST_PUT (data, 6, 64, 8, num); \ + _GST_PUT (data, 7, 64, 0, num); \ + } while (0) + +/** + * GST_WRITE_UINT64_LE: + * @data: memory location + * @num: value to store + * + * Store a 64 bit unsigned integer value in little endian format into the memory buffer. + */ +#define GST_WRITE_UINT64_LE(data, num) do { \ + _GST_PUT (data, 0, 64, 0, num); \ + _GST_PUT (data, 1, 64, 8, num); \ + _GST_PUT (data, 2, 64, 16, num); \ + _GST_PUT (data, 3, 64, 24, num); \ + _GST_PUT (data, 4, 64, 32, num); \ + _GST_PUT (data, 5, 64, 40, num); \ + _GST_PUT (data, 6, 64, 48, num); \ + _GST_PUT (data, 7, 64, 56, num); \ + } while (0) + +/** + * GST_WRITE_UINT32_BE: + * @data: memory location + * @num: value to store + * + * Store a 32 bit unsigned integer value in big endian format into the memory buffer. + */ +#define GST_WRITE_UINT32_BE(data, num) do { \ + _GST_PUT (data, 0, 32, 24, num); \ + _GST_PUT (data, 1, 32, 16, num); \ + _GST_PUT (data, 2, 32, 8, num); \ + _GST_PUT (data, 3, 32, 0, num); \ + } while (0) + +/** + * GST_WRITE_UINT32_LE: + * @data: memory location + * @num: value to store + * + * Store a 32 bit unsigned integer value in little endian format into the memory buffer. + */ +#define GST_WRITE_UINT32_LE(data, num) do { \ + _GST_PUT (data, 0, 32, 0, num); \ + _GST_PUT (data, 1, 32, 8, num); \ + _GST_PUT (data, 2, 32, 16, num); \ + _GST_PUT (data, 3, 32, 24, num); \ + } while (0) + +/** + * GST_WRITE_UINT24_BE: + * @data: memory location + * @num: value to store + * + * Store a 24 bit unsigned integer value in big endian format into the memory buffer. + * + * Since: 0.10.22 + */ +#define GST_WRITE_UINT24_BE(data, num) do { \ + _GST_PUT (data, 0, 32, 16, num); \ + _GST_PUT (data, 1, 32, 8, num); \ + _GST_PUT (data, 2, 32, 0, num); \ + } while (0) + +/** + * GST_WRITE_UINT24_LE: + * @data: memory location + * @num: value to store + * + * Store a 24 bit unsigned integer value in little endian format into the memory buffer. + * + * Since: 0.10.22 + */ +#define GST_WRITE_UINT24_LE(data, num) do { \ + _GST_PUT (data, 0, 32, 0, num); \ + _GST_PUT (data, 1, 32, 8, num); \ + _GST_PUT (data, 2, 32, 16, num); \ + } while (0) + +/** + * GST_WRITE_UINT16_BE: + * @data: memory location + * @num: value to store + * + * Store a 16 bit unsigned integer value in big endian format into the memory buffer. + */ +#define GST_WRITE_UINT16_BE(data, num) do { \ + _GST_PUT (data, 0, 16, 8, num); \ + _GST_PUT (data, 1, 16, 0, num); \ + } while (0) + +/** + * GST_WRITE_UINT16_LE: + * @data: memory location + * @num: value to store + * + * Store a 16 bit unsigned integer value in little endian format into the memory buffer. + */ +#define GST_WRITE_UINT16_LE(data, num) do { \ + _GST_PUT (data, 0, 16, 0, num); \ + _GST_PUT (data, 1, 16, 8, num); \ + } while (0) + +/** + * GST_WRITE_UINT8: + * @data: memory location + * @num: value to store + * + * Store an 8 bit unsigned integer value into the memory buffer. + */ +#define GST_WRITE_UINT8(data, num) do { \ + _GST_PUT (data, 0, 8, 0, num); \ + } while (0) + +/* Float endianness conversion macros */ + +/* FIXME: Remove this once we depend on a GLib version with this */ +#ifndef GFLOAT_FROM_LE +/** + * GFLOAT_SWAP_LE_BE: + * @in: input value + * + * Swap byte order of a 32-bit floating point value (float). + * + * Returns: @in byte-swapped. + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC gfloat GFLOAT_SWAP_LE_BE (gfloat in); +#endif + +inline static gfloat +GFLOAT_SWAP_LE_BE(gfloat in) +{ + union + { + guint32 i; + gfloat f; + } u; + + u.f = in; + u.i = GUINT32_SWAP_LE_BE (u.i); + return u.f; +} + +/** + * GDOUBLE_SWAP_LE_BE: + * @in: input value + * + * Swap byte order of a 64-bit floating point value (double). + * + * Returns: @in byte-swapped. + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC gdouble GDOUBLE_SWAP_LE_BE (gdouble in); +#endif + +inline static gdouble +GDOUBLE_SWAP_LE_BE(gdouble in) +{ + union + { + guint64 i; + gdouble d; + } u; + + u.d = in; + u.i = GUINT64_SWAP_LE_BE (u.i); + return u.d; +} + +/** + * GDOUBLE_TO_LE: + * @val: value + * + * Convert 64-bit floating point value (double) from native byte order into + * little endian byte order. + * + * Since: 0.10.22 + * + */ +/** + * GDOUBLE_TO_BE: + * @val: value + * + * Convert 64-bit floating point value (double) from native byte order into + * big endian byte order. + * + * Since: 0.10.22 + * + */ +/** + * GDOUBLE_FROM_LE: + * @val: value + * + * Convert 64-bit floating point value (double) from little endian byte order + * into native byte order. + * + * Since: 0.10.22 + * + */ +/** + * GDOUBLE_FROM_BE: + * @val: value + * + * Convert 64-bit floating point value (double) from big endian byte order + * into native byte order. + * + * Since: 0.10.22 + * + */ + +/** + * GFLOAT_TO_LE: + * @val: value + * + * Convert 32-bit floating point value (float) from native byte order into + * little endian byte order. + * + * Since: 0.10.22 + * + */ +/** + * GFLOAT_TO_BE: + * @val: value + * + * Convert 32-bit floating point value (float) from native byte order into + * big endian byte order. + * + * Since: 0.10.22 + * + */ +/** + * GFLOAT_FROM_LE: + * @val: value + * + * Convert 32-bit floating point value (float) from little endian byte order + * into native byte order. + * + * Since: 0.10.22 + * + */ +/** + * GFLOAT_FROM_BE: + * @val: value + * + * Convert 32-bit floating point value (float) from big endian byte order + * into native byte order. + * + * Since: 0.10.22 + * + */ + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +#define GFLOAT_TO_LE(val) ((gfloat) (val)) +#define GFLOAT_TO_BE(val) (GFLOAT_SWAP_LE_BE (val)) +#define GDOUBLE_TO_LE(val) ((gdouble) (val)) +#define GDOUBLE_TO_BE(val) (GDOUBLE_SWAP_LE_BE (val)) + +#elif G_BYTE_ORDER == G_BIG_ENDIAN +#define GFLOAT_TO_LE(val) (GFLOAT_SWAP_LE_BE (val)) +#define GFLOAT_TO_BE(val) ((gfloat) (val)) +#define GDOUBLE_TO_LE(val) (GDOUBLE_SWAP_LE_BE (val)) +#define GDOUBLE_TO_BE(val) ((gdouble) (val)) + +#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ +#error unknown ENDIAN type +#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ + +#define GFLOAT_FROM_LE(val) (GFLOAT_TO_LE (val)) +#define GFLOAT_FROM_BE(val) (GFLOAT_TO_BE (val)) +#define GDOUBLE_FROM_LE(val) (GDOUBLE_TO_LE (val)) +#define GDOUBLE_FROM_BE(val) (GDOUBLE_TO_BE (val)) + +#endif /* !defined(GFLOAT_FROM_LE) */ + +/** + * GST_READ_FLOAT_LE: + * @data: memory location + * + * Read a 32 bit float value in little endian format from the memory buffer. + * + * Returns: The floating point value read from @data + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC gfloat GST_READ_FLOAT_LE (const guint8 *data); +#endif + +inline static gfloat +GST_READ_FLOAT_LE(const guint8 *data) +{ + union + { + guint32 i; + gfloat f; + } u; + + u.i = GST_READ_UINT32_LE (data); + return u.f; +} + +/** + * GST_READ_FLOAT_BE: + * @data: memory location + * + * Read a 32 bit float value in big endian format from the memory buffer. + * + * Returns: The floating point value read from @data + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC gfloat GST_READ_FLOAT_BE (const guint8 *data); +#endif + +inline static gfloat +GST_READ_FLOAT_BE(const guint8 *data) +{ + union + { + guint32 i; + gfloat f; + } u; + + u.i = GST_READ_UINT32_BE (data); + return u.f; +} + +/** + * GST_READ_DOUBLE_LE: + * @data: memory location + * + * Read a 64 bit double value in little endian format from the memory buffer. + * + * Returns: The double-precision floating point value read from @data + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC gdouble GST_READ_DOUBLE_LE (const guint8 *data); +#endif + +inline static gdouble +GST_READ_DOUBLE_LE(const guint8 *data) +{ + union + { + guint64 i; + gdouble d; + } u; + + u.i = GST_READ_UINT64_LE (data); + return u.d; +} + +/** + * GST_READ_DOUBLE_BE: + * @data: memory location + * + * Read a 64 bit double value in big endian format from the memory buffer. + * + * Returns: The double-precision floating point value read from @data + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC gdouble GST_READ_DOUBLE_BE (const guint8 *data); +#endif + +inline static gdouble +GST_READ_DOUBLE_BE(const guint8 *data) +{ + union + { + guint64 i; + gdouble d; + } u; + + u.i = GST_READ_UINT64_BE (data); + return u.d; +} + +/** + * GST_WRITE_FLOAT_LE: + * @data: memory location + * @num: value to store + * + * Store a 32 bit float value in little endian format into the memory buffer. + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void GST_WRITE_FLOAT_LE (guint8 *data, gfloat num); +#endif + +inline static void +GST_WRITE_FLOAT_LE(guint8 *data, gfloat num) +{ + union + { + guint32 i; + gfloat f; + } u; + + u.f = num; + GST_WRITE_UINT32_LE (data, u.i); +} + +/** + * GST_WRITE_FLOAT_BE: + * @data: memory location + * @num: value to store + * + * Store a 32 bit float value in big endian format into the memory buffer. + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void GST_WRITE_FLOAT_BE (guint8 *data, gfloat num); +#endif + +inline static void +GST_WRITE_FLOAT_BE(guint8 *data, gfloat num) +{ + union + { + guint32 i; + gfloat f; + } u; + + u.f = num; + GST_WRITE_UINT32_BE (data, u.i); +} + +/** + * GST_WRITE_DOUBLE_LE: + * @data: memory location + * @num: value to store + * + * Store a 64 bit double value in little endian format into the memory buffer. + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void GST_WRITE_DOUBLE_LE (guint8 *data, gdouble num); +#endif + +inline static void +GST_WRITE_DOUBLE_LE(guint8 *data, gdouble num) +{ + union + { + guint64 i; + gdouble d; + } u; + + u.d = num; + GST_WRITE_UINT64_LE (data, u.i); +} + +/** + * GST_WRITE_DOUBLE_BE: + * @data: memory location + * @num: value to store + * + * Store a 64 bit double value in big endian format into the memory buffer. + * + * Since: 0.10.22 + * + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void GST_WRITE_DOUBLE_BE (guint8 *data, gdouble num); +#endif + +inline static void +GST_WRITE_DOUBLE_BE(guint8 *data, gdouble num) +{ + union + { + guint64 i; + gdouble d; + } u; + + u.d = num; + GST_WRITE_UINT64_BE (data, u.i); +} + +/* Miscellaneous utility macros */ + +/** + * GST_ROUND_UP_2: + * @num: integer value to round up + * + * Rounds an integer value up to the next multiple of 2. + */ +#define GST_ROUND_UP_2(num) (((num)+1)&~1) +/** + * GST_ROUND_UP_4: + * @num: integer value to round up + * + * Rounds an integer value up to the next multiple of 4. + */ +#define GST_ROUND_UP_4(num) (((num)+3)&~3) +/** + * GST_ROUND_UP_8: + * @num: integer value to round up + * + * Rounds an integer value up to the next multiple of 8. + */ +#define GST_ROUND_UP_8(num) (((num)+7)&~7) +/** + * GST_ROUND_UP_16: + * @num: integer value to round up + * + * Rounds an integer value up to the next multiple of 16. + */ +#define GST_ROUND_UP_16(num) (((num)+15)&~15) +/** + * GST_ROUND_UP_32: + * @num: integer value to round up + * + * Rounds an integer value up to the next multiple of 32. + */ +#define GST_ROUND_UP_32(num) (((num)+31)&~31) +/** + * GST_ROUND_UP_64: + * @num: integer value to round up + * + * Rounds an integer value up to the next multiple of 64. + */ +#define GST_ROUND_UP_64(num) (((num)+63)&~63) + +/** + * GST_ROUND_DOWN_2: + * @num: integer value to round down + * + * Rounds an integer value down to the next multiple of 2. + * + * Since: 0.10.12 + */ +#define GST_ROUND_DOWN_2(num) ((num)&(~1)) +/** + * GST_ROUND_DOWN_4: + * @num: integer value to round down + * + * Rounds an integer value down to the next multiple of 4. + * + * Since: 0.10.12 + */ +#define GST_ROUND_DOWN_4(num) ((num)&(~3)) +/** + * GST_ROUND_DOWN_8: + * @num: integer value to round down + * + * Rounds an integer value down to the next multiple of 8. + * + * Since: 0.10.12 + */ +#define GST_ROUND_DOWN_8(num) ((num)&(~7)) +/** + * GST_ROUND_DOWN_16: + * @num: integer value to round down + * + * Rounds an integer value down to the next multiple of 16. + * + * Since: 0.10.12 + */ +#define GST_ROUND_DOWN_16(num) ((num)&(~15)) +/** + * GST_ROUND_DOWN_32: + * @num: integer value to round down + * + * Rounds an integer value down to the next multiple of 32. + * + * Since: 0.10.12 + */ +#define GST_ROUND_DOWN_32(num) ((num)&(~31)) +/** + * GST_ROUND_DOWN_64: + * @num: integer value to round down + * + * Rounds an integer value down to the next multiple of 64. + * + * Since: 0.10.12 + */ +#define GST_ROUND_DOWN_64(num) ((num)&(~63)) + +void gst_object_default_error (GstObject * source, + const GError * error, + const gchar * debug); + +/* element functions */ +void gst_element_create_all_pads (GstElement *element); +GstPad* gst_element_get_compatible_pad (GstElement *element, GstPad *pad, + const GstCaps *caps); + +GstPadTemplate* gst_element_get_compatible_pad_template (GstElement *element, GstPadTemplate *compattempl); + +const gchar* gst_element_state_get_name (GstState state); +const gchar * gst_element_state_change_return_get_name (GstStateChangeReturn state_ret); + +gboolean gst_element_link (GstElement *src, GstElement *dest); +gboolean gst_element_link_many (GstElement *element_1, + GstElement *element_2, ...) G_GNUC_NULL_TERMINATED; +gboolean gst_element_link_filtered (GstElement * src, + GstElement * dest, + GstCaps *filter); +void gst_element_unlink (GstElement *src, GstElement *dest); +void gst_element_unlink_many (GstElement *element_1, + GstElement *element_2, ...) G_GNUC_NULL_TERMINATED; + +gboolean gst_element_link_pads (GstElement *src, const gchar *srcpadname, + GstElement *dest, const gchar *destpadname); +gboolean gst_element_link_pads_full (GstElement *src, const gchar *srcpadname, + GstElement *dest, const gchar *destpadname, + GstPadLinkCheck flags); +void gst_element_unlink_pads (GstElement *src, const gchar *srcpadname, + GstElement *dest, const gchar *destpadname); + +gboolean gst_element_link_pads_filtered (GstElement * src, const gchar * srcpadname, + GstElement * dest, const gchar * destpadname, + GstCaps *filter); + +gboolean gst_element_seek_simple (GstElement *element, + GstFormat format, + GstSeekFlags seek_flags, + gint64 seek_pos); + +/* util elementfactory functions */ +gboolean gst_element_factory_can_sink_all_caps (GstElementFactory *factory, const GstCaps *caps); +gboolean gst_element_factory_can_src_all_caps (GstElementFactory *factory, const GstCaps *caps); +gboolean gst_element_factory_can_sink_any_caps (GstElementFactory *factory, const GstCaps *caps); +gboolean gst_element_factory_can_src_any_caps (GstElementFactory *factory, const GstCaps *caps); + +/* util query functions */ +gboolean gst_element_query_position (GstElement *element, GstFormat format, gint64 *cur); +gboolean gst_element_query_duration (GstElement *element, GstFormat format, gint64 *duration); +gboolean gst_element_query_convert (GstElement *element, GstFormat src_format, gint64 src_val, + GstFormat dest_format, gint64 *dest_val); + +/* element class functions */ +void gst_element_class_install_std_props (GstElementClass * klass, + const gchar * first_name, ...) G_GNUC_NULL_TERMINATED; + +/* pad functions */ +void gst_pad_use_fixed_caps (GstPad *pad); +GstCaps* gst_pad_proxy_getcaps (GstPad * pad, GstCaps * filter); + +GstElement* gst_pad_get_parent_element (GstPad *pad); + +/* util query functions */ +gboolean gst_pad_query_position (GstPad *pad, GstFormat format, gint64 *cur); +gboolean gst_pad_query_duration (GstPad *pad, GstFormat format, gint64 *duration); +gboolean gst_pad_query_convert (GstPad *pad, GstFormat src_format, gint64 src_val, + GstFormat dest_format, gint64 *dest_val); + +gboolean gst_pad_query_peer_position (GstPad *pad, GstFormat format, gint64 *cur); +gboolean gst_pad_query_peer_duration (GstPad *pad, GstFormat format, gint64 *duration); +gboolean gst_pad_query_peer_convert (GstPad *pad, GstFormat src_format, gint64 src_val, + GstFormat dest_format, gint64 *dest_val); + +/* bin functions */ +void gst_bin_add_many (GstBin *bin, GstElement *element_1, ...) G_GNUC_NULL_TERMINATED; +void gst_bin_remove_many (GstBin *bin, GstElement *element_1, ...) G_GNUC_NULL_TERMINATED; +GstPad * gst_bin_find_unlinked_pad (GstBin *bin, GstPadDirection direction); + +/* buffer functions */ +GstBuffer * gst_buffer_merge (GstBuffer * buf1, GstBuffer * buf2); +GstBuffer * gst_buffer_join (GstBuffer * buf1, GstBuffer * buf2); + +/* tag emission utility functions */ +void gst_element_found_tags_for_pad (GstElement * element, + GstPad * pad, + GstTagList * list); +void gst_element_found_tags (GstElement * element, + GstTagList * list); + +/* parse utility functions */ +GstElement * gst_parse_bin_from_description (const gchar * bin_description, + gboolean ghost_unlinked_pads, + GError ** err); + +GstElement * gst_parse_bin_from_description_full (const gchar * bin_description, + gboolean ghost_unlinked_pads, + GstParseContext * context, + GstParseFlags flags, + GError ** err); + +GstClockTime gst_util_get_timestamp (void); + +/** + * GstSearchMode: + * @GST_SEARCH_MODE_EXACT : Only search for exact matches. + * @GST_SEARCH_MODE_BEFORE: Search for an exact match or the element just before. + * @GST_SEARCH_MODE_AFTER : Search for an exact match or the element just after. + * + * The different search modes. + * + * Since: 0.10.23 + */ +typedef enum { + GST_SEARCH_MODE_EXACT = 0, + GST_SEARCH_MODE_BEFORE, + GST_SEARCH_MODE_AFTER +} GstSearchMode; + +gpointer gst_util_array_binary_search (gpointer array, guint num_elements, + gsize element_size, GCompareDataFunc search_func, + GstSearchMode mode, gconstpointer search_data, + gpointer user_data); + +gint gst_util_greatest_common_divisor (gint a, gint b); +void gst_util_fraction_to_double (gint src_n, gint src_d, gdouble *dest); +void gst_util_double_to_fraction (gdouble src, gint *dest_n, gint *dest_d); +gboolean gst_util_fraction_multiply (gint a_n, gint a_d, gint b_n, gint b_d, gint *res_n, gint *res_d); +gboolean gst_util_fraction_add (gint a_n, gint a_d, gint b_n, gint b_d, gint *res_n, gint *res_d); +gint gst_util_fraction_compare (gint a_n, gint a_d, gint b_n, gint b_d); + + +G_END_DECLS + +#endif /* __GST_UTILS_H__ */ diff --git a/gst/gstvalue.c b/gst/gstvalue.c new file mode 100644 index 0000000..6cbed59 --- /dev/null +++ b/gst/gstvalue.c @@ -0,0 +1,4971 @@ +/* GStreamer + * Copyright (C) <2003> David A. Schleef <ds@schleef.org> + * + * 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:gstvalue + * @short_description: GValue implementations specific + * to GStreamer + * + * GValue implementations specific to GStreamer. + * + * Note that operations on the same #GValue from multiple threads may lead to + * undefined behaviour. + * + * Last reviewed on 2008-03-11 (0.10.18) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include "gst_private.h" +#include "glib-compat-private.h" +#include <gst/gst.h> +#include <gobject/gvaluecollector.h> +#include "gstutils.h" + +typedef struct _GstValueUnionInfo GstValueUnionInfo; +struct _GstValueUnionInfo +{ + GType type1; + GType type2; + GstValueUnionFunc func; +}; + +typedef struct _GstValueIntersectInfo GstValueIntersectInfo; +struct _GstValueIntersectInfo +{ + GType type1; + GType type2; + GstValueIntersectFunc func; +}; + +typedef struct _GstValueSubtractInfo GstValueSubtractInfo; +struct _GstValueSubtractInfo +{ + GType minuend; + GType subtrahend; + GstValueSubtractFunc func; +}; + +#define FUNDAMENTAL_TYPE_ID_MAX \ + (G_TYPE_FUNDAMENTAL_MAX >> G_TYPE_FUNDAMENTAL_SHIFT) +#define FUNDAMENTAL_TYPE_ID(type) \ + ((type) >> G_TYPE_FUNDAMENTAL_SHIFT) + +#define VALUE_LIST_SIZE(v) (((GArray *) (v)->data[0].v_pointer)->len) +#define VALUE_LIST_GET_VALUE(v, index) ((const GValue *) &g_array_index ((GArray *) (v)->data[0].v_pointer, GValue, (index))) + +static GArray *gst_value_table; +static GHashTable *gst_value_hash; +static GstValueTable *gst_value_tables_fundamental[FUNDAMENTAL_TYPE_ID_MAX + 1]; +static GArray *gst_value_union_funcs; +static GArray *gst_value_intersect_funcs; +static GArray *gst_value_subtract_funcs; + +/* Forward declarations */ +static gchar *gst_value_serialize_fraction (const GValue * value); + +static GstValueCompareFunc gst_value_get_compare_func (const GValue * value1); +static gint gst_value_compare_with_func (const GValue * value1, + const GValue * value2, GstValueCompareFunc compare); + +static gchar *gst_string_wrap (const gchar * s); +static gchar *gst_string_take_and_wrap (gchar * s); +static gchar *gst_string_unwrap (const gchar * s); + +static inline GstValueTable * +gst_value_hash_lookup_type (GType type) +{ + if (G_LIKELY (G_TYPE_IS_FUNDAMENTAL (type))) + return gst_value_tables_fundamental[FUNDAMENTAL_TYPE_ID (type)]; + else + return g_hash_table_lookup (gst_value_hash, (gpointer) type); +} + +static void +gst_value_hash_add_type (GType type, const GstValueTable * table) +{ + if (G_TYPE_IS_FUNDAMENTAL (type)) + gst_value_tables_fundamental[FUNDAMENTAL_TYPE_ID (type)] = (gpointer) table; + + g_hash_table_insert (gst_value_hash, (gpointer) type, (gpointer) table); +} + +/******** + * list * + ********/ + +/* two helper functions to serialize/stringify any type of list + * regular lists are done with { }, arrays with < > + */ +static gchar * +gst_value_serialize_any_list (const GValue * value, const gchar * begin, + const gchar * end) +{ + guint i; + GArray *array = value->data[0].v_pointer; + GString *s; + GValue *v; + gchar *s_val; + guint alen = array->len; + + /* estimate minimum string length to minimise re-allocs in GString */ + s = g_string_sized_new (2 + (6 * alen) + 2); + g_string_append (s, begin); + for (i = 0; i < alen; i++) { + v = &g_array_index (array, GValue, i); + s_val = gst_value_serialize (v); + g_string_append (s, s_val); + g_free (s_val); + if (i < alen - 1) { + g_string_append_len (s, ", ", 2); + } + } + g_string_append (s, end); + return g_string_free (s, FALSE); +} + +static void +gst_value_transform_any_list_string (const GValue * src_value, + GValue * dest_value, const gchar * begin, const gchar * end) +{ + GValue *list_value; + GArray *array; + GString *s; + guint i; + gchar *list_s; + guint alen; + + array = src_value->data[0].v_pointer; + alen = array->len; + + /* estimate minimum string length to minimise re-allocs in GString */ + s = g_string_sized_new (2 + (10 * alen) + 2); + g_string_append (s, begin); + for (i = 0; i < alen; i++) { + list_value = &g_array_index (array, GValue, i); + + if (i != 0) { + g_string_append_len (s, ", ", 2); + } + list_s = g_strdup_value_contents (list_value); + g_string_append (s, list_s); + g_free (list_s); + } + g_string_append (s, end); + + dest_value->data[0].v_pointer = g_string_free (s, FALSE); +} + +/* + * helper function to see if a type is fixed. Is used internally here and + * there. Do not export, since it doesn't work for types where the content + * decides the fixedness (e.g. GST_TYPE_ARRAY). + */ +static gboolean +gst_type_is_fixed (GType type) +{ + /* the basic int, string, double types */ + if (type <= G_TYPE_MAKE_FUNDAMENTAL (G_TYPE_RESERVED_GLIB_LAST)) { + return TRUE; + } + /* our fundamental types that are certainly not fixed */ + if (type == GST_TYPE_INT_RANGE || type == GST_TYPE_DOUBLE_RANGE || + type == GST_TYPE_INT64_RANGE || + type == GST_TYPE_LIST || type == GST_TYPE_FRACTION_RANGE) { + return FALSE; + } + /* other (boxed) types that are fixed */ + if (type == GST_TYPE_BUFFER) { + return TRUE; + } + /* heavy checks */ + if (G_TYPE_IS_FUNDAMENTAL (type) || G_TYPE_FUNDAMENTAL (type) <= + G_TYPE_MAKE_FUNDAMENTAL (G_TYPE_RESERVED_GLIB_LAST)) { + return TRUE; + } + + return FALSE; +} + +/* GValue functions usable for both regular lists and arrays */ +static void +gst_value_init_list_or_array (GValue * value) +{ + value->data[0].v_pointer = g_array_new (FALSE, TRUE, sizeof (GValue)); +} + +static GArray * +copy_garray_of_gstvalue (const GArray * src) +{ + GArray *dest; + guint i, len; + + len = src->len; + dest = g_array_sized_new (FALSE, TRUE, sizeof (GValue), len); + g_array_set_size (dest, len); + for (i = 0; i < len; i++) { + gst_value_init_and_copy (&g_array_index (dest, GValue, i), + &g_array_index (src, GValue, i)); + } + + return dest; +} + +static void +gst_value_copy_list_or_array (const GValue * src_value, GValue * dest_value) +{ + dest_value->data[0].v_pointer = + copy_garray_of_gstvalue ((GArray *) src_value->data[0].v_pointer); +} + +static void +gst_value_free_list_or_array (GValue * value) +{ + guint i, len; + GArray *src = (GArray *) value->data[0].v_pointer; + len = src->len; + + if ((value->data[1].v_uint & G_VALUE_NOCOPY_CONTENTS) == 0) { + for (i = 0; i < len; i++) { + g_value_unset (&g_array_index (src, GValue, i)); + } + g_array_free (src, TRUE); + } +} + +static gpointer +gst_value_list_or_array_peek_pointer (const GValue * value) +{ + return value->data[0].v_pointer; +} + +static gchar * +gst_value_collect_list_or_array (GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + if (collect_flags & G_VALUE_NOCOPY_CONTENTS) { + value->data[0].v_pointer = collect_values[0].v_pointer; + value->data[1].v_uint = G_VALUE_NOCOPY_CONTENTS; + } else { + value->data[0].v_pointer = + copy_garray_of_gstvalue ((GArray *) collect_values[0].v_pointer); + } + return NULL; +} + +static gchar * +gst_value_lcopy_list_or_array (const GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + GArray **dest = collect_values[0].v_pointer; + + if (!dest) + return g_strdup_printf ("value location for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + if (!value->data[0].v_pointer) + return g_strdup_printf ("invalid value given for `%s'", + G_VALUE_TYPE_NAME (value)); + if (collect_flags & G_VALUE_NOCOPY_CONTENTS) { + *dest = (GArray *) value->data[0].v_pointer; + } else { + *dest = copy_garray_of_gstvalue ((GArray *) value->data[0].v_pointer); + } + return NULL; +} + +/** + * gst_value_list_append_value: + * @value: a #GValue of type #GST_TYPE_LIST + * @append_value: the value to append + * + * Appends @append_value to the GstValueList in @value. + */ +void +gst_value_list_append_value (GValue * value, const GValue * append_value) +{ + GValue val = { 0, }; + + g_return_if_fail (GST_VALUE_HOLDS_LIST (value)); + g_return_if_fail (G_IS_VALUE (append_value)); + + gst_value_init_and_copy (&val, append_value); + g_array_append_vals ((GArray *) value->data[0].v_pointer, &val, 1); +} + +/** + * gst_value_list_prepend_value: + * @value: a #GValue of type #GST_TYPE_LIST + * @prepend_value: the value to prepend + * + * Prepends @prepend_value to the GstValueList in @value. + */ +void +gst_value_list_prepend_value (GValue * value, const GValue * prepend_value) +{ + GValue val = { 0, }; + + g_return_if_fail (GST_VALUE_HOLDS_LIST (value)); + g_return_if_fail (G_IS_VALUE (prepend_value)); + + gst_value_init_and_copy (&val, prepend_value); + g_array_prepend_vals ((GArray *) value->data[0].v_pointer, &val, 1); +} + +/** + * gst_value_list_concat: + * @dest: (out caller-allocates): an uninitialized #GValue to take the result + * @value1: a #GValue + * @value2: a #GValue + * + * Concatenates copies of @value1 and @value2 into a list. Values that are not + * of type #GST_TYPE_LIST are treated as if they were lists of length 1. + * @dest will be initialized to the type #GST_TYPE_LIST. + */ +void +gst_value_list_concat (GValue * dest, const GValue * value1, + const GValue * value2) +{ + guint i, value1_length, value2_length; + GArray *array; + + g_return_if_fail (dest != NULL); + g_return_if_fail (G_VALUE_TYPE (dest) == 0); + g_return_if_fail (G_IS_VALUE (value1)); + g_return_if_fail (G_IS_VALUE (value2)); + + value1_length = + (GST_VALUE_HOLDS_LIST (value1) ? VALUE_LIST_SIZE (value1) : 1); + value2_length = + (GST_VALUE_HOLDS_LIST (value2) ? VALUE_LIST_SIZE (value2) : 1); + g_value_init (dest, GST_TYPE_LIST); + array = (GArray *) dest->data[0].v_pointer; + g_array_set_size (array, value1_length + value2_length); + + if (GST_VALUE_HOLDS_LIST (value1)) { + for (i = 0; i < value1_length; i++) { + gst_value_init_and_copy (&g_array_index (array, GValue, i), + VALUE_LIST_GET_VALUE (value1, i)); + } + } else { + gst_value_init_and_copy (&g_array_index (array, GValue, 0), value1); + } + + if (GST_VALUE_HOLDS_LIST (value2)) { + for (i = 0; i < value2_length; i++) { + gst_value_init_and_copy (&g_array_index (array, GValue, + i + value1_length), VALUE_LIST_GET_VALUE (value2, i)); + } + } else { + gst_value_init_and_copy (&g_array_index (array, GValue, value1_length), + value2); + } +} + +/** + * gst_value_list_merge: + * @dest: (out caller-allocates): an uninitialized #GValue to take the result + * @value1: a #GValue + * @value2: a #GValue + * + * Merges copies of @value1 and @value2. Values that are not + * of type #GST_TYPE_LIST are treated as if they were lists of length 1. + * + * The result will be put into @dest and will either be a list that will not + * contain any duplicates, or a non-list type (if @value1 and @value2 + * were equal). + * + * Since: 0.10.32 + */ +void +gst_value_list_merge (GValue * dest, const GValue * value1, + const GValue * value2) +{ + guint i, j, k, value1_length, value2_length, skipped; + const GValue *src; + gboolean skip; + GArray *array; + + g_return_if_fail (dest != NULL); + g_return_if_fail (G_VALUE_TYPE (dest) == 0); + g_return_if_fail (G_IS_VALUE (value1)); + g_return_if_fail (G_IS_VALUE (value2)); + + value1_length = + (GST_VALUE_HOLDS_LIST (value1) ? VALUE_LIST_SIZE (value1) : 1); + value2_length = + (GST_VALUE_HOLDS_LIST (value2) ? VALUE_LIST_SIZE (value2) : 1); + g_value_init (dest, GST_TYPE_LIST); + array = (GArray *) dest->data[0].v_pointer; + g_array_set_size (array, value1_length + value2_length); + + if (GST_VALUE_HOLDS_LIST (value1)) { + for (i = 0; i < value1_length; i++) { + gst_value_init_and_copy (&g_array_index (array, GValue, i), + VALUE_LIST_GET_VALUE (value1, i)); + } + } else { + gst_value_init_and_copy (&g_array_index (array, GValue, 0), value1); + } + + j = value1_length; + skipped = 0; + if (GST_VALUE_HOLDS_LIST (value2)) { + for (i = 0; i < value2_length; i++) { + skip = FALSE; + src = VALUE_LIST_GET_VALUE (value2, i); + for (k = 0; k < value1_length; k++) { + if (gst_value_compare (&g_array_index (array, GValue, k), + src) == GST_VALUE_EQUAL) { + skip = TRUE; + skipped++; + break; + } + } + if (!skip) { + gst_value_init_and_copy (&g_array_index (array, GValue, j), src); + j++; + } + } + } else { + skip = FALSE; + for (k = 0; k < value1_length; k++) { + if (gst_value_compare (&g_array_index (array, GValue, k), + value2) == GST_VALUE_EQUAL) { + skip = TRUE; + skipped++; + break; + } + } + if (!skip) { + gst_value_init_and_copy (&g_array_index (array, GValue, j), value2); + } + } + if (skipped) { + guint new_size = value1_length + (value2_length - skipped); + + if (new_size > 1) { + /* shrink list */ + g_array_set_size (array, new_size); + } else { + GValue single_dest; + + /* size is 1, take single value in list and make it new dest */ + single_dest = g_array_index (array, GValue, 0); + + /* clean up old value allocations: must set array size to 0, because + * allocated values are not inited meaning g_value_unset() will not + * work on them */ + g_array_set_size (array, 0); + g_value_unset (dest); + + /* the single value is our new result */ + *dest = single_dest; + } + } +} + +/** + * gst_value_list_get_size: + * @value: a #GValue of type #GST_TYPE_LIST + * + * Gets the number of values contained in @value. + * + * Returns: the number of values + */ +guint +gst_value_list_get_size (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_LIST (value), 0); + + return ((GArray *) value->data[0].v_pointer)->len; +} + +/** + * gst_value_list_get_value: + * @value: a #GValue of type #GST_TYPE_LIST + * @index: index of value to get from the list + * + * Gets the value that is a member of the list contained in @value and + * has the index @index. + * + * Returns: (transfer none): the value at the given index + */ +const GValue * +gst_value_list_get_value (const GValue * value, guint index) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_LIST (value), NULL); + g_return_val_if_fail (index < VALUE_LIST_SIZE (value), NULL); + + return (const GValue *) &g_array_index ((GArray *) value->data[0].v_pointer, + GValue, index); +} + +/** + * gst_value_array_append_value: + * @value: a #GValue of type #GST_TYPE_ARRAY + * @append_value: the value to append + * + * Appends @append_value to the GstValueArray in @value. + */ +void +gst_value_array_append_value (GValue * value, const GValue * append_value) +{ + GValue val = { 0, }; + + g_return_if_fail (GST_VALUE_HOLDS_ARRAY (value)); + g_return_if_fail (G_IS_VALUE (append_value)); + + gst_value_init_and_copy (&val, append_value); + g_array_append_vals ((GArray *) value->data[0].v_pointer, &val, 1); +} + +/** + * gst_value_array_prepend_value: + * @value: a #GValue of type #GST_TYPE_ARRAY + * @prepend_value: the value to prepend + * + * Prepends @prepend_value to the GstValueArray in @value. + */ +void +gst_value_array_prepend_value (GValue * value, const GValue * prepend_value) +{ + GValue val = { 0, }; + + g_return_if_fail (GST_VALUE_HOLDS_ARRAY (value)); + g_return_if_fail (G_IS_VALUE (prepend_value)); + + gst_value_init_and_copy (&val, prepend_value); + g_array_prepend_vals ((GArray *) value->data[0].v_pointer, &val, 1); +} + +/** + * gst_value_array_get_size: + * @value: a #GValue of type #GST_TYPE_ARRAY + * + * Gets the number of values contained in @value. + * + * Returns: the number of values + */ +guint +gst_value_array_get_size (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_ARRAY (value), 0); + + return ((GArray *) value->data[0].v_pointer)->len; +} + +/** + * gst_value_array_get_value: + * @value: a #GValue of type #GST_TYPE_ARRAY + * @index: index of value to get from the array + * + * Gets the value that is a member of the array contained in @value and + * has the index @index. + * + * Returns: (transfer none): the value at the given index + */ +const GValue * +gst_value_array_get_value (const GValue * value, guint index) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_ARRAY (value), NULL); + g_return_val_if_fail (index < gst_value_array_get_size (value), NULL); + + return (const GValue *) &g_array_index ((GArray *) value->data[0].v_pointer, + GValue, index); +} + +static void +gst_value_transform_list_string (const GValue * src_value, GValue * dest_value) +{ + gst_value_transform_any_list_string (src_value, dest_value, "{ ", " }"); +} + +static void +gst_value_transform_array_string (const GValue * src_value, GValue * dest_value) +{ + gst_value_transform_any_list_string (src_value, dest_value, "< ", " >"); +} + +/* Do an unordered compare of the contents of a list */ +static gint +gst_value_compare_list (const GValue * value1, const GValue * value2) +{ + guint i, j; + GArray *array1 = value1->data[0].v_pointer; + GArray *array2 = value2->data[0].v_pointer; + GValue *v1; + GValue *v2; + gint len, to_remove; + guint8 *removed; + GstValueCompareFunc compare; + + /* get length and do initial length check. */ + len = array1->len; + if (len != array2->len) + return GST_VALUE_UNORDERED; + + /* place to mark removed value indices of array2 */ + removed = g_newa (guint8, len); + memset (removed, 0, len); + to_remove = len; + + /* loop over array1, all items should be in array2. When we find an + * item in array2, remove it from array2 by marking it as removed */ + for (i = 0; i < len; i++) { + v1 = &g_array_index (array1, GValue, i); + if ((compare = gst_value_get_compare_func (v1))) { + for (j = 0; j < len; j++) { + /* item is removed, we can skip it */ + if (removed[j]) + continue; + v2 = &g_array_index (array2, GValue, j); + if (gst_value_compare_with_func (v1, v2, compare) == GST_VALUE_EQUAL) { + /* mark item as removed now that we found it in array2 and + * decrement the number of remaining items in array2. */ + removed[j] = 1; + to_remove--; + break; + } + } + /* item in array1 and not in array2, UNORDERED */ + if (j == len) + return GST_VALUE_UNORDERED; + } else + return GST_VALUE_UNORDERED; + } + /* if not all items were removed, array2 contained something not in array1 */ + if (to_remove != 0) + return GST_VALUE_UNORDERED; + + /* arrays are equal */ + return GST_VALUE_EQUAL; +} + +/* Perform an ordered comparison of the contents of an array */ +static gint +gst_value_compare_array (const GValue * value1, const GValue * value2) +{ + guint i; + GArray *array1 = value1->data[0].v_pointer; + GArray *array2 = value2->data[0].v_pointer; + guint len = array1->len; + GValue *v1; + GValue *v2; + + if (len != array2->len) + return GST_VALUE_UNORDERED; + + for (i = 0; i < len; i++) { + v1 = &g_array_index (array1, GValue, i); + v2 = &g_array_index (array2, GValue, i); + if (gst_value_compare (v1, v2) != GST_VALUE_EQUAL) + return GST_VALUE_UNORDERED; + } + + return GST_VALUE_EQUAL; +} + +static gchar * +gst_value_serialize_list (const GValue * value) +{ + return gst_value_serialize_any_list (value, "{ ", " }"); +} + +static gboolean +gst_value_deserialize_list (GValue * dest, const gchar * s) +{ + g_warning ("gst_value_deserialize_list: unimplemented"); + return FALSE; +} + +static gchar * +gst_value_serialize_array (const GValue * value) +{ + return gst_value_serialize_any_list (value, "< ", " >"); +} + +static gboolean +gst_value_deserialize_array (GValue * dest, const gchar * s) +{ + g_warning ("gst_value_deserialize_array: unimplemented"); + return FALSE; +} + +/************* + * int range * + *************/ + +static void +gst_value_init_int_range (GValue * value) +{ + value->data[0].v_int = 0; + value->data[1].v_int = 0; +} + +static void +gst_value_copy_int_range (const GValue * src_value, GValue * dest_value) +{ + dest_value->data[0].v_int = src_value->data[0].v_int; + dest_value->data[1].v_int = src_value->data[1].v_int; +} + +static gchar * +gst_value_collect_int_range (GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + if (n_collect_values != 2) + return g_strdup_printf ("not enough value locations for `%s' passed", + G_VALUE_TYPE_NAME (value)); + if (collect_values[0].v_int >= collect_values[1].v_int) + return g_strdup_printf ("range start is not smaller than end for `%s'", + G_VALUE_TYPE_NAME (value)); + + value->data[0].v_int = collect_values[0].v_int; + value->data[1].v_int = collect_values[1].v_int; + + return NULL; +} + +static gchar * +gst_value_lcopy_int_range (const GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + guint32 *int_range_start = collect_values[0].v_pointer; + guint32 *int_range_end = collect_values[1].v_pointer; + + if (!int_range_start) + return g_strdup_printf ("start value location for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + if (!int_range_end) + return g_strdup_printf ("end value location for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + + *int_range_start = value->data[0].v_int; + *int_range_end = value->data[1].v_int; + + return NULL; +} + +/** + * gst_value_set_int_range: + * @value: a GValue initialized to GST_TYPE_INT_RANGE + * @start: the start of the range + * @end: the end of the range + * + * Sets @value to the range specified by @start and @end. + */ +void +gst_value_set_int_range (GValue * value, gint start, gint end) +{ + g_return_if_fail (GST_VALUE_HOLDS_INT_RANGE (value)); + g_return_if_fail (start < end); + + value->data[0].v_int = start; + value->data[1].v_int = end; +} + +/** + * gst_value_get_int_range_min: + * @value: a GValue initialized to GST_TYPE_INT_RANGE + * + * Gets the minimum of the range specified by @value. + * + * Returns: the minimum of the range + */ +gint +gst_value_get_int_range_min (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_INT_RANGE (value), 0); + + return value->data[0].v_int; +} + +/** + * gst_value_get_int_range_max: + * @value: a GValue initialized to GST_TYPE_INT_RANGE + * + * Gets the maximum of the range specified by @value. + * + * Returns: the maxumum of the range + */ +gint +gst_value_get_int_range_max (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_INT_RANGE (value), 0); + + return value->data[1].v_int; +} + +static void +gst_value_transform_int_range_string (const GValue * src_value, + GValue * dest_value) +{ + dest_value->data[0].v_pointer = g_strdup_printf ("[%d,%d]", + (int) src_value->data[0].v_int, (int) src_value->data[1].v_int); +} + +static gint +gst_value_compare_int_range (const GValue * value1, const GValue * value2) +{ + if (value2->data[0].v_int == value1->data[0].v_int && + value2->data[1].v_int == value1->data[1].v_int) + return GST_VALUE_EQUAL; + return GST_VALUE_UNORDERED; +} + +static gchar * +gst_value_serialize_int_range (const GValue * value) +{ + return g_strdup_printf ("[ %d, %d ]", value->data[0].v_int, + value->data[1].v_int); +} + +static gboolean +gst_value_deserialize_int_range (GValue * dest, const gchar * s) +{ + g_warning ("unimplemented"); + return FALSE; +} + +/*************** + * int64 range * + ***************/ + +static void +gst_value_init_int64_range (GValue * value) +{ + value->data[0].v_int64 = 0; + value->data[1].v_int64 = 0; +} + +static void +gst_value_copy_int64_range (const GValue * src_value, GValue * dest_value) +{ + dest_value->data[0].v_int64 = src_value->data[0].v_int64; + dest_value->data[1].v_int64 = src_value->data[1].v_int64; +} + +static gchar * +gst_value_collect_int64_range (GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + if (n_collect_values != 2) + return g_strdup_printf ("not enough value locations for `%s' passed", + G_VALUE_TYPE_NAME (value)); + if (collect_values[0].v_int64 >= collect_values[1].v_int64) + return g_strdup_printf ("range start is not smaller than end for `%s'", + G_VALUE_TYPE_NAME (value)); + + value->data[0].v_int64 = collect_values[0].v_int64; + value->data[1].v_int64 = collect_values[1].v_int64; + + return NULL; +} + +static gchar * +gst_value_lcopy_int64_range (const GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + guint64 *int_range_start = collect_values[0].v_pointer; + guint64 *int_range_end = collect_values[1].v_pointer; + + if (!int_range_start) + return g_strdup_printf ("start value location for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + if (!int_range_end) + return g_strdup_printf ("end value location for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + + *int_range_start = value->data[0].v_int64; + *int_range_end = value->data[1].v_int64; + + return NULL; +} + +/** + * gst_value_set_int64_range: + * @value: a GValue initialized to GST_TYPE_INT64_RANGE + * @start: the start of the range + * @end: the end of the range + * + * Sets @value to the range specified by @start and @end. + * + * Since: 0.10.31 + */ +void +gst_value_set_int64_range (GValue * value, gint64 start, gint64 end) +{ + g_return_if_fail (GST_VALUE_HOLDS_INT64_RANGE (value)); + g_return_if_fail (start < end); + + value->data[0].v_int64 = start; + value->data[1].v_int64 = end; +} + +/** + * gst_value_get_int64_range_min: + * @value: a GValue initialized to GST_TYPE_INT64_RANGE + * + * Gets the minimum of the range specified by @value. + * + * Returns: the minimum of the range + * + * Since: 0.10.31 + */ +gint64 +gst_value_get_int64_range_min (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_INT64_RANGE (value), 0); + + return value->data[0].v_int64; +} + +/** + * gst_value_get_int64_range_max: + * @value: a GValue initialized to GST_TYPE_INT64_RANGE + * + * Gets the maximum of the range specified by @value. + * + * Returns: the maxumum of the range + * + * Since: 0.10.31 + */ +gint64 +gst_value_get_int64_range_max (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_INT64_RANGE (value), 0); + + return value->data[1].v_int64; +} + +static void +gst_value_transform_int64_range_string (const GValue * src_value, + GValue * dest_value) +{ + dest_value->data[0].v_pointer = + g_strdup_printf ("(gint64)[%" G_GINT64_FORMAT ",%" G_GINT64_FORMAT "]", + src_value->data[0].v_int64, src_value->data[1].v_int64); +} + +static gint +gst_value_compare_int64_range (const GValue * value1, const GValue * value2) +{ + if (value2->data[0].v_int64 == value1->data[0].v_int64 && + value2->data[1].v_int64 == value1->data[1].v_int64) + return GST_VALUE_EQUAL; + return GST_VALUE_UNORDERED; +} + +static gchar * +gst_value_serialize_int64_range (const GValue * value) +{ + return g_strdup_printf ("[ %" G_GINT64_FORMAT ", %" G_GINT64_FORMAT " ]", + value->data[0].v_int64, value->data[1].v_int64); +} + +static gboolean +gst_value_deserialize_int64_range (GValue * dest, const gchar * s) +{ + g_warning ("unimplemented"); + return FALSE; +} + +/**************** + * double range * + ****************/ + +static void +gst_value_init_double_range (GValue * value) +{ + value->data[0].v_double = 0; + value->data[1].v_double = 0; +} + +static void +gst_value_copy_double_range (const GValue * src_value, GValue * dest_value) +{ + dest_value->data[0].v_double = src_value->data[0].v_double; + dest_value->data[1].v_double = src_value->data[1].v_double; +} + +static gchar * +gst_value_collect_double_range (GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + if (n_collect_values != 2) + return g_strdup_printf ("not enough value locations for `%s' passed", + G_VALUE_TYPE_NAME (value)); + if (collect_values[0].v_double >= collect_values[1].v_double) + return g_strdup_printf ("range start is not smaller than end for `%s'", + G_VALUE_TYPE_NAME (value)); + + value->data[0].v_double = collect_values[0].v_double; + value->data[1].v_double = collect_values[1].v_double; + + return NULL; +} + +static gchar * +gst_value_lcopy_double_range (const GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + gdouble *double_range_start = collect_values[0].v_pointer; + gdouble *double_range_end = collect_values[1].v_pointer; + + if (!double_range_start) + return g_strdup_printf ("start value location for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + if (!double_range_end) + return g_strdup_printf ("end value location for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + + *double_range_start = value->data[0].v_double; + *double_range_end = value->data[1].v_double; + + return NULL; +} + +/** + * gst_value_set_double_range: + * @value: a GValue initialized to GST_TYPE_DOUBLE_RANGE + * @start: the start of the range + * @end: the end of the range + * + * Sets @value to the range specified by @start and @end. + */ +void +gst_value_set_double_range (GValue * value, gdouble start, gdouble end) +{ + g_return_if_fail (GST_VALUE_HOLDS_DOUBLE_RANGE (value)); + g_return_if_fail (start < end); + + value->data[0].v_double = start; + value->data[1].v_double = end; +} + +/** + * gst_value_get_double_range_min: + * @value: a GValue initialized to GST_TYPE_DOUBLE_RANGE + * + * Gets the minimum of the range specified by @value. + * + * Returns: the minimum of the range + */ +gdouble +gst_value_get_double_range_min (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_DOUBLE_RANGE (value), 0); + + return value->data[0].v_double; +} + +/** + * gst_value_get_double_range_max: + * @value: a GValue initialized to GST_TYPE_DOUBLE_RANGE + * + * Gets the maximum of the range specified by @value. + * + * Returns: the maxumum of the range + */ +gdouble +gst_value_get_double_range_max (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_DOUBLE_RANGE (value), 0); + + return value->data[1].v_double; +} + +static void +gst_value_transform_double_range_string (const GValue * src_value, + GValue * dest_value) +{ + gchar s1[G_ASCII_DTOSTR_BUF_SIZE], s2[G_ASCII_DTOSTR_BUF_SIZE]; + + dest_value->data[0].v_pointer = g_strdup_printf ("[%s,%s]", + g_ascii_dtostr (s1, G_ASCII_DTOSTR_BUF_SIZE, + src_value->data[0].v_double), + g_ascii_dtostr (s2, G_ASCII_DTOSTR_BUF_SIZE, + src_value->data[1].v_double)); +} + +static gint +gst_value_compare_double_range (const GValue * value1, const GValue * value2) +{ + if (value2->data[0].v_double == value1->data[0].v_double && + value2->data[0].v_double == value1->data[0].v_double) + return GST_VALUE_EQUAL; + return GST_VALUE_UNORDERED; +} + +static gchar * +gst_value_serialize_double_range (const GValue * value) +{ + gchar d1[G_ASCII_DTOSTR_BUF_SIZE]; + gchar d2[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_dtostr (d1, G_ASCII_DTOSTR_BUF_SIZE, value->data[0].v_double); + g_ascii_dtostr (d2, G_ASCII_DTOSTR_BUF_SIZE, value->data[1].v_double); + return g_strdup_printf ("[ %s, %s ]", d1, d2); +} + +static gboolean +gst_value_deserialize_double_range (GValue * dest, const gchar * s) +{ + g_warning ("unimplemented"); + return FALSE; +} + +/**************** + * fraction range * + ****************/ + +static void +gst_value_init_fraction_range (GValue * value) +{ + GValue *vals; + GType ftype; + + ftype = GST_TYPE_FRACTION; + + value->data[0].v_pointer = vals = g_slice_alloc0 (2 * sizeof (GValue)); + g_value_init (&vals[0], ftype); + g_value_init (&vals[1], ftype); +} + +static void +gst_value_free_fraction_range (GValue * value) +{ + GValue *vals = (GValue *) value->data[0].v_pointer; + + if (vals != NULL) { + g_value_unset (&vals[0]); + g_value_unset (&vals[1]); + g_slice_free1 (2 * sizeof (GValue), vals); + value->data[0].v_pointer = NULL; + } +} + +static void +gst_value_copy_fraction_range (const GValue * src_value, GValue * dest_value) +{ + GValue *vals = (GValue *) dest_value->data[0].v_pointer; + GValue *src_vals = (GValue *) src_value->data[0].v_pointer; + + if (vals == NULL) { + gst_value_init_fraction_range (dest_value); + vals = dest_value->data[0].v_pointer; + } + if (src_vals != NULL) { + g_value_copy (&src_vals[0], &vals[0]); + g_value_copy (&src_vals[1], &vals[1]); + } +} + +static gchar * +gst_value_collect_fraction_range (GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + GValue *vals = (GValue *) value->data[0].v_pointer; + + if (n_collect_values != 4) + return g_strdup_printf ("not enough value locations for `%s' passed", + G_VALUE_TYPE_NAME (value)); + if (collect_values[1].v_int == 0) + return g_strdup_printf ("passed '0' as first denominator for `%s'", + G_VALUE_TYPE_NAME (value)); + if (collect_values[3].v_int == 0) + return g_strdup_printf ("passed '0' as second denominator for `%s'", + G_VALUE_TYPE_NAME (value)); + if (gst_util_fraction_compare (collect_values[0].v_int, + collect_values[1].v_int, collect_values[2].v_int, + collect_values[3].v_int) >= 0) + return g_strdup_printf ("range start is not smaller than end for `%s'", + G_VALUE_TYPE_NAME (value)); + + if (vals == NULL) { + gst_value_init_fraction_range (value); + vals = value->data[0].v_pointer; + } + + gst_value_set_fraction (&vals[0], collect_values[0].v_int, + collect_values[1].v_int); + gst_value_set_fraction (&vals[1], collect_values[2].v_int, + collect_values[3].v_int); + + return NULL; +} + +static gchar * +gst_value_lcopy_fraction_range (const GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + gint i; + gint *dest_values[4]; + GValue *vals = (GValue *) value->data[0].v_pointer; + + if (G_UNLIKELY (n_collect_values != 4)) + return g_strdup_printf ("not enough value locations for `%s' passed", + G_VALUE_TYPE_NAME (value)); + + for (i = 0; i < 4; i++) { + if (G_UNLIKELY (collect_values[i].v_pointer == NULL)) { + return g_strdup_printf ("value location for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + } + dest_values[i] = collect_values[i].v_pointer; + } + + if (G_UNLIKELY (vals == NULL)) { + return g_strdup_printf ("Uninitialised `%s' passed", + G_VALUE_TYPE_NAME (value)); + } + + dest_values[0][0] = gst_value_get_fraction_numerator (&vals[0]); + dest_values[1][0] = gst_value_get_fraction_denominator (&vals[0]); + dest_values[2][0] = gst_value_get_fraction_numerator (&vals[1]); + dest_values[3][0] = gst_value_get_fraction_denominator (&vals[1]); + return NULL; +} + +/** + * gst_value_set_fraction_range: + * @value: a GValue initialized to GST_TYPE_FRACTION_RANGE + * @start: the start of the range (a GST_TYPE_FRACTION GValue) + * @end: the end of the range (a GST_TYPE_FRACTION GValue) + * + * Sets @value to the range specified by @start and @end. + */ +void +gst_value_set_fraction_range (GValue * value, const GValue * start, + const GValue * end) +{ + GValue *vals; + + g_return_if_fail (GST_VALUE_HOLDS_FRACTION_RANGE (value)); + g_return_if_fail (GST_VALUE_HOLDS_FRACTION (start)); + g_return_if_fail (GST_VALUE_HOLDS_FRACTION (end)); + g_return_if_fail (gst_util_fraction_compare (start->data[0].v_int, + start->data[1].v_int, end->data[0].v_int, end->data[1].v_int) < 0); + + vals = (GValue *) value->data[0].v_pointer; + if (vals == NULL) { + gst_value_init_fraction_range (value); + vals = value->data[0].v_pointer; + } + g_value_copy (start, &vals[0]); + g_value_copy (end, &vals[1]); +} + +/** + * gst_value_set_fraction_range_full: + * @value: a GValue initialized to GST_TYPE_FRACTION_RANGE + * @numerator_start: the numerator start of the range + * @denominator_start: the denominator start of the range + * @numerator_end: the numerator end of the range + * @denominator_end: the denominator end of the range + * + * Sets @value to the range specified by @numerator_start/@denominator_start + * and @numerator_end/@denominator_end. + */ +void +gst_value_set_fraction_range_full (GValue * value, + gint numerator_start, gint denominator_start, + gint numerator_end, gint denominator_end) +{ + GValue start = { 0 }; + GValue end = { 0 }; + + g_return_if_fail (value != NULL); + g_return_if_fail (denominator_start != 0); + g_return_if_fail (denominator_end != 0); + g_return_if_fail (gst_util_fraction_compare (numerator_start, + denominator_start, numerator_end, denominator_end) < 0); + + g_value_init (&start, GST_TYPE_FRACTION); + g_value_init (&end, GST_TYPE_FRACTION); + + gst_value_set_fraction (&start, numerator_start, denominator_start); + gst_value_set_fraction (&end, numerator_end, denominator_end); + gst_value_set_fraction_range (value, &start, &end); + + g_value_unset (&start); + g_value_unset (&end); +} + +/** + * gst_value_get_fraction_range_min: + * @value: a GValue initialized to GST_TYPE_FRACTION_RANGE + * + * Gets the minimum of the range specified by @value. + * + * Returns: the minimum of the range + */ +const GValue * +gst_value_get_fraction_range_min (const GValue * value) +{ + GValue *vals; + + g_return_val_if_fail (GST_VALUE_HOLDS_FRACTION_RANGE (value), NULL); + + vals = (GValue *) value->data[0].v_pointer; + if (vals != NULL) { + return &vals[0]; + } + + return NULL; +} + +/** + * gst_value_get_fraction_range_max: + * @value: a GValue initialized to GST_TYPE_FRACTION_RANGE + * + * Gets the maximum of the range specified by @value. + * + * Returns: the maximum of the range + */ +const GValue * +gst_value_get_fraction_range_max (const GValue * value) +{ + GValue *vals; + + g_return_val_if_fail (GST_VALUE_HOLDS_FRACTION_RANGE (value), NULL); + + vals = (GValue *) value->data[0].v_pointer; + if (vals != NULL) { + return &vals[1]; + } + + return NULL; +} + +static gchar * +gst_value_serialize_fraction_range (const GValue * value) +{ + GValue *vals = (GValue *) value->data[0].v_pointer; + gchar *retval; + + if (vals == NULL) { + retval = g_strdup ("[ 0/1, 0/1 ]"); + } else { + gchar *start, *end; + + start = gst_value_serialize_fraction (&vals[0]); + end = gst_value_serialize_fraction (&vals[1]); + + retval = g_strdup_printf ("[ %s, %s ]", start, end); + g_free (start); + g_free (end); + } + + return retval; +} + +static void +gst_value_transform_fraction_range_string (const GValue * src_value, + GValue * dest_value) +{ + dest_value->data[0].v_pointer = + gst_value_serialize_fraction_range (src_value); +} + +static gint +gst_value_compare_fraction_range (const GValue * value1, const GValue * value2) +{ + GValue *vals1, *vals2; + GstValueCompareFunc compare; + + if (value2->data[0].v_pointer == value1->data[0].v_pointer) + return GST_VALUE_EQUAL; /* Only possible if both are NULL */ + + if (value2->data[0].v_pointer == NULL || value1->data[0].v_pointer == NULL) + return GST_VALUE_UNORDERED; + + vals1 = (GValue *) value1->data[0].v_pointer; + vals2 = (GValue *) value2->data[0].v_pointer; + if ((compare = gst_value_get_compare_func (&vals1[0]))) { + if (gst_value_compare_with_func (&vals1[0], &vals2[0], compare) == + GST_VALUE_EQUAL && + gst_value_compare_with_func (&vals1[1], &vals2[1], compare) == + GST_VALUE_EQUAL) + return GST_VALUE_EQUAL; + } + return GST_VALUE_UNORDERED; +} + +static gboolean +gst_value_deserialize_fraction_range (GValue * dest, const gchar * s) +{ + g_warning ("unimplemented"); + return FALSE; +} + +/*********** + * GstCaps * + ***********/ + +/** + * gst_value_set_caps: + * @value: a GValue initialized to GST_TYPE_CAPS + * @caps: (transfer none): the caps to set the value to + * + * Sets the contents of @value to @caps. A reference to the + * provided @caps will be taken by the @value. + */ +void +gst_value_set_caps (GValue * value, const GstCaps * caps) +{ + g_return_if_fail (G_IS_VALUE (value)); + g_return_if_fail (G_VALUE_TYPE (value) == GST_TYPE_CAPS); + g_return_if_fail (caps == NULL || GST_IS_CAPS (caps)); + + g_value_set_boxed (value, caps); +} + +/** + * gst_value_get_caps: + * @value: a GValue initialized to GST_TYPE_CAPS + * + * Gets the contents of @value. The reference count of the returned + * #GstCaps will not be modified, therefore the caller must take one + * before getting rid of the @value. + * + * Returns: (transfer none): the contents of @value + */ +const GstCaps * +gst_value_get_caps (const GValue * value) +{ + g_return_val_if_fail (G_IS_VALUE (value), NULL); + g_return_val_if_fail (G_VALUE_TYPE (value) == GST_TYPE_CAPS, NULL); + + return (GstCaps *) g_value_get_boxed (value); +} + +static gchar * +gst_value_serialize_caps (const GValue * value) +{ + GstCaps *caps = g_value_get_boxed (value); + + return gst_caps_to_string (caps); +} + +static gboolean +gst_value_deserialize_caps (GValue * dest, const gchar * s) +{ + GstCaps *caps; + + caps = gst_caps_from_string (s); + + if (caps) { + g_value_take_boxed (dest, caps); + return TRUE; + } + return FALSE; +} + +/**************** + * GstStructure * + ****************/ + +/** + * gst_value_set_structure: + * @value: a GValue initialized to GST_TYPE_STRUCTURE + * @structure: the structure to set the value to + * + * Sets the contents of @value to @structure. The actual + * + * Since: 0.10.15 + */ +void +gst_value_set_structure (GValue * value, const GstStructure * structure) +{ + g_return_if_fail (G_IS_VALUE (value)); + g_return_if_fail (G_VALUE_TYPE (value) == GST_TYPE_STRUCTURE); + g_return_if_fail (structure == NULL || GST_IS_STRUCTURE (structure)); + + g_value_set_boxed (value, structure); +} + +/** + * gst_value_get_structure: + * @value: a GValue initialized to GST_TYPE_STRUCTURE + * + * Gets the contents of @value. + * + * Returns: (transfer none): the contents of @value + * + * Since: 0.10.15 + */ +const GstStructure * +gst_value_get_structure (const GValue * value) +{ + g_return_val_if_fail (G_IS_VALUE (value), NULL); + g_return_val_if_fail (G_VALUE_TYPE (value) == GST_TYPE_STRUCTURE, NULL); + + return (GstStructure *) g_value_get_boxed (value); +} + +static gchar * +gst_value_serialize_structure (const GValue * value) +{ + GstStructure *structure = g_value_get_boxed (value); + + return gst_string_take_and_wrap (gst_structure_to_string (structure)); +} + +static gboolean +gst_value_deserialize_structure (GValue * dest, const gchar * s) +{ + GstStructure *structure; + + if (*s != '"') { + structure = gst_structure_from_string (s, NULL); + } else { + gchar *str = gst_string_unwrap (s); + + if (G_UNLIKELY (!str)) + return FALSE; + + structure = gst_structure_from_string (str, NULL); + g_free (str); + } + + if (G_LIKELY (structure)) { + g_value_take_boxed (dest, structure); + return TRUE; + } + return FALSE; +} + +/************* + * GstBuffer * + *************/ + +static gint +gst_value_compare_buffer (const GValue * value1, const GValue * value2) +{ + GstBuffer *buf1 = gst_value_get_buffer (value1); + GstBuffer *buf2 = gst_value_get_buffer (value2); + gsize size1, size2; + gpointer data1, data2; + gint result = GST_VALUE_UNORDERED; + + size1 = gst_buffer_get_size (buf1); + size2 = gst_buffer_get_size (buf2); + + if (size1 != size2) + return GST_VALUE_UNORDERED; + + if (size1 == 0) + return GST_VALUE_EQUAL; + + data1 = gst_buffer_map (buf1, &size1, NULL, GST_MAP_READ); + data2 = gst_buffer_map (buf2, &size2, NULL, GST_MAP_READ); + g_assert (data1); + g_assert (data2); + + if (memcmp (data1, data2, size1) == 0) + result = GST_VALUE_EQUAL; + + gst_buffer_unmap (buf2, data2, size2); + gst_buffer_unmap (buf1, data1, size1); + + return result; +} + +static gchar * +gst_value_serialize_buffer (const GValue * value) +{ + guint8 *data; + gint i; + gsize size; + gchar *string; + GstBuffer *buffer; + + buffer = gst_value_get_buffer (value); + if (buffer == NULL) + return NULL; + + data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ); + + string = g_malloc (size * 2 + 1); + for (i = 0; i < size; i++) { + sprintf (string + i * 2, "%02x", data[i]); + } + string[size * 2] = 0; + + gst_buffer_unmap (buffer, data, size); + + return string; +} + +static gboolean +gst_value_deserialize_buffer (GValue * dest, const gchar * s) +{ + GstBuffer *buffer; + gint len; + gchar ts[3]; + guint8 *data; + gint i; + gsize size; + + len = strlen (s); + if (len & 1) + goto wrong_length; + + buffer = gst_buffer_new_allocate (NULL, len / 2, 0); + data = gst_buffer_map (buffer, &size, NULL, GST_MAP_WRITE); + + for (i = 0; i < len / 2; i++) { + if (!isxdigit ((int) s[i * 2]) || !isxdigit ((int) s[i * 2 + 1])) + goto wrong_char; + + ts[0] = s[i * 2 + 0]; + ts[1] = s[i * 2 + 1]; + ts[2] = 0; + + data[i] = (guint8) strtoul (ts, NULL, 16); + } + gst_buffer_unmap (buffer, data, size); + + gst_value_take_buffer (dest, buffer); + + return TRUE; + + /* ERRORS */ +wrong_length: + { + return FALSE; + } +wrong_char: + { + gst_buffer_unref (buffer); + gst_buffer_unmap (buffer, data, size); + return FALSE; + } +} + + +/*********** + * boolean * + ***********/ + +static gint +gst_value_compare_boolean (const GValue * value1, const GValue * value2) +{ + if ((value1->data[0].v_int != 0) == (value2->data[0].v_int != 0)) + return GST_VALUE_EQUAL; + return GST_VALUE_UNORDERED; +} + +static gchar * +gst_value_serialize_boolean (const GValue * value) +{ + if (value->data[0].v_int) { + return g_strdup ("true"); + } + return g_strdup ("false"); +} + +static gboolean +gst_value_deserialize_boolean (GValue * dest, const gchar * s) +{ + gboolean ret = FALSE; + + if (g_ascii_strcasecmp (s, "true") == 0 || + g_ascii_strcasecmp (s, "yes") == 0 || + g_ascii_strcasecmp (s, "t") == 0 || strcmp (s, "1") == 0) { + g_value_set_boolean (dest, TRUE); + ret = TRUE; + } else if (g_ascii_strcasecmp (s, "false") == 0 || + g_ascii_strcasecmp (s, "no") == 0 || + g_ascii_strcasecmp (s, "f") == 0 || strcmp (s, "0") == 0) { + g_value_set_boolean (dest, FALSE); + ret = TRUE; + } + + return ret; +} + +#define CREATE_SERIALIZATION_START(_type,_macro) \ +static gint \ +gst_value_compare_ ## _type \ +(const GValue * value1, const GValue * value2) \ +{ \ + g ## _type val1 = g_value_get_ ## _type (value1); \ + g ## _type val2 = g_value_get_ ## _type (value2); \ + if (val1 > val2) \ + return GST_VALUE_GREATER_THAN; \ + if (val1 < val2) \ + return GST_VALUE_LESS_THAN; \ + return GST_VALUE_EQUAL; \ +} \ + \ +static gchar * \ +gst_value_serialize_ ## _type (const GValue * value) \ +{ \ + GValue val = { 0, }; \ + g_value_init (&val, G_TYPE_STRING); \ + if (!g_value_transform (value, &val)) \ + g_assert_not_reached (); \ + /* NO_COPY_MADNESS!!! */ \ + return (char *) g_value_get_string (&val); \ +} + +/* deserialize the given s into to as a gint64. + * check if the result is actually storeable in the given size number of + * bytes. + */ +static gboolean +gst_value_deserialize_int_helper (gint64 * to, const gchar * s, + gint64 min, gint64 max, gint size) +{ + gboolean ret = FALSE; + gchar *end; + gint64 mask = -1; + + errno = 0; + *to = g_ascii_strtoull (s, &end, 0); + /* a range error is a definitive no-no */ + if (errno == ERANGE) { + return FALSE; + } + + if (*end == 0) { + ret = TRUE; + } else { + if (g_ascii_strcasecmp (s, "little_endian") == 0) { + *to = G_LITTLE_ENDIAN; + ret = TRUE; + } else if (g_ascii_strcasecmp (s, "big_endian") == 0) { + *to = G_BIG_ENDIAN; + ret = TRUE; + } else if (g_ascii_strcasecmp (s, "byte_order") == 0) { + *to = G_BYTE_ORDER; + ret = TRUE; + } else if (g_ascii_strcasecmp (s, "min") == 0) { + *to = min; + ret = TRUE; + } else if (g_ascii_strcasecmp (s, "max") == 0) { + *to = max; + ret = TRUE; + } + } + if (ret) { + /* by definition, a gint64 fits into a gint64; so ignore those */ + if (size != sizeof (mask)) { + if (*to >= 0) { + /* for positive numbers, we create a mask of 1's outside of the range + * and 0's inside the range. An and will thus keep only 1 bits + * outside of the range */ + mask <<= (size * 8); + if ((mask & *to) != 0) { + ret = FALSE; + } + } else { + /* for negative numbers, we do a 2's complement version */ + mask <<= ((size * 8) - 1); + if ((mask & *to) != mask) { + ret = FALSE; + } + } + } + } + return ret; +} + +#define CREATE_SERIALIZATION(_type,_macro) \ +CREATE_SERIALIZATION_START(_type,_macro) \ + \ +static gboolean \ +gst_value_deserialize_ ## _type (GValue * dest, const gchar *s) \ +{ \ + gint64 x; \ + \ + if (gst_value_deserialize_int_helper (&x, s, G_MIN ## _macro, \ + G_MAX ## _macro, sizeof (g ## _type))) { \ + g_value_set_ ## _type (dest, /*(g ## _type)*/ x); \ + return TRUE; \ + } else { \ + return FALSE; \ + } \ +} + +#define CREATE_USERIALIZATION(_type,_macro) \ +CREATE_SERIALIZATION_START(_type,_macro) \ + \ +static gboolean \ +gst_value_deserialize_ ## _type (GValue * dest, const gchar *s) \ +{ \ + gint64 x; \ + gchar *end; \ + gboolean ret = FALSE; \ + \ + errno = 0; \ + x = g_ascii_strtoull (s, &end, 0); \ + /* a range error is a definitive no-no */ \ + if (errno == ERANGE) { \ + return FALSE; \ + } \ + /* the cast ensures the range check later on makes sense */ \ + x = (g ## _type) x; \ + if (*end == 0) { \ + ret = TRUE; \ + } else { \ + if (g_ascii_strcasecmp (s, "little_endian") == 0) { \ + x = G_LITTLE_ENDIAN; \ + ret = TRUE; \ + } else if (g_ascii_strcasecmp (s, "big_endian") == 0) { \ + x = G_BIG_ENDIAN; \ + ret = TRUE; \ + } else if (g_ascii_strcasecmp (s, "byte_order") == 0) { \ + x = G_BYTE_ORDER; \ + ret = TRUE; \ + } else if (g_ascii_strcasecmp (s, "min") == 0) { \ + x = 0; \ + ret = TRUE; \ + } else if (g_ascii_strcasecmp (s, "max") == 0) { \ + x = G_MAX ## _macro; \ + ret = TRUE; \ + } \ + } \ + if (ret) { \ + if (x > G_MAX ## _macro) { \ + ret = FALSE; \ + } else { \ + g_value_set_ ## _type (dest, x); \ + } \ + } \ + return ret; \ +} + +#define REGISTER_SERIALIZATION(_gtype, _type) \ +G_STMT_START { \ + static const GstValueTable gst_value = { \ + _gtype, \ + gst_value_compare_ ## _type, \ + gst_value_serialize_ ## _type, \ + gst_value_deserialize_ ## _type, \ + }; \ + \ + gst_value_register (&gst_value); \ +} G_STMT_END + +CREATE_SERIALIZATION (int, INT); +CREATE_SERIALIZATION (int64, INT64); +CREATE_SERIALIZATION (long, LONG); + +CREATE_USERIALIZATION (uint, UINT); +CREATE_USERIALIZATION (uint64, UINT64); +CREATE_USERIALIZATION (ulong, ULONG); + +/* FIXME 0.11: remove this again, plugins shouldn't have uchar properties */ +#ifndef G_MAXUCHAR +#define G_MAXUCHAR 255 +#endif +CREATE_USERIALIZATION (uchar, UCHAR); + +/********** + * double * + **********/ +static gint +gst_value_compare_double (const GValue * value1, const GValue * value2) +{ + if (value1->data[0].v_double > value2->data[0].v_double) + return GST_VALUE_GREATER_THAN; + if (value1->data[0].v_double < value2->data[0].v_double) + return GST_VALUE_LESS_THAN; + if (value1->data[0].v_double == value2->data[0].v_double) + return GST_VALUE_EQUAL; + return GST_VALUE_UNORDERED; +} + +static gchar * +gst_value_serialize_double (const GValue * value) +{ + gchar d[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_dtostr (d, G_ASCII_DTOSTR_BUF_SIZE, value->data[0].v_double); + return g_strdup (d); +} + +static gboolean +gst_value_deserialize_double (GValue * dest, const gchar * s) +{ + gdouble x; + gboolean ret = FALSE; + gchar *end; + + x = g_ascii_strtod (s, &end); + if (*end == 0) { + ret = TRUE; + } else { + if (g_ascii_strcasecmp (s, "min") == 0) { + x = -G_MAXDOUBLE; + ret = TRUE; + } else if (g_ascii_strcasecmp (s, "max") == 0) { + x = G_MAXDOUBLE; + ret = TRUE; + } + } + if (ret) { + g_value_set_double (dest, x); + } + return ret; +} + +/********* + * float * + *********/ + +static gint +gst_value_compare_float (const GValue * value1, const GValue * value2) +{ + if (value1->data[0].v_float > value2->data[0].v_float) + return GST_VALUE_GREATER_THAN; + if (value1->data[0].v_float < value2->data[0].v_float) + return GST_VALUE_LESS_THAN; + if (value1->data[0].v_float == value2->data[0].v_float) + return GST_VALUE_EQUAL; + return GST_VALUE_UNORDERED; +} + +static gchar * +gst_value_serialize_float (const GValue * value) +{ + gchar d[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_dtostr (d, G_ASCII_DTOSTR_BUF_SIZE, value->data[0].v_float); + return g_strdup (d); +} + +static gboolean +gst_value_deserialize_float (GValue * dest, const gchar * s) +{ + gdouble x; + gboolean ret = FALSE; + gchar *end; + + x = g_ascii_strtod (s, &end); + if (*end == 0) { + ret = TRUE; + } else { + if (g_ascii_strcasecmp (s, "min") == 0) { + x = -G_MAXFLOAT; + ret = TRUE; + } else if (g_ascii_strcasecmp (s, "max") == 0) { + x = G_MAXFLOAT; + ret = TRUE; + } + } + if (x > G_MAXFLOAT || x < -G_MAXFLOAT) + ret = FALSE; + if (ret) { + g_value_set_float (dest, (float) x); + } + return ret; +} + +/********** + * string * + **********/ + +static gint +gst_value_compare_string (const GValue * value1, const GValue * value2) +{ + if (G_UNLIKELY (!value1->data[0].v_pointer || !value2->data[0].v_pointer)) { + /* if only one is NULL, no match - otherwise both NULL == EQUAL */ + if (value1->data[0].v_pointer != value2->data[0].v_pointer) + return GST_VALUE_UNORDERED; + } else { + gint x = strcmp (value1->data[0].v_pointer, value2->data[0].v_pointer); + + if (x < 0) + return GST_VALUE_LESS_THAN; + if (x > 0) + return GST_VALUE_GREATER_THAN; + } + + return GST_VALUE_EQUAL; +} + +static gint +gst_string_measure_wrapping (const gchar * s) +{ + gint len; + gboolean wrap = FALSE; + + if (G_UNLIKELY (s == NULL)) + return -1; + + /* Special case: the actual string NULL needs wrapping */ + if (G_UNLIKELY (strcmp (s, "NULL") == 0)) + return 4; + + len = 0; + while (*s) { + if (GST_ASCII_IS_STRING (*s)) { + len++; + } else if (*s < 0x20 || *s >= 0x7f) { + wrap = TRUE; + len += 4; + } else { + wrap = TRUE; + len += 2; + } + s++; + } + + /* Wrap the string if we found something that needs + * wrapping, or the empty string (len == 0) */ + return (wrap || len == 0) ? len : -1; +} + +static gchar * +gst_string_wrap_inner (const gchar * s, gint len) +{ + gchar *d, *e; + + e = d = g_malloc (len + 3); + + *e++ = '\"'; + while (*s) { + if (GST_ASCII_IS_STRING (*s)) { + *e++ = *s++; + } else if (*s < 0x20 || *s >= 0x7f) { + *e++ = '\\'; + *e++ = '0' + ((*(guchar *) s) >> 6); + *e++ = '0' + (((*s) >> 3) & 0x7); + *e++ = '0' + ((*s++) & 0x7); + } else { + *e++ = '\\'; + *e++ = *s++; + } + } + *e++ = '\"'; + *e = 0; + + g_assert (e - d <= len + 3); + return d; +} + +/* Do string wrapping/escaping */ +static gchar * +gst_string_wrap (const gchar * s) +{ + gint len = gst_string_measure_wrapping (s); + + if (G_LIKELY (len < 0)) + return g_strdup (s); + + return gst_string_wrap_inner (s, len); +} + +/* Same as above, but take ownership of the string */ +static gchar * +gst_string_take_and_wrap (gchar * s) +{ + gchar *out; + gint len = gst_string_measure_wrapping (s); + + if (G_LIKELY (len < 0)) + return s; + + out = gst_string_wrap_inner (s, len); + g_free (s); + + return out; +} + +/* + * This function takes a string delimited with double quotes (") + * and unescapes any \xxx octal numbers. + * + * If sequences of \y are found where y is not in the range of + * 0->3, y is copied unescaped. + * + * If \xyy is found where x is an octal number but y is not, an + * error is encountered and NULL is returned. + * + * the input string must be \0 terminated. + */ +static gchar * +gst_string_unwrap (const gchar * s) +{ + gchar *ret; + gchar *read, *write; + + /* NULL string returns NULL */ + if (s == NULL) + return NULL; + + /* strings not starting with " are invalid */ + if (*s != '"') + return NULL; + + /* make copy of original string to hold the result. This + * string will always be smaller than the original */ + ret = g_strdup (s); + read = ret; + write = ret; + + /* need to move to the next position as we parsed the " */ + read++; + + while (*read) { + if (GST_ASCII_IS_STRING (*read)) { + /* normal chars are just copied */ + *write++ = *read++; + } else if (*read == '"') { + /* quote marks end of string */ + break; + } else if (*read == '\\') { + /* got an escape char, move to next position to read a tripplet + * of octal numbers */ + read++; + /* is the next char a possible first octal number? */ + if (*read >= '0' && *read <= '3') { + /* parse other 2 numbers, if one of them is not in the range of + * an octal number, we error. We also catch the case where a zero + * byte is found here. */ + if (read[1] < '0' || read[1] > '7' || read[2] < '0' || read[2] > '7') + goto beach; + + /* now convert the octal number to a byte again. */ + *write++ = ((read[0] - '0') << 6) + + ((read[1] - '0') << 3) + (read[2] - '0'); + + read += 3; + } else { + /* if we run into a \0 here, we definitely won't get a quote later */ + if (*read == 0) + goto beach; + + /* else copy \X sequence */ + *write++ = *read++; + } + } else { + /* weird character, error */ + goto beach; + } + } + /* if the string is not ending in " and zero terminated, we error */ + if (*read != '"' || read[1] != '\0') + goto beach; + + /* null terminate result string and return */ + *write = '\0'; + return ret; + +beach: + g_free (ret); + return NULL; +} + +static gchar * +gst_value_serialize_string (const GValue * value) +{ + return gst_string_wrap (value->data[0].v_pointer); +} + +static gboolean +gst_value_deserialize_string (GValue * dest, const gchar * s) +{ + if (G_UNLIKELY (strcmp (s, "NULL") == 0)) { + g_value_set_string (dest, NULL); + return TRUE; + } else if (G_LIKELY (*s != '"')) { + if (!g_utf8_validate (s, -1, NULL)) + return FALSE; + g_value_set_string (dest, s); + return TRUE; + } else { + gchar *str = gst_string_unwrap (s); + if (G_UNLIKELY (!str)) + return FALSE; + g_value_take_string (dest, str); + } + + return TRUE; +} + +/******** + * enum * + ********/ + +static gint +gst_value_compare_enum (const GValue * value1, const GValue * value2) +{ + GEnumValue *en1, *en2; + GEnumClass *klass1 = (GEnumClass *) g_type_class_ref (G_VALUE_TYPE (value1)); + GEnumClass *klass2 = (GEnumClass *) g_type_class_ref (G_VALUE_TYPE (value2)); + + g_return_val_if_fail (klass1, GST_VALUE_UNORDERED); + g_return_val_if_fail (klass2, GST_VALUE_UNORDERED); + en1 = g_enum_get_value (klass1, g_value_get_enum (value1)); + en2 = g_enum_get_value (klass2, g_value_get_enum (value2)); + g_type_class_unref (klass1); + g_type_class_unref (klass2); + g_return_val_if_fail (en1, GST_VALUE_UNORDERED); + g_return_val_if_fail (en2, GST_VALUE_UNORDERED); + if (en1->value < en2->value) + return GST_VALUE_LESS_THAN; + if (en1->value > en2->value) + return GST_VALUE_GREATER_THAN; + + return GST_VALUE_EQUAL; +} + +static gchar * +gst_value_serialize_enum (const GValue * value) +{ + GEnumValue *en; + GEnumClass *klass = (GEnumClass *) g_type_class_ref (G_VALUE_TYPE (value)); + + g_return_val_if_fail (klass, NULL); + en = g_enum_get_value (klass, g_value_get_enum (value)); + g_type_class_unref (klass); + + /* might be one of the custom formats registered later */ + if (G_UNLIKELY (en == NULL && G_VALUE_TYPE (value) == GST_TYPE_FORMAT)) { + const GstFormatDefinition *format_def; + + format_def = gst_format_get_details ((GstFormat) g_value_get_enum (value)); + g_return_val_if_fail (format_def != NULL, NULL); + return g_strdup (format_def->description); + } + + g_return_val_if_fail (en, NULL); + return g_strdup (en->value_name); +} + +static gint +gst_value_deserialize_enum_iter_cmp (const GValue * format_def_value, + const gchar * s) +{ + const GstFormatDefinition *format_def = + g_value_get_pointer (format_def_value); + + if (g_ascii_strcasecmp (s, format_def->nick) == 0) + return 0; + + return g_ascii_strcasecmp (s, format_def->description); +} + +static gboolean +gst_value_deserialize_enum (GValue * dest, const gchar * s) +{ + GEnumValue *en; + gchar *endptr = NULL; + GEnumClass *klass = (GEnumClass *) g_type_class_ref (G_VALUE_TYPE (dest)); + + g_return_val_if_fail (klass, FALSE); + if (!(en = g_enum_get_value_by_name (klass, s))) { + if (!(en = g_enum_get_value_by_nick (klass, s))) { + gint i = strtol (s, &endptr, 0); + + if (endptr && *endptr == '\0') { + en = g_enum_get_value (klass, i); + } + } + } + g_type_class_unref (klass); + + /* might be one of the custom formats registered later */ + if (G_UNLIKELY (en == NULL && G_VALUE_TYPE (dest) == GST_TYPE_FORMAT)) { + GValue res = { 0, }; + const GstFormatDefinition *format_def; + GstIterator *iter; + gboolean found; + + iter = gst_format_iterate_definitions (); + + found = gst_iterator_find_custom (iter, + (GCompareFunc) gst_value_deserialize_enum_iter_cmp, &res, (gpointer) s); + + g_return_val_if_fail (found, FALSE); + format_def = g_value_get_pointer (&res); + g_return_val_if_fail (format_def != NULL, FALSE); + g_value_set_enum (dest, (gint) format_def->value); + g_value_unset (&res); + gst_iterator_free (iter); + return TRUE; + } + + g_return_val_if_fail (en, FALSE); + g_value_set_enum (dest, en->value); + return TRUE; +} + +/******** + * flags * + ********/ + +/* we just compare the value here */ +static gint +gst_value_compare_flags (const GValue * value1, const GValue * value2) +{ + guint fl1, fl2; + GFlagsClass *klass1 = + (GFlagsClass *) g_type_class_ref (G_VALUE_TYPE (value1)); + GFlagsClass *klass2 = + (GFlagsClass *) g_type_class_ref (G_VALUE_TYPE (value2)); + + g_return_val_if_fail (klass1, GST_VALUE_UNORDERED); + g_return_val_if_fail (klass2, GST_VALUE_UNORDERED); + fl1 = g_value_get_flags (value1); + fl2 = g_value_get_flags (value2); + g_type_class_unref (klass1); + g_type_class_unref (klass2); + if (fl1 < fl2) + return GST_VALUE_LESS_THAN; + if (fl1 > fl2) + return GST_VALUE_GREATER_THAN; + + return GST_VALUE_EQUAL; +} + +/* the different flags are serialized separated with a + */ +static gchar * +gst_value_serialize_flags (const GValue * value) +{ + guint flags; + GFlagsValue *fl; + GFlagsClass *klass = (GFlagsClass *) g_type_class_ref (G_VALUE_TYPE (value)); + gchar *result, *tmp; + gboolean first = TRUE; + + g_return_val_if_fail (klass, NULL); + + flags = g_value_get_flags (value); + + /* if no flags are set, try to serialize to the _NONE string */ + if (!flags) { + fl = g_flags_get_first_value (klass, flags); + return g_strdup (fl->value_name); + } + + /* some flags are set, so serialize one by one */ + result = g_strdup (""); + while (flags) { + fl = g_flags_get_first_value (klass, flags); + if (fl != NULL) { + tmp = g_strconcat (result, (first ? "" : "+"), fl->value_name, NULL); + g_free (result); + result = tmp; + first = FALSE; + + /* clear flag */ + flags &= ~fl->value; + } + } + g_type_class_unref (klass); + + return result; +} + +static gboolean +gst_value_deserialize_flags (GValue * dest, const gchar * s) +{ + GFlagsValue *fl; + gchar *endptr = NULL; + GFlagsClass *klass = (GFlagsClass *) g_type_class_ref (G_VALUE_TYPE (dest)); + gchar **split; + guint flags; + gint i; + + g_return_val_if_fail (klass, FALSE); + + /* split into parts delimited with + */ + split = g_strsplit (s, "+", 0); + + flags = 0; + i = 0; + /* loop over each part */ + while (split[i]) { + if (!(fl = g_flags_get_value_by_name (klass, split[i]))) { + if (!(fl = g_flags_get_value_by_nick (klass, split[i]))) { + gint val = strtol (split[i], &endptr, 0); + + /* just or numeric value */ + if (endptr && *endptr == '\0') { + flags |= val; + } + } + } + if (fl) { + flags |= fl->value; + } + i++; + } + g_strfreev (split); + g_type_class_unref (klass); + g_value_set_flags (dest, flags); + + return TRUE; +} + +/********* + * union * + *********/ + +static gboolean +gst_value_union_int_int_range (GValue * dest, const GValue * src1, + const GValue * src2) +{ + if (src2->data[0].v_int <= src1->data[0].v_int && + src2->data[1].v_int >= src1->data[0].v_int) { + gst_value_init_and_copy (dest, src2); + return TRUE; + } + return FALSE; +} + +static gboolean +gst_value_union_int_range_int_range (GValue * dest, const GValue * src1, + const GValue * src2) +{ + gint min; + gint max; + + min = MAX (src1->data[0].v_int, src2->data[0].v_int); + max = MIN (src1->data[1].v_int, src2->data[1].v_int); + + if (min <= max) { + g_value_init (dest, GST_TYPE_INT_RANGE); + gst_value_set_int_range (dest, + MIN (src1->data[0].v_int, src2->data[0].v_int), + MAX (src1->data[1].v_int, src2->data[1].v_int)); + return TRUE; + } + + return FALSE; +} + +/**************** + * intersection * + ****************/ + +static gboolean +gst_value_intersect_int_int_range (GValue * dest, const GValue * src1, + const GValue * src2) +{ + if (src2->data[0].v_int <= src1->data[0].v_int && + src2->data[1].v_int >= src1->data[0].v_int) { + gst_value_init_and_copy (dest, src1); + return TRUE; + } + + return FALSE; +} + +static gboolean +gst_value_intersect_int_range_int_range (GValue * dest, const GValue * src1, + const GValue * src2) +{ + gint min; + gint max; + + min = MAX (src1->data[0].v_int, src2->data[0].v_int); + max = MIN (src1->data[1].v_int, src2->data[1].v_int); + + if (min < max) { + g_value_init (dest, GST_TYPE_INT_RANGE); + gst_value_set_int_range (dest, min, max); + return TRUE; + } + if (min == max) { + g_value_init (dest, G_TYPE_INT); + g_value_set_int (dest, min); + return TRUE; + } + + return FALSE; +} + +static gboolean +gst_value_intersect_int64_int64_range (GValue * dest, const GValue * src1, + const GValue * src2) +{ + if (src2->data[0].v_int64 <= src1->data[0].v_int64 && + src2->data[1].v_int64 >= src1->data[0].v_int64) { + gst_value_init_and_copy (dest, src1); + return TRUE; + } + + return FALSE; +} + +static gboolean +gst_value_intersect_int64_range_int64_range (GValue * dest, const GValue * src1, + const GValue * src2) +{ + gint64 min; + gint64 max; + + min = MAX (src1->data[0].v_int64, src2->data[0].v_int64); + max = MIN (src1->data[1].v_int64, src2->data[1].v_int64); + + if (min < max) { + g_value_init (dest, GST_TYPE_INT64_RANGE); + gst_value_set_int64_range (dest, min, max); + return TRUE; + } + if (min == max) { + g_value_init (dest, G_TYPE_INT64); + g_value_set_int64 (dest, min); + return TRUE; + } + + return FALSE; +} + +static gboolean +gst_value_intersect_double_double_range (GValue * dest, const GValue * src1, + const GValue * src2) +{ + if (src2->data[0].v_double <= src1->data[0].v_double && + src2->data[1].v_double >= src1->data[0].v_double) { + gst_value_init_and_copy (dest, src1); + return TRUE; + } + + return FALSE; +} + +static gboolean +gst_value_intersect_double_range_double_range (GValue * dest, + const GValue * src1, const GValue * src2) +{ + gdouble min; + gdouble max; + + min = MAX (src1->data[0].v_double, src2->data[0].v_double); + max = MIN (src1->data[1].v_double, src2->data[1].v_double); + + if (min < max) { + g_value_init (dest, GST_TYPE_DOUBLE_RANGE); + gst_value_set_double_range (dest, min, max); + return TRUE; + } + if (min == max) { + g_value_init (dest, G_TYPE_DOUBLE); + g_value_set_int (dest, (int) min); + return TRUE; + } + + return FALSE; +} + +static gboolean +gst_value_intersect_list (GValue * dest, const GValue * value1, + const GValue * value2) +{ + guint i, size; + GValue intersection = { 0, }; + gboolean ret = FALSE; + + size = VALUE_LIST_SIZE (value1); + for (i = 0; i < size; i++) { + const GValue *cur = VALUE_LIST_GET_VALUE (value1, i); + + if (gst_value_intersect (&intersection, cur, value2)) { + /* append value */ + if (!ret) { + gst_value_init_and_copy (dest, &intersection); + ret = TRUE; + } else if (GST_VALUE_HOLDS_LIST (dest)) { + gst_value_list_append_value (dest, &intersection); + } else { + GValue temp = { 0, }; + + gst_value_init_and_copy (&temp, dest); + g_value_unset (dest); + gst_value_list_concat (dest, &temp, &intersection); + g_value_unset (&temp); + } + g_value_unset (&intersection); + } + } + + return ret; +} + +static gboolean +gst_value_intersect_array (GValue * dest, const GValue * src1, + const GValue * src2) +{ + guint size; + guint n; + GValue val = { 0 }; + + /* only works on similar-sized arrays */ + size = gst_value_array_get_size (src1); + if (size != gst_value_array_get_size (src2)) + return FALSE; + g_value_init (dest, GST_TYPE_ARRAY); + + for (n = 0; n < size; n++) { + if (!gst_value_intersect (&val, gst_value_array_get_value (src1, n), + gst_value_array_get_value (src2, n))) { + g_value_unset (dest); + return FALSE; + } + gst_value_array_append_value (dest, &val); + g_value_unset (&val); + } + + return TRUE; +} + +static gboolean +gst_value_intersect_fraction_fraction_range (GValue * dest, const GValue * src1, + const GValue * src2) +{ + gint res1, res2; + GValue *vals; + GstValueCompareFunc compare; + + vals = src2->data[0].v_pointer; + + if (vals == NULL) + return FALSE; + + if ((compare = gst_value_get_compare_func (src1))) { + res1 = gst_value_compare_with_func (&vals[0], src1, compare); + res2 = gst_value_compare_with_func (&vals[1], src1, compare); + + if ((res1 == GST_VALUE_EQUAL || res1 == GST_VALUE_LESS_THAN) && + (res2 == GST_VALUE_EQUAL || res2 == GST_VALUE_GREATER_THAN)) { + gst_value_init_and_copy (dest, src1); + return TRUE; + } + } + + return FALSE; +} + +static gboolean +gst_value_intersect_fraction_range_fraction_range (GValue * dest, + const GValue * src1, const GValue * src2) +{ + GValue *min; + GValue *max; + gint res; + GValue *vals1, *vals2; + GstValueCompareFunc compare; + + vals1 = src1->data[0].v_pointer; + vals2 = src2->data[0].v_pointer; + g_return_val_if_fail (vals1 != NULL && vals2 != NULL, FALSE); + + if ((compare = gst_value_get_compare_func (&vals1[0]))) { + /* min = MAX (src1.start, src2.start) */ + res = gst_value_compare_with_func (&vals1[0], &vals2[0], compare); + g_return_val_if_fail (res != GST_VALUE_UNORDERED, FALSE); + if (res == GST_VALUE_LESS_THAN) + min = &vals2[0]; /* Take the max of the 2 */ + else + min = &vals1[0]; + + /* max = MIN (src1.end, src2.end) */ + res = gst_value_compare_with_func (&vals1[1], &vals2[1], compare); + g_return_val_if_fail (res != GST_VALUE_UNORDERED, FALSE); + if (res == GST_VALUE_GREATER_THAN) + max = &vals2[1]; /* Take the min of the 2 */ + else + max = &vals1[1]; + + res = gst_value_compare_with_func (min, max, compare); + g_return_val_if_fail (res != GST_VALUE_UNORDERED, FALSE); + if (res == GST_VALUE_LESS_THAN) { + g_value_init (dest, GST_TYPE_FRACTION_RANGE); + vals1 = dest->data[0].v_pointer; + g_value_copy (min, &vals1[0]); + g_value_copy (max, &vals1[1]); + return TRUE; + } + if (res == GST_VALUE_EQUAL) { + gst_value_init_and_copy (dest, min); + return TRUE; + } + } + + return FALSE; +} + +/*************** + * subtraction * + ***************/ + +static gboolean +gst_value_subtract_int_int_range (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + gint min = gst_value_get_int_range_min (subtrahend); + gint max = gst_value_get_int_range_max (subtrahend); + gint val = g_value_get_int (minuend); + + /* subtracting a range from an int only works if the int is not in the + * range */ + if (val < min || val > max) { + /* and the result is the int */ + gst_value_init_and_copy (dest, minuend); + return TRUE; + } + return FALSE; +} + +/* creates a new int range based on input values. + */ +static gboolean +gst_value_create_new_range (GValue * dest, gint min1, gint max1, gint min2, + gint max2) +{ + GValue v1 = { 0, }; + GValue v2 = { 0, }; + GValue *pv1, *pv2; /* yeah, hungarian! */ + + if (min1 <= max1 && min2 <= max2) { + pv1 = &v1; + pv2 = &v2; + } else if (min1 <= max1) { + pv1 = dest; + pv2 = NULL; + } else if (min2 <= max2) { + pv1 = NULL; + pv2 = dest; + } else { + return FALSE; + } + + if (min1 < max1) { + g_value_init (pv1, GST_TYPE_INT_RANGE); + gst_value_set_int_range (pv1, min1, max1); + } else if (min1 == max1) { + g_value_init (pv1, G_TYPE_INT); + g_value_set_int (pv1, min1); + } + if (min2 < max2) { + g_value_init (pv2, GST_TYPE_INT_RANGE); + gst_value_set_int_range (pv2, min2, max2); + } else if (min2 == max2) { + g_value_init (pv2, G_TYPE_INT); + g_value_set_int (pv2, min2); + } + + if (min1 <= max1 && min2 <= max2) { + gst_value_list_concat (dest, pv1, pv2); + g_value_unset (pv1); + g_value_unset (pv2); + } + return TRUE; +} + +static gboolean +gst_value_subtract_int_range_int (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + gint min = gst_value_get_int_range_min (minuend); + gint max = gst_value_get_int_range_max (minuend); + gint val = g_value_get_int (subtrahend); + + g_return_val_if_fail (min < max, FALSE); + + /* value is outside of the range, return range unchanged */ + if (val < min || val > max) { + gst_value_init_and_copy (dest, minuend); + return TRUE; + } else { + /* max must be MAXINT too as val <= max */ + if (val == G_MAXINT) { + max--; + val--; + } + /* min must be MININT too as val >= max */ + if (val == G_MININT) { + min++; + val++; + } + gst_value_create_new_range (dest, min, val - 1, val + 1, max); + } + return TRUE; +} + +static gboolean +gst_value_subtract_int_range_int_range (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + gint min1 = gst_value_get_int_range_min (minuend); + gint max1 = gst_value_get_int_range_max (minuend); + gint min2 = gst_value_get_int_range_min (subtrahend); + gint max2 = gst_value_get_int_range_max (subtrahend); + + if (max2 == G_MAXINT && min2 == G_MININT) { + return FALSE; + } else if (max2 == G_MAXINT) { + return gst_value_create_new_range (dest, min1, MIN (min2 - 1, max1), 1, 0); + } else if (min2 == G_MININT) { + return gst_value_create_new_range (dest, MAX (max2 + 1, min1), max1, 1, 0); + } else { + return gst_value_create_new_range (dest, min1, MIN (min2 - 1, max1), + MAX (max2 + 1, min1), max1); + } +} + +static gboolean +gst_value_subtract_int64_int64_range (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + gint64 min = gst_value_get_int64_range_min (subtrahend); + gint64 max = gst_value_get_int64_range_max (subtrahend); + gint64 val = g_value_get_int64 (minuend); + + /* subtracting a range from an int64 only works if the int64 is not in the + * range */ + if (val < min || val > max) { + /* and the result is the int64 */ + gst_value_init_and_copy (dest, minuend); + return TRUE; + } + return FALSE; +} + +/* creates a new int64 range based on input values. + */ +static gboolean +gst_value_create_new_int64_range (GValue * dest, gint64 min1, gint64 max1, + gint64 min2, gint64 max2) +{ + GValue v1 = { 0, }; + GValue v2 = { 0, }; + GValue *pv1, *pv2; /* yeah, hungarian! */ + + if (min1 <= max1 && min2 <= max2) { + pv1 = &v1; + pv2 = &v2; + } else if (min1 <= max1) { + pv1 = dest; + pv2 = NULL; + } else if (min2 <= max2) { + pv1 = NULL; + pv2 = dest; + } else { + return FALSE; + } + + if (min1 < max1) { + g_value_init (pv1, GST_TYPE_INT64_RANGE); + gst_value_set_int64_range (pv1, min1, max1); + } else if (min1 == max1) { + g_value_init (pv1, G_TYPE_INT64); + g_value_set_int64 (pv1, min1); + } + if (min2 < max2) { + g_value_init (pv2, GST_TYPE_INT64_RANGE); + gst_value_set_int64_range (pv2, min2, max2); + } else if (min2 == max2) { + g_value_init (pv2, G_TYPE_INT64); + g_value_set_int64 (pv2, min2); + } + + if (min1 <= max1 && min2 <= max2) { + gst_value_list_concat (dest, pv1, pv2); + g_value_unset (pv1); + g_value_unset (pv2); + } + return TRUE; +} + +static gboolean +gst_value_subtract_int64_range_int64 (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + gint64 min = gst_value_get_int64_range_min (minuend); + gint64 max = gst_value_get_int64_range_max (minuend); + gint64 val = g_value_get_int64 (subtrahend); + + g_return_val_if_fail (min < max, FALSE); + + /* value is outside of the range, return range unchanged */ + if (val < min || val > max) { + gst_value_init_and_copy (dest, minuend); + return TRUE; + } else { + /* max must be MAXINT64 too as val <= max */ + if (val == G_MAXINT64) { + max--; + val--; + } + /* min must be MININT64 too as val >= max */ + if (val == G_MININT64) { + min++; + val++; + } + gst_value_create_new_int64_range (dest, min, val - 1, val + 1, max); + } + return TRUE; +} + +static gboolean +gst_value_subtract_int64_range_int64_range (GValue * dest, + const GValue * minuend, const GValue * subtrahend) +{ + gint64 min1 = gst_value_get_int64_range_min (minuend); + gint64 max1 = gst_value_get_int64_range_max (minuend); + gint64 min2 = gst_value_get_int64_range_min (subtrahend); + gint64 max2 = gst_value_get_int64_range_max (subtrahend); + + if (max2 == G_MAXINT64 && min2 == G_MININT64) { + return FALSE; + } else if (max2 == G_MAXINT64) { + return gst_value_create_new_int64_range (dest, min1, MIN (min2 - 1, max1), + 1, 0); + } else if (min2 == G_MININT64) { + return gst_value_create_new_int64_range (dest, MAX (max2 + 1, min1), max1, + 1, 0); + } else { + return gst_value_create_new_int64_range (dest, min1, MIN (min2 - 1, max1), + MAX (max2 + 1, min1), max1); + } +} + +static gboolean +gst_value_subtract_double_double_range (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + gdouble min = gst_value_get_double_range_min (subtrahend); + gdouble max = gst_value_get_double_range_max (subtrahend); + gdouble val = g_value_get_double (minuend); + + if (val < min || val > max) { + gst_value_init_and_copy (dest, minuend); + return TRUE; + } + return FALSE; +} + +static gboolean +gst_value_subtract_double_range_double (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + /* since we don't have open ranges, we cannot create a hole in + * a double range. We return the original range */ + gst_value_init_and_copy (dest, minuend); + return TRUE; +} + +static gboolean +gst_value_subtract_double_range_double_range (GValue * dest, + const GValue * minuend, const GValue * subtrahend) +{ + /* since we don't have open ranges, we have to approximate */ + /* done like with ints */ + gdouble min1 = gst_value_get_double_range_min (minuend); + gdouble max2 = gst_value_get_double_range_max (minuend); + gdouble max1 = MIN (gst_value_get_double_range_min (subtrahend), max2); + gdouble min2 = MAX (gst_value_get_double_range_max (subtrahend), min1); + GValue v1 = { 0, }; + GValue v2 = { 0, }; + GValue *pv1, *pv2; /* yeah, hungarian! */ + + if (min1 < max1 && min2 < max2) { + pv1 = &v1; + pv2 = &v2; + } else if (min1 < max1) { + pv1 = dest; + pv2 = NULL; + } else if (min2 < max2) { + pv1 = NULL; + pv2 = dest; + } else { + return FALSE; + } + + if (min1 < max1) { + g_value_init (pv1, GST_TYPE_DOUBLE_RANGE); + gst_value_set_double_range (pv1, min1, max1); + } + if (min2 < max2) { + g_value_init (pv2, GST_TYPE_DOUBLE_RANGE); + gst_value_set_double_range (pv2, min2, max2); + } + + if (min1 < max1 && min2 < max2) { + gst_value_list_concat (dest, pv1, pv2); + g_value_unset (pv1); + g_value_unset (pv2); + } + return TRUE; +} + +static gboolean +gst_value_subtract_from_list (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + guint i, size; + GValue subtraction = { 0, }; + gboolean ret = FALSE; + GType ltype; + + ltype = gst_value_list_get_type (); + + size = VALUE_LIST_SIZE (minuend); + for (i = 0; i < size; i++) { + const GValue *cur = VALUE_LIST_GET_VALUE (minuend, i); + + if (gst_value_subtract (&subtraction, cur, subtrahend)) { + if (!ret) { + gst_value_init_and_copy (dest, &subtraction); + ret = TRUE; + } else if (G_VALUE_HOLDS (dest, ltype) + && !G_VALUE_HOLDS (&subtraction, ltype)) { + gst_value_list_append_value (dest, &subtraction); + } else { + GValue temp = { 0, }; + + gst_value_init_and_copy (&temp, dest); + g_value_unset (dest); + gst_value_list_concat (dest, &temp, &subtraction); + g_value_unset (&temp); + } + g_value_unset (&subtraction); + } + } + return ret; +} + +static gboolean +gst_value_subtract_list (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + guint i, size; + GValue data[2] = { {0,}, {0,} }; + GValue *subtraction = &data[0], *result = &data[1]; + + gst_value_init_and_copy (result, minuend); + size = VALUE_LIST_SIZE (subtrahend); + for (i = 0; i < size; i++) { + const GValue *cur = VALUE_LIST_GET_VALUE (subtrahend, i); + + if (gst_value_subtract (subtraction, result, cur)) { + GValue *temp = result; + + result = subtraction; + subtraction = temp; + g_value_unset (subtraction); + } else { + g_value_unset (result); + return FALSE; + } + } + gst_value_init_and_copy (dest, result); + g_value_unset (result); + return TRUE; +} + +static gboolean +gst_value_subtract_fraction_fraction_range (GValue * dest, + const GValue * minuend, const GValue * subtrahend) +{ + const GValue *min = gst_value_get_fraction_range_min (subtrahend); + const GValue *max = gst_value_get_fraction_range_max (subtrahend); + GstValueCompareFunc compare; + + if ((compare = gst_value_get_compare_func (minuend))) { + /* subtracting a range from an fraction only works if the fraction + * is not in the range */ + if (gst_value_compare_with_func (minuend, min, compare) == + GST_VALUE_LESS_THAN || + gst_value_compare_with_func (minuend, max, compare) == + GST_VALUE_GREATER_THAN) { + /* and the result is the value */ + gst_value_init_and_copy (dest, minuend); + return TRUE; + } + } + return FALSE; +} + +static gboolean +gst_value_subtract_fraction_range_fraction (GValue * dest, + const GValue * minuend, const GValue * subtrahend) +{ + /* since we don't have open ranges, we cannot create a hole in + * a range. We return the original range */ + gst_value_init_and_copy (dest, minuend); + return TRUE; +} + +static gboolean +gst_value_subtract_fraction_range_fraction_range (GValue * dest, + const GValue * minuend, const GValue * subtrahend) +{ + /* since we don't have open ranges, we have to approximate */ + /* done like with ints and doubles. Creates a list of 2 fraction ranges */ + const GValue *min1 = gst_value_get_fraction_range_min (minuend); + const GValue *max2 = gst_value_get_fraction_range_max (minuend); + const GValue *max1 = gst_value_get_fraction_range_min (subtrahend); + const GValue *min2 = gst_value_get_fraction_range_max (subtrahend); + gint cmp1, cmp2; + GValue v1 = { 0, }; + GValue v2 = { 0, }; + GValue *pv1, *pv2; /* yeah, hungarian! */ + GstValueCompareFunc compare; + + g_return_val_if_fail (min1 != NULL && max1 != NULL, FALSE); + g_return_val_if_fail (min2 != NULL && max2 != NULL, FALSE); + + compare = gst_value_get_compare_func (min1); + g_return_val_if_fail (compare, FALSE); + + cmp1 = gst_value_compare_with_func (max2, max1, compare); + g_return_val_if_fail (cmp1 != GST_VALUE_UNORDERED, FALSE); + if (cmp1 == GST_VALUE_LESS_THAN) + max1 = max2; + cmp1 = gst_value_compare_with_func (min1, min2, compare); + g_return_val_if_fail (cmp1 != GST_VALUE_UNORDERED, FALSE); + if (cmp1 == GST_VALUE_GREATER_THAN) + min2 = min1; + + cmp1 = gst_value_compare_with_func (min1, max1, compare); + cmp2 = gst_value_compare_with_func (min2, max2, compare); + + if (cmp1 == GST_VALUE_LESS_THAN && cmp2 == GST_VALUE_LESS_THAN) { + pv1 = &v1; + pv2 = &v2; + } else if (cmp1 == GST_VALUE_LESS_THAN) { + pv1 = dest; + pv2 = NULL; + } else if (cmp2 == GST_VALUE_LESS_THAN) { + pv1 = NULL; + pv2 = dest; + } else { + return FALSE; + } + + if (cmp1 == GST_VALUE_LESS_THAN) { + g_value_init (pv1, GST_TYPE_FRACTION_RANGE); + gst_value_set_fraction_range (pv1, min1, max1); + } + if (cmp2 == GST_VALUE_LESS_THAN) { + g_value_init (pv2, GST_TYPE_FRACTION_RANGE); + gst_value_set_fraction_range (pv2, min2, max2); + } + + if (cmp1 == GST_VALUE_LESS_THAN && cmp2 == GST_VALUE_LESS_THAN) { + gst_value_list_concat (dest, pv1, pv2); + g_value_unset (pv1); + g_value_unset (pv2); + } + return TRUE; +} + + +/************** + * comparison * + **************/ + +/* + * gst_value_get_compare_func: + * @value1: a value to get the compare function for + * + * Determines the compare function to be used with values of the same type as + * @value1. The function can be given to gst_value_compare_with_func(). + * + * Returns: A #GstValueCompareFunc value + */ +static GstValueCompareFunc +gst_value_get_compare_func (const GValue * value1) +{ + GstValueTable *table, *best = NULL; + guint i; + GType type1; + + type1 = G_VALUE_TYPE (value1); + + /* this is a fast check */ + best = gst_value_hash_lookup_type (type1); + + /* slower checks */ + if (G_UNLIKELY (!best || !best->compare)) { + guint len = gst_value_table->len; + + best = NULL; + for (i = 0; i < len; i++) { + table = &g_array_index (gst_value_table, GstValueTable, i); + if (table->compare && g_type_is_a (type1, table->type)) { + if (!best || g_type_is_a (table->type, best->type)) + best = table; + } + } + } + if (G_LIKELY (best)) + return best->compare; + + return NULL; +} + +/** + * gst_value_can_compare: + * @value1: a value to compare + * @value2: another value to compare + * + * Determines if @value1 and @value2 can be compared. + * + * Returns: TRUE if the values can be compared + */ +gboolean +gst_value_can_compare (const GValue * value1, const GValue * value2) +{ + g_return_val_if_fail (G_IS_VALUE (value1), FALSE); + g_return_val_if_fail (G_IS_VALUE (value2), FALSE); + + if (G_VALUE_TYPE (value1) != G_VALUE_TYPE (value2)) + return FALSE; + + return gst_value_get_compare_func (value1) != NULL; +} + +/** + * gst_value_compare: + * @value1: a value to compare + * @value2: another value to compare + * + * Compares @value1 and @value2. If @value1 and @value2 cannot be + * compared, the function returns GST_VALUE_UNORDERED. Otherwise, + * if @value1 is greater than @value2, GST_VALUE_GREATER_THAN is returned. + * If @value1 is less than @value2, GST_VALUE_LESS_THAN is returned. + * If the values are equal, GST_VALUE_EQUAL is returned. + * + * Returns: comparison result + */ +gint +gst_value_compare (const GValue * value1, const GValue * value2) +{ + GstValueCompareFunc compare; + GType ltype; + + g_return_val_if_fail (G_IS_VALUE (value1), GST_VALUE_LESS_THAN); + g_return_val_if_fail (G_IS_VALUE (value2), GST_VALUE_GREATER_THAN); + + /* Special case: lists and scalar values + * "{ 1 }" and "1" are equal */ + ltype = gst_value_list_get_type (); + if (G_VALUE_HOLDS (value1, ltype) && !G_VALUE_HOLDS (value2, ltype) + && gst_value_list_get_size (value1) == 1) { + const GValue *elt; + + elt = gst_value_list_get_value (value1, 0); + return gst_value_compare (elt, value2); + } else if (G_VALUE_HOLDS (value2, ltype) && !G_VALUE_HOLDS (value1, ltype) + && gst_value_list_get_size (value2) == 1) { + const GValue *elt; + + elt = gst_value_list_get_value (value2, 0); + return gst_value_compare (elt, value1); + } + + if (G_VALUE_TYPE (value1) != G_VALUE_TYPE (value2)) + return GST_VALUE_UNORDERED; + + compare = gst_value_get_compare_func (value1); + if (compare) { + return compare (value1, value2); + } + + g_critical ("unable to compare values of type %s\n", + g_type_name (G_VALUE_TYPE (value1))); + return GST_VALUE_UNORDERED; +} + +/* + * gst_value_compare_with_func: + * @value1: a value to compare + * @value2: another value to compare + * @compare: compare function + * + * Compares @value1 and @value2 using the @compare function. Works like + * gst_value_compare() but allows to save time determining the compare function + * a multiple times. + * + * Returns: comparison result + */ +static gint +gst_value_compare_with_func (const GValue * value1, const GValue * value2, + GstValueCompareFunc compare) +{ + g_assert (compare); + + if (G_VALUE_TYPE (value1) != G_VALUE_TYPE (value2)) + return GST_VALUE_UNORDERED; + + return compare (value1, value2); +} + +/* union */ + +/** + * gst_value_can_union: + * @value1: a value to union + * @value2: another value to union + * + * Determines if @value1 and @value2 can be non-trivially unioned. + * Any two values can be trivially unioned by adding both of them + * to a GstValueList. However, certain types have the possibility + * to be unioned in a simpler way. For example, an integer range + * and an integer can be unioned if the integer is a subset of the + * integer range. If there is the possibility that two values can + * be unioned, this function returns TRUE. + * + * Returns: TRUE if there is a function allowing the two values to + * be unioned. + */ +gboolean +gst_value_can_union (const GValue * value1, const GValue * value2) +{ + GstValueUnionInfo *union_info; + guint i, len; + + g_return_val_if_fail (G_IS_VALUE (value1), FALSE); + g_return_val_if_fail (G_IS_VALUE (value2), FALSE); + + len = gst_value_union_funcs->len; + + for (i = 0; i < len; i++) { + union_info = &g_array_index (gst_value_union_funcs, GstValueUnionInfo, i); + if (union_info->type1 == G_VALUE_TYPE (value1) && + union_info->type2 == G_VALUE_TYPE (value2)) + return TRUE; + if (union_info->type1 == G_VALUE_TYPE (value2) && + union_info->type2 == G_VALUE_TYPE (value1)) + return TRUE; + } + + return FALSE; +} + +/** + * gst_value_union: + * @dest: (out caller-allocates): the destination value + * @value1: a value to union + * @value2: another value to union + * + * Creates a GValue corresponding to the union of @value1 and @value2. + * + * Returns: always returns %TRUE + */ +/* FIXME: change return type to 'void'? */ +gboolean +gst_value_union (GValue * dest, const GValue * value1, const GValue * value2) +{ + GstValueUnionInfo *union_info; + guint i, len; + + g_return_val_if_fail (dest != NULL, FALSE); + g_return_val_if_fail (G_IS_VALUE (value1), FALSE); + g_return_val_if_fail (G_IS_VALUE (value2), FALSE); + + len = gst_value_union_funcs->len; + + for (i = 0; i < len; i++) { + union_info = &g_array_index (gst_value_union_funcs, GstValueUnionInfo, i); + if (union_info->type1 == G_VALUE_TYPE (value1) && + union_info->type2 == G_VALUE_TYPE (value2)) { + if (union_info->func (dest, value1, value2)) { + return TRUE; + } + } + if (union_info->type1 == G_VALUE_TYPE (value2) && + union_info->type2 == G_VALUE_TYPE (value1)) { + if (union_info->func (dest, value2, value1)) { + return TRUE; + } + } + } + + gst_value_list_concat (dest, value1, value2); + return TRUE; +} + +/** + * gst_value_register_union_func: + * @type1: a type to union + * @type2: another type to union + * @func: a function that implements creating a union between the two types + * + * Registers a union function that can create a union between #GValue items + * of the type @type1 and @type2. + * + * Union functions should be registered at startup before any pipelines are + * started, as gst_value_register_union_func() is not thread-safe and cannot + * be used at the same time as gst_value_union() or gst_value_can_union(). + */ +void +gst_value_register_union_func (GType type1, GType type2, GstValueUnionFunc func) +{ + GstValueUnionInfo union_info; + + union_info.type1 = type1; + union_info.type2 = type2; + union_info.func = func; + + g_array_append_val (gst_value_union_funcs, union_info); +} + +/* intersection */ + +/** + * gst_value_can_intersect: + * @value1: a value to intersect + * @value2: another value to intersect + * + * Determines if intersecting two values will produce a valid result. + * Two values will produce a valid intersection if they have the same + * type, or if there is a method (registered by + * gst_value_register_intersect_func()) to calculate the intersection. + * + * Returns: TRUE if the values can intersect + */ +gboolean +gst_value_can_intersect (const GValue * value1, const GValue * value2) +{ + GstValueIntersectInfo *intersect_info; + guint i, len; + GType ltype, type1, type2; + + g_return_val_if_fail (G_IS_VALUE (value1), FALSE); + g_return_val_if_fail (G_IS_VALUE (value2), FALSE); + + ltype = gst_value_list_get_type (); + + /* special cases */ + if (G_VALUE_HOLDS (value1, ltype) || G_VALUE_HOLDS (value2, ltype)) + return TRUE; + + type1 = G_VALUE_TYPE (value1); + type2 = G_VALUE_TYPE (value2); + + /* practically all GstValue types have a compare function (_can_compare=TRUE) + * GstStructure and GstCaps have npot, but are intersectable */ + if (type1 == type2) + return TRUE; + + /* check registered intersect functions */ + len = gst_value_intersect_funcs->len; + for (i = 0; i < len; i++) { + intersect_info = &g_array_index (gst_value_intersect_funcs, + GstValueIntersectInfo, i); + if ((intersect_info->type1 == type1 && intersect_info->type2 == type2) || + (intersect_info->type1 == type2 && intersect_info->type2 == type1)) + return TRUE; + } + + return gst_value_can_compare (value1, value2); +} + +/** + * gst_value_intersect: + * @dest: (out caller-allocates): a uninitialized #GValue that will hold the calculated + * intersection value + * @value1: a value to intersect + * @value2: another value to intersect + * + * Calculates the intersection of two values. If the values have + * a non-empty intersection, the value representing the intersection + * is placed in @dest. If the intersection is non-empty, @dest is + * not modified. + * + * Returns: TRUE if the intersection is non-empty + */ +gboolean +gst_value_intersect (GValue * dest, const GValue * value1, + const GValue * value2) +{ + GstValueIntersectInfo *intersect_info; + guint i, len; + GType ltype, type1, type2; + + g_return_val_if_fail (dest != NULL, FALSE); + g_return_val_if_fail (G_IS_VALUE (value1), FALSE); + g_return_val_if_fail (G_IS_VALUE (value2), FALSE); + + ltype = gst_value_list_get_type (); + + /* special cases first */ + if (G_VALUE_HOLDS (value1, ltype)) + return gst_value_intersect_list (dest, value1, value2); + if (G_VALUE_HOLDS (value2, ltype)) + return gst_value_intersect_list (dest, value2, value1); + + if (gst_value_compare (value1, value2) == GST_VALUE_EQUAL) { + gst_value_init_and_copy (dest, value1); + return TRUE; + } + + type1 = G_VALUE_TYPE (value1); + type2 = G_VALUE_TYPE (value2); + + len = gst_value_intersect_funcs->len; + for (i = 0; i < len; i++) { + intersect_info = &g_array_index (gst_value_intersect_funcs, + GstValueIntersectInfo, i); + if (intersect_info->type1 == type1 && intersect_info->type2 == type2) { + return intersect_info->func (dest, value1, value2); + } + if (intersect_info->type1 == type2 && intersect_info->type2 == type1) { + return intersect_info->func (dest, value2, value1); + } + } + return FALSE; +} + +/** + * gst_value_register_intersect_func: + * @type1: the first type to intersect + * @type2: the second type to intersect + * @func: the intersection function + * + * Registers a function that is called to calculate the intersection + * of the values having the types @type1 and @type2. + * + * Intersect functions should be registered at startup before any pipelines are + * started, as gst_value_register_intersect_func() is not thread-safe and + * cannot be used at the same time as gst_value_intersect() or + * gst_value_can_intersect(). + */ +void +gst_value_register_intersect_func (GType type1, GType type2, + GstValueIntersectFunc func) +{ + GstValueIntersectInfo intersect_info; + + intersect_info.type1 = type1; + intersect_info.type2 = type2; + intersect_info.func = func; + + g_array_append_val (gst_value_intersect_funcs, intersect_info); +} + + +/* subtraction */ + +/** + * gst_value_subtract: + * @dest: (out caller-allocates): the destination value for the result if the + * subtraction is not empty + * @minuend: the value to subtract from + * @subtrahend: the value to subtract + * + * Subtracts @subtrahend from @minuend and stores the result in @dest. + * Note that this means subtraction as in sets, not as in mathematics. + * + * Returns: %TRUE if the subtraction is not empty + */ +gboolean +gst_value_subtract (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + GstValueSubtractInfo *info; + guint i, len; + GType ltype, mtype, stype; + + g_return_val_if_fail (dest != NULL, FALSE); + g_return_val_if_fail (G_IS_VALUE (minuend), FALSE); + g_return_val_if_fail (G_IS_VALUE (subtrahend), FALSE); + + ltype = gst_value_list_get_type (); + + /* special cases first */ + if (G_VALUE_HOLDS (minuend, ltype)) + return gst_value_subtract_from_list (dest, minuend, subtrahend); + if (G_VALUE_HOLDS (subtrahend, ltype)) + return gst_value_subtract_list (dest, minuend, subtrahend); + + mtype = G_VALUE_TYPE (minuend); + stype = G_VALUE_TYPE (subtrahend); + + len = gst_value_subtract_funcs->len; + for (i = 0; i < len; i++) { + info = &g_array_index (gst_value_subtract_funcs, GstValueSubtractInfo, i); + if (info->minuend == mtype && info->subtrahend == stype) { + return info->func (dest, minuend, subtrahend); + } + } + + if (gst_value_compare (minuend, subtrahend) != GST_VALUE_EQUAL) { + gst_value_init_and_copy (dest, minuend); + return TRUE; + } + + return FALSE; +} + +#if 0 +gboolean +gst_value_subtract (GValue * dest, const GValue * minuend, + const GValue * subtrahend) +{ + gboolean ret = gst_value_subtract2 (dest, minuend, subtrahend); + + g_printerr ("\"%s\" - \"%s\" = \"%s\"\n", gst_value_serialize (minuend), + gst_value_serialize (subtrahend), + ret ? gst_value_serialize (dest) : "---"); + return ret; +} +#endif + +/** + * gst_value_can_subtract: + * @minuend: the value to subtract from + * @subtrahend: the value to subtract + * + * Checks if it's possible to subtract @subtrahend from @minuend. + * + * Returns: TRUE if a subtraction is possible + */ +gboolean +gst_value_can_subtract (const GValue * minuend, const GValue * subtrahend) +{ + GstValueSubtractInfo *info; + guint i, len; + GType ltype, mtype, stype; + + g_return_val_if_fail (G_IS_VALUE (minuend), FALSE); + g_return_val_if_fail (G_IS_VALUE (subtrahend), FALSE); + + ltype = gst_value_list_get_type (); + + /* special cases */ + if (G_VALUE_HOLDS (minuend, ltype) || G_VALUE_HOLDS (subtrahend, ltype)) + return TRUE; + + mtype = G_VALUE_TYPE (minuend); + stype = G_VALUE_TYPE (subtrahend); + + len = gst_value_subtract_funcs->len; + for (i = 0; i < len; i++) { + info = &g_array_index (gst_value_subtract_funcs, GstValueSubtractInfo, i); + if (info->minuend == mtype && info->subtrahend == stype) + return TRUE; + } + + return gst_value_can_compare (minuend, subtrahend); +} + +/** + * gst_value_register_subtract_func: + * @minuend_type: type of the minuend + * @subtrahend_type: type of the subtrahend + * @func: function to use + * + * Registers @func as a function capable of subtracting the values of + * @subtrahend_type from values of @minuend_type. + * + * Subtract functions should be registered at startup before any pipelines are + * started, as gst_value_register_subtract_func() is not thread-safe and + * cannot be used at the same time as gst_value_subtract(). + */ +void +gst_value_register_subtract_func (GType minuend_type, GType subtrahend_type, + GstValueSubtractFunc func) +{ + GstValueSubtractInfo info; + + /* one type must be unfixed, other subtractions can be done as comparisons */ + g_return_if_fail (!gst_type_is_fixed (minuend_type) + || !gst_type_is_fixed (subtrahend_type)); + + info.minuend = minuend_type; + info.subtrahend = subtrahend_type; + info.func = func; + + g_array_append_val (gst_value_subtract_funcs, info); +} + +/** + * gst_value_register: + * @table: structure containing functions to register + * + * Registers functions to perform calculations on #GValue items of a given + * type. Each type can only be added once. + */ +void +gst_value_register (const GstValueTable * table) +{ + GstValueTable *found; + + g_return_if_fail (table != NULL); + + g_array_append_val (gst_value_table, *table); + + found = gst_value_hash_lookup_type (table->type); + if (found) + g_warning ("adding type %s multiple times", g_type_name (table->type)); + + /* FIXME: we're not really doing the const justice, we assume the table is + * static */ + gst_value_hash_add_type (table->type, table); +} + +/** + * gst_value_init_and_copy: + * @dest: (out caller-allocates): the target value + * @src: the source value + * + * Initialises the target value to be of the same type as source and then copies + * the contents from source to target. + */ +void +gst_value_init_and_copy (GValue * dest, const GValue * src) +{ + g_return_if_fail (G_IS_VALUE (src)); + g_return_if_fail (dest != NULL); + + g_value_init (dest, G_VALUE_TYPE (src)); + g_value_copy (src, dest); +} + +/** + * gst_value_serialize: + * @value: a #GValue to serialize + * + * tries to transform the given @value into a string representation that allows + * getting back this string later on using gst_value_deserialize(). + * + * Free-function: g_free + * + * Returns: (transfer full): the serialization for @value or NULL if none exists + */ +gchar * +gst_value_serialize (const GValue * value) +{ + guint i, len; + GValue s_val = { 0 }; + GstValueTable *table, *best; + gchar *s; + GType type; + + g_return_val_if_fail (G_IS_VALUE (value), NULL); + + type = G_VALUE_TYPE (value); + + best = gst_value_hash_lookup_type (type); + + if (G_UNLIKELY (!best || !best->serialize)) { + len = gst_value_table->len; + best = NULL; + for (i = 0; i < len; i++) { + table = &g_array_index (gst_value_table, GstValueTable, i); + if (table->serialize && g_type_is_a (type, table->type)) { + if (!best || g_type_is_a (table->type, best->type)) + best = table; + } + } + } + if (G_LIKELY (best)) + return best->serialize (value); + + g_value_init (&s_val, G_TYPE_STRING); + if (g_value_transform (value, &s_val)) { + s = gst_string_wrap (g_value_get_string (&s_val)); + } else { + s = NULL; + } + g_value_unset (&s_val); + + return s; +} + +/** + * gst_value_deserialize: + * @dest: (out caller-allocates): #GValue to fill with contents of + * deserialization + * @src: string to deserialize + * + * Tries to deserialize a string into the type specified by the given GValue. + * If the operation succeeds, TRUE is returned, FALSE otherwise. + * + * Returns: TRUE on success + */ +gboolean +gst_value_deserialize (GValue * dest, const gchar * src) +{ + GstValueTable *table, *best; + guint i, len; + GType type; + + g_return_val_if_fail (src != NULL, FALSE); + g_return_val_if_fail (G_IS_VALUE (dest), FALSE); + + type = G_VALUE_TYPE (dest); + + best = gst_value_hash_lookup_type (type); + if (G_UNLIKELY (!best || !best->deserialize)) { + len = gst_value_table->len; + best = NULL; + for (i = 0; i < len; i++) { + table = &g_array_index (gst_value_table, GstValueTable, i); + if (table->deserialize && g_type_is_a (type, table->type)) { + if (!best || g_type_is_a (table->type, best->type)) + best = table; + } + } + } + if (G_LIKELY (best)) + return best->deserialize (dest, src); + + return FALSE; +} + +/** + * gst_value_is_fixed: + * @value: the #GValue to check + * + * Tests if the given GValue, if available in a GstStructure (or any other + * container) contains a "fixed" (which means: one value) or an "unfixed" + * (which means: multiple possible values, such as data lists or data + * ranges) value. + * + * Returns: true if the value is "fixed". + */ + +gboolean +gst_value_is_fixed (const GValue * value) +{ + GType type; + + g_return_val_if_fail (G_IS_VALUE (value), FALSE); + + type = G_VALUE_TYPE (value); + + /* the most common types are just basic plain glib types */ + if (type <= G_TYPE_MAKE_FUNDAMENTAL (G_TYPE_RESERVED_GLIB_LAST)) { + return TRUE; + } + + if (type == GST_TYPE_ARRAY) { + gint size, n; + const GValue *kid; + + /* check recursively */ + size = gst_value_array_get_size (value); + for (n = 0; n < size; n++) { + kid = gst_value_array_get_value (value, n); + if (!gst_value_is_fixed (kid)) + return FALSE; + } + return TRUE; + } + return gst_type_is_fixed (type); +} + +/** + * gst_value_fixate: + * @dest: the #GValue destination + * @src: the #GValue to fixate + * + * Fixate @src into a new value @dest. + * For ranges, the first element is taken. For lists and arrays, the + * first item is fixated and returned. + * If @src is already fixed, this function returns FALSE. + * + * Returns: true if @dest contains a fixated version of @src. + */ +gboolean +gst_value_fixate (GValue * dest, const GValue * src) +{ + g_return_val_if_fail (G_IS_VALUE (src), FALSE); + g_return_val_if_fail (dest != NULL, FALSE); + + if (G_VALUE_TYPE (src) == GST_TYPE_INT_RANGE) { + g_value_init (dest, G_TYPE_INT); + g_value_set_int (dest, gst_value_get_int_range_min (src)); + } else if (G_VALUE_TYPE (src) == GST_TYPE_DOUBLE_RANGE) { + g_value_init (dest, G_TYPE_DOUBLE); + g_value_set_double (dest, gst_value_get_double_range_min (src)); + } else if (G_VALUE_TYPE (src) == GST_TYPE_FRACTION_RANGE) { + gst_value_init_and_copy (dest, gst_value_get_fraction_range_min (src)); + } else if (G_VALUE_TYPE (src) == GST_TYPE_LIST) { + GValue temp = { 0 }; + + /* list could be empty */ + if (gst_value_list_get_size (src) <= 0) + return FALSE; + + gst_value_init_and_copy (&temp, gst_value_list_get_value (src, 0)); + + if (!gst_value_fixate (dest, &temp)) + gst_value_init_and_copy (dest, &temp); + g_value_unset (&temp); + } else if (G_VALUE_TYPE (src) == GST_TYPE_ARRAY) { + gboolean res = FALSE; + guint n, len; + + len = gst_value_array_get_size (src); + g_value_init (dest, GST_TYPE_ARRAY); + for (n = 0; n < len; n++) { + GValue kid = { 0 }; + const GValue *orig_kid = gst_value_array_get_value (src, n); + + if (!gst_value_fixate (&kid, orig_kid)) + gst_value_init_and_copy (&kid, orig_kid); + else + res = TRUE; + gst_value_array_append_value (dest, &kid); + g_value_unset (&kid); + } + + if (!res) + g_value_unset (dest); + + return res; + } else { + return FALSE; + } + return TRUE; +} + + +/************ + * fraction * + ************/ + +/* helper functions */ +static void +gst_value_init_fraction (GValue * value) +{ + value->data[0].v_int = 0; + value->data[1].v_int = 1; +} + +static void +gst_value_copy_fraction (const GValue * src_value, GValue * dest_value) +{ + dest_value->data[0].v_int = src_value->data[0].v_int; + dest_value->data[1].v_int = src_value->data[1].v_int; +} + +static gchar * +gst_value_collect_fraction (GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + if (n_collect_values != 2) + return g_strdup_printf ("not enough value locations for `%s' passed", + G_VALUE_TYPE_NAME (value)); + if (collect_values[1].v_int == 0) + return g_strdup_printf ("passed '0' as denominator for `%s'", + G_VALUE_TYPE_NAME (value)); + if (collect_values[0].v_int < -G_MAXINT) + return + g_strdup_printf + ("passed value smaller than -G_MAXINT as numerator for `%s'", + G_VALUE_TYPE_NAME (value)); + if (collect_values[1].v_int < -G_MAXINT) + return + g_strdup_printf + ("passed value smaller than -G_MAXINT as denominator for `%s'", + G_VALUE_TYPE_NAME (value)); + + gst_value_set_fraction (value, + collect_values[0].v_int, collect_values[1].v_int); + + return NULL; +} + +static gchar * +gst_value_lcopy_fraction (const GValue * value, guint n_collect_values, + GTypeCValue * collect_values, guint collect_flags) +{ + gint *numerator = collect_values[0].v_pointer; + gint *denominator = collect_values[1].v_pointer; + + if (!numerator) + return g_strdup_printf ("numerator for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + if (!denominator) + return g_strdup_printf ("denominator for `%s' passed as NULL", + G_VALUE_TYPE_NAME (value)); + + *numerator = value->data[0].v_int; + *denominator = value->data[1].v_int; + + return NULL; +} + +/** + * gst_value_set_fraction: + * @value: a GValue initialized to #GST_TYPE_FRACTION + * @numerator: the numerator of the fraction + * @denominator: the denominator of the fraction + * + * Sets @value to the fraction specified by @numerator over @denominator. + * The fraction gets reduced to the smallest numerator and denominator, + * and if necessary the sign is moved to the numerator. + */ +void +gst_value_set_fraction (GValue * value, gint numerator, gint denominator) +{ + gint gcd = 0; + + g_return_if_fail (GST_VALUE_HOLDS_FRACTION (value)); + g_return_if_fail (denominator != 0); + g_return_if_fail (denominator >= -G_MAXINT); + g_return_if_fail (numerator >= -G_MAXINT); + + /* normalize sign */ + if (denominator < 0) { + numerator = -numerator; + denominator = -denominator; + } + + /* check for reduction */ + gcd = gst_util_greatest_common_divisor (numerator, denominator); + if (gcd) { + numerator /= gcd; + denominator /= gcd; + } + + g_assert (denominator > 0); + + value->data[0].v_int = numerator; + value->data[1].v_int = denominator; +} + +/** + * gst_value_get_fraction_numerator: + * @value: a GValue initialized to #GST_TYPE_FRACTION + * + * Gets the numerator of the fraction specified by @value. + * + * Returns: the numerator of the fraction. + */ +gint +gst_value_get_fraction_numerator (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_FRACTION (value), 0); + + return value->data[0].v_int; +} + +/** + * gst_value_get_fraction_denominator: + * @value: a GValue initialized to #GST_TYPE_FRACTION + * + * Gets the denominator of the fraction specified by @value. + * + * Returns: the denominator of the fraction. + */ +gint +gst_value_get_fraction_denominator (const GValue * value) +{ + g_return_val_if_fail (GST_VALUE_HOLDS_FRACTION (value), 1); + + return value->data[1].v_int; +} + +/** + * gst_value_fraction_multiply: + * @product: a GValue initialized to #GST_TYPE_FRACTION + * @factor1: a GValue initialized to #GST_TYPE_FRACTION + * @factor2: a GValue initialized to #GST_TYPE_FRACTION + * + * Multiplies the two #GValue items containing a #GST_TYPE_FRACTION and sets + * @product to the product of the two fractions. + * + * Returns: FALSE in case of an error (like integer overflow), TRUE otherwise. + */ +gboolean +gst_value_fraction_multiply (GValue * product, const GValue * factor1, + const GValue * factor2) +{ + gint n1, n2, d1, d2; + gint res_n, res_d; + + g_return_val_if_fail (product != NULL, FALSE); + g_return_val_if_fail (GST_VALUE_HOLDS_FRACTION (factor1), FALSE); + g_return_val_if_fail (GST_VALUE_HOLDS_FRACTION (factor2), FALSE); + + n1 = factor1->data[0].v_int; + n2 = factor2->data[0].v_int; + d1 = factor1->data[1].v_int; + d2 = factor2->data[1].v_int; + + if (!gst_util_fraction_multiply (n1, d1, n2, d2, &res_n, &res_d)) + return FALSE; + + gst_value_set_fraction (product, res_n, res_d); + + return TRUE; +} + +/** + * gst_value_fraction_subtract: + * @dest: a GValue initialized to #GST_TYPE_FRACTION + * @minuend: a GValue initialized to #GST_TYPE_FRACTION + * @subtrahend: a GValue initialized to #GST_TYPE_FRACTION + * + * Subtracts the @subtrahend from the @minuend and sets @dest to the result. + * + * Returns: FALSE in case of an error (like integer overflow), TRUE otherwise. + */ +gboolean +gst_value_fraction_subtract (GValue * dest, + const GValue * minuend, const GValue * subtrahend) +{ + gint n1, n2, d1, d2; + gint res_n, res_d; + + g_return_val_if_fail (dest != NULL, FALSE); + g_return_val_if_fail (GST_VALUE_HOLDS_FRACTION (minuend), FALSE); + g_return_val_if_fail (GST_VALUE_HOLDS_FRACTION (subtrahend), FALSE); + + n1 = minuend->data[0].v_int; + n2 = subtrahend->data[0].v_int; + d1 = minuend->data[1].v_int; + d2 = subtrahend->data[1].v_int; + + if (!gst_util_fraction_add (n1, d1, -n2, d2, &res_n, &res_d)) + return FALSE; + gst_value_set_fraction (dest, res_n, res_d); + + return TRUE; +} + +static gchar * +gst_value_serialize_fraction (const GValue * value) +{ + gint32 numerator = value->data[0].v_int; + gint32 denominator = value->data[1].v_int; + gboolean positive = TRUE; + + /* get the sign and make components absolute */ + if (numerator < 0) { + numerator = -numerator; + positive = !positive; + } + if (denominator < 0) { + denominator = -denominator; + positive = !positive; + } + + return g_strdup_printf ("%s%d/%d", + positive ? "" : "-", numerator, denominator); +} + +static gboolean +gst_value_deserialize_fraction (GValue * dest, const gchar * s) +{ + gint num, den; + gint num_chars; + + if (G_UNLIKELY (s == NULL)) + return FALSE; + + if (G_UNLIKELY (dest == NULL || !GST_VALUE_HOLDS_FRACTION (dest))) + return FALSE; + + if (sscanf (s, "%d/%d%n", &num, &den, &num_chars) >= 2) { + if (s[num_chars] != 0) + return FALSE; + if (den == 0) + return FALSE; + + gst_value_set_fraction (dest, num, den); + return TRUE; + } else if (g_ascii_strcasecmp (s, "1/max") == 0) { + gst_value_set_fraction (dest, 1, G_MAXINT); + return TRUE; + } else if (sscanf (s, "%d%n", &num, &num_chars) >= 1) { + if (s[num_chars] != 0) + return FALSE; + gst_value_set_fraction (dest, num, 1); + return TRUE; + } else if (g_ascii_strcasecmp (s, "min") == 0) { + gst_value_set_fraction (dest, -G_MAXINT, 1); + return TRUE; + } else if (g_ascii_strcasecmp (s, "max") == 0) { + gst_value_set_fraction (dest, G_MAXINT, 1); + return TRUE; + } + + return FALSE; +} + +static void +gst_value_transform_fraction_string (const GValue * src_value, + GValue * dest_value) +{ + dest_value->data[0].v_pointer = gst_value_serialize_fraction (src_value); +} + +static void +gst_value_transform_string_fraction (const GValue * src_value, + GValue * dest_value) +{ + if (!gst_value_deserialize_fraction (dest_value, + src_value->data[0].v_pointer)) + /* If the deserialize fails, ensure we leave the fraction in a + * valid, if incorrect, state */ + gst_value_set_fraction (dest_value, 0, 1); +} + +static void +gst_value_transform_double_fraction (const GValue * src_value, + GValue * dest_value) +{ + gdouble src = g_value_get_double (src_value); + gint n, d; + + gst_util_double_to_fraction (src, &n, &d); + gst_value_set_fraction (dest_value, n, d); +} + +static void +gst_value_transform_float_fraction (const GValue * src_value, + GValue * dest_value) +{ + gfloat src = g_value_get_float (src_value); + gint n, d; + + gst_util_double_to_fraction (src, &n, &d); + gst_value_set_fraction (dest_value, n, d); +} + +static void +gst_value_transform_fraction_double (const GValue * src_value, + GValue * dest_value) +{ + dest_value->data[0].v_double = ((double) src_value->data[0].v_int) / + ((double) src_value->data[1].v_int); +} + +static void +gst_value_transform_fraction_float (const GValue * src_value, + GValue * dest_value) +{ + dest_value->data[0].v_float = ((float) src_value->data[0].v_int) / + ((float) src_value->data[1].v_int); +} + +static gint +gst_value_compare_fraction (const GValue * value1, const GValue * value2) +{ + gint n1, n2; + gint d1, d2; + gint ret; + + n1 = value1->data[0].v_int; + n2 = value2->data[0].v_int; + d1 = value1->data[1].v_int; + d2 = value2->data[1].v_int; + + /* fractions are reduced when set, so we can quickly see if they're equal */ + if (n1 == n2 && d1 == d2) + return GST_VALUE_EQUAL; + + if (d1 == 0 && d2 == 0) + return GST_VALUE_UNORDERED; + else if (d1 == 0) + return GST_VALUE_GREATER_THAN; + else if (d2 == 0) + return GST_VALUE_LESS_THAN; + + ret = gst_util_fraction_compare (n1, d1, n2, d2); + if (ret == -1) + return GST_VALUE_LESS_THAN; + else if (ret == 1) + return GST_VALUE_GREATER_THAN; + + /* Equality can't happen here because we check for that + * first already */ + g_return_val_if_reached (GST_VALUE_UNORDERED); +} + +/********* + * GDate * + *********/ + +/** + * gst_value_set_date: + * @value: a GValue initialized to GST_TYPE_DATE + * @date: the date to set the value to + * + * Sets the contents of @value to correspond to @date. The actual + * #GDate structure is copied before it is used. + */ +void +gst_value_set_date (GValue * value, const GDate * date) +{ + g_return_if_fail (G_VALUE_TYPE (value) == GST_TYPE_DATE); + g_return_if_fail (g_date_valid (date)); + + g_value_set_boxed (value, date); +} + +/** + * gst_value_get_date: + * @value: a GValue initialized to GST_TYPE_DATE + * + * Gets the contents of @value. + * + * Returns: (transfer none): the contents of @value + */ +const GDate * +gst_value_get_date (const GValue * value) +{ + g_return_val_if_fail (G_VALUE_TYPE (value) == GST_TYPE_DATE, NULL); + + return (const GDate *) g_value_get_boxed (value); +} + +static gpointer +gst_date_copy (gpointer boxed) +{ + const GDate *date = (const GDate *) boxed; + + if (!g_date_valid (date)) { + GST_WARNING ("invalid GDate"); + return NULL; + } + + return g_date_new_julian (g_date_get_julian (date)); +} + +static gint +gst_value_compare_date (const GValue * value1, const GValue * value2) +{ + const GDate *date1 = (const GDate *) g_value_get_boxed (value1); + const GDate *date2 = (const GDate *) g_value_get_boxed (value2); + guint32 j1, j2; + + if (date1 == date2) + return GST_VALUE_EQUAL; + + if ((date1 == NULL || !g_date_valid (date1)) + && (date2 != NULL && g_date_valid (date2))) { + return GST_VALUE_LESS_THAN; + } + + if ((date2 == NULL || !g_date_valid (date2)) + && (date1 != NULL && g_date_valid (date1))) { + return GST_VALUE_GREATER_THAN; + } + + if (date1 == NULL || date2 == NULL || !g_date_valid (date1) + || !g_date_valid (date2)) { + return GST_VALUE_UNORDERED; + } + + j1 = g_date_get_julian (date1); + j2 = g_date_get_julian (date2); + + if (j1 == j2) + return GST_VALUE_EQUAL; + else if (j1 < j2) + return GST_VALUE_LESS_THAN; + else + return GST_VALUE_GREATER_THAN; +} + +static gchar * +gst_value_serialize_date (const GValue * val) +{ + const GDate *date = (const GDate *) g_value_get_boxed (val); + + if (date == NULL || !g_date_valid (date)) + return g_strdup ("9999-99-99"); + + return g_strdup_printf ("%04u-%02u-%02u", g_date_get_year (date), + g_date_get_month (date), g_date_get_day (date)); +} + +static gboolean +gst_value_deserialize_date (GValue * dest, const gchar * s) +{ + guint year, month, day; + + if (!s || sscanf (s, "%04u-%02u-%02u", &year, &month, &day) != 3) + return FALSE; + + if (!g_date_valid_dmy (day, month, year)) + return FALSE; + + g_value_take_boxed (dest, g_date_new_dmy (day, month, year)); + return TRUE; +} + +/************* + * GstDateTime * + *************/ + +static gint +gst_value_compare_date_time (const GValue * value1, const GValue * value2) +{ + const GstDateTime *date1 = (const GstDateTime *) g_value_get_boxed (value1); + const GstDateTime *date2 = (const GstDateTime *) g_value_get_boxed (value2); + gint ret; + + if (date1 == date2) + return GST_VALUE_EQUAL; + + if ((date1 == NULL) && (date2 != NULL)) { + return GST_VALUE_LESS_THAN; + } + if ((date2 == NULL) && (date1 != NULL)) { + return GST_VALUE_LESS_THAN; + } + + ret = priv_gst_date_time_compare (date1, date2); + + if (ret == 0) + return GST_VALUE_EQUAL; + else if (ret < 0) + return GST_VALUE_LESS_THAN; + else + return GST_VALUE_GREATER_THAN; +} + +static gchar * +gst_value_serialize_date_time (const GValue * val) +{ + GstDateTime *date = (GstDateTime *) g_value_get_boxed (val); + gfloat offset; + gint tzhour, tzminute; + + if (date == NULL) + return g_strdup ("null"); + + offset = gst_date_time_get_time_zone_offset (date); + + tzhour = (gint) ABS (offset); + tzminute = (gint) ((ABS (offset) - tzhour) * 60); + + return g_strdup_printf ("\"%04d-%02d-%02dT%02d:%02d:%02d.%06d" + "%c%02d%02d\"", gst_date_time_get_year (date), + gst_date_time_get_month (date), gst_date_time_get_day (date), + gst_date_time_get_hour (date), gst_date_time_get_minute (date), + gst_date_time_get_second (date), gst_date_time_get_microsecond (date), + offset >= 0 ? '+' : '-', tzhour, tzminute); +} + +static gboolean +gst_value_deserialize_date_time (GValue * dest, const gchar * s) +{ + gint year, month, day, hour, minute, second, usecond; + gchar signal; + gint offset = 0; + gfloat tzoffset = 0; + gint ret; + + if (!s || strcmp (s, "null") == 0) { + return FALSE; + } + + ret = sscanf (s, "%04d-%02d-%02dT%02d:%02d:%02d.%06d%c%04d", + &year, &month, &day, &hour, &minute, &second, &usecond, &signal, &offset); + if (ret >= 9) { + tzoffset = (offset / 100) + ((offset % 100) / 60.0); + if (signal == '-') + tzoffset = -tzoffset; + } else + return FALSE; + + g_value_take_boxed (dest, gst_date_time_new (tzoffset, year, month, day, hour, + minute, second + (usecond / 1000000.0))); + return TRUE; +} + +static void +gst_value_transform_date_string (const GValue * src_value, GValue * dest_value) +{ + dest_value->data[0].v_pointer = gst_value_serialize_date (src_value); +} + +static void +gst_value_transform_string_date (const GValue * src_value, GValue * dest_value) +{ + gst_value_deserialize_date (dest_value, src_value->data[0].v_pointer); +} + +static void +gst_value_transform_object_string (const GValue * src_value, + GValue * dest_value) +{ + GstObject *obj; + gchar *str; + + obj = g_value_get_object (src_value); + if (obj) { + str = + g_strdup_printf ("(%s) %s", G_OBJECT_TYPE_NAME (obj), + GST_OBJECT_NAME (obj)); + } else { + str = g_strdup ("NULL"); + } + + dest_value->data[0].v_pointer = str; +} + +static GTypeInfo _info = { + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + 0, + 0, + NULL, + NULL, +}; + +static GTypeFundamentalInfo _finfo = { + 0 +}; + +#define FUNC_VALUE_GET_TYPE(type, name) \ +GType gst_ ## type ## _get_type (void) \ +{ \ + static volatile GType gst_ ## type ## _type = 0; \ + \ + if (g_once_init_enter (&gst_ ## type ## _type)) { \ + GType _type; \ + _info.value_table = & _gst_ ## type ## _value_table; \ + _type = g_type_register_fundamental ( \ + g_type_fundamental_next (), \ + name, &_info, &_finfo, 0); \ + g_once_init_leave(&gst_ ## type ## _type, _type); \ + } \ + \ + return gst_ ## type ## _type; \ +} + +static const GTypeValueTable _gst_int_range_value_table = { + gst_value_init_int_range, + NULL, + gst_value_copy_int_range, + NULL, + (char *) "ii", + gst_value_collect_int_range, + (char *) "pp", + gst_value_lcopy_int_range +}; + +FUNC_VALUE_GET_TYPE (int_range, "GstIntRange"); + +static const GTypeValueTable _gst_int64_range_value_table = { + gst_value_init_int64_range, + NULL, + gst_value_copy_int64_range, + NULL, + (char *) "qq", + gst_value_collect_int64_range, + (char *) "pp", + gst_value_lcopy_int64_range +}; + +FUNC_VALUE_GET_TYPE (int64_range, "GstInt64Range"); + +static const GTypeValueTable _gst_double_range_value_table = { + gst_value_init_double_range, + NULL, + gst_value_copy_double_range, + NULL, + (char *) "dd", + gst_value_collect_double_range, + (char *) "pp", + gst_value_lcopy_double_range +}; + +FUNC_VALUE_GET_TYPE (double_range, "GstDoubleRange"); + +static const GTypeValueTable _gst_fraction_range_value_table = { + gst_value_init_fraction_range, + gst_value_free_fraction_range, + gst_value_copy_fraction_range, + NULL, + (char *) "iiii", + gst_value_collect_fraction_range, + (char *) "pppp", + gst_value_lcopy_fraction_range +}; + +FUNC_VALUE_GET_TYPE (fraction_range, "GstFractionRange"); + +static const GTypeValueTable _gst_value_list_value_table = { + gst_value_init_list_or_array, + gst_value_free_list_or_array, + gst_value_copy_list_or_array, + gst_value_list_or_array_peek_pointer, + (char *) "p", + gst_value_collect_list_or_array, + (char *) "p", + gst_value_lcopy_list_or_array +}; + +FUNC_VALUE_GET_TYPE (value_list, "GstValueList"); + +static const GTypeValueTable _gst_value_array_value_table = { + gst_value_init_list_or_array, + gst_value_free_list_or_array, + gst_value_copy_list_or_array, + gst_value_list_or_array_peek_pointer, + (char *) "p", + gst_value_collect_list_or_array, + (char *) "p", + gst_value_lcopy_list_or_array +}; + +FUNC_VALUE_GET_TYPE (value_array, "GstValueArray"); + +static const GTypeValueTable _gst_fraction_value_table = { + gst_value_init_fraction, + NULL, + gst_value_copy_fraction, + NULL, + (char *) "ii", + gst_value_collect_fraction, + (char *) "pp", + gst_value_lcopy_fraction +}; + +FUNC_VALUE_GET_TYPE (fraction, "GstFraction"); + + +GType +gst_date_get_type (void) +{ + static GType gst_date_type = 0; + + if (G_UNLIKELY (gst_date_type == 0)) { + /* FIXME 0.11: we require GLib 2.8 already + * Not using G_TYPE_DATE here on purpose, even if we could + * if GLIB_CHECK_VERSION(2,8,0) was true: we don't want the + * serialised strings to have different type strings depending + * on what version is used, so FIXME when we require GLib-2.8 */ + gst_date_type = g_boxed_type_register_static ("GstDate", + (GBoxedCopyFunc) gst_date_copy, (GBoxedFreeFunc) g_date_free); + } + + return gst_date_type; +} + +GType +gst_date_time_get_type (void) +{ + static GType gst_date_time_type = 0; + + if (G_UNLIKELY (gst_date_time_type == 0)) { + gst_date_time_type = g_boxed_type_register_static ("GstDateTime", + (GBoxedCopyFunc) gst_date_time_ref, + (GBoxedFreeFunc) gst_date_time_unref); + } + + return gst_date_time_type; +} + + +void +_priv_gst_value_initialize (void) +{ + gst_value_table = g_array_new (FALSE, FALSE, sizeof (GstValueTable)); + gst_value_hash = g_hash_table_new (NULL, NULL); + gst_value_union_funcs = g_array_new (FALSE, FALSE, + sizeof (GstValueUnionInfo)); + gst_value_intersect_funcs = g_array_new (FALSE, FALSE, + sizeof (GstValueIntersectInfo)); + gst_value_subtract_funcs = g_array_new (FALSE, FALSE, + sizeof (GstValueSubtractInfo)); + + { + static GstValueTable gst_value = { + 0, + gst_value_compare_int_range, + gst_value_serialize_int_range, + gst_value_deserialize_int_range, + }; + + gst_value.type = gst_int_range_get_type (); + gst_value_register (&gst_value); + } + + { + static GstValueTable gst_value = { + 0, + gst_value_compare_int64_range, + gst_value_serialize_int64_range, + gst_value_deserialize_int64_range, + }; + + gst_value.type = gst_int64_range_get_type (); + gst_value_register (&gst_value); + } + + { + static GstValueTable gst_value = { + 0, + gst_value_compare_double_range, + gst_value_serialize_double_range, + gst_value_deserialize_double_range, + }; + + gst_value.type = gst_double_range_get_type (); + gst_value_register (&gst_value); + } + + { + static GstValueTable gst_value = { + 0, + gst_value_compare_fraction_range, + gst_value_serialize_fraction_range, + gst_value_deserialize_fraction_range, + }; + + gst_value.type = gst_fraction_range_get_type (); + gst_value_register (&gst_value); + } + + { + static GstValueTable gst_value = { + 0, + gst_value_compare_list, + gst_value_serialize_list, + gst_value_deserialize_list, + }; + + gst_value.type = gst_value_list_get_type (); + gst_value_register (&gst_value); + } + + { + static GstValueTable gst_value = { + 0, + gst_value_compare_array, + gst_value_serialize_array, + gst_value_deserialize_array, + }; + + gst_value.type = gst_value_array_get_type (); + gst_value_register (&gst_value); + } + + { +#if 0 + static const GTypeValueTable value_table = { + gst_value_init_buffer, + NULL, + gst_value_copy_buffer, + NULL, + "i", + NULL, /*gst_value_collect_buffer, */ + "p", + NULL /*gst_value_lcopy_buffer */ + }; +#endif + static GstValueTable gst_value = { + 0, + gst_value_compare_buffer, + gst_value_serialize_buffer, + gst_value_deserialize_buffer, + }; + + gst_value.type = GST_TYPE_BUFFER; + gst_value_register (&gst_value); + } + { + static GstValueTable gst_value = { + 0, + gst_value_compare_fraction, + gst_value_serialize_fraction, + gst_value_deserialize_fraction, + }; + + gst_value.type = gst_fraction_get_type (); + gst_value_register (&gst_value); + } + { + static GstValueTable gst_value = { + 0, + NULL, + gst_value_serialize_caps, + gst_value_deserialize_caps, + }; + + gst_value.type = GST_TYPE_CAPS; + gst_value_register (&gst_value); + } + { + static GstValueTable gst_value = { + 0, + NULL, + gst_value_serialize_structure, + gst_value_deserialize_structure, + }; + + gst_value.type = GST_TYPE_STRUCTURE; + gst_value_register (&gst_value); + } + { + static GstValueTable gst_value = { + 0, + gst_value_compare_date, + gst_value_serialize_date, + gst_value_deserialize_date, + }; + + gst_value.type = gst_date_get_type (); + gst_value_register (&gst_value); + } + { + static GstValueTable gst_value = { + 0, + gst_value_compare_date_time, + gst_value_serialize_date_time, + gst_value_deserialize_date_time, + }; + + gst_value.type = gst_date_time_get_type (); + gst_value_register (&gst_value); + } + + REGISTER_SERIALIZATION (G_TYPE_DOUBLE, double); + REGISTER_SERIALIZATION (G_TYPE_FLOAT, float); + + REGISTER_SERIALIZATION (G_TYPE_STRING, string); + REGISTER_SERIALIZATION (G_TYPE_BOOLEAN, boolean); + REGISTER_SERIALIZATION (G_TYPE_ENUM, enum); + + REGISTER_SERIALIZATION (G_TYPE_FLAGS, flags); + + REGISTER_SERIALIZATION (G_TYPE_INT, int); + + REGISTER_SERIALIZATION (G_TYPE_INT64, int64); + REGISTER_SERIALIZATION (G_TYPE_LONG, long); + + REGISTER_SERIALIZATION (G_TYPE_UINT, uint); + REGISTER_SERIALIZATION (G_TYPE_UINT64, uint64); + REGISTER_SERIALIZATION (G_TYPE_ULONG, ulong); + + REGISTER_SERIALIZATION (G_TYPE_UCHAR, uchar); + + g_value_register_transform_func (GST_TYPE_INT_RANGE, G_TYPE_STRING, + gst_value_transform_int_range_string); + g_value_register_transform_func (GST_TYPE_INT64_RANGE, G_TYPE_STRING, + gst_value_transform_int64_range_string); + g_value_register_transform_func (GST_TYPE_DOUBLE_RANGE, G_TYPE_STRING, + gst_value_transform_double_range_string); + g_value_register_transform_func (GST_TYPE_FRACTION_RANGE, G_TYPE_STRING, + gst_value_transform_fraction_range_string); + g_value_register_transform_func (GST_TYPE_LIST, G_TYPE_STRING, + gst_value_transform_list_string); + g_value_register_transform_func (GST_TYPE_ARRAY, G_TYPE_STRING, + gst_value_transform_array_string); + g_value_register_transform_func (GST_TYPE_FRACTION, G_TYPE_STRING, + gst_value_transform_fraction_string); + g_value_register_transform_func (G_TYPE_STRING, GST_TYPE_FRACTION, + gst_value_transform_string_fraction); + g_value_register_transform_func (GST_TYPE_FRACTION, G_TYPE_DOUBLE, + gst_value_transform_fraction_double); + g_value_register_transform_func (GST_TYPE_FRACTION, G_TYPE_FLOAT, + gst_value_transform_fraction_float); + g_value_register_transform_func (G_TYPE_DOUBLE, GST_TYPE_FRACTION, + gst_value_transform_double_fraction); + g_value_register_transform_func (G_TYPE_FLOAT, GST_TYPE_FRACTION, + gst_value_transform_float_fraction); + g_value_register_transform_func (GST_TYPE_DATE, G_TYPE_STRING, + gst_value_transform_date_string); + g_value_register_transform_func (G_TYPE_STRING, GST_TYPE_DATE, + gst_value_transform_string_date); + g_value_register_transform_func (GST_TYPE_OBJECT, G_TYPE_STRING, + gst_value_transform_object_string); + + gst_value_register_intersect_func (G_TYPE_INT, GST_TYPE_INT_RANGE, + gst_value_intersect_int_int_range); + gst_value_register_intersect_func (GST_TYPE_INT_RANGE, GST_TYPE_INT_RANGE, + gst_value_intersect_int_range_int_range); + gst_value_register_intersect_func (G_TYPE_INT64, GST_TYPE_INT64_RANGE, + gst_value_intersect_int64_int64_range); + gst_value_register_intersect_func (GST_TYPE_INT64_RANGE, GST_TYPE_INT64_RANGE, + gst_value_intersect_int64_range_int64_range); + gst_value_register_intersect_func (G_TYPE_DOUBLE, GST_TYPE_DOUBLE_RANGE, + gst_value_intersect_double_double_range); + gst_value_register_intersect_func (GST_TYPE_DOUBLE_RANGE, + GST_TYPE_DOUBLE_RANGE, gst_value_intersect_double_range_double_range); + gst_value_register_intersect_func (GST_TYPE_ARRAY, + GST_TYPE_ARRAY, gst_value_intersect_array); + gst_value_register_intersect_func (GST_TYPE_FRACTION, GST_TYPE_FRACTION_RANGE, + gst_value_intersect_fraction_fraction_range); + gst_value_register_intersect_func (GST_TYPE_FRACTION_RANGE, + GST_TYPE_FRACTION_RANGE, + gst_value_intersect_fraction_range_fraction_range); + + gst_value_register_subtract_func (G_TYPE_INT, GST_TYPE_INT_RANGE, + gst_value_subtract_int_int_range); + gst_value_register_subtract_func (GST_TYPE_INT_RANGE, G_TYPE_INT, + gst_value_subtract_int_range_int); + gst_value_register_subtract_func (GST_TYPE_INT_RANGE, GST_TYPE_INT_RANGE, + gst_value_subtract_int_range_int_range); + gst_value_register_subtract_func (G_TYPE_INT64, GST_TYPE_INT64_RANGE, + gst_value_subtract_int64_int64_range); + gst_value_register_subtract_func (GST_TYPE_INT64_RANGE, G_TYPE_INT64, + gst_value_subtract_int64_range_int64); + gst_value_register_subtract_func (GST_TYPE_INT64_RANGE, GST_TYPE_INT64_RANGE, + gst_value_subtract_int64_range_int64_range); + gst_value_register_subtract_func (G_TYPE_DOUBLE, GST_TYPE_DOUBLE_RANGE, + gst_value_subtract_double_double_range); + gst_value_register_subtract_func (GST_TYPE_DOUBLE_RANGE, G_TYPE_DOUBLE, + gst_value_subtract_double_range_double); + gst_value_register_subtract_func (GST_TYPE_DOUBLE_RANGE, + GST_TYPE_DOUBLE_RANGE, gst_value_subtract_double_range_double_range); + + gst_value_register_subtract_func (GST_TYPE_FRACTION, GST_TYPE_FRACTION_RANGE, + gst_value_subtract_fraction_fraction_range); + gst_value_register_subtract_func (GST_TYPE_FRACTION_RANGE, GST_TYPE_FRACTION, + gst_value_subtract_fraction_range_fraction); + gst_value_register_subtract_func (GST_TYPE_FRACTION_RANGE, + GST_TYPE_FRACTION_RANGE, + gst_value_subtract_fraction_range_fraction_range); + + /* see bug #317246, #64994, #65041 */ + { + volatile GType date_type = G_TYPE_DATE; + + g_type_name (date_type); + } + + gst_value_register_union_func (G_TYPE_INT, GST_TYPE_INT_RANGE, + gst_value_union_int_int_range); + gst_value_register_union_func (GST_TYPE_INT_RANGE, GST_TYPE_INT_RANGE, + gst_value_union_int_range_int_range); + +#if 0 + /* Implement these if needed */ + gst_value_register_union_func (GST_TYPE_FRACTION, GST_TYPE_FRACTION_RANGE, + gst_value_union_fraction_fraction_range); + gst_value_register_union_func (GST_TYPE_FRACTION_RANGE, + GST_TYPE_FRACTION_RANGE, gst_value_union_fraction_range_fraction_range); +#endif +} diff --git a/gst/gstvalue.h b/gst/gstvalue.h new file mode 100644 index 0000000..3bf9222 --- /dev/null +++ b/gst/gstvalue.h @@ -0,0 +1,573 @@ +/* GStreamer + * Copyright (C) <2003> David A. Schleef <ds@schleef.org> + * + * 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_VALUE_H__ +#define __GST_VALUE_H__ + +#include <gst/gstconfig.h> +#include <gst/gstcaps.h> +#include <gst/gststructure.h> + +G_BEGIN_DECLS + +/** + * GST_MAKE_FOURCC: + * @a: the first character + * @b: the second character + * @c: the third character + * @d: the fourth character + * + * Transform four characters into a #guint32 fourcc value with host + * endianness. + * <informalexample> + * <programlisting> + * guint32 fourcc = GST_MAKE_FOURCC ('M', 'J', 'P', 'G'); + * </programlisting> + * </informalexample> + */ +#define GST_MAKE_FOURCC(a,b,c,d) ((guint32)((a)|(b)<<8|(c)<<16|(d)<<24)) + +/** + * GST_STR_FOURCC: + * @f: a string with at least four characters + * + * Transform an input string into a #guint32 fourcc value with host + * endianness. + * Caller is responsible for ensuring the input string consists of at least + * four characters. + * <informalexample> + * <programlisting> + * guint32 fourcc = GST_STR_FOURCC ("MJPG"); + * </programlisting> + * </informalexample> + */ +#define GST_STR_FOURCC(f) ((guint32)(((f)[0])|((f)[1]<<8)|((f)[2]<<16)|((f)[3]<<24))) + +/** + * GST_FOURCC_FORMAT: + * + * Can be used together with #GST_FOURCC_ARGS to properly output a + * #guint32 fourcc value in a printf()-style text message. + * <informalexample> + * <programlisting> + * printf ("fourcc: %" GST_FOURCC_FORMAT "\n", GST_FOURCC_ARGS (fcc)); + * </programlisting> + * </informalexample> + */ +#define GST_FOURCC_FORMAT "c%c%c%c" + +/** + * GST_FOURCC_ARGS: + * @fourcc: a #guint32 fourcc value to output + * + * Can be used together with #GST_FOURCC_FORMAT to properly output a + * #guint32 fourcc value in a printf()-style text message. + */ +#define GST_FOURCC_ARGS(fourcc) \ + ((gchar) ((fourcc) &0xff)), \ + ((gchar) (((fourcc)>>8 )&0xff)), \ + ((gchar) (((fourcc)>>16)&0xff)), \ + ((gchar) (((fourcc)>>24)&0xff)) + +/** + * GST_VALUE_HOLDS_INT_RANGE: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_INT_RANGE value. + */ +#define GST_VALUE_HOLDS_INT_RANGE(x) (G_VALUE_HOLDS((x), gst_int_range_get_type ())) + +/** + * GST_VALUE_HOLDS_INT64_RANGE: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_INT64_RANGE value. + * + * Since: 0.10.31 + */ +#define GST_VALUE_HOLDS_INT64_RANGE(x) (G_VALUE_HOLDS((x), gst_int64_range_get_type ())) + +/** + * GST_VALUE_HOLDS_DOUBLE_RANGE: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_DOUBLE_RANGE value. + */ +#define GST_VALUE_HOLDS_DOUBLE_RANGE(x) (G_VALUE_HOLDS((x), gst_double_range_get_type ())) + +/** + * GST_VALUE_HOLDS_FRACTION_RANGE: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_FRACTION_RANGE value. + */ +#define GST_VALUE_HOLDS_FRACTION_RANGE(x) (G_VALUE_HOLDS((x), gst_fraction_range_get_type ())) + +/** + * GST_VALUE_HOLDS_LIST: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_LIST value. + */ +#define GST_VALUE_HOLDS_LIST(x) (G_VALUE_HOLDS((x), gst_value_list_get_type ())) + +/** + * GST_VALUE_HOLDS_ARRAY: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_ARRAY value. + */ +#define GST_VALUE_HOLDS_ARRAY(x) (G_VALUE_HOLDS((x), gst_value_array_get_type ())) + +/** + * GST_VALUE_HOLDS_CAPS: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_CAPS value. + */ +#define GST_VALUE_HOLDS_CAPS(x) (G_VALUE_HOLDS((x), GST_TYPE_CAPS)) + +/** + * GST_VALUE_HOLDS_STRUCTURE: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_STRUCTURE value. + * + * Since: 0.10.15 + */ +#define GST_VALUE_HOLDS_STRUCTURE(x) (G_VALUE_HOLDS((x), GST_TYPE_STRUCTURE)) + +/** + * GST_VALUE_HOLDS_BUFFER: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_BUFFER value. + */ +#define GST_VALUE_HOLDS_BUFFER(x) (G_VALUE_HOLDS((x), GST_TYPE_BUFFER)) + +/** + * GST_VALUE_HOLDS_FRACTION: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_FRACTION value. + */ +#define GST_VALUE_HOLDS_FRACTION(x) (G_VALUE_HOLDS((x), gst_fraction_get_type ())) + +/** + * GST_VALUE_HOLDS_DATE: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_DATE value. + */ +#define GST_VALUE_HOLDS_DATE(x) (G_VALUE_HOLDS((x), gst_date_get_type ())) + +/** + * GST_VALUE_HOLDS_DATE_TIME: + * @x: the #GValue to check + * + * Checks if the given #GValue contains a #GST_TYPE_DATE_TIME value. + * + * Since: 0.10.31 + */ +#define GST_VALUE_HOLDS_DATE_TIME(x) (G_VALUE_HOLDS((x), gst_date_time_get_type ())) + +/** + * GST_TYPE_INT_RANGE: + * + * a #GValue type that represents an integer range + * + * Returns: the #GType of GstIntRange + */ +#define GST_TYPE_INT_RANGE gst_int_range_get_type () + +/** + * GST_TYPE_INT64_RANGE: + * + * a #GValue type that represents an #gint64 range + * + * Returns: the #GType of GstInt64Range + * + * Since: 0.10.31 + */ +#define GST_TYPE_INT64_RANGE gst_int64_range_get_type () + +/** + * GST_TYPE_DOUBLE_RANGE: + * + * a #GValue type that represents a floating point range with double precission + * + * Returns: the #GType of GstIntRange + */ +#define GST_TYPE_DOUBLE_RANGE gst_double_range_get_type () + +/** + * GST_TYPE_FRACTION_RANGE: + * + * a #GValue type that represents a GstFraction range + * + * Returns: the #GType of GstFractionRange + */ +#define GST_TYPE_FRACTION_RANGE gst_fraction_range_get_type () + +/** + * GST_TYPE_LIST: + * + * a #GValue type that represents an unordered list of #GValue values. This + * is used for example to express a list of possible values for a field in + * a caps structure, like a list of possible sample rates, of which only one + * will be chosen in the end. This means that all values in the list are + * meaningful on their own. + * + * Returns: the #GType of GstValueList (which is not explicitly typed) + */ +#define GST_TYPE_LIST gst_value_list_get_type () + +/** + * GST_TYPE_ARRAY: + * + * a #GValue type that represents an ordered list of #GValue values. This is + * used to express a set of values that is meaningful only in their specific + * combination and order of values. Each value on its own is not particularly + * meaningful, only the ordered array in its entirety is meaningful. This is + * used for example to express channel layouts for multichannel audio where + * each channel needs to be mapped to a position in the room. + * + * Returns: the #GType of GstArrayList (which is not explicitly typed) + */ +#define GST_TYPE_ARRAY gst_value_array_get_type () + +/** + * GST_TYPE_FRACTION: + * + * a #GValue type that represents a fraction of an integer numerator over + * an integer denominator + * + * Returns: the #GType of GstFraction (which is not explicitly typed) + */ + +#define GST_TYPE_FRACTION gst_fraction_get_type () + +/** + * GST_TYPE_DATE: + * + * a boxed #GValue type for #GDate that represents a date. + * + * Returns: the #GType of GstDate + */ + +#define GST_TYPE_DATE gst_date_get_type () + +/** + * GST_TYPE_DATE_TIME: + * + * a boxed #GValue type for #GstDateTime that represents a date and time. + * + * Returns: the #GType of GstDateTime + * Since: 0.10.31 + */ + +#define GST_TYPE_DATE_TIME gst_date_time_get_type () + +/** + * GST_VALUE_LESS_THAN: + * + * Indicates that the first value provided to a comparison function + * (gst_value_compare()) is lesser than the second one. + */ +#define GST_VALUE_LESS_THAN (-1) + +/** + * GST_VALUE_EQUAL: + * + * Indicates that the first value provided to a comparison function + * (gst_value_compare()) is equal to the second one. + */ +#define GST_VALUE_EQUAL 0 + +/** + * GST_VALUE_GREATER_THAN: + * + * Indicates that the first value provided to a comparison function + * (gst_value_compare()) is greater than the second one. + */ +#define GST_VALUE_GREATER_THAN 1 + +/** + * GST_VALUE_UNORDERED: + * + * Indicates that the comparison function (gst_value_compare()) can not + * determine a order for the two provided values. + */ +#define GST_VALUE_UNORDERED 2 + +/** + * GstValueCompareFunc: + * @value1: first value for comparison + * @value2: second value for comparison + * + * Used together with gst_value_compare() to compare #GValue items. + * + * Returns: one of GST_VALUE_LESS_THAN, GST_VALUE_EQUAL, GST_VALUE_GREATER_THAN + * or GST_VALUE_UNORDERED + */ +typedef gint (* GstValueCompareFunc) (const GValue *value1, + const GValue *value2); + +/** + * GstValueSerializeFunc: + * @value1: a #GValue + * + * Used by gst_value_serialize() to obtain a non-binary form of the #GValue. + * + * Free-function: g_free + * + * Returns: (transfer full): the string representation of the value + */ +typedef gchar * (* GstValueSerializeFunc) (const GValue *value1); + +/** + * GstValueDeserializeFunc: + * @dest: a #GValue + * @s: a string + * + * Used by gst_value_deserialize() to parse a non-binary form into the #GValue. + * + * Returns: %TRUE for success + */ +typedef gboolean (* GstValueDeserializeFunc) (GValue *dest, + const gchar *s); + +/** + * GstValueUnionFunc: + * @dest: a #GValue for the result + * @value1: a #GValue operand + * @value2: a #GValue operand + * + * Used by gst_value_union() to perform unification for a specific #GValue + * type. Register a new implementation with gst_value_register_union_func(). + * + * Returns: %TRUE if a union was successful + */ +typedef gboolean (* GstValueUnionFunc) (GValue *dest, + const GValue *value1, + const GValue *value2); + +/** + * GstValueIntersectFunc: + * @dest: (out caller-allocates): a #GValue for the result + * @value1: a #GValue operand + * @value2: a #GValue operand + * + * Used by gst_value_intersect() to perform intersection for a specific #GValue + * type. If the intersection is non-empty, the result is + * placed in @dest and TRUE is returned. If the intersection is + * empty, @dest is unmodified and FALSE is returned. + * Register a new implementation with gst_value_register_intersect_func(). + * + * Returns: %TRUE if the values can intersect + */ +typedef gboolean (* GstValueIntersectFunc) (GValue *dest, + const GValue *value1, + const GValue *value2); + +/** + * GstValueSubtractFunc: + * @dest: (out caller-allocates): a #GValue for the result + * @minuend: a #GValue operand + * @subtrahend: a #GValue operand + * + * Used by gst_value_subtract() to perform subtraction for a specific #GValue + * type. Register a new implementation with gst_value_register_subtract_func(). + * + * Returns: %TRUE if the subtraction is not empty + */ +typedef gboolean (* GstValueSubtractFunc) (GValue *dest, + const GValue *minuend, + const GValue *subtrahend); + +typedef struct _GstValueTable GstValueTable; +/** + * GstValueTable: + * @type: a #GType + * @compare: a #GstValueCompareFunc + * @serialize: a #GstValueSerializeFunc + * @deserialize: a #GstValueDeserializeFunc + * + * VTable for the #GValue @type. + */ +struct _GstValueTable { + GType type; + GstValueCompareFunc compare; + GstValueSerializeFunc serialize; + GstValueDeserializeFunc deserialize; + + /*< private >*/ + void *_gst_reserved [GST_PADDING]; +}; + +GType gst_int_range_get_type (void); +GType gst_int64_range_get_type (void); +GType gst_double_range_get_type (void); +GType gst_fraction_range_get_type (void); +GType gst_fraction_get_type (void); +GType gst_value_list_get_type (void); +GType gst_value_array_get_type (void); + +GType gst_date_get_type (void); +GType gst_date_time_get_type (void); + +void gst_value_register (const GstValueTable *table); +void gst_value_init_and_copy (GValue *dest, + const GValue *src); + +gchar * gst_value_serialize (const GValue *value); +gboolean gst_value_deserialize (GValue *dest, + const gchar *src); + +/* list */ +void gst_value_list_append_value (GValue *value, + const GValue *append_value); +void gst_value_list_prepend_value (GValue *value, + const GValue *prepend_value); +void gst_value_list_concat (GValue *dest, + const GValue *value1, + const GValue *value2); +void gst_value_list_merge (GValue *dest, + const GValue *value1, + const GValue *value2); +guint gst_value_list_get_size (const GValue *value); +const GValue * gst_value_list_get_value (const GValue *value, + guint index); + +/* array */ +void gst_value_array_append_value (GValue *value, + const GValue *append_value); +void gst_value_array_prepend_value (GValue *value, + const GValue *prepend_value); +guint gst_value_array_get_size (const GValue *value); +const GValue * gst_value_array_get_value (const GValue *value, + guint index); + +/* int range */ +void gst_value_set_int_range (GValue *value, + gint start, + gint end); +gint gst_value_get_int_range_min (const GValue *value); +gint gst_value_get_int_range_max (const GValue *value); + +/* int64 range */ +void gst_value_set_int64_range (GValue *value, + gint64 start, + gint64 end); +gint64 gst_value_get_int64_range_min (const GValue *value); +gint64 gst_value_get_int64_range_max (const GValue *value); + +/* double range */ +void gst_value_set_double_range (GValue *value, + gdouble start, + gdouble end); +gdouble gst_value_get_double_range_min (const GValue *value); +gdouble gst_value_get_double_range_max (const GValue *value); + +/* caps */ +const GstCaps * gst_value_get_caps (const GValue *value); +void gst_value_set_caps (GValue *value, + const GstCaps *caps); + +/* structure */ +const GstStructure * + gst_value_get_structure (const GValue *value); +void gst_value_set_structure (GValue *value, + const GstStructure *structure); + +/* fraction */ +void gst_value_set_fraction (GValue *value, + gint numerator, + gint denominator); +gint gst_value_get_fraction_numerator (const GValue *value); +gint gst_value_get_fraction_denominator(const GValue *value); +gboolean gst_value_fraction_multiply (GValue *product, + const GValue *factor1, + const GValue *factor2); +gboolean gst_value_fraction_subtract (GValue * dest, + const GValue * minuend, + const GValue * subtrahend); + +/* fraction range */ +void gst_value_set_fraction_range (GValue *value, + const GValue *start, + const GValue *end); +void gst_value_set_fraction_range_full (GValue *value, + gint numerator_start, + gint denominator_start, + gint numerator_end, + gint denominator_end); +const GValue *gst_value_get_fraction_range_min (const GValue *value); +const GValue *gst_value_get_fraction_range_max (const GValue *value); + +/* date */ +const GDate * gst_value_get_date (const GValue *value); +void gst_value_set_date (GValue *value, + const GDate *date); + +/* compare */ +gint gst_value_compare (const GValue *value1, + const GValue *value2); +gboolean gst_value_can_compare (const GValue *value1, + const GValue *value2); +/* union */ +gboolean gst_value_union (GValue *dest, + const GValue *value1, + const GValue *value2); +gboolean gst_value_can_union (const GValue *value1, + const GValue *value2); +void gst_value_register_union_func (GType type1, + GType type2, + GstValueUnionFunc func); + +/* intersection */ +gboolean gst_value_intersect (GValue *dest, + const GValue *value1, + const GValue *value2); +gboolean gst_value_can_intersect (const GValue *value1, + const GValue *value2); +void gst_value_register_intersect_func (GType type1, + GType type2, + GstValueIntersectFunc func); + +/* subtraction */ +gboolean gst_value_subtract (GValue *dest, + const GValue *minuend, + const GValue *subtrahend); +gboolean gst_value_can_subtract (const GValue *minuend, + const GValue *subtrahend); +void gst_value_register_subtract_func (GType minuend_type, + GType subtrahend_type, + GstValueSubtractFunc func); + +/* fixation */ +gboolean gst_value_is_fixed (const GValue *value); +gboolean gst_value_fixate (GValue *dest, + const GValue *src); + +G_END_DECLS + +#endif + + diff --git a/gst/gstversion.h.in b/gst/gstversion.h.in new file mode 100644 index 0000000..86a6609 --- /dev/null +++ b/gst/gstversion.h.in @@ -0,0 +1,90 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * + * gstversion.h: Version information for GStreamer + * + * 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:gstversion + * @short_description: GStreamer version macros. + * + * Use the GST_VERSION_* macros e.g. when defining own plugins. The GStreamer + * runtime checks if these plugin and core version match and refuses to use a + * plugin compiled against a different version of GStreamer. + * You can also use the macros to keep the GStreamer version information in + * your application. + * + * Use the gst_version() function if you want to know which version of + * GStreamer you are currently linked against. + * + * The version macros get defined by including "gst/gst.h". + */ + +#ifndef __GST_VERSION_H__ +#define __GST_VERSION_H__ + +G_BEGIN_DECLS + +/** + * GST_VERSION_MAJOR: + * + * The major version of GStreamer at compile time: + */ +#define GST_VERSION_MAJOR (@PACKAGE_VERSION_MAJOR@) +/** + * GST_VERSION_MINOR: + * + * The minor version of GStreamer at compile time: + */ +#define GST_VERSION_MINOR (@PACKAGE_VERSION_MINOR@) +/** + * GST_VERSION_MICRO: + * + * The micro version of GStreamer at compile time: + */ +#define GST_VERSION_MICRO (@PACKAGE_VERSION_MICRO@) +/** + * GST_VERSION_NANO: + * + * The nano version of GStreamer at compile time: + * Actual releases have 0, GIT versions have 1, prerelease versions have 2-... + */ +#define GST_VERSION_NANO (@PACKAGE_VERSION_NANO@) + +/** + * GST_CHECK_VERSION: + * @major: a number indicating the major version + * @minor: a number indicating the minor version + * @micro: a number indicating the micro version + * + * Check whether a GStreamer version equal to or greater than + * major.minor.micro is present. + * + * Since: 0.10.18 + */ +#define GST_CHECK_VERSION(major,minor,micro) \ + (GST_VERSION_MAJOR > (major) || \ + (GST_VERSION_MAJOR == (major) && GST_VERSION_MINOR > (minor)) || \ + (GST_VERSION_MAJOR == (major) && GST_VERSION_MINOR == (minor) && \ + GST_VERSION_MICRO >= (micro)) || \ + (GST_VERSION_MAJOR == (major) && GST_VERSION_MINOR == (minor) && \ + GST_VERSION_MICRO + 1 == (micro) && GST_VERSION_NANO > 0)) + +G_END_DECLS + +#endif /* __GST_VERSION_H__ */ diff --git a/gst/math-compat.h b/gst/math-compat.h new file mode 100644 index 0000000..d9b368c --- /dev/null +++ b/gst/math-compat.h @@ -0,0 +1,84 @@ +/* GStreamer + * Copyright (C) 2010 Tim-Philipp Müller <tim centricular 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. + */ + +#ifndef __GST_MATH_COMPAT_H__ +#define __GST_MATH_COMPAT_H__ + +/* This header is not included automatically via gst/gst.h, you need to + * include it explicitly if you need it. */ + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES /* so MSVC defines M_PI etc. */ +#endif +#include <math.h> + +#include <glib.h> + +G_BEGIN_DECLS + +/* http://en.wikipedia.org/wiki/Math.h */ + +#define __GST_MATH_COMPAT_NEED_RINT +#define __GST_MATH_COMPAT_NEED_RINTF + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#undef __GST_MATH_COMPAT_NEED_RINT +#undef __GST_MATH_COMPAT_NEED_RINTF +#endif + +#if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L +#undef __GST_MATH_COMPAT_NEED_RINT +#undef __GST_MATH_COMPAT_NEED_RINTF +#endif + +#ifndef M_PI +#define M_PI G_PI +#endif + +#ifndef M_PI_2 +#define M_PI_2 G_PI_2 +#endif + +#ifndef M_PI_4 +#define M_PI_4 G_PI_4 +#endif + +static inline double +__gst_math_compat_rint (double x) +{ + return floor (x + 0.5); +} + +static inline float +__gst_math_compat_rintf (float x) +{ + return floorf (x + 0.5); +} + +#if defined (__GST_MATH_COMPAT_NEED_RINT) && !defined (rint) +#define rint(x) __gst_math_compat_rint(x) +#endif + +#if defined (__GST_MATH_COMPAT_NEED_RINTF) && !defined (rintf) +#define rintf(x) __gst_math_compat_rintf(x) +#endif + +G_END_DECLS + +#endif /* __GST_MATH_COMPAT_H__ */ diff --git a/gst/parse/Makefile.am b/gst/parse/Makefile.am new file mode 100644 index 0000000..b837ec2 --- /dev/null +++ b/gst/parse/Makefile.am @@ -0,0 +1,55 @@ +# libgstparse.la is an optionally built helper library linked into core +noinst_LTLIBRARIES = libgstparse.la + +CLEANFILES = grammar.tab.h grammar.output +EXTRA_DIST = \ + grammar.y \ + parse.l + +# uncomment these lines to dist the generated sources +#BUILT_SOURCES = grammar.tab.h grammar.tab.c lex._gst_parse_yy.c +#libgstparse_la_SOURCES = lex._gst_parse_yy.c grammar.tab.c + +# uncomment these lines to _NOT_ dist the generated sources +nodist_libgstparse_la_SOURCES = lex._gst_parse_yy.c grammar.tab.c +CLEANFILES += grammar.tab.c lex._gst_parse_yy.c + +# can't use GST_ALL_CFLAGS here because that'd pull in -Werror +libgstparse_la_CFLAGS = -I$(top_srcdir) -I$(top_builddir) \ + $(GLIB_CFLAGS) $(GST_OPTION_CFLAGS) +libgstparse_la_LIBADD = $(GST_ALL_LIBS) + +noinst_HEADERS = grammar.tab.h types.h + +Android.mk: Makefile.am + androgenizer -:PROJECT gstreamer -:STATIC libgstparse -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstparse_la_SOURCES) $(nodist_libgstparse_la_SOURCES) \ + -:CFLAGS $(DEFS) $(libgstparse_la_CFLAGS) \ + -:LDFLAGS $(libgstparse_la_LIBADD) \ + > $@ + +grammar.tab.c grammar.tab.h: grammar.y + $(AM_V_GEN)$(BISON_PATH) -d -v -p_gst_parse_yy $(srcdir)/grammar.y -o grammar.tab.c && \ + mv grammar.tab.c grammar.tab_tmp.c && \ + echo '#ifdef HAVE_CONFIG_H' > grammar.tab_tmp2.c && \ + echo '#include <config.h>' >> grammar.tab_tmp2.c && \ + echo '#endif' >> grammar.tab_tmp2.c && \ + cat grammar.tab_tmp.c >> grammar.tab_tmp2.c && \ + rm grammar.tab_tmp.c && \ + mv grammar.tab_tmp2.c grammar.tab.c + +lex._gst_parse_yy.c: parse.l grammar.tab.h + $(AM_V_GEN)$(FLEX_PATH) -P_gst_parse_yy $^ && \ + mv lex._gst_parse_yy.c lex._gst_parse_yy_tmp.c && \ + echo '#ifdef HAVE_CONFIG_H' > lex._gst_parse_yy_tmp2.c && \ + echo '#include <config.h>' >> lex._gst_parse_yy_tmp2.c && \ + echo '#endif' >> lex._gst_parse_yy_tmp2.c && \ + echo 'static inline int _gst_parse_yyget_column (void * yyscanner);' >> lex._gst_parse_yy_tmp2.c && \ + echo 'static inline void _gst_parse_yyset_column (int column_no , void * yyscanner);' >> lex._gst_parse_yy_tmp2.c && \ + cat lex._gst_parse_yy_tmp.c >> lex._gst_parse_yy_tmp2.c && \ + rm lex._gst_parse_yy_tmp.c && \ + mv lex._gst_parse_yy_tmp2.c lex._gst_parse_yy.c + +.NOTPARALLEL: + diff --git a/gst/parse/Makefile.in b/gst/parse/Makefile.in new file mode 100644 index 0000000..4e34b8b --- /dev/null +++ b/gst/parse/Makefile.in @@ -0,0 +1,745 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = gst/parse +DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/common/m4/as-ac-expand.m4 \ + $(top_srcdir)/common/m4/as-auto-alt.m4 \ + $(top_srcdir)/common/m4/as-compiler-flag.m4 \ + $(top_srcdir)/common/m4/as-docbook.m4 \ + $(top_srcdir)/common/m4/as-libtool.m4 \ + $(top_srcdir)/common/m4/as-python.m4 \ + $(top_srcdir)/common/m4/as-scrub-include.m4 \ + $(top_srcdir)/common/m4/as-version.m4 \ + $(top_srcdir)/common/m4/ax_create_stdint_h.m4 \ + $(top_srcdir)/common/m4/gst-arch.m4 \ + $(top_srcdir)/common/m4/gst-args.m4 \ + $(top_srcdir)/common/m4/gst-check.m4 \ + $(top_srcdir)/common/m4/gst-doc.m4 \ + $(top_srcdir)/common/m4/gst-error.m4 \ + $(top_srcdir)/common/m4/gst-feature.m4 \ + $(top_srcdir)/common/m4/gst-function.m4 \ + $(top_srcdir)/common/m4/gst-gettext.m4 \ + $(top_srcdir)/common/m4/gst-glib2.m4 \ + $(top_srcdir)/common/m4/gst-package-release-datetime.m4 \ + $(top_srcdir)/common/m4/gst-parser.m4 \ + $(top_srcdir)/common/m4/gst-platform.m4 \ + $(top_srcdir)/common/m4/gst-plugin-docs.m4 \ + $(top_srcdir)/common/m4/gst-plugindir.m4 \ + $(top_srcdir)/common/m4/gst.m4 \ + $(top_srcdir)/common/m4/gtk-doc.m4 \ + $(top_srcdir)/common/m4/introspection.m4 \ + $(top_srcdir)/common/m4/pkg.m4 \ + $(top_srcdir)/m4/check-checks.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +am__DEPENDENCIES_1 = +libgstparse_la_DEPENDENCIES = $(am__DEPENDENCIES_1) +nodist_libgstparse_la_OBJECTS = libgstparse_la-lex._gst_parse_yy.lo \ + libgstparse_la-grammar.tab.lo +libgstparse_la_OBJECTS = $(nodist_libgstparse_la_OBJECTS) +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +libgstparse_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(libgstparse_la_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o \ + $@ +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(nodist_libgstparse_la_SOURCES) +DIST_SOURCES = +HEADERS = $(noinst_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BISON_PATH = @BISON_PATH@ +CAT_ENTRY_END = @CAT_ENTRY_END@ +CAT_ENTRY_START = @CAT_ENTRY_START@ +CC = @CC@ +CCAS = @CCAS@ +CCASDEPMODE = @CCASDEPMODE@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CHECK_MAJOR_VERSION = @CHECK_MAJOR_VERSION@ +CHECK_MICRO_VERSION = @CHECK_MICRO_VERSION@ +CHECK_MINOR_VERSION = @CHECK_MINOR_VERSION@ +CHECK_VERSION = @CHECK_VERSION@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIR = @DATADIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DEPRECATED_CFLAGS = @DEPRECATED_CFLAGS@ +DLLTOOL = @DLLTOOL@ +DOCBOOK_ROOT = @DOCBOOK_ROOT@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLE_SUBUNIT = @ENABLE_SUBUNIT@ +ERROR_CFLAGS = @ERROR_CFLAGS@ +EXEEXT = @EXEEXT@ +FFLAGS = @FFLAGS@ +FGREP = @FGREP@ +FLEX_PATH = @FLEX_PATH@ +GCOV = @GCOV@ +GCOV_CFLAGS = @GCOV_CFLAGS@ +GCOV_LIBS = @GCOV_LIBS@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_ONLY_CFLAGS = @GLIB_ONLY_CFLAGS@ +GLIB_ONLY_LIBS = @GLIB_ONLY_LIBS@ +GLIB_PREFIX = @GLIB_PREFIX@ +GLIB_REQ = @GLIB_REQ@ +GMP_LIBS = @GMP_LIBS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GREP = @GREP@ +GSL_LIBS = @GSL_LIBS@ +GST_AGE = @GST_AGE@ +GST_ALL_CFLAGS = @GST_ALL_CFLAGS@ +GST_ALL_CXXFLAGS = @GST_ALL_CXXFLAGS@ +GST_ALL_LDFLAGS = @GST_ALL_LDFLAGS@ +GST_ALL_LIBS = @GST_ALL_LIBS@ +GST_CURRENT = @GST_CURRENT@ +GST_DISABLE_ALLOC_TRACE_DEFINE = @GST_DISABLE_ALLOC_TRACE_DEFINE@ +GST_DISABLE_GST_DEBUG_DEFINE = @GST_DISABLE_GST_DEBUG_DEFINE@ +GST_DISABLE_NET_DEFINE = @GST_DISABLE_NET_DEFINE@ +GST_DISABLE_OPTION_PARSING_DEFINE = @GST_DISABLE_OPTION_PARSING_DEFINE@ +GST_DISABLE_PARSE_DEFINE = @GST_DISABLE_PARSE_DEFINE@ +GST_DISABLE_PLUGIN_DEFINE = @GST_DISABLE_PLUGIN_DEFINE@ +GST_DISABLE_REGISTRY_DEFINE = @GST_DISABLE_REGISTRY_DEFINE@ +GST_DISABLE_TRACE_DEFINE = @GST_DISABLE_TRACE_DEFINE@ +GST_HAVE_MONOTONIC_CLOCK_DEFINE = @GST_HAVE_MONOTONIC_CLOCK_DEFINE@ +GST_HAVE_POSIX_TIMERS_DEFINE = @GST_HAVE_POSIX_TIMERS_DEFINE@ +GST_HAVE_UNALIGNED_ACCESS_DEFINE = @GST_HAVE_UNALIGNED_ACCESS_DEFINE@ +GST_LEVEL_DEFAULT = @GST_LEVEL_DEFAULT@ +GST_LIBVERSION = @GST_LIBVERSION@ +GST_LIB_LDFLAGS = @GST_LIB_LDFLAGS@ +GST_LICENSE = @GST_LICENSE@ +GST_LT_LDFLAGS = @GST_LT_LDFLAGS@ +GST_MAJORMINOR = @GST_MAJORMINOR@ +GST_OBJ_CFLAGS = @GST_OBJ_CFLAGS@ +GST_OBJ_CXXFLAGS = @GST_OBJ_CXXFLAGS@ +GST_OBJ_LIBS = @GST_OBJ_LIBS@ +GST_OPTION_CFLAGS = @GST_OPTION_CFLAGS@ +GST_OPTION_CXXFLAGS = @GST_OPTION_CXXFLAGS@ +GST_PACKAGE_NAME = @GST_PACKAGE_NAME@ +GST_PACKAGE_ORIGIN = @GST_PACKAGE_ORIGIN@ +GST_PKG_CONFIG_PATH = @GST_PKG_CONFIG_PATH@ +GST_PKG_DEPS = @GST_PKG_DEPS@ +GST_PLUGIN_LDFLAGS = @GST_PLUGIN_LDFLAGS@ +GST_PLUGIN_SCANNER_INSTALLED = @GST_PLUGIN_SCANNER_INSTALLED@ +GST_PRINTF_EXTENSION_POINTER_FORMAT_DEFINE = @GST_PRINTF_EXTENSION_POINTER_FORMAT_DEFINE@ +GST_PRINTF_EXTENSION_SEGMENT_FORMAT_DEFINE = @GST_PRINTF_EXTENSION_SEGMENT_FORMAT_DEFINE@ +GST_REGISTRY_DOC_TYPES = @GST_REGISTRY_DOC_TYPES@ +GST_REVISION = @GST_REVISION@ +GST_USING_PRINTF_EXTENSION_DEFINE = @GST_USING_PRINTF_EXTENSION_DEFINE@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +HAVE_DOCBOOK2HTML = @HAVE_DOCBOOK2HTML@ +HAVE_DOCBOOK2PS = @HAVE_DOCBOOK2PS@ +HAVE_DVIPS = @HAVE_DVIPS@ +HAVE_EPSTOPDF = @HAVE_EPSTOPDF@ +HAVE_FIG2DEV = @HAVE_FIG2DEV@ +HAVE_GMP = @HAVE_GMP@ +HAVE_GSL = @HAVE_GSL@ +HAVE_JADETEX = @HAVE_JADETEX@ +HAVE_PNGTOPNM = @HAVE_PNGTOPNM@ +HAVE_PNMTOPS = @HAVE_PNMTOPS@ +HAVE_PS2PDF = @HAVE_PS2PDF@ +HAVE_XMLLINT = @HAVE_XMLLINT@ +HOST_CPU = @HOST_CPU@ +HTML_DIR = @HTML_DIR@ +INET_ATON_LIBS = @INET_ATON_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTROSPECTION_CFLAGS = @INTROSPECTION_CFLAGS@ +INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ +INTROSPECTION_GENERATE = @INTROSPECTION_GENERATE@ +INTROSPECTION_GIRDIR = @INTROSPECTION_GIRDIR@ +INTROSPECTION_LIBS = @INTROSPECTION_LIBS@ +INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@ +INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ +INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBDIR = @LIBDIR@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOCALEDIR = @LOCALEDIR@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@ +PACKAGE_VERSION_MICRO = @PACKAGE_VERSION_MICRO@ +PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@ +PACKAGE_VERSION_NANO = @PACKAGE_VERSION_NANO@ +PACKAGE_VERSION_RELEASE = @PACKAGE_VERSION_RELEASE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL_PATH = @PERL_PATH@ +PKG_CONFIG = @PKG_CONFIG@ +PLUGINDIR = @PLUGINDIR@ +POSUB = @POSUB@ +PROFILE_CFLAGS = @PROFILE_CFLAGS@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +USE_NLS = @USE_NLS@ +VALGRIND_CFLAGS = @VALGRIND_CFLAGS@ +VALGRIND_LIBS = @VALGRIND_LIBS@ +VALGRIND_PATH = @VALGRIND_PATH@ +VERSION = @VERSION@ +WARNING_CFLAGS = @WARNING_CFLAGS@ +WIN32_LIBS = @WIN32_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +XML_CATALOG = @XML_CATALOG@ +XSLTPROC = @XSLTPROC@ +XSLTPROC_FLAGS = @XSLTPROC_FLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +lt_ECHO = @lt_ECHO@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +plugindir = @plugindir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ + +# libgstparse.la is an optionally built helper library linked into core +noinst_LTLIBRARIES = libgstparse.la +CLEANFILES = grammar.tab.h grammar.output grammar.tab.c \ + lex._gst_parse_yy.c +EXTRA_DIST = \ + grammar.y \ + parse.l + + +# uncomment these lines to dist the generated sources +#BUILT_SOURCES = grammar.tab.h grammar.tab.c lex._gst_parse_yy.c +#libgstparse_la_SOURCES = lex._gst_parse_yy.c grammar.tab.c + +# uncomment these lines to _NOT_ dist the generated sources +nodist_libgstparse_la_SOURCES = lex._gst_parse_yy.c grammar.tab.c + +# can't use GST_ALL_CFLAGS here because that'd pull in -Werror +libgstparse_la_CFLAGS = -I$(top_srcdir) -I$(top_builddir) \ + $(GLIB_CFLAGS) $(GST_OPTION_CFLAGS) + +libgstparse_la_LIBADD = $(GST_ALL_LIBS) +noinst_HEADERS = grammar.tab.h types.h +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu gst/parse/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu gst/parse/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libgstparse.la: $(libgstparse_la_OBJECTS) $(libgstparse_la_DEPENDENCIES) + $(AM_V_CCLD)$(libgstparse_la_LINK) $(libgstparse_la_OBJECTS) $(libgstparse_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstparse_la-grammar.tab.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstparse_la-lex._gst_parse_yy.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +libgstparse_la-lex._gst_parse_yy.lo: lex._gst_parse_yy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstparse_la_CFLAGS) $(CFLAGS) -MT libgstparse_la-lex._gst_parse_yy.lo -MD -MP -MF $(DEPDIR)/libgstparse_la-lex._gst_parse_yy.Tpo -c -o libgstparse_la-lex._gst_parse_yy.lo `test -f 'lex._gst_parse_yy.c' || echo '$(srcdir)/'`lex._gst_parse_yy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstparse_la-lex._gst_parse_yy.Tpo $(DEPDIR)/libgstparse_la-lex._gst_parse_yy.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='lex._gst_parse_yy.c' object='libgstparse_la-lex._gst_parse_yy.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstparse_la_CFLAGS) $(CFLAGS) -c -o libgstparse_la-lex._gst_parse_yy.lo `test -f 'lex._gst_parse_yy.c' || echo '$(srcdir)/'`lex._gst_parse_yy.c + +libgstparse_la-grammar.tab.lo: grammar.tab.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstparse_la_CFLAGS) $(CFLAGS) -MT libgstparse_la-grammar.tab.lo -MD -MP -MF $(DEPDIR)/libgstparse_la-grammar.tab.Tpo -c -o libgstparse_la-grammar.tab.lo `test -f 'grammar.tab.c' || echo '$(srcdir)/'`grammar.tab.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstparse_la-grammar.tab.Tpo $(DEPDIR)/libgstparse_la-grammar.tab.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='grammar.tab.c' object='libgstparse_la-grammar.tab.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstparse_la_CFLAGS) $(CFLAGS) -c -o libgstparse_la-grammar.tab.lo `test -f 'grammar.tab.c' || echo '$(srcdir)/'`grammar.tab.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstLTLIBRARIES ctags distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags uninstall uninstall-am + + +Android.mk: Makefile.am + androgenizer -:PROJECT gstreamer -:STATIC libgstparse -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstparse_la_SOURCES) $(nodist_libgstparse_la_SOURCES) \ + -:CFLAGS $(DEFS) $(libgstparse_la_CFLAGS) \ + -:LDFLAGS $(libgstparse_la_LIBADD) \ + > $@ + +grammar.tab.c grammar.tab.h: grammar.y + $(AM_V_GEN)$(BISON_PATH) -d -v -p_gst_parse_yy $(srcdir)/grammar.y -o grammar.tab.c && \ + mv grammar.tab.c grammar.tab_tmp.c && \ + echo '#ifdef HAVE_CONFIG_H' > grammar.tab_tmp2.c && \ + echo '#include <config.h>' >> grammar.tab_tmp2.c && \ + echo '#endif' >> grammar.tab_tmp2.c && \ + cat grammar.tab_tmp.c >> grammar.tab_tmp2.c && \ + rm grammar.tab_tmp.c && \ + mv grammar.tab_tmp2.c grammar.tab.c + +lex._gst_parse_yy.c: parse.l grammar.tab.h + $(AM_V_GEN)$(FLEX_PATH) -P_gst_parse_yy $^ && \ + mv lex._gst_parse_yy.c lex._gst_parse_yy_tmp.c && \ + echo '#ifdef HAVE_CONFIG_H' > lex._gst_parse_yy_tmp2.c && \ + echo '#include <config.h>' >> lex._gst_parse_yy_tmp2.c && \ + echo '#endif' >> lex._gst_parse_yy_tmp2.c && \ + echo 'static inline int _gst_parse_yyget_column (void * yyscanner);' >> lex._gst_parse_yy_tmp2.c && \ + echo 'static inline void _gst_parse_yyset_column (int column_no , void * yyscanner);' >> lex._gst_parse_yy_tmp2.c && \ + cat lex._gst_parse_yy_tmp.c >> lex._gst_parse_yy_tmp2.c && \ + rm lex._gst_parse_yy_tmp.c && \ + mv lex._gst_parse_yy_tmp2.c lex._gst_parse_yy.c + +.NOTPARALLEL: + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/gst/parse/grammar.tab.h b/gst/parse/grammar.tab.h new file mode 100644 index 0000000..95fa2b6 --- /dev/null +++ b/gst/parse/grammar.tab.h @@ -0,0 +1,80 @@ + +/* A Bison parser, made by GNU Bison 2.4.1. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + PARSE_URL = 258, + IDENTIFIER = 259, + BINREF = 260, + PADREF = 261, + REF = 262, + ASSIGNMENT = 263, + LINK = 264 + }; +#endif + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +{ + +/* Line 1676 of yacc.c */ +#line 623 "./grammar.y" + + gchar *s; + chain_t *c; + link_t *l; + GstElement *e; + GSList *p; + graph_t *g; + + + +/* Line 1676 of yacc.c */ +#line 72 "grammar.tab.h" +} YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + + + + diff --git a/gst/parse/grammar.y b/gst/parse/grammar.y new file mode 100644 index 0000000..51755cf --- /dev/null +++ b/gst/parse/grammar.y @@ -0,0 +1,1044 @@ +%{ +#include "../gst_private.h" + +#include <glib-object.h> +#include <glib.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "../gst-i18n-lib.h" + +#include "../gstconfig.h" +#include "../gstparse.h" +#include "../gstinfo.h" +#include "../gsterror.h" +#include "../gststructure.h" +#include "../gsturi.h" +#include "../gstutils.h" +#include "../gstvalue.h" +#include "../gstchildproxy.h" +#include "types.h" + +/* All error messages in this file are user-visible and need to be translated. + * Don't start the message with a capital, and don't end them with a period, + * as they will be presented inside a sentence/error. + */ + +#define YYERROR_VERBOSE 1 +#define YYLEX_PARAM scanner + +#define YYENABLE_NLS 0 + +#ifndef YYLTYPE_IS_TRIVIAL +#define YYLTYPE_IS_TRIVIAL 0 +#endif + +typedef void* yyscan_t; + +int _gst_parse_yylex (void * yylval_param , yyscan_t yyscanner); +int _gst_parse_yylex_init (yyscan_t scanner); +int _gst_parse_yylex_destroy (yyscan_t scanner); +struct yy_buffer_state * _gst_parse_yy_scan_string (char* , yyscan_t); +void _gst_parse_yypush_buffer_state (void * new_buffer ,yyscan_t yyscanner ); +void _gst_parse_yypop_buffer_state (yyscan_t yyscanner ); + +#ifdef __GST_PARSE_TRACE +static guint __strings; +static guint __links; +static guint __chains; +gchar * +__gst_parse_strdup (gchar *org) +{ + gchar *ret; + __strings++; + ret = g_strdup (org); + /* g_print ("ALLOCATED STR (%3u): %p %s\n", __strings, ret, ret); */ + return ret; +} +void +__gst_parse_strfree (gchar *str) +{ + if (str) { + /* g_print ("FREEING STR (%3u): %p %s\n", __strings - 1, str, str); */ + g_free (str); + g_return_if_fail (__strings > 0); + __strings--; + } +} +link_t *__gst_parse_link_new () +{ + link_t *ret; + __links++; + ret = g_slice_new0 (link_t); + /* g_print ("ALLOCATED LINK (%3u): %p\n", __links, ret); */ + return ret; +} +void +__gst_parse_link_free (link_t *data) +{ + if (data) { + /* g_print ("FREEING LINK (%3u): %p\n", __links - 1, data); */ + g_slice_free (link_t, data); + g_return_if_fail (__links > 0); + __links--; + } +} +chain_t * +__gst_parse_chain_new () +{ + chain_t *ret; + __chains++; + ret = g_slice_new0 (chain_t); + /* g_print ("ALLOCATED CHAIN (%3u): %p\n", __chains, ret); */ + return ret; +} +void +__gst_parse_chain_free (chain_t *data) +{ + /* g_print ("FREEING CHAIN (%3u): %p\n", __chains - 1, data); */ + g_slice_free (chain_t, data); + g_return_if_fail (__chains > 0); + __chains--; +} + +#endif /* __GST_PARSE_TRACE */ + +typedef struct { + gchar *src_pad; + gchar *sink_pad; + GstElement *sink; + GstCaps *caps; + gulong signal_id; +} DelayedLink; + +typedef struct { + gchar *name; + gchar *value_str; + gulong signal_id; +} DelayedSet; + +/*** define SET_ERROR macro/function */ + +#ifdef G_HAVE_ISO_VARARGS + +# define SET_ERROR(error, type, ...) \ +G_STMT_START { \ + GST_CAT_ERROR (GST_CAT_PIPELINE, __VA_ARGS__); \ + if ((error) && !*(error)) { \ + g_set_error ((error), GST_PARSE_ERROR, (type), __VA_ARGS__); \ + } \ +} G_STMT_END + +#elif defined(G_HAVE_GNUC_VARARGS) + +# define SET_ERROR(error, type, args...) \ +G_STMT_START { \ + GST_CAT_ERROR (GST_CAT_PIPELINE, args ); \ + if ((error) && !*(error)) { \ + g_set_error ((error), GST_PARSE_ERROR, (type), args ); \ + } \ +} G_STMT_END + +#else + +static inline void +SET_ERROR (GError **error, gint type, const char *format, ...) +{ + if (error) { + if (*error) { + g_warning ("error while parsing"); + } else { + va_list varargs; + char *string; + + va_start (varargs, format); + string = g_strdup_vprintf (format, varargs); + va_end (varargs); + + g_set_error (error, GST_PARSE_ERROR, type, string); + + g_free (string); + } + } +} + +#endif /* G_HAVE_ISO_VARARGS */ + +/*** define YYPRINTF macro/function if we're debugging */ + +/* bison 1.35 calls this macro with side effects, we need to make sure the + side effects work - crappy bison */ + +#ifndef GST_DISABLE_GST_DEBUG +# define YYDEBUG 1 + +# ifdef G_HAVE_ISO_VARARGS + +/* # define YYFPRINTF(a, ...) GST_CAT_DEBUG (GST_CAT_PIPELINE, __VA_ARGS__) */ +# define YYFPRINTF(a, ...) \ +G_STMT_START { \ + GST_CAT_LOG (GST_CAT_PIPELINE, __VA_ARGS__); \ +} G_STMT_END + +# elif defined(G_HAVE_GNUC_VARARGS) + +# define YYFPRINTF(a, args...) \ +G_STMT_START { \ + GST_CAT_LOG (GST_CAT_PIPELINE, args); \ +} G_STMT_END + +# else + +static inline void +YYPRINTF(const char *format, ...) +{ + va_list varargs; + gchar *temp; + + va_start (varargs, format); + temp = g_strdup_vprintf (format, varargs); + GST_CAT_LOG (GST_CAT_PIPELINE, "%s", temp); + g_free (temp); + va_end (varargs); +} + +# endif /* G_HAVE_ISO_VARARGS */ + +#endif /* GST_DISABLE_GST_DEBUG */ + +#define ADD_MISSING_ELEMENT(graph,name) G_STMT_START { \ + if ((graph)->ctx) { \ + (graph)->ctx->missing_elements = \ + g_list_append ((graph)->ctx->missing_elements, g_strdup (name)); \ + } } G_STMT_END + +static void +no_free (gconstpointer foo) +{ + /* do nothing */ +} + +#define GST_BIN_MAKE(res, type, chainval, assign, type_string_free_func) \ +G_STMT_START { \ + chain_t *chain = chainval; \ + GSList *walk; \ + GstBin *bin = (GstBin *) gst_element_factory_make (type, NULL); \ + if (!chain) { \ + SET_ERROR (graph->error, GST_PARSE_ERROR_EMPTY_BIN, \ + _("specified empty bin \"%s\", not allowed"), type); \ + g_slist_foreach (assign, (GFunc) gst_parse_strfree, NULL); \ + g_slist_free (assign); \ + gst_object_unref (bin); \ + type_string_free_func (type); /* Need to clean up the string */ \ + YYERROR; \ + } else if (!bin) { \ + ADD_MISSING_ELEMENT(graph, type); \ + SET_ERROR (graph->error, GST_PARSE_ERROR_NO_SUCH_ELEMENT, \ + _("no bin \"%s\", skipping"), type); \ + g_slist_foreach (assign, (GFunc) gst_parse_strfree, NULL); \ + g_slist_free (assign); \ + res = chain; \ + } else { \ + for (walk = chain->elements; walk; walk = walk->next ) \ + gst_bin_add (bin, GST_ELEMENT (walk->data)); \ + g_slist_free (chain->elements); \ + chain->elements = g_slist_prepend (NULL, bin); \ + res = chain; \ + /* set the properties now */ \ + for (walk = assign; walk; walk = walk->next) \ + gst_parse_element_set ((gchar *) walk->data, GST_ELEMENT (bin), graph); \ + g_slist_free (assign); \ + } \ +} G_STMT_END + +#define MAKE_LINK(link, _src, _src_name, _src_pads, _sink, _sink_name, _sink_pads) \ +G_STMT_START { \ + link = gst_parse_link_new (); \ + link->src = _src; \ + link->sink = _sink; \ + link->src_name = _src_name; \ + link->sink_name = _sink_name; \ + link->src_pads = _src_pads; \ + link->sink_pads = _sink_pads; \ + link->caps = NULL; \ +} G_STMT_END + +#define MAKE_REF(link, _src, _pads) \ +G_STMT_START { \ + gchar *padname = _src; \ + GSList *pads = _pads; \ + if (padname) { \ + while (*padname != '.') padname++; \ + *padname = '\0'; \ + padname++; \ + if (*padname != '\0') \ + pads = g_slist_prepend (pads, gst_parse_strdup (padname)); \ + } \ + MAKE_LINK (link, NULL, _src, pads, NULL, NULL, NULL); \ +} G_STMT_END + +static void +gst_parse_free_delayed_set (DelayedSet *set) +{ + g_free(set->name); + g_free(set->value_str); + g_slice_free(DelayedSet, set); +} + +static void gst_parse_new_child(GstChildProxy *child_proxy, GObject *object, + gpointer data); + +static void +gst_parse_add_delayed_set (GstElement *element, gchar *name, gchar *value_str) +{ + DelayedSet *data = g_slice_new0 (DelayedSet); + + GST_CAT_LOG_OBJECT (GST_CAT_PIPELINE, element, "delaying property set %s to %s", + name, value_str); + + data->name = g_strdup(name); + data->value_str = g_strdup(value_str); + data->signal_id = g_signal_connect_data(element, "child-added", + G_CALLBACK (gst_parse_new_child), data, (GClosureNotify) + gst_parse_free_delayed_set, (GConnectFlags) 0); + + /* FIXME: we would need to listen on all intermediate bins too */ + if (GST_IS_BIN (element)) { + gchar **names, **current; + GstElement *parent, *child; + + current = names = g_strsplit (name, "::", -1); + parent = gst_bin_get_by_name (GST_BIN_CAST (element), current[0]); + current++; + while (parent && current[0]) { + child = gst_bin_get_by_name (GST_BIN (parent), current[0]); + if (!child && current[1]) { + char *sub_name = g_strjoinv ("::", ¤t[0]); + + gst_parse_add_delayed_set(parent, sub_name, value_str); + g_free (sub_name); + } + parent = child; + current++; + } + g_strfreev (names); + } +} + +static void gst_parse_new_child(GstChildProxy *child_proxy, GObject *object, + gpointer data) +{ + DelayedSet *set = (DelayedSet *) data; + GParamSpec *pspec; + GValue v = { 0, }; + GstObject *target = NULL; + GType value_type; + + GST_CAT_LOG_OBJECT (GST_CAT_PIPELINE, child_proxy, "new child %s, checking property %s", + GST_OBJECT_NAME(object), set->name); + + if (gst_child_proxy_lookup (GST_OBJECT (child_proxy), set->name, &target, &pspec)) { + gboolean got_value = FALSE; + + value_type = pspec->value_type; + + GST_CAT_LOG_OBJECT (GST_CAT_PIPELINE, child_proxy, "parsing delayed property %s as a %s from %s", + pspec->name, g_type_name (value_type), set->value_str); + g_value_init (&v, value_type); + if (gst_value_deserialize (&v, set->value_str)) + got_value = TRUE; + else if (g_type_is_a (value_type, GST_TYPE_ELEMENT)) { + GstElement *bin; + + bin = gst_parse_bin_from_description (set->value_str, TRUE, NULL); + if (bin) { + g_value_set_object (&v, bin); + got_value = TRUE; + } + } + g_signal_handler_disconnect (child_proxy, set->signal_id); + if (!got_value) + goto error; + g_object_set_property (G_OBJECT (target), pspec->name, &v); + } else { + const gchar *obj_name = GST_OBJECT_NAME(object); + gint len = strlen (obj_name); + + /* do a delayed set */ + if ((strlen (set->name) > (len + 2)) && !strncmp (set->name, obj_name, len) && !strncmp (&set->name[len], "::", 2)) { + gst_parse_add_delayed_set (GST_ELEMENT(child_proxy), set->name, set->value_str); + } + } + +out: + if (G_IS_VALUE (&v)) + g_value_unset (&v); + if (target) + gst_object_unref (target); + return; + +error: + GST_CAT_ERROR (GST_CAT_PIPELINE, "could not set property \"%s\" in element \"%s\"", + pspec->name, GST_ELEMENT_NAME (target)); + goto out; +} + +static void +gst_parse_element_set (gchar *value, GstElement *element, graph_t *graph) +{ + GParamSpec *pspec; + gchar *pos = value; + GValue v = { 0, }; + GstObject *target = NULL; + GType value_type; + + /* do nothing if assignment is for missing element */ + if (element == NULL) + goto out; + + /* parse the string, so the property name is null-terminated an pos points + to the beginning of the value */ + while (!g_ascii_isspace (*pos) && (*pos != '=')) pos++; + if (*pos == '=') { + *pos = '\0'; + } else { + *pos = '\0'; + pos++; + while (g_ascii_isspace (*pos)) pos++; + } + pos++; + while (g_ascii_isspace (*pos)) pos++; + if (*pos == '"') { + pos++; + pos[strlen (pos) - 1] = '\0'; + } + gst_parse_unescape (pos); + + if (gst_child_proxy_lookup (GST_OBJECT (element), value, &target, &pspec)) { + gboolean got_value = FALSE; + + value_type = pspec->value_type; + + GST_CAT_LOG_OBJECT (GST_CAT_PIPELINE, element, "parsing property %s as a %s", pspec->name, + g_type_name (value_type)); + g_value_init (&v, value_type); + if (gst_value_deserialize (&v, pos)) + got_value = TRUE; + else if (g_type_is_a (value_type, GST_TYPE_ELEMENT)) { + GstElement *bin; + + bin = gst_parse_bin_from_description (pos, TRUE, NULL); + if (bin) { + g_value_set_object (&v, bin); + got_value = TRUE; + } + } + if (!got_value) + goto error; + g_object_set_property (G_OBJECT (target), pspec->name, &v); + } else { + /* do a delayed set */ + if (GST_IS_CHILD_PROXY (element)) { + gst_parse_add_delayed_set (element, value, pos); + } + else { + SET_ERROR (graph->error, GST_PARSE_ERROR_NO_SUCH_PROPERTY, \ + _("no property \"%s\" in element \"%s\""), value, \ + GST_ELEMENT_NAME (element)); + } + } + +out: + gst_parse_strfree (value); + if (G_IS_VALUE (&v)) + g_value_unset (&v); + if (target) + gst_object_unref (target); + return; + +error: + SET_ERROR (graph->error, GST_PARSE_ERROR_COULD_NOT_SET_PROPERTY, + _("could not set property \"%s\" in element \"%s\" to \"%s\""), + value, GST_ELEMENT_NAME (element), pos); + goto out; +} + +static inline void +gst_parse_free_link (link_t *link) +{ + gst_parse_strfree (link->src_name); + gst_parse_strfree (link->sink_name); + g_slist_foreach (link->src_pads, (GFunc) gst_parse_strfree, NULL); + g_slist_foreach (link->sink_pads, (GFunc) gst_parse_strfree, NULL); + g_slist_free (link->src_pads); + g_slist_free (link->sink_pads); + if (link->caps) gst_caps_unref (link->caps); + gst_parse_link_free (link); +} + +static void +gst_parse_free_delayed_link (DelayedLink *link) +{ + g_free (link->src_pad); + g_free (link->sink_pad); + if (link->caps) gst_caps_unref (link->caps); + g_slice_free (DelayedLink, link); +} + +static void +gst_parse_found_pad (GstElement *src, GstPad *pad, gpointer data) +{ + DelayedLink *link = data; + + GST_CAT_INFO (GST_CAT_PIPELINE, "trying delayed linking %s:%s to %s:%s", + GST_STR_NULL (GST_ELEMENT_NAME (src)), GST_STR_NULL (link->src_pad), + GST_STR_NULL (GST_ELEMENT_NAME (link->sink)), GST_STR_NULL (link->sink_pad)); + + if (gst_element_link_pads_filtered (src, link->src_pad, link->sink, + link->sink_pad, link->caps)) { + /* do this here, we don't want to get any problems later on when + * unlocking states */ + GST_CAT_DEBUG (GST_CAT_PIPELINE, "delayed linking %s:%s to %s:%s worked", + GST_STR_NULL (GST_ELEMENT_NAME (src)), GST_STR_NULL (link->src_pad), + GST_STR_NULL (GST_ELEMENT_NAME (link->sink)), GST_STR_NULL (link->sink_pad)); + g_signal_handler_disconnect (src, link->signal_id); + } +} + +/* both padnames and the caps may be NULL */ +static gboolean +gst_parse_perform_delayed_link (GstElement *src, const gchar *src_pad, + GstElement *sink, const gchar *sink_pad, + GstCaps *caps) +{ + GList *templs = gst_element_class_get_pad_template_list ( + GST_ELEMENT_GET_CLASS (src)); + + for (; templs; templs = templs->next) { + GstPadTemplate *templ = (GstPadTemplate *) templs->data; + if ((GST_PAD_TEMPLATE_DIRECTION (templ) == GST_PAD_SRC) && + (GST_PAD_TEMPLATE_PRESENCE(templ) == GST_PAD_SOMETIMES)) + { + DelayedLink *data = g_slice_new (DelayedLink); + + /* TODO: maybe we should check if src_pad matches this template's names */ + + GST_CAT_DEBUG (GST_CAT_PIPELINE, "trying delayed link %s:%s to %s:%s", + GST_STR_NULL (GST_ELEMENT_NAME (src)), GST_STR_NULL (src_pad), + GST_STR_NULL (GST_ELEMENT_NAME (sink)), GST_STR_NULL (sink_pad)); + + data->src_pad = g_strdup (src_pad); + data->sink = sink; + data->sink_pad = g_strdup (sink_pad); + if (caps) { + data->caps = gst_caps_copy (caps); + } else { + data->caps = NULL; + } + data->signal_id = g_signal_connect_data (src, "pad-added", + G_CALLBACK (gst_parse_found_pad), data, + (GClosureNotify) gst_parse_free_delayed_link, (GConnectFlags) 0); + return TRUE; + } + } + return FALSE; +} + +/* + * performs a link and frees the struct. src and sink elements must be given + * return values 0 - link performed + * 1 - link delayed + * <0 - error + */ +static gint +gst_parse_perform_link (link_t *link, graph_t *graph) +{ + GstElement *src = link->src; + GstElement *sink = link->sink; + GSList *srcs = link->src_pads; + GSList *sinks = link->sink_pads; + g_assert (GST_IS_ELEMENT (src)); + g_assert (GST_IS_ELEMENT (sink)); + + GST_CAT_INFO (GST_CAT_PIPELINE, + "linking %s:%s to %s:%s (%u/%u) with caps \"%" GST_PTR_FORMAT "\"", + GST_ELEMENT_NAME (src), link->src_name ? link->src_name : "(any)", + GST_ELEMENT_NAME (sink), link->sink_name ? link->sink_name : "(any)", + g_slist_length (srcs), g_slist_length (sinks), link->caps); + + if (!srcs || !sinks) { + if (gst_element_link_pads_filtered (src, + srcs ? (const gchar *) srcs->data : NULL, sink, + sinks ? (const gchar *) sinks->data : NULL, link->caps)) { + goto success; + } else { + if (gst_parse_perform_delayed_link (src, + srcs ? (const gchar *) srcs->data : NULL, + sink, sinks ? (const gchar *) sinks->data : NULL, link->caps)) { + goto success; + } else { + goto error; + } + } + } + if (g_slist_length (link->src_pads) != g_slist_length (link->sink_pads)) { + goto error; + } + while (srcs && sinks) { + const gchar *src_pad = (const gchar *) srcs->data; + const gchar *sink_pad = (const gchar *) sinks->data; + srcs = g_slist_next (srcs); + sinks = g_slist_next (sinks); + if (gst_element_link_pads_filtered (src, src_pad, sink, sink_pad, + link->caps)) { + continue; + } else { + if (gst_parse_perform_delayed_link (src, src_pad, + sink, sink_pad, + link->caps)) { + continue; + } else { + goto error; + } + } + } + +success: + gst_parse_free_link (link); + return 0; + +error: + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, + _("could not link %s to %s"), GST_ELEMENT_NAME (src), + GST_ELEMENT_NAME (sink)); + gst_parse_free_link (link); + return -1; +} + + +static int yyerror (void *scanner, graph_t *graph, const char *s); +%} + +%union { + gchar *s; + chain_t *c; + link_t *l; + GstElement *e; + GSList *p; + graph_t *g; +} + +%token <s> PARSE_URL +%token <s> IDENTIFIER +%left <s> REF PADREF BINREF +%token <s> ASSIGNMENT +%token <s> LINK + +%type <g> graph +%type <c> chain bin +%type <l> reference +%type <l> linkpart link +%type <p> linklist +%type <e> element +%type <p> padlist pads assignments + +%left '(' ')' +%left ',' +%right '.' +%left '!' '=' + +%parse-param { void *scanner } +%parse-param { graph_t *graph } +%pure-parser + +%start graph +%% + +element: IDENTIFIER { $$ = gst_element_factory_make ($1, NULL); + if ($$ == NULL) { + ADD_MISSING_ELEMENT (graph, $1); + SET_ERROR (graph->error, GST_PARSE_ERROR_NO_SUCH_ELEMENT, _("no element \"%s\""), $1); + /* if FATAL_ERRORS flag is set, we don't have to worry about backwards + * compatibility and can continue parsing and check for other missing + * elements */ + if ((graph->flags & GST_PARSE_FLAG_FATAL_ERRORS) == 0) { + gst_parse_strfree ($1); + YYERROR; + } + } + gst_parse_strfree ($1); + } + | element ASSIGNMENT { gst_parse_element_set ($2, $1, graph); + $$ = $1; + } + ; +assignments: /* NOP */ { $$ = NULL; } + | assignments ASSIGNMENT { $$ = g_slist_prepend ($1, $2); } + ; +bin: '(' assignments chain ')' { GST_BIN_MAKE ($$, "bin", $3, $2, no_free); } + | BINREF assignments chain ')' { GST_BIN_MAKE ($$, $1, $3, $2, gst_parse_strfree); + gst_parse_strfree ($1); + } + | BINREF assignments ')' { GST_BIN_MAKE ($$, $1, NULL, $2, gst_parse_strfree); + gst_parse_strfree ($1); + } + | BINREF assignments error ')' { GST_BIN_MAKE ($$, $1, NULL, $2, gst_parse_strfree); + gst_parse_strfree ($1); + } + ; + +pads: PADREF { $$ = g_slist_prepend (NULL, $1); } + | PADREF padlist { $$ = $2; + $$ = g_slist_prepend ($$, $1); + } + ; +padlist: ',' IDENTIFIER { $$ = g_slist_prepend (NULL, $2); } + | ',' IDENTIFIER padlist { $$ = g_slist_prepend ($3, $2); } + ; + +reference: REF { MAKE_REF ($$, $1, NULL); } + | REF padlist { MAKE_REF ($$, $1, $2); } + ; + +linkpart: reference { $$ = $1; } + | pads { MAKE_REF ($$, NULL, $1); } + | /* NOP */ { MAKE_REF ($$, NULL, NULL); } + ; + +link: linkpart LINK linkpart { $$ = $1; + if ($2) { + $$->caps = gst_caps_from_string ($2); + if ($$->caps == NULL) + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, _("could not parse caps \"%s\""), $2); + gst_parse_strfree ($2); + } + $$->sink_name = $3->src_name; + $$->sink_pads = $3->src_pads; + gst_parse_link_free ($3); + } + ; + +linklist: link { $$ = g_slist_prepend (NULL, $1); } + | link linklist { $$ = g_slist_prepend ($2, $1); } + | linklist error { $$ = $1; } + ; + +chain: element { $$ = gst_parse_chain_new (); + $$->first = $$->last = $1; + $$->front = $$->back = NULL; + $$->elements = g_slist_prepend (NULL, $1); + } + | bin { $$ = $1; } + | chain chain { if ($1->back && $2->front) { + if (!$1->back->sink_name) { + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, _("link without source element")); + gst_parse_free_link ($1->back); + } else { + graph->links = g_slist_prepend (graph->links, $1->back); + } + if (!$2->front->src_name) { + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, _("link without sink element")); + gst_parse_free_link ($2->front); + } else { + graph->links = g_slist_prepend (graph->links, $2->front); + } + $1->back = NULL; + } else if ($1->back) { + if (!$1->back->sink_name) { + $1->back->sink = $2->first; + } + } else if ($2->front) { + if (!$2->front->src_name) { + $2->front->src = $1->last; + } + $1->back = $2->front; + } + + if ($1->back) { + graph->links = g_slist_prepend (graph->links, $1->back); + } + $1->last = $2->last; + $1->back = $2->back; + $1->elements = g_slist_concat ($1->elements, $2->elements); + if ($2) + gst_parse_chain_free ($2); + $$ = $1; + } + | chain linklist { GSList *walk; + if ($1->back) { + $2 = g_slist_prepend ($2, $1->back); + $1->back = NULL; + } else { + if (!((link_t *) $2->data)->src_name) { + ((link_t *) $2->data)->src = $1->last; + } + } + for (walk = $2; walk; walk = walk->next) { + link_t *link = (link_t *) walk->data; + if (!link->sink_name && walk->next) { + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, _("link without sink element")); + gst_parse_free_link (link); + } else if (!link->src_name && !link->src) { + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, _("link without source element")); + gst_parse_free_link (link); + } else { + if (walk->next) { + graph->links = g_slist_prepend (graph->links, link); + } else { + $1->back = link; + } + } + } + g_slist_free ($2); + $$ = $1; + } + | chain error { $$ = $1; } + | link chain { if ($2->front) { + if (!$2->front->src_name) { + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, _("link without source element")); + gst_parse_free_link ($2->front); + } else { + graph->links = g_slist_prepend (graph->links, $2->front); + } + } + if (!$1->sink_name) { + $1->sink = $2->first; + } + $2->front = $1; + $$ = $2; + } + | PARSE_URL chain { $$ = $2; + if ($$->front) { + GstElement *element = + gst_element_make_from_uri (GST_URI_SRC, $1, NULL); + if (!element) { + SET_ERROR (graph->error, GST_PARSE_ERROR_NO_SUCH_ELEMENT, + _("no source element for URI \"%s\""), $1); + } else { + $$->front->src = element; + graph->links = g_slist_prepend ( + graph->links, $$->front); + $$->front = NULL; + $$->elements = g_slist_prepend ($$->elements, element); + } + } else { + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, + _("no element to link URI \"%s\" to"), $1); + } + g_free ($1); + } + | link PARSE_URL { GstElement *element = + gst_element_make_from_uri (GST_URI_SINK, $2, NULL); + if (!element) { + SET_ERROR (graph->error, GST_PARSE_ERROR_NO_SUCH_ELEMENT, + _("no sink element for URI \"%s\""), $2); + gst_parse_link_free ($1); + g_free ($2); + YYERROR; + } else if ($1->sink_name || $1->sink_pads) { + gst_object_unref (element); + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, + _("could not link sink element for URI \"%s\""), $2); + gst_parse_link_free ($1); + g_free ($2); + YYERROR; + } else { + $$ = gst_parse_chain_new (); + $$->first = $$->last = element; + $$->front = $1; + $$->front->sink = element; + $$->elements = g_slist_prepend (NULL, element); + } + g_free ($2); + } + ; +graph: /* NOP */ { SET_ERROR (graph->error, GST_PARSE_ERROR_EMPTY, _("empty pipeline not allowed")); + $$ = graph; + } + | chain { $$ = graph; + if ($1->front) { + if (!$1->front->src_name) { + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, _("link without source element")); + gst_parse_free_link ($1->front); + } else { + $$->links = g_slist_prepend ($$->links, $1->front); + } + $1->front = NULL; + } + if ($1->back) { + if (!$1->back->sink_name) { + SET_ERROR (graph->error, GST_PARSE_ERROR_LINK, _("link without sink element")); + gst_parse_free_link ($1->back); + } else { + $$->links = g_slist_prepend ($$->links, $1->back); + } + $1->back = NULL; + } + $$->chain = $1; + } + ; + +%% + + +static int +yyerror (void *scanner, graph_t *graph, const char *s) +{ + /* FIXME: This should go into the GError somehow, but how? */ + GST_WARNING ("Error during parsing: %s", s); + return -1; +} + + +GstElement * +_gst_parse_launch (const gchar *str, GError **error, GstParseContext *ctx, + GstParseFlags flags) +{ + graph_t g; + gchar *dstr; + GSList *walk; + GstBin *bin = NULL; + GstElement *ret; + yyscan_t scanner; + + g_return_val_if_fail (str != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + g.chain = NULL; + g.links = NULL; + g.error = error; + g.ctx = ctx; + g.flags = flags; + +#ifdef __GST_PARSE_TRACE + GST_CAT_DEBUG (GST_CAT_PIPELINE, "TRACE: tracing enabled"); + __strings = __chains = __links = 0; +#endif /* __GST_PARSE_TRACE */ + + dstr = g_strdup (str); + _gst_parse_yylex_init (&scanner); + _gst_parse_yy_scan_string (dstr, scanner); + +#ifndef YYDEBUG + yydebug = 1; +#endif + + if (yyparse (scanner, &g) != 0) { + SET_ERROR (error, GST_PARSE_ERROR_SYNTAX, + "Unrecoverable syntax error while parsing pipeline %s", str); + + _gst_parse_yylex_destroy (scanner); + g_free (dstr); + + goto error1; + } + _gst_parse_yylex_destroy (scanner); + g_free (dstr); + + GST_CAT_DEBUG (GST_CAT_PIPELINE, "got %u elements and %u links", + g.chain ? g_slist_length (g.chain->elements) : 0, + g_slist_length (g.links)); + + if (!g.chain) { + ret = NULL; + } else if (!g.chain->elements->next) { + /* only one toplevel element */ + ret = (GstElement *) g.chain->elements->data; + g_slist_free (g.chain->elements); + if (GST_IS_BIN (ret)) + bin = GST_BIN (ret); + gst_parse_chain_free (g.chain); + } else { + /* put all elements in our bin */ + bin = GST_BIN (gst_element_factory_make ("pipeline", NULL)); + g_assert (bin); + + for (walk = g.chain->elements; walk; walk = walk->next) { + if (walk->data != NULL) + gst_bin_add (bin, GST_ELEMENT (walk->data)); + } + + g_slist_free (g.chain->elements); + ret = GST_ELEMENT (bin); + gst_parse_chain_free (g.chain); + } + + /* remove links */ + for (walk = g.links; walk; walk = walk->next) { + link_t *l = (link_t *) walk->data; + if (!l->src) { + if (l->src_name) { + if (bin) { + l->src = gst_bin_get_by_name_recurse_up (bin, l->src_name); + if (l->src) + gst_object_unref (l->src); + } else { + l->src = strcmp (GST_ELEMENT_NAME (ret), l->src_name) == 0 ? ret : NULL; + } + } + if (!l->src) { + if (l->src_name) { + SET_ERROR (error, GST_PARSE_ERROR_NO_SUCH_ELEMENT, + "No element named \"%s\" - omitting link", l->src_name); + } else { + /* probably a missing element which we've handled already */ + } + gst_parse_free_link (l); + continue; + } + } + if (!l->sink) { + if (l->sink_name) { + if (bin) { + l->sink = gst_bin_get_by_name_recurse_up (bin, l->sink_name); + if (l->sink) + gst_object_unref (l->sink); + } else { + l->sink = strcmp (GST_ELEMENT_NAME (ret), l->sink_name) == 0 ? ret : NULL; + } + } + if (!l->sink) { + if (l->sink_name) { + SET_ERROR (error, GST_PARSE_ERROR_NO_SUCH_ELEMENT, + "No element named \"%s\" - omitting link", l->sink_name); + } else { + /* probably a missing element which we've handled already */ + } + gst_parse_free_link (l); + continue; + } + } + gst_parse_perform_link (l, &g); + } + g_slist_free (g.links); + +out: +#ifdef __GST_PARSE_TRACE + GST_CAT_DEBUG (GST_CAT_PIPELINE, + "TRACE: %u strings, %u chains and %u links left", __strings, __chains, + __links); + if (__strings || __chains || __links) { + g_warning ("TRACE: %u strings, %u chains and %u links left", __strings, + __chains, __links); + } +#endif /* __GST_PARSE_TRACE */ + + return ret; + +error1: + if (g.chain) { + g_slist_foreach (g.chain->elements, (GFunc)gst_object_unref, NULL); + g_slist_free (g.chain->elements); + gst_parse_chain_free (g.chain); + } + + g_slist_foreach (g.links, (GFunc)gst_parse_free_link, NULL); + g_slist_free (g.links); + + if (error) + g_assert (*error); + ret = NULL; + + goto out; +} diff --git a/gst/parse/parse.l b/gst/parse/parse.l new file mode 100644 index 0000000..bcd6527 --- /dev/null +++ b/gst/parse/parse.l @@ -0,0 +1,148 @@ +%{ +#include "../gst_private.h" + +#include <math.h> +#include <string.h> + +#include <glib/gprintf.h> + +#include "types.h" +#include "../gstinfo.h" +#include "../gsturi.h" +#include "grammar.tab.h" + +/* Override the default ECHO so as to avoid fortify warnings. Ignore the + embedded-NUL case for now. We know yytext is NUL-terminated. */ +#define ECHO g_fprintf(yyout, "%s", yytext) + +#ifdef G_HAVE_ISO_VARARGS +#define PRINT(...) GST_CAT_DEBUG (GST_CAT_PIPELINE, "flex: " __VA_ARGS__) +#elif defined(G_HAVE_GNUC_VARARGS) +#define PRINT(args...) GST_CAT_DEBUG (GST_CAT_PIPELINE, "flex: " args) +#else +static inline void +PRINT (const char *format, ...) +{ + va_list varargs; + + va_start (varargs, format); + GST_CAT_LEVEL_LOG_valist (GST_CAT_PIPELINE, GST_LEVEL_DEBUG, NULL, + format, varargs); + va_end (varargs); +} +#endif + +%} + +_operator [(){}.!,;=] +_identifier [[:alnum:]_][[:alnum:]\-_%:]* + +_char ("\\".)|([^[:space:]]) +_string {_char}+|("\""([^\"]|"\\\"")*"\"")|("'"([^']|"\\\'")*"'") + +_assign [[:space:]]*"="[[:space:]]* + +_protocol [[:alpha:]][[:alnum:]+-\.]* +_url ({_protocol}"://"{_string}|["."{_identifier}]?"/"{_string})|({_protocol}"://") + +/* we must do this here, because nearly everything matches a {_string} */ +_assignment {_identifier}{_assign}{_string} + +/* get pad/element references and stuff with dots right */ +_padref "."{_identifier} +_ref {_identifier}"."{_identifier}? +_binref {_identifier}[[:space:]]*"."[[:space:]]*"(" + +/* links */ +_mimechar [[:alnum:]-] +_mimetype {_mimechar}+"/"{_mimechar}+ +_capschar ("\\".)|([^\;!]) +_capsstring {_capschar}+ +_caps {_mimetype}(","[^!]|{_capsstring})* +_link ("!"[[:space:]]*{_caps}([[:space:]]*(";"[[:space:]]*{_caps})*[[:space:]]*)*"!")|("!") + +%x value +%option noyywrap +%option nounput +%option reentrant +%option bison-bridge +%option never-interactive +%option noinput +%% + +{_assignment} { + /* "=" */ + PRINT ("ASSIGNMENT: %s", yytext); + yylval->s = gst_parse_strdup (yytext); + BEGIN (INITIAL); + return ASSIGNMENT; +} + +{_padref} { + yytext++; + PRINT ("PADREF: %s", yytext); + yylval->s = gst_parse_strdup (yytext); + BEGIN (INITIAL); + return PADREF; +} + +{_ref} { + PRINT ("REF: %s", yytext); + yylval->s = gst_parse_strdup (yytext); + BEGIN (INITIAL); + return REF; +} + +{_binref} { + gchar *pos = yytext; + while (!g_ascii_isspace (*pos) && (*pos != '.')) pos++; + *pos = '\0'; + PRINT ("BINREF: %s", yytext); + yylval->s = gst_parse_strdup (yytext); + BEGIN (INITIAL); + return BINREF; +} + +{_identifier} { + PRINT ("IDENTIFIER: %s", yytext); + yylval->s = gst_parse_strdup (yytext); + BEGIN (INITIAL); + return IDENTIFIER; +} + +{_link} { + gchar *c = yytext; + PRINT ("LINK: %s", yytext); + c++; + if (*c) { + while (g_ascii_isspace (*c)) c++; + c = yylval->s = gst_parse_strdup (c); + while (*c) c++; + if (*--c != '!') + g_assert_not_reached (); + while (g_ascii_isspace (*--c)); + *++c = '\0'; + } else { + yylval->s = NULL; + } + BEGIN (INITIAL); + return LINK; +} +{_url} { + PRINT ("URL: %s", yytext); + yylval->s = g_strdup (yytext); + gst_parse_unescape (yylval->s); + BEGIN (INITIAL); + return PARSE_URL; +} + +{_operator} { PRINT ("OPERATOR: [%s]", yytext); return *yytext; } + +[[:space:]]+ { PRINT ("SPACE: [%s]", yytext); } + +. { + PRINT ("Invalid Lexer element: %s\n", yytext); + return *yytext; +} + +%% diff --git a/gst/parse/types.h b/gst/parse/types.h new file mode 100644 index 0000000..db1f585 --- /dev/null +++ b/gst/parse/types.h @@ -0,0 +1,101 @@ +#ifndef __GST_PARSE_TYPES_H__ +#define __GST_PARSE_TYPES_H__ + +#include <glib-object.h> +#include "../gstelement.h" +#include "../gstparse.h" + +typedef struct { + GstElement *src; + GstElement *sink; + gchar *src_name; + gchar *sink_name; + GSList *src_pads; + GSList *sink_pads; + GstCaps *caps; +} link_t; + +typedef struct { + GSList *elements; + GstElement *first; + GstElement *last; + link_t *front; + link_t *back; +} chain_t; + +typedef struct _graph_t graph_t; +struct _graph_t { + chain_t *chain; /* links are supposed to be done now */ + GSList *links; + GError **error; + GstParseContext *ctx; /* may be NULL */ + GstParseFlags flags; +}; + + +/* + * Memory checking. Should probably be done with gsttrace stuff, but that + * doesn't really work. + * This is not safe from reentrance issues, but that doesn't matter as long as + * we lock a mutex before parsing anyway. + */ +#ifdef GST_DEBUG_ENABLED +# define __GST_PARSE_TRACE +#endif + +#ifdef __GST_PARSE_TRACE +gchar *__gst_parse_strdup (gchar *org); +void __gst_parse_strfree (gchar *str); +link_t *__gst_parse_link_new (); +void __gst_parse_link_free (link_t *data); +chain_t *__gst_parse_chain_new (); +void __gst_parse_chain_free (chain_t *data); +# define gst_parse_strdup __gst_parse_strdup +# define gst_parse_strfree __gst_parse_strfree +# define gst_parse_link_new __gst_parse_link_new +# define gst_parse_link_free __gst_parse_link_free +# define gst_parse_chain_new __gst_parse_chain_new +# define gst_parse_chain_free __gst_parse_chain_free +#else /* __GST_PARSE_TRACE */ +# define gst_parse_strdup g_strdup +# define gst_parse_strfree g_free +# define gst_parse_link_new() g_new0 (link_t, 1) +# define gst_parse_link_free g_free +# define gst_parse_chain_new() g_new0 (chain_t, 1) +# define gst_parse_chain_free g_free +#endif /* __GST_PARSE_TRACE */ + +static inline void +gst_parse_unescape (gchar *str) +{ + gchar *walk; + gboolean in_quotes; + + g_return_if_fail (str != NULL); + + walk = str; + in_quotes = FALSE; + + while (*walk) { + if (*walk == '\\' && !in_quotes) { + walk++; + /* make sure we don't read beyond the end of the string */ + if (*walk == '\0') + break; + } else if (*walk == '"' && (!in_quotes || (in_quotes + && (*(walk - 1) != '\\')))) { + /* don't unescape inside quotes and don't switch + * state with escaped quoted inside quotes */ + in_quotes = !in_quotes; + } + *str = *walk; + str++; + walk++; + } + *str = '\0'; +} + +GstElement *_gst_parse_launch (const gchar *, GError **, + GstParseContext *, GstParseFlags); + +#endif /* __GST_PARSE_TYPES_H__ */ |