diff options
Diffstat (limited to 'gst/asfdemux')
-rw-r--r-- | gst/asfdemux/Makefile.am | 27 | ||||
-rw-r--r-- | gst/asfdemux/Makefile.in | 888 | ||||
-rw-r--r-- | gst/asfdemux/README | 88 | ||||
-rw-r--r-- | gst/asfdemux/asfheaders.c | 212 | ||||
-rw-r--r-- | gst/asfdemux/asfheaders.h | 188 | ||||
-rwxr-xr-x | gst/asfdemux/asfpacket.c | 658 | ||||
-rw-r--r-- | gst/asfdemux/asfpacket.h | 74 | ||||
-rw-r--r-- | gst/asfdemux/gstasf.c | 72 | ||||
-rw-r--r-- | gst/asfdemux/gstasfdemux.c | 4367 | ||||
-rw-r--r-- | gst/asfdemux/gstasfdemux.h | 223 | ||||
-rw-r--r-- | gst/asfdemux/gstrtpasfdepay.c | 545 | ||||
-rw-r--r-- | gst/asfdemux/gstrtpasfdepay.h | 64 | ||||
-rw-r--r-- | gst/asfdemux/gstrtspwms.c | 236 | ||||
-rw-r--r-- | gst/asfdemux/gstrtspwms.h | 50 |
14 files changed, 7692 insertions, 0 deletions
diff --git a/gst/asfdemux/Makefile.am b/gst/asfdemux/Makefile.am new file mode 100644 index 0000000..93fa1e8 --- /dev/null +++ b/gst/asfdemux/Makefile.am @@ -0,0 +1,27 @@ +plugin_LTLIBRARIES = libgstasf.la + +libgstasf_la_SOURCES = gstasfdemux.c gstasf.c asfheaders.c asfpacket.c gstrtpasfdepay.c gstrtspwms.c +libgstasf_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) +libgstasf_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) \ + -lgstriff-@GST_API_VERSION@ -lgstrtsp-@GST_API_VERSION@ -lgstsdp-@GST_API_VERSION@ \ + -lgstrtp-@GST_API_VERSION@ -lgstaudio-@GST_API_VERSION@ -lgsttag-@GST_API_VERSION@ \ + $(GST_BASE_LIBS) $(GST_LIBS) \ + $(WIN32_LIBS) +libgstasf_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstasf_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +noinst_HEADERS = gstasfdemux.h asfheaders.h asfpacket.h gstrtpasfdepay.h gstrtspwms.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstasf -:SHARED libgstasf \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstasf_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstasf_la_CFLAGS) \ + -:LDFLAGS $(libgstasf_la_LDFLAGS) \ + $(libgstasf_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ diff --git a/gst/asfdemux/Makefile.in b/gst/asfdemux/Makefile.in new file mode 100644 index 0000000..b64afd9 --- /dev/null +++ b/gst/asfdemux/Makefile.in @@ -0,0 +1,888 @@ +# Makefile.in generated by automake 1.14.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 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@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +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@ +target_triplet = @target@ +subdir = gst/asfdemux +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/depcomp $(noinst_HEADERS) README +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-libtool.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-function.m4 \ + $(top_srcdir)/common/m4/gst-gettext.m4 \ + $(top_srcdir)/common/m4/gst-glib2.m4 \ + $(top_srcdir)/common/m4/gst-package-release-datetime.m4 \ + $(top_srcdir)/common/m4/gst-plugin-docs.m4 \ + $(top_srcdir)/common/m4/gst-plugindir.m4 \ + $(top_srcdir)/common/m4/gst.m4 \ + $(top_srcdir)/common/m4/gtk-doc.m4 \ + $(top_srcdir)/common/m4/orc.m4 $(top_srcdir)/common/m4/pkg.m4 \ + $(top_srcdir)/m4/a52.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gst-sid.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__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(plugindir)" +LTLIBRARIES = $(plugin_LTLIBRARIES) +am__DEPENDENCIES_1 = +libgstasf_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_libgstasf_la_OBJECTS = libgstasf_la-gstasfdemux.lo \ + libgstasf_la-gstasf.lo libgstasf_la-asfheaders.lo \ + libgstasf_la-asfpacket.lo libgstasf_la-gstrtpasfdepay.lo \ + libgstasf_la-gstrtspwms.lo +libgstasf_la_OBJECTS = $(am_libgstasf_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libgstasf_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(libgstasf_la_CFLAGS) $(CFLAGS) \ + $(libgstasf_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +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_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +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_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libgstasf_la_SOURCES) +DIST_SOURCES = $(libgstasf_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +A52DEC_CFLAGS = @A52DEC_CFLAGS@ +A52DEC_LIBS = @A52DEC_LIBS@ +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMRNB_CFLAGS = @AMRNB_CFLAGS@ +AMRNB_LIBS = @AMRNB_LIBS@ +AMRWB_CFLAGS = @AMRWB_CFLAGS@ +AMRWB_LIBS = @AMRWB_LIBS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CDIO_CFLAGS = @CDIO_CFLAGS@ +CDIO_LIBS = @CDIO_LIBS@ +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@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +DVDREAD_LIBS = @DVDREAD_LIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ERROR_CFLAGS = @ERROR_CFLAGS@ +ERROR_CXXFLAGS = @ERROR_CXXFLAGS@ +EXEEXT = @EXEEXT@ +FFLAGS = @FFLAGS@ +FGREP = @FGREP@ +GCOV = @GCOV@ +GCOV_CFLAGS = @GCOV_CFLAGS@ +GCOV_LIBS = @GCOV_LIBS@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GIO_CFLAGS = @GIO_CFLAGS@ +GIO_LDFLAGS = @GIO_LDFLAGS@ +GIO_LIBS = @GIO_LIBS@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_EXTRA_CFLAGS = @GLIB_EXTRA_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GLIB_PREFIX = @GLIB_PREFIX@ +GLIB_REQ = @GLIB_REQ@ +GMODULE_NO_EXPORT_CFLAGS = @GMODULE_NO_EXPORT_CFLAGS@ +GMODULE_NO_EXPORT_LIBS = @GMODULE_NO_EXPORT_LIBS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GREP = @GREP@ +GSTPB_PLUGINS_DIR = @GSTPB_PLUGINS_DIR@ +GSTPB_PREFIX = @GSTPB_PREFIX@ +GST_AGE = @GST_AGE@ +GST_ALL_LDFLAGS = @GST_ALL_LDFLAGS@ +GST_API_VERSION = @GST_API_VERSION@ +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_CURRENT = @GST_CURRENT@ +GST_CXXFLAGS = @GST_CXXFLAGS@ +GST_LEVEL_DEFAULT = @GST_LEVEL_DEFAULT@ +GST_LIBS = @GST_LIBS@ +GST_LIBVERSION = @GST_LIBVERSION@ +GST_LICENSE = @GST_LICENSE@ +GST_LT_LDFLAGS = @GST_LT_LDFLAGS@ +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_NONPORTED = @GST_PLUGINS_NONPORTED@ +GST_PLUGINS_SELECTED = @GST_PLUGINS_SELECTED@ +GST_PLUGIN_LDFLAGS = @GST_PLUGIN_LDFLAGS@ +GST_PLUGIN_LIBTOOLFLAGS = @GST_PLUGIN_LIBTOOLFLAGS@ +GST_PREFIX = @GST_PREFIX@ +GST_REVISION = @GST_REVISION@ +GST_TOOLS_DIR = @GST_TOOLS_DIR@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +HAVE_CXX = @HAVE_CXX@ +HAVE_DVDREAD = @HAVE_DVDREAD@ +HAVE_LAME = @HAVE_LAME@ +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@ +LAME_CFLAGS = @LAME_CFLAGS@ +LAME_LIBS = @LAME_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOCALEDIR = @LOCALEDIR@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAD_CFLAGS = @MAD_CFLAGS@ +MAD_LIBS = @MAD_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MPEG2DEC_CFLAGS = @MPEG2DEC_CFLAGS@ +MPEG2DEC_LIBS = @MPEG2DEC_LIBS@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NM = @NM@ +NMEDIT = @NMEDIT@ +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@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SIDPLAY_CFLAGS = @SIDPLAY_CFLAGS@ +SIDPLAY_LIBS = @SIDPLAY_LIBS@ +STRIP = @STRIP@ +TWOLAME_CFLAGS = @TWOLAME_CFLAGS@ +TWOLAME_LIBS = @TWOLAME_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@ +WIN32_LIBS = @WIN32_LIBS@ +X264_CFLAGS = @X264_CFLAGS@ +X264_LIBS = @X264_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +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@ +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 = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +plugin_LTLIBRARIES = libgstasf.la +libgstasf_la_SOURCES = gstasfdemux.c gstasf.c asfheaders.c asfpacket.c gstrtpasfdepay.c gstrtspwms.c +libgstasf_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) +libgstasf_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) \ + -lgstriff-@GST_API_VERSION@ -lgstrtsp-@GST_API_VERSION@ -lgstsdp-@GST_API_VERSION@ \ + -lgstrtp-@GST_API_VERSION@ -lgstaudio-@GST_API_VERSION@ -lgsttag-@GST_API_VERSION@ \ + $(GST_BASE_LIBS) $(GST_LIBS) \ + $(WIN32_LIBS) + +libgstasf_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstasf_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) +noinst_HEADERS = gstasfdemux.h asfheaders.h asfpacket.h gstrtpasfdepay.h gstrtspwms.h +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu gst/asfdemux/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu gst/asfdemux/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) + @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 " $(MKDIR_P) '$(DESTDIR)$(plugindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(plugindir)" || exit 1; \ + 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)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libgstasf.la: $(libgstasf_la_OBJECTS) $(libgstasf_la_DEPENDENCIES) $(EXTRA_libgstasf_la_DEPENDENCIES) + $(AM_V_CCLD)$(libgstasf_la_LINK) -rpath $(plugindir) $(libgstasf_la_OBJECTS) $(libgstasf_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstasf_la-asfheaders.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstasf_la-asfpacket.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstasf_la-gstasf.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstasf_la-gstasfdemux.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstasf_la-gstrtpasfdepay.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstasf_la-gstrtspwms.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +libgstasf_la-gstasfdemux.lo: gstasfdemux.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -MT libgstasf_la-gstasfdemux.lo -MD -MP -MF $(DEPDIR)/libgstasf_la-gstasfdemux.Tpo -c -o libgstasf_la-gstasfdemux.lo `test -f 'gstasfdemux.c' || echo '$(srcdir)/'`gstasfdemux.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstasf_la-gstasfdemux.Tpo $(DEPDIR)/libgstasf_la-gstasfdemux.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstasfdemux.c' object='libgstasf_la-gstasfdemux.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -c -o libgstasf_la-gstasfdemux.lo `test -f 'gstasfdemux.c' || echo '$(srcdir)/'`gstasfdemux.c + +libgstasf_la-gstasf.lo: gstasf.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -MT libgstasf_la-gstasf.lo -MD -MP -MF $(DEPDIR)/libgstasf_la-gstasf.Tpo -c -o libgstasf_la-gstasf.lo `test -f 'gstasf.c' || echo '$(srcdir)/'`gstasf.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstasf_la-gstasf.Tpo $(DEPDIR)/libgstasf_la-gstasf.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstasf.c' object='libgstasf_la-gstasf.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -c -o libgstasf_la-gstasf.lo `test -f 'gstasf.c' || echo '$(srcdir)/'`gstasf.c + +libgstasf_la-asfheaders.lo: asfheaders.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -MT libgstasf_la-asfheaders.lo -MD -MP -MF $(DEPDIR)/libgstasf_la-asfheaders.Tpo -c -o libgstasf_la-asfheaders.lo `test -f 'asfheaders.c' || echo '$(srcdir)/'`asfheaders.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstasf_la-asfheaders.Tpo $(DEPDIR)/libgstasf_la-asfheaders.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='asfheaders.c' object='libgstasf_la-asfheaders.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -c -o libgstasf_la-asfheaders.lo `test -f 'asfheaders.c' || echo '$(srcdir)/'`asfheaders.c + +libgstasf_la-asfpacket.lo: asfpacket.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -MT libgstasf_la-asfpacket.lo -MD -MP -MF $(DEPDIR)/libgstasf_la-asfpacket.Tpo -c -o libgstasf_la-asfpacket.lo `test -f 'asfpacket.c' || echo '$(srcdir)/'`asfpacket.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstasf_la-asfpacket.Tpo $(DEPDIR)/libgstasf_la-asfpacket.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='asfpacket.c' object='libgstasf_la-asfpacket.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -c -o libgstasf_la-asfpacket.lo `test -f 'asfpacket.c' || echo '$(srcdir)/'`asfpacket.c + +libgstasf_la-gstrtpasfdepay.lo: gstrtpasfdepay.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -MT libgstasf_la-gstrtpasfdepay.lo -MD -MP -MF $(DEPDIR)/libgstasf_la-gstrtpasfdepay.Tpo -c -o libgstasf_la-gstrtpasfdepay.lo `test -f 'gstrtpasfdepay.c' || echo '$(srcdir)/'`gstrtpasfdepay.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstasf_la-gstrtpasfdepay.Tpo $(DEPDIR)/libgstasf_la-gstrtpasfdepay.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstrtpasfdepay.c' object='libgstasf_la-gstrtpasfdepay.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -c -o libgstasf_la-gstrtpasfdepay.lo `test -f 'gstrtpasfdepay.c' || echo '$(srcdir)/'`gstrtpasfdepay.c + +libgstasf_la-gstrtspwms.lo: gstrtspwms.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -MT libgstasf_la-gstrtspwms.lo -MD -MP -MF $(DEPDIR)/libgstasf_la-gstrtspwms.Tpo -c -o libgstasf_la-gstrtspwms.lo `test -f 'gstrtspwms.c' || echo '$(srcdir)/'`gstrtspwms.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstasf_la-gstrtspwms.Tpo $(DEPDIR)/libgstasf_la-gstrtspwms.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstrtspwms.c' object='libgstasf_la-gstrtspwms.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstasf_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstasf_la_CFLAGS) $(CFLAGS) -c -o libgstasf_la-gstrtspwms.lo `test -f 'gstrtspwms.c' || echo '$(srcdir)/'`gstrtspwms.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + 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-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + 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" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +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: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +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 TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-pluginLTLIBRARIES cscopelist-am ctags \ + ctags-am 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 tags-am uninstall uninstall-am \ + uninstall-pluginLTLIBRARIES + + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstasf -:SHARED libgstasf \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstasf_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstasf_la_CFLAGS) \ + -:LDFLAGS $(libgstasf_la_LDFLAGS) \ + $(libgstasf_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 --git a/gst/asfdemux/README b/gst/asfdemux/README new file mode 100644 index 0000000..d864eb0 --- /dev/null +++ b/gst/asfdemux/README @@ -0,0 +1,88 @@ +ASF Demuxer Plugin +================== + +Overview +-------- + +This plugin is a demuxer for Microsoft's ASF Advanced Streaming Format +or ASF [1]. This demuxer only supports ASF v1.0 since the vast +majority of existing ASF files use that version. The specification +has been derived from a third party source [2] without reference to +the original. + +Design +------ + +The ASF format can carry any combination of audio, video or +'ASF_Command_Media' streams. For simplicity it is assumed that each +file can carry up to 16 audio streams and 16 video streams. These are +implemented as dynamic pads and appear as appropriate once the file +headers have been parsed. + + (-------------------------) + ! asfdemux ! + ! (video/raw0)--- + ! (video/raw1)--- + ! (video/raw... + --- src ! + ! (audio/raw0)--- + ! (audio/raw1)--- + ! (audio/raw... + ! ! + (-------------------------) + + +Known stream fourccs are: + +Type Tags MIME type +------------------------------------------ +H263 H263 I263 video/x-h263 +MJPEG MJPG image/jpeg +MPEG4 DIVX divx DX50 video/mpeg + XVID xvid mp4s + MP4S M4S2 m4s2 + 0x04000000 +MSMPEG4V1 MPG4 video/mpeg +MSMPEG4V2 MP42 video/mpeg +MSMPEG4V3 MP43 DIV3 video/mpeg +WMV1 WMV1 video/x-wmv, wmvversion = (int) 1 +WMV2 WMV2 video/x-wmv, wmvversion = (int) 2 +WMV3 WMV3 video/x-wmv, wmvversion = (int) 3 +WMA1 WMA1 audio/x-wma, wmaversion = (int) 1 +WMA2 WMA2 audio/x-wma, wmaversion = (int) 2 + audio/x-wma, wmaversion = (int) 3 + +These video stream headers is very similar to that used in the AVI +format as are the audio stream headers. In addition the content types +are basically the same also so, for compatibility with existing +plugins the src pads are set up as video/x-msvideo. This enables +compatibility with the ffmpeg plugin. + +The demuxing process begins with the loop function gst_asf_demux_loop +and parses the file in a recursive tree as follows: + + gst_asf_demux_loop() + +-> gst_asf_demux_process_object() <---- + +-> gst_asf_demux_process_stream() \ + |-> gst_asf_demux_process_file() | + |-> gst_asf_demux_process_header() --+ + |-> gst_asf_demux_process_data() + +-> gst_asf_demux_process_segment() + +-> gst_asf_demux_process_chunk() + +Todo +---- + +- Support for ASF v2.0 +- Support for command media streams + + + +References +---------- + +[1] Microsoft. ASF Specification - Windows Media Technologies. +http://www.microsoft.com/windows/windowsmedia/format/asfspec.aspx (v01.20.01e, September 2003) + +[2] divx at euro.ru. ASF format version 1.0, +reconstruction. http://avifile.sourceforge.net/asf-1.0.htm diff --git a/gst/asfdemux/asfheaders.c b/gst/asfdemux/asfheaders.c new file mode 100644 index 0000000..b8e8a3c --- /dev/null +++ b/gst/asfdemux/asfheaders.c @@ -0,0 +1,212 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/gst.h> + +#include "asfheaders.h" + +const ASFGuidHash asf_payload_ext_guids[] = { + {ASF_PAYLOAD_EXTENSION_DURATION, "ASF_PAYLOAD_EXTENSION_DURATION", + {0xC6BD9450, 0x4907867F, 0x79C7A383, 0xAD33B721} + }, + {ASF_PAYLOAD_EXTENSION_SYSTEM_CONTENT, "ASF_PAYLOAD_EXTENSION_SYSTEM_CONTENT", + {0xD590DC20, 0x436C07BC, 0xBBF3f79C, 0xDCA4F1FB}}, + {ASF_PAYLOAD_EXTENSION_SYSTEM_PIXEL_ASPECT_RATIO, + "ASF_PAYLOAD_EXTENSION_SYSTEM_PIXEL_ASPECT_RATIO", + {0x1b1ee554, 0x4bc8f9ea, 0x6b371a82, 0xb8c4e474}}, + {ASF_PAYLOAD_EXTENSION_TIMING, "ASF_PAYLOAD_EXTENSION_TIMING", + {0XFD3CC02A, 0X4CFA06DB, 0X12721C80, 0XE44587D3}}, + {ASF_PAYLOAD_EXTENSION_UNDEFINED, "ASF_PAYLOAD_EXTENSION_UNDEFINED", + {0, 0, 0, 0} + } +}; + +const ASFGuidHash asf_correction_guids[] = { + {ASF_CORRECTION_ON, "ASF_CORRECTION_ON", + {0xBFC3CD50, 0x11CF618F, 0xAA00B28B, 0x20E2B400} + }, + {ASF_CORRECTION_OFF, "ASF_CORRECTION_OFF", + {0x20FB5700, 0x11CF5B55, 0x8000FDA8, 0x2B445C5F} + }, + /* CHECKME: where does this 49F1A440... GUID come from? (tpm) */ + {ASF_CORRECTION_OFF, "ASF_CORRECTION_OFF", + {0x49F1A440, 0x11D04ECE, 0xA000ACA3, 0xF64803C9} + }, + {ASF_CORRECTION_UNDEFINED, "ASF_CORRECTION_UNDEFINED", + {0, 0, 0, 0} + } +}; + +const ASFGuidHash asf_stream_guids[] = { + {ASF_STREAM_VIDEO, "ASF_STREAM_VIDEO", + {0xBC19EFC0, 0x11CF5B4D, 0x8000FDA8, 0x2B445C5F} + }, + {ASF_STREAM_AUDIO, "ASF_STREAM_AUDIO", + {0xF8699E40, 0x11CF5B4D, 0x8000FDA8, 0x2B445C5F} + }, + {ASF_STREAM_EXT_EMBED_HEADER, "ASF_STREAM_EXT_EMBED_HEADER", + {0X3AFB65E2, 0X40F247EF, 0XA9702CAC, 0X43D3710D}}, + {ASF_STREAM_UNDEFINED, "ASF_STREAM_UNDEFINED", + {0, 0, 0, 0} + } +}; + +const ASFGuidHash asf_ext_stream_guids[] = { + {ASF_EXT_STREAM_AUDIO, "ASF_EXT_STREAM_AUDIO", + {0X31178C9D, 0X452803E1, 0XF93D82B5, 0X03F522DB} + }, + {ASF_EXT_STREAM_UNDEFINED, "ASF_EXT_STREAM_UNDEFINED", + {0, 0, 0, 0} + } +}; + +const ASFGuidHash asf_object_guids[] = { + {ASF_OBJ_STREAM, "ASF_OBJ_STREAM", + {0xB7DC0791, 0x11CFA9B7, 0xC000E68E, 0x6553200C} + }, + {ASF_OBJ_DATA, "ASF_OBJ_DATA", + {0x75b22636, 0x11cf668e, 0xAA00D9a6, 0x6Cce6200} + }, + {ASF_OBJ_FILE, "ASF_OBJ_FILE", + {0x8CABDCA1, 0x11CFA947, 0xC000E48E, 0x6553200C} + }, + {ASF_OBJ_HEADER, "ASF_OBJ_HEADER", + {0x75B22630, 0x11CF668E, 0xAA00D9A6, 0x6CCE6200} + }, + {ASF_OBJ_CONCEAL_NONE, "ASF_OBJ_CONCEAL_NONE", + {0x20fb5700, 0x11cf5b55, 0x8000FDa8, 0x2B445C5f} + }, + {ASF_OBJ_COMMENT, "ASF_OBJ_COMMENT", + {0x75b22633, 0x11cf668e, 0xAA00D9a6, 0x6Cce6200} + }, + {ASF_OBJ_CODEC_COMMENT, "ASF_OBJ_CODEC_COMMENT", + {0x86D15240, 0x11D0311D, 0xA000A4A3, 0xF64803C9} + }, + {ASF_OBJ_CODEC_COMMENT1, "ASF_OBJ_CODEC_COMMENT1", + {0x86d15241, 0x11d0311d, 0xA000A4a3, 0xF64803c9} + }, + {ASF_OBJ_SIMPLE_INDEX, "ASF_OBJ_SIMPLE_INDEX", + {0x33000890, 0x11cfe5b1, 0xA000F489, 0xCB4903c9} + }, + {ASF_OBJ_INDEX, "ASF_OBJ_INDEX", + {0xd6e229d3, 0x11d135da, 0xa0003490, 0xbe4903c9} + }, + {ASF_OBJ_HEAD1, "ASF_OBJ_HEAD1", + {0x5fbf03b5, 0x11cfa92e, 0xC000E38e, 0x6553200c} + }, + {ASF_OBJ_HEAD2, "ASF_OBJ_HEAD2", + {0xabd3d211, 0x11cfa9ba, 0xC000E68e, 0x6553200c} + }, + {ASF_OBJ_PADDING, "ASF_OBJ_PADDING", + {0x1806D474, 0x4509CADF, 0xAB9ABAA4, 0xE8AA96CB} + }, + {ASF_OBJ_BITRATE_PROPS, "ASF_OBJ_BITRATE_PROPS", + {0x7bf875ce, 0x11d1468d, 0x6000828d, 0xb2a2c997} + }, + {ASF_OBJ_EXT_CONTENT_DESC, "ASF_OBJ_EXT_CONTENT_DESC", + {0xd2d0a440, 0x11d2e307, 0xa000f097, 0x50a85ec9} + }, + {ASF_OBJ_BITRATE_MUTEX, "ASF_OBJ_BITRATE_MUTEX", + {0xd6e229dc, 0x11d135da, 0xa0003490, 0xbe4903c9} + }, + {ASF_OBJ_LANGUAGE_LIST, "ASF_OBJ_LANGUAGE_LIST", + {0x7c4346a9, 0x4bfcefe0, 0x3e3929b2, 0x855c41de} + }, + {ASF_OBJ_METADATA_OBJECT, "ASF_OBJ_METADATA_OBJECT", + {0xc5f8cbea, 0x48775baf, 0x8caa6784, 0xca4cfa44} + }, + {ASF_OBJ_EXTENDED_STREAM_PROPS, "ASF_OBJ_EXTENDED_STREAM_PROPS", + {0x14e6a5cb, 0x4332c672, 0x69a99983, 0x5a5b0652} + }, + {ASF_OBJ_COMPATIBILITY, "ASF_OBJ_COMPATIBILITY", + {0x26f18b5d, 0x47ec4584, 0x650e5f9f, 0xc952041f} + }, + {ASF_OBJ_INDEX_PLACEHOLDER, "ASF_OBJ_INDEX_PLACEHOLDER", + {0xd9aade20, 0x4f9c7c17, 0x558528bc, 0xa2e298dd} + }, + {ASF_OBJ_INDEX_PARAMETERS, "ASF_OBJ_INDEX_PARAMETERS", + {0xd6e229df, 0x11d135da, 0xa0003490, 0xbe4903c9} + }, + {ASF_OBJ_ADVANCED_MUTUAL_EXCLUSION, "ASF_OBJ_ADVANCED_MUTUAL_EXCLUSION", + {0xa08649cf, 0x46704775, 0x356e168a, 0xcd667535} + }, + {ASF_OBJ_STREAM_PRIORITIZATION, "ASF_OBJ_STREAM_PRIORITIZATION", + {0xd4fed15b, 0x454f88d3, 0x5cedf081, 0x249e9945} + }, + {ASF_OBJ_CONTENT_ENCRYPTION, "ASF_OBJ_CONTENT_ENCRYPTION", + {0x2211b3fb, 0x11d2bd23, 0xa000b7b4, 0x6efc55c9} + }, + {ASF_OBJ_EXT_CONTENT_ENCRYPTION, "ASF_OBJ_EXT_CONTENT_ENCRYPTION", + {0x298ae614, 0x4c172622, 0xe0da35b9, 0x9c28e97e} + }, + {ASF_OBJ_DIGITAL_SIGNATURE_OBJECT, "ASF_OBJ_DIGITAL_SIGNATURE_OBJECT", + {0x2211b3fc, 0x11d2bd23, 0xa000b7b4, 0x6efc55c9} + }, + {ASF_OBJ_SCRIPT_COMMAND, "ASF_OBJ_SCRIPT_COMMAND", + {0x1efb1a30, 0x11d00b62, 0xa0009ba3, 0xf64803c9} + }, + {ASF_OBJ_MARKER, "ASF_OBJ_MARKER", + {0xf487cd01, 0x11cfa951, 0xc000e68e, 0x6553200c} + }, + /* This guid is definitely used for encryption (mentioned in MS smooth + * streaming docs) in new PlayReady (c) (tm) (wtf) system, but I haven't + * found a proper name for it. + * (Edward Jan 11 2011).*/ + {ASF_OBJ_UNKNOWN_ENCRYPTION_OBJECT, "ASF_OBJ_UNKNOWN_ENCRYPTION_OBJECT", + {0x9a04f079, 0x42869840, 0x5be692ab, 0x955f88e0} + }, + {ASF_OBJ_METADATA_LIBRARY_OBJECT, "ASF_OBJ_METADATA_LIBRARY_OBJECT", + {0x44231c94, 0x49d19498, 0x131d41a1, 0x5470454e} + }, + {ASF_OBJ_UNDEFINED, "ASF_OBJ_UNDEFINED", + {0, 0, 0, 0} + } +}; + +guint32 +gst_asf_identify_guid (const ASFGuidHash * guids, ASFGuid * guid) +{ + gint i; + + for (i = 0; guids[i].obj_id != ASF_OBJ_UNDEFINED; ++i) { + if (guids[i].guid.v1 == guid->v1 && + guids[i].guid.v2 == guid->v2 && + guids[i].guid.v3 == guid->v3 && guids[i].guid.v4 == guid->v4) { + return guids[i].obj_id; + } + } + + /* The base case if none is found */ + return ASF_OBJ_UNDEFINED; +} + +const gchar * +gst_asf_get_guid_nick (const ASFGuidHash * guids, guint32 obj_id) +{ + gint i; + + for (i = 0; guids[i].obj_id != ASF_OBJ_UNDEFINED; ++i) { + if (guids[i].obj_id == obj_id) { + return guids[i].obj_id_str; + } + } + + /* The base case if none is found */ + return "ASF_OBJ_UNDEFINED"; +} diff --git a/gst/asfdemux/asfheaders.h b/gst/asfdemux/asfheaders.h new file mode 100644 index 0000000..9e8d972 --- /dev/null +++ b/gst/asfdemux/asfheaders.h @@ -0,0 +1,188 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __ASFHEADERS_H__ +#define __ASFHEADERS_H__ + +G_BEGIN_DECLS + +typedef struct { + guint32 v1; + guint32 v2; + guint32 v3; + guint32 v4; +} ASFGuid; + + + +typedef struct { + guint8 obj_id; + const gchar *obj_id_str; + ASFGuid guid; +} ASFGuidHash; + +typedef enum { + ASF_OBJ_UNDEFINED = 0, + ASF_OBJ_STREAM, + ASF_OBJ_DATA, + ASF_OBJ_FILE, + ASF_OBJ_HEADER, + ASF_OBJ_CONCEAL_NONE, + ASF_OBJ_COMMENT, + ASF_OBJ_CODEC_COMMENT, + ASF_OBJ_CODEC_COMMENT1, + ASF_OBJ_SIMPLE_INDEX, + ASF_OBJ_INDEX, + ASF_OBJ_HEAD1, + ASF_OBJ_HEAD2, + ASF_OBJ_PADDING, + ASF_OBJ_BITRATE_PROPS, + ASF_OBJ_EXT_CONTENT_DESC, + ASF_OBJ_BITRATE_MUTEX, + ASF_OBJ_LANGUAGE_LIST, + ASF_OBJ_METADATA_OBJECT, + ASF_OBJ_EXTENDED_STREAM_PROPS, + ASF_OBJ_COMPATIBILITY, + ASF_OBJ_INDEX_PLACEHOLDER, + ASF_OBJ_INDEX_PARAMETERS, + ASF_OBJ_ADVANCED_MUTUAL_EXCLUSION, + ASF_OBJ_STREAM_PRIORITIZATION, + ASF_OBJ_CONTENT_ENCRYPTION, + ASF_OBJ_EXT_CONTENT_ENCRYPTION, + ASF_OBJ_DIGITAL_SIGNATURE_OBJECT, + ASF_OBJ_SCRIPT_COMMAND, + ASF_OBJ_MARKER, + ASF_OBJ_UNKNOWN_ENCRYPTION_OBJECT, + ASF_OBJ_METADATA_LIBRARY_OBJECT, +} AsfObjectID; + +typedef enum { + ASF_STREAM_UNDEFINED = 0, + ASF_STREAM_VIDEO, + ASF_STREAM_AUDIO, + ASF_STREAM_EXT_EMBED_HEADER +} AsfStreamType; + +typedef enum { + ASF_EXT_STREAM_UNDEFINED = 0, + ASF_EXT_STREAM_AUDIO +} AsfExtStreamType; + +typedef enum { + ASF_CORRECTION_UNDEFINED = 0, + ASF_CORRECTION_ON, + ASF_CORRECTION_OFF +} AsfCorrectionType; + +typedef enum { + ASF_PAYLOAD_EXTENSION_UNDEFINED = 0, + ASF_PAYLOAD_EXTENSION_DURATION, + ASF_PAYLOAD_EXTENSION_SYSTEM_CONTENT, + ASF_PAYLOAD_EXTENSION_SYSTEM_PIXEL_ASPECT_RATIO, + ASF_PAYLOAD_EXTENSION_TIMING +} AsfPayloadExtensionID; + +extern const ASFGuidHash asf_payload_ext_guids[]; + +extern const ASFGuidHash asf_correction_guids[]; + +extern const ASFGuidHash asf_stream_guids[]; + +extern const ASFGuidHash asf_ext_stream_guids[]; + +extern const ASFGuidHash asf_object_guids[]; + +/* GUID utilities */ +guint32 gst_asf_identify_guid (const ASFGuidHash * guids, + ASFGuid * guid); + +const gchar *gst_asf_get_guid_nick (const ASFGuidHash * guids, + guint32 obj_id); + +struct _asf_stream_audio { + guint16 codec_tag; + guint16 channels; + guint32 sample_rate; + guint32 byte_rate; + guint16 block_align; + guint16 word_size; + guint16 size; +}; + +typedef struct _asf_stream_audio asf_stream_audio; + +struct _asf_stream_video { + guint32 width; + guint32 height; + guint8 unknown; + guint16 size; +}; + +typedef struct _asf_stream_video asf_stream_video; + +struct _asf_stream_video_format { + guint32 size; + guint32 width; + guint32 height; + guint16 planes; + guint16 depth; + guint32 tag; + guint32 image_size; + guint32 xpels_meter; + guint32 ypels_meter; + guint32 num_colors; + guint32 imp_colors; +}; + +typedef struct _asf_stream_video_format asf_stream_video_format; + +struct _asf_obj_data_correction { + guint8 type; + guint8 cycle; +}; + +typedef struct _asf_obj_data_correction asf_obj_data_correction; + +struct _asf_packet_info { + guint32 padsize; + guint8 replicsizetype; + guint8 fragoffsettype; + guint8 seqtype; + guint8 segsizetype; + gboolean multiple; + guint32 size_left; +}; + +typedef struct _asf_packet_info asf_packet_info; + +struct _asf_segment_info { + guint8 stream_number; + guint32 chunk_size; + guint32 frag_offset; + guint32 segment_size; + guint32 sequence; + guint32 frag_timestamp; + gboolean compressed; +}; + +typedef struct _asf_segment_info asf_segment_info; + +G_END_DECLS + +#endif /* __ASFHEADERS_H__ */ diff --git a/gst/asfdemux/asfpacket.c b/gst/asfdemux/asfpacket.c new file mode 100755 index 0000000..7379d86 --- /dev/null +++ b/gst/asfdemux/asfpacket.c @@ -0,0 +1,658 @@ +/* GStreamer ASF/WMV/WMA demuxer + * Copyright (C) 2007 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* FIXME: + * file:///home/tpm/samples/video/asf//336370-regis-velo862.wmv + * file:///home/tpm/samples/video/asf//336370-eichhoer.wmv + * throw errors (not always necessarily) in this code path + * (looks like they carry broken payloads/packets though) */ + +#include "asfpacket.h" + +#include <gst/gstutils.h> +#include <gst/gstinfo.h> +#include <string.h> + +/* we are unlikely to deal with lengths > 2GB here any time soon, so just + * return a signed int and use that for error reporting */ +static inline gint +asf_packet_read_varlen_int (guint lentype_flags, guint lentype_bit_offset, + const guint8 ** p_data, guint * p_size) +{ + static const guint lens[4] = { 0, 1, 2, 4 }; + guint len, val; + + len = lens[(lentype_flags >> lentype_bit_offset) & 0x03]; + + /* will make caller bail out with a short read if there's not enough data */ + if (G_UNLIKELY (*p_size < len)) { + GST_WARNING ("need %u bytes, but only %u bytes available", len, *p_size); + return -1; + } + + switch (len) { + case 0: + val = 0; + break; + case 1: + val = GST_READ_UINT8 (*p_data); + break; + case 2: + val = GST_READ_UINT16_LE (*p_data); + break; + case 4: + val = GST_READ_UINT32_LE (*p_data); + break; + default: + val = 0; + g_assert_not_reached (); + } + + *p_data += len; + *p_size -= len; + + return (gint) val; +} + +static GstBuffer * +asf_packet_create_payload_buffer (AsfPacket * packet, const guint8 ** p_data, + guint * p_size, guint payload_len) +{ + guint off; + + g_assert (payload_len <= *p_size); + + off = (guint) (*p_data - packet->bdata); + g_assert (off < gst_buffer_get_size (packet->buf)); + + *p_data += payload_len; + *p_size -= payload_len; + + return gst_buffer_copy_region (packet->buf, GST_BUFFER_COPY_ALL, off, + payload_len); +} + +static AsfPayload * +asf_payload_find_previous_fragment (AsfPayload * payload, AsfStream * stream) +{ + AsfPayload *ret; + + if (G_UNLIKELY (stream->payloads->len == 0)) { + GST_DEBUG ("No previous fragments to merge with for stream %u", stream->id); + return NULL; + } + + ret = + &g_array_index (stream->payloads, AsfPayload, stream->payloads->len - 1); + + if (G_UNLIKELY (ret->mo_size != payload->mo_size || + ret->mo_number != payload->mo_number || ret->mo_offset != 0)) { + if (payload->mo_size != 0) { + GST_WARNING ("Previous fragment does not match continued fragment"); + return NULL; + } else { + /* Warn about this case, but accept it anyway: files in the wild sometimes + * have continued packets where the subsequent fragments say that they're + * zero-sized. */ + GST_WARNING ("Previous fragment found, but current fragment has " + "zero size, accepting anyway"); + } + } +#if 0 + if (this_fragment->mo_offset + this_payload_len > first_fragment->mo_size) { + GST_WARNING ("Merged fragments would be bigger than the media object"); + return FALSE; + } +#endif + + return ret; +} + +/* TODO: if we have another payload already queued for this stream and that + * payload doesn't have a duration, maybe we can calculate a duration for it + * (if the previous timestamp is smaller etc. etc.) */ +static void +gst_asf_payload_queue_for_stream (GstASFDemux * demux, AsfPayload * payload, + AsfStream * stream) +{ + GST_DEBUG_OBJECT (demux, "Got payload for stream %d ts:%" GST_TIME_FORMAT, + stream->id, GST_TIME_ARGS (payload->ts)); + + /* make timestamps start from 0; first_ts will be determined during activation (once we have enough data), + which will also update ts of all packets queued before we knew first_ts; */ + if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (demux->first_ts) + && GST_CLOCK_TIME_IS_VALID (payload->ts))) { + if (payload->ts > demux->first_ts) + payload->ts -= demux->first_ts; + else + payload->ts = 0; + } + + /* remove any incomplete payloads that will never be completed */ + while (stream->payloads->len > 0) { + AsfPayload *prev; + guint idx_last; + + idx_last = stream->payloads->len - 1; + prev = &g_array_index (stream->payloads, AsfPayload, idx_last); + + if (G_UNLIKELY (gst_asf_payload_is_complete (prev))) + break; + + GST_DEBUG_OBJECT (demux, "Dropping incomplete fragmented media object " + "queued for stream %u", stream->id); + + gst_buffer_replace (&prev->buf, NULL); + g_array_remove_index (stream->payloads, idx_last); + + /* there's data missing, so there's a discontinuity now */ + GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DISCONT); + } + + /* If we're about to queue a key frame that is before the segment start, we + * can ditch any previously queued payloads (which would also be before the + * segment start). This makes sure the decoder doesn't decode more than + * absolutely necessary after a seek (we don't push out payloads that are + * before the segment start until we have at least one that falls within the + * segment) */ + if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (payload->ts) && + payload->ts < demux->segment.start && payload->keyframe)) { + GST_DEBUG_OBJECT (demux, "Queueing keyframe before segment start, removing" + " %u previously-queued payloads, which would be out of segment too and" + " hence don't have to be decoded", stream->payloads->len); + while (stream->payloads->len > 0) { + AsfPayload *last; + guint idx_last; + + idx_last = stream->payloads->len - 1; + last = &g_array_index (stream->payloads, AsfPayload, idx_last); + gst_buffer_replace (&last->buf, NULL); + g_array_remove_index (stream->payloads, idx_last); + } + + /* Mark discontinuity (should be done via stream->discont anyway though) */ + GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DISCONT); + } + + g_array_append_vals (stream->payloads, payload, 1); +} + +static void +asf_payload_parse_replicated_data_extensions (AsfStream * stream, + AsfPayload * payload) +{ + AsfPayloadExtension *ext; + guint off; + guint16 ext_len; + + if (!stream->ext_props.valid || stream->ext_props.payload_extensions == NULL) + return; + + off = 8; + for (ext = stream->ext_props.payload_extensions; ext->len > 0; ++ext) { + ext_len = ext->len; + if (ext_len == 0xFFFF) { /* extension length is determined by first two bytes in replicated data */ + ext_len = GST_READ_UINT16_LE (payload->rep_data + off); + off += 2; + } + if (G_UNLIKELY (off + ext_len > payload->rep_data_len)) { + GST_WARNING ("not enough replicated data for defined extensions"); + return; + } + switch (ext->id) { + case ASF_PAYLOAD_EXTENSION_DURATION: + if (G_LIKELY (ext_len == 2)) { + guint16 tdur = GST_READ_UINT16_LE (payload->rep_data + off); + /* packet durations of 1ms are mostly invalid */ + if (tdur != 1) + payload->duration = tdur * GST_MSECOND; + } else { + GST_WARNING ("unexpected DURATION extensions len %u", ext_len); + } + break; + case ASF_PAYLOAD_EXTENSION_SYSTEM_CONTENT: + if (G_LIKELY (ext_len == 1)) { + guint8 data = payload->rep_data[off]; + + payload->interlaced = data & 0x1; + payload->rff = data & 0x8; + payload->tff = (data & 0x2) || !(data & 0x4); + GST_DEBUG ("SYSTEM_CONTENT: interlaced:%d, rff:%d, tff:%d", + payload->interlaced, payload->rff, payload->tff); + } else { + GST_WARNING ("unexpected SYSTEM_CONTE extensions len %u", ext_len); + } + break; + case ASF_PAYLOAD_EXTENSION_SYSTEM_PIXEL_ASPECT_RATIO: + if (G_LIKELY (ext_len == 2)) { + payload->par_x = payload->rep_data[off]; + payload->par_y = payload->rep_data[off + 1]; + GST_DEBUG ("PAR %d / %d", payload->par_x, payload->par_y); + } else { + GST_WARNING ("unexpected SYSTEM_PIXEL_ASPECT_RATIO extensions len %u", + ext_len); + } + break; + case ASF_PAYLOAD_EXTENSION_TIMING: + { + /* dvr-ms timing - this will override packet timestamp */ + guint64 time = GST_READ_UINT64_LE (payload->rep_data + off + 8); + if (time != 0xFFFFFFFFFFFFFFFF) + payload->ts = time * 100; + else + payload->ts = GST_CLOCK_TIME_NONE; + } + break; + default: + GST_LOG ("UNKNOWN PAYLOAD EXTENSION!"); + break; + } + off += ext_len; + } +} + +static gboolean +gst_asf_demux_parse_payload (GstASFDemux * demux, AsfPacket * packet, + gint lentype, const guint8 ** p_data, guint * p_size) +{ + AsfPayload payload = { 0, }; + AsfStream *stream; + gboolean is_compressed; + guint payload_len; + guint stream_num; + + if (G_UNLIKELY (*p_size < 1)) { + GST_WARNING_OBJECT (demux, "Short packet!"); + return FALSE; + } + + stream_num = GST_READ_UINT8 (*p_data) & 0x7f; + payload.keyframe = ((GST_READ_UINT8 (*p_data) & 0x80) != 0); + + *p_data += 1; + *p_size -= 1; + + payload.ts = GST_CLOCK_TIME_NONE; + payload.duration = GST_CLOCK_TIME_NONE; + payload.par_x = 0; + payload.par_y = 0; + payload.interlaced = FALSE; + payload.tff = FALSE; + payload.rff = FALSE; + + payload.mo_number = + asf_packet_read_varlen_int (packet->prop_flags, 4, p_data, p_size); + payload.mo_offset = + asf_packet_read_varlen_int (packet->prop_flags, 2, p_data, p_size); + payload.rep_data_len = + asf_packet_read_varlen_int (packet->prop_flags, 0, p_data, p_size); + + is_compressed = (payload.rep_data_len == 1); + + GST_LOG_OBJECT (demux, "payload for stream %u", stream_num); + GST_LOG_OBJECT (demux, "keyframe : %s", (payload.keyframe) ? "yes" : "no"); + GST_LOG_OBJECT (demux, "compressed : %s", (is_compressed) ? "yes" : "no"); + + if (G_UNLIKELY (*p_size < payload.rep_data_len)) { + GST_WARNING_OBJECT (demux, "Short packet! rep_data_len=%u, size=%u", + payload.rep_data_len, *p_size); + return FALSE; + } + + memcpy (payload.rep_data, *p_data, + MIN (sizeof (payload.rep_data), payload.rep_data_len)); + + *p_data += payload.rep_data_len; + *p_size -= payload.rep_data_len; + + if (G_UNLIKELY (*p_size == 0)) { + GST_WARNING_OBJECT (demux, "payload without data!?"); + return FALSE; + } + + /* we use -1 as lentype for a single payload that's the size of the packet */ + if (G_UNLIKELY ((lentype >= 0 && lentype <= 3))) { + payload_len = asf_packet_read_varlen_int (lentype, 0, p_data, p_size); + if (*p_size < payload_len) { + GST_WARNING_OBJECT (demux, "Short packet! payload_len=%u, size=%u", + payload_len, *p_size); + return FALSE; + } + } else { + payload_len = *p_size; + } + + GST_LOG_OBJECT (demux, "payload length: %u", payload_len); + + stream = gst_asf_demux_get_stream (demux, stream_num); + + if (G_UNLIKELY (stream == NULL)) { + if (gst_asf_demux_is_unknown_stream (demux, stream_num)) { + GST_WARNING_OBJECT (demux, "Payload for unknown stream %u, skipping", + stream_num); + } + if (*p_size < payload_len) { + *p_data += *p_size; + *p_size = 0; + } else { + *p_data += payload_len; + *p_size -= payload_len; + } + return TRUE; + } + + if (G_UNLIKELY (!is_compressed)) { + GST_LOG_OBJECT (demux, "replicated data length: %u", payload.rep_data_len); + + if (payload.rep_data_len >= 8) { + payload.mo_size = GST_READ_UINT32_LE (payload.rep_data); + payload.ts = GST_READ_UINT32_LE (payload.rep_data + 4) * GST_MSECOND; + if (G_UNLIKELY (payload.ts < demux->preroll)) + payload.ts = 0; + else + payload.ts -= demux->preroll; + asf_payload_parse_replicated_data_extensions (stream, &payload); + + GST_LOG_OBJECT (demux, "media object size : %u", payload.mo_size); + GST_LOG_OBJECT (demux, "media object ts : %" GST_TIME_FORMAT, + GST_TIME_ARGS (payload.ts)); + GST_LOG_OBJECT (demux, "media object dur : %" GST_TIME_FORMAT, + GST_TIME_ARGS (payload.duration)); + } else if (payload.rep_data_len == 0) { + payload.mo_size = 0; + } else if (payload.rep_data_len != 0) { + GST_WARNING_OBJECT (demux, "invalid replicated data length, very bad"); + *p_data += payload_len; + *p_size -= payload_len; + return FALSE; + } + + GST_LOG_OBJECT (demux, "media object offset : %u", payload.mo_offset); + + GST_LOG_OBJECT (demux, "payload length: %u", payload_len); + + if (payload_len == 0) { + GST_DEBUG_OBJECT (demux, "skipping empty payload"); + } else if (payload.mo_offset == 0 && payload.mo_size == payload_len) { + /* if the media object is not fragmented, just create a sub-buffer */ + GST_LOG_OBJECT (demux, "unfragmented media object size %u", payload_len); + payload.buf = asf_packet_create_payload_buffer (packet, p_data, p_size, + payload_len); + payload.buf_filled = payload_len; + gst_asf_payload_queue_for_stream (demux, &payload, stream); + } else { + const guint8 *payload_data = *p_data; + + g_assert (payload_len <= *p_size); + + *p_data += payload_len; + *p_size -= payload_len; + + /* n-th fragment of a fragmented media object? */ + if (payload.mo_offset != 0) { + AsfPayload *prev; + + if ((prev = asf_payload_find_previous_fragment (&payload, stream))) { + if (prev->buf == NULL || (payload.mo_size > 0 + && payload.mo_size != prev->mo_size) + || payload.mo_offset >= gst_buffer_get_size (prev->buf) + || payload.mo_offset + payload_len > + gst_buffer_get_size (prev->buf)) { + GST_WARNING_OBJECT (demux, "Offset doesn't match previous data?!"); + } else { + /* we assume fragments are payloaded with increasing mo_offset */ + if (payload.mo_offset != prev->buf_filled) { + GST_WARNING_OBJECT (demux, "media object payload discontinuity: " + "offset=%u vs buf_filled=%u", payload.mo_offset, + prev->buf_filled); + } + gst_buffer_fill (prev->buf, payload.mo_offset, + payload_data, payload_len); + prev->buf_filled = + MAX (prev->buf_filled, payload.mo_offset + payload_len); + GST_LOG_OBJECT (demux, "Merged media object fragments, size now %u", + prev->buf_filled); + } + } else { + GST_DEBUG_OBJECT (demux, "n-th payload fragment, but don't have " + "any previous fragment, ignoring payload"); + } + } else { + GST_LOG_OBJECT (demux, "allocating buffer of size %u for fragmented " + "media object", payload.mo_size); + payload.buf = gst_buffer_new_allocate (NULL, payload.mo_size, NULL); + gst_buffer_fill (payload.buf, 0, payload_data, payload_len); + payload.buf_filled = payload_len; + + gst_asf_payload_queue_for_stream (demux, &payload, stream); + } + } + } else { + const guint8 *payload_data; + GstClockTime ts, ts_delta; + guint num; + + GST_LOG_OBJECT (demux, "Compressed payload, length=%u", payload_len); + + payload_data = *p_data; + + *p_data += payload_len; + *p_size -= payload_len; + + ts = payload.mo_offset * GST_MSECOND; + if (G_UNLIKELY (ts < demux->preroll)) + ts = 0; + else + ts -= demux->preroll; + ts_delta = payload.rep_data[0] * GST_MSECOND; + + for (num = 0; payload_len > 0; ++num) { + guint sub_payload_len; + + sub_payload_len = GST_READ_UINT8 (payload_data); + + GST_LOG_OBJECT (demux, "subpayload #%u: len=%u, ts=%" GST_TIME_FORMAT, + num, sub_payload_len, GST_TIME_ARGS (ts)); + + ++payload_data; + --payload_len; + + if (G_UNLIKELY (payload_len < sub_payload_len)) { + GST_WARNING_OBJECT (demux, "Short payload! %u bytes left", payload_len); + return FALSE; + } + + if (G_LIKELY (sub_payload_len > 0)) { + payload.buf = asf_packet_create_payload_buffer (packet, + &payload_data, &payload_len, sub_payload_len); + payload.buf_filled = sub_payload_len; + + payload.ts = ts; + if (G_LIKELY (ts_delta)) + payload.duration = ts_delta; + else + payload.duration = GST_CLOCK_TIME_NONE; + + gst_asf_payload_queue_for_stream (demux, &payload, stream); + } + + ts += ts_delta; + } + } + + return TRUE; +} + +GstAsfDemuxParsePacketError +gst_asf_demux_parse_packet (GstASFDemux * demux, GstBuffer * buf) +{ + AsfPacket packet = { 0, }; + GstMapInfo map; + const guint8 *data; + gboolean has_multiple_payloads; + GstAsfDemuxParsePacketError ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE; + guint8 ec_flags, flags1; + guint size; + + gst_buffer_map (buf, &map, GST_MAP_READ); + data = map.data; + size = map.size; + GST_LOG_OBJECT (demux, "Buffer size: %u", size); + + /* need at least two payload flag bytes, send time, and duration */ + if (G_UNLIKELY (size < 2 + 4 + 2)) { + GST_WARNING_OBJECT (demux, "Packet size is < 8"); + ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE; + goto done; + } + + packet.buf = buf; + /* evidently transient */ + packet.bdata = data; + + ec_flags = GST_READ_UINT8 (data); + + /* skip optional error correction stuff */ + if ((ec_flags & 0x80) != 0) { + guint ec_len_type, ec_len; + + ec_len_type = (ec_flags & 0x60) >> 5; + if (ec_len_type == 0) { + ec_len = ec_flags & 0x0f; + } else { + GST_WARNING_OBJECT (demux, "unexpected error correction length type %u", + ec_len_type); + ec_len = 2; + } + GST_LOG_OBJECT (demux, "packet has error correction (%u bytes)", ec_len); + + /* still need at least two payload flag bytes, send time, and duration */ + if (size <= (1 + ec_len) + 2 + 4 + 2) { + GST_WARNING_OBJECT (demux, "Packet size is < 8 with Error Correction"); + ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_FATAL; + goto done; + } + + data += 1 + ec_len; + size -= 1 + ec_len; + } + + /* parse payload info */ + flags1 = GST_READ_UINT8 (data); + packet.prop_flags = GST_READ_UINT8 (data + 1); + + data += 2; + size -= 2; + + has_multiple_payloads = (flags1 & 0x01) != 0; + + packet.length = asf_packet_read_varlen_int (flags1, 5, &data, &size); + + packet.sequence = asf_packet_read_varlen_int (flags1, 1, &data, &size); + + packet.padding = asf_packet_read_varlen_int (flags1, 3, &data, &size); + + if (G_UNLIKELY (size < 6)) { + GST_WARNING_OBJECT (demux, "Packet size is < 6"); + ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_FATAL; + goto done; + } + + packet.send_time = GST_READ_UINT32_LE (data) * GST_MSECOND; + packet.duration = GST_READ_UINT16_LE (data + 4) * GST_MSECOND; + + data += 4 + 2; + size -= 4 + 2; + + GST_LOG_OBJECT (demux, "flags : 0x%x", flags1); + GST_LOG_OBJECT (demux, "multiple payloads: %u", has_multiple_payloads); + GST_LOG_OBJECT (demux, "packet length : %u", packet.length); + GST_LOG_OBJECT (demux, "sequence : %u", packet.sequence); + GST_LOG_OBJECT (demux, "padding : %u", packet.padding); + GST_LOG_OBJECT (demux, "send time : %" GST_TIME_FORMAT, + GST_TIME_ARGS (packet.send_time)); + GST_LOG_OBJECT (demux, "duration : %" GST_TIME_FORMAT, + GST_TIME_ARGS (packet.duration)); + + if (G_UNLIKELY (packet.padding == (guint) - 1 || size < packet.padding)) { + GST_WARNING_OBJECT (demux, "No padding, or padding bigger than buffer"); + ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE; + goto done; + } + + size -= packet.padding; + + /* adjust available size for parsing if there's less actual packet data for + * parsing than there is data in bytes (for sample see bug 431318) */ + if (G_UNLIKELY (packet.length != 0 && packet.padding == 0 + && packet.length < demux->packet_size)) { + GST_LOG_OBJECT (demux, "shortened packet with implicit padding, " + "adjusting available data size"); + if (size < demux->packet_size - packet.length) { + /* the buffer is smaller than the implicit padding */ + GST_WARNING_OBJECT (demux, "Buffer is smaller than the implicit padding"); + ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE; + goto done; + } else { + /* subtract the implicit padding */ + size -= (demux->packet_size - packet.length); + } + } + + if (has_multiple_payloads) { + guint i, num, lentype; + + if (G_UNLIKELY (size < 1)) { + GST_WARNING_OBJECT (demux, "No room more in buffer"); + ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE; + goto done; + } + + num = (GST_READ_UINT8 (data) & 0x3F) >> 0; + lentype = (GST_READ_UINT8 (data) & 0xC0) >> 6; + + ++data; + --size; + + GST_LOG_OBJECT (demux, "num payloads : %u", num); + + for (i = 0; i < num; ++i) { + GST_LOG_OBJECT (demux, "Parsing payload %u/%u, size left: %u", i + 1, num, + size); + + if (G_UNLIKELY (!gst_asf_demux_parse_payload (demux, &packet, lentype, + &data, &size))) { + GST_WARNING_OBJECT (demux, "Failed to parse payload %u/%u", i + 1, num); + ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_FATAL; + break; + } + } + } else { + GST_LOG_OBJECT (demux, "Parsing single payload"); + if (G_UNLIKELY (!gst_asf_demux_parse_payload (demux, &packet, -1, &data, + &size))) { + GST_WARNING_OBJECT (demux, "Failed to parse payload"); + ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE; + } + } + +done: + gst_buffer_unmap (buf, &map); + return ret; +} diff --git a/gst/asfdemux/asfpacket.h b/gst/asfdemux/asfpacket.h new file mode 100644 index 0000000..a812e74 --- /dev/null +++ b/gst/asfdemux/asfpacket.h @@ -0,0 +1,74 @@ +/* GStreamer ASF/WMV/WMA demuxer + * Copyright (C) 2007 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __ASF_PACKET_H__ +#define __ASF_PACKET_H__ + +#include <gst/gstbuffer.h> +#include <gst/gstclock.h> + +#include "gstasfdemux.h" + +G_BEGIN_DECLS + +typedef struct { + gboolean keyframe; /* buffer flags might not survive merge.. */ + guint mo_number; /* media object number (unused) */ + guint mo_offset; /* offset (timestamp for compressed data) */ + guint mo_size; /* size of media-object-to-be, or 0 */ + guint buf_filled; /* how much of the mo data we got so far */ + GstBuffer *buf; /* buffer to assemble media-object or NULL*/ + guint rep_data_len; /* should never be more than 256, since */ + guint8 rep_data[256]; /* the length should be stored in a byte */ + GstClockTime ts; + GstClockTime duration; /* is not always available */ + guint8 par_x; /* not always available (0:deactivated) */ + guint8 par_y; /* not always available (0:deactivated) */ + gboolean interlaced; /* default: FALSE */ + gboolean tff; + gboolean rff; +} AsfPayload; + +typedef struct { + GstBuffer *buf; + const guint8 *bdata; + guint length; /* packet length (unused) */ + guint padding; /* length of padding at end of packet */ + guint sequence; /* sequence (unused) */ + GstClockTime send_time; + GstClockTime duration; + + guint8 prop_flags; /* payload length types */ +} AsfPacket; + +typedef enum { + GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE, + GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE, + GST_ASF_DEMUX_PARSE_PACKET_ERROR_FATAL +} GstAsfDemuxParsePacketError; + +GstAsfDemuxParsePacketError gst_asf_demux_parse_packet (GstASFDemux * demux, GstBuffer * buf); + +#define gst_asf_payload_is_complete(payload) \ + ((payload)->buf_filled >= (payload)->mo_size) + +G_END_DECLS + +#endif /* __ASF_PACKET_H__ */ + diff --git a/gst/asfdemux/gstasf.c b/gst/asfdemux/gstasf.c new file mode 100644 index 0000000..01d289f --- /dev/null +++ b/gst/asfdemux/gstasf.c @@ -0,0 +1,72 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <gst/riff/riff-read.h> +#include "gst/gst-i18n-plugin.h" + +#include "gstasfdemux.h" +#include "gstrtspwms.h" +#include "gstrtpasfdepay.h" + +/* #include "gstasfmux.h" */ + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (asfdemux_dbg, "asfdemux", 0, "asf demuxer element"); + +#ifdef ENABLE_NLS + GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, + LOCALEDIR); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +#endif /* ENABLE_NLS */ + + gst_riff_init (); + + if (!gst_element_register (plugin, "asfdemux", GST_RANK_SECONDARY, + GST_TYPE_ASF_DEMUX)) { + return FALSE; + } + if (!gst_element_register (plugin, "rtspwms", GST_RANK_SECONDARY, + GST_TYPE_RTSP_WMS)) { + return FALSE; + } + if (!gst_element_register (plugin, "rtpasfdepay", GST_RANK_MARGINAL, + GST_TYPE_RTP_ASF_DEPAY)) { + return FALSE; + } +/* + if (!gst_element_register (plugin, "asfmux", GST_RANK_NONE, GST_TYPE_ASFMUX)) + return FALSE; +*/ + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + asf, + "Demuxes and muxes audio and video in Microsofts ASF format", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/asfdemux/gstasfdemux.c b/gst/asfdemux/gstasfdemux.c new file mode 100644 index 0000000..1794380 --- /dev/null +++ b/gst/asfdemux/gstasfdemux.c @@ -0,0 +1,4367 @@ +/* GStreamer ASF/WMV/WMA demuxer + * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu> + * Copyright (C) 2006-2009 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* TODO: + * + * - _loop(): + * stop if at end of segment if != end of file, ie. demux->segment.stop + * + * - fix packet parsing: + * there's something wrong with timestamps for packets with keyframes, + * and durations too. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gstutils.h> +#include <gst/base/gstbytereader.h> +#include <gst/base/gsttypefindhelper.h> +#include <gst/riff/riff-media.h> +#include <gst/tag/tag.h> +#include <gst/gst-i18n-plugin.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "gstasfdemux.h" +#include "asfheaders.h" +#include "asfpacket.h" + +static GstStaticPadTemplate gst_asf_demux_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-ms-asf") + ); + +static GstStaticPadTemplate audio_src_template = +GST_STATIC_PAD_TEMPLATE ("audio_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate video_src_template = +GST_STATIC_PAD_TEMPLATE ("video_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +/* size of an ASF object header, ie. GUID (16 bytes) + object size (8 bytes) */ +#define ASF_OBJECT_HEADER_SIZE (16+8) + +/* FIXME: get rid of this */ +/* abuse this GstFlowReturn enum for internal usage */ +#define ASF_FLOW_NEED_MORE_DATA 99 + +#define gst_asf_get_flow_name(flow) \ + (flow == ASF_FLOW_NEED_MORE_DATA) ? \ + "need-more-data" : gst_flow_get_name (flow) + +GST_DEBUG_CATEGORY (asfdemux_dbg); + +static GstStateChangeReturn gst_asf_demux_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_asf_demux_element_send_event (GstElement * element, + GstEvent * event); +static gboolean gst_asf_demux_send_event_unlocked (GstASFDemux * demux, + GstEvent * event); +static gboolean gst_asf_demux_handle_src_query (GstPad * pad, + GstObject * parent, GstQuery * query); +static GstFlowReturn gst_asf_demux_chain (GstPad * pad, GstObject * parent, + GstBuffer * buf); +static gboolean gst_asf_demux_sink_event (GstPad * pad, GstObject * parent, + GstEvent * event); +static GstFlowReturn gst_asf_demux_process_object (GstASFDemux * demux, + guint8 ** p_data, guint64 * p_size); +static gboolean gst_asf_demux_activate (GstPad * sinkpad, GstObject * parent); +static gboolean gst_asf_demux_activate_mode (GstPad * sinkpad, + GstObject * parent, GstPadMode mode, gboolean active); +static void gst_asf_demux_loop (GstASFDemux * demux); +static void +gst_asf_demux_process_queued_extended_stream_objects (GstASFDemux * demux); +static gboolean gst_asf_demux_pull_headers (GstASFDemux * demux); +static void gst_asf_demux_pull_indices (GstASFDemux * demux); +static void gst_asf_demux_reset_stream_state_after_discont (GstASFDemux * asf); +static gboolean +gst_asf_demux_parse_data_object_start (GstASFDemux * demux, guint8 * data); +static void gst_asf_demux_descramble_buffer (GstASFDemux * demux, + AsfStream * stream, GstBuffer ** p_buffer); +static void gst_asf_demux_activate_stream (GstASFDemux * demux, + AsfStream * stream); +static GstStructure *gst_asf_demux_get_metadata_for_stream (GstASFDemux * d, + guint stream_num); +static GstFlowReturn gst_asf_demux_push_complete_payloads (GstASFDemux * demux, + gboolean force); + +#define gst_asf_demux_parent_class parent_class +G_DEFINE_TYPE (GstASFDemux, gst_asf_demux, GST_TYPE_ELEMENT); + +static void +gst_asf_demux_class_init (GstASFDemuxClass * klass) +{ + GstElementClass *gstelement_class; + + gstelement_class = (GstElementClass *) klass; + + gst_element_class_set_static_metadata (gstelement_class, "ASF Demuxer", + "Codec/Demuxer", + "Demultiplexes ASF Streams", "Owen Fraser-Green <owen@discobabe.net>"); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&audio_src_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&video_src_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_asf_demux_sink_template)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_asf_demux_change_state); + gstelement_class->send_event = + GST_DEBUG_FUNCPTR (gst_asf_demux_element_send_event); +} + +static void +gst_asf_demux_free_stream (GstASFDemux * demux, AsfStream * stream) +{ + gst_caps_replace (&stream->caps, NULL); + if (stream->pending_tags) { + gst_tag_list_unref (stream->pending_tags); + stream->pending_tags = NULL; + } + if (stream->pad) { + if (stream->active) { + gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad); + gst_flow_combiner_remove_pad (demux->flowcombiner, stream->pad); + } else + gst_object_unref (stream->pad); + stream->pad = NULL; + } + + if (stream->payloads) { + while (stream->payloads->len > 0) { + AsfPayload *payload; + guint last; + + last = stream->payloads->len - 1; + payload = &g_array_index (stream->payloads, AsfPayload, last); + gst_buffer_replace (&payload->buf, NULL); + g_array_remove_index (stream->payloads, last); + } + g_array_free (stream->payloads, TRUE); + stream->payloads = NULL; + } + if (stream->ext_props.valid) { + g_free (stream->ext_props.payload_extensions); + stream->ext_props.payload_extensions = NULL; + } +} + +static void +gst_asf_demux_reset (GstASFDemux * demux, gboolean chain_reset) +{ + GST_LOG_OBJECT (demux, "resetting"); + + gst_segment_init (&demux->segment, GST_FORMAT_UNDEFINED); + demux->segment_running = FALSE; + if (demux->adapter && !chain_reset) { + gst_adapter_clear (demux->adapter); + g_object_unref (demux->adapter); + demux->adapter = NULL; + } + if (demux->taglist) { + gst_tag_list_unref (demux->taglist); + demux->taglist = NULL; + } + if (demux->metadata) { + gst_caps_unref (demux->metadata); + demux->metadata = NULL; + } + if (demux->global_metadata) { + gst_structure_free (demux->global_metadata); + demux->global_metadata = NULL; + } + + demux->state = GST_ASF_DEMUX_STATE_HEADER; + g_free (demux->objpath); + demux->objpath = NULL; + g_strfreev (demux->languages); + demux->languages = NULL; + demux->num_languages = 0; + g_slist_foreach (demux->ext_stream_props, (GFunc) gst_mini_object_unref, + NULL); + g_slist_free (demux->ext_stream_props); + demux->ext_stream_props = NULL; + + while (demux->old_num_streams > 0) { + gst_asf_demux_free_stream (demux, + &demux->old_stream[demux->old_num_streams - 1]); + --demux->old_num_streams; + } + memset (demux->old_stream, 0, sizeof (demux->old_stream)); + demux->old_num_streams = 0; + + /* when resetting for a new chained asf, we don't want to remove the pads + * before adding the new ones */ + if (chain_reset) { + memcpy (demux->old_stream, demux->stream, sizeof (demux->stream)); + demux->old_num_streams = demux->num_streams; + demux->num_streams = 0; + } + + while (demux->num_streams > 0) { + gst_asf_demux_free_stream (demux, &demux->stream[demux->num_streams - 1]); + --demux->num_streams; + } + memset (demux->stream, 0, sizeof (demux->stream)); + if (!chain_reset) { + /* do not remove those for not adding pads with same name */ + demux->num_audio_streams = 0; + demux->num_video_streams = 0; + demux->have_group_id = FALSE; + demux->group_id = G_MAXUINT; + } + demux->num_streams = 0; + demux->activated_streams = FALSE; + demux->first_ts = GST_CLOCK_TIME_NONE; + demux->segment_ts = GST_CLOCK_TIME_NONE; + demux->in_gap = 0; + if (!chain_reset) + gst_segment_init (&demux->in_segment, GST_FORMAT_UNDEFINED); + demux->state = GST_ASF_DEMUX_STATE_HEADER; + demux->seekable = FALSE; + demux->broadcast = FALSE; + demux->sidx_interval = 0; + demux->sidx_num_entries = 0; + g_free (demux->sidx_entries); + demux->sidx_entries = NULL; + + demux->speed_packets = 1; + + if (chain_reset) { + GST_LOG_OBJECT (demux, "Restarting"); + gst_segment_init (&demux->segment, GST_FORMAT_TIME); + demux->need_newsegment = TRUE; + demux->segment_seqnum = 0; + demux->segment_running = FALSE; + demux->accurate = FALSE; + demux->metadata = gst_caps_new_empty (); + demux->global_metadata = gst_structure_new_empty ("metadata"); + demux->data_size = 0; + demux->data_offset = 0; + demux->index_offset = 0; + } else { + demux->base_offset = 0; + } + + g_slist_free (demux->other_streams); + demux->other_streams = NULL; +} + +static void +gst_asf_demux_init (GstASFDemux * demux) +{ + demux->sinkpad = + gst_pad_new_from_static_template (&gst_asf_demux_sink_template, "sink"); + gst_pad_set_chain_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_asf_demux_chain)); + gst_pad_set_event_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_asf_demux_sink_event)); + gst_pad_set_activate_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_asf_demux_activate)); + gst_pad_set_activatemode_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_asf_demux_activate_mode)); + gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); + + /* set initial state */ + gst_asf_demux_reset (demux, FALSE); +} + +static gboolean +gst_asf_demux_activate (GstPad * sinkpad, GstObject * parent) +{ + GstQuery *query; + gboolean pull_mode; + + query = gst_query_new_scheduling (); + + if (!gst_pad_peer_query (sinkpad, query)) { + gst_query_unref (query); + goto activate_push; + } + + pull_mode = gst_query_has_scheduling_mode_with_flags (query, + GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE); + gst_query_unref (query); + + if (!pull_mode) + goto activate_push; + + GST_DEBUG_OBJECT (sinkpad, "activating pull"); + return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE); + +activate_push: + { + GST_DEBUG_OBJECT (sinkpad, "activating push"); + return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); + } +} + +static gboolean +gst_asf_demux_activate_mode (GstPad * sinkpad, GstObject * parent, + GstPadMode mode, gboolean active) +{ + gboolean res; + GstASFDemux *demux; + + demux = GST_ASF_DEMUX (parent); + + switch (mode) { + case GST_PAD_MODE_PUSH: + demux->state = GST_ASF_DEMUX_STATE_HEADER; + demux->streaming = TRUE; + res = TRUE; + break; + case GST_PAD_MODE_PULL: + if (active) { + demux->state = GST_ASF_DEMUX_STATE_HEADER; + demux->streaming = FALSE; + + res = gst_pad_start_task (sinkpad, (GstTaskFunction) gst_asf_demux_loop, + demux, NULL); + } else { + res = gst_pad_stop_task (sinkpad); + } + break; + default: + res = FALSE; + break; + } + return res; +} + +static gboolean +gst_asf_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GstASFDemux *demux; + gboolean ret = TRUE; + + demux = GST_ASF_DEMUX (parent); + + GST_LOG_OBJECT (demux, "handling %s event", GST_EVENT_TYPE_NAME (event)); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT:{ + const GstSegment *segment; + + gst_event_parse_segment (event, &segment); + + if (segment->format == GST_FORMAT_BYTES) { + if (demux->packet_size && segment->start > demux->data_offset) + demux->packet = (segment->start - demux->data_offset) / + demux->packet_size; + else + demux->packet = 0; + } else if (segment->format == GST_FORMAT_TIME) { + /* do not know packet position, not really a problem */ + demux->packet = -1; + } else { + GST_WARNING_OBJECT (demux, "unsupported newsegment format, ignoring"); + gst_event_unref (event); + break; + } + + /* record upstream segment for interpolation */ + if (segment->format != demux->in_segment.format) + gst_segment_init (&demux->in_segment, GST_FORMAT_UNDEFINED); + gst_segment_copy_into (segment, &demux->in_segment); + + /* in either case, clear some state and generate newsegment later on */ + GST_OBJECT_LOCK (demux); + demux->segment_ts = GST_CLOCK_TIME_NONE; + demux->in_gap = GST_CLOCK_TIME_NONE; + demux->need_newsegment = TRUE; + demux->segment_seqnum = gst_event_get_seqnum (event); + gst_asf_demux_reset_stream_state_after_discont (demux); + GST_OBJECT_UNLOCK (demux); + + gst_event_unref (event); + break; + } + case GST_EVENT_EOS:{ + GstFlowReturn flow; + + if (demux->state == GST_ASF_DEMUX_STATE_HEADER) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("This stream contains no data.")), + ("got eos and didn't receive a complete header object")); + break; + } + flow = gst_asf_demux_push_complete_payloads (demux, TRUE); + if (flow < GST_FLOW_EOS || flow == GST_FLOW_NOT_LINKED) { + GST_ELEMENT_ERROR (demux, STREAM, FAILED, + (_("Internal data stream error.")), + ("streaming stopped, reason %s", gst_flow_get_name (flow))); + break; + } + + GST_OBJECT_LOCK (demux); + gst_adapter_clear (demux->adapter); + GST_OBJECT_UNLOCK (demux); + gst_asf_demux_send_event_unlocked (demux, event); + break; + } + + case GST_EVENT_FLUSH_STOP: + GST_OBJECT_LOCK (demux); + gst_asf_demux_reset_stream_state_after_discont (demux); + GST_OBJECT_UNLOCK (demux); + gst_asf_demux_send_event_unlocked (demux, event); + /* upon activation, latency is no longer introduced, e.g. after seek */ + if (demux->activated_streams) + demux->latency = 0; + break; + + default: + ret = gst_pad_event_default (pad, parent, event); + break; + } + + return ret; +} + +static gboolean +gst_asf_demux_seek_index_lookup (GstASFDemux * demux, guint * packet, + GstClockTime seek_time, GstClockTime * p_idx_time, guint * speed, + gboolean next, gboolean * eos) +{ + GstClockTime idx_time; + guint idx; + + if (eos) + *eos = FALSE; + + if (G_UNLIKELY (demux->sidx_num_entries == 0 || demux->sidx_interval == 0)) + return FALSE; + + idx = (guint) ((seek_time + demux->preroll) / demux->sidx_interval); + + if (next) { + /* if we want the next keyframe, we have to go forward till we find + a different packet number */ + guint idx2 = idx; + if (idx >= demux->sidx_num_entries - 1) { + /* If we get here, we're asking for next keyframe after the last one. There isn't one. */ + if (eos) + *eos = TRUE; + return FALSE; + } + for (idx2 = idx + 1; idx2 < demux->sidx_num_entries; ++idx2) { + if (demux->sidx_entries[idx].packet != demux->sidx_entries[idx2].packet) { + idx = idx2; + break; + } + } + } + + if (G_UNLIKELY (idx >= demux->sidx_num_entries)) { + if (eos) + *eos = TRUE; + return FALSE; + } + + *packet = demux->sidx_entries[idx].packet; + if (speed) + *speed = demux->sidx_entries[idx].count; + + /* so we get closer to the actual time of the packet ... actually, let's not + * do this, since we throw away superfluous payloads before the seek position + * anyway; this way, our key unit seek 'snap resolution' is a bit better + * (ie. same as index resolution) */ + /* + while (idx > 0 && demux->sidx_entries[idx-1] == demux->sidx_entries[idx]) + --idx; + */ + + idx_time = demux->sidx_interval * idx; + if (G_LIKELY (idx_time >= demux->preroll)) + idx_time -= demux->preroll; + + GST_DEBUG_OBJECT (demux, "%" GST_TIME_FORMAT " => packet %u at %" + GST_TIME_FORMAT, GST_TIME_ARGS (seek_time), *packet, + GST_TIME_ARGS (idx_time)); + + if (G_LIKELY (p_idx_time)) + *p_idx_time = idx_time; + + return TRUE; +} + +static void +gst_asf_demux_reset_stream_state_after_discont (GstASFDemux * demux) +{ + guint n; + + gst_adapter_clear (demux->adapter); + + GST_DEBUG_OBJECT (demux, "reset stream state"); + + for (n = 0; n < demux->num_streams; n++) { + demux->stream[n].discont = TRUE; + + while (demux->stream[n].payloads->len > 0) { + AsfPayload *payload; + guint last; + + last = demux->stream[n].payloads->len - 1; + payload = &g_array_index (demux->stream[n].payloads, AsfPayload, last); + gst_buffer_replace (&payload->buf, NULL); + g_array_remove_index (demux->stream[n].payloads, last); + } + } +} + +static void +gst_asf_demux_mark_discont (GstASFDemux * demux) +{ + guint n; + + GST_DEBUG_OBJECT (demux, "Mark stream discont"); + + for (n = 0; n < demux->num_streams; n++) + demux->stream[n].discont = TRUE; +} + +/* do a seek in push based mode */ +static gboolean +gst_asf_demux_handle_seek_push (GstASFDemux * demux, GstEvent * event) +{ + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop; + guint packet; + gboolean res; + GstEvent *byte_event; + + gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, + &stop_type, &stop); + + stop_type = GST_SEEK_TYPE_NONE; + stop = -1; + + GST_DEBUG_OBJECT (demux, "seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (cur)); + + /* determine packet, by index or by estimation */ + if (!gst_asf_demux_seek_index_lookup (demux, &packet, cur, NULL, NULL, FALSE, + NULL)) { + packet = + (guint) gst_util_uint64_scale (demux->num_packets, cur, + demux->play_time); + } + + if (packet > demux->num_packets) { + GST_DEBUG_OBJECT (demux, "could not determine packet to seek to, " + "seek aborted."); + return FALSE; + } + + GST_DEBUG_OBJECT (demux, "seeking to packet %d", packet); + + cur = demux->data_offset + (packet * demux->packet_size); + + GST_DEBUG_OBJECT (demux, "Pushing BYTE seek rate %g, " + "start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT, rate, cur, stop); + /* BYTE seek event */ + byte_event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type, + cur, stop_type, stop); + gst_event_set_seqnum (byte_event, gst_event_get_seqnum (event)); + res = gst_pad_push_event (demux->sinkpad, byte_event); + + return res; +} + +static gboolean +gst_asf_demux_handle_seek_event (GstASFDemux * demux, GstEvent * event) +{ + GstClockTime idx_time; + GstSegment segment; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + GstFormat format; + gboolean only_need_update; + gboolean keyunit_sync, after, before, next; + gboolean flush; + gdouble rate; + gint64 cur, stop; + gint64 seek_time; + guint packet, speed_count = 1; + gboolean eos; + guint32 seqnum; + GstEvent *fevent; + + if (G_UNLIKELY (demux->seekable == FALSE || demux->packet_size == 0 || + demux->num_packets == 0 || demux->play_time == 0)) { + GST_LOG_OBJECT (demux, "stream is not seekable"); + return FALSE; + } + + if (G_UNLIKELY (!demux->activated_streams)) { + GST_LOG_OBJECT (demux, "streams not yet activated, ignoring seek"); + return FALSE; + } + + gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, + &stop_type, &stop); + seqnum = gst_event_get_seqnum (event); + + if (G_UNLIKELY (format != GST_FORMAT_TIME)) { + GST_LOG_OBJECT (demux, "seeking is only supported in TIME format"); + return FALSE; + } + + if (G_UNLIKELY (rate <= 0.0)) { + GST_LOG_OBJECT (demux, "backward playback is not supported yet"); + return FALSE; + } + + flush = ((flags & GST_SEEK_FLAG_FLUSH) == GST_SEEK_FLAG_FLUSH); + demux->accurate = + ((flags & GST_SEEK_FLAG_ACCURATE) == GST_SEEK_FLAG_ACCURATE); + keyunit_sync = ((flags & GST_SEEK_FLAG_KEY_UNIT) == GST_SEEK_FLAG_KEY_UNIT); + after = ((flags & GST_SEEK_FLAG_SNAP_AFTER) == GST_SEEK_FLAG_SNAP_AFTER); + before = ((flags & GST_SEEK_FLAG_SNAP_BEFORE) == GST_SEEK_FLAG_SNAP_BEFORE); + next = after && !before; + + if (G_UNLIKELY (demux->streaming)) { + /* support it safely needs more segment handling, e.g. closing etc */ + if (!flush) { + GST_LOG_OBJECT (demux, "streaming; non-flushing seek not supported"); + return FALSE; + } + /* we can (re)construct the start later on, but not the end */ + if (stop_type != GST_SEEK_TYPE_NONE && + (stop_type != GST_SEEK_TYPE_SET || GST_CLOCK_TIME_IS_VALID (stop))) { + GST_LOG_OBJECT (demux, "streaming; end position must be NONE"); + return FALSE; + } + gst_event_ref (event); + /* upstream might handle TIME seek, e.g. mms or rtsp, + * or not, e.g. http, then we give it a hand */ + if (!gst_pad_push_event (demux->sinkpad, event)) + return gst_asf_demux_handle_seek_push (demux, event); + else + return TRUE; + } + + /* unlock the streaming thread */ + if (G_LIKELY (flush)) { + fevent = gst_event_new_flush_start (); + + gst_event_set_seqnum (fevent, seqnum); + gst_pad_push_event (demux->sinkpad, gst_event_ref (fevent)); + gst_asf_demux_send_event_unlocked (demux, fevent); + } else { + gst_pad_pause_task (demux->sinkpad); + } + + /* grab the stream lock so that streaming cannot continue, for + * non flushing seeks when the element is in PAUSED this could block + * forever */ + GST_PAD_STREAM_LOCK (demux->sinkpad); + + /* we now can stop flushing, since we have the stream lock now */ + fevent = gst_event_new_flush_stop (TRUE); + gst_event_set_seqnum (fevent, seqnum); + gst_pad_push_event (demux->sinkpad, gst_event_ref (fevent)); + + if (G_LIKELY (flush)) + gst_asf_demux_send_event_unlocked (demux, fevent); + else + gst_event_unref (fevent); + + /* operating on copy of segment until we know the seek worked */ + segment = demux->segment; + + if (G_UNLIKELY (demux->segment_running && !flush)) { + GstSegment newsegment; + GstEvent *newseg; + + /* create the segment event to close the current segment */ + gst_segment_copy_into (&segment, &newsegment); + newseg = gst_event_new_segment (&newsegment); + gst_event_set_seqnum (newseg, seqnum); + + gst_asf_demux_send_event_unlocked (demux, newseg); + } + + gst_segment_do_seek (&segment, rate, format, flags, cur_type, + cur, stop_type, stop, &only_need_update); + + GST_DEBUG_OBJECT (demux, "seeking to time %" GST_TIME_FORMAT ", segment: " + "%" GST_SEGMENT_FORMAT, GST_TIME_ARGS (segment.start), &segment); + + if (cur_type != GST_SEEK_TYPE_SET) + seek_time = segment.start; + else + seek_time = cur; + + /* FIXME: should check the KEY_UNIT flag; need to adjust position to + * real start of data and segment_start to indexed time for key unit seek*/ + if (G_UNLIKELY (!gst_asf_demux_seek_index_lookup (demux, &packet, seek_time, + &idx_time, &speed_count, next, &eos))) { + gint64 offset; + + if (eos) { + demux->packet = demux->num_packets; + goto skip; + } + + /* First try to query our source to see if it can convert for us. This is + the case when our source is an mms stream, notice that in this case + gstmms will do a time based seek to get the byte offset, this is not a + problem as the seek to this offset needs to happen anway. */ + if (gst_pad_peer_query_convert (demux->sinkpad, GST_FORMAT_TIME, seek_time, + GST_FORMAT_BYTES, &offset)) { + packet = (offset - demux->data_offset) / demux->packet_size; + GST_LOG_OBJECT (demux, "convert %" GST_TIME_FORMAT + " to bytes query result: %" G_GINT64_FORMAT ", data_ofset: %" + G_GINT64_FORMAT ", packet_size: %u," " resulting packet: %u\n", + GST_TIME_ARGS (seek_time), offset, demux->data_offset, + demux->packet_size, packet); + } else { + /* FIXME: For streams containing video, seek to an earlier position in + * the hope of hitting a keyframe and let the sinks throw away the stuff + * before the segment start. For audio-only this is unnecessary as every + * frame is 'key'. */ + if (flush && (demux->accurate || (keyunit_sync && !next)) + && demux->num_video_streams > 0) { + seek_time -= 5 * GST_SECOND; + if (seek_time < 0) + seek_time = 0; + } + + packet = (guint) gst_util_uint64_scale (demux->num_packets, + seek_time, demux->play_time); + + if (packet > demux->num_packets) + packet = demux->num_packets; + } + } else { + if (G_LIKELY (keyunit_sync)) { + GST_DEBUG_OBJECT (demux, "key unit seek, adjust seek_time = %" + GST_TIME_FORMAT " to index_time = %" GST_TIME_FORMAT, + GST_TIME_ARGS (seek_time), GST_TIME_ARGS (idx_time)); + segment.start = idx_time; + segment.position = idx_time; + segment.time = idx_time; + } + } + + GST_DEBUG_OBJECT (demux, "seeking to packet %u (%d)", packet, speed_count); + + GST_OBJECT_LOCK (demux); + demux->segment = segment; + demux->packet = packet; + demux->need_newsegment = TRUE; + demux->segment_seqnum = seqnum; + demux->speed_packets = speed_count; + gst_asf_demux_reset_stream_state_after_discont (demux); + GST_OBJECT_UNLOCK (demux); + +skip: + /* restart our task since it might have been stopped when we did the flush */ + gst_pad_start_task (demux->sinkpad, (GstTaskFunction) gst_asf_demux_loop, + demux, NULL); + + /* streaming can continue now */ + GST_PAD_STREAM_UNLOCK (demux->sinkpad); + + return TRUE; +} + +static gboolean +gst_asf_demux_handle_src_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + GstASFDemux *demux; + gboolean ret; + + demux = GST_ASF_DEMUX (parent); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + GST_LOG_OBJECT (pad, "seek event"); + ret = gst_asf_demux_handle_seek_event (demux, event); + gst_event_unref (event); + break; + case GST_EVENT_QOS: + case GST_EVENT_NAVIGATION: + /* just drop these two silently */ + gst_event_unref (event); + ret = FALSE; + break; + default: + GST_LOG_OBJECT (pad, "%s event", GST_EVENT_TYPE_NAME (event)); + ret = gst_pad_event_default (pad, parent, event); + break; + } + + return ret; +} + +static inline guint32 +gst_asf_demux_identify_guid (const ASFGuidHash * guids, ASFGuid * guid) +{ + guint32 ret; + + ret = gst_asf_identify_guid (guids, guid); + + GST_LOG ("%s 0x%08x-0x%08x-0x%08x-0x%08x", + gst_asf_get_guid_nick (guids, ret), + guid->v1, guid->v2, guid->v3, guid->v4); + + return ret; +} + +typedef struct +{ + AsfObjectID id; + guint64 size; +} AsfObject; + + +/* expect is true when the user is expeting an object, + * when false, it will give no warnings if the object + * is not identified + */ +static gboolean +asf_demux_peek_object (GstASFDemux * demux, const guint8 * data, + guint data_len, AsfObject * object, gboolean expect) +{ + ASFGuid guid; + + if (data_len < ASF_OBJECT_HEADER_SIZE) + return FALSE; + + guid.v1 = GST_READ_UINT32_LE (data + 0); + guid.v2 = GST_READ_UINT32_LE (data + 4); + guid.v3 = GST_READ_UINT32_LE (data + 8); + guid.v4 = GST_READ_UINT32_LE (data + 12); + + object->size = GST_READ_UINT64_LE (data + 16); + + /* FIXME: make asf_demux_identify_object_guid() */ + object->id = gst_asf_demux_identify_guid (asf_object_guids, &guid); + if (object->id == ASF_OBJ_UNDEFINED && expect) { + GST_WARNING_OBJECT (demux, "Unknown object %08x-%08x-%08x-%08x", + guid.v1, guid.v2, guid.v3, guid.v4); + } + + return TRUE; +} + +static void +gst_asf_demux_release_old_pads (GstASFDemux * demux) +{ + GST_DEBUG_OBJECT (demux, "Releasing old pads"); + + while (demux->old_num_streams > 0) { + gst_pad_push_event (demux->old_stream[demux->old_num_streams - 1].pad, + gst_event_new_eos ()); + gst_asf_demux_free_stream (demux, + &demux->old_stream[demux->old_num_streams - 1]); + --demux->old_num_streams; + } + memset (demux->old_stream, 0, sizeof (demux->old_stream)); + demux->old_num_streams = 0; +} + +static GstFlowReturn +gst_asf_demux_chain_headers (GstASFDemux * demux) +{ + GstFlowReturn flow; + AsfObject obj; + guint8 *header_data, *data = NULL; + const guint8 *cdata = NULL; + guint64 header_size; + + cdata = (guint8 *) gst_adapter_map (demux->adapter, ASF_OBJECT_HEADER_SIZE); + if (cdata == NULL) + goto need_more_data; + + asf_demux_peek_object (demux, cdata, ASF_OBJECT_HEADER_SIZE, &obj, TRUE); + if (obj.id != ASF_OBJ_HEADER) + goto wrong_type; + + GST_LOG_OBJECT (demux, "header size = %u", (guint) obj.size); + + /* + 50 for non-packet data at beginning of ASF_OBJ_DATA */ + if (gst_adapter_available (demux->adapter) < obj.size + 50) + goto need_more_data; + + data = gst_adapter_take (demux->adapter, obj.size + 50); + + header_data = data; + header_size = obj.size; + flow = gst_asf_demux_process_object (demux, &header_data, &header_size); + if (flow != GST_FLOW_OK) + goto parse_failed; + + /* calculate where the packet data starts */ + demux->data_offset = obj.size + 50; + + /* now parse the beginning of the ASF_OBJ_DATA object */ + if (!gst_asf_demux_parse_data_object_start (demux, data + obj.size)) + goto wrong_type; + + if (demux->num_streams == 0) + goto no_streams; + + g_free (data); + return GST_FLOW_OK; + +/* NON-FATAL */ +need_more_data: + { + GST_LOG_OBJECT (demux, "not enough data in adapter yet"); + return GST_FLOW_OK; + } + +/* ERRORS */ +wrong_type: + { + GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL), + ("This doesn't seem to be an ASF file")); + g_free (data); + return GST_FLOW_ERROR; + } +no_streams: +parse_failed: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("header parsing failed, or no streams found, flow = %s", + gst_flow_get_name (flow))); + g_free (data); + return GST_FLOW_ERROR; + } +} + +static gboolean +gst_asf_demux_pull_data (GstASFDemux * demux, guint64 offset, guint size, + GstBuffer ** p_buf, GstFlowReturn * p_flow) +{ + gsize buffer_size; + GstFlowReturn flow; + + GST_LOG_OBJECT (demux, "pulling buffer at %" G_GUINT64_FORMAT "+%u", + offset, size); + + flow = gst_pad_pull_range (demux->sinkpad, offset, size, p_buf); + + if (G_LIKELY (p_flow)) + *p_flow = flow; + + if (G_UNLIKELY (flow != GST_FLOW_OK)) { + GST_DEBUG_OBJECT (demux, "flow %s pulling buffer at %" G_GUINT64_FORMAT + "+%u", gst_flow_get_name (flow), offset, size); + *p_buf = NULL; + return FALSE; + } + + g_assert (*p_buf != NULL); + + buffer_size = gst_buffer_get_size (*p_buf); + if (G_UNLIKELY (buffer_size < size)) { + GST_DEBUG_OBJECT (demux, "short read pulling buffer at %" G_GUINT64_FORMAT + "+%u (got only %" G_GSIZE_FORMAT " bytes)", offset, size, buffer_size); + gst_buffer_unref (*p_buf); + if (G_LIKELY (p_flow)) + *p_flow = GST_FLOW_EOS; + *p_buf = NULL; + return FALSE; + } + + return TRUE; +} + +static void +gst_asf_demux_pull_indices (GstASFDemux * demux) +{ + GstBuffer *buf = NULL; + guint64 offset; + guint num_read = 0; + + offset = demux->index_offset; + + if (G_UNLIKELY (offset == 0)) { + GST_DEBUG_OBJECT (demux, "can't read indices, don't know index offset"); + return; + } + + while (gst_asf_demux_pull_data (demux, offset, 16 + 8, &buf, NULL)) { + GstFlowReturn flow; + AsfObject obj; + GstMapInfo map; + guint8 *bufdata; + + gst_buffer_map (buf, &map, GST_MAP_READ); + g_assert (map.size >= 16 + 8); + asf_demux_peek_object (demux, map.data, 16 + 8, &obj, TRUE); + gst_buffer_unmap (buf, &map); + gst_buffer_replace (&buf, NULL); + + /* check for sanity */ + if (G_UNLIKELY (obj.size > (5 * 1024 * 1024))) { + GST_DEBUG_OBJECT (demux, "implausible index object size, bailing out"); + break; + } + + if (G_UNLIKELY (!gst_asf_demux_pull_data (demux, offset, obj.size, &buf, + NULL))) + break; + + GST_LOG_OBJECT (demux, "index object at offset 0x%" G_GINT64_MODIFIER "X" + ", size %u", offset, (guint) obj.size); + + offset += obj.size; /* increase before _process_object changes it */ + + gst_buffer_map (buf, &map, GST_MAP_READ); + g_assert (map.size >= obj.size); + bufdata = (guint8 *) map.data; + flow = gst_asf_demux_process_object (demux, &bufdata, &obj.size); + gst_buffer_unmap (buf, &map); + gst_buffer_replace (&buf, NULL); + + if (G_UNLIKELY (flow != GST_FLOW_OK)) + break; + + ++num_read; + } + GST_DEBUG_OBJECT (demux, "read %u index objects", num_read); +} + +static gboolean +gst_asf_demux_parse_data_object_start (GstASFDemux * demux, guint8 * data) +{ + AsfObject obj; + + asf_demux_peek_object (demux, data, 50, &obj, TRUE); + if (obj.id != ASF_OBJ_DATA) { + GST_WARNING_OBJECT (demux, "headers not followed by a DATA object"); + return FALSE; + } + + demux->state = GST_ASF_DEMUX_STATE_DATA; + + if (!demux->broadcast && obj.size > 50) { + demux->data_size = obj.size - 50; + /* CHECKME: for at least one file this is off by +158 bytes?! */ + demux->index_offset = demux->data_offset + demux->data_size; + } else { + demux->data_size = 0; + demux->index_offset = 0; + } + + demux->packet = 0; + + if (!demux->broadcast) { + /* skip object header (24 bytes) and file GUID (16 bytes) */ + demux->num_packets = GST_READ_UINT64_LE (data + (16 + 8) + 16); + } else { + demux->num_packets = 0; + } + + if (demux->num_packets == 0) + demux->seekable = FALSE; + + /* fallback in the unlikely case that headers are inconsistent, can't hurt */ + if (demux->data_size == 0 && demux->num_packets > 0) { + demux->data_size = demux->num_packets * demux->packet_size; + demux->index_offset = demux->data_offset + demux->data_size; + } + + /* process pending stream objects and create pads for those */ + gst_asf_demux_process_queued_extended_stream_objects (demux); + + GST_INFO_OBJECT (demux, "Stream has %" G_GUINT64_FORMAT " packets, " + "data_offset=%" G_GINT64_FORMAT ", data_size=%" G_GINT64_FORMAT + ", index_offset=%" G_GUINT64_FORMAT, demux->num_packets, + demux->data_offset, demux->data_size, demux->index_offset); + + return TRUE; +} + +static gboolean +gst_asf_demux_pull_headers (GstASFDemux * demux) +{ + GstFlowReturn flow; + AsfObject obj; + GstBuffer *buf = NULL; + guint64 size; + GstMapInfo map; + guint8 *bufdata; + + GST_LOG_OBJECT (demux, "reading headers"); + + /* pull HEADER object header, so we know its size */ + if (!gst_asf_demux_pull_data (demux, demux->base_offset, 16 + 8, &buf, NULL)) + goto read_failed; + + gst_buffer_map (buf, &map, GST_MAP_READ); + g_assert (map.size >= 16 + 8); + asf_demux_peek_object (demux, map.data, 16 + 8, &obj, TRUE); + gst_buffer_unmap (buf, &map); + gst_buffer_replace (&buf, NULL); + + if (obj.id != ASF_OBJ_HEADER) + goto wrong_type; + + GST_LOG_OBJECT (demux, "header size = %u", (guint) obj.size); + + /* pull HEADER object */ + if (!gst_asf_demux_pull_data (demux, demux->base_offset, obj.size, &buf, + NULL)) + goto read_failed; + + size = obj.size; /* don't want obj.size changed */ + gst_buffer_map (buf, &map, GST_MAP_READ); + g_assert (map.size >= size); + bufdata = (guint8 *) map.data; + flow = gst_asf_demux_process_object (demux, &bufdata, &size); + gst_buffer_unmap (buf, &map); + gst_buffer_replace (&buf, NULL); + + if (flow != GST_FLOW_OK) { + GST_WARNING_OBJECT (demux, "process_object: %s", gst_flow_get_name (flow)); + goto parse_failed; + } + + /* calculate where the packet data starts */ + demux->data_offset = demux->base_offset + obj.size + 50; + + /* now pull beginning of DATA object before packet data */ + if (!gst_asf_demux_pull_data (demux, demux->base_offset + obj.size, 50, &buf, + NULL)) + goto read_failed; + + gst_buffer_map (buf, &map, GST_MAP_READ); + g_assert (map.size >= size); + bufdata = (guint8 *) map.data; + if (!gst_asf_demux_parse_data_object_start (demux, bufdata)) + goto wrong_type; + + if (demux->num_streams == 0) + goto no_streams; + + gst_buffer_unmap (buf, &map); + gst_buffer_replace (&buf, NULL); + + return TRUE; + +/* ERRORS */ +wrong_type: + { + if (buf != NULL) { + gst_buffer_unmap (buf, &map); + gst_buffer_replace (&buf, NULL); + } + GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL), + ("This doesn't seem to be an ASF file")); + return FALSE; + } + +no_streams: +read_failed: +parse_failed: + { + if (buf) + gst_buffer_unmap (buf, &map); + gst_buffer_replace (&buf, NULL); + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), (NULL)); + return FALSE; + } +} + +static gboolean +all_streams_prerolled (GstASFDemux * demux) +{ + GstClockTime preroll_time; + guint i, num_no_data = 0; + + /* Allow at least 500ms of preroll_time */ + preroll_time = MAX (demux->preroll, 500 * GST_MSECOND); + + /* returns TRUE as long as there isn't a stream which (a) has data queued + * and (b) the timestamp of last piece of data queued is < demux->preroll + * AND there is at least one other stream with data queued */ + for (i = 0; i < demux->num_streams; ++i) { + AsfPayload *last_payload = NULL; + AsfStream *stream; + gint last_idx; + + stream = &demux->stream[i]; + if (G_UNLIKELY (stream->payloads->len == 0)) { + ++num_no_data; + GST_LOG_OBJECT (stream->pad, "no data queued"); + continue; + } + + /* find last payload with timestamp */ + for (last_idx = stream->payloads->len - 1; + last_idx >= 0 && (last_payload == NULL + || !GST_CLOCK_TIME_IS_VALID (last_payload->ts)); --last_idx) { + last_payload = &g_array_index (stream->payloads, AsfPayload, last_idx); + } + + GST_LOG_OBJECT (stream->pad, "checking if %" GST_TIME_FORMAT " > %" + GST_TIME_FORMAT, GST_TIME_ARGS (last_payload->ts), + GST_TIME_ARGS (preroll_time)); + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (last_payload->ts) + || last_payload->ts <= preroll_time)) { + GST_LOG_OBJECT (stream->pad, "not beyond preroll point yet"); + return FALSE; + } + } + + if (G_UNLIKELY (num_no_data > 0)) + return FALSE; + + return TRUE; +} + +#if 0 +static gboolean +gst_asf_demux_have_mutually_exclusive_active_stream (GstASFDemux * demux, + AsfStream * stream) +{ + GSList *l; + + for (l = demux->mut_ex_streams; l != NULL; l = l->next) { + guint8 *mes; + + /* check for each mutual exclusion group whether it affects this stream */ + for (mes = (guint8 *) l->data; mes != NULL && *mes != 0xff; ++mes) { + if (*mes == stream->id) { + /* we are in this group; let's check if we've already activated streams + * that are in the same group (and hence mutually exclusive to this + * one) */ + for (mes = (guint8 *) l->data; mes != NULL && *mes != 0xff; ++mes) { + guint i; + + for (i = 0; i < demux->num_streams; ++i) { + if (demux->stream[i].id == *mes && demux->stream[i].active) { + GST_LOG_OBJECT (demux, "stream with ID %d is mutually exclusive " + "to already active stream with ID %d", stream->id, + demux->stream[i].id); + return TRUE; + } + } + } + /* we can only be in this group once, let's break out and move on to + * the next mutual exclusion group */ + break; + } + } + } + + return FALSE; +} +#endif + +static void +gst_asf_demux_check_segment_ts (GstASFDemux * demux, GstClockTime payload_ts) +{ + /* remember the first queued timestamp for the segment */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (demux->segment_ts) && + GST_CLOCK_TIME_IS_VALID (demux->first_ts))) { + GST_DEBUG_OBJECT (demux, "segment ts: %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->first_ts)); + demux->segment_ts = payload_ts; + /* always note, but only determines segment when streaming */ + if (demux->streaming) + gst_segment_do_seek (&demux->segment, demux->in_segment.rate, + GST_FORMAT_TIME, (GstSeekFlags) demux->segment.flags, + GST_SEEK_TYPE_SET, demux->segment_ts, GST_SEEK_TYPE_NONE, 0, NULL); + } +} + +static gboolean +gst_asf_demux_check_first_ts (GstASFDemux * demux, gboolean force) +{ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (demux->first_ts))) { + GstClockTime first_ts = GST_CLOCK_TIME_NONE; + int i; + + /* go trhough each stream, find smallest timestamp */ + for (i = 0; i < demux->num_streams; ++i) { + AsfStream *stream; + int j; + GstClockTime stream_min_ts = GST_CLOCK_TIME_NONE; + GstClockTime stream_min_ts2 = GST_CLOCK_TIME_NONE; /* second smallest timestamp */ + stream = &demux->stream[i]; + + for (j = 0; j < stream->payloads->len; ++j) { + AsfPayload *payload = &g_array_index (stream->payloads, AsfPayload, j); + if (GST_CLOCK_TIME_IS_VALID (payload->ts) && + (!GST_CLOCK_TIME_IS_VALID (stream_min_ts) + || stream_min_ts > payload->ts)) { + stream_min_ts = payload->ts; + } + if (GST_CLOCK_TIME_IS_VALID (payload->ts) && + payload->ts > stream_min_ts && + (!GST_CLOCK_TIME_IS_VALID (stream_min_ts2) + || stream_min_ts2 > payload->ts)) { + stream_min_ts2 = payload->ts; + } + } + + /* there are some DVR ms files where first packet has TS of 0 (instead of -1) while subsequent packets have + regular (singificantly larger) timestamps. If we don't deal with it, we may end up with huge gap in timestamps + which makes playback stuck. The 0 timestamp may also be valid though, if the second packet timestamp continues + from it. I havent found a better way to distinguish between these two, except to set an arbitrary boundary + and disregard the first 0 timestamp if the second timestamp is bigger than the boundary) */ + + if (stream_min_ts == 0 && stream_min_ts2 == GST_CLOCK_TIME_NONE && !force) /* still waiting for the second timestamp */ + return FALSE; + + if (stream_min_ts == 0 && stream_min_ts2 > GST_SECOND) /* first timestamp is 0 and second is significantly larger, disregard the 0 */ + stream_min_ts = stream_min_ts2; + + /* if we don't have timestamp for this stream, wait for more data */ + if (!GST_CLOCK_TIME_IS_VALID (stream_min_ts) && !force) + return FALSE; + + if (GST_CLOCK_TIME_IS_VALID (stream_min_ts) && + (!GST_CLOCK_TIME_IS_VALID (first_ts) || first_ts > stream_min_ts)) + first_ts = stream_min_ts; + } + + if (!GST_CLOCK_TIME_IS_VALID (first_ts)) /* can happen with force = TRUE */ + first_ts = 0; + + demux->first_ts = first_ts; + + /* update packets queued before we knew first timestamp */ + for (i = 0; i < demux->num_streams; ++i) { + AsfStream *stream; + int j; + stream = &demux->stream[i]; + + for (j = 0; j < stream->payloads->len; ++j) { + AsfPayload *payload = &g_array_index (stream->payloads, AsfPayload, j); + if (GST_CLOCK_TIME_IS_VALID (payload->ts)) { + if (payload->ts > first_ts) + payload->ts -= first_ts; + else + payload->ts = 0; + } + } + } + } + + gst_asf_demux_check_segment_ts (demux, 0); + + return TRUE; +} + +static gboolean +gst_asf_demux_update_caps_from_payload (GstASFDemux * demux, AsfStream * stream) +{ + /* try to determine whether the stream is AC-3 or MPEG; In dvr-ms the codecTag is unreliable + and often set wrong, inspecting the data is the only way that seem to be working */ + GstTypeFindProbability prob = GST_TYPE_FIND_NONE; + GstCaps *caps = NULL; + int i; + GstAdapter *adapter = gst_adapter_new (); + + for (i = 0; i < stream->payloads->len && prob < GST_TYPE_FIND_LIKELY; ++i) { + const guint8 *data; + AsfPayload *payload; + int len; + + payload = &g_array_index (stream->payloads, AsfPayload, i); + gst_adapter_push (adapter, gst_buffer_ref (payload->buf)); + len = gst_adapter_available (adapter); + data = gst_adapter_map (adapter, len); + + again: + +#define MIN_LENGTH 128 + + /* look for the sync points */ + while (TRUE) { + if (len < MIN_LENGTH || /* give typefind something to work on */ + (data[0] == 0x0b && data[1] == 0x77) || /* AC-3 sync point */ + (data[0] == 0xFF && ((data[1] & 0xF0) >> 4) == 0xF)) /* MPEG sync point */ + break; + ++data; + --len; + } + + gst_caps_take (&caps, gst_type_find_helper_for_data (GST_OBJECT (demux), + data, len, &prob)); + + if (prob < GST_TYPE_FIND_LIKELY) { + ++data; + --len; + if (len > MIN_LENGTH) + /* this wasn't it, look for another sync point */ + goto again; + } + + gst_adapter_unmap (adapter); + } + + gst_object_unref (adapter); + + if (caps) { + gst_caps_take (&stream->caps, caps); + return TRUE; + } else { + return FALSE; + } +} + +static gboolean +gst_asf_demux_check_activate_streams (GstASFDemux * demux, gboolean force) +{ + guint i; + + if (demux->activated_streams) + return TRUE; + + if (G_UNLIKELY (!gst_asf_demux_check_first_ts (demux, force))) + return FALSE; + + if (!all_streams_prerolled (demux) && !force) { + GST_DEBUG_OBJECT (demux, "not all streams with data beyond preroll yet"); + return FALSE; + } + + for (i = 0; i < demux->num_streams; ++i) { + AsfStream *stream = &demux->stream[i]; + + if (stream->payloads->len > 0) { + + if (stream->inspect_payload && /* dvr-ms required payload inspection */ + !stream->active && /* do not inspect active streams (caps were already set) */ + !gst_asf_demux_update_caps_from_payload (demux, stream) && /* failed to determine caps */ + stream->payloads->len < 20) { /* if we couldn't determine the caps from 20 packets then just give up and use whatever was in codecTag */ + /* try to gather some more data */ + return FALSE; + } + /* we don't check mutual exclusion stuff here; either we have data for + * a stream, then we active it, or we don't, then we'll ignore it */ + GST_LOG_OBJECT (stream->pad, "is prerolled - activate!"); + gst_asf_demux_activate_stream (demux, stream); + } else { + GST_LOG_OBJECT (stream->pad, "no data, ignoring stream"); + } + } + + gst_asf_demux_release_old_pads (demux); + + demux->activated_streams = TRUE; + GST_LOG_OBJECT (demux, "signalling no more pads"); + gst_element_no_more_pads (GST_ELEMENT (demux)); + return TRUE; +} + +/* returns the stream that has a complete payload with the lowest timestamp + * queued, or NULL (we push things by timestamp because during the internal + * prerolling we might accumulate more data then the external queues can take, + * so we'd lock up if we pushed all accumulated data for stream N in one go) */ +static AsfStream * +gst_asf_demux_find_stream_with_complete_payload (GstASFDemux * demux) +{ + AsfPayload *best_payload = NULL; + AsfStream *best_stream = NULL; + guint i; + + for (i = 0; i < demux->num_streams; ++i) { + AsfStream *stream; + int j; + + stream = &demux->stream[i]; + + /* Don't push any data until we have at least one payload that falls within + * the current segment. This way we can remove out-of-segment payloads that + * don't need to be decoded after a seek, sending only data from the + * keyframe directly before our segment start */ + if (stream->payloads->len > 0) { + AsfPayload *payload = NULL; + gint last_idx; + + /* find last payload with timestamp */ + for (last_idx = stream->payloads->len - 1; + last_idx >= 0 && (payload == NULL + || !GST_CLOCK_TIME_IS_VALID (payload->ts)); --last_idx) { + payload = &g_array_index (stream->payloads, AsfPayload, last_idx); + } + + /* if this is first payload after seek we might need to update the segment */ + if (GST_CLOCK_TIME_IS_VALID (payload->ts)) + gst_asf_demux_check_segment_ts (demux, payload->ts); + + if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (payload->ts) && + (payload->ts < demux->segment.start))) { + if (G_UNLIKELY ((!demux->accurate) && payload->keyframe)) { + GST_DEBUG_OBJECT (stream->pad, + "Found keyframe, updating segment start to %" GST_TIME_FORMAT, + GST_TIME_ARGS (payload->ts)); + demux->segment.start = payload->ts; + demux->segment.time = payload->ts; + } else { + GST_DEBUG_OBJECT (stream->pad, "Last queued payload has timestamp %" + GST_TIME_FORMAT " which is before our segment start %" + GST_TIME_FORMAT ", not pushing yet", GST_TIME_ARGS (payload->ts), + GST_TIME_ARGS (demux->segment.start)); + continue; + } + } + + /* Now see if there's a complete payload queued for this stream */ + + payload = NULL; + /* find first complete payload with timestamp */ + for (j = 0; + j < stream->payloads->len && (payload == NULL + || !GST_CLOCK_TIME_IS_VALID (payload->ts)); ++j) { + payload = &g_array_index (stream->payloads, AsfPayload, j); + } + + if (!gst_asf_payload_is_complete (payload)) + continue; + + /* ... and whether its timestamp is lower than the current best */ + if (best_stream == NULL || best_payload->ts > payload->ts) { + best_stream = stream; + best_payload = payload; + } + } + } + + return best_stream; +} + +static GstFlowReturn +gst_asf_demux_push_complete_payloads (GstASFDemux * demux, gboolean force) +{ + AsfStream *stream; + GstFlowReturn ret = GST_FLOW_OK; + + if (G_UNLIKELY (!demux->activated_streams)) { + if (!gst_asf_demux_check_activate_streams (demux, force)) + return GST_FLOW_OK; + /* streams are now activated */ + } + + while ((stream = gst_asf_demux_find_stream_with_complete_payload (demux))) { + AsfPayload *payload; + + /* wait until we had a chance to "lock on" some payload's timestamp */ + if (G_UNLIKELY (demux->need_newsegment + && !GST_CLOCK_TIME_IS_VALID (demux->segment_ts))) + return GST_FLOW_OK; + + payload = &g_array_index (stream->payloads, AsfPayload, 0); + + /* do we need to send a newsegment event */ + if ((G_UNLIKELY (demux->need_newsegment))) { + GstEvent *segment_event; + + /* safe default if insufficient upstream info */ + if (!GST_CLOCK_TIME_IS_VALID (demux->in_gap)) + demux->in_gap = 0; + + if (demux->segment.stop == GST_CLOCK_TIME_NONE && + demux->segment.duration > 0) { + /* slight HACK; prevent clipping of last bit */ + demux->segment.stop = demux->segment.duration + demux->in_gap; + } + + /* FIXME : only if ACCURATE ! */ + if (G_LIKELY (!demux->accurate + && (GST_CLOCK_TIME_IS_VALID (payload->ts)))) { + GST_DEBUG ("Adjusting newsegment start to %" GST_TIME_FORMAT, + GST_TIME_ARGS (payload->ts)); + demux->segment.start = payload->ts; + demux->segment.time = payload->ts; + } + + GST_DEBUG_OBJECT (demux, "sending new-segment event %" GST_SEGMENT_FORMAT, + &demux->segment); + + /* note: we fix up all timestamps to start from 0, so this should be ok */ + segment_event = gst_event_new_segment (&demux->segment); + if (demux->segment_seqnum) + gst_event_set_seqnum (segment_event, demux->segment_seqnum); + gst_asf_demux_send_event_unlocked (demux, segment_event); + + /* now post any global tags we may have found */ + if (demux->taglist == NULL) { + demux->taglist = gst_tag_list_new_empty (); + gst_tag_list_set_scope (demux->taglist, GST_TAG_SCOPE_GLOBAL); + } + + gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE, + GST_TAG_CONTAINER_FORMAT, "ASF", NULL); + + GST_DEBUG_OBJECT (demux, "global tags: %" GST_PTR_FORMAT, demux->taglist); + gst_asf_demux_send_event_unlocked (demux, + gst_event_new_tag (demux->taglist)); + demux->taglist = NULL; + + demux->need_newsegment = FALSE; + demux->segment_seqnum = 0; + demux->segment_running = TRUE; + } + + /* Do we have tags pending for this stream? */ + if (G_UNLIKELY (stream->pending_tags)) { + GST_LOG_OBJECT (stream->pad, "%" GST_PTR_FORMAT, stream->pending_tags); + gst_pad_push_event (stream->pad, + gst_event_new_tag (stream->pending_tags)); + stream->pending_tags = NULL; + } + + /* We have the whole packet now so we should push the packet to + * the src pad now. First though we should check if we need to do + * descrambling */ + if (G_UNLIKELY (stream->span > 1)) { + gst_asf_demux_descramble_buffer (demux, stream, &payload->buf); + } + + payload->buf = gst_buffer_make_writable (payload->buf); + + if (G_LIKELY (!payload->keyframe)) { + GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DELTA_UNIT); + } + + if (G_UNLIKELY (stream->discont)) { + GST_DEBUG_OBJECT (stream->pad, "marking DISCONT on stream"); + GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } + + if (G_UNLIKELY (stream->is_video && payload->par_x && payload->par_y && + (payload->par_x != stream->par_x) && + (payload->par_y != stream->par_y))) { + GST_DEBUG ("Updating PAR (%d/%d => %d/%d)", + stream->par_x, stream->par_y, payload->par_x, payload->par_y); + stream->par_x = payload->par_x; + stream->par_y = payload->par_y; + stream->caps = gst_caps_make_writable (stream->caps); + gst_caps_set_simple (stream->caps, "pixel-aspect-ratio", + GST_TYPE_FRACTION, stream->par_x, stream->par_y, NULL); + gst_pad_set_caps (stream->pad, stream->caps); + } + + if (G_UNLIKELY (stream->interlaced != payload->interlaced)) { + GST_DEBUG ("Updating interlaced status (%d => %d)", stream->interlaced, + payload->interlaced); + stream->interlaced = payload->interlaced; + stream->caps = gst_caps_make_writable (stream->caps); + gst_caps_set_simple (stream->caps, "interlace-mode", G_TYPE_BOOLEAN, + (stream->interlaced ? "mixed" : "progressive"), NULL); + gst_pad_set_caps (stream->pad, stream->caps); + } + + /* (sort of) interpolate timestamps using upstream "frame of reference", + * typically useful for live src, but might (unavoidably) mess with + * position reporting if a live src is playing not so live content + * (e.g. rtspsrc taking some time to fall back to tcp) */ + GST_BUFFER_PTS (payload->buf) = payload->ts; + if (GST_BUFFER_PTS_IS_VALID (payload->buf)) { + GST_BUFFER_PTS (payload->buf) += demux->in_gap; + } + if (payload->duration == GST_CLOCK_TIME_NONE + && stream->ext_props.avg_time_per_frame != 0) + GST_BUFFER_DURATION (payload->buf) = + stream->ext_props.avg_time_per_frame * 100; + else + GST_BUFFER_DURATION (payload->buf) = payload->duration; + + /* FIXME: we should really set durations on buffers if we can */ + + GST_LOG_OBJECT (stream->pad, "pushing buffer, ts=%" GST_TIME_FORMAT + ", dur=%" GST_TIME_FORMAT " size=%" G_GSIZE_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (payload->buf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (payload->buf)), + gst_buffer_get_size (payload->buf)); + + if (stream->active) { + ret = gst_pad_push (stream->pad, payload->buf); + ret = gst_flow_combiner_update_flow (demux->flowcombiner, ret); + } else { + gst_buffer_unref (payload->buf); + ret = GST_FLOW_OK; + } + payload->buf = NULL; + g_array_remove_index (stream->payloads, 0); + + /* Break out as soon as we have an issue */ + if (G_UNLIKELY (ret != GST_FLOW_OK)) + break; + } + + return ret; +} + +static gboolean +gst_asf_demux_check_buffer_is_header (GstASFDemux * demux, GstBuffer * buf) +{ + AsfObject obj; + GstMapInfo map; + g_assert (buf != NULL); + + GST_LOG_OBJECT (demux, "Checking if buffer is a header"); + + gst_buffer_map (buf, &map, GST_MAP_READ); + + /* we return false on buffer too small */ + if (map.size < ASF_OBJECT_HEADER_SIZE) { + gst_buffer_unmap (buf, &map); + return FALSE; + } + + /* check if it is a header */ + asf_demux_peek_object (demux, map.data, ASF_OBJECT_HEADER_SIZE, &obj, TRUE); + gst_buffer_unmap (buf, &map); + if (obj.id == ASF_OBJ_HEADER) { + return TRUE; + } + return FALSE; +} + +static gboolean +gst_asf_demux_check_chained_asf (GstASFDemux * demux) +{ + guint64 off = demux->data_offset + (demux->packet * demux->packet_size); + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *buf = NULL; + gboolean header = FALSE; + + /* TODO maybe we should skip index objects after the data and look + * further for a new header */ + if (gst_asf_demux_pull_data (demux, off, ASF_OBJECT_HEADER_SIZE, &buf, &ret)) { + g_assert (buf != NULL); + /* check if it is a header */ + if (gst_asf_demux_check_buffer_is_header (demux, buf)) { + GST_DEBUG_OBJECT (demux, "new base offset: %" G_GUINT64_FORMAT, off); + demux->base_offset = off; + header = TRUE; + } + + gst_buffer_unref (buf); + } + + return header; +} + +static void +gst_asf_demux_loop (GstASFDemux * demux) +{ + GstFlowReturn flow = GST_FLOW_OK; + GstBuffer *buf = NULL; + guint64 off; + gboolean sent_eos = FALSE; + + if (G_UNLIKELY (demux->state == GST_ASF_DEMUX_STATE_HEADER)) { + if (!gst_asf_demux_pull_headers (demux)) { + flow = GST_FLOW_ERROR; + goto pause; + } + + gst_asf_demux_pull_indices (demux); + } + + g_assert (demux->state == GST_ASF_DEMUX_STATE_DATA); + + if (G_UNLIKELY (demux->num_packets != 0 + && demux->packet >= demux->num_packets)) + goto eos; + + GST_LOG_OBJECT (demux, "packet %u/%u", (guint) demux->packet + 1, + (guint) demux->num_packets); + + off = demux->data_offset + (demux->packet * demux->packet_size); + + if (G_UNLIKELY (!gst_asf_demux_pull_data (demux, off, + demux->packet_size * demux->speed_packets, &buf, &flow))) { + GST_DEBUG_OBJECT (demux, "got flow %s", gst_flow_get_name (flow)); + if (flow == GST_FLOW_EOS) + goto eos; + else if (flow == GST_FLOW_FLUSHING) { + GST_DEBUG_OBJECT (demux, "Not fatal"); + goto pause; + } else + goto read_failed; + } + + if (G_LIKELY (demux->speed_packets == 1)) { + GstAsfDemuxParsePacketError err; + err = gst_asf_demux_parse_packet (demux, buf); + if (G_UNLIKELY (err != GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE)) { + /* when we don't know when the data object ends, we should check + * for a chained asf */ + if (demux->num_packets == 0) { + if (gst_asf_demux_check_buffer_is_header (demux, buf)) { + GST_INFO_OBJECT (demux, "Chained asf found"); + demux->base_offset = off; + gst_asf_demux_reset (demux, TRUE); + gst_buffer_unref (buf); + return; + } + } + /* FIXME: We should tally up fatal errors and error out only + * after a few broken packets in a row? */ + + GST_INFO_OBJECT (demux, "Ignoring recoverable parse error"); + gst_buffer_unref (buf); + ++demux->packet; + return; + } + + flow = gst_asf_demux_push_complete_payloads (demux, FALSE); + + ++demux->packet; + + } else { + guint n; + for (n = 0; n < demux->speed_packets; n++) { + GstBuffer *sub; + GstAsfDemuxParsePacketError err; + + sub = + gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL, + n * demux->packet_size, demux->packet_size); + err = gst_asf_demux_parse_packet (demux, sub); + if (G_UNLIKELY (err != GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE)) { + /* when we don't know when the data object ends, we should check + * for a chained asf */ + if (demux->num_packets == 0) { + if (gst_asf_demux_check_buffer_is_header (demux, sub)) { + GST_INFO_OBJECT (demux, "Chained asf found"); + demux->base_offset = off + n * demux->packet_size; + gst_asf_demux_reset (demux, TRUE); + gst_buffer_unref (sub); + gst_buffer_unref (buf); + return; + } + } + /* FIXME: We should tally up fatal errors and error out only + * after a few broken packets in a row? */ + + GST_INFO_OBJECT (demux, "Ignoring recoverable parse error"); + flow = GST_FLOW_OK; + } + + gst_buffer_unref (sub); + + if (err == GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE) + flow = gst_asf_demux_push_complete_payloads (demux, FALSE); + + ++demux->packet; + + } + + /* reset speed pull */ + demux->speed_packets = 1; + } + + gst_buffer_unref (buf); + + if (G_UNLIKELY (demux->num_packets > 0 + && demux->packet >= demux->num_packets)) { + GST_LOG_OBJECT (demux, "reached EOS"); + goto eos; + } + + if (G_UNLIKELY (flow != GST_FLOW_OK)) { + GST_DEBUG_OBJECT (demux, "pushing complete payloads failed"); + goto pause; + } + + /* check if we're at the end of the configured segment */ + /* FIXME: check if segment end reached etc. */ + + return; + +eos: + { + /* if we haven't activated our streams yet, this might be because we have + * less data queued than required for preroll; force stream activation and + * send any pending payloads before sending EOS */ + if (!demux->activated_streams) + gst_asf_demux_push_complete_payloads (demux, TRUE); + + /* we want to push an eos or post a segment-done in any case */ + 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->segment.duration; + + GST_INFO_OBJECT (demux, "Posting segment-done, at end of segment"); + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_segment_done (GST_OBJECT (demux), GST_FORMAT_TIME, + stop)); + gst_asf_demux_send_event_unlocked (demux, + gst_event_new_segment_done (GST_FORMAT_TIME, stop)); + } else if (flow != GST_FLOW_EOS) { + /* check if we have a chained asf, in case, we don't eos yet */ + if (gst_asf_demux_check_chained_asf (demux)) { + GST_INFO_OBJECT (demux, "Chained ASF starting"); + gst_asf_demux_reset (demux, TRUE); + return; + } + } + /* normal playback, send EOS to all linked pads */ + GST_INFO_OBJECT (demux, "Sending EOS, at end of stream"); + gst_asf_demux_send_event_unlocked (demux, gst_event_new_eos ()); + sent_eos = TRUE; + /* ... and fall through to pause */ + } +pause: + { + GST_DEBUG_OBJECT (demux, "pausing task, flow return: %s", + gst_flow_get_name (flow)); + demux->segment_running = FALSE; + gst_pad_pause_task (demux->sinkpad); + + /* For the error cases (not EOS) */ + if (!sent_eos) { + if (flow == GST_FLOW_EOS) + gst_asf_demux_send_event_unlocked (demux, gst_event_new_eos ()); + else if (flow < GST_FLOW_EOS || flow == GST_FLOW_NOT_LINKED) { + /* Post an error. Hopefully something else already has, but if not... */ + GST_ELEMENT_ERROR (demux, STREAM, FAILED, + (_("Internal data stream error.")), + ("streaming stopped, reason %s", gst_flow_get_name (flow))); + } + } + return; + } + +/* ERRORS */ +read_failed: + { + GST_DEBUG_OBJECT (demux, "Read failed, doh"); + gst_asf_demux_send_event_unlocked (demux, gst_event_new_eos ()); + flow = GST_FLOW_EOS; + goto pause; + } +#if 0 + /* See FIXMEs above */ +parse_error: + { + gst_buffer_unref (buf); + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("Error parsing ASF packet %u", (guint) demux->packet)); + gst_asf_demux_send_event_unlocked (demux, gst_event_new_eos ()); + flow = GST_FLOW_ERROR; + goto pause; + } +#endif +} + +#define GST_ASF_DEMUX_CHECK_HEADER_YES 0 +#define GST_ASF_DEMUX_CHECK_HEADER_NO 1 +#define GST_ASF_DEMUX_CHECK_HEADER_NEED_DATA 2 + +static gint +gst_asf_demux_check_header (GstASFDemux * demux) +{ + AsfObject obj; + guint8 *cdata = (guint8 *) gst_adapter_map (demux->adapter, + ASF_OBJECT_HEADER_SIZE); + if (cdata == NULL) /* need more data */ + return GST_ASF_DEMUX_CHECK_HEADER_NEED_DATA; + + asf_demux_peek_object (demux, cdata, ASF_OBJECT_HEADER_SIZE, &obj, FALSE); + if (obj.id != ASF_OBJ_HEADER) { + return GST_ASF_DEMUX_CHECK_HEADER_NO; + } else { + return GST_ASF_DEMUX_CHECK_HEADER_YES; + } +} + +static GstFlowReturn +gst_asf_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstASFDemux *demux; + + demux = GST_ASF_DEMUX (parent); + + GST_LOG_OBJECT (demux, + "buffer: size=%" G_GSIZE_FORMAT ", offset=%" G_GINT64_FORMAT ", time=%" + GST_TIME_FORMAT, gst_buffer_get_size (buf), GST_BUFFER_OFFSET (buf), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + + if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (buf))) { + GST_DEBUG_OBJECT (demux, "received DISCONT"); + gst_asf_demux_mark_discont (demux); + } + + if (G_UNLIKELY ((!GST_CLOCK_TIME_IS_VALID (demux->in_gap) && + GST_BUFFER_TIMESTAMP_IS_VALID (buf)))) { + demux->in_gap = GST_BUFFER_TIMESTAMP (buf) - demux->in_segment.start; + GST_DEBUG_OBJECT (demux, "upstream segment start %" GST_TIME_FORMAT + ", interpolation gap: %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->in_segment.start), GST_TIME_ARGS (demux->in_gap)); + } + + gst_adapter_push (demux->adapter, buf); + + switch (demux->state) { + case GST_ASF_DEMUX_STATE_INDEX:{ + gint result = gst_asf_demux_check_header (demux); + if (result == GST_ASF_DEMUX_CHECK_HEADER_NEED_DATA) /* need more data */ + break; + + if (result == GST_ASF_DEMUX_CHECK_HEADER_NO) { + /* we don't care about this, probably an index */ + /* TODO maybe would be smarter to skip all the indices + * until we got a new header or EOS to decide */ + GST_LOG_OBJECT (demux, "Received index object, its EOS"); + goto eos; + } else { + GST_INFO_OBJECT (demux, "Chained asf starting"); + /* cleanup and get ready for a chained asf */ + gst_asf_demux_reset (demux, TRUE); + /* fall through */ + } + } + case GST_ASF_DEMUX_STATE_HEADER:{ + ret = gst_asf_demux_chain_headers (demux); + if (demux->state != GST_ASF_DEMUX_STATE_DATA) + break; + /* otherwise fall through */ + } + case GST_ASF_DEMUX_STATE_DATA: + { + guint64 data_size; + + data_size = demux->packet_size; + + while (gst_adapter_available (demux->adapter) >= data_size) { + GstBuffer *buf; + GstAsfDemuxParsePacketError err; + + /* we don't know the length of the stream + * check for a chained asf everytime */ + if (demux->num_packets == 0) { + gint result = gst_asf_demux_check_header (demux); + + if (result == GST_ASF_DEMUX_CHECK_HEADER_YES) { + GST_INFO_OBJECT (demux, "Chained asf starting"); + /* cleanup and get ready for a chained asf */ + gst_asf_demux_reset (demux, TRUE); + break; + } + } else if (G_UNLIKELY (demux->num_packets != 0 && demux->packet >= 0 + && demux->packet >= demux->num_packets)) { + /* do not overshoot data section when streaming */ + break; + } + + buf = gst_adapter_take_buffer (demux->adapter, data_size); + + /* FIXME: We should tally up fatal errors and error out only + * after a few broken packets in a row? */ + err = gst_asf_demux_parse_packet (demux, buf); + + gst_buffer_unref (buf); + + if (G_LIKELY (err == GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE)) + ret = gst_asf_demux_push_complete_payloads (demux, FALSE); + else + GST_WARNING_OBJECT (demux, "Parse error"); + + if (demux->packet >= 0) + ++demux->packet; + } + if (G_UNLIKELY (demux->num_packets != 0 && demux->packet >= 0 + && demux->packet >= demux->num_packets)) { + demux->state = GST_ASF_DEMUX_STATE_INDEX; + } + break; + } + default: + g_assert_not_reached (); + } + +done: + if (ret != GST_FLOW_OK) + GST_DEBUG_OBJECT (demux, "flow: %s", gst_flow_get_name (ret)); + + return ret; + +eos: + { + GST_DEBUG_OBJECT (demux, "Handled last packet, setting EOS"); + ret = GST_FLOW_EOS; + goto done; + } +} + +static inline gboolean +gst_asf_demux_skip_bytes (guint num_bytes, guint8 ** p_data, guint64 * p_size) +{ + if (*p_size < num_bytes) + return FALSE; + + *p_data += num_bytes; + *p_size -= num_bytes; + return TRUE; +} + +static inline guint8 +gst_asf_demux_get_uint8 (guint8 ** p_data, guint64 * p_size) +{ + guint8 ret; + + g_assert (*p_size >= 1); + ret = GST_READ_UINT8 (*p_data); + *p_data += sizeof (guint8); + *p_size -= sizeof (guint8); + return ret; +} + +static inline guint16 +gst_asf_demux_get_uint16 (guint8 ** p_data, guint64 * p_size) +{ + guint16 ret; + + g_assert (*p_size >= 2); + ret = GST_READ_UINT16_LE (*p_data); + *p_data += sizeof (guint16); + *p_size -= sizeof (guint16); + return ret; +} + +static inline guint32 +gst_asf_demux_get_uint32 (guint8 ** p_data, guint64 * p_size) +{ + guint32 ret; + + g_assert (*p_size >= 4); + ret = GST_READ_UINT32_LE (*p_data); + *p_data += sizeof (guint32); + *p_size -= sizeof (guint32); + return ret; +} + +static inline guint64 +gst_asf_demux_get_uint64 (guint8 ** p_data, guint64 * p_size) +{ + guint64 ret; + + g_assert (*p_size >= 8); + ret = GST_READ_UINT64_LE (*p_data); + *p_data += sizeof (guint64); + *p_size -= sizeof (guint64); + return ret; +} + +static gboolean +gst_asf_demux_get_buffer (GstBuffer ** p_buf, guint num_bytes_to_read, + guint8 ** p_data, guint64 * p_size) +{ + *p_buf = NULL; + + if (*p_size < num_bytes_to_read) + return FALSE; + + *p_buf = gst_buffer_new_and_alloc (num_bytes_to_read); + gst_buffer_fill (*p_buf, 0, *p_data, num_bytes_to_read); + + *p_data += num_bytes_to_read; + *p_size -= num_bytes_to_read; + + return TRUE; +} + +static gboolean +gst_asf_demux_get_bytes (guint8 ** p_buf, guint num_bytes_to_read, + guint8 ** p_data, guint64 * p_size) +{ + *p_buf = NULL; + + if (*p_size < num_bytes_to_read) + return FALSE; + + *p_buf = g_memdup (*p_data, num_bytes_to_read); + *p_data += num_bytes_to_read; + *p_size -= num_bytes_to_read; + return TRUE; +} + +static gboolean +gst_asf_demux_get_string (gchar ** p_str, guint16 * p_strlen, + guint8 ** p_data, guint64 * p_size) +{ + guint16 s_length; + guint8 *s; + + *p_str = NULL; + + if (*p_size < 2) + return FALSE; + + s_length = gst_asf_demux_get_uint16 (p_data, p_size); + + if (p_strlen) + *p_strlen = s_length; + + if (s_length == 0) { + GST_WARNING ("zero-length string"); + *p_str = g_strdup (""); + return TRUE; + } + + if (!gst_asf_demux_get_bytes (&s, s_length, p_data, p_size)) + return FALSE; + + g_assert (s != NULL); + + /* just because They don't exist doesn't + * mean They are not out to get you ... */ + if (s[s_length - 1] != '\0') { + s = g_realloc (s, s_length + 1); + s[s_length] = '\0'; + } + + *p_str = (gchar *) s; + return TRUE; +} + + +static void +gst_asf_demux_get_guid (ASFGuid * guid, guint8 ** p_data, guint64 * p_size) +{ + g_assert (*p_size >= 4 * sizeof (guint32)); + + guid->v1 = gst_asf_demux_get_uint32 (p_data, p_size); + guid->v2 = gst_asf_demux_get_uint32 (p_data, p_size); + guid->v3 = gst_asf_demux_get_uint32 (p_data, p_size); + guid->v4 = gst_asf_demux_get_uint32 (p_data, p_size); +} + +static gboolean +gst_asf_demux_get_stream_audio (asf_stream_audio * audio, guint8 ** p_data, + guint64 * p_size) +{ + if (*p_size < (2 + 2 + 4 + 4 + 2 + 2 + 2)) + return FALSE; + + /* WAVEFORMATEX Structure */ + audio->codec_tag = gst_asf_demux_get_uint16 (p_data, p_size); + audio->channels = gst_asf_demux_get_uint16 (p_data, p_size); + audio->sample_rate = gst_asf_demux_get_uint32 (p_data, p_size); + audio->byte_rate = gst_asf_demux_get_uint32 (p_data, p_size); + audio->block_align = gst_asf_demux_get_uint16 (p_data, p_size); + audio->word_size = gst_asf_demux_get_uint16 (p_data, p_size); + /* Codec specific data size */ + audio->size = gst_asf_demux_get_uint16 (p_data, p_size); + return TRUE; +} + +static gboolean +gst_asf_demux_get_stream_video (asf_stream_video * video, guint8 ** p_data, + guint64 * p_size) +{ + if (*p_size < (4 + 4 + 1 + 2)) + return FALSE; + + video->width = gst_asf_demux_get_uint32 (p_data, p_size); + video->height = gst_asf_demux_get_uint32 (p_data, p_size); + video->unknown = gst_asf_demux_get_uint8 (p_data, p_size); + video->size = gst_asf_demux_get_uint16 (p_data, p_size); + return TRUE; +} + +static gboolean +gst_asf_demux_get_stream_video_format (asf_stream_video_format * fmt, + guint8 ** p_data, guint64 * p_size) +{ + if (*p_size < (4 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 4)) + return FALSE; + + fmt->size = gst_asf_demux_get_uint32 (p_data, p_size); + fmt->width = gst_asf_demux_get_uint32 (p_data, p_size); + fmt->height = gst_asf_demux_get_uint32 (p_data, p_size); + fmt->planes = gst_asf_demux_get_uint16 (p_data, p_size); + fmt->depth = gst_asf_demux_get_uint16 (p_data, p_size); + fmt->tag = gst_asf_demux_get_uint32 (p_data, p_size); + fmt->image_size = gst_asf_demux_get_uint32 (p_data, p_size); + fmt->xpels_meter = gst_asf_demux_get_uint32 (p_data, p_size); + fmt->ypels_meter = gst_asf_demux_get_uint32 (p_data, p_size); + fmt->num_colors = gst_asf_demux_get_uint32 (p_data, p_size); + fmt->imp_colors = gst_asf_demux_get_uint32 (p_data, p_size); + return TRUE; +} + +AsfStream * +gst_asf_demux_get_stream (GstASFDemux * demux, guint16 id) +{ + guint i; + + for (i = 0; i < demux->num_streams; i++) { + if (demux->stream[i].id == id) + return &demux->stream[i]; + } + + if (gst_asf_demux_is_unknown_stream (demux, id)) + GST_WARNING ("Segment found for undefined stream: (%d)", id); + return NULL; +} + +static AsfStream * +gst_asf_demux_setup_pad (GstASFDemux * demux, GstPad * src_pad, + GstCaps * caps, guint16 id, gboolean is_video, GstTagList * tags) +{ + AsfStream *stream; + + gst_pad_use_fixed_caps (src_pad); + gst_pad_set_caps (src_pad, caps); + + gst_pad_set_event_function (src_pad, + GST_DEBUG_FUNCPTR (gst_asf_demux_handle_src_event)); + gst_pad_set_query_function (src_pad, + GST_DEBUG_FUNCPTR (gst_asf_demux_handle_src_query)); + + stream = &demux->stream[demux->num_streams]; + stream->caps = caps; + stream->pad = src_pad; + stream->id = id; + stream->fps_known = !is_video; /* bit hacky for audio */ + stream->is_video = is_video; + stream->pending_tags = tags; + stream->discont = TRUE; + if (is_video) { + GstStructure *st; + gint par_x, par_y; + st = gst_caps_get_structure (caps, 0); + if (gst_structure_get_fraction (st, "pixel-aspect-ratio", &par_x, &par_y) && + par_x > 0 && par_y > 0) { + GST_DEBUG ("PAR %d/%d", par_x, par_y); + stream->par_x = par_x; + stream->par_y = par_y; + } + } + + stream->payloads = g_array_new (FALSE, FALSE, sizeof (AsfPayload)); + + GST_INFO ("Created pad %s for stream %u with caps %" GST_PTR_FORMAT, + GST_PAD_NAME (src_pad), demux->num_streams, caps); + + ++demux->num_streams; + + stream->active = FALSE; + + return stream; +} + +static AsfStream * +gst_asf_demux_add_audio_stream (GstASFDemux * demux, + asf_stream_audio * audio, guint16 id, guint8 ** p_data, guint64 * p_size) +{ + GstTagList *tags = NULL; + GstBuffer *extradata = NULL; + GstPad *src_pad; + GstCaps *caps; + guint16 size_left = 0; + gchar *codec_name = NULL; + gchar *name = NULL; + + size_left = audio->size; + + /* Create the audio pad */ + name = g_strdup_printf ("audio_%u", demux->num_audio_streams); + + src_pad = gst_pad_new_from_static_template (&audio_src_template, name); + g_free (name); + + /* Swallow up any left over data and set up the + * standard properties from the header info */ + if (size_left) { + GST_INFO_OBJECT (demux, "Audio header contains %d bytes of " + "codec specific data", size_left); + + g_assert (size_left <= *p_size); + gst_asf_demux_get_buffer (&extradata, size_left, p_data, p_size); + } + + /* asf_stream_audio is the same as gst_riff_strf_auds, but with an + * additional two bytes indicating extradata. */ + /* FIXME: Handle the channel reorder map here */ + caps = gst_riff_create_audio_caps (audio->codec_tag, NULL, + (gst_riff_strf_auds *) audio, extradata, NULL, &codec_name, NULL); + + if (caps == NULL) { + caps = gst_caps_new_simple ("audio/x-asf-unknown", "codec_id", + G_TYPE_INT, (gint) audio->codec_tag, NULL); + } + + /* Informing about that audio format we just added */ + if (codec_name) { + tags = gst_tag_list_new (GST_TAG_AUDIO_CODEC, codec_name, NULL); + g_free (codec_name); + } + + if (extradata) + gst_buffer_unref (extradata); + + GST_INFO ("Adding audio stream #%u, id %u codec %u (0x%04x), tags=%" + GST_PTR_FORMAT, demux->num_audio_streams, id, audio->codec_tag, + audio->codec_tag, tags); + + ++demux->num_audio_streams; + + return gst_asf_demux_setup_pad (demux, src_pad, caps, id, FALSE, tags); +} + +static AsfStream * +gst_asf_demux_add_video_stream (GstASFDemux * demux, + asf_stream_video_format * video, guint16 id, + guint8 ** p_data, guint64 * p_size) +{ + GstTagList *tags = NULL; + GstStructure *caps_s; + GstBuffer *extradata = NULL; + GstPad *src_pad; + GstCaps *caps; + gchar *str; + gchar *name = NULL; + gchar *codec_name = NULL; + gint size_left = video->size - 40; + + /* Create the video pad */ + name = g_strdup_printf ("video_%u", demux->num_video_streams); + src_pad = gst_pad_new_from_static_template (&video_src_template, name); + g_free (name); + + /* Now try some gstreamer formatted MIME types (from gst_avi_demux_strf_vids) */ + if (size_left) { + GST_LOG ("Video header has %d bytes of codec specific data", size_left); + g_assert (size_left <= *p_size); + gst_asf_demux_get_buffer (&extradata, size_left, p_data, p_size); + } + + GST_DEBUG ("video codec %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (video->tag)); + + /* yes, asf_stream_video_format and gst_riff_strf_vids are the same */ + caps = gst_riff_create_video_caps (video->tag, NULL, + (gst_riff_strf_vids *) video, extradata, NULL, &codec_name); + + if (caps == NULL) { + caps = gst_caps_new_simple ("video/x-asf-unknown", "fourcc", + G_TYPE_UINT, video->tag, NULL); + } else { + GstStructure *s; + gint ax, ay; + + s = gst_asf_demux_get_metadata_for_stream (demux, id); + if (gst_structure_get_int (s, "AspectRatioX", &ax) && + gst_structure_get_int (s, "AspectRatioY", &ay) && (ax > 0 && ay > 0)) { + gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION, + ax, ay, NULL); + + } else { + guint ax, ay; + /* retry with the global metadata */ + GST_DEBUG ("Retrying with global metadata %" GST_PTR_FORMAT, + demux->global_metadata); + s = demux->global_metadata; + if (gst_structure_get_uint (s, "AspectRatioX", &ax) && + gst_structure_get_uint (s, "AspectRatioY", &ay)) { + GST_DEBUG ("ax:%d, ay:%d", ax, ay); + if (ax > 0 && ay > 0) + gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION, + ax, ay, NULL); + } + } + s = gst_caps_get_structure (caps, 0); + gst_structure_remove_field (s, "framerate"); + } + + caps_s = gst_caps_get_structure (caps, 0); + + /* add format field with fourcc to WMV/VC1 caps to differentiate variants */ + if (gst_structure_has_name (caps_s, "video/x-wmv")) { + str = g_strdup_printf ("%" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (video->tag)); + gst_caps_set_simple (caps, "format", G_TYPE_STRING, str, NULL); + g_free (str); + } + + if (codec_name) { + tags = gst_tag_list_new (GST_TAG_VIDEO_CODEC, codec_name, NULL); + g_free (codec_name); + } + + if (extradata) + gst_buffer_unref (extradata); + + GST_INFO ("Adding video stream #%u, id %u, codec %" + GST_FOURCC_FORMAT " (0x%08x)", demux->num_video_streams, id, + GST_FOURCC_ARGS (video->tag), video->tag); + + ++demux->num_video_streams; + + return gst_asf_demux_setup_pad (demux, src_pad, caps, id, TRUE, tags); +} + +static void +gst_asf_demux_activate_stream (GstASFDemux * demux, AsfStream * stream) +{ + if (!stream->active) { + GstEvent *event; + gchar *stream_id; + + GST_INFO_OBJECT (demux, "Activating stream %2u, pad %s, caps %" + GST_PTR_FORMAT, stream->id, GST_PAD_NAME (stream->pad), stream->caps); + gst_pad_set_active (stream->pad, TRUE); + + stream_id = + gst_pad_create_stream_id_printf (stream->pad, GST_ELEMENT_CAST (demux), + "%03u", stream->id); + + event = + gst_pad_get_sticky_event (demux->sinkpad, GST_EVENT_STREAM_START, 0); + if (event) { + if (gst_event_parse_group_id (event, &demux->group_id)) + demux->have_group_id = TRUE; + else + demux->have_group_id = FALSE; + gst_event_unref (event); + } else if (!demux->have_group_id) { + demux->have_group_id = TRUE; + demux->group_id = gst_util_group_id_next (); + } + + event = gst_event_new_stream_start (stream_id); + if (demux->have_group_id) + gst_event_set_group_id (event, demux->group_id); + + gst_pad_push_event (stream->pad, event); + g_free (stream_id); + gst_pad_set_caps (stream->pad, stream->caps); + + gst_element_add_pad (GST_ELEMENT_CAST (demux), stream->pad); + gst_flow_combiner_add_pad (demux->flowcombiner, stream->pad); + stream->active = TRUE; + } +} + +static AsfStream * +gst_asf_demux_parse_stream_object (GstASFDemux * demux, guint8 * data, + guint64 size) +{ + AsfCorrectionType correction_type; + AsfStreamType stream_type; + GstClockTime time_offset; + gboolean is_encrypted G_GNUC_UNUSED; + guint16 stream_id; + guint16 flags; + ASFGuid guid; + guint stream_specific_size; + guint type_specific_size G_GNUC_UNUSED; + guint unknown G_GNUC_UNUSED; + gboolean inspect_payload = FALSE; + AsfStream *stream = NULL; + + /* Get the rest of the header's header */ + if (size < (16 + 16 + 8 + 4 + 4 + 2 + 4)) + goto not_enough_data; + + gst_asf_demux_get_guid (&guid, &data, &size); + stream_type = gst_asf_demux_identify_guid (asf_stream_guids, &guid); + + gst_asf_demux_get_guid (&guid, &data, &size); + correction_type = gst_asf_demux_identify_guid (asf_correction_guids, &guid); + + time_offset = gst_asf_demux_get_uint64 (&data, &size) * 100; + + type_specific_size = gst_asf_demux_get_uint32 (&data, &size); + stream_specific_size = gst_asf_demux_get_uint32 (&data, &size); + + flags = gst_asf_demux_get_uint16 (&data, &size); + stream_id = flags & 0x7f; + is_encrypted = ! !((flags & 0x8000) << 15); + unknown = gst_asf_demux_get_uint32 (&data, &size); + + GST_DEBUG_OBJECT (demux, "Found stream %u, time_offset=%" GST_TIME_FORMAT, + stream_id, GST_TIME_ARGS (time_offset)); + + /* dvr-ms has audio stream declared in stream specific data */ + if (stream_type == ASF_STREAM_EXT_EMBED_HEADER) { + AsfExtStreamType ext_stream_type; + gst_asf_demux_get_guid (&guid, &data, &size); + ext_stream_type = gst_asf_demux_identify_guid (asf_ext_stream_guids, &guid); + + if (ext_stream_type == ASF_EXT_STREAM_AUDIO) { + inspect_payload = TRUE; + + gst_asf_demux_get_guid (&guid, &data, &size); + gst_asf_demux_get_uint32 (&data, &size); + gst_asf_demux_get_uint32 (&data, &size); + gst_asf_demux_get_uint32 (&data, &size); + gst_asf_demux_get_guid (&guid, &data, &size); + gst_asf_demux_get_uint32 (&data, &size); + stream_type = ASF_STREAM_AUDIO; + } + } + + switch (stream_type) { + case ASF_STREAM_AUDIO:{ + asf_stream_audio audio_object; + + if (!gst_asf_demux_get_stream_audio (&audio_object, &data, &size)) + goto not_enough_data; + + GST_INFO ("Object is an audio stream with %u bytes of additional data", + audio_object.size); + + stream = gst_asf_demux_add_audio_stream (demux, &audio_object, stream_id, + &data, &size); + + switch (correction_type) { + case ASF_CORRECTION_ON:{ + guint span, packet_size, chunk_size, data_size, silence_data; + + GST_INFO ("Using error correction"); + + if (size < (1 + 2 + 2 + 2 + 1)) + goto not_enough_data; + + span = gst_asf_demux_get_uint8 (&data, &size); + packet_size = gst_asf_demux_get_uint16 (&data, &size); + chunk_size = gst_asf_demux_get_uint16 (&data, &size); + data_size = gst_asf_demux_get_uint16 (&data, &size); + silence_data = gst_asf_demux_get_uint8 (&data, &size); + + stream->span = span; + + GST_DEBUG_OBJECT (demux, "Descrambling ps:%u cs:%u ds:%u s:%u sd:%u", + packet_size, chunk_size, data_size, span, silence_data); + + if (stream->span > 1) { + if (chunk_size == 0 || ((packet_size / chunk_size) <= 1)) { + /* Disable descrambling */ + stream->span = 0; + } else { + /* FIXME: this else branch was added for + * weird_al_yankovic - the saga begins.asf */ + stream->ds_packet_size = packet_size; + stream->ds_chunk_size = chunk_size; + } + } else { + /* Descambling is enabled */ + stream->ds_packet_size = packet_size; + stream->ds_chunk_size = chunk_size; + } +#if 0 + /* Now skip the rest of the silence data */ + if (data_size > 1) + gst_bytestream_flush (demux->bs, data_size - 1); +#else + /* FIXME: CHECKME. And why -1? */ + if (data_size > 1) { + if (!gst_asf_demux_skip_bytes (data_size - 1, &data, &size)) { + goto not_enough_data; + } + } +#endif + break; + } + case ASF_CORRECTION_OFF:{ + GST_INFO ("Error correction off"); + if (!gst_asf_demux_skip_bytes (stream_specific_size, &data, &size)) + goto not_enough_data; + break; + } + default: + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("Audio stream using unknown error correction")); + return NULL; + } + + break; + } + + case ASF_STREAM_VIDEO:{ + asf_stream_video_format video_format_object; + asf_stream_video video_object; + guint16 vsize; + + if (!gst_asf_demux_get_stream_video (&video_object, &data, &size)) + goto not_enough_data; + + vsize = video_object.size - 40; /* Byte order gets offset by single byte */ + + GST_INFO ("object is a video stream with %u bytes of " + "additional data", vsize); + + if (!gst_asf_demux_get_stream_video_format (&video_format_object, + &data, &size)) { + goto not_enough_data; + } + + stream = gst_asf_demux_add_video_stream (demux, &video_format_object, + stream_id, &data, &size); + + break; + } + + default: + GST_WARNING_OBJECT (demux, "Unknown stream type for stream %u", + stream_id); + demux->other_streams = + g_slist_append (demux->other_streams, GINT_TO_POINTER (stream_id)); + break; + } + + if (stream) + stream->inspect_payload = inspect_payload; + return stream; + +not_enough_data: + { + GST_WARNING_OBJECT (demux, "Unexpected end of data parsing stream object"); + /* we'll error out later if we found no streams */ + return NULL; + } +} + +static const gchar * +gst_asf_demux_get_gst_tag_from_tag_name (const gchar * name_utf8) +{ + const struct + { + const gchar *asf_name; + const gchar *gst_name; + } tags[] = { + { + "WM/Genre", GST_TAG_GENRE}, { + "WM/AlbumTitle", GST_TAG_ALBUM}, { + "WM/AlbumArtist", GST_TAG_ARTIST}, { + "WM/Picture", GST_TAG_IMAGE}, { + "WM/Track", GST_TAG_TRACK_NUMBER}, { + "WM/TrackNumber", GST_TAG_TRACK_NUMBER}, { + "WM/Year", GST_TAG_DATE_TIME} + /* { "WM/Composer", GST_TAG_COMPOSER } */ + }; + gsize out; + guint i; + + if (name_utf8 == NULL) { + GST_WARNING ("Failed to convert name to UTF8, skipping"); + return NULL; + } + + out = strlen (name_utf8); + + for (i = 0; i < G_N_ELEMENTS (tags); ++i) { + if (strncmp (tags[i].asf_name, name_utf8, out) == 0) { + GST_LOG ("map tagname '%s' -> '%s'", name_utf8, tags[i].gst_name); + return tags[i].gst_name; + } + } + + return NULL; +} + +/* gst_asf_demux_add_global_tags() takes ownership of taglist! */ +static void +gst_asf_demux_add_global_tags (GstASFDemux * demux, GstTagList * taglist) +{ + GstTagList *t; + + GST_DEBUG_OBJECT (demux, "adding global tags: %" GST_PTR_FORMAT, taglist); + + if (taglist == NULL) + return; + + if (gst_tag_list_is_empty (taglist)) { + gst_tag_list_unref (taglist); + return; + } + + t = gst_tag_list_merge (demux->taglist, taglist, GST_TAG_MERGE_APPEND); + gst_tag_list_set_scope (t, GST_TAG_SCOPE_GLOBAL); + if (demux->taglist) + gst_tag_list_unref (demux->taglist); + gst_tag_list_unref (taglist); + demux->taglist = t; + GST_LOG_OBJECT (demux, "global tags now: %" GST_PTR_FORMAT, demux->taglist); +} + +#define ASF_DEMUX_DATA_TYPE_UTF16LE_STRING 0 +#define ASF_DEMUX_DATA_TYPE_BYTE_ARRAY 1 +#define ASF_DEMUX_DATA_TYPE_DWORD 3 + +static void +asf_demux_parse_picture_tag (GstTagList * tags, const guint8 * tag_data, + guint tag_data_len) +{ + GstByteReader r; + const guint8 *img_data = NULL; + guint32 img_data_len = 0; + guint8 pic_type = 0; + + gst_byte_reader_init (&r, tag_data, tag_data_len); + + /* skip mime type string (we don't trust it and do our own typefinding), + * and also skip the description string, since we don't use it */ + if (!gst_byte_reader_get_uint8 (&r, &pic_type) || + !gst_byte_reader_get_uint32_le (&r, &img_data_len) || + !gst_byte_reader_skip_string_utf16 (&r) || + !gst_byte_reader_skip_string_utf16 (&r) || + !gst_byte_reader_get_data (&r, img_data_len, &img_data)) { + goto not_enough_data; + } + + + if (!gst_tag_list_add_id3_image (tags, img_data, img_data_len, pic_type)) + GST_DEBUG ("failed to add image extracted from WM/Picture tag to taglist"); + + return; + +not_enough_data: + { + GST_DEBUG ("Failed to read WM/Picture tag: not enough data"); + GST_MEMDUMP ("WM/Picture data", tag_data, tag_data_len); + return; + } +} + +/* Extended Content Description Object */ +static GstFlowReturn +gst_asf_demux_process_ext_content_desc (GstASFDemux * demux, guint8 * data, + guint64 size) +{ + /* Other known (and unused) 'text/unicode' metadata available : + * + * WM/Lyrics = + * WM/MediaPrimaryClassID = {D1607DBC-E323-4BE2-86A1-48A42A28441E} + * WMFSDKVersion = 9.00.00.2980 + * WMFSDKNeeded = 0.0.0.0000 + * WM/UniqueFileIdentifier = AMGa_id=R 15334;AMGp_id=P 5149;AMGt_id=T 2324984 + * WM/Publisher = 4AD + * WM/Provider = AMG + * WM/ProviderRating = 8 + * WM/ProviderStyle = Rock (similar to WM/Genre) + * WM/GenreID (similar to WM/Genre) + * WM/TrackNumber (same as WM/Track but as a string) + * + * Other known (and unused) 'non-text' metadata available : + * + * WM/EncodingTime + * WM/MCDI + * IsVBR + * + * We might want to read WM/TrackNumber and use atoi() if we don't have + * WM/Track + */ + + GstTagList *taglist; + guint16 blockcount, i; + + GST_INFO_OBJECT (demux, "object is an extended content description"); + + taglist = gst_tag_list_new_empty (); + + /* Content Descriptor Count */ + if (size < 2) + goto not_enough_data; + + blockcount = gst_asf_demux_get_uint16 (&data, &size); + + for (i = 1; i <= blockcount; ++i) { + const gchar *gst_tag_name; + guint16 datatype; + guint16 value_len; + guint16 name_len; + GValue tag_value = { 0, }; + gsize in, out; + gchar *name; + gchar *name_utf8 = NULL; + gchar *value; + + /* Descriptor */ + if (!gst_asf_demux_get_string (&name, &name_len, &data, &size)) + goto not_enough_data; + + if (size < 2) { + g_free (name); + goto not_enough_data; + } + /* Descriptor Value Data Type */ + datatype = gst_asf_demux_get_uint16 (&data, &size); + + /* Descriptor Value (not really a string, but same thing reading-wise) */ + if (!gst_asf_demux_get_string (&value, &value_len, &data, &size)) { + g_free (name); + goto not_enough_data; + } + + name_utf8 = + g_convert (name, name_len, "UTF-8", "UTF-16LE", &in, &out, NULL); + + if (name_utf8 != NULL) { + GST_DEBUG ("Found tag/metadata %s", name_utf8); + + gst_tag_name = gst_asf_demux_get_gst_tag_from_tag_name (name_utf8); + GST_DEBUG ("gst_tag_name %s", GST_STR_NULL (gst_tag_name)); + + switch (datatype) { + case ASF_DEMUX_DATA_TYPE_UTF16LE_STRING:{ + gchar *value_utf8; + + value_utf8 = g_convert (value, value_len, "UTF-8", "UTF-16LE", + &in, &out, NULL); + + /* get rid of tags with empty value */ + if (value_utf8 != NULL && *value_utf8 != '\0') { + GST_DEBUG ("string value %s", value_utf8); + + value_utf8[out] = '\0'; + + if (gst_tag_name != NULL) { + if (strcmp (gst_tag_name, GST_TAG_DATE_TIME) == 0) { + guint year = atoi (value_utf8); + + if (year > 0) { + g_value_init (&tag_value, GST_TYPE_DATE_TIME); + g_value_take_boxed (&tag_value, gst_date_time_new_y (year)); + } + } else if (strcmp (gst_tag_name, GST_TAG_GENRE) == 0) { + guint id3v1_genre_id; + const gchar *genre_str; + + if (sscanf (value_utf8, "(%u)", &id3v1_genre_id) == 1 && + ((genre_str = gst_tag_id3_genre_get (id3v1_genre_id)))) { + GST_DEBUG ("Genre: %s -> %s", value_utf8, genre_str); + g_free (value_utf8); + value_utf8 = g_strdup (genre_str); + } + } else { + GType tag_type; + + /* convert tag from string to other type if required */ + tag_type = gst_tag_get_type (gst_tag_name); + g_value_init (&tag_value, tag_type); + if (!gst_value_deserialize (&tag_value, value_utf8)) { + GValue from_val = { 0, }; + + g_value_init (&from_val, G_TYPE_STRING); + g_value_set_string (&from_val, value_utf8); + if (!g_value_transform (&from_val, &tag_value)) { + GST_WARNING_OBJECT (demux, + "Could not transform string tag to " "%s tag type %s", + gst_tag_name, g_type_name (tag_type)); + g_value_unset (&tag_value); + } + g_value_unset (&from_val); + } + } + } else { + /* metadata ! */ + GST_DEBUG ("Setting metadata"); + g_value_init (&tag_value, G_TYPE_STRING); + g_value_set_string (&tag_value, value_utf8); + } + } else if (value_utf8 == NULL) { + GST_WARNING ("Failed to convert string value to UTF8, skipping"); + } else { + GST_DEBUG ("Skipping empty string value for %s", + GST_STR_NULL (gst_tag_name)); + } + g_free (value_utf8); + break; + } + case ASF_DEMUX_DATA_TYPE_BYTE_ARRAY:{ + if (gst_tag_name) { + if (!g_str_equal (gst_tag_name, GST_TAG_IMAGE)) { + GST_FIXME ("Unhandled byte array tag %s", + GST_STR_NULL (gst_tag_name)); + break; + } else { + asf_demux_parse_picture_tag (taglist, (guint8 *) value, + value_len); + } + } + break; + } + case ASF_DEMUX_DATA_TYPE_DWORD:{ + guint uint_val = GST_READ_UINT32_LE (value); + + /* this is the track number */ + g_value_init (&tag_value, G_TYPE_UINT); + + /* WM/Track counts from 0 */ + if (!strcmp (name_utf8, "WM/Track")) + ++uint_val; + + g_value_set_uint (&tag_value, uint_val); + break; + } + default:{ + GST_DEBUG ("Skipping tag %s of type %d", gst_tag_name, datatype); + break; + } + } + + if (G_IS_VALUE (&tag_value)) { + if (gst_tag_name) { + GstTagMergeMode merge_mode = GST_TAG_MERGE_APPEND; + + /* WM/TrackNumber is more reliable than WM/Track, since the latter + * is supposed to have a 0 base but is often wrongly written to start + * from 1 as well, so prefer WM/TrackNumber when we have it: either + * replace the value added earlier from WM/Track or put it first in + * the list, so that it will get picked up by _get_uint() */ + if (strcmp (name_utf8, "WM/TrackNumber") == 0) + merge_mode = GST_TAG_MERGE_REPLACE; + + gst_tag_list_add_values (taglist, merge_mode, gst_tag_name, + &tag_value, NULL); + } else { + GST_DEBUG ("Setting global metadata %s", name_utf8); + gst_structure_set_value (demux->global_metadata, name_utf8, + &tag_value); + } + + g_value_unset (&tag_value); + } + } + + g_free (name); + g_free (value); + g_free (name_utf8); + } + + gst_asf_demux_add_global_tags (demux, taglist); + + return GST_FLOW_OK; + + /* Errors */ +not_enough_data: + { + GST_WARNING ("Unexpected end of data parsing ext content desc object"); + gst_tag_list_unref (taglist); + return GST_FLOW_OK; /* not really fatal */ + } +} + +static GstStructure * +gst_asf_demux_get_metadata_for_stream (GstASFDemux * demux, guint stream_num) +{ + gchar sname[32]; + guint i; + + g_snprintf (sname, sizeof (sname), "stream-%u", stream_num); + + for (i = 0; i < gst_caps_get_size (demux->metadata); ++i) { + GstStructure *s; + + s = gst_caps_get_structure (demux->metadata, i); + if (gst_structure_has_name (s, sname)) + return s; + } + + gst_caps_append_structure (demux->metadata, gst_structure_new_empty (sname)); + + /* try lookup again; demux->metadata took ownership of the structure, so we + * can't really make any assumptions about what happened to it, so we can't + * just return it directly after appending it */ + return gst_asf_demux_get_metadata_for_stream (demux, stream_num); +} + +static GstFlowReturn +gst_asf_demux_process_metadata (GstASFDemux * demux, guint8 * data, + guint64 size) +{ + guint16 blockcount, i; + + GST_INFO_OBJECT (demux, "object is a metadata object"); + + /* Content Descriptor Count */ + if (size < 2) + goto not_enough_data; + + blockcount = gst_asf_demux_get_uint16 (&data, &size); + + for (i = 0; i < blockcount; ++i) { + GstStructure *s; + guint16 stream_num, name_len, data_type, lang_idx G_GNUC_UNUSED; + guint32 data_len, ival; + gchar *name_utf8; + + if (size < (2 + 2 + 2 + 2 + 4)) + goto not_enough_data; + + lang_idx = gst_asf_demux_get_uint16 (&data, &size); + stream_num = gst_asf_demux_get_uint16 (&data, &size); + name_len = gst_asf_demux_get_uint16 (&data, &size); + data_type = gst_asf_demux_get_uint16 (&data, &size); + data_len = gst_asf_demux_get_uint32 (&data, &size); + + if (size < name_len + data_len) + goto not_enough_data; + + /* convert name to UTF-8 */ + name_utf8 = g_convert ((gchar *) data, name_len, "UTF-8", "UTF-16LE", + NULL, NULL, NULL); + gst_asf_demux_skip_bytes (name_len, &data, &size); + + if (name_utf8 == NULL) { + GST_WARNING ("Failed to convert value name to UTF8, skipping"); + gst_asf_demux_skip_bytes (data_len, &data, &size); + continue; + } + + if (data_type != ASF_DEMUX_DATA_TYPE_DWORD) { + gst_asf_demux_skip_bytes (data_len, &data, &size); + g_free (name_utf8); + continue; + } + + /* read DWORD */ + if (size < 4) { + g_free (name_utf8); + goto not_enough_data; + } + + ival = gst_asf_demux_get_uint32 (&data, &size); + + /* skip anything else there may be, just in case */ + gst_asf_demux_skip_bytes (data_len - 4, &data, &size); + + s = gst_asf_demux_get_metadata_for_stream (demux, stream_num); + gst_structure_set (s, name_utf8, G_TYPE_INT, ival, NULL); + g_free (name_utf8); + } + + GST_INFO_OBJECT (demux, "metadata = %" GST_PTR_FORMAT, demux->metadata); + return GST_FLOW_OK; + + /* Errors */ +not_enough_data: + { + GST_WARNING ("Unexpected end of data parsing metadata object"); + return GST_FLOW_OK; /* not really fatal */ + } +} + +static GstFlowReturn +gst_asf_demux_process_header (GstASFDemux * demux, guint8 * data, guint64 size) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 i, num_objects; + guint8 unknown G_GNUC_UNUSED; + + /* Get the rest of the header's header */ + if (size < (4 + 1 + 1)) + goto not_enough_data; + + num_objects = gst_asf_demux_get_uint32 (&data, &size); + unknown = gst_asf_demux_get_uint8 (&data, &size); + unknown = gst_asf_demux_get_uint8 (&data, &size); + + GST_INFO_OBJECT (demux, "object is a header with %u parts", num_objects); + + /* Loop through the header's objects, processing those */ + for (i = 0; i < num_objects; ++i) { + GST_INFO_OBJECT (demux, "reading header part %u", i); + ret = gst_asf_demux_process_object (demux, &data, &size); + if (ret != GST_FLOW_OK) { + GST_WARNING ("process_object returned %s", gst_asf_get_flow_name (ret)); + break; + } + } + + return ret; + +not_enough_data: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("short read parsing HEADER object")); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_asf_demux_process_file (GstASFDemux * demux, guint8 * data, guint64 size) +{ + guint64 creation_time G_GNUC_UNUSED; + guint64 file_size G_GNUC_UNUSED; + guint64 send_time G_GNUC_UNUSED; + guint64 packets_count, play_time, preroll; + guint32 flags, min_pktsize, max_pktsize, min_bitrate G_GNUC_UNUSED; + + if (size < (16 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4 + 4 + 4)) + goto not_enough_data; + + gst_asf_demux_skip_bytes (16, &data, &size); /* skip GUID */ + file_size = gst_asf_demux_get_uint64 (&data, &size); + creation_time = gst_asf_demux_get_uint64 (&data, &size); + packets_count = gst_asf_demux_get_uint64 (&data, &size); + play_time = gst_asf_demux_get_uint64 (&data, &size); + send_time = gst_asf_demux_get_uint64 (&data, &size); + preroll = gst_asf_demux_get_uint64 (&data, &size); + flags = gst_asf_demux_get_uint32 (&data, &size); + min_pktsize = gst_asf_demux_get_uint32 (&data, &size); + max_pktsize = gst_asf_demux_get_uint32 (&data, &size); + min_bitrate = gst_asf_demux_get_uint32 (&data, &size); + + demux->broadcast = ! !(flags & 0x01); + demux->seekable = ! !(flags & 0x02); + + GST_DEBUG_OBJECT (demux, "min_pktsize = %u", min_pktsize); + GST_DEBUG_OBJECT (demux, "flags::broadcast = %d", demux->broadcast); + GST_DEBUG_OBJECT (demux, "flags::seekable = %d", demux->seekable); + + if (demux->broadcast) { + /* these fields are invalid if the broadcast flag is set */ + play_time = 0; + file_size = 0; + } + + if (min_pktsize != max_pktsize) + goto non_fixed_packet_size; + + demux->packet_size = max_pktsize; + + /* FIXME: do we need send_time as well? what is it? */ + if ((play_time * 100) >= (preroll * GST_MSECOND)) + demux->play_time = (play_time * 100) - (preroll * GST_MSECOND); + else + demux->play_time = 0; + + demux->preroll = preroll * GST_MSECOND; + + /* initial latency */ + demux->latency = demux->preroll; + + if (demux->play_time == 0) + demux->seekable = FALSE; + + GST_DEBUG_OBJECT (demux, "play_time %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->play_time)); + GST_DEBUG_OBJECT (demux, "preroll %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->preroll)); + + if (demux->play_time > 0) { + demux->segment.duration = demux->play_time; + } + + GST_INFO ("object is a file with %" G_GUINT64_FORMAT " data packets", + packets_count); + GST_INFO ("preroll = %" G_GUINT64_FORMAT, demux->preroll); + + return GST_FLOW_OK; + +/* ERRORS */ +non_fixed_packet_size: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("packet size must be fixed")); + return GST_FLOW_ERROR; + } +not_enough_data: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("short read parsing FILE object")); + return GST_FLOW_ERROR; + } +} + +/* Content Description Object */ +static GstFlowReturn +gst_asf_demux_process_comment (GstASFDemux * demux, guint8 * data, guint64 size) +{ + struct + { + const gchar *gst_tag; + guint16 val_length; + gchar *val_utf8; + } tags[5] = { + { + GST_TAG_TITLE, 0, NULL}, { + GST_TAG_ARTIST, 0, NULL}, { + GST_TAG_COPYRIGHT, 0, NULL}, { + GST_TAG_DESCRIPTION, 0, NULL}, { + GST_TAG_COMMENT, 0, NULL} + }; + GstTagList *taglist; + GValue value = { 0 }; + gsize in, out; + gint i = -1; + + GST_INFO_OBJECT (demux, "object is a comment"); + + if (size < (2 + 2 + 2 + 2 + 2)) + goto not_enough_data; + + tags[0].val_length = gst_asf_demux_get_uint16 (&data, &size); + tags[1].val_length = gst_asf_demux_get_uint16 (&data, &size); + tags[2].val_length = gst_asf_demux_get_uint16 (&data, &size); + tags[3].val_length = gst_asf_demux_get_uint16 (&data, &size); + tags[4].val_length = gst_asf_demux_get_uint16 (&data, &size); + + GST_DEBUG_OBJECT (demux, "Comment lengths: title=%d author=%d copyright=%d " + "description=%d rating=%d", tags[0].val_length, tags[1].val_length, + tags[2].val_length, tags[3].val_length, tags[4].val_length); + + for (i = 0; i < G_N_ELEMENTS (tags); ++i) { + if (size < tags[i].val_length) + goto not_enough_data; + + /* might be just '/0', '/0'... */ + if (tags[i].val_length > 2 && tags[i].val_length % 2 == 0) { + /* convert to UTF-8 */ + tags[i].val_utf8 = g_convert ((gchar *) data, tags[i].val_length, + "UTF-8", "UTF-16LE", &in, &out, NULL); + } + gst_asf_demux_skip_bytes (tags[i].val_length, &data, &size); + } + + /* parse metadata into taglist */ + taglist = gst_tag_list_new_empty (); + g_value_init (&value, G_TYPE_STRING); + for (i = 0; i < G_N_ELEMENTS (tags); ++i) { + if (tags[i].val_utf8 && strlen (tags[i].val_utf8) > 0 && tags[i].gst_tag) { + g_value_set_string (&value, tags[i].val_utf8); + gst_tag_list_add_values (taglist, GST_TAG_MERGE_APPEND, + tags[i].gst_tag, &value, NULL); + } + } + g_value_unset (&value); + + gst_asf_demux_add_global_tags (demux, taglist); + + for (i = 0; i < G_N_ELEMENTS (tags); ++i) + g_free (tags[i].val_utf8); + + return GST_FLOW_OK; + +not_enough_data: + { + GST_WARNING_OBJECT (demux, "unexpectedly short of data while processing " + "comment tag section %d, skipping comment object", i); + for (i = 0; i < G_N_ELEMENTS (tags); i++) + g_free (tags[i].val_utf8); + return GST_FLOW_OK; /* not really fatal */ + } +} + +static GstFlowReturn +gst_asf_demux_process_bitrate_props_object (GstASFDemux * demux, guint8 * data, + guint64 size) +{ + guint16 num_streams, i; + AsfStream *stream; + + if (size < 2) + goto not_enough_data; + + num_streams = gst_asf_demux_get_uint16 (&data, &size); + + GST_INFO ("object is a bitrate properties object with %u streams", + num_streams); + + if (size < (num_streams * (2 + 4))) + goto not_enough_data; + + for (i = 0; i < num_streams; ++i) { + guint32 bitrate; + guint16 stream_id; + + stream_id = gst_asf_demux_get_uint16 (&data, &size); + bitrate = gst_asf_demux_get_uint32 (&data, &size); + + if (stream_id < GST_ASF_DEMUX_NUM_STREAM_IDS) { + GST_DEBUG_OBJECT (demux, "bitrate of stream %u = %u", stream_id, bitrate); + stream = gst_asf_demux_get_stream (demux, stream_id); + if (stream) { + if (stream->pending_tags == NULL) { + stream->pending_tags = + gst_tag_list_new (GST_TAG_BITRATE, bitrate, NULL); + } + } else { + GST_WARNING_OBJECT (demux, "Stream id %u wasn't found", stream_id); + } + } else { + GST_WARNING ("stream id %u is too large", stream_id); + } + } + + return GST_FLOW_OK; + +not_enough_data: + { + GST_WARNING_OBJECT (demux, "short read parsing bitrate props object!"); + return GST_FLOW_OK; /* not really fatal */ + } +} + +static GstFlowReturn +gst_asf_demux_process_header_ext (GstASFDemux * demux, guint8 * data, + guint64 size) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint64 hdr_size; + + /* Get the rest of the header's header */ + if (size < (16 + 2 + 4)) + goto not_enough_data; + + /* skip GUID and two other bytes */ + gst_asf_demux_skip_bytes (16 + 2, &data, &size); + hdr_size = gst_asf_demux_get_uint32 (&data, &size); + + GST_INFO ("extended header object with a size of %u bytes", (guint) size); + + /* FIXME: does data_size include the rest of the header that we have read? */ + if (hdr_size > size) + goto not_enough_data; + + while (hdr_size > 0) { + ret = gst_asf_demux_process_object (demux, &data, &hdr_size); + if (ret != GST_FLOW_OK) + break; + } + + return ret; + +not_enough_data: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("short read parsing extended header object")); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_asf_demux_process_language_list (GstASFDemux * demux, guint8 * data, + guint64 size) +{ + guint i; + + if (size < 2) + goto not_enough_data; + + if (demux->languages) { + GST_WARNING ("More than one LANGUAGE_LIST object in stream"); + g_strfreev (demux->languages); + demux->languages = NULL; + demux->num_languages = 0; + } + + demux->num_languages = gst_asf_demux_get_uint16 (&data, &size); + GST_LOG ("%u languages:", demux->num_languages); + + demux->languages = g_new0 (gchar *, demux->num_languages + 1); + for (i = 0; i < demux->num_languages; ++i) { + guint8 len, *lang_data = NULL; + + if (size < 1) + goto not_enough_data; + len = gst_asf_demux_get_uint8 (&data, &size); + if (gst_asf_demux_get_bytes (&lang_data, len, &data, &size)) { + gchar *utf8; + + utf8 = g_convert ((gchar *) lang_data, len, "UTF-8", "UTF-16LE", NULL, + NULL, NULL); + + /* truncate "en-us" etc. to just "en" */ + if (utf8 && strlen (utf8) >= 5 && (utf8[2] == '-' || utf8[2] == '_')) { + utf8[2] = '\0'; + } + GST_DEBUG ("[%u] %s", i, GST_STR_NULL (utf8)); + demux->languages[i] = utf8; + g_free (lang_data); + } else { + goto not_enough_data; + } + } + + return GST_FLOW_OK; + +not_enough_data: + { + GST_WARNING_OBJECT (demux, "short read parsing language list object!"); + g_free (demux->languages); + demux->languages = NULL; + return GST_FLOW_OK; /* not fatal */ + } +} + +static GstFlowReturn +gst_asf_demux_process_simple_index (GstASFDemux * demux, guint8 * data, + guint64 size) +{ + GstClockTime interval; + guint32 count, i; + + if (size < (16 + 8 + 4 + 4)) + goto not_enough_data; + + /* skip file id */ + gst_asf_demux_skip_bytes (16, &data, &size); + interval = gst_asf_demux_get_uint64 (&data, &size) * (GstClockTime) 100; + gst_asf_demux_skip_bytes (4, &data, &size); + count = gst_asf_demux_get_uint32 (&data, &size); + if (count > 0) { + demux->sidx_interval = interval; + demux->sidx_num_entries = count; + g_free (demux->sidx_entries); + demux->sidx_entries = g_new0 (AsfSimpleIndexEntry, count); + + for (i = 0; i < count; ++i) { + if (G_UNLIKELY (size < 6)) { + /* adjust for broken files, to avoid having entries at the end + * of the parsed index that point to time=0. Resulting in seeking to + * the end of the file leading back to the beginning */ + demux->sidx_num_entries -= (count - i); + break; + } + demux->sidx_entries[i].packet = gst_asf_demux_get_uint32 (&data, &size); + demux->sidx_entries[i].count = gst_asf_demux_get_uint16 (&data, &size); + GST_LOG_OBJECT (demux, "%" GST_TIME_FORMAT " = packet %4u count : %2d", + GST_TIME_ARGS (i * interval), demux->sidx_entries[i].packet, + demux->sidx_entries[i].count); + } + } else { + GST_DEBUG_OBJECT (demux, "simple index object with 0 entries"); + } + + return GST_FLOW_OK; + +not_enough_data: + { + GST_WARNING_OBJECT (demux, "short read parsing simple index object!"); + return GST_FLOW_OK; /* not fatal */ + } +} + +static GstFlowReturn +gst_asf_demux_process_advanced_mutual_exclusion (GstASFDemux * demux, + guint8 * data, guint64 size) +{ + ASFGuid guid; + guint16 num, i; + guint8 *mes; + + if (size < 16 + 2 + (2 * 2)) + goto not_enough_data; + + gst_asf_demux_get_guid (&guid, &data, &size); + num = gst_asf_demux_get_uint16 (&data, &size); + + if (num < 2) { + GST_WARNING_OBJECT (demux, "nonsensical mutually exclusive streams count"); + return GST_FLOW_OK; + } + + if (size < (num * sizeof (guint16))) + goto not_enough_data; + + /* read mutually exclusive stream numbers */ + mes = g_new (guint8, num + 1); + for (i = 0; i < num; ++i) { + mes[i] = gst_asf_demux_get_uint16 (&data, &size) & 0x7f; + GST_LOG_OBJECT (demux, "mutually exclusive: stream #%d", mes[i]); + } + + /* add terminator so we can easily get the count or know when to stop */ + mes[i] = (guint8) - 1; + + demux->mut_ex_streams = g_slist_append (demux->mut_ex_streams, mes); + + return GST_FLOW_OK; + + /* Errors */ +not_enough_data: + { + GST_WARNING_OBJECT (demux, "short read parsing advanced mutual exclusion"); + return GST_FLOW_OK; /* not absolutely fatal */ + } +} + +gboolean +gst_asf_demux_is_unknown_stream (GstASFDemux * demux, guint stream_num) +{ + return g_slist_find (demux->other_streams, + GINT_TO_POINTER (stream_num)) == NULL; +} + +static GstFlowReturn +gst_asf_demux_process_ext_stream_props (GstASFDemux * demux, guint8 * data, + guint64 size) +{ + AsfStreamExtProps esp; + AsfStream *stream = NULL; + AsfObject stream_obj; + guint16 stream_name_count; + guint16 num_payload_ext; + guint64 len; + guint8 *stream_obj_data = NULL; + guint8 *data_start; + guint obj_size; + guint i, stream_num; + + data_start = data; + obj_size = (guint) size; + + if (size < 64) + goto not_enough_data; + + esp.valid = TRUE; + esp.start_time = gst_asf_demux_get_uint64 (&data, &size) * GST_MSECOND; + esp.end_time = gst_asf_demux_get_uint64 (&data, &size) * GST_MSECOND; + esp.data_bitrate = gst_asf_demux_get_uint32 (&data, &size); + esp.buffer_size = gst_asf_demux_get_uint32 (&data, &size); + esp.intial_buf_fullness = gst_asf_demux_get_uint32 (&data, &size); + esp.data_bitrate2 = gst_asf_demux_get_uint32 (&data, &size); + esp.buffer_size2 = gst_asf_demux_get_uint32 (&data, &size); + esp.intial_buf_fullness2 = gst_asf_demux_get_uint32 (&data, &size); + esp.max_obj_size = gst_asf_demux_get_uint32 (&data, &size); + esp.flags = gst_asf_demux_get_uint32 (&data, &size); + stream_num = gst_asf_demux_get_uint16 (&data, &size); + esp.lang_idx = gst_asf_demux_get_uint16 (&data, &size); + esp.avg_time_per_frame = gst_asf_demux_get_uint64 (&data, &size); + stream_name_count = gst_asf_demux_get_uint16 (&data, &size); + num_payload_ext = gst_asf_demux_get_uint16 (&data, &size); + + GST_INFO ("start_time = %" GST_TIME_FORMAT, + GST_TIME_ARGS (esp.start_time)); + GST_INFO ("end_time = %" GST_TIME_FORMAT, + GST_TIME_ARGS (esp.end_time)); + GST_INFO ("flags = %08x", esp.flags); + GST_INFO ("average time per frame = %" GST_TIME_FORMAT, + GST_TIME_ARGS (esp.avg_time_per_frame * 100)); + GST_INFO ("stream number = %u", stream_num); + GST_INFO ("stream language ID idx = %u (%s)", esp.lang_idx, + (esp.lang_idx < demux->num_languages) ? + GST_STR_NULL (demux->languages[esp.lang_idx]) : "??"); + GST_INFO ("stream name count = %u", stream_name_count); + + /* read stream names */ + for (i = 0; i < stream_name_count; ++i) { + guint16 stream_lang_idx G_GNUC_UNUSED; + gchar *stream_name = NULL; + + if (size < 2) + goto not_enough_data; + stream_lang_idx = gst_asf_demux_get_uint16 (&data, &size); + if (!gst_asf_demux_get_string (&stream_name, NULL, &data, &size)) + goto not_enough_data; + GST_INFO ("stream name %d: %s", i, GST_STR_NULL (stream_name)); + g_free (stream_name); /* TODO: store names in struct */ + } + + /* read payload extension systems stuff */ + GST_LOG ("payload extension systems count = %u", num_payload_ext); + + if (num_payload_ext > 0) + esp.payload_extensions = g_new0 (AsfPayloadExtension, num_payload_ext + 1); + else + esp.payload_extensions = NULL; + + for (i = 0; i < num_payload_ext; ++i) { + AsfPayloadExtension ext; + ASFGuid ext_guid; + guint32 sys_info_len; + + if (size < 16 + 2 + 4) + goto not_enough_data; + + gst_asf_demux_get_guid (&ext_guid, &data, &size); + ext.id = gst_asf_demux_identify_guid (asf_payload_ext_guids, &ext_guid); + ext.len = gst_asf_demux_get_uint16 (&data, &size); + + sys_info_len = gst_asf_demux_get_uint32 (&data, &size); + GST_LOG ("payload systems info len = %u", sys_info_len); + if (!gst_asf_demux_skip_bytes (sys_info_len, &data, &size)) + goto not_enough_data; + + esp.payload_extensions[i] = ext; + } + + GST_LOG ("bytes read: %u/%u", (guint) (data - data_start), obj_size); + + /* there might be an optional STREAM_INFO object here now; if not, we + * should have parsed the corresponding stream info object already (since + * we are parsing the extended stream properties objects delayed) */ + if (size == 0) { + stream = gst_asf_demux_get_stream (demux, stream_num); + goto done; + } + + /* get size of the stream object */ + if (!asf_demux_peek_object (demux, data, size, &stream_obj, TRUE)) + goto not_enough_data; + + if (stream_obj.id != ASF_OBJ_STREAM) + goto expected_stream_object; + + if (stream_obj.size < ASF_OBJECT_HEADER_SIZE || + stream_obj.size > (10 * 1024 * 1024)) + goto not_enough_data; + + gst_asf_demux_skip_bytes (ASF_OBJECT_HEADER_SIZE, &data, &size); + + /* process this stream object later after all the other 'normal' ones + * have been processed (since the others are more important/non-hidden) */ + len = stream_obj.size - ASF_OBJECT_HEADER_SIZE; + if (!gst_asf_demux_get_bytes (&stream_obj_data, len, &data, &size)) + goto not_enough_data; + + /* parse stream object */ + stream = gst_asf_demux_parse_stream_object (demux, stream_obj_data, len); + g_free (stream_obj_data); + +done: + + if (stream) { + stream->ext_props = esp; + + /* try to set the framerate */ + if (stream->is_video && stream->caps) { + GValue framerate = { 0 }; + GstStructure *s; + gint num, denom; + + g_value_init (&framerate, GST_TYPE_FRACTION); + + num = GST_SECOND / 100; + denom = esp.avg_time_per_frame; + if (denom == 0) { + /* avoid division by 0, assume 25/1 framerate */ + denom = GST_SECOND / 2500; + } + + gst_value_set_fraction (&framerate, num, denom); + + stream->caps = gst_caps_make_writable (stream->caps); + s = gst_caps_get_structure (stream->caps, 0); + gst_structure_set_value (s, "framerate", &framerate); + g_value_unset (&framerate); + GST_DEBUG_OBJECT (demux, "setting framerate of %d/%d = %f", + num, denom, ((gdouble) num) / denom); + } + + /* add language info now if we have it */ + if (stream->ext_props.lang_idx < demux->num_languages) { + if (stream->pending_tags == NULL) + stream->pending_tags = gst_tag_list_new_empty (); + GST_LOG_OBJECT (demux, "stream %u has language '%s'", stream->id, + demux->languages[stream->ext_props.lang_idx]); + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_APPEND, + GST_TAG_LANGUAGE_CODE, demux->languages[stream->ext_props.lang_idx], + NULL); + } + } else if (gst_asf_demux_is_unknown_stream (demux, stream_num)) { + GST_WARNING_OBJECT (demux, "Ext. stream properties for unknown stream"); + } + + return GST_FLOW_OK; + + /* Errors */ +not_enough_data: + { + GST_WARNING_OBJECT (demux, "short read parsing ext stream props object!"); + return GST_FLOW_OK; /* not absolutely fatal */ + } +expected_stream_object: + { + GST_WARNING_OBJECT (demux, "error parsing extended stream properties " + "object: expected embedded stream object, but got %s object instead!", + gst_asf_get_guid_nick (asf_object_guids, stream_obj.id)); + return GST_FLOW_OK; /* not absolutely fatal */ + } +} + +static const gchar * +gst_asf_demux_push_obj (GstASFDemux * demux, guint32 obj_id) +{ + const gchar *nick; + + nick = gst_asf_get_guid_nick (asf_object_guids, obj_id); + if (g_str_has_prefix (nick, "ASF_OBJ_")) + nick += strlen ("ASF_OBJ_"); + + if (demux->objpath == NULL) { + demux->objpath = g_strdup (nick); + } else { + gchar *newpath; + + newpath = g_strdup_printf ("%s/%s", demux->objpath, nick); + g_free (demux->objpath); + demux->objpath = newpath; + } + + return (const gchar *) demux->objpath; +} + +static void +gst_asf_demux_pop_obj (GstASFDemux * demux) +{ + gchar *s; + + if ((s = g_strrstr (demux->objpath, "/"))) { + *s = '\0'; + } else { + g_free (demux->objpath); + demux->objpath = NULL; + } +} + +static void +gst_asf_demux_process_queued_extended_stream_objects (GstASFDemux * demux) +{ + GSList *l; + guint i; + + /* Parse the queued extended stream property objects and add the info + * to the existing streams or add the new embedded streams, but without + * activating them yet */ + GST_LOG_OBJECT (demux, "%u queued extended stream properties objects", + g_slist_length (demux->ext_stream_props)); + + for (l = demux->ext_stream_props, i = 0; l != NULL; l = l->next, ++i) { + GstBuffer *buf = GST_BUFFER (l->data); + GstMapInfo map; + + gst_buffer_map (buf, &map, GST_MAP_READ); + + GST_LOG_OBJECT (demux, "parsing ext. stream properties object #%u", i); + gst_asf_demux_process_ext_stream_props (demux, map.data, map.size); + gst_buffer_unmap (buf, &map); + gst_buffer_unref (buf); + } + g_slist_free (demux->ext_stream_props); + demux->ext_stream_props = NULL; +} + +#if 0 +static void +gst_asf_demux_activate_ext_props_streams (GstASFDemux * demux) +{ + guint i, j; + + for (i = 0; i < demux->num_streams; ++i) { + AsfStream *stream; + gboolean is_hidden; + GSList *x; + + stream = &demux->stream[i]; + + GST_LOG_OBJECT (demux, "checking stream %2u", stream->id); + + if (stream->active) { + GST_LOG_OBJECT (demux, "stream %2u is already activated", stream->id); + continue; + } + + is_hidden = FALSE; + for (x = demux->mut_ex_streams; x != NULL; x = x->next) { + guint8 *mes; + + /* check for each mutual exclusion whether it affects this stream */ + for (mes = (guint8 *) x->data; mes != NULL && *mes != 0xff; ++mes) { + if (*mes == stream->id) { + /* if yes, check if we've already added streams that are mutually + * exclusive with the stream we're about to add */ + for (mes = (guint8 *) x->data; mes != NULL && *mes != 0xff; ++mes) { + for (j = 0; j < demux->num_streams; ++j) { + /* if the broadcast flag is set, assume the hidden streams aren't + * actually streamed and hide them (or playbin won't work right), + * otherwise assume their data is available */ + if (demux->stream[j].id == *mes && demux->broadcast) { + is_hidden = TRUE; + GST_LOG_OBJECT (demux, "broadcast stream ID %d to be added is " + "mutually exclusive with already existing stream ID %d, " + "hiding stream", stream->id, demux->stream[j].id); + goto next; + } + } + } + break; + } + } + } + + next: + + /* FIXME: we should do stream activation based on preroll data in + * streaming mode too */ + if (demux->streaming && !is_hidden) + gst_asf_demux_activate_stream (demux, stream); + } +} +#endif + +static GstFlowReturn +gst_asf_demux_process_object (GstASFDemux * demux, guint8 ** p_data, + guint64 * p_size) +{ + GstFlowReturn ret = GST_FLOW_OK; + AsfObject obj; + guint64 obj_data_size; + + if (*p_size < ASF_OBJECT_HEADER_SIZE) + return ASF_FLOW_NEED_MORE_DATA; + + asf_demux_peek_object (demux, *p_data, ASF_OBJECT_HEADER_SIZE, &obj, TRUE); + gst_asf_demux_skip_bytes (ASF_OBJECT_HEADER_SIZE, p_data, p_size); + + obj_data_size = obj.size - ASF_OBJECT_HEADER_SIZE; + + if (*p_size < obj_data_size) + return ASF_FLOW_NEED_MORE_DATA; + + gst_asf_demux_push_obj (demux, obj.id); + + GST_INFO ("%s: size %" G_GUINT64_FORMAT, demux->objpath, obj.size); + + switch (obj.id) { + case ASF_OBJ_STREAM: + gst_asf_demux_parse_stream_object (demux, *p_data, obj_data_size); + ret = GST_FLOW_OK; + break; + case ASF_OBJ_FILE: + ret = gst_asf_demux_process_file (demux, *p_data, obj_data_size); + break; + case ASF_OBJ_HEADER: + ret = gst_asf_demux_process_header (demux, *p_data, obj_data_size); + break; + case ASF_OBJ_COMMENT: + ret = gst_asf_demux_process_comment (demux, *p_data, obj_data_size); + break; + case ASF_OBJ_HEAD1: + ret = gst_asf_demux_process_header_ext (demux, *p_data, obj_data_size); + break; + case ASF_OBJ_BITRATE_PROPS: + ret = + gst_asf_demux_process_bitrate_props_object (demux, *p_data, + obj_data_size); + break; + case ASF_OBJ_EXT_CONTENT_DESC: + ret = + gst_asf_demux_process_ext_content_desc (demux, *p_data, + obj_data_size); + break; + case ASF_OBJ_METADATA_OBJECT: + ret = gst_asf_demux_process_metadata (demux, *p_data, obj_data_size); + break; + case ASF_OBJ_EXTENDED_STREAM_PROPS:{ + GstBuffer *buf; + + /* process these later, we might not have parsed the corresponding + * stream object yet */ + GST_LOG ("%s: queued for later parsing", demux->objpath); + buf = gst_buffer_new_and_alloc (obj_data_size); + gst_buffer_fill (buf, 0, *p_data, obj_data_size); + demux->ext_stream_props = g_slist_append (demux->ext_stream_props, buf); + ret = GST_FLOW_OK; + break; + } + case ASF_OBJ_LANGUAGE_LIST: + ret = gst_asf_demux_process_language_list (demux, *p_data, obj_data_size); + break; + case ASF_OBJ_ADVANCED_MUTUAL_EXCLUSION: + ret = gst_asf_demux_process_advanced_mutual_exclusion (demux, *p_data, + obj_data_size); + break; + case ASF_OBJ_SIMPLE_INDEX: + ret = gst_asf_demux_process_simple_index (demux, *p_data, obj_data_size); + break; + case ASF_OBJ_CONTENT_ENCRYPTION: + case ASF_OBJ_EXT_CONTENT_ENCRYPTION: + case ASF_OBJ_DIGITAL_SIGNATURE_OBJECT: + case ASF_OBJ_UNKNOWN_ENCRYPTION_OBJECT: + goto error_encrypted; + case ASF_OBJ_CONCEAL_NONE: + case ASF_OBJ_HEAD2: + case ASF_OBJ_UNDEFINED: + case ASF_OBJ_CODEC_COMMENT: + case ASF_OBJ_INDEX: + case ASF_OBJ_PADDING: + case ASF_OBJ_BITRATE_MUTEX: + case ASF_OBJ_COMPATIBILITY: + case ASF_OBJ_INDEX_PLACEHOLDER: + case ASF_OBJ_INDEX_PARAMETERS: + case ASF_OBJ_STREAM_PRIORITIZATION: + case ASF_OBJ_SCRIPT_COMMAND: + case ASF_OBJ_METADATA_LIBRARY_OBJECT: + default: + /* Unknown/unhandled object, skip it and hope for the best */ + GST_INFO ("%s: skipping object", demux->objpath); + ret = GST_FLOW_OK; + break; + } + + /* this can't fail, we checked the number of bytes available before */ + gst_asf_demux_skip_bytes (obj_data_size, p_data, p_size); + + GST_LOG ("%s: ret = %s", demux->objpath, gst_asf_get_flow_name (ret)); + + gst_asf_demux_pop_obj (demux); + + return ret; + +/* ERRORS */ +error_encrypted: + { + GST_ELEMENT_ERROR (demux, STREAM, DECRYPT, (NULL), (NULL)); + return GST_FLOW_ERROR; + } +} + +static void +gst_asf_demux_descramble_buffer (GstASFDemux * demux, AsfStream * stream, + GstBuffer ** p_buffer) +{ + GstBuffer *descrambled_buffer; + GstBuffer *scrambled_buffer; + GstBuffer *sub_buffer; + guint offset; + guint off; + guint row; + guint col; + guint idx; + + /* descrambled_buffer is initialised in the first iteration */ + descrambled_buffer = NULL; + scrambled_buffer = *p_buffer; + + if (gst_buffer_get_size (scrambled_buffer) < + stream->ds_packet_size * stream->span) + return; + + for (offset = 0; offset < gst_buffer_get_size (scrambled_buffer); + offset += stream->ds_chunk_size) { + off = offset / stream->ds_chunk_size; + row = off / stream->span; + col = off % stream->span; + idx = row + col * stream->ds_packet_size / stream->ds_chunk_size; + GST_DEBUG ("idx=%u, row=%u, col=%u, off=%u, ds_chunk_size=%u", idx, row, + col, off, stream->ds_chunk_size); + GST_DEBUG ("scrambled buffer size=%" G_GSIZE_FORMAT + ", span=%u, packet_size=%u", gst_buffer_get_size (scrambled_buffer), + stream->span, stream->ds_packet_size); + GST_DEBUG ("gst_buffer_get_size (scrambled_buffer) = %" G_GSIZE_FORMAT, + gst_buffer_get_size (scrambled_buffer)); + sub_buffer = + gst_buffer_copy_region (scrambled_buffer, GST_BUFFER_COPY_MEMORY, + idx * stream->ds_chunk_size, stream->ds_chunk_size); + if (!offset) { + descrambled_buffer = sub_buffer; + } else { + descrambled_buffer = gst_buffer_append (descrambled_buffer, sub_buffer); + } + } + + GST_BUFFER_TIMESTAMP (descrambled_buffer) = + GST_BUFFER_TIMESTAMP (scrambled_buffer); + GST_BUFFER_DURATION (descrambled_buffer) = + GST_BUFFER_DURATION (scrambled_buffer); + GST_BUFFER_OFFSET (descrambled_buffer) = GST_BUFFER_OFFSET (scrambled_buffer); + GST_BUFFER_OFFSET_END (descrambled_buffer) = + GST_BUFFER_OFFSET_END (scrambled_buffer); + + /* FIXME/CHECK: do we need to transfer buffer flags here too? */ + + gst_buffer_unref (scrambled_buffer); + *p_buffer = descrambled_buffer; +} + +static gboolean +gst_asf_demux_element_send_event (GstElement * element, GstEvent * event) +{ + GstASFDemux *demux = GST_ASF_DEMUX (element); + gint i; + + GST_DEBUG ("handling element event of type %s", GST_EVENT_TYPE_NAME (event)); + + for (i = 0; i < demux->num_streams; ++i) { + gst_event_ref (event); + if (gst_asf_demux_handle_src_event (demux->stream[i].pad, + GST_OBJECT_CAST (element), event)) { + gst_event_unref (event); + return TRUE; + } + } + + gst_event_unref (event); + return FALSE; +} + +/* takes ownership of the passed event */ +static gboolean +gst_asf_demux_send_event_unlocked (GstASFDemux * demux, GstEvent * event) +{ + gboolean ret = TRUE; + gint i; + + GST_DEBUG_OBJECT (demux, "sending %s event to all source pads", + GST_EVENT_TYPE_NAME (event)); + + for (i = 0; i < demux->num_streams; ++i) { + gst_event_ref (event); + ret &= gst_pad_push_event (demux->stream[i].pad, event); + } + gst_event_unref (event); + return ret; +} + +static gboolean +gst_asf_demux_handle_src_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + GstASFDemux *demux; + gboolean res = FALSE; + + demux = GST_ASF_DEMUX (parent); + + GST_DEBUG ("handling %s query", + gst_query_type_get_name (GST_QUERY_TYPE (query))); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration (query, &format, NULL); + + if (format != GST_FORMAT_TIME) { + GST_LOG ("only support duration queries in TIME format"); + break; + } + + res = gst_pad_query_default (pad, parent, query); + if (!res) { + GST_OBJECT_LOCK (demux); + + if (demux->segment.duration != GST_CLOCK_TIME_NONE) { + GST_LOG ("returning duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->segment.duration)); + + gst_query_set_duration (query, GST_FORMAT_TIME, + demux->segment.duration); + + res = TRUE; + } else { + GST_LOG ("duration not known yet"); + } + + GST_OBJECT_UNLOCK (demux); + } + break; + } + + case GST_QUERY_POSITION:{ + GstFormat format; + + gst_query_parse_position (query, &format, NULL); + + if (format != GST_FORMAT_TIME) { + GST_LOG ("only support position queries in TIME format"); + break; + } + + GST_OBJECT_LOCK (demux); + + if (demux->segment.position != GST_CLOCK_TIME_NONE) { + GST_LOG ("returning position: %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->segment.position)); + + gst_query_set_position (query, GST_FORMAT_TIME, + demux->segment.position); + + res = TRUE; + } else { + GST_LOG ("position not known yet"); + } + + GST_OBJECT_UNLOCK (demux); + break; + } + + case GST_QUERY_SEEKING:{ + GstFormat format; + + gst_query_parse_seeking (query, &format, NULL, NULL, NULL); + if (format == GST_FORMAT_TIME) { + gint64 duration; + + GST_OBJECT_LOCK (demux); + duration = demux->segment.duration; + GST_OBJECT_UNLOCK (demux); + + if (!demux->streaming || !demux->seekable) { + gst_query_set_seeking (query, GST_FORMAT_TIME, demux->seekable, 0, + duration); + res = TRUE; + } else { + GstFormat fmt; + gboolean seekable; + + /* try downstream first in TIME */ + res = gst_pad_query_default (pad, parent, query); + + gst_query_parse_seeking (query, &fmt, &seekable, NULL, NULL); + GST_LOG_OBJECT (demux, "upstream %s seekable %d", + GST_STR_NULL (gst_format_get_name (fmt)), seekable); + /* if no luck, maybe in BYTES */ + if (!seekable || fmt != GST_FORMAT_TIME) { + GstQuery *q; + + q = gst_query_new_seeking (GST_FORMAT_BYTES); + if ((res = gst_pad_peer_query (demux->sinkpad, q))) { + gst_query_parse_seeking (q, &fmt, &seekable, NULL, NULL); + GST_LOG_OBJECT (demux, "upstream %s seekable %d", + GST_STR_NULL (gst_format_get_name (fmt)), seekable); + if (fmt != GST_FORMAT_BYTES) + seekable = FALSE; + } + gst_query_unref (q); + gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, + duration); + res = TRUE; + } + } + } else + GST_LOG_OBJECT (demux, "only support seeking in TIME format"); + break; + } + + case GST_QUERY_LATENCY: + { + gboolean live; + GstClockTime min, max; + + /* preroll delay does not matter in non-live pipeline, + * but we might end up in a live (rtsp) one ... */ + + /* first forward */ + res = gst_pad_query_default (pad, parent, query); + if (!res) + break; + + gst_query_parse_latency (query, &live, &min, &max); + + GST_DEBUG_OBJECT (demux, "Peer latency: live %d, min %" + GST_TIME_FORMAT " max %" GST_TIME_FORMAT, live, + GST_TIME_ARGS (min), GST_TIME_ARGS (max)); + + GST_OBJECT_LOCK (demux); + if (min != -1) + min += demux->latency; + if (max != -1) + max += demux->latency; + GST_OBJECT_UNLOCK (demux); + + gst_query_set_latency (query, live, min, max); + break; + } + case GST_QUERY_SEGMENT: + { + GstFormat format; + gint64 start, stop; + + format = demux->segment.format; + + start = + gst_segment_to_stream_time (&demux->segment, format, + demux->segment.start); + if ((stop = demux->segment.stop) == -1) + stop = demux->segment.duration; + else + stop = gst_segment_to_stream_time (&demux->segment, format, stop); + + gst_query_set_segment (query, demux->segment.rate, format, start, stop); + res = TRUE; + break; + } + default: + res = gst_pad_query_default (pad, parent, query); + break; + } + + return res; +} + +static GstStateChangeReturn +gst_asf_demux_change_state (GstElement * element, GstStateChange transition) +{ + GstASFDemux *demux = GST_ASF_DEMUX (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY:{ + gst_segment_init (&demux->segment, GST_FORMAT_TIME); + demux->need_newsegment = TRUE; + demux->segment_running = FALSE; + demux->accurate = FALSE; + demux->adapter = gst_adapter_new (); + demux->metadata = gst_caps_new_empty (); + demux->global_metadata = gst_structure_new_empty ("metadata"); + demux->data_size = 0; + demux->data_offset = 0; + demux->index_offset = 0; + demux->base_offset = 0; + demux->flowcombiner = gst_flow_combiner_new (); + break; + } + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_asf_demux_reset (demux, FALSE); + break; + + case GST_STATE_CHANGE_READY_TO_NULL: + gst_asf_demux_reset (demux, FALSE); + gst_flow_combiner_free (demux->flowcombiner); + demux->flowcombiner = NULL; + break; + default: + break; + } + + return ret; +} diff --git a/gst/asfdemux/gstasfdemux.h b/gst/asfdemux/gstasfdemux.h new file mode 100644 index 0000000..46e1e13 --- /dev/null +++ b/gst/asfdemux/gstasfdemux.h @@ -0,0 +1,223 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __ASF_DEMUX_H__ +#define __ASF_DEMUX_H__ + +#include <gst/gst.h> +#include <gst/base/gstadapter.h> +#include <gst/base/gstflowcombiner.h> + +#include "asfheaders.h" + +G_BEGIN_DECLS + +#define GST_TYPE_ASF_DEMUX \ + (gst_asf_demux_get_type()) +#define GST_ASF_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ASF_DEMUX,GstASFDemux)) +#define GST_ASF_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ASF_DEMUX,GstASFDemuxClass)) +#define GST_IS_ASF_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ASF_DEMUX)) +#define GST_IS_ASF_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ASF_DEMUX)) + +GST_DEBUG_CATEGORY_EXTERN (asfdemux_dbg); +#define GST_CAT_DEFAULT asfdemux_dbg + +typedef struct _GstASFDemux GstASFDemux; +typedef struct _GstASFDemuxClass GstASFDemuxClass; + +typedef struct { + guint32 packet; + guint16 count; +} AsfSimpleIndexEntry; + +typedef struct { + AsfPayloadExtensionID id : 16; /* extension ID; the :16 makes sure the + * struct gets packed into 4 bytes */ + guint16 len; /* save this so we can skip unknown IDs */ +} AsfPayloadExtension; + +typedef struct +{ + gboolean valid; /* TRUE if structure is valid/filled */ + + GstClockTime start_time; + GstClockTime end_time; + GstClockTime avg_time_per_frame; + guint32 data_bitrate; + guint32 buffer_size; + guint32 intial_buf_fullness; + guint32 data_bitrate2; + guint32 buffer_size2; + guint32 intial_buf_fullness2; + guint32 max_obj_size; + guint32 flags; + guint16 lang_idx; + + /* may be NULL if there are no extensions; otherwise, terminated by + * an AsfPayloadExtension record with len 0 */ + AsfPayloadExtension *payload_extensions; + + /* missing: stream names */ +} AsfStreamExtProps; + +typedef struct +{ + AsfStreamType type; + + gboolean active; /* if the stream has been activated (pad added) */ + + GstPad *pad; + guint16 id; + + /* video-only */ + gboolean is_video; + gboolean fps_known; + + GstCaps *caps; + + GstTagList *pending_tags; + + gboolean discont; + + /* Descrambler settings */ + guint8 span; + guint16 ds_packet_size; + guint16 ds_chunk_size; + guint16 ds_data_size; + + /* for new parsing code */ + GArray *payloads; /* pending payloads */ + + /* Video stream PAR & interlacing */ + guint8 par_x; + guint8 par_y; + gboolean interlaced; + + /* extended stream properties (optional) */ + AsfStreamExtProps ext_props; + + gboolean inspect_payload; +} AsfStream; + +typedef enum { + GST_ASF_DEMUX_STATE_HEADER, + GST_ASF_DEMUX_STATE_DATA, + GST_ASF_DEMUX_STATE_INDEX +} GstASFDemuxState; + +#define GST_ASF_DEMUX_NUM_VIDEO_PADS 16 +#define GST_ASF_DEMUX_NUM_AUDIO_PADS 32 +#define GST_ASF_DEMUX_NUM_STREAMS 32 +#define GST_ASF_DEMUX_NUM_STREAM_IDS 127 + +struct _GstASFDemux { + GstElement element; + + GstPad *sinkpad; + + gboolean have_group_id; + guint group_id; + + GstAdapter *adapter; + GstTagList *taglist; + GstASFDemuxState state; + + /* byte offset where the asf starts, which might not be zero on chained + * asfs, index_offset and data_offset already are 'offseted' by base_offset */ + guint64 base_offset; + + guint64 index_offset; /* byte offset where index might be, or 0 */ + guint64 data_offset; /* byte offset where packets start */ + guint64 data_size; /* total size of packet data in bytes, or 0 */ + guint64 num_packets; /* total number of data packets, or 0 */ + gint64 packet; /* current packet */ + guint speed_packets; /* Known number of packets to get in one go*/ + + gchar **languages; + guint num_languages; + + GstCaps *metadata; /* metadata, for delayed parsing; one + * structure ('stream-N') per stream */ + GstStructure *global_metadata; /* metadata which isn't specific to one stream */ + GSList *ext_stream_props; /* for delayed processing (buffers) */ + GSList *mut_ex_streams; /* mutually exclusive streams */ + + guint32 num_audio_streams; + guint32 num_video_streams; + guint32 num_streams; + AsfStream stream[GST_ASF_DEMUX_NUM_STREAMS]; + gboolean activated_streams; + GstFlowCombiner *flowcombiner; + + /* for chained asf handling, we need to hold the old asf streams until + * we detect the new ones */ + AsfStream old_stream[GST_ASF_DEMUX_NUM_STREAMS]; + gboolean old_num_streams; + + GstClockTime first_ts; /* smallest timestamp found */ + + guint32 packet_size; + guint64 play_time; + + guint64 preroll; + + gboolean seekable; + gboolean broadcast; + + GstSegment segment; /* configured play segment */ + gboolean accurate; + + gboolean need_newsegment; /* do we need to send a new-segment event? */ + guint32 segment_seqnum; /* if the new segment must have this seqnum */ + GstClockTime segment_ts; /* streaming; timestamp for segment start */ + GstSegment in_segment; /* streaming; upstream segment info */ + GstClockTime in_gap; /* streaming; upstream initial segment gap for interpolation */ + gboolean segment_running; /* if we've started the current segment */ + gboolean streaming; /* TRUE if we are operating chain-based */ + GstClockTime latency; + + /* for debugging only */ + gchar *objpath; + + /* simple index, if available */ + GstClockTime sidx_interval; /* interval between entries in ns */ + guint sidx_num_entries; /* number of index entries */ + AsfSimpleIndexEntry *sidx_entries; /* packet number for each entry */ + + GSList *other_streams; /* remember streams that are in header but have unknown type */ +}; + +struct _GstASFDemuxClass { + GstElementClass parent_class; +}; + +GType gst_asf_demux_get_type (void); + +AsfStream * gst_asf_demux_get_stream (GstASFDemux * demux, guint16 id); + +gboolean gst_asf_demux_is_unknown_stream(GstASFDemux *demux, guint stream_num); + +G_END_DECLS + +#endif /* __ASF_DEMUX_H__ */ diff --git a/gst/asfdemux/gstrtpasfdepay.c b/gst/asfdemux/gstrtpasfdepay.c new file mode 100644 index 0000000..1ba5d02 --- /dev/null +++ b/gst/asfdemux/gstrtpasfdepay.c @@ -0,0 +1,545 @@ +/* GStreamer RTP ASF depayloader + * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net> + * 2009 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gstrtpasfdepay.h" +#include <gst/rtp/gstrtpbuffer.h> + +#include <string.h> +#include <stdlib.h> + +GST_DEBUG_CATEGORY_STATIC (rtpasfdepayload_debug); +#define GST_CAT_DEFAULT rtpasfdepayload_debug + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-ms-asf") + ); + +/* Other parameters: config, maxps */ +#define SINK_CAPS \ + "application/x-rtp, " \ + "media = (string) { \"application\", \"video\", \"audio\" }, " \ + "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", " \ + "clock-rate = (int) [1, MAX ], " \ + "encoding-name = (string) \"X-ASF-PF\"" + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SINK_CAPS) + ); + +#define gst_rtp_asf_depay_parent_class parent_class +G_DEFINE_TYPE (GstRtpAsfDepay, gst_rtp_asf_depay, GST_TYPE_RTP_BASE_DEPAYLOAD); + +static void gst_rtp_asf_depay_finalize (GObject * object); + +static GstStateChangeReturn gst_rtp_asf_depay_change_state (GstElement * + element, GstStateChange transition); + +static gboolean gst_rtp_asf_depay_setcaps (GstRTPBaseDepayload * depay, + GstCaps * caps); +static GstBuffer *gst_rtp_asf_depay_process (GstRTPBaseDepayload * basedepay, + GstBuffer * buf); + +static void +gst_rtp_asf_depay_class_init (GstRtpAsfDepayClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstRTPBaseDepayloadClass *gstrtpbasedepayload_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstrtpbasedepayload_class = (GstRTPBaseDepayloadClass *) klass; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sink_factory)); + + gst_element_class_set_static_metadata (gstelement_class, + "RTP ASF packet depayloader", "Codec/Depayloader/Network", + "Extracts ASF streams from RTP", + "Tim-Philipp Müller <tim centricular net>, " + "Wim Taymans <wim.taymans@gmail.com>"); + + gobject_class->finalize = gst_rtp_asf_depay_finalize; + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_rtp_asf_depay_change_state); + + gstrtpbasedepayload_class->set_caps = + GST_DEBUG_FUNCPTR (gst_rtp_asf_depay_setcaps); + gstrtpbasedepayload_class->process = + GST_DEBUG_FUNCPTR (gst_rtp_asf_depay_process); + + GST_DEBUG_CATEGORY_INIT (rtpasfdepayload_debug, "rtpasfdepayload", 0, + "RTP asf depayloader element"); +} + +static void +gst_rtp_asf_depay_init (GstRtpAsfDepay * depay) +{ + depay->adapter = gst_adapter_new (); +} + +static void +gst_rtp_asf_depay_finalize (GObject * object) +{ + GstRtpAsfDepay *depay; + + depay = GST_RTP_ASF_DEPAY (object); + + g_object_unref (depay->adapter); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static const guint8 asf_marker[16] = { 0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, + 0xcf, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c +}; + +static gboolean +gst_rtp_asf_depay_setcaps (GstRTPBaseDepayload * depayload, GstCaps * caps) +{ + GstRtpAsfDepay *depay; + GstStructure *s; + const gchar *config_str, *ps_string; + GstBuffer *buf; + GstCaps *src_caps; + guint8 *headers; + gsize headers_len; + gint clock_rate; + + depay = GST_RTP_ASF_DEPAY (depayload); + + s = gst_caps_get_structure (caps, 0); + + if (!gst_structure_get_int (s, "clock-rate", &clock_rate) || clock_rate < 0) + clock_rate = 1000; + depayload->clock_rate = clock_rate; + + /* config contains the asf headers in base64 coding */ + config_str = gst_structure_get_string (s, "config"); + if (config_str == NULL || *config_str == '\0') + goto no_config; + + ps_string = gst_structure_get_string (s, "maxps"); + if (ps_string == NULL || *ps_string == '\0') + goto no_packetsize; + + if (depay->packet_size) { + /* header sent again following seek; + * discard to avoid confusing upstream */ + if (depay->packet_size == atoi (ps_string)) { + goto duplicate_header; + } else { + /* since we should fiddle with downstream state to handle this */ + goto refuse_renegotiation; + } + } else + depay->packet_size = atoi (ps_string); + if (depay->packet_size <= 16) + goto invalid_packetsize; + + headers = (guint8 *) g_base64_decode (config_str, &headers_len); + + if (headers == NULL || headers_len < 16 + || memcmp (headers, asf_marker, 16) != 0) + goto invalid_headers; + + src_caps = gst_caps_new_empty_simple ("video/x-ms-asf"); + gst_pad_set_caps (depayload->srcpad, src_caps); + gst_caps_unref (src_caps); + + buf = gst_buffer_new (); + gst_buffer_append_memory (buf, + gst_memory_new_wrapped (0, headers, headers_len, 0, headers_len, headers, + g_free)); + + gst_rtp_base_depayload_push (depayload, buf); + + return TRUE; + + /* ERRORS */ +no_config: + { + GST_WARNING_OBJECT (depay, "caps without 'config' field with asf headers"); + return FALSE; + } +no_packetsize: + { + GST_WARNING_OBJECT (depay, "caps without 'maxps' (packet size) field"); + return FALSE; + } +invalid_packetsize: + { + GST_WARNING_OBJECT (depay, "packet size %u invalid", depay->packet_size); + return FALSE; + } +invalid_headers: + { + GST_WARNING_OBJECT (depay, "headers don't look like valid ASF headers"); + g_free (headers); + return FALSE; + } +duplicate_header: + { + GST_DEBUG_OBJECT (depayload, "discarding duplicate header"); + return TRUE; + } +refuse_renegotiation: + { + GST_WARNING_OBJECT (depayload, "cannot renegotiate to different header"); + return FALSE; + } +} + +static gint +field_size (guint8 field) +{ + switch (field) { + /* DWORD - 32 bits */ + case 3: + return 4; + + /* WORD - 16 bits */ + case 2: + return 2; + + /* BYTE - 8 bits */ + case 1: + return 1; + + /* non-exitent */ + case 0: + default: + return 0; + } +} + +/* Set the padding field to te correct value as the spec + * says it should be se to 0 in the rtp packets + */ +static GstBuffer * +gst_rtp_asf_depay_update_padding (GstRtpAsfDepay * depayload, GstBuffer * buf) +{ + GstBuffer *result; + GstMapInfo map; + guint8 *data; + gint offset = 0; + guint8 aux; + guint8 seq_type; + guint8 pad_type; + guint8 pkt_type; + gsize plen, padding; + + plen = gst_buffer_get_size (buf); + if (plen == depayload->packet_size) + return buf; + + padding = depayload->packet_size - plen; + + GST_LOG_OBJECT (depayload, + "padding buffer size %" G_GSIZE_FORMAT " to packet size %d", plen, + depayload->packet_size); + + result = gst_buffer_new_and_alloc (depayload->packet_size); + + gst_buffer_map (result, &map, GST_MAP_READ); + data = map.data; + memset (data + plen, 0, padding); + + gst_buffer_extract (buf, 0, data, plen); + gst_buffer_unref (buf); + + aux = data[offset++]; + if (aux & 0x80) { + guint8 err_len = 0; + if (aux & 0x60) { + GST_WARNING_OBJECT (depayload, "Error correction length type should be " + "set to 0"); + /* this packet doesn't follow the spec */ + gst_buffer_unmap (result, &map); + return result; + } + err_len = aux & 0x0F; + offset += err_len; + + aux = data[offset++]; + } + seq_type = (aux >> 1) & 0x3; + pad_type = (aux >> 3) & 0x3; + pkt_type = (aux >> 5) & 0x3; + + offset += 1; /* skip property flags */ + offset += field_size (pkt_type); /* skip packet length */ + offset += field_size (seq_type); /* skip sequence field */ + + /* write padding */ + switch (pad_type) { + /* DWORD */ + case 3: + GST_WRITE_UINT32_LE (&(data[offset]), padding); + break; + + /* WORD */ + case 2: + GST_WRITE_UINT16_LE (&(data[offset]), padding); + break; + + /* BYTE */ + case 1: + data[offset] = (guint8) padding; + break; + + /* non-existent */ + case 0: + default: + break; + } + gst_buffer_unmap (result, &map); + + return result; +} + +/* Docs: 'RTSP Protocol PDF' document from http://sdp.ppona.com/ (page 8) */ + +static GstBuffer * +gst_rtp_asf_depay_process (GstRTPBaseDepayload * depayload, GstBuffer * buf) +{ + GstRtpAsfDepay *depay; + const guint8 *payload; + GstBuffer *outbuf; + gboolean S, L, R, D, I; + guint payload_len, hdr_len, offset; + guint len_offs; + GstClockTime timestamp; + GstRTPBuffer rtpbuf = { NULL }; + + depay = GST_RTP_ASF_DEPAY (depayload); + + /* flush remaining data on discont */ + if (GST_BUFFER_IS_DISCONT (buf)) { + GST_LOG_OBJECT (depay, "got DISCONT"); + gst_adapter_clear (depay->adapter); + depay->discont = TRUE; + } + + gst_rtp_buffer_map (buf, GST_MAP_READ, &rtpbuf); + timestamp = GST_BUFFER_TIMESTAMP (buf); + + payload_len = gst_rtp_buffer_get_payload_len (&rtpbuf); + payload = gst_rtp_buffer_get_payload (&rtpbuf); + offset = 0; + + GST_LOG_OBJECT (depay, "got payload len of %u", payload_len); + + do { + guint packet_len; + + /* packet header is at least 4 bytes */ + if (payload_len < 4) + goto too_small; + + /* 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |S|L|R|D|I|RES | Length/Offset | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Relative Timestamp (optional) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Duration (optional) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | LocationId (optional) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * S: packet contains a keyframe. + * L: If 1, Length/Offset contains length, else contains the byte offset + * of the fragment's first byte counted from the beginning of the + * complete ASF data packet. + * R: relative timestamp present + * D: duration present + * I: locationid present + */ + + S = ((payload[0] & 0x80) != 0); + L = ((payload[0] & 0x40) != 0); + R = ((payload[0] & 0x20) != 0); + D = ((payload[0] & 0x10) != 0); + I = ((payload[0] & 0x08) != 0); + + hdr_len = 4; + + len_offs = (payload[1] << 16) | (payload[2] << 8) | payload[3]; + + if (R) { + GST_DEBUG ("Relative timestamp field present : %u", + GST_READ_UINT32_BE (payload + hdr_len)); + hdr_len += 4; + } + if (D) { + GST_DEBUG ("Duration field present : %u", + GST_READ_UINT32_BE (payload + hdr_len)); + hdr_len += 4; + } + if (I) { + GST_DEBUG ("LocationId field present : %u", + GST_READ_UINT32_BE (payload + hdr_len)); + hdr_len += 4; + } + + GST_LOG_OBJECT (depay, "S %d, L %d, R %d, D %d, I %d", S, L, R, D, I); + GST_LOG_OBJECT (depay, "payload_len:%d, hdr_len:%d, len_offs:%d", + payload_len, hdr_len, len_offs); + + if (payload_len < hdr_len) + goto too_small; + + /* skip headers */ + payload_len -= hdr_len; + payload += hdr_len; + offset += hdr_len; + + if (L) { + /* L bit set, len contains the length of the packet */ + packet_len = len_offs; + } else { + /* else it contains an offset which we don't handle yet */ + GST_LOG_OBJECT (depay, "We have a fragmented packet"); + packet_len = payload_len; + } + + if (packet_len > payload_len) + packet_len = payload_len; + + GST_LOG_OBJECT (depay, "packet len %u, payload len %u, packet_size:%u", + packet_len, payload_len, depay->packet_size); + + if (!L) { + guint available; + GstBuffer *sub; + + /* Fragmented packet handling */ + outbuf = NULL; + + if (len_offs == (available = gst_adapter_available (depay->adapter))) { + /* fragment aligns with what we have, add it */ + GST_LOG_OBJECT (depay, "collecting fragment"); + sub = + gst_rtp_buffer_get_payload_subbuffer (&rtpbuf, offset, packet_len); + gst_adapter_push (depay->adapter, sub); + /* RTP marker bit M is set if this is last fragment */ + if (gst_rtp_buffer_get_marker (&rtpbuf)) { + GST_LOG_OBJECT (depay, "last fragment, assembling packet"); + outbuf = + gst_adapter_take_buffer (depay->adapter, available + packet_len); + } + } else { + if (available) { + GST_WARNING_OBJECT (depay, "Offset doesn't match previous data?!"); + GST_DEBUG_OBJECT (depay, "clearing for re-sync"); + gst_adapter_clear (depay->adapter); + } else + GST_DEBUG_OBJECT (depay, "waiting for start of packet"); + } + } else { + GST_LOG_OBJECT (depay, "collecting packet"); + outbuf = + gst_rtp_buffer_get_payload_subbuffer (&rtpbuf, offset, packet_len); + } + + /* If we haven't completed a full ASF packet, return */ + if (!outbuf) + return NULL; + + outbuf = gst_rtp_asf_depay_update_padding (depay, outbuf); + + if (!S) + GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); + + if (depay->discont) { + GST_LOG_OBJECT (depay, "setting DISCONT"); + GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); + depay->discont = FALSE; + } + + GST_BUFFER_TIMESTAMP (outbuf) = timestamp; + + gst_rtp_base_depayload_push (depayload, outbuf); + + /* only apply the timestamp to the first buffer of this packet */ + timestamp = -1; + + /* skip packet data */ + payload += packet_len; + offset += packet_len; + payload_len -= packet_len; + } while (payload_len > 0); + + gst_rtp_buffer_unmap (&rtpbuf); + + return NULL; + +/* ERRORS */ +too_small: + { + gst_rtp_buffer_unmap (&rtpbuf); + GST_WARNING_OBJECT (depayload, "Payload too small, expected at least 4 " + "bytes for header, but got only %d bytes", payload_len); + return NULL; + } +} + +static GstStateChangeReturn +gst_rtp_asf_depay_change_state (GstElement * element, GstStateChange trans) +{ + GstStateChangeReturn ret; + GstRtpAsfDepay *depay; + + depay = GST_RTP_ASF_DEPAY (element); + + switch (trans) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_adapter_clear (depay->adapter); + depay->discont = TRUE; + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans); + + switch (trans) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_adapter_clear (depay->adapter); + break; + default: + break; + } + + return ret; +} diff --git a/gst/asfdemux/gstrtpasfdepay.h b/gst/asfdemux/gstrtpasfdepay.h new file mode 100644 index 0000000..8388c8a --- /dev/null +++ b/gst/asfdemux/gstrtpasfdepay.h @@ -0,0 +1,64 @@ +/* GStreamer RTP ASF depayloader + * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net> + * 2009 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_RTP_ASF_DEPAY_H__ +#define __GST_RTP_ASF_DEPAY_H__ + +#include <gst/gst.h> +#include <gst/base/gstadapter.h> + +#include <gst/rtp/gstrtpbasedepayload.h> + +G_BEGIN_DECLS + +#define GST_TYPE_RTP_ASF_DEPAY \ + (gst_rtp_asf_depay_get_type()) +#define GST_RTP_ASF_DEPAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_ASF_DEPAY,GstRtpAsfDepay)) +#define GST_RTP_ASF_DEPAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_ASF_DEPAY,GstRtpAsfDepayClass)) +#define GST_IS_RTP_ASF_DEPAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_ASF_DEPAY)) +#define GST_IS_RTP_ASF_DEPAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_ASF_DEPAY)) + +typedef struct _GstRtpAsfDepay GstRtpAsfDepay; +typedef struct _GstRtpAsfDepayClass GstRtpAsfDepayClass; + +struct _GstRtpAsfDepay +{ + GstRTPBaseDepayload depayload; + + guint packet_size; + + GstAdapter *adapter; + gboolean discont; +}; + +struct _GstRtpAsfDepayClass +{ + GstRTPBaseDepayloadClass depayload_class; +}; + +GType gst_rtp_asf_depay_get_type (void); + +G_END_DECLS + +#endif /* __GST_RTP_ASF_DEPAY_H__ */ diff --git a/gst/asfdemux/gstrtspwms.c b/gst/asfdemux/gstrtspwms.c new file mode 100644 index 0000000..c864287 --- /dev/null +++ b/gst/asfdemux/gstrtspwms.c @@ -0,0 +1,236 @@ +/* GStreamer + * Copyright (C) <2005,2006> Wim Taymans <wim@fluendo.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/* Element-Checklist-Version: 5 */ + +/** + * SECTION:element-rtspwms + * + * A WMS RTSP extension + */ + +#include <string.h> + +#include <gst/rtsp/gstrtspextension.h> + +#include "gstrtspwms.h" + +GST_DEBUG_CATEGORY_STATIC (rtspwms_debug); +#define GST_CAT_DEFAULT (rtspwms_debug) + +#define SERVER_PREFIX "WMServer/" +#define HEADER_PREFIX "data:application/vnd.ms.wms-hdr.asfv1;base64," +#define EXTENSION_CMD "application/x-wms-extension-cmd" + +static GstRTSPResult +gst_rtsp_wms_before_send (GstRTSPExtension * ext, GstRTSPMessage * request) +{ + GstRTSPWMS *ctx = (GstRTSPWMS *) ext; + + GST_DEBUG_OBJECT (ext, "before send"); + + switch (request->type_data.request.method) { + case GST_RTSP_OPTIONS: + { + /* activate ourselves with the first request */ + ctx->active = TRUE; + break; + } + default: + break; + } + return GST_RTSP_OK; +} + +static GstRTSPResult +gst_rtsp_wms_after_send (GstRTSPExtension * ext, GstRTSPMessage * req, + GstRTSPMessage * resp) +{ + GstRTSPWMS *ctx = (GstRTSPWMS *) ext; + + GST_DEBUG_OBJECT (ext, "after send"); + + switch (req->type_data.request.method) { + case GST_RTSP_OPTIONS: + { + gchar *server = NULL; + + gst_rtsp_message_get_header (resp, GST_RTSP_HDR_SERVER, &server, 0); + if (server && g_str_has_prefix (server, SERVER_PREFIX)) + ctx->active = TRUE; + else + ctx->active = FALSE; + break; + } + default: + break; + } + return GST_RTSP_OK; +} + + +static GstRTSPResult +gst_rtsp_wms_parse_sdp (GstRTSPExtension * ext, GstSDPMessage * sdp, + GstStructure * props) +{ + const gchar *config, *maxps; + gint i; + GstRTSPWMS *ctx = (GstRTSPWMS *) ext; + + if (!ctx->active) + return GST_RTSP_OK; + + for (i = 0; (config = gst_sdp_message_get_attribute_val_n (sdp, "pgmpu", i)); + i++) { + if (g_str_has_prefix (config, HEADER_PREFIX)) { + config += strlen (HEADER_PREFIX); + gst_structure_set (props, "config", G_TYPE_STRING, config, NULL); + break; + } + } + if (config == NULL) + goto no_config; + + gst_structure_set (props, "config", G_TYPE_STRING, config, NULL); + + maxps = gst_sdp_message_get_attribute_val (sdp, "maxps"); + if (maxps) + gst_structure_set (props, "maxps", G_TYPE_STRING, maxps, NULL); + + gst_structure_set (props, "encoding-name", G_TYPE_STRING, "X-ASF-PF", NULL); + gst_structure_set (props, "media", G_TYPE_STRING, "application", NULL); + + return GST_RTSP_OK; + + /* ERRORS */ +no_config: + { + GST_DEBUG_OBJECT (ctx, "Could not find config SDP field, deactivating."); + ctx->active = FALSE; + return GST_RTSP_OK; + } +} + +static gboolean +gst_rtsp_wms_configure_stream (GstRTSPExtension * ext, GstCaps * caps) +{ + GstRTSPWMS *ctx; + GstStructure *s; + const gchar *encoding; + + ctx = (GstRTSPWMS *) ext; + s = gst_caps_get_structure (caps, 0); + encoding = gst_structure_get_string (s, "encoding-name"); + + if (!encoding) + return TRUE; + + GST_DEBUG_OBJECT (ctx, "%" GST_PTR_FORMAT " encoding-name: %s", caps, + encoding); + + /* rtx streams do not need to be configured */ + if (!strcmp (encoding, "X-WMS-RTX")) + return FALSE; + + return TRUE; +} + +static GstRTSPResult +gst_rtsp_wms_receive_request (GstRTSPExtension * ext, GstRTSPMessage * request) +{ + GstRTSPWMS *ctx; + GstRTSPResult res = GST_RTSP_ENOTIMPL; + GstRTSPMessage response = { 0 }; + + ctx = (GstRTSPWMS *) ext; + + GST_DEBUG_OBJECT (ext, "before send"); + + switch (request->type_data.request.method) { + case GST_RTSP_SET_PARAMETER: + { + gchar *content_type = NULL; + + gst_rtsp_message_get_header (request, GST_RTSP_HDR_CONTENT_TYPE, + &content_type, 0); + + if (content_type && !g_ascii_strcasecmp (content_type, EXTENSION_CMD)) { + /* parse the command */ + + /* default implementation, send OK */ + res = gst_rtsp_message_init_response (&response, GST_RTSP_STS_OK, "OK", + request); + if (res < 0) + goto send_error; + + GST_DEBUG_OBJECT (ctx, "replying with OK"); + + /* send reply */ + if ((res = gst_rtsp_extension_send (ext, request, &response)) < 0) + goto send_error; + + res = GST_RTSP_EEOF; + } + break; + } + default: + break; + } + return res; + +send_error: + { + return res; + } +} + +static void gst_rtsp_wms_extension_init (gpointer g_iface, gpointer iface_data); + +G_DEFINE_TYPE_WITH_CODE (GstRTSPWMS, gst_rtsp_wms, GST_TYPE_ELEMENT, + G_IMPLEMENT_INTERFACE (GST_TYPE_RTSP_EXTENSION, + gst_rtsp_wms_extension_init)); + +static void +gst_rtsp_wms_class_init (GstRTSPWMSClass * g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + GST_DEBUG_CATEGORY_INIT (rtspwms_debug, "rtspwms", 0, "WMS RTSP extension"); + + gst_element_class_set_static_metadata (element_class, "WMS RTSP Extension", + "Network/Extension/Protocol", + "Extends RTSP so that it can handle WMS setup", + "Wim Taymans <wim.taymans@gmail.com>"); +} + +static void +gst_rtsp_wms_init (GstRTSPWMS * rtspwms) +{ +} + +static void +gst_rtsp_wms_extension_init (gpointer g_iface, gpointer iface_data) +{ + GstRTSPExtensionInterface *iface = (GstRTSPExtensionInterface *) g_iface; + + iface->parse_sdp = gst_rtsp_wms_parse_sdp; + iface->before_send = gst_rtsp_wms_before_send; + iface->after_send = gst_rtsp_wms_after_send; + iface->configure_stream = gst_rtsp_wms_configure_stream; + iface->receive_request = gst_rtsp_wms_receive_request; +} diff --git a/gst/asfdemux/gstrtspwms.h b/gst/asfdemux/gstrtspwms.h new file mode 100644 index 0000000..feb8c43 --- /dev/null +++ b/gst/asfdemux/gstrtspwms.h @@ -0,0 +1,50 @@ +/* GStreamer + * Copyright (C) <2005,2006> Wim Taymans <wim@fluendo.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_RTSP_WMS_H__ +#define __GST_RTSP_WMS_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define GST_TYPE_RTSP_WMS (gst_rtsp_wms_get_type()) +#define GST_IS_RTSP_WMS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTSP_WMS)) +#define GST_IS_RTSP_WMS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTSP_WMS)) +#define GST_RTSP_WMS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTSP_WMS, GstRTSPWMS)) +#define GST_RTSP_WMS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTSP_WMS, GstRTSPWMSClass)) + +typedef struct _GstRTSPWMS GstRTSPWMS; +typedef struct _GstRTSPWMSClass GstRTSPWMSClass; + +struct _GstRTSPWMS { + GstElement element; + + gboolean active; +}; + +struct _GstRTSPWMSClass { + GstElementClass parent_class; +}; + +GType gst_rtsp_wms_get_type(void); + +G_END_DECLS + +#endif /* __GST_RTSP_WMS_H__ */ |