# HG changeset patch # User berry120 # Date 1395789291 0 # Node ID e3c305de970cb617383e17ae80f6694452bae2db # Parent 6c7047fd93f0343af779f1e69c1b248d44461338 Add support for the MKV container format to the JFX media libraries. diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/java/com/sun/media/jfxmediaimpl/MediaUtils.java --- a/modules/media/src/main/java/com/sun/media/jfxmediaimpl/MediaUtils.java Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/java/com/sun/media/jfxmediaimpl/MediaUtils.java Tue Mar 25 23:14:51 2014 +0000 @@ -61,6 +61,7 @@ public static final String CONTENT_TYPE_MP4 = "video/mp4"; public static final String CONTENT_TYPE_M4A = "audio/x-m4a"; public static final String CONTENT_TYPE_M4V = "video/x-m4v"; + public static final String CONTENT_TYPE_MKV = "video/x-matroska"; public static final String CONTENT_TYPE_M3U8 = "application/vnd.apple.mpegurl"; public static final String CONTENT_TYPE_M3U = "audio/mpegurl"; private static final String FILE_TYPE_AIF = "aif"; @@ -72,6 +73,7 @@ private static final String FILE_TYPE_MP4 = "mp4"; private static final String FILE_TYPE_M4A = "m4a"; private static final String FILE_TYPE_M4V = "m4v"; + private static final String FILE_TYPE_MKV = "mkv"; private static final String FILE_TYPE_M3U8 = "m3u8"; private static final String FILE_TYPE_M3U = "m3u"; @@ -194,6 +196,8 @@ contentType = CONTENT_TYPE_M3U8; } else if (extension.equals(FILE_TYPE_M3U)) { contentType = CONTENT_TYPE_M3U; + } else if (extension.equals(FILE_TYPE_MKV)) { + contentType = CONTENT_TYPE_MKV; } } diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/java/com/sun/media/jfxmediaimpl/platform/gstreamer/GSTPlatform.java --- a/modules/media/src/main/java/com/sun/media/jfxmediaimpl/platform/gstreamer/GSTPlatform.java Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/java/com/sun/media/jfxmediaimpl/platform/gstreamer/GSTPlatform.java Tue Mar 25 23:14:51 2014 +0000 @@ -53,6 +53,7 @@ "video/mp4", "audio/x-m4a", "video/x-m4v", + "video/x-matroska", "application/vnd.apple.mpegurl", "audio/mpegurl" }; diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/3rd_party/glib/glib-2.28.8/build/win32/vs100/glib-lite.def --- a/modules/media/src/main/native/gstreamer/3rd_party/glib/glib-2.28.8/build/win32/vs100/glib-lite.def Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/gstreamer/3rd_party/glib/glib-2.28.8/build/win32/vs100/glib-lite.def Tue Mar 25 23:14:51 2014 +0000 @@ -393,3 +393,10 @@ g_win32_get_package_installation_directory_of_module @392 NONAME g_source_destroy @393 NONAME g_main_context_new @394 NONAME +g_array_remove_range @395 NONAME +g_random_int @396 NONAME +g_utf8_strdown @397 NONAME +g_convert_with_fallback @398 NONAME +g_try_realloc @399 NONAME +g_object_set_qdata_full @400 NONAME +g_slist_find_custom @401 NONAME \ No newline at end of file diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/3rd_party/glib/glib-2.28.8/build/win32/vs100/glib-liteD.def --- a/modules/media/src/main/native/gstreamer/3rd_party/glib/glib-2.28.8/build/win32/vs100/glib-liteD.def Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/gstreamer/3rd_party/glib/glib-2.28.8/build/win32/vs100/glib-liteD.def Tue Mar 25 23:14:51 2014 +0000 @@ -420,3 +420,10 @@ g_source_destroy @419 NONAME g_main_context_new @420 NONAME g_value_get_param @421 NONAME +g_array_remove_range @422 NONAME +g_random_int @423 NONAME +g_utf8_strdown @424 NONAME +g_convert_with_fallback @425 NONAME +g_try_realloc @426 NONAME +g_object_set_qdata_full @427 NONAME +g_slist_find_custom @428 NONAME \ No newline at end of file diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/Makefile.am Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,56 @@ +plugin_LTLIBRARIES = libgstmatroska.la + +libgstmatroska_la_SOURCES = \ + ebml-read.c \ + ebml-write.c \ + matroska.c \ + matroska-demux.c \ + matroska-parse.c \ + matroska-ids.c \ + matroska-mux.c \ + webm-mux.c \ + lzo.c + +noinst_HEADERS = \ + ebml-ids.h \ + ebml-read.h \ + ebml-write.h \ + matroska-demux.h \ + matroska-parse.h \ + matroska-ids.h \ + matroska-mux.h \ + webm-mux.h \ + lzo.h + +libgstmatroska_la_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) +libgstmatroska_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstriff-@GST_MAJORMINOR@ \ + -lgstaudio-@GST_MAJORMINOR@ \ + -lgsttag-@GST_MAJORMINOR@ \ + -lgstpbutils-@GST_MAJORMINOR@ \ + $(GST_BASE_LIBS) \ + $(GST_LIBS) \ + $(ZLIB_LIBS) \ + $(BZ2_LIBS) \ + $(LIBM) +libgstmatroska_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstmatroska_la_LIBTOOLFLAGS = --tag=disable-static + + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstmatroska -:SHARED libgstmatroska \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstmatroska_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) \ + -:LDFLAGS $(libgstmatroska_la_LDFLAGS) \ + $(libgstmatroska_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/Makefile.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/Makefile.in Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,934 @@ +# 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/matroska +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-gcc-inline-assembly.m4 \ + $(top_srcdir)/common/m4/as-objc.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-default.m4 \ + $(top_srcdir)/common/m4/gst-dowhile.m4 \ + $(top_srcdir)/common/m4/gst-error.m4 \ + $(top_srcdir)/common/m4/gst-feature.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-platform.m4 \ + $(top_srcdir)/common/m4/gst-plugin-docs.m4 \ + $(top_srcdir)/common/m4/gst-plugindir.m4 \ + $(top_srcdir)/common/m4/gst-x11.m4 \ + $(top_srcdir)/common/m4/gst.m4 \ + $(top_srcdir)/common/m4/gtk-doc.m4 \ + $(top_srcdir)/common/m4/orc.m4 $(top_srcdir)/common/m4/pkg.m4 \ + $(top_srcdir)/m4/aalib.m4 $(top_srcdir)/m4/esd.m4 \ + $(top_srcdir)/m4/gconf-2.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gst-fionread.m4 \ + $(top_srcdir)/m4/gst-shout2.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 = +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)$(plugindir)" +LTLIBRARIES = $(plugin_LTLIBRARIES) +am__DEPENDENCIES_1 = +libgstmatroska_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_libgstmatroska_la_OBJECTS = libgstmatroska_la-ebml-read.lo \ + libgstmatroska_la-ebml-write.lo libgstmatroska_la-matroska.lo \ + libgstmatroska_la-matroska-demux.lo \ + libgstmatroska_la-matroska-parse.lo \ + libgstmatroska_la-matroska-ids.lo \ + libgstmatroska_la-matroska-mux.lo \ + libgstmatroska_la-webm-mux.lo libgstmatroska_la-lzo.lo +libgstmatroska_la_OBJECTS = $(am_libgstmatroska_la_OBJECTS) +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +libgstmatroska_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(libgstmatroska_la_CFLAGS) $(CFLAGS) \ + $(libgstmatroska_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 = $(libgstmatroska_la_SOURCES) +DIST_SOURCES = $(libgstmatroska_la_SOURCES) +HEADERS = $(noinst_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +AALIB_CFLAGS = @AALIB_CFLAGS@ +AALIB_CONFIG = @AALIB_CONFIG@ +AALIB_LIBS = @AALIB_LIBS@ +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +ANNODEX_CFLAGS = @ANNODEX_CFLAGS@ +ANNODEX_LIBS = @ANNODEX_LIBS@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BZ2_LIBS = @BZ2_LIBS@ +CAIRO_CFLAGS = @CAIRO_CFLAGS@ +CAIRO_GOBJECT_CFLAGS = @CAIRO_GOBJECT_CFLAGS@ +CAIRO_GOBJECT_LIBS = @CAIRO_GOBJECT_LIBS@ +CAIRO_LIBS = @CAIRO_LIBS@ +CC = @CC@ +CCAS = @CCAS@ +CCASDEPMODE = @CCASDEPMODE@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFAULT_AUDIOSINK = @DEFAULT_AUDIOSINK@ +DEFAULT_AUDIOSRC = @DEFAULT_AUDIOSRC@ +DEFAULT_VIDEOSINK = @DEFAULT_VIDEOSINK@ +DEFAULT_VIDEOSRC = @DEFAULT_VIDEOSRC@ +DEFAULT_VISUALIZER = @DEFAULT_VISUALIZER@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DEPRECATED_CFLAGS = @DEPRECATED_CFLAGS@ +DIRECTSOUND_CFLAGS = @DIRECTSOUND_CFLAGS@ +DIRECTSOUND_LDFLAGS = @DIRECTSOUND_LDFLAGS@ +DIRECTSOUND_LIBS = @DIRECTSOUND_LIBS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +DV1394_CFLAGS = @DV1394_CFLAGS@ +DV1394_LIBS = @DV1394_LIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ERROR_CFLAGS = @ERROR_CFLAGS@ +ERROR_CXXFLAGS = @ERROR_CXXFLAGS@ +ESD_CFLAGS = @ESD_CFLAGS@ +ESD_CONFIG = @ESD_CONFIG@ +ESD_LIBS = @ESD_LIBS@ +EXEEXT = @EXEEXT@ +FFLAGS = @FFLAGS@ +FGREP = @FGREP@ +FLAC_CFLAGS = @FLAC_CFLAGS@ +FLAC_LIBS = @FLAC_LIBS@ +GCONFTOOL = @GCONFTOOL@ +GCONF_CFLAGS = @GCONF_CFLAGS@ +GCONF_LIBS = @GCONF_LIBS@ +GCONF_SCHEMA_CONFIG_SOURCE = @GCONF_SCHEMA_CONFIG_SOURCE@ +GCONF_SCHEMA_FILE_DIR = @GCONF_SCHEMA_FILE_DIR@ +GCOV = @GCOV@ +GCOV_CFLAGS = @GCOV_CFLAGS@ +GCOV_LIBS = @GCOV_LIBS@ +GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@ +GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_PREFIX = @GLIB_PREFIX@ +GLIB_REQ = @GLIB_REQ@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GREP = @GREP@ +GSTPB_PLUGINS_DIR = @GSTPB_PLUGINS_DIR@ +GSTPB_PREFIX = @GSTPB_PREFIX@ +GST_ALL_LDFLAGS = @GST_ALL_LDFLAGS@ +GST_BASE_CFLAGS = @GST_BASE_CFLAGS@ +GST_BASE_LIBS = @GST_BASE_LIBS@ +GST_CFLAGS = @GST_CFLAGS@ +GST_CHECK_CFLAGS = @GST_CHECK_CFLAGS@ +GST_CHECK_LIBS = @GST_CHECK_LIBS@ +GST_CONTROLLER_CFLAGS = @GST_CONTROLLER_CFLAGS@ +GST_CONTROLLER_LIBS = @GST_CONTROLLER_LIBS@ +GST_CXXFLAGS = @GST_CXXFLAGS@ +GST_GDP_CFLAGS = @GST_GDP_CFLAGS@ +GST_GDP_LIBS = @GST_GDP_LIBS@ +GST_LEVEL_DEFAULT = @GST_LEVEL_DEFAULT@ +GST_LIBS = @GST_LIBS@ +GST_LICENSE = @GST_LICENSE@ +GST_LT_LDFLAGS = @GST_LT_LDFLAGS@ +GST_MAJORMINOR = @GST_MAJORMINOR@ +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_PLUGINS_ALL = @GST_PLUGINS_ALL@ +GST_PLUGINS_BASE_CFLAGS = @GST_PLUGINS_BASE_CFLAGS@ +GST_PLUGINS_BASE_DIR = @GST_PLUGINS_BASE_DIR@ +GST_PLUGINS_BASE_LIBS = @GST_PLUGINS_BASE_LIBS@ +GST_PLUGINS_DIR = @GST_PLUGINS_DIR@ +GST_PLUGINS_SELECTED = @GST_PLUGINS_SELECTED@ +GST_PLUGIN_LDFLAGS = @GST_PLUGIN_LDFLAGS@ +GST_PREFIX = @GST_PREFIX@ +GST_TOOLS_DIR = @GST_TOOLS_DIR@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTK_CFLAGS = @GTK_CFLAGS@ +GTK_LIBS = @GTK_LIBS@ +GTK_X11_CFLAGS = @GTK_X11_CFLAGS@ +GTK_X11_LIBS = @GTK_X11_LIBS@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HAL_CFLAGS = @HAL_CFLAGS@ +HAL_LIBS = @HAL_LIBS@ +HAVE_AVC1394 = @HAVE_AVC1394@ +HAVE_BZ2 = @HAVE_BZ2@ +HAVE_CXX = @HAVE_CXX@ +HAVE_DIRECTSOUND = @HAVE_DIRECTSOUND@ +HAVE_GCONFTOOL = @HAVE_GCONFTOOL@ +HAVE_ROM1394 = @HAVE_ROM1394@ +HAVE_SPEEX = @HAVE_SPEEX@ +HAVE_X = @HAVE_X@ +HAVE_XSHM = @HAVE_XSHM@ +HAVE_ZLIB = @HAVE_ZLIB@ +HTML_DIR = @HTML_DIR@ +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@ +JACK_0_120_1_CFLAGS = @JACK_0_120_1_CFLAGS@ +JACK_0_120_1_LIBS = @JACK_0_120_1_LIBS@ +JACK_1_9_7_CFLAGS = @JACK_1_9_7_CFLAGS@ +JACK_1_9_7_LIBS = @JACK_1_9_7_LIBS@ +JACK_CFLAGS = @JACK_CFLAGS@ +JACK_LIBS = @JACK_LIBS@ +JPEG_LIBS = @JPEG_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBCACA_CFLAGS = @LIBCACA_CFLAGS@ +LIBCACA_CONFIG = @LIBCACA_CONFIG@ +LIBCACA_LIBS = @LIBCACA_LIBS@ +LIBDV_CFLAGS = @LIBDV_CFLAGS@ +LIBDV_LIBS = @LIBDV_LIBS@ +LIBICONV = @LIBICONV@ +LIBIEC61883_CFLAGS = @LIBIEC61883_CFLAGS@ +LIBIEC61883_LIBS = @LIBIEC61883_LIBS@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBOBJS = @LIBOBJS@ +LIBPNG_CFLAGS = @LIBPNG_CFLAGS@ +LIBPNG_LIBS = @LIBPNG_LIBS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBV4L2_CFLAGS = @LIBV4L2_CFLAGS@ +LIBV4L2_LIBS = @LIBV4L2_LIBS@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOCALEDIR = @LOCALEDIR@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJC = @OBJC@ +OBJCDEPMODE = @OBJCDEPMODE@ +OBJC_LDFLAGS = @OBJC_LDFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +ORCC = @ORCC@ +ORCC_FLAGS = @ORCC_FLAGS@ +ORC_CFLAGS = @ORC_CFLAGS@ +ORC_LIBS = @ORC_LIBS@ +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@ +PKG_CONFIG = @PKG_CONFIG@ +PLUGINDIR = @PLUGINDIR@ +POSUB = @POSUB@ +PROFILE_CFLAGS = @PROFILE_CFLAGS@ +PULSE_0_9_11_CFLAGS = @PULSE_0_9_11_CFLAGS@ +PULSE_0_9_11_LIBS = @PULSE_0_9_11_LIBS@ +PULSE_0_9_12_CFLAGS = @PULSE_0_9_12_CFLAGS@ +PULSE_0_9_12_LIBS = @PULSE_0_9_12_LIBS@ +PULSE_0_9_13_CFLAGS = @PULSE_0_9_13_CFLAGS@ +PULSE_0_9_13_LIBS = @PULSE_0_9_13_LIBS@ +PULSE_0_9_15_CFLAGS = @PULSE_0_9_15_CFLAGS@ +PULSE_0_9_15_LIBS = @PULSE_0_9_15_LIBS@ +PULSE_0_9_16_CFLAGS = @PULSE_0_9_16_CFLAGS@ +PULSE_0_9_16_LIBS = @PULSE_0_9_16_LIBS@ +PULSE_0_9_20_CFLAGS = @PULSE_0_9_20_CFLAGS@ +PULSE_0_9_20_LIBS = @PULSE_0_9_20_LIBS@ +PULSE_CFLAGS = @PULSE_CFLAGS@ +PULSE_LIBS = @PULSE_LIBS@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RAW1394_CFLAGS = @RAW1394_CFLAGS@ +RAW1394_LIBS = @RAW1394_LIBS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHOUT2_CFLAGS = @SHOUT2_CFLAGS@ +SHOUT2_LIBS = @SHOUT2_LIBS@ +SOUP_CFLAGS = @SOUP_CFLAGS@ +SOUP_LIBS = @SOUP_LIBS@ +SPEEX_CFLAGS = @SPEEX_CFLAGS@ +SPEEX_LIBS = @SPEEX_LIBS@ +STRIP = @STRIP@ +TAGLIB_CFLAGS = @TAGLIB_CFLAGS@ +TAGLIB_CXXFLAGS = @TAGLIB_CXXFLAGS@ +TAGLIB_LIBS = @TAGLIB_LIBS@ +USE_NLS = @USE_NLS@ +VALGRIND_CFLAGS = @VALGRIND_CFLAGS@ +VALGRIND_LIBS = @VALGRIND_LIBS@ +VALGRIND_PATH = @VALGRIND_PATH@ +VERSION = @VERSION@ +WARNING_CFLAGS = @WARNING_CFLAGS@ +WARNING_CXXFLAGS = @WARNING_CXXFLAGS@ +WAVPACK_CFLAGS = @WAVPACK_CFLAGS@ +WAVPACK_LIBS = @WAVPACK_LIBS@ +WIN32_LIBS = @WIN32_LIBS@ +XDAMAGE_CFLAGS = @XDAMAGE_CFLAGS@ +XDAMAGE_LIBS = @XDAMAGE_LIBS@ +XFIXES_CFLAGS = @XFIXES_CFLAGS@ +XFIXES_LIBS = @XFIXES_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +XMKMF = @XMKMF@ +XSHM_LIBS = @XSHM_LIBS@ +XVIDEO_LIBS = @XVIDEO_LIBS@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +ZLIB_LIBS = @ZLIB_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +ac_ct_OBJC = @ac_ct_OBJC@ +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@ +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@ +plugin_LTLIBRARIES = libgstmatroska.la +libgstmatroska_la_SOURCES = \ + ebml-read.c \ + ebml-write.c \ + matroska.c \ + matroska-demux.c \ + matroska-parse.c \ + matroska-ids.c \ + matroska-mux.c \ + webm-mux.c \ + lzo.c + +noinst_HEADERS = \ + ebml-ids.h \ + ebml-read.h \ + ebml-write.h \ + matroska-demux.h \ + matroska-parse.h \ + matroska-ids.h \ + matroska-mux.h \ + webm-mux.h \ + lzo.h + +libgstmatroska_la_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) + +libgstmatroska_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstriff-@GST_MAJORMINOR@ \ + -lgstaudio-@GST_MAJORMINOR@ \ + -lgsttag-@GST_MAJORMINOR@ \ + -lgstpbutils-@GST_MAJORMINOR@ \ + $(GST_BASE_LIBS) \ + $(GST_LIBS) \ + $(ZLIB_LIBS) \ + $(BZ2_LIBS) \ + $(LIBM) + +libgstmatroska_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstmatroska_la_LIBTOOLFLAGS = --tag=disable-static +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/matroska/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu gst/matroska/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): +install-pluginLTLIBRARIES: $(plugin_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(plugindir)" || $(MKDIR_P) "$(DESTDIR)$(plugindir)" + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || 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)$(plugindir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(plugindir)"; \ + } + +uninstall-pluginLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(plugindir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(plugindir)/$$f"; \ + done + +clean-pluginLTLIBRARIES: + -test -z "$(plugin_LTLIBRARIES)" || rm -f $(plugin_LTLIBRARIES) + @list='$(plugin_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 +libgstmatroska.la: $(libgstmatroska_la_OBJECTS) $(libgstmatroska_la_DEPENDENCIES) + $(AM_V_CCLD)$(libgstmatroska_la_LINK) -rpath $(plugindir) $(libgstmatroska_la_OBJECTS) $(libgstmatroska_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstmatroska_la-ebml-read.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstmatroska_la-ebml-write.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstmatroska_la-lzo.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstmatroska_la-matroska-demux.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstmatroska_la-matroska-ids.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstmatroska_la-matroska-mux.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstmatroska_la-matroska-parse.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstmatroska_la-matroska.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstmatroska_la-webm-mux.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 $@ $< + +libgstmatroska_la-ebml-read.lo: ebml-read.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -MT libgstmatroska_la-ebml-read.lo -MD -MP -MF $(DEPDIR)/libgstmatroska_la-ebml-read.Tpo -c -o libgstmatroska_la-ebml-read.lo `test -f 'ebml-read.c' || echo '$(srcdir)/'`ebml-read.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstmatroska_la-ebml-read.Tpo $(DEPDIR)/libgstmatroska_la-ebml-read.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ebml-read.c' object='libgstmatroska_la-ebml-read.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -c -o libgstmatroska_la-ebml-read.lo `test -f 'ebml-read.c' || echo '$(srcdir)/'`ebml-read.c + +libgstmatroska_la-ebml-write.lo: ebml-write.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -MT libgstmatroska_la-ebml-write.lo -MD -MP -MF $(DEPDIR)/libgstmatroska_la-ebml-write.Tpo -c -o libgstmatroska_la-ebml-write.lo `test -f 'ebml-write.c' || echo '$(srcdir)/'`ebml-write.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstmatroska_la-ebml-write.Tpo $(DEPDIR)/libgstmatroska_la-ebml-write.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ebml-write.c' object='libgstmatroska_la-ebml-write.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -c -o libgstmatroska_la-ebml-write.lo `test -f 'ebml-write.c' || echo '$(srcdir)/'`ebml-write.c + +libgstmatroska_la-matroska.lo: matroska.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -MT libgstmatroska_la-matroska.lo -MD -MP -MF $(DEPDIR)/libgstmatroska_la-matroska.Tpo -c -o libgstmatroska_la-matroska.lo `test -f 'matroska.c' || echo '$(srcdir)/'`matroska.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstmatroska_la-matroska.Tpo $(DEPDIR)/libgstmatroska_la-matroska.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='matroska.c' object='libgstmatroska_la-matroska.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -c -o libgstmatroska_la-matroska.lo `test -f 'matroska.c' || echo '$(srcdir)/'`matroska.c + +libgstmatroska_la-matroska-demux.lo: matroska-demux.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -MT libgstmatroska_la-matroska-demux.lo -MD -MP -MF $(DEPDIR)/libgstmatroska_la-matroska-demux.Tpo -c -o libgstmatroska_la-matroska-demux.lo `test -f 'matroska-demux.c' || echo '$(srcdir)/'`matroska-demux.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstmatroska_la-matroska-demux.Tpo $(DEPDIR)/libgstmatroska_la-matroska-demux.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='matroska-demux.c' object='libgstmatroska_la-matroska-demux.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -c -o libgstmatroska_la-matroska-demux.lo `test -f 'matroska-demux.c' || echo '$(srcdir)/'`matroska-demux.c + +libgstmatroska_la-matroska-parse.lo: matroska-parse.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -MT libgstmatroska_la-matroska-parse.lo -MD -MP -MF $(DEPDIR)/libgstmatroska_la-matroska-parse.Tpo -c -o libgstmatroska_la-matroska-parse.lo `test -f 'matroska-parse.c' || echo '$(srcdir)/'`matroska-parse.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstmatroska_la-matroska-parse.Tpo $(DEPDIR)/libgstmatroska_la-matroska-parse.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='matroska-parse.c' object='libgstmatroska_la-matroska-parse.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -c -o libgstmatroska_la-matroska-parse.lo `test -f 'matroska-parse.c' || echo '$(srcdir)/'`matroska-parse.c + +libgstmatroska_la-matroska-ids.lo: matroska-ids.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -MT libgstmatroska_la-matroska-ids.lo -MD -MP -MF $(DEPDIR)/libgstmatroska_la-matroska-ids.Tpo -c -o libgstmatroska_la-matroska-ids.lo `test -f 'matroska-ids.c' || echo '$(srcdir)/'`matroska-ids.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstmatroska_la-matroska-ids.Tpo $(DEPDIR)/libgstmatroska_la-matroska-ids.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='matroska-ids.c' object='libgstmatroska_la-matroska-ids.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -c -o libgstmatroska_la-matroska-ids.lo `test -f 'matroska-ids.c' || echo '$(srcdir)/'`matroska-ids.c + +libgstmatroska_la-matroska-mux.lo: matroska-mux.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -MT libgstmatroska_la-matroska-mux.lo -MD -MP -MF $(DEPDIR)/libgstmatroska_la-matroska-mux.Tpo -c -o libgstmatroska_la-matroska-mux.lo `test -f 'matroska-mux.c' || echo '$(srcdir)/'`matroska-mux.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstmatroska_la-matroska-mux.Tpo $(DEPDIR)/libgstmatroska_la-matroska-mux.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='matroska-mux.c' object='libgstmatroska_la-matroska-mux.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -c -o libgstmatroska_la-matroska-mux.lo `test -f 'matroska-mux.c' || echo '$(srcdir)/'`matroska-mux.c + +libgstmatroska_la-webm-mux.lo: webm-mux.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -MT libgstmatroska_la-webm-mux.lo -MD -MP -MF $(DEPDIR)/libgstmatroska_la-webm-mux.Tpo -c -o libgstmatroska_la-webm-mux.lo `test -f 'webm-mux.c' || echo '$(srcdir)/'`webm-mux.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstmatroska_la-webm-mux.Tpo $(DEPDIR)/libgstmatroska_la-webm-mux.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='webm-mux.c' object='libgstmatroska_la-webm-mux.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -c -o libgstmatroska_la-webm-mux.lo `test -f 'webm-mux.c' || echo '$(srcdir)/'`webm-mux.c + +libgstmatroska_la-lzo.lo: lzo.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -MT libgstmatroska_la-lzo.lo -MD -MP -MF $(DEPDIR)/libgstmatroska_la-lzo.Tpo -c -o libgstmatroska_la-lzo.lo `test -f 'lzo.c' || echo '$(srcdir)/'`lzo.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstmatroska_la-lzo.Tpo $(DEPDIR)/libgstmatroska_la-lzo.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='lzo.c' object='libgstmatroska_la-lzo.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstmatroska_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) $(CFLAGS) -c -o libgstmatroska_la-lzo.lo `test -f 'lzo.c' || echo '$(srcdir)/'`lzo.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: + for dir in "$(DESTDIR)$(plugindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +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: + +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-pluginLTLIBRARIES \ + 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-pluginLTLIBRARIES + +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: uninstall-pluginLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-pluginLTLIBRARIES 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-pluginLTLIBRARIES \ + 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 uninstall-pluginLTLIBRARIES + + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstmatroska -:SHARED libgstmatroska \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstmatroska_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(CPPFLAGS) $(libgstmatroska_la_CFLAGS) \ + -:LDFLAGS $(libgstmatroska_la_LDFLAGS) \ + $(libgstmatroska_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ + +# 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 -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-ids.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-ids.h Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,54 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * + * ebml-ids.h: definition of EBML data IDs + * + * 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_EBML_IDS_H__ +#define __GST_EBML_IDS_H__ + +G_BEGIN_DECLS + +/* EBML version supported */ +#define GST_EBML_VERSION 1 + +/* Unknown size (all bits set to 1) */ +#define GST_EBML_SIZE_UNKNOWN G_GINT64_CONSTANT(0x00ffffffffffffff) + +/* top-level master-IDs */ +#define GST_EBML_ID_HEADER 0x1A45DFA3 + +/* IDs in the HEADER master */ +#define GST_EBML_ID_EBMLVERSION 0x4286 +#define GST_EBML_ID_EBMLREADVERSION 0x42F7 +#define GST_EBML_ID_EBMLMAXIDLENGTH 0x42F2 +#define GST_EBML_ID_EBMLMAXSIZELENGTH 0x42F3 +#define GST_EBML_ID_DOCTYPE 0x4282 +#define GST_EBML_ID_DOCTYPEVERSION 0x4287 +#define GST_EBML_ID_DOCTYPEREADVERSION 0x4285 + +/* general EBML types */ +#define GST_EBML_ID_VOID 0xEC +#define GST_EBML_ID_CRC32 0xBF + +/* EbmlDate offset from the unix epoch in seconds, 2001/01/01 00:00:00 UTC */ +#define GST_EBML_DATE_OFFSET 978307200 + +G_END_DECLS + +#endif /* __GST_EBML_IDS_H__ */ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-read.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-read.c Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,675 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * + * ebml-read.c: read EBML data from file/stream + * + * 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 + +#include "ebml-read.h" +#include "ebml-ids.h" + +#include + +/* NAN is supposed to be in math.h, Microsoft defines it in xmath.h */ +#ifdef _MSC_VER +#include +#endif + +/* If everything goes wrong try 0.0/0.0 which should be NAN */ +#ifndef NAN +#define NAN 0 +#endif + +GST_DEBUG_CATEGORY (ebmlread_debug); +#define GST_CAT_DEFAULT ebmlread_debug + +/* Peeks following element id and element length in datastream provided + * by @peek with @ctx as user data. + * Returns GST_FLOW_UNEXPECTED if not enough data to read id and length. + * Otherwise, @needed provides the prefix length (id + length), and + * @length provides element length. + * + * @object and @offset are provided for informative messaging/debug purposes. + */ +GstFlowReturn +gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, guint * _needed, + GstPeekData peek, gpointer * ctx, GstElement * el, guint64 offset) +{ + guint needed; + const guint8 *buf; + gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; + guint64 total; + guint8 b; + + g_return_val_if_fail (_id != NULL, GST_FLOW_ERROR); + g_return_val_if_fail (_length != NULL, GST_FLOW_ERROR); + g_return_val_if_fail (_needed != NULL, GST_FLOW_ERROR); + + /* well ... */ + *_id = (guint32) GST_EBML_SIZE_UNKNOWN; + *_length = GST_EBML_SIZE_UNKNOWN; + + /* read element id */ + needed = 2; + buf = peek (ctx, needed); + if (!buf) + goto not_enough_data; + + b = GST_READ_UINT8 (buf); + total = (guint64) b; + while (read <= 4 && !(total & len_mask)) { + read++; + len_mask >>= 1; + } + if (G_UNLIKELY (read > 4)) + goto invalid_id; + + /* need id and at least something for subsequent length */ + needed = read + 1; + buf = peek (ctx, needed); + if (!buf) + goto not_enough_data; + + while (n < read) { + b = GST_READ_UINT8 (buf + n); + total = (total << 8) | b; + ++n; + } + *_id = (guint32) total; + + /* read element length */ + b = GST_READ_UINT8 (buf + n); + total = (guint64) b; + len_mask = 0x80; + read = 1; + while (read <= 8 && !(total & len_mask)) { + read++; + len_mask >>= 1; + } + if (G_UNLIKELY (read > 8)) + goto invalid_length; + if ((total &= (len_mask - 1)) == len_mask - 1) + num_ffs++; + + needed += read - 1; + buf = peek (ctx, needed); + if (!buf) + goto not_enough_data; + + buf += (needed - read); + n = 1; + while (n < read) { + guint8 b = GST_READ_UINT8 (buf + n); + + if (G_UNLIKELY (b == 0xff)) + num_ffs++; + total = (total << 8) | b; + ++n; + } + + if (G_UNLIKELY (read == num_ffs)) + *_length = G_MAXUINT64; + else + *_length = total; + *_length = total; + + *_needed = needed; + + return GST_FLOW_OK; + + /* ERRORS */ +not_enough_data: + { + *_needed = needed; + return GST_FLOW_UNEXPECTED; + } +invalid_id: + { + GST_ERROR_OBJECT (el, + "Invalid EBML ID size tag (0x%x) at position %" G_GUINT64_FORMAT " (0x%" + G_GINT64_MODIFIER "x)", (guint) b, offset, offset); + return GST_FLOW_ERROR; + } +invalid_length: + { + GST_ERROR_OBJECT (el, + "Invalid EBML length size tag (0x%x) at position %" G_GUINT64_FORMAT + " (0x%" G_GINT64_MODIFIER "x)", (guint) b, offset, offset); + return GST_FLOW_ERROR; + } +} + +/* setup for parsing @buf at position @offset on behalf of @el. + * Takes ownership of @buf. */ +void +gst_ebml_read_init (GstEbmlRead * ebml, GstElement * el, GstBuffer * buf, + guint64 offset) +{ + GstEbmlMaster m; + + g_return_if_fail (el); + g_return_if_fail (buf); + + ebml->el = el; + ebml->offset = offset; + ebml->buf = buf; + ebml->readers = g_array_sized_new (FALSE, FALSE, sizeof (GstEbmlMaster), 10); + m.offset = ebml->offset; + gst_byte_reader_init (&m.br, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + g_array_append_val (ebml->readers, m); +} + +void +gst_ebml_read_clear (GstEbmlRead * ebml) +{ + if (ebml->readers) + g_array_free (ebml->readers, TRUE); + ebml->readers = NULL; + if (ebml->buf) + gst_buffer_unref (ebml->buf); + ebml->buf = NULL; + ebml->el = NULL; +} + +static const guint8 * +gst_ebml_read_peek (GstByteReader * br, guint peek) +{ + const guint8 *data = NULL; + + if (G_LIKELY (gst_byte_reader_peek_data (br, peek, &data))) + return data; + else + return NULL; +} + +static GstFlowReturn +gst_ebml_peek_id_full (GstEbmlRead * ebml, guint32 * id, guint64 * length, + guint * prefix) +{ + GstFlowReturn ret; + + ret = gst_ebml_peek_id_length (id, length, prefix, + (GstPeekData) gst_ebml_read_peek, (gpointer) gst_ebml_read_br (ebml), + ebml->el, gst_ebml_read_get_pos (ebml)); + if (ret != GST_FLOW_OK) + return ret; + + GST_LOG_OBJECT (ebml->el, "id 0x%x at offset 0x%" G_GINT64_MODIFIER "x" + " of length %" G_GUINT64_FORMAT ", prefix %d", *id, + gst_ebml_read_get_pos (ebml), *length, *prefix); + +#ifndef GST_DISABLE_GST_DEBUG + { + const guint8 *data = NULL; + GstByteReader *br = gst_ebml_read_br (ebml); + guint size = gst_byte_reader_get_remaining (br); + + gst_byte_reader_peek_data (br, size, &data); + + GST_LOG_OBJECT (ebml->el, "current br %p; remaining %d", br, size); + if (data) + GST_MEMDUMP_OBJECT (ebml->el, "element", data, MIN (size, *length)); + } +#endif + + return ret; +} + +GstFlowReturn +gst_ebml_peek_id (GstEbmlRead * ebml, guint32 * id) +{ + guint64 length; + guint needed; + + return gst_ebml_peek_id_full (ebml, id, &length, &needed); +} + +/* + * Read the next element, the contents are supposed to be sub-elements which + * can be read separately. A new bytereader is setup for doing so. + */ +GstFlowReturn +gst_ebml_read_master (GstEbmlRead * ebml, guint32 * id) +{ + guint64 length; + guint prefix; + const guint8 *data = NULL; + GstFlowReturn ret; + GstEbmlMaster m; + + ret = gst_ebml_peek_id_full (ebml, id, &length, &prefix); + if (ret != GST_FLOW_OK) + return ret; + + /* we just at least peeked the id */ + if (!gst_byte_reader_skip (gst_ebml_read_br (ebml), prefix)) + return GST_FLOW_ERROR; /* FIXME: do proper error handling */ + + m.offset = gst_ebml_read_get_pos (ebml); + if (!gst_byte_reader_get_data (gst_ebml_read_br (ebml), length, &data)) + return GST_FLOW_PARSE; + + GST_LOG_OBJECT (ebml->el, "pushing level %d at offset %" G_GUINT64_FORMAT, + ebml->readers->len, m.offset); + gst_byte_reader_init (&m.br, data, length); + g_array_append_val (ebml->readers, m); + + return GST_FLOW_OK; +} + +/* explicitly pop a bytereader from stack. Usually invoked automagically. */ +GstFlowReturn +gst_ebml_read_pop_master (GstEbmlRead * ebml) +{ + g_return_val_if_fail (ebml->readers, GST_FLOW_ERROR); + + /* never remove initial bytereader */ + if (ebml->readers->len > 1) { + GST_LOG_OBJECT (ebml->el, "popping level %d", ebml->readers->len - 1); + g_array_remove_index (ebml->readers, ebml->readers->len - 1); + } + + return GST_FLOW_OK; +} + +/* + * Skip the next element. + */ + +GstFlowReturn +gst_ebml_read_skip (GstEbmlRead * ebml) +{ + guint64 length; + guint32 id; + guint prefix; + GstFlowReturn ret; + + ret = gst_ebml_peek_id_full (ebml, &id, &length, &prefix); + if (ret != GST_FLOW_OK) + return ret; + + if (!gst_byte_reader_skip (gst_ebml_read_br (ebml), length + prefix)) + return GST_FLOW_PARSE; + + return ret; +} + +/* + * Read the next element as a GstBuffer (binary). + */ + +GstFlowReturn +gst_ebml_read_buffer (GstEbmlRead * ebml, guint32 * id, GstBuffer ** buf) +{ + guint64 length; + guint prefix; + GstFlowReturn ret; + + ret = gst_ebml_peek_id_full (ebml, id, &length, &prefix); + if (ret != GST_FLOW_OK) + return ret; + + /* we just at least peeked the id */ + if (!gst_byte_reader_skip (gst_ebml_read_br (ebml), prefix)) + return GST_FLOW_ERROR; /* FIXME: do proper error handling */ + + if (G_LIKELY (length > 0)) { + guint offset; + + offset = gst_ebml_read_get_pos (ebml) - ebml->offset; + if (G_LIKELY (gst_byte_reader_skip (gst_ebml_read_br (ebml), length))) { + *buf = gst_buffer_create_sub (ebml->buf, offset, length); + } else { + *buf = NULL; + return GST_FLOW_PARSE; + } + } else { + *buf = gst_buffer_new (); + } + + return ret; +} + +/* + * Read the next element, return a pointer to it and its size. + */ + +static GstFlowReturn +gst_ebml_read_bytes (GstEbmlRead * ebml, guint32 * id, const guint8 ** data, + guint * size) +{ + guint64 length; + guint prefix; + GstFlowReturn ret; + + *size = 0; + + ret = gst_ebml_peek_id_full (ebml, id, &length, &prefix); + if (ret != GST_FLOW_OK) + return ret; + + /* we just at least peeked the id */ + if (!gst_byte_reader_skip (gst_ebml_read_br (ebml), prefix)) + return GST_FLOW_ERROR; /* FIXME: do proper error handling */ + + *data = NULL; + if (G_LIKELY (length >= 0)) { + if (!gst_byte_reader_get_data (gst_ebml_read_br (ebml), length, data)) + return GST_FLOW_PARSE; + } + + *size = length; + + return ret; +} + +/* + * Read the next element as an unsigned int. + */ + +GstFlowReturn +gst_ebml_read_uint (GstEbmlRead * ebml, guint32 * id, guint64 * num) +{ + const guint8 *data; + guint size; + GstFlowReturn ret; + + ret = gst_ebml_read_bytes (ebml, id, &data, &size); + if (ret != GST_FLOW_OK) + return ret; + + if (size > 8) { + GST_ERROR_OBJECT (ebml->el, + "Invalid integer element size %d at position %" G_GUINT64_FORMAT " (0x%" + G_GINT64_MODIFIER "x)", size, gst_ebml_read_get_pos (ebml) - size, + gst_ebml_read_get_pos (ebml) - size); + return GST_FLOW_ERROR; + } + + if (size == 0) { + *num = 0; + return ret; + } + + *num = 0; + while (size > 0) { + *num = (*num << 8) | *data; + size--; + data++; + } + + return ret; +} + +/* + * Read the next element as a signed int. + */ + +GstFlowReturn +gst_ebml_read_sint (GstEbmlRead * ebml, guint32 * id, gint64 * num) +{ + const guint8 *data; + guint size; + gboolean negative = 0; + GstFlowReturn ret; + + ret = gst_ebml_read_bytes (ebml, id, &data, &size); + if (ret != GST_FLOW_OK) + return ret; + + if (size > 8) { + GST_ERROR_OBJECT (ebml->el, + "Invalid integer element size %d at position %" G_GUINT64_FORMAT " (0x%" + G_GINT64_MODIFIER "x)", size, gst_ebml_read_get_pos (ebml) - size, + gst_ebml_read_get_pos (ebml) - size); + return GST_FLOW_ERROR; + } + + if (size == 0) { + *num = 0; + return ret; + } + + *num = 0; + if (*data & 0x80) { + negative = 1; + *num = *data & ~0x80; + size--; + data++; + } + + while (size > 0) { + *num = (*num << 8) | *data; + size--; + data++; + } + + /* make signed */ + if (negative) { + *num = 0 - *num; + } + + return ret; +} + +/* Convert 80 bit extended precision float in big endian format to double. + * Code taken from libavutil/intfloat_readwrite.c from ffmpeg, + * licensed under LGPL */ + +struct _ext_float +{ + guint8 exponent[2]; + guint8 mantissa[8]; +}; + +static gdouble +_ext2dbl (const guint8 * data) +{ + struct _ext_float ext; + guint64 m = 0; + gint e, i; + + memcpy (&ext.exponent, data, 2); + memcpy (&ext.mantissa, data + 2, 8); + + for (i = 0; i < 8; i++) + m = (m << 8) + ext.mantissa[i]; + e = (((gint) ext.exponent[0] & 0x7f) << 8) | ext.exponent[1]; + if (e == 0x7fff && m) + return NAN; + e -= 16383 + 63; /* In IEEE 80 bits, the whole (i.e. 1.xxxx) + * mantissa bit is written as opposed to the + * single and double precision formats */ + if (ext.exponent[0] & 0x80) + m = -m; + return ldexp (m, e); +} + +/* + * Read the next element as a float. + */ + +GstFlowReturn +gst_ebml_read_float (GstEbmlRead * ebml, guint32 * id, gdouble * num) +{ + const guint8 *data; + guint size; + GstFlowReturn ret; + + ret = gst_ebml_read_bytes (ebml, id, &data, &size); + if (ret != GST_FLOW_OK) + return ret; + + if (size != 0 && size != 4 && size != 8 && size != 10) { + GST_ERROR_OBJECT (ebml->el, + "Invalid float element size %d at position %" G_GUINT64_FORMAT " (0x%" + G_GINT64_MODIFIER "x)", size, gst_ebml_read_get_pos (ebml) - size, + gst_ebml_read_get_pos (ebml) - size); + return GST_FLOW_ERROR; + } + + if (size == 4) { + gfloat f; + + memcpy (&f, data, 4); + f = GFLOAT_FROM_BE (f); + + *num = f; + } else if (size == 8) { + gdouble d; + + memcpy (&d, data, 8); + d = GDOUBLE_FROM_BE (d); + + *num = d; + } else if (size == 10) { + *num = _ext2dbl (data); + } else { + /* size == 0 means a value of 0.0 */ + *num = 0.0; + } + + return ret; +} + +/* + * Read the next element as a C string. + */ + +static GstFlowReturn +gst_ebml_read_string (GstEbmlRead * ebml, guint32 * id, gchar ** str) +{ + const guint8 *data; + guint size; + GstFlowReturn ret; + + ret = gst_ebml_read_bytes (ebml, id, &data, &size); + if (ret != GST_FLOW_OK) + return ret; + + *str = g_malloc (size + 1); + memcpy (*str, data, size); + (*str)[size] = '\0'; + + return ret; +} + +/* + * Read the next element as an ASCII string. + */ + +GstFlowReturn +gst_ebml_read_ascii (GstEbmlRead * ebml, guint32 * id, gchar ** str_out) +{ + GstFlowReturn ret; + gchar *str; + gchar *iter; + +#ifndef GST_DISABLE_GST_DEBUG + guint64 oldoff = ebml->offset; +#endif + + ret = gst_ebml_read_string (ebml, id, &str); + if (ret != GST_FLOW_OK) + return ret; + + for (iter = str; *iter != '\0'; iter++) { + if (G_UNLIKELY (*iter & 0x80)) { + GST_ERROR_OBJECT (ebml, + "Invalid ASCII string at offset %" G_GUINT64_FORMAT, oldoff); + g_free (str); + return GST_FLOW_ERROR; + } + } + + *str_out = str; + return ret; +} + +/* + * Read the next element as a UTF-8 string. + */ + +GstFlowReturn +gst_ebml_read_utf8 (GstEbmlRead * ebml, guint32 * id, gchar ** str) +{ + GstFlowReturn ret; + +#ifndef GST_DISABLE_GST_DEBUG + guint64 oldoff = gst_ebml_read_get_pos (ebml); +#endif + + ret = gst_ebml_read_string (ebml, id, str); + if (ret != GST_FLOW_OK) + return ret; + + if (str != NULL && *str != NULL && **str != '\0' && + !g_utf8_validate (*str, -1, NULL)) { + GST_WARNING_OBJECT (ebml->el, + "Invalid UTF-8 string at offset %" G_GUINT64_FORMAT, oldoff); + } + + return ret; +} + +/* + * Read the next element as a date. + * Returns the seconds since the unix epoch. + */ + +GstFlowReturn +gst_ebml_read_date (GstEbmlRead * ebml, guint32 * id, gint64 * date) +{ + gint64 ebml_date; + GstFlowReturn ret; + + ret = gst_ebml_read_sint (ebml, id, &ebml_date); + if (ret != GST_FLOW_OK) + return ret; + + *date = (ebml_date / GST_SECOND) + GST_EBML_DATE_OFFSET; + + return ret; +} + +/* + * Read the next element as binary data. + */ + +GstFlowReturn +gst_ebml_read_binary (GstEbmlRead * ebml, + guint32 * id, guint8 ** binary, guint64 * length) +{ + const guint8 *data; + guint size; + GstFlowReturn ret; + + ret = gst_ebml_read_bytes (ebml, id, &data, &size); + if (ret != GST_FLOW_OK) + return ret; + + *length = size; + *binary = g_memdup (data, size); + + return GST_FLOW_OK; +} diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-read.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-read.h Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,170 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * + * ebml-read.c: read EBML data from file/stream + * + * 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_EBML_READ_H__ +#define __GST_EBML_READ_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_EBML_READ \ + (gst_ebml_read_get_type ()) +#define GST_EBML_READ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_EBML_READ, GstEbmlRead)) +#define GST_EBML_READ_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_EBML_READ, GstEbmlReadClass)) +#define GST_IS_EBML_READ(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_EBML_READ)) +#define GST_IS_EBML_READ_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_EBML_READ)) +#define GST_EBML_READ_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_EBML_READ, GstEbmlReadClass)) + +GST_DEBUG_CATEGORY_EXTERN (ebmlread_debug); + +/* custom flow return code */ +#define GST_FLOW_PARSE GST_FLOW_CUSTOM_ERROR + +typedef struct _GstEbmlMaster { + guint64 offset; + GstByteReader br; +} GstEbmlMaster; + +typedef struct _GstEbmlRead { + GstElement *el; + + GstBuffer *buf; + guint64 offset; + + GArray *readers; +} GstEbmlRead; + +typedef const guint8 * (*GstPeekData) (gpointer * context, guint peek); + +/* returns UNEXPECTED if not enough data */ +GstFlowReturn gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, + guint * _needed, + GstPeekData peek, gpointer * ctx, + GstElement * el, guint64 offset); + +void gst_ebml_read_init (GstEbmlRead * ebml, + GstElement * el, GstBuffer * buf, + guint64 offset); + +void gst_ebml_read_clear (GstEbmlRead * ebml); + +GstFlowReturn gst_ebml_peek_id (GstEbmlRead * ebml, guint32 * id); + +/* return _PARSE if not enough data to read what is needed, _ERROR or _OK */ +GstFlowReturn gst_ebml_read_skip (GstEbmlRead *ebml); + +GstFlowReturn gst_ebml_read_buffer (GstEbmlRead *ebml, + guint32 *id, + GstBuffer **buf); + +GstFlowReturn gst_ebml_read_uint (GstEbmlRead *ebml, + guint32 *id, + guint64 *num); + +GstFlowReturn gst_ebml_read_sint (GstEbmlRead *ebml, + guint32 *id, + gint64 *num); + +GstFlowReturn gst_ebml_read_float (GstEbmlRead *ebml, + guint32 *id, + gdouble *num); + +GstFlowReturn gst_ebml_read_ascii (GstEbmlRead *ebml, + guint32 *id, + gchar **str); + +GstFlowReturn gst_ebml_read_utf8 (GstEbmlRead *ebml, + guint32 *id, + gchar **str); + +GstFlowReturn gst_ebml_read_date (GstEbmlRead *ebml, + guint32 *id, + gint64 *date); + +GstFlowReturn gst_ebml_read_master (GstEbmlRead *ebml, + guint32 *id); + +GstFlowReturn gst_ebml_read_pop_master (GstEbmlRead *ebml); + +GstFlowReturn gst_ebml_read_binary (GstEbmlRead *ebml, + guint32 *id, + guint8 **binary, + guint64 *length); + +GstFlowReturn gst_ebml_read_header (GstEbmlRead *read, + gchar **doctype, + guint *version); + +/* Returns current (absolute) position of Ebml parser, + * i.e. taking into account offset provided at init */ +static inline guint64 +gst_ebml_read_get_pos (GstEbmlRead * ebml) +{ + GstEbmlMaster *m; + + g_return_val_if_fail (ebml->readers, 0); + g_return_val_if_fail (ebml->readers->len, 0); + + m = &(g_array_index (ebml->readers, GstEbmlMaster, ebml->readers->len - 1)); + return m->offset + gst_byte_reader_get_pos (&m->br); +} + +/* Returns starting offset of Ebml parser */ +static inline guint64 +gst_ebml_read_get_offset (GstEbmlRead * ebml) +{ + return ebml->offset; +} + +static inline GstByteReader * +gst_ebml_read_br (GstEbmlRead * ebml) +{ + g_return_val_if_fail (ebml->readers, NULL); + g_return_val_if_fail (ebml->readers->len, NULL); + + return &(g_array_index (ebml->readers, + GstEbmlMaster, ebml->readers->len - 1).br); +} + +static inline gboolean +gst_ebml_read_has_remaining (GstEbmlRead * ebml, guint64 bytes_needed, + gboolean auto_pop) +{ + gboolean res; + + res = (gst_byte_reader_get_remaining (gst_ebml_read_br (ebml)) >= bytes_needed); + if (G_LIKELY (!res && auto_pop)) { + gst_ebml_read_pop_master (ebml); + } + + return G_LIKELY (res); +} + +G_END_DECLS + +#endif /* __GST_EBML_READ_H__ */ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-write.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-write.c Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,838 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * (c) 2005 Michal Benes + * + * ebml-write.c: write EBML data to file/stream + * + * 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 +#include + +#include "ebml-write.h" +#include "ebml-ids.h" + + +GST_DEBUG_CATEGORY_STATIC (gst_ebml_write_debug); +#define GST_CAT_DEFAULT gst_ebml_write_debug + +#define _do_init(thing) \ + GST_DEBUG_CATEGORY_INIT (gst_ebml_write_debug, "ebmlwrite", 0, "Write EBML structured data") +GST_BOILERPLATE_FULL (GstEbmlWrite, gst_ebml_write, GstObject, GST_TYPE_OBJECT, + _do_init); + +static void gst_ebml_write_finalize (GObject * object); + +static void +gst_ebml_write_base_init (gpointer g_class) +{ +} + +static void +gst_ebml_write_class_init (GstEbmlWriteClass * klass) +{ + GObjectClass *object = G_OBJECT_CLASS (klass); + + object->finalize = gst_ebml_write_finalize; +} + +static void +gst_ebml_write_init (GstEbmlWrite * ebml, GstEbmlWriteClass * klass) +{ + ebml->srcpad = NULL; + ebml->pos = 0; + ebml->last_pos = G_MAXUINT64; /* force newsegment event */ + + ebml->cache = NULL; + ebml->streamheader = NULL; + ebml->streamheader_pos = 0; + ebml->writing_streamheader = FALSE; + ebml->caps = NULL; +} + +static void +gst_ebml_write_finalize (GObject * object) +{ + GstEbmlWrite *ebml = GST_EBML_WRITE (object); + + gst_object_unref (ebml->srcpad); + + if (ebml->cache) { + gst_byte_writer_free (ebml->cache); + ebml->cache = NULL; + } + + if (ebml->streamheader) { + gst_byte_writer_free (ebml->streamheader); + ebml->streamheader = NULL; + } + + if (ebml->caps) { + gst_caps_unref (ebml->caps); + ebml->caps = NULL; + } + + GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + + +/** + * gst_ebml_write_new: + * @srcpad: Source pad to which the output will be pushed. + * + * Creates a new #GstEbmlWrite. + * + * Returns: a new #GstEbmlWrite + */ +GstEbmlWrite * +gst_ebml_write_new (GstPad * srcpad) +{ + GstEbmlWrite *ebml = + GST_EBML_WRITE (g_object_new (GST_TYPE_EBML_WRITE, NULL)); + + ebml->srcpad = gst_object_ref (srcpad); + ebml->timestamp = GST_CLOCK_TIME_NONE; + + gst_ebml_write_reset (ebml); + + return ebml; +} + + +/** + * gst_ebml_write_reset: + * @ebml: a #GstEbmlWrite. + * + * Reset internal state of #GstEbmlWrite. + */ +void +gst_ebml_write_reset (GstEbmlWrite * ebml) +{ + ebml->pos = 0; + ebml->last_pos = G_MAXUINT64; /* force newsegment event */ + + if (ebml->cache) { + gst_byte_writer_free (ebml->cache); + ebml->cache = NULL; + } + + if (ebml->caps) { + gst_caps_unref (ebml->caps); + ebml->caps = NULL; + } + + ebml->last_write_result = GST_FLOW_OK; + ebml->timestamp = GST_CLOCK_TIME_NONE; +} + + +/** + * gst_ebml_last_write_result: + * @ebml: a #GstEbmlWrite. + * + * Returns: GST_FLOW_OK if there was not write error since the last call of + * gst_ebml_last_write_result or code of the error. + */ +GstFlowReturn +gst_ebml_last_write_result (GstEbmlWrite * ebml) +{ + GstFlowReturn res = ebml->last_write_result; + + ebml->last_write_result = GST_FLOW_OK; + + return res; +} + + +void +gst_ebml_start_streamheader (GstEbmlWrite * ebml) +{ + g_return_if_fail (ebml->streamheader == NULL); + + GST_DEBUG ("Starting streamheader at %" G_GUINT64_FORMAT, ebml->pos); + ebml->streamheader = gst_byte_writer_new_with_size (1000, FALSE); + ebml->streamheader_pos = ebml->pos; + ebml->writing_streamheader = TRUE; +} + +GstBuffer * +gst_ebml_stop_streamheader (GstEbmlWrite * ebml) +{ + GstBuffer *buffer; + + if (!ebml->streamheader) + return NULL; + + buffer = gst_byte_writer_free_and_get_buffer (ebml->streamheader); + ebml->streamheader = NULL; + GST_DEBUG ("Streamheader was size %d", GST_BUFFER_SIZE (buffer)); + + ebml->writing_streamheader = FALSE; + return buffer; +} + +/** + * gst_ebml_write_set_cache: + * @ebml: a #GstEbmlWrite. + * @size: size of the cache. + * Create a cache. + * + * The idea is that you use this for writing a lot + * of small elements. This will just "queue" all of + * them and they'll be pushed to the next element all + * at once. This saves memory and time for buffer + * allocation and init, and it looks better. + */ +void +gst_ebml_write_set_cache (GstEbmlWrite * ebml, guint size) +{ + g_return_if_fail (ebml->cache == NULL); + + GST_DEBUG ("Starting cache at %" G_GUINT64_FORMAT, ebml->pos); + ebml->cache = gst_byte_writer_new_with_size (size, FALSE); + ebml->cache_pos = ebml->pos; +} + +static gboolean +gst_ebml_writer_send_new_segment_event (GstEbmlWrite * ebml, guint64 new_pos) +{ + gboolean res; + + GST_INFO ("seeking to %" G_GUINT64_FORMAT, new_pos); + + res = gst_pad_push_event (ebml->srcpad, + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, new_pos, -1, 0)); + + if (!res) + GST_WARNING ("seek to %" G_GUINT64_FORMAT "failed", new_pos); + + return res; +} + +/** + * gst_ebml_write_flush_cache: + * @ebml: a #GstEbmlWrite. + * @timestamp: timestamp of the buffer. + * + * Flush the cache. + */ +void +gst_ebml_write_flush_cache (GstEbmlWrite * ebml, gboolean is_keyframe, + GstClockTime timestamp) +{ + GstBuffer *buffer; + + if (!ebml->cache) + return; + + buffer = gst_byte_writer_free_and_get_buffer (ebml->cache); + ebml->cache = NULL; + GST_DEBUG ("Flushing cache of size %d", GST_BUFFER_SIZE (buffer)); + gst_buffer_set_caps (buffer, ebml->caps); + GST_BUFFER_TIMESTAMP (buffer) = timestamp; + GST_BUFFER_OFFSET (buffer) = ebml->pos - GST_BUFFER_SIZE (buffer); + GST_BUFFER_OFFSET_END (buffer) = ebml->pos; + if (ebml->last_write_result == GST_FLOW_OK) { + if (GST_BUFFER_OFFSET (buffer) != ebml->last_pos) { + gst_ebml_writer_send_new_segment_event (ebml, GST_BUFFER_OFFSET (buffer)); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + } + if (ebml->writing_streamheader) { + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_IN_CAPS); + } + if (!is_keyframe) { + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + } + ebml->last_pos = ebml->pos; + ebml->last_write_result = gst_pad_push (ebml->srcpad, buffer); + } else { + gst_buffer_unref (buffer); + } +} + + +/** + * gst_ebml_write_element_new: + * @ebml: a #GstEbmlWrite. + * @size: size of the requested buffer. + * + * Create a buffer for one element. If there is + * a cache, use that instead. + * + * Returns: A new #GstBuffer. + */ +static GstBuffer * +gst_ebml_write_element_new (GstEbmlWrite * ebml, guint size) +{ + /* Create new buffer of size + ID + length */ + GstBuffer *buf; + + /* length, ID */ + size += 12; + + buf = gst_buffer_new_and_alloc (size); + GST_BUFFER_SIZE (buf) = 0; + GST_BUFFER_TIMESTAMP (buf) = ebml->timestamp; + + return buf; +} + + +/** + * gst_ebml_write_element_id: + * @buf: Buffer to which id should be written. + * @id: Element ID that should be written. + * + * Write element ID into a buffer. + */ +static void +gst_ebml_write_element_id (GstBuffer * buf, guint32 id) +{ + guint8 *data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + guint bytes = 4, mask = 0x10; + + /* get ID length */ + while (!(id & (mask << ((bytes - 1) * 8))) && bytes > 0) { + mask <<= 1; + bytes--; + } + + /* if invalid ID, use dummy */ + if (bytes == 0) { + GST_WARNING ("Invalid ID, voiding"); + bytes = 1; + id = GST_EBML_ID_VOID; + } + + /* write out, BE */ + GST_BUFFER_SIZE (buf) += bytes; + while (bytes--) { + data[bytes] = id & 0xff; + id >>= 8; + } +} + + +/** + * gst_ebml_write_element_size: + * @buf: #GstBuffer to which size should be written. + * @size: Element length. + * + * Write element length into a buffer. + */ +static void +gst_ebml_write_element_size (GstBuffer * buf, guint64 size) +{ + guint8 *data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + guint bytes = 1, mask = 0x80; + + if (size != GST_EBML_SIZE_UNKNOWN) { + /* how many bytes? - use mask-1 because an all-1 bitset is not allowed */ + while ((size >> ((bytes - 1) * 8)) >= (mask - 1) && bytes <= 8) { + mask >>= 1; + bytes++; + } + + /* if invalid size, use max. */ + if (bytes > 8) { + GST_WARNING ("Invalid size, writing size unknown"); + mask = 0x01; + bytes = 8; + /* Now here's a real FIXME: we cannot read those yet! */ + size = GST_EBML_SIZE_UNKNOWN; + } + } else { + mask = 0x01; + bytes = 8; + } + + /* write out, BE, with length size marker */ + GST_BUFFER_SIZE (buf) += bytes; + while (bytes-- > 0) { + data[bytes] = size & 0xff; + size >>= 8; + if (!bytes) + *data |= mask; + } +} + + +/** + * gst_ebml_write_element_data: + * @buf: #GstBuffer to which data should be written. + * @write: Data that should be written. + * @length: Length of the data. + * + * Write element data into a buffer. + */ +static void +gst_ebml_write_element_data (GstBuffer * buf, guint8 * write, guint64 length) +{ + guint8 *data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + + memcpy (data, write, length); + GST_BUFFER_SIZE (buf) += length; +} + + +/** + * gst_ebml_write_element_push: + * @ebml: #GstEbmlWrite + * @buf: #GstBuffer to be written. + * + * Write out buffer by moving it to the next element. + */ +static void +gst_ebml_write_element_push (GstEbmlWrite * ebml, GstBuffer * buf) +{ + guint data_size = GST_BUFFER_SIZE (buf); + + ebml->pos += data_size; + + /* if there's no cache, then don't push it! */ + if (ebml->writing_streamheader) { + gst_byte_writer_put_data (ebml->streamheader, GST_BUFFER_DATA (buf), + data_size); + } + if (ebml->cache) { + gst_byte_writer_put_data (ebml->cache, GST_BUFFER_DATA (buf), data_size); + gst_buffer_unref (buf); + return; + } + + if (ebml->last_write_result == GST_FLOW_OK) { + buf = gst_buffer_make_metadata_writable (buf); + gst_buffer_set_caps (buf, ebml->caps); + GST_BUFFER_OFFSET (buf) = ebml->pos - GST_BUFFER_SIZE (buf); + GST_BUFFER_OFFSET_END (buf) = ebml->pos; + if (ebml->writing_streamheader) { + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS); + } + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + + if (GST_BUFFER_OFFSET (buf) != ebml->last_pos) { + gst_ebml_writer_send_new_segment_event (ebml, GST_BUFFER_OFFSET (buf)); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + } + ebml->last_pos = ebml->pos; + ebml->last_write_result = gst_pad_push (ebml->srcpad, buf); + } else { + gst_buffer_unref (buf); + } +} + + +/** + * gst_ebml_write_seek: + * @ebml: #GstEbmlWrite + * @pos: Seek position. + * + * Seek. + */ +void +gst_ebml_write_seek (GstEbmlWrite * ebml, guint64 pos) +{ + if (ebml->writing_streamheader) { + GST_DEBUG ("wanting to seek to pos %" G_GUINT64_FORMAT, pos); + if (pos >= ebml->streamheader_pos && + pos <= ebml->streamheader_pos + ebml->streamheader->parent.size) { + gst_byte_writer_set_pos (ebml->streamheader, + pos - ebml->streamheader_pos); + GST_DEBUG ("seeked in streamheader to position %" G_GUINT64_FORMAT, + pos - ebml->streamheader_pos); + } else { + GST_WARNING + ("we are writing streamheader still and seek is out of bounds"); + } + } + /* Cache seeking. A bit dangerous, we assume the client writer + * knows what he's doing... */ + if (ebml->cache) { + /* within bounds? */ + if (pos >= ebml->cache_pos && + pos <= ebml->cache_pos + ebml->cache->parent.size) { + GST_DEBUG ("seeking in cache to %" G_GUINT64_FORMAT, pos); + ebml->pos = pos; + gst_byte_writer_set_pos (ebml->cache, ebml->pos - ebml->cache_pos); + return; + } else { + GST_LOG ("Seek outside cache range. Clearing..."); + gst_ebml_write_flush_cache (ebml, FALSE, GST_CLOCK_TIME_NONE); + } + } + + GST_INFO ("scheduling seek to %" G_GUINT64_FORMAT, pos); + ebml->pos = pos; +} + + +/** + * gst_ebml_write_get_uint_size: + * @num: Number to be encoded. + * + * Get number of bytes needed to write a uint. + * + * Returns: Encoded uint length. + */ +static guint +gst_ebml_write_get_uint_size (guint64 num) +{ + guint size = 1; + + /* get size */ + while (num >= (G_GINT64_CONSTANT (1) << (size * 8)) && size < 8) { + size++; + } + + return size; +} + + +/** + * gst_ebml_write_set_uint: + * @buf: #GstBuffer to which ithe number should be written. + * @num: Number to be written. + * @size: Encoded number length. + * + * Write an uint into a buffer. + */ +static void +gst_ebml_write_set_uint (GstBuffer * buf, guint64 num, guint size) +{ + guint8 *data; + + data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + GST_BUFFER_SIZE (buf) += size; + while (size-- > 0) { + data[size] = num & 0xff; + num >>= 8; + } +} + + +/** + * gst_ebml_write_uint: + * @ebml: #GstEbmlWrite + * @id: Element ID. + * @num: Number to be written. + * + * Write uint element. + */ +void +gst_ebml_write_uint (GstEbmlWrite * ebml, guint32 id, guint64 num) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, sizeof (num)); + guint size = gst_ebml_write_get_uint_size (num); + + /* write */ + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, size); + gst_ebml_write_set_uint (buf, num, size); + gst_ebml_write_element_push (ebml, buf); +} + + +/** + * gst_ebml_write_sint: + * @ebml: #GstEbmlWrite + * @id: Element ID. + * @num: Number to be written. + * + * Write sint element. + */ +void +gst_ebml_write_sint (GstEbmlWrite * ebml, guint32 id, gint64 num) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, sizeof (num)); + + /* if the signed number is on the edge of a extra-byte, + * then we'll fall over when detecting it. Example: if I + * have a number (-)0x8000 (G_MINSHORT), then my abs()<<1 + * will be 0x10000; this is G_MAXUSHORT+1! So: if (<0) -1. */ + guint64 unum = (num < 0 ? (-num - 1) << 1 : num << 1); + guint size = gst_ebml_write_get_uint_size (unum); + + /* make unsigned */ + if (num >= 0) { + unum = num; + } else { + unum = 0x80 << (size - 1); + unum += num; + unum |= 0x80 << (size - 1); + } + + /* write */ + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, size); + gst_ebml_write_set_uint (buf, unum, size); + gst_ebml_write_element_push (ebml, buf); +} + + +/** + * gst_ebml_write_float: + * @ebml: #GstEbmlWrite + * @id: Element ID. + * @num: Number to be written. + * + * Write float element. + */ +void +gst_ebml_write_float (GstEbmlWrite * ebml, guint32 id, gdouble num) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, sizeof (num)); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, 8); + num = GDOUBLE_TO_BE (num); + gst_ebml_write_element_data (buf, (guint8 *) & num, 8); + gst_ebml_write_element_push (ebml, buf); +} + + +/** + * gst_ebml_write_ascii: + * @ebml: #GstEbmlWrite + * @id: Element ID. + * @str: String to be written. + * + * Write string element. + */ +void +gst_ebml_write_ascii (GstEbmlWrite * ebml, guint32 id, const gchar * str) +{ + gint len = strlen (str) + 1; /* add trailing '\0' */ + GstBuffer *buf = gst_ebml_write_element_new (ebml, len); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, len); + gst_ebml_write_element_data (buf, (guint8 *) str, len); + gst_ebml_write_element_push (ebml, buf); +} + + +/** + * gst_ebml_write_utf8: + * @ebml: #GstEbmlWrite + * @id: Element ID. + * @str: String to be written. + * + * Write utf8 encoded string element. + */ +void +gst_ebml_write_utf8 (GstEbmlWrite * ebml, guint32 id, const gchar * str) +{ + gst_ebml_write_ascii (ebml, id, str); +} + + +/** + * gst_ebml_write_date: + * @ebml: #GstEbmlWrite + * @id: Element ID. + * @date: Date in seconds since the unix epoch. + * + * Write date element. + */ +void +gst_ebml_write_date (GstEbmlWrite * ebml, guint32 id, gint64 date) +{ + gst_ebml_write_sint (ebml, id, (date - GST_EBML_DATE_OFFSET) * GST_SECOND); +} + +/** + * gst_ebml_write_master_start: + * @ebml: #GstEbmlWrite + * @id: Element ID. + * + * Start wiriting mater element. + * + * Master writing is annoying. We use a size marker of + * the max. allowed length, so that we can later fill it + * in validly. + * + * Returns: Master starting position. + */ +guint64 +gst_ebml_write_master_start (GstEbmlWrite * ebml, guint32 id) +{ + guint64 pos = ebml->pos, t; + GstBuffer *buf = gst_ebml_write_element_new (ebml, 0); + + t = GST_BUFFER_SIZE (buf); + gst_ebml_write_element_id (buf, id); + pos += GST_BUFFER_SIZE (buf) - t; + gst_ebml_write_element_size (buf, GST_EBML_SIZE_UNKNOWN); + gst_ebml_write_element_push (ebml, buf); + + return pos; +} + + +/** + * gst_ebml_write_master_finish_full: + * @ebml: #GstEbmlWrite + * @startpos: Master starting position. + * + * Finish writing master element. Size of master element is difference between + * current position and the element start, and @extra_size added to this. + */ +void +gst_ebml_write_master_finish_full (GstEbmlWrite * ebml, guint64 startpos, + guint64 extra_size) +{ + guint64 pos = ebml->pos; + GstBuffer *buf; + + gst_ebml_write_seek (ebml, startpos); + buf = gst_buffer_new_and_alloc (8); + GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), + (G_GINT64_CONSTANT (1) << 56) | (pos - startpos - 8 + extra_size)); + gst_ebml_write_element_push (ebml, buf); + gst_ebml_write_seek (ebml, pos); +} + +void +gst_ebml_write_master_finish (GstEbmlWrite * ebml, guint64 startpos) +{ + gst_ebml_write_master_finish_full (ebml, startpos, 0); +} + +/** + * gst_ebml_write_binary: + * @ebml: #GstEbmlWrite + * @id: Element ID. + * @binary: Data to be written. + * @length: Length of the data + * + * Write an element with binary data. + */ +void +gst_ebml_write_binary (GstEbmlWrite * ebml, + guint32 id, guint8 * binary, guint64 length) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, length); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, length); + gst_ebml_write_element_data (buf, binary, length); + gst_ebml_write_element_push (ebml, buf); +} + + +/** + * gst_ebml_write_buffer_header: + * @ebml: #GstEbmlWrite + * @id: Element ID. + * @length: Length of the data + * + * Write header of the binary element (use with gst_ebml_write_buffer function). + * + * For things like video frames and audio samples, + * you want to use this function, as it doesn't have + * the overhead of memcpy() that other functions + * such as write_binary() do have. + */ +void +gst_ebml_write_buffer_header (GstEbmlWrite * ebml, guint32 id, guint64 length) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, 0); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, length); + gst_ebml_write_element_push (ebml, buf); +} + + +/** + * gst_ebml_write_buffer: + * @ebml: #GstEbmlWrite + * @data: #GstBuffer cointaining the data. + * + * Write binary element (see gst_ebml_write_buffer_header). + */ +void +gst_ebml_write_buffer (GstEbmlWrite * ebml, GstBuffer * data) +{ + gst_ebml_write_element_push (ebml, data); +} + + +/** + * gst_ebml_replace_uint: + * @ebml: #GstEbmlWrite + * @pos: Position of the uint that should be replaced. + * @num: New value. + * + * Replace uint with a new value. + * + * When replacing a uint, we assume that it is *always* + * 8-byte, since that's the safest guess we can do. This + * is just for simplicity. + * + * FIXME: this function needs to be replaced with something + * proper. This is a crude hack. + */ +void +gst_ebml_replace_uint (GstEbmlWrite * ebml, guint64 pos, guint64 num) +{ + guint64 oldpos = ebml->pos; + GstBuffer *buf = gst_buffer_new_and_alloc (8); + + gst_ebml_write_seek (ebml, pos); + GST_BUFFER_SIZE (buf) = 0; + gst_ebml_write_set_uint (buf, num, 8); + gst_ebml_write_element_push (ebml, buf); + gst_ebml_write_seek (ebml, oldpos); +} + +/** + * gst_ebml_write_header: + * @ebml: #GstEbmlWrite + * @doctype: Document type. + * @version: Document type version. + * + * Write EBML header. + */ +void +gst_ebml_write_header (GstEbmlWrite * ebml, const gchar * doctype, + guint version) +{ + guint64 pos; + + /* write the basic EBML header */ + gst_ebml_write_set_cache (ebml, 0x40); + pos = gst_ebml_write_master_start (ebml, GST_EBML_ID_HEADER); +#if (GST_EBML_VERSION != 1) + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLVERSION, GST_EBML_VERSION); + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLREADVERSION, GST_EBML_VERSION); +#endif +#if 0 + /* we don't write these until they're "non-default" (never!) */ + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLMAXIDLENGTH, sizeof (guint32)); + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLMAXSIZELENGTH, sizeof (guint64)); +#endif + gst_ebml_write_ascii (ebml, GST_EBML_ID_DOCTYPE, doctype); + gst_ebml_write_uint (ebml, GST_EBML_ID_DOCTYPEVERSION, version); + gst_ebml_write_uint (ebml, GST_EBML_ID_DOCTYPEREADVERSION, version); + gst_ebml_write_master_finish (ebml, pos); + gst_ebml_write_flush_cache (ebml, FALSE, 0); +} diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-write.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/ebml-write.h Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,152 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * (c) 2005 Michal Benes + * + * ebml-write.c: write EBML data to file/stream + * + * 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_EBML_WRITE_H__ +#define __GST_EBML_WRITE_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_EBML_WRITE \ + (gst_ebml_write_get_type ()) +#define GST_EBML_WRITE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_EBML_WRITE, GstEbmlWrite)) +#define GST_EBML_WRITE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_EBML_WRITE, GstEbmlWriteClass)) +#define GST_IS_EBML_WRITE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_EBML_WRITE)) +#define GST_IS_EBML_WRITE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_EBML_WRITE)) +#define GST_EBML_WRITE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_EBML_WRITE, GstEbmlWriteClass)) + +typedef struct _GstEbmlWrite { + GstObject object; + + GstPad *srcpad; + guint64 pos; + guint64 last_pos; + GstClockTime timestamp; + + GstByteWriter *cache; + guint64 cache_pos; + + GstFlowReturn last_write_result; + + gboolean writing_streamheader; + GstByteWriter *streamheader; + guint64 streamheader_pos; + + GstCaps *caps; +} GstEbmlWrite; + +typedef struct _GstEbmlWriteClass { + GstObjectClass parent; +} GstEbmlWriteClass; + +GType gst_ebml_write_get_type (void); + +GstEbmlWrite *gst_ebml_write_new (GstPad *srcpad); +void gst_ebml_write_reset (GstEbmlWrite *ebml); + +GstFlowReturn gst_ebml_last_write_result (GstEbmlWrite *ebml); + +/* Used to create streamheaders */ +void gst_ebml_start_streamheader (GstEbmlWrite *ebml); +GstBuffer* gst_ebml_stop_streamheader (GstEbmlWrite *ebml); + +/* + * Caching means that we do not push one buffer for + * each element, but fill this one until a flush. + */ +void gst_ebml_write_set_cache (GstEbmlWrite *ebml, + guint size); +void gst_ebml_write_flush_cache (GstEbmlWrite *ebml, + gboolean is_keyframe, + GstClockTime timestamp); + +/* + * Seeking. + */ +void gst_ebml_write_seek (GstEbmlWrite *ebml, + guint64 pos); + +/* + * Data writing. + */ +void gst_ebml_write_uint (GstEbmlWrite *ebml, + guint32 id, + guint64 num); +void gst_ebml_write_sint (GstEbmlWrite *ebml, + guint32 id, + gint64 num); +void gst_ebml_write_float (GstEbmlWrite *ebml, + guint32 id, + gdouble num); +void gst_ebml_write_ascii (GstEbmlWrite *ebml, + guint32 id, + const gchar *str); +void gst_ebml_write_utf8 (GstEbmlWrite *ebml, + guint32 id, + const gchar *str); +void gst_ebml_write_date (GstEbmlWrite *ebml, + guint32 id, + gint64 date); +guint64 gst_ebml_write_master_start (GstEbmlWrite *ebml, + guint32 id); +void gst_ebml_write_master_finish (GstEbmlWrite *ebml, + guint64 startpos); +void gst_ebml_write_master_finish_full (GstEbmlWrite * ebml, + guint64 startpos, + guint64 extra_size); +void gst_ebml_write_binary (GstEbmlWrite *ebml, + guint32 id, + guchar *binary, + guint64 length); +void gst_ebml_write_header (GstEbmlWrite *ebml, + const gchar *doctype, + guint version); + +/* + * Note: this is supposed to be used only for media data. + */ +void gst_ebml_write_buffer_header (GstEbmlWrite *ebml, + guint32 id, + guint64 length); +void gst_ebml_write_buffer (GstEbmlWrite *ebml, + GstBuffer *data); + +/* + * A hack, basically... See matroska-mux.c. I should actually + * make a nice _replace_element_with_size() or so, but this + * works for now. + */ +void gst_ebml_replace_uint (GstEbmlWrite *ebml, + guint64 pos, + guint64 num); + +G_END_DECLS + +#endif /* __GST_EBML_WRITE_H__ */ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/lzo.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/lzo.c Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,288 @@ +/* + * LZO 1x decompression + * Copyright (c) 2006 Reimar Doeffinger + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include "_stdint.h" +#include "lzo.h" + +/*! define if we may write up to 12 bytes beyond the output buffer */ +/* #define OUTBUF_PADDED 1 */ +/*! define if we may read up to 8 bytes beyond the input buffer */ +/* #define INBUF_PADDED 1 */ +typedef struct LZOContext +{ + const uint8_t *in, *in_end; + uint8_t *out_start, *out, *out_end; + int error; +} LZOContext; + +/* + * \brief read one byte from input buffer, avoiding overrun + * \return byte read + */ +static inline int +get_byte (LZOContext * c) +{ + if (c->in < c->in_end) + return *c->in++; + c->error |= LZO_INPUT_DEPLETED; + return 1; +} + +#ifdef INBUF_PADDED +#define GETB(c) (*(c).in++) +#else +#define GETB(c) get_byte(&(c)) +#endif + +/* + * \brief decode a length value in the coding used by lzo + * \param x previous byte value + * \param mask bits used from x + * \return decoded length value + */ +static inline int +get_len (LZOContext * c, int x, int mask) +{ + int cnt = x & mask; + if (!cnt) { + while (!(x = get_byte (c))) + cnt += 255; + cnt += mask + x; + } + return cnt; +} + +/*#define UNALIGNED_LOADSTORE */ +#define BUILTIN_MEMCPY +#ifdef UNALIGNED_LOADSTORE +#define COPY2(d, s) *(uint16_t *)(d) = *(uint16_t *)(s); +#define COPY4(d, s) *(uint32_t *)(d) = *(uint32_t *)(s); +#elif defined(BUILTIN_MEMCPY) +#define COPY2(d, s) memcpy(d, s, 2); +#define COPY4(d, s) memcpy(d, s, 4); +#else +#define COPY2(d, s) (d)[0] = (s)[0]; (d)[1] = (s)[1]; +#define COPY4(d, s) (d)[0] = (s)[0]; (d)[1] = (s)[1]; (d)[2] = (s)[2]; (d)[3] = (s)[3]; +#endif + +/* + * \brief copy bytes from input to output buffer with checking + * \param cnt number of bytes to copy, must be >= 0 + */ +static inline void +copy (LZOContext * c, int cnt) +{ + register const uint8_t *src = c->in; + register uint8_t *dst = c->out; + if (cnt > c->in_end - src) { + cnt = MAX (c->in_end - src, 0); + c->error |= LZO_INPUT_DEPLETED; + } + if (cnt > c->out_end - dst) { + cnt = MAX (c->out_end - dst, 0); + c->error |= LZO_OUTPUT_FULL; + } +#if defined(INBUF_PADDED) && defined(OUTBUF_PADDED) + COPY4 (dst, src); + src += 4; + dst += 4; + cnt -= 4; + if (cnt > 0) +#endif + memcpy (dst, src, cnt); + c->in = src + cnt; + c->out = dst + cnt; +} + +/* + * \brief copy previously decoded bytes to current position + * \param back how many bytes back we start + * \param cnt number of bytes to copy, must be >= 0 + * + * cnt > back is valid, this will copy the bytes we just copied, + * thus creating a repeating pattern with a period length of back. + */ +static inline void +copy_backptr (LZOContext * c, int back, int cnt) +{ + register const uint8_t *src = &c->out[-back]; + register uint8_t *dst = c->out; + if (src < c->out_start || src > dst) { + c->error |= LZO_INVALID_BACKPTR; + return; + } + if (cnt > c->out_end - dst) { + cnt = MAX (c->out_end - dst, 0); + c->error |= LZO_OUTPUT_FULL; + } + if (back == 1) { + memset (dst, *src, cnt); + dst += cnt; + } else { +#ifdef OUTBUF_PADDED + COPY2 (dst, src); + COPY2 (dst + 2, src + 2); + src += 4; + dst += 4; + cnt -= 4; + if (cnt > 0) { + COPY2 (dst, src); + COPY2 (dst + 2, src + 2); + COPY2 (dst + 4, src + 4); + COPY2 (dst + 6, src + 6); + src += 8; + dst += 8; + cnt -= 8; + } +#endif + if (cnt > 0) { + int blocklen = back; + while (cnt > blocklen) { + memcpy (dst, src, blocklen); + dst += blocklen; + cnt -= blocklen; + blocklen <<= 1; + } + memcpy (dst, src, cnt); + } + dst += cnt; + } + c->out = dst; +} + +/* + * \brief decode LZO 1x compressed data + * \param out output buffer + * \param outlen size of output buffer, number of bytes left are returned here + * \param in input buffer + * \param inlen size of input buffer, number of bytes left are returned here + * \return 0 on success, otherwise error flags, see lzo.h + * + * make sure all buffers are appropriately padded, in must provide + * LZO_INPUT_PADDING, out must provide LZO_OUTPUT_PADDING additional bytes + */ +int +lzo1x_decode (void *out, int *outlen, const void *in, int *inlen) +{ + int state = 0; + int x; + LZOContext c; + c.in = in; + c.in_end = (const uint8_t *) in + *inlen; + c.out = c.out_start = out; + c.out_end = (uint8_t *) out + *outlen; + c.error = 0; + x = GETB (c); + if (x > 17) { + copy (&c, x - 17); + x = GETB (c); + if (x < 16) + c.error |= LZO_ERROR; + } + if (c.in > c.in_end) + c.error |= LZO_INPUT_DEPLETED; + while (!c.error) { + int cnt, back; + if (x > 15) { + if (x > 63) { + cnt = (x >> 5) - 1; + back = (GETB (c) << 3) + ((x >> 2) & 7) + 1; + } else if (x > 31) { + cnt = get_len (&c, x, 31); + x = GETB (c); + back = (GETB (c) << 6) + (x >> 2) + 1; + } else { + cnt = get_len (&c, x, 7); + back = (1 << 14) + ((x & 8) << 11); + x = GETB (c); + back += (GETB (c) << 6) + (x >> 2); + if (back == (1 << 14)) { + if (cnt != 1) + c.error |= LZO_ERROR; + break; + } + } + } else if (!state) { + cnt = get_len (&c, x, 15); + copy (&c, cnt + 3); + x = GETB (c); + if (x > 15) + continue; + cnt = 1; + back = (1 << 11) + (GETB (c) << 2) + (x >> 2) + 1; + } else { + cnt = 0; + back = (GETB (c) << 2) + (x >> 2) + 1; + } + copy_backptr (&c, back, cnt + 2); + state = cnt = x & 3; + copy (&c, cnt); + x = GETB (c); + } + *inlen = c.in_end - c.in; + if (c.in > c.in_end) + *inlen = 0; + *outlen = c.out_end - c.out; + return c.error; +} + +#ifdef TEST +#include +#include +#include "log.h" +#define MAXSZ (10*1024*1024) +int +main (int argc, char *argv[]) +{ + FILE *in = fopen (argv[1], "rb"); + uint8_t *orig = av_malloc (MAXSZ + 16); + uint8_t *comp = av_malloc (2 * MAXSZ + 16); + uint8_t *decomp = av_malloc (MAXSZ + 16); + size_t s = fread (orig, 1, MAXSZ, in); + lzo_uint clen = 0; + long tmp[LZO1X_MEM_COMPRESS]; + int inlen, outlen; + int i; + av_log_level = AV_LOG_DEBUG; + lzo1x_999_compress (orig, s, comp, &clen, tmp); + for (i = 0; i < 300; i++) { + START_TIMER inlen = clen; + outlen = MAXSZ; +#ifdef LIBLZO + if (lzo1x_decompress_safe (comp, inlen, decomp, &outlen, NULL)) +#elif defined(LIBLZO_UNSAFE) + if (lzo1x_decompress (comp, inlen, decomp, &outlen, NULL)) +#else + if (lzo1x_decode (decomp, &outlen, comp, &inlen)) +#endif + av_log (NULL, AV_LOG_ERROR, "decompression error\n"); + STOP_TIMER ("lzod") + } + if (memcmp (orig, decomp, s)) + av_log (NULL, AV_LOG_ERROR, "decompression incorrect\n"); + else + av_log (NULL, AV_LOG_ERROR, "decompression ok\n"); + return 0; +} +#endif diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/lzo.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/lzo.h Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,35 @@ +/* + * LZO 1x decompression + * copyright (c) 2006 Reimar Doeffinger + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef FFMPEG_LZO_H +#define FFMPEG_LZO_H + +#define LZO_INPUT_DEPLETED 1 +#define LZO_OUTPUT_FULL 2 +#define LZO_INVALID_BACKPTR 4 +#define LZO_ERROR 8 + +#define LZO_INPUT_PADDING 8 +#define LZO_OUTPUT_PADDING 12 + +int lzo1x_decode(void *out, int *outlen, const void *in, int *inlen); + +#endif /* FFMPEG_LZO_H */ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-demux.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-demux.c Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,7141 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * (c) 2006 Tim-Philipp Müller + * (c) 2008 Sebastian Dröge + * + * matroska-demux.c: matroska file/stream demuxer + * + * 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: check CRC32 if present + * TODO: there can be a segment after the first segment. Handle like + * chained oggs. Fixes #334082 + * TODO: Test samples: http://www.matroska.org/samples/matrix/index.html + * http://samples.mplayerhq.hu/Matroska/ + * TODO: check if demuxing is done correct for all codecs according to spec + * TODO: seeking with incomplete or without CUE + */ + +/** + * SECTION:element-matroskademux + * + * matroskademux demuxes a Matroska file into the different contained streams. + * + * + * Example launch line + * |[ + * gst-launch -v filesrc location=/path/to/mkv ! matroskademux ! vorbisdec ! audioconvert ! audioresample ! autoaudiosink + * ]| This pipeline demuxes a Matroska file and outputs the contained Vorbis audio. + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +/* For AVI compatibility mode + and for fourcc stuff */ +#include +#include +#include + +#include + +#include + +#ifdef HAVE_ZLIB +#include +#endif + +#ifdef HAVE_BZ2 +#include +#endif + +#include + +#include "lzo.h" + +#include "matroska-demux.h" +#include "matroska-ids.h" + +GST_DEBUG_CATEGORY_STATIC (matroskademux_debug); +#define GST_CAT_DEFAULT matroskademux_debug + +#define DEBUG_ELEMENT_START(demux, ebml, element) \ + GST_DEBUG_OBJECT (demux, "Parsing " element " element at offset %" \ + G_GUINT64_FORMAT, gst_ebml_read_get_pos (ebml)) + +#define DEBUG_ELEMENT_STOP(demux, ebml, element, ret) \ + GST_DEBUG_OBJECT (demux, "Parsing " element " element " \ + " finished with '%s'", gst_flow_get_name (ret)) + +enum +{ + ARG_0, + ARG_METADATA, + ARG_STREAMINFO +}; + +static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-matroska; video/webm") + ); + +/* TODO: fill in caps! */ + +static GstStaticPadTemplate audio_src_templ = +GST_STATIC_PAD_TEMPLATE ("audio_%02d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("ANY") + ); + +static GstStaticPadTemplate video_src_templ = +GST_STATIC_PAD_TEMPLATE ("video_%02d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("ANY") + ); + +static GstStaticPadTemplate subtitle_src_templ = + GST_STATIC_PAD_TEMPLATE ("subtitle_%02d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("text/plain; application/x-ssa; application/x-ass; " + "application/x-usf; video/x-dvd-subpicture; " + "subpicture/x-pgs; subtitle/x-kate; " "application/x-subtitle-unknown") + ); + +static GstFlowReturn gst_matroska_demux_parse_id (GstMatroskaDemux * demux, + guint32 id, guint64 length, guint needed); + +/* element functions */ +static void gst_matroska_demux_loop (GstPad * pad); + +static gboolean gst_matroska_demux_element_send_event (GstElement * element, + GstEvent * event); +static gboolean gst_matroska_demux_element_query (GstElement * element, + GstQuery * query); + +/* pad functions */ +static gboolean gst_matroska_demux_sink_activate_pull (GstPad * sinkpad, + gboolean active); +static gboolean gst_matroska_demux_sink_activate (GstPad * sinkpad); + +static gboolean gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux, + GstPad * pad, GstEvent * event); +static gboolean gst_matroska_demux_handle_src_event (GstPad * pad, + GstEvent * event); +static const GstQueryType *gst_matroska_demux_get_src_query_types (GstPad * + pad); +static gboolean gst_matroska_demux_handle_src_query (GstPad * pad, + GstQuery * query); + +static gboolean gst_matroska_demux_handle_sink_event (GstPad * pad, + GstEvent * event); +static GstFlowReturn gst_matroska_demux_chain (GstPad * pad, + GstBuffer * buffer); + +static GstStateChangeReturn +gst_matroska_demux_change_state (GstElement * element, + GstStateChange transition); +static void +gst_matroska_demux_set_index (GstElement * element, GstIndex * index); +static GstIndex *gst_matroska_demux_get_index (GstElement * element); + +/* caps functions */ +static GstCaps *gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext + * videocontext, const gchar * codec_id, guint8 * data, guint size, + gchar ** codec_name, guint32 * riff_fourcc); +static GstCaps *gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext + * audiocontext, const gchar * codec_id, guint8 * data, guint size, + gchar ** codec_name, guint16 * riff_audio_fmt); +static GstCaps + * gst_matroska_demux_subtitle_caps (GstMatroskaTrackSubtitleContext * + subtitlecontext, const gchar * codec_id, gpointer data, guint size); + +/* stream methods */ +static void gst_matroska_demux_reset (GstElement * element); +static gboolean perform_seek_to_offset (GstMatroskaDemux * demux, + guint64 offset); + +GType gst_matroska_demux_get_type (void); +GST_BOILERPLATE (GstMatroskaDemux, gst_matroska_demux, GstElement, + GST_TYPE_ELEMENT); + +static void +gst_matroska_demux_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&video_src_templ)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&audio_src_templ)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&subtitle_src_templ)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_templ)); + + gst_element_class_set_details_simple (element_class, "Matroska demuxer", + "Codec/Demuxer", + "Demuxes Matroska/WebM streams into video/audio/subtitles", + "GStreamer maintainers "); +} + +static void +gst_matroska_demux_finalize (GObject * object) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (object); + + if (demux->src) { + g_ptr_array_free (demux->src, TRUE); + demux->src = NULL; + } + + if (demux->global_tags) { + gst_tag_list_free (demux->global_tags); + demux->global_tags = NULL; + } + + g_object_unref (demux->adapter); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_matroska_demux_class_init (GstMatroskaDemuxClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + + GST_DEBUG_CATEGORY_INIT (matroskademux_debug, "matroskademux", 0, + "Matroska demuxer"); + + gobject_class->finalize = gst_matroska_demux_finalize; + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_matroska_demux_change_state); + gstelement_class->send_event = + GST_DEBUG_FUNCPTR (gst_matroska_demux_element_send_event); + gstelement_class->query = + GST_DEBUG_FUNCPTR (gst_matroska_demux_element_query); + + gstelement_class->set_index = + GST_DEBUG_FUNCPTR (gst_matroska_demux_set_index); + gstelement_class->get_index = + GST_DEBUG_FUNCPTR (gst_matroska_demux_get_index); +} + +static void +gst_matroska_demux_init (GstMatroskaDemux * demux, + GstMatroskaDemuxClass * klass) +{ + demux->sinkpad = gst_pad_new_from_static_template (&sink_templ, "sink"); + gst_pad_set_activate_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_matroska_demux_sink_activate)); + gst_pad_set_activatepull_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_matroska_demux_sink_activate_pull)); + gst_pad_set_chain_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_matroska_demux_chain)); + gst_pad_set_event_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_matroska_demux_handle_sink_event)); + gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); + + /* initial stream no. */ + demux->src = NULL; + + demux->writing_app = NULL; + demux->muxing_app = NULL; + demux->index = NULL; + demux->global_tags = NULL; + + demux->adapter = gst_adapter_new (); + + /* finish off */ + gst_matroska_demux_reset (GST_ELEMENT (demux)); +} + +static void +gst_matroska_track_free (GstMatroskaTrackContext * track) +{ + g_free (track->codec_id); + g_free (track->codec_name); + g_free (track->name); + g_free (track->language); + g_free (track->codec_priv); + g_free (track->codec_state); + + if (track->encodings != NULL) { + int i; + + for (i = 0; i < track->encodings->len; ++i) { + GstMatroskaTrackEncoding *enc = &g_array_index (track->encodings, + GstMatroskaTrackEncoding, + i); + + g_free (enc->comp_settings); + } + g_array_free (track->encodings, TRUE); + } + + if (track->pending_tags) + gst_tag_list_free (track->pending_tags); + + if (track->index_table) + g_array_free (track->index_table, TRUE); + + g_free (track); +} + +/* + * Returns the aggregated GstFlowReturn. + */ +static GstFlowReturn +gst_matroska_demux_combine_flows (GstMatroskaDemux * demux, + GstMatroskaTrackContext * track, GstFlowReturn ret) +{ + guint i; + + /* store the value */ + track->last_flow = ret; + + /* any other error that is not-linked can be returned right away */ + if (ret != GST_FLOW_NOT_LINKED) + goto done; + + /* only return NOT_LINKED if all other pads returned NOT_LINKED */ + g_assert (demux->src->len == demux->num_streams); + for (i = 0; i < demux->src->len; i++) { + GstMatroskaTrackContext *ostream = g_ptr_array_index (demux->src, i); + + if (ostream == NULL) + continue; + + ret = ostream->last_flow; + /* some other return value (must be SUCCESS but we can return + * other values as well) */ + if (ret != GST_FLOW_NOT_LINKED) + goto done; + } + /* if we get here, all other pads were unlinked and we return + * NOT_LINKED then */ +done: + GST_LOG_OBJECT (demux, "combined return %s", gst_flow_get_name (ret)); + return ret; +} + +static void +gst_matroska_demux_free_parsed_el (gpointer mem, gpointer user_data) +{ + g_slice_free (guint64, mem); +} + +static void +gst_matroska_demux_reset (GstElement * element) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); + guint i; + + GST_DEBUG_OBJECT (demux, "Resetting state"); + + /* reset input */ + demux->state = GST_MATROSKA_DEMUX_STATE_START; + + /* clean up existing streams */ + if (demux->src) { + g_assert (demux->src->len == demux->num_streams); + for (i = 0; i < demux->src->len; i++) { + GstMatroskaTrackContext *context = g_ptr_array_index (demux->src, i); + + if (context->pad != NULL) + gst_element_remove_pad (GST_ELEMENT (demux), context->pad); + + gst_caps_replace (&context->caps, NULL); + gst_matroska_track_free (context); + } + g_ptr_array_free (demux->src, TRUE); + } + demux->src = g_ptr_array_new (); + + demux->num_streams = 0; + demux->num_a_streams = 0; + demux->num_t_streams = 0; + demux->num_v_streams = 0; + + /* reset media info */ + g_free (demux->writing_app); + demux->writing_app = NULL; + g_free (demux->muxing_app); + demux->muxing_app = NULL; + + /* reset indexes */ + if (demux->index) { + g_array_free (demux->index, TRUE); + demux->index = NULL; + } + + if (demux->clusters) { + g_array_free (demux->clusters, TRUE); + demux->clusters = NULL; + } + + /* reset timers */ + demux->clock = NULL; + demux->time_scale = 1000000; + demux->created = G_MININT64; + + demux->index_parsed = FALSE; + demux->tracks_parsed = FALSE; + demux->segmentinfo_parsed = FALSE; + demux->attachments_parsed = FALSE; + + g_list_foreach (demux->tags_parsed, + (GFunc) gst_matroska_demux_free_parsed_el, NULL); + g_list_free (demux->tags_parsed); + demux->tags_parsed = NULL; + + g_list_foreach (demux->seek_parsed, + (GFunc) gst_matroska_demux_free_parsed_el, NULL); + g_list_free (demux->seek_parsed); + demux->seek_parsed = NULL; + + gst_segment_init (&demux->segment, GST_FORMAT_TIME); + demux->last_stop_end = GST_CLOCK_TIME_NONE; + demux->seek_block = 0; + + demux->offset = 0; + demux->cluster_time = GST_CLOCK_TIME_NONE; + demux->cluster_offset = 0; + demux->next_cluster_offset = 0; + demux->index_offset = 0; + demux->seekable = FALSE; + demux->need_newsegment = FALSE; + demux->building_index = FALSE; + if (demux->seek_event) { + gst_event_unref (demux->seek_event); + demux->seek_event = NULL; + } + + demux->seek_index = NULL; + demux->seek_entry = 0; + + if (demux->close_segment) { + gst_event_unref (demux->close_segment); + demux->close_segment = NULL; + } + + if (demux->new_segment) { + gst_event_unref (demux->new_segment); + demux->new_segment = NULL; + } + + if (demux->element_index) { + gst_object_unref (demux->element_index); + demux->element_index = NULL; + } + demux->element_index_writer_id = -1; + + if (demux->global_tags) { + gst_tag_list_free (demux->global_tags); + } + demux->global_tags = gst_tag_list_new (); + + if (demux->cached_buffer) { + gst_buffer_unref (demux->cached_buffer); + demux->cached_buffer = NULL; + } +} + +/* + * Calls pull_range for (offset,size) without advancing our offset + */ +static GstFlowReturn +gst_matroska_demux_peek_bytes (GstMatroskaDemux * demux, guint64 offset, + guint size, GstBuffer ** p_buf, guint8 ** bytes) +{ + GstFlowReturn ret; + + /* Caching here actually makes much less difference than one would expect. + * We do it mainly to avoid pulling buffers of 1 byte all the time */ + if (demux->cached_buffer) { + guint64 cache_offset = GST_BUFFER_OFFSET (demux->cached_buffer); + guint cache_size = GST_BUFFER_SIZE (demux->cached_buffer); + + if (cache_offset <= demux->offset && + (demux->offset + size) <= (cache_offset + cache_size)) { + if (p_buf) + *p_buf = gst_buffer_create_sub (demux->cached_buffer, + demux->offset - cache_offset, size); + if (bytes) + *bytes = GST_BUFFER_DATA (demux->cached_buffer) + demux->offset - + cache_offset; + return GST_FLOW_OK; + } + /* not enough data in the cache, free cache and get a new one */ + gst_buffer_unref (demux->cached_buffer); + demux->cached_buffer = NULL; + } + + /* refill the cache */ + ret = gst_pad_pull_range (demux->sinkpad, demux->offset, + MAX (size, 64 * 1024), &demux->cached_buffer); + if (ret != GST_FLOW_OK) { + demux->cached_buffer = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (demux->cached_buffer) >= size) { + if (p_buf) + *p_buf = gst_buffer_create_sub (demux->cached_buffer, 0, size); + if (bytes) + *bytes = GST_BUFFER_DATA (demux->cached_buffer); + return GST_FLOW_OK; + } + + /* Not possible to get enough data, try a last time with + * requesting exactly the size we need */ + gst_buffer_unref (demux->cached_buffer); + demux->cached_buffer = NULL; + + ret = + gst_pad_pull_range (demux->sinkpad, demux->offset, size, + &demux->cached_buffer); + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (demux, "pull_range returned %d", ret); + if (p_buf) + *p_buf = NULL; + if (bytes) + *bytes = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (demux->cached_buffer) < size) { + GST_WARNING_OBJECT (demux, "Dropping short buffer at offset %" + G_GUINT64_FORMAT ": wanted %u bytes, got %u bytes", demux->offset, + size, GST_BUFFER_SIZE (demux->cached_buffer)); + + gst_buffer_unref (demux->cached_buffer); + demux->cached_buffer = NULL; + if (p_buf) + *p_buf = NULL; + if (bytes) + *bytes = NULL; + return GST_FLOW_UNEXPECTED; + } + + if (p_buf) + *p_buf = gst_buffer_create_sub (demux->cached_buffer, 0, size); + if (bytes) + *bytes = GST_BUFFER_DATA (demux->cached_buffer); + + return GST_FLOW_OK; +} + +static const guint8 * +gst_matroska_demux_peek_pull (GstMatroskaDemux * demux, guint peek) +{ + guint8 *data = NULL; + + gst_matroska_demux_peek_bytes (demux, demux->offset, peek, NULL, &data); + return data; +} + +static GstFlowReturn +gst_matroska_demux_peek_id_length_pull (GstMatroskaDemux * demux, guint32 * _id, + guint64 * _length, guint * _needed) +{ + return gst_ebml_peek_id_length (_id, _length, _needed, + (GstPeekData) gst_matroska_demux_peek_pull, (gpointer) demux, + GST_ELEMENT_CAST (demux), demux->offset); +} + +static gint64 +gst_matroska_demux_get_length (GstMatroskaDemux * demux) +{ + GstFormat fmt = GST_FORMAT_BYTES; + gint64 end = -1; + + if (!gst_pad_query_peer_duration (demux->sinkpad, &fmt, &end) || + fmt != GST_FORMAT_BYTES || end < 0) + GST_DEBUG_OBJECT (demux, "no upstream length"); + + return end; +} + +static gint +gst_matroska_demux_stream_from_num (GstMatroskaDemux * demux, guint track_num) +{ + guint n; + + g_assert (demux->src->len == demux->num_streams); + for (n = 0; n < demux->src->len; n++) { + GstMatroskaTrackContext *context = g_ptr_array_index (demux->src, n); + + if (context->num == track_num) { + return n; + } + } + + if (n == demux->num_streams) + GST_WARNING_OBJECT (demux, + "Failed to find corresponding pad for tracknum %d", track_num); + + return -1; +} + +static gint +gst_matroska_demux_encoding_cmp (GstMatroskaTrackEncoding * a, + GstMatroskaTrackEncoding * b) +{ + if (b->order > a->order) + return 1; + else if (b->order < a->order) + return -1; + else + return 0; +} + +static gboolean +gst_matroska_demux_encoding_order_unique (GArray * encodings, guint64 order) +{ + gint i; + + if (encodings == NULL || encodings->len == 0) + return TRUE; + + for (i = 0; i < encodings->len; i++) + if (g_array_index (encodings, GstMatroskaTrackEncoding, i).order == order) + return FALSE; + + return TRUE; +} + +static GstFlowReturn +gst_matroska_demux_read_track_encoding (GstMatroskaDemux * demux, + GstEbmlRead * ebml, GstMatroskaTrackContext * context) +{ + GstMatroskaTrackEncoding enc = { 0, }; + GstFlowReturn ret; + guint32 id; + + DEBUG_ELEMENT_START (demux, ebml, "ContentEncoding"); + /* Set default values */ + enc.scope = 1; + /* All other default values are 0 */ + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "ContentEncoding", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_CONTENTENCODINGORDER:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (!gst_matroska_demux_encoding_order_unique (context->encodings, num)) { + GST_ERROR_OBJECT (demux, "ContentEncodingOrder %" G_GUINT64_FORMAT + "is not unique for track %d", num, context->num); + ret = GST_FLOW_ERROR; + break; + } + + GST_DEBUG_OBJECT (demux, "ContentEncodingOrder: %" G_GUINT64_FORMAT, + num); + enc.order = num; + break; + } + case GST_MATROSKA_ID_CONTENTENCODINGSCOPE:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num > 7 && num == 0) { + GST_ERROR_OBJECT (demux, "Invalid ContentEncodingScope %" + G_GUINT64_FORMAT, num); + ret = GST_FLOW_ERROR; + break; + } + + GST_DEBUG_OBJECT (demux, "ContentEncodingScope: %" G_GUINT64_FORMAT, + num); + enc.scope = num; + + break; + } + case GST_MATROSKA_ID_CONTENTENCODINGTYPE:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num > 1) { + GST_ERROR_OBJECT (demux, "Invalid ContentEncodingType %" + G_GUINT64_FORMAT, num); + ret = GST_FLOW_ERROR; + break; + } else if (num != 0) { + GST_ERROR_OBJECT (demux, "Encrypted tracks are not supported yet"); + ret = GST_FLOW_ERROR; + break; + } + GST_DEBUG_OBJECT (demux, "ContentEncodingType: %" G_GUINT64_FORMAT, + num); + enc.type = num; + break; + } + case GST_MATROSKA_ID_CONTENTCOMPRESSION:{ + + DEBUG_ELEMENT_START (demux, ebml, "ContentCompression"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) + break; + + while (ret == GST_FLOW_OK && + gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_CONTENTCOMPALGO:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) { + break; + } + if (num > 3) { + GST_ERROR_OBJECT (demux, "Invalid ContentCompAlgo %" + G_GUINT64_FORMAT, num); + ret = GST_FLOW_ERROR; + break; + } + GST_DEBUG_OBJECT (demux, "ContentCompAlgo: %" G_GUINT64_FORMAT, + num); + enc.comp_algo = num; + + break; + } + case GST_MATROSKA_ID_CONTENTCOMPSETTINGS:{ + guint8 *data; + guint64 size; + + if ((ret = + gst_ebml_read_binary (ebml, &id, &data, + &size)) != GST_FLOW_OK) { + break; + } + enc.comp_settings = data; + enc.comp_settings_length = size; + GST_DEBUG_OBJECT (demux, + "ContentCompSettings of size %" G_GUINT64_FORMAT, size); + break; + } + default: + GST_WARNING_OBJECT (demux, + "Unknown ContentCompression subelement 0x%x - ignoring", id); + ret = gst_ebml_read_skip (ebml); + break; + } + } + DEBUG_ELEMENT_STOP (demux, ebml, "ContentCompression", ret); + break; + } + + case GST_MATROSKA_ID_CONTENTENCRYPTION: + GST_ERROR_OBJECT (demux, "Encrypted tracks not yet supported"); + gst_ebml_read_skip (ebml); + ret = GST_FLOW_ERROR; + break; + default: + GST_WARNING_OBJECT (demux, + "Unknown ContentEncoding subelement 0x%x - ignoring", id); + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "ContentEncoding", ret); + if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED) + return ret; + + /* TODO: Check if the combination of values is valid */ + + g_array_append_val (context->encodings, enc); + + return ret; +} + +static gboolean +gst_matroska_decompress_data (GstMatroskaTrackEncoding * enc, + guint8 ** data_out, guint * size_out, + GstMatroskaTrackCompressionAlgorithm algo) +{ + guint8 *new_data = NULL; + guint new_size = 0; + guint8 *data = *data_out; + guint size = *size_out; + gboolean ret = TRUE; + + if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_ZLIB) { +#ifdef HAVE_ZLIB + /* zlib encoded data */ + z_stream zstream; + guint orig_size; + int result; + + orig_size = size; + zstream.zalloc = (alloc_func) 0; + zstream.zfree = (free_func) 0; + zstream.opaque = (voidpf) 0; + if (inflateInit (&zstream) != Z_OK) { + GST_WARNING ("zlib initialization failed."); + ret = FALSE; + goto out; + } + zstream.next_in = (Bytef *) data; + zstream.avail_in = orig_size; + new_size = orig_size; + new_data = g_malloc (new_size); + zstream.avail_out = new_size; + zstream.next_out = (Bytef *) new_data; + + do { + result = inflate (&zstream, Z_NO_FLUSH); + if (result != Z_OK && result != Z_STREAM_END) { + GST_WARNING ("zlib decompression failed."); + g_free (new_data); + inflateEnd (&zstream); + break; + } + new_size += 4000; + new_data = g_realloc (new_data, new_size); + zstream.next_out = (Bytef *) (new_data + zstream.total_out); + zstream.avail_out += 4000; + } while (zstream.avail_in != 0 && result != Z_STREAM_END); + + if (result != Z_STREAM_END) { + ret = FALSE; + goto out; + } else { + new_size = zstream.total_out; + inflateEnd (&zstream); + } +#else + GST_WARNING ("zlib encoded tracks not supported."); + ret = FALSE; + goto out; +#endif + } else if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_BZLIB) { +#ifdef HAVE_BZ2 + /* bzip2 encoded data */ + bz_stream bzstream; + guint orig_size; + int result; + + bzstream.bzalloc = NULL; + bzstream.bzfree = NULL; + bzstream.opaque = NULL; + orig_size = size; + + if (BZ2_bzDecompressInit (&bzstream, 0, 0) != BZ_OK) { + GST_WARNING ("bzip2 initialization failed."); + ret = FALSE; + goto out; + } + + bzstream.next_in = (char *) data; + bzstream.avail_in = orig_size; + new_size = orig_size; + new_data = g_malloc (new_size); + bzstream.avail_out = new_size; + bzstream.next_out = (char *) new_data; + + do { + result = BZ2_bzDecompress (&bzstream); + if (result != BZ_OK && result != BZ_STREAM_END) { + GST_WARNING ("bzip2 decompression failed."); + g_free (new_data); + BZ2_bzDecompressEnd (&bzstream); + break; + } + new_size += 4000; + new_data = g_realloc (new_data, new_size); + bzstream.next_out = (char *) (new_data + bzstream.total_out_lo32); + bzstream.avail_out += 4000; + } while (bzstream.avail_in != 0 && result != BZ_STREAM_END); + + if (result != BZ_STREAM_END) { + ret = FALSE; + goto out; + } else { + new_size = bzstream.total_out_lo32; + BZ2_bzDecompressEnd (&bzstream); + } +#else + GST_WARNING ("bzip2 encoded tracks not supported."); + ret = FALSE; + goto out; +#endif + } else if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_LZO1X) { + /* lzo encoded data */ + int result; + int orig_size, out_size; + + orig_size = size; + out_size = size; + new_size = size; + new_data = g_malloc (new_size); + + do { + orig_size = size; + out_size = new_size; + + result = lzo1x_decode (new_data, &out_size, data, &orig_size); + + if (orig_size > 0) { + new_size += 4000; + new_data = g_realloc (new_data, new_size); + } + } while (orig_size > 0 && result == LZO_OUTPUT_FULL); + + new_size -= out_size; + + if (result != LZO_OUTPUT_FULL) { + GST_WARNING ("lzo decompression failed"); + g_free (new_data); + + ret = FALSE; + goto out; + } + + } else if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_HEADERSTRIP) { + /* header stripped encoded data */ + if (enc->comp_settings_length > 0) { + new_data = g_malloc (size + enc->comp_settings_length); + new_size = size + enc->comp_settings_length; + + memcpy (new_data, enc->comp_settings, enc->comp_settings_length); + memcpy (new_data + enc->comp_settings_length, data, size); + } + } else { + GST_ERROR ("invalid compression algorithm %d", algo); + ret = FALSE; + } + +out: + + if (!ret) { + *data_out = NULL; + *size_out = 0; + } else { + *data_out = new_data; + *size_out = new_size; + } + + return ret; +} + +static gboolean +gst_matroska_decode_data (GArray * encodings, guint8 ** data_out, + guint * size_out, GstMatroskaTrackEncodingScope scope, gboolean free) +{ + guint8 *data; + guint size; + gboolean ret = TRUE; + gint i; + + g_return_val_if_fail (encodings != NULL, FALSE); + g_return_val_if_fail (data_out != NULL && *data_out != NULL, FALSE); + g_return_val_if_fail (size_out != NULL, FALSE); + + data = *data_out; + size = *size_out; + + for (i = 0; i < encodings->len; i++) { + GstMatroskaTrackEncoding *enc = + &g_array_index (encodings, GstMatroskaTrackEncoding, i); + guint8 *new_data = NULL; + guint new_size = 0; + + if ((enc->scope & scope) == 0) + continue; + + /* Encryption not supported yet */ + if (enc->type != 0) { + ret = FALSE; + break; + } + + new_data = data; + new_size = size; + + ret = + gst_matroska_decompress_data (enc, &new_data, &new_size, + enc->comp_algo); + + if (!ret) + break; + + if ((data == *data_out && free) || (data != *data_out)) + g_free (data); + + data = new_data; + size = new_size; + } + + if (!ret) { + if ((data == *data_out && free) || (data != *data_out)) + g_free (data); + + *data_out = NULL; + *size_out = 0; + } else { + *data_out = data; + *size_out = size; + } + + return ret; +} + +static GstBuffer * +gst_matroska_decode_buffer (GstMatroskaTrackContext * context, GstBuffer * buf) +{ + guint8 *data; + guint size; + GstBuffer *new_buf; + + g_return_val_if_fail (GST_IS_BUFFER (buf), NULL); + + GST_DEBUG ("decoding buffer %p", buf); + + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + + g_return_val_if_fail (data != NULL && size > 0, buf); + + if (gst_matroska_decode_data (context->encodings, &data, &size, + GST_MATROSKA_TRACK_ENCODING_SCOPE_FRAME, FALSE)) { + new_buf = gst_buffer_new (); + GST_BUFFER_MALLOCDATA (new_buf) = (guint8 *) data; + GST_BUFFER_DATA (new_buf) = (guint8 *) data; + GST_BUFFER_SIZE (new_buf) = size; + + gst_buffer_unref (buf); + buf = new_buf; + + return buf; + } else { + GST_DEBUG ("decode data failed"); + gst_buffer_unref (buf); + return NULL; + } +} + +static GstFlowReturn +gst_matroska_decode_content_encodings (GArray * encodings) +{ + gint i; + + if (encodings == NULL) + return GST_FLOW_OK; + + for (i = 0; i < encodings->len; i++) { + GstMatroskaTrackEncoding *enc = + &g_array_index (encodings, GstMatroskaTrackEncoding, i); + guint8 *data = NULL; + guint size; + + if ((enc->scope & GST_MATROSKA_TRACK_ENCODING_SCOPE_NEXT_CONTENT_ENCODING) + == 0) + continue; + + /* Encryption not supported yet */ + if (enc->type != 0) + return GST_FLOW_ERROR; + + if (i + 1 >= encodings->len) + return GST_FLOW_ERROR; + + if (enc->comp_settings_length == 0) + continue; + + data = enc->comp_settings; + size = enc->comp_settings_length; + + if (!gst_matroska_decompress_data (enc, &data, &size, enc->comp_algo)) + return GST_FLOW_ERROR; + + g_free (enc->comp_settings); + + enc->comp_settings = data; + enc->comp_settings_length = size; + } + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_matroska_demux_read_track_encodings (GstMatroskaDemux * demux, + GstEbmlRead * ebml, GstMatroskaTrackContext * context) +{ + GstFlowReturn ret; + guint32 id; + + DEBUG_ELEMENT_START (demux, ebml, "ContentEncodings"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "ContentEncodings", ret); + return ret; + } + + context->encodings = + g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaTrackEncoding), 1); + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_CONTENTENCODING: + ret = gst_matroska_demux_read_track_encoding (demux, ebml, context); + break; + default: + GST_WARNING_OBJECT (demux, + "Unknown ContentEncodings subelement 0x%x - ignoring", id); + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "ContentEncodings", ret); + if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED) + return ret; + + /* Sort encodings according to their order */ + g_array_sort (context->encodings, + (GCompareFunc) gst_matroska_demux_encoding_cmp); + + return gst_matroska_decode_content_encodings (context->encodings); +} + +static gboolean +gst_matroska_demux_tracknumber_unique (GstMatroskaDemux * demux, guint64 num) +{ + gint i; + + g_assert (demux->src->len == demux->num_streams); + for (i = 0; i < demux->src->len; i++) { + GstMatroskaTrackContext *context = g_ptr_array_index (demux->src, i); + + if (context->num == num) + return FALSE; + } + + return TRUE; +} + +static GstFlowReturn +gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (demux); + GstMatroskaTrackContext *context; + GstPadTemplate *templ = NULL; + GstCaps *caps = NULL; + gchar *padname = NULL; + GstFlowReturn ret; + guint32 id, riff_fourcc = 0; + guint16 riff_audio_fmt = 0; + GstTagList *list = NULL; + gchar *codec = NULL; + + DEBUG_ELEMENT_START (demux, ebml, "TrackEntry"); + + /* start with the master */ + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "TrackEntry", ret); + return ret; + } + + /* allocate generic... if we know the type, we'll g_renew() + * with the precise type */ + context = g_new0 (GstMatroskaTrackContext, 1); + g_ptr_array_add (demux->src, context); + context->index = demux->num_streams; + context->index_writer_id = -1; + context->type = 0; /* no type yet */ + context->default_duration = 0; + context->pos = 0; + context->set_discont = TRUE; + context->timecodescale = 1.0; + context->flags = + GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT | + GST_MATROSKA_TRACK_LACING; + context->last_flow = GST_FLOW_OK; + context->to_offset = G_MAXINT64; + demux->num_streams++; + g_assert (demux->src->len == demux->num_streams); + + GST_DEBUG_OBJECT (demux, "Stream number %d", context->index); + + /* try reading the trackentry headers */ + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* track number (unique stream ID) */ + case GST_MATROSKA_ID_TRACKNUMBER:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_ERROR_OBJECT (demux, "Invalid TrackNumber 0"); + ret = GST_FLOW_ERROR; + break; + } else if (!gst_matroska_demux_tracknumber_unique (demux, num)) { + GST_ERROR_OBJECT (demux, "TrackNumber %" G_GUINT64_FORMAT + " is not unique", num); + ret = GST_FLOW_ERROR; + break; + } + + GST_DEBUG_OBJECT (demux, "TrackNumber: %" G_GUINT64_FORMAT, num); + context->num = num; + break; + } + /* track UID (unique identifier) */ + case GST_MATROSKA_ID_TRACKUID:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_ERROR_OBJECT (demux, "Invalid TrackUID 0"); + ret = GST_FLOW_ERROR; + break; + } + + GST_DEBUG_OBJECT (demux, "TrackUID: %" G_GUINT64_FORMAT, num); + context->uid = num; + break; + } + + /* track type (video, audio, combined, subtitle, etc.) */ + case GST_MATROSKA_ID_TRACKTYPE:{ + guint64 track_type; + + if ((ret = gst_ebml_read_uint (ebml, &id, &track_type)) != GST_FLOW_OK) { + break; + } + + if (context->type != 0 && context->type != track_type) { + GST_WARNING_OBJECT (demux, + "More than one tracktype defined in a TrackEntry - skipping"); + break; + } else if (track_type < 1 || track_type > 254) { + GST_WARNING_OBJECT (demux, "Invalid TrackType %" G_GUINT64_FORMAT, + track_type); + break; + } + + GST_DEBUG_OBJECT (demux, "TrackType: %" G_GUINT64_FORMAT, track_type); + + /* ok, so we're actually going to reallocate this thing */ + switch (track_type) { + case GST_MATROSKA_TRACK_TYPE_VIDEO: + gst_matroska_track_init_video_context (&context); + break; + case GST_MATROSKA_TRACK_TYPE_AUDIO: + gst_matroska_track_init_audio_context (&context); + break; + case GST_MATROSKA_TRACK_TYPE_SUBTITLE: + gst_matroska_track_init_subtitle_context (&context); + break; + case GST_MATROSKA_TRACK_TYPE_COMPLEX: + case GST_MATROSKA_TRACK_TYPE_LOGO: + case GST_MATROSKA_TRACK_TYPE_BUTTONS: + case GST_MATROSKA_TRACK_TYPE_CONTROL: + default: + GST_WARNING_OBJECT (demux, + "Unknown or unsupported TrackType %" G_GUINT64_FORMAT, + track_type); + context->type = 0; + break; + } + g_ptr_array_index (demux->src, demux->num_streams - 1) = context; + break; + } + + /* tracktype specific stuff for video */ + case GST_MATROSKA_ID_TRACKVIDEO:{ + GstMatroskaTrackVideoContext *videocontext; + + DEBUG_ELEMENT_START (demux, ebml, "TrackVideo"); + + if (!gst_matroska_track_init_video_context (&context)) { + GST_WARNING_OBJECT (demux, + "TrackVideo element in non-video track - ignoring track"); + ret = GST_FLOW_ERROR; + break; + } else if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + break; + } + videocontext = (GstMatroskaTrackVideoContext *) context; + g_ptr_array_index (demux->src, demux->num_streams - 1) = context; + + while (ret == GST_FLOW_OK && + gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* Should be one level up but some broken muxers write it here. */ + case GST_MATROSKA_ID_TRACKDEFAULTDURATION:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid TrackDefaultDuration 0"); + break; + } + + GST_DEBUG_OBJECT (demux, + "TrackDefaultDuration: %" G_GUINT64_FORMAT, num); + context->default_duration = num; + break; + } + + /* video framerate */ + /* NOTE: This one is here only for backward compatibility. + * Use _TRACKDEFAULDURATION one level up. */ + case GST_MATROSKA_ID_VIDEOFRAMERATE:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num <= 0.0) { + GST_WARNING_OBJECT (demux, "Invalid TrackVideoFPS %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "TrackVideoFrameRate: %lf", num); + if (context->default_duration == 0) + context->default_duration = + gst_gdouble_to_guint64 ((gdouble) GST_SECOND * (1.0 / num)); + videocontext->default_fps = num; + break; + } + + /* width of the size to display the video at */ + case GST_MATROSKA_ID_VIDEODISPLAYWIDTH:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid TrackVideoDisplayWidth 0"); + break; + } + + GST_DEBUG_OBJECT (demux, + "TrackVideoDisplayWidth: %" G_GUINT64_FORMAT, num); + videocontext->display_width = num; + break; + } + + /* height of the size to display the video at */ + case GST_MATROSKA_ID_VIDEODISPLAYHEIGHT:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid TrackVideoDisplayHeight 0"); + break; + } + + GST_DEBUG_OBJECT (demux, + "TrackVideoDisplayHeight: %" G_GUINT64_FORMAT, num); + videocontext->display_height = num; + break; + } + + /* width of the video in the file */ + case GST_MATROSKA_ID_VIDEOPIXELWIDTH:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid TrackVideoPixelWidth 0"); + break; + } + + GST_DEBUG_OBJECT (demux, + "TrackVideoPixelWidth: %" G_GUINT64_FORMAT, num); + videocontext->pixel_width = num; + break; + } + + /* height of the video in the file */ + case GST_MATROSKA_ID_VIDEOPIXELHEIGHT:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid TrackVideoPixelHeight 0"); + break; + } + + GST_DEBUG_OBJECT (demux, + "TrackVideoPixelHeight: %" G_GUINT64_FORMAT, num); + videocontext->pixel_height = num; + break; + } + + /* whether the video is interlaced */ + case GST_MATROSKA_ID_VIDEOFLAGINTERLACED:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED; + else + context->flags &= ~GST_MATROSKA_VIDEOTRACK_INTERLACED; + GST_DEBUG_OBJECT (demux, "TrackVideoInterlaced: %d", + (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED) ? 1 : + 0); + break; + } + + /* aspect ratio behaviour */ + case GST_MATROSKA_ID_VIDEOASPECTRATIOTYPE:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num != GST_MATROSKA_ASPECT_RATIO_MODE_FREE && + num != GST_MATROSKA_ASPECT_RATIO_MODE_KEEP && + num != GST_MATROSKA_ASPECT_RATIO_MODE_FIXED) { + GST_WARNING_OBJECT (demux, + "Unknown TrackVideoAspectRatioType 0x%x", (guint) num); + break; + } + GST_DEBUG_OBJECT (demux, + "TrackVideoAspectRatioType: %" G_GUINT64_FORMAT, num); + videocontext->asr_mode = num; + break; + } + + /* colourspace (only matters for raw video) fourcc */ + case GST_MATROSKA_ID_VIDEOCOLOURSPACE:{ + guint8 *data; + guint64 datalen; + + if ((ret = + gst_ebml_read_binary (ebml, &id, &data, + &datalen)) != GST_FLOW_OK) + break; + + if (datalen != 4) { + g_free (data); + GST_WARNING_OBJECT (demux, + "Invalid TrackVideoColourSpace length %" G_GUINT64_FORMAT, + datalen); + break; + } + + memcpy (&videocontext->fourcc, data, 4); + GST_DEBUG_OBJECT (demux, + "TrackVideoColourSpace: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (videocontext->fourcc)); + g_free (data); + break; + } + + default: + GST_WARNING_OBJECT (demux, + "Unknown TrackVideo subelement 0x%x - ignoring", id); + /* fall through */ + case GST_MATROSKA_ID_VIDEOSTEREOMODE: + case GST_MATROSKA_ID_VIDEODISPLAYUNIT: + case GST_MATROSKA_ID_VIDEOPIXELCROPBOTTOM: + case GST_MATROSKA_ID_VIDEOPIXELCROPTOP: + case GST_MATROSKA_ID_VIDEOPIXELCROPLEFT: + case GST_MATROSKA_ID_VIDEOPIXELCROPRIGHT: + case GST_MATROSKA_ID_VIDEOGAMMAVALUE: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "TrackVideo", ret); + break; + } + + /* tracktype specific stuff for audio */ + case GST_MATROSKA_ID_TRACKAUDIO:{ + GstMatroskaTrackAudioContext *audiocontext; + + DEBUG_ELEMENT_START (demux, ebml, "TrackAudio"); + + if (!gst_matroska_track_init_audio_context (&context)) { + GST_WARNING_OBJECT (demux, + "TrackAudio element in non-audio track - ignoring track"); + ret = GST_FLOW_ERROR; + break; + } + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) + break; + + audiocontext = (GstMatroskaTrackAudioContext *) context; + g_ptr_array_index (demux->src, demux->num_streams - 1) = context; + + while (ret == GST_FLOW_OK && + gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* samplerate */ + case GST_MATROSKA_ID_AUDIOSAMPLINGFREQ:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + + if (num <= 0.0) { + GST_WARNING_OBJECT (demux, + "Invalid TrackAudioSamplingFrequency %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "TrackAudioSamplingFrequency: %lf", num); + audiocontext->samplerate = num; + break; + } + + /* bitdepth */ + case GST_MATROSKA_ID_AUDIOBITDEPTH:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid TrackAudioBitDepth 0"); + break; + } + + GST_DEBUG_OBJECT (demux, "TrackAudioBitDepth: %" G_GUINT64_FORMAT, + num); + audiocontext->bitdepth = num; + break; + } + + /* channels */ + case GST_MATROSKA_ID_AUDIOCHANNELS:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid TrackAudioChannels 0"); + break; + } + + GST_DEBUG_OBJECT (demux, "TrackAudioChannels: %" G_GUINT64_FORMAT, + num); + audiocontext->channels = num; + break; + } + + default: + GST_WARNING_OBJECT (demux, + "Unknown TrackAudio subelement 0x%x - ignoring", id); + /* fall through */ + case GST_MATROSKA_ID_AUDIOCHANNELPOSITIONS: + case GST_MATROSKA_ID_AUDIOOUTPUTSAMPLINGFREQ: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "TrackAudio", ret); + + break; + } + + /* codec identifier */ + case GST_MATROSKA_ID_CODECID:{ + gchar *text; + + if ((ret = gst_ebml_read_ascii (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "CodecID: %s", GST_STR_NULL (text)); + context->codec_id = text; + break; + } + + /* codec private data */ + case GST_MATROSKA_ID_CODECPRIVATE:{ + guint8 *data; + guint64 size; + + if ((ret = + gst_ebml_read_binary (ebml, &id, &data, &size)) != GST_FLOW_OK) + break; + + context->codec_priv = data; + context->codec_priv_size = size; + + GST_DEBUG_OBJECT (demux, "CodecPrivate of size %" G_GUINT64_FORMAT, + size); + break; + } + + /* name of the codec */ + case GST_MATROSKA_ID_CODECNAME:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "CodecName: %s", GST_STR_NULL (text)); + context->codec_name = text; + break; + } + + /* name of this track */ + case GST_MATROSKA_ID_TRACKNAME:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + context->name = text; + GST_DEBUG_OBJECT (demux, "TrackName: %s", GST_STR_NULL (text)); + break; + } + + /* language (matters for audio/subtitles, mostly) */ + case GST_MATROSKA_ID_TRACKLANGUAGE:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + + context->language = text; + + /* fre-ca => fre */ + if (strlen (context->language) >= 4 && context->language[3] == '-') + context->language[3] = '\0'; + + GST_DEBUG_OBJECT (demux, "TrackLanguage: %s", + GST_STR_NULL (context->language)); + break; + } + + /* whether this is actually used */ + case GST_MATROSKA_ID_TRACKFLAGENABLED:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_TRACK_ENABLED; + else + context->flags &= ~GST_MATROSKA_TRACK_ENABLED; + + GST_DEBUG_OBJECT (demux, "TrackEnabled: %d", + (context->flags & GST_MATROSKA_TRACK_ENABLED) ? 1 : 0); + break; + } + + /* whether it's the default for this track type */ + case GST_MATROSKA_ID_TRACKFLAGDEFAULT:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_TRACK_DEFAULT; + else + context->flags &= ~GST_MATROSKA_TRACK_DEFAULT; + + GST_DEBUG_OBJECT (demux, "TrackDefault: %d", + (context->flags & GST_MATROSKA_TRACK_ENABLED) ? 1 : 0); + break; + } + + /* whether the track must be used during playback */ + case GST_MATROSKA_ID_TRACKFLAGFORCED:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_TRACK_FORCED; + else + context->flags &= ~GST_MATROSKA_TRACK_FORCED; + + GST_DEBUG_OBJECT (demux, "TrackForced: %d", + (context->flags & GST_MATROSKA_TRACK_ENABLED) ? 1 : 0); + break; + } + + /* lacing (like MPEG, where blocks don't end/start on frame + * boundaries) */ + case GST_MATROSKA_ID_TRACKFLAGLACING:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_TRACK_LACING; + else + context->flags &= ~GST_MATROSKA_TRACK_LACING; + + GST_DEBUG_OBJECT (demux, "TrackLacing: %d", + (context->flags & GST_MATROSKA_TRACK_ENABLED) ? 1 : 0); + break; + } + + /* default length (in time) of one data block in this track */ + case GST_MATROSKA_ID_TRACKDEFAULTDURATION:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid TrackDefaultDuration 0"); + break; + } + + GST_DEBUG_OBJECT (demux, "TrackDefaultDuration: %" G_GUINT64_FORMAT, + num); + context->default_duration = num; + break; + } + + case GST_MATROSKA_ID_CONTENTENCODINGS:{ + ret = gst_matroska_demux_read_track_encodings (demux, ebml, context); + break; + } + + case GST_MATROSKA_ID_TRACKTIMECODESCALE:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num <= 0.0) { + GST_WARNING_OBJECT (demux, "Invalid TrackTimeCodeScale %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "TrackTimeCodeScale: %lf", num); + context->timecodescale = num; + break; + } + + default: + GST_WARNING ("Unknown TrackEntry subelement 0x%x - ignoring", id); + /* pass-through */ + + /* we ignore these because they're nothing useful (i.e. crap) + * or simply not implemented yet. */ + case GST_MATROSKA_ID_TRACKMINCACHE: + case GST_MATROSKA_ID_TRACKMAXCACHE: + case GST_MATROSKA_ID_MAXBLOCKADDITIONID: + case GST_MATROSKA_ID_TRACKATTACHMENTLINK: + case GST_MATROSKA_ID_TRACKOVERLAY: + case GST_MATROSKA_ID_TRACKTRANSLATE: + case GST_MATROSKA_ID_TRACKOFFSET: + case GST_MATROSKA_ID_CODECSETTINGS: + case GST_MATROSKA_ID_CODECINFOURL: + case GST_MATROSKA_ID_CODECDOWNLOADURL: + case GST_MATROSKA_ID_CODECDECODEALL: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "TrackEntry", ret); + + /* Decode codec private data if necessary */ + if (context->encodings && context->encodings->len > 0 && context->codec_priv + && context->codec_priv_size > 0) { + if (!gst_matroska_decode_data (context->encodings, + &context->codec_priv, &context->codec_priv_size, + GST_MATROSKA_TRACK_ENCODING_SCOPE_CODEC_DATA, TRUE)) { + GST_WARNING_OBJECT (demux, "Decoding codec private data failed"); + ret = GST_FLOW_ERROR; + } + } + + if (context->type == 0 || context->codec_id == NULL || (ret != GST_FLOW_OK + && ret != GST_FLOW_UNEXPECTED)) { + if (ret == GST_FLOW_OK || ret == GST_FLOW_UNEXPECTED) + GST_WARNING_OBJECT (ebml, "Unknown stream/codec in track entry header"); + + demux->num_streams--; + g_ptr_array_remove_index (demux->src, demux->num_streams); + g_assert (demux->src->len == demux->num_streams); + if (context) { + gst_matroska_track_free (context); + } + + return ret; + } + + /* now create the GStreamer connectivity */ + switch (context->type) { + case GST_MATROSKA_TRACK_TYPE_VIDEO:{ + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + + padname = g_strdup_printf ("video_%02d", demux->num_v_streams++); + templ = gst_element_class_get_pad_template (klass, "video_%02d"); + caps = gst_matroska_demux_video_caps (videocontext, + context->codec_id, (guint8 *) context->codec_priv, + context->codec_priv_size, &codec, &riff_fourcc); + + if (codec) { + list = gst_tag_list_new (); + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_VIDEO_CODEC, codec, NULL); + g_free (codec); + } + break; + } + + case GST_MATROSKA_TRACK_TYPE_AUDIO:{ + GstMatroskaTrackAudioContext *audiocontext = + (GstMatroskaTrackAudioContext *) context; + + padname = g_strdup_printf ("audio_%02d", demux->num_a_streams++); + templ = gst_element_class_get_pad_template (klass, "audio_%02d"); + caps = gst_matroska_demux_audio_caps (audiocontext, + context->codec_id, context->codec_priv, context->codec_priv_size, + &codec, &riff_audio_fmt); + + if (codec) { + list = gst_tag_list_new (); + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_AUDIO_CODEC, codec, NULL); + g_free (codec); + } + break; + } + + case GST_MATROSKA_TRACK_TYPE_SUBTITLE:{ + GstMatroskaTrackSubtitleContext *subtitlecontext = + (GstMatroskaTrackSubtitleContext *) context; + + padname = g_strdup_printf ("subtitle_%02d", demux->num_t_streams++); + templ = gst_element_class_get_pad_template (klass, "subtitle_%02d"); + caps = gst_matroska_demux_subtitle_caps (subtitlecontext, + context->codec_id, context->codec_priv, context->codec_priv_size); + break; + } + + case GST_MATROSKA_TRACK_TYPE_COMPLEX: + case GST_MATROSKA_TRACK_TYPE_LOGO: + case GST_MATROSKA_TRACK_TYPE_BUTTONS: + case GST_MATROSKA_TRACK_TYPE_CONTROL: + default: + /* we should already have quit by now */ + g_assert_not_reached (); + } + + if ((context->language == NULL || *context->language == '\0') && + (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO || + context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE)) { + GST_LOG ("stream %d: language=eng (assuming default)", context->index); + context->language = g_strdup ("eng"); + } + + if (context->language) { + const gchar *lang; + + if (!list) + list = gst_tag_list_new (); + + /* Matroska contains ISO 639-2B codes, we want ISO 639-1 */ + lang = gst_tag_get_language_code (context->language); + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_LANGUAGE_CODE, (lang) ? lang : context->language, NULL); + } + + if (caps == NULL) { + GST_WARNING_OBJECT (demux, "could not determine caps for stream with " + "codec_id='%s'", context->codec_id); + switch (context->type) { + case GST_MATROSKA_TRACK_TYPE_VIDEO: + caps = gst_caps_new_simple ("video/x-unknown", NULL); + break; + case GST_MATROSKA_TRACK_TYPE_AUDIO: + caps = gst_caps_new_simple ("audio/x-unknown", NULL); + break; + case GST_MATROSKA_TRACK_TYPE_SUBTITLE: + caps = gst_caps_new_simple ("application/x-subtitle-unknown", NULL); + break; + case GST_MATROSKA_TRACK_TYPE_COMPLEX: + default: + caps = gst_caps_new_simple ("application/x-matroska-unknown", NULL); + break; + } + gst_caps_set_simple (caps, "codec-id", G_TYPE_STRING, context->codec_id, + NULL); + + /* add any unrecognised riff fourcc / audio format, but after codec-id */ + if (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO && riff_audio_fmt != 0) + gst_caps_set_simple (caps, "format", G_TYPE_INT, riff_audio_fmt, NULL); + else if (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO && riff_fourcc != 0) + gst_caps_set_simple (caps, "fourcc", GST_TYPE_FOURCC, riff_fourcc, NULL); + } + + /* the pad in here */ + context->pad = gst_pad_new_from_template (templ, padname); + context->caps = caps; + + gst_pad_set_event_function (context->pad, + GST_DEBUG_FUNCPTR (gst_matroska_demux_handle_src_event)); + gst_pad_set_query_type_function (context->pad, + GST_DEBUG_FUNCPTR (gst_matroska_demux_get_src_query_types)); + gst_pad_set_query_function (context->pad, + GST_DEBUG_FUNCPTR (gst_matroska_demux_handle_src_query)); + + GST_INFO_OBJECT (demux, "Adding pad '%s' with caps %" GST_PTR_FORMAT, + padname, caps); + + context->pending_tags = list; + + gst_pad_set_element_private (context->pad, context); + + gst_pad_use_fixed_caps (context->pad); + gst_pad_set_caps (context->pad, context->caps); + gst_pad_set_active (context->pad, TRUE); + gst_element_add_pad (GST_ELEMENT (demux), context->pad); + + g_free (padname); + + /* tadaah! */ + return ret; +} + +static const GstQueryType * +gst_matroska_demux_get_src_query_types (GstPad * pad) +{ + static const GstQueryType query_types[] = { + GST_QUERY_POSITION, + GST_QUERY_DURATION, + GST_QUERY_SEEKING, + 0 + }; + + return query_types; +} + +static gboolean +gst_matroska_demux_query (GstMatroskaDemux * demux, GstPad * pad, + GstQuery * query) +{ + gboolean res = FALSE; + GstMatroskaTrackContext *context = NULL; + + if (pad) { + context = gst_pad_get_element_private (pad); + } + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + GstFormat format; + + gst_query_parse_position (query, &format, NULL); + + if (format == GST_FORMAT_TIME) { + GST_OBJECT_LOCK (demux); + if (context) + gst_query_set_position (query, GST_FORMAT_TIME, context->pos); + else + gst_query_set_position (query, GST_FORMAT_TIME, + demux->segment.last_stop); + GST_OBJECT_UNLOCK (demux); + } else if (format == GST_FORMAT_DEFAULT && context + && context->default_duration) { + GST_OBJECT_LOCK (demux); + gst_query_set_position (query, GST_FORMAT_DEFAULT, + context->pos / context->default_duration); + GST_OBJECT_UNLOCK (demux); + } else { + GST_DEBUG_OBJECT (demux, + "only position query in TIME and DEFAULT format is supported"); + } + + res = TRUE; + break; + } + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration (query, &format, NULL); + + if (format == GST_FORMAT_TIME) { + GST_OBJECT_LOCK (demux); + gst_query_set_duration (query, GST_FORMAT_TIME, + demux->segment.duration); + GST_OBJECT_UNLOCK (demux); + } else if (format == GST_FORMAT_DEFAULT && context + && context->default_duration) { + GST_OBJECT_LOCK (demux); + gst_query_set_duration (query, GST_FORMAT_DEFAULT, + demux->segment.duration / context->default_duration); + GST_OBJECT_UNLOCK (demux); + } else { + GST_DEBUG_OBJECT (demux, + "only duration query in TIME and DEFAULT format is supported"); + } + + res = TRUE; + break; + } + + case GST_QUERY_SEEKING: + { + GstFormat fmt; + + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + if (fmt == GST_FORMAT_TIME) { + gboolean seekable; + + if (demux->streaming) { + /* assuming we'll be able to get an index ... */ + seekable = demux->seekable; + } else { + seekable = TRUE; + } + + gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, + 0, demux->segment.duration); + res = TRUE; + } + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + + return res; +} + +static gboolean +gst_matroska_demux_element_query (GstElement * element, GstQuery * query) +{ + return gst_matroska_demux_query (GST_MATROSKA_DEMUX (element), NULL, query); +} + +static gboolean +gst_matroska_demux_handle_src_query (GstPad * pad, GstQuery * query) +{ + gboolean ret; + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (gst_pad_get_parent (pad)); + + ret = gst_matroska_demux_query (demux, pad, query); + + gst_object_unref (demux); + + return ret; +} + +static gint +gst_matroska_index_seek_find (GstMatroskaIndex * i1, GstClockTime * time, + gpointer user_data) +{ + if (i1->time < *time) + return -1; + else if (i1->time > *time) + return 1; + else + return 0; +} + +static GstMatroskaIndex * +gst_matroskademux_do_index_seek (GstMatroskaDemux * demux, + GstMatroskaTrackContext * track, gint64 seek_pos, GArray ** _index, + gint * _entry_index) +{ + GstMatroskaIndex *entry = NULL; + GArray *index; + + if (!demux->index || !demux->index->len) + return NULL; + + /* find entry just before or at the requested position */ + if (track && track->index_table) + index = track->index_table; + else + index = demux->index; + + entry = + gst_util_array_binary_search (index->data, index->len, + sizeof (GstMatroskaIndex), + (GCompareDataFunc) gst_matroska_index_seek_find, GST_SEARCH_MODE_BEFORE, + &seek_pos, NULL); + + if (entry == NULL) + entry = &g_array_index (index, GstMatroskaIndex, 0); + + if (_index) + *_index = index; + if (_entry_index) + *_entry_index = entry - (GstMatroskaIndex *) index->data; + + return entry; +} + +/* takes ownership of taglist */ +static void +gst_matroska_demux_found_global_tag (GstMatroskaDemux * demux, + GstTagList * taglist) +{ + if (demux->global_tags) { + /* nothing sent yet, add to cache */ + gst_tag_list_insert (demux->global_tags, taglist, GST_TAG_MERGE_APPEND); + gst_tag_list_free (taglist); + } else { + /* hm, already sent, no need to cache and wait anymore */ + GST_DEBUG_OBJECT (demux, "Sending late global tags %" GST_PTR_FORMAT, + taglist); + gst_element_found_tags (GST_ELEMENT (demux), taglist); + } +} + +/* returns FALSE if there are no pads to deliver event to, + * otherwise TRUE (whatever the outcome of event sending), + * takes ownership of the passed event! */ +static gboolean +gst_matroska_demux_send_event (GstMatroskaDemux * demux, GstEvent * event) +{ + gboolean is_newsegment; + gboolean ret = FALSE; + gint i; + + g_return_val_if_fail (event != NULL, FALSE); + + GST_DEBUG_OBJECT (demux, "Sending event of type %s to all source pads", + GST_EVENT_TYPE_NAME (event)); + + is_newsegment = (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT); + + g_assert (demux->src->len == demux->num_streams); + for (i = 0; i < demux->src->len; i++) { + GstMatroskaTrackContext *stream; + + stream = g_ptr_array_index (demux->src, i); + gst_event_ref (event); + gst_pad_push_event (stream->pad, event); + ret = TRUE; + + /* FIXME: send global tags before stream tags */ + if (G_UNLIKELY (is_newsegment && stream->pending_tags != NULL)) { + GST_DEBUG_OBJECT (demux, "Sending pending_tags %p for pad %s:%s : %" + GST_PTR_FORMAT, stream->pending_tags, + GST_DEBUG_PAD_NAME (stream->pad), stream->pending_tags); + gst_element_found_tags_for_pad (GST_ELEMENT (demux), stream->pad, + stream->pending_tags); + stream->pending_tags = NULL; + } + } + + if (G_UNLIKELY (is_newsegment && demux->global_tags != NULL)) { + gst_tag_list_add (demux->global_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_CONTAINER_FORMAT, "Matroska", NULL); + GST_DEBUG_OBJECT (demux, "Sending global_tags %p : %" GST_PTR_FORMAT, + demux->global_tags, demux->global_tags); + gst_element_found_tags (GST_ELEMENT (demux), demux->global_tags); + demux->global_tags = NULL; + } + + gst_event_unref (event); + return ret; +} + +static gboolean +gst_matroska_demux_element_send_event (GstElement * element, GstEvent * event) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); + gboolean res; + + g_return_val_if_fail (event != NULL, FALSE); + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) { + res = gst_matroska_demux_handle_seek_event (demux, NULL, event); + } else { + GST_WARNING_OBJECT (demux, "Unhandled event of type %s", + GST_EVENT_TYPE_NAME (event)); + res = FALSE; + } + gst_event_unref (event); + return res; +} + +/* determine track to seek in */ +static GstMatroskaTrackContext * +gst_matroska_demux_get_seek_track (GstMatroskaDemux * demux, + GstMatroskaTrackContext * track) +{ + gint i; + + if (track && track->type == GST_MATROSKA_TRACK_TYPE_VIDEO) + return track; + + for (i = 0; i < demux->src->len; i++) { + GstMatroskaTrackContext *stream; + + stream = g_ptr_array_index (demux->src, i); + if (stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO && stream->index_table) + track = stream; + } + + return track; +} + +static void +gst_matroska_demux_reset_streams (GstMatroskaDemux * demux, GstClockTime time, + gboolean full) +{ + gint i; + + GST_DEBUG_OBJECT (demux, "resetting stream state"); + + g_assert (demux->src->len == demux->num_streams); + for (i = 0; i < demux->src->len; i++) { + GstMatroskaTrackContext *context = g_ptr_array_index (demux->src, i); + context->pos = time; + context->set_discont = TRUE; + context->eos = FALSE; + context->from_time = GST_CLOCK_TIME_NONE; + if (full) + context->last_flow = GST_FLOW_OK; + if (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO) { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + /* demux object lock held by caller */ + videocontext->earliest_time = GST_CLOCK_TIME_NONE; + } + } +} + +static gboolean +gst_matroska_demux_move_to_entry (GstMatroskaDemux * demux, + GstMatroskaIndex * entry, gboolean reset) +{ + gint i; + + GST_OBJECT_LOCK (demux); + + /* seek (relative to matroska segment) */ + /* position might be invalid; will error when streaming resumes ... */ + demux->offset = entry->pos + demux->ebml_segment_start; + + GST_DEBUG_OBJECT (demux, "Seeked to offset %" G_GUINT64_FORMAT ", block %d, " + "time %" GST_TIME_FORMAT, entry->pos + demux->ebml_segment_start, + entry->block, GST_TIME_ARGS (entry->time)); + + /* update the time */ + gst_matroska_demux_reset_streams (demux, entry->time, TRUE); + demux->segment.last_stop = entry->time; + demux->seek_block = entry->block; + demux->seek_first = TRUE; + demux->last_stop_end = GST_CLOCK_TIME_NONE; + + for (i = 0; i < demux->src->len; i++) { + GstMatroskaTrackContext *stream = g_ptr_array_index (demux->src, i); + + if (reset) { + stream->to_offset = G_MAXINT64; + } else { + if (stream->from_offset != -1) + stream->to_offset = stream->from_offset; + } + stream->from_offset = -1; + } + + GST_OBJECT_UNLOCK (demux); + + return TRUE; +} + +static gint +gst_matroska_cluster_compare (gint64 * i1, gint64 * i2) +{ + if (*i1 < *i2) + return -1; + else if (*i1 > *i2) + return 1; + else + return 0; +} + +/* searches for a cluster start from @pos, + * return GST_FLOW_OK and cluster position in @pos if found */ +static GstFlowReturn +gst_matroska_demux_search_cluster (GstMatroskaDemux * demux, gint64 * pos) +{ + gint64 newpos = *pos; + gint64 orig_offset; + GstFlowReturn ret = GST_FLOW_OK; + const guint chunk = 64 * 1024; + GstBuffer *buf = NULL; + guint64 length; + guint32 id; + guint needed; + + orig_offset = demux->offset; + + GST_LOG_OBJECT (demux, "searching cluster following offset %" G_GINT64_FORMAT, + *pos); + + if (demux->clusters) { + gint64 *cpos; + + cpos = gst_util_array_binary_search (demux->clusters->data, + demux->clusters->len, sizeof (gint64), + (GCompareDataFunc) gst_matroska_cluster_compare, + GST_SEARCH_MODE_AFTER, pos, NULL); + /* sanity check */ + if (cpos) { + GST_DEBUG_OBJECT (demux, + "cluster reported at offset %" G_GINT64_FORMAT, *cpos); + demux->offset = *cpos; + ret = + gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed); + if (ret == GST_FLOW_OK && id == GST_MATROSKA_ID_CLUSTER) { + newpos = *cpos; + goto exit; + } + } + } + + /* read in at newpos and scan for ebml cluster id */ + while (1) { + GstByteReader reader; + gint cluster_pos; + + ret = gst_pad_pull_range (demux->sinkpad, newpos, chunk, &buf); + if (ret != GST_FLOW_OK) + break; + GST_DEBUG_OBJECT (demux, "read buffer size %d at offset %" G_GINT64_FORMAT, + GST_BUFFER_SIZE (buf), newpos); + gst_byte_reader_init_from_buffer (&reader, buf); + resume: + cluster_pos = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff, + GST_MATROSKA_ID_CLUSTER, 0, gst_byte_reader_get_remaining (&reader)); + if (cluster_pos >= 0) { + newpos += cluster_pos; + /* prepare resuming at next byte */ + gst_byte_reader_skip (&reader, cluster_pos + 1); + GST_DEBUG_OBJECT (demux, + "found cluster ebml id at offset %" G_GINT64_FORMAT, newpos); + /* extra checks whether we really sync'ed to a cluster: + * - either it is the first and only cluster + * - either there is a cluster after this one + * - either cluster length is undefined + */ + /* ok if first cluster (there may not a subsequent one) */ + if (newpos == demux->first_cluster_offset) { + GST_DEBUG_OBJECT (demux, "cluster is first cluster -> OK"); + break; + } + demux->offset = newpos; + ret = + gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed); + if (ret != GST_FLOW_OK) + goto resume; + g_assert (id == GST_MATROSKA_ID_CLUSTER); + GST_DEBUG_OBJECT (demux, "cluster size %" G_GUINT64_FORMAT ", prefix %d", + length, needed); + /* ok if undefined length or first cluster */ + if (length == G_MAXUINT64) { + GST_DEBUG_OBJECT (demux, "cluster has undefined length -> OK"); + break; + } + /* skip cluster */ + demux->offset += length + needed; + ret = + gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed); + if (ret != GST_FLOW_OK) + goto resume; + GST_DEBUG_OBJECT (demux, "next element is %scluster", + id == GST_MATROSKA_ID_CLUSTER ? "" : "not "); + if (id == GST_MATROSKA_ID_CLUSTER) + break; + /* not ok, resume */ + goto resume; + } else { + /* partial cluster id may have been in tail of buffer */ + newpos += MAX (gst_byte_reader_get_remaining (&reader), 4) - 3; + gst_buffer_unref (buf); + buf = NULL; + } + } + + if (buf) { + gst_buffer_unref (buf); + buf = NULL; + } + +exit: + demux->offset = orig_offset; + *pos = newpos; + return ret; +} + +/* bisect and scan through file for cluster starting before @time, + * returns fake index entry with corresponding info on cluster */ +static GstMatroskaIndex * +gst_matroska_demux_search_pos (GstMatroskaDemux * demux, GstClockTime time) +{ + GstMatroskaIndex *entry = NULL; + GstMatroskaDemuxState current_state; + GstClockTime otime, prev_cluster_time, current_cluster_time, cluster_time; + gint64 opos, newpos, startpos = 0, current_offset; + gint64 prev_cluster_offset = -1, current_cluster_offset, cluster_offset; + const guint chunk = 64 * 1024; + GstBuffer *buf = NULL; + GstFlowReturn ret; + guint64 length; + guint32 id; + guint needed; + + /* (under)estimate new position, resync using cluster ebml id, + * and scan forward to appropriate cluster + * (and re-estimate if need to go backward) */ + + prev_cluster_time = GST_CLOCK_TIME_NONE; + + /* store some current state */ + current_state = demux->state; + g_return_val_if_fail (current_state == GST_MATROSKA_DEMUX_STATE_DATA, NULL); + + current_cluster_offset = demux->cluster_offset; + current_cluster_time = demux->cluster_time; + current_offset = demux->offset; + + demux->state = GST_MATROSKA_DEMUX_STATE_SCANNING; + + /* estimate using start and current position */ + opos = demux->offset - demux->ebml_segment_start; + otime = demux->segment.last_stop; + +retry: + GST_LOG_OBJECT (demux, + "opos: %" G_GUINT64_FORMAT ", otime: %" GST_TIME_FORMAT, opos, + GST_TIME_ARGS (otime)); + newpos = gst_util_uint64_scale (opos, time, otime) - chunk; + if (newpos < 0) + newpos = 0; + /* favour undershoot */ + newpos = newpos * 90 / 100; + newpos += demux->ebml_segment_start; + + GST_DEBUG_OBJECT (demux, + "estimated offset for %" GST_TIME_FORMAT ": %" G_GINT64_FORMAT, + GST_TIME_ARGS (time), newpos); + + /* and at least start scanning before previous scan start to avoid looping */ + startpos = startpos * 90 / 100; + if (startpos && startpos < newpos) + newpos = startpos; + + /* read in at newpos and scan for ebml cluster id */ + startpos = newpos; + while (1) { + + ret = gst_matroska_demux_search_cluster (demux, &newpos); + if (ret == GST_FLOW_UNEXPECTED) { + /* heuristic HACK */ + newpos = startpos * 80 / 100; + GST_DEBUG_OBJECT (demux, "EOS; " + "new estimated offset for %" GST_TIME_FORMAT ": %" G_GINT64_FORMAT, + GST_TIME_ARGS (time), newpos); + startpos = newpos; + continue; + } else if (ret != GST_FLOW_OK) { + goto exit; + } else { + break; + } + } + + /* then start scanning and parsing for cluster time, + * re-estimate if overshoot, otherwise next cluster and so on */ + demux->offset = newpos; + demux->cluster_time = cluster_time = GST_CLOCK_TIME_NONE; + while (1) { + guint64 cluster_size = 0; + + /* peek and parse some elements */ + ret = gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed); + if (ret != GST_FLOW_OK) + goto error; + GST_LOG_OBJECT (demux, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, " + "size %" G_GUINT64_FORMAT ", needed %d", demux->offset, id, + length, needed); + ret = gst_matroska_demux_parse_id (demux, id, length, needed); + if (ret != GST_FLOW_OK) + goto error; + + if (id == GST_MATROSKA_ID_CLUSTER) { + cluster_time = GST_CLOCK_TIME_NONE; + if (length == G_MAXUINT64) + cluster_size = 0; + else + cluster_size = length + needed; + } + if (demux->cluster_time != GST_CLOCK_TIME_NONE && + cluster_time == GST_CLOCK_TIME_NONE) { + cluster_time = demux->cluster_time * demux->time_scale; + cluster_offset = demux->cluster_offset; + GST_DEBUG_OBJECT (demux, "found cluster at offset %" G_GINT64_FORMAT + " with time %" GST_TIME_FORMAT, cluster_offset, + GST_TIME_ARGS (cluster_time)); + if (cluster_time > time) { + GST_DEBUG_OBJECT (demux, "overshot target"); + /* cluster overshoots */ + if (cluster_offset == demux->first_cluster_offset) { + /* but no prev one */ + GST_DEBUG_OBJECT (demux, "but using first cluster anyway"); + prev_cluster_time = cluster_time; + prev_cluster_offset = cluster_offset; + break; + } + if (prev_cluster_time != GST_CLOCK_TIME_NONE) { + /* prev cluster did not overshoot, so prev cluster is target */ + break; + } else { + /* re-estimate using this new position info */ + opos = cluster_offset; + otime = cluster_time; + goto retry; + } + } else { + /* cluster undershoots, goto next one */ + prev_cluster_time = cluster_time; + prev_cluster_offset = cluster_offset; + /* skip cluster if length is defined, + * otherwise will be skippingly parsed into */ + if (cluster_size) { + GST_DEBUG_OBJECT (demux, "skipping to next cluster"); + demux->offset = cluster_offset + cluster_size; + demux->cluster_time = GST_CLOCK_TIME_NONE; + } else { + GST_DEBUG_OBJECT (demux, "parsing/skipping cluster elements"); + } + } + } + continue; + + error: + if (ret == GST_FLOW_UNEXPECTED) { + if (prev_cluster_time != GST_CLOCK_TIME_NONE) + break; + } + goto exit; + } + + entry = g_new0 (GstMatroskaIndex, 1); + entry->time = prev_cluster_time; + entry->pos = prev_cluster_offset - demux->ebml_segment_start; + GST_DEBUG_OBJECT (demux, "simulated index entry; time %" GST_TIME_FORMAT + ", pos %" G_GUINT64_FORMAT, GST_TIME_ARGS (entry->time), entry->pos); + +exit: + if (buf) + gst_buffer_unref (buf); + + /* restore some state */ + demux->cluster_offset = current_cluster_offset; + demux->cluster_time = current_cluster_time; + demux->offset = current_offset; + demux->state = current_state; + + return entry; +} + +static gboolean +gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux, + GstPad * pad, GstEvent * event) +{ + GstMatroskaIndex *entry = NULL; + GstMatroskaIndex scan_entry; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + GstFormat format; + gboolean flush, keyunit; + gdouble rate; + gint64 cur, stop; + GstMatroskaTrackContext *track = NULL; + GstSegment seeksegment = { 0, }; + gboolean update; + + if (pad) + track = gst_pad_get_element_private (pad); + + track = gst_matroska_demux_get_seek_track (demux, track); + + gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, + &stop_type, &stop); + + /* we can only seek on time */ + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (demux, "Can only seek on TIME"); + return FALSE; + } + + /* copy segment, we need this because we still need the old + * segment when we close the current segment. */ + memcpy (&seeksegment, &demux->segment, sizeof (GstSegment)); + + if (event) { + GST_DEBUG_OBJECT (demux, "configuring seek"); + gst_segment_set_seek (&seeksegment, rate, format, flags, + cur_type, cur, stop_type, stop, &update); + } + + GST_DEBUG_OBJECT (demux, "New segment %" GST_SEGMENT_FORMAT, &seeksegment); + + /* check sanity before we start flushing and all that */ + GST_OBJECT_LOCK (demux); + if ((entry = gst_matroskademux_do_index_seek (demux, track, + seeksegment.last_stop, &demux->seek_index, &demux->seek_entry)) == + NULL) { + /* pull mode without index can scan later on */ + if (demux->index || demux->streaming) { + GST_DEBUG_OBJECT (demux, "No matching seek entry in index"); + GST_OBJECT_UNLOCK (demux); + return FALSE; + } + } + GST_DEBUG_OBJECT (demux, "Seek position looks sane"); + GST_OBJECT_UNLOCK (demux); + + if (demux->streaming) { + /* need to seek to cluster start to pick up cluster time */ + /* upstream takes care of flushing and all that + * ... and newsegment event handling takes care of the rest */ + return perform_seek_to_offset (demux, + entry->pos + demux->ebml_segment_start); + } + + flush = ! !(flags & GST_SEEK_FLAG_FLUSH); + keyunit = ! !(flags & GST_SEEK_FLAG_KEY_UNIT); + + if (flush) { + GST_DEBUG_OBJECT (demux, "Starting flush"); + gst_pad_push_event (demux->sinkpad, gst_event_new_flush_start ()); + gst_matroska_demux_send_event (demux, gst_event_new_flush_start ()); + } else { + GST_DEBUG_OBJECT (demux, "Non-flushing seek, pausing task"); + gst_pad_pause_task (demux->sinkpad); + } + + /* now grab the stream lock so that streaming cannot continue, for + * non flushing seeks when the element is in PAUSED this could block + * forever. */ + GST_DEBUG_OBJECT (demux, "Waiting for streaming to stop"); + GST_PAD_STREAM_LOCK (demux->sinkpad); + + /* pull mode without index can do some scanning */ + if (!demux->streaming && !demux->index) { + /* need to stop flushing upstream as we need it next */ + if (flush) + gst_pad_push_event (demux->sinkpad, gst_event_new_flush_stop ()); + entry = gst_matroska_demux_search_pos (demux, seeksegment.last_stop); + /* keep local copy */ + if (entry) { + scan_entry = *entry; + g_free (entry); + entry = &scan_entry; + } else { + GST_DEBUG_OBJECT (demux, "Scan failed to find matching position"); + if (flush) + gst_matroska_demux_send_event (demux, gst_event_new_flush_stop ()); + goto seek_error; + } + } + + if (keyunit) { + GST_DEBUG_OBJECT (demux, "seek to key unit, adjusting segment start to %" + GST_TIME_FORMAT, GST_TIME_ARGS (entry->time)); + seeksegment.start = entry->time; + seeksegment.last_stop = entry->time; + seeksegment.time = entry->time; + } + + if (flush) { + GST_DEBUG_OBJECT (demux, "Stopping flush"); + gst_pad_push_event (demux->sinkpad, gst_event_new_flush_stop ()); + gst_matroska_demux_send_event (demux, gst_event_new_flush_stop ()); + } else if (demux->segment_running) { + GST_DEBUG_OBJECT (demux, "Closing currently running segment"); + + GST_OBJECT_LOCK (demux); + if (demux->close_segment) + gst_event_unref (demux->close_segment); + + demux->close_segment = gst_event_new_new_segment (TRUE, + demux->segment.rate, GST_FORMAT_TIME, demux->segment.start, + demux->segment.last_stop, demux->segment.time); + GST_OBJECT_UNLOCK (demux); + } + + GST_OBJECT_LOCK (demux); + /* now update the real segment info */ + GST_DEBUG_OBJECT (demux, "Committing new seek segment"); + memcpy (&demux->segment, &seeksegment, sizeof (GstSegment)); + GST_OBJECT_UNLOCK (demux); + + /* update some (segment) state */ + if (!gst_matroska_demux_move_to_entry (demux, entry, TRUE)) + goto seek_error; + + /* notify start of new segment */ + if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) { + GstMessage *msg; + + msg = gst_message_new_segment_start (GST_OBJECT (demux), + GST_FORMAT_TIME, demux->segment.start); + gst_element_post_message (GST_ELEMENT (demux), msg); + } + + GST_OBJECT_LOCK (demux); + if (demux->new_segment) + gst_event_unref (demux->new_segment); + demux->new_segment = gst_event_new_new_segment_full (FALSE, + demux->segment.rate, demux->segment.applied_rate, demux->segment.format, + demux->segment.start, demux->segment.stop, demux->segment.time); + GST_OBJECT_UNLOCK (demux); + + /* restart our task since it might have been stopped when we did the + * flush. */ + demux->segment_running = TRUE; + gst_pad_start_task (demux->sinkpad, (GstTaskFunction) gst_matroska_demux_loop, + demux->sinkpad); + + /* streaming can continue now */ + GST_PAD_STREAM_UNLOCK (demux->sinkpad); + + return TRUE; + +seek_error: + { + GST_PAD_STREAM_UNLOCK (demux->sinkpad); + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("Got a seek error")); + return FALSE; + } +} + +/* + * Handle whether we can perform the seek event or if we have to let the chain + * function handle seeks to build the seek indexes first. + */ +static gboolean +gst_matroska_demux_handle_seek_push (GstMatroskaDemux * demux, GstPad * pad, + GstEvent * event) +{ + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + GstFormat format; + gdouble rate; + gint64 cur, stop; + + gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, + &stop_type, &stop); + + /* sanity checks */ + + /* we can only seek on time */ + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (demux, "Can only seek on TIME"); + return FALSE; + } + + if (stop_type != GST_SEEK_TYPE_NONE && stop != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (demux, "Seek end-time not supported in streaming mode"); + return FALSE; + } + + if (!(flags & GST_SEEK_FLAG_FLUSH)) { + GST_DEBUG_OBJECT (demux, + "Non-flushing seek not supported in streaming mode"); + return FALSE; + } + + if (flags & GST_SEEK_FLAG_SEGMENT) { + GST_DEBUG_OBJECT (demux, "Segment seek not supported in streaming mode"); + return FALSE; + } + + /* check for having parsed index already */ + if (!demux->index_parsed) { + gboolean building_index; + guint64 offset = 0; + + if (!demux->index_offset) { + GST_DEBUG_OBJECT (demux, "no index (location); no seek in push mode"); + return FALSE; + } + + GST_OBJECT_LOCK (demux); + /* handle the seek event in the chain function */ + demux->state = GST_MATROSKA_DEMUX_STATE_SEEK; + /* no more seek can be issued until state reset to _DATA */ + + /* copy the event */ + if (demux->seek_event) + gst_event_unref (demux->seek_event); + demux->seek_event = gst_event_ref (event); + + /* set the building_index flag so that only one thread can setup the + * structures for index seeking. */ + building_index = demux->building_index; + if (!building_index) { + demux->building_index = TRUE; + offset = demux->index_offset; + } + GST_OBJECT_UNLOCK (demux); + + if (!building_index) { + /* seek to the first subindex or legacy index */ + GST_INFO_OBJECT (demux, "Seeking to Cues at %" G_GUINT64_FORMAT, offset); + return perform_seek_to_offset (demux, offset); + } + + /* well, we are handling it already */ + return TRUE; + } + + /* delegate to tweaked regular seek */ + return gst_matroska_demux_handle_seek_event (demux, pad, event); +} + +static gboolean +gst_matroska_demux_handle_src_event (GstPad * pad, GstEvent * event) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (gst_pad_get_parent (pad)); + gboolean res = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + /* no seeking until we are (safely) ready */ + if (demux->state != GST_MATROSKA_DEMUX_STATE_DATA) { + GST_DEBUG_OBJECT (demux, "not ready for seeking yet"); + return FALSE; + } + if (!demux->streaming) + res = gst_matroska_demux_handle_seek_event (demux, pad, event); + else + res = gst_matroska_demux_handle_seek_push (demux, pad, event); + gst_event_unref (event); + break; + + case GST_EVENT_QOS: + { + GstMatroskaTrackContext *context = gst_pad_get_element_private (pad); + if (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO) { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + gdouble proportion; + GstClockTimeDiff diff; + GstClockTime timestamp; + + gst_event_parse_qos (event, &proportion, &diff, ×tamp); + + GST_OBJECT_LOCK (demux); + videocontext->earliest_time = timestamp + diff; + GST_OBJECT_UNLOCK (demux); + } + res = TRUE; + gst_event_unref (event); + break; + } + + /* events we don't need to handle */ + case GST_EVENT_NAVIGATION: + gst_event_unref (event); + res = FALSE; + break; + + case GST_EVENT_LATENCY: + default: + res = gst_pad_push_event (demux->sinkpad, event); + break; + } + + gst_object_unref (demux); + + return res; +} + +static GstFlowReturn +gst_matroska_demux_seek_to_previous_keyframe (GstMatroskaDemux * demux) +{ + GstFlowReturn ret = GST_FLOW_UNEXPECTED; + gboolean done = TRUE; + gint i; + + g_return_val_if_fail (demux->seek_index, GST_FLOW_UNEXPECTED); + g_return_val_if_fail (demux->seek_entry < demux->seek_index->len, + GST_FLOW_UNEXPECTED); + + GST_DEBUG_OBJECT (demux, "locating previous keyframe"); + + if (!demux->seek_entry) { + GST_DEBUG_OBJECT (demux, "no earlier index entry"); + goto exit; + } + + for (i = 0; i < demux->src->len; i++) { + GstMatroskaTrackContext *stream = g_ptr_array_index (demux->src, i); + + GST_DEBUG_OBJECT (demux, "segment start %" GST_TIME_FORMAT + ", stream %d at %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->segment.start), stream->index, + GST_TIME_ARGS (stream->from_time)); + if (GST_CLOCK_TIME_IS_VALID (stream->from_time)) { + if (stream->from_time > demux->segment.start) { + GST_DEBUG_OBJECT (demux, "stream %d not finished yet", stream->index); + done = FALSE; + } + } else { + /* nothing pushed for this stream; + * likely seek entry did not start at keyframe, so all was skipped. + * So we need an earlier entry */ + done = FALSE; + } + } + + if (!done) { + GstMatroskaIndex *entry; + + entry = &g_array_index (demux->seek_index, GstMatroskaIndex, + --demux->seek_entry); + if (!gst_matroska_demux_move_to_entry (demux, entry, FALSE)) + goto exit; + + ret = GST_FLOW_OK; + } + +exit: + return ret; +} + +/* skip unknown or alike element */ +static GstFlowReturn +gst_matroska_demux_parse_skip (GstMatroskaDemux * demux, GstEbmlRead * ebml, + const gchar * parent_name, guint id) +{ + if (id == GST_EBML_ID_VOID) { + GST_DEBUG_OBJECT (demux, "Skipping EBML Void element"); + } else if (id == GST_EBML_ID_CRC32) { + GST_DEBUG_OBJECT (demux, "Skipping EBML CRC32 element"); + } else { + GST_WARNING_OBJECT (demux, + "Unknown %s subelement 0x%x - ignoring", parent_name, id); + } + + return gst_ebml_read_skip (ebml); +} + +static GstFlowReturn +gst_matroska_demux_parse_header (GstMatroskaDemux * demux, GstEbmlRead * ebml) +{ + GstFlowReturn ret; + gchar *doctype; + guint version; + guint32 id; + + /* this function is the first to be called */ + + /* default init */ + doctype = NULL; + version = 1; + + ret = gst_ebml_peek_id (ebml, &id); + if (ret != GST_FLOW_OK) + return ret; + + GST_DEBUG_OBJECT (demux, "id: %08x", id); + + if (id != GST_EBML_ID_HEADER) { + GST_ERROR_OBJECT (demux, "Failed to read header"); + goto exit; + } + + ret = gst_ebml_read_master (ebml, &id); + if (ret != GST_FLOW_OK) + return ret; + + while (gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + ret = gst_ebml_peek_id (ebml, &id); + if (ret != GST_FLOW_OK) + return ret; + + switch (id) { + /* is our read version uptodate? */ + case GST_EBML_ID_EBMLREADVERSION:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + if (num != GST_EBML_VERSION) { + GST_ERROR_OBJECT (ebml, "Unsupported EBML version %" G_GUINT64_FORMAT, + num); + return GST_FLOW_ERROR; + } + + GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); + break; + } + + /* we only handle 8 byte lengths at max */ + case GST_EBML_ID_EBMLMAXSIZELENGTH:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + if (num > sizeof (guint64)) { + GST_ERROR_OBJECT (ebml, + "Unsupported EBML maximum size %" G_GUINT64_FORMAT, num); + return GST_FLOW_ERROR; + } + GST_DEBUG_OBJECT (ebml, "EbmlMaxSizeLength: %" G_GUINT64_FORMAT, num); + break; + } + + /* we handle 4 byte IDs at max */ + case GST_EBML_ID_EBMLMAXIDLENGTH:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + if (num > sizeof (guint32)) { + GST_ERROR_OBJECT (ebml, + "Unsupported EBML maximum ID %" G_GUINT64_FORMAT, num); + return GST_FLOW_ERROR; + } + GST_DEBUG_OBJECT (ebml, "EbmlMaxIdLength: %" G_GUINT64_FORMAT, num); + break; + } + + case GST_EBML_ID_DOCTYPE:{ + gchar *text; + + ret = gst_ebml_read_ascii (ebml, &id, &text); + if (ret != GST_FLOW_OK) + return ret; + + GST_DEBUG_OBJECT (ebml, "EbmlDocType: %s", GST_STR_NULL (text)); + + if (doctype) + g_free (doctype); + doctype = text; + break; + } + + case GST_EBML_ID_DOCTYPEREADVERSION:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + version = num; + GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); + break; + } + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "EBML header", id); + if (ret != GST_FLOW_OK) + return ret; + break; + + /* we ignore these two, as they don't tell us anything we care about */ + case GST_EBML_ID_EBMLVERSION: + case GST_EBML_ID_DOCTYPEVERSION: + ret = gst_ebml_read_skip (ebml); + if (ret != GST_FLOW_OK) + return ret; + break; + } + } + +exit: + + if ((doctype != NULL && !strcmp (doctype, GST_MATROSKA_DOCTYPE_MATROSKA)) || + (doctype != NULL && !strcmp (doctype, GST_MATROSKA_DOCTYPE_WEBM)) || + (doctype == NULL)) { + if (version <= 2) { + if (doctype) { + GST_INFO_OBJECT (demux, "Input is %s version %d", doctype, version); + } else { + GST_WARNING_OBJECT (demux, "Input is EBML without doctype, assuming " + "matroska (version %d)", version); + } + ret = GST_FLOW_OK; + } else { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("Demuxer version (2) is too old to read %s version %d", + GST_STR_NULL (doctype), version)); + ret = GST_FLOW_ERROR; + } + g_free (doctype); + } else { + GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL), + ("Input is not a matroska stream (doctype=%s)", doctype)); + ret = GST_FLOW_ERROR; + g_free (doctype); + } + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_tracks (GstMatroskaDemux * demux, GstEbmlRead * ebml) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + + DEBUG_ELEMENT_START (demux, ebml, "Tracks"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "Tracks", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* one track within the "all-tracks" header */ + case GST_MATROSKA_ID_TRACKENTRY: + ret = gst_matroska_demux_add_stream (demux, ebml); + break; + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "Track", id); + break; + } + } + DEBUG_ELEMENT_STOP (demux, ebml, "Tracks", ret); + + demux->tracks_parsed = TRUE; + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_index_cuetrack (GstMatroskaDemux * demux, + GstEbmlRead * ebml, guint * nentries) +{ + guint32 id; + GstFlowReturn ret; + GstMatroskaIndex idx; + + idx.pos = (guint64) - 1; + idx.track = 0; + idx.time = GST_CLOCK_TIME_NONE; + idx.block = 1; + + DEBUG_ELEMENT_START (demux, ebml, "CueTrackPositions"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "CueTrackPositions", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* track number */ + case GST_MATROSKA_ID_CUETRACK: + { + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + idx.track = 0; + GST_WARNING_OBJECT (demux, "Invalid CueTrack 0"); + break; + } + + GST_DEBUG_OBJECT (demux, "CueTrack: %" G_GUINT64_FORMAT, num); + idx.track = num; + break; + } + + /* position in file */ + case GST_MATROSKA_ID_CUECLUSTERPOSITION: + { + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num > G_MAXINT64) { + GST_WARNING_OBJECT (demux, "CueClusterPosition %" G_GUINT64_FORMAT + " too large", num); + break; + } + + idx.pos = num; + break; + } + + /* number of block in the cluster */ + case GST_MATROSKA_ID_CUEBLOCKNUMBER: + { + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid CueBlockNumber 0"); + break; + } + + GST_DEBUG_OBJECT (demux, "CueBlockNumber: %" G_GUINT64_FORMAT, num); + idx.block = num; + + /* mild sanity check, disregard strange cases ... */ + if (idx.block > G_MAXUINT16) { + GST_DEBUG_OBJECT (demux, "... looks suspicious, ignoring"); + idx.block = 1; + } + break; + } + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "CueTrackPositions", + id); + break; + + case GST_MATROSKA_ID_CUECODECSTATE: + case GST_MATROSKA_ID_CUEREFERENCE: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "CueTrackPositions", ret); + + if ((ret == GST_FLOW_OK || ret == GST_FLOW_UNEXPECTED) + && idx.pos != (guint64) - 1 && idx.track > 0) { + g_array_append_val (demux->index, idx); + (*nentries)++; + } else if (ret == GST_FLOW_OK || ret == GST_FLOW_UNEXPECTED) { + GST_DEBUG_OBJECT (demux, "CueTrackPositions without valid content"); + } + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_index_pointentry (GstMatroskaDemux * demux, + GstEbmlRead * ebml) +{ + guint32 id; + GstFlowReturn ret; + GstClockTime time = GST_CLOCK_TIME_NONE; + guint nentries = 0; + + DEBUG_ELEMENT_START (demux, ebml, "CuePoint"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "CuePoint", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* one single index entry ('point') */ + case GST_MATROSKA_ID_CUETIME: + { + if ((ret = gst_ebml_read_uint (ebml, &id, &time)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "CueTime: %" G_GUINT64_FORMAT, time); + time = time * demux->time_scale; + break; + } + + /* position in the file + track to which it belongs */ + case GST_MATROSKA_ID_CUETRACKPOSITIONS: + { + if ((ret = + gst_matroska_demux_parse_index_cuetrack (demux, ebml, + &nentries)) != GST_FLOW_OK) + break; + break; + } + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "CuePoint", id); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "CuePoint", ret); + + if (nentries > 0) { + if (time == GST_CLOCK_TIME_NONE) { + GST_WARNING_OBJECT (demux, "CuePoint without valid time"); + g_array_remove_range (demux->index, demux->index->len - nentries, + nentries); + } else { + gint i; + + for (i = demux->index->len - nentries; i < demux->index->len; i++) { + GstMatroskaIndex *idx = + &g_array_index (demux->index, GstMatroskaIndex, i); + + idx->time = time; + GST_DEBUG_OBJECT (demux, "Index entry: pos=%" G_GUINT64_FORMAT + ", time=%" GST_TIME_FORMAT ", track=%u, block=%u", idx->pos, + GST_TIME_ARGS (idx->time), (guint) idx->track, (guint) idx->block); + } + } + } else { + GST_DEBUG_OBJECT (demux, "Empty CuePoint"); + } + + return ret; +} + +static gint +gst_matroska_index_compare (GstMatroskaIndex * i1, GstMatroskaIndex * i2) +{ + if (i1->time < i2->time) + return -1; + else if (i1->time > i2->time) + return 1; + else if (i1->block < i2->block) + return -1; + else if (i1->block > i2->block) + return 1; + else + return 0; +} + +static GstFlowReturn +gst_matroska_demux_parse_index (GstMatroskaDemux * demux, GstEbmlRead * ebml) +{ + guint32 id; + GstFlowReturn ret = GST_FLOW_OK; + guint i; + + if (demux->index) + g_array_free (demux->index, TRUE); + demux->index = + g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaIndex), 128); + + DEBUG_ELEMENT_START (demux, ebml, "Cues"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "Cues", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* one single index entry ('point') */ + case GST_MATROSKA_ID_POINTENTRY: + ret = gst_matroska_demux_parse_index_pointentry (demux, ebml); + break; + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "Cues", id); + break; + } + } + DEBUG_ELEMENT_STOP (demux, ebml, "Cues", ret); + + /* Sort index by time, smallest time first, for easier searching */ + g_array_sort (demux->index, (GCompareFunc) gst_matroska_index_compare); + + /* Now sort the track specific index entries into their own arrays */ + for (i = 0; i < demux->index->len; i++) { + GstMatroskaIndex *idx = &g_array_index (demux->index, GstMatroskaIndex, i); + gint track_num; + GstMatroskaTrackContext *ctx; + + if (demux->element_index) { + gint writer_id; + + if (idx->track != 0 && + (track_num = + gst_matroska_demux_stream_from_num (demux, idx->track)) != -1) { + ctx = g_ptr_array_index (demux->src, track_num); + + if (ctx->index_writer_id == -1) + gst_index_get_writer_id (demux->element_index, GST_OBJECT (ctx->pad), + &ctx->index_writer_id); + writer_id = ctx->index_writer_id; + } else { + if (demux->element_index_writer_id == -1) + gst_index_get_writer_id (demux->element_index, GST_OBJECT (demux), + &demux->element_index_writer_id); + writer_id = demux->element_index_writer_id; + } + + GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT " for writer id %d", GST_TIME_ARGS (idx->time), + idx->pos, writer_id); + gst_index_add_association (demux->element_index, writer_id, + GST_ASSOCIATION_FLAG_KEY_UNIT, GST_FORMAT_TIME, idx->time, + GST_FORMAT_BYTES, idx->pos + demux->ebml_segment_start, NULL); + } + + if (idx->track == 0) + continue; + + track_num = gst_matroska_demux_stream_from_num (demux, idx->track); + if (track_num == -1) + continue; + + ctx = g_ptr_array_index (demux->src, track_num); + + if (ctx->index_table == NULL) + ctx->index_table = + g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaIndex), 128); + + g_array_append_vals (ctx->index_table, idx, 1); + } + + demux->index_parsed = TRUE; + + /* sanity check; empty index normalizes to no index */ + if (demux->index->len == 0) { + g_array_free (demux->index, TRUE); + demux->index = NULL; + } + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_info (GstMatroskaDemux * demux, GstEbmlRead * ebml) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + + DEBUG_ELEMENT_START (demux, ebml, "SegmentInfo"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "SegmentInfo", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* cluster timecode */ + case GST_MATROSKA_ID_TIMECODESCALE:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + + GST_DEBUG_OBJECT (demux, "TimeCodeScale: %" G_GUINT64_FORMAT, num); + demux->time_scale = num; + break; + } + + case GST_MATROSKA_ID_DURATION:{ + gdouble num; + GstClockTime dur; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num <= 0.0) { + GST_WARNING_OBJECT (demux, "Invalid duration %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "Duration: %lf", num); + + dur = gst_gdouble_to_guint64 (num * + gst_guint64_to_gdouble (demux->time_scale)); + if (GST_CLOCK_TIME_IS_VALID (dur) && dur <= G_MAXINT64) + gst_segment_set_duration (&demux->segment, GST_FORMAT_TIME, dur); + break; + } + + case GST_MATROSKA_ID_WRITINGAPP:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "WritingApp: %s", GST_STR_NULL (text)); + demux->writing_app = text; + break; + } + + case GST_MATROSKA_ID_MUXINGAPP:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "MuxingApp: %s", GST_STR_NULL (text)); + demux->muxing_app = text; + break; + } + + case GST_MATROSKA_ID_DATEUTC:{ + gint64 time; + + if ((ret = gst_ebml_read_date (ebml, &id, &time)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "DateUTC: %" G_GINT64_FORMAT, time); + demux->created = time; + break; + } + + case GST_MATROSKA_ID_TITLE:{ + gchar *text; + GstTagList *taglist; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "Title: %s", GST_STR_NULL (text)); + taglist = gst_tag_list_new (); + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, text, + NULL); + gst_matroska_demux_found_global_tag (demux, taglist); + g_free (text); + break; + } + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "SegmentInfo", id); + break; + + /* fall through */ + case GST_MATROSKA_ID_SEGMENTUID: + case GST_MATROSKA_ID_SEGMENTFILENAME: + case GST_MATROSKA_ID_PREVUID: + case GST_MATROSKA_ID_PREVFILENAME: + case GST_MATROSKA_ID_NEXTUID: + case GST_MATROSKA_ID_NEXTFILENAME: + case GST_MATROSKA_ID_SEGMENTFAMILY: + case GST_MATROSKA_ID_CHAPTERTRANSLATE: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "SegmentInfo", ret); + + demux->segmentinfo_parsed = TRUE; + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_metadata_id_simple_tag (GstMatroskaDemux * demux, + GstEbmlRead * ebml, GstTagList ** p_taglist) +{ + /* FIXME: check if there are more useful mappings */ + static const struct + { + const gchar *matroska_tagname; + const gchar *gstreamer_tagname; + } + tag_conv[] = { + { + GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, { + GST_MATROSKA_TAG_ID_ARTIST, GST_TAG_ARTIST}, { + GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, { + GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, { + GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, { + GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, { + GST_MATROSKA_TAG_ID_BPS, GST_TAG_BITRATE}, { + GST_MATROSKA_TAG_ID_ENCODER, GST_TAG_ENCODER}, { + GST_MATROSKA_TAG_ID_DATE, GST_TAG_DATE}, { + GST_MATROSKA_TAG_ID_ISRC, GST_TAG_ISRC}, { + GST_MATROSKA_TAG_ID_COPYRIGHT, GST_TAG_COPYRIGHT}, { + GST_MATROSKA_TAG_ID_BPM, GST_TAG_BEATS_PER_MINUTE}, { + GST_MATROSKA_TAG_ID_TERMS_OF_USE, GST_TAG_LICENSE}, { + GST_MATROSKA_TAG_ID_COMPOSER, GST_TAG_COMPOSER}, { + GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, { + GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE} + }; + GstFlowReturn ret; + guint32 id; + gchar *value = NULL; + gchar *tag = NULL; + + DEBUG_ELEMENT_START (demux, ebml, "SimpleTag"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "SimpleTag", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + /* read all sub-entries */ + + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_TAGNAME: + g_free (tag); + tag = NULL; + ret = gst_ebml_read_ascii (ebml, &id, &tag); + GST_DEBUG_OBJECT (demux, "TagName: %s", GST_STR_NULL (tag)); + break; + + case GST_MATROSKA_ID_TAGSTRING: + g_free (value); + value = NULL; + ret = gst_ebml_read_utf8 (ebml, &id, &value); + GST_DEBUG_OBJECT (demux, "TagString: %s", GST_STR_NULL (value)); + break; + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "SimpleTag", id); + break; + /* fall-through */ + + case GST_MATROSKA_ID_TAGLANGUAGE: + case GST_MATROSKA_ID_TAGDEFAULT: + case GST_MATROSKA_ID_TAGBINARY: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "SimpleTag", ret); + + if (tag && value) { + guint i; + + for (i = 0; i < G_N_ELEMENTS (tag_conv); i++) { + const gchar *tagname_gst = tag_conv[i].gstreamer_tagname; + + const gchar *tagname_mkv = tag_conv[i].matroska_tagname; + + if (strcmp (tagname_mkv, tag) == 0) { + GValue dest = { 0, }; + GType dest_type = gst_tag_get_type (tagname_gst); + + /* Ensure that any date string is complete */ + if (dest_type == GST_TYPE_DATE) { + guint year = 1901, month = 1, day = 1; + + /* Dates can be yyyy-MM-dd, yyyy-MM or yyyy, but we need + * the first type */ + if (sscanf (value, "%04u-%02u-%02u", &year, &month, &day) != 0) { + g_free (value); + value = g_strdup_printf ("%04u-%02u-%02u", year, month, day); + } + } + + g_value_init (&dest, dest_type); + if (gst_value_deserialize (&dest, value)) { + gst_tag_list_add_values (*p_taglist, GST_TAG_MERGE_APPEND, + tagname_gst, &dest, NULL); + } else { + GST_WARNING_OBJECT (demux, "Can't transform tag '%s' with " + "value '%s' to target type '%s'", tag, value, + g_type_name (dest_type)); + } + g_value_unset (&dest); + break; + } + } + } + + g_free (tag); + g_free (value); + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_metadata_id_tag (GstMatroskaDemux * demux, + GstEbmlRead * ebml, GstTagList ** p_taglist) +{ + guint32 id; + GstFlowReturn ret; + + DEBUG_ELEMENT_START (demux, ebml, "Tag"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "Tag", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + /* read all sub-entries */ + + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_SIMPLETAG: + ret = gst_matroska_demux_parse_metadata_id_simple_tag (demux, ebml, + p_taglist); + break; + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "Tag", id); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "Tag", ret); + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_metadata (GstMatroskaDemux * demux, GstEbmlRead * ebml) +{ + GstTagList *taglist; + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + GList *l; + guint64 curpos; + + curpos = gst_ebml_read_get_pos (ebml); + + /* Make sure we don't parse a tags element twice and + * post it's tags twice */ + curpos = gst_ebml_read_get_pos (ebml); + for (l = demux->tags_parsed; l; l = l->next) { + guint64 *pos = l->data; + + if (*pos == curpos) { + GST_DEBUG_OBJECT (demux, "Skipping already parsed Tags at offset %" + G_GUINT64_FORMAT, curpos); + return GST_FLOW_OK; + } + } + + demux->tags_parsed = + g_list_prepend (demux->tags_parsed, g_slice_new (guint64)); + *((guint64 *) demux->tags_parsed->data) = curpos; + /* fall-through */ + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "Tags", ret); + return ret; + } + + taglist = gst_tag_list_new (); + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_TAG: + ret = gst_matroska_demux_parse_metadata_id_tag (demux, ebml, &taglist); + break; + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "Tags", id); + break; + /* FIXME: Use to limit the tags to specific pads */ + case GST_MATROSKA_ID_TARGETS: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "Tags", ret); + + gst_matroska_demux_found_global_tag (demux, taglist); + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_attached_file (GstMatroskaDemux * demux, + GstEbmlRead * ebml, GstTagList * taglist) +{ + guint32 id; + GstFlowReturn ret; + gchar *description = NULL; + gchar *filename = NULL; + gchar *mimetype = NULL; + guint8 *data = NULL; + guint64 datalen = 0; + + DEBUG_ELEMENT_START (demux, ebml, "AttachedFile"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "AttachedFile", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + /* read all sub-entries */ + + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_FILEDESCRIPTION: + if (description) { + GST_WARNING_OBJECT (demux, "FileDescription can only appear once"); + break; + } + + ret = gst_ebml_read_utf8 (ebml, &id, &description); + GST_DEBUG_OBJECT (demux, "FileDescription: %s", + GST_STR_NULL (description)); + break; + case GST_MATROSKA_ID_FILENAME: + if (filename) { + GST_WARNING_OBJECT (demux, "FileName can only appear once"); + break; + } + + ret = gst_ebml_read_utf8 (ebml, &id, &filename); + + GST_DEBUG_OBJECT (demux, "FileName: %s", GST_STR_NULL (filename)); + break; + case GST_MATROSKA_ID_FILEMIMETYPE: + if (mimetype) { + GST_WARNING_OBJECT (demux, "FileMimeType can only appear once"); + break; + } + + ret = gst_ebml_read_ascii (ebml, &id, &mimetype); + GST_DEBUG_OBJECT (demux, "FileMimeType: %s", GST_STR_NULL (mimetype)); + break; + case GST_MATROSKA_ID_FILEDATA: + if (data) { + GST_WARNING_OBJECT (demux, "FileData can only appear once"); + break; + } + + ret = gst_ebml_read_binary (ebml, &id, &data, &datalen); + GST_DEBUG_OBJECT (demux, "FileData of size %" G_GUINT64_FORMAT, + datalen); + break; + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "AttachedFile", id); + break; + case GST_MATROSKA_ID_FILEUID: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "AttachedFile", ret); + + if (filename && mimetype && data && datalen > 0) { + GstTagImageType image_type = GST_TAG_IMAGE_TYPE_NONE; + GstBuffer *tagbuffer = NULL; + GstCaps *caps; + gchar *filename_lc = g_utf8_strdown (filename, -1); + + GST_DEBUG_OBJECT (demux, "Creating tag for attachment with filename '%s', " + "mimetype '%s', description '%s', size %" G_GUINT64_FORMAT, filename, + mimetype, GST_STR_NULL (description), datalen); + + /* TODO: better heuristics for different image types */ + if (strstr (filename_lc, "cover")) { + if (strstr (filename_lc, "back")) + image_type = GST_TAG_IMAGE_TYPE_BACK_COVER; + else + image_type = GST_TAG_IMAGE_TYPE_FRONT_COVER; + } else if (g_str_has_prefix (mimetype, "image/") || + g_str_has_suffix (filename_lc, "png") || + g_str_has_suffix (filename_lc, "jpg") || + g_str_has_suffix (filename_lc, "jpeg") || + g_str_has_suffix (filename_lc, "gif") || + g_str_has_suffix (filename_lc, "bmp")) { + image_type = GST_TAG_IMAGE_TYPE_UNDEFINED; + } + g_free (filename_lc); + + /* First try to create an image tag buffer from this */ + if (image_type != GST_TAG_IMAGE_TYPE_NONE) { + tagbuffer = + gst_tag_image_data_to_image_buffer (data, datalen, image_type); + + if (!tagbuffer) + image_type = GST_TAG_IMAGE_TYPE_NONE; + } + + /* if this failed create an attachment buffer */ + if (!tagbuffer) { + tagbuffer = gst_buffer_new_and_alloc (datalen); + + memcpy (GST_BUFFER_DATA (tagbuffer), data, datalen); + GST_BUFFER_SIZE (tagbuffer) = datalen; + + caps = gst_type_find_helper_for_buffer (NULL, tagbuffer, NULL); + if (caps == NULL) + caps = gst_caps_new_simple (mimetype, NULL); + gst_buffer_set_caps (tagbuffer, caps); + gst_caps_unref (caps); + } + + /* Set filename and description on the caps */ + caps = GST_BUFFER_CAPS (tagbuffer); + gst_caps_set_simple (caps, "filename", G_TYPE_STRING, filename, NULL); + if (description) + gst_caps_set_simple (caps, "description", G_TYPE_STRING, description, + NULL); + + GST_DEBUG_OBJECT (demux, + "Created attachment buffer with caps: %" GST_PTR_FORMAT, caps); + + /* and append to the tag list */ + if (image_type != GST_TAG_IMAGE_TYPE_NONE) + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, GST_TAG_IMAGE, tagbuffer, + NULL); + else + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, GST_TAG_ATTACHMENT, + tagbuffer, NULL); + } + + g_free (filename); + g_free (mimetype); + g_free (data); + g_free (description); + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_attachments (GstMatroskaDemux * demux, + GstEbmlRead * ebml) +{ + guint32 id; + GstFlowReturn ret = GST_FLOW_OK; + GstTagList *taglist; + + DEBUG_ELEMENT_START (demux, ebml, "Attachments"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "Attachments", ret); + return ret; + } + + taglist = gst_tag_list_new (); + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_ATTACHEDFILE: + ret = gst_matroska_demux_parse_attached_file (demux, ebml, taglist); + break; + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "Attachments", id); + break; + } + } + DEBUG_ELEMENT_STOP (demux, ebml, "Attachments", ret); + + if (gst_structure_n_fields (GST_STRUCTURE (taglist)) > 0) { + GST_DEBUG_OBJECT (demux, "Storing attachment tags"); + gst_matroska_demux_found_global_tag (demux, taglist); + } else { + GST_DEBUG_OBJECT (demux, "No valid attachments found"); + gst_tag_list_free (taglist); + } + + demux->attachments_parsed = TRUE; + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_chapters (GstMatroskaDemux * demux, GstEbmlRead * ebml) +{ + guint32 id; + GstFlowReturn ret = GST_FLOW_OK; + + GST_WARNING_OBJECT (demux, "Parsing of chapters not implemented yet"); + + /* TODO: implement parsing of chapters */ + + DEBUG_ELEMENT_START (demux, ebml, "Chapters"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "Chapters", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + default: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "Chapters", ret); + return ret; +} + +/* + * Read signed/unsigned "EBML" numbers. + * Return: number of bytes processed. + */ + +static gint +gst_matroska_ebmlnum_uint (guint8 * data, guint size, guint64 * num) +{ + gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; + guint64 total; + + if (size <= 0) { + return -1; + } + + total = data[0]; + while (read <= 8 && !(total & len_mask)) { + read++; + len_mask >>= 1; + } + if (read > 8) + return -1; + + if ((total &= (len_mask - 1)) == len_mask - 1) + num_ffs++; + if (size < read) + return -1; + while (n < read) { + if (data[n] == 0xff) + num_ffs++; + total = (total << 8) | data[n]; + n++; + } + + if (read == num_ffs && total != 0) + *num = G_MAXUINT64; + else + *num = total; + + return read; +} + +static gint +gst_matroska_ebmlnum_sint (guint8 * data, guint size, gint64 * num) +{ + guint64 unum; + gint res; + + /* read as unsigned number first */ + if ((res = gst_matroska_ebmlnum_uint (data, size, &unum)) < 0) + return -1; + + /* make signed */ + if (unum == G_MAXUINT64) + *num = G_MAXINT64; + else + *num = unum - ((1 << ((7 * res) - 1)) - 1); + + return res; +} + +/* + * Mostly used for subtitles. We add void filler data for each + * lagging stream to make sure we don't deadlock. + */ + +static void +gst_matroska_demux_sync_streams (GstMatroskaDemux * demux) +{ + gint stream_nr; + + GST_LOG_OBJECT (demux, "Sync to %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->segment.last_stop)); + + g_assert (demux->num_streams == demux->src->len); + for (stream_nr = 0; stream_nr < demux->src->len; stream_nr++) { + GstMatroskaTrackContext *context; + + context = g_ptr_array_index (demux->src, stream_nr); + + GST_LOG_OBJECT (demux, + "Checking for resync on stream %d (%" GST_TIME_FORMAT ")", stream_nr, + GST_TIME_ARGS (context->pos)); + + if (G_LIKELY (context->type != GST_MATROSKA_TRACK_TYPE_SUBTITLE)) { + GST_LOG_OBJECT (demux, "Skipping sync on non-subtitle stream"); + continue; + } + + /* does it lag? 0.5 seconds is a random threshold... + * lag need only be considered if we have advanced into requested segment */ + if (GST_CLOCK_TIME_IS_VALID (context->pos) && + GST_CLOCK_TIME_IS_VALID (demux->segment.last_stop) && + demux->segment.last_stop > demux->segment.start && + context->pos + (GST_SECOND / 2) < demux->segment.last_stop) { + gint64 new_start; + + new_start = demux->segment.last_stop - (GST_SECOND / 2); + if (GST_CLOCK_TIME_IS_VALID (demux->segment.stop)) + new_start = MIN (new_start, demux->segment.stop); + GST_DEBUG_OBJECT (demux, + "Synchronizing stream %d with others by advancing time " "from %" + GST_TIME_FORMAT " to %" GST_TIME_FORMAT, stream_nr, + GST_TIME_ARGS (context->pos), GST_TIME_ARGS (new_start)); + + context->pos = new_start; + + /* advance stream time */ + gst_pad_push_event (context->pad, + gst_event_new_new_segment (TRUE, demux->segment.rate, + demux->segment.format, new_start, + demux->segment.stop, new_start)); + } + } +} + +static GstFlowReturn +gst_matroska_demux_push_hdr_buf (GstMatroskaDemux * demux, + GstMatroskaTrackContext * stream, guint8 * data, guint len) +{ + GstFlowReturn ret, cret; + GstBuffer *header_buf; + + header_buf = gst_buffer_new_and_alloc (len); + gst_buffer_set_caps (header_buf, stream->caps); + memcpy (GST_BUFFER_DATA (header_buf), data, len); + + if (stream->set_discont) { + GST_BUFFER_FLAG_SET (header_buf, GST_BUFFER_FLAG_DISCONT); + stream->set_discont = FALSE; + } + + ret = gst_pad_push (stream->pad, header_buf); + + /* combine flows */ + cret = gst_matroska_demux_combine_flows (demux, stream, ret); + + return cret; +} + +static GstFlowReturn +gst_matroska_demux_push_flac_codec_priv_data (GstMatroskaDemux * demux, + GstMatroskaTrackContext * stream) +{ + GstFlowReturn ret; + guint8 *pdata; + guint off, len; + + GST_LOG_OBJECT (demux, "priv data size = %u", stream->codec_priv_size); + + pdata = (guint8 *) stream->codec_priv; + + /* need at least 'fLaC' marker + STREAMINFO metadata block */ + if (stream->codec_priv_size < ((4) + (4 + 34))) { + GST_WARNING_OBJECT (demux, "not enough codec priv data for flac headers"); + return GST_FLOW_ERROR; + } + + if (memcmp (pdata, "fLaC", 4) != 0) { + GST_WARNING_OBJECT (demux, "no flac marker at start of stream headers"); + return GST_FLOW_ERROR; + } + + ret = gst_matroska_demux_push_hdr_buf (demux, stream, pdata, 4); + if (ret != GST_FLOW_OK) + return ret; + + off = 4; /* skip fLaC marker */ + while (off < stream->codec_priv_size) { + len = GST_READ_UINT8 (pdata + off + 1) << 16; + len |= GST_READ_UINT8 (pdata + off + 2) << 8; + len |= GST_READ_UINT8 (pdata + off + 3); + + GST_DEBUG_OBJECT (demux, "header packet: len=%u bytes, flags=0x%02x", + len, (guint) pdata[off]); + + ret = gst_matroska_demux_push_hdr_buf (demux, stream, pdata + off, len + 4); + if (ret != GST_FLOW_OK) + return ret; + + off += 4 + len; + } + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_matroska_demux_push_speex_codec_priv_data (GstMatroskaDemux * demux, + GstMatroskaTrackContext * stream) +{ + GstFlowReturn ret; + guint8 *pdata; + + GST_LOG_OBJECT (demux, "priv data size = %u", stream->codec_priv_size); + + pdata = (guint8 *) stream->codec_priv; + + /* need at least 'fLaC' marker + STREAMINFO metadata block */ + if (stream->codec_priv_size < 80) { + GST_WARNING_OBJECT (demux, "not enough codec priv data for speex headers"); + return GST_FLOW_ERROR; + } + + if (memcmp (pdata, "Speex ", 8) != 0) { + GST_WARNING_OBJECT (demux, "no Speex marker at start of stream headers"); + return GST_FLOW_ERROR; + } + + ret = gst_matroska_demux_push_hdr_buf (demux, stream, pdata, 80); + if (ret != GST_FLOW_OK) + return ret; + + if (stream->codec_priv_size == 80) + return ret; + else + return gst_matroska_demux_push_hdr_buf (demux, stream, pdata + 80, + stream->codec_priv_size - 80); +} + +static GstFlowReturn +gst_matroska_demux_push_xiph_codec_priv_data (GstMatroskaDemux * demux, + GstMatroskaTrackContext * stream) +{ + GstFlowReturn ret; + guint8 *p = (guint8 *) stream->codec_priv; + gint i, offset, num_packets; + guint *length, last; + + if (stream->codec_priv == NULL || stream->codec_priv_size == 0) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("Missing codec private data for xiph headers, broken file")); + return GST_FLOW_ERROR; + } + + /* start of the stream and vorbis audio or theora video, need to + * send the codec_priv data as first three packets */ + num_packets = p[0] + 1; + GST_DEBUG_OBJECT (demux, "%u stream headers, total length=%u bytes", + (guint) num_packets, stream->codec_priv_size); + + length = g_alloca (num_packets * sizeof (guint)); + last = 0; + offset = 1; + + /* first packets, read length values */ + for (i = 0; i < num_packets - 1; i++) { + length[i] = 0; + while (offset < stream->codec_priv_size) { + length[i] += p[offset]; + if (p[offset++] != 0xff) + break; + } + last += length[i]; + } + if (offset + last > stream->codec_priv_size) + return GST_FLOW_ERROR; + + /* last packet is the remaining size */ + length[i] = stream->codec_priv_size - offset - last; + + for (i = 0; i < num_packets; i++) { + GST_DEBUG_OBJECT (demux, "buffer %d: length=%u bytes", i, + (guint) length[i]); + if (offset + length[i] > stream->codec_priv_size) + return GST_FLOW_ERROR; + + ret = + gst_matroska_demux_push_hdr_buf (demux, stream, p + offset, length[i]); + if (ret != GST_FLOW_OK) + return ret; + + offset += length[i]; + } + return GST_FLOW_OK; +} + +static void +gst_matroska_demux_push_dvd_clut_change_event (GstMatroskaDemux * demux, + GstMatroskaTrackContext * stream) +{ + gchar *buf, *start; + + g_assert (!strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_VOBSUB)); + + if (!stream->codec_priv) + return; + + /* ideally, VobSub private data should be parsed and stored more convenient + * elsewhere, but for now, only interested in a small part */ + + /* make sure we have terminating 0 */ + buf = g_strndup ((gchar *) stream->codec_priv, stream->codec_priv_size); + + /* just locate and parse palette part */ + start = strstr (buf, "palette:"); + if (start) { + gint i; + guint32 clut[16]; + guint32 col; + guint8 r, g, b, y, u, v; + + start += 8; + while (g_ascii_isspace (*start)) + start++; + for (i = 0; i < 16; i++) { + if (sscanf (start, "%06x", &col) != 1) + break; + start += 6; + while ((*start == ',') || g_ascii_isspace (*start)) + start++; + /* sigh, need to convert this from vobsub pseudo-RGB to YUV */ + r = (col >> 16) & 0xff; + g = (col >> 8) & 0xff; + b = col & 0xff; + y = CLAMP ((0.1494 * r + 0.6061 * g + 0.2445 * b) * 219 / 255 + 16, 0, + 255); + u = CLAMP (0.6066 * r - 0.4322 * g - 0.1744 * b + 128, 0, 255); + v = CLAMP (-0.08435 * r - 0.3422 * g + 0.4266 * b + 128, 0, 255); + clut[i] = (y << 16) | (u << 8) | v; + } + + /* got them all without problems; build and send event */ + if (i == 16) { + GstStructure *s; + + s = gst_structure_new ("application/x-gst-dvd", "event", G_TYPE_STRING, + "dvd-spu-clut-change", "clut00", G_TYPE_INT, clut[0], "clut01", + G_TYPE_INT, clut[1], "clut02", G_TYPE_INT, clut[2], "clut03", + G_TYPE_INT, clut[3], "clut04", G_TYPE_INT, clut[4], "clut05", + G_TYPE_INT, clut[5], "clut06", G_TYPE_INT, clut[6], "clut07", + G_TYPE_INT, clut[7], "clut08", G_TYPE_INT, clut[8], "clut09", + G_TYPE_INT, clut[9], "clut10", G_TYPE_INT, clut[10], "clut11", + G_TYPE_INT, clut[11], "clut12", G_TYPE_INT, clut[12], "clut13", + G_TYPE_INT, clut[13], "clut14", G_TYPE_INT, clut[14], "clut15", + G_TYPE_INT, clut[15], NULL); + + gst_pad_push_event (stream->pad, + gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s)); + } + } + g_free (buf); +} + +static GstFlowReturn +gst_matroska_demux_add_mpeg_seq_header (GstElement * element, + GstMatroskaTrackContext * stream, GstBuffer ** buf) +{ + guint8 *seq_header; + guint seq_header_len; + guint32 header; + + if (stream->codec_state) { + seq_header = stream->codec_state; + seq_header_len = stream->codec_state_size; + } else if (stream->codec_priv) { + seq_header = stream->codec_priv; + seq_header_len = stream->codec_priv_size; + } else { + return GST_FLOW_OK; + } + + /* Sequence header only needed for keyframes */ + if (GST_BUFFER_FLAG_IS_SET (*buf, GST_BUFFER_FLAG_DELTA_UNIT)) + return GST_FLOW_OK; + + if (GST_BUFFER_SIZE (*buf) < 4) + return GST_FLOW_OK; + + header = GST_READ_UINT32_BE (GST_BUFFER_DATA (*buf)); + /* Sequence start code, if not found prepend */ + if (header != 0x000001b3) { + GstBuffer *newbuf; + + newbuf = gst_buffer_new_and_alloc (GST_BUFFER_SIZE (*buf) + seq_header_len); + gst_buffer_set_caps (newbuf, stream->caps); + + GST_DEBUG_OBJECT (element, "Prepending MPEG sequence header"); + gst_buffer_copy_metadata (newbuf, *buf, GST_BUFFER_COPY_TIMESTAMPS | + GST_BUFFER_COPY_FLAGS); + g_memmove (GST_BUFFER_DATA (newbuf), seq_header, seq_header_len); + g_memmove (GST_BUFFER_DATA (newbuf) + seq_header_len, + GST_BUFFER_DATA (*buf), GST_BUFFER_SIZE (*buf)); + gst_buffer_unref (*buf); + *buf = newbuf; + } + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_matroska_demux_add_wvpk_header (GstElement * element, + GstMatroskaTrackContext * stream, GstBuffer ** buf) +{ + GstMatroskaTrackAudioContext *audiocontext = + (GstMatroskaTrackAudioContext *) stream; + GstBuffer *newbuf = NULL; + guint8 *data; + guint newlen; + Wavpack4Header wvh; + + wvh.ck_id[0] = 'w'; + wvh.ck_id[1] = 'v'; + wvh.ck_id[2] = 'p'; + wvh.ck_id[3] = 'k'; + + wvh.version = GST_READ_UINT16_LE (stream->codec_priv); + wvh.track_no = 0; + wvh.index_no = 0; + wvh.total_samples = -1; + wvh.block_index = audiocontext->wvpk_block_index; + + if (audiocontext->channels <= 2) { + guint32 block_samples; + + block_samples = GST_READ_UINT32_LE (GST_BUFFER_DATA (*buf)); + /* we need to reconstruct the header of the wavpack block */ + + /* -20 because ck_size is the size of the wavpack block -8 + * and lace_size is the size of the wavpack block + 12 + * (the three guint32 of the header that already are in the buffer) */ + wvh.ck_size = GST_BUFFER_SIZE (*buf) + sizeof (Wavpack4Header) - 20; + + /* block_samples, flags and crc are already in the buffer */ + newlen = GST_BUFFER_SIZE (*buf) + sizeof (Wavpack4Header) - 12; + newbuf = gst_buffer_new_and_alloc (newlen); + gst_buffer_set_caps (newbuf, stream->caps); + + data = GST_BUFFER_DATA (newbuf); + data[0] = 'w'; + data[1] = 'v'; + data[2] = 'p'; + data[3] = 'k'; + GST_WRITE_UINT32_LE (data + 4, wvh.ck_size); + GST_WRITE_UINT16_LE (data + 8, wvh.version); + GST_WRITE_UINT8 (data + 10, wvh.track_no); + GST_WRITE_UINT8 (data + 11, wvh.index_no); + GST_WRITE_UINT32_LE (data + 12, wvh.total_samples); + GST_WRITE_UINT32_LE (data + 16, wvh.block_index); + g_memmove (data + 20, GST_BUFFER_DATA (*buf), GST_BUFFER_SIZE (*buf)); + gst_buffer_copy_metadata (newbuf, *buf, + GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_FLAGS); + gst_buffer_unref (*buf); + *buf = newbuf; + audiocontext->wvpk_block_index += block_samples; + } else { + guint8 *outdata; + guint outpos = 0; + guint size; + guint32 block_samples, flags, crc, blocksize; + + data = GST_BUFFER_DATA (*buf); + size = GST_BUFFER_SIZE (*buf); + + if (size < 4) { + GST_ERROR_OBJECT (element, "Too small wavpack buffer"); + return GST_FLOW_ERROR; + } + + block_samples = GST_READ_UINT32_LE (data); + data += 4; + size -= 4; + + while (size > 12) { + flags = GST_READ_UINT32_LE (data); + data += 4; + size -= 4; + crc = GST_READ_UINT32_LE (data); + data += 4; + size -= 4; + blocksize = GST_READ_UINT32_LE (data); + data += 4; + size -= 4; + + if (blocksize == 0 || size < blocksize) + break; + + if (newbuf == NULL) { + newbuf = gst_buffer_new_and_alloc (sizeof (Wavpack4Header) + blocksize); + gst_buffer_set_caps (newbuf, stream->caps); + + gst_buffer_copy_metadata (newbuf, *buf, + GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_FLAGS); + + outpos = 0; + outdata = GST_BUFFER_DATA (newbuf); + } else { + GST_BUFFER_SIZE (newbuf) += sizeof (Wavpack4Header) + blocksize; + GST_BUFFER_DATA (newbuf) = + g_realloc (GST_BUFFER_DATA (newbuf), GST_BUFFER_SIZE (newbuf)); + GST_BUFFER_MALLOCDATA (newbuf) = GST_BUFFER_DATA (newbuf); + outdata = GST_BUFFER_DATA (newbuf); + } + + outdata[outpos] = 'w'; + outdata[outpos + 1] = 'v'; + outdata[outpos + 2] = 'p'; + outdata[outpos + 3] = 'k'; + outpos += 4; + + GST_WRITE_UINT32_LE (outdata + outpos, + blocksize + sizeof (Wavpack4Header) - 8); + GST_WRITE_UINT16_LE (outdata + outpos + 4, wvh.version); + GST_WRITE_UINT8 (outdata + outpos + 6, wvh.track_no); + GST_WRITE_UINT8 (outdata + outpos + 7, wvh.index_no); + GST_WRITE_UINT32_LE (outdata + outpos + 8, wvh.total_samples); + GST_WRITE_UINT32_LE (outdata + outpos + 12, wvh.block_index); + GST_WRITE_UINT32_LE (outdata + outpos + 16, block_samples); + GST_WRITE_UINT32_LE (outdata + outpos + 20, flags); + GST_WRITE_UINT32_LE (outdata + outpos + 24, crc); + outpos += 28; + + g_memmove (outdata + outpos, data, blocksize); + outpos += blocksize; + data += blocksize; + size -= blocksize; + } + gst_buffer_unref (*buf); + *buf = newbuf; + audiocontext->wvpk_block_index += block_samples; + } + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_matroska_demux_check_subtitle_buffer (GstElement * element, + GstMatroskaTrackContext * stream, GstBuffer ** buf) +{ + GstMatroskaTrackSubtitleContext *sub_stream; + const gchar *encoding, *data; + GError *err = NULL; + GstBuffer *newbuf; + gchar *utf8; + guint size; + + sub_stream = (GstMatroskaTrackSubtitleContext *) stream; + + data = (const gchar *) GST_BUFFER_DATA (*buf); + size = GST_BUFFER_SIZE (*buf); + + if (!sub_stream->invalid_utf8) { + if (g_utf8_validate (data, size, NULL)) { + return GST_FLOW_OK; + } + GST_WARNING_OBJECT (element, "subtitle stream %d is not valid UTF-8, this " + "is broken according to the matroska specification", stream->num); + sub_stream->invalid_utf8 = TRUE; + } + + /* file with broken non-UTF8 subtitle, do the best we can do to fix it */ + encoding = g_getenv ("GST_SUBTITLE_ENCODING"); + if (encoding == NULL || *encoding == '\0') { + /* if local encoding is UTF-8 and no encoding specified + * via the environment variable, assume ISO-8859-15 */ + if (g_get_charset (&encoding)) { + encoding = "ISO-8859-15"; + } + } + + utf8 = g_convert_with_fallback (data, size, "UTF-8", encoding, (char *) "*", + NULL, NULL, &err); + + if (err) { + GST_LOG_OBJECT (element, "could not convert string from '%s' to UTF-8: %s", + encoding, err->message); + g_error_free (err); + g_free (utf8); + + /* invalid input encoding, fall back to ISO-8859-15 (always succeeds) */ + encoding = "ISO-8859-15"; + utf8 = g_convert_with_fallback (data, size, "UTF-8", encoding, (char *) "*", + NULL, NULL, NULL); + } + + GST_LOG_OBJECT (element, "converted subtitle text from %s to UTF-8 %s", + encoding, (err) ? "(using ISO-8859-15 as fallback)" : ""); + + if (utf8 == NULL) + utf8 = g_strdup ("invalid subtitle"); + + newbuf = gst_buffer_new (); + GST_BUFFER_MALLOCDATA (newbuf) = (guint8 *) utf8; + GST_BUFFER_DATA (newbuf) = (guint8 *) utf8; + GST_BUFFER_SIZE (newbuf) = strlen (utf8); + gst_buffer_copy_metadata (newbuf, *buf, + GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_FLAGS); + gst_buffer_unref (*buf); + + *buf = newbuf; + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_matroska_demux_check_aac (GstElement * element, + GstMatroskaTrackContext * stream, GstBuffer ** buf) +{ + const guint8 *data; + guint size; + + data = GST_BUFFER_DATA (*buf); + size = GST_BUFFER_SIZE (*buf); + + if (size > 2 && data[0] == 0xff && (data[1] >> 4 == 0x0f)) { + GstCaps *new_caps; + GstStructure *s; + + /* tss, ADTS data, remove codec_data + * still assume it is at least parsed */ + new_caps = gst_caps_copy (stream->caps); + s = gst_caps_get_structure (new_caps, 0); + g_assert (s); + gst_structure_remove_field (s, "codec_data"); + gst_caps_replace (&stream->caps, new_caps); + gst_pad_set_caps (stream->pad, new_caps); + gst_buffer_set_caps (*buf, new_caps); + GST_DEBUG_OBJECT (element, "ADTS AAC audio data; removing codec-data, " + "new caps: %" GST_PTR_FORMAT, new_caps); + gst_caps_unref (new_caps); + } + + /* disable subsequent checking */ + stream->postprocess_frame = NULL; + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, + GstEbmlRead * ebml, guint64 cluster_time, guint64 cluster_offset, + gboolean is_simpleblock) +{ + GstMatroskaTrackContext *stream = NULL; + GstFlowReturn ret = GST_FLOW_OK; + gboolean readblock = FALSE; + guint32 id; + guint64 block_duration = 0; + GstBuffer *buf = NULL; + gint stream_num = -1, n, laces = 0; + guint size = 0; + gint *lace_size = NULL; + gint64 time = 0; + gint flags = 0; + gint64 referenceblock = 0; + gint64 offset; + + offset = gst_ebml_read_get_offset (ebml); + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if (!is_simpleblock) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) { + goto data_error; + } + } else { + id = GST_MATROSKA_ID_SIMPLEBLOCK; + } + + switch (id) { + /* one block inside the group. Note, block parsing is one + * of the harder things, so this code is a bit complicated. + * See http://www.matroska.org/ for documentation. */ + case GST_MATROSKA_ID_SIMPLEBLOCK: + case GST_MATROSKA_ID_BLOCK: + { + guint64 num; + guint8 *data; + + if (buf) { + gst_buffer_unref (buf); + buf = NULL; + } + if ((ret = gst_ebml_read_buffer (ebml, &id, &buf)) != GST_FLOW_OK) + break; + + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + + /* first byte(s): blocknum */ + if ((n = gst_matroska_ebmlnum_uint (data, size, &num)) < 0) + goto data_error; + data += n; + size -= n; + + /* fetch stream from num */ + stream_num = gst_matroska_demux_stream_from_num (demux, num); + if (G_UNLIKELY (size < 3)) { + GST_WARNING_OBJECT (demux, "Invalid size %u", size); + /* non-fatal, try next block(group) */ + ret = GST_FLOW_OK; + goto done; + } else if (G_UNLIKELY (stream_num < 0 || + stream_num >= demux->num_streams)) { + /* let's not give up on a stray invalid track number */ + GST_WARNING_OBJECT (demux, + "Invalid stream %d for track number %" G_GUINT64_FORMAT + "; ignoring block", stream_num, num); + goto done; + } + + stream = g_ptr_array_index (demux->src, stream_num); + + /* time (relative to cluster time) */ + time = ((gint16) GST_READ_UINT16_BE (data)); + data += 2; + size -= 2; + flags = GST_READ_UINT8 (data); + data += 1; + size -= 1; + + GST_LOG_OBJECT (demux, "time %" G_GUINT64_FORMAT ", flags %d", time, + flags); + + switch ((flags & 0x06) >> 1) { + case 0x0: /* no lacing */ + laces = 1; + lace_size = g_new (gint, 1); + lace_size[0] = size; + break; + + case 0x1: /* xiph lacing */ + case 0x2: /* fixed-size lacing */ + case 0x3: /* EBML lacing */ + if (size == 0) + goto invalid_lacing; + laces = GST_READ_UINT8 (data) + 1; + data += 1; + size -= 1; + lace_size = g_new0 (gint, laces); + + switch ((flags & 0x06) >> 1) { + case 0x1: /* xiph lacing */ { + guint temp, total = 0; + + for (n = 0; ret == GST_FLOW_OK && n < laces - 1; n++) { + while (1) { + if (size == 0) + goto invalid_lacing; + temp = GST_READ_UINT8 (data); + lace_size[n] += temp; + data += 1; + size -= 1; + if (temp != 0xff) + break; + } + total += lace_size[n]; + } + lace_size[n] = size - total; + break; + } + + case 0x2: /* fixed-size lacing */ + for (n = 0; n < laces; n++) + lace_size[n] = size / laces; + break; + + case 0x3: /* EBML lacing */ { + guint total; + + if ((n = gst_matroska_ebmlnum_uint (data, size, &num)) < 0) + goto data_error; + data += n; + size -= n; + total = lace_size[0] = num; + for (n = 1; ret == GST_FLOW_OK && n < laces - 1; n++) { + gint64 snum; + gint r; + + if ((r = gst_matroska_ebmlnum_sint (data, size, &snum)) < 0) + goto data_error; + data += r; + size -= r; + lace_size[n] = lace_size[n - 1] + snum; + total += lace_size[n]; + } + if (n < laces) + lace_size[n] = size - total; + break; + } + } + break; + } + + if (stream->send_xiph_headers) { + ret = gst_matroska_demux_push_xiph_codec_priv_data (demux, stream); + stream->send_xiph_headers = FALSE; + } + + if (stream->send_flac_headers) { + ret = gst_matroska_demux_push_flac_codec_priv_data (demux, stream); + stream->send_flac_headers = FALSE; + } + + if (stream->send_speex_headers) { + ret = gst_matroska_demux_push_speex_codec_priv_data (demux, stream); + stream->send_speex_headers = FALSE; + } + + if (stream->send_dvd_event) { + gst_matroska_demux_push_dvd_clut_change_event (demux, stream); + /* FIXME: should we send this event again after (flushing) seek ? */ + stream->send_dvd_event = FALSE; + } + + if (ret != GST_FLOW_OK) + break; + + readblock = TRUE; + break; + } + + case GST_MATROSKA_ID_BLOCKDURATION:{ + ret = gst_ebml_read_uint (ebml, &id, &block_duration); + GST_DEBUG_OBJECT (demux, "BlockDuration: %" G_GUINT64_FORMAT, + block_duration); + break; + } + + case GST_MATROSKA_ID_REFERENCEBLOCK:{ + ret = gst_ebml_read_sint (ebml, &id, &referenceblock); + GST_DEBUG_OBJECT (demux, "ReferenceBlock: %" G_GINT64_FORMAT, + referenceblock); + break; + } + + case GST_MATROSKA_ID_CODECSTATE:{ + guint8 *data; + guint64 data_len = 0; + + if ((ret = + gst_ebml_read_binary (ebml, &id, &data, + &data_len)) != GST_FLOW_OK) + break; + + if (G_UNLIKELY (stream == NULL)) { + GST_WARNING_OBJECT (demux, + "Unexpected CodecState subelement - ignoring"); + break; + } + + g_free (stream->codec_state); + stream->codec_state = data; + stream->codec_state_size = data_len; + + /* Decode if necessary */ + if (stream->encodings && stream->encodings->len > 0 + && stream->codec_state && stream->codec_state_size > 0) { + if (!gst_matroska_decode_data (stream->encodings, + &stream->codec_state, &stream->codec_state_size, + GST_MATROSKA_TRACK_ENCODING_SCOPE_CODEC_DATA, TRUE)) { + GST_WARNING_OBJECT (demux, "Decoding codec state failed"); + } + } + + GST_DEBUG_OBJECT (demux, "CodecState of %u bytes", + stream->codec_state_size); + break; + } + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "BlockGroup", id); + break; + + case GST_MATROSKA_ID_BLOCKVIRTUAL: + case GST_MATROSKA_ID_BLOCKADDITIONS: + case GST_MATROSKA_ID_REFERENCEPRIORITY: + case GST_MATROSKA_ID_REFERENCEVIRTUAL: + case GST_MATROSKA_ID_SLICES: + GST_DEBUG_OBJECT (demux, + "Skipping BlockGroup subelement 0x%x - ignoring", id); + ret = gst_ebml_read_skip (ebml); + break; + } + + if (is_simpleblock) + break; + } + + /* reading a number or so could have failed */ + if (ret != GST_FLOW_OK) + goto data_error; + + if (ret == GST_FLOW_OK && readblock) { + guint64 duration = 0; + gint64 lace_time = 0; + gboolean delta_unit; + + stream = g_ptr_array_index (demux->src, stream_num); + + if (cluster_time != GST_CLOCK_TIME_NONE) { + /* FIXME: What to do with negative timestamps? Give timestamp 0 or -1? + * Drop unless the lace contains timestamp 0? */ + if (time < 0 && (-time) > cluster_time) { + lace_time = 0; + } else { + if (stream->timecodescale == 1.0) + lace_time = (cluster_time + time) * demux->time_scale; + else + lace_time = + gst_util_guint64_to_gdouble ((cluster_time + time) * + demux->time_scale) * stream->timecodescale; + } + } else { + lace_time = GST_CLOCK_TIME_NONE; + } + + /* need to refresh segment info ASAP */ + if (GST_CLOCK_TIME_IS_VALID (lace_time) && demux->need_newsegment) { + GST_DEBUG_OBJECT (demux, + "generating segment starting at %" GST_TIME_FORMAT, + GST_TIME_ARGS (lace_time)); + /* pretend we seeked here */ + gst_segment_set_seek (&demux->segment, demux->segment.rate, + GST_FORMAT_TIME, 0, GST_SEEK_TYPE_SET, lace_time, + GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE, NULL); + /* now convey our segment notion downstream */ + gst_matroska_demux_send_event (demux, gst_event_new_new_segment (FALSE, + demux->segment.rate, demux->segment.format, demux->segment.start, + demux->segment.stop, demux->segment.start)); + demux->need_newsegment = FALSE; + } + + if (block_duration) { + if (stream->timecodescale == 1.0) + duration = gst_util_uint64_scale (block_duration, demux->time_scale, 1); + else + duration = + gst_util_gdouble_to_guint64 (gst_util_guint64_to_gdouble + (gst_util_uint64_scale (block_duration, demux->time_scale, + 1)) * stream->timecodescale); + } else if (stream->default_duration) { + duration = stream->default_duration * laces; + } + /* else duration is diff between timecode of this and next block */ + + /* For SimpleBlock, look at the keyframe bit in flags. Otherwise, + a ReferenceBlock implies that this is not a keyframe. In either + case, it only makes sense for video streams. */ + delta_unit = stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO && + ((is_simpleblock && !(flags & 0x80)) || referenceblock); + + if (delta_unit && stream->set_discont) { + /* When doing seeks or such, we need to restart on key frames or + * decoders might choke. */ + GST_DEBUG_OBJECT (demux, "skipping delta unit"); + goto done; + } + + for (n = 0; n < laces; n++) { + GstBuffer *sub; + + if (G_UNLIKELY (lace_size[n] > size)) { + GST_WARNING_OBJECT (demux, "Invalid lace size"); + break; + } + + /* QoS for video track with an index. the assumption is that + index entries point to keyframes, but if that is not true we + will instad skip until the next keyframe. */ + if (GST_CLOCK_TIME_IS_VALID (lace_time) && + stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO && + stream->index_table && demux->segment.rate > 0.0) { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) stream; + GstClockTime earliest_time; + GstClockTime earliest_stream_time; + + GST_OBJECT_LOCK (demux); + earliest_time = videocontext->earliest_time; + GST_OBJECT_UNLOCK (demux); + earliest_stream_time = gst_segment_to_position (&demux->segment, + GST_FORMAT_TIME, earliest_time); + + if (GST_CLOCK_TIME_IS_VALID (lace_time) && + GST_CLOCK_TIME_IS_VALID (earliest_stream_time) && + lace_time <= earliest_stream_time) { + /* find index entry (keyframe) <= earliest_stream_time */ + GstMatroskaIndex *entry = + gst_util_array_binary_search (stream->index_table->data, + stream->index_table->len, sizeof (GstMatroskaIndex), + (GCompareDataFunc) gst_matroska_index_seek_find, + GST_SEARCH_MODE_BEFORE, &earliest_stream_time, NULL); + + /* if that entry (keyframe) is after the current the current + buffer, we can skip pushing (and thus decoding) all + buffers until that keyframe. */ + if (entry && GST_CLOCK_TIME_IS_VALID (entry->time) && + entry->time > lace_time) { + GST_LOG_OBJECT (demux, "Skipping lace before late keyframe"); + stream->set_discont = TRUE; + goto next_lace; + } + } + } + + sub = gst_buffer_create_sub (buf, + GST_BUFFER_SIZE (buf) - size, lace_size[n]); + GST_DEBUG_OBJECT (demux, "created subbuffer %p", sub); + + if (delta_unit) + GST_BUFFER_FLAG_SET (sub, GST_BUFFER_FLAG_DELTA_UNIT); + else + GST_BUFFER_FLAG_UNSET (sub, GST_BUFFER_FLAG_DELTA_UNIT); + + if (stream->encodings != NULL && stream->encodings->len > 0) + sub = gst_matroska_decode_buffer (stream, sub); + + if (sub == NULL) { + GST_WARNING_OBJECT (demux, "Decoding buffer failed"); + goto next_lace; + } + + GST_BUFFER_TIMESTAMP (sub) = lace_time; + + if (GST_CLOCK_TIME_IS_VALID (lace_time)) { + GstClockTime last_stop_end; + + /* Check if this stream is after segment stop */ + if (GST_CLOCK_TIME_IS_VALID (demux->segment.stop) && + lace_time >= demux->segment.stop) { + GST_DEBUG_OBJECT (demux, + "Stream %d after segment stop %" GST_TIME_FORMAT, stream->index, + GST_TIME_ARGS (demux->segment.stop)); + gst_buffer_unref (sub); + goto eos; + } + if (offset >= stream->to_offset) { + GST_DEBUG_OBJECT (demux, "Stream %d after playback section", + stream->index); + gst_buffer_unref (sub); + goto eos; + } + + /* handle gaps, e.g. non-zero start-time, or an cue index entry + * that landed us with timestamps not quite intended */ + if (GST_CLOCK_TIME_IS_VALID (demux->segment.last_stop) && + demux->segment.rate > 0.0) { + GstClockTimeDiff diff; + + /* only send newsegments with increasing start times, + * otherwise if these go back and forth downstream (sinks) increase + * accumulated time and running_time */ + diff = GST_CLOCK_DIFF (demux->segment.last_stop, lace_time); + if (diff > 2 * GST_SECOND && lace_time > demux->segment.start && + (!GST_CLOCK_TIME_IS_VALID (demux->segment.stop) || + lace_time < demux->segment.stop)) { + GST_DEBUG_OBJECT (demux, + "Gap of %" G_GINT64_FORMAT " ns detected in" + "stream %d (%" GST_TIME_FORMAT " -> %" GST_TIME_FORMAT "). " + "Sending updated NEWSEGMENT events", diff, + stream->index, GST_TIME_ARGS (stream->pos), + GST_TIME_ARGS (lace_time)); + /* send newsegment events such that the gap is not accounted in + * accum time, hence running_time */ + /* close ahead of gap */ + gst_matroska_demux_send_event (demux, + gst_event_new_new_segment (TRUE, demux->segment.rate, + demux->segment.format, demux->segment.last_stop, + demux->segment.last_stop, demux->segment.last_stop)); + /* skip gap */ + gst_matroska_demux_send_event (demux, + gst_event_new_new_segment (FALSE, demux->segment.rate, + demux->segment.format, lace_time, demux->segment.stop, + lace_time)); + /* align segment view with downstream, + * prevents double-counting accum when closing segment */ + gst_segment_set_newsegment (&demux->segment, FALSE, + demux->segment.rate, demux->segment.format, lace_time, + demux->segment.stop, lace_time); + demux->segment.last_stop = lace_time; + } + } + + if (!GST_CLOCK_TIME_IS_VALID (demux->segment.last_stop) + || demux->segment.last_stop < lace_time) { + demux->segment.last_stop = lace_time; + } + + last_stop_end = lace_time; + if (duration) { + GST_BUFFER_DURATION (sub) = duration / laces; + last_stop_end += GST_BUFFER_DURATION (sub); + } + + if (!GST_CLOCK_TIME_IS_VALID (demux->last_stop_end) || + demux->last_stop_end < last_stop_end) + demux->last_stop_end = last_stop_end; + + if (demux->segment.duration == -1 || + demux->segment.duration < lace_time) { + gst_segment_set_duration (&demux->segment, GST_FORMAT_TIME, + last_stop_end); + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_duration (GST_OBJECT_CAST (demux), + GST_FORMAT_TIME, GST_CLOCK_TIME_NONE)); + } + } + + stream->pos = lace_time; + + gst_matroska_demux_sync_streams (demux); + + if (stream->set_discont) { + GST_DEBUG_OBJECT (demux, "marking DISCONT"); + GST_BUFFER_FLAG_SET (sub, GST_BUFFER_FLAG_DISCONT); + stream->set_discont = FALSE; + } + + /* reverse playback book-keeping */ + if (!GST_CLOCK_TIME_IS_VALID (stream->from_time)) + stream->from_time = lace_time; + if (stream->from_offset == -1) + stream->from_offset = offset; + + GST_DEBUG_OBJECT (demux, + "Pushing lace %d, data of size %d for stream %d, time=%" + GST_TIME_FORMAT " and duration=%" GST_TIME_FORMAT, n, + GST_BUFFER_SIZE (sub), stream_num, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (sub)), + GST_TIME_ARGS (GST_BUFFER_DURATION (sub))); + + if (demux->element_index) { + if (stream->index_writer_id == -1) + gst_index_get_writer_id (demux->element_index, + GST_OBJECT (stream->pad), &stream->index_writer_id); + + GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT " for writer id %d", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (sub)), cluster_offset, + stream->index_writer_id); + gst_index_add_association (demux->element_index, + stream->index_writer_id, GST_BUFFER_FLAG_IS_SET (sub, + GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : GST_ASSOCIATION_FLAG_KEY_UNIT, + GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (sub), GST_FORMAT_BYTES, + cluster_offset, NULL); + } + + gst_buffer_set_caps (sub, GST_PAD_CAPS (stream->pad)); + + /* Postprocess the buffers depending on the codec used */ + if (stream->postprocess_frame) { + GST_LOG_OBJECT (demux, "running post process"); + ret = stream->postprocess_frame (GST_ELEMENT (demux), stream, &sub); + } + + ret = gst_pad_push (stream->pad, sub); + if (demux->segment.rate < 0) { + if (lace_time > demux->segment.stop && ret == GST_FLOW_UNEXPECTED) { + /* In reverse playback we can get a GST_FLOW_UNEXPECTED when + * we are at the end of the segment, so we just need to jump + * back to the previous section. */ + GST_DEBUG_OBJECT (demux, "downstream has reached end of segment"); + ret = GST_FLOW_OK; + } + } + /* combine flows */ + ret = gst_matroska_demux_combine_flows (demux, stream, ret); + + next_lace: + size -= lace_size[n]; + if (lace_time != GST_CLOCK_TIME_NONE && duration) + lace_time += duration / laces; + else + lace_time = GST_CLOCK_TIME_NONE; + } + } + +done: + if (buf) + gst_buffer_unref (buf); + g_free (lace_size); + + return ret; + + /* EXITS */ +eos: + { + stream->eos = TRUE; + ret = GST_FLOW_OK; + /* combine flows */ + ret = gst_matroska_demux_combine_flows (demux, stream, ret); + goto done; + } +invalid_lacing: + { + GST_ELEMENT_WARNING (demux, STREAM, DEMUX, (NULL), ("Invalid lacing size")); + /* non-fatal, try next block(group) */ + ret = GST_FLOW_OK; + goto done; + } +data_error: + { + GST_ELEMENT_WARNING (demux, STREAM, DEMUX, (NULL), ("Data error")); + /* non-fatal, try next block(group) */ + ret = GST_FLOW_OK; + goto done; + } +} + +/* return FALSE if block(group) should be skipped (due to a seek) */ +static inline gboolean +gst_matroska_demux_seek_block (GstMatroskaDemux * demux) +{ + if (G_UNLIKELY (demux->seek_block)) { + if (!(--demux->seek_block)) { + return TRUE; + } else { + GST_LOG_OBJECT (demux, "should skip block due to seek"); + return FALSE; + } + } else { + return TRUE; + } +} + +static GstFlowReturn +gst_matroska_demux_parse_contents_seekentry (GstMatroskaDemux * demux, + GstEbmlRead * ebml) +{ + GstFlowReturn ret; + guint64 seek_pos = (guint64) - 1; + guint32 seek_id = 0; + guint32 id; + + DEBUG_ELEMENT_START (demux, ebml, "Seek"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "Seek", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_SEEKID: + { + guint64 t; + + if ((ret = gst_ebml_read_uint (ebml, &id, &t)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "SeekID: %" G_GUINT64_FORMAT, t); + seek_id = t; + break; + } + + case GST_MATROSKA_ID_SEEKPOSITION: + { + guint64 t; + + if ((ret = gst_ebml_read_uint (ebml, &id, &t)) != GST_FLOW_OK) + break; + + if (t > G_MAXINT64) { + GST_WARNING_OBJECT (demux, + "Too large SeekPosition %" G_GUINT64_FORMAT, t); + break; + } + + GST_DEBUG_OBJECT (demux, "SeekPosition: %" G_GUINT64_FORMAT, t); + seek_pos = t; + break; + } + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "SeekHead", id); + break; + } + } + + if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED) + return ret; + + if (!seek_id || seek_pos == (guint64) - 1) { + GST_WARNING_OBJECT (demux, "Incomplete seekhead entry (0x%x/%" + G_GUINT64_FORMAT ")", seek_id, seek_pos); + return GST_FLOW_OK; + } + + switch (seek_id) { + case GST_MATROSKA_ID_SEEKHEAD: + { + } + case GST_MATROSKA_ID_CUES: + case GST_MATROSKA_ID_TAGS: + case GST_MATROSKA_ID_TRACKS: + case GST_MATROSKA_ID_SEGMENTINFO: + case GST_MATROSKA_ID_ATTACHMENTS: + case GST_MATROSKA_ID_CHAPTERS: + { + guint64 before_pos, length; + guint needed; + + /* remember */ + length = gst_matroska_demux_get_length (demux); + before_pos = demux->offset; + + if (length == (guint64) - 1) { + GST_DEBUG_OBJECT (demux, "no upstream length, skipping SeakHead entry"); + break; + } + + /* check for validity */ + if (seek_pos + demux->ebml_segment_start + 12 >= length) { + GST_WARNING_OBJECT (demux, + "SeekHead reference lies outside file!" " (%" + G_GUINT64_FORMAT "+%" G_GUINT64_FORMAT "+12 >= %" + G_GUINT64_FORMAT ")", seek_pos, demux->ebml_segment_start, length); + break; + } + + /* only pick up index location when streaming */ + if (demux->streaming) { + if (seek_id == GST_MATROSKA_ID_CUES) { + demux->index_offset = seek_pos + demux->ebml_segment_start; + GST_DEBUG_OBJECT (demux, "Cues located at offset %" G_GUINT64_FORMAT, + demux->index_offset); + } + break; + } + + /* seek */ + demux->offset = seek_pos + demux->ebml_segment_start; + + /* check ID */ + if ((ret = gst_matroska_demux_peek_id_length_pull (demux, &id, &length, + &needed)) != GST_FLOW_OK) + goto finish; + + if (id != seek_id) { + GST_WARNING_OBJECT (demux, + "We looked for ID=0x%x but got ID=0x%x (pos=%" G_GUINT64_FORMAT ")", + seek_id, id, seek_pos + demux->ebml_segment_start); + } else { + /* now parse */ + ret = gst_matroska_demux_parse_id (demux, id, length, needed); + } + + finish: + /* seek back */ + demux->offset = before_pos; + break; + } + + case GST_MATROSKA_ID_CLUSTER: + { + guint64 pos = seek_pos + demux->ebml_segment_start; + + GST_LOG_OBJECT (demux, "Cluster position"); + if (G_UNLIKELY (!demux->clusters)) + demux->clusters = g_array_sized_new (TRUE, TRUE, sizeof (guint64), 100); + g_array_append_val (demux->clusters, pos); + break; + } + + default: + GST_DEBUG_OBJECT (demux, "Ignoring Seek entry for ID=0x%x", seek_id); + break; + } + DEBUG_ELEMENT_STOP (demux, ebml, "Seek", ret); + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_contents (GstMatroskaDemux * demux, GstEbmlRead * ebml) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + + DEBUG_ELEMENT_START (demux, ebml, "SeekHead"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "SeekHead", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_SEEKENTRY: + { + ret = gst_matroska_demux_parse_contents_seekentry (demux, ebml); + /* Ignore EOS and errors here */ + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (demux, "Ignoring %s", gst_flow_get_name (ret)); + ret = GST_FLOW_OK; + } + break; + } + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "SeekHead", id); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "SeekHead", ret); + + /* Sort clusters by position for easier searching */ + if (demux->clusters) + g_array_sort (demux->clusters, (GCompareFunc) gst_matroska_cluster_compare); + + return ret; +} + +#define GST_FLOW_OVERFLOW GST_FLOW_CUSTOM_ERROR + +#define MAX_BLOCK_SIZE (15 * 1024 * 1024) + +static inline GstFlowReturn +gst_matroska_demux_check_read_size (GstMatroskaDemux * demux, guint64 bytes) +{ + if (G_UNLIKELY (bytes > MAX_BLOCK_SIZE)) { + /* only a few blocks are expected/allowed to be large, + * and will be recursed into, whereas others will be read and must fit */ + if (demux->streaming) { + /* fatal in streaming case, as we can't step over easily */ + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("reading large block of size %" G_GUINT64_FORMAT " not supported; " + "file might be corrupt.", bytes)); + return GST_FLOW_ERROR; + } else { + /* indicate higher level to quietly give up */ + GST_DEBUG_OBJECT (demux, + "too large block of size %" G_GUINT64_FORMAT, bytes); + return GST_FLOW_ERROR; + } + } else { + return GST_FLOW_OK; + } +} + +/* returns TRUE if we truely are in error state, and should give up */ +static inline gboolean +gst_matroska_demux_check_parse_error (GstMatroskaDemux * demux) +{ + if (!demux->streaming && demux->next_cluster_offset > 0) { + /* just repositioning to where next cluster should be and try from there */ + GST_WARNING_OBJECT (demux, "parse error, trying next cluster expected at %" + G_GUINT64_FORMAT, demux->next_cluster_offset); + demux->offset = demux->next_cluster_offset; + demux->next_cluster_offset = 0; + return FALSE; + } else { + gint64 pos; + + /* sigh, one last attempt above and beyond call of duty ...; + * search for cluster mark following current pos */ + pos = demux->offset; + GST_WARNING_OBJECT (demux, "parse error, looking for next cluster"); + if (gst_matroska_demux_search_cluster (demux, &pos) != GST_FLOW_OK) { + /* did not work, give up */ + return TRUE; + } else { + GST_DEBUG_OBJECT (demux, "... found at %" G_GUINT64_FORMAT, pos); + /* try that position */ + demux->offset = pos; + return FALSE; + } + } +} + +static inline GstFlowReturn +gst_matroska_demux_flush (GstMatroskaDemux * demux, guint flush) +{ + GST_LOG_OBJECT (demux, "skipping %d bytes", flush); + demux->offset += flush; + if (demux->streaming) { + GstFlowReturn ret; + + /* hard to skip large blocks when streaming */ + ret = gst_matroska_demux_check_read_size (demux, flush); + if (ret != GST_FLOW_OK) + return ret; + if (flush <= gst_adapter_available (demux->adapter)) + gst_adapter_flush (demux->adapter, flush); + else + return GST_FLOW_UNEXPECTED; + } + return GST_FLOW_OK; +} + +/* initializes @ebml with @bytes from input stream at current offset. + * Returns UNEXPECTED if insufficient available, + * ERROR if too much was attempted to read. */ +static inline GstFlowReturn +gst_matroska_demux_take (GstMatroskaDemux * demux, guint64 bytes, + GstEbmlRead * ebml) +{ + GstBuffer *buffer = NULL; + GstFlowReturn ret = GST_FLOW_OK; + + GST_LOG_OBJECT (demux, "taking %" G_GUINT64_FORMAT " bytes for parsing", + bytes); + ret = gst_matroska_demux_check_read_size (demux, bytes); + if (G_UNLIKELY (ret != GST_FLOW_OK)) { + if (!demux->streaming) { + /* in pull mode, we can skip */ + if ((ret = gst_matroska_demux_flush (demux, bytes)) == GST_FLOW_OK) + ret = GST_FLOW_OVERFLOW; + } else { + /* otherwise fatal */ + ret = GST_FLOW_ERROR; + } + goto exit; + } + if (demux->streaming) { + if (gst_adapter_available (demux->adapter) >= bytes) + buffer = gst_adapter_take_buffer (demux->adapter, bytes); + else + ret = GST_FLOW_UNEXPECTED; + } else + ret = gst_matroska_demux_peek_bytes (demux, demux->offset, bytes, &buffer, + NULL); + if (G_LIKELY (buffer)) { + gst_ebml_read_init (ebml, GST_ELEMENT_CAST (demux), buffer, demux->offset); + demux->offset += bytes; + } +exit: + return ret; +} + +static void +gst_matroska_demux_check_seekability (GstMatroskaDemux * demux) +{ + GstQuery *query; + gboolean seekable = FALSE; + gint64 start = -1, stop = -1; + + query = gst_query_new_seeking (GST_FORMAT_BYTES); + if (!gst_pad_peer_query (demux->sinkpad, query)) { + GST_DEBUG_OBJECT (demux, "seeking query failed"); + goto done; + } + + gst_query_parse_seeking (query, NULL, &seekable, &start, &stop); + + /* try harder to query upstream size if we didn't get it the first time */ + if (seekable && stop == -1) { + GstFormat fmt = GST_FORMAT_BYTES; + + GST_DEBUG_OBJECT (demux, "doing duration query to fix up unset stop"); + gst_pad_query_peer_duration (demux->sinkpad, &fmt, &stop); + } + + /* if upstream doesn't know the size, it's likely that it's not seekable in + * practice even if it technically may be seekable */ + if (seekable && (start != 0 || stop <= start)) { + GST_DEBUG_OBJECT (demux, "seekable but unknown start/stop -> disable"); + seekable = FALSE; + } + +done: + GST_INFO_OBJECT (demux, "seekable: %d (%" G_GUINT64_FORMAT " - %" + G_GUINT64_FORMAT ")", seekable, start, stop); + demux->seekable = seekable; + + gst_query_unref (query); +} + +static GstFlowReturn +gst_matroska_demux_find_tracks (GstMatroskaDemux * demux) +{ + guint32 id; + guint64 before_pos; + guint64 length; + guint needed; + GstFlowReturn ret = GST_FLOW_OK; + + GST_WARNING_OBJECT (demux, + "Found Cluster element before Tracks, searching Tracks"); + + /* remember */ + before_pos = demux->offset; + + /* Search Tracks element */ + while (TRUE) { + ret = gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed); + if (ret != GST_FLOW_OK) + break; + + if (id != GST_MATROSKA_ID_TRACKS) { + /* we may be skipping large cluster here, so forego size check etc */ + /* ... but we can't skip undefined size; force error */ + if (length == G_MAXUINT64) { + ret = gst_matroska_demux_check_read_size (demux, length); + break; + } else { + demux->offset += needed; + demux->offset += length; + } + continue; + } + + /* will lead to track parsing ... */ + ret = gst_matroska_demux_parse_id (demux, id, length, needed); + break; + } + + /* seek back */ + demux->offset = before_pos; + + return ret; +} + +#define GST_READ_CHECK(stmt) \ +G_STMT_START { \ + if (G_UNLIKELY ((ret = (stmt)) != GST_FLOW_OK)) { \ + if (ret == GST_FLOW_OVERFLOW) { \ + ret = GST_FLOW_OK; \ + } \ + goto read_error; \ + } \ +} G_STMT_END + +static GstFlowReturn +gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id, + guint64 length, guint needed) +{ + GstEbmlRead ebml = { 0, }; + GstFlowReturn ret = GST_FLOW_OK; + guint64 read; + + GST_LOG_OBJECT (demux, "Parsing Element id 0x%x, " + "size %" G_GUINT64_FORMAT ", prefix %d", id, length, needed); + + /* if we plan to read and parse this element, we need prefix (id + length) + * and the contents */ + /* mind about overflow wrap-around when dealing with undefined size */ + read = length; + if (G_LIKELY (length != G_MAXUINT64)) + read += needed; + + switch (demux->state) { + case GST_MATROSKA_DEMUX_STATE_START: + switch (id) { + case GST_EBML_ID_HEADER: + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_header (demux, &ebml); + if (ret != GST_FLOW_OK) + goto parse_failed; + demux->state = GST_MATROSKA_DEMUX_STATE_SEGMENT; + gst_matroska_demux_check_seekability (demux); + break; + default: + goto invalid_header; + break; + } + break; + case GST_MATROSKA_DEMUX_STATE_SEGMENT: + switch (id) { + case GST_MATROSKA_ID_SEGMENT: + /* eat segment prefix */ + GST_READ_CHECK (gst_matroska_demux_flush (demux, needed)); + GST_DEBUG_OBJECT (demux, + "Found Segment start at offset %" G_GUINT64_FORMAT, + demux->offset); + /* seeks are from the beginning of the segment, + * after the segment ID/length */ + demux->ebml_segment_start = demux->offset; + demux->state = GST_MATROSKA_DEMUX_STATE_HEADER; + break; + default: + GST_WARNING_OBJECT (demux, + "Expected a Segment ID (0x%x), but received 0x%x!", + GST_MATROSKA_ID_SEGMENT, id); + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + break; + } + break; + case GST_MATROSKA_DEMUX_STATE_SCANNING: + if (id != GST_MATROSKA_ID_CLUSTER && + id != GST_MATROSKA_ID_CLUSTERTIMECODE) + goto skip; + /* fall-through */ + case GST_MATROSKA_DEMUX_STATE_HEADER: + case GST_MATROSKA_DEMUX_STATE_DATA: + case GST_MATROSKA_DEMUX_STATE_SEEK: + switch (id) { + case GST_MATROSKA_ID_SEGMENTINFO: + if (!demux->segmentinfo_parsed) { + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_info (demux, &ebml); + } else { + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + } + break; + case GST_MATROSKA_ID_TRACKS: + if (!demux->tracks_parsed) { + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_tracks (demux, &ebml); + } else { + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + } + break; + case GST_MATROSKA_ID_CLUSTER: + if (G_UNLIKELY (!demux->tracks_parsed)) { + if (demux->streaming) { + GST_DEBUG_OBJECT (demux, "Cluster before Track"); + goto not_streamable; + } else { + ret = gst_matroska_demux_find_tracks (demux); + if (!demux->tracks_parsed) + goto no_tracks; + } + } + if (G_UNLIKELY (demux->state == GST_MATROSKA_DEMUX_STATE_HEADER)) { + demux->state = GST_MATROSKA_DEMUX_STATE_DATA; + demux->first_cluster_offset = demux->offset; + GST_DEBUG_OBJECT (demux, "signaling no more pads"); + gst_element_no_more_pads (GST_ELEMENT (demux)); + /* send initial newsegment */ + gst_matroska_demux_send_event (demux, + gst_event_new_new_segment (FALSE, 1.0, + GST_FORMAT_TIME, 0, + (demux->segment.duration > + 0) ? demux->segment.duration : -1, 0)); + } + demux->cluster_time = GST_CLOCK_TIME_NONE; + demux->cluster_offset = demux->offset; + if (G_UNLIKELY (!demux->seek_first && demux->seek_block)) { + GST_DEBUG_OBJECT (demux, "seek target block %" G_GUINT64_FORMAT + " not found in Cluster, trying next Cluster's first block instead", + demux->seek_block); + demux->seek_block = 0; + } + demux->seek_first = FALSE; + /* record next cluster for recovery */ + if (read != G_MAXUINT64) + demux->next_cluster_offset = demux->cluster_offset + read; + /* eat cluster prefix */ + gst_matroska_demux_flush (demux, needed); + break; + case GST_MATROSKA_ID_CLUSTERTIMECODE: + { + guint64 num; + + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + if ((ret = gst_ebml_read_uint (&ebml, &id, &num)) != GST_FLOW_OK) + goto parse_failed; + GST_DEBUG_OBJECT (demux, "ClusterTimeCode: %" G_GUINT64_FORMAT, num); + demux->cluster_time = num; + if (demux->element_index) { + if (demux->element_index_writer_id == -1) + gst_index_get_writer_id (demux->element_index, + GST_OBJECT (demux), &demux->element_index_writer_id); + GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT " for writer id %d", + GST_TIME_ARGS (demux->cluster_time), demux->cluster_offset, + demux->element_index_writer_id); + gst_index_add_association (demux->element_index, + demux->element_index_writer_id, GST_ASSOCIATION_FLAG_KEY_UNIT, + GST_FORMAT_TIME, demux->cluster_time, + GST_FORMAT_BYTES, demux->cluster_offset, NULL); + } + break; + } + case GST_MATROSKA_ID_BLOCKGROUP: + if (!gst_matroska_demux_seek_block (demux)) + goto skip; + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + DEBUG_ELEMENT_START (demux, &ebml, "BlockGroup"); + if ((ret = gst_ebml_read_master (&ebml, &id)) == GST_FLOW_OK) { + ret = gst_matroska_demux_parse_blockgroup_or_simpleblock (demux, + &ebml, demux->cluster_time, demux->cluster_offset, FALSE); + } + DEBUG_ELEMENT_STOP (demux, &ebml, "BlockGroup", ret); + break; + case GST_MATROSKA_ID_SIMPLEBLOCK: + if (!gst_matroska_demux_seek_block (demux)) + goto skip; + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + DEBUG_ELEMENT_START (demux, &ebml, "SimpleBlock"); + ret = gst_matroska_demux_parse_blockgroup_or_simpleblock (demux, + &ebml, demux->cluster_time, demux->cluster_offset, TRUE); + DEBUG_ELEMENT_STOP (demux, &ebml, "SimpleBlock", ret); + break; + case GST_MATROSKA_ID_ATTACHMENTS: + if (!demux->attachments_parsed) { + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_attachments (demux, &ebml); + } else { + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + } + break; + case GST_MATROSKA_ID_TAGS: + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_metadata (demux, &ebml); + break; + case GST_MATROSKA_ID_CHAPTERS: + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_chapters (demux, &ebml); + break; + case GST_MATROSKA_ID_SEEKHEAD: + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_contents (demux, &ebml); + break; + case GST_MATROSKA_ID_CUES: + if (demux->index_parsed) { + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + break; + } + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_index (demux, &ebml); + /* only push based; delayed index building */ + if (ret == GST_FLOW_OK + && demux->state == GST_MATROSKA_DEMUX_STATE_SEEK) { + GstEvent *event; + + GST_OBJECT_LOCK (demux); + event = demux->seek_event; + demux->seek_event = NULL; + GST_OBJECT_UNLOCK (demux); + + g_assert (event); + /* unlikely to fail, since we managed to seek to this point */ + if (!gst_matroska_demux_handle_seek_event (demux, NULL, event)) + goto seek_failed; + /* resume data handling, main thread clear to seek again */ + GST_OBJECT_LOCK (demux); + demux->state = GST_MATROSKA_DEMUX_STATE_DATA; + GST_OBJECT_UNLOCK (demux); + } + break; + case GST_MATROSKA_ID_POSITION: + case GST_MATROSKA_ID_PREVSIZE: + case GST_MATROSKA_ID_ENCRYPTEDBLOCK: + case GST_MATROSKA_ID_SILENTTRACKS: + GST_DEBUG_OBJECT (demux, + "Skipping Cluster subelement 0x%x - ignoring", id); + /* fall-through */ + default: + skip: + GST_DEBUG_OBJECT (demux, "skipping Element 0x%x", id); + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + break; + } + break; + } + + if (ret == GST_FLOW_PARSE) + goto parse_failed; + +exit: + gst_ebml_read_clear (&ebml); + return ret; + + /* ERRORS */ +read_error: + { + /* simply exit, maybe not enough data yet */ + /* no ebml to clear if read error */ + return ret; + } +parse_failed: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("Failed to parse Element 0x%x", id)); + ret = GST_FLOW_ERROR; + goto exit; + } +not_streamable: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("File layout does not permit streaming")); + ret = GST_FLOW_ERROR; + goto exit; + } +no_tracks: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("No Tracks element found")); + ret = GST_FLOW_ERROR; + goto exit; + } +invalid_header: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("Invalid header")); + ret = GST_FLOW_ERROR; + goto exit; + } +seek_failed: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("Failed to seek")); + ret = GST_FLOW_ERROR; + goto exit; + } +} + +static void +gst_matroska_demux_loop (GstPad * pad) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (GST_PAD_PARENT (pad)); + GstFlowReturn ret; + guint32 id; + guint64 length; + guint needed; + + /* If we have to close a segment, send a new segment to do this now */ + if (G_LIKELY (demux->state == GST_MATROSKA_DEMUX_STATE_DATA)) { + if (G_UNLIKELY (demux->close_segment)) { + gst_matroska_demux_send_event (demux, demux->close_segment); + demux->close_segment = NULL; + } + if (G_UNLIKELY (demux->new_segment)) { + gst_matroska_demux_send_event (demux, demux->new_segment); + demux->new_segment = NULL; + } + } + + ret = gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed); + if (ret == GST_FLOW_UNEXPECTED) + goto eos; + if (ret != GST_FLOW_OK) { + if (gst_matroska_demux_check_parse_error (demux)) + goto pause; + else + return; + } + + GST_LOG_OBJECT (demux, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, " + "size %" G_GUINT64_FORMAT ", needed %d", demux->offset, id, + length, needed); + + ret = gst_matroska_demux_parse_id (demux, id, length, needed); + if (ret == GST_FLOW_UNEXPECTED) + goto eos; + if (ret != GST_FLOW_OK) + goto pause; + + /* check if we're at the end of a configured segment */ + if (G_LIKELY (demux->src->len)) { + guint i; + + g_assert (demux->num_streams == demux->src->len); + for (i = 0; i < demux->src->len; i++) { + GstMatroskaTrackContext *context = g_ptr_array_index (demux->src, i); + GST_DEBUG_OBJECT (context->pad, "pos %" GST_TIME_FORMAT, + GST_TIME_ARGS (context->pos)); + if (context->eos == FALSE) + goto next; + } + + GST_INFO_OBJECT (demux, "All streams are EOS"); + ret = GST_FLOW_UNEXPECTED; + goto eos; + } + +next: + if (G_UNLIKELY (demux->offset == gst_matroska_demux_get_length (demux))) { + GST_LOG_OBJECT (demux, "Reached end of stream"); + ret = GST_FLOW_UNEXPECTED; + goto eos; + } + + return; + + /* ERRORS */ +eos: + { + if (demux->segment.rate < 0.0) { + ret = gst_matroska_demux_seek_to_previous_keyframe (demux); + if (ret == GST_FLOW_OK) + return; + } + /* fall-through */ + } +pause: + { + const gchar *reason = gst_flow_get_name (ret); + gboolean push_eos = FALSE; + + GST_LOG_OBJECT (demux, "pausing task, reason %s", reason); + demux->segment_running = FALSE; + gst_pad_pause_task (demux->sinkpad); + + if (ret == GST_FLOW_UNEXPECTED) { + /* perform EOS logic */ + + /* Close the segment, i.e. update segment stop with the duration + * if no stop was set */ + if (GST_CLOCK_TIME_IS_VALID (demux->last_stop_end) && + !GST_CLOCK_TIME_IS_VALID (demux->segment.stop)) { + GstEvent *event = + gst_event_new_new_segment_full (TRUE, demux->segment.rate, + demux->segment.applied_rate, demux->segment.format, + demux->segment.start, + MAX (demux->last_stop_end, demux->segment.start), + demux->segment.time); + gst_matroska_demux_send_event (demux, event); + } + + if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) { + gint64 stop; + + /* for segment playback we need to post when (in stream time) + * we stopped, this is either stop (when set) or the duration. */ + if ((stop = demux->segment.stop) == -1) + stop = demux->last_stop_end; + + GST_LOG_OBJECT (demux, "Sending segment done, at end of segment"); + gst_element_post_message (GST_ELEMENT (demux), + gst_message_new_segment_done (GST_OBJECT (demux), GST_FORMAT_TIME, + stop)); + } else { + push_eos = TRUE; + } + } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_UNEXPECTED) { + /* for fatal errors we post an error message */ + GST_ELEMENT_ERROR (demux, STREAM, FAILED, (NULL), + ("stream stopped, reason %s", reason)); + push_eos = TRUE; + } + if (push_eos) { + /* send EOS, and prevent hanging if no streams yet */ + GST_LOG_OBJECT (demux, "Sending EOS, at end of stream"); + if (!gst_matroska_demux_send_event (demux, gst_event_new_eos ()) && + (ret == GST_FLOW_UNEXPECTED)) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (NULL), ("got eos but no streams (yet)")); + } + } + return; + } +} + +/* + * Create and push a flushing seek event upstream + */ +static gboolean +perform_seek_to_offset (GstMatroskaDemux * demux, guint64 offset) +{ + GstEvent *event; + gboolean res = 0; + + GST_DEBUG_OBJECT (demux, "Seeking to %" G_GUINT64_FORMAT, offset); + + event = + gst_event_new_seek (1.0, GST_FORMAT_BYTES, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, offset, + GST_SEEK_TYPE_NONE, -1); + + res = gst_pad_push_event (demux->sinkpad, event); + + /* newsegment event will update offset */ + return res; +} + +static const guint8 * +gst_matroska_demux_peek_adapter (GstMatroskaDemux * demux, guint peek) +{ + return gst_adapter_peek (demux->adapter, peek); +} + +static GstFlowReturn +gst_matroska_demux_peek_id_length_push (GstMatroskaDemux * demux, guint32 * _id, + guint64 * _length, guint * _needed) +{ + return gst_ebml_peek_id_length (_id, _length, _needed, + (GstPeekData) gst_matroska_demux_peek_adapter, (gpointer) demux, + GST_ELEMENT_CAST (demux), demux->offset); +} + +static GstFlowReturn +gst_matroska_demux_chain (GstPad * pad, GstBuffer * buffer) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (GST_PAD_PARENT (pad)); + guint available; + GstFlowReturn ret = GST_FLOW_OK; + guint needed = 0; + guint32 id; + guint64 length; + + if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (buffer))) { + GST_DEBUG_OBJECT (demux, "got DISCONT"); + gst_adapter_clear (demux->adapter); + GST_OBJECT_LOCK (demux); + gst_matroska_demux_reset_streams (demux, GST_CLOCK_TIME_NONE, FALSE); + GST_OBJECT_UNLOCK (demux); + } + + gst_adapter_push (demux->adapter, buffer); + buffer = NULL; + +next: + available = gst_adapter_available (demux->adapter); + + ret = gst_matroska_demux_peek_id_length_push (demux, &id, &length, &needed); + if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED)) + return ret; + + GST_LOG_OBJECT (demux, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, " + "size %" G_GUINT64_FORMAT ", needed %d, available %d", demux->offset, id, + length, needed, available); + + if (needed > available) + return GST_FLOW_OK; + + ret = gst_matroska_demux_parse_id (demux, id, length, needed); + if (ret == GST_FLOW_UNEXPECTED) { + /* need more data */ + return GST_FLOW_OK; + } else if (ret != GST_FLOW_OK) { + return ret; + } else + goto next; +} + +static gboolean +gst_matroska_demux_handle_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (GST_PAD_PARENT (pad)); + + GST_DEBUG_OBJECT (demux, + "have event type %s: %p on sink pad", GST_EVENT_TYPE_NAME (event), event); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time = 0; + gboolean update; + GstSegment segment; + + /* some debug output */ + gst_segment_init (&segment, GST_FORMAT_UNDEFINED); + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + gst_segment_set_newsegment_full (&segment, update, rate, arate, format, + start, stop, time); + GST_DEBUG_OBJECT (demux, + "received format %d newsegment %" GST_SEGMENT_FORMAT, format, + &segment); + + if (demux->state < GST_MATROSKA_DEMUX_STATE_DATA) { + GST_DEBUG_OBJECT (demux, "still starting"); + goto exit; + } + + /* we only expect a BYTE segment, e.g. following a seek */ + if (format != GST_FORMAT_BYTES) { + GST_DEBUG_OBJECT (demux, "unsupported segment format, ignoring"); + goto exit; + } + + GST_DEBUG_OBJECT (demux, "clearing segment state"); + /* clear current segment leftover */ + gst_adapter_clear (demux->adapter); + /* and some streaming setup */ + demux->offset = start; + /* do not know where we are; + * need to come across a cluster and generate newsegment */ + demux->segment.last_stop = GST_CLOCK_TIME_NONE; + demux->cluster_time = GST_CLOCK_TIME_NONE; + demux->cluster_offset = 0; + demux->need_newsegment = TRUE; + /* but keep some of the upstream segment */ + demux->segment.rate = rate; + exit: + /* chain will send initial newsegment after pads have been added, + * or otherwise come up with one */ + GST_DEBUG_OBJECT (demux, "eating event"); + gst_event_unref (event); + res = TRUE; + break; + } + case GST_EVENT_EOS: + { + if (demux->state != GST_MATROSKA_DEMUX_STATE_DATA) { + gst_event_unref (event); + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (NULL), ("got eos and didn't receive a complete header object")); + } else if (demux->num_streams == 0) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (NULL), ("got eos but no streams (yet)")); + } else { + gst_matroska_demux_send_event (demux, event); + } + break; + } + case GST_EVENT_FLUSH_STOP: + { + gst_adapter_clear (demux->adapter); + GST_OBJECT_LOCK (demux); + gst_matroska_demux_reset_streams (demux, GST_CLOCK_TIME_NONE, TRUE); + GST_OBJECT_UNLOCK (demux); + demux->segment.last_stop = GST_CLOCK_TIME_NONE; + demux->cluster_time = GST_CLOCK_TIME_NONE; + demux->cluster_offset = 0; + /* fall-through */ + } + default: + res = gst_pad_event_default (pad, event); + break; + } + + return res; +} + +static gboolean +gst_matroska_demux_sink_activate (GstPad * sinkpad) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (GST_PAD_PARENT (sinkpad)); + + if (gst_pad_check_pull_range (sinkpad)) { + GST_DEBUG ("going to pull mode"); + demux->streaming = FALSE; + return gst_pad_activate_pull (sinkpad, TRUE); + } else { + GST_DEBUG ("going to push (streaming) mode"); + demux->streaming = TRUE; + return gst_pad_activate_push (sinkpad, TRUE); + } + + return FALSE; +} + +static gboolean +gst_matroska_demux_sink_activate_pull (GstPad * sinkpad, gboolean active) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (GST_PAD_PARENT (sinkpad)); + + if (active) { + /* if we have a scheduler we can start the task */ + demux->segment_running = TRUE; + gst_pad_start_task (sinkpad, (GstTaskFunction) gst_matroska_demux_loop, + sinkpad); + } else { + demux->segment_running = FALSE; + gst_pad_stop_task (sinkpad); + } + + return TRUE; +} + +static void +gst_duration_to_fraction (guint64 duration, gint * dest_n, gint * dest_d) +{ + static const int common_den[] = { 1, 2, 3, 4, 1001 }; + int n, d; + int i; + guint64 a; + + for (i = 0; i < G_N_ELEMENTS (common_den); i++) { + d = common_den[i]; + n = floor (0.5 + (d * 1e9) / duration); + a = gst_util_uint64_scale_int (1000000000, d, n); + if (duration >= a - 1 && duration <= a + 1) { + goto out; + } + } + + gst_util_double_to_fraction (1e9 / duration, &n, &d); + +out: + /* set results */ + *dest_n = n; + *dest_d = d; +} + +static GstCaps * +gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * + videocontext, const gchar * codec_id, guint8 * data, guint size, + gchar ** codec_name, guint32 * riff_fourcc) +{ + GstMatroskaTrackContext *context = (GstMatroskaTrackContext *) videocontext; + GstCaps *caps = NULL; + + g_assert (videocontext != NULL); + g_assert (codec_name != NULL); + + context->send_xiph_headers = FALSE; + context->send_flac_headers = FALSE; + context->send_speex_headers = FALSE; + + if (riff_fourcc) + *riff_fourcc = 0; + + /* TODO: check if we have all codec types from matroska-ids.h + * check if we have to do more special things with codec_private + * + * Add support for + * GST_MATROSKA_CODEC_ID_VIDEO_QUICKTIME + * GST_MATROSKA_CODEC_ID_VIDEO_SNOW + */ + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC)) { + gst_riff_strf_vids *vids = NULL; + + if (data) { + GstBuffer *buf = NULL; + + vids = (gst_riff_strf_vids *) data; + + /* assure size is big enough */ + if (size < 24) { + GST_WARNING ("Too small BITMAPINFOHEADER (%d bytes)", size); + return NULL; + } + if (size < sizeof (gst_riff_strf_vids)) { + vids = g_new (gst_riff_strf_vids, 1); + memcpy (vids, data, size); + } + + /* little-endian -> byte-order */ + vids->size = GUINT32_FROM_LE (vids->size); + vids->width = GUINT32_FROM_LE (vids->width); + vids->height = GUINT32_FROM_LE (vids->height); + vids->planes = GUINT16_FROM_LE (vids->planes); + vids->bit_cnt = GUINT16_FROM_LE (vids->bit_cnt); + vids->compression = GUINT32_FROM_LE (vids->compression); + vids->image_size = GUINT32_FROM_LE (vids->image_size); + vids->xpels_meter = GUINT32_FROM_LE (vids->xpels_meter); + vids->ypels_meter = GUINT32_FROM_LE (vids->ypels_meter); + vids->num_colors = GUINT32_FROM_LE (vids->num_colors); + vids->imp_colors = GUINT32_FROM_LE (vids->imp_colors); + + if (size > sizeof (gst_riff_strf_vids)) { /* some extra_data */ + buf = gst_buffer_new_and_alloc (size - sizeof (gst_riff_strf_vids)); + memcpy (GST_BUFFER_DATA (buf), + (guint8 *) vids + sizeof (gst_riff_strf_vids), + GST_BUFFER_SIZE (buf)); + } + + if (riff_fourcc) + *riff_fourcc = vids->compression; + + caps = gst_riff_create_video_caps (vids->compression, NULL, vids, + buf, NULL, codec_name); + + if (caps == NULL) { + GST_WARNING ("Unhandled RIFF fourcc %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (vids->compression)); + } + + if (buf) + gst_buffer_unref (buf); + + if (vids != (gst_riff_strf_vids *) data) + g_free (vids); + } + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED)) { + guint32 fourcc = 0; + + switch (videocontext->fourcc) { + case GST_MAKE_FOURCC ('I', '4', '2', '0'): + *codec_name = g_strdup ("Raw planar YUV 4:2:0"); + fourcc = videocontext->fourcc; + break; + case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'): + *codec_name = g_strdup ("Raw packed YUV 4:2:2"); + fourcc = videocontext->fourcc; + break; + case GST_MAKE_FOURCC ('Y', 'V', '1', '2'): + *codec_name = g_strdup ("Raw packed YUV 4:2:0"); + fourcc = videocontext->fourcc; + break; + case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'): + *codec_name = g_strdup ("Raw packed YUV 4:2:2"); + fourcc = videocontext->fourcc; + break; + case GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'): + *codec_name = g_strdup ("Raw packed YUV 4:4:4 with alpha channel"); + fourcc = videocontext->fourcc; + break; + + default: + GST_DEBUG ("Unknown fourcc %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (videocontext->fourcc)); + return NULL; + } + + caps = gst_caps_new_simple ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, fourcc, NULL); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP)) { + caps = gst_caps_new_simple ("video/x-divx", + "divxversion", G_TYPE_INT, 4, NULL); + *codec_name = g_strdup ("MPEG-4 simple profile"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AP)) { +#if 0 + caps = gst_caps_new_full (gst_structure_new ("video/x-divx", + "divxversion", G_TYPE_INT, 5, NULL), + gst_structure_new ("video/x-xvid", NULL), + gst_structure_new ("video/mpeg", + "mpegversion", G_TYPE_INT, 4, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL), NULL); +#endif + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, 4, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + if (data) { + GstBuffer *priv = gst_buffer_new_and_alloc (size); + + memcpy (GST_BUFFER_DATA (priv), data, size); + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, priv, NULL); + gst_buffer_unref (priv); + } + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP)) + *codec_name = g_strdup ("MPEG-4 advanced simple profile"); + else + *codec_name = g_strdup ("MPEG-4 advanced profile"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3)) { +#if 0 + caps = gst_caps_new_full (gst_structure_new ("video/x-divx", + "divxversion", G_TYPE_INT, 3, NULL), + gst_structure_new ("video/x-msmpeg", + "msmpegversion", G_TYPE_INT, 43, NULL), NULL); +#endif + caps = gst_caps_new_simple ("video/x-msmpeg", + "msmpegversion", G_TYPE_INT, 43, NULL); + *codec_name = g_strdup ("Microsoft MPEG-4 v.3"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG1) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG2)) { + gint mpegversion; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG1)) + mpegversion = 1; + else + mpegversion = 2; + + caps = gst_caps_new_simple ("video/mpeg", + "systemstream", G_TYPE_BOOLEAN, FALSE, + "mpegversion", G_TYPE_INT, mpegversion, NULL); + *codec_name = g_strdup_printf ("MPEG-%d video", mpegversion); + context->postprocess_frame = gst_matroska_demux_add_mpeg_seq_header; + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MJPEG)) { + caps = gst_caps_new_simple ("image/jpeg", NULL); + *codec_name = g_strdup ("Motion-JPEG"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC)) { + caps = gst_caps_new_simple ("video/x-h264", NULL); + if (data) { + GstBuffer *priv = gst_buffer_new_and_alloc (size); + + /* First byte is the version, second is the profile indication, and third + * is the 5 contraint_set_flags and 3 reserved bits. Fourth byte is the + * level indication. */ + gst_codec_utils_h264_caps_set_level_and_profile (caps, data + 1, + size - 1); + + memcpy (GST_BUFFER_DATA (priv), data, size); + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, priv, NULL); + gst_buffer_unref (priv); + + gst_caps_set_simple (caps, "stream-format", G_TYPE_STRING, "avc", + "alignment", G_TYPE_STRING, "au", NULL); + } else { + GST_WARNING ("No codec data found, assuming output is byte-stream"); + gst_caps_set_simple (caps, "stream-format", G_TYPE_STRING, "byte-stream", + NULL); + } + *codec_name = g_strdup ("H264"); + } else if ((!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1)) || + (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2)) || + (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3)) || + (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4))) { + gint rmversion = -1; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1)) + rmversion = 1; + else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2)) + rmversion = 2; + else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3)) + rmversion = 3; + else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4)) + rmversion = 4; + + caps = gst_caps_new_simple ("video/x-pn-realvideo", + "rmversion", G_TYPE_INT, rmversion, NULL); + GST_DEBUG ("data:%p, size:0x%x", data, size); + /* We need to extract the extradata ! */ + if (data && (size >= 0x22)) { + GstBuffer *priv; + guint rformat; + guint subformat; + + subformat = GST_READ_UINT32_BE (data + 0x1a); + rformat = GST_READ_UINT32_BE (data + 0x1e); + + priv = gst_buffer_new_and_alloc (size - 0x1a); + + memcpy (GST_BUFFER_DATA (priv), data + 0x1a, size - 0x1a); + gst_caps_set_simple (caps, + "codec_data", GST_TYPE_BUFFER, priv, + "format", G_TYPE_INT, rformat, + "subformat", G_TYPE_INT, subformat, NULL); + gst_buffer_unref (priv); + + } + *codec_name = g_strdup_printf ("RealVideo %d.0", rmversion); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_THEORA)) { + caps = gst_caps_new_simple ("video/x-theora", NULL); + context->send_xiph_headers = TRUE; + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_DIRAC)) { + caps = gst_caps_new_simple ("video/x-dirac", NULL); + *codec_name = g_strdup_printf ("Dirac"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VP8)) { + caps = gst_caps_new_simple ("video/x-vp8", NULL); + *codec_name = g_strdup_printf ("On2 VP8"); + } else { + GST_WARNING ("Unknown codec '%s', cannot build Caps", codec_id); + return NULL; + } + + if (caps != NULL) { + int i; + GstStructure *structure; + + for (i = 0; i < gst_caps_get_size (caps); i++) { + structure = gst_caps_get_structure (caps, i); + + /* FIXME: use the real unit here! */ + GST_DEBUG ("video size %dx%d, target display size %dx%d (any unit)", + videocontext->pixel_width, + videocontext->pixel_height, + videocontext->display_width, videocontext->display_height); + + /* pixel width and height are the w and h of the video in pixels */ + if (videocontext->pixel_width > 0 && videocontext->pixel_height > 0) { + gint w = videocontext->pixel_width; + + gint h = videocontext->pixel_height; + + gst_structure_set (structure, + "width", G_TYPE_INT, w, "height", G_TYPE_INT, h, NULL); + } + + if (videocontext->display_width > 0 && videocontext->display_height > 0) { + int n, d; + + /* calculate the pixel aspect ratio using the display and pixel w/h */ + n = videocontext->display_width * videocontext->pixel_height; + d = videocontext->display_height * videocontext->pixel_width; + GST_DEBUG ("setting PAR to %d/%d", n, d); + gst_structure_set (structure, "pixel-aspect-ratio", + GST_TYPE_FRACTION, + videocontext->display_width * videocontext->pixel_height, + videocontext->display_height * videocontext->pixel_width, NULL); + } + + if (videocontext->default_fps > 0.0) { + GValue fps_double = { 0, }; + GValue fps_fraction = { 0, }; + + g_value_init (&fps_double, G_TYPE_DOUBLE); + g_value_init (&fps_fraction, GST_TYPE_FRACTION); + g_value_set_double (&fps_double, videocontext->default_fps); + g_value_transform (&fps_double, &fps_fraction); + + GST_DEBUG ("using default fps %f", videocontext->default_fps); + + gst_structure_set_value (structure, "framerate", &fps_fraction); + g_value_unset (&fps_double); + g_value_unset (&fps_fraction); + } else if (context->default_duration > 0) { + int fps_n, fps_d; + + gst_duration_to_fraction (context->default_duration, &fps_n, &fps_d); + + GST_INFO ("using default duration %" G_GUINT64_FORMAT + " framerate %d/%d", context->default_duration, fps_n, fps_d); + + gst_structure_set (structure, "framerate", GST_TYPE_FRACTION, + fps_n, fps_d, NULL); + } else { + /* sort of a hack to get most codecs to support, + * even if the default_duration is missing */ + gst_structure_set (structure, "framerate", GST_TYPE_FRACTION, + 25, 1, NULL); + } + + if (videocontext->parent.flags & GST_MATROSKA_VIDEOTRACK_INTERLACED) + gst_structure_set (structure, "interlaced", G_TYPE_BOOLEAN, TRUE, NULL); + } + + gst_caps_do_simplify (caps); + } + + return caps; +} + +/* + * Some AAC specific code... *sigh* + * FIXME: maybe we should use '15' and code the sample rate explicitly + * if the sample rate doesn't match the predefined rates exactly? (tpm) + */ + +static gint +aac_rate_idx (gint rate) +{ + if (92017 <= rate) + return 0; + else if (75132 <= rate) + return 1; + else if (55426 <= rate) + return 2; + else if (46009 <= rate) + return 3; + else if (37566 <= rate) + return 4; + else if (27713 <= rate) + return 5; + else if (23004 <= rate) + return 6; + else if (18783 <= rate) + return 7; + else if (13856 <= rate) + return 8; + else if (11502 <= rate) + return 9; + else if (9391 <= rate) + return 10; + else + return 11; +} + +static gint +aac_profile_idx (const gchar * codec_id) +{ + gint profile; + + if (strlen (codec_id) <= 12) + profile = 3; + else if (!strncmp (&codec_id[12], "MAIN", 4)) + profile = 0; + else if (!strncmp (&codec_id[12], "LC", 2)) + profile = 1; + else if (!strncmp (&codec_id[12], "SSR", 3)) + profile = 2; + else + profile = 3; + + return profile; +} + +#define AAC_SYNC_EXTENSION_TYPE 0x02b7 + +static GstCaps * +gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * + audiocontext, const gchar * codec_id, guint8 * data, guint size, + gchar ** codec_name, guint16 * riff_audio_fmt) +{ + GstMatroskaTrackContext *context = (GstMatroskaTrackContext *) audiocontext; + GstCaps *caps = NULL; + + g_assert (audiocontext != NULL); + g_assert (codec_name != NULL); + + if (riff_audio_fmt) + *riff_audio_fmt = 0; + + context->send_xiph_headers = FALSE; + context->send_flac_headers = FALSE; + context->send_speex_headers = FALSE; + + /* TODO: check if we have all codec types from matroska-ids.h + * check if we have to do more special things with codec_private + * check if we need bitdepth in different places too + * implement channel position magic + * Add support for: + * GST_MATROSKA_CODEC_ID_AUDIO_AC3_BSID9 + * GST_MATROSKA_CODEC_ID_AUDIO_AC3_BSID10 + * GST_MATROSKA_CODEC_ID_AUDIO_QUICKTIME_QDMC + * GST_MATROSKA_CODEC_ID_AUDIO_QUICKTIME_QDM2 + */ + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3)) { + gint layer; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1)) + layer = 1; + else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2)) + layer = 2; + else + layer = 3; + + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 1, "layer", G_TYPE_INT, layer, NULL); + *codec_name = g_strdup_printf ("MPEG-1 layer %d", layer); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE)) { + gint endianness; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE)) + endianness = G_BIG_ENDIAN; + else + endianness = G_LITTLE_ENDIAN; + + caps = gst_caps_new_simple ("audio/x-raw-int", + "width", G_TYPE_INT, audiocontext->bitdepth, + "depth", G_TYPE_INT, audiocontext->bitdepth, + "signed", G_TYPE_BOOLEAN, audiocontext->bitdepth != 8, + "endianness", G_TYPE_INT, endianness, NULL); + + *codec_name = g_strdup_printf ("Raw %d-bit PCM audio", + audiocontext->bitdepth); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT)) { + caps = gst_caps_new_simple ("audio/x-raw-float", + "endianness", G_TYPE_INT, G_LITTLE_ENDIAN, + "width", G_TYPE_INT, audiocontext->bitdepth, NULL); + *codec_name = g_strdup_printf ("Raw %d-bit floating-point audio", + audiocontext->bitdepth); + } else if (!strncmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_AC3, + strlen (GST_MATROSKA_CODEC_ID_AUDIO_AC3))) { + caps = gst_caps_new_simple ("audio/x-ac3", + "framed", G_TYPE_BOOLEAN, TRUE, NULL); + *codec_name = g_strdup ("AC-3 audio"); + } else if (!strncmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_EAC3, + strlen (GST_MATROSKA_CODEC_ID_AUDIO_EAC3))) { + caps = gst_caps_new_simple ("audio/x-eac3", + "framed", G_TYPE_BOOLEAN, TRUE, NULL); + *codec_name = g_strdup ("E-AC-3 audio"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_DTS)) { + caps = gst_caps_new_simple ("audio/x-dts", NULL); + *codec_name = g_strdup ("DTS audio"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_VORBIS)) { + caps = gst_caps_new_simple ("audio/x-vorbis", NULL); + context->send_xiph_headers = TRUE; + /* vorbis decoder does tags */ + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_FLAC)) { + caps = gst_caps_new_simple ("audio/x-flac", NULL); + context->send_flac_headers = TRUE; + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_SPEEX)) { + caps = gst_caps_new_simple ("audio/x-speex", NULL); + context->send_speex_headers = TRUE; + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_ACM)) { + gst_riff_strf_auds auds; + + if (data) { + GstBuffer *codec_data = gst_buffer_new (); + + /* little-endian -> byte-order */ + auds.format = GST_READ_UINT16_LE (data); + auds.channels = GST_READ_UINT16_LE (data + 2); + auds.rate = GST_READ_UINT32_LE (data + 4); + auds.av_bps = GST_READ_UINT32_LE (data + 8); + auds.blockalign = GST_READ_UINT16_LE (data + 12); + auds.size = GST_READ_UINT16_LE (data + 16); + + /* 18 is the waveformatex size */ + gst_buffer_set_data (codec_data, data + 18, auds.size); + + if (riff_audio_fmt) + *riff_audio_fmt = auds.format; + + caps = gst_riff_create_audio_caps (auds.format, NULL, &auds, NULL, + codec_data, codec_name); + gst_buffer_unref (codec_data); + + if (caps == NULL) { + GST_WARNING ("Unhandled RIFF audio format 0x%02x", auds.format); + } + } + } else if (g_str_has_prefix (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_AAC)) { + GstBuffer *priv = NULL; + gint mpegversion; + gint rate_idx, profile; + guint8 *data = NULL; + + /* unspecified AAC profile with opaque private codec data */ + if (strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_AAC) == 0) { + if (context->codec_priv_size >= 2) { + guint obj_type, freq_index, explicit_freq_bytes = 0; + + codec_id = GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4; + mpegversion = 4; + freq_index = (GST_READ_UINT16_BE (context->codec_priv) & 0x780) >> 7; + obj_type = (GST_READ_UINT16_BE (context->codec_priv) & 0xF800) >> 11; + if (freq_index == 15) + explicit_freq_bytes = 3; + GST_DEBUG ("obj_type = %u, freq_index = %u", obj_type, freq_index); + priv = gst_buffer_new_and_alloc (context->codec_priv_size); + memcpy (GST_BUFFER_DATA (priv), context->codec_priv, + context->codec_priv_size); + /* assume SBR if samplerate <= 24kHz */ + if (obj_type == 5 || (freq_index >= 6 && freq_index != 15) || + (context->codec_priv_size == (5 + explicit_freq_bytes))) { + audiocontext->samplerate *= 2; + } + } else { + GST_WARNING ("Opaque A_AAC codec ID, but no codec private data"); + /* this is pretty broken; + * maybe we need to make up some default private, + * or maybe ADTS data got dumped in. + * Let's set up some private data now, and check actual data later */ + /* just try this and see what happens ... */ + codec_id = GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4; + context->postprocess_frame = gst_matroska_demux_check_aac; + } + } + + /* make up decoder-specific data if it is not supplied */ + if (priv == NULL) { + priv = gst_buffer_new_and_alloc (5); + data = GST_BUFFER_DATA (priv); + rate_idx = aac_rate_idx (audiocontext->samplerate); + profile = aac_profile_idx (codec_id); + + data[0] = ((profile + 1) << 3) | ((rate_idx & 0xE) >> 1); + data[1] = ((rate_idx & 0x1) << 7) | (audiocontext->channels << 3); + GST_BUFFER_SIZE (priv) = 2; + + if (!strncmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2, + strlen (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2))) { + mpegversion = 2; + } else if (!strncmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4, + strlen (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4))) { + mpegversion = 4; + + if (g_strrstr (codec_id, "SBR")) { + /* HE-AAC (aka SBR AAC) */ + audiocontext->samplerate *= 2; + rate_idx = aac_rate_idx (audiocontext->samplerate); + data[2] = AAC_SYNC_EXTENSION_TYPE >> 3; + data[3] = ((AAC_SYNC_EXTENSION_TYPE & 0x07) << 5) | 5; + data[4] = (1 << 7) | (rate_idx << 3); + GST_BUFFER_SIZE (priv) = 5; + } + } else { + gst_buffer_unref (priv); + priv = NULL; + GST_ERROR ("Unknown AAC profile and no codec private data"); + } + } + + if (priv) { + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, mpegversion, + "framed", G_TYPE_BOOLEAN, TRUE, NULL); + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, priv, NULL); + *codec_name = g_strdup_printf ("MPEG-%d AAC audio", mpegversion); + } + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_TTA)) { + caps = gst_caps_new_simple ("audio/x-tta", + "width", G_TYPE_INT, audiocontext->bitdepth, NULL); + *codec_name = g_strdup ("TTA audio"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_WAVPACK4)) { + caps = gst_caps_new_simple ("audio/x-wavpack", + "width", G_TYPE_INT, audiocontext->bitdepth, + "framed", G_TYPE_BOOLEAN, TRUE, NULL); + *codec_name = g_strdup ("Wavpack audio"); + context->postprocess_frame = gst_matroska_demux_add_wvpk_header; + audiocontext->wvpk_block_index = 0; + } else if ((!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4)) || + (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4)) || + (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK))) { + gint raversion = -1; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4)) + raversion = 1; + else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK)) + raversion = 8; + else + raversion = 2; + + caps = gst_caps_new_simple ("audio/x-pn-realaudio", + "raversion", G_TYPE_INT, raversion, NULL); + /* Extract extra information from caps, mapping varies based on codec */ + if (data && (size >= 0x50)) { + GstBuffer *priv; + guint flavor; + guint packet_size; + guint height; + guint leaf_size; + guint sample_width; + guint extra_data_size; + + GST_ERROR ("real audio raversion:%d", raversion); + if (raversion == 8) { + /* COOK */ + flavor = GST_READ_UINT16_BE (data + 22); + packet_size = GST_READ_UINT32_BE (data + 24); + height = GST_READ_UINT16_BE (data + 40); + leaf_size = GST_READ_UINT16_BE (data + 44); + sample_width = GST_READ_UINT16_BE (data + 58); + extra_data_size = GST_READ_UINT32_BE (data + 74); + + GST_ERROR + ("flavor:%d, packet_size:%d, height:%d, leaf_size:%d, sample_width:%d, extra_data_size:%d", + flavor, packet_size, height, leaf_size, sample_width, + extra_data_size); + gst_caps_set_simple (caps, "flavor", G_TYPE_INT, flavor, "packet_size", + G_TYPE_INT, packet_size, "height", G_TYPE_INT, height, "leaf_size", + G_TYPE_INT, leaf_size, "width", G_TYPE_INT, sample_width, NULL); + + if ((size - 78) >= extra_data_size) { + priv = gst_buffer_new_and_alloc (extra_data_size); + memcpy (GST_BUFFER_DATA (priv), data + 78, extra_data_size); + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, priv, NULL); + gst_buffer_unref (priv); + } + } + } + + *codec_name = g_strdup_printf ("RealAudio %d.0", raversion); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_SIPR)) { + caps = gst_caps_new_simple ("audio/x-sipro", NULL); + *codec_name = g_strdup ("Sipro/ACELP.NET Voice Codec"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_RALF)) { + caps = gst_caps_new_simple ("audio/x-ralf-mpeg4-generic", NULL); + *codec_name = g_strdup ("Real Audio Lossless"); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_ATRC)) { + caps = gst_caps_new_simple ("audio/x-vnd.sony.atrac3", NULL); + *codec_name = g_strdup ("Sony ATRAC3"); + } else { + GST_WARNING ("Unknown codec '%s', cannot build Caps", codec_id); + return NULL; + } + + if (caps != NULL) { + if (audiocontext->samplerate > 0 && audiocontext->channels > 0) { + gint i; + + for (i = 0; i < gst_caps_get_size (caps); i++) { + gst_structure_set (gst_caps_get_structure (caps, i), + "channels", G_TYPE_INT, audiocontext->channels, + "rate", G_TYPE_INT, audiocontext->samplerate, NULL); + } + } + + gst_caps_do_simplify (caps); + } + + return caps; +} + +static GstCaps * +gst_matroska_demux_subtitle_caps (GstMatroskaTrackSubtitleContext * + subtitlecontext, const gchar * codec_id, gpointer data, guint size) +{ + GstCaps *caps = NULL; + GstMatroskaTrackContext *context = + (GstMatroskaTrackContext *) subtitlecontext; + + /* for backwards compatibility */ + if (!g_ascii_strcasecmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_ASCII)) + codec_id = GST_MATROSKA_CODEC_ID_SUBTITLE_UTF8; + else if (!g_ascii_strcasecmp (codec_id, "S_SSA")) + codec_id = GST_MATROSKA_CODEC_ID_SUBTITLE_SSA; + else if (!g_ascii_strcasecmp (codec_id, "S_ASS")) + codec_id = GST_MATROSKA_CODEC_ID_SUBTITLE_ASS; + else if (!g_ascii_strcasecmp (codec_id, "S_USF")) + codec_id = GST_MATROSKA_CODEC_ID_SUBTITLE_USF; + + /* TODO: Add GST_MATROSKA_CODEC_ID_SUBTITLE_BMP support + * Check if we have to do something with codec_private */ + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_UTF8)) { + caps = gst_caps_new_simple ("text/plain", NULL); + context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer; + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_SSA)) { + caps = gst_caps_new_simple ("application/x-ssa", NULL); + context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer; + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_ASS)) { + caps = gst_caps_new_simple ("application/x-ass", NULL); + context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer; + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_USF)) { + caps = gst_caps_new_simple ("application/x-usf", NULL); + context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer; + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_VOBSUB)) { + caps = gst_caps_new_simple ("video/x-dvd-subpicture", NULL); + ((GstMatroskaTrackContext *) subtitlecontext)->send_dvd_event = TRUE; + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_HDMVPGS)) { + caps = gst_caps_new_simple ("subpicture/x-pgs", NULL); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_KATE)) { + caps = gst_caps_new_simple ("subtitle/x-kate", NULL); + context->send_xiph_headers = TRUE; + } else { + GST_DEBUG ("Unknown subtitle stream: codec_id='%s'", codec_id); + caps = gst_caps_new_simple ("application/x-subtitle-unknown", NULL); + } + + if (data != NULL && size > 0) { + GstBuffer *buf; + + buf = gst_buffer_new_and_alloc (size); + memcpy (GST_BUFFER_DATA (buf), data, size); + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + + return caps; +} + +static void +gst_matroska_demux_set_index (GstElement * element, GstIndex * index) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); + + GST_OBJECT_LOCK (demux); + if (demux->element_index) + gst_object_unref (demux->element_index); + demux->element_index = index ? gst_object_ref (index) : NULL; + GST_OBJECT_UNLOCK (demux); + GST_DEBUG_OBJECT (demux, "Set index %" GST_PTR_FORMAT, demux->element_index); +} + +static GstIndex * +gst_matroska_demux_get_index (GstElement * element) +{ + GstIndex *result = NULL; + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); + + GST_OBJECT_LOCK (demux); + if (demux->element_index) + result = gst_object_ref (demux->element_index); + GST_OBJECT_UNLOCK (demux); + + GST_DEBUG_OBJECT (demux, "Returning index %" GST_PTR_FORMAT, result); + + return result; +} + +static GstStateChangeReturn +gst_matroska_demux_change_state (GstElement * element, + GstStateChange transition) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + /* handle upwards state changes here */ + switch (transition) { + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + /* handle downwards state changes */ + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_matroska_demux_reset (GST_ELEMENT (demux)); + break; + default: + break; + } + + return ret; +} + +gboolean +gst_matroska_demux_plugin_init (GstPlugin * plugin) +{ + gst_riff_init (); + + /* parser helper separate debug */ + GST_DEBUG_CATEGORY_INIT (ebmlread_debug, "ebmlread", + 0, "EBML stream helper class"); + + /* create an elementfactory for the matroska_demux element */ + if (!gst_element_register (plugin, "matroskademux", + GST_RANK_PRIMARY, GST_TYPE_MATROSKA_DEMUX)) + return FALSE; + + return TRUE; +} diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-demux.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-demux.h Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,143 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska-demux.h: matroska file/stream demuxer definition + * + * 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_MATROSKA_DEMUX_H__ +#define __GST_MATROSKA_DEMUX_H__ + +#include +#include + +#include "ebml-read.h" +#include "matroska-ids.h" + +G_BEGIN_DECLS + +#define GST_TYPE_MATROSKA_DEMUX \ + (gst_matroska_demux_get_type ()) +#define GST_MATROSKA_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_MATROSKA_DEMUX, GstMatroskaDemux)) +#define GST_MATROSKA_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_MATROSKA_DEMUX, GstMatroskaDemuxClass)) +#define GST_IS_MATROSKA_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_MATROSKA_DEMUX)) +#define GST_IS_MATROSKA_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MATROSKA_DEMUX)) + +typedef enum { + GST_MATROSKA_DEMUX_STATE_START, + GST_MATROSKA_DEMUX_STATE_SEGMENT, + GST_MATROSKA_DEMUX_STATE_HEADER, + GST_MATROSKA_DEMUX_STATE_DATA, + GST_MATROSKA_DEMUX_STATE_SEEK, + GST_MATROSKA_DEMUX_STATE_SCANNING +} GstMatroskaDemuxState; + +typedef struct _GstMatroskaDemux { + GstElement parent; + + /* < private > */ + + GstIndex *element_index; + gint element_index_writer_id; + + /* pads */ + GstPad *sinkpad; + GPtrArray *src; + GstClock *clock; + guint num_streams; + guint num_v_streams; + guint num_a_streams; + guint num_t_streams; + + /* metadata */ + gchar *muxing_app; + gchar *writing_app; + gint64 created; + + /* state */ + gboolean streaming; + GstMatroskaDemuxState state; + guint level_up; + guint64 seek_block; + gboolean seek_first; + + /* did we parse cues/tracks/segmentinfo already? */ + gboolean index_parsed; + gboolean tracks_parsed; + gboolean segmentinfo_parsed; + gboolean attachments_parsed; + GList *tags_parsed; + GList *seek_parsed; + + /* start-of-segment */ + guint64 ebml_segment_start; + + /* a cue (index) table */ + GArray *index; + /* cluster positions (optional) */ + GArray *clusters; + + /* timescale in the file */ + guint64 time_scale; + + /* keeping track of playback position */ + GstSegment segment; + gboolean segment_running; + GstClockTime last_stop_end; + + GstEvent *close_segment; + GstEvent *new_segment; + GstTagList *global_tags; + + /* pull mode caching */ + GstBuffer *cached_buffer; + + /* push and pull mode */ + guint64 offset; + /* some state saving */ + GstClockTime cluster_time; + guint64 cluster_offset; + guint64 first_cluster_offset; + guint64 next_cluster_offset; + + /* push based mode usual suspects */ + GstAdapter *adapter; + /* index stuff */ + gboolean seekable; + gboolean building_index; + guint64 index_offset; + GstEvent *seek_event; + gboolean need_newsegment; + + /* reverse playback */ + GArray *seek_index; + gint seek_entry; +} GstMatroskaDemux; + +typedef struct _GstMatroskaDemuxClass { + GstElementClass parent; +} GstMatroskaDemuxClass; + +gboolean gst_matroska_demux_plugin_init (GstPlugin *plugin); + +G_END_DECLS + +#endif /* __GST_MATROSKA_DEMUX_H__ */ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-ids.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-ids.c Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,117 @@ +/* GStreamer Matroska muxer/demuxer + * (C) 2003 Ronald Bultje + * (C) 2006 Tim-Philipp Müller + * + * matroska-ids.c: matroska track context 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "matroska-ids.h" + +gboolean +gst_matroska_track_init_video_context (GstMatroskaTrackContext ** p_context) +{ + GstMatroskaTrackVideoContext *video_context; + + g_assert (p_context != NULL && *p_context != NULL); + + /* already set up? (track info might come before track type) */ + if ((*p_context)->type == GST_MATROSKA_TRACK_TYPE_VIDEO) { + GST_LOG ("video context already set up"); + return TRUE; + } + + /* it better not have been set up as some other track type ... */ + if ((*p_context)->type != 0) { + g_return_val_if_reached (FALSE); + } + + video_context = g_renew (GstMatroskaTrackVideoContext, *p_context, 1); + *p_context = (GstMatroskaTrackContext *) video_context; + + /* defaults */ + (*p_context)->type = GST_MATROSKA_TRACK_TYPE_VIDEO; + video_context->display_width = 0; + video_context->display_height = 0; + video_context->pixel_width = 0; + video_context->pixel_height = 0; + video_context->asr_mode = 0; + video_context->fourcc = 0; + video_context->default_fps = 0.0; + video_context->earliest_time = GST_CLOCK_TIME_NONE; + return TRUE; +} + +gboolean +gst_matroska_track_init_audio_context (GstMatroskaTrackContext ** p_context) +{ + GstMatroskaTrackAudioContext *audio_context; + + g_assert (p_context != NULL && *p_context != NULL); + + /* already set up? (track info might come before track type) */ + if ((*p_context)->type == GST_MATROSKA_TRACK_TYPE_AUDIO) + return TRUE; + + /* it better not have been set up as some other track type ... */ + if ((*p_context)->type != 0) { + g_return_val_if_reached (FALSE); + } + + audio_context = g_renew (GstMatroskaTrackAudioContext, *p_context, 1); + *p_context = (GstMatroskaTrackContext *) audio_context; + + /* defaults */ + (*p_context)->type = GST_MATROSKA_TRACK_TYPE_AUDIO; + audio_context->channels = 1; + audio_context->samplerate = 8000; + return TRUE; +} + +gboolean +gst_matroska_track_init_subtitle_context (GstMatroskaTrackContext ** p_context) +{ + GstMatroskaTrackSubtitleContext *subtitle_context; + + g_assert (p_context != NULL && *p_context != NULL); + + /* already set up? (track info might come before track type) */ + if ((*p_context)->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE) + return TRUE; + + /* it better not have been set up as some other track type ... */ + if ((*p_context)->type != 0) { + g_return_val_if_reached (FALSE); + } + + subtitle_context = g_renew (GstMatroskaTrackSubtitleContext, *p_context, 1); + *p_context = (GstMatroskaTrackContext *) subtitle_context; + + (*p_context)->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE; + subtitle_context->invalid_utf8 = FALSE; + return TRUE; +} + +void +gst_matroska_register_tags (void) +{ + /* TODO: register other custom tags */ +} diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-ids.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-ids.h Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,620 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska-ids.h: matroska file/stream data IDs + * + * 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_MATROSKA_IDS_H__ +#define __GST_MATROSKA_IDS_H__ + +#include + +#include "ebml-ids.h" + +/* + * EBML DocType. + */ + +#define GST_MATROSKA_DOCTYPE_MATROSKA "matroska" +#define GST_MATROSKA_DOCTYPE_WEBM "webm" + +/* + * Matroska element IDs. max. 32-bit. + */ + +/* toplevel Segment */ +#define GST_MATROSKA_ID_SEGMENT 0x18538067 + +/* matroska top-level master IDs, childs of Segment */ +#define GST_MATROSKA_ID_SEGMENTINFO 0x1549A966 +#define GST_MATROSKA_ID_TRACKS 0x1654AE6B +#define GST_MATROSKA_ID_CUES 0x1C53BB6B +#define GST_MATROSKA_ID_TAGS 0x1254C367 +#define GST_MATROSKA_ID_SEEKHEAD 0x114D9B74 +#define GST_MATROSKA_ID_CLUSTER 0x1F43B675 +#define GST_MATROSKA_ID_ATTACHMENTS 0x1941A469 +#define GST_MATROSKA_ID_CHAPTERS 0x1043A770 + +/* IDs in the SegmentInfo master */ +#define GST_MATROSKA_ID_TIMECODESCALE 0x2AD7B1 +#define GST_MATROSKA_ID_DURATION 0x4489 +#define GST_MATROSKA_ID_WRITINGAPP 0x5741 +#define GST_MATROSKA_ID_MUXINGAPP 0x4D80 +#define GST_MATROSKA_ID_DATEUTC 0x4461 +#define GST_MATROSKA_ID_SEGMENTUID 0x73A4 +#define GST_MATROSKA_ID_SEGMENTFILENAME 0x7384 +#define GST_MATROSKA_ID_PREVUID 0x3CB923 +#define GST_MATROSKA_ID_PREVFILENAME 0x3C83AB +#define GST_MATROSKA_ID_NEXTUID 0x3EB923 +#define GST_MATROSKA_ID_NEXTFILENAME 0x3E83BB +#define GST_MATROSKA_ID_TITLE 0x7BA9 +#define GST_MATROSKA_ID_SEGMENTFAMILY 0x4444 +#define GST_MATROSKA_ID_CHAPTERTRANSLATE 0x6924 + +/* IDs in the ChapterTranslate master */ +#define GST_MATROSKA_ID_CHAPTERTRANSLATEEDITIONUID 0x69FC +#define GST_MATROSKA_ID_CHAPTERTRANSLATECODEC 0x69BF +#define GST_MATROSKA_ID_CHAPTERTRANSLATEID 0x69A5 + +/* ID in the Tracks master */ +#define GST_MATROSKA_ID_TRACKENTRY 0xAE + +/* IDs in the TrackEntry master */ +#define GST_MATROSKA_ID_TRACKNUMBER 0xD7 +#define GST_MATROSKA_ID_TRACKUID 0x73C5 +#define GST_MATROSKA_ID_TRACKTYPE 0x83 +#define GST_MATROSKA_ID_TRACKAUDIO 0xE1 +#define GST_MATROSKA_ID_TRACKVIDEO 0xE0 +#define GST_MATROSKA_ID_CONTENTENCODINGS 0x6D80 +#define GST_MATROSKA_ID_CODECID 0x86 +#define GST_MATROSKA_ID_CODECPRIVATE 0x63A2 +#define GST_MATROSKA_ID_CODECNAME 0x258688 +#define GST_MATROSKA_ID_TRACKNAME 0x536E +#define GST_MATROSKA_ID_TRACKLANGUAGE 0x22B59C +#define GST_MATROSKA_ID_TRACKFLAGENABLED 0xB9 +#define GST_MATROSKA_ID_TRACKFLAGDEFAULT 0x88 +#define GST_MATROSKA_ID_TRACKFLAGFORCED 0x55AA +#define GST_MATROSKA_ID_TRACKFLAGLACING 0x9C +#define GST_MATROSKA_ID_TRACKMINCACHE 0x6DE7 +#define GST_MATROSKA_ID_TRACKMAXCACHE 0x6DF8 +#define GST_MATROSKA_ID_TRACKDEFAULTDURATION 0x23E383 +#define GST_MATROSKA_ID_TRACKTIMECODESCALE 0x23314F +#define GST_MATROSKA_ID_MAXBLOCKADDITIONID 0x55EE +#define GST_MATROSKA_ID_TRACKATTACHMENTLINK 0x7446 +#define GST_MATROSKA_ID_TRACKOVERLAY 0x6FAB +#define GST_MATROSKA_ID_TRACKTRANSLATE 0x6624 +/* semi-draft */ +#define GST_MATROSKA_ID_TRACKOFFSET 0x537F +/* semi-draft */ +#define GST_MATROSKA_ID_CODECSETTINGS 0x3A9697 +/* semi-draft */ +#define GST_MATROSKA_ID_CODECINFOURL 0x3B4040 +/* semi-draft */ +#define GST_MATROSKA_ID_CODECDOWNLOADURL 0x26B240 +/* semi-draft */ +#define GST_MATROSKA_ID_CODECDECODEALL 0xAA + +/* IDs in the TrackTranslate master */ +#define GST_MATROSKA_ID_TRACKTRANSLATEEDITIONUID 0x66FC +#define GST_MATROSKA_ID_TRACKTRANSLATECODEC 0x66BF +#define GST_MATROSKA_ID_TRACKTRANSLATETRACKID 0x66A5 + + +/* IDs in the TrackVideo master */ +/* NOTE: This one is here only for backward compatibility. + * Use _TRACKDEFAULDURATION */ +#define GST_MATROSKA_ID_VIDEOFRAMERATE 0x2383E3 +#define GST_MATROSKA_ID_VIDEODISPLAYWIDTH 0x54B0 +#define GST_MATROSKA_ID_VIDEODISPLAYHEIGHT 0x54BA +#define GST_MATROSKA_ID_VIDEODISPLAYUNIT 0x54B2 +#define GST_MATROSKA_ID_VIDEOPIXELWIDTH 0xB0 +#define GST_MATROSKA_ID_VIDEOPIXELHEIGHT 0xBA +#define GST_MATROSKA_ID_VIDEOPIXELCROPBOTTOM 0x54AA +#define GST_MATROSKA_ID_VIDEOPIXELCROPTOP 0x54BB +#define GST_MATROSKA_ID_VIDEOPIXELCROPLEFT 0x54CC +#define GST_MATROSKA_ID_VIDEOPIXELCROPRIGHT 0x54DD +#define GST_MATROSKA_ID_VIDEOFLAGINTERLACED 0x9A +/* semi-draft */ +#define GST_MATROSKA_ID_VIDEOSTEREOMODE 0x53B8 +#define GST_MATROSKA_ID_VIDEOASPECTRATIOTYPE 0x54B3 +#define GST_MATROSKA_ID_VIDEOCOLOURSPACE 0x2EB524 +/* semi-draft */ +#define GST_MATROSKA_ID_VIDEOGAMMAVALUE 0x2FB523 + +/* IDs in the TrackAudio master */ +#define GST_MATROSKA_ID_AUDIOSAMPLINGFREQ 0xB5 +#define GST_MATROSKA_ID_AUDIOBITDEPTH 0x6264 +#define GST_MATROSKA_ID_AUDIOCHANNELS 0x9F +/* semi-draft */ +#define GST_MATROSKA_ID_AUDIOCHANNELPOSITIONS 0x7D7B +#define GST_MATROSKA_ID_AUDIOOUTPUTSAMPLINGFREQ 0x78B5 + +/* IDs in the TrackContentEncoding master */ +#define GST_MATROSKA_ID_CONTENTENCODING 0x6240 + +/* IDs in the ContentEncoding master */ +#define GST_MATROSKA_ID_CONTENTENCODINGORDER 0x5031 +#define GST_MATROSKA_ID_CONTENTENCODINGSCOPE 0x5032 +#define GST_MATROSKA_ID_CONTENTENCODINGTYPE 0x5033 +#define GST_MATROSKA_ID_CONTENTCOMPRESSION 0x5034 +#define GST_MATROSKA_ID_CONTENTENCRYPTION 0x5035 + +/* IDs in the ContentCompression master */ +#define GST_MATROSKA_ID_CONTENTCOMPALGO 0x4254 +#define GST_MATROSKA_ID_CONTENTCOMPSETTINGS 0x4255 + +/* IDs in the ContentEncryption master */ +#define GST_MATROSKA_ID_CONTENTENCALGO 0x47E1 +#define GST_MATROSKA_ID_CONTENTENCKEYID 0x47E2 +#define GST_MATROSKA_ID_CONTENTSIGNATURE 0x47E3 +#define GST_MATROSKA_ID_CONTENTSIGKEYID 0x47E4 +#define GST_MATROSKA_ID_CONTENTSIGALGO 0x47E5 +#define GST_MATROSKA_ID_CONTENTSIGHASHALGO 0x47E6 + +/* ID in the CUEs master */ +#define GST_MATROSKA_ID_POINTENTRY 0xBB + +/* IDs in the pointentry master */ +#define GST_MATROSKA_ID_CUETIME 0xB3 +#define GST_MATROSKA_ID_CUETRACKPOSITIONS 0xB7 + +/* IDs in the CueTrackPositions master */ +#define GST_MATROSKA_ID_CUETRACK 0xF7 +#define GST_MATROSKA_ID_CUECLUSTERPOSITION 0xF1 +#define GST_MATROSKA_ID_CUEBLOCKNUMBER 0x5378 +/* semi-draft */ +#define GST_MATROSKA_ID_CUECODECSTATE 0xEA +/* semi-draft */ +#define GST_MATROSKA_ID_CUEREFERENCE 0xDB + +/* IDs in the CueReference master */ +/* semi-draft */ +#define GST_MATROSKA_ID_CUEREFTIME 0x96 +/* semi-draft */ +#define GST_MATROSKA_ID_CUEREFCLUSTER 0x97 +/* semi-draft */ +#define GST_MATROSKA_ID_CUEREFNUMBER 0x535F +/* semi-draft */ +#define GST_MATROSKA_ID_CUEREFCODECSTATE 0xEB + +/* IDs in the Tags master */ +#define GST_MATROSKA_ID_TAG 0x7373 + +/* in the Tag master */ +#define GST_MATROSKA_ID_SIMPLETAG 0x67C8 +#define GST_MATROSKA_ID_TARGETS 0x63C0 + +/* in the SimpleTag master */ +#define GST_MATROSKA_ID_TAGNAME 0x45A3 +#define GST_MATROSKA_ID_TAGSTRING 0x4487 +#define GST_MATROSKA_ID_TAGLANGUAGE 0x447A +#define GST_MATROSKA_ID_TAGDEFAULT 0x4484 +#define GST_MATROSKA_ID_TAGBINARY 0x4485 + +/* in the Targets master */ +#define GST_MATROSKA_ID_TARGETTYPEVALUE 0x68CA +#define GST_MATROSKA_ID_TARGETTYPE 0x63CA +#define GST_MATROSKA_ID_TARGETTRACKUID 0x63C5 +#define GST_MATROSKA_ID_TARGETEDITIONUID 0x63C5 +#define GST_MATROSKA_ID_TARGETCHAPTERUID 0x63C4 +#define GST_MATROSKA_ID_TARGETATTACHMENTUID 0x63C6 + +/* IDs in the SeekHead master */ +#define GST_MATROSKA_ID_SEEKENTRY 0x4DBB + +/* IDs in the SeekEntry master */ +#define GST_MATROSKA_ID_SEEKID 0x53AB +#define GST_MATROSKA_ID_SEEKPOSITION 0x53AC + +/* IDs in the Cluster master */ +#define GST_MATROSKA_ID_CLUSTERTIMECODE 0xE7 +#define GST_MATROSKA_ID_BLOCKGROUP 0xA0 +#define GST_MATROSKA_ID_SIMPLEBLOCK 0xA3 +#define GST_MATROSKA_ID_REFERENCEBLOCK 0xFB +#define GST_MATROSKA_ID_POSITION 0xA7 +#define GST_MATROSKA_ID_PREVSIZE 0xAB +/* semi-draft */ +#define GST_MATROSKA_ID_ENCRYPTEDBLOCK 0xAF +#define GST_MATROSKA_ID_SILENTTRACKS 0x5854 + +/* IDs in the SilentTracks master */ +#define GST_MATROSKA_ID_SILENTTRACKNUMBER 0x58D7 + +/* IDs in the BlockGroup master */ +#define GST_MATROSKA_ID_BLOCK 0xA1 +#define GST_MATROSKA_ID_BLOCKDURATION 0x9B +/* semi-draft */ +#define GST_MATROSKA_ID_BLOCKVIRTUAL 0xA2 +#define GST_MATROSKA_ID_REFERENCEBLOCK 0xFB +#define GST_MATROSKA_ID_BLOCKADDITIONS 0x75A1 +#define GST_MATROSKA_ID_REFERENCEPRIORITY 0xFA +/* semi-draft */ +#define GST_MATROSKA_ID_REFERENCEVIRTUAL 0xFD +/* semi-draft */ +#define GST_MATROSKA_ID_CODECSTATE 0xA4 +#define GST_MATROSKA_ID_SLICES 0x8E + +/* IDs in the BlockAdditions master */ +#define GST_MATROSKA_ID_BLOCKMORE 0xA6 + +/* IDs in the BlockMore master */ +#define GST_MATROSKA_ID_BLOCKADDID 0xEE +#define GST_MATROSKA_ID_BLOCKADDITIONAL 0xA5 + +/* IDs in the Slices master */ +#define GST_MATROSKA_ID_TIMESLICE 0xE8 + +/* IDs in the TimeSlice master */ +#define GST_MATROSKA_ID_LACENUMBER 0xCC +/* semi-draft */ +#define GST_MATROSKA_ID_FRAMENUMBER 0xCD +/* semi-draft */ +#define GST_MATROSKA_ID_BLOCKADDITIONID 0xCB +/* semi-draft */ +#define GST_MATROSKA_ID_TIMESLICEDELAY 0xCE +#define GST_MATROSKA_ID_TIMESLICEDURATION 0xCF + +/* IDs in the Attachments master */ +#define GST_MATROSKA_ID_ATTACHEDFILE 0x61A7 + +/* IDs in the AttachedFile master */ +#define GST_MATROSKA_ID_FILEDESCRIPTION 0x467E +#define GST_MATROSKA_ID_FILENAME 0x466E +#define GST_MATROSKA_ID_FILEMIMETYPE 0x4660 +#define GST_MATROSKA_ID_FILEDATA 0x465C +#define GST_MATROSKA_ID_FILEUID 0x46AE +/* semi-draft */ +#define GST_MATROSKA_ID_FILEREFERRAL 0x4675 + +/* IDs in the Chapters master */ +#define GST_MATROSKA_ID_EDITIONENTRY 0x45B9 + +/* IDs in the EditionEntry master */ +#define GST_MATROSKA_ID_EDITIONUID 0x45BC +#define GST_MATROSKA_ID_EDITIONFLAGHIDDEN 0x45BD +#define GST_MATROSKA_ID_EDITIONFLAGDEFAULT 0x45DB +#define GST_MATROSKA_ID_EDITIONFLAGORDERED 0x45DD +#define GST_MATROSKA_ID_CHAPTERATOM 0xB6 + +/* IDs in the ChapterAtom master */ +#define GST_MATROSKA_ID_CHAPTERUID 0x73C4 +#define GST_MATROSKA_ID_CHAPTERTIMESTART 0x91 +#define GST_MATROSKA_ID_CHAPTERTIMESTOP 0x92 +#define GST_MATROSKA_ID_CHAPTERFLAGHIDDEN 0x98 +#define GST_MATROSKA_ID_CHAPTERFLAGENABLED 0x4598 +#define GST_MATROSKA_ID_CHAPTERSEGMENTUID 0x6E67 +#define GST_MATROSKA_ID_CHAPTERSEGMENTEDITIONUID 0x6EBC +#define GST_MATROSKA_ID_CHAPTERPHYSICALEQUIV 0x63C3 +#define GST_MATROSKA_ID_CHAPTERTRACK 0x8F +#define GST_MATROSKA_ID_CHAPTERDISPLAY 0x80 +#define GST_MATROSKA_ID_CHAPPROCESS 0x6944 + +/* IDs in the ChapProcess master */ +#define GST_MATROSKA_ID_CHAPPROCESSCODECID 0x6955 +#define GST_MATROSKA_ID_CHAPPROCESSPRIVATE 0x450D +#define GST_MATROSKA_ID_CHAPPROCESSCOMMAND 0x6911 + +/* IDs in the ChapProcessCommand master */ +#define GST_MATROSKA_ID_CHAPPROCESSTIME 0x6922 +#define GST_MATROSKA_ID_CHAPPROCESSDATA 0x6933 + +/* IDs in the ChapterDisplay master */ +#define GST_MATROSKA_ID_CHAPSTRING 0x85 +#define GST_MATROSKA_ID_CHAPLANGUAGE 0x437C +#define GST_MATROSKA_ID_CHAPCOUNTRY 0x437E + +/* IDs in the ChapterTrack master */ +#define GST_MATROSKA_ID_CHAPTERTRACKNUMBER 0x89 + +/* + * Matroska Codec IDs. Strings. + */ + +#define GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC "V_MS/VFW/FOURCC" +#define GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED "V_UNCOMPRESSED" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP "V_MPEG4/ISO/SP" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP "V_MPEG4/ISO/ASP" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AP "V_MPEG4/ISO/AP" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC "V_MPEG4/ISO/AVC" +#define GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3 "V_MPEG4/MS/V3" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG1 "V_MPEG1" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG2 "V_MPEG2" +/* FIXME: not (yet) in the spec! */ +#define GST_MATROSKA_CODEC_ID_VIDEO_MJPEG "V_MJPEG" +#define GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1 "V_REAL/RV10" +#define GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2 "V_REAL/RV20" +#define GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3 "V_REAL/RV30" +#define GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4 "V_REAL/RV40" +#define GST_MATROSKA_CODEC_ID_VIDEO_THEORA "V_THEORA" +#define GST_MATROSKA_CODEC_ID_VIDEO_QUICKTIME "V_QUICKTIME" +#define GST_MATROSKA_CODEC_ID_VIDEO_SNOW "V_SNOW" +#define GST_MATROSKA_CODEC_ID_VIDEO_DIRAC "V_DIRAC" +#define GST_MATROSKA_CODEC_ID_VIDEO_VP8 "V_VP8" + +#define GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1 "A_MPEG/L1" +#define GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2 "A_MPEG/L2" +#define GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3 "A_MPEG/L3" +#define GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE "A_PCM/INT/BIG" +#define GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE "A_PCM/INT/LIT" +#define GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT "A_PCM/FLOAT/IEEE" +#define GST_MATROSKA_CODEC_ID_AUDIO_AC3 "A_AC3" +#define GST_MATROSKA_CODEC_ID_AUDIO_AC3_BSID9 "A_AC3/BSID9" +#define GST_MATROSKA_CODEC_ID_AUDIO_AC3_BSID10 "A_AC3/BSID10" +#define GST_MATROSKA_CODEC_ID_AUDIO_EAC3 "A_EAC3" +#define GST_MATROSKA_CODEC_ID_AUDIO_DTS "A_DTS" +#define GST_MATROSKA_CODEC_ID_AUDIO_VORBIS "A_VORBIS" +#define GST_MATROSKA_CODEC_ID_AUDIO_FLAC "A_FLAC" +/* FIXME: not yet in the spec */ +#define GST_MATROSKA_CODEC_ID_AUDIO_SPEEX "A_SPEEX" +#define GST_MATROSKA_CODEC_ID_AUDIO_ACM "A_MS/ACM" +#define GST_MATROSKA_CODEC_ID_AUDIO_TTA "A_TTA1" +#define GST_MATROSKA_CODEC_ID_AUDIO_WAVPACK4 "A_WAVPACK4" +#define GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4 "A_REAL/14_4" +#define GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8 "A_REAL/28_8" +#define GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK "A_REAL/COOK" +#define GST_MATROSKA_CODEC_ID_AUDIO_REAL_SIPR "A_REAL/SIPR" +#define GST_MATROSKA_CODEC_ID_AUDIO_REAL_RALF "A_REAL/RALF" +#define GST_MATROSKA_CODEC_ID_AUDIO_REAL_ATRC "A_REAL/ATRC" +#define GST_MATROSKA_CODEC_ID_AUDIO_AAC "A_AAC" +#define GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "A_AAC/MPEG2/" +#define GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "A_AAC/MPEG4/" +#define GST_MATROSKA_CODEC_ID_AUDIO_QUICKTIME_QDMC "A_QUICKTIME/QDMC" +#define GST_MATROSKA_CODEC_ID_AUDIO_QUICKTIME_QDM2 "A_QUICKTIME/QDM2" +/* Undefined for now: +#define GST_MATROSKA_CODEC_ID_AUDIO_MPC "A_MPC" +*/ + +#define GST_MATROSKA_CODEC_ID_SUBTITLE_ASCII "S_TEXT/ASCII" +#define GST_MATROSKA_CODEC_ID_SUBTITLE_UTF8 "S_TEXT/UTF8" +#define GST_MATROSKA_CODEC_ID_SUBTITLE_SSA "S_TEXT/SSA" +#define GST_MATROSKA_CODEC_ID_SUBTITLE_ASS "S_TEXT/ASS" +#define GST_MATROSKA_CODEC_ID_SUBTITLE_USF "S_TEXT/USF" +#define GST_MATROSKA_CODEC_ID_SUBTITLE_VOBSUB "S_VOBSUB" +#define GST_MATROSKA_CODEC_ID_SUBTITLE_HDMVPGS "S_HDMV/PGS" +#define GST_MATROSKA_CODEC_ID_SUBTITLE_BMP "S_IMAGE/BMP" +#define GST_MATROSKA_CODEC_ID_SUBTITLE_KATE "S_KATE" + +/* + * Matroska tags. Strings. + */ + +#define GST_MATROSKA_TAG_ID_TITLE "TITLE" +#define GST_MATROSKA_TAG_ID_AUTHOR "AUTHOR" +#define GST_MATROSKA_TAG_ID_ARTIST "ARTIST" +#define GST_MATROSKA_TAG_ID_ALBUM "ALBUM" +#define GST_MATROSKA_TAG_ID_COMMENTS "COMMENTS" +#define GST_MATROSKA_TAG_ID_BITSPS "BITSPS" +#define GST_MATROSKA_TAG_ID_BPS "BPS" +#define GST_MATROSKA_TAG_ID_ENCODER "ENCODER" +#define GST_MATROSKA_TAG_ID_DATE "DATE" +#define GST_MATROSKA_TAG_ID_ISRC "ISRC" +#define GST_MATROSKA_TAG_ID_COPYRIGHT "COPYRIGHT" +#define GST_MATROSKA_TAG_ID_BPM "BPM" +#define GST_MATROSKA_TAG_ID_TERMS_OF_USE "TERMS_OF_USE" +#define GST_MATROSKA_TAG_ID_DATE "DATE" +#define GST_MATROSKA_TAG_ID_COMPOSER "COMPOSER" +#define GST_MATROSKA_TAG_ID_LEAD_PERFORMER "LEAD_PERFOMER" +#define GST_MATROSKA_TAG_ID_GENRE "GENRE" + +/* + * TODO: add this tag & mappings + * "REPLAYGAIN_GAIN" -> GST_TAG_*_GAIN see http://replaygain.hydrogenaudio.org/rg_data_format.html + * "REPLAYGAIN_PEAK" -> GST_TAG_*_PEAK see http://replaygain.hydrogenaudio.org/peak_data_format.html + * both are depending on the target (track, album?) + * + * "TOTAL_PARTS" -> GST_TAG_TRACK_COUNT depending on target + * "PART_NUMBER" -> GST_TAG_TRACK_NUMBER depending on target + * + * "SORT_WITH" -> nested in other elements, GST_TAG_TITLE_SORTNAME, etc + * + * TODO: maybe add custom gstreamer tags for other standard matroska tags, + * see http://matroska.org/technical/specs/tagging/index.html + * + * TODO: handle tag targets and nesting correctly + */ + +/* + * Enumerations for various types (mapping from binary + * value to what it actually means). + */ + +typedef enum { + GST_MATROSKA_TRACK_TYPE_VIDEO = 0x1, + GST_MATROSKA_TRACK_TYPE_AUDIO = 0x2, + GST_MATROSKA_TRACK_TYPE_COMPLEX = 0x3, + GST_MATROSKA_TRACK_TYPE_LOGO = 0x10, + GST_MATROSKA_TRACK_TYPE_SUBTITLE = 0x11, + GST_MATROSKA_TRACK_TYPE_BUTTONS = 0x12, + GST_MATROSKA_TRACK_TYPE_CONTROL = 0x20, +} GstMatroskaTrackType; + +typedef enum { + GST_MATROSKA_ASPECT_RATIO_MODE_FREE = 0x0, + GST_MATROSKA_ASPECT_RATIO_MODE_KEEP = 0x1, + GST_MATROSKA_ASPECT_RATIO_MODE_FIXED = 0x2, +} GstMatroskaAspectRatioMode; + +/* + * These aren't in any way "matroska-form" things, + * it's just something I use in the muxer/demuxer. + */ + +typedef enum { + GST_MATROSKA_TRACK_ENABLED = (1<<0), + GST_MATROSKA_TRACK_DEFAULT = (1<<1), + GST_MATROSKA_TRACK_LACING = (1<<2), + GST_MATROSKA_TRACK_FORCED = (1<<3), + GST_MATROSKA_TRACK_SHIFT = (1<<16) +} GstMatroskaTrackFlags; + +typedef enum { + GST_MATROSKA_VIDEOTRACK_INTERLACED = (GST_MATROSKA_TRACK_SHIFT<<0) +} GstMatroskaVideoTrackFlags; + + +typedef struct _GstMatroskaTrackContext GstMatroskaTrackContext; + +/* TODO: check if all fields are used */ +struct _GstMatroskaTrackContext { + GstPad *pad; + GstCaps *caps; + guint index; + GstFlowReturn last_flow; + /* reverse playback */ + GstClockTime from_time; + gint64 from_offset; + gint64 to_offset; + + GArray *index_table; + + gint index_writer_id; + + /* some often-used info */ + gchar *codec_id, *codec_name, *name, *language; + guint8 *codec_priv; + guint codec_priv_size; + guint8 *codec_state; + guint codec_state_size; + GstMatroskaTrackType type; + guint uid, num; + GstMatroskaTrackFlags flags; + guint64 default_duration; + guint64 pos; + gdouble timecodescale; + + gboolean set_discont; /* TRUE = set DISCONT flag on next buffer */ + + /* Special flag for Vorbis and Theora, for which we need to send + * codec_priv first before sending any data, and just testing + * for time == 0 is not enough to detect that. Used by demuxer */ + gboolean send_xiph_headers; + + /* Special flag for Flac, for which we need to reconstruct the header + * buffer from the codec_priv data before sending any data, and just + * testing for time == 0 is not enough to detect that. Used by demuxer */ + gboolean send_flac_headers; + + /* Special flag for Speex, for which we need to reconstruct the header + * buffer from the codec_priv data before sending any data, and just + * testing for time == 0 is not enough to detect that. Used by demuxer */ + gboolean send_speex_headers; + + /* Special flag for VobSub, for which we have to send colour table info + * (if available) first before sending any data, and just testing + * for time == 0 is not enough to detect that. Used by demuxer */ + gboolean send_dvd_event; + + /* Special counter for muxer to skip the first N vorbis/theora headers - + * they are put into codec private data, not muxed into the stream */ + guint xiph_headers_to_skip; + + /* Used for postprocessing a frame before it is pushed from the demuxer */ + GstFlowReturn (*postprocess_frame) (GstElement *element, + GstMatroskaTrackContext *context, + GstBuffer **buffer); + + /* Tags to send after newsegment event */ + GstTagList *pending_tags; + + /* A GArray of GstMatroskaTrackEncoding structures which contain the + * encoding (compression/encryption) settings for this track, if any */ + GArray *encodings; + + /* Whether the stream is EOS */ + gboolean eos; +}; + +typedef struct _GstMatroskaTrackVideoContext { + GstMatroskaTrackContext parent; + + guint pixel_width, pixel_height; + guint display_width, display_height; + gdouble default_fps; + GstMatroskaAspectRatioMode asr_mode; + guint32 fourcc; + + /* QoS */ + GstClockTime earliest_time; + + GstBuffer *dirac_unit; +} GstMatroskaTrackVideoContext; + +typedef struct _GstMatroskaTrackAudioContext { + GstMatroskaTrackContext parent; + + guint samplerate, channels, bitdepth; + + guint32 wvpk_block_index; +} GstMatroskaTrackAudioContext; + +typedef struct _GstMatroskaTrackSubtitleContext { + GstMatroskaTrackContext parent; + + gboolean check_utf8; /* buffers should be valid UTF-8 */ + gboolean invalid_utf8; /* work around broken files */ +} GstMatroskaTrackSubtitleContext; + +typedef struct _GstMatroskaIndex { + guint64 pos; /* of the corresponding *cluster*! */ + guint16 track; /* reference to 'num' */ + GstClockTime time; /* in nanoseconds */ + guint32 block; /* number of the block in the cluster */ +} GstMatroskaIndex; + +typedef struct _Wavpack4Header { + guchar ck_id [4]; /* "wvpk" */ + guint32 ck_size; /* size of entire frame (minus 8, of course) */ + guint16 version; /* 0x403 for now */ + guint8 track_no; /* track number (0 if not used, like now) */ + guint8 index_no; /* remember these? (0 if not used, like now) */ + guint32 total_samples; /* for entire file (-1 if unknown) */ + guint32 block_index; /* index of first sample in block (to file begin) */ + guint32 block_samples; /* # samples in this block */ + guint32 flags; /* various flags for id and decoding */ + guint32 crc; /* crc for actual decoded data */ +} Wavpack4Header; + +typedef enum { + GST_MATROSKA_TRACK_ENCODING_SCOPE_FRAME = (1<<0), + GST_MATROSKA_TRACK_ENCODING_SCOPE_CODEC_DATA = (1<<1), + GST_MATROSKA_TRACK_ENCODING_SCOPE_NEXT_CONTENT_ENCODING = (1<<2) +} GstMatroskaTrackEncodingScope; + +typedef enum { + GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_ZLIB = 0, + GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_BZLIB = 1, + GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_LZO1X = 2, + GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_HEADERSTRIP = 3 +} GstMatroskaTrackCompressionAlgorithm; + +typedef struct _GstMatroskaTrackEncoding { + guint order; + guint scope : 3; + guint type : 1; + guint comp_algo : 2; + guint8 *comp_settings; + guint comp_settings_length; +} GstMatroskaTrackEncoding; + +gboolean gst_matroska_track_init_video_context (GstMatroskaTrackContext ** p_context); +gboolean gst_matroska_track_init_audio_context (GstMatroskaTrackContext ** p_context); +gboolean gst_matroska_track_init_subtitle_context (GstMatroskaTrackContext ** p_context); + +void gst_matroska_register_tags (void); + +#endif /* __GST_MATROSKA_IDS_H__ */ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-mux.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-mux.c Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,3001 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * (c) 2005 Michal Benes + * (c) 2008 Sebastian Dröge + * + * matroska-mux.c: matroska file/stream muxer + * + * 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: - check everywhere that we don't write invalid values + * - make sure timestamps are correctly scaled everywhere + */ + +/** + * SECTION:element-matroskamux + * + * matroskamux muxes different input streams into a Matroska file. + * + * + * Example launch line + * |[ + * gst-launch -v filesrc location=/path/to/mp3 ! mp3parse ! matroskamux name=mux ! filesink location=test.mkv filesrc location=/path/to/theora.ogg ! oggdemux ! theoraparse ! mux. + * ]| This pipeline muxes an MP3 file and a Ogg Theora video into a Matroska file. + * |[ + * gst-launch -v audiotestsrc num-buffers=100 ! audioconvert ! vorbisenc ! matroskamux ! filesink location=test.mka + * ]| This pipeline muxes a 440Hz sine wave encoded with the Vorbis codec into a Matroska file. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "matroska-mux.h" +#include "matroska-ids.h" + +GST_DEBUG_CATEGORY_STATIC (matroskamux_debug); +#define GST_CAT_DEFAULT matroskamux_debug + + +enum +{ + ARG_0, + ARG_WRITING_APP, + ARG_DOCTYPE_VERSION, + ARG_MIN_INDEX_INTERVAL, + ARG_STREAMABLE +}; + +#define DEFAULT_DOCTYPE_VERSION 2 +#define DEFAULT_WRITING_APP "GStreamer Matroska muxer" +#define DEFAULT_MIN_INDEX_INTERVAL 0 +#define DEFAULT_STREAMABLE FALSE + +/* WAVEFORMATEX is gst_riff_strf_auds + an extra guint16 extension size */ +#define WAVEFORMATEX_SIZE (2 + sizeof (gst_riff_strf_auds)) + +static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-matroska") + ); + +#define COMMON_VIDEO_CAPS \ + "width = (int) [ 16, 4096 ], " \ + "height = (int) [ 16, 4096 ], " \ + "framerate = (fraction) [ 0, MAX ]" + +#define COMMON_VIDEO_CAPS_NO_FRAMERATE \ + "width = (int) [ 16, 4096 ], " \ + "height = (int) [ 16, 4096 ] " + +/* FIXME: + * * require codec data, etc as needed + */ + +static GstStaticPadTemplate videosink_templ = + GST_STATIC_PAD_TEMPLATE ("video_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("video/mpeg, " + "mpegversion = (int) { 1, 2, 4 }, " + "systemstream = (boolean) false, " + COMMON_VIDEO_CAPS "; " + "video/x-h264, stream-format=avc, alignment=au, " + COMMON_VIDEO_CAPS "; " + "video/x-divx, " + COMMON_VIDEO_CAPS "; " + "video/x-xvid, " + COMMON_VIDEO_CAPS "; " + "video/x-huffyuv, " + COMMON_VIDEO_CAPS "; " + "video/x-dv, " + COMMON_VIDEO_CAPS "; " + "video/x-h263, " + COMMON_VIDEO_CAPS "; " + "video/x-msmpeg, " + COMMON_VIDEO_CAPS "; " + "image/jpeg, " + COMMON_VIDEO_CAPS_NO_FRAMERATE "; " + "video/x-theora; " + "video/x-dirac, " + COMMON_VIDEO_CAPS "; " + "video/x-pn-realvideo, " + "rmversion = (int) [1, 4], " + COMMON_VIDEO_CAPS "; " + "video/x-vp8, " + COMMON_VIDEO_CAPS "; " + "video/x-raw-yuv, " + "format = (fourcc) { YUY2, I420, YV12, UYVY, AYUV }, " + COMMON_VIDEO_CAPS "; " + "video/x-wmv, " "wmvversion = (int) [ 1, 3 ], " COMMON_VIDEO_CAPS) + ); + +#define COMMON_AUDIO_CAPS \ + "channels = (int) [ 1, MAX ], " \ + "rate = (int) [ 1, MAX ]" + +/* FIXME: + * * require codec data, etc as needed + */ +static GstStaticPadTemplate audiosink_templ = + GST_STATIC_PAD_TEMPLATE ("audio_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("audio/mpeg, " + "mpegversion = (int) 1, " + "layer = (int) [ 1, 3 ], " + "stream-format = (string) { raw }, " + COMMON_AUDIO_CAPS "; " + "audio/mpeg, " + "mpegversion = (int) { 2, 4 }, " + COMMON_AUDIO_CAPS "; " + "audio/x-ac3, " + COMMON_AUDIO_CAPS "; " + "audio/x-eac3, " + COMMON_AUDIO_CAPS "; " + "audio/x-dts, " + COMMON_AUDIO_CAPS "; " + "audio/x-vorbis, " + COMMON_AUDIO_CAPS "; " + "audio/x-flac, " + COMMON_AUDIO_CAPS "; " + "audio/x-speex, " + COMMON_AUDIO_CAPS "; " + "audio/x-raw-int, " + "width = (int) 8, " + "depth = (int) 8, " + "signed = (boolean) false, " + COMMON_AUDIO_CAPS ";" + "audio/x-raw-int, " + "width = (int) 16, " + "depth = (int) 16, " + "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " + "signed = (boolean) true, " + COMMON_AUDIO_CAPS ";" + "audio/x-raw-int, " + "width = (int) 24, " + "depth = (int) 24, " + "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " + "signed = (boolean) true, " + COMMON_AUDIO_CAPS ";" + "audio/x-raw-int, " + "width = (int) 32, " + "depth = (int) 32, " + "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " + "signed = (boolean) true, " + COMMON_AUDIO_CAPS ";" + "audio/x-raw-float, " + "width = (int) [ 32, 64 ], " + "endianness = (int) LITTLE_ENDIAN, " + COMMON_AUDIO_CAPS ";" + "audio/x-tta, " + "width = (int) { 8, 16, 24 }, " + "channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]; " + "audio/x-pn-realaudio, " + "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS "; " + "audio/x-wma, " "wmaversion = (int) [ 1, 3 ], " + "block_align = (int) [ 0, 65535 ], bitrate = (int) [ 0, 524288 ], " + COMMON_AUDIO_CAPS ";" + "audio/x-alaw, " + "channels = (int) {1, 2}, " "rate = (int) [ 8000, 192000 ]; " + "audio/x-mulaw, " + "channels = (int) {1, 2}, " "rate = (int) [ 8000, 192000 ]") + ); + +static GstStaticPadTemplate subtitlesink_templ = +GST_STATIC_PAD_TEMPLATE ("subtitle_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("subtitle/x-kate")); + +static GArray *used_uids; +G_LOCK_DEFINE_STATIC (used_uids); + +static void gst_matroska_mux_add_interfaces (GType type); + +GST_BOILERPLATE_FULL (GstMatroskaMux, gst_matroska_mux, GstElement, + GST_TYPE_ELEMENT, gst_matroska_mux_add_interfaces); + +/* Matroska muxer destructor */ +static void gst_matroska_mux_finalize (GObject * object); + +/* Pads collected callback */ +static GstFlowReturn +gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data); + +/* pad functions */ +static gboolean gst_matroska_mux_handle_src_event (GstPad * pad, + GstEvent * event); +static GstPad *gst_matroska_mux_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name); +static void gst_matroska_mux_release_pad (GstElement * element, GstPad * pad); + +/* gst internal change state handler */ +static GstStateChangeReturn +gst_matroska_mux_change_state (GstElement * element, GstStateChange transition); + +/* gobject bla bla */ +static void gst_matroska_mux_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_matroska_mux_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +/* reset muxer */ +static void gst_matroska_mux_reset (GstElement * element); + +/* uid generation */ +static guint64 gst_matroska_mux_create_uid (); + +static gboolean theora_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context); +static gboolean vorbis_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context); +static gboolean speex_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context); +static gboolean kate_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context); +static gboolean flac_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context); + +static void +gst_matroska_mux_add_interfaces (GType type) +{ + static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL }; + + g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info); +} + +static void +gst_matroska_mux_base_init (gpointer g_class) +{ +} + +static void +gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&videosink_templ)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&audiosink_templ)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&subtitlesink_templ)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_templ)); + gst_element_class_set_details_simple (gstelement_class, "Matroska muxer", + "Codec/Muxer", + "Muxes video/audio/subtitle streams into a matroska stream", + "GStreamer maintainers "); + + GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0, + "Matroska muxer"); + + gobject_class->finalize = gst_matroska_mux_finalize; + + gobject_class->get_property = gst_matroska_mux_get_property; + gobject_class->set_property = gst_matroska_mux_set_property; + + g_object_class_install_property (gobject_class, ARG_WRITING_APP, + g_param_spec_string ("writing-app", "Writing application.", + "The name the application that creates the matroska file.", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_DOCTYPE_VERSION, + g_param_spec_int ("version", "DocType version", + "This parameter determines what Matroska features can be used.", + 1, 2, DEFAULT_DOCTYPE_VERSION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_MIN_INDEX_INTERVAL, + g_param_spec_int64 ("min-index-interval", "Minimum time between index " + "entries", "An index entry is created every so many nanoseconds.", + 0, G_MAXINT64, DEFAULT_MIN_INDEX_INTERVAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_STREAMABLE, + g_param_spec_boolean ("streamable", "Determines whether output should " + "be streamable", "If set to true, the output should be as if it is " + "to be streamed and hence no indexes written or duration written.", + DEFAULT_STREAMABLE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_STATIC_STRINGS)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state); + gstelement_class->request_new_pad = + GST_DEBUG_FUNCPTR (gst_matroska_mux_request_new_pad); + gstelement_class->release_pad = + GST_DEBUG_FUNCPTR (gst_matroska_mux_release_pad); +} + + +/** + * gst_matroska_mux_init: + * @mux: #GstMatroskaMux that should be initialized. + * @g_class: Class of the muxer. + * + * Matroska muxer constructor. + */ +static void +gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class) +{ + GstPadTemplate *templ; + + templ = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src"); + mux->srcpad = gst_pad_new_from_template (templ, "src"); + + gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event); + gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); + + mux->collect = gst_collect_pads_new (); + gst_collect_pads_set_function (mux->collect, + (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_matroska_mux_collected), + mux); + + mux->ebml_write = gst_ebml_write_new (mux->srcpad); + mux->doctype = GST_MATROSKA_DOCTYPE_MATROSKA; + + /* property defaults */ + mux->doctype_version = DEFAULT_DOCTYPE_VERSION; + mux->writing_app = g_strdup (DEFAULT_WRITING_APP); + mux->min_index_interval = DEFAULT_MIN_INDEX_INTERVAL; + mux->streamable = DEFAULT_STREAMABLE; + + /* initialize internal variables */ + mux->index = NULL; + mux->num_streams = 0; + mux->num_a_streams = 0; + mux->num_t_streams = 0; + mux->num_v_streams = 0; + + /* initialize remaining variables */ + gst_matroska_mux_reset (GST_ELEMENT (mux)); +} + + +/** + * gst_matroska_mux_finalize: + * @object: #GstMatroskaMux that should be finalized. + * + * Finalize matroska muxer. + */ +static void +gst_matroska_mux_finalize (GObject * object) +{ + GstMatroskaMux *mux = GST_MATROSKA_MUX (object); + + gst_object_unref (mux->collect); + gst_object_unref (mux->ebml_write); + if (mux->writing_app) + g_free (mux->writing_app); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +/** + * gst_matroska_mux_create_uid: + * + * Generate new unused track UID. + * + * Returns: New track UID. + */ +static guint64 +gst_matroska_mux_create_uid (void) +{ + guint64 uid = 0; + + G_LOCK (used_uids); + + if (!used_uids) + used_uids = g_array_sized_new (FALSE, FALSE, sizeof (guint64), 10); + + while (!uid) { + guint i; + + uid = (((guint64) g_random_int ()) << 32) | g_random_int (); + for (i = 0; i < used_uids->len; i++) { + if (g_array_index (used_uids, guint64, i) == uid) { + uid = 0; + break; + } + } + g_array_append_val (used_uids, uid); + } + + G_UNLOCK (used_uids); + return uid; +} + + +/** + * gst_matroska_pad_reset: + * @collect_pad: the #GstMatroskaPad + * + * Reset and/or release resources of a matroska collect pad. + */ +static void +gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full) +{ + gchar *name = NULL; + GstMatroskaTrackType type = 0; + + /* free track information */ + if (collect_pad->track != NULL) { + /* retrieve for optional later use */ + name = collect_pad->track->name; + type = collect_pad->track->type; + /* extra for video */ + if (type == GST_MATROSKA_TRACK_TYPE_VIDEO) { + GstMatroskaTrackVideoContext *ctx = + (GstMatroskaTrackVideoContext *) collect_pad->track; + + if (ctx->dirac_unit) { + gst_buffer_unref (ctx->dirac_unit); + ctx->dirac_unit = NULL; + } + } + g_free (collect_pad->track->codec_id); + g_free (collect_pad->track->codec_name); + if (full) + g_free (collect_pad->track->name); + g_free (collect_pad->track->language); + g_free (collect_pad->track->codec_priv); + g_free (collect_pad->track); + collect_pad->track = NULL; + } + + /* free cached buffer */ + if (collect_pad->buffer != NULL) { + gst_buffer_unref (collect_pad->buffer); + collect_pad->buffer = NULL; + } + + if (!full && type != 0) { + GstMatroskaTrackContext *context; + + /* create a fresh context */ + switch (type) { + case GST_MATROSKA_TRACK_TYPE_VIDEO: + context = (GstMatroskaTrackContext *) + g_new0 (GstMatroskaTrackVideoContext, 1); + break; + case GST_MATROSKA_TRACK_TYPE_AUDIO: + context = (GstMatroskaTrackContext *) + g_new0 (GstMatroskaTrackAudioContext, 1); + break; + case GST_MATROSKA_TRACK_TYPE_SUBTITLE: + context = (GstMatroskaTrackContext *) + g_new0 (GstMatroskaTrackSubtitleContext, 1); + break; + default: + g_assert_not_reached (); + return; + } + + context->type = type; + context->name = name; + /* TODO: check default values for the context */ + context->flags = GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT; + collect_pad->track = context; + collect_pad->buffer = NULL; + collect_pad->duration = 0; + collect_pad->start_ts = GST_CLOCK_TIME_NONE; + collect_pad->end_ts = GST_CLOCK_TIME_NONE; + } +} + +/** + * gst_matroska_pad_free: + * @collect_pad: the #GstMatroskaPad + * + * Release resources of a matroska collect pad. + */ +static void +gst_matroska_pad_free (GstMatroskaPad * collect_pad) +{ + gst_matroska_pad_reset (collect_pad, TRUE); +} + + +/** + * gst_matroska_mux_reset: + * @element: #GstMatroskaMux that should be reseted. + * + * Reset matroska muxer back to initial state. + */ +static void +gst_matroska_mux_reset (GstElement * element) +{ + GstMatroskaMux *mux = GST_MATROSKA_MUX (element); + GSList *walk; + + /* reset EBML write */ + gst_ebml_write_reset (mux->ebml_write); + + /* reset input */ + mux->state = GST_MATROSKA_MUX_STATE_START; + + /* clean up existing streams */ + + for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) { + GstMatroskaPad *collect_pad; + + collect_pad = (GstMatroskaPad *) walk->data; + + /* reset collect pad to pristine state */ + gst_matroska_pad_reset (collect_pad, FALSE); + } + + /* reset indexes */ + mux->num_indexes = 0; + g_free (mux->index); + mux->index = NULL; + + /* reset timers */ + mux->time_scale = GST_MSECOND; + mux->max_cluster_duration = G_MAXINT16 * mux->time_scale; + mux->duration = 0; + + /* reset cluster */ + mux->cluster = 0; + mux->cluster_time = 0; + mux->cluster_pos = 0; + mux->prev_cluster_size = 0; + + /* reset tags */ + gst_tag_setter_reset_tags (GST_TAG_SETTER (mux)); +} + +/** + * gst_matroska_mux_handle_src_event: + * @pad: Pad which received the event. + * @event: Received event. + * + * handle events - copied from oggmux without understanding + * + * Returns: #TRUE on success. + */ +static gboolean +gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event) +{ + GstEventType type; + + type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN; + + switch (type) { + case GST_EVENT_SEEK: + /* disable seeking for now */ + return FALSE; + default: + break; + } + + return gst_pad_event_default (pad, event); +} + +/** + * gst_matroska_mux_handle_sink_event: + * @pad: Pad which received the event. + * @event: Received event. + * + * handle events - informational ones like tags + * + * Returns: #TRUE on success. + */ +static gboolean +gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event) +{ + GstMatroskaTrackContext *context; + GstMatroskaPad *collect_pad; + GstMatroskaMux *mux; + GstTagList *list; + gboolean ret = TRUE; + + mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_TAG:{ + gchar *lang = NULL; + + GST_DEBUG_OBJECT (mux, "received tag event"); + gst_event_parse_tag (event, &list); + + collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad); + g_assert (collect_pad); + context = collect_pad->track; + g_assert (context); + + /* Matroska wants ISO 639-2B code, taglist most likely contains 639-1 */ + if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &lang)) { + const gchar *lang_code; + + lang_code = gst_tag_get_language_code_iso_639_2B (lang); + if (lang_code) { + GST_INFO_OBJECT (pad, "Setting language to '%s'", lang_code); + context->language = g_strdup (lang_code); + } else { + GST_WARNING_OBJECT (pad, "Did not get language code for '%s'", lang); + } + g_free (lang); + } + + /* FIXME: what about stream-specific tags? */ + gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list, + gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux))); + + gst_event_unref (event); + /* handled this, don't want collectpads to forward it downstream */ + event = NULL; + break; + } + case GST_EVENT_NEWSEGMENT: + /* We don't support NEWSEGMENT events */ + ret = FALSE; + gst_event_unref (event); + event = NULL; + break; + default: + break; + } + + /* now GstCollectPads can take care of the rest, e.g. EOS */ + if (event) + ret = mux->collect_event (pad, event); + + gst_object_unref (mux); + + return ret; +} + + +/** + * gst_matroska_mux_video_pad_setcaps: + * @pad: Pad which got the caps. + * @caps: New caps. + * + * Setcaps function for video sink pad. + * + * Returns: #TRUE on success. + */ +static gboolean +gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps) +{ + GstMatroskaTrackContext *context = NULL; + GstMatroskaTrackVideoContext *videocontext; + GstMatroskaMux *mux; + GstMatroskaPad *collect_pad; + GstStructure *structure; + const gchar *mimetype; + const GValue *value = NULL; + const GstBuffer *codec_buf = NULL; + gint width, height, pixel_width, pixel_height; + gint fps_d, fps_n; + gboolean interlaced = FALSE; + + mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad)); + + /* find context */ + collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad); + g_assert (collect_pad); + context = collect_pad->track; + g_assert (context); + g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO); + videocontext = (GstMatroskaTrackVideoContext *) context; + + /* gst -> matroska ID'ing */ + structure = gst_caps_get_structure (caps, 0); + + mimetype = gst_structure_get_name (structure); + + if (gst_structure_get_boolean (structure, "interlaced", &interlaced) + && interlaced) + context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED; + + if (!strcmp (mimetype, "video/x-theora")) { + /* we'll extract the details later from the theora identification header */ + goto skip_details; + } + + /* get general properties */ + /* spec says it is mandatory */ + if (!gst_structure_get_int (structure, "width", &width) || + !gst_structure_get_int (structure, "height", &height)) + goto refuse_caps; + + videocontext->pixel_width = width; + videocontext->pixel_height = height; + if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d) + && fps_n > 0) { + context->default_duration = + gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); + GST_LOG_OBJECT (pad, "default duration = %" GST_TIME_FORMAT, + GST_TIME_ARGS (context->default_duration)); + } else { + context->default_duration = 0; + } + if (gst_structure_get_fraction (structure, "pixel-aspect-ratio", + &pixel_width, &pixel_height)) { + if (pixel_width > pixel_height) { + videocontext->display_width = width * pixel_width / pixel_height; + videocontext->display_height = height; + } else if (pixel_width < pixel_height) { + videocontext->display_width = width; + videocontext->display_height = height * pixel_height / pixel_width; + } else { + videocontext->display_width = 0; + videocontext->display_height = 0; + } + } else { + videocontext->display_width = 0; + videocontext->display_height = 0; + } + +skip_details: + + videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE; + videocontext->fourcc = 0; + + /* TODO: - check if we handle all codecs by the spec, i.e. codec private + * data and other settings + * - add new formats + */ + + /* extract codec_data, may turn out needed */ + value = gst_structure_get_value (structure, "codec_data"); + if (value) + codec_buf = gst_value_get_buffer (value); + + /* find type */ + if (!strcmp (mimetype, "video/x-raw-yuv")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED); + gst_structure_get_fourcc (structure, "format", &videocontext->fourcc); + } else if (!strcmp (mimetype, "image/jpeg")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG); + } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */ + ||!strcmp (mimetype, "video/x-huffyuv") + || !strcmp (mimetype, "video/x-divx") + || !strcmp (mimetype, "video/x-dv") + || !strcmp (mimetype, "video/x-h263") + || !strcmp (mimetype, "video/x-msmpeg") + || !strcmp (mimetype, "video/x-wmv")) { + gst_riff_strf_vids *bih; + gint size = sizeof (gst_riff_strf_vids); + guint32 fourcc = 0; + + if (!strcmp (mimetype, "video/x-xvid")) + fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D'); + else if (!strcmp (mimetype, "video/x-huffyuv")) + fourcc = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U'); + else if (!strcmp (mimetype, "video/x-dv")) + fourcc = GST_MAKE_FOURCC ('D', 'V', 'S', 'D'); + else if (!strcmp (mimetype, "video/x-h263")) + fourcc = GST_MAKE_FOURCC ('H', '2', '6', '3'); + else if (!strcmp (mimetype, "video/x-divx")) { + gint divxversion; + + gst_structure_get_int (structure, "divxversion", &divxversion); + switch (divxversion) { + case 3: + fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', '3'); + break; + case 4: + fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', 'X'); + break; + case 5: + fourcc = GST_MAKE_FOURCC ('D', 'X', '5', '0'); + break; + } + } else if (!strcmp (mimetype, "video/x-msmpeg")) { + gint msmpegversion; + + gst_structure_get_int (structure, "msmpegversion", &msmpegversion); + switch (msmpegversion) { + case 41: + fourcc = GST_MAKE_FOURCC ('M', 'P', 'G', '4'); + break; + case 42: + fourcc = GST_MAKE_FOURCC ('M', 'P', '4', '2'); + break; + case 43: + goto msmpeg43; + break; + } + } else if (!strcmp (mimetype, "video/x-wmv")) { + gint wmvversion; + guint32 format; + if (gst_structure_get_fourcc (structure, "format", &format)) { + fourcc = format; + } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) { + if (wmvversion == 2) { + fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2'); + } else if (wmvversion == 1) { + fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '1'); + } else if (wmvversion == 3) { + fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3'); + } + } + } + + if (!fourcc) + goto refuse_caps; + + bih = g_new0 (gst_riff_strf_vids, 1); + GST_WRITE_UINT32_LE (&bih->size, size); + GST_WRITE_UINT32_LE (&bih->width, videocontext->pixel_width); + GST_WRITE_UINT32_LE (&bih->height, videocontext->pixel_height); + GST_WRITE_UINT32_LE (&bih->compression, fourcc); + GST_WRITE_UINT16_LE (&bih->planes, (guint16) 1); + GST_WRITE_UINT16_LE (&bih->bit_cnt, (guint16) 24); + GST_WRITE_UINT32_LE (&bih->image_size, videocontext->pixel_width * + videocontext->pixel_height * 3); + + /* process codec private/initialization data, if any */ + if (codec_buf) { + size += GST_BUFFER_SIZE (codec_buf); + bih = g_realloc (bih, size); + GST_WRITE_UINT32_LE (&bih->size, size); + memcpy ((guint8 *) bih + sizeof (gst_riff_strf_vids), + GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf)); + } + + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC); + context->codec_priv = (gpointer) bih; + context->codec_priv_size = size; + } else if (!strcmp (mimetype, "video/x-h264")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC); + + if (context->codec_priv != NULL) { + g_free (context->codec_priv); + context->codec_priv = NULL; + context->codec_priv_size = 0; + } + + /* Create avcC header */ + if (codec_buf != NULL) { + context->codec_priv_size = GST_BUFFER_SIZE (codec_buf); + context->codec_priv = g_malloc0 (context->codec_priv_size); + memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf), + context->codec_priv_size); + } + } else if (!strcmp (mimetype, "video/x-theora")) { + const GValue *streamheader; + + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA); + + if (context->codec_priv != NULL) { + g_free (context->codec_priv); + context->codec_priv = NULL; + context->codec_priv_size = 0; + } + + streamheader = gst_structure_get_value (structure, "streamheader"); + if (!theora_streamheader_to_codecdata (streamheader, context)) { + GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), + ("theora stream headers missing or malformed")); + goto refuse_caps; + } + } else if (!strcmp (mimetype, "video/x-dirac")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC); + } else if (!strcmp (mimetype, "video/x-vp8")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VP8); + } else if (!strcmp (mimetype, "video/mpeg")) { + gint mpegversion; + + gst_structure_get_int (structure, "mpegversion", &mpegversion); + switch (mpegversion) { + case 1: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1); + break; + case 2: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2); + break; + case 4: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP); + break; + default: + goto refuse_caps; + } + + /* global headers may be in codec data */ + if (codec_buf != NULL) { + context->codec_priv_size = GST_BUFFER_SIZE (codec_buf); + context->codec_priv = g_malloc0 (context->codec_priv_size); + memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf), + context->codec_priv_size); + } + } else if (!strcmp (mimetype, "video/x-msmpeg")) { + msmpeg43: + /* can only make it here if preceding case verified it was version 3 */ + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3); + } else if (!strcmp (mimetype, "video/x-pn-realvideo")) { + gint rmversion; + const GValue *mdpr_data; + + gst_structure_get_int (structure, "rmversion", &rmversion); + switch (rmversion) { + case 1: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1); + break; + case 2: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2); + break; + case 3: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3); + break; + case 4: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4); + break; + default: + goto refuse_caps; + } + + mdpr_data = gst_structure_get_value (structure, "mdpr_data"); + if (mdpr_data != NULL) { + guint8 *priv_data = NULL; + guint priv_data_size = 0; + + GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data); + + priv_data_size = GST_BUFFER_SIZE (codec_data_buf); + priv_data = g_malloc0 (priv_data_size); + + memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size); + + context->codec_priv = priv_data; + context->codec_priv_size = priv_data_size; + } + } + + return TRUE; + + /* ERRORS */ +refuse_caps: + { + GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT, + GST_PAD_NAME (pad), caps); + return FALSE; + } +} + +/* N > 0 to expect a particular number of headers, negative if the + number of headers is variable */ +static gboolean +xiphN_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context, GstBuffer ** p_buf0, int N) +{ + GstBuffer **buf = NULL; + GArray *bufarr; + guint8 *priv_data; + guint bufi, i, offset, priv_data_size; + + if (streamheader == NULL) + goto no_stream_headers; + + if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) + goto wrong_type; + + bufarr = g_value_peek_pointer (streamheader); + if (bufarr->len <= 0 || bufarr->len > 255) /* at least one header, and count stored in a byte */ + goto wrong_count; + if (N > 0 && bufarr->len != N) + goto wrong_count; + + context->xiph_headers_to_skip = bufarr->len; + + buf = (GstBuffer **) g_malloc0 (sizeof (GstBuffer *) * bufarr->len); + for (i = 0; i < bufarr->len; i++) { + GValue *bufval = &g_array_index (bufarr, GValue, i); + + if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) { + g_free (buf); + goto wrong_content_type; + } + + buf[i] = g_value_peek_pointer (bufval); + } + + priv_data_size = 1; + if (bufarr->len > 0) { + for (i = 0; i < bufarr->len - 1; i++) { + priv_data_size += GST_BUFFER_SIZE (buf[i]) / 0xff + 1; + } + } + + for (i = 0; i < bufarr->len; ++i) { + priv_data_size += GST_BUFFER_SIZE (buf[i]); + } + + priv_data = g_malloc0 (priv_data_size); + + priv_data[0] = bufarr->len - 1; + offset = 1; + + if (bufarr->len > 0) { + for (bufi = 0; bufi < bufarr->len - 1; bufi++) { + for (i = 0; i < GST_BUFFER_SIZE (buf[bufi]) / 0xff; ++i) { + priv_data[offset++] = 0xff; + } + priv_data[offset++] = GST_BUFFER_SIZE (buf[bufi]) % 0xff; + } + } + + for (i = 0; i < bufarr->len; ++i) { + memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]), + GST_BUFFER_SIZE (buf[i])); + offset += GST_BUFFER_SIZE (buf[i]); + } + + context->codec_priv = priv_data; + context->codec_priv_size = priv_data_size; + + if (p_buf0) + *p_buf0 = gst_buffer_ref (buf[0]); + + g_free (buf); + + return TRUE; + +/* ERRORS */ +no_stream_headers: + { + GST_WARNING ("required streamheaders missing in sink caps!"); + return FALSE; + } +wrong_type: + { + GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s", + G_VALUE_TYPE_NAME (streamheader)); + return FALSE; + } +wrong_count: + { + GST_WARNING ("got %u streamheaders, not %d as expected", bufarr->len, N); + return FALSE; + } +wrong_content_type: + { + GST_WARNING ("streamheaders array does not contain GstBuffers"); + return FALSE; + } +} + +static gboolean +vorbis_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context) +{ + GstBuffer *buf0 = NULL; + + if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3)) + return FALSE; + + if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) { + GST_WARNING ("First vorbis header too small, ignoring"); + } else { + if (memcmp (GST_BUFFER_DATA (buf0) + 1, "vorbis", 6) == 0) { + GstMatroskaTrackAudioContext *audiocontext; + guint8 *hdr; + + hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 4; + audiocontext = (GstMatroskaTrackAudioContext *) context; + audiocontext->channels = GST_READ_UINT8 (hdr); + audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1); + } + } + + if (buf0) + gst_buffer_unref (buf0); + + return TRUE; +} + +static gboolean +theora_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context) +{ + GstBuffer *buf0 = NULL; + + if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3)) + return FALSE; + + if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) { + GST_WARNING ("First theora header too small, ignoring"); + } else if (memcmp (GST_BUFFER_DATA (buf0), "\200theora\003\002", 9) != 0) { + GST_WARNING ("First header not a theora identification header, ignoring"); + } else { + GstMatroskaTrackVideoContext *videocontext; + guint fps_num, fps_denom, par_num, par_denom; + guint8 *hdr; + + hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 3 + 2 + 2; + + videocontext = (GstMatroskaTrackVideoContext *) context; + videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8; + videocontext->pixel_height = GST_READ_UINT32_BE (hdr + 3) >> 8; + hdr += 3 + 3 + 1 + 1; + fps_num = GST_READ_UINT32_BE (hdr); + fps_denom = GST_READ_UINT32_BE (hdr + 4); + context->default_duration = gst_util_uint64_scale_int (GST_SECOND, + fps_denom, fps_num); + hdr += 4 + 4; + par_num = GST_READ_UINT32_BE (hdr) >> 8; + par_denom = GST_READ_UINT32_BE (hdr + 3) >> 8; + if (par_num > 0 && par_num > 0) { + if (par_num > par_denom) { + videocontext->display_width = + videocontext->pixel_width * par_num / par_denom; + videocontext->display_height = videocontext->pixel_height; + } else if (par_num < par_denom) { + videocontext->display_width = videocontext->pixel_width; + videocontext->display_height = + videocontext->pixel_height * par_denom / par_num; + } else { + videocontext->display_width = 0; + videocontext->display_height = 0; + } + } else { + videocontext->display_width = 0; + videocontext->display_height = 0; + } + hdr += 3 + 3; + } + + if (buf0) + gst_buffer_unref (buf0); + + return TRUE; +} + +static gboolean +kate_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context) +{ + GstBuffer *buf0 = NULL; + + if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, -1)) + return FALSE; + + if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 64) { /* Kate ID header is 64 bytes */ + GST_WARNING ("First kate header too small, ignoring"); + } else if (memcmp (GST_BUFFER_DATA (buf0), "\200kate\0\0\0", 8) != 0) { + GST_WARNING ("First header not a kate identification header, ignoring"); + } + + if (buf0) + gst_buffer_unref (buf0); + + return TRUE; +} + +static gboolean +flac_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context) +{ + GArray *bufarr; + gint i; + GValue *bufval; + GstBuffer *buffer; + + if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) { + GST_WARNING ("No or invalid streamheader field in the caps"); + return FALSE; + } + + bufarr = g_value_peek_pointer (streamheader); + if (bufarr->len < 2) { + GST_WARNING ("Too few headers in streamheader field"); + return FALSE; + } + + context->xiph_headers_to_skip = bufarr->len + 1; + + bufval = &g_array_index (bufarr, GValue, 0); + if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) { + GST_WARNING ("streamheaders array does not contain GstBuffers"); + return FALSE; + } + + buffer = g_value_peek_pointer (bufval); + + /* Need at least OggFLAC mapping header, fLaC marker and STREAMINFO block */ + if (GST_BUFFER_SIZE (buffer) < 9 + 4 + 4 + 34 + || memcmp (GST_BUFFER_DATA (buffer) + 1, "FLAC", 4) != 0 + || memcmp (GST_BUFFER_DATA (buffer) + 9, "fLaC", 4) != 0) { + GST_WARNING ("Invalid streamheader for FLAC"); + return FALSE; + } + + context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer) - 9); + context->codec_priv_size = GST_BUFFER_SIZE (buffer) - 9; + memcpy (context->codec_priv, GST_BUFFER_DATA (buffer) + 9, + GST_BUFFER_SIZE (buffer) - 9); + + for (i = 1; i < bufarr->len; i++) { + bufval = &g_array_index (bufarr, GValue, i); + + if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) { + g_free (context->codec_priv); + context->codec_priv = NULL; + context->codec_priv_size = 0; + GST_WARNING ("streamheaders array does not contain GstBuffers"); + return FALSE; + } + + buffer = g_value_peek_pointer (bufval); + + context->codec_priv = + g_realloc (context->codec_priv, + context->codec_priv_size + GST_BUFFER_SIZE (buffer)); + memcpy ((guint8 *) context->codec_priv + context->codec_priv_size, + GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer)); + context->codec_priv_size = + context->codec_priv_size + GST_BUFFER_SIZE (buffer); + } + + return TRUE; +} + +static gboolean +speex_streamheader_to_codecdata (const GValue * streamheader, + GstMatroskaTrackContext * context) +{ + GArray *bufarr; + GValue *bufval; + GstBuffer *buffer; + + if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) { + GST_WARNING ("No or invalid streamheader field in the caps"); + return FALSE; + } + + bufarr = g_value_peek_pointer (streamheader); + if (bufarr->len != 2) { + GST_WARNING ("Too few headers in streamheader field"); + return FALSE; + } + + context->xiph_headers_to_skip = bufarr->len + 1; + + bufval = &g_array_index (bufarr, GValue, 0); + if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) { + GST_WARNING ("streamheaders array does not contain GstBuffers"); + return FALSE; + } + + buffer = g_value_peek_pointer (bufval); + + if (GST_BUFFER_SIZE (buffer) < 80 + || memcmp (GST_BUFFER_DATA (buffer), "Speex ", 8) != 0) { + GST_WARNING ("Invalid streamheader for Speex"); + return FALSE; + } + + context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer)); + context->codec_priv_size = GST_BUFFER_SIZE (buffer); + memcpy (context->codec_priv, GST_BUFFER_DATA (buffer), + GST_BUFFER_SIZE (buffer)); + + bufval = &g_array_index (bufarr, GValue, 1); + + if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) { + g_free (context->codec_priv); + context->codec_priv = NULL; + context->codec_priv_size = 0; + GST_WARNING ("streamheaders array does not contain GstBuffers"); + return FALSE; + } + + buffer = g_value_peek_pointer (bufval); + + context->codec_priv = + g_realloc (context->codec_priv, + context->codec_priv_size + GST_BUFFER_SIZE (buffer)); + memcpy ((guint8 *) context->codec_priv + context->codec_priv_size, + GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer)); + context->codec_priv_size = + context->codec_priv_size + GST_BUFFER_SIZE (buffer); + + return TRUE; +} + +static const gchar * +aac_codec_data_to_codec_id (const GstBuffer * buf) +{ + const gchar *result; + gint profile; + + /* default to MAIN */ + profile = 1; + + if (GST_BUFFER_SIZE (buf) >= 2) { + profile = GST_READ_UINT8 (GST_BUFFER_DATA (buf)); + profile >>= 3; + } + + switch (profile) { + case 1: + result = "MAIN"; + break; + case 2: + result = "LC"; + break; + case 3: + result = "SSR"; + break; + case 4: + result = "LTP"; + break; + default: + GST_WARNING ("unknown AAC profile, defaulting to MAIN"); + result = "MAIN"; + break; + } + + return result; +} + +/** + * gst_matroska_mux_audio_pad_setcaps: + * @pad: Pad which got the caps. + * @caps: New caps. + * + * Setcaps function for audio sink pad. + * + * Returns: #TRUE on success. + */ +static gboolean +gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) +{ + GstMatroskaTrackContext *context = NULL; + GstMatroskaTrackAudioContext *audiocontext; + GstMatroskaMux *mux; + GstMatroskaPad *collect_pad; + const gchar *mimetype; + gint samplerate = 0, channels = 0; + GstStructure *structure; + const GValue *codec_data = NULL; + const GstBuffer *buf = NULL; + const gchar *stream_format = NULL; + + mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad)); + + /* find context */ + collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad); + g_assert (collect_pad); + context = collect_pad->track; + g_assert (context); + g_assert (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO); + audiocontext = (GstMatroskaTrackAudioContext *) context; + + structure = gst_caps_get_structure (caps, 0); + mimetype = gst_structure_get_name (structure); + + /* general setup */ + gst_structure_get_int (structure, "rate", &samplerate); + gst_structure_get_int (structure, "channels", &channels); + + audiocontext->samplerate = samplerate; + audiocontext->channels = channels; + audiocontext->bitdepth = 0; + context->default_duration = 0; + + codec_data = gst_structure_get_value (structure, "codec_data"); + if (codec_data) + buf = gst_value_get_buffer (codec_data); + + /* TODO: - check if we handle all codecs by the spec, i.e. codec private + * data and other settings + * - add new formats + */ + + if (!strcmp (mimetype, "audio/mpeg")) { + gint mpegversion = 0; + + gst_structure_get_int (structure, "mpegversion", &mpegversion); + switch (mpegversion) { + case 1:{ + gint layer; + gint version = 1; + gint spf; + + gst_structure_get_int (structure, "layer", &layer); + + if (!gst_structure_get_int (structure, "mpegaudioversion", &version)) { + GST_WARNING_OBJECT (mux, + "Unable to determine MPEG audio version, assuming 1"); + version = 1; + } + + if (layer == 1) + spf = 384; + else if (layer == 2) + spf = 1152; + else if (version == 2) + spf = 576; + else + spf = 1152; + + context->default_duration = + gst_util_uint64_scale (GST_SECOND, spf, audiocontext->samplerate); + + switch (layer) { + case 1: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1); + break; + case 2: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2); + break; + case 3: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3); + break; + default: + goto refuse_caps; + } + break; + } + case 2: + case 4: + stream_format = gst_structure_get_string (structure, "stream-format"); + /* check this is raw aac */ + if (stream_format) { + if (strcmp (stream_format, "raw") != 0) { + GST_WARNING_OBJECT (mux, "AAC stream-format must be 'raw', not %s", + stream_format); + } + } else { + GST_WARNING_OBJECT (mux, "AAC stream-format not specified, " + "assuming 'raw'"); + } + + if (buf) { + if (mpegversion == 2) + context->codec_id = + g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s", + aac_codec_data_to_codec_id (buf)); + else if (mpegversion == 4) + context->codec_id = + g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s", + aac_codec_data_to_codec_id (buf)); + else + g_assert_not_reached (); + } else { + GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized"); + goto refuse_caps; + } + break; + default: + goto refuse_caps; + } + } else if (!strcmp (mimetype, "audio/x-raw-int")) { + gint width, depth; + gint endianness = G_LITTLE_ENDIAN; + gboolean signedness = TRUE; + + if (!gst_structure_get_int (structure, "width", &width) || + !gst_structure_get_int (structure, "depth", &depth) || + !gst_structure_get_boolean (structure, "signed", &signedness)) { + GST_DEBUG_OBJECT (mux, "broken caps, width/depth/signed field missing"); + goto refuse_caps; + } + + if (depth > 8 && + !gst_structure_get_int (structure, "endianness", &endianness)) { + GST_DEBUG_OBJECT (mux, "broken caps, no endianness specified"); + goto refuse_caps; + } + + if (width != depth) { + GST_DEBUG_OBJECT (mux, "width must be same as depth!"); + goto refuse_caps; + } + + /* FIXME: where is this spec'ed out? (tpm) */ + if ((width == 8 && signedness) || (width >= 16 && !signedness)) { + GST_DEBUG_OBJECT (mux, "8-bit PCM must be unsigned, 16-bit PCM signed"); + goto refuse_caps; + } + + audiocontext->bitdepth = depth; + if (endianness == G_BIG_ENDIAN) + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE); + else + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE); + + } else if (!strcmp (mimetype, "audio/x-raw-float")) { + gint width; + + if (!gst_structure_get_int (structure, "width", &width)) { + GST_DEBUG_OBJECT (mux, "broken caps, width field missing"); + goto refuse_caps; + } + + audiocontext->bitdepth = width; + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT); + + } else if (!strcmp (mimetype, "audio/x-vorbis")) { + const GValue *streamheader; + + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_VORBIS); + + if (context->codec_priv != NULL) { + g_free (context->codec_priv); + context->codec_priv = NULL; + context->codec_priv_size = 0; + } + + streamheader = gst_structure_get_value (structure, "streamheader"); + if (!vorbis_streamheader_to_codecdata (streamheader, context)) { + GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), + ("vorbis stream headers missing or malformed")); + goto refuse_caps; + } + } else if (!strcmp (mimetype, "audio/x-flac")) { + const GValue *streamheader; + + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_FLAC); + if (context->codec_priv != NULL) { + g_free (context->codec_priv); + context->codec_priv = NULL; + context->codec_priv_size = 0; + } + + streamheader = gst_structure_get_value (structure, "streamheader"); + if (!flac_streamheader_to_codecdata (streamheader, context)) { + GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), + ("flac stream headers missing or malformed")); + goto refuse_caps; + } + } else if (!strcmp (mimetype, "audio/x-speex")) { + const GValue *streamheader; + + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_SPEEX); + if (context->codec_priv != NULL) { + g_free (context->codec_priv); + context->codec_priv = NULL; + context->codec_priv_size = 0; + } + + streamheader = gst_structure_get_value (structure, "streamheader"); + if (!speex_streamheader_to_codecdata (streamheader, context)) { + GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), + ("speex stream headers missing or malformed")); + goto refuse_caps; + } + } else if (!strcmp (mimetype, "audio/x-ac3")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3); + } else if (!strcmp (mimetype, "audio/x-eac3")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_EAC3); + } else if (!strcmp (mimetype, "audio/x-dts")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_DTS); + } else if (!strcmp (mimetype, "audio/x-tta")) { + gint width; + + /* TTA frame duration */ + context->default_duration = 1.04489795918367346939 * GST_SECOND; + + gst_structure_get_int (structure, "width", &width); + audiocontext->bitdepth = width; + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA); + + } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) { + gint raversion; + const GValue *mdpr_data; + + gst_structure_get_int (structure, "raversion", &raversion); + switch (raversion) { + case 1: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4); + break; + case 2: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8); + break; + case 8: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK); + break; + default: + goto refuse_caps; + } + + mdpr_data = gst_structure_get_value (structure, "mdpr_data"); + if (mdpr_data != NULL) { + guint8 *priv_data = NULL; + guint priv_data_size = 0; + + GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data); + + priv_data_size = GST_BUFFER_SIZE (codec_data_buf); + priv_data = g_malloc0 (priv_data_size); + + memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size); + + context->codec_priv = priv_data; + context->codec_priv_size = priv_data_size; + } + + } else if (!strcmp (mimetype, "audio/x-wma") + || !strcmp (mimetype, "audio/x-alaw") + || !strcmp (mimetype, "audio/x-mulaw")) { + guint8 *codec_priv; + guint codec_priv_size; + guint16 format = 0; + gint block_align; + gint bitrate; + + if (samplerate == 0 || channels == 0) { + GST_WARNING_OBJECT (mux, "Missing channels/samplerate on caps"); + goto refuse_caps; + } + + if (!strcmp (mimetype, "audio/x-wma")) { + gint wmaversion; + gint depth; + + if (!gst_structure_get_int (structure, "wmaversion", &wmaversion) + || !gst_structure_get_int (structure, "block_align", &block_align) + || !gst_structure_get_int (structure, "bitrate", &bitrate)) { + GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate" + " on WMA caps"); + goto refuse_caps; + } + + switch (wmaversion) { + case 1: + format = GST_RIFF_WAVE_FORMAT_WMAV1; + break; + case 2: + format = GST_RIFF_WAVE_FORMAT_WMAV2; + break; + case 3: + format = GST_RIFF_WAVE_FORMAT_WMAV3; + break; + default: + GST_WARNING_OBJECT (mux, "Unexpected WMA version: %d", wmaversion); + goto refuse_caps; + } + + if (gst_structure_get_int (structure, "depth", &depth)) + audiocontext->bitdepth = depth; + } else if (!strcmp (mimetype, "audio/x-alaw") + || !strcmp (mimetype, "audio/x-mulaw")) { + audiocontext->bitdepth = 8; + if (!strcmp (mimetype, "audio/x-alaw")) + format = GST_RIFF_WAVE_FORMAT_ALAW; + else + format = GST_RIFF_WAVE_FORMAT_MULAW; + + block_align = channels; + bitrate = block_align * samplerate; + } + g_assert (format != 0); + + codec_priv_size = WAVEFORMATEX_SIZE; + if (buf) + codec_priv_size += GST_BUFFER_SIZE (buf); + + /* serialize waveformatex structure */ + codec_priv = g_malloc0 (codec_priv_size); + GST_WRITE_UINT16_LE (codec_priv, format); + GST_WRITE_UINT16_LE (codec_priv + 2, channels); + GST_WRITE_UINT32_LE (codec_priv + 4, samplerate); + GST_WRITE_UINT32_LE (codec_priv + 8, bitrate / 8); + GST_WRITE_UINT16_LE (codec_priv + 12, block_align); + GST_WRITE_UINT16_LE (codec_priv + 14, 0); + if (buf) + GST_WRITE_UINT16_LE (codec_priv + 16, GST_BUFFER_SIZE (buf)); + else + GST_WRITE_UINT16_LE (codec_priv + 16, 0); + + /* process codec private/initialization data, if any */ + if (buf) { + memcpy ((guint8 *) codec_priv + WAVEFORMATEX_SIZE, + GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + } + + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM); + context->codec_priv = (gpointer) codec_priv; + context->codec_priv_size = codec_priv_size; + } + + return TRUE; + + /* ERRORS */ +refuse_caps: + { + GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT, + GST_PAD_NAME (pad), caps); + return FALSE; + } +} + + +/** + * gst_matroska_mux_subtitle_pad_setcaps: + * @pad: Pad which got the caps. + * @caps: New caps. + * + * Setcaps function for subtitle sink pad. + * + * Returns: #TRUE on success. + */ +static gboolean +gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps) +{ + /* FIXME: + * Consider this as boilerplate code for now. There is + * no single subtitle creation element in GStreamer, + * neither do I know how subtitling works at all. */ + + /* There is now (at least) one such alement (kateenc), and I'm going + to handle it here and claim it works when it can be piped back + through GStreamer and VLC */ + + GstMatroskaTrackContext *context = NULL; + GstMatroskaTrackSubtitleContext *scontext; + GstMatroskaMux *mux; + GstMatroskaPad *collect_pad; + const gchar *mimetype; + GstStructure *structure; + + mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad)); + + /* find context */ + collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad); + g_assert (collect_pad); + context = collect_pad->track; + g_assert (context); + g_assert (context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE); + scontext = (GstMatroskaTrackSubtitleContext *) context; + + structure = gst_caps_get_structure (caps, 0); + mimetype = gst_structure_get_name (structure); + + /* general setup */ + scontext->check_utf8 = 1; + scontext->invalid_utf8 = 0; + context->default_duration = 0; + + /* TODO: - other format than Kate */ + + if (!strcmp (mimetype, "subtitle/x-kate")) { + const GValue *streamheader; + + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_SUBTITLE_KATE); + + if (context->codec_priv != NULL) { + g_free (context->codec_priv); + context->codec_priv = NULL; + context->codec_priv_size = 0; + } + + streamheader = gst_structure_get_value (structure, "streamheader"); + if (!kate_streamheader_to_codecdata (streamheader, context)) { + GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), + ("kate stream headers missing or malformed")); + return FALSE; + } + return TRUE; + } + + return FALSE; +} + + +/** + * gst_matroska_mux_request_new_pad: + * @element: #GstMatroskaMux. + * @templ: #GstPadTemplate. + * @pad_name: New pad name. + * + * Request pad function for sink templates. + * + * Returns: New #GstPad. + */ +static GstPad * +gst_matroska_mux_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * req_name) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); + GstMatroskaMux *mux = GST_MATROSKA_MUX (element); + GstMatroskaPad *collect_pad; + GstPad *newpad = NULL; + gchar *name = NULL; + const gchar *pad_name = NULL; + GstPadSetCapsFunction setcapsfunc = NULL; + GstMatroskaTrackContext *context = NULL; + gint pad_id; + + if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) { + /* don't mix named and unnamed pads, if the pad already exists we fail when + * trying to add it */ + if (req_name != NULL && sscanf (req_name, "audio_%d", &pad_id) == 1) { + pad_name = req_name; + } else { + name = g_strdup_printf ("audio_%d", mux->num_a_streams++); + pad_name = name; + } + setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_audio_pad_setcaps); + context = (GstMatroskaTrackContext *) + g_new0 (GstMatroskaTrackAudioContext, 1); + context->type = GST_MATROSKA_TRACK_TYPE_AUDIO; + context->name = g_strdup ("Audio"); + } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) { + /* don't mix named and unnamed pads, if the pad already exists we fail when + * trying to add it */ + if (req_name != NULL && sscanf (req_name, "video_%d", &pad_id) == 1) { + pad_name = req_name; + } else { + name = g_strdup_printf ("video_%d", mux->num_v_streams++); + pad_name = name; + } + setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_video_pad_setcaps); + context = (GstMatroskaTrackContext *) + g_new0 (GstMatroskaTrackVideoContext, 1); + context->type = GST_MATROSKA_TRACK_TYPE_VIDEO; + context->name = g_strdup ("Video"); + } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) { + /* don't mix named and unnamed pads, if the pad already exists we fail when + * trying to add it */ + if (req_name != NULL && sscanf (req_name, "subtitle_%d", &pad_id) == 1) { + pad_name = req_name; + } else { + name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++); + pad_name = name; + } + setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps); + context = (GstMatroskaTrackContext *) + g_new0 (GstMatroskaTrackSubtitleContext, 1); + context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE; + context->name = g_strdup ("Subtitle"); + } else { + GST_WARNING_OBJECT (mux, "This is not our template!"); + return NULL; + } + + newpad = gst_pad_new_from_template (templ, pad_name); + g_free (name); + collect_pad = (GstMatroskaPad *) + gst_collect_pads_add_pad_full (mux->collect, newpad, + sizeof (GstMatroskaPad), + (GstCollectDataDestroyNotify) gst_matroska_pad_free); + + collect_pad->track = context; + gst_matroska_pad_reset (collect_pad, FALSE); + + /* FIXME: hacked way to override/extend the event function of + * GstCollectPads; because it sets its own event function giving the + * element no access to events. + * TODO GstCollectPads should really give its 'users' a clean chance to + * properly handle events that are not meant for collectpads itself. + * Perhaps a callback or so, though rejected (?) in #340060. + * This would allow (clean) transcoding of info from demuxer/streams + * to another muxer */ + mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad); + gst_pad_set_event_function (newpad, + GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event)); + + gst_pad_set_setcaps_function (newpad, setcapsfunc); + gst_pad_set_active (newpad, TRUE); + if (!gst_element_add_pad (element, newpad)) + goto pad_add_failed; + + mux->num_streams++; + + GST_DEBUG_OBJECT (newpad, "Added new request pad"); + + return newpad; + + /* ERROR cases */ +pad_add_failed: + { + GST_WARNING_OBJECT (mux, "Adding the new pad '%s' failed", pad_name); + gst_object_unref (newpad); + return NULL; + } +} + +/** + * gst_matroska_mux_release_pad: + * @element: #GstMatroskaMux. + * @pad: Pad to release. + * + * Release a previously requested pad. +*/ +static void +gst_matroska_mux_release_pad (GstElement * element, GstPad * pad) +{ + GstMatroskaMux *mux; + GSList *walk; + + mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad)); + + for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) { + GstCollectData *cdata = (GstCollectData *) walk->data; + GstMatroskaPad *collect_pad = (GstMatroskaPad *) cdata; + + if (cdata->pad == pad) { + GstClockTime min_dur; /* observed minimum duration */ + + if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) && + GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) { + min_dur = GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts); + if (collect_pad->duration < min_dur) + collect_pad->duration = min_dur; + } + + if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) && + mux->duration < collect_pad->duration) + mux->duration = collect_pad->duration; + + break; + } + } + + gst_collect_pads_remove_pad (mux->collect, pad); + if (gst_element_remove_pad (element, pad)) + mux->num_streams--; +} + + +/** + * gst_matroska_mux_track_header: + * @mux: #GstMatroskaMux + * @context: Tack context. + * + * Write a track header. + */ +static void +gst_matroska_mux_track_header (GstMatroskaMux * mux, + GstMatroskaTrackContext * context) +{ + GstEbmlWrite *ebml = mux->ebml_write; + guint64 master; + + /* TODO: check if everything necessary is written and check default values */ + + /* track type goes before the type-specific stuff */ + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKNUMBER, context->num); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKTYPE, context->type); + + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKUID, + gst_matroska_mux_create_uid ()); + if (context->default_duration) { + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKDEFAULTDURATION, + context->default_duration); + } + if (context->language) { + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKLANGUAGE, + context->language); + } + + /* type-specific stuff */ + switch (context->type) { + case GST_MATROSKA_TRACK_TYPE_VIDEO:{ + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKVIDEO); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELWIDTH, + videocontext->pixel_width); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELHEIGHT, + videocontext->pixel_height); + if (videocontext->display_width && videocontext->display_height) { + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYWIDTH, + videocontext->display_width); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYHEIGHT, + videocontext->display_height); + } + if (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED) + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOFLAGINTERLACED, 1); + if (videocontext->fourcc) { + guint32 fcc_le = GUINT32_TO_LE (videocontext->fourcc); + + gst_ebml_write_binary (ebml, GST_MATROSKA_ID_VIDEOCOLOURSPACE, + (gpointer) & fcc_le, 4); + } + gst_ebml_write_master_finish (ebml, master); + + break; + } + + case GST_MATROSKA_TRACK_TYPE_AUDIO:{ + GstMatroskaTrackAudioContext *audiocontext = + (GstMatroskaTrackAudioContext *) context; + + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKAUDIO); + if (audiocontext->samplerate != 8000) + gst_ebml_write_float (ebml, GST_MATROSKA_ID_AUDIOSAMPLINGFREQ, + audiocontext->samplerate); + if (audiocontext->channels != 1) + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOCHANNELS, + audiocontext->channels); + if (audiocontext->bitdepth) { + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOBITDEPTH, + audiocontext->bitdepth); + } + gst_ebml_write_master_finish (ebml, master); + + break; + } + + default: + /* doesn't need type-specific data */ + break; + } + + gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, context->codec_id); + if (context->codec_priv) + gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE, + context->codec_priv, context->codec_priv_size); + /* FIXME: until we have a nice way of getting the codecname + * out of the caps, I'm not going to enable this. Too much + * (useless, double, boring) work... */ + /* TODO: Use value from tags if any */ + /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME, + context->codec_name); */ + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name); +} + + +/** + * gst_matroska_mux_start: + * @mux: #GstMatroskaMux + * + * Start a new matroska file (write headers etc...) + */ +static void +gst_matroska_mux_start (GstMatroskaMux * mux) +{ + GstEbmlWrite *ebml = mux->ebml_write; + const gchar *doctype; + guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO, + GST_MATROSKA_ID_TRACKS, + GST_MATROSKA_ID_CUES, + GST_MATROSKA_ID_TAGS, + 0 + }; + guint64 master, child; + GSList *collected; + int i; + guint tracknum = 1; + GstClockTime duration = 0; + guint32 segment_uid[4]; + GTimeVal time = { 0, 0 }; + + if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) { + ebml->caps = gst_caps_new_simple ("video/webm", NULL); + } else { + ebml->caps = gst_caps_new_simple ("video/x-matroska", NULL); + } + /* we start with a EBML header */ + doctype = mux->doctype; + GST_INFO_OBJECT (ebml, "DocType: %s, Version: %d", + doctype, mux->doctype_version); + gst_ebml_write_header (ebml, doctype, mux->doctype_version); + + /* the rest of the header is cached */ + gst_ebml_write_set_cache (ebml, 0x1000); + + /* start a segment */ + mux->segment_pos = + gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT); + mux->segment_master = ebml->pos; + + if (!mux->streamable) { + /* seekhead (table of contents) - we set the positions later */ + mux->seekhead_pos = ebml->pos; + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD); + for (i = 0; seekhead_id[i] != 0; i++) { + child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1); + gst_ebml_write_master_finish (ebml, child); + } + gst_ebml_write_master_finish (ebml, master); + } + + /* segment info */ + mux->info_pos = ebml->pos; + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENTINFO); + for (i = 0; i < 4; i++) { + segment_uid[i] = g_random_int (); + } + gst_ebml_write_binary (ebml, GST_MATROSKA_ID_SEGMENTUID, + (guint8 *) segment_uid, 16); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale); + mux->duration_pos = ebml->pos; + /* get duration */ + if (!mux->streamable) { + for (collected = mux->collect->data; collected; + collected = g_slist_next (collected)) { + GstMatroskaPad *collect_pad; + GstFormat format = GST_FORMAT_TIME; + GstPad *thepad; + gint64 trackduration; + + collect_pad = (GstMatroskaPad *) collected->data; + thepad = collect_pad->collect.pad; + + /* Query the total length of the track. */ + GST_DEBUG_OBJECT (thepad, "querying peer duration"); + if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) { + GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (trackduration)); + if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) { + duration = (GstClockTime) trackduration; + } + } + } + gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION, + gst_guint64_to_gdouble (duration) / + gst_guint64_to_gdouble (mux->time_scale)); + } + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP, + "GStreamer plugin version " PACKAGE_VERSION); + if (mux->writing_app && mux->writing_app[0]) { + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_WRITINGAPP, mux->writing_app); + } + g_get_current_time (&time); + gst_ebml_write_date (ebml, GST_MATROSKA_ID_DATEUTC, time.tv_sec); + gst_ebml_write_master_finish (ebml, master); + + /* tracks */ + mux->tracks_pos = ebml->pos; + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKS); + + for (collected = mux->collect->data; collected; + collected = g_slist_next (collected)) { + GstMatroskaPad *collect_pad; + GstPad *thepad; + + collect_pad = (GstMatroskaPad *) collected->data; + thepad = collect_pad->collect.pad; + + if (gst_pad_is_linked (thepad) && gst_pad_is_active (thepad) && + collect_pad->track->codec_id != 0) { + collect_pad->track->num = tracknum++; + child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY); + gst_matroska_mux_track_header (mux, collect_pad->track); + gst_ebml_write_master_finish (ebml, child); + } + } + gst_ebml_write_master_finish (ebml, master); + + /* lastly, flush the cache */ + gst_ebml_write_flush_cache (ebml, FALSE, 0); +} + +static void +gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, + gpointer data) +{ + /* TODO: more sensible tag mappings */ + static const struct + { + const gchar *matroska_tagname; + const gchar *gstreamer_tagname; + } + tag_conv[] = { + { + GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, { + GST_MATROSKA_TAG_ID_ARTIST, GST_TAG_ARTIST}, { + GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, { + GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, { + GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, { + GST_MATROSKA_TAG_ID_BPS, GST_TAG_BITRATE}, { + GST_MATROSKA_TAG_ID_ENCODER, GST_TAG_ENCODER}, { + GST_MATROSKA_TAG_ID_DATE, GST_TAG_DATE}, { + GST_MATROSKA_TAG_ID_ISRC, GST_TAG_ISRC}, { + GST_MATROSKA_TAG_ID_COPYRIGHT, GST_TAG_COPYRIGHT}, { + GST_MATROSKA_TAG_ID_BPM, GST_TAG_BEATS_PER_MINUTE}, { + GST_MATROSKA_TAG_ID_TERMS_OF_USE, GST_TAG_LICENSE}, { + GST_MATROSKA_TAG_ID_COMPOSER, GST_TAG_COMPOSER}, { + GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, { + GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE} + }; + GstEbmlWrite *ebml = (GstEbmlWrite *) data; + guint i; + guint64 simpletag_master; + + for (i = 0; i < G_N_ELEMENTS (tag_conv); i++) { + const gchar *tagname_gst = tag_conv[i].gstreamer_tagname; + const gchar *tagname_mkv = tag_conv[i].matroska_tagname; + + if (strcmp (tagname_gst, tag) == 0) { + GValue src = { 0, }; + gchar *dest; + + if (!gst_tag_list_copy_value (&src, list, tag)) + break; + if ((dest = gst_value_serialize (&src))) { + + simpletag_master = gst_ebml_write_master_start (ebml, + GST_MATROSKA_ID_SIMPLETAG); + gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_TAGNAME, tagname_mkv); + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TAGSTRING, dest); + gst_ebml_write_master_finish (ebml, simpletag_master); + g_free (dest); + } else { + GST_WARNING ("Can't transform tag '%s' to string", tagname_mkv); + } + g_value_unset (&src); + break; + } + } +} + + +/** + * gst_matroska_mux_finish: + * @mux: #GstMatroskaMux + * + * Finish a new matroska file (write index etc...) + */ +static void +gst_matroska_mux_finish (GstMatroskaMux * mux) +{ + GstEbmlWrite *ebml = mux->ebml_write; + guint64 pos; + guint64 duration = 0; + GSList *collected; + const GstTagList *tags; + + /* finish last cluster */ + if (mux->cluster) { + gst_ebml_write_master_finish (ebml, mux->cluster); + } + + /* cues */ + if (mux->index != NULL) { + guint n; + guint64 master, pointentry_master, trackpos_master; + + mux->cues_pos = ebml->pos; + gst_ebml_write_set_cache (ebml, 12 + 41 * mux->num_indexes); + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CUES); + + for (n = 0; n < mux->num_indexes; n++) { + GstMatroskaIndex *idx = &mux->index[n]; + + pointentry_master = gst_ebml_write_master_start (ebml, + GST_MATROSKA_ID_POINTENTRY); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETIME, + idx->time / mux->time_scale); + trackpos_master = gst_ebml_write_master_start (ebml, + GST_MATROSKA_ID_CUETRACKPOSITIONS); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETRACK, idx->track); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUECLUSTERPOSITION, + idx->pos - mux->segment_master); + gst_ebml_write_master_finish (ebml, trackpos_master); + gst_ebml_write_master_finish (ebml, pointentry_master); + } + + gst_ebml_write_master_finish (ebml, master); + gst_ebml_write_flush_cache (ebml, FALSE, GST_CLOCK_TIME_NONE); + } + + /* tags */ + tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux)); + + if (tags != NULL && !gst_tag_list_is_empty (tags)) { + guint64 master_tags, master_tag; + + GST_DEBUG ("Writing tags"); + + /* TODO: maybe limit via the TARGETS id by looking at the source pad */ + mux->tags_pos = ebml->pos; + master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS); + master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG); + gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml); + gst_ebml_write_master_finish (ebml, master_tag); + gst_ebml_write_master_finish (ebml, master_tags); + } + + /* update seekhead. We know that: + * - a seekhead contains 4 entries. + * - order of entries is as above. + * - a seekhead has a 4-byte header + 8-byte length + * - each entry is 2-byte master, 2-byte ID pointer, + * 2-byte length pointer, all 8/1-byte length, 4- + * byte ID and 8-byte length pointer, where the + * length pointer starts at 20. + * - all entries are local to the segment (so pos - segment_master). + * - so each entry is at 12 + 20 + num * 28. */ + gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32, + mux->info_pos - mux->segment_master); + gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60, + mux->tracks_pos - mux->segment_master); + if (mux->index != NULL) { + gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88, + mux->cues_pos - mux->segment_master); + } else { + /* void'ify */ + guint64 my_pos = ebml->pos; + + gst_ebml_write_seek (ebml, mux->seekhead_pos + 68); + gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26); + gst_ebml_write_seek (ebml, my_pos); + } + if (tags != NULL) { + gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116, + mux->tags_pos - mux->segment_master); + } else { + /* void'ify */ + guint64 my_pos = ebml->pos; + + gst_ebml_write_seek (ebml, mux->seekhead_pos + 96); + gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26); + gst_ebml_write_seek (ebml, my_pos); + } + + /* update duration */ + /* first get the overall duration */ + /* a released track may have left a duration in here */ + duration = mux->duration; + for (collected = mux->collect->data; collected; + collected = g_slist_next (collected)) { + GstMatroskaPad *collect_pad; + GstClockTime min_duration; /* observed minimum duration */ + + collect_pad = (GstMatroskaPad *) collected->data; + + GST_DEBUG_OBJECT (mux, + "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT + " end ts %" GST_TIME_FORMAT, collect_pad, + GST_TIME_ARGS (collect_pad->start_ts), + GST_TIME_ARGS (collect_pad->end_ts)); + + if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) && + GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) { + min_duration = + GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts); + if (collect_pad->duration < min_duration) + collect_pad->duration = min_duration; + GST_DEBUG_OBJECT (collect_pad, + "final track duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (collect_pad->duration)); + } + + if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) && + duration < collect_pad->duration) + duration = collect_pad->duration; + } + if (duration != 0) { + GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); + pos = mux->ebml_write->pos; + gst_ebml_write_seek (ebml, mux->duration_pos); + gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION, + gst_guint64_to_gdouble (duration) / + gst_guint64_to_gdouble (mux->time_scale)); + gst_ebml_write_seek (ebml, pos); + } else { + /* void'ify */ + guint64 my_pos = ebml->pos; + + gst_ebml_write_seek (ebml, mux->duration_pos); + gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 8); + gst_ebml_write_seek (ebml, my_pos); + } + GST_DEBUG_OBJECT (mux, "finishing segment"); + /* finish segment - this also writes element length */ + gst_ebml_write_master_finish (ebml, mux->segment_pos); +} + + +/** + * gst_matroska_mux_best_pad: + * @mux: #GstMatroskaMux + * @popped: True if at least one buffer was popped from #GstCollectPads + * + * Find a pad with the oldest data + * (data from this pad should be written first). + * + * Returns: Selected pad. + */ +static GstMatroskaPad * +gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped) +{ + GSList *collected; + GstMatroskaPad *best = NULL; + + *popped = FALSE; + for (collected = mux->collect->data; collected; + collected = g_slist_next (collected)) { + GstMatroskaPad *collect_pad; + + collect_pad = (GstMatroskaPad *) collected->data; + /* fetch a new buffer if needed */ + if (collect_pad->buffer == NULL) { + collect_pad->buffer = gst_collect_pads_pop (mux->collect, + (GstCollectData *) collect_pad); + + if (collect_pad->buffer != NULL) { + GstClockTime time; + + *popped = TRUE; + /* convert to running time */ + time = GST_BUFFER_TIMESTAMP (collect_pad->buffer); + /* invalid should pass */ + if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (time))) { + time = gst_segment_to_running_time (&collect_pad->collect.segment, + GST_FORMAT_TIME, time); + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time))) { + GST_DEBUG_OBJECT (mux, "clipping buffer on pad %s outside segment", + GST_PAD_NAME (collect_pad->collect.pad)); + gst_buffer_unref (collect_pad->buffer); + collect_pad->buffer = NULL; + return NULL; + } else { + collect_pad->buffer = + gst_buffer_make_metadata_writable (collect_pad->buffer); + GST_BUFFER_TIMESTAMP (collect_pad->buffer) = time; + } + } + } + } + + /* if we have a buffer check if it is better then the current best one */ + if (collect_pad->buffer != NULL) { + if (best == NULL || !GST_BUFFER_TIMESTAMP_IS_VALID (collect_pad->buffer) + || (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer) + && GST_BUFFER_TIMESTAMP (collect_pad->buffer) < + GST_BUFFER_TIMESTAMP (best->buffer))) { + best = collect_pad; + } + } + } + + return best; +} + +/** + * gst_matroska_mux_buffer_header: + * @track: Track context. + * @relative_timestamp: relative timestamp of the buffer + * @flags: Buffer flags. + * + * Create a buffer containing buffer header. + * + * Returns: New buffer. + */ +static GstBuffer * +gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track, + gint16 relative_timestamp, int flags) +{ + GstBuffer *hdr; + + hdr = gst_buffer_new_and_alloc (4); + /* track num - FIXME: what if num >= 0x80 (unlikely)? */ + GST_BUFFER_DATA (hdr)[0] = track->num | 0x80; + /* time relative to clustertime */ + GST_WRITE_UINT16_BE (GST_BUFFER_DATA (hdr) + 1, relative_timestamp); + + /* flags */ + GST_BUFFER_DATA (hdr)[3] = flags; + + return hdr; +} + +#define DIRAC_PARSE_CODE_SEQUENCE_HEADER 0x00 +#define DIRAC_PARSE_CODE_END_OF_SEQUENCE 0x10 +#define DIRAC_PARSE_CODE_IS_PICTURE(x) ((x & 0x08) != 0) + +static GstBuffer * +gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, + GstMatroskaPad * collect_pad, GstBuffer * buf) +{ + GstMatroskaTrackVideoContext *ctx = + (GstMatroskaTrackVideoContext *) collect_pad->track; + const guint8 *data = GST_BUFFER_DATA (buf); + guint size = GST_BUFFER_SIZE (buf); + guint8 parse_code; + guint32 next_parse_offset; + GstBuffer *ret = NULL; + gboolean is_muxing_unit = FALSE; + + if (GST_BUFFER_SIZE (buf) < 13) { + gst_buffer_unref (buf); + return ret; + } + + /* Check if this buffer contains a picture or end-of-sequence packet */ + while (size >= 13) { + if (GST_READ_UINT32_BE (data) != 0x42424344 /* 'BBCD' */ ) { + gst_buffer_unref (buf); + return ret; + } + + parse_code = GST_READ_UINT8 (data + 4); + if (parse_code == DIRAC_PARSE_CODE_SEQUENCE_HEADER) { + if (ctx->dirac_unit) { + gst_buffer_unref (ctx->dirac_unit); + ctx->dirac_unit = NULL; + } + } else if (DIRAC_PARSE_CODE_IS_PICTURE (parse_code) || + parse_code == DIRAC_PARSE_CODE_END_OF_SEQUENCE) { + is_muxing_unit = TRUE; + break; + } + + next_parse_offset = GST_READ_UINT32_BE (data + 5); + + if (G_UNLIKELY (next_parse_offset == 0)) + break; + + data += next_parse_offset; + size -= next_parse_offset; + } + + if (ctx->dirac_unit) + ctx->dirac_unit = gst_buffer_join (ctx->dirac_unit, gst_buffer_ref (buf)); + else + ctx->dirac_unit = gst_buffer_ref (buf); + + if (is_muxing_unit) { + ret = gst_buffer_make_metadata_writable (ctx->dirac_unit); + ctx->dirac_unit = NULL; + gst_buffer_copy_metadata (ret, buf, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS | + GST_BUFFER_COPY_CAPS); + gst_buffer_unref (buf); + } else { + gst_buffer_unref (buf); + ret = NULL; + } + + return ret; +} + +static void +gst_matroska_mux_stop_streamheader (GstMatroskaMux * mux) +{ + GstCaps *caps; + GstStructure *s; + GValue streamheader = { 0 }; + GValue bufval = { 0 }; + GstBuffer *streamheader_buffer; + GstEbmlWrite *ebml = mux->ebml_write; + + streamheader_buffer = gst_ebml_stop_streamheader (ebml); + if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) { + caps = gst_caps_new_simple ("video/webm", NULL); + } else { + caps = gst_caps_new_simple ("video/x-matroska", NULL); + } + s = gst_caps_get_structure (caps, 0); + g_value_init (&streamheader, GST_TYPE_ARRAY); + g_value_init (&bufval, GST_TYPE_BUFFER); + GST_BUFFER_FLAG_SET (streamheader_buffer, GST_BUFFER_FLAG_IN_CAPS); + gst_value_set_buffer (&bufval, streamheader_buffer); + gst_value_array_append_value (&streamheader, &bufval); + g_value_unset (&bufval); + gst_structure_set_value (s, "streamheader", &streamheader); + g_value_unset (&streamheader); + gst_caps_replace (&ebml->caps, caps); + gst_buffer_unref (streamheader_buffer); + gst_caps_unref (caps); +} + +/** + * gst_matroska_mux_write_data: + * @mux: #GstMatroskaMux + * @collect_pad: #GstMatroskaPad with the data + * + * Write collected data (called from gst_matroska_mux_collected). + * + * Returns: Result of the gst_pad_push issued to write the data. + */ +static GstFlowReturn +gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) +{ + GstEbmlWrite *ebml = mux->ebml_write; + GstBuffer *buf, *hdr; + guint64 blockgroup; + gboolean write_duration; + gint16 relative_timestamp; + gint64 relative_timestamp64; + guint64 block_duration; + gboolean is_video_keyframe = FALSE; + + /* write data */ + buf = collect_pad->buffer; + collect_pad->buffer = NULL; + + /* vorbis/theora headers are retrieved from caps and put in CodecPrivate */ + if (collect_pad->track->xiph_headers_to_skip > 0) { + GST_LOG_OBJECT (collect_pad->collect.pad, "dropping streamheader buffer"); + gst_buffer_unref (buf); + --collect_pad->track->xiph_headers_to_skip; + return GST_FLOW_OK; + } + + /* for dirac we have to queue up everything up to a picture unit */ + if (collect_pad->track->codec_id != NULL && + strcmp (collect_pad->track->codec_id, + GST_MATROSKA_CODEC_ID_VIDEO_DIRAC) == 0) { + buf = gst_matroska_mux_handle_dirac_packet (mux, collect_pad, buf); + if (!buf) + return GST_FLOW_OK; + } + + /* hm, invalid timestamp (due to --to be fixed--- element upstream); + * this would wreak havoc with time stored in matroska file */ + /* TODO: maybe calculate a timestamp by using the previous timestamp + * and default duration */ + if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + GST_WARNING_OBJECT (collect_pad->collect.pad, + "Invalid buffer timestamp; dropping buffer"); + gst_buffer_unref (buf); + return GST_FLOW_OK; + } + + /* set the timestamp for outgoing buffers */ + ebml->timestamp = GST_BUFFER_TIMESTAMP (buf); + + if (collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_VIDEO && + !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) { + GST_LOG_OBJECT (mux, "have video keyframe, ts=%" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + is_video_keyframe = TRUE; + } + + if (mux->cluster) { + /* start a new cluster at every keyframe or when we may be reaching the + * limit of the relative timestamp */ + if (mux->cluster_time + + mux->max_cluster_duration < GST_BUFFER_TIMESTAMP (buf) + || is_video_keyframe) { + if (!mux->streamable) + gst_ebml_write_master_finish (ebml, mux->cluster); + mux->prev_cluster_size = ebml->pos - mux->cluster_pos; + mux->cluster_pos = ebml->pos; + gst_ebml_write_set_cache (ebml, 0x20); + mux->cluster = + gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE, + gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1, + mux->time_scale)); + GST_LOG_OBJECT (mux, "cluster timestamp %" G_GUINT64_FORMAT, + gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1, + mux->time_scale)); + gst_ebml_write_flush_cache (ebml, TRUE, GST_BUFFER_TIMESTAMP (buf)); + mux->cluster_time = GST_BUFFER_TIMESTAMP (buf); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_PREVSIZE, + mux->prev_cluster_size); + } + } else { + /* first cluster */ + + mux->cluster_pos = ebml->pos; + gst_ebml_write_set_cache (ebml, 0x20); + mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE, + gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1, mux->time_scale)); + gst_ebml_write_flush_cache (ebml, TRUE, GST_BUFFER_TIMESTAMP (buf)); + mux->cluster_time = GST_BUFFER_TIMESTAMP (buf); + } + + /* update duration of this track */ + if (GST_BUFFER_DURATION_IS_VALID (buf)) + collect_pad->duration += GST_BUFFER_DURATION (buf); + + /* We currently write index entries for all video tracks or for the audio + * track in a single-track audio file. This could be improved by keeping the + * index only for the *first* video track. */ + + /* TODO: index is useful for every track, should contain the number of + * the block in the cluster which contains the timestamp, should also work + * for files with multiple audio tracks. + */ + if (!mux->streamable && + (is_video_keyframe || + ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) && + (mux->num_streams == 1)))) { + gint last_idx = -1; + + if (mux->min_index_interval != 0) { + for (last_idx = mux->num_indexes - 1; last_idx >= 0; last_idx--) { + if (mux->index[last_idx].track == collect_pad->track->num) + break; + } + } + + if (last_idx < 0 || mux->min_index_interval == 0 || + (GST_CLOCK_DIFF (mux->index[last_idx].time, GST_BUFFER_TIMESTAMP (buf)) + >= mux->min_index_interval)) { + GstMatroskaIndex *idx; + + if (mux->num_indexes % 32 == 0) { + mux->index = g_renew (GstMatroskaIndex, mux->index, + mux->num_indexes + 32); + } + idx = &mux->index[mux->num_indexes++]; + + idx->pos = mux->cluster_pos; + idx->time = GST_BUFFER_TIMESTAMP (buf); + idx->track = collect_pad->track->num; + } + } + + /* Check if the duration differs from the default duration. */ + write_duration = FALSE; + block_duration = GST_BUFFER_DURATION (buf); + if (GST_BUFFER_DURATION_IS_VALID (buf)) { + if (block_duration != collect_pad->track->default_duration) { + write_duration = TRUE; + } + } + + /* write the block, for doctype v2 use SimpleBlock if possible + * one slice (*breath*). + * FIXME: Need to do correct lacing! */ + relative_timestamp64 = GST_BUFFER_TIMESTAMP (buf) - mux->cluster_time; + if (relative_timestamp64 >= 0) { + /* round the timestamp */ + relative_timestamp64 += gst_util_uint64_scale (mux->time_scale, 1, 2); + } else { + /* round the timestamp */ + relative_timestamp64 -= gst_util_uint64_scale (mux->time_scale, 1, 2); + } + relative_timestamp = gst_util_uint64_scale (relative_timestamp64, 1, + mux->time_scale); + if (mux->doctype_version > 1 && !write_duration) { + int flags = + GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : 0x80; + + hdr = + gst_matroska_mux_create_buffer_header (collect_pad->track, + relative_timestamp, flags); + gst_ebml_write_set_cache (ebml, 0x40); + gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_SIMPLEBLOCK, + GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr)); + gst_ebml_write_buffer (ebml, hdr); + gst_ebml_write_flush_cache (ebml, FALSE, GST_BUFFER_TIMESTAMP (buf)); + gst_ebml_write_buffer (ebml, buf); + + return gst_ebml_last_write_result (ebml); + } else { + gst_ebml_write_set_cache (ebml, GST_BUFFER_SIZE (buf) * 2); + /* write and call order slightly unnatural, + * but avoids seek and minizes pushing */ + blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP); + hdr = + gst_matroska_mux_create_buffer_header (collect_pad->track, + relative_timestamp, 0); + if (write_duration) { + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION, + gst_util_uint64_scale (block_duration, 1, mux->time_scale)); + } + gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK, + GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr)); + gst_ebml_write_buffer (ebml, hdr); + gst_ebml_write_master_finish_full (ebml, blockgroup, GST_BUFFER_SIZE (buf)); + gst_ebml_write_flush_cache (ebml, FALSE, GST_BUFFER_TIMESTAMP (buf)); + gst_ebml_write_buffer (ebml, buf); + + return gst_ebml_last_write_result (ebml); + } +} + + +/** + * gst_matroska_mux_collected: + * @pads: #GstCollectPads + * @uuser_data: #GstMatroskaMux + * + * Collectpads callback. + * + * Returns: #GstFlowReturn + */ +static GstFlowReturn +gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data) +{ + GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data); + GstEbmlWrite *ebml = mux->ebml_write; + GstMatroskaPad *best; + gboolean popped; + GstFlowReturn ret = GST_FLOW_OK; + + GST_DEBUG_OBJECT (mux, "Collected pads"); + + /* start with a header */ + if (mux->state == GST_MATROSKA_MUX_STATE_START) { + if (mux->collect->data == NULL) { + GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), + ("No input streams configured")); + return GST_FLOW_ERROR; + } + mux->state = GST_MATROSKA_MUX_STATE_HEADER; + gst_ebml_start_streamheader (ebml); + gst_matroska_mux_start (mux); + gst_matroska_mux_stop_streamheader (mux); + mux->state = GST_MATROSKA_MUX_STATE_DATA; + } + + do { + /* which stream to write from? */ + best = gst_matroska_mux_best_pad (mux, &popped); + + /* if there is no best pad, we have reached EOS */ + if (best == NULL) { + /* buffer popped, but none returned means it was clipped */ + if (popped) + break; + GST_DEBUG_OBJECT (mux, "No best pad finishing..."); + if (!mux->streamable) { + gst_matroska_mux_finish (mux); + } else { + GST_DEBUG_OBJECT (mux, "... but streamable, nothing to finish"); + } + gst_pad_push_event (mux->srcpad, gst_event_new_eos ()); + ret = GST_FLOW_UNEXPECTED; + break; + } + GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %" + GST_TIME_FORMAT " dur %" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (best->buffer)), + GST_TIME_ARGS (GST_BUFFER_DURATION (best->buffer))); + + /* make note of first and last encountered timestamps, so we can calculate + * the actual duration later when we send an updated header on eos */ + if (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)) { + GstClockTime start_ts = GST_BUFFER_TIMESTAMP (best->buffer); + GstClockTime end_ts = start_ts; + + if (GST_BUFFER_DURATION_IS_VALID (best->buffer)) + end_ts += GST_BUFFER_DURATION (best->buffer); + else if (best->track->default_duration) + end_ts += best->track->default_duration; + + if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts) + best->end_ts = end_ts; + + if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE || + start_ts < best->start_ts)) + best->start_ts = start_ts; + } + + /* write one buffer */ + ret = gst_matroska_mux_write_data (mux, best); + } while (ret == GST_FLOW_OK && !popped); + + return ret; +} + + +/** + * gst_matroska_mux_change_state: + * @element: #GstMatroskaMux + * @transition: State change transition. + * + * Change the muxer state. + * + * Returns: #GstStateChangeReturn + */ +static GstStateChangeReturn +gst_matroska_mux_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstMatroskaMux *mux = GST_MATROSKA_MUX (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_collect_pads_start (mux->collect); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_collect_pads_stop (mux->collect); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_matroska_mux_reset (GST_ELEMENT (mux)); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} + +static void +gst_matroska_mux_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstMatroskaMux *mux; + + g_return_if_fail (GST_IS_MATROSKA_MUX (object)); + mux = GST_MATROSKA_MUX (object); + + switch (prop_id) { + case ARG_WRITING_APP: + if (!g_value_get_string (value)) { + GST_WARNING_OBJECT (mux, "writing-app property can not be NULL"); + break; + } + g_free (mux->writing_app); + mux->writing_app = g_value_dup_string (value); + break; + case ARG_DOCTYPE_VERSION: + mux->doctype_version = g_value_get_int (value); + break; + case ARG_MIN_INDEX_INTERVAL: + mux->min_index_interval = g_value_get_int64 (value); + break; + case ARG_STREAMABLE: + mux->streamable = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_matroska_mux_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstMatroskaMux *mux; + + g_return_if_fail (GST_IS_MATROSKA_MUX (object)); + mux = GST_MATROSKA_MUX (object); + + switch (prop_id) { + case ARG_WRITING_APP: + g_value_set_string (value, mux->writing_app); + break; + case ARG_DOCTYPE_VERSION: + g_value_set_int (value, mux->doctype_version); + break; + case ARG_MIN_INDEX_INTERVAL: + g_value_set_int64 (value, mux->min_index_interval); + break; + case ARG_STREAMABLE: + g_value_set_boolean (value, mux->streamable); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-mux.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-mux.h Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,138 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * (c) 2005 Michal Benes + * + * matroska-mux.h: matroska file/stream muxer object types + * + * 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_MATROSKA_MUX_H__ +#define __GST_MATROSKA_MUX_H__ + +#include +#include + +#include "ebml-write.h" +#include "matroska-ids.h" + +G_BEGIN_DECLS + +#define GST_TYPE_MATROSKA_MUX \ + (gst_matroska_mux_get_type ()) +#define GST_MATROSKA_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_MATROSKA_MUX, GstMatroskaMux)) +#define GST_MATROSKA_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_MATROSKA_MUX, GstMatroskaMuxClass)) +#define GST_IS_MATROSKA_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_MATROSKA_MUX)) +#define GST_IS_MATROSKA_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MATROSKA_MUX)) + +typedef enum { + GST_MATROSKA_MUX_STATE_START, + GST_MATROSKA_MUX_STATE_HEADER, + GST_MATROSKA_MUX_STATE_DATA, +} GstMatroskaMuxState; + +typedef struct _GstMatroskaMetaSeekIndex { + guint32 id; + guint64 pos; +} GstMatroskaMetaSeekIndex; + +/* all information needed for one matroska stream */ +typedef struct +{ + GstCollectData collect; /* we extend the CollectData */ + GstMatroskaTrackContext *track; + + GstBuffer *buffer; /* the queued buffer for this pad */ + + guint64 duration; + GstClockTime start_ts; + GstClockTime end_ts; /* last timestamp + (if available) duration */ +} +GstMatroskaPad; + + +typedef struct _GstMatroskaMux { + GstElement element; + + /* < private > */ + + /* pads */ + GstPad *srcpad; + GstCollectPads *collect; + GstPadEventFunction collect_event; + GstEbmlWrite *ebml_write; + + guint num_streams, + num_v_streams, num_a_streams, num_t_streams; + + /* Application name (for the writing application header element) */ + gchar *writing_app; + + /* EBML DocType. */ + const gchar *doctype; + + /* DocType version. */ + guint doctype_version; + + /* state */ + GstMatroskaMuxState state; + + /* a cue (index) table */ + GstMatroskaIndex *index; + guint num_indexes; + GstClockTimeDiff min_index_interval; + gboolean streamable; + + /* timescale in the file */ + guint64 time_scale; + /* based on timescale, limit of nanoseconds you can have in a cluster */ + guint64 max_cluster_duration; + + /* length, position (time, ns) */ + guint64 duration; + + /* byte-positions of master-elements (for replacing contents) */ + guint64 segment_pos, + seekhead_pos, + cues_pos, + tags_pos, + info_pos, + tracks_pos, + duration_pos, + meta_pos; + guint64 segment_master; + + /* current cluster */ + guint64 cluster, + cluster_time, + cluster_pos, + prev_cluster_size; + +} GstMatroskaMux; + +typedef struct _GstMatroskaMuxClass { + GstElementClass parent; +} GstMatroskaMuxClass; + +GType gst_matroska_mux_get_type (void); + +G_END_DECLS + +#endif /* __GST_MATROSKA_MUX_H__ */ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-parse.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-parse.c Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,5054 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * (c) 2006 Tim-Philipp Müller + * (c) 2008 Sebastian Dröge + * + * matroska-parse.c: matroska file/stream parser + * + * 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: check CRC32 if present + * TODO: there can be a segment after the first segment. Handle like + * chained oggs. Fixes #334082 + * TODO: Test samples: http://www.matroska.org/samples/matrix/index.html + * http://samples.mplayerhq.hu/Matroska/ + * TODO: check if parseing is done correct for all codecs according to spec + * TODO: seeking with incomplete or without CUE + */ + +/** + * SECTION:element-matroskaparse + * + * matroskaparse parsees a Matroska file into the different contained streams. + * + * + * Example launch line + * |[ + * gst-launch -v filesrc location=/path/to/mkv ! matroskaparse ! vorbisdec ! audioconvert ! audioresample ! autoaudiosink + * ]| This pipeline parsees a Matroska file and outputs the contained Vorbis audio. + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +/* For AVI compatibility mode + and for fourcc stuff */ +#include +#include +#include + +#include + +#include + +#ifdef HAVE_ZLIB +#include +#endif + +#ifdef HAVE_BZ2 +#include +#endif + +#include + +#include "lzo.h" + +#include "matroska-parse.h" +#include "matroska-ids.h" + +GST_DEBUG_CATEGORY_STATIC (matroskaparse_debug); +#define GST_CAT_DEFAULT matroskaparse_debug + +#define DEBUG_ELEMENT_START(parse, ebml, element) \ + GST_DEBUG_OBJECT (parse, "Parsing " element " element at offset %" \ + G_GUINT64_FORMAT, gst_ebml_read_get_pos (ebml)) + +#define DEBUG_ELEMENT_STOP(parse, ebml, element, ret) \ + GST_DEBUG_OBJECT (parse, "Parsing " element " element " \ + " finished with '%s'", gst_flow_get_name (ret)) + +enum +{ + ARG_0, + ARG_METADATA, + ARG_STREAMINFO +}; + +static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-matroska; video/webm") + ); + +static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-matroska; video/webm") + ); + +static GstFlowReturn gst_matroska_parse_parse_id (GstMatroskaParse * parse, + guint32 id, guint64 length, guint needed); + +/* element functions */ +//static void gst_matroska_parse_loop (GstPad * pad); + +static gboolean gst_matroska_parse_element_send_event (GstElement * element, + GstEvent * event); +static gboolean gst_matroska_parse_element_query (GstElement * element, + GstQuery * query); + +/* pad functions */ +static gboolean gst_matroska_parse_handle_seek_event (GstMatroskaParse * parse, + GstPad * pad, GstEvent * event); +static gboolean gst_matroska_parse_handle_src_event (GstPad * pad, + GstEvent * event); +static const GstQueryType *gst_matroska_parse_get_src_query_types (GstPad * + pad); +static gboolean gst_matroska_parse_handle_src_query (GstPad * pad, + GstQuery * query); + +static gboolean gst_matroska_parse_handle_sink_event (GstPad * pad, + GstEvent * event); +static GstFlowReturn gst_matroska_parse_chain (GstPad * pad, + GstBuffer * buffer); + +static GstStateChangeReturn +gst_matroska_parse_change_state (GstElement * element, + GstStateChange transition); +static void +gst_matroska_parse_set_index (GstElement * element, GstIndex * index); +static GstIndex *gst_matroska_parse_get_index (GstElement * element); + +/* stream methods */ +static void gst_matroska_parse_reset (GstElement * element); +static gboolean perform_seek_to_offset (GstMatroskaParse * parse, + guint64 offset); + +GType gst_matroska_parse_get_type (void); +GST_BOILERPLATE (GstMatroskaParse, gst_matroska_parse, GstElement, + GST_TYPE_ELEMENT); + +static void +gst_matroska_parse_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_templ)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_templ)); + + gst_element_class_set_details_simple (element_class, "Matroska parser", + "Codec/Parser", + "Parses Matroska/WebM streams into video/audio/subtitles", + "GStreamer maintainers "); +} + +static void +gst_matroska_parse_finalize (GObject * object) +{ + GstMatroskaParse *parse = GST_MATROSKA_PARSE (object); + + if (parse->src) { + g_ptr_array_free (parse->src, TRUE); + parse->src = NULL; + } + + if (parse->global_tags) { + gst_tag_list_free (parse->global_tags); + parse->global_tags = NULL; + } + + g_object_unref (parse->adapter); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_matroska_parse_class_init (GstMatroskaParseClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + + GST_DEBUG_CATEGORY_INIT (matroskaparse_debug, "matroskaparse", 0, + "Matroska parser"); + + gobject_class->finalize = gst_matroska_parse_finalize; + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_matroska_parse_change_state); + gstelement_class->send_event = + GST_DEBUG_FUNCPTR (gst_matroska_parse_element_send_event); + gstelement_class->query = + GST_DEBUG_FUNCPTR (gst_matroska_parse_element_query); + + gstelement_class->set_index = + GST_DEBUG_FUNCPTR (gst_matroska_parse_set_index); + gstelement_class->get_index = + GST_DEBUG_FUNCPTR (gst_matroska_parse_get_index); +} + +static void +gst_matroska_parse_init (GstMatroskaParse * parse, + GstMatroskaParseClass * klass) +{ + parse->sinkpad = gst_pad_new_from_static_template (&sink_templ, "sink"); + gst_pad_set_chain_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_matroska_parse_chain)); + gst_pad_set_event_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_matroska_parse_handle_sink_event)); + gst_element_add_pad (GST_ELEMENT (parse), parse->sinkpad); + + parse->srcpad = gst_pad_new_from_static_template (&src_templ, "src"); + gst_pad_set_event_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_matroska_parse_handle_src_event)); + gst_pad_set_query_type_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_matroska_parse_get_src_query_types)); + gst_pad_set_query_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_matroska_parse_handle_src_query)); + gst_pad_use_fixed_caps (parse->srcpad); + + gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad); + + /* initial stream no. */ + parse->src = NULL; + + parse->writing_app = NULL; + parse->muxing_app = NULL; + parse->index = NULL; + parse->global_tags = NULL; + + parse->adapter = gst_adapter_new (); + + /* finish off */ + gst_matroska_parse_reset (GST_ELEMENT (parse)); +} + +static void +gst_matroska_track_free (GstMatroskaTrackContext * track) +{ + g_free (track->codec_id); + g_free (track->codec_name); + g_free (track->name); + g_free (track->language); + g_free (track->codec_priv); + g_free (track->codec_state); + + if (track->encodings != NULL) { + int i; + + for (i = 0; i < track->encodings->len; ++i) { + GstMatroskaTrackEncoding *enc = &g_array_index (track->encodings, + GstMatroskaTrackEncoding, + i); + + g_free (enc->comp_settings); + } + g_array_free (track->encodings, TRUE); + } + + if (track->pending_tags) + gst_tag_list_free (track->pending_tags); + + if (track->index_table) + g_array_free (track->index_table, TRUE); + + g_free (track); +} + +static void +gst_matroska_parse_free_parsed_el (gpointer mem, gpointer user_data) +{ + g_slice_free (guint64, mem); +} + +static void +gst_matroska_parse_reset (GstElement * element) +{ + GstMatroskaParse *parse = GST_MATROSKA_PARSE (element); + guint i; + + GST_DEBUG_OBJECT (parse, "Resetting state"); + + /* reset input */ + parse->state = GST_MATROSKA_PARSE_STATE_START; + + /* clean up existing streams */ + if (parse->src) { + g_assert (parse->src->len == parse->num_streams); + for (i = 0; i < parse->src->len; i++) { + GstMatroskaTrackContext *context = g_ptr_array_index (parse->src, i); + + gst_caps_replace (&context->caps, NULL); + gst_matroska_track_free (context); + } + g_ptr_array_free (parse->src, TRUE); + } + parse->src = g_ptr_array_new (); + + parse->num_streams = 0; + parse->num_a_streams = 0; + parse->num_t_streams = 0; + parse->num_v_streams = 0; + + /* reset media info */ + g_free (parse->writing_app); + parse->writing_app = NULL; + g_free (parse->muxing_app); + parse->muxing_app = NULL; + + /* reset indexes */ + if (parse->index) { + g_array_free (parse->index, TRUE); + parse->index = NULL; + } + + /* reset timers */ + parse->clock = NULL; + parse->time_scale = 1000000; + parse->created = G_MININT64; + + parse->index_parsed = FALSE; + parse->tracks_parsed = FALSE; + parse->segmentinfo_parsed = FALSE; + parse->attachments_parsed = FALSE; + + g_list_foreach (parse->tags_parsed, + (GFunc) gst_matroska_parse_free_parsed_el, NULL); + g_list_free (parse->tags_parsed); + parse->tags_parsed = NULL; + + g_list_foreach (parse->seek_parsed, + (GFunc) gst_matroska_parse_free_parsed_el, NULL); + g_list_free (parse->seek_parsed); + parse->seek_parsed = NULL; + + gst_segment_init (&parse->segment, GST_FORMAT_TIME); + parse->last_stop_end = GST_CLOCK_TIME_NONE; + parse->seek_block = 0; + + parse->offset = 0; + parse->cluster_time = GST_CLOCK_TIME_NONE; + parse->cluster_offset = 0; + parse->next_cluster_offset = 0; + parse->index_offset = 0; + parse->seekable = FALSE; + parse->need_newsegment = FALSE; + parse->building_index = FALSE; + if (parse->seek_event) { + gst_event_unref (parse->seek_event); + parse->seek_event = NULL; + } + + parse->seek_index = NULL; + parse->seek_entry = 0; + + if (parse->close_segment) { + gst_event_unref (parse->close_segment); + parse->close_segment = NULL; + } + + if (parse->new_segment) { + gst_event_unref (parse->new_segment); + parse->new_segment = NULL; + } + + if (parse->element_index) { + gst_object_unref (parse->element_index); + parse->element_index = NULL; + } + parse->element_index_writer_id = -1; + + if (parse->global_tags) { + gst_tag_list_free (parse->global_tags); + } + parse->global_tags = gst_tag_list_new (); + + if (parse->cached_buffer) { + gst_buffer_unref (parse->cached_buffer); + parse->cached_buffer = NULL; + } +} + +/* + * Calls pull_range for (offset,size) without advancing our offset + */ +static GstFlowReturn +gst_matroska_parse_peek_bytes (GstMatroskaParse * parse, guint64 offset, + guint size, GstBuffer ** p_buf, guint8 ** bytes) +{ + GstFlowReturn ret; + + /* Caching here actually makes much less difference than one would expect. + * We do it mainly to avoid pulling buffers of 1 byte all the time */ + if (parse->cached_buffer) { + guint64 cache_offset = GST_BUFFER_OFFSET (parse->cached_buffer); + guint cache_size = GST_BUFFER_SIZE (parse->cached_buffer); + + if (cache_offset <= parse->offset && + (parse->offset + size) <= (cache_offset + cache_size)) { + if (p_buf) + *p_buf = gst_buffer_create_sub (parse->cached_buffer, + parse->offset - cache_offset, size); + if (bytes) + *bytes = GST_BUFFER_DATA (parse->cached_buffer) + parse->offset - + cache_offset; + return GST_FLOW_OK; + } + /* not enough data in the cache, free cache and get a new one */ + gst_buffer_unref (parse->cached_buffer); + parse->cached_buffer = NULL; + } + + /* refill the cache */ + ret = gst_pad_pull_range (parse->sinkpad, parse->offset, + MAX (size, 64 * 1024), &parse->cached_buffer); + if (ret != GST_FLOW_OK) { + parse->cached_buffer = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (parse->cached_buffer) >= size) { + if (p_buf) + *p_buf = gst_buffer_create_sub (parse->cached_buffer, 0, size); + if (bytes) + *bytes = GST_BUFFER_DATA (parse->cached_buffer); + return GST_FLOW_OK; + } + + /* Not possible to get enough data, try a last time with + * requesting exactly the size we need */ + gst_buffer_unref (parse->cached_buffer); + parse->cached_buffer = NULL; + + ret = + gst_pad_pull_range (parse->sinkpad, parse->offset, size, + &parse->cached_buffer); + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (parse, "pull_range returned %d", ret); + if (p_buf) + *p_buf = NULL; + if (bytes) + *bytes = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (parse->cached_buffer) < size) { + GST_WARNING_OBJECT (parse, "Dropping short buffer at offset %" + G_GUINT64_FORMAT ": wanted %u bytes, got %u bytes", parse->offset, + size, GST_BUFFER_SIZE (parse->cached_buffer)); + + gst_buffer_unref (parse->cached_buffer); + parse->cached_buffer = NULL; + if (p_buf) + *p_buf = NULL; + if (bytes) + *bytes = NULL; + return GST_FLOW_UNEXPECTED; + } + + if (p_buf) + *p_buf = gst_buffer_create_sub (parse->cached_buffer, 0, size); + if (bytes) + *bytes = GST_BUFFER_DATA (parse->cached_buffer); + + return GST_FLOW_OK; +} + +static const guint8 * +gst_matroska_parse_peek_pull (GstMatroskaParse * parse, guint peek) +{ + guint8 *data = NULL; + + gst_matroska_parse_peek_bytes (parse, parse->offset, peek, NULL, &data); + return data; +} + +static GstFlowReturn +gst_matroska_parse_peek_id_length_pull (GstMatroskaParse * parse, guint32 * _id, + guint64 * _length, guint * _needed) +{ + return gst_ebml_peek_id_length (_id, _length, _needed, + (GstPeekData) gst_matroska_parse_peek_pull, (gpointer) parse, + GST_ELEMENT_CAST (parse), parse->offset); +} + +static gint64 +gst_matroska_parse_get_length (GstMatroskaParse * parse) +{ + GstFormat fmt = GST_FORMAT_BYTES; + gint64 end = -1; + + if (!gst_pad_query_peer_duration (parse->sinkpad, &fmt, &end) || + fmt != GST_FORMAT_BYTES || end < 0) + GST_DEBUG_OBJECT (parse, "no upstream length"); + + return end; +} + +static gint +gst_matroska_parse_stream_from_num (GstMatroskaParse * parse, guint track_num) +{ + guint n; + + g_assert (parse->src->len == parse->num_streams); + for (n = 0; n < parse->src->len; n++) { + GstMatroskaTrackContext *context = g_ptr_array_index (parse->src, n); + + if (context->num == track_num) { + return n; + } + } + + if (n == parse->num_streams) + GST_WARNING_OBJECT (parse, + "Failed to find corresponding pad for tracknum %d", track_num); + + return -1; +} + +static gint +gst_matroska_parse_encoding_cmp (GstMatroskaTrackEncoding * a, + GstMatroskaTrackEncoding * b) +{ + if (b->order > a->order) + return 1; + else if (b->order < a->order) + return -1; + else + return 0; +} + +static gboolean +gst_matroska_parse_encoding_order_unique (GArray * encodings, guint64 order) +{ + gint i; + + if (encodings == NULL || encodings->len == 0) + return TRUE; + + for (i = 0; i < encodings->len; i++) + if (g_array_index (encodings, GstMatroskaTrackEncoding, i).order == order) + return FALSE; + + return TRUE; +} + +static GstFlowReturn +gst_matroska_parse_read_track_encoding (GstMatroskaParse * parse, + GstEbmlRead * ebml, GstMatroskaTrackContext * context) +{ + GstMatroskaTrackEncoding enc = { 0, }; + GstFlowReturn ret; + guint32 id; + + DEBUG_ELEMENT_START (parse, ebml, "ContentEncoding"); + /* Set default values */ + enc.scope = 1; + /* All other default values are 0 */ + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "ContentEncoding", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_CONTENTENCODINGORDER:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (!gst_matroska_parse_encoding_order_unique (context->encodings, num)) { + GST_ERROR_OBJECT (parse, "ContentEncodingOrder %" G_GUINT64_FORMAT + "is not unique for track %d", num, context->num); + ret = GST_FLOW_ERROR; + break; + } + + GST_DEBUG_OBJECT (parse, "ContentEncodingOrder: %" G_GUINT64_FORMAT, + num); + enc.order = num; + break; + } + case GST_MATROSKA_ID_CONTENTENCODINGSCOPE:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num > 7 && num == 0) { + GST_ERROR_OBJECT (parse, "Invalid ContentEncodingScope %" + G_GUINT64_FORMAT, num); + ret = GST_FLOW_ERROR; + break; + } + + GST_DEBUG_OBJECT (parse, "ContentEncodingScope: %" G_GUINT64_FORMAT, + num); + enc.scope = num; + + break; + } + case GST_MATROSKA_ID_CONTENTENCODINGTYPE:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num > 1) { + GST_ERROR_OBJECT (parse, "Invalid ContentEncodingType %" + G_GUINT64_FORMAT, num); + ret = GST_FLOW_ERROR; + break; + } else if (num != 0) { + GST_ERROR_OBJECT (parse, "Encrypted tracks are not supported yet"); + ret = GST_FLOW_ERROR; + break; + } + GST_DEBUG_OBJECT (parse, "ContentEncodingType: %" G_GUINT64_FORMAT, + num); + enc.type = num; + break; + } + case GST_MATROSKA_ID_CONTENTCOMPRESSION:{ + + DEBUG_ELEMENT_START (parse, ebml, "ContentCompression"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) + break; + + while (ret == GST_FLOW_OK && + gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_CONTENTCOMPALGO:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) { + break; + } + if (num > 3) { + GST_ERROR_OBJECT (parse, "Invalid ContentCompAlgo %" + G_GUINT64_FORMAT, num); + ret = GST_FLOW_ERROR; + break; + } + GST_DEBUG_OBJECT (parse, "ContentCompAlgo: %" G_GUINT64_FORMAT, + num); + enc.comp_algo = num; + + break; + } + case GST_MATROSKA_ID_CONTENTCOMPSETTINGS:{ + guint8 *data; + guint64 size; + + if ((ret = + gst_ebml_read_binary (ebml, &id, &data, + &size)) != GST_FLOW_OK) { + break; + } + enc.comp_settings = data; + enc.comp_settings_length = size; + GST_DEBUG_OBJECT (parse, + "ContentCompSettings of size %" G_GUINT64_FORMAT, size); + break; + } + default: + GST_WARNING_OBJECT (parse, + "Unknown ContentCompression subelement 0x%x - ignoring", id); + ret = gst_ebml_read_skip (ebml); + break; + } + } + DEBUG_ELEMENT_STOP (parse, ebml, "ContentCompression", ret); + break; + } + + case GST_MATROSKA_ID_CONTENTENCRYPTION: + GST_ERROR_OBJECT (parse, "Encrypted tracks not yet supported"); + gst_ebml_read_skip (ebml); + ret = GST_FLOW_ERROR; + break; + default: + GST_WARNING_OBJECT (parse, + "Unknown ContentEncoding subelement 0x%x - ignoring", id); + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "ContentEncoding", ret); + if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED) + return ret; + + /* TODO: Check if the combination of values is valid */ + + g_array_append_val (context->encodings, enc); + + return ret; +} + +static gboolean +gst_matroska_decompress_data (GstMatroskaTrackEncoding * enc, + guint8 ** data_out, guint * size_out, + GstMatroskaTrackCompressionAlgorithm algo) +{ + guint8 *new_data = NULL; + guint new_size = 0; + guint8 *data = *data_out; + guint size = *size_out; + gboolean ret = TRUE; + + if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_ZLIB) { +#ifdef HAVE_ZLIB + /* zlib encoded data */ + z_stream zstream; + guint orig_size; + int result; + + orig_size = size; + zstream.zalloc = (alloc_func) 0; + zstream.zfree = (free_func) 0; + zstream.opaque = (voidpf) 0; + if (inflateInit (&zstream) != Z_OK) { + GST_WARNING ("zlib initialization failed."); + ret = FALSE; + goto out; + } + zstream.next_in = (Bytef *) data; + zstream.avail_in = orig_size; + new_size = orig_size; + new_data = g_malloc (new_size); + zstream.avail_out = new_size; + zstream.next_out = (Bytef *) new_data; + + do { + result = inflate (&zstream, Z_NO_FLUSH); + if (result != Z_OK && result != Z_STREAM_END) { + GST_WARNING ("zlib decompression failed."); + g_free (new_data); + inflateEnd (&zstream); + break; + } + new_size += 4000; + new_data = g_realloc (new_data, new_size); + zstream.next_out = (Bytef *) (new_data + zstream.total_out); + zstream.avail_out += 4000; + } while (zstream.avail_in != 0 && result != Z_STREAM_END); + + if (result != Z_STREAM_END) { + ret = FALSE; + goto out; + } else { + new_size = zstream.total_out; + inflateEnd (&zstream); + } +#else + GST_WARNING ("zlib encoded tracks not supported."); + ret = FALSE; + goto out; +#endif + } else if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_BZLIB) { +#ifdef HAVE_BZ2 + /* bzip2 encoded data */ + bz_stream bzstream; + guint orig_size; + int result; + + bzstream.bzalloc = NULL; + bzstream.bzfree = NULL; + bzstream.opaque = NULL; + orig_size = size; + + if (BZ2_bzDecompressInit (&bzstream, 0, 0) != BZ_OK) { + GST_WARNING ("bzip2 initialization failed."); + ret = FALSE; + goto out; + } + + bzstream.next_in = (char *) data; + bzstream.avail_in = orig_size; + new_size = orig_size; + new_data = g_malloc (new_size); + bzstream.avail_out = new_size; + bzstream.next_out = (char *) new_data; + + do { + result = BZ2_bzDecompress (&bzstream); + if (result != BZ_OK && result != BZ_STREAM_END) { + GST_WARNING ("bzip2 decompression failed."); + g_free (new_data); + BZ2_bzDecompressEnd (&bzstream); + break; + } + new_size += 4000; + new_data = g_realloc (new_data, new_size); + bzstream.next_out = (char *) (new_data + bzstream.total_out_lo32); + bzstream.avail_out += 4000; + } while (bzstream.avail_in != 0 && result != BZ_STREAM_END); + + if (result != BZ_STREAM_END) { + ret = FALSE; + goto out; + } else { + new_size = bzstream.total_out_lo32; + BZ2_bzDecompressEnd (&bzstream); + } +#else + GST_WARNING ("bzip2 encoded tracks not supported."); + ret = FALSE; + goto out; +#endif + } else if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_LZO1X) { + /* lzo encoded data */ + int result; + int orig_size, out_size; + + orig_size = size; + out_size = size; + new_size = size; + new_data = g_malloc (new_size); + + do { + orig_size = size; + out_size = new_size; + + result = lzo1x_decode (new_data, &out_size, data, &orig_size); + + if (orig_size > 0) { + new_size += 4000; + new_data = g_realloc (new_data, new_size); + } + } while (orig_size > 0 && result == LZO_OUTPUT_FULL); + + new_size -= out_size; + + if (result != LZO_OUTPUT_FULL) { + GST_WARNING ("lzo decompression failed"); + g_free (new_data); + + ret = FALSE; + goto out; + } + + } else if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_HEADERSTRIP) { + /* header stripped encoded data */ + if (enc->comp_settings_length > 0) { + new_data = g_malloc (size + enc->comp_settings_length); + new_size = size + enc->comp_settings_length; + + memcpy (new_data, enc->comp_settings, enc->comp_settings_length); + memcpy (new_data + enc->comp_settings_length, data, size); + } + } else { + GST_ERROR ("invalid compression algorithm %d", algo); + ret = FALSE; + } + +out: + + if (!ret) { + *data_out = NULL; + *size_out = 0; + } else { + *data_out = new_data; + *size_out = new_size; + } + + return ret; +} + +static gboolean +gst_matroska_decode_data (GArray * encodings, guint8 ** data_out, + guint * size_out, GstMatroskaTrackEncodingScope scope, gboolean free) +{ + guint8 *data; + guint size; + gboolean ret = TRUE; + gint i; + + g_return_val_if_fail (encodings != NULL, FALSE); + g_return_val_if_fail (data_out != NULL && *data_out != NULL, FALSE); + g_return_val_if_fail (size_out != NULL, FALSE); + + data = *data_out; + size = *size_out; + + for (i = 0; i < encodings->len; i++) { + GstMatroskaTrackEncoding *enc = + &g_array_index (encodings, GstMatroskaTrackEncoding, i); + guint8 *new_data = NULL; + guint new_size = 0; + + if ((enc->scope & scope) == 0) + continue; + + /* Encryption not supported yet */ + if (enc->type != 0) { + ret = FALSE; + break; + } + + new_data = data; + new_size = size; + + ret = + gst_matroska_decompress_data (enc, &new_data, &new_size, + enc->comp_algo); + + if (!ret) + break; + + if ((data == *data_out && free) || (data != *data_out)) + g_free (data); + + data = new_data; + size = new_size; + } + + if (!ret) { + if ((data == *data_out && free) || (data != *data_out)) + g_free (data); + + *data_out = NULL; + *size_out = 0; + } else { + *data_out = data; + *size_out = size; + } + + return ret; +} + +static GstFlowReturn +gst_matroska_decode_content_encodings (GArray * encodings) +{ + gint i; + + if (encodings == NULL) + return GST_FLOW_OK; + + for (i = 0; i < encodings->len; i++) { + GstMatroskaTrackEncoding *enc = + &g_array_index (encodings, GstMatroskaTrackEncoding, i); + guint8 *data = NULL; + guint size; + + if ((enc->scope & GST_MATROSKA_TRACK_ENCODING_SCOPE_NEXT_CONTENT_ENCODING) + == 0) + continue; + + /* Encryption not supported yet */ + if (enc->type != 0) + return GST_FLOW_ERROR; + + if (i + 1 >= encodings->len) + return GST_FLOW_ERROR; + + if (enc->comp_settings_length == 0) + continue; + + data = enc->comp_settings; + size = enc->comp_settings_length; + + if (!gst_matroska_decompress_data (enc, &data, &size, enc->comp_algo)) + return GST_FLOW_ERROR; + + g_free (enc->comp_settings); + + enc->comp_settings = data; + enc->comp_settings_length = size; + } + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_matroska_parse_read_track_encodings (GstMatroskaParse * parse, + GstEbmlRead * ebml, GstMatroskaTrackContext * context) +{ + GstFlowReturn ret; + guint32 id; + + DEBUG_ELEMENT_START (parse, ebml, "ContentEncodings"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "ContentEncodings", ret); + return ret; + } + + context->encodings = + g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaTrackEncoding), 1); + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_CONTENTENCODING: + ret = gst_matroska_parse_read_track_encoding (parse, ebml, context); + break; + default: + GST_WARNING_OBJECT (parse, + "Unknown ContentEncodings subelement 0x%x - ignoring", id); + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "ContentEncodings", ret); + if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED) + return ret; + + /* Sort encodings according to their order */ + g_array_sort (context->encodings, + (GCompareFunc) gst_matroska_parse_encoding_cmp); + + return gst_matroska_decode_content_encodings (context->encodings); +} + +static gboolean +gst_matroska_parse_tracknumber_unique (GstMatroskaParse * parse, guint64 num) +{ + gint i; + + g_assert (parse->src->len == parse->num_streams); + for (i = 0; i < parse->src->len; i++) { + GstMatroskaTrackContext *context = g_ptr_array_index (parse->src, i); + + if (context->num == num) + return FALSE; + } + + return TRUE; +} + +static GstFlowReturn +gst_matroska_parse_add_stream (GstMatroskaParse * parse, GstEbmlRead * ebml) +{ + GstMatroskaTrackContext *context; + GstFlowReturn ret; + guint32 id; + + DEBUG_ELEMENT_START (parse, ebml, "TrackEntry"); + + /* start with the master */ + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "TrackEntry", ret); + return ret; + } + + /* allocate generic... if we know the type, we'll g_renew() + * with the precise type */ + context = g_new0 (GstMatroskaTrackContext, 1); + g_ptr_array_add (parse->src, context); + context->index = parse->num_streams; + context->index_writer_id = -1; + context->type = 0; /* no type yet */ + context->default_duration = 0; + context->pos = 0; + context->set_discont = TRUE; + context->timecodescale = 1.0; + context->flags = + GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT | + GST_MATROSKA_TRACK_LACING; + context->last_flow = GST_FLOW_OK; + context->to_offset = G_MAXINT64; + parse->num_streams++; + g_assert (parse->src->len == parse->num_streams); + + GST_DEBUG_OBJECT (parse, "Stream number %d", context->index); + + /* try reading the trackentry headers */ + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* track number (unique stream ID) */ + case GST_MATROSKA_ID_TRACKNUMBER:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_ERROR_OBJECT (parse, "Invalid TrackNumber 0"); + ret = GST_FLOW_ERROR; + break; + } else if (!gst_matroska_parse_tracknumber_unique (parse, num)) { + GST_ERROR_OBJECT (parse, "TrackNumber %" G_GUINT64_FORMAT + " is not unique", num); + ret = GST_FLOW_ERROR; + break; + } + + GST_DEBUG_OBJECT (parse, "TrackNumber: %" G_GUINT64_FORMAT, num); + context->num = num; + break; + } + /* track UID (unique identifier) */ + case GST_MATROSKA_ID_TRACKUID:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_ERROR_OBJECT (parse, "Invalid TrackUID 0"); + ret = GST_FLOW_ERROR; + break; + } + + GST_DEBUG_OBJECT (parse, "TrackUID: %" G_GUINT64_FORMAT, num); + context->uid = num; + break; + } + + /* track type (video, audio, combined, subtitle, etc.) */ + case GST_MATROSKA_ID_TRACKTYPE:{ + guint64 track_type; + + if ((ret = gst_ebml_read_uint (ebml, &id, &track_type)) != GST_FLOW_OK) { + break; + } + + if (context->type != 0 && context->type != track_type) { + GST_WARNING_OBJECT (parse, + "More than one tracktype defined in a TrackEntry - skipping"); + break; + } else if (track_type < 1 || track_type > 254) { + GST_WARNING_OBJECT (parse, "Invalid TrackType %" G_GUINT64_FORMAT, + track_type); + break; + } + + GST_DEBUG_OBJECT (parse, "TrackType: %" G_GUINT64_FORMAT, track_type); + + /* ok, so we're actually going to reallocate this thing */ + switch (track_type) { + case GST_MATROSKA_TRACK_TYPE_VIDEO: + gst_matroska_track_init_video_context (&context); + break; + case GST_MATROSKA_TRACK_TYPE_AUDIO: + gst_matroska_track_init_audio_context (&context); + break; + case GST_MATROSKA_TRACK_TYPE_SUBTITLE: + gst_matroska_track_init_subtitle_context (&context); + break; + case GST_MATROSKA_TRACK_TYPE_COMPLEX: + case GST_MATROSKA_TRACK_TYPE_LOGO: + case GST_MATROSKA_TRACK_TYPE_BUTTONS: + case GST_MATROSKA_TRACK_TYPE_CONTROL: + default: + GST_WARNING_OBJECT (parse, + "Unknown or unsupported TrackType %" G_GUINT64_FORMAT, + track_type); + context->type = 0; + break; + } + g_ptr_array_index (parse->src, parse->num_streams - 1) = context; + break; + } + + /* tracktype specific stuff for video */ + case GST_MATROSKA_ID_TRACKVIDEO:{ + GstMatroskaTrackVideoContext *videocontext; + + DEBUG_ELEMENT_START (parse, ebml, "TrackVideo"); + + if (!gst_matroska_track_init_video_context (&context)) { + GST_WARNING_OBJECT (parse, + "TrackVideo element in non-video track - ignoring track"); + ret = GST_FLOW_ERROR; + break; + } else if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + break; + } + videocontext = (GstMatroskaTrackVideoContext *) context; + g_ptr_array_index (parse->src, parse->num_streams - 1) = context; + + while (ret == GST_FLOW_OK && + gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* Should be one level up but some broken muxers write it here. */ + case GST_MATROSKA_ID_TRACKDEFAULTDURATION:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (parse, "Invalid TrackDefaultDuration 0"); + break; + } + + GST_DEBUG_OBJECT (parse, + "TrackDefaultDuration: %" G_GUINT64_FORMAT, num); + context->default_duration = num; + break; + } + + /* video framerate */ + /* NOTE: This one is here only for backward compatibility. + * Use _TRACKDEFAULDURATION one level up. */ + case GST_MATROSKA_ID_VIDEOFRAMERATE:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num <= 0.0) { + GST_WARNING_OBJECT (parse, "Invalid TrackVideoFPS %lf", num); + break; + } + + GST_DEBUG_OBJECT (parse, "TrackVideoFrameRate: %lf", num); + if (context->default_duration == 0) + context->default_duration = + gst_gdouble_to_guint64 ((gdouble) GST_SECOND * (1.0 / num)); + videocontext->default_fps = num; + break; + } + + /* width of the size to display the video at */ + case GST_MATROSKA_ID_VIDEODISPLAYWIDTH:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (parse, "Invalid TrackVideoDisplayWidth 0"); + break; + } + + GST_DEBUG_OBJECT (parse, + "TrackVideoDisplayWidth: %" G_GUINT64_FORMAT, num); + videocontext->display_width = num; + break; + } + + /* height of the size to display the video at */ + case GST_MATROSKA_ID_VIDEODISPLAYHEIGHT:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (parse, "Invalid TrackVideoDisplayHeight 0"); + break; + } + + GST_DEBUG_OBJECT (parse, + "TrackVideoDisplayHeight: %" G_GUINT64_FORMAT, num); + videocontext->display_height = num; + break; + } + + /* width of the video in the file */ + case GST_MATROSKA_ID_VIDEOPIXELWIDTH:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (parse, "Invalid TrackVideoPixelWidth 0"); + break; + } + + GST_DEBUG_OBJECT (parse, + "TrackVideoPixelWidth: %" G_GUINT64_FORMAT, num); + videocontext->pixel_width = num; + break; + } + + /* height of the video in the file */ + case GST_MATROSKA_ID_VIDEOPIXELHEIGHT:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (parse, "Invalid TrackVideoPixelHeight 0"); + break; + } + + GST_DEBUG_OBJECT (parse, + "TrackVideoPixelHeight: %" G_GUINT64_FORMAT, num); + videocontext->pixel_height = num; + break; + } + + /* whether the video is interlaced */ + case GST_MATROSKA_ID_VIDEOFLAGINTERLACED:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED; + else + context->flags &= ~GST_MATROSKA_VIDEOTRACK_INTERLACED; + GST_DEBUG_OBJECT (parse, "TrackVideoInterlaced: %d", + (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED) ? 1 : + 0); + break; + } + + /* aspect ratio behaviour */ + case GST_MATROSKA_ID_VIDEOASPECTRATIOTYPE:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num != GST_MATROSKA_ASPECT_RATIO_MODE_FREE && + num != GST_MATROSKA_ASPECT_RATIO_MODE_KEEP && + num != GST_MATROSKA_ASPECT_RATIO_MODE_FIXED) { + GST_WARNING_OBJECT (parse, + "Unknown TrackVideoAspectRatioType 0x%x", (guint) num); + break; + } + GST_DEBUG_OBJECT (parse, + "TrackVideoAspectRatioType: %" G_GUINT64_FORMAT, num); + videocontext->asr_mode = num; + break; + } + + /* colourspace (only matters for raw video) fourcc */ + case GST_MATROSKA_ID_VIDEOCOLOURSPACE:{ + guint8 *data; + guint64 datalen; + + if ((ret = + gst_ebml_read_binary (ebml, &id, &data, + &datalen)) != GST_FLOW_OK) + break; + + if (datalen != 4) { + g_free (data); + GST_WARNING_OBJECT (parse, + "Invalid TrackVideoColourSpace length %" G_GUINT64_FORMAT, + datalen); + break; + } + + memcpy (&videocontext->fourcc, data, 4); + GST_DEBUG_OBJECT (parse, + "TrackVideoColourSpace: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (videocontext->fourcc)); + g_free (data); + break; + } + + default: + GST_WARNING_OBJECT (parse, + "Unknown TrackVideo subelement 0x%x - ignoring", id); + /* fall through */ + case GST_MATROSKA_ID_VIDEOSTEREOMODE: + case GST_MATROSKA_ID_VIDEODISPLAYUNIT: + case GST_MATROSKA_ID_VIDEOPIXELCROPBOTTOM: + case GST_MATROSKA_ID_VIDEOPIXELCROPTOP: + case GST_MATROSKA_ID_VIDEOPIXELCROPLEFT: + case GST_MATROSKA_ID_VIDEOPIXELCROPRIGHT: + case GST_MATROSKA_ID_VIDEOGAMMAVALUE: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "TrackVideo", ret); + break; + } + + /* tracktype specific stuff for audio */ + case GST_MATROSKA_ID_TRACKAUDIO:{ + GstMatroskaTrackAudioContext *audiocontext; + + DEBUG_ELEMENT_START (parse, ebml, "TrackAudio"); + + if (!gst_matroska_track_init_audio_context (&context)) { + GST_WARNING_OBJECT (parse, + "TrackAudio element in non-audio track - ignoring track"); + ret = GST_FLOW_ERROR; + break; + } + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) + break; + + audiocontext = (GstMatroskaTrackAudioContext *) context; + g_ptr_array_index (parse->src, parse->num_streams - 1) = context; + + while (ret == GST_FLOW_OK && + gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* samplerate */ + case GST_MATROSKA_ID_AUDIOSAMPLINGFREQ:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + + if (num <= 0.0) { + GST_WARNING_OBJECT (parse, + "Invalid TrackAudioSamplingFrequency %lf", num); + break; + } + + GST_DEBUG_OBJECT (parse, "TrackAudioSamplingFrequency: %lf", num); + audiocontext->samplerate = num; + break; + } + + /* bitdepth */ + case GST_MATROSKA_ID_AUDIOBITDEPTH:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (parse, "Invalid TrackAudioBitDepth 0"); + break; + } + + GST_DEBUG_OBJECT (parse, "TrackAudioBitDepth: %" G_GUINT64_FORMAT, + num); + audiocontext->bitdepth = num; + break; + } + + /* channels */ + case GST_MATROSKA_ID_AUDIOCHANNELS:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (parse, "Invalid TrackAudioChannels 0"); + break; + } + + GST_DEBUG_OBJECT (parse, "TrackAudioChannels: %" G_GUINT64_FORMAT, + num); + audiocontext->channels = num; + break; + } + + default: + GST_WARNING_OBJECT (parse, + "Unknown TrackAudio subelement 0x%x - ignoring", id); + /* fall through */ + case GST_MATROSKA_ID_AUDIOCHANNELPOSITIONS: + case GST_MATROSKA_ID_AUDIOOUTPUTSAMPLINGFREQ: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "TrackAudio", ret); + + break; + } + + /* codec identifier */ + case GST_MATROSKA_ID_CODECID:{ + gchar *text; + + if ((ret = gst_ebml_read_ascii (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (parse, "CodecID: %s", GST_STR_NULL (text)); + context->codec_id = text; + break; + } + + /* codec private data */ + case GST_MATROSKA_ID_CODECPRIVATE:{ + guint8 *data; + guint64 size; + + if ((ret = + gst_ebml_read_binary (ebml, &id, &data, &size)) != GST_FLOW_OK) + break; + + context->codec_priv = data; + context->codec_priv_size = size; + + GST_DEBUG_OBJECT (parse, "CodecPrivate of size %" G_GUINT64_FORMAT, + size); + break; + } + + /* name of the codec */ + case GST_MATROSKA_ID_CODECNAME:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (parse, "CodecName: %s", GST_STR_NULL (text)); + context->codec_name = text; + break; + } + + /* name of this track */ + case GST_MATROSKA_ID_TRACKNAME:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + context->name = text; + GST_DEBUG_OBJECT (parse, "TrackName: %s", GST_STR_NULL (text)); + break; + } + + /* language (matters for audio/subtitles, mostly) */ + case GST_MATROSKA_ID_TRACKLANGUAGE:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + + context->language = text; + + /* fre-ca => fre */ + if (strlen (context->language) >= 4 && context->language[3] == '-') + context->language[3] = '\0'; + + GST_DEBUG_OBJECT (parse, "TrackLanguage: %s", + GST_STR_NULL (context->language)); + break; + } + + /* whether this is actually used */ + case GST_MATROSKA_ID_TRACKFLAGENABLED:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_TRACK_ENABLED; + else + context->flags &= ~GST_MATROSKA_TRACK_ENABLED; + + GST_DEBUG_OBJECT (parse, "TrackEnabled: %d", + (context->flags & GST_MATROSKA_TRACK_ENABLED) ? 1 : 0); + break; + } + + /* whether it's the default for this track type */ + case GST_MATROSKA_ID_TRACKFLAGDEFAULT:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_TRACK_DEFAULT; + else + context->flags &= ~GST_MATROSKA_TRACK_DEFAULT; + + GST_DEBUG_OBJECT (parse, "TrackDefault: %d", + (context->flags & GST_MATROSKA_TRACK_ENABLED) ? 1 : 0); + break; + } + + /* whether the track must be used during playback */ + case GST_MATROSKA_ID_TRACKFLAGFORCED:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_TRACK_FORCED; + else + context->flags &= ~GST_MATROSKA_TRACK_FORCED; + + GST_DEBUG_OBJECT (parse, "TrackForced: %d", + (context->flags & GST_MATROSKA_TRACK_ENABLED) ? 1 : 0); + break; + } + + /* lacing (like MPEG, where blocks don't end/start on frame + * boundaries) */ + case GST_MATROSKA_ID_TRACKFLAGLACING:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num) + context->flags |= GST_MATROSKA_TRACK_LACING; + else + context->flags &= ~GST_MATROSKA_TRACK_LACING; + + GST_DEBUG_OBJECT (parse, "TrackLacing: %d", + (context->flags & GST_MATROSKA_TRACK_ENABLED) ? 1 : 0); + break; + } + + /* default length (in time) of one data block in this track */ + case GST_MATROSKA_ID_TRACKDEFAULTDURATION:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + + if (num == 0) { + GST_WARNING_OBJECT (parse, "Invalid TrackDefaultDuration 0"); + break; + } + + GST_DEBUG_OBJECT (parse, "TrackDefaultDuration: %" G_GUINT64_FORMAT, + num); + context->default_duration = num; + break; + } + + case GST_MATROSKA_ID_CONTENTENCODINGS:{ + ret = gst_matroska_parse_read_track_encodings (parse, ebml, context); + break; + } + + case GST_MATROSKA_ID_TRACKTIMECODESCALE:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num <= 0.0) { + GST_WARNING_OBJECT (parse, "Invalid TrackTimeCodeScale %lf", num); + break; + } + + GST_DEBUG_OBJECT (parse, "TrackTimeCodeScale: %lf", num); + context->timecodescale = num; + break; + } + + default: + GST_WARNING ("Unknown TrackEntry subelement 0x%x - ignoring", id); + /* pass-through */ + + /* we ignore these because they're nothing useful (i.e. crap) + * or simply not implemented yet. */ + case GST_MATROSKA_ID_TRACKMINCACHE: + case GST_MATROSKA_ID_TRACKMAXCACHE: + case GST_MATROSKA_ID_MAXBLOCKADDITIONID: + case GST_MATROSKA_ID_TRACKATTACHMENTLINK: + case GST_MATROSKA_ID_TRACKOVERLAY: + case GST_MATROSKA_ID_TRACKTRANSLATE: + case GST_MATROSKA_ID_TRACKOFFSET: + case GST_MATROSKA_ID_CODECSETTINGS: + case GST_MATROSKA_ID_CODECINFOURL: + case GST_MATROSKA_ID_CODECDOWNLOADURL: + case GST_MATROSKA_ID_CODECDECODEALL: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "TrackEntry", ret); + + /* Decode codec private data if necessary */ + if (context->encodings && context->encodings->len > 0 && context->codec_priv + && context->codec_priv_size > 0) { + if (!gst_matroska_decode_data (context->encodings, + &context->codec_priv, &context->codec_priv_size, + GST_MATROSKA_TRACK_ENCODING_SCOPE_CODEC_DATA, TRUE)) { + GST_WARNING_OBJECT (parse, "Decoding codec private data failed"); + ret = GST_FLOW_ERROR; + } + } + + if (context->type == 0 || context->codec_id == NULL || (ret != GST_FLOW_OK + && ret != GST_FLOW_UNEXPECTED)) { + if (ret == GST_FLOW_OK || ret == GST_FLOW_UNEXPECTED) + GST_WARNING_OBJECT (ebml, "Unknown stream/codec in track entry header"); + + parse->num_streams--; + g_ptr_array_remove_index (parse->src, parse->num_streams); + g_assert (parse->src->len == parse->num_streams); + if (context) { + gst_matroska_track_free (context); + } + + return ret; + } + + if ((context->language == NULL || *context->language == '\0') && + (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO || + context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE)) { + GST_LOG ("stream %d: language=eng (assuming default)", context->index); + context->language = g_strdup ("eng"); + } + + + /* tadaah! */ + return ret; +} + +static const GstQueryType * +gst_matroska_parse_get_src_query_types (GstPad * pad) +{ + static const GstQueryType query_types[] = { + GST_QUERY_POSITION, + GST_QUERY_DURATION, + GST_QUERY_SEEKING, + 0 + }; + + return query_types; +} + +static gboolean +gst_matroska_parse_query (GstMatroskaParse * parse, GstPad * pad, + GstQuery * query) +{ + gboolean res = FALSE; + GstMatroskaTrackContext *context = NULL; + + if (pad) { + context = gst_pad_get_element_private (pad); + } + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + GstFormat format; + + gst_query_parse_position (query, &format, NULL); + + if (format == GST_FORMAT_TIME) { + GST_OBJECT_LOCK (parse); + if (context) + gst_query_set_position (query, GST_FORMAT_TIME, context->pos); + else + gst_query_set_position (query, GST_FORMAT_TIME, + parse->segment.last_stop); + GST_OBJECT_UNLOCK (parse); + } else if (format == GST_FORMAT_DEFAULT && context + && context->default_duration) { + GST_OBJECT_LOCK (parse); + gst_query_set_position (query, GST_FORMAT_DEFAULT, + context->pos / context->default_duration); + GST_OBJECT_UNLOCK (parse); + } else { + GST_DEBUG_OBJECT (parse, + "only position query in TIME and DEFAULT format is supported"); + } + + res = TRUE; + break; + } + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration (query, &format, NULL); + + if (format == GST_FORMAT_TIME) { + GST_OBJECT_LOCK (parse); + gst_query_set_duration (query, GST_FORMAT_TIME, + parse->segment.duration); + GST_OBJECT_UNLOCK (parse); + } else if (format == GST_FORMAT_DEFAULT && context + && context->default_duration) { + GST_OBJECT_LOCK (parse); + gst_query_set_duration (query, GST_FORMAT_DEFAULT, + parse->segment.duration / context->default_duration); + GST_OBJECT_UNLOCK (parse); + } else { + GST_DEBUG_OBJECT (parse, + "only duration query in TIME and DEFAULT format is supported"); + } + + res = TRUE; + break; + } + + case GST_QUERY_SEEKING: + { + GstFormat fmt; + + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + if (fmt == GST_FORMAT_TIME) { + gboolean seekable; + + /* assuming we'll be able to get an index ... */ + seekable = parse->seekable; + + gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, + 0, parse->segment.duration); + res = TRUE; + } + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + + return res; +} + +static gboolean +gst_matroska_parse_element_query (GstElement * element, GstQuery * query) +{ + return gst_matroska_parse_query (GST_MATROSKA_PARSE (element), NULL, query); +} + +static gboolean +gst_matroska_parse_handle_src_query (GstPad * pad, GstQuery * query) +{ + gboolean ret; + GstMatroskaParse *parse = GST_MATROSKA_PARSE (gst_pad_get_parent (pad)); + + ret = gst_matroska_parse_query (parse, pad, query); + + gst_object_unref (parse); + + return ret; +} + +static gint +gst_matroska_index_seek_find (GstMatroskaIndex * i1, GstClockTime * time, + gpointer user_data) +{ + if (i1->time < *time) + return -1; + else if (i1->time > *time) + return 1; + else + return 0; +} + +static GstMatroskaIndex * +gst_matroskaparse_do_index_seek (GstMatroskaParse * parse, + GstMatroskaTrackContext * track, gint64 seek_pos, GArray ** _index, + gint * _entry_index) +{ + GstMatroskaIndex *entry = NULL; + GArray *index; + + if (!parse->index || !parse->index->len) + return NULL; + + /* find entry just before or at the requested position */ + if (track && track->index_table) + index = track->index_table; + else + index = parse->index; + + entry = + gst_util_array_binary_search (index->data, index->len, + sizeof (GstMatroskaIndex), + (GCompareDataFunc) gst_matroska_index_seek_find, GST_SEARCH_MODE_BEFORE, + &seek_pos, NULL); + + if (entry == NULL) + entry = &g_array_index (index, GstMatroskaIndex, 0); + + if (_index) + *_index = index; + if (_entry_index) + *_entry_index = entry - (GstMatroskaIndex *) index->data; + + return entry; +} + +/* takes ownership of taglist */ +static void +gst_matroska_parse_found_global_tag (GstMatroskaParse * parse, + GstTagList * taglist) +{ + if (parse->global_tags) { + /* nothing sent yet, add to cache */ + gst_tag_list_insert (parse->global_tags, taglist, GST_TAG_MERGE_APPEND); + gst_tag_list_free (taglist); + } else { + /* hm, already sent, no need to cache and wait anymore */ + GST_DEBUG_OBJECT (parse, "Sending late global tags %" GST_PTR_FORMAT, + taglist); + gst_element_found_tags (GST_ELEMENT (parse), taglist); + } +} + +/* returns FALSE if there are no pads to deliver event to, + * otherwise TRUE (whatever the outcome of event sending), + * takes ownership of the passed event! */ +static gboolean +gst_matroska_parse_send_event (GstMatroskaParse * parse, GstEvent * event) +{ + gboolean ret = FALSE; + + g_return_val_if_fail (event != NULL, FALSE); + + GST_DEBUG_OBJECT (parse, "Sending event of type %s to all source pads", + GST_EVENT_TYPE_NAME (event)); + + gst_pad_push_event (parse->srcpad, event); + + return ret; +} + +static gboolean +gst_matroska_parse_element_send_event (GstElement * element, GstEvent * event) +{ + GstMatroskaParse *parse = GST_MATROSKA_PARSE (element); + gboolean res; + + g_return_val_if_fail (event != NULL, FALSE); + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) { + res = gst_matroska_parse_handle_seek_event (parse, NULL, event); + } else { + GST_WARNING_OBJECT (parse, "Unhandled event of type %s", + GST_EVENT_TYPE_NAME (event)); + res = FALSE; + } + gst_event_unref (event); + return res; +} + +/* determine track to seek in */ +static GstMatroskaTrackContext * +gst_matroska_parse_get_seek_track (GstMatroskaParse * parse, + GstMatroskaTrackContext * track) +{ + gint i; + + if (track && track->type == GST_MATROSKA_TRACK_TYPE_VIDEO) + return track; + + for (i = 0; i < parse->src->len; i++) { + GstMatroskaTrackContext *stream; + + stream = g_ptr_array_index (parse->src, i); + if (stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO && stream->index_table) + track = stream; + } + + return track; +} + +static void +gst_matroska_parse_reset_streams (GstMatroskaParse * parse, GstClockTime time, + gboolean full) +{ + gint i; + + GST_DEBUG_OBJECT (parse, "resetting stream state"); + + g_assert (parse->src->len == parse->num_streams); + for (i = 0; i < parse->src->len; i++) { + GstMatroskaTrackContext *context = g_ptr_array_index (parse->src, i); + context->pos = time; + context->set_discont = TRUE; + context->eos = FALSE; + context->from_time = GST_CLOCK_TIME_NONE; + if (full) + context->last_flow = GST_FLOW_OK; + if (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO) { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + /* parse object lock held by caller */ + videocontext->earliest_time = GST_CLOCK_TIME_NONE; + } + } +} + +/* searches for a cluster start from @pos, + * return GST_FLOW_OK and cluster position in @pos if found */ +static GstFlowReturn +gst_matroska_parse_search_cluster (GstMatroskaParse * parse, gint64 * pos) +{ + gint64 newpos = *pos; + gint64 orig_offset; + GstFlowReturn ret = GST_FLOW_OK; + const guint chunk = 64 * 1024; + GstBuffer *buf = NULL; + guint64 length; + guint32 id; + guint needed; + + orig_offset = parse->offset; + + /* read in at newpos and scan for ebml cluster id */ + while (1) { + GstByteReader reader; + gint cluster_pos; + + ret = gst_pad_pull_range (parse->sinkpad, newpos, chunk, &buf); + if (ret != GST_FLOW_OK) + break; + GST_DEBUG_OBJECT (parse, "read buffer size %d at offset %" G_GINT64_FORMAT, + GST_BUFFER_SIZE (buf), newpos); + gst_byte_reader_init_from_buffer (&reader, buf); + cluster_pos = 0; + resume: + cluster_pos = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff, + GST_MATROSKA_ID_CLUSTER, cluster_pos, + GST_BUFFER_SIZE (buf) - cluster_pos); + if (cluster_pos >= 0) { + newpos += cluster_pos; + GST_DEBUG_OBJECT (parse, + "found cluster ebml id at offset %" G_GINT64_FORMAT, newpos); + /* extra checks whether we really sync'ed to a cluster: + * - either it is the first and only cluster + * - either there is a cluster after this one + * - either cluster length is undefined + */ + /* ok if first cluster (there may not a subsequent one) */ + if (newpos == parse->first_cluster_offset) { + GST_DEBUG_OBJECT (parse, "cluster is first cluster -> OK"); + break; + } + parse->offset = newpos; + ret = + gst_matroska_parse_peek_id_length_pull (parse, &id, &length, &needed); + if (ret != GST_FLOW_OK) + goto resume; + g_assert (id == GST_MATROSKA_ID_CLUSTER); + GST_DEBUG_OBJECT (parse, "cluster size %" G_GUINT64_FORMAT ", prefix %d", + length, needed); + /* ok if undefined length or first cluster */ + if (length == G_MAXUINT64) { + GST_DEBUG_OBJECT (parse, "cluster has undefined length -> OK"); + break; + } + /* skip cluster */ + parse->offset += length + needed; + ret = + gst_matroska_parse_peek_id_length_pull (parse, &id, &length, &needed); + if (ret != GST_FLOW_OK) + goto resume; + GST_DEBUG_OBJECT (parse, "next element is %scluster", + id == GST_MATROSKA_ID_CLUSTER ? "" : "not "); + if (id == GST_MATROSKA_ID_CLUSTER) + break; + /* not ok, resume */ + goto resume; + } else { + /* partial cluster id may have been in tail of buffer */ + newpos += MAX (GST_BUFFER_SIZE (buf), 4) - 3; + gst_buffer_unref (buf); + buf = NULL; + } + } + + if (buf) { + gst_buffer_unref (buf); + buf = NULL; + } + + parse->offset = orig_offset; + *pos = newpos; + return ret; +} + + +static gboolean +gst_matroska_parse_handle_seek_event (GstMatroskaParse * parse, + GstPad * pad, GstEvent * event) +{ + GstMatroskaIndex *entry = NULL; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + GstFormat format; + gdouble rate; + gint64 cur, stop; + GstMatroskaTrackContext *track = NULL; + GstSegment seeksegment = { 0, }; + gboolean update; + + if (pad) + track = gst_pad_get_element_private (pad); + + track = gst_matroska_parse_get_seek_track (parse, track); + + gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, + &stop_type, &stop); + + /* we can only seek on time */ + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (parse, "Can only seek on TIME"); + return FALSE; + } + + /* copy segment, we need this because we still need the old + * segment when we close the current segment. */ + memcpy (&seeksegment, &parse->segment, sizeof (GstSegment)); + + if (event) { + GST_DEBUG_OBJECT (parse, "configuring seek"); + gst_segment_set_seek (&seeksegment, rate, format, flags, + cur_type, cur, stop_type, stop, &update); + } + + GST_DEBUG_OBJECT (parse, "New segment %" GST_SEGMENT_FORMAT, &seeksegment); + + /* check sanity before we start flushing and all that */ + GST_OBJECT_LOCK (parse); + if ((entry = gst_matroskaparse_do_index_seek (parse, track, + seeksegment.last_stop, &parse->seek_index, &parse->seek_entry)) == + NULL) { + /* pull mode without index can scan later on */ + GST_DEBUG_OBJECT (parse, "No matching seek entry in index"); + GST_OBJECT_UNLOCK (parse); + return FALSE; + } + GST_DEBUG_OBJECT (parse, "Seek position looks sane"); + GST_OBJECT_UNLOCK (parse); + + /* need to seek to cluster start to pick up cluster time */ + /* upstream takes care of flushing and all that + * ... and newsegment event handling takes care of the rest */ + return perform_seek_to_offset (parse, entry->pos + parse->ebml_segment_start); +} + +/* + * Handle whether we can perform the seek event or if we have to let the chain + * function handle seeks to build the seek indexes first. + */ +static gboolean +gst_matroska_parse_handle_seek_push (GstMatroskaParse * parse, GstPad * pad, + GstEvent * event) +{ + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + GstFormat format; + gdouble rate; + gint64 cur, stop; + + gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, + &stop_type, &stop); + + /* sanity checks */ + + /* we can only seek on time */ + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (parse, "Can only seek on TIME"); + return FALSE; + } + + if (stop_type != GST_SEEK_TYPE_NONE && stop != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (parse, "Seek end-time not supported in streaming mode"); + return FALSE; + } + + if (!(flags & GST_SEEK_FLAG_FLUSH)) { + GST_DEBUG_OBJECT (parse, + "Non-flushing seek not supported in streaming mode"); + return FALSE; + } + + if (flags & GST_SEEK_FLAG_SEGMENT) { + GST_DEBUG_OBJECT (parse, "Segment seek not supported in streaming mode"); + return FALSE; + } + + /* check for having parsed index already */ + if (!parse->index_parsed) { + gboolean building_index; + guint64 offset = 0; + + if (!parse->index_offset) { + GST_DEBUG_OBJECT (parse, "no index (location); no seek in push mode"); + return FALSE; + } + + GST_OBJECT_LOCK (parse); + /* handle the seek event in the chain function */ + parse->state = GST_MATROSKA_PARSE_STATE_SEEK; + /* no more seek can be issued until state reset to _DATA */ + + /* copy the event */ + if (parse->seek_event) + gst_event_unref (parse->seek_event); + parse->seek_event = gst_event_ref (event); + + /* set the building_index flag so that only one thread can setup the + * structures for index seeking. */ + building_index = parse->building_index; + if (!building_index) { + parse->building_index = TRUE; + offset = parse->index_offset; + } + GST_OBJECT_UNLOCK (parse); + + if (!building_index) { + /* seek to the first subindex or legacy index */ + GST_INFO_OBJECT (parse, "Seeking to Cues at %" G_GUINT64_FORMAT, offset); + return perform_seek_to_offset (parse, offset); + } + + /* well, we are handling it already */ + return TRUE; + } + + /* delegate to tweaked regular seek */ + return gst_matroska_parse_handle_seek_event (parse, pad, event); +} + +static gboolean +gst_matroska_parse_handle_src_event (GstPad * pad, GstEvent * event) +{ + GstMatroskaParse *parse = GST_MATROSKA_PARSE (gst_pad_get_parent (pad)); + gboolean res = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + /* no seeking until we are (safely) ready */ + if (parse->state != GST_MATROSKA_PARSE_STATE_DATA) { + GST_DEBUG_OBJECT (parse, "not ready for seeking yet"); + return FALSE; + } + res = gst_matroska_parse_handle_seek_push (parse, pad, event); + gst_event_unref (event); + break; + + case GST_EVENT_QOS: + { + GstMatroskaTrackContext *context = gst_pad_get_element_private (pad); + if (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO) { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + gdouble proportion; + GstClockTimeDiff diff; + GstClockTime timestamp; + + gst_event_parse_qos (event, &proportion, &diff, ×tamp); + + GST_OBJECT_LOCK (parse); + videocontext->earliest_time = timestamp + diff; + GST_OBJECT_UNLOCK (parse); + } + res = TRUE; + gst_event_unref (event); + break; + } + + /* events we don't need to handle */ + case GST_EVENT_NAVIGATION: + gst_event_unref (event); + res = FALSE; + break; + + case GST_EVENT_LATENCY: + default: + res = gst_pad_push_event (parse->sinkpad, event); + break; + } + + gst_object_unref (parse); + + return res; +} + + +/* skip unknown or alike element */ +static GstFlowReturn +gst_matroska_parse_parse_skip (GstMatroskaParse * parse, GstEbmlRead * ebml, + const gchar * parent_name, guint id) +{ + if (id == GST_EBML_ID_VOID) { + GST_DEBUG_OBJECT (parse, "Skipping EBML Void element"); + } else if (id == GST_EBML_ID_CRC32) { + GST_DEBUG_OBJECT (parse, "Skipping EBML CRC32 element"); + } else { + GST_WARNING_OBJECT (parse, + "Unknown %s subelement 0x%x - ignoring", parent_name, id); + } + + return gst_ebml_read_skip (ebml); +} + +static GstFlowReturn +gst_matroska_parse_parse_header (GstMatroskaParse * parse, GstEbmlRead * ebml) +{ + GstFlowReturn ret; + gchar *doctype; + guint version; + guint32 id; + + /* this function is the first to be called */ + + /* default init */ + doctype = NULL; + version = 1; + + ret = gst_ebml_peek_id (ebml, &id); + if (ret != GST_FLOW_OK) + return ret; + + GST_DEBUG_OBJECT (parse, "id: %08x", id); + + if (id != GST_EBML_ID_HEADER) { + GST_ERROR_OBJECT (parse, "Failed to read header"); + goto exit; + } + + ret = gst_ebml_read_master (ebml, &id); + if (ret != GST_FLOW_OK) + return ret; + + while (gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + ret = gst_ebml_peek_id (ebml, &id); + if (ret != GST_FLOW_OK) + return ret; + + switch (id) { + /* is our read version uptodate? */ + case GST_EBML_ID_EBMLREADVERSION:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + if (num != GST_EBML_VERSION) { + GST_ERROR_OBJECT (ebml, "Unsupported EBML version %" G_GUINT64_FORMAT, + num); + return GST_FLOW_ERROR; + } + + GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); + break; + } + + /* we only handle 8 byte lengths at max */ + case GST_EBML_ID_EBMLMAXSIZELENGTH:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + if (num > sizeof (guint64)) { + GST_ERROR_OBJECT (ebml, + "Unsupported EBML maximum size %" G_GUINT64_FORMAT, num); + return GST_FLOW_ERROR; + } + GST_DEBUG_OBJECT (ebml, "EbmlMaxSizeLength: %" G_GUINT64_FORMAT, num); + break; + } + + /* we handle 4 byte IDs at max */ + case GST_EBML_ID_EBMLMAXIDLENGTH:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + if (num > sizeof (guint32)) { + GST_ERROR_OBJECT (ebml, + "Unsupported EBML maximum ID %" G_GUINT64_FORMAT, num); + return GST_FLOW_ERROR; + } + GST_DEBUG_OBJECT (ebml, "EbmlMaxIdLength: %" G_GUINT64_FORMAT, num); + break; + } + + case GST_EBML_ID_DOCTYPE:{ + gchar *text; + + ret = gst_ebml_read_ascii (ebml, &id, &text); + if (ret != GST_FLOW_OK) + return ret; + + GST_DEBUG_OBJECT (ebml, "EbmlDocType: %s", GST_STR_NULL (text)); + + if (doctype) + g_free (doctype); + doctype = text; + break; + } + + case GST_EBML_ID_DOCTYPEREADVERSION:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + version = num; + GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); + break; + } + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "EBML header", id); + if (ret != GST_FLOW_OK) + return ret; + break; + + /* we ignore these two, as they don't tell us anything we care about */ + case GST_EBML_ID_EBMLVERSION: + case GST_EBML_ID_DOCTYPEVERSION: + ret = gst_ebml_read_skip (ebml); + if (ret != GST_FLOW_OK) + return ret; + break; + } + } + +exit: + + if ((doctype != NULL && !strcmp (doctype, GST_MATROSKA_DOCTYPE_MATROSKA)) || + (doctype != NULL && !strcmp (doctype, GST_MATROSKA_DOCTYPE_WEBM)) || + (doctype == NULL)) { + if (version <= 2) { + if (doctype) { + GST_INFO_OBJECT (parse, "Input is %s version %d", doctype, version); + } else { + GST_WARNING_OBJECT (parse, "Input is EBML without doctype, assuming " + "matroska (version %d)", version); + } + ret = GST_FLOW_OK; + } else { + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, (NULL), + ("Parser version (2) is too old to read %s version %d", + GST_STR_NULL (doctype), version)); + ret = GST_FLOW_ERROR; + } + g_free (doctype); + } else { + GST_ELEMENT_ERROR (parse, STREAM, WRONG_TYPE, (NULL), + ("Input is not a matroska stream (doctype=%s)", doctype)); + ret = GST_FLOW_ERROR; + g_free (doctype); + } + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_tracks (GstMatroskaParse * parse, GstEbmlRead * ebml) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + + DEBUG_ELEMENT_START (parse, ebml, "Tracks"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "Tracks", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* one track within the "all-tracks" header */ + case GST_MATROSKA_ID_TRACKENTRY: + ret = gst_matroska_parse_add_stream (parse, ebml); + break; + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "Track", id); + break; + } + } + DEBUG_ELEMENT_STOP (parse, ebml, "Tracks", ret); + + parse->tracks_parsed = TRUE; + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_index_cuetrack (GstMatroskaParse * parse, + GstEbmlRead * ebml, guint * nentries) +{ + guint32 id; + GstFlowReturn ret; + GstMatroskaIndex idx; + + idx.pos = (guint64) - 1; + idx.track = 0; + idx.time = GST_CLOCK_TIME_NONE; + idx.block = 1; + + DEBUG_ELEMENT_START (parse, ebml, "CueTrackPositions"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "CueTrackPositions", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* track number */ + case GST_MATROSKA_ID_CUETRACK: + { + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + idx.track = 0; + GST_WARNING_OBJECT (parse, "Invalid CueTrack 0"); + break; + } + + GST_DEBUG_OBJECT (parse, "CueTrack: %" G_GUINT64_FORMAT, num); + idx.track = num; + break; + } + + /* position in file */ + case GST_MATROSKA_ID_CUECLUSTERPOSITION: + { + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num > G_MAXINT64) { + GST_WARNING_OBJECT (parse, "CueClusterPosition %" G_GUINT64_FORMAT + " too large", num); + break; + } + + idx.pos = num; + break; + } + + /* number of block in the cluster */ + case GST_MATROSKA_ID_CUEBLOCKNUMBER: + { + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (parse, "Invalid CueBlockNumber 0"); + break; + } + + GST_DEBUG_OBJECT (parse, "CueBlockNumber: %" G_GUINT64_FORMAT, num); + idx.block = num; + + /* mild sanity check, disregard strange cases ... */ + if (idx.block > G_MAXUINT16) { + GST_DEBUG_OBJECT (parse, "... looks suspicious, ignoring"); + idx.block = 1; + } + break; + } + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "CueTrackPositions", + id); + break; + + case GST_MATROSKA_ID_CUECODECSTATE: + case GST_MATROSKA_ID_CUEREFERENCE: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "CueTrackPositions", ret); + + if ((ret == GST_FLOW_OK || ret == GST_FLOW_UNEXPECTED) + && idx.pos != (guint64) - 1 && idx.track > 0) { + g_array_append_val (parse->index, idx); + (*nentries)++; + } else if (ret == GST_FLOW_OK || ret == GST_FLOW_UNEXPECTED) { + GST_DEBUG_OBJECT (parse, "CueTrackPositions without valid content"); + } + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_index_pointentry (GstMatroskaParse * parse, + GstEbmlRead * ebml) +{ + guint32 id; + GstFlowReturn ret; + GstClockTime time = GST_CLOCK_TIME_NONE; + guint nentries = 0; + + DEBUG_ELEMENT_START (parse, ebml, "CuePoint"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "CuePoint", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* one single index entry ('point') */ + case GST_MATROSKA_ID_CUETIME: + { + if ((ret = gst_ebml_read_uint (ebml, &id, &time)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (parse, "CueTime: %" G_GUINT64_FORMAT, time); + time = time * parse->time_scale; + break; + } + + /* position in the file + track to which it belongs */ + case GST_MATROSKA_ID_CUETRACKPOSITIONS: + { + if ((ret = + gst_matroska_parse_parse_index_cuetrack (parse, ebml, + &nentries)) != GST_FLOW_OK) + break; + break; + } + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "CuePoint", id); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "CuePoint", ret); + + if (nentries > 0) { + if (time == GST_CLOCK_TIME_NONE) { + GST_WARNING_OBJECT (parse, "CuePoint without valid time"); + g_array_remove_range (parse->index, parse->index->len - nentries, + nentries); + } else { + gint i; + + for (i = parse->index->len - nentries; i < parse->index->len; i++) { + GstMatroskaIndex *idx = + &g_array_index (parse->index, GstMatroskaIndex, i); + + idx->time = time; + GST_DEBUG_OBJECT (parse, "Index entry: pos=%" G_GUINT64_FORMAT + ", time=%" GST_TIME_FORMAT ", track=%u, block=%u", idx->pos, + GST_TIME_ARGS (idx->time), (guint) idx->track, (guint) idx->block); + } + } + } else { + GST_DEBUG_OBJECT (parse, "Empty CuePoint"); + } + + return ret; +} + +static gint +gst_matroska_index_compare (GstMatroskaIndex * i1, GstMatroskaIndex * i2) +{ + if (i1->time < i2->time) + return -1; + else if (i1->time > i2->time) + return 1; + else if (i1->block < i2->block) + return -1; + else if (i1->block > i2->block) + return 1; + else + return 0; +} + +static GstFlowReturn +gst_matroska_parse_parse_index (GstMatroskaParse * parse, GstEbmlRead * ebml) +{ + guint32 id; + GstFlowReturn ret = GST_FLOW_OK; + guint i; + + if (parse->index) + g_array_free (parse->index, TRUE); + parse->index = + g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaIndex), 128); + + DEBUG_ELEMENT_START (parse, ebml, "Cues"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "Cues", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* one single index entry ('point') */ + case GST_MATROSKA_ID_POINTENTRY: + ret = gst_matroska_parse_parse_index_pointentry (parse, ebml); + break; + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "Cues", id); + break; + } + } + DEBUG_ELEMENT_STOP (parse, ebml, "Cues", ret); + + /* Sort index by time, smallest time first, for easier searching */ + g_array_sort (parse->index, (GCompareFunc) gst_matroska_index_compare); + + /* Now sort the track specific index entries into their own arrays */ + for (i = 0; i < parse->index->len; i++) { + GstMatroskaIndex *idx = &g_array_index (parse->index, GstMatroskaIndex, i); + gint track_num; + GstMatroskaTrackContext *ctx; + + if (parse->element_index) { + gint writer_id; + + if (idx->track != 0 && + (track_num = + gst_matroska_parse_stream_from_num (parse, idx->track)) != -1) { + ctx = g_ptr_array_index (parse->src, track_num); + + if (ctx->index_writer_id == -1) + gst_index_get_writer_id (parse->element_index, GST_OBJECT (ctx->pad), + &ctx->index_writer_id); + writer_id = ctx->index_writer_id; + } else { + if (parse->element_index_writer_id == -1) + gst_index_get_writer_id (parse->element_index, GST_OBJECT (parse), + &parse->element_index_writer_id); + writer_id = parse->element_index_writer_id; + } + + GST_LOG_OBJECT (parse, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT " for writer id %d", GST_TIME_ARGS (idx->time), + idx->pos, writer_id); + gst_index_add_association (parse->element_index, writer_id, + GST_ASSOCIATION_FLAG_KEY_UNIT, GST_FORMAT_TIME, idx->time, + GST_FORMAT_BYTES, idx->pos + parse->ebml_segment_start, NULL); + } + + if (idx->track == 0) + continue; + + track_num = gst_matroska_parse_stream_from_num (parse, idx->track); + if (track_num == -1) + continue; + + ctx = g_ptr_array_index (parse->src, track_num); + + if (ctx->index_table == NULL) + ctx->index_table = + g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaIndex), 128); + + g_array_append_vals (ctx->index_table, idx, 1); + } + + parse->index_parsed = TRUE; + + /* sanity check; empty index normalizes to no index */ + if (parse->index->len == 0) { + g_array_free (parse->index, TRUE); + parse->index = NULL; + } + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_info (GstMatroskaParse * parse, GstEbmlRead * ebml) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + + DEBUG_ELEMENT_START (parse, ebml, "SegmentInfo"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "SegmentInfo", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + /* cluster timecode */ + case GST_MATROSKA_ID_TIMECODESCALE:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + + GST_DEBUG_OBJECT (parse, "TimeCodeScale: %" G_GUINT64_FORMAT, num); + parse->time_scale = num; + break; + } + + case GST_MATROSKA_ID_DURATION:{ + gdouble num; + GstClockTime dur; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num <= 0.0) { + GST_WARNING_OBJECT (parse, "Invalid duration %lf", num); + break; + } + + GST_DEBUG_OBJECT (parse, "Duration: %lf", num); + + dur = gst_gdouble_to_guint64 (num * + gst_guint64_to_gdouble (parse->time_scale)); + if (GST_CLOCK_TIME_IS_VALID (dur) && dur <= G_MAXINT64) + gst_segment_set_duration (&parse->segment, GST_FORMAT_TIME, dur); + break; + } + + case GST_MATROSKA_ID_WRITINGAPP:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (parse, "WritingApp: %s", GST_STR_NULL (text)); + parse->writing_app = text; + break; + } + + case GST_MATROSKA_ID_MUXINGAPP:{ + gchar *text; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (parse, "MuxingApp: %s", GST_STR_NULL (text)); + parse->muxing_app = text; + break; + } + + case GST_MATROSKA_ID_DATEUTC:{ + gint64 time; + + if ((ret = gst_ebml_read_date (ebml, &id, &time)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (parse, "DateUTC: %" G_GINT64_FORMAT, time); + parse->created = time; + break; + } + + case GST_MATROSKA_ID_TITLE:{ + gchar *text; + GstTagList *taglist; + + if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (parse, "Title: %s", GST_STR_NULL (text)); + taglist = gst_tag_list_new (); + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, text, + NULL); + gst_matroska_parse_found_global_tag (parse, taglist); + g_free (text); + break; + } + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "SegmentInfo", id); + break; + + /* fall through */ + case GST_MATROSKA_ID_SEGMENTUID: + case GST_MATROSKA_ID_SEGMENTFILENAME: + case GST_MATROSKA_ID_PREVUID: + case GST_MATROSKA_ID_PREVFILENAME: + case GST_MATROSKA_ID_NEXTUID: + case GST_MATROSKA_ID_NEXTFILENAME: + case GST_MATROSKA_ID_SEGMENTFAMILY: + case GST_MATROSKA_ID_CHAPTERTRANSLATE: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "SegmentInfo", ret); + + parse->segmentinfo_parsed = TRUE; + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_metadata_id_simple_tag (GstMatroskaParse * parse, + GstEbmlRead * ebml, GstTagList ** p_taglist) +{ + /* FIXME: check if there are more useful mappings */ + struct + { + const gchar *matroska_tagname; + const gchar *gstreamer_tagname; + } + tag_conv[] = { + { + GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, { + GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, { + GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, { + GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, { + GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, { + GST_MATROSKA_TAG_ID_BPS, GST_TAG_BITRATE}, { + GST_MATROSKA_TAG_ID_ENCODER, GST_TAG_ENCODER}, { + GST_MATROSKA_TAG_ID_DATE, GST_TAG_DATE}, { + GST_MATROSKA_TAG_ID_ISRC, GST_TAG_ISRC}, { + GST_MATROSKA_TAG_ID_COPYRIGHT, GST_TAG_COPYRIGHT}, { + GST_MATROSKA_TAG_ID_BPM, GST_TAG_BEATS_PER_MINUTE}, { + GST_MATROSKA_TAG_ID_TERMS_OF_USE, GST_TAG_LICENSE}, { + GST_MATROSKA_TAG_ID_COMPOSER, GST_TAG_COMPOSER}, { + GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, { + GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE} + }; + GstFlowReturn ret; + guint32 id; + gchar *value = NULL; + gchar *tag = NULL; + + DEBUG_ELEMENT_START (parse, ebml, "SimpleTag"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "SimpleTag", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + /* read all sub-entries */ + + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_TAGNAME: + g_free (tag); + tag = NULL; + ret = gst_ebml_read_ascii (ebml, &id, &tag); + GST_DEBUG_OBJECT (parse, "TagName: %s", GST_STR_NULL (tag)); + break; + + case GST_MATROSKA_ID_TAGSTRING: + g_free (value); + value = NULL; + ret = gst_ebml_read_utf8 (ebml, &id, &value); + GST_DEBUG_OBJECT (parse, "TagString: %s", GST_STR_NULL (value)); + break; + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "SimpleTag", id); + break; + /* fall-through */ + + case GST_MATROSKA_ID_TAGLANGUAGE: + case GST_MATROSKA_ID_TAGDEFAULT: + case GST_MATROSKA_ID_TAGBINARY: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "SimpleTag", ret); + + if (tag && value) { + guint i; + + for (i = 0; i < G_N_ELEMENTS (tag_conv); i++) { + const gchar *tagname_gst = tag_conv[i].gstreamer_tagname; + + const gchar *tagname_mkv = tag_conv[i].matroska_tagname; + + if (strcmp (tagname_mkv, tag) == 0) { + GValue dest = { 0, }; + GType dest_type = gst_tag_get_type (tagname_gst); + + /* Ensure that any date string is complete */ + if (dest_type == GST_TYPE_DATE) { + guint year = 1901, month = 1, day = 1; + + /* Dates can be yyyy-MM-dd, yyyy-MM or yyyy, but we need + * the first type */ + if (sscanf (value, "%04u-%02u-%02u", &year, &month, &day) != 0) { + g_free (value); + value = g_strdup_printf ("%04u-%02u-%02u", year, month, day); + } + } + + g_value_init (&dest, dest_type); + if (gst_value_deserialize (&dest, value)) { + gst_tag_list_add_values (*p_taglist, GST_TAG_MERGE_APPEND, + tagname_gst, &dest, NULL); + } else { + GST_WARNING_OBJECT (parse, "Can't transform tag '%s' with " + "value '%s' to target type '%s'", tag, value, + g_type_name (dest_type)); + } + g_value_unset (&dest); + break; + } + } + } + + g_free (tag); + g_free (value); + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_metadata_id_tag (GstMatroskaParse * parse, + GstEbmlRead * ebml, GstTagList ** p_taglist) +{ + guint32 id; + GstFlowReturn ret; + + DEBUG_ELEMENT_START (parse, ebml, "Tag"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "Tag", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + /* read all sub-entries */ + + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_SIMPLETAG: + ret = gst_matroska_parse_parse_metadata_id_simple_tag (parse, ebml, + p_taglist); + break; + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "Tag", id); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "Tag", ret); + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_metadata (GstMatroskaParse * parse, GstEbmlRead * ebml) +{ + GstTagList *taglist; + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + GList *l; + guint64 curpos; + + curpos = gst_ebml_read_get_pos (ebml); + + /* Make sure we don't parse a tags element twice and + * post it's tags twice */ + curpos = gst_ebml_read_get_pos (ebml); + for (l = parse->tags_parsed; l; l = l->next) { + guint64 *pos = l->data; + + if (*pos == curpos) { + GST_DEBUG_OBJECT (parse, "Skipping already parsed Tags at offset %" + G_GUINT64_FORMAT, curpos); + return GST_FLOW_OK; + } + } + + parse->tags_parsed = + g_list_prepend (parse->tags_parsed, g_slice_new (guint64)); + *((guint64 *) parse->tags_parsed->data) = curpos; + /* fall-through */ + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "Tags", ret); + return ret; + } + + taglist = gst_tag_list_new (); + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_TAG: + ret = gst_matroska_parse_parse_metadata_id_tag (parse, ebml, &taglist); + break; + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "Tags", id); + break; + /* FIXME: Use to limit the tags to specific pads */ + case GST_MATROSKA_ID_TARGETS: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "Tags", ret); + + gst_matroska_parse_found_global_tag (parse, taglist); + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_attached_file (GstMatroskaParse * parse, + GstEbmlRead * ebml, GstTagList * taglist) +{ + guint32 id; + GstFlowReturn ret; + gchar *description = NULL; + gchar *filename = NULL; + gchar *mimetype = NULL; + guint8 *data = NULL; + guint64 datalen = 0; + + DEBUG_ELEMENT_START (parse, ebml, "AttachedFile"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "AttachedFile", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + /* read all sub-entries */ + + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_FILEDESCRIPTION: + if (description) { + GST_WARNING_OBJECT (parse, "FileDescription can only appear once"); + break; + } + + ret = gst_ebml_read_utf8 (ebml, &id, &description); + GST_DEBUG_OBJECT (parse, "FileDescription: %s", + GST_STR_NULL (description)); + break; + case GST_MATROSKA_ID_FILENAME: + if (filename) { + GST_WARNING_OBJECT (parse, "FileName can only appear once"); + break; + } + + ret = gst_ebml_read_utf8 (ebml, &id, &filename); + + GST_DEBUG_OBJECT (parse, "FileName: %s", GST_STR_NULL (filename)); + break; + case GST_MATROSKA_ID_FILEMIMETYPE: + if (mimetype) { + GST_WARNING_OBJECT (parse, "FileMimeType can only appear once"); + break; + } + + ret = gst_ebml_read_ascii (ebml, &id, &mimetype); + GST_DEBUG_OBJECT (parse, "FileMimeType: %s", GST_STR_NULL (mimetype)); + break; + case GST_MATROSKA_ID_FILEDATA: + if (data) { + GST_WARNING_OBJECT (parse, "FileData can only appear once"); + break; + } + + ret = gst_ebml_read_binary (ebml, &id, &data, &datalen); + GST_DEBUG_OBJECT (parse, "FileData of size %" G_GUINT64_FORMAT, + datalen); + break; + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "AttachedFile", id); + break; + case GST_MATROSKA_ID_FILEUID: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "AttachedFile", ret); + + if (filename && mimetype && data && datalen > 0) { + GstTagImageType image_type = GST_TAG_IMAGE_TYPE_NONE; + GstBuffer *tagbuffer = NULL; + GstCaps *caps; + gchar *filename_lc = g_utf8_strdown (filename, -1); + + GST_DEBUG_OBJECT (parse, "Creating tag for attachment with filename '%s', " + "mimetype '%s', description '%s', size %" G_GUINT64_FORMAT, filename, + mimetype, GST_STR_NULL (description), datalen); + + /* TODO: better heuristics for different image types */ + if (strstr (filename_lc, "cover")) { + if (strstr (filename_lc, "back")) + image_type = GST_TAG_IMAGE_TYPE_BACK_COVER; + else + image_type = GST_TAG_IMAGE_TYPE_FRONT_COVER; + } else if (g_str_has_prefix (mimetype, "image/") || + g_str_has_suffix (filename_lc, "png") || + g_str_has_suffix (filename_lc, "jpg") || + g_str_has_suffix (filename_lc, "jpeg") || + g_str_has_suffix (filename_lc, "gif") || + g_str_has_suffix (filename_lc, "bmp")) { + image_type = GST_TAG_IMAGE_TYPE_UNDEFINED; + } + g_free (filename_lc); + + /* First try to create an image tag buffer from this */ + if (image_type != GST_TAG_IMAGE_TYPE_NONE) { + tagbuffer = + gst_tag_image_data_to_image_buffer (data, datalen, image_type); + + if (!tagbuffer) + image_type = GST_TAG_IMAGE_TYPE_NONE; + } + + /* if this failed create an attachment buffer */ + if (!tagbuffer) { + tagbuffer = gst_buffer_new_and_alloc (datalen); + + memcpy (GST_BUFFER_DATA (tagbuffer), data, datalen); + GST_BUFFER_SIZE (tagbuffer) = datalen; + + caps = gst_type_find_helper_for_buffer (NULL, tagbuffer, NULL); + if (caps == NULL) + caps = gst_caps_new_simple (mimetype, NULL); + gst_buffer_set_caps (tagbuffer, caps); + gst_caps_unref (caps); + } + + /* Set filename and description on the caps */ + caps = GST_BUFFER_CAPS (tagbuffer); + gst_caps_set_simple (caps, "filename", G_TYPE_STRING, filename, NULL); + if (description) + gst_caps_set_simple (caps, "description", G_TYPE_STRING, description, + NULL); + + GST_DEBUG_OBJECT (parse, + "Created attachment buffer with caps: %" GST_PTR_FORMAT, caps); + + /* and append to the tag list */ + if (image_type != GST_TAG_IMAGE_TYPE_NONE) + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, GST_TAG_IMAGE, tagbuffer, + NULL); + else + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, GST_TAG_ATTACHMENT, + tagbuffer, NULL); + } + + g_free (filename); + g_free (mimetype); + g_free (data); + g_free (description); + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_attachments (GstMatroskaParse * parse, + GstEbmlRead * ebml) +{ + guint32 id; + GstFlowReturn ret = GST_FLOW_OK; + GstTagList *taglist; + + DEBUG_ELEMENT_START (parse, ebml, "Attachments"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "Attachments", ret); + return ret; + } + + taglist = gst_tag_list_new (); + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_ATTACHEDFILE: + ret = gst_matroska_parse_parse_attached_file (parse, ebml, taglist); + break; + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "Attachments", id); + break; + } + } + DEBUG_ELEMENT_STOP (parse, ebml, "Attachments", ret); + + if (gst_structure_n_fields (GST_STRUCTURE (taglist)) > 0) { + GST_DEBUG_OBJECT (parse, "Storing attachment tags"); + gst_matroska_parse_found_global_tag (parse, taglist); + } else { + GST_DEBUG_OBJECT (parse, "No valid attachments found"); + gst_tag_list_free (taglist); + } + + parse->attachments_parsed = TRUE; + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_chapters (GstMatroskaParse * parse, GstEbmlRead * ebml) +{ + guint32 id; + GstFlowReturn ret = GST_FLOW_OK; + + GST_WARNING_OBJECT (parse, "Parsing of chapters not implemented yet"); + + /* TODO: implement parsing of chapters */ + + DEBUG_ELEMENT_START (parse, ebml, "Chapters"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "Chapters", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + default: + ret = gst_ebml_read_skip (ebml); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "Chapters", ret); + return ret; +} + +/* + * Read signed/unsigned "EBML" numbers. + * Return: number of bytes processed. + */ + +static gint +gst_matroska_ebmlnum_uint (guint8 * data, guint size, guint64 * num) +{ + gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; + guint64 total; + + if (size <= 0) { + return -1; + } + + total = data[0]; + while (read <= 8 && !(total & len_mask)) { + read++; + len_mask >>= 1; + } + if (read > 8) + return -1; + + if ((total &= (len_mask - 1)) == len_mask - 1) + num_ffs++; + if (size < read) + return -1; + while (n < read) { + if (data[n] == 0xff) + num_ffs++; + total = (total << 8) | data[n]; + n++; + } + + if (read == num_ffs && total != 0) + *num = G_MAXUINT64; + else + *num = total; + + return read; +} + +static gint +gst_matroska_ebmlnum_sint (guint8 * data, guint size, gint64 * num) +{ + guint64 unum; + gint res; + + /* read as unsigned number first */ + if ((res = gst_matroska_ebmlnum_uint (data, size, &unum)) < 0) + return -1; + + /* make signed */ + if (unum == G_MAXUINT64) + *num = G_MAXINT64; + else + *num = unum - ((1 << ((7 * res) - 1)) - 1); + + return res; +} + +static GstFlowReturn +gst_matroska_parse_parse_blockgroup_or_simpleblock (GstMatroskaParse * parse, + GstEbmlRead * ebml, guint64 cluster_time, guint64 cluster_offset, + gboolean is_simpleblock) +{ + GstMatroskaTrackContext *stream = NULL; + GstFlowReturn ret = GST_FLOW_OK; + gboolean readblock = FALSE; + guint32 id; + guint64 block_duration = 0; + GstBuffer *buf = NULL; + gint stream_num = -1, n, laces = 0; + guint size = 0; + gint *lace_size = NULL; + gint64 time = 0; + gint flags = 0; + gint64 referenceblock = 0; + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if (!is_simpleblock) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) { + goto data_error; + } + } else { + id = GST_MATROSKA_ID_SIMPLEBLOCK; + } + + switch (id) { + /* one block inside the group. Note, block parsing is one + * of the harder things, so this code is a bit complicated. + * See http://www.matroska.org/ for documentation. */ + case GST_MATROSKA_ID_SIMPLEBLOCK: + case GST_MATROSKA_ID_BLOCK: + { + guint64 num; + guint8 *data; + + if (buf) { + gst_buffer_unref (buf); + buf = NULL; + } + if ((ret = gst_ebml_read_buffer (ebml, &id, &buf)) != GST_FLOW_OK) + break; + + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + + /* first byte(s): blocknum */ + if ((n = gst_matroska_ebmlnum_uint (data, size, &num)) < 0) + goto data_error; + data += n; + size -= n; + + /* fetch stream from num */ + stream_num = gst_matroska_parse_stream_from_num (parse, num); + if (G_UNLIKELY (size < 3)) { + GST_WARNING_OBJECT (parse, "Invalid size %u", size); + /* non-fatal, try next block(group) */ + ret = GST_FLOW_OK; + goto done; + } else if (G_UNLIKELY (stream_num < 0 || + stream_num >= parse->num_streams)) { + /* let's not give up on a stray invalid track number */ + GST_WARNING_OBJECT (parse, + "Invalid stream %d for track number %" G_GUINT64_FORMAT + "; ignoring block", stream_num, num); + goto done; + } + + stream = g_ptr_array_index (parse->src, stream_num); + + /* time (relative to cluster time) */ + time = ((gint16) GST_READ_UINT16_BE (data)); + data += 2; + size -= 2; + flags = GST_READ_UINT8 (data); + data += 1; + size -= 1; + + GST_LOG_OBJECT (parse, "time %" G_GUINT64_FORMAT ", flags %d", time, + flags); + + switch ((flags & 0x06) >> 1) { + case 0x0: /* no lacing */ + laces = 1; + lace_size = g_new (gint, 1); + lace_size[0] = size; + break; + + case 0x1: /* xiph lacing */ + case 0x2: /* fixed-size lacing */ + case 0x3: /* EBML lacing */ + if (size == 0) + goto invalid_lacing; + laces = GST_READ_UINT8 (data) + 1; + data += 1; + size -= 1; + lace_size = g_new0 (gint, laces); + + switch ((flags & 0x06) >> 1) { + case 0x1: /* xiph lacing */ { + guint temp, total = 0; + + for (n = 0; ret == GST_FLOW_OK && n < laces - 1; n++) { + while (1) { + if (size == 0) + goto invalid_lacing; + temp = GST_READ_UINT8 (data); + lace_size[n] += temp; + data += 1; + size -= 1; + if (temp != 0xff) + break; + } + total += lace_size[n]; + } + lace_size[n] = size - total; + break; + } + + case 0x2: /* fixed-size lacing */ + for (n = 0; n < laces; n++) + lace_size[n] = size / laces; + break; + + case 0x3: /* EBML lacing */ { + guint total; + + if ((n = gst_matroska_ebmlnum_uint (data, size, &num)) < 0) + goto data_error; + data += n; + size -= n; + total = lace_size[0] = num; + for (n = 1; ret == GST_FLOW_OK && n < laces - 1; n++) { + gint64 snum; + gint r; + + if ((r = gst_matroska_ebmlnum_sint (data, size, &snum)) < 0) + goto data_error; + data += r; + size -= r; + lace_size[n] = lace_size[n - 1] + snum; + total += lace_size[n]; + } + if (n < laces) + lace_size[n] = size - total; + break; + } + } + break; + } + + if (ret != GST_FLOW_OK) + break; + + readblock = TRUE; + break; + } + + case GST_MATROSKA_ID_BLOCKDURATION:{ + ret = gst_ebml_read_uint (ebml, &id, &block_duration); + GST_DEBUG_OBJECT (parse, "BlockDuration: %" G_GUINT64_FORMAT, + block_duration); + break; + } + + case GST_MATROSKA_ID_REFERENCEBLOCK:{ + ret = gst_ebml_read_sint (ebml, &id, &referenceblock); + GST_DEBUG_OBJECT (parse, "ReferenceBlock: %" G_GINT64_FORMAT, + referenceblock); + break; + } + + case GST_MATROSKA_ID_CODECSTATE:{ + guint8 *data; + guint64 data_len = 0; + + if ((ret = + gst_ebml_read_binary (ebml, &id, &data, + &data_len)) != GST_FLOW_OK) + break; + + if (G_UNLIKELY (stream == NULL)) { + GST_WARNING_OBJECT (parse, + "Unexpected CodecState subelement - ignoring"); + break; + } + + g_free (stream->codec_state); + stream->codec_state = data; + stream->codec_state_size = data_len; + + break; + } + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "BlockGroup", id); + break; + + case GST_MATROSKA_ID_BLOCKVIRTUAL: + case GST_MATROSKA_ID_BLOCKADDITIONS: + case GST_MATROSKA_ID_REFERENCEPRIORITY: + case GST_MATROSKA_ID_REFERENCEVIRTUAL: + case GST_MATROSKA_ID_SLICES: + GST_DEBUG_OBJECT (parse, + "Skipping BlockGroup subelement 0x%x - ignoring", id); + ret = gst_ebml_read_skip (ebml); + break; + } + + if (is_simpleblock) + break; + } + + /* reading a number or so could have failed */ + if (ret != GST_FLOW_OK) + goto data_error; + + if (ret == GST_FLOW_OK && readblock) { + guint64 duration = 0; + gint64 lace_time = 0; + gboolean delta_unit; + + stream = g_ptr_array_index (parse->src, stream_num); + + if (cluster_time != GST_CLOCK_TIME_NONE) { + /* FIXME: What to do with negative timestamps? Give timestamp 0 or -1? + * Drop unless the lace contains timestamp 0? */ + if (time < 0 && (-time) > cluster_time) { + lace_time = 0; + } else { + if (stream->timecodescale == 1.0) + lace_time = (cluster_time + time) * parse->time_scale; + else + lace_time = + gst_util_guint64_to_gdouble ((cluster_time + time) * + parse->time_scale) * stream->timecodescale; + } + } else { + lace_time = GST_CLOCK_TIME_NONE; + } + + if (lace_time != GST_CLOCK_TIME_NONE) { + parse->last_timestamp = lace_time; + } + /* need to refresh segment info ASAP */ + if (GST_CLOCK_TIME_IS_VALID (lace_time) && parse->need_newsegment) { + GST_DEBUG_OBJECT (parse, + "generating segment starting at %" GST_TIME_FORMAT, + GST_TIME_ARGS (lace_time)); + /* pretend we seeked here */ + gst_segment_set_seek (&parse->segment, parse->segment.rate, + GST_FORMAT_TIME, 0, GST_SEEK_TYPE_SET, lace_time, + GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE, NULL); + /* now convey our segment notion downstream */ + gst_matroska_parse_send_event (parse, gst_event_new_new_segment (FALSE, + parse->segment.rate, parse->segment.format, parse->segment.start, + parse->segment.stop, parse->segment.start)); + parse->need_newsegment = FALSE; + } + + if (block_duration) { + if (stream->timecodescale == 1.0) + duration = gst_util_uint64_scale (block_duration, parse->time_scale, 1); + else + duration = + gst_util_gdouble_to_guint64 (gst_util_guint64_to_gdouble + (gst_util_uint64_scale (block_duration, parse->time_scale, + 1)) * stream->timecodescale); + } else if (stream->default_duration) { + duration = stream->default_duration * laces; + } + /* else duration is diff between timecode of this and next block */ + + /* For SimpleBlock, look at the keyframe bit in flags. Otherwise, + a ReferenceBlock implies that this is not a keyframe. In either + case, it only makes sense for video streams. */ + delta_unit = stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO && + ((is_simpleblock && !(flags & 0x80)) || referenceblock); + + if (delta_unit && stream->set_discont) { + /* When doing seeks or such, we need to restart on key frames or + * decoders might choke. */ + GST_DEBUG_OBJECT (parse, "skipping delta unit"); + goto done; + } + + for (n = 0; n < laces; n++) { + if (G_UNLIKELY (lace_size[n] > size)) { + GST_WARNING_OBJECT (parse, "Invalid lace size"); + break; + } + + /* QoS for video track with an index. the assumption is that + index entries point to keyframes, but if that is not true we + will instad skip until the next keyframe. */ + if (GST_CLOCK_TIME_IS_VALID (lace_time) && + stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO && + stream->index_table && parse->segment.rate > 0.0) { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) stream; + GstClockTime earliest_time; + GstClockTime earliest_stream_time; + + GST_OBJECT_LOCK (parse); + earliest_time = videocontext->earliest_time; + GST_OBJECT_UNLOCK (parse); + earliest_stream_time = gst_segment_to_position (&parse->segment, + GST_FORMAT_TIME, earliest_time); + + if (GST_CLOCK_TIME_IS_VALID (lace_time) && + GST_CLOCK_TIME_IS_VALID (earliest_stream_time) && + lace_time <= earliest_stream_time) { + /* find index entry (keyframe) <= earliest_stream_time */ + GstMatroskaIndex *entry = + gst_util_array_binary_search (stream->index_table->data, + stream->index_table->len, sizeof (GstMatroskaIndex), + (GCompareDataFunc) gst_matroska_index_seek_find, + GST_SEARCH_MODE_BEFORE, &earliest_stream_time, NULL); + + /* if that entry (keyframe) is after the current the current + buffer, we can skip pushing (and thus decoding) all + buffers until that keyframe. */ + if (entry && GST_CLOCK_TIME_IS_VALID (entry->time) && + entry->time > lace_time) { + GST_LOG_OBJECT (parse, "Skipping lace before late keyframe"); + stream->set_discont = TRUE; + goto next_lace; + } + } + } +#if 0 + sub = gst_buffer_create_sub (buf, + GST_BUFFER_SIZE (buf) - size, lace_size[n]); + GST_DEBUG_OBJECT (parse, "created subbuffer %p", sub); + + if (delta_unit) + GST_BUFFER_FLAG_SET (sub, GST_BUFFER_FLAG_DELTA_UNIT); + else + GST_BUFFER_FLAG_UNSET (sub, GST_BUFFER_FLAG_DELTA_UNIT); + + if (stream->encodings != NULL && stream->encodings->len > 0) + sub = gst_matroska_decode_buffer (stream, sub); + + if (sub == NULL) { + GST_WARNING_OBJECT (parse, "Decoding buffer failed"); + goto next_lace; + } + + GST_BUFFER_TIMESTAMP (sub) = lace_time; + + if (GST_CLOCK_TIME_IS_VALID (lace_time)) { + GstClockTime last_stop_end; + + /* Check if this stream is after segment stop */ + if (GST_CLOCK_TIME_IS_VALID (parse->segment.stop) && + lace_time >= parse->segment.stop) { + GST_DEBUG_OBJECT (parse, + "Stream %d after segment stop %" GST_TIME_FORMAT, stream->index, + GST_TIME_ARGS (parse->segment.stop)); + gst_buffer_unref (sub); + goto eos; + } + if (offset >= stream->to_offset) { + GST_DEBUG_OBJECT (parse, "Stream %d after playback section", + stream->index); + gst_buffer_unref (sub); + goto eos; + } + + /* handle gaps, e.g. non-zero start-time, or an cue index entry + * that landed us with timestamps not quite intended */ + if (GST_CLOCK_TIME_IS_VALID (parse->segment.last_stop) && + parse->segment.rate > 0.0) { + GstClockTimeDiff diff; + + /* only send newsegments with increasing start times, + * otherwise if these go back and forth downstream (sinks) increase + * accumulated time and running_time */ + diff = GST_CLOCK_DIFF (parse->segment.last_stop, lace_time); + if (diff > 2 * GST_SECOND && lace_time > parse->segment.start && + (!GST_CLOCK_TIME_IS_VALID (parse->segment.stop) || + lace_time < parse->segment.stop)) { + GST_DEBUG_OBJECT (parse, + "Gap of %" G_GINT64_FORMAT " ns detected in" + "stream %d (%" GST_TIME_FORMAT " -> %" GST_TIME_FORMAT "). " + "Sending updated NEWSEGMENT events", diff, + stream->index, GST_TIME_ARGS (stream->pos), + GST_TIME_ARGS (lace_time)); + /* send newsegment events such that the gap is not accounted in + * accum time, hence running_time */ + /* close ahead of gap */ + gst_matroska_parse_send_event (parse, + gst_event_new_new_segment (TRUE, parse->segment.rate, + parse->segment.format, parse->segment.last_stop, + parse->segment.last_stop, parse->segment.last_stop)); + /* skip gap */ + gst_matroska_parse_send_event (parse, + gst_event_new_new_segment (FALSE, parse->segment.rate, + parse->segment.format, lace_time, parse->segment.stop, + lace_time)); + /* align segment view with downstream, + * prevents double-counting accum when closing segment */ + gst_segment_set_newsegment (&parse->segment, FALSE, + parse->segment.rate, parse->segment.format, lace_time, + parse->segment.stop, lace_time); + parse->segment.last_stop = lace_time; + } + } + + if (!GST_CLOCK_TIME_IS_VALID (parse->segment.last_stop) + || parse->segment.last_stop < lace_time) { + parse->segment.last_stop = lace_time; + } + + last_stop_end = lace_time; + if (duration) { + GST_BUFFER_DURATION (sub) = duration / laces; + last_stop_end += GST_BUFFER_DURATION (sub); + } + + if (!GST_CLOCK_TIME_IS_VALID (parse->last_stop_end) || + parse->last_stop_end < last_stop_end) + parse->last_stop_end = last_stop_end; + + if (parse->segment.duration == -1 || + parse->segment.duration < lace_time) { + gst_segment_set_duration (&parse->segment, GST_FORMAT_TIME, + last_stop_end); + gst_element_post_message (GST_ELEMENT_CAST (parse), + gst_message_new_duration (GST_OBJECT_CAST (parse), + GST_FORMAT_TIME, GST_CLOCK_TIME_NONE)); + } + } + + stream->pos = lace_time; + + gst_matroska_parse_sync_streams (parse); + + if (stream->set_discont) { + GST_DEBUG_OBJECT (parse, "marking DISCONT"); + GST_BUFFER_FLAG_SET (sub, GST_BUFFER_FLAG_DISCONT); + stream->set_discont = FALSE; + } + + /* reverse playback book-keeping */ + if (!GST_CLOCK_TIME_IS_VALID (stream->from_time)) + stream->from_time = lace_time; + if (stream->from_offset == -1) + stream->from_offset = offset; + + GST_DEBUG_OBJECT (parse, + "Pushing lace %d, data of size %d for stream %d, time=%" + GST_TIME_FORMAT " and duration=%" GST_TIME_FORMAT, n, + GST_BUFFER_SIZE (sub), stream_num, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (sub)), + GST_TIME_ARGS (GST_BUFFER_DURATION (sub))); + + if (parse->element_index) { + if (stream->index_writer_id == -1) + gst_index_get_writer_id (parse->element_index, + GST_OBJECT (stream->pad), &stream->index_writer_id); + + GST_LOG_OBJECT (parse, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT " for writer id %d", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (sub)), cluster_offset, + stream->index_writer_id); + gst_index_add_association (parse->element_index, + stream->index_writer_id, GST_BUFFER_FLAG_IS_SET (sub, + GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : GST_ASSOCIATION_FLAG_KEY_UNIT, + GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (sub), GST_FORMAT_BYTES, + cluster_offset, NULL); + } + + gst_buffer_set_caps (sub, GST_PAD_CAPS (parse->srcpad)); + + /* Postprocess the buffers depending on the codec used */ + if (stream->postprocess_frame) { + GST_LOG_OBJECT (parse, "running post process"); + ret = stream->postprocess_frame (GST_ELEMENT (parse), stream, &sub); + } + + ret = gst_pad_push (stream->pad, sub); + if (parse->segment.rate < 0) { + if (lace_time > parse->segment.stop && ret == GST_FLOW_UNEXPECTED) { + /* In reverse playback we can get a GST_FLOW_UNEXPECTED when + * we are at the end of the segment, so we just need to jump + * back to the previous section. */ + GST_DEBUG_OBJECT (parse, "downstream has reached end of segment"); + ret = GST_FLOW_OK; + } + } + /* combine flows */ + ret = gst_matroska_parse_combine_flows (parse, stream, ret); +#endif + + next_lace: + size -= lace_size[n]; + if (lace_time != GST_CLOCK_TIME_NONE && duration) + lace_time += duration / laces; + else + lace_time = GST_CLOCK_TIME_NONE; + } + } + +done: + if (buf) + gst_buffer_unref (buf); + g_free (lace_size); + + return ret; + + /* EXITS */ +invalid_lacing: + { + GST_ELEMENT_WARNING (parse, STREAM, DEMUX, (NULL), ("Invalid lacing size")); + /* non-fatal, try next block(group) */ + ret = GST_FLOW_OK; + goto done; + } +data_error: + { + GST_ELEMENT_WARNING (parse, STREAM, DEMUX, (NULL), ("Data error")); + /* non-fatal, try next block(group) */ + ret = GST_FLOW_OK; + goto done; + } +} + +/* return FALSE if block(group) should be skipped (due to a seek) */ +static inline gboolean +gst_matroska_parse_seek_block (GstMatroskaParse * parse) +{ + if (G_UNLIKELY (parse->seek_block)) { + if (!(--parse->seek_block)) { + return TRUE; + } else { + GST_LOG_OBJECT (parse, "should skip block due to seek"); + return FALSE; + } + } else { + return TRUE; + } +} + +static GstFlowReturn +gst_matroska_parse_parse_contents_seekentry (GstMatroskaParse * parse, + GstEbmlRead * ebml) +{ + GstFlowReturn ret; + guint64 seek_pos = (guint64) - 1; + guint32 seek_id = 0; + guint32 id; + + DEBUG_ELEMENT_START (parse, ebml, "Seek"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "Seek", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_SEEKID: + { + guint64 t; + + if ((ret = gst_ebml_read_uint (ebml, &id, &t)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (parse, "SeekID: %" G_GUINT64_FORMAT, t); + seek_id = t; + break; + } + + case GST_MATROSKA_ID_SEEKPOSITION: + { + guint64 t; + + if ((ret = gst_ebml_read_uint (ebml, &id, &t)) != GST_FLOW_OK) + break; + + if (t > G_MAXINT64) { + GST_WARNING_OBJECT (parse, + "Too large SeekPosition %" G_GUINT64_FORMAT, t); + break; + } + + GST_DEBUG_OBJECT (parse, "SeekPosition: %" G_GUINT64_FORMAT, t); + seek_pos = t; + break; + } + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "SeekHead", id); + break; + } + } + + if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED) + return ret; + + if (!seek_id || seek_pos == (guint64) - 1) { + GST_WARNING_OBJECT (parse, "Incomplete seekhead entry (0x%x/%" + G_GUINT64_FORMAT ")", seek_id, seek_pos); + return GST_FLOW_OK; + } + + switch (seek_id) { + case GST_MATROSKA_ID_SEEKHEAD: + { + } + case GST_MATROSKA_ID_CUES: + case GST_MATROSKA_ID_TAGS: + case GST_MATROSKA_ID_TRACKS: + case GST_MATROSKA_ID_SEGMENTINFO: + case GST_MATROSKA_ID_ATTACHMENTS: + case GST_MATROSKA_ID_CHAPTERS: + { + guint64 length; + + /* remember */ + length = gst_matroska_parse_get_length (parse); + + if (length == (guint64) - 1) { + GST_DEBUG_OBJECT (parse, "no upstream length, skipping SeakHead entry"); + break; + } + + /* check for validity */ + if (seek_pos + parse->ebml_segment_start + 12 >= length) { + GST_WARNING_OBJECT (parse, + "SeekHead reference lies outside file!" " (%" + G_GUINT64_FORMAT "+%" G_GUINT64_FORMAT "+12 >= %" + G_GUINT64_FORMAT ")", seek_pos, parse->ebml_segment_start, length); + break; + } + + /* only pick up index location when streaming */ + if (seek_id == GST_MATROSKA_ID_CUES) { + parse->index_offset = seek_pos + parse->ebml_segment_start; + GST_DEBUG_OBJECT (parse, "Cues located at offset %" G_GUINT64_FORMAT, + parse->index_offset); + } + break; + } + + default: + GST_DEBUG_OBJECT (parse, "Ignoring Seek entry for ID=0x%x", seek_id); + break; + } + DEBUG_ELEMENT_STOP (parse, ebml, "Seek", ret); + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_contents (GstMatroskaParse * parse, GstEbmlRead * ebml) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + + DEBUG_ELEMENT_START (parse, ebml, "SeekHead"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (parse, ebml, "SeekHead", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_SEEKENTRY: + { + ret = gst_matroska_parse_parse_contents_seekentry (parse, ebml); + /* Ignore EOS and errors here */ + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (parse, "Ignoring %s", gst_flow_get_name (ret)); + ret = GST_FLOW_OK; + } + break; + } + + default: + ret = gst_matroska_parse_parse_skip (parse, ebml, "SeekHead", id); + break; + } + } + + DEBUG_ELEMENT_STOP (parse, ebml, "SeekHead", ret); + + return ret; +} + +#define GST_FLOW_OVERFLOW GST_FLOW_CUSTOM_ERROR + +#define MAX_BLOCK_SIZE (15 * 1024 * 1024) + +static inline GstFlowReturn +gst_matroska_parse_check_read_size (GstMatroskaParse * parse, guint64 bytes) +{ + if (G_UNLIKELY (bytes > MAX_BLOCK_SIZE)) { + /* only a few blocks are expected/allowed to be large, + * and will be recursed into, whereas others will be read and must fit */ + /* fatal in streaming case, as we can't step over easily */ + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, (NULL), + ("reading large block of size %" G_GUINT64_FORMAT " not supported; " + "file might be corrupt.", bytes)); + return GST_FLOW_ERROR; + } else { + return GST_FLOW_OK; + } +} + +/* returns TRUE if we truely are in error state, and should give up */ +static inline gboolean +gst_matroska_parse_check_parse_error (GstMatroskaParse * parse) +{ + gint64 pos; + + /* sigh, one last attempt above and beyond call of duty ...; + * search for cluster mark following current pos */ + pos = parse->offset; + GST_WARNING_OBJECT (parse, "parse error, looking for next cluster"); + if (gst_matroska_parse_search_cluster (parse, &pos) != GST_FLOW_OK) { + /* did not work, give up */ + return TRUE; + } else { + GST_DEBUG_OBJECT (parse, "... found at %" G_GUINT64_FORMAT, pos); + /* try that position */ + parse->offset = pos; + return FALSE; + } +} + +/* initializes @ebml with @bytes from input stream at current offset. + * Returns UNEXPECTED if insufficient available, + * ERROR if too much was attempted to read. */ +static inline GstFlowReturn +gst_matroska_parse_take (GstMatroskaParse * parse, guint64 bytes, + GstEbmlRead * ebml) +{ + GstBuffer *buffer = NULL; + GstFlowReturn ret = GST_FLOW_OK; + + GST_LOG_OBJECT (parse, "taking %" G_GUINT64_FORMAT " bytes for parsing", + bytes); + ret = gst_matroska_parse_check_read_size (parse, bytes); + if (G_UNLIKELY (ret != GST_FLOW_OK)) { + /* otherwise fatal */ + ret = GST_FLOW_ERROR; + goto exit; + } + if (gst_adapter_available (parse->adapter) >= bytes) + buffer = gst_adapter_take_buffer (parse->adapter, bytes); + else + ret = GST_FLOW_UNEXPECTED; + if (G_LIKELY (buffer)) { + gst_ebml_read_init (ebml, GST_ELEMENT_CAST (parse), buffer, parse->offset); + parse->offset += bytes; + } +exit: + return ret; +} + +static void +gst_matroska_parse_check_seekability (GstMatroskaParse * parse) +{ + GstQuery *query; + gboolean seekable = FALSE; + gint64 start = -1, stop = -1; + + query = gst_query_new_seeking (GST_FORMAT_BYTES); + if (!gst_pad_peer_query (parse->sinkpad, query)) { + GST_DEBUG_OBJECT (parse, "seeking query failed"); + goto done; + } + + gst_query_parse_seeking (query, NULL, &seekable, &start, &stop); + + /* try harder to query upstream size if we didn't get it the first time */ + if (seekable && stop == -1) { + GstFormat fmt = GST_FORMAT_BYTES; + + GST_DEBUG_OBJECT (parse, "doing duration query to fix up unset stop"); + gst_pad_query_peer_duration (parse->sinkpad, &fmt, &stop); + } + + /* if upstream doesn't know the size, it's likely that it's not seekable in + * practice even if it technically may be seekable */ + if (seekable && (start != 0 || stop <= start)) { + GST_DEBUG_OBJECT (parse, "seekable but unknown start/stop -> disable"); + seekable = FALSE; + } + +done: + GST_INFO_OBJECT (parse, "seekable: %d (%" G_GUINT64_FORMAT " - %" + G_GUINT64_FORMAT ")", seekable, start, stop); + parse->seekable = seekable; + + gst_query_unref (query); +} + +#if 0 +static GstFlowReturn +gst_matroska_parse_find_tracks (GstMatroskaParse * parse) +{ + guint32 id; + guint64 before_pos; + guint64 length; + guint needed; + GstFlowReturn ret = GST_FLOW_OK; + + GST_WARNING_OBJECT (parse, + "Found Cluster element before Tracks, searching Tracks"); + + /* remember */ + before_pos = parse->offset; + + /* Search Tracks element */ + while (TRUE) { + ret = gst_matroska_parse_peek_id_length_pull (parse, &id, &length, &needed); + if (ret != GST_FLOW_OK) + break; + + if (id != GST_MATROSKA_ID_TRACKS) { + /* we may be skipping large cluster here, so forego size check etc */ + /* ... but we can't skip undefined size; force error */ + if (length == G_MAXUINT64) { + ret = gst_matroska_parse_check_read_size (parse, length); + break; + } else { + parse->offset += needed; + parse->offset += length; + } + continue; + } + + /* will lead to track parsing ... */ + ret = gst_matroska_parse_parse_id (parse, id, length, needed); + break; + } + + /* seek back */ + parse->offset = before_pos; + + return ret; +} +#endif + +#define GST_READ_CHECK(stmt) \ +G_STMT_START { \ + if (G_UNLIKELY ((ret = (stmt)) != GST_FLOW_OK)) { \ + if (ret == GST_FLOW_OVERFLOW) { \ + ret = GST_FLOW_OK; \ + } \ + goto read_error; \ + } \ +} G_STMT_END + +static void +gst_matroska_parse_accumulate_streamheader (GstMatroskaParse * parse, + GstBuffer * buffer) +{ + if (parse->streamheader) { + GstBuffer *buf; + + buf = gst_buffer_span (parse->streamheader, 0, buffer, + GST_BUFFER_SIZE (parse->streamheader) + GST_BUFFER_SIZE (buffer)); + gst_buffer_unref (parse->streamheader); + parse->streamheader = buf; + } else { + parse->streamheader = gst_buffer_ref (buffer); + } + + GST_DEBUG ("%d", GST_BUFFER_SIZE (parse->streamheader)); +} + +static GstFlowReturn +gst_matroska_parse_output (GstMatroskaParse * parse, GstBuffer * buffer, + gboolean keyframe) +{ + GstFlowReturn ret = GST_FLOW_OK; + + if (!parse->pushed_headers) { + GstCaps *caps; + GstStructure *s; + GValue streamheader = { 0 }; + GValue bufval = { 0 }; + GstBuffer *buf; + + caps = gst_caps_new_simple ("video/x-matroska", NULL); + s = gst_caps_get_structure (caps, 0); + g_value_init (&streamheader, GST_TYPE_ARRAY); + g_value_init (&bufval, GST_TYPE_BUFFER); + GST_BUFFER_FLAG_SET (parse->streamheader, GST_BUFFER_FLAG_IN_CAPS); + gst_value_set_buffer (&bufval, parse->streamheader); + gst_value_array_append_value (&streamheader, &bufval); + g_value_unset (&bufval); + gst_structure_set_value (s, "streamheader", &streamheader); + g_value_unset (&streamheader); + //gst_caps_replace (parse->caps, caps); + gst_pad_set_caps (parse->srcpad, caps); + + buf = gst_buffer_make_metadata_writable (parse->streamheader); + gst_buffer_set_caps (buf, caps); + gst_caps_unref (caps); + + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + + ret = gst_pad_push (parse->srcpad, buf); + + parse->pushed_headers = TRUE; + } + + if (!keyframe) { + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + } else { + GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + } + if (GST_BUFFER_TIMESTAMP (buffer) != GST_CLOCK_TIME_NONE) { + parse->last_timestamp = GST_BUFFER_TIMESTAMP (buffer); + } else { + GST_BUFFER_TIMESTAMP (buffer) = parse->last_timestamp; + } + gst_buffer_set_caps (buffer, GST_PAD_CAPS (parse->srcpad)); + ret = gst_pad_push (parse->srcpad, gst_buffer_ref (buffer)); + + return ret; +} + +static GstFlowReturn +gst_matroska_parse_parse_id (GstMatroskaParse * parse, guint32 id, + guint64 length, guint needed) +{ + GstEbmlRead ebml = { 0, }; + GstFlowReturn ret = GST_FLOW_OK; + guint64 read; + //GstBuffer *buffer; + + GST_DEBUG_OBJECT (parse, "Parsing Element id 0x%x, " + "size %" G_GUINT64_FORMAT ", prefix %d", id, length, needed); + +#if 0 + if (gst_adapter_available (parse->adapter) >= length + needed) { + buffer = gst_adapter_take_buffer (parse->adapter, length + needed); + gst_pad_push (parse->srcpad, buffer); + } else { + ret = GST_FLOW_UNEXPECTED; + } + //GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + + return ret; +#endif + + + + /* if we plan to read and parse this element, we need prefix (id + length) + * and the contents */ + /* mind about overflow wrap-around when dealing with undefined size */ + read = length; + if (G_LIKELY (length != G_MAXUINT64)) + read += needed; + + switch (parse->state) { + case GST_MATROSKA_PARSE_STATE_START: + switch (id) { + case GST_EBML_ID_HEADER: + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + ret = gst_matroska_parse_parse_header (parse, &ebml); + if (ret != GST_FLOW_OK) + goto parse_failed; + parse->state = GST_MATROSKA_PARSE_STATE_SEGMENT; + gst_matroska_parse_check_seekability (parse); + gst_matroska_parse_accumulate_streamheader (parse, ebml.buf); + break; + default: + goto invalid_header; + break; + } + break; + case GST_MATROSKA_PARSE_STATE_SEGMENT: + switch (id) { + case GST_MATROSKA_ID_SEGMENT: + /* eat segment prefix */ + GST_READ_CHECK (gst_matroska_parse_take (parse, needed, &ebml)); + GST_DEBUG_OBJECT (parse, + "Found Segment start at offset %" G_GUINT64_FORMAT, + parse->offset); + /* seeks are from the beginning of the segment, + * after the segment ID/length */ + parse->ebml_segment_start = parse->offset; + parse->state = GST_MATROSKA_PARSE_STATE_HEADER; + gst_matroska_parse_accumulate_streamheader (parse, ebml.buf); + break; + default: + GST_WARNING_OBJECT (parse, + "Expected a Segment ID (0x%x), but received 0x%x!", + GST_MATROSKA_ID_SEGMENT, id); + GST_READ_CHECK (gst_matroska_parse_take (parse, needed, &ebml)); + gst_matroska_parse_accumulate_streamheader (parse, ebml.buf); + break; + } + break; + case GST_MATROSKA_PARSE_STATE_SCANNING: + if (id != GST_MATROSKA_ID_CLUSTER && + id != GST_MATROSKA_ID_CLUSTERTIMECODE) + goto skip; + /* fall-through */ + case GST_MATROSKA_PARSE_STATE_HEADER: + case GST_MATROSKA_PARSE_STATE_DATA: + case GST_MATROSKA_PARSE_STATE_SEEK: + switch (id) { + case GST_MATROSKA_ID_SEGMENTINFO: + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + if (!parse->segmentinfo_parsed) { + ret = gst_matroska_parse_parse_info (parse, &ebml); + } + gst_matroska_parse_accumulate_streamheader (parse, ebml.buf); + break; + case GST_MATROSKA_ID_TRACKS: + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + if (!parse->tracks_parsed) { + ret = gst_matroska_parse_parse_tracks (parse, &ebml); + } + gst_matroska_parse_accumulate_streamheader (parse, ebml.buf); + break; + case GST_MATROSKA_ID_CLUSTER: + if (G_UNLIKELY (!parse->tracks_parsed)) { + GST_DEBUG_OBJECT (parse, "Cluster before Track"); + goto not_streamable; + } + if (G_UNLIKELY (parse->state == GST_MATROSKA_PARSE_STATE_HEADER)) { + parse->state = GST_MATROSKA_PARSE_STATE_DATA; + parse->first_cluster_offset = parse->offset; + GST_DEBUG_OBJECT (parse, "signaling no more pads"); + } + parse->cluster_time = GST_CLOCK_TIME_NONE; + parse->cluster_offset = parse->offset; + if (G_UNLIKELY (!parse->seek_first && parse->seek_block)) { + GST_DEBUG_OBJECT (parse, "seek target block %" G_GUINT64_FORMAT + " not found in Cluster, trying next Cluster's first block instead", + parse->seek_block); + parse->seek_block = 0; + } + parse->seek_first = FALSE; + /* record next cluster for recovery */ + if (read != G_MAXUINT64) + parse->next_cluster_offset = parse->cluster_offset + read; + /* eat cluster prefix */ + GST_READ_CHECK (gst_matroska_parse_take (parse, needed, &ebml)); + ret = gst_matroska_parse_output (parse, ebml.buf, TRUE); + //gst_matroska_parse_accumulate_streamheader (parse, ebml.buf); + break; + case GST_MATROSKA_ID_CLUSTERTIMECODE: + { + guint64 num; + + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + if ((ret = gst_ebml_read_uint (&ebml, &id, &num)) != GST_FLOW_OK) + goto parse_failed; + GST_DEBUG_OBJECT (parse, "ClusterTimeCode: %" G_GUINT64_FORMAT, num); + parse->cluster_time = num; + if (parse->element_index) { + if (parse->element_index_writer_id == -1) + gst_index_get_writer_id (parse->element_index, + GST_OBJECT (parse), &parse->element_index_writer_id); + GST_LOG_OBJECT (parse, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT " for writer id %d", + GST_TIME_ARGS (parse->cluster_time), parse->cluster_offset, + parse->element_index_writer_id); + gst_index_add_association (parse->element_index, + parse->element_index_writer_id, GST_ASSOCIATION_FLAG_KEY_UNIT, + GST_FORMAT_TIME, parse->cluster_time, + GST_FORMAT_BYTES, parse->cluster_offset, NULL); + } + gst_matroska_parse_output (parse, ebml.buf, FALSE); + break; + } + case GST_MATROSKA_ID_BLOCKGROUP: + if (!gst_matroska_parse_seek_block (parse)) + goto skip; + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + DEBUG_ELEMENT_START (parse, &ebml, "BlockGroup"); + if ((ret = gst_ebml_read_master (&ebml, &id)) == GST_FLOW_OK) { + ret = gst_matroska_parse_parse_blockgroup_or_simpleblock (parse, + &ebml, parse->cluster_time, parse->cluster_offset, FALSE); + } + DEBUG_ELEMENT_STOP (parse, &ebml, "BlockGroup", ret); + gst_matroska_parse_output (parse, ebml.buf, FALSE); + break; + case GST_MATROSKA_ID_SIMPLEBLOCK: + if (!gst_matroska_parse_seek_block (parse)) + goto skip; + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + DEBUG_ELEMENT_START (parse, &ebml, "SimpleBlock"); + ret = gst_matroska_parse_parse_blockgroup_or_simpleblock (parse, + &ebml, parse->cluster_time, parse->cluster_offset, TRUE); + DEBUG_ELEMENT_STOP (parse, &ebml, "SimpleBlock", ret); + gst_matroska_parse_output (parse, ebml.buf, FALSE); + break; + case GST_MATROSKA_ID_ATTACHMENTS: + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + if (!parse->attachments_parsed) { + ret = gst_matroska_parse_parse_attachments (parse, &ebml); + } + gst_matroska_parse_output (parse, ebml.buf, FALSE); + break; + case GST_MATROSKA_ID_TAGS: + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + ret = gst_matroska_parse_parse_metadata (parse, &ebml); + gst_matroska_parse_output (parse, ebml.buf, FALSE); + break; + case GST_MATROSKA_ID_CHAPTERS: + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + ret = gst_matroska_parse_parse_chapters (parse, &ebml); + gst_matroska_parse_output (parse, ebml.buf, FALSE); + break; + case GST_MATROSKA_ID_SEEKHEAD: + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + ret = gst_matroska_parse_parse_contents (parse, &ebml); + gst_matroska_parse_output (parse, ebml.buf, FALSE); + break; + case GST_MATROSKA_ID_CUES: + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + if (!parse->index_parsed) { + ret = gst_matroska_parse_parse_index (parse, &ebml); + /* only push based; delayed index building */ + if (ret == GST_FLOW_OK + && parse->state == GST_MATROSKA_PARSE_STATE_SEEK) { + GstEvent *event; + + GST_OBJECT_LOCK (parse); + event = parse->seek_event; + parse->seek_event = NULL; + GST_OBJECT_UNLOCK (parse); + + g_assert (event); + /* unlikely to fail, since we managed to seek to this point */ + if (!gst_matroska_parse_handle_seek_event (parse, NULL, event)) + goto seek_failed; + /* resume data handling, main thread clear to seek again */ + GST_OBJECT_LOCK (parse); + parse->state = GST_MATROSKA_PARSE_STATE_DATA; + GST_OBJECT_UNLOCK (parse); + } + } + gst_matroska_parse_output (parse, ebml.buf, FALSE); + break; + case GST_MATROSKA_ID_POSITION: + case GST_MATROSKA_ID_PREVSIZE: + case GST_MATROSKA_ID_ENCRYPTEDBLOCK: + case GST_MATROSKA_ID_SILENTTRACKS: + GST_DEBUG_OBJECT (parse, + "Skipping Cluster subelement 0x%x - ignoring", id); + /* fall-through */ + default: + skip: + GST_DEBUG_OBJECT (parse, "skipping Element 0x%x", id); + GST_READ_CHECK (gst_matroska_parse_take (parse, read, &ebml)); + gst_matroska_parse_output (parse, ebml.buf, FALSE); + break; + } + break; + } + + if (ret == GST_FLOW_PARSE) + goto parse_failed; + +exit: + gst_ebml_read_clear (&ebml); + return ret; + + /* ERRORS */ +read_error: + { + /* simply exit, maybe not enough data yet */ + /* no ebml to clear if read error */ + return ret; + } +parse_failed: + { + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, (NULL), + ("Failed to parse Element 0x%x", id)); + ret = GST_FLOW_ERROR; + goto exit; + } +not_streamable: + { + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, (NULL), + ("File layout does not permit streaming")); + ret = GST_FLOW_ERROR; + goto exit; + } +#if 0 +no_tracks: + { + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, (NULL), + ("No Tracks element found")); + ret = GST_FLOW_ERROR; + goto exit; + } +#endif +invalid_header: + { + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, (NULL), ("Invalid header")); + ret = GST_FLOW_ERROR; + goto exit; + } +seek_failed: + { + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, (NULL), ("Failed to seek")); + ret = GST_FLOW_ERROR; + goto exit; + } +} + +#if 0 +static void +gst_matroska_parse_loop (GstPad * pad) +{ + GstMatroskaParse *parse = GST_MATROSKA_PARSE (GST_PAD_PARENT (pad)); + GstFlowReturn ret; + guint32 id; + guint64 length; + guint needed; + + /* If we have to close a segment, send a new segment to do this now */ + if (G_LIKELY (parse->state == GST_MATROSKA_PARSE_STATE_DATA)) { + if (G_UNLIKELY (parse->close_segment)) { + gst_matroska_parse_send_event (parse, parse->close_segment); + parse->close_segment = NULL; + } + if (G_UNLIKELY (parse->new_segment)) { + gst_matroska_parse_send_event (parse, parse->new_segment); + parse->new_segment = NULL; + } + } + + ret = gst_matroska_parse_peek_id_length_pull (parse, &id, &length, &needed); + if (ret == GST_FLOW_UNEXPECTED) + goto eos; + if (ret != GST_FLOW_OK) { + if (gst_matroska_parse_check_parse_error (parse)) + goto pause; + else + return; + } + + GST_LOG_OBJECT (parse, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, " + "size %" G_GUINT64_FORMAT ", needed %d", parse->offset, id, + length, needed); + + ret = gst_matroska_parse_parse_id (parse, id, length, needed); + if (ret == GST_FLOW_UNEXPECTED) + goto eos; + if (ret != GST_FLOW_OK) + goto pause; + + /* check if we're at the end of a configured segment */ + if (G_LIKELY (parse->src->len)) { + guint i; + + g_assert (parse->num_streams == parse->src->len); + for (i = 0; i < parse->src->len; i++) { + GstMatroskaTrackContext *context = g_ptr_array_index (parse->src, i); + GST_DEBUG_OBJECT (context->pad, "pos %" GST_TIME_FORMAT, + GST_TIME_ARGS (context->pos)); + if (context->eos == FALSE) + goto next; + } + + GST_INFO_OBJECT (parse, "All streams are EOS"); + ret = GST_FLOW_UNEXPECTED; + goto eos; + } + +next: + if (G_UNLIKELY (parse->offset == gst_matroska_parse_get_length (parse))) { + GST_LOG_OBJECT (parse, "Reached end of stream"); + ret = GST_FLOW_UNEXPECTED; + goto eos; + } + + return; + + /* ERRORS */ +eos: + { + if (parse->segment.rate < 0.0) { + ret = gst_matroska_parse_seek_to_previous_keyframe (parse); + if (ret == GST_FLOW_OK) + return; + } + /* fall-through */ + } +pause: + { + const gchar *reason = gst_flow_get_name (ret); + gboolean push_eos = FALSE; + + GST_LOG_OBJECT (parse, "pausing task, reason %s", reason); + parse->segment_running = FALSE; + gst_pad_pause_task (parse->sinkpad); + + if (ret == GST_FLOW_UNEXPECTED) { + /* perform EOS logic */ + + /* Close the segment, i.e. update segment stop with the duration + * if no stop was set */ + if (GST_CLOCK_TIME_IS_VALID (parse->last_stop_end) && + !GST_CLOCK_TIME_IS_VALID (parse->segment.stop)) { + GstEvent *event = + gst_event_new_new_segment_full (TRUE, parse->segment.rate, + parse->segment.applied_rate, parse->segment.format, + parse->segment.start, + MAX (parse->last_stop_end, parse->segment.start), + parse->segment.time); + gst_matroska_parse_send_event (parse, event); + } + + if (parse->segment.flags & GST_SEEK_FLAG_SEGMENT) { + gint64 stop; + + /* for segment playback we need to post when (in stream time) + * we stopped, this is either stop (when set) or the duration. */ + if ((stop = parse->segment.stop) == -1) + stop = parse->last_stop_end; + + GST_LOG_OBJECT (parse, "Sending segment done, at end of segment"); + gst_element_post_message (GST_ELEMENT (parse), + gst_message_new_segment_done (GST_OBJECT (parse), GST_FORMAT_TIME, + stop)); + } else { + push_eos = TRUE; + } + } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_UNEXPECTED) { + /* for fatal errors we post an error message */ + GST_ELEMENT_ERROR (parse, STREAM, FAILED, (NULL), + ("stream stopped, reason %s", reason)); + push_eos = TRUE; + } + if (push_eos) { + /* send EOS, and prevent hanging if no streams yet */ + GST_LOG_OBJECT (parse, "Sending EOS, at end of stream"); + if (!gst_matroska_parse_send_event (parse, gst_event_new_eos ()) && + (ret == GST_FLOW_UNEXPECTED)) { + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, + (NULL), ("got eos but no streams (yet)")); + } + } + return; + } +} +#endif + +/* + * Create and push a flushing seek event upstream + */ +static gboolean +perform_seek_to_offset (GstMatroskaParse * parse, guint64 offset) +{ + GstEvent *event; + gboolean res = 0; + + GST_DEBUG_OBJECT (parse, "Seeking to %" G_GUINT64_FORMAT, offset); + + event = + gst_event_new_seek (1.0, GST_FORMAT_BYTES, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, offset, + GST_SEEK_TYPE_NONE, -1); + + res = gst_pad_push_event (parse->sinkpad, event); + + /* newsegment event will update offset */ + return res; +} + +static const guint8 * +gst_matroska_parse_peek_adapter (GstMatroskaParse * parse, guint peek) +{ + return gst_adapter_peek (parse->adapter, peek); +} + +static GstFlowReturn +gst_matroska_parse_peek_id_length_push (GstMatroskaParse * parse, guint32 * _id, + guint64 * _length, guint * _needed) +{ + return gst_ebml_peek_id_length (_id, _length, _needed, + (GstPeekData) gst_matroska_parse_peek_adapter, (gpointer) parse, + GST_ELEMENT_CAST (parse), parse->offset); +} + +static GstFlowReturn +gst_matroska_parse_chain (GstPad * pad, GstBuffer * buffer) +{ + GstMatroskaParse *parse = GST_MATROSKA_PARSE (GST_PAD_PARENT (pad)); + guint available; + GstFlowReturn ret = GST_FLOW_OK; + guint needed = 0; + guint32 id; + guint64 length; + + if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (buffer))) { + GST_DEBUG_OBJECT (parse, "got DISCONT"); + gst_adapter_clear (parse->adapter); + GST_OBJECT_LOCK (parse); + gst_matroska_parse_reset_streams (parse, GST_CLOCK_TIME_NONE, FALSE); + GST_OBJECT_UNLOCK (parse); + } + + gst_adapter_push (parse->adapter, buffer); + buffer = NULL; + +next: + available = gst_adapter_available (parse->adapter); + + ret = gst_matroska_parse_peek_id_length_push (parse, &id, &length, &needed); + if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED)) + return ret; + + GST_LOG_OBJECT (parse, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, " + "size %" G_GUINT64_FORMAT ", needed %d, available %d", parse->offset, id, + length, needed, available); + + if (needed > available) + return GST_FLOW_OK; + + ret = gst_matroska_parse_parse_id (parse, id, length, needed); + if (ret == GST_FLOW_UNEXPECTED) { + /* need more data */ + return GST_FLOW_OK; + } else if (ret != GST_FLOW_OK) { + return ret; + } else + goto next; +} + +static gboolean +gst_matroska_parse_handle_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + GstMatroskaParse *parse = GST_MATROSKA_PARSE (GST_PAD_PARENT (pad)); + + GST_DEBUG_OBJECT (parse, + "have event type %s: %p on sink pad", GST_EVENT_TYPE_NAME (event), event); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time = 0; + gboolean update; + GstSegment segment; + + /* some debug output */ + gst_segment_init (&segment, GST_FORMAT_UNDEFINED); + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + gst_segment_set_newsegment_full (&segment, update, rate, arate, format, + start, stop, time); + GST_DEBUG_OBJECT (parse, + "received format %d newsegment %" GST_SEGMENT_FORMAT, format, + &segment); + + if (parse->state < GST_MATROSKA_PARSE_STATE_DATA) { + GST_DEBUG_OBJECT (parse, "still starting"); + goto exit; + } + + /* we only expect a BYTE segment, e.g. following a seek */ + if (format != GST_FORMAT_BYTES) { + GST_DEBUG_OBJECT (parse, "unsupported segment format, ignoring"); + goto exit; + } + + GST_DEBUG_OBJECT (parse, "clearing segment state"); + /* clear current segment leftover */ + gst_adapter_clear (parse->adapter); + /* and some streaming setup */ + parse->offset = start; + /* do not know where we are; + * need to come across a cluster and generate newsegment */ + parse->segment.last_stop = GST_CLOCK_TIME_NONE; + parse->cluster_time = GST_CLOCK_TIME_NONE; + parse->cluster_offset = 0; + parse->need_newsegment = TRUE; + /* but keep some of the upstream segment */ + parse->segment.rate = rate; + exit: + /* chain will send initial newsegment after pads have been added, + * or otherwise come up with one */ + GST_DEBUG_OBJECT (parse, "eating event"); + gst_event_unref (event); + res = TRUE; + break; + } + case GST_EVENT_EOS: + { + if (parse->state != GST_MATROSKA_PARSE_STATE_DATA) { + gst_event_unref (event); + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, + (NULL), ("got eos and didn't receive a complete header object")); + } else if (parse->num_streams == 0) { + GST_ELEMENT_ERROR (parse, STREAM, DEMUX, + (NULL), ("got eos but no streams (yet)")); + } else { + gst_matroska_parse_send_event (parse, event); + } + break; + } + case GST_EVENT_FLUSH_STOP: + { + gst_adapter_clear (parse->adapter); + GST_OBJECT_LOCK (parse); + gst_matroska_parse_reset_streams (parse, GST_CLOCK_TIME_NONE, TRUE); + GST_OBJECT_UNLOCK (parse); + parse->segment.last_stop = GST_CLOCK_TIME_NONE; + parse->cluster_time = GST_CLOCK_TIME_NONE; + parse->cluster_offset = 0; + /* fall-through */ + } + default: + res = gst_pad_event_default (pad, event); + break; + } + + return res; +} + +static void +gst_matroska_parse_set_index (GstElement * element, GstIndex * index) +{ + GstMatroskaParse *parse = GST_MATROSKA_PARSE (element); + + GST_OBJECT_LOCK (parse); + if (parse->element_index) + gst_object_unref (parse->element_index); + parse->element_index = index ? gst_object_ref (index) : NULL; + GST_OBJECT_UNLOCK (parse); + GST_DEBUG_OBJECT (parse, "Set index %" GST_PTR_FORMAT, parse->element_index); +} + +static GstIndex * +gst_matroska_parse_get_index (GstElement * element) +{ + GstIndex *result = NULL; + GstMatroskaParse *parse = GST_MATROSKA_PARSE (element); + + GST_OBJECT_LOCK (parse); + if (parse->element_index) + result = gst_object_ref (parse->element_index); + GST_OBJECT_UNLOCK (parse); + + GST_DEBUG_OBJECT (parse, "Returning index %" GST_PTR_FORMAT, result); + + return result; +} + +static GstStateChangeReturn +gst_matroska_parse_change_state (GstElement * element, + GstStateChange transition) +{ + GstMatroskaParse *parse = GST_MATROSKA_PARSE (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + /* handle upwards state changes here */ + switch (transition) { + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + /* handle downwards state changes */ + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_matroska_parse_reset (GST_ELEMENT (parse)); + break; + default: + break; + } + + return ret; +} + +gboolean +gst_matroska_parse_plugin_init (GstPlugin * plugin) +{ + gst_riff_init (); + + /* create an elementfactory for the matroska_parse element */ + if (!gst_element_register (plugin, "matroskaparse", + GST_RANK_NONE, GST_TYPE_MATROSKA_PARSE)) + return FALSE; + + return TRUE; +} diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-parse.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska-parse.h Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,146 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska-parse.h: matroska file/stream parseer definition + * + * 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_MATROSKA_PARSE_H__ +#define __GST_MATROSKA_PARSE_H__ + +#include +#include + +#include "ebml-read.h" +#include "matroska-ids.h" + +G_BEGIN_DECLS + +#define GST_TYPE_MATROSKA_PARSE \ + (gst_matroska_parse_get_type ()) +#define GST_MATROSKA_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_MATROSKA_PARSE, GstMatroskaParse)) +#define GST_MATROSKA_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_MATROSKA_PARSE, GstMatroskaParseClass)) +#define GST_IS_MATROSKA_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_MATROSKA_PARSE)) +#define GST_IS_MATROSKA_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MATROSKA_PARSE)) + +typedef enum { + GST_MATROSKA_PARSE_STATE_START, + GST_MATROSKA_PARSE_STATE_SEGMENT, + GST_MATROSKA_PARSE_STATE_HEADER, + GST_MATROSKA_PARSE_STATE_DATA, + GST_MATROSKA_PARSE_STATE_SEEK, + GST_MATROSKA_PARSE_STATE_SCANNING +} GstMatroskaParseState; + +typedef struct _GstMatroskaParse { + GstElement parent; + + /* < private > */ + + GstIndex *element_index; + gint element_index_writer_id; + + /* pads */ + GstPad *sinkpad; + GstPad *srcpad; + GPtrArray *src; + GstClock *clock; + guint num_streams; + guint num_v_streams; + guint num_a_streams; + guint num_t_streams; + + GstBuffer *streamheader; + gboolean pushed_headers; + GstClockTime last_timestamp; + + /* metadata */ + gchar *muxing_app; + gchar *writing_app; + gint64 created; + + /* state */ + //gboolean streaming; + GstMatroskaParseState state; + guint level_up; + guint64 seek_block; + gboolean seek_first; + + /* did we parse cues/tracks/segmentinfo already? */ + gboolean index_parsed; + gboolean tracks_parsed; + gboolean segmentinfo_parsed; + gboolean attachments_parsed; + GList *tags_parsed; + GList *seek_parsed; + + /* start-of-segment */ + guint64 ebml_segment_start; + + /* a cue (index) table */ + GArray *index; + + /* timescale in the file */ + guint64 time_scale; + + /* keeping track of playback position */ + GstSegment segment; + gboolean segment_running; + GstClockTime last_stop_end; + + GstEvent *close_segment; + GstEvent *new_segment; + GstTagList *global_tags; + + /* pull mode caching */ + GstBuffer *cached_buffer; + + /* push and pull mode */ + guint64 offset; + /* some state saving */ + GstClockTime cluster_time; + guint64 cluster_offset; + guint64 first_cluster_offset; + guint64 next_cluster_offset; + + /* push based mode usual suspects */ + GstAdapter *adapter; + /* index stuff */ + gboolean seekable; + gboolean building_index; + guint64 index_offset; + GstEvent *seek_event; + gboolean need_newsegment; + + /* reverse playback */ + GArray *seek_index; + gint seek_entry; +} GstMatroskaParse; + +typedef struct _GstMatroskaParseClass { + GstElementClass parent; +} GstMatroskaParseClass; + +gboolean gst_matroska_parse_plugin_init (GstPlugin *plugin); + +G_END_DECLS + +#endif /* __GST_MATROSKA_PARSE_H__ */ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/matroska.c Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,65 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska.c: plugin loader + * + * 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 "matroska-demux.h" +#include "matroska-parse.h" +#include "matroska-mux.h" +#include "matroska-ids.h" +#include "webm-mux.h" + +#include + +#ifdef GSTREAMER_LITE +gboolean +plugin_init_matroska (GstPlugin * plugin) +#else // GSTREAMER_LITE +static gboolean +plugin_init (GstPlugin * plugin) +#endif // GSTREAMER_LITE + +{ + gboolean ret; + + gst_pb_utils_init (); + + gst_matroska_register_tags (); + + ret = gst_matroska_demux_plugin_init (plugin); + ret &= gst_matroska_parse_plugin_init (plugin); + ret &= gst_element_register (plugin, "matroskamux", GST_RANK_PRIMARY, + GST_TYPE_MATROSKA_MUX); + ret &= gst_element_register (plugin, "webmmux", GST_RANK_PRIMARY, + GST_TYPE_WEBM_MUX); + + return ret; +} + +#ifndef GSTREAMER_LITE +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "matroska", + "Matroska and WebM stream handling", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) +#endif diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/webm-mux.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/webm-mux.c Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,105 @@ +/* GStreamer WebM muxer + * Copyright (c) 2010 Sebastian Dröge + * + * 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:element-webmmux + * + * webmmux muxes VP8 video and Vorbis audio streams into a WebM file. + * + * + * Example launch line + * |[ + * gst-launch-0.10 webmmux name=mux ! filesink location=newfile.webm \ + * uridecodebin uri=file:///path/to/somefile.ogv name=demux \ + * demux. ! ffmpegcolorspace ! vp8enc ! queue ! mux.video_0 \ + * demux. ! progressreport ! audioconvert ! audiorate ! vorbisenc ! queue ! mux.audio_0 + * ]| This pipeline re-encodes a video file of any format into a WebM file. + * |[ + * gst-launch-0.10 webmmux name=mux ! filesink location=test.webm \ + * videotestsrc num-buffers=250 ! video/x-raw-yuv,framerate=25/1 ! ffmpegcolorspace ! vp8enc ! queue ! mux.video_0 \ + * audiotestsrc samplesperbuffer=44100 num-buffers=10 ! audio/x-raw-float,rate=44100 ! vorbisenc ! queue ! mux.audio_0 + * ]| This pipeline muxes a test video and a sine wave into a WebM file. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "webm-mux.h" + +#define COMMON_VIDEO_CAPS \ + "width = (int) [ 16, 4096 ], " \ + "height = (int) [ 16, 4096 ], " \ + "framerate = (fraction) [ 0, MAX ]" + +#define COMMON_AUDIO_CAPS \ + "channels = (int) [ 1, MAX ], " \ + "rate = (int) [ 1, MAX ]" + +GST_BOILERPLATE (GstWebMMux, gst_webm_mux, GstMatroskaMux, + GST_TYPE_MATROSKA_MUX); + +static GstStaticPadTemplate webm_src_templ = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/webm") + ); + +static GstStaticPadTemplate webm_videosink_templ = +GST_STATIC_PAD_TEMPLATE ("video_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("video/x-vp8, " COMMON_VIDEO_CAPS) + ); + +static GstStaticPadTemplate webm_audiosink_templ = +GST_STATIC_PAD_TEMPLATE ("audio_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("audio/x-vorbis, " COMMON_AUDIO_CAPS) + ); + +static void +gst_webm_mux_base_init (gpointer g_class) +{ +} + +static void +gst_webm_mux_class_init (GstWebMMuxClass * klass) +{ + GstElementClass *gstelement_class = (GstElementClass *) klass; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&webm_videosink_templ)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&webm_audiosink_templ)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&webm_src_templ)); + gst_element_class_set_details_simple (gstelement_class, "WebM muxer", + "Codec/Muxer", + "Muxes video and audio streams into a WebM stream", + "GStreamer maintainers "); +} + +static void +gst_webm_mux_init (GstWebMMux * mux, GstWebMMuxClass * g_class) +{ + GST_MATROSKA_MUX (mux)->doctype = GST_MATROSKA_DOCTYPE_WEBM; +} diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/webm-mux.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/matroska/webm-mux.h Tue Mar 25 23:14:51 2014 +0000 @@ -0,0 +1,49 @@ +/* GStreamer WebM muxer + * Copyright (c) 2010 Sebastian Dröge + * + * 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_WEBM_MUX_H__ +#define __GST_WEBM_MUX_H__ + +#include "matroska-mux.h" + +#define GST_TYPE_WEBM_MUX \ + (gst_webm_mux_get_type ()) +#define GST_WEBM_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WEBM_MUX, GstWebMMux)) +#define GST_WEBM_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_WEBM_MUX, GstWebMMuxClass)) +#define GST_IS_WEBM_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_WEBM_MUX)) +#define GST_IS_WEBM_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_WEBM_MUX)) + +typedef struct _GstWebMMux GstWebMMux; +typedef struct _GstWebMMuxClass GstWebMMuxClass; + +struct _GstWebMMux { + GstMatroskaMux matroskamux; +}; + +struct _GstWebMMuxClass { + GstMatroskaMuxClass matroskamuxclass; +}; + +GType gst_webm_mux_get_type (void); + +#endif /* __GST_WEBM_MUX_H__ */ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.c --- a/modules/media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.c Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.c Tue Mar 25 23:14:51 2014 +0000 @@ -20,6 +20,7 @@ !plugin_init_aiff(plugin) || !plugin_init_app(plugin) || !plugin_init_audioparsers(plugin) || + !plugin_init_matroska(plugin) || !plugin_init_qtdemux(plugin)) return FALSE; diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.h --- a/modules/media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.h Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.h Tue Mar 25 23:14:51 2014 +0000 @@ -18,6 +18,7 @@ gboolean plugin_init_aiff (GstPlugin * plugin); gboolean plugin_init_app (GstPlugin * plugin); gboolean plugin_init_audioparsers (GstPlugin * plugin); +gboolean plugin_init_matroska (GstPlugin * plugin); gboolean plugin_init_qtdemux (GstPlugin * plugin); #ifdef WIN32 diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite.def --- a/modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite.def Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite.def Tue Mar 25 23:14:51 2014 +0000 @@ -182,3 +182,6 @@ gst_type_register_static_full @181 NONAME gst_value_get_mini_object @182 NONAME gst_value_list_get_value @183 NONAME +gst_byte_writer_free_and_get_buffer @184 NONAME +gst_byte_writer_free @185 NONAME +gst_byte_writer_new_with_size @186 NONAME \ No newline at end of file diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstplugins --- a/modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstplugins Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstplugins Tue Mar 25 23:14:51 2014 +0000 @@ -19,6 +19,7 @@ gst-plugins-good/gst/isomp4/ \ gst-plugins-good/gst/spectrum/ \ gst-plugins-good/gst/wavparse/ \ + gst-plugins-good/gst/matroska/ \ gstreamer/plugins/elements/ \ gstreamer/plugins/indexers/ \ projects/plugins/ @@ -48,6 +49,15 @@ gst-plugins-good/gst/isomp4/qtdemux_types.c \ gst-plugins-good/gst/spectrum/gstspectrum.c \ gst-plugins-good/gst/wavparse/gstwavparse.c \ + gst-plugins-good/gst/matroska/webm-mux.c \ + gst-plugins-good/gst/matroska/matroska-parse.c \ + gst-plugins-good/gst/matroska/matroska-mux.c \ + gst-plugins-good/gst/matroska/matroska-ids.c \ + gst-plugins-good/gst/matroska/matroska-demux.c \ + gst-plugins-good/gst/matroska/matroska.c \ + gst-plugins-good/gst/matroska/lzo.c \ + gst-plugins-good/gst/matroska/ebml-write.c \ + gst-plugins-good/gst/matroska/ebml-read.c \ gstreamer/plugins/elements/gstcapsfilter.c \ gstreamer/plugins/elements/gstelements.c \ gstreamer/plugins/elements/gstfakesink.c \ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstreamer --- a/modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstreamer Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstreamer Tue Mar 25 23:14:51 2014 +0000 @@ -82,6 +82,7 @@ gstreamer/libs/gst/base/gstbasesrc.c \ gstreamer/libs/gst/base/gstbasetransform.c \ gstreamer/libs/gst/base/gstbytereader.c \ + gstreamer/libs/gst/base/gstbytewriter.c \ gstreamer/libs/gst/base/gstcollectpads.c \ gstreamer/libs/gst/base/gstpushsrc.c \ gstreamer/libs/gst/base/gsttypefindhelper.c \ diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/jfxmedia/MediaManagement/MediaTypes.h --- a/modules/media/src/main/native/jfxmedia/MediaManagement/MediaTypes.h Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/jfxmedia/MediaManagement/MediaTypes.h Tue Mar 25 23:14:51 2014 +0000 @@ -40,6 +40,7 @@ #define CONTENT_TYPE_MP4 "video/mp4" #define CONTENT_TYPE_M4A "audio/x-m4a" #define CONTENT_TYPE_M4V "video/x-m4v" +#define CONTENT_TYPE_MKV "video/x-matroska" #define CONTENT_TYPE_M3U8 "application/vnd.apple.mpegurl" #define CONTENT_TYPE_M3U "audio/mpegurl" #define CONTENT_TYPE_MP2T "video/MP2T" diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.cpp --- a/modules/media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.cpp Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.cpp Tue Mar 25 23:14:51 2014 +0000 @@ -63,6 +63,7 @@ m_ContentTypes.push_back(CONTENT_TYPE_MP4); m_ContentTypes.push_back(CONTENT_TYPE_M4A); m_ContentTypes.push_back(CONTENT_TYPE_M4V); + m_ContentTypes.push_back(CONTENT_TYPE_MKV); m_ContentTypes.push_back(CONTENT_TYPE_M3U8); m_ContentTypes.push_back(CONTENT_TYPE_M3U); } @@ -106,6 +107,7 @@ CONTENT_TYPE_FXM == locator->GetContentType() || CONTENT_TYPE_MP4 == locator->GetContentType() || CONTENT_TYPE_M4A == locator->GetContentType() || + CONTENT_TYPE_MKV == locator->GetContentType() || CONTENT_TYPE_M4V == locator->GetContentType()) { GstElement* pVideoSink = NULL; @@ -129,7 +131,12 @@ uRetCode = CreateMP4Pipeline(pSource, pVideoSink, (CPipelineOptions*) pOptions, ppPipeline); if (ERROR_NONE != uRetCode) return uRetCode; - } + } else if (CONTENT_TYPE_MKV == locator->GetContentType()) + { + uRetCode = CreateMKVPipeline(pSource, pVideoSink, (CPipelineOptions*) pOptions, ppPipeline); + if (ERROR_NONE != uRetCode) + return uRetCode; + } } else if (CONTENT_TYPE_MPA == locator->GetContentType() || CONTENT_TYPE_MP3 == locator->GetContentType()) @@ -549,6 +556,25 @@ #endif // TARGET_OS_WIN32 } +uint32_t CGstPipelineFactory::CreateMKVPipeline(GstElement* source, GstElement* pVideoSink, + CPipelineOptions* pOptions, CPipeline** ppPipeline) +{ +#if TARGET_OS_WIN32 + return CreateAVPipeline(source, "matroskademux", "dshowwrapper", true, "dshowwrapper", pVideoSink, pOptions, ppPipeline); +#elif TARGET_OS_MAC + return CreateAVPipeline(source, "matroskademux", "audioconverter", false, "avcdecoder", pVideoSink, pOptions, ppPipeline); +#elif TARGET_OS_LINUX +#if ENABLE_GST_FFMPEG + return CreateAVPipeline(source, "matroskademux", "ffdec_aac", true, + "ffdec_h264", pVideoSink, pOptions, ppPipeline); +#else // ENABLE_GST_FFMPEG + return CreateAVPipeline(source, "matroskademux", "avaudiodecoder", false, "avvideodecoder", pVideoSink, pOptions, ppPipeline); +#endif // ENABLE_GST_FFMPEG +#else + return ERROR_PLATFORM_UNSUPPORTED; +#endif // TARGET_OS_WIN32 +} + /** * GstElement* CreateMP4Pipeline(GstElement* source, char* demux_factory, * char* audiodec_factory, char* videodec_factory, diff -r 6c7047fd93f0 -r e3c305de970c modules/media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.h --- a/modules/media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.h Fri Mar 21 17:15:41 2014 -0700 +++ b/modules/media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.h Tue Mar 25 23:14:51 2014 +0000 @@ -57,6 +57,7 @@ void NegotiatePixelFormat(GstElement* pVideoSink, CPipelineOptions* pOptions); uint32_t CreateFLVPipeline(GstElement* source, GstElement* videosink, CPipelineOptions* pOptions, CPipeline** ppPipeline); + uint32_t CreateMKVPipeline(GstElement* source, GstElement* videosink, CPipelineOptions* pOptions, CPipeline** ppPipeline); uint32_t CreateMP4Pipeline(GstElement* source, GstElement* videosink, CPipelineOptions* pOptions, CPipeline** ppPipeline); uint32_t CreateMp3AudioPipeline(GstElement* source, CPipelineOptions* pOptions, CPipeline** ppPipeline); uint32_t CreateWavPcmAudioPipeline(GstElement* source, CPipelineOptions* pOptions, CPipeline **ppPipeline);