diff options
Diffstat (limited to 'gst/rtsp')
-rwxr-xr-x | gst/rtsp/Makefile.am | 16 | ||||
-rwxr-xr-x | gst/rtsp/Makefile.in | 928 | ||||
-rwxr-xr-x | gst/rtsp/README | 377 | ||||
-rwxr-xr-x | gst/rtsp/gstrtpdec.c | 897 | ||||
-rwxr-xr-x | gst/rtsp/gstrtpdec.h | 88 | ||||
-rwxr-xr-x | gst/rtsp/gstrtsp.c | 74 | ||||
-rwxr-xr-x | gst/rtsp/gstrtsp.h | 53 | ||||
-rwxr-xr-x | gst/rtsp/gstrtspext.c | 268 | ||||
-rwxr-xr-x | gst/rtsp/gstrtspext.h | 83 | ||||
-rwxr-xr-x | gst/rtsp/gstrtspsrc.c | 8317 | ||||
-rwxr-xr-x | gst/rtsp/gstrtspsrc.h | 283 |
11 files changed, 11384 insertions, 0 deletions
diff --git a/gst/rtsp/Makefile.am b/gst/rtsp/Makefile.am new file mode 100755 index 0000000..267e49a --- /dev/null +++ b/gst/rtsp/Makefile.am @@ -0,0 +1,16 @@ +plugin_LTLIBRARIES = libgstrtsp.la + +libgstrtsp_la_SOURCES = gstrtsp.c gstrtspsrc.c \ + gstrtpdec.c gstrtspext.c + +libgstrtsp_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(GIO_CFLAGS) +libgstrtsp_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) $(GST_LIBS) $(GST_BASE_LIBS) $(GIO_LIBS) \ + -lgstrtp-@GST_API_VERSION@ -lgstrtsp-@GST_API_VERSION@ \ + -lgstsdp-@GST_API_VERSION@ $(GST_NET_LIBS) $(GST_LIBS) +libgstrtsp_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstrtsp_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +noinst_HEADERS = gstrtspsrc.h \ + gstrtsp.h \ + gstrtpdec.h \ + gstrtspext.h diff --git a/gst/rtsp/Makefile.in b/gst/rtsp/Makefile.in new file mode 100755 index 0000000..34928f7 --- /dev/null +++ b/gst/rtsp/Makefile.in @@ -0,0 +1,928 @@ +# 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/rtsp +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-gcc-inline-assembly.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-gettext.m4 \ + $(top_srcdir)/common/m4/gst-glib2.m4 \ + $(top_srcdir)/common/m4/gst-package-release-datetime.m4 \ + $(top_srcdir)/common/m4/gst-platform.m4 \ + $(top_srcdir)/common/m4/gst-plugin-docs.m4 \ + $(top_srcdir)/common/m4/gst-plugindir.m4 \ + $(top_srcdir)/common/m4/gst-x11.m4 \ + $(top_srcdir)/common/m4/gst.m4 \ + $(top_srcdir)/common/m4/gtk-doc.m4 \ + $(top_srcdir)/common/m4/orc.m4 $(top_srcdir)/common/m4/pkg.m4 \ + $(top_srcdir)/m4/aalib.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gst-fionread.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 = +libgstrtsp_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_libgstrtsp_la_OBJECTS = libgstrtsp_la-gstrtsp.lo \ + libgstrtsp_la-gstrtspsrc.lo libgstrtsp_la-gstrtpdec.lo \ + libgstrtsp_la-gstrtspext.lo +libgstrtsp_la_OBJECTS = $(am_libgstrtsp_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 = +libgstrtsp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(libgstrtsp_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(libgstrtsp_la_CFLAGS) $(CFLAGS) \ + $(libgstrtsp_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 = $(libgstrtsp_la_SOURCES) +DIST_SOURCES = $(libgstrtsp_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) +AALIB_CFLAGS = @AALIB_CFLAGS@ +AALIB_CONFIG = @AALIB_CONFIG@ +AALIB_LIBS = @AALIB_LIBS@ +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BZ2_LIBS = @BZ2_LIBS@ +CAIRO_CFLAGS = @CAIRO_CFLAGS@ +CAIRO_LIBS = @CAIRO_LIBS@ +CC = @CC@ +CCAS = @CCAS@ +CCASDEPMODE = @CCASDEPMODE@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFAULT_AUDIOSINK = @DEFAULT_AUDIOSINK@ +DEFAULT_AUDIOSRC = @DEFAULT_AUDIOSRC@ +DEFAULT_VIDEOSINK = @DEFAULT_VIDEOSINK@ +DEFAULT_VIDEOSRC = @DEFAULT_VIDEOSRC@ +DEFAULT_VISUALIZER = @DEFAULT_VISUALIZER@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DEPRECATED_CFLAGS = @DEPRECATED_CFLAGS@ +DIRECTSOUND_CFLAGS = @DIRECTSOUND_CFLAGS@ +DIRECTSOUND_LDFLAGS = @DIRECTSOUND_LDFLAGS@ +DIRECTSOUND_LIBS = @DIRECTSOUND_LIBS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +DV1394_CFLAGS = @DV1394_CFLAGS@ +DV1394_LIBS = @DV1394_LIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ERROR_CFLAGS = @ERROR_CFLAGS@ +ERROR_CXXFLAGS = @ERROR_CXXFLAGS@ +ERROR_OBJCFLAGS = @ERROR_OBJCFLAGS@ +EXEEXT = @EXEEXT@ +FFLAGS = @FFLAGS@ +FGREP = @FGREP@ +FLAC_CFLAGS = @FLAC_CFLAGS@ +FLAC_LIBS = @FLAC_LIBS@ +GCOV = @GCOV@ +GCOV_CFLAGS = @GCOV_CFLAGS@ +GCOV_LIBS = @GCOV_LIBS@ +GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@ +GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +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@ +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_CONTROLLER_CFLAGS = @GST_CONTROLLER_CFLAGS@ +GST_CONTROLLER_LIBS = @GST_CONTROLLER_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_NET_CFLAGS = @GST_NET_CFLAGS@ +GST_NET_LIBS = @GST_NET_LIBS@ +GST_OBJCFLAGS = @GST_OBJCFLAGS@ +GST_OPTION_CFLAGS = @GST_OPTION_CFLAGS@ +GST_OPTION_CXXFLAGS = @GST_OPTION_CXXFLAGS@ +GST_OPTION_OBJCFLAGS = @GST_OPTION_OBJCFLAGS@ +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@ +GTK_CFLAGS = @GTK_CFLAGS@ +GTK_LIBS = @GTK_LIBS@ +GTK_X11_CFLAGS = @GTK_X11_CFLAGS@ +GTK_X11_LIBS = @GTK_X11_LIBS@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HAVE_AVC1394 = @HAVE_AVC1394@ +HAVE_CXX = @HAVE_CXX@ +HAVE_DIRECTSOUND = @HAVE_DIRECTSOUND@ +HAVE_ROM1394 = @HAVE_ROM1394@ +HAVE_SPEEX = @HAVE_SPEEX@ +HAVE_X = @HAVE_X@ +HAVE_XSHM = @HAVE_XSHM@ +HAVE_ZLIB = @HAVE_ZLIB@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +JACK_0_120_1_CFLAGS = @JACK_0_120_1_CFLAGS@ +JACK_0_120_1_LIBS = @JACK_0_120_1_LIBS@ +JACK_1_9_7_CFLAGS = @JACK_1_9_7_CFLAGS@ +JACK_1_9_7_LIBS = @JACK_1_9_7_LIBS@ +JACK_CFLAGS = @JACK_CFLAGS@ +JACK_LIBS = @JACK_LIBS@ +JPEG_LIBS = @JPEG_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBCACA_CFLAGS = @LIBCACA_CFLAGS@ +LIBCACA_LIBS = @LIBCACA_LIBS@ +LIBDV_CFLAGS = @LIBDV_CFLAGS@ +LIBDV_LIBS = @LIBDV_LIBS@ +LIBICONV = @LIBICONV@ +LIBIEC61883_CFLAGS = @LIBIEC61883_CFLAGS@ +LIBIEC61883_LIBS = @LIBIEC61883_LIBS@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBOBJS = @LIBOBJS@ +LIBPNG_CFLAGS = @LIBPNG_CFLAGS@ +LIBPNG_LIBS = @LIBPNG_LIBS@ +LIBRT = @LIBRT@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBV4L2_CFLAGS = @LIBV4L2_CFLAGS@ +LIBV4L2_LIBS = @LIBV4L2_LIBS@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOCALEDIR = @LOCALEDIR@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJC = @OBJC@ +OBJCDEPMODE = @OBJCDEPMODE@ +OBJCFLAGS = @OBJCFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +ORCC = @ORCC@ +ORCC_FLAGS = @ORCC_FLAGS@ +ORC_CFLAGS = @ORC_CFLAGS@ +ORC_LIBS = @ORC_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@ +PACKAGE_VERSION_MICRO = @PACKAGE_VERSION_MICRO@ +PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@ +PACKAGE_VERSION_NANO = @PACKAGE_VERSION_NANO@ +PACKAGE_VERSION_RELEASE = @PACKAGE_VERSION_RELEASE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PLUGINDIR = @PLUGINDIR@ +POSUB = @POSUB@ +PROFILE_CFLAGS = @PROFILE_CFLAGS@ +PULSE_CFLAGS = @PULSE_CFLAGS@ +PULSE_LIBS = @PULSE_LIBS@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RAW1394_CFLAGS = @RAW1394_CFLAGS@ +RAW1394_LIBS = @RAW1394_LIBS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHOUT2_CFLAGS = @SHOUT2_CFLAGS@ +SHOUT2_LIBS = @SHOUT2_LIBS@ +SOUP_CFLAGS = @SOUP_CFLAGS@ +SOUP_LIBS = @SOUP_LIBS@ +SPEEX_CFLAGS = @SPEEX_CFLAGS@ +SPEEX_LIBS = @SPEEX_LIBS@ +STRIP = @STRIP@ +TAGLIB_CFLAGS = @TAGLIB_CFLAGS@ +TAGLIB_CXXFLAGS = @TAGLIB_CXXFLAGS@ +TAGLIB_LIBS = @TAGLIB_LIBS@ +USE_NLS = @USE_NLS@ +VALGRIND_CFLAGS = @VALGRIND_CFLAGS@ +VALGRIND_LIBS = @VALGRIND_LIBS@ +VALGRIND_PATH = @VALGRIND_PATH@ +VERSION = @VERSION@ +VPX_130_CFLAGS = @VPX_130_CFLAGS@ +VPX_130_LIBS = @VPX_130_LIBS@ +VPX_CFLAGS = @VPX_CFLAGS@ +VPX_LIBS = @VPX_LIBS@ +WARNING_CFLAGS = @WARNING_CFLAGS@ +WARNING_CXXFLAGS = @WARNING_CXXFLAGS@ +WARNING_OBJCFLAGS = @WARNING_OBJCFLAGS@ +WAVPACK_CFLAGS = @WAVPACK_CFLAGS@ +WAVPACK_LIBS = @WAVPACK_LIBS@ +XDAMAGE_CFLAGS = @XDAMAGE_CFLAGS@ +XDAMAGE_LIBS = @XDAMAGE_LIBS@ +XFIXES_CFLAGS = @XFIXES_CFLAGS@ +XFIXES_LIBS = @XFIXES_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +XMKMF = @XMKMF@ +XSHM_LIBS = @XSHM_LIBS@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +ZLIB_LIBS = @ZLIB_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +ac_ct_OBJC = @ac_ct_OBJC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +plugindir = @plugindir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @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 = libgstrtsp.la +libgstrtsp_la_SOURCES = gstrtsp.c gstrtspsrc.c \ + gstrtpdec.c gstrtspext.c + +libgstrtsp_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(GIO_CFLAGS) +libgstrtsp_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) $(GST_LIBS) $(GST_BASE_LIBS) $(GIO_LIBS) \ + -lgstrtp-@GST_API_VERSION@ -lgstrtsp-@GST_API_VERSION@ \ + -lgstsdp-@GST_API_VERSION@ $(GST_NET_LIBS) $(GST_LIBS) + +libgstrtsp_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstrtsp_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) +noinst_HEADERS = gstrtspsrc.h \ + gstrtsp.h \ + gstrtpdec.h \ + gstrtspext.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/rtsp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu gst/rtsp/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}; \ + } + +libgstrtsp.la: $(libgstrtsp_la_OBJECTS) $(libgstrtsp_la_DEPENDENCIES) $(EXTRA_libgstrtsp_la_DEPENDENCIES) + $(AM_V_CCLD)$(libgstrtsp_la_LINK) -rpath $(plugindir) $(libgstrtsp_la_OBJECTS) $(libgstrtsp_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstrtsp_la-gstrtpdec.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstrtsp_la-gstrtsp.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstrtsp_la-gstrtspext.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstrtsp_la-gstrtspsrc.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 $@ $< + +libgstrtsp_la-gstrtsp.lo: gstrtsp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstrtsp_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstrtsp_la_CFLAGS) $(CFLAGS) -MT libgstrtsp_la-gstrtsp.lo -MD -MP -MF $(DEPDIR)/libgstrtsp_la-gstrtsp.Tpo -c -o libgstrtsp_la-gstrtsp.lo `test -f 'gstrtsp.c' || echo '$(srcdir)/'`gstrtsp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstrtsp_la-gstrtsp.Tpo $(DEPDIR)/libgstrtsp_la-gstrtsp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstrtsp.c' object='libgstrtsp_la-gstrtsp.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 $(libgstrtsp_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstrtsp_la_CFLAGS) $(CFLAGS) -c -o libgstrtsp_la-gstrtsp.lo `test -f 'gstrtsp.c' || echo '$(srcdir)/'`gstrtsp.c + +libgstrtsp_la-gstrtspsrc.lo: gstrtspsrc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstrtsp_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstrtsp_la_CFLAGS) $(CFLAGS) -MT libgstrtsp_la-gstrtspsrc.lo -MD -MP -MF $(DEPDIR)/libgstrtsp_la-gstrtspsrc.Tpo -c -o libgstrtsp_la-gstrtspsrc.lo `test -f 'gstrtspsrc.c' || echo '$(srcdir)/'`gstrtspsrc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstrtsp_la-gstrtspsrc.Tpo $(DEPDIR)/libgstrtsp_la-gstrtspsrc.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstrtspsrc.c' object='libgstrtsp_la-gstrtspsrc.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 $(libgstrtsp_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstrtsp_la_CFLAGS) $(CFLAGS) -c -o libgstrtsp_la-gstrtspsrc.lo `test -f 'gstrtspsrc.c' || echo '$(srcdir)/'`gstrtspsrc.c + +libgstrtsp_la-gstrtpdec.lo: gstrtpdec.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstrtsp_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstrtsp_la_CFLAGS) $(CFLAGS) -MT libgstrtsp_la-gstrtpdec.lo -MD -MP -MF $(DEPDIR)/libgstrtsp_la-gstrtpdec.Tpo -c -o libgstrtsp_la-gstrtpdec.lo `test -f 'gstrtpdec.c' || echo '$(srcdir)/'`gstrtpdec.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstrtsp_la-gstrtpdec.Tpo $(DEPDIR)/libgstrtsp_la-gstrtpdec.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstrtpdec.c' object='libgstrtsp_la-gstrtpdec.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 $(libgstrtsp_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstrtsp_la_CFLAGS) $(CFLAGS) -c -o libgstrtsp_la-gstrtpdec.lo `test -f 'gstrtpdec.c' || echo '$(srcdir)/'`gstrtpdec.c + +libgstrtsp_la-gstrtspext.lo: gstrtspext.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstrtsp_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstrtsp_la_CFLAGS) $(CFLAGS) -MT libgstrtsp_la-gstrtspext.lo -MD -MP -MF $(DEPDIR)/libgstrtsp_la-gstrtspext.Tpo -c -o libgstrtsp_la-gstrtspext.lo `test -f 'gstrtspext.c' || echo '$(srcdir)/'`gstrtspext.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstrtsp_la-gstrtspext.Tpo $(DEPDIR)/libgstrtsp_la-gstrtspext.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstrtspext.c' object='libgstrtsp_la-gstrtspext.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 $(libgstrtsp_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstrtsp_la_CFLAGS) $(CFLAGS) -c -o libgstrtsp_la-gstrtspext.lo `test -f 'gstrtspext.c' || echo '$(srcdir)/'`gstrtspext.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 + + +# 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/rtsp/README b/gst/rtsp/README new file mode 100755 index 0000000..0245891 --- /dev/null +++ b/gst/rtsp/README @@ -0,0 +1,377 @@ +RTSP source +----------- + +The RTSP source establishes a connection to an RTSP server and sets up +the UDP sources and RTP session handlers. + +An RTSP session is created as follows: + +- Parse RTSP URL: + + ex: + rtsp://thread:5454/south-rtsp.mp3 + +- Open a connection to the server with the url. All further conversation with + the server should be done with this connection. Each request/reply has + a CSeq number added to the header. + +- Send a DESCRIBE request for the url. We currently support a response in + SDP. + + ex: + + >> DESCRIBE rtsp://thread:5454/south-rtsp.mp3 RTSP/1.0 + >> Accept: application/sdp + >> CSeq: 0 + >> + << RTSP/1.0 200 OK + << Content-Length: 84 + << Content-Type: application/sdp + << CSeq: 0 + << Date: Wed May 11 13:09:37 2005 GMT + << + << v=0 + << o=- 0 0 IN IP4 192.168.1.1 + << s=No Title + << m=audio 0 RTP/AVP 14 + << a=control:streamid=0 + +- Parse the SDP message, for each stream (m=) we create an GstRTPStream. We need + to allocate two local UDP ports for receiving the RTP and RTCP data because we + need to send the port numbers to the server in the next request. + + In RTSPSrc we first create an element that can handle the udp://0.0.0.0:0 uri. This + will create an udp source element with a random port number. We get the port + number by getting the "port" property of the element after setting the element to + PAUSED. This element is used for the RTP packets and has to be an even number. If + the random port number is not an even number we retry to allocate a new udp source. + + We then create another UDP source element with the next (uneven) port number to + receive the RTCP packet on. After this step we have two udp ports we can use to + accept RTP packets. + + +-----------------+ + | +------------+ | + | | udpsrc0 | | + | | port=5000 | | + | +------------+ | + | +------------+ | + | | udpsrc1 | | + | | port=5001 | | + | +------------+ | + +-----------------+ + +- Send a SETUP message to the server with the RTP ports. We get the setup URI from + the a= attribute in the SDP message. This can be an absolute URL or a relative + url. + + ex: + + >> SETUP rtsp://thread:5454/south-rtsp.mp3/streamid=0 RTSP/1.0 + >> CSeq: 1 + >> Transport: RTP/AVP/UDP;unicast;client_port=5000-5001,RTP/AVP/UDP;multicast,RTP/AVP/TCP + >> + << RTSP/1.0 200 OK + << Transport: RTP/AVP/UDP;unicast;client_port=5000-5001;server_port=6000-6001 + << CSeq: 1 + << Date: Wed May 11 13:21:43 2005 GMT + << Session: 5d5cb94413288ccd + << + + The client needs to send the local ports to the server along with the supported + transport types. The server selects the final transport which it returns in the + Transport header field. The server also includes its ports where RTP and RTCP + messages can be sent to. + + In the above example UDP was choosen as a transport. At this point the RTSPSrc element + will furter configure its elements to process this stream. + + The RTSPSrc will create and connect an RTP session manager element and will + connect it to the src pads of the udp element. The data pad from the RTP session + manager is ghostpadded to RTPSrc. + The RTCP pad of the rtpdec is routed to a new udpsink that sends data to the RTCP + port of the server as returned in the Transport: header field. + + +---------------------------------------------+ + | +------------+ | + | | udpsrc0 | +--------+ | + | | port=5000 ----- rtpdec -------------------- + | +------------+ | | | + | +------------+ | | +------------+ | + | | udpsrc1 ----- RTCP ---- udpsink | | + | | port=5001 | +--------+ | port=6001 | | + | +------------+ +------------+ | + +---------------------------------------------+ + + The output type of rtpdec is configured as the media type specified in the SDP + message. + +- All the elements are set to PAUSED/PLAYING and the PLAY RTSP message is + sent. + + >> PLAY rtsp://thread:5454/south-rtsp.mp3 RTSP/1.0 + >> CSeq: 2 + >> Session: 5d5cb94413288ccd + >> + << RTSP/1.0 200 OK + << CSeq: 2 + << Date: Wed May 11 13:21:43 2005 GMT + << Session: 5d5cb94413288ccd + << + +- The udp source elements receive data from that point and the RTP/RTCP messages + are processed by the elements. + +- In the case of interleaved mode, the SETUP method yields: + + >> SETUP rtsp://thread:5454/south-rtsp.mp3/streamid=0 RTSP/1.0 + >> CSeq: 1 + >> Transport: RTP/AVP/UDP;unicast;client_port=5000-5001,RTP/AVP/UDP;multicast,RTP/AVP/TCP + >> + << RTSP/1.0 200 OK + << Transport: RTP/AVP/TCP;interleaved=0-1 + << CSeq: 1 + << Date: Wed May 11 13:21:43 2005 GMT + << Session: 5d5cb94413288ccd + << + + This means that RTP/RTCP messages will be sent on channel 0/1 respectively and that + the data will be received on the same connection as the RTSP connection. + + At this point, we remove the UDP source elements as we don't need them anymore. We + set up the rtpsess session manager element though as follows: + + +---------------------------------------------+ + | +------------+ | + | | _loop() | +--------+ | + | | ----- rtpses -------------------- + | | | | | | + | | | | | +------------+ | + | | ----- RTCP ---- udpsink | | + | | | +--------+ | port=6001 | | + | +------------+ +------------+ | + +---------------------------------------------+ + + We start an interal task to start reading from the RTSP connection waiting + for data. The received data is then pushed to the rtpdec element. + + When reading from the RTSP connection we receive data packets in the + following layout (see also RFC2326) + + $<1 byte channel><2 bytes length, big endian><length bytes of data> + + +RTSP server +----------- + +An RTSP server listen on a port (default 554) for client connections. The client +typically keeps this channel open during the RTSP session to instruct the server +to pause/play/stop the stream. + +The server exposes a stream consisting of one or more media streams using an +URL. The media streams are typically audio and video. + + ex: + rtsp://thread:5454/alien-rtsp.mpeg + + exposes an audio/video stream. The video is mpeg packetized in RTP and + the audio is mp3 in RTP. + +The streaming server typically uses a different channel to send the media +data to clients, typically using RTP over UDP. It is also possible to stream +the data to the client using the initial RTSP TCP session (the interleaved +mode). This last mode is usufull when the client is behind a firewall but +does not take advantage of the RTP/UDP features. + +In both cases, media data is send to the clients in an unmultiplexed format +packetized as RTP packets. + +The streaming server has to negotiate a connection protocol for each of the +media streams with the client. + +Minimal server requirements: + +- The server should copy the CSeq header field in a client request to the + response so that the client can match the response to the request. + +- The server should keep a session for each client after the client issued + a SETUP command. The client should use the same session id for all future + request to this server. + +- the server must support an OPTIONS request send over the RTSP connection. + + >> OPTIONS * RTSP/1.0 + >> CSeq: 1 + >> + << RTSP/1.0 200 OK + << CSeq: 1 + << Date: Wed May 11 13:21:43 2005 GMT + << Session: 5d5cb94413288ccd + << Public: DESCRIBE, SETUP, TEARDOWN, PLAY + << + + The OPTIONS request should list all supported methods on the server. + + - The server should support the DESCRIBE method. + + >> DESCRIBE rtsp://thread:5454/south-rtsp.mp3 RTSP/1.0 + >> Accept: application/sdp + >> CSeq: 2 + >> + << RTSP/1.0 200 OK + << Content-Length: 84 + << Content-Type: application/sdp + << CSeq: 2 + << Date: Wed May 11 13:09:37 2005 GMT + << + << v=0 + << o=- 0 0 IN IP4 192.168.1.1 + << s=No Title + << m=audio 0 RTP/AVP 14 + << a=control:streamid=0 + + The client issues a DESCRIBE command for a specific URL that corresponds + to an available stream. The client will also send an Accept header to + list its supported formats. + + The server answers this request with a reply in one of the client supported + formats (application/sdp is the most common). The server typically sends a + fixed reply to all clients for each configured stream. + + - The server must support the SETUP command to configure the media streams + that were listed in the DESCRIBE command. + + >> SETUP rtsp://thread:5454/south-rtsp.mp3/streamid=0 RTSP/1.0 + >> CSeq: 3 + >> Transport: RTP/AVP/UDP;unicast;client_port=5000-5001,RTP/AVP/UDP;multicast,RTP/AVP/TCP + >> + << RTSP/1.0 200 OK + << Transport: RTP/AVP/UDP;unicast;client_port=5000-5001;server_port=6000-6001 + << CSeq: 3 + << Date: Wed May 11 13:21:43 2005 GMT + << Session: 5d5cb94413288ccd + + The client will send a SETUP command for each of the streams listed in the + DESCRIBE reply. For sdp will use a URL as listed in the a=control: property. + + The client will list the supported transports in the Transport: header field. + Each transport is separated with a comma (,) and listed in order of preference. + The server has to select the first supported transport. + + In the above example 3 transport are listed: + + RTP/AVP/UDP;unicast;client_port=5000-5001 + + The client will accept RTP over UDP on the port pair 5000-5001. Port + 5000 will accept the RTP packets, 5001 the RTSP packets send by the + server. + + RTP/AVP/UDP;multicast + + The client can join a multicast group for the specific media stream. + The port numbers of the multicast group it will connect to have to + be specified by the server in the reply. + + RTP/AVP/TCP + + the client can accept RTP packets interleaved on the RTSP connection. + + The server selects a supported transport an allocates an RTP port pair to + receive RTP and RTSP data from the client. This last step is optional when + the server does not accept RTP data. + + The server should allocate a session for the client and should send the + sessionId to the client. The client should use this session id for all + future requests. + + The server may refuse a client that does not use the same transport method + for all media streams. + + The server stores all client port pairs in the server client session along + with the transport method. + + ex: + + For an on-demand stream the server could construct a (minimal) RTP GStreamer + pipeline for the client as follows (for an mp3 stream): + + +---------+ +-----------+ +------------+ +-------------+ + | filesrc | | rtpmp3enc | | rtpsession | | udpsink | + | | | | | | | host=XXX | + | | | | | | | port=5000 | + | src--sink src--rtpsink rtpsrc--sink | + +---------+ +-----------+ | | +-------------+ + | | +-------------+ + | | | udpsink | + | | | host=XXX | + | | | port=5001 | + | rtspsrc--sink | + +------------+ +-------------+ + + The server would set the above pipeline to PAUSE to make sure no data + is sent to the client yet. + + optionally udpsrc elements can be configured to receive client RTP and + RTSP messages. + + ex: + + For a live stream the server could construct a (minimal) RTP GStreamer + pipeline for the clients as follows (for an mp3 stream): + + +---------+ +--------+ +-----------+ +------------+ +--------------+ + | source | | mp3enc | | rtpmp3enc | | rtpsession | | multiudpsink | + | | | | | | | | | host=... | + | | | | | | | | | port=... | + | src--sink src--sink src--rtpsink rtpsrc--sink | + +---------+ +--------+ +-----------+ | | +--------------+ + | | +--------------+ + | | | multiudpsink | + | | | host=... | + | | | port=... | + | rtspsrc--sink | + +------------+ +--------------+ + + Media data is streamed to clients by adding the client host and port numbers + to the multiudpsinks. + + optionally udpsrc elements can be configured to receive client RTP and + RTSP messages. + + - The server must support the PLAY command to start playback of the configured + media streams. + + >> PLAY rtsp://thread:5454/south-rtsp.mp3 RTSP/1.0 + >> CSeq: 2 + >> Session: 5d5cb94413288ccd + >> + << RTSP/1.0 200 OK + << CSeq: 2 + << Date: Wed May 11 13:21:43 2005 GMT + << Session: 5d5cb94413288ccd + << + + Using the Session: header field, the server finds the pipeline of the session + to PLAY and sets the pipeline to PLAYING at which point the client receives + the media stream data. + + In case of a live stream, the server adds the port numbers to a multiudpsink + element. + + - The server must support the TEARDOWN command to stop playback and free the + session of a client. + + >> TEARDOWN rtsp://thread:5454/south-rtsp.mp3 RTSP/1.0 + >> CSeq: 4 + >> Session: 5d5cb94413288ccd + >> + << RTSP/1.0 200 OK + << CSeq: 4 + << Date: Wed May 11 13:21:43 2005 GMT + << + + The server destroys the client pipeline in case of an on-demand stream or + removes the client ports from the multiudpsinks. This effectively stops + streaming to the client. + + diff --git a/gst/rtsp/gstrtpdec.c b/gst/rtsp/gstrtpdec.c new file mode 100755 index 0000000..e24927b --- /dev/null +++ b/gst/rtsp/gstrtpdec.c @@ -0,0 +1,897 @@ +/* GStreamer + * Copyright (C) <2005,2006> 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. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/* Element-Checklist-Version: 5 */ + +/** + * SECTION:element-rtpdec + * + * A simple RTP session manager used internally by rtspsrc. + */ + +/* #define HAVE_RTCP */ + +#include <gst/rtp/gstrtpbuffer.h> + +#ifdef HAVE_RTCP +#include <gst/rtp/gstrtcpbuffer.h> +#endif + +#include "gstrtpdec.h" +#include <stdio.h> + +GST_DEBUG_CATEGORY_STATIC (rtpdec_debug); +#define GST_CAT_DEFAULT (rtpdec_debug) + +/* GstRTPDec signals and args */ +enum +{ + SIGNAL_REQUEST_PT_MAP, + SIGNAL_CLEAR_PT_MAP, + + SIGNAL_ON_NEW_SSRC, + SIGNAL_ON_SSRC_COLLISION, + SIGNAL_ON_SSRC_VALIDATED, + SIGNAL_ON_BYE_SSRC, + SIGNAL_ON_BYE_TIMEOUT, + SIGNAL_ON_TIMEOUT, + LAST_SIGNAL +}; + +#define DEFAULT_LATENCY_MS 200 + +enum +{ + PROP_0, + PROP_LATENCY +}; + +static GstStaticPadTemplate gst_rtp_dec_recv_rtp_sink_template = +GST_STATIC_PAD_TEMPLATE ("recv_rtp_sink_%u", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("application/x-rtp") + ); + +static GstStaticPadTemplate gst_rtp_dec_recv_rtcp_sink_template = +GST_STATIC_PAD_TEMPLATE ("recv_rtcp_sink_%u", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("application/x-rtcp") + ); + +static GstStaticPadTemplate gst_rtp_dec_recv_rtp_src_template = +GST_STATIC_PAD_TEMPLATE ("recv_rtp_src_%u_%u_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("application/x-rtp") + ); + +static GstStaticPadTemplate gst_rtp_dec_rtcp_src_template = +GST_STATIC_PAD_TEMPLATE ("rtcp_src_%u", + GST_PAD_SRC, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("application/x-rtcp") + ); + +static void gst_rtp_dec_finalize (GObject * object); +static void gst_rtp_dec_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_rtp_dec_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstClock *gst_rtp_dec_provide_clock (GstElement * element); +static GstStateChangeReturn gst_rtp_dec_change_state (GstElement * element, + GstStateChange transition); +static GstPad *gst_rtp_dec_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name, const GstCaps * caps); +static void gst_rtp_dec_release_pad (GstElement * element, GstPad * pad); + +static GstFlowReturn gst_rtp_dec_chain_rtp (GstPad * pad, GstObject * parent, + GstBuffer * buffer); +static GstFlowReturn gst_rtp_dec_chain_rtcp (GstPad * pad, GstObject * parent, + GstBuffer * buffer); + + +/* Manages the receiving end of the packets. + * + * There is one such structure for each RTP session (audio/video/...). + * We get the RTP/RTCP packets and stuff them into the session manager. + */ +struct _GstRTPDecSession +{ + /* session id */ + gint id; + /* the parent bin */ + GstRTPDec *dec; + + gboolean active; + /* we only support one ssrc and one pt */ + guint32 ssrc; + guint8 pt; + GstCaps *caps; + + /* the pads of the session */ + GstPad *recv_rtp_sink; + GstPad *recv_rtp_src; + GstPad *recv_rtcp_sink; + GstPad *rtcp_src; +}; + +/* find a session with the given id */ +static GstRTPDecSession * +find_session_by_id (GstRTPDec * rtpdec, gint id) +{ + GSList *walk; + + for (walk = rtpdec->sessions; walk; walk = g_slist_next (walk)) { + GstRTPDecSession *sess = (GstRTPDecSession *) walk->data; + + if (sess->id == id) + return sess; + } + return NULL; +} + +/* create a session with the given id */ +static GstRTPDecSession * +create_session (GstRTPDec * rtpdec, gint id) +{ + GstRTPDecSession *sess; + + sess = g_new0 (GstRTPDecSession, 1); + sess->id = id; + sess->dec = rtpdec; + rtpdec->sessions = g_slist_prepend (rtpdec->sessions, sess); + + return sess; +} + +static void +free_session (GstRTPDecSession * session) +{ + g_free (session); +} + +static guint gst_rtp_dec_signals[LAST_SIGNAL] = { 0 }; + +#define gst_rtp_dec_parent_class parent_class +G_DEFINE_TYPE (GstRTPDec, gst_rtp_dec, GST_TYPE_ELEMENT); + +static void +gst_rtp_dec_class_init (GstRTPDecClass * g_class) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstRTPDecClass *klass; + + klass = (GstRTPDecClass *) g_class; + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + GST_DEBUG_CATEGORY_INIT (rtpdec_debug, "rtpdec", 0, "RTP decoder"); + + gobject_class->finalize = gst_rtp_dec_finalize; + gobject_class->set_property = gst_rtp_dec_set_property; + gobject_class->get_property = gst_rtp_dec_get_property; + + g_object_class_install_property (gobject_class, PROP_LATENCY, + g_param_spec_uint ("latency", "Buffer latency in ms", + "Amount of ms to buffer", 0, G_MAXUINT, DEFAULT_LATENCY_MS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTPDec::request-pt-map: + * @rtpdec: the object which received the signal + * @session: the session + * @pt: the pt + * + * Request the payload type as #GstCaps for @pt in @session. + */ + gst_rtp_dec_signals[SIGNAL_REQUEST_PT_MAP] = + g_signal_new ("request-pt-map", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTPDecClass, request_pt_map), + NULL, NULL, g_cclosure_marshal_generic, GST_TYPE_CAPS, 2, G_TYPE_UINT, + G_TYPE_UINT); + + gst_rtp_dec_signals[SIGNAL_CLEAR_PT_MAP] = + g_signal_new ("clear-pt-map", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTPDecClass, clear_pt_map), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); + + /** + * GstRTPDec::on-new-ssrc: + * @rtpbin: the object which received the signal + * @session: the session + * @ssrc: the SSRC + * + * Notify of a new SSRC that entered @session. + */ + gst_rtp_dec_signals[SIGNAL_ON_NEW_SSRC] = + g_signal_new ("on-new-ssrc", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTPDecClass, on_new_ssrc), + NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT, + G_TYPE_UINT); + /** + * GstRTPDec::on-ssrc_collision: + * @rtpbin: the object which received the signal + * @session: the session + * @ssrc: the SSRC + * + * Notify when we have an SSRC collision + */ + gst_rtp_dec_signals[SIGNAL_ON_SSRC_COLLISION] = + g_signal_new ("on-ssrc-collision", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTPDecClass, on_ssrc_collision), + NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT, + G_TYPE_UINT); + /** + * GstRTPDec::on-ssrc_validated: + * @rtpbin: the object which received the signal + * @session: the session + * @ssrc: the SSRC + * + * Notify of a new SSRC that became validated. + */ + gst_rtp_dec_signals[SIGNAL_ON_SSRC_VALIDATED] = + g_signal_new ("on-ssrc-validated", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTPDecClass, on_ssrc_validated), + NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT, + G_TYPE_UINT); + + /** + * GstRTPDec::on-bye-ssrc: + * @rtpbin: the object which received the signal + * @session: the session + * @ssrc: the SSRC + * + * Notify of an SSRC that became inactive because of a BYE packet. + */ + gst_rtp_dec_signals[SIGNAL_ON_BYE_SSRC] = + g_signal_new ("on-bye-ssrc", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTPDecClass, on_bye_ssrc), + NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT, + G_TYPE_UINT); + /** + * GstRTPDec::on-bye-timeout: + * @rtpbin: the object which received the signal + * @session: the session + * @ssrc: the SSRC + * + * Notify of an SSRC that has timed out because of BYE + */ + gst_rtp_dec_signals[SIGNAL_ON_BYE_TIMEOUT] = + g_signal_new ("on-bye-timeout", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTPDecClass, on_bye_timeout), + NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT, + G_TYPE_UINT); + /** + * GstRTPDec::on-timeout: + * @rtpbin: the object which received the signal + * @session: the session + * @ssrc: the SSRC + * + * Notify of an SSRC that has timed out + */ + gst_rtp_dec_signals[SIGNAL_ON_TIMEOUT] = + g_signal_new ("on-timeout", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTPDecClass, on_timeout), + NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT, + G_TYPE_UINT); + + gstelement_class->provide_clock = + GST_DEBUG_FUNCPTR (gst_rtp_dec_provide_clock); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_rtp_dec_change_state); + gstelement_class->request_new_pad = + GST_DEBUG_FUNCPTR (gst_rtp_dec_request_new_pad); + gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_rtp_dec_release_pad); + + /* sink pads */ + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dec_recv_rtp_sink_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dec_recv_rtcp_sink_template)); + /* src pads */ + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dec_recv_rtp_src_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dec_rtcp_src_template)); + + gst_element_class_set_static_metadata (gstelement_class, "RTP Decoder", + "Codec/Parser/Network", + "Accepts raw RTP and RTCP packets and sends them forward", + "Wim Taymans <wim.taymans@gmail.com>"); +} + +static void +gst_rtp_dec_init (GstRTPDec * rtpdec) +{ + rtpdec->provided_clock = gst_system_clock_obtain (); + rtpdec->latency = DEFAULT_LATENCY_MS; + + GST_OBJECT_FLAG_SET (rtpdec, GST_ELEMENT_FLAG_PROVIDE_CLOCK); +} + +static void +gst_rtp_dec_finalize (GObject * object) +{ + GstRTPDec *rtpdec; + + rtpdec = GST_RTP_DEC (object); + + g_slist_foreach (rtpdec->sessions, (GFunc) free_session, NULL); + g_slist_free (rtpdec->sessions); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_rtp_dec_query_src (GstPad * pad, GstObject * parent, GstQuery * query) +{ + gboolean res; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + { + /* we pretend to be live with a 3 second latency */ + /* FIXME: Do we really have infinite maximum latency? */ + gst_query_set_latency (query, TRUE, 3 * GST_SECOND, -1); + res = TRUE; + break; + } + default: + res = gst_pad_query_default (pad, parent, query); + break; + } + return res; +} + +static GstFlowReturn +gst_rtp_dec_chain_rtp (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + GstFlowReturn res; + GstRTPDec *rtpdec; + GstRTPDecSession *session; + guint32 ssrc; + guint8 pt; + GstRTPBuffer rtp = { NULL, }; + + rtpdec = GST_RTP_DEC (parent); + + GST_DEBUG_OBJECT (rtpdec, "got rtp packet"); + + if (!gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp)) + goto bad_packet; + + ssrc = gst_rtp_buffer_get_ssrc (&rtp); + pt = gst_rtp_buffer_get_payload_type (&rtp); + gst_rtp_buffer_unmap (&rtp); + + GST_DEBUG_OBJECT (rtpdec, "SSRC %08x, PT %d", ssrc, pt); + + /* find session */ + session = gst_pad_get_element_private (pad); + + /* see if we have the pad */ + if (!session->active) { + GstPadTemplate *templ; + GstElementClass *klass; + gchar *name; + GstCaps *caps; + GValue ret = { 0 }; + GValue args[3] = { {0} + , {0} + , {0} + }; + + GST_DEBUG_OBJECT (rtpdec, "creating stream"); + + session->ssrc = ssrc; + session->pt = pt; + + /* get pt map */ + g_value_init (&args[0], GST_TYPE_ELEMENT); + g_value_set_object (&args[0], rtpdec); + g_value_init (&args[1], G_TYPE_UINT); + g_value_set_uint (&args[1], session->id); + g_value_init (&args[2], G_TYPE_UINT); + g_value_set_uint (&args[2], pt); + + g_value_init (&ret, GST_TYPE_CAPS); + g_value_set_boxed (&ret, NULL); + + g_signal_emitv (args, gst_rtp_dec_signals[SIGNAL_REQUEST_PT_MAP], 0, &ret); + + caps = (GstCaps *) g_value_get_boxed (&ret); + + name = g_strdup_printf ("recv_rtp_src_%u_%u_%u", session->id, ssrc, pt); + klass = GST_ELEMENT_GET_CLASS (rtpdec); + templ = gst_element_class_get_pad_template (klass, "recv_rtp_src_%u_%u_%u"); + session->recv_rtp_src = gst_pad_new_from_template (templ, name); + g_free (name); + + gst_pad_set_caps (session->recv_rtp_src, caps); + + gst_pad_set_element_private (session->recv_rtp_src, session); + gst_pad_set_query_function (session->recv_rtp_src, gst_rtp_dec_query_src); + gst_pad_set_active (session->recv_rtp_src, TRUE); + gst_element_add_pad (GST_ELEMENT_CAST (rtpdec), session->recv_rtp_src); + + session->active = TRUE; + } + + res = gst_pad_push (session->recv_rtp_src, buffer); + + return res; + +bad_packet: + { + GST_ELEMENT_WARNING (rtpdec, STREAM, DECODE, (NULL), + ("RTP packet did not validate, dropping")); + gst_buffer_unref (buffer); + return GST_FLOW_OK; + } +} + +static GstFlowReturn +gst_rtp_dec_chain_rtcp (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + GstRTPDec *src; + +#ifdef HAVE_RTCP + gboolean valid; + GstRTCPPacket packet; + gboolean more; +#endif + + src = GST_RTP_DEC (parent); + + GST_DEBUG_OBJECT (src, "got rtcp packet"); + +#ifdef HAVE_RTCP + valid = gst_rtcp_buffer_validate (buffer); + if (!valid) + goto bad_packet; + + /* position on first packet */ + more = gst_rtcp_buffer_get_first_packet (buffer, &packet); + while (more) { + switch (gst_rtcp_packet_get_type (&packet)) { + case GST_RTCP_TYPE_SR: + { + guint32 ssrc, rtptime, packet_count, octet_count; + guint64 ntptime; + guint count, i; + + gst_rtcp_packet_sr_get_sender_info (&packet, &ssrc, &ntptime, &rtptime, + &packet_count, &octet_count); + + GST_DEBUG_OBJECT (src, + "got SR packet: SSRC %08x, NTP %" G_GUINT64_FORMAT + ", RTP %u, PC %u, OC %u", ssrc, ntptime, rtptime, packet_count, + octet_count); + + count = gst_rtcp_packet_get_rb_count (&packet); + for (i = 0; i < count; i++) { + guint32 ssrc, exthighestseq, jitter, lsr, dlsr; + guint8 fractionlost; + gint32 packetslost; + + gst_rtcp_packet_get_rb (&packet, i, &ssrc, &fractionlost, + &packetslost, &exthighestseq, &jitter, &lsr, &dlsr); + + GST_DEBUG_OBJECT (src, "got RB packet %d: SSRC %08x, FL %u" + ", PL %u, HS %u, JITTER %u, LSR %u, DLSR %u", ssrc, fractionlost, + packetslost, exthighestseq, jitter, lsr, dlsr); + } + break; + } + case GST_RTCP_TYPE_RR: + { + guint32 ssrc; + guint count, i; + + ssrc = gst_rtcp_packet_rr_get_ssrc (&packet); + + GST_DEBUG_OBJECT (src, "got RR packet: SSRC %08x", ssrc); + + count = gst_rtcp_packet_get_rb_count (&packet); + for (i = 0; i < count; i++) { + guint32 ssrc, exthighestseq, jitter, lsr, dlsr; + guint8 fractionlost; + gint32 packetslost; + + gst_rtcp_packet_get_rb (&packet, i, &ssrc, &fractionlost, + &packetslost, &exthighestseq, &jitter, &lsr, &dlsr); + + GST_DEBUG_OBJECT (src, "got RB packet %d: SSRC %08x, FL %u" + ", PL %u, HS %u, JITTER %u, LSR %u, DLSR %u", ssrc, fractionlost, + packetslost, exthighestseq, jitter, lsr, dlsr); + } + break; + } + case GST_RTCP_TYPE_SDES: + { + guint chunks, i, j; + gboolean more_chunks, more_items; + + chunks = gst_rtcp_packet_sdes_get_chunk_count (&packet); + GST_DEBUG_OBJECT (src, "got SDES packet with %d chunks", chunks); + + more_chunks = gst_rtcp_packet_sdes_first_chunk (&packet); + i = 0; + while (more_chunks) { + guint32 ssrc; + + ssrc = gst_rtcp_packet_sdes_get_ssrc (&packet); + + GST_DEBUG_OBJECT (src, "chunk %d, SSRC %08x", i, ssrc); + + more_items = gst_rtcp_packet_sdes_first_item (&packet); + j = 0; + while (more_items) { + GstRTCPSDESType type; + guint8 len; + gchar *data; + + gst_rtcp_packet_sdes_get_item (&packet, &type, &len, &data); + + GST_DEBUG_OBJECT (src, "item %d, type %d, len %d, data %s", j, + type, len, data); + + more_items = gst_rtcp_packet_sdes_next_item (&packet); + j++; + } + more_chunks = gst_rtcp_packet_sdes_next_chunk (&packet); + i++; + } + break; + } + case GST_RTCP_TYPE_BYE: + { + guint count, i; + gchar *reason; + + reason = gst_rtcp_packet_bye_get_reason (&packet); + GST_DEBUG_OBJECT (src, "got BYE packet (reason: %s)", + GST_STR_NULL (reason)); + g_free (reason); + + count = gst_rtcp_packet_bye_get_ssrc_count (&packet); + for (i = 0; i < count; i++) { + guint32 ssrc; + + + ssrc = gst_rtcp_packet_bye_get_nth_ssrc (&packet, i); + + GST_DEBUG_OBJECT (src, "SSRC: %08x", ssrc); + } + break; + } + case GST_RTCP_TYPE_APP: + GST_DEBUG_OBJECT (src, "got APP packet"); + break; + default: + GST_WARNING_OBJECT (src, "got unknown RTCP packet"); + break; + } + more = gst_rtcp_packet_move_to_next (&packet); + } + gst_buffer_unref (buffer); + return GST_FLOW_OK; + +bad_packet: + { + GST_WARNING_OBJECT (src, "got invalid RTCP packet"); + gst_buffer_unref (buffer); + return GST_FLOW_OK; + } +#else + gst_buffer_unref (buffer); + return GST_FLOW_OK; +#endif +} + +static void +gst_rtp_dec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRTPDec *src; + + src = GST_RTP_DEC (object); + + switch (prop_id) { + case PROP_LATENCY: + src->latency = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_dec_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstRTPDec *src; + + src = GST_RTP_DEC (object); + + switch (prop_id) { + case PROP_LATENCY: + g_value_set_uint (value, src->latency); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstClock * +gst_rtp_dec_provide_clock (GstElement * element) +{ + GstRTPDec *rtpdec; + + rtpdec = GST_RTP_DEC (element); + + return GST_CLOCK_CAST (gst_object_ref (rtpdec->provided_clock)); +} + +static GstStateChangeReturn +gst_rtp_dec_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + + switch (transition) { + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + /* we're NO_PREROLL when going to PAUSED */ + ret = GST_STATE_CHANGE_NO_PREROLL; + break; + default: + break; + } + + return ret; +} + +/* Create a pad for receiving RTP for the session in @name + */ +static GstPad * +create_recv_rtp (GstRTPDec * rtpdec, GstPadTemplate * templ, const gchar * name) +{ + guint sessid; + GstRTPDecSession *session; + + /* first get the session number */ + if (name == NULL || sscanf (name, "recv_rtp_sink_%u", &sessid) != 1) + goto no_name; + + GST_DEBUG_OBJECT (rtpdec, "finding session %d", sessid); + + /* get or create session */ + session = find_session_by_id (rtpdec, sessid); + if (!session) { + GST_DEBUG_OBJECT (rtpdec, "creating session %d", sessid); + /* create session now */ + session = create_session (rtpdec, sessid); + if (session == NULL) + goto create_error; + } + /* check if pad was requested */ + if (session->recv_rtp_sink != NULL) + goto existed; + + GST_DEBUG_OBJECT (rtpdec, "getting RTP sink pad"); + + session->recv_rtp_sink = gst_pad_new_from_template (templ, name); + gst_pad_set_element_private (session->recv_rtp_sink, session); + gst_pad_set_chain_function (session->recv_rtp_sink, gst_rtp_dec_chain_rtp); + gst_pad_set_active (session->recv_rtp_sink, TRUE); + gst_element_add_pad (GST_ELEMENT_CAST (rtpdec), session->recv_rtp_sink); + + return session->recv_rtp_sink; + + /* ERRORS */ +no_name: + { + g_warning ("rtpdec: invalid name given"); + return NULL; + } +create_error: + { + /* create_session already warned */ + return NULL; + } +existed: + { + g_warning ("rtpdec: recv_rtp pad already requested for session %d", sessid); + return NULL; + } +} + +/* Create a pad for receiving RTCP for the session in @name + */ +static GstPad * +create_recv_rtcp (GstRTPDec * rtpdec, GstPadTemplate * templ, + const gchar * name) +{ + guint sessid; + GstRTPDecSession *session; + + /* first get the session number */ + if (name == NULL || sscanf (name, "recv_rtcp_sink_%u", &sessid) != 1) + goto no_name; + + GST_DEBUG_OBJECT (rtpdec, "finding session %d", sessid); + + /* get the session, it must exist or we error */ + session = find_session_by_id (rtpdec, sessid); + if (!session) + goto no_session; + + /* check if pad was requested */ + if (session->recv_rtcp_sink != NULL) + goto existed; + + GST_DEBUG_OBJECT (rtpdec, "getting RTCP sink pad"); + + session->recv_rtcp_sink = gst_pad_new_from_template (templ, name); + gst_pad_set_element_private (session->recv_rtp_sink, session); + gst_pad_set_chain_function (session->recv_rtcp_sink, gst_rtp_dec_chain_rtcp); + gst_pad_set_active (session->recv_rtcp_sink, TRUE); + gst_element_add_pad (GST_ELEMENT_CAST (rtpdec), session->recv_rtcp_sink); + + return session->recv_rtcp_sink; + + /* ERRORS */ +no_name: + { + g_warning ("rtpdec: invalid name given"); + return NULL; + } +no_session: + { + g_warning ("rtpdec: no session with id %d", sessid); + return NULL; + } +existed: + { + g_warning ("rtpdec: recv_rtcp pad already requested for session %d", + sessid); + return NULL; + } +} + +/* Create a pad for sending RTCP for the session in @name + */ +static GstPad * +create_rtcp (GstRTPDec * rtpdec, GstPadTemplate * templ, const gchar * name) +{ + guint sessid; + GstRTPDecSession *session; + + /* first get the session number */ + if (name == NULL || sscanf (name, "rtcp_src_%u", &sessid) != 1) + goto no_name; + + /* get or create session */ + session = find_session_by_id (rtpdec, sessid); + if (!session) + goto no_session; + + /* check if pad was requested */ + if (session->rtcp_src != NULL) + goto existed; + + session->rtcp_src = gst_pad_new_from_template (templ, name); + gst_pad_set_active (session->rtcp_src, TRUE); + gst_element_add_pad (GST_ELEMENT_CAST (rtpdec), session->rtcp_src); + + return session->rtcp_src; + + /* ERRORS */ +no_name: + { + g_warning ("rtpdec: invalid name given"); + return NULL; + } +no_session: + { + g_warning ("rtpdec: session with id %d does not exist", sessid); + return NULL; + } +existed: + { + g_warning ("rtpdec: rtcp_src pad already requested for session %d", sessid); + return NULL; + } +} + +/* + */ +static GstPad * +gst_rtp_dec_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name, const GstCaps * caps) +{ + GstRTPDec *rtpdec; + GstElementClass *klass; + GstPad *result; + + g_return_val_if_fail (templ != NULL, NULL); + g_return_val_if_fail (GST_IS_RTP_DEC (element), NULL); + + rtpdec = GST_RTP_DEC (element); + klass = GST_ELEMENT_GET_CLASS (element); + + /* figure out the template */ + if (templ == gst_element_class_get_pad_template (klass, "recv_rtp_sink_%u")) { + result = create_recv_rtp (rtpdec, templ, name); + } else if (templ == gst_element_class_get_pad_template (klass, + "recv_rtcp_sink_%u")) { + result = create_recv_rtcp (rtpdec, templ, name); + } else if (templ == gst_element_class_get_pad_template (klass, "rtcp_src_%u")) { + result = create_rtcp (rtpdec, templ, name); + } else + goto wrong_template; + + return result; + + /* ERRORS */ +wrong_template: + { + g_warning ("rtpdec: this is not our template"); + return NULL; + } +} + +static void +gst_rtp_dec_release_pad (GstElement * element, GstPad * pad) +{ +} diff --git a/gst/rtsp/gstrtpdec.h b/gst/rtsp/gstrtpdec.h new file mode 100755 index 0000000..5e83e23 --- /dev/null +++ b/gst/rtsp/gstrtpdec.h @@ -0,0 +1,88 @@ +/* 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. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __GST_RTP_DEC_H__ +#define __GST_RTP_DEC_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define GST_TYPE_RTP_DEC (gst_rtp_dec_get_type()) +#define GST_IS_RTP_DEC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DEC)) +#define GST_IS_RTP_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DEC)) +#define GST_RTP_DEC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DEC, GstRTPDec)) +#define GST_RTP_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DEC, GstRTPDecClass)) + +typedef struct _GstRTPDec GstRTPDec; +typedef struct _GstRTPDecClass GstRTPDecClass; +typedef struct _GstRTPDecSession GstRTPDecSession; + +struct _GstRTPDec { + GstElement element; + + guint latency; + GSList *sessions; + GstClock *provided_clock; +}; + +struct _GstRTPDecClass { + GstElementClass parent_class; + + /* get the caps for pt */ + GstCaps* (*request_pt_map) (GstRTPDec *rtpdec, guint session, guint pt); + + void (*clear_pt_map) (GstRTPDec *rtpdec); + + void (*on_new_ssrc) (GstRTPDec *rtpdec, guint session, guint32 ssrc); + void (*on_ssrc_collision) (GstRTPDec *rtpdec, guint session, guint32 ssrc); + void (*on_ssrc_validated) (GstRTPDec *rtpdec, guint session, guint32 ssrc); + void (*on_bye_ssrc) (GstRTPDec *rtpdec, guint session, guint32 ssrc); + void (*on_bye_timeout) (GstRTPDec *rtpdec, guint session, guint32 ssrc); + void (*on_timeout) (GstRTPDec *rtpdec, guint session, guint32 ssrc); +}; + +GType gst_rtp_dec_get_type(void); + +G_END_DECLS + +#endif /* __GST_RTP_DEC_H__ */ diff --git a/gst/rtsp/gstrtsp.c b/gst/rtsp/gstrtsp.c new file mode 100755 index 0000000..a2d568a --- /dev/null +++ b/gst/rtsp/gstrtsp.c @@ -0,0 +1,74 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * <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. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gst/gst-i18n-plugin.h" + +#include "gstrtpdec.h" +#include "gstrtspsrc.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ +#ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +#endif /* ENABLE_NLS */ + + if (!gst_element_register (plugin, "rtspsrc", GST_RANK_PRIMARY, + GST_TYPE_RTSPSRC)) + return FALSE; + if (!gst_element_register (plugin, "rtpdec", GST_RANK_NONE, GST_TYPE_RTP_DEC)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + rtsp, + "transfer data via RTSP", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/rtsp/gstrtsp.h b/gst/rtsp/gstrtsp.h new file mode 100755 index 0000000..e0f5ef8 --- /dev/null +++ b/gst/rtsp/gstrtsp.h @@ -0,0 +1,53 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * <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. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __GST_RTSP_H__ +#define __GST_RTSP_H__ + +G_BEGIN_DECLS + + +G_END_DECLS + +#endif /* __GST_RTSP_H__ */ + diff --git a/gst/rtsp/gstrtspext.c b/gst/rtsp/gstrtspext.c new file mode 100755 index 0000000..07b5a97 --- /dev/null +++ b/gst/rtsp/gstrtspext.c @@ -0,0 +1,268 @@ +/* GStreamer + * Copyright (C) <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. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "gstrtspext.h" + +GST_DEBUG_CATEGORY_STATIC (rtspext_debug); +#define GST_CAT_DEFAULT (rtspext_debug) + +static GList *extensions; + +static gboolean +gst_rtsp_ext_list_filter (GstPluginFeature * feature, gpointer user_data) +{ + GstElementFactory *factory; + guint rank; + + /* we only care about element factories */ + if (!GST_IS_ELEMENT_FACTORY (feature)) + return FALSE; + + factory = GST_ELEMENT_FACTORY (feature); + + if (!gst_element_factory_has_interface (factory, "GstRTSPExtension")) + return FALSE; + + /* only select elements with autoplugging rank */ + rank = gst_plugin_feature_get_rank (feature); + if (rank < GST_RANK_MARGINAL) + return FALSE; + + return TRUE; +} + +void +gst_rtsp_ext_list_init (void) +{ + GST_DEBUG_CATEGORY_INIT (rtspext_debug, "rtspext", 0, "RTSP extension"); + + /* get a list of all extensions */ + extensions = gst_registry_feature_filter (gst_registry_get (), + (GstPluginFeatureFilter) gst_rtsp_ext_list_filter, FALSE, NULL); +} + +GstRTSPExtensionList * +gst_rtsp_ext_list_get (void) +{ + GstRTSPExtensionList *result; + GList *walk; + + result = g_new0 (GstRTSPExtensionList, 1); + + for (walk = extensions; walk; walk = g_list_next (walk)) { + GstElementFactory *factory = GST_ELEMENT_FACTORY (walk->data); + GstElement *element; + + element = gst_element_factory_create (factory, NULL); + if (!element) { + GST_ERROR ("could not create extension instance"); + continue; + } + + GST_DEBUG ("added extension interface for '%s'", + GST_ELEMENT_NAME (element)); + result->extensions = g_list_prepend (result->extensions, element); + } + return result; +} + +void +gst_rtsp_ext_list_free (GstRTSPExtensionList * ext) +{ + GList *walk; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + gst_object_unref (GST_OBJECT_CAST (elem)); + } + g_list_free (ext->extensions); + g_free (ext); +} + +gboolean +gst_rtsp_ext_list_detect_server (GstRTSPExtensionList * ext, + GstRTSPMessage * resp) +{ + GList *walk; + gboolean res = TRUE; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + res = gst_rtsp_extension_detect_server (elem, resp); + } + return res; +} + +GstRTSPResult +gst_rtsp_ext_list_before_send (GstRTSPExtensionList * ext, GstRTSPMessage * req) +{ + GList *walk; + GstRTSPResult res = GST_RTSP_OK; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + res = gst_rtsp_extension_before_send (elem, req); + } + return res; +} + +GstRTSPResult +gst_rtsp_ext_list_after_send (GstRTSPExtensionList * ext, GstRTSPMessage * req, + GstRTSPMessage * resp) +{ + GList *walk; + GstRTSPResult res = GST_RTSP_OK; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + res = gst_rtsp_extension_after_send (elem, req, resp); + } + return res; +} + +GstRTSPResult +gst_rtsp_ext_list_parse_sdp (GstRTSPExtensionList * ext, GstSDPMessage * sdp, + GstStructure * s) +{ + GList *walk; + GstRTSPResult res = GST_RTSP_OK; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + res = gst_rtsp_extension_parse_sdp (elem, sdp, s); + } + return res; +} + +GstRTSPResult +gst_rtsp_ext_list_setup_media (GstRTSPExtensionList * ext, GstSDPMedia * media) +{ + GList *walk; + GstRTSPResult res = GST_RTSP_OK; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + res = gst_rtsp_extension_setup_media (elem, media); + } + return res; +} + +gboolean +gst_rtsp_ext_list_configure_stream (GstRTSPExtensionList * ext, GstCaps * caps) +{ + GList *walk; + gboolean res = TRUE; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + res = gst_rtsp_extension_configure_stream (elem, caps); + if (!res) + break; + } + return res; +} + +GstRTSPResult +gst_rtsp_ext_list_get_transports (GstRTSPExtensionList * ext, + GstRTSPLowerTrans protocols, gchar ** transport) +{ + GList *walk; + GstRTSPResult res = GST_RTSP_OK; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + res = gst_rtsp_extension_get_transports (elem, protocols, transport); + } + return res; +} + +GstRTSPResult +gst_rtsp_ext_list_stream_select (GstRTSPExtensionList * ext, GstRTSPUrl * url) +{ + GList *walk; + GstRTSPResult res = GST_RTSP_OK; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + res = gst_rtsp_extension_stream_select (elem, url); + } + return res; +} + +void +gst_rtsp_ext_list_connect (GstRTSPExtensionList * ext, + const gchar * detailed_signal, GCallback c_handler, gpointer data) +{ + GList *walk; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + g_signal_connect (elem, detailed_signal, c_handler, data); + } +} + +GstRTSPResult +gst_rtsp_ext_list_receive_request (GstRTSPExtensionList * ext, + GstRTSPMessage * req) +{ + GList *walk; + GstRTSPResult res = GST_RTSP_ENOTIMPL; + + for (walk = ext->extensions; walk; walk = g_list_next (walk)) { + GstRTSPExtension *elem = (GstRTSPExtension *) walk->data; + + res = gst_rtsp_extension_receive_request (elem, req); + if (res != GST_RTSP_ENOTIMPL) + break; + } + return res; +} diff --git a/gst/rtsp/gstrtspext.h b/gst/rtsp/gstrtspext.h new file mode 100755 index 0000000..2e87796 --- /dev/null +++ b/gst/rtsp/gstrtspext.h @@ -0,0 +1,83 @@ +/* GStreamer + * Copyright (C) <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. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __GST_RTSP_EXT_H__ +#define __GST_RTSP_EXT_H__ + +#include <gst/gst.h> +#include <gst/rtsp/gstrtspextension.h> + +G_BEGIN_DECLS + +typedef struct _GstRTSPExtensionList GstRTSPExtensionList; + +struct _GstRTSPExtensionList +{ + GList *extensions; +}; + +void gst_rtsp_ext_list_init (void); + +GstRTSPExtensionList * gst_rtsp_ext_list_get (void); +void gst_rtsp_ext_list_free (GstRTSPExtensionList *ext); + +gboolean gst_rtsp_ext_list_detect_server (GstRTSPExtensionList *ext, GstRTSPMessage *resp); + +GstRTSPResult gst_rtsp_ext_list_before_send (GstRTSPExtensionList *ext, GstRTSPMessage *req); +GstRTSPResult gst_rtsp_ext_list_after_send (GstRTSPExtensionList *ext, GstRTSPMessage *req, + GstRTSPMessage *resp); +GstRTSPResult gst_rtsp_ext_list_parse_sdp (GstRTSPExtensionList *ext, GstSDPMessage *sdp, + GstStructure *s); +GstRTSPResult gst_rtsp_ext_list_setup_media (GstRTSPExtensionList *ext, GstSDPMedia *media); +gboolean gst_rtsp_ext_list_configure_stream (GstRTSPExtensionList *ext, GstCaps *caps); +GstRTSPResult gst_rtsp_ext_list_get_transports (GstRTSPExtensionList *ext, GstRTSPLowerTrans protocols, + gchar **transport); +GstRTSPResult gst_rtsp_ext_list_stream_select (GstRTSPExtensionList *ext, GstRTSPUrl *url); + +void gst_rtsp_ext_list_connect (GstRTSPExtensionList *ext, + const gchar *detailed_signal, GCallback c_handler, + gpointer data); +GstRTSPResult gst_rtsp_ext_list_receive_request (GstRTSPExtensionList *ext, GstRTSPMessage *req); + +G_END_DECLS + +#endif /* __GST_RTSP_EXT_H__ */ diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c new file mode 100755 index 0000000..b3b2a2e --- /dev/null +++ b/gst/rtsp/gstrtspsrc.c @@ -0,0 +1,8317 @@ +/* GStreamer + * Copyright (C) <2005,2006> Wim Taymans <wim at fluendo dot com> + * <2006> Lutz Mueller <lutz at topfrose dot de> + * + * 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. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * SECTION:element-rtspsrc + * + * Makes a connection to an RTSP server and read the data. + * rtspsrc strictly follows RFC 2326 and therefore does not (yet) support + * RealMedia/Quicktime/Microsoft extensions. + * + * RTSP supports transport over TCP or UDP in unicast or multicast mode. By + * default rtspsrc will negotiate a connection in the following order: + * UDP unicast/UDP multicast/TCP. The order cannot be changed but the allowed + * protocols can be controlled with the #GstRTSPSrc:protocols property. + * + * rtspsrc currently understands SDP as the format of the session description. + * For each stream listed in the SDP a new rtp_stream\%d pad will be created + * with caps derived from the SDP media description. This is a caps of mime type + * "application/x-rtp" that can be connected to any available RTP depayloader + * element. + * + * rtspsrc will internally instantiate an RTP session manager element + * that will handle the RTCP messages to and from the server, jitter removal, + * packet reordering along with providing a clock for the pipeline. + * This feature is implemented using the gstrtpbin element. + * + * rtspsrc acts like a live source and will therefore only generate data in the + * PLAYING state. + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch-1.0 rtspsrc location=rtsp://some.server/url ! fakesink + * ]| Establish a connection to an RTSP server and send the raw RTP packets to a + * fakesink. + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif /* HAVE_UNISTD_H */ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> + +#include <gst/net/gstnet.h> +#include <gst/sdp/gstsdpmessage.h> +#include <gst/sdp/gstmikey.h> +#include <gst/rtp/gstrtppayloads.h> + +#include "gst/gst-i18n-plugin.h" + +#include "gstrtspsrc.h" + +GST_DEBUG_CATEGORY_STATIC (rtspsrc_debug); +#define GST_CAT_DEFAULT (rtspsrc_debug) + +static GstStaticPadTemplate rtptemplate = GST_STATIC_PAD_TEMPLATE ("stream_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("application/x-rtp; application/x-rdt")); + +/* templates used internally */ +static GstStaticPadTemplate anysrctemplate = +GST_STATIC_PAD_TEMPLATE ("internalsrc_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate anysinktemplate = +GST_STATIC_PAD_TEMPLATE ("internalsink_%u", + GST_PAD_SINK, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +enum +{ + SIGNAL_HANDLE_REQUEST, + SIGNAL_ON_SDP, + SIGNAL_SELECT_STREAM, + SIGNAL_NEW_MANAGER, + SIGNAL_REQUEST_RTCP_KEY, + LAST_SIGNAL +}; + +enum _GstRtspSrcRtcpSyncMode +{ + RTCP_SYNC_ALWAYS, + RTCP_SYNC_INITIAL, + RTCP_SYNC_RTP +}; + +enum _GstRtspSrcBufferMode +{ + BUFFER_MODE_NONE, + BUFFER_MODE_SLAVE, + BUFFER_MODE_BUFFER, + BUFFER_MODE_AUTO, + BUFFER_MODE_SYNCED +}; + +#define GST_TYPE_RTSP_SRC_BUFFER_MODE (gst_rtsp_src_buffer_mode_get_type()) +static GType +gst_rtsp_src_buffer_mode_get_type (void) +{ + static GType buffer_mode_type = 0; + static const GEnumValue buffer_modes[] = { + {BUFFER_MODE_NONE, "Only use RTP timestamps", "none"}, + {BUFFER_MODE_SLAVE, "Slave receiver to sender clock", "slave"}, + {BUFFER_MODE_BUFFER, "Do low/high watermark buffering", "buffer"}, + {BUFFER_MODE_AUTO, "Choose mode depending on stream live", "auto"}, + {BUFFER_MODE_SYNCED, "Synchronized sender and receiver clocks", "synced"}, + {0, NULL, NULL}, + }; + + if (!buffer_mode_type) { + buffer_mode_type = + g_enum_register_static ("GstRTSPSrcBufferMode", buffer_modes); + } + return buffer_mode_type; +} + +#define AES_128_KEY_LEN 16 +#define AES_256_KEY_LEN 32 + +#define HMAC_32_KEY_LEN 4 +#define HMAC_80_KEY_LEN 10 + +#define DEFAULT_LOCATION NULL +#define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP +#define DEFAULT_DEBUG FALSE +#define DEFAULT_RETRY 20 +#define DEFAULT_TIMEOUT 5000000 +#define DEFAULT_UDP_BUFFER_SIZE 0x80000 +#define DEFAULT_TCP_TIMEOUT 20000000 +#define DEFAULT_LATENCY_MS 2000 +#define DEFAULT_DROP_ON_LATENCY FALSE +#define DEFAULT_CONNECTION_SPEED 0 +#define DEFAULT_NAT_METHOD GST_RTSP_NAT_DUMMY +#define DEFAULT_DO_RTCP TRUE +#define DEFAULT_DO_RTSP_KEEP_ALIVE TRUE +#define DEFAULT_PROXY NULL +#define DEFAULT_RTP_BLOCKSIZE 0 +#define DEFAULT_USER_ID NULL +#define DEFAULT_USER_PW NULL +#define DEFAULT_BUFFER_MODE BUFFER_MODE_AUTO +#define DEFAULT_PORT_RANGE NULL +#define DEFAULT_SHORT_HEADER FALSE +#define DEFAULT_PROBATION 2 +#define DEFAULT_UDP_RECONNECT TRUE +#define DEFAULT_MULTICAST_IFACE NULL +#define DEFAULT_NTP_SYNC FALSE +#define DEFAULT_USE_PIPELINE_CLOCK FALSE +#define DEFAULT_TLS_VALIDATION_FLAGS G_TLS_CERTIFICATE_VALIDATE_ALL +#define DEFAULT_TLS_DATABASE NULL +#define DEFAULT_DO_RETRANSMISSION TRUE + +#ifdef GST_EXT_RTSP_MODIFICATION +#define DEFAULT_START_POSITION 0 +#endif + +enum +{ + PROP_0, + PROP_LOCATION, + PROP_PROTOCOLS, + PROP_DEBUG, + PROP_RETRY, + PROP_TIMEOUT, +#ifdef GST_EXT_RTSP_MODIFICATION + PROP_START_POSITION, +#endif + PROP_TCP_TIMEOUT, + PROP_LATENCY, + PROP_DROP_ON_LATENCY, + PROP_CONNECTION_SPEED, + PROP_NAT_METHOD, + PROP_DO_RTCP, + PROP_DO_RTSP_KEEP_ALIVE, + PROP_PROXY, + PROP_PROXY_ID, + PROP_PROXY_PW, + PROP_RTP_BLOCKSIZE, + PROP_USER_ID, + PROP_USER_PW, + PROP_BUFFER_MODE, + PROP_PORT_RANGE, + PROP_UDP_BUFFER_SIZE, + PROP_SHORT_HEADER, + PROP_PROBATION, + PROP_UDP_RECONNECT, + PROP_MULTICAST_IFACE, + PROP_NTP_SYNC, + PROP_USE_PIPELINE_CLOCK, + PROP_SDES, + PROP_TLS_VALIDATION_FLAGS, + PROP_TLS_DATABASE, + PROP_DO_RETRANSMISSION +}; + +#define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type()) +static GType +gst_rtsp_nat_method_get_type (void) +{ + static GType rtsp_nat_method_type = 0; + static const GEnumValue rtsp_nat_method[] = { + {GST_RTSP_NAT_NONE, "None", "none"}, + {GST_RTSP_NAT_DUMMY, "Send Dummy packets", "dummy"}, + {0, NULL, NULL}, + }; + + if (!rtsp_nat_method_type) { + rtsp_nat_method_type = + g_enum_register_static ("GstRTSPNatMethod", rtsp_nat_method); + } + return rtsp_nat_method_type; +} + +static void gst_rtspsrc_finalize (GObject * object); + +static void gst_rtspsrc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rtspsrc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstClock *gst_rtspsrc_provide_clock (GstElement * element); + +static void gst_rtspsrc_uri_handler_init (gpointer g_iface, + gpointer iface_data); + +static void gst_rtspsrc_sdp_attributes_to_caps (GArray * attributes, + GstCaps * caps); + +static gboolean gst_rtspsrc_set_proxy (GstRTSPSrc * rtsp, const gchar * proxy); +static void gst_rtspsrc_set_tcp_timeout (GstRTSPSrc * rtspsrc, guint64 timeout); + +static GstCaps *gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media); + +static GstStateChangeReturn gst_rtspsrc_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_rtspsrc_send_event (GstElement * element, GstEvent * event); +static void gst_rtspsrc_handle_message (GstBin * bin, GstMessage * message); + +static gboolean gst_rtspsrc_setup_auth (GstRTSPSrc * src, + GstRTSPMessage * response); + +static gboolean gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, + gint mask); +static GstRTSPResult gst_rtspsrc_send_cb (GstRTSPExtension * ext, + GstRTSPMessage * request, GstRTSPMessage * response, GstRTSPSrc * src); + +static GstRTSPResult gst_rtspsrc_open (GstRTSPSrc * src, gboolean async); +static GstRTSPResult gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, + gboolean async); +static GstRTSPResult gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async); +static GstRTSPResult gst_rtspsrc_close (GstRTSPSrc * src, gboolean async, + gboolean only_close); + +static gboolean gst_rtspsrc_uri_set_uri (GstURIHandler * handler, + const gchar * uri, GError ** error); +static gchar *gst_rtspsrc_uri_get_uri (GstURIHandler * handler); + +static gboolean gst_rtspsrc_activate_streams (GstRTSPSrc * src); +static gboolean gst_rtspsrc_loop (GstRTSPSrc * src); +static gboolean gst_rtspsrc_stream_push_event (GstRTSPSrc * src, + GstRTSPStream * stream, GstEvent * event); +static gboolean gst_rtspsrc_push_event (GstRTSPSrc * src, GstEvent * event); +static void gst_rtspsrc_connection_flush (GstRTSPSrc * src, gboolean flush); + +typedef struct +{ + guint8 pt; + GstCaps *caps; +} PtMapItem; + +/* commands we send to out loop to notify it of events */ +#define CMD_OPEN (1 << 0) +#define CMD_PLAY (1 << 1) +#define CMD_PAUSE (1 << 2) +#define CMD_CLOSE (1 << 3) +#define CMD_WAIT (1 << 4) +#define CMD_RECONNECT (1 << 5) +#define CMD_LOOP (1 << 6) + +/* mask for all commands */ +#define CMD_ALL ((CMD_LOOP << 1) - 1) + +#define GST_ELEMENT_PROGRESS(el, type, code, text) \ +G_STMT_START { \ + gchar *__txt = _gst_element_error_printf text; \ + gst_element_post_message (GST_ELEMENT_CAST (el), \ + gst_message_new_progress (GST_OBJECT_CAST (el), \ + GST_PROGRESS_TYPE_ ##type, code, __txt)); \ + g_free (__txt); \ +} G_STMT_END + +static guint gst_rtspsrc_signals[LAST_SIGNAL] = { 0 }; + +#define gst_rtspsrc_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstRTSPSrc, gst_rtspsrc, GST_TYPE_BIN, + G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_rtspsrc_uri_handler_init)); + +#ifndef GST_DISABLE_GST_DEBUG +static inline const char * +cmd_to_string (guint cmd) +{ + switch (cmd) { + case CMD_OPEN: + return "OPEN"; + case CMD_PLAY: + return "PLAY"; + case CMD_PAUSE: + return "PAUSE"; + case CMD_CLOSE: + return "CLOSE"; + case CMD_WAIT: + return "WAIT"; + case CMD_RECONNECT: + return "RECONNECT"; + case CMD_LOOP: + return "LOOP"; + } + + return "unknown"; +} +#endif + +static gboolean +default_select_stream (GstRTSPSrc * src, guint id, GstCaps * caps) +{ + GST_DEBUG_OBJECT (src, "default handler"); + return TRUE; +} + +static gboolean +select_stream_accum (GSignalInvocationHint * ihint, + GValue * return_accu, const GValue * handler_return, gpointer data) +{ + gboolean myboolean; + + myboolean = g_value_get_boolean (handler_return); + GST_DEBUG ("accum %d", myboolean); + g_value_set_boolean (return_accu, myboolean); + + /* stop emission if FALSE */ + return myboolean; +} + +static void +gst_rtspsrc_class_init (GstRTSPSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBinClass *gstbin_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbin_class = (GstBinClass *) klass; + + GST_DEBUG_CATEGORY_INIT (rtspsrc_debug, "rtspsrc", 0, "RTSP src"); + + gobject_class->set_property = gst_rtspsrc_set_property; + gobject_class->get_property = gst_rtspsrc_get_property; + + gobject_class->finalize = gst_rtspsrc_finalize; + + g_object_class_install_property (gobject_class, PROP_LOCATION, + g_param_spec_string ("location", "RTSP Location", + "Location of the RTSP url to read", + DEFAULT_LOCATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_PROTOCOLS, + g_param_spec_flags ("protocols", "Protocols", + "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS, + DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_DEBUG, + g_param_spec_boolean ("debug", "Debug", + "Dump request and response messages to stdout", + DEFAULT_DEBUG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_RETRY, + g_param_spec_uint ("retry", "Retry", + "Max number of retries when allocating RTP ports.", + 0, G_MAXUINT16, DEFAULT_RETRY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_TIMEOUT, + g_param_spec_uint64 ("timeout", "Timeout", + "Retry TCP transport after UDP timeout microseconds (0 = disabled)", + 0, G_MAXUINT64, DEFAULT_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#ifdef GST_EXT_RTSP_MODIFICATION + g_object_class_install_property (gobject_class, PROP_START_POSITION, + g_param_spec_uint64 ("pending-start-position", "set start position", + "Set start position before PLAYING request.", + 0, G_MAXUINT64, DEFAULT_START_POSITION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#endif + g_object_class_install_property (gobject_class, PROP_TCP_TIMEOUT, + g_param_spec_uint64 ("tcp-timeout", "TCP Timeout", + "Fail after timeout microseconds on TCP connections (0 = disabled)", + 0, G_MAXUINT64, DEFAULT_TCP_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_LATENCY, + g_param_spec_uint ("latency", "Buffer latency in ms", + "Amount of ms to buffer", 0, G_MAXUINT, DEFAULT_LATENCY_MS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_DROP_ON_LATENCY, + g_param_spec_boolean ("drop-on-latency", + "Drop buffers when maximum latency is reached", + "Tells the jitterbuffer to never exceed the given latency in size", + DEFAULT_DROP_ON_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED, + g_param_spec_uint64 ("connection-speed", "Connection Speed", + "Network connection speed in kbps (0 = unknown)", + 0, G_MAXUINT64 / 1000, DEFAULT_CONNECTION_SPEED, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_NAT_METHOD, + g_param_spec_enum ("nat-method", "NAT Method", + "Method to use for traversing firewalls and NAT", + GST_TYPE_RTSP_NAT_METHOD, DEFAULT_NAT_METHOD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc:do-rtcp: + * + * Enable RTCP support. Some old server don't like RTCP and then this property + * needs to be set to FALSE. + */ + g_object_class_install_property (gobject_class, PROP_DO_RTCP, + g_param_spec_boolean ("do-rtcp", "Do RTCP", + "Send RTCP packets, disable for old incompatible server.", + DEFAULT_DO_RTCP, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc:do-rtsp-keep-alive: + * + * Enable RTSP keep alive support. Some old server don't like RTSP + * keep alive and then this property needs to be set to FALSE. + */ + g_object_class_install_property (gobject_class, PROP_DO_RTSP_KEEP_ALIVE, + g_param_spec_boolean ("do-rtsp-keep-alive", "Do RTSP Keep Alive", + "Send RTSP keep alive packets, disable for old incompatible server.", + DEFAULT_DO_RTSP_KEEP_ALIVE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc:proxy: + * + * Set the proxy parameters. This has to be a string of the format + * [http://][user:passwd@]host[:port]. + */ + g_object_class_install_property (gobject_class, PROP_PROXY, + g_param_spec_string ("proxy", "Proxy", + "Proxy settings for HTTP tunneling. Format: [http://][user:passwd@]host[:port]", + DEFAULT_PROXY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRTSPSrc:proxy-id: + * + * Sets the proxy URI user id for authentication. If the URI set via the + * "proxy" property contains a user-id already, that will take precedence. + * + * Since: 1.2 + */ + g_object_class_install_property (gobject_class, PROP_PROXY_ID, + g_param_spec_string ("proxy-id", "proxy-id", + "HTTP proxy URI user id for authentication", "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRTSPSrc:proxy-pw: + * + * Sets the proxy URI password for authentication. If the URI set via the + * "proxy" property contains a password already, that will take precedence. + * + * Since: 1.2 + */ + g_object_class_install_property (gobject_class, PROP_PROXY_PW, + g_param_spec_string ("proxy-pw", "proxy-pw", + "HTTP proxy URI user password for authentication", "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc:rtp-blocksize: + * + * RTP package size to suggest to server. + */ + g_object_class_install_property (gobject_class, PROP_RTP_BLOCKSIZE, + g_param_spec_uint ("rtp-blocksize", "RTP Blocksize", + "RTP package size to suggest to server (0 = disabled)", + 0, 65536, DEFAULT_RTP_BLOCKSIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_USER_ID, + g_param_spec_string ("user-id", "user-id", + "RTSP location URI user id for authentication", DEFAULT_USER_ID, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_USER_PW, + g_param_spec_string ("user-pw", "user-pw", + "RTSP location URI user password for authentication", DEFAULT_USER_PW, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc:buffer-mode: + * + * Control the buffering and timestamping mode used by the jitterbuffer. + */ + g_object_class_install_property (gobject_class, PROP_BUFFER_MODE, + g_param_spec_enum ("buffer-mode", "Buffer Mode", + "Control the buffering algorithm in use", + GST_TYPE_RTSP_SRC_BUFFER_MODE, DEFAULT_BUFFER_MODE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc:port-range: + * + * Configure the client port numbers that can be used to recieve RTP and + * RTCP. + */ + g_object_class_install_property (gobject_class, PROP_PORT_RANGE, + g_param_spec_string ("port-range", "Port range", + "Client port range that can be used to receive RTP and RTCP data, " + "eg. 3000-3005 (NULL = no restrictions)", DEFAULT_PORT_RANGE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc:udp-buffer-size: + * + * Size of the kernel UDP receive buffer in bytes. + */ + g_object_class_install_property (gobject_class, PROP_UDP_BUFFER_SIZE, + g_param_spec_int ("udp-buffer-size", "UDP Buffer Size", + "Size of the kernel UDP receive buffer in bytes, 0=default", + 0, G_MAXINT, DEFAULT_UDP_BUFFER_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc:short-header: + * + * Only send the basic RTSP headers for broken encoders. + */ + g_object_class_install_property (gobject_class, PROP_SHORT_HEADER, + g_param_spec_boolean ("short-header", "Short Header", + "Only send the basic RTSP headers for broken encoders", + DEFAULT_SHORT_HEADER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_PROBATION, + g_param_spec_uint ("probation", "Number of probations", + "Consecutive packet sequence numbers to accept the source", + 0, G_MAXUINT, DEFAULT_PROBATION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_UDP_RECONNECT, + g_param_spec_boolean ("udp-reconnect", "Reconnect to the server", + "Reconnect to the server if RTSP connection is closed when doing UDP", + DEFAULT_UDP_RECONNECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_MULTICAST_IFACE, + g_param_spec_string ("multicast-iface", "Multicast Interface", + "The network interface on which to join the multicast group", + DEFAULT_MULTICAST_IFACE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_NTP_SYNC, + g_param_spec_boolean ("ntp-sync", "Sync on NTP clock", + "Synchronize received streams to the NTP clock", DEFAULT_NTP_SYNC, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_USE_PIPELINE_CLOCK, + g_param_spec_boolean ("use-pipeline-clock", "Use pipeline clock", + "Use the pipeline running-time to set the NTP time in the RTCP SR messages", + DEFAULT_USE_PIPELINE_CLOCK, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SDES, + g_param_spec_boxed ("sdes", "SDES", + "The SDES items of this session", + GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc::tls-validation-flags: + * + * TLS certificate validation flags used to validate server + * certificate. + * + * Since: 1.2.1 + */ + g_object_class_install_property (gobject_class, PROP_TLS_VALIDATION_FLAGS, + g_param_spec_flags ("tls-validation-flags", "TLS validation flags", + "TLS certificate validation flags used to validate the server certificate", + G_TYPE_TLS_CERTIFICATE_FLAGS, DEFAULT_TLS_VALIDATION_FLAGS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc::tls-database: + * + * TLS database with anchor certificate authorities used to validate + * the server certificate. + * + * Since: 1.4 + */ + g_object_class_install_property (gobject_class, PROP_TLS_DATABASE, + g_param_spec_object ("tls-database", "TLS database", + "TLS database with anchor certificate authorities used to validate the server certificate", + G_TYPE_TLS_DATABASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc::do-retransmission: + * + * Attempt to ask the server to retransmit lost packets according to RFC4588. + * + * Note: currently only works with SSRC-multiplexed retransmission streams + * + * Since: 1.6 + */ + g_object_class_install_property (gobject_class, PROP_DO_RETRANSMISSION, + g_param_spec_boolean ("do-retransmission", "Retransmission", + "Ask the server to retransmit lost packets", + DEFAULT_DO_RETRANSMISSION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc::handle-request: + * @rtspsrc: a #GstRTSPSrc + * @request: a #GstRTSPMessage + * @response: a #GstRTSPMessage + * + * Handle a server request in @request and prepare @response. + * + * This signal is called from the streaming thread, you should therefore not + * do any state changes on @rtspsrc because this might deadlock. If you want + * to modify the state as a result of this signal, post a + * #GST_MESSAGE_REQUEST_STATE message on the bus or signal the main thread + * in some other way. + * + * Since: 1.2 + */ + gst_rtspsrc_signals[SIGNAL_HANDLE_REQUEST] = + g_signal_new ("handle-request", G_TYPE_FROM_CLASS (klass), 0, + 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, + G_TYPE_POINTER, G_TYPE_POINTER); + + /** + * GstRTSPSrc::on-sdp: + * @rtspsrc: a #GstRTSPSrc + * @sdp: a #GstSDPMessage + * + * Emited when the client has retrieved the SDP and before it configures the + * streams in the SDP. @sdp can be inspected and modified. + * + * This signal is called from the streaming thread, you should therefore not + * do any state changes on @rtspsrc because this might deadlock. If you want + * to modify the state as a result of this signal, post a + * #GST_MESSAGE_REQUEST_STATE message on the bus or signal the main thread + * in some other way. + * + * Since: 1.2 + */ + gst_rtspsrc_signals[SIGNAL_ON_SDP] = + g_signal_new ("on-sdp", G_TYPE_FROM_CLASS (klass), 0, + 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, + GST_TYPE_SDP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE); + + /** + * GstRTSPSrc::select-stream: + * @rtspsrc: a #GstRTSPSrc + * @num: the stream number + * @caps: the stream caps + * + * Emited before the client decides to configure the stream @num with + * @caps. + * + * Returns: %TRUE when the stream should be selected, %FALSE when the stream + * is to be ignored. + * + * Since: 1.2 + */ + gst_rtspsrc_signals[SIGNAL_SELECT_STREAM] = + g_signal_new_class_handler ("select-stream", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_CLEANUP, + (GCallback) default_select_stream, select_stream_accum, NULL, + g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 2, G_TYPE_UINT, + GST_TYPE_CAPS); + /** + * GstRTSPSrc::new-manager: + * @rtspsrc: a #GstRTSPSrc + * @manager: a #GstElement + * + * Emited after a new manager (like rtpbin) was created and the default + * properties were configured. + * + * Since: 1.4 + */ + gst_rtspsrc_signals[SIGNAL_NEW_MANAGER] = + g_signal_new_class_handler ("new-manager", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_CLEANUP, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_ELEMENT); + + /** + * GstRTSPSrc::request-rtcp-key: + * @rtspsrc: a #GstRTSPSrc + * @num: the stream number + * + * Signal emited to get the crypto parameters relevant to the RTCP + * stream. User should provide the key and the RTCP encryption ciphers + * and authentication, and return them wrapped in a GstCaps. + * + * Since: 1.4 + */ + gst_rtspsrc_signals[SIGNAL_REQUEST_RTCP_KEY] = + g_signal_new ("request-rtcp-key", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT); + + gstelement_class->send_event = gst_rtspsrc_send_event; + gstelement_class->provide_clock = gst_rtspsrc_provide_clock; + gstelement_class->change_state = gst_rtspsrc_change_state; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&rtptemplate)); + + gst_element_class_set_static_metadata (gstelement_class, + "RTSP packet receiver", "Source/Network", + "Receive data over the network via RTSP (RFC 2326)", + "Wim Taymans <wim@fluendo.com>, " + "Thijs Vermeir <thijs.vermeir@barco.com>, " + "Lutz Mueller <lutz@topfrose.de>"); + + gstbin_class->handle_message = gst_rtspsrc_handle_message; + + gst_rtsp_ext_list_init (); +} + +static void +gst_rtspsrc_init (GstRTSPSrc * src) +{ + src->conninfo.location = g_strdup (DEFAULT_LOCATION); + src->protocols = DEFAULT_PROTOCOLS; + src->debug = DEFAULT_DEBUG; + src->retry = DEFAULT_RETRY; + src->udp_timeout = DEFAULT_TIMEOUT; +#ifdef GST_EXT_RTSP_MODIFICATION + src->start_position = DEFAULT_START_POSITION; +#endif + gst_rtspsrc_set_tcp_timeout (src, DEFAULT_TCP_TIMEOUT); + src->latency = DEFAULT_LATENCY_MS; + src->drop_on_latency = DEFAULT_DROP_ON_LATENCY; + src->connection_speed = DEFAULT_CONNECTION_SPEED; + src->nat_method = DEFAULT_NAT_METHOD; + src->do_rtcp = DEFAULT_DO_RTCP; + src->do_rtsp_keep_alive = DEFAULT_DO_RTSP_KEEP_ALIVE; + gst_rtspsrc_set_proxy (src, DEFAULT_PROXY); + src->rtp_blocksize = DEFAULT_RTP_BLOCKSIZE; + src->user_id = g_strdup (DEFAULT_USER_ID); + src->user_pw = g_strdup (DEFAULT_USER_PW); + src->buffer_mode = DEFAULT_BUFFER_MODE; + src->client_port_range.min = 0; + src->client_port_range.max = 0; + src->udp_buffer_size = DEFAULT_UDP_BUFFER_SIZE; + src->short_header = DEFAULT_SHORT_HEADER; + src->probation = DEFAULT_PROBATION; + src->udp_reconnect = DEFAULT_UDP_RECONNECT; + src->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE); + src->ntp_sync = DEFAULT_NTP_SYNC; + src->use_pipeline_clock = DEFAULT_USE_PIPELINE_CLOCK; + src->sdes = NULL; + src->tls_validation_flags = DEFAULT_TLS_VALIDATION_FLAGS; + src->tls_database = DEFAULT_TLS_DATABASE; + src->do_retransmission = DEFAULT_DO_RETRANSMISSION; + +#ifdef GST_EXT_RTSP_MODIFICATION + g_mutex_init (&(src)->pause_lock); + g_cond_init (&(src)->open_end); +#endif + /* get a list of all extensions */ + src->extensions = gst_rtsp_ext_list_get (); + + /* connect to send signal */ + gst_rtsp_ext_list_connect (src->extensions, "send", + (GCallback) gst_rtspsrc_send_cb, src); + + /* protects the streaming thread in interleaved mode or the polling + * thread in UDP mode. */ + g_rec_mutex_init (&src->stream_rec_lock); + + /* protects our state changes from multiple invocations */ + g_rec_mutex_init (&src->state_rec_lock); + + src->state = GST_RTSP_STATE_INVALID; + + GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_SOURCE); +} + +static void +gst_rtspsrc_finalize (GObject * object) +{ + GstRTSPSrc *rtspsrc; + + rtspsrc = GST_RTSPSRC (object); + + gst_rtsp_ext_list_free (rtspsrc->extensions); + g_free (rtspsrc->conninfo.location); + gst_rtsp_url_free (rtspsrc->conninfo.url); + g_free (rtspsrc->conninfo.url_str); + g_free (rtspsrc->user_id); + g_free (rtspsrc->user_pw); + g_free (rtspsrc->multi_iface); + +#ifdef GST_EXT_RTSP_MODIFICATION + g_mutex_clear (&(rtspsrc)->pause_lock); + g_cond_clear (&(rtspsrc)->open_end); +#endif + + if (rtspsrc->sdp) { + gst_sdp_message_free (rtspsrc->sdp); + rtspsrc->sdp = NULL; + } + if (rtspsrc->provided_clock) + gst_object_unref (rtspsrc->provided_clock); + + if (rtspsrc->sdes) + gst_structure_free (rtspsrc->sdes); + + if (rtspsrc->tls_database) + g_object_unref (rtspsrc->tls_database); + + /* free locks */ + g_rec_mutex_clear (&rtspsrc->stream_rec_lock); + g_rec_mutex_clear (&rtspsrc->state_rec_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstClock * +gst_rtspsrc_provide_clock (GstElement * element) +{ + GstRTSPSrc *src = GST_RTSPSRC (element); + GstClock *clock; + + if ((clock = src->provided_clock) != NULL) + gst_object_ref (clock); + + return clock; +} + +/* a proxy string of the format [user:passwd@]host[:port] */ +static gboolean +gst_rtspsrc_set_proxy (GstRTSPSrc * rtsp, const gchar * proxy) +{ + gchar *p, *at, *col; + + g_free (rtsp->proxy_user); + rtsp->proxy_user = NULL; + g_free (rtsp->proxy_passwd); + rtsp->proxy_passwd = NULL; + g_free (rtsp->proxy_host); + rtsp->proxy_host = NULL; + rtsp->proxy_port = 0; + + p = (gchar *) proxy; + + if (p == NULL) + return TRUE; + + /* we allow http:// in front but ignore it */ + if (g_str_has_prefix (p, "http://")) + p += 7; + + at = strchr (p, '@'); + if (at) { + /* look for user:passwd */ + col = strchr (proxy, ':'); + if (col == NULL || col > at) + return FALSE; + + rtsp->proxy_user = g_strndup (p, col - p); + col++; + rtsp->proxy_passwd = g_strndup (col, at - col); + + /* move to host */ + p = at + 1; + } else { + if (rtsp->prop_proxy_id != NULL && *rtsp->prop_proxy_id != '\0') + rtsp->proxy_user = g_strdup (rtsp->prop_proxy_id); + if (rtsp->prop_proxy_pw != NULL && *rtsp->prop_proxy_pw != '\0') + rtsp->proxy_passwd = g_strdup (rtsp->prop_proxy_pw); + if (rtsp->proxy_user != NULL || rtsp->proxy_passwd != NULL) { + GST_LOG_OBJECT (rtsp, "set proxy user/pw from properties: %s:%s", + GST_STR_NULL (rtsp->proxy_user), GST_STR_NULL (rtsp->proxy_passwd)); + } + } + col = strchr (p, ':'); + + if (col) { + /* everything before the colon is the hostname */ + rtsp->proxy_host = g_strndup (p, col - p); + p = col + 1; + rtsp->proxy_port = strtoul (p, (char **) &p, 10); + } else { + rtsp->proxy_host = g_strdup (p); + rtsp->proxy_port = 8080; + } + return TRUE; +} + +static void +gst_rtspsrc_set_tcp_timeout (GstRTSPSrc * rtspsrc, guint64 timeout) +{ + rtspsrc->tcp_timeout.tv_sec = timeout / G_USEC_PER_SEC; + rtspsrc->tcp_timeout.tv_usec = timeout % G_USEC_PER_SEC; + + if (timeout != 0) + rtspsrc->ptcp_timeout = &rtspsrc->tcp_timeout; + else + rtspsrc->ptcp_timeout = NULL; +} + +static void +gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, + GParamSpec * pspec) +{ + GstRTSPSrc *rtspsrc; + + rtspsrc = GST_RTSPSRC (object); + + switch (prop_id) { + case PROP_LOCATION: + gst_rtspsrc_uri_set_uri (GST_URI_HANDLER (rtspsrc), + g_value_get_string (value), NULL); + break; + case PROP_PROTOCOLS: + rtspsrc->protocols = g_value_get_flags (value); + break; + case PROP_DEBUG: + rtspsrc->debug = g_value_get_boolean (value); + break; + case PROP_RETRY: + rtspsrc->retry = g_value_get_uint (value); + break; + case PROP_TIMEOUT: + rtspsrc->udp_timeout = g_value_get_uint64 (value); + break; +#ifdef GST_EXT_RTSP_MODIFICATION + case PROP_START_POSITION: + rtspsrc->start_position = g_value_get_uint64 (value); + break; +#endif + case PROP_TCP_TIMEOUT: + gst_rtspsrc_set_tcp_timeout (rtspsrc, g_value_get_uint64 (value)); + break; + case PROP_LATENCY: + rtspsrc->latency = g_value_get_uint (value); + break; + case PROP_DROP_ON_LATENCY: + rtspsrc->drop_on_latency = g_value_get_boolean (value); + break; + case PROP_CONNECTION_SPEED: + rtspsrc->connection_speed = g_value_get_uint64 (value); + break; + case PROP_NAT_METHOD: + rtspsrc->nat_method = g_value_get_enum (value); + break; + case PROP_DO_RTCP: + rtspsrc->do_rtcp = g_value_get_boolean (value); + break; + case PROP_DO_RTSP_KEEP_ALIVE: + rtspsrc->do_rtsp_keep_alive = g_value_get_boolean (value); + break; + case PROP_PROXY: + gst_rtspsrc_set_proxy (rtspsrc, g_value_get_string (value)); + break; + case PROP_PROXY_ID: + if (rtspsrc->prop_proxy_id) + g_free (rtspsrc->prop_proxy_id); + rtspsrc->prop_proxy_id = g_value_dup_string (value); + break; + case PROP_PROXY_PW: + if (rtspsrc->prop_proxy_pw) + g_free (rtspsrc->prop_proxy_pw); + rtspsrc->prop_proxy_pw = g_value_dup_string (value); + break; + case PROP_RTP_BLOCKSIZE: + rtspsrc->rtp_blocksize = g_value_get_uint (value); + break; + case PROP_USER_ID: + if (rtspsrc->user_id) + g_free (rtspsrc->user_id); + rtspsrc->user_id = g_value_dup_string (value); + break; + case PROP_USER_PW: + if (rtspsrc->user_pw) + g_free (rtspsrc->user_pw); + rtspsrc->user_pw = g_value_dup_string (value); + break; + case PROP_BUFFER_MODE: + rtspsrc->buffer_mode = g_value_get_enum (value); + break; + case PROP_PORT_RANGE: + { + const gchar *str; + + str = g_value_get_string (value); + if (str) { + sscanf (str, "%u-%u", + &rtspsrc->client_port_range.min, &rtspsrc->client_port_range.max); + } else { + rtspsrc->client_port_range.min = 0; + rtspsrc->client_port_range.max = 0; + } + break; + } + case PROP_UDP_BUFFER_SIZE: + rtspsrc->udp_buffer_size = g_value_get_int (value); + break; + case PROP_SHORT_HEADER: + rtspsrc->short_header = g_value_get_boolean (value); + break; + case PROP_PROBATION: + rtspsrc->probation = g_value_get_uint (value); + break; + case PROP_UDP_RECONNECT: + rtspsrc->udp_reconnect = g_value_get_boolean (value); + break; + case PROP_MULTICAST_IFACE: + g_free (rtspsrc->multi_iface); + + if (g_value_get_string (value) == NULL) + rtspsrc->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE); + else + rtspsrc->multi_iface = g_value_dup_string (value); + break; + case PROP_NTP_SYNC: + rtspsrc->ntp_sync = g_value_get_boolean (value); + break; + case PROP_USE_PIPELINE_CLOCK: + rtspsrc->use_pipeline_clock = g_value_get_boolean (value); + break; + case PROP_SDES: + rtspsrc->sdes = g_value_dup_boxed (value); + break; + case PROP_TLS_VALIDATION_FLAGS: + rtspsrc->tls_validation_flags = g_value_get_flags (value); + break; + case PROP_TLS_DATABASE: + g_clear_object (&rtspsrc->tls_database); + rtspsrc->tls_database = g_value_dup_object (value); + break; + case PROP_DO_RETRANSMISSION: + rtspsrc->do_retransmission = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstRTSPSrc *rtspsrc; + + rtspsrc = GST_RTSPSRC (object); + + switch (prop_id) { + case PROP_LOCATION: + g_value_set_string (value, rtspsrc->conninfo.location); + break; + case PROP_PROTOCOLS: + g_value_set_flags (value, rtspsrc->protocols); + break; + case PROP_DEBUG: + g_value_set_boolean (value, rtspsrc->debug); + break; + case PROP_RETRY: + g_value_set_uint (value, rtspsrc->retry); + break; + case PROP_TIMEOUT: + g_value_set_uint64 (value, rtspsrc->udp_timeout); + break; +#ifdef GST_EXT_RTSP_MODIFICATION + case PROP_START_POSITION: + g_value_set_uint64 (value, rtspsrc->start_position); + break; +#endif + case PROP_TCP_TIMEOUT: + { + guint64 timeout; + + timeout = rtspsrc->tcp_timeout.tv_sec * G_USEC_PER_SEC + + rtspsrc->tcp_timeout.tv_usec; + g_value_set_uint64 (value, timeout); + break; + } + case PROP_LATENCY: + g_value_set_uint (value, rtspsrc->latency); + break; + case PROP_DROP_ON_LATENCY: + g_value_set_boolean (value, rtspsrc->drop_on_latency); + break; + case PROP_CONNECTION_SPEED: + g_value_set_uint64 (value, rtspsrc->connection_speed); + break; + case PROP_NAT_METHOD: + g_value_set_enum (value, rtspsrc->nat_method); + break; + case PROP_DO_RTCP: + g_value_set_boolean (value, rtspsrc->do_rtcp); + break; + case PROP_DO_RTSP_KEEP_ALIVE: + g_value_set_boolean (value, rtspsrc->do_rtsp_keep_alive); + break; + case PROP_PROXY: + { + gchar *str; + + if (rtspsrc->proxy_host) { + str = + g_strdup_printf ("%s:%d", rtspsrc->proxy_host, rtspsrc->proxy_port); + } else { + str = NULL; + } + g_value_take_string (value, str); + break; + } + case PROP_PROXY_ID: + g_value_set_string (value, rtspsrc->prop_proxy_id); + break; + case PROP_PROXY_PW: + g_value_set_string (value, rtspsrc->prop_proxy_pw); + break; + case PROP_RTP_BLOCKSIZE: + g_value_set_uint (value, rtspsrc->rtp_blocksize); + break; + case PROP_USER_ID: + g_value_set_string (value, rtspsrc->user_id); + break; + case PROP_USER_PW: + g_value_set_string (value, rtspsrc->user_pw); + break; + case PROP_BUFFER_MODE: + g_value_set_enum (value, rtspsrc->buffer_mode); + break; + case PROP_PORT_RANGE: + { + gchar *str; + + if (rtspsrc->client_port_range.min != 0) { + str = g_strdup_printf ("%u-%u", rtspsrc->client_port_range.min, + rtspsrc->client_port_range.max); + } else { + str = NULL; + } + g_value_take_string (value, str); + break; + } + case PROP_UDP_BUFFER_SIZE: + g_value_set_int (value, rtspsrc->udp_buffer_size); + break; + case PROP_SHORT_HEADER: + g_value_set_boolean (value, rtspsrc->short_header); + break; + case PROP_PROBATION: + g_value_set_uint (value, rtspsrc->probation); + break; + case PROP_UDP_RECONNECT: + g_value_set_boolean (value, rtspsrc->udp_reconnect); + break; + case PROP_MULTICAST_IFACE: + g_value_set_string (value, rtspsrc->multi_iface); + break; + case PROP_NTP_SYNC: + g_value_set_boolean (value, rtspsrc->ntp_sync); + break; + case PROP_USE_PIPELINE_CLOCK: + g_value_set_boolean (value, rtspsrc->use_pipeline_clock); + break; + case PROP_SDES: + g_value_set_boxed (value, rtspsrc->sdes); + break; + case PROP_TLS_VALIDATION_FLAGS: + g_value_set_flags (value, rtspsrc->tls_validation_flags); + break; + case PROP_TLS_DATABASE: + g_value_set_object (value, rtspsrc->tls_database); + break; + case PROP_DO_RETRANSMISSION: + g_value_set_boolean (value, rtspsrc->do_retransmission); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gint +find_stream_by_id (GstRTSPStream * stream, gint * id) +{ + if (stream->id == *id) + return 0; + + return -1; +} + +static gint +find_stream_by_channel (GstRTSPStream * stream, gint * channel) +{ + if (stream->channel[0] == *channel || stream->channel[1] == *channel) + return 0; + + return -1; +} + +static gint +find_stream_by_udpsrc (GstRTSPStream * stream, gconstpointer a) +{ + GstElement *src = (GstElement *) a; + + if (stream->udpsrc[0] == src) + return 0; + if (stream->udpsrc[1] == src) + return 0; + + return -1; +} + +static gint +find_stream_by_setup (GstRTSPStream * stream, gconstpointer a) +{ + if (stream->conninfo.location) { + /* check qualified setup_url */ + if (!strcmp (stream->conninfo.location, (gchar *) a)) + return 0; + } + if (stream->control_url) { + /* check original control_url */ + if (!strcmp (stream->control_url, (gchar *) a)) + return 0; + + /* check if qualified setup_url ends with string */ + if (g_str_has_suffix (stream->control_url, (gchar *) a)) + return 0; + } + + return -1; +} + +static GstRTSPStream * +find_stream (GstRTSPSrc * src, gconstpointer data, gconstpointer func) +{ + GList *lstream; + + /* find and get stream */ + if ((lstream = g_list_find_custom (src->streams, data, (GCompareFunc) func))) + return (GstRTSPStream *) lstream->data; + + return NULL; +} + +static const GstSDPBandwidth * +gst_rtspsrc_get_bandwidth (GstRTSPSrc * src, const GstSDPMessage * sdp, + const GstSDPMedia * media, const gchar * type) +{ + guint i, len; + + /* first look in the media specific section */ + len = gst_sdp_media_bandwidths_len (media); + for (i = 0; i < len; i++) { + const GstSDPBandwidth *bw = gst_sdp_media_get_bandwidth (media, i); + + if (strcmp (bw->bwtype, type) == 0) + return bw; + } + /* then look in the message specific section */ + len = gst_sdp_message_bandwidths_len (sdp); + for (i = 0; i < len; i++) { + const GstSDPBandwidth *bw = gst_sdp_message_get_bandwidth (sdp, i); + + if (strcmp (bw->bwtype, type) == 0) + return bw; + } + return NULL; +} + +static void +gst_rtspsrc_collect_bandwidth (GstRTSPSrc * src, const GstSDPMessage * sdp, + const GstSDPMedia * media, GstRTSPStream * stream) +{ + const GstSDPBandwidth *bw; + + if ((bw = gst_rtspsrc_get_bandwidth (src, sdp, media, GST_SDP_BWTYPE_AS))) + stream->as_bandwidth = bw->bandwidth; + else + stream->as_bandwidth = -1; + + if ((bw = gst_rtspsrc_get_bandwidth (src, sdp, media, GST_SDP_BWTYPE_RR))) + stream->rr_bandwidth = bw->bandwidth; + else + stream->rr_bandwidth = -1; + + if ((bw = gst_rtspsrc_get_bandwidth (src, sdp, media, GST_SDP_BWTYPE_RS))) + stream->rs_bandwidth = bw->bandwidth; + else + stream->rs_bandwidth = -1; +} + +static void +gst_rtspsrc_do_stream_connection (GstRTSPSrc * src, GstRTSPStream * stream, + const GstSDPConnection * conn) +{ + if (conn->nettype == NULL || strcmp (conn->nettype, "IN") != 0) + return; + + if (conn->addrtype == NULL) + return; + + /* check for IPV6 */ + if (strcmp (conn->addrtype, "IP4") == 0) + stream->is_ipv6 = FALSE; + else if (strcmp (conn->addrtype, "IP6") == 0) + stream->is_ipv6 = TRUE; + else + return; + + /* save address */ + g_free (stream->destination); + stream->destination = g_strdup (conn->address); + + /* check for multicast */ + stream->is_multicast = + gst_sdp_address_is_multicast (conn->nettype, conn->addrtype, + conn->address); + stream->ttl = conn->ttl; +} + +/* Go over the connections for a stream. + * - If we are dealing with IPV6, we will setup IPV6 sockets for sending and + * receiving. + * - If we are dealing with a localhost address, we disable multicast + */ +static void +gst_rtspsrc_collect_connections (GstRTSPSrc * src, const GstSDPMessage * sdp, + const GstSDPMedia * media, GstRTSPStream * stream) +{ + const GstSDPConnection *conn; + guint i, len; + + /* first look in the media specific section */ + len = gst_sdp_media_connections_len (media); + for (i = 0; i < len; i++) { + conn = gst_sdp_media_get_connection (media, i); + + gst_rtspsrc_do_stream_connection (src, stream, conn); + } + /* then look in the message specific section */ + if ((conn = gst_sdp_message_get_connection (sdp))) { + gst_rtspsrc_do_stream_connection (src, stream, conn); + } +} + +/* m=<media> <UDP port> RTP/AVP <payload> + */ +static void +gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp, + const GstSDPMedia * media, GstRTSPStream * stream) +{ + guint i, len; + const gchar *proto; + + /* get proto */ + proto = gst_sdp_media_get_proto (media); + if (proto == NULL) + goto no_proto; + + if (g_str_equal (proto, "RTP/AVP")) + stream->profile = GST_RTSP_PROFILE_AVP; + else if (g_str_equal (proto, "RTP/SAVP")) + stream->profile = GST_RTSP_PROFILE_SAVP; + else if (g_str_equal (proto, "RTP/AVPF")) + stream->profile = GST_RTSP_PROFILE_AVPF; + else if (g_str_equal (proto, "RTP/SAVPF")) + stream->profile = GST_RTSP_PROFILE_SAVPF; + else + goto unknown_proto; + + len = gst_sdp_media_formats_len (media); + for (i = 0; i < len; i++) { + gint pt; + GstCaps *caps; + GstStructure *s; + const gchar *enc; + PtMapItem item; + + pt = atoi (gst_sdp_media_get_format (media, i)); + + GST_DEBUG_OBJECT (src, " looking at %d pt: %d", i, pt); + + /* convert caps */ + caps = gst_rtspsrc_media_to_caps (pt, media); + if (caps == NULL) { + GST_WARNING_OBJECT (src, " skipping pt %d without caps", pt); + continue; + } + + /* do some tweaks */ + s = gst_caps_get_structure (caps, 0); + if ((enc = gst_structure_get_string (s, "encoding-name"))) { + stream->is_real = (strstr (enc, "-REAL") != NULL); + if (strcmp (enc, "X-ASF-PF") == 0) + stream->container = TRUE; + } + GST_DEBUG ("mapping sdp session level attributes to caps"); + gst_rtspsrc_sdp_attributes_to_caps (sdp->attributes, caps); + GST_DEBUG ("mapping sdp media level attributes to caps"); + gst_rtspsrc_sdp_attributes_to_caps (media->attributes, caps); + + /* the first pt will be the default */ + if (stream->ptmap->len == 0) + stream->default_pt = pt; + + item.pt = pt; + item.caps = caps; + g_array_append_val (stream->ptmap, item); + } + return; + +no_proto: + { + GST_ERROR_OBJECT (src, "can't find proto in media"); + return; + } +unknown_proto: + { + GST_ERROR_OBJECT (src, "unknown proto in media %s", proto); + return; + } +} + +static const gchar * +get_aggregate_control (GstRTSPSrc * src) +{ + const gchar *base; + + if (src->control) + base = src->control; + else if (src->content_base) + base = src->content_base; + else if (src->conninfo.url_str) + base = src->conninfo.url_str; + else + base = "/"; + + return base; +} + +static void +clear_ptmap_item (PtMapItem * item) +{ + if (item->caps) + gst_caps_unref (item->caps); +} + +static GstRTSPStream * +gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) +{ + GstRTSPStream *stream; + const gchar *control_url; + const GstSDPMedia *media; + + /* get media, should not return NULL */ + media = gst_sdp_message_get_media (sdp, idx); + if (media == NULL) + return NULL; + + stream = g_new0 (GstRTSPStream, 1); + stream->parent = src; + /* we mark the pad as not linked, we will mark it as OK when we add the pad to + * the element. */ + stream->last_ret = GST_FLOW_NOT_LINKED; + stream->added = FALSE; + stream->setup = FALSE; + stream->skipped = FALSE; + stream->id = idx; + stream->eos = FALSE; + stream->discont = TRUE; + stream->seqbase = -1; + stream->timebase = -1; + stream->send_ssrc = g_random_int (); + stream->profile = GST_RTSP_PROFILE_AVP; + stream->ptmap = g_array_new (FALSE, FALSE, sizeof (PtMapItem)); + g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item); + + /* collect bandwidth information for this steam. FIXME, configure in the RTP + * session manager to scale RTCP. */ + gst_rtspsrc_collect_bandwidth (src, sdp, media, stream); + + /* collect connection info */ + gst_rtspsrc_collect_connections (src, sdp, media, stream); + + /* make the payload type map */ + gst_rtspsrc_collect_payloads (src, sdp, media, stream); + + /* collect port number */ + stream->port = gst_sdp_media_get_port (media); + + /* get control url to construct the setup url. The setup url is used to + * configure the transport of the stream and is used to identity the stream in + * the RTP-Info header field returned from PLAY. */ + control_url = gst_sdp_media_get_attribute_val (media, "control"); + if (control_url == NULL) + control_url = gst_sdp_message_get_attribute_val_n (sdp, "control", 0); + + GST_DEBUG_OBJECT (src, "stream %d, (%p)", stream->id, stream); + GST_DEBUG_OBJECT (src, " port: %d", stream->port); + GST_DEBUG_OBJECT (src, " container: %d", stream->container); + GST_DEBUG_OBJECT (src, " control: %s", GST_STR_NULL (control_url)); + + if (control_url != NULL) { + stream->control_url = g_strdup (control_url); + /* Build a fully qualified url using the content_base if any or by prefixing + * the original request. + * If the control_url starts with a '/' or a non rtsp: protocol we will most + * likely build a URL that the server will fail to understand, this is ok, + * we will fail then. */ + if (g_str_has_prefix (control_url, "rtsp://")) + stream->conninfo.location = g_strdup (control_url); + else { + const gchar *base; + gboolean has_slash; + + if (g_strcmp0 (control_url, "*") == 0) + control_url = ""; + + base = get_aggregate_control (src); + + /* check if the base ends or control starts with / */ + has_slash = g_str_has_prefix (control_url, "/"); + has_slash = has_slash || g_str_has_suffix (base, "/"); + + /* concatenate the two strings, insert / when not present */ + stream->conninfo.location = + g_strdup_printf ("%s%s%s", base, has_slash ? "" : "/", control_url); + } + } + GST_DEBUG_OBJECT (src, " setup: %s", + GST_STR_NULL (stream->conninfo.location)); + + /* we keep track of all streams */ + src->streams = g_list_append (src->streams, stream); + + return stream; + + /* ERRORS */ +} + +static void +gst_rtspsrc_stream_free (GstRTSPSrc * src, GstRTSPStream * stream) +{ + gint i; + + GST_DEBUG_OBJECT (src, "free stream %p", stream); + + g_array_free (stream->ptmap, TRUE); + + g_free (stream->destination); + g_free (stream->control_url); + g_free (stream->conninfo.location); + + for (i = 0; i < 2; i++) { + if (stream->udpsrc[i]) { + gst_element_set_state (stream->udpsrc[i], GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (src), stream->udpsrc[i]); + gst_object_unref (stream->udpsrc[i]); + } + if (stream->channelpad[i]) + gst_object_unref (stream->channelpad[i]); + + if (stream->udpsink[i]) { + gst_element_set_state (stream->udpsink[i], GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (src), stream->udpsink[i]); + gst_object_unref (stream->udpsink[i]); + } + } + if (stream->fakesrc) { + gst_element_set_state (stream->fakesrc, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (src), stream->fakesrc); + gst_object_unref (stream->fakesrc); + } + if (stream->srcpad) { + gst_pad_set_active (stream->srcpad, FALSE); + if (stream->added) + gst_element_remove_pad (GST_ELEMENT_CAST (src), stream->srcpad); + } + if (stream->srtpenc) + gst_object_unref (stream->srtpenc); + if (stream->srtpdec) + gst_object_unref (stream->srtpdec); + if (stream->srtcpparams) + gst_caps_unref (stream->srtcpparams); + if (stream->rtcppad) + gst_object_unref (stream->rtcppad); + if (stream->session) + g_object_unref (stream->session); + if (stream->rtx_pt_map) + gst_structure_free (stream->rtx_pt_map); + g_free (stream); +} + +static void +gst_rtspsrc_cleanup (GstRTSPSrc * src) +{ + GList *walk; + + GST_DEBUG_OBJECT (src, "cleanup"); + + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + + gst_rtspsrc_stream_free (src, stream); + } + g_list_free (src->streams); + src->streams = NULL; + if (src->manager) { + if (src->manager_sig_id) { + g_signal_handler_disconnect (src->manager, src->manager_sig_id); + src->manager_sig_id = 0; + } + gst_element_set_state (src->manager, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (src), src->manager); + src->manager = NULL; + } + if (src->props) + gst_structure_free (src->props); + src->props = NULL; + + g_free (src->content_base); + src->content_base = NULL; + + g_free (src->control); + src->control = NULL; + + if (src->range) + gst_rtsp_range_free (src->range); + src->range = NULL; + + /* don't clear the SDP when it was used in the url */ + if (src->sdp && !src->from_sdp) { + gst_sdp_message_free (src->sdp); + src->sdp = NULL; + } + + src->need_segment = FALSE; + + if (src->provided_clock) { + gst_object_unref (src->provided_clock); + src->provided_clock = NULL; + } +} + +#define PARSE_INT(p, del, res) \ +G_STMT_START { \ + gchar *t = p; \ + p = strstr (p, del); \ + if (p == NULL) \ + res = -1; \ + else { \ + *p = '\0'; \ + p++; \ + res = atoi (t); \ + } \ +} G_STMT_END + +#define PARSE_STRING(p, del, res) \ +G_STMT_START { \ + gchar *t = p; \ + p = strstr (p, del); \ + if (p == NULL) { \ + res = NULL; \ + p = t; \ + } \ + else { \ + *p = '\0'; \ + p++; \ + res = t; \ + } \ +} G_STMT_END + +#define SKIP_SPACES(p) \ + while (*p && g_ascii_isspace (*p)) \ + p++; + +/* rtpmap contains: + * + * <payload> <encoding_name>/<clock_rate>[/<encoding_params>] + */ +static gboolean +gst_rtspsrc_parse_rtpmap (const gchar * rtpmap, gint * payload, gchar ** name, + gint * rate, gchar ** params) +{ + gchar *p, *t; + + p = (gchar *) rtpmap; + + PARSE_INT (p, " ", *payload); + if (*payload == -1) + return FALSE; + + SKIP_SPACES (p); + if (*p == '\0') + return FALSE; + + PARSE_STRING (p, "/", *name); + if (*name == NULL) { + GST_DEBUG ("no rate, name %s", p); + /* no rate, assume -1 then, this is not supposed to happen but RealMedia + * streams seem to omit the rate. */ + *name = p; + *rate = -1; + return TRUE; + } + + t = p; + p = strstr (p, "/"); + if (p == NULL) { + *rate = atoi (t); + return TRUE; + } + *p = '\0'; + p++; + *rate = atoi (t); + + t = p; + if (*p == '\0') + return TRUE; + *params = t; + + return TRUE; +} + +static gboolean +parse_keymgmt (const gchar * keymgmt, GstCaps * caps) +{ + gboolean res = FALSE; + gchar *p, *kmpid; + gsize size; + guchar *data; + GstMIKEYMessage *msg; + const GstMIKEYPayload *payload; + const gchar *srtp_cipher; + const gchar *srtp_auth; + + p = (gchar *) keymgmt; + + SKIP_SPACES (p); + if (*p == '\0') + return FALSE; + + PARSE_STRING (p, " ", kmpid); + if (!g_str_equal (kmpid, "mikey")) + return FALSE; + + data = g_base64_decode (p, &size); + if (data == NULL) + return FALSE; + + msg = gst_mikey_message_new_from_data (data, size, NULL, NULL); + g_free (data); + if (msg == NULL) + return FALSE; + + srtp_cipher = "aes-128-icm"; + srtp_auth = "hmac-sha1-80"; + + /* check the Security policy if any */ + if ((payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, 0))) { + GstMIKEYPayloadSP *p = (GstMIKEYPayloadSP *) payload; + guint len, i; + + if (p->proto != GST_MIKEY_SEC_PROTO_SRTP) + goto done; + + len = gst_mikey_payload_sp_get_n_params (payload); + for (i = 0; i < len; i++) { + const GstMIKEYPayloadSPParam *param = + gst_mikey_payload_sp_get_param (payload, i); + + switch (param->type) { + case GST_MIKEY_SP_SRTP_ENC_ALG: + switch (param->val[0]) { + case 0: + srtp_cipher = "null"; + break; + case 2: + case 1: + srtp_cipher = "aes-128-icm"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_ENC_KEY_LEN: + switch (param->val[0]) { + case AES_128_KEY_LEN: + srtp_cipher = "aes-128-icm"; + break; + case AES_256_KEY_LEN: + srtp_cipher = "aes-256-icm"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_AUTH_ALG: + switch (param->val[0]) { + case 0: + srtp_auth = "null"; + break; + case 2: + case 1: + srtp_auth = "hmac-sha1-80"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN: + switch (param->val[0]) { + case HMAC_32_KEY_LEN: + srtp_auth = "hmac-sha1-32"; + break; + case HMAC_80_KEY_LEN: + srtp_auth = "hmac-sha1-80"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_SRTP_ENC: + break; + case GST_MIKEY_SP_SRTP_SRTCP_ENC: + break; + default: + break; + } + } + } + + if (!(payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_KEMAC, 0))) + goto done; + else { + GstMIKEYPayloadKEMAC *p = (GstMIKEYPayloadKEMAC *) payload; + const GstMIKEYPayload *sub; + GstMIKEYPayloadKeyData *pkd; + GstBuffer *buf; + + if (p->enc_alg != GST_MIKEY_ENC_NULL || p->mac_alg != GST_MIKEY_MAC_NULL) + goto done; + + if (!(sub = gst_mikey_payload_kemac_get_sub (payload, 0))) + goto done; + + if (sub->type != GST_MIKEY_PT_KEY_DATA) + goto done; + + pkd = (GstMIKEYPayloadKeyData *) sub; + buf = + gst_buffer_new_wrapped (g_memdup (pkd->key_data, pkd->key_len), + pkd->key_len); + gst_caps_set_simple (caps, "srtp-key", GST_TYPE_BUFFER, buf, NULL); + } + + gst_caps_set_simple (caps, + "srtp-cipher", G_TYPE_STRING, srtp_cipher, + "srtp-auth", G_TYPE_STRING, srtp_auth, + "srtcp-cipher", G_TYPE_STRING, srtp_cipher, + "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL); + + res = TRUE; +done: + gst_mikey_message_unref (msg); + + return res; +} + +/* + * Mapping SDP attributes to caps + * + * prepend 'a-' to IANA registered sdp attributes names + * (ie: not prefixed with 'x-') in order to avoid + * collision with gstreamer standard caps properties names + */ +static void +gst_rtspsrc_sdp_attributes_to_caps (GArray * attributes, GstCaps * caps) +{ + if (attributes->len > 0) { + GstStructure *s; + guint i; + + s = gst_caps_get_structure (caps, 0); + + for (i = 0; i < attributes->len; i++) { + GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i); + gchar *tofree, *key; + + key = attr->key; + + /* skip some of the attribute we already handle */ + if (!strcmp (key, "fmtp")) + continue; + if (!strcmp (key, "rtpmap")) + continue; + if (!strcmp (key, "control")) + continue; + if (!strcmp (key, "range")) + continue; + if (!strcmp (key, "framesize")) + continue; + if (g_str_equal (key, "key-mgmt")) { + parse_keymgmt (attr->value, caps); + continue; + } + + /* string must be valid UTF8 */ + if (!g_utf8_validate (attr->value, -1, NULL)) + continue; + + if (!g_str_has_prefix (key, "x-")) + tofree = key = g_strdup_printf ("a-%s", key); + else + tofree = NULL; + + GST_DEBUG ("adding caps: %s=%s", key, attr->value); + gst_structure_set (s, key, G_TYPE_STRING, attr->value, NULL); + g_free (tofree); + } + } +} + +static const gchar * +rtsp_get_attribute_for_pt (const GstSDPMedia * media, const gchar * name, + gint pt) +{ + guint i; + + for (i = 0;; i++) { + const gchar *attr; + gint val; + + if ((attr = gst_sdp_media_get_attribute_val_n (media, name, i)) == NULL) + break; + + if (sscanf (attr, "%d ", &val) != 1) + continue; + + if (val == pt) + return attr; + } + return NULL; +} + +/* + * Mapping of caps to and from SDP fields: + * + * a=rtpmap:<payload> <encoding_name>/<clock_rate>[/<encoding_params>] + * a=framesize:<payload> <width>-<height> + * a=fmtp:<payload> <param>[=<value>];... + */ +static GstCaps * +gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) +{ + GstCaps *caps; + const gchar *rtpmap; + const gchar *fmtp; + const gchar *framesize; + gchar *name = NULL; + gint rate = -1; + gchar *params = NULL; + gchar *tmp; + GstStructure *s; + gint payload = 0; + gboolean ret; + + /* get and parse rtpmap */ + rtpmap = rtsp_get_attribute_for_pt (media, "rtpmap", pt); + + if (rtpmap) { + ret = gst_rtspsrc_parse_rtpmap (rtpmap, &payload, &name, &rate, ¶ms); + if (!ret) { + g_warning ("error parsing rtpmap, ignoring"); + rtpmap = NULL; + } + } + /* dynamic payloads need rtpmap or we fail */ + if (rtpmap == NULL && pt >= 96) + goto no_rtpmap; + + /* check if we have a rate, if not, we need to look up the rate from the + * default rates based on the payload types. */ + if (rate == -1) { + const GstRTPPayloadInfo *info; + + if (GST_RTP_PAYLOAD_IS_DYNAMIC (pt)) { + /* dynamic types, use media and encoding_name */ + tmp = g_ascii_strdown (media->media, -1); + info = gst_rtp_payload_info_for_name (tmp, name); + g_free (tmp); + } else { + /* static types, use payload type */ + info = gst_rtp_payload_info_for_pt (pt); + } + + if (info) { + if ((rate = info->clock_rate) == 0) + rate = -1; + } + /* we fail if we cannot find one */ + if (rate == -1) + goto no_rate; + } + + tmp = g_ascii_strdown (media->media, -1); + caps = gst_caps_new_simple ("application/x-unknown", + "media", G_TYPE_STRING, tmp, "payload", G_TYPE_INT, pt, NULL); + g_free (tmp); + s = gst_caps_get_structure (caps, 0); + + gst_structure_set (s, "clock-rate", G_TYPE_INT, rate, NULL); + + /* encoding name must be upper case */ + if (name != NULL) { + tmp = g_ascii_strup (name, -1); + gst_structure_set (s, "encoding-name", G_TYPE_STRING, tmp, NULL); + g_free (tmp); + } + + /* params must be lower case */ + if (params != NULL) { + tmp = g_ascii_strdown (params, -1); + gst_structure_set (s, "encoding-params", G_TYPE_STRING, tmp, NULL); + g_free (tmp); + } + + /* parse optional fmtp: field */ + if ((fmtp = rtsp_get_attribute_for_pt (media, "fmtp", pt))) { + gchar *p; + gint payload = 0; + + p = (gchar *) fmtp; + + /* p is now of the format <payload> <param>[=<value>];... */ + PARSE_INT (p, " ", payload); + if (payload != -1 && payload == pt) { + gchar **pairs; + gint i; + + /* <param>[=<value>] are separated with ';' */ + pairs = g_strsplit (p, ";", 0); + for (i = 0; pairs[i]; i++) { + gchar *valpos; + const gchar *val, *key; + + /* the key may not have a '=', the value can have other '='s */ + valpos = strstr (pairs[i], "="); + if (valpos) { + /* we have a '=' and thus a value, remove the '=' with \0 */ + *valpos = '\0'; + /* value is everything between '=' and ';'. We split the pairs at ; + * boundaries so we can take the remainder of the value. Some servers + * put spaces around the value which we strip off here. Alternatively + * we could strip those spaces in the depayloaders should these spaces + * actually carry any meaning in the future. */ + val = g_strstrip (valpos + 1); + } else { + /* simple <param>;.. is translated into <param>=1;... */ + val = "1"; + } + /* strip the key of spaces, convert key to lowercase but not the value. */ + key = g_strstrip (pairs[i]); + if (strlen (key) > 1) { + tmp = g_ascii_strdown (key, -1); + gst_structure_set (s, tmp, G_TYPE_STRING, val, NULL); + g_free (tmp); + } + } + g_strfreev (pairs); + } + } + + /* parse framesize: field */ + if ((framesize = gst_sdp_media_get_attribute_val (media, "framesize"))) { + gchar *p; + + /* p is now of the format <payload> <width>-<height> */ + p = (gchar *) framesize; + + PARSE_INT (p, " ", payload); + if (payload != -1 && payload == pt) { + gst_structure_set (s, "a-framesize", G_TYPE_STRING, p, NULL); + } + } + return caps; + + /* ERRORS */ +no_rtpmap: + { + g_warning ("rtpmap type not given for dynamic payload %d", pt); + return NULL; + } +no_rate: + { + g_warning ("rate unknown for payload type %d", pt); + return NULL; + } +} + +static gboolean +gst_rtspsrc_alloc_udp_ports (GstRTSPStream * stream, + gint * rtpport, gint * rtcpport) +{ + GstRTSPSrc *src; + GstStateChangeReturn ret; + GstElement *udpsrc0, *udpsrc1; + gint tmp_rtp, tmp_rtcp; + guint count; + const gchar *host; + + src = stream->parent; + + udpsrc0 = NULL; + udpsrc1 = NULL; + count = 0; + + /* Start at next port */ + tmp_rtp = src->next_port_num; + + if (stream->is_ipv6) + host = "udp://[::0]"; + else + host = "udp://0.0.0.0"; + + /* try to allocate 2 UDP ports, the RTP port should be an even + * number and the RTCP port should be the next (uneven) port */ +again: + + if (tmp_rtp != 0 && src->client_port_range.max > 0 && + tmp_rtp >= src->client_port_range.max) + goto no_ports; + + udpsrc0 = gst_element_make_from_uri (GST_URI_SRC, host, NULL, NULL); + if (udpsrc0 == NULL) + goto no_udp_protocol; + g_object_set (G_OBJECT (udpsrc0), "port", tmp_rtp, "reuse", FALSE, NULL); + + if (src->udp_buffer_size != 0) + g_object_set (G_OBJECT (udpsrc0), "buffer-size", src->udp_buffer_size, + NULL); + + ret = gst_element_set_state (udpsrc0, GST_STATE_READY); + if (ret == GST_STATE_CHANGE_FAILURE) { + if (tmp_rtp != 0) { + GST_DEBUG_OBJECT (src, "Unable to make udpsrc from RTP port %d", tmp_rtp); + + tmp_rtp += 2; + if (++count > src->retry) + goto no_ports; + + GST_DEBUG_OBJECT (src, "free RTP udpsrc"); + gst_element_set_state (udpsrc0, GST_STATE_NULL); + gst_object_unref (udpsrc0); + udpsrc0 = NULL; + + GST_DEBUG_OBJECT (src, "retry %d", count); + goto again; + } + goto no_udp_protocol; + } + + g_object_get (G_OBJECT (udpsrc0), "port", &tmp_rtp, NULL); + GST_DEBUG_OBJECT (src, "got RTP port %d", tmp_rtp); + + /* check if port is even */ + if ((tmp_rtp & 0x01) != 0) { + /* port not even, close and allocate another */ + if (++count > src->retry) + goto no_ports; + + GST_DEBUG_OBJECT (src, "RTP port not even"); + + GST_DEBUG_OBJECT (src, "free RTP udpsrc"); + gst_element_set_state (udpsrc0, GST_STATE_NULL); + gst_object_unref (udpsrc0); + udpsrc0 = NULL; + + GST_DEBUG_OBJECT (src, "retry %d", count); + tmp_rtp++; + goto again; + } + + /* allocate port+1 for RTCP now */ + udpsrc1 = gst_element_make_from_uri (GST_URI_SRC, host, NULL, NULL); + if (udpsrc1 == NULL) + goto no_udp_rtcp_protocol; + + /* set port */ + tmp_rtcp = tmp_rtp + 1; + if (src->client_port_range.max > 0 && tmp_rtcp > src->client_port_range.max) + goto no_ports; + + g_object_set (G_OBJECT (udpsrc1), "port", tmp_rtcp, "reuse", FALSE, NULL); + + GST_DEBUG_OBJECT (src, "starting RTCP on port %d", tmp_rtcp); + ret = gst_element_set_state (udpsrc1, GST_STATE_READY); + /* tmp_rtcp port is busy already : retry to make rtp/rtcp pair */ + if (ret == GST_STATE_CHANGE_FAILURE) { + GST_DEBUG_OBJECT (src, "Unable to make udpsrc from RTCP port %d", tmp_rtcp); + + if (++count > src->retry) + goto no_ports; + + GST_DEBUG_OBJECT (src, "free RTP udpsrc"); + gst_element_set_state (udpsrc0, GST_STATE_NULL); + gst_object_unref (udpsrc0); + udpsrc0 = NULL; + + GST_DEBUG_OBJECT (src, "free RTCP udpsrc"); + gst_element_set_state (udpsrc1, GST_STATE_NULL); + gst_object_unref (udpsrc1); + udpsrc1 = NULL; + + tmp_rtp += 2; + GST_DEBUG_OBJECT (src, "retry %d", count); + goto again; + } + + /* all fine, do port check */ + g_object_get (G_OBJECT (udpsrc0), "port", rtpport, NULL); + g_object_get (G_OBJECT (udpsrc1), "port", rtcpport, NULL); + + /* this should not happen... */ + if (*rtpport != tmp_rtp || *rtcpport != tmp_rtcp) + goto port_error; + + /* we keep these elements, we configure all in configure_transport when the + * server told us to really use the UDP ports. */ + stream->udpsrc[0] = gst_object_ref_sink (udpsrc0); + stream->udpsrc[1] = gst_object_ref_sink (udpsrc1); + gst_element_set_locked_state (stream->udpsrc[0], TRUE); + gst_element_set_locked_state (stream->udpsrc[1], TRUE); + + /* keep track of next available port number when we have a range + * configured */ + if (src->next_port_num != 0) + src->next_port_num = tmp_rtcp + 1; + + return TRUE; + + /* ERRORS */ +no_udp_protocol: + { + GST_DEBUG_OBJECT (src, "could not get UDP source"); + goto cleanup; + } +no_ports: + { + GST_DEBUG_OBJECT (src, "could not allocate UDP port pair after %d retries", + count); + goto cleanup; + } +no_udp_rtcp_protocol: + { + GST_DEBUG_OBJECT (src, "could not get UDP source for RTCP"); + goto cleanup; + } +port_error: + { + GST_DEBUG_OBJECT (src, "ports don't match rtp: %d<->%d, rtcp: %d<->%d", + tmp_rtp, *rtpport, tmp_rtcp, *rtcpport); + goto cleanup; + } +cleanup: + { + if (udpsrc0) { + gst_element_set_state (udpsrc0, GST_STATE_NULL); + gst_object_unref (udpsrc0); + } + if (udpsrc1) { + gst_element_set_state (udpsrc1, GST_STATE_NULL); + gst_object_unref (udpsrc1); + } + return FALSE; + } +} + +static void +gst_rtspsrc_set_state (GstRTSPSrc * src, GstState state) +{ + GList *walk; + + if (src->manager) + gst_element_set_state (GST_ELEMENT_CAST (src->manager), state); + + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + gint i; + + for (i = 0; i < 2; i++) { + if (stream->udpsrc[i]) + gst_element_set_state (stream->udpsrc[i], state); + } + } +} + +static void +gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing) +{ + GstEvent *event; + gint cmd; + GstState state; + + if (flush) { + event = gst_event_new_flush_start (); + GST_DEBUG_OBJECT (src, "start flush"); + cmd = CMD_WAIT; + state = GST_STATE_PAUSED; + } else { + event = gst_event_new_flush_stop (FALSE); + GST_DEBUG_OBJECT (src, "stop flush; playing %d", playing); + cmd = CMD_LOOP; + if (playing) + state = GST_STATE_PLAYING; + else + state = GST_STATE_PAUSED; + } + gst_rtspsrc_push_event (src, event); + gst_rtspsrc_loop_send_cmd (src, cmd, CMD_LOOP); + gst_rtspsrc_set_state (src, state); +} + +static GstRTSPResult +gst_rtspsrc_connection_send (GstRTSPSrc * src, GstRTSPConnection * conn, + GstRTSPMessage * message, GTimeVal * timeout) +{ + GstRTSPResult ret; + + if (conn) + ret = gst_rtsp_connection_send (conn, message, timeout); + else + ret = GST_RTSP_ERROR; + + return ret; +} + +static GstRTSPResult +gst_rtspsrc_connection_receive (GstRTSPSrc * src, GstRTSPConnection * conn, + GstRTSPMessage * message, GTimeVal * timeout) +{ + GstRTSPResult ret; + + if (conn) + ret = gst_rtsp_connection_receive (conn, message, timeout); + else + ret = GST_RTSP_ERROR; + + return ret; +} + +static void +gst_rtspsrc_get_position (GstRTSPSrc * src) +{ + GstQuery *query; + GList *walk; + + query = gst_query_new_position (GST_FORMAT_TIME); + /* should be known somewhere down the stream (e.g. jitterbuffer) */ + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + GstFormat fmt; + gint64 pos; + + if (stream->srcpad) { + if (gst_pad_query (stream->srcpad, query)) { + gst_query_parse_position (query, &fmt, &pos); + GST_DEBUG_OBJECT (src, "retaining position %" GST_TIME_FORMAT, + GST_TIME_ARGS (pos)); + src->last_pos = pos; + goto out; + } + } + } + + src->last_pos = 0; + +out: + + gst_query_unref (query); +} + +static gboolean +gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) +{ + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType cur_type = GST_SEEK_TYPE_NONE, stop_type; + gint64 cur, stop; + gboolean flush, skip; + gboolean update; + gboolean playing; + GstSegment seeksegment = { 0, }; + GList *walk; + + if (event) { + GST_DEBUG_OBJECT (src, "doing seek with event"); + + gst_event_parse_seek (event, &rate, &format, &flags, + &cur_type, &cur, &stop_type, &stop); + + /* no negative rates yet */ + if (rate < 0.0) + goto negative_rate; + + /* we need TIME format */ + if (format != src->segment.format) + goto no_format; + } else { + GST_DEBUG_OBJECT (src, "doing seek without event"); + flags = 0; + cur_type = GST_SEEK_TYPE_SET; + stop_type = GST_SEEK_TYPE_SET; + } + + /* get flush flag */ + flush = flags & GST_SEEK_FLAG_FLUSH; + skip = flags & GST_SEEK_FLAG_SKIP; + + /* now we need to make sure the streaming thread is stopped. We do this by + * either sending a FLUSH_START event downstream which will cause the + * streaming thread to stop with a WRONG_STATE. + * For a non-flushing seek we simply pause the task, which will happen as soon + * as it completes one iteration (and thus might block when the sink is + * blocking in preroll). */ + if (flush) { + GST_DEBUG_OBJECT (src, "starting flush"); + gst_rtspsrc_flush (src, TRUE, FALSE); + } else { + if (src->task) { + gst_task_pause (src->task); + } + } + + /* we should now be able to grab the streaming thread because we stopped it + * with the above flush/pause code */ + GST_RTSP_STREAM_LOCK (src); + + GST_DEBUG_OBJECT (src, "stopped streaming"); + + /* stop flushing the rtsp connection so we can send PAUSE/PLAY below */ + gst_rtspsrc_connection_flush (src, FALSE); + + /* copy segment, we need this because we still need the old + * segment when we close the current segment. */ + memcpy (&seeksegment, &src->segment, sizeof (GstSegment)); + + /* configure the seek parameters in the seeksegment. We will then have the + * right values in the segment to perform the seek */ + if (event) { + GST_DEBUG_OBJECT (src, "configuring seek"); + gst_segment_do_seek (&seeksegment, rate, format, flags, + cur_type, cur, stop_type, stop, &update); + } + + /* figure out the last position we need to play. If it's configured (stop != + * -1), use that, else we play until the total duration of the file */ + if ((stop = seeksegment.stop) == -1) + stop = seeksegment.duration; + + playing = (src->state == GST_RTSP_STATE_PLAYING); + + /* if we were playing, pause first */ + if (playing) { + /* obtain current position in case seek fails */ + gst_rtspsrc_get_position (src); + gst_rtspsrc_pause (src, FALSE); + } + src->skip = skip; + + src->state = GST_RTSP_STATE_SEEKING; + + /* PLAY will add the range header now. */ + src->need_range = TRUE; + + /* and continue playing */ + if (playing) + gst_rtspsrc_play (src, &seeksegment, FALSE); + + /* prepare for streaming again */ + if (flush) { + /* if we started flush, we stop now */ + GST_DEBUG_OBJECT (src, "stopping flush"); + gst_rtspsrc_flush (src, FALSE, playing); + } + + /* now we did the seek and can activate the new segment values */ + memcpy (&src->segment, &seeksegment, sizeof (GstSegment)); + + /* if we're doing a segment seek, post a SEGMENT_START message */ + if (src->segment.flags & GST_SEEK_FLAG_SEGMENT) { + gst_element_post_message (GST_ELEMENT_CAST (src), + gst_message_new_segment_start (GST_OBJECT_CAST (src), + src->segment.format, src->segment.position)); + } + + /* now create the newsegment */ + GST_DEBUG_OBJECT (src, "Creating newsegment from %" G_GINT64_FORMAT + " to %" G_GINT64_FORMAT, src->segment.position, stop); + + /* mark discont */ + GST_DEBUG_OBJECT (src, "mark DISCONT, we did a seek to another position"); + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + stream->discont = TRUE; + } + + GST_RTSP_STREAM_UNLOCK (src); + + return TRUE; + + /* ERRORS */ +negative_rate: + { + GST_DEBUG_OBJECT (src, "negative playback rates are not supported yet."); + return FALSE; + } +no_format: + { + GST_DEBUG_OBJECT (src, "unsupported format given, seek aborted."); + return FALSE; + } +} + +static gboolean +gst_rtspsrc_handle_src_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + GstRTSPSrc *src; + gboolean res = TRUE; + gboolean forward; + + src = GST_RTSPSRC_CAST (parent); + + GST_DEBUG_OBJECT (src, "pad %s:%s received event %s", + GST_DEBUG_PAD_NAME (pad), GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + res = gst_rtspsrc_perform_seek (src, event); + forward = FALSE; + break; + case GST_EVENT_QOS: + case GST_EVENT_NAVIGATION: + case GST_EVENT_LATENCY: + default: + forward = TRUE; + break; + } + if (forward) { + GstPad *target; + + if ((target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (pad)))) { + res = gst_pad_send_event (target, event); + gst_object_unref (target); + } else { + gst_event_unref (event); + } + } else { + gst_event_unref (event); + } + + return res; +} + +/* this is the final event function we receive on the internal source pad when + * we deal with TCP connections */ +static gboolean +gst_rtspsrc_handle_internal_src_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + gboolean res; + + GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + case GST_EVENT_QOS: + case GST_EVENT_NAVIGATION: + case GST_EVENT_LATENCY: + default: + gst_event_unref (event); + res = TRUE; + break; + } + return res; +} + +/* this is the final query function we receive on the internal source pad when + * we deal with TCP connections */ +static gboolean +gst_rtspsrc_handle_internal_src_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + GstRTSPSrc *src; + gboolean res = TRUE; + + src = GST_RTSPSRC_CAST (gst_pad_get_element_private (pad)); + + GST_DEBUG_OBJECT (src, "pad %s:%s received query %s", + GST_DEBUG_PAD_NAME (pad), GST_QUERY_TYPE_NAME (query)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + /* no idea */ + break; + } + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration (query, &format, NULL); + + switch (format) { + case GST_FORMAT_TIME: + gst_query_set_duration (query, format, src->segment.duration); + break; + default: + res = FALSE; + break; + } + break; + } + case GST_QUERY_LATENCY: + { + /* we are live with a min latency of 0 and unlimited max latency, this + * result will be updated by the session manager if there is any. */ + gst_query_set_latency (query, TRUE, 0, -1); + break; + } + default: + break; + } + + return res; +} + +/* this query is executed on the ghost source pad exposed on rtspsrc. */ +static gboolean +gst_rtspsrc_handle_src_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + GstRTSPSrc *src; + gboolean res = FALSE; + + src = GST_RTSPSRC_CAST (parent); + + GST_DEBUG_OBJECT (src, "pad %s:%s received query %s", + GST_DEBUG_PAD_NAME (pad), GST_QUERY_TYPE_NAME (query)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration (query, &format, NULL); + + switch (format) { + case GST_FORMAT_TIME: + gst_query_set_duration (query, format, src->segment.duration); + res = TRUE; + break; + default: + break; + } + break; + } + case GST_QUERY_SEEKING: + { + GstFormat format; + + gst_query_parse_seeking (query, &format, NULL, NULL, NULL); + if (format == GST_FORMAT_TIME) { + gboolean seekable = + src->cur_protocols != GST_RTSP_LOWER_TRANS_UDP_MCAST; + + /* seeking without duration is unlikely */ + seekable = seekable && src->seekable && src->segment.duration && + GST_CLOCK_TIME_IS_VALID (src->segment.duration); + + gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, + src->segment.duration); + res = TRUE; + } + break; + } + case GST_QUERY_URI: + { + gchar *uri; + + uri = gst_rtspsrc_uri_get_uri (GST_URI_HANDLER (src)); + if (uri != NULL) { + gst_query_set_uri (query, uri); + g_free (uri); + res = TRUE; + } + break; + } + default: + { + GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (pad)); + + /* forward the query to the proxy target pad */ + if (target) { + res = gst_pad_query (target, query); + gst_object_unref (target); + } + break; + } + } + + return res; +} + +/* callback for RTCP messages to be sent to the server when operating in TCP + * mode. */ +static GstFlowReturn +gst_rtspsrc_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + GstRTSPSrc *src; + GstRTSPStream *stream; + GstFlowReturn res = GST_FLOW_OK; + GstMapInfo map; + guint8 *data; + guint size; + GstRTSPResult ret; + GstRTSPMessage message = { 0 }; + GstRTSPConnection *conn; + + stream = (GstRTSPStream *) gst_pad_get_element_private (pad); + src = stream->parent; + + gst_buffer_map (buffer, &map, GST_MAP_READ); + size = map.size; + data = map.data; + + gst_rtsp_message_init_data (&message, stream->channel[1]); + + /* lend the body data to the message */ + gst_rtsp_message_take_body (&message, data, size); + + if (stream->conninfo.connection) + conn = stream->conninfo.connection; + else + conn = src->conninfo.connection; + + GST_DEBUG_OBJECT (src, "sending %u bytes RTCP", size); + ret = gst_rtspsrc_connection_send (src, conn, &message, NULL); + GST_DEBUG_OBJECT (src, "sent RTCP, %d", ret); + + /* and steal it away again because we will free it when unreffing the + * buffer */ + gst_rtsp_message_steal_body (&message, &data, &size); + gst_rtsp_message_unset (&message); + + gst_buffer_unmap (buffer, &map); + gst_buffer_unref (buffer); + + return res; +} + +static GstPadProbeReturn +pad_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstRTSPSrc *src = user_data; + + GST_DEBUG_OBJECT (src, "pad %s:%s blocked, activating streams", + GST_DEBUG_PAD_NAME (pad)); + + /* activate the streams */ + GST_OBJECT_LOCK (src); + if (!src->need_activate) + goto was_ok; + + src->need_activate = FALSE; + GST_OBJECT_UNLOCK (src); + + gst_rtspsrc_activate_streams (src); + + return GST_PAD_PROBE_OK; + +was_ok: + { + GST_OBJECT_UNLOCK (src); + return GST_PAD_PROBE_OK; + } +} + +static gboolean +copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data) +{ + GstPad *gpad = GST_PAD_CAST (user_data); + + GST_DEBUG_OBJECT (gpad, "store sticky event %" GST_PTR_FORMAT, *event); + gst_pad_store_sticky_event (gpad, *event); + + return TRUE; +} + +/* this callback is called when the session manager generated a new src pad with + * payloaded RTP packets. We simply ghost the pad here. */ +static void +new_manager_pad (GstElement * manager, GstPad * pad, GstRTSPSrc * src) +{ + gchar *name; + GstPadTemplate *template; + gint id, ssrc, pt; + GList *ostreams; + GstRTSPStream *stream; + gboolean all_added; + + GST_DEBUG_OBJECT (src, "got new manager pad %" GST_PTR_FORMAT, pad); + + GST_RTSP_STATE_LOCK (src); + /* find stream */ + name = gst_object_get_name (GST_OBJECT_CAST (pad)); + if (sscanf (name, "recv_rtp_src_%u_%u_%u", &id, &ssrc, &pt) != 3) + goto unknown_stream; + + GST_DEBUG_OBJECT (src, "stream: %u, SSRC %08x, PT %d", id, ssrc, pt); + + stream = find_stream (src, &id, (gpointer) find_stream_by_id); + if (stream == NULL) + goto unknown_stream; + + /* save SSRC */ + stream->ssrc = ssrc; + + /* we'll add it later see below */ + stream->added = TRUE; + + /* check if we added all streams */ + all_added = TRUE; + for (ostreams = src->streams; ostreams; ostreams = g_list_next (ostreams)) { + GstRTSPStream *ostream = (GstRTSPStream *) ostreams->data; + + GST_DEBUG_OBJECT (src, "stream %p, container %d, added %d, setup %d", + ostream, ostream->container, ostream->added, ostream->setup); + + /* if we find a stream for which we did a setup that is not added, we + * need to wait some more */ + if (ostream->setup && !ostream->added) { + all_added = FALSE; + break; + } + } + GST_RTSP_STATE_UNLOCK (src); + + /* create a new pad we will use to stream to */ + template = gst_static_pad_template_get (&rtptemplate); + stream->srcpad = gst_ghost_pad_new_from_template (name, pad, template); + gst_object_unref (template); + g_free (name); + + gst_pad_set_event_function (stream->srcpad, gst_rtspsrc_handle_src_event); + gst_pad_set_query_function (stream->srcpad, gst_rtspsrc_handle_src_query); + gst_pad_set_active (stream->srcpad, TRUE); + gst_pad_sticky_events_foreach (pad, copy_sticky_events, stream->srcpad); + gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad); + + if (all_added) { + GST_DEBUG_OBJECT (src, "We added all streams"); + /* when we get here, all stream are added and we can fire the no-more-pads + * signal. */ + gst_element_no_more_pads (GST_ELEMENT_CAST (src)); + } + + return; + + /* ERRORS */ +unknown_stream: + { + GST_DEBUG_OBJECT (src, "ignoring unknown stream"); + GST_RTSP_STATE_UNLOCK (src); + g_free (name); + return; + } +} + +static GstCaps * +stream_get_caps_for_pt (GstRTSPStream * stream, guint pt) +{ + guint i, len; + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + if (item->pt == pt) + return item->caps; + } + return NULL; +} + +static GstCaps * +request_pt_map (GstElement * manager, guint session, guint pt, GstRTSPSrc * src) +{ + GstRTSPStream *stream; + GstCaps *caps; + + GST_DEBUG_OBJECT (src, "getting pt map for pt %d in session %d", pt, session); + + GST_RTSP_STATE_LOCK (src); + stream = find_stream (src, &session, (gpointer) find_stream_by_id); + if (!stream) + goto unknown_stream; + + if ((caps = stream_get_caps_for_pt (stream, pt))) + gst_caps_ref (caps); + GST_RTSP_STATE_UNLOCK (src); + + return caps; + +unknown_stream: + { + GST_DEBUG_OBJECT (src, "unknown stream %d", session); + GST_RTSP_STATE_UNLOCK (src); + return NULL; + } +} + +static void +gst_rtspsrc_do_stream_eos (GstRTSPSrc * src, GstRTSPStream * stream) +{ + GST_DEBUG_OBJECT (src, "setting stream for session %u to EOS", stream->id); + if (stream->eos) + goto was_eos; + + stream->eos = TRUE; + gst_rtspsrc_stream_push_event (src, stream, gst_event_new_eos ()); + return; + + /* ERRORS */ +was_eos: + { + GST_DEBUG_OBJECT (src, "stream for session %u was already EOS", stream->id); + return; + } +} + +static void +on_bye_ssrc (GObject * session, GObject * source, GstRTSPStream * stream) +{ + GstRTSPSrc *src = stream->parent; + guint ssrc; + + g_object_get (source, "ssrc", &ssrc, NULL); + + GST_DEBUG_OBJECT (src, "source %08x, stream %08x, session %u received BYE", + ssrc, stream->ssrc, stream->id); + + if (ssrc == stream->ssrc) + gst_rtspsrc_do_stream_eos (src, stream); +} + +static void +on_timeout (GObject * session, GObject * source, GstRTSPStream * stream) +{ + GstRTSPSrc *src = stream->parent; + guint ssrc; + + g_object_get (source, "ssrc", &ssrc, NULL); + + GST_WARNING_OBJECT (src, "source %08x, stream %08x in session %u timed out", + ssrc, stream->ssrc, stream->id); + + if (ssrc == stream->ssrc) + gst_rtspsrc_do_stream_eos (src, stream); +} + +static void +on_npt_stop (GstElement * rtpbin, guint session, guint ssrc, GstRTSPSrc * src) +{ + GstRTSPStream *stream; + + GST_DEBUG_OBJECT (src, "source in session %u reached NPT stop", session); + + /* get stream for session */ + stream = find_stream (src, &session, (gpointer) find_stream_by_id); + if (stream) { + gst_rtspsrc_do_stream_eos (src, stream); + } +} + +static void +on_ssrc_active (GObject * session, GObject * source, GstRTSPStream * stream) +{ + GST_DEBUG_OBJECT (stream->parent, "source in session %u is active", + stream->id); +} + +static void +set_manager_buffer_mode (GstRTSPSrc * src) +{ + GObjectClass *klass; + + if (src->manager == NULL) + return; + + klass = G_OBJECT_GET_CLASS (G_OBJECT (src->manager)); + + if (!g_object_class_find_property (klass, "buffer-mode")) + return; + + if (src->buffer_mode != BUFFER_MODE_AUTO) { + g_object_set (src->manager, "buffer-mode", src->buffer_mode, NULL); + + return; + } + + GST_DEBUG_OBJECT (src, + "auto buffering mode, have clock %" GST_PTR_FORMAT, src->provided_clock); + + if (src->provided_clock) { + GstClock *clock = gst_element_get_clock (GST_ELEMENT_CAST (src)); + + if (clock == src->provided_clock) { + GST_DEBUG_OBJECT (src, "selected synced"); + g_object_set (src->manager, "buffer-mode", BUFFER_MODE_SYNCED, NULL); + + if (clock) + gst_object_unref (clock); + + return; + } + + /* Otherwise fall-through and use another buffer mode */ + if (clock) + gst_object_unref (clock); + } + + GST_DEBUG_OBJECT (src, "auto buffering mode"); + if (src->use_buffering) { + GST_DEBUG_OBJECT (src, "selected buffer"); + g_object_set (src->manager, "buffer-mode", BUFFER_MODE_BUFFER, NULL); + } else { + GST_DEBUG_OBJECT (src, "selected slave"); + g_object_set (src->manager, "buffer-mode", BUFFER_MODE_SLAVE, NULL); + } +} + +static GstCaps * +request_key (GstElement * srtpdec, guint ssrc, GstRTSPStream * stream) +{ + GST_DEBUG ("request key %u", ssrc); + return gst_caps_ref (stream_get_caps_for_pt (stream, stream->default_pt)); +} + +static GstElement * +request_rtp_decoder (GstElement * rtpbin, guint session, GstRTSPStream * stream) +{ + GST_DEBUG ("decoder session %u, stream %p, %d", session, stream, stream->id); + if (stream->id != session) + return NULL; + + if (stream->profile != GST_RTSP_PROFILE_SAVP && + stream->profile != GST_RTSP_PROFILE_SAVPF) + return NULL; + + if (stream->srtpdec == NULL) { + gchar *name; + + name = g_strdup_printf ("srtpdec_%u", session); + stream->srtpdec = gst_element_factory_make ("srtpdec", name); + g_free (name); + + g_signal_connect (stream->srtpdec, "request-key", + (GCallback) request_key, stream); + } + return gst_object_ref (stream->srtpdec); +} + +static GstElement * +request_rtcp_encoder (GstElement * rtpbin, guint session, + GstRTSPStream * stream) +{ + gchar *name; + GstPad *pad; + + GST_DEBUG ("decoder session %u, stream %p, %d", session, stream, stream->id); + if (stream->id != session) + return NULL; + + if (stream->profile != GST_RTSP_PROFILE_SAVP && + stream->profile != GST_RTSP_PROFILE_SAVPF) + return NULL; + + if (stream->srtpenc == NULL) { + GstStructure *s; + + name = g_strdup_printf ("srtpenc_%u", session); + stream->srtpenc = gst_element_factory_make ("srtpenc", name); + g_free (name); + + /* get RTCP crypto parameters from caps */ + s = gst_caps_get_structure (stream->srtcpparams, 0); + if (s) { + GstBuffer *buf; + const gchar *str; + GType ciphertype, authtype; + GValue rtcp_cipher = G_VALUE_INIT, rtcp_auth = G_VALUE_INIT; + + ciphertype = g_type_from_name ("GstSrtpCipherType"); + authtype = g_type_from_name ("GstSrtpAuthType"); + g_value_init (&rtcp_cipher, ciphertype); + g_value_init (&rtcp_auth, authtype); + + str = gst_structure_get_string (s, "srtcp-cipher"); + gst_value_deserialize (&rtcp_cipher, str); + str = gst_structure_get_string (s, "srtcp-auth"); + gst_value_deserialize (&rtcp_auth, str); + gst_structure_get (s, "srtp-key", GST_TYPE_BUFFER, &buf, NULL); + + g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-cipher", + &rtcp_cipher); + g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-auth", + &rtcp_auth); + g_object_set (stream->srtpenc, "key", buf, NULL); + + g_value_unset (&rtcp_cipher); + g_value_unset (&rtcp_auth); + gst_buffer_unref (buf); + } + } + name = g_strdup_printf ("rtcp_sink_%d", session); + pad = gst_element_get_request_pad (stream->srtpenc, name); + g_free (name); + gst_object_unref (pad); + + return gst_object_ref (stream->srtpenc); +} + +static GstElement * +request_aux_receiver (GstElement * rtpbin, guint sessid, GstRTSPSrc * src) +{ + GstElement *rtx, *bin; + GstPad *pad; + gchar *name; + GstRTSPStream *stream; + + stream = find_stream (src, &sessid, (gpointer) find_stream_by_id); + if (!stream) { + GST_WARNING_OBJECT (src, "Stream %u not found", sessid); + return NULL; + } + + GST_INFO_OBJECT (src, "creating retransmision receiver for session %u " + "with map %" GST_PTR_FORMAT, sessid, stream->rtx_pt_map); + bin = gst_bin_new (NULL); + rtx = gst_element_factory_make ("rtprtxreceive", NULL); + g_object_set (rtx, "payload-type-map", stream->rtx_pt_map, NULL); + gst_bin_add (GST_BIN (bin), rtx); + + pad = gst_element_get_static_pad (rtx, "src"); + name = g_strdup_printf ("src_%u", sessid); + gst_element_add_pad (bin, gst_ghost_pad_new (name, pad)); + g_free (name); + gst_object_unref (pad); + + pad = gst_element_get_static_pad (rtx, "sink"); + name = g_strdup_printf ("sink_%u", sessid); + gst_element_add_pad (bin, gst_ghost_pad_new (name, pad)); + g_free (name); + gst_object_unref (pad); + + return bin; +} + +static void +add_retransmission (GstRTSPSrc * src, GstRTSPTransport * transport) +{ + GList *walk; + guint signal_id; + gboolean do_retransmission = FALSE; + + if (transport->trans != GST_RTSP_TRANS_RTP) + return; + if (transport->profile != GST_RTSP_PROFILE_AVPF && + transport->profile != GST_RTSP_PROFILE_SAVPF) + return; + + signal_id = g_signal_lookup ("request-aux-receiver", + G_OBJECT_TYPE (src->manager)); + /* there's already something connected */ + if (g_signal_handler_find (src->manager, G_SIGNAL_MATCH_ID, signal_id, 0, + NULL, NULL, NULL) != 0) { + GST_DEBUG_OBJECT (src, "Not adding RTX AUX element as " + "\"request-aux-receiver\" signal is " + "already used by the application"); + return; + } + + /* build the retransmission payload type map */ + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + gboolean do_retransmission_stream = FALSE; + int i; + + if (stream->rtx_pt_map) + gst_structure_free (stream->rtx_pt_map); + stream->rtx_pt_map = gst_structure_new_empty ("application/x-rtp-pt-map"); + + for (i = 0; i < stream->ptmap->len; i++) { + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + GstStructure *s = gst_caps_get_structure (item->caps, 0); + const gchar *encoding; + + /* we only care about RTX streams */ + if ((encoding = gst_structure_get_string (s, "encoding-name")) + && g_strcmp0 (encoding, "RTX") == 0) { + const gchar *stream_pt_s; + gint rtx_pt; + + if (gst_structure_get_int (s, "payload", &rtx_pt) + && (stream_pt_s = gst_structure_get_string (s, "apt"))) { + + if (rtx_pt != 0) { + gst_structure_set (stream->rtx_pt_map, stream_pt_s, G_TYPE_UINT, + rtx_pt, NULL); + do_retransmission_stream = TRUE; + } + } + } + } + + if (do_retransmission_stream) { + GST_DEBUG_OBJECT (src, "built retransmission payload map for stream " + "id %i: %" GST_PTR_FORMAT, stream->id, stream->rtx_pt_map); + do_retransmission = TRUE; + } else { + GST_DEBUG_OBJECT (src, "no retransmission payload map for stream " + "id %i", stream->id); + gst_structure_free (stream->rtx_pt_map); + stream->rtx_pt_map = NULL; + } + } + + if (do_retransmission) { + GST_DEBUG_OBJECT (src, "Enabling retransmissions"); + + g_object_set (src->manager, "do-retransmission", TRUE, NULL); + + /* enable RFC4588 retransmission handling by setting rtprtxreceive + * as the "aux" element of rtpbin */ + g_signal_connect (src->manager, "request-aux-receiver", + (GCallback) request_aux_receiver, src); + } else { + GST_DEBUG_OBJECT (src, + "Not enabling retransmissions as no stream had a retransmission payload map"); + } +} + +/* try to get and configure a manager */ +static gboolean +gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, + GstRTSPTransport * transport) +{ + const gchar *manager; + gchar *name; + GstStateChangeReturn ret; + + /* find a manager */ + if (gst_rtsp_transport_get_manager (transport->trans, &manager, 0) < 0) + goto no_manager; + + if (manager) { + GST_DEBUG_OBJECT (src, "using manager %s", manager); + + /* configure the manager */ + if (src->manager == NULL) { + GObjectClass *klass; + + if (!(src->manager = gst_element_factory_make (manager, "manager"))) { + /* fallback */ + if (gst_rtsp_transport_get_manager (transport->trans, &manager, 1) < 0) + goto no_manager; + + if (!manager) + goto use_no_manager; + + if (!(src->manager = gst_element_factory_make (manager, "manager"))) + goto manager_failed; + } + + /* we manage this element */ + gst_element_set_locked_state (src->manager, TRUE); + gst_bin_add (GST_BIN_CAST (src), src->manager); + + ret = gst_element_set_state (src->manager, GST_STATE_PAUSED); + if (ret == GST_STATE_CHANGE_FAILURE) + goto start_manager_failure; + + g_object_set (src->manager, "latency", src->latency, NULL); + + klass = G_OBJECT_GET_CLASS (G_OBJECT (src->manager)); + + if (g_object_class_find_property (klass, "ntp-sync")) { + g_object_set (src->manager, "ntp-sync", src->ntp_sync, NULL); + } + + if (g_object_class_find_property (klass, "use-pipeline-clock")) { + g_object_set (src->manager, "use-pipeline-clock", + src->use_pipeline_clock, NULL); + } + + if (src->sdes && g_object_class_find_property (klass, "sdes")) { + g_object_set (src->manager, "sdes", src->sdes, NULL); + } + + if (g_object_class_find_property (klass, "drop-on-latency")) { + g_object_set (src->manager, "drop-on-latency", src->drop_on_latency, + NULL); + } + + /* buffer mode pauses are handled by adding offsets to buffer times, + * but some depayloaders may have a hard time syncing output times + * with such input times, e.g. container ones, most notably ASF */ + /* TODO alternatives are having an event that indicates these shifts, + * or having rtsp extensions provide suggestion on buffer mode */ + /* valid duration implies not likely live pipeline, + * so slaving in jitterbuffer does not make much sense + * (and might mess things up due to bursts) */ + if (GST_CLOCK_TIME_IS_VALID (src->segment.duration) && + src->segment.duration && stream->container) { + src->use_buffering = TRUE; + } else { + src->use_buffering = FALSE; + } + + set_manager_buffer_mode (src); + + /* connect to signals */ + GST_DEBUG_OBJECT (src, "connect to signals on session manager, stream %p", + stream); + src->manager_sig_id = + g_signal_connect (src->manager, "pad-added", + (GCallback) new_manager_pad, src); + src->manager_ptmap_id = + g_signal_connect (src->manager, "request-pt-map", + (GCallback) request_pt_map, src); + + g_signal_connect (src->manager, "on-npt-stop", (GCallback) on_npt_stop, + src); + + g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_NEW_MANAGER], 0, + src->manager); + + if (src->do_retransmission) + add_retransmission (src, transport); + } + g_signal_connect (src->manager, "request-rtp-decoder", + (GCallback) request_rtp_decoder, stream); + g_signal_connect (src->manager, "request-rtcp-decoder", + (GCallback) request_rtp_decoder, stream); + g_signal_connect (src->manager, "request-rtcp-encoder", + (GCallback) request_rtcp_encoder, stream); + + /* we stream directly to the manager, get some pads. Each RTSP stream goes + * into a separate RTP session. */ + name = g_strdup_printf ("recv_rtp_sink_%u", stream->id); + stream->channelpad[0] = gst_element_get_request_pad (src->manager, name); + g_free (name); + name = g_strdup_printf ("recv_rtcp_sink_%u", stream->id); + stream->channelpad[1] = gst_element_get_request_pad (src->manager, name); + g_free (name); + + /* now configure the bandwidth in the manager */ + if (g_signal_lookup ("get-internal-session", + G_OBJECT_TYPE (src->manager)) != 0) { + GObject *rtpsession; + + g_signal_emit_by_name (src->manager, "get-internal-session", stream->id, + &rtpsession); + if (rtpsession) { + GST_INFO_OBJECT (src, "configure bandwidth in session %p", rtpsession); + + stream->session = rtpsession; + + if (stream->as_bandwidth != -1) { + GST_INFO_OBJECT (src, "setting AS: %f", + (gdouble) (stream->as_bandwidth * 1000)); + g_object_set (rtpsession, "bandwidth", + (gdouble) (stream->as_bandwidth * 1000), NULL); + } + if (stream->rr_bandwidth != -1) { + GST_INFO_OBJECT (src, "setting RR: %u", stream->rr_bandwidth); + g_object_set (rtpsession, "rtcp-rr-bandwidth", stream->rr_bandwidth, + NULL); + } + if (stream->rs_bandwidth != -1) { + GST_INFO_OBJECT (src, "setting RS: %u", stream->rs_bandwidth); + g_object_set (rtpsession, "rtcp-rs-bandwidth", stream->rs_bandwidth, + NULL); + } + + g_object_set (rtpsession, "probation", src->probation, NULL); + + g_object_set (rtpsession, "internal-ssrc", stream->send_ssrc, NULL); + + g_signal_connect (rtpsession, "on-bye-ssrc", (GCallback) on_bye_ssrc, + stream); + g_signal_connect (rtpsession, "on-bye-timeout", (GCallback) on_timeout, + stream); + g_signal_connect (rtpsession, "on-timeout", (GCallback) on_timeout, + stream); + g_signal_connect (rtpsession, "on-ssrc-active", + (GCallback) on_ssrc_active, stream); + } + } + } + +use_no_manager: + return TRUE; + + /* ERRORS */ +no_manager: + { + GST_DEBUG_OBJECT (src, "cannot get a session manager"); + return FALSE; + } +manager_failed: + { + GST_DEBUG_OBJECT (src, "no session manager element %s found", manager); + return FALSE; + } +start_manager_failure: + { + GST_DEBUG_OBJECT (src, "could not start session manager"); + return FALSE; + } +} + +/* free the UDP sources allocated when negotiating a transport. + * This function is called when the server negotiated to a transport where the + * UDP sources are not needed anymore, such as TCP or multicast. */ +static void +gst_rtspsrc_stream_free_udp (GstRTSPStream * stream) +{ + gint i; + + for (i = 0; i < 2; i++) { + if (stream->udpsrc[i]) { + GST_DEBUG ("free UDP source %d for stream %p", i, stream); + gst_element_set_state (stream->udpsrc[i], GST_STATE_NULL); + gst_object_unref (stream->udpsrc[i]); + stream->udpsrc[i] = NULL; + } + } +} + +/* for TCP, create pads to send and receive data to and from the manager and to + * intercept various events and queries + */ +static gboolean +gst_rtspsrc_stream_configure_tcp (GstRTSPSrc * src, GstRTSPStream * stream, + GstRTSPTransport * transport, GstPad ** outpad) +{ + gchar *name; + GstPadTemplate *template; + GstPad *pad0, *pad1; + + /* configure for interleaved delivery, nothing needs to be done + * here, the loop function will call the chain functions of the + * session manager. */ + stream->channel[0] = transport->interleaved.min; + stream->channel[1] = transport->interleaved.max; + GST_DEBUG_OBJECT (src, "stream %p on channels %d-%d", stream, + stream->channel[0], stream->channel[1]); + + /* we can remove the allocated UDP ports now */ + gst_rtspsrc_stream_free_udp (stream); + + /* no session manager, send data to srcpad directly */ + if (!stream->channelpad[0]) { + GST_DEBUG_OBJECT (src, "no manager, creating pad"); + + /* create a new pad we will use to stream to */ + name = g_strdup_printf ("stream_%u", stream->id); + template = gst_static_pad_template_get (&rtptemplate); + stream->channelpad[0] = gst_pad_new_from_template (template, name); + gst_object_unref (template); + g_free (name); + + /* set caps and activate */ + gst_pad_use_fixed_caps (stream->channelpad[0]); + gst_pad_set_active (stream->channelpad[0], TRUE); + + *outpad = gst_object_ref (stream->channelpad[0]); + } else { + GST_DEBUG_OBJECT (src, "using manager source pad"); + + template = gst_static_pad_template_get (&anysrctemplate); + + /* allocate pads for sending the channel data into the manager */ + pad0 = gst_pad_new_from_template (template, "internalsrc_0"); + gst_pad_link_full (pad0, stream->channelpad[0], GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (stream->channelpad[0]); + stream->channelpad[0] = pad0; + gst_pad_set_event_function (pad0, gst_rtspsrc_handle_internal_src_event); + gst_pad_set_query_function (pad0, gst_rtspsrc_handle_internal_src_query); + gst_pad_set_element_private (pad0, src); + gst_pad_set_active (pad0, TRUE); + + if (stream->channelpad[1]) { + /* if we have a sinkpad for the other channel, create a pad and link to the + * manager. */ + pad1 = gst_pad_new_from_template (template, "internalsrc_1"); + gst_pad_set_event_function (pad1, gst_rtspsrc_handle_internal_src_event); + gst_pad_link_full (pad1, stream->channelpad[1], + GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (stream->channelpad[1]); + stream->channelpad[1] = pad1; + gst_pad_set_active (pad1, TRUE); + } + gst_object_unref (template); + } + /* setup RTCP transport back to the server if we have to. */ + if (src->manager && src->do_rtcp) { + GstPad *pad; + + template = gst_static_pad_template_get (&anysinktemplate); + + stream->rtcppad = gst_pad_new_from_template (template, "internalsink_0"); + gst_pad_set_chain_function (stream->rtcppad, gst_rtspsrc_sink_chain); + gst_pad_set_element_private (stream->rtcppad, stream); + gst_pad_set_active (stream->rtcppad, TRUE); + + /* get session RTCP pad */ + name = g_strdup_printf ("send_rtcp_src_%u", stream->id); + pad = gst_element_get_request_pad (src->manager, name); + g_free (name); + + /* and link */ + if (pad) { + gst_pad_link_full (pad, stream->rtcppad, GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (pad); + } + + gst_object_unref (template); + } + return TRUE; +} + +static void +gst_rtspsrc_get_transport_info (GstRTSPSrc * src, GstRTSPStream * stream, + GstRTSPTransport * transport, const gchar ** destination, gint * min, + gint * max, guint * ttl) +{ + if (transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) { + if (destination) { + if (!(*destination = transport->destination)) + *destination = stream->destination; + } + if (min && max) { + /* transport first */ + *min = transport->port.min; + *max = transport->port.max; + if (*min == -1 && *max == -1) { + /* then try from SDP */ + if (stream->port != 0) { + *min = stream->port; + *max = stream->port + 1; + } + } + } + + if (ttl) { + if (!(*ttl = transport->ttl)) + *ttl = stream->ttl; + } + } else { + if (destination) { + /* first take the source, then the endpoint to figure out where to send + * the RTCP. */ + if (!(*destination = transport->source)) { + if (src->conninfo.connection) + *destination = gst_rtsp_connection_get_ip (src->conninfo.connection); + else if (stream->conninfo.connection) + *destination = + gst_rtsp_connection_get_ip (stream->conninfo.connection); + } + } + if (min && max) { + /* for unicast we only expect the ports here */ + *min = transport->server_port.min; + *max = transport->server_port.max; + } + } +} + +/* For multicast create UDP sources and join the multicast group. */ +static gboolean +gst_rtspsrc_stream_configure_mcast (GstRTSPSrc * src, GstRTSPStream * stream, + GstRTSPTransport * transport, GstPad ** outpad) +{ + gchar *uri; + const gchar *destination; + gint min, max; + + GST_DEBUG_OBJECT (src, "creating UDP sources for multicast"); + + /* we can remove the allocated UDP ports now */ + gst_rtspsrc_stream_free_udp (stream); + + gst_rtspsrc_get_transport_info (src, stream, transport, &destination, &min, + &max, NULL); + + /* we need a destination now */ + if (destination == NULL) + goto no_destination; + + /* we really need ports now or we won't be able to receive anything at all */ + if (min == -1 && max == -1) + goto no_ports; + + GST_DEBUG_OBJECT (src, "have destination '%s' and ports (%d)-(%d)", + destination, min, max); + + /* creating UDP source for RTP */ + if (min != -1) { + uri = g_strdup_printf ("udp://%s:%d", destination, min); + stream->udpsrc[0] = + gst_element_make_from_uri (GST_URI_SRC, uri, NULL, NULL); + g_free (uri); + if (stream->udpsrc[0] == NULL) + goto no_element; + + /* take ownership */ + gst_object_ref_sink (stream->udpsrc[0]); + + if (src->udp_buffer_size != 0) + g_object_set (G_OBJECT (stream->udpsrc[0]), "buffer-size", + src->udp_buffer_size, NULL); + + if (src->multi_iface != NULL) + g_object_set (G_OBJECT (stream->udpsrc[0]), "multicast-iface", + src->multi_iface, NULL); + + /* change state */ + gst_element_set_locked_state (stream->udpsrc[0], TRUE); + gst_element_set_state (stream->udpsrc[0], GST_STATE_READY); + } + + /* creating another UDP source for RTCP */ + if (max != -1) { + GstCaps *caps; + + uri = g_strdup_printf ("udp://%s:%d", destination, max); + stream->udpsrc[1] = + gst_element_make_from_uri (GST_URI_SRC, uri, NULL, NULL); + g_free (uri); + if (stream->udpsrc[1] == NULL) + goto no_element; + + if (stream->profile == GST_RTSP_PROFILE_SAVP || + stream->profile == GST_RTSP_PROFILE_SAVPF) + caps = gst_caps_new_empty_simple ("application/x-srtcp"); + else + caps = gst_caps_new_empty_simple ("application/x-rtcp"); + g_object_set (stream->udpsrc[1], "caps", caps, NULL); + gst_caps_unref (caps); + + /* take ownership */ + gst_object_ref_sink (stream->udpsrc[1]); + + if (src->multi_iface != NULL) + g_object_set (G_OBJECT (stream->udpsrc[0]), "multicast-iface", + src->multi_iface, NULL); + + gst_element_set_state (stream->udpsrc[1], GST_STATE_READY); + } + return TRUE; + + /* ERRORS */ +no_element: + { + GST_DEBUG_OBJECT (src, "no UDP source element found"); + return FALSE; + } +no_destination: + { + GST_DEBUG_OBJECT (src, "no destination found"); + return FALSE; + } +no_ports: + { + GST_DEBUG_OBJECT (src, "no ports found"); + return FALSE; + } +} + +/* configure the remainder of the UDP ports */ +static gboolean +gst_rtspsrc_stream_configure_udp (GstRTSPSrc * src, GstRTSPStream * stream, + GstRTSPTransport * transport, GstPad ** outpad) +{ + /* we manage the UDP elements now. For unicast, the UDP sources where + * allocated in the stream when we suggested a transport. */ + if (stream->udpsrc[0]) { + GstCaps *caps; + + gst_element_set_locked_state (stream->udpsrc[0], TRUE); + gst_bin_add (GST_BIN_CAST (src), stream->udpsrc[0]); + + GST_DEBUG_OBJECT (src, "setting up UDP source"); + + /* configure a timeout on the UDP port. When the timeout message is + * posted, we assume UDP transport is not possible. We reconnect using TCP + * if we can. */ + g_object_set (G_OBJECT (stream->udpsrc[0]), "timeout", + src->udp_timeout * 1000, NULL); + + if ((caps = stream_get_caps_for_pt (stream, stream->default_pt))) + g_object_set (stream->udpsrc[0], "caps", caps, NULL); + + /* get output pad of the UDP source. */ + *outpad = gst_element_get_static_pad (stream->udpsrc[0], "src"); + + /* save it so we can unblock */ + stream->blockedpad = *outpad; + + /* configure pad block on the pad. As soon as there is dataflow on the + * UDP source, we know that UDP is not blocked by a firewall and we can + * configure all the streams to let the application autoplug decoders. */ + stream->blockid = + gst_pad_add_probe (stream->blockedpad, + GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | + GST_PAD_PROBE_TYPE_BUFFER_LIST, pad_blocked, src, NULL); + + if (stream->channelpad[0]) { + GST_DEBUG_OBJECT (src, "connecting UDP source 0 to manager"); + /* configure for UDP delivery, we need to connect the UDP pads to + * the session plugin. */ + gst_pad_link_full (*outpad, stream->channelpad[0], + GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (*outpad); + *outpad = NULL; + /* we connected to pad-added signal to get pads from the manager */ + } else { + GST_DEBUG_OBJECT (src, "using UDP src pad as output"); + } + } + + /* RTCP port */ + if (stream->udpsrc[1]) { + GstCaps *caps; + + gst_element_set_locked_state (stream->udpsrc[1], TRUE); + gst_bin_add (GST_BIN_CAST (src), stream->udpsrc[1]); + + if (stream->profile == GST_RTSP_PROFILE_SAVP || + stream->profile == GST_RTSP_PROFILE_SAVPF) + caps = gst_caps_new_empty_simple ("application/x-srtcp"); + else + caps = gst_caps_new_empty_simple ("application/x-rtcp"); + g_object_set (stream->udpsrc[1], "caps", caps, NULL); + gst_caps_unref (caps); + + if (stream->channelpad[1]) { + GstPad *pad; + + GST_DEBUG_OBJECT (src, "connecting UDP source 1 to manager"); + + pad = gst_element_get_static_pad (stream->udpsrc[1], "src"); + gst_pad_link_full (pad, stream->channelpad[1], + GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (pad); + } else { + /* leave unlinked */ + } + } + return TRUE; +} + +/* configure the UDP sink back to the server for status reports */ +static gboolean +gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src, + GstRTSPStream * stream, GstRTSPTransport * transport) +{ + GstPad *pad; + gint rtp_port, rtcp_port; + gboolean do_rtp, do_rtcp; + const gchar *destination; + gchar *uri, *name; + guint ttl = 0; + GSocket *socket; + + /* get transport info */ + gst_rtspsrc_get_transport_info (src, stream, transport, &destination, + &rtp_port, &rtcp_port, &ttl); + + /* see what we need to do */ + do_rtp = (rtp_port != -1); + /* it's possible that the server does not want us to send RTCP in which case + * the port is -1 */ + do_rtcp = (rtcp_port != -1 && src->manager != NULL && src->do_rtcp); + + /* we need a destination when we have RTP or RTCP ports */ + if (destination == NULL && (do_rtp || do_rtcp)) + goto no_destination; + + /* try to construct the fakesrc to the RTP port of the server to open up any + * NAT firewalls */ + if (do_rtp) { + GST_DEBUG_OBJECT (src, "configure RTP UDP sink for %s:%d", destination, + rtp_port); + + uri = g_strdup_printf ("udp://%s:%d", destination, rtp_port); + stream->udpsink[0] = + gst_element_make_from_uri (GST_URI_SINK, uri, NULL, NULL); + g_free (uri); + if (stream->udpsink[0] == NULL) + goto no_sink_element; + + /* don't join multicast group, we will have the source socket do that */ + /* no sync or async state changes needed */ + g_object_set (G_OBJECT (stream->udpsink[0]), "auto-multicast", FALSE, + "loop", FALSE, "sync", FALSE, "async", FALSE, NULL); + if (ttl > 0) + g_object_set (G_OBJECT (stream->udpsink[0]), "ttl", ttl, NULL); + + if (stream->udpsrc[0]) { + /* configure socket, we give it the same UDP socket as the udpsrc for RTP + * so that NAT firewalls will open a hole for us */ + g_object_get (G_OBJECT (stream->udpsrc[0]), "used-socket", &socket, NULL); + GST_DEBUG_OBJECT (src, "RTP UDP src has sock %p", socket); + /* configure socket and make sure udpsink does not close it when shutting + * down, it belongs to udpsrc after all. */ + g_object_set (G_OBJECT (stream->udpsink[0]), "socket", socket, + "close-socket", FALSE, NULL); + g_object_unref (socket); + } + + /* the source for the dummy packets to open up NAT */ + stream->fakesrc = gst_element_factory_make ("fakesrc", NULL); + if (stream->fakesrc == NULL) + goto no_fakesrc_element; + + /* random data in 5 buffers, a size of 200 bytes should be fine */ + g_object_set (G_OBJECT (stream->fakesrc), "filltype", 3, "num-buffers", 5, + "sizetype", 2, "sizemax", 200, "silent", TRUE, NULL); + + /* we don't want to consider this a sink */ + GST_OBJECT_FLAG_UNSET (stream->udpsink[0], GST_ELEMENT_FLAG_SINK); + + /* keep everything locked */ + gst_element_set_locked_state (stream->udpsink[0], TRUE); + gst_element_set_locked_state (stream->fakesrc, TRUE); + + gst_object_ref (stream->udpsink[0]); + gst_bin_add (GST_BIN_CAST (src), stream->udpsink[0]); + gst_object_ref (stream->fakesrc); + gst_bin_add (GST_BIN_CAST (src), stream->fakesrc); + + gst_element_link_pads_full (stream->fakesrc, "src", stream->udpsink[0], + "sink", GST_PAD_LINK_CHECK_NOTHING); + } + if (do_rtcp) { + GST_DEBUG_OBJECT (src, "configure RTCP UDP sink for %s:%d", destination, + rtcp_port); + + uri = g_strdup_printf ("udp://%s:%d", destination, rtcp_port); + stream->udpsink[1] = + gst_element_make_from_uri (GST_URI_SINK, uri, NULL, NULL); + g_free (uri); + if (stream->udpsink[1] == NULL) + goto no_sink_element; + + /* don't join multicast group, we will have the source socket do that */ + /* no sync or async state changes needed */ + g_object_set (G_OBJECT (stream->udpsink[1]), "auto-multicast", FALSE, + "loop", FALSE, "sync", FALSE, "async", FALSE, NULL); + if (ttl > 0) + g_object_set (G_OBJECT (stream->udpsink[0]), "ttl", ttl, NULL); + + if (stream->udpsrc[1]) { + /* configure socket, we give it the same UDP socket as the udpsrc for RTCP + * because some servers check the port number of where it sends RTCP to identify + * the RTCP packets it receives */ + g_object_get (G_OBJECT (stream->udpsrc[1]), "used-socket", &socket, NULL); + GST_DEBUG_OBJECT (src, "RTCP UDP src has sock %p", socket); + /* configure socket and make sure udpsink does not close it when shutting + * down, it belongs to udpsrc after all. */ + g_object_set (G_OBJECT (stream->udpsink[1]), "socket", socket, + "close-socket", FALSE, NULL); + g_object_unref (socket); + } + + /* we don't want to consider this a sink */ + GST_OBJECT_FLAG_UNSET (stream->udpsink[1], GST_ELEMENT_FLAG_SINK); + + /* we keep this playing always */ + gst_element_set_locked_state (stream->udpsink[1], TRUE); + gst_element_set_state (stream->udpsink[1], GST_STATE_PLAYING); + + gst_object_ref (stream->udpsink[1]); + gst_bin_add (GST_BIN_CAST (src), stream->udpsink[1]); + + stream->rtcppad = gst_element_get_static_pad (stream->udpsink[1], "sink"); + + /* get session RTCP pad */ + name = g_strdup_printf ("send_rtcp_src_%u", stream->id); + pad = gst_element_get_request_pad (src->manager, name); + g_free (name); + + /* and link */ + if (pad) { + gst_pad_link_full (pad, stream->rtcppad, GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (pad); + } + } + + return TRUE; + + /* ERRORS */ +no_destination: + { + GST_DEBUG_OBJECT (src, "no destination address specified"); + return FALSE; + } +no_sink_element: + { + GST_DEBUG_OBJECT (src, "no UDP sink element found"); + return FALSE; + } +no_fakesrc_element: + { + GST_DEBUG_OBJECT (src, "no fakesrc element found"); + return FALSE; + } +} + +/* sets up all elements needed for streaming over the specified transport. + * Does not yet expose the element pads, this will be done when there is actuall + * dataflow detected, which might never happen when UDP is blocked in a + * firewall, for example. + */ +static gboolean +gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, + GstRTSPTransport * transport) +{ + GstRTSPSrc *src; + GstPad *outpad = NULL; + GstPadTemplate *template; + gchar *name; + const gchar *media_type; + guint i, len; + + src = stream->parent; + + GST_DEBUG_OBJECT (src, "configuring transport for stream %p", stream); + + /* get the proper media type for this stream now */ + if (gst_rtsp_transport_get_media_type (transport, &media_type) < 0) + goto unknown_transport; + if (!media_type) + goto unknown_transport; + + /* configure the final media type */ + GST_DEBUG_OBJECT (src, "setting media type to %s", media_type); + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + GstStructure *s; + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + + if (item->caps == NULL) + continue; + + s = gst_caps_get_structure (item->caps, 0); + gst_structure_set_name (s, media_type); + /* set ssrc if known */ + if (transport->ssrc) + gst_structure_set (s, "ssrc", G_TYPE_UINT, transport->ssrc, NULL); + } + + /* try to get and configure a manager, channelpad[0-1] will be configured with + * the pads for the manager, or NULL when no manager is needed. */ + if (!gst_rtspsrc_stream_configure_manager (src, stream, transport)) + goto no_manager; + + switch (transport->lower_transport) { + case GST_RTSP_LOWER_TRANS_TCP: + if (!gst_rtspsrc_stream_configure_tcp (src, stream, transport, &outpad)) + goto transport_failed; + break; + case GST_RTSP_LOWER_TRANS_UDP_MCAST: + if (!gst_rtspsrc_stream_configure_mcast (src, stream, transport, &outpad)) + goto transport_failed; + /* fallthrough, the rest is the same for UDP and MCAST */ + case GST_RTSP_LOWER_TRANS_UDP: + if (!gst_rtspsrc_stream_configure_udp (src, stream, transport, &outpad)) + goto transport_failed; + /* configure udpsinks back to the server for RTCP messages and for the + * dummy RTP messages to open NAT. */ + if (!gst_rtspsrc_stream_configure_udp_sinks (src, stream, transport)) + goto transport_failed; + break; + default: + goto unknown_transport; + } + + if (outpad) { + GST_DEBUG_OBJECT (src, "creating ghostpad"); + + gst_pad_use_fixed_caps (outpad); + + /* create ghostpad, don't add just yet, this will be done when we activate + * the stream. */ + name = g_strdup_printf ("stream_%u", stream->id); + template = gst_static_pad_template_get (&rtptemplate); + stream->srcpad = gst_ghost_pad_new_from_template (name, outpad, template); + gst_pad_set_event_function (stream->srcpad, gst_rtspsrc_handle_src_event); + gst_pad_set_query_function (stream->srcpad, gst_rtspsrc_handle_src_query); + gst_object_unref (template); + g_free (name); + + gst_object_unref (outpad); + } + /* mark pad as ok */ + stream->last_ret = GST_FLOW_OK; + + return TRUE; + + /* ERRORS */ +transport_failed: + { + GST_DEBUG_OBJECT (src, "failed to configure transport"); + return FALSE; + } +unknown_transport: + { + GST_DEBUG_OBJECT (src, "unknown transport"); + return FALSE; + } +no_manager: + { + GST_DEBUG_OBJECT (src, "cannot get a session manager"); + return FALSE; + } +} + +/* send a couple of dummy random packets on the receiver RTP port to the server, + * this should make a firewall think we initiated the data transfer and + * hopefully allow packets to go from the sender port to our RTP receiver port */ +static gboolean +gst_rtspsrc_send_dummy_packets (GstRTSPSrc * src) +{ + GList *walk; + + if (src->nat_method != GST_RTSP_NAT_DUMMY) + return TRUE; + + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + + if (stream->fakesrc && stream->udpsink[0]) { + GST_DEBUG_OBJECT (src, "sending dummy packet to stream %p", stream); + gst_element_set_state (stream->udpsink[0], GST_STATE_NULL); + gst_element_set_state (stream->fakesrc, GST_STATE_NULL); + gst_element_set_state (stream->udpsink[0], GST_STATE_PLAYING); + gst_element_set_state (stream->fakesrc, GST_STATE_PLAYING); + } + } + return TRUE; +} + +/* Adds the source pads of all configured streams to the element. + * This code is performed when we detected dataflow. + * + * We detect dataflow from either the _loop function or with pad probes on the + * udp sources. + */ +static gboolean +gst_rtspsrc_activate_streams (GstRTSPSrc * src) +{ + GList *walk; + + GST_DEBUG_OBJECT (src, "activating streams"); + + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + + if (stream->udpsrc[0]) { + /* remove timeout, we are streaming now and timeouts will be handled by + * the session manager and jitter buffer */ + g_object_set (G_OBJECT (stream->udpsrc[0]), "timeout", (guint64) 0, NULL); + } + if (stream->srcpad) { + GST_DEBUG_OBJECT (src, "activating stream pad %p", stream); + gst_pad_set_active (stream->srcpad, TRUE); + + /* if we don't have a session manager, set the caps now. If we have a + * session, we will get a notification of the pad and the caps. */ + if (!src->manager) { + GstCaps *caps; + + caps = stream_get_caps_for_pt (stream, stream->default_pt); + GST_DEBUG_OBJECT (src, "setting pad caps for stream %p", stream); + gst_pad_set_caps (stream->srcpad, caps); + } + /* add the pad */ + if (!stream->added) { + GST_DEBUG_OBJECT (src, "adding stream pad %p", stream); + gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad); + stream->added = TRUE; + } + } + } + + /* unblock all pads */ + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + + if (stream->blockid) { + GST_DEBUG_OBJECT (src, "unblocking stream pad %p", stream); + gst_pad_remove_probe (stream->blockedpad, stream->blockid); + stream->blockid = 0; + } + } + + return TRUE; +} + +static void +gst_rtspsrc_configure_caps (GstRTSPSrc * src, GstSegment * segment, + gboolean reset_manager) +{ + GList *walk; + guint64 start, stop; + gdouble play_speed, play_scale; + + GST_DEBUG_OBJECT (src, "configuring stream caps"); + + start = segment->position; + stop = segment->duration; + play_speed = segment->rate; + play_scale = segment->applied_rate; + + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + guint j, len; + + if (!stream->setup) + continue; + + len = stream->ptmap->len; + for (j = 0; j < len; j++) { + GstCaps *caps; + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, j); + + if (item->caps == NULL) + continue; + + caps = gst_caps_make_writable (item->caps); + /* update caps */ + if (stream->timebase != -1) + gst_caps_set_simple (caps, "clock-base", G_TYPE_UINT, + (guint) stream->timebase, NULL); + if (stream->seqbase != -1) + gst_caps_set_simple (caps, "seqnum-base", G_TYPE_UINT, + (guint) stream->seqbase, NULL); + gst_caps_set_simple (caps, "npt-start", G_TYPE_UINT64, start, NULL); + if (stop != -1) + gst_caps_set_simple (caps, "npt-stop", G_TYPE_UINT64, stop, NULL); + gst_caps_set_simple (caps, "play-speed", G_TYPE_DOUBLE, play_speed, NULL); + gst_caps_set_simple (caps, "play-scale", G_TYPE_DOUBLE, play_scale, NULL); + + item->caps = caps; + GST_DEBUG_OBJECT (src, "stream %p, pt %d, caps %" GST_PTR_FORMAT, stream, + item->pt, caps); + + if (item->pt == stream->default_pt && stream->udpsrc[0]) { + g_object_set (stream->udpsrc[0], "caps", caps, NULL); + } + } + } + if (reset_manager && src->manager) { + GST_DEBUG_OBJECT (src, "clear session"); + g_signal_emit_by_name (src->manager, "clear-pt-map", NULL); + } +} + +static GstFlowReturn +gst_rtspsrc_combine_flows (GstRTSPSrc * src, GstRTSPStream * stream, + GstFlowReturn ret) +{ + GList *streams; + + /* store the value */ + stream->last_ret = ret; + + /* if it's success we can return the value right away */ + if (ret == GST_FLOW_OK) + goto done; + + /* any other error that is not-linked can be returned right + * away */ + if (ret != GST_FLOW_NOT_LINKED) + goto done; + + /* only return NOT_LINKED if all other pads returned NOT_LINKED */ + for (streams = src->streams; streams; streams = g_list_next (streams)) { + GstRTSPStream *ostream = (GstRTSPStream *) streams->data; + + ret = ostream->last_ret; + /* some other return value (must be SUCCESS but we can return + * other values as well) */ + if (ret != GST_FLOW_NOT_LINKED) + goto done; + } + /* if we get here, all other pads were unlinked and we return + * NOT_LINKED then */ +done: + return ret; +} + +static gboolean +gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream, + GstEvent * event) +{ + gboolean res = TRUE; + + /* only streams that have a connection to the outside world */ + if (!stream->setup) + goto done; + + if (stream->udpsrc[0]) { + gst_event_ref (event); + res = gst_element_send_event (stream->udpsrc[0], event); + } else if (stream->channelpad[0]) { + gst_event_ref (event); + if (GST_PAD_IS_SRC (stream->channelpad[0])) + res = gst_pad_push_event (stream->channelpad[0], event); + else + res = gst_pad_send_event (stream->channelpad[0], event); + } + + if (stream->udpsrc[1]) { + gst_event_ref (event); + res &= gst_element_send_event (stream->udpsrc[1], event); + } else if (stream->channelpad[1]) { + gst_event_ref (event); + if (GST_PAD_IS_SRC (stream->channelpad[1])) + res &= gst_pad_push_event (stream->channelpad[1], event); + else + res &= gst_pad_send_event (stream->channelpad[1], event); + } + +done: + gst_event_unref (event); + + return res; +} + +static gboolean +gst_rtspsrc_push_event (GstRTSPSrc * src, GstEvent * event) +{ + GList *streams; + gboolean res = TRUE; + + for (streams = src->streams; streams; streams = g_list_next (streams)) { + GstRTSPStream *ostream = (GstRTSPStream *) streams->data; + + gst_event_ref (event); + res &= gst_rtspsrc_stream_push_event (src, ostream, event); + } + gst_event_unref (event); + + return res; +} + +static GstRTSPResult +gst_rtsp_conninfo_connect (GstRTSPSrc * src, GstRTSPConnInfo * info, + gboolean async) +{ + GstRTSPResult res; + + if (info->connection == NULL) { + if (info->url == NULL) { + GST_DEBUG_OBJECT (src, "parsing uri (%s)...", info->location); + if ((res = gst_rtsp_url_parse (info->location, &info->url)) < 0) + goto parse_error; + } + + /* create connection */ + GST_DEBUG_OBJECT (src, "creating connection (%s)...", info->location); + if ((res = gst_rtsp_connection_create (info->url, &info->connection)) < 0) + goto could_not_create; + + if (info->url_str) + g_free (info->url_str); + info->url_str = gst_rtsp_url_get_request_uri (info->url); + + GST_DEBUG_OBJECT (src, "sanitized uri %s", info->url_str); + + if (info->url->transports & GST_RTSP_LOWER_TRANS_TLS) { + if (!gst_rtsp_connection_set_tls_validation_flags (info->connection, + src->tls_validation_flags)) + GST_WARNING_OBJECT (src, "Unable to set TLS validation flags"); + + if (src->tls_database) + gst_rtsp_connection_set_tls_database (info->connection, + src->tls_database); + } + + if (info->url->transports & GST_RTSP_LOWER_TRANS_HTTP) + gst_rtsp_connection_set_tunneled (info->connection, TRUE); + + if (src->proxy_host) { + GST_DEBUG_OBJECT (src, "setting proxy %s:%d", src->proxy_host, + src->proxy_port); + gst_rtsp_connection_set_proxy (info->connection, src->proxy_host, + src->proxy_port); + } + } + + if (!info->connected) { + /* connect */ + if (async) + GST_ELEMENT_PROGRESS (src, CONTINUE, "connect", + ("Connecting to %s", info->location)); + GST_DEBUG_OBJECT (src, "connecting (%s)...", info->location); + if ((res = + gst_rtsp_connection_connect (info->connection, + src->ptcp_timeout)) < 0) + goto could_not_connect; + + info->connected = TRUE; + } + return GST_RTSP_OK; + + /* ERRORS */ +parse_error: + { + GST_ERROR_OBJECT (src, "No valid RTSP URL was provided"); + return res; + } +could_not_create: + { + gchar *str = gst_rtsp_strresult (res); + GST_ERROR_OBJECT (src, "Could not create connection. (%s)", str); + g_free (str); + return res; + } +could_not_connect: + { + gchar *str = gst_rtsp_strresult (res); + GST_ERROR_OBJECT (src, "Could not connect to server. (%s)", str); + g_free (str); + return res; + } +} + +static GstRTSPResult +gst_rtsp_conninfo_close (GstRTSPSrc * src, GstRTSPConnInfo * info, + gboolean free) +{ + GST_RTSP_STATE_LOCK (src); + if (info->connected) { + GST_DEBUG_OBJECT (src, "closing connection..."); + gst_rtsp_connection_close (info->connection); + info->connected = FALSE; + } + if (free && info->connection) { + /* free connection */ + GST_DEBUG_OBJECT (src, "freeing connection..."); + gst_rtsp_connection_free (info->connection); + info->connection = NULL; + } + GST_RTSP_STATE_UNLOCK (src); + return GST_RTSP_OK; +} + +static GstRTSPResult +gst_rtsp_conninfo_reconnect (GstRTSPSrc * src, GstRTSPConnInfo * info, + gboolean async) +{ + GstRTSPResult res; + + GST_DEBUG_OBJECT (src, "reconnecting connection..."); + gst_rtsp_conninfo_close (src, info, FALSE); + res = gst_rtsp_conninfo_connect (src, info, async); + + return res; +} + +static void +gst_rtspsrc_connection_flush (GstRTSPSrc * src, gboolean flush) +{ + GList *walk; + + GST_DEBUG_OBJECT (src, "set flushing %d", flush); + GST_RTSP_STATE_LOCK (src); + if (src->conninfo.connection && src->conninfo.flushing != flush) { + GST_DEBUG_OBJECT (src, "connection flush"); + gst_rtsp_connection_flush (src->conninfo.connection, flush); + src->conninfo.flushing = flush; + } + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + if (stream->conninfo.connection && stream->conninfo.flushing != flush) { + GST_DEBUG_OBJECT (src, "stream %p flush", stream); + gst_rtsp_connection_flush (stream->conninfo.connection, flush); + stream->conninfo.flushing = flush; + } + } + GST_RTSP_STATE_UNLOCK (src); +} + +/* FIXME, handle server request, reply with OK, for now */ +static GstRTSPResult +gst_rtspsrc_handle_request (GstRTSPSrc * src, GstRTSPConnection * conn, + GstRTSPMessage * request) +{ + GstRTSPMessage response = { 0 }; + GstRTSPResult res; + + GST_DEBUG_OBJECT (src, "got server request message"); + + if (src->debug) + gst_rtsp_message_dump (request); + + res = gst_rtsp_ext_list_receive_request (src->extensions, request); + + if (res == GST_RTSP_ENOTIMPL) { + /* default implementation, send OK */ + GST_DEBUG_OBJECT (src, "prepare OK reply"); + res = + gst_rtsp_message_init_response (&response, GST_RTSP_STS_OK, "OK", + request); + if (res < 0) + goto send_error; + + /* let app parse and reply */ + g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_HANDLE_REQUEST], + 0, request, &response); + + if (src->debug) + gst_rtsp_message_dump (&response); + + res = gst_rtspsrc_connection_send (src, conn, &response, NULL); + if (res < 0) + goto send_error; + + gst_rtsp_message_unset (&response); + } else if (res == GST_RTSP_EEOF) + return res; + + return GST_RTSP_OK; + + /* ERRORS */ +send_error: + { + gst_rtsp_message_unset (&response); + return res; + } +} + +/* send server keep-alive */ +static GstRTSPResult +gst_rtspsrc_send_keep_alive (GstRTSPSrc * src) +{ + GstRTSPMessage request = { 0 }; + GstRTSPResult res; + GstRTSPMethod method; + const gchar *control; + + if (src->do_rtsp_keep_alive == FALSE) { + GST_DEBUG_OBJECT (src, "do-rtsp-keep-alive is FALSE, not sending."); + gst_rtsp_connection_reset_timeout (src->conninfo.connection); + return GST_RTSP_OK; + } + + GST_DEBUG_OBJECT (src, "creating server keep-alive"); + + /* find a method to use for keep-alive */ + if (src->methods & GST_RTSP_GET_PARAMETER) + method = GST_RTSP_GET_PARAMETER; + else + method = GST_RTSP_OPTIONS; + + control = get_aggregate_control (src); + if (control == NULL) + goto no_control; + + res = gst_rtsp_message_init_request (&request, method, control); + if (res < 0) + goto send_error; + + if (src->debug) + gst_rtsp_message_dump (&request); + + res = + gst_rtspsrc_connection_send (src, src->conninfo.connection, &request, + NULL); + if (res < 0) + goto send_error; + + gst_rtsp_connection_reset_timeout (src->conninfo.connection); + gst_rtsp_message_unset (&request); + + return GST_RTSP_OK; + + /* ERRORS */ +no_control: + { + GST_WARNING_OBJECT (src, "no control url to send keepalive"); + return GST_RTSP_OK; + } +send_error: + { + gchar *str = gst_rtsp_strresult (res); + + gst_rtsp_message_unset (&request); + GST_ELEMENT_WARNING (src, RESOURCE, WRITE, (NULL), + ("Could not send keep-alive. (%s)", str)); + g_free (str); + return res; + } +} + +static GstFlowReturn +gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message) +{ + GstFlowReturn ret = GST_FLOW_OK; + gint channel; + GstRTSPStream *stream; + GstPad *outpad = NULL; + guint8 *data; + guint size; + GstBuffer *buf; + gboolean is_rtcp; + + channel = message->type_data.data.channel; + + stream = find_stream (src, &channel, (gpointer) find_stream_by_channel); + if (!stream) + goto unknown_stream; + + if (channel == stream->channel[0]) { + outpad = stream->channelpad[0]; + is_rtcp = FALSE; + } else if (channel == stream->channel[1]) { + outpad = stream->channelpad[1]; + is_rtcp = TRUE; + } else { + is_rtcp = FALSE; + } + + /* take a look at the body to figure out what we have */ + gst_rtsp_message_get_body (message, &data, &size); + if (size < 2) + goto invalid_length; + + /* channels are not correct on some servers, do extra check */ + if (data[1] >= 200 && data[1] <= 204) { + /* hmm RTCP message switch to the RTCP pad of the same stream. */ + outpad = stream->channelpad[1]; + is_rtcp = TRUE; + } + + /* we have no clue what this is, just ignore then. */ + if (outpad == NULL) + goto unknown_stream; + + /* take the message body for further processing */ + gst_rtsp_message_steal_body (message, &data, &size); + + /* strip the trailing \0 */ + size -= 1; + + buf = gst_buffer_new (); + gst_buffer_append_memory (buf, + gst_memory_new_wrapped (0, data, size, 0, size, data, g_free)); + + /* don't need message anymore */ + gst_rtsp_message_unset (message); + + GST_DEBUG_OBJECT (src, "pushing data of size %d on channel %d", size, + channel); + + if (src->need_activate) { + gchar *stream_id; + GstEvent *event; + GChecksum *cs; + gchar *uri; + GList *streams; + guint group_id = gst_util_group_id_next (); + + /* generate an SHA256 sum of the URI */ + cs = g_checksum_new (G_CHECKSUM_SHA256); + uri = src->conninfo.location; + g_checksum_update (cs, (const guchar *) uri, strlen (uri)); + + for (streams = src->streams; streams; streams = g_list_next (streams)) { + GstRTSPStream *ostream = (GstRTSPStream *) streams->data; + GstCaps *caps; + + stream_id = + g_strdup_printf ("%s/%d", g_checksum_get_string (cs), ostream->id); + event = gst_event_new_stream_start (stream_id); + gst_event_set_group_id (event, group_id); + + g_free (stream_id); + gst_rtspsrc_stream_push_event (src, ostream, event); + + if ((caps = stream_get_caps_for_pt (ostream, ostream->default_pt))) { + /* only streams that have a connection to the outside world */ + if (ostream->setup) { + if (ostream->udpsrc[0]) { + gst_element_send_event (ostream->udpsrc[0], + gst_event_new_caps (caps)); + } else if (ostream->channelpad[0]) { + if (GST_PAD_IS_SRC (ostream->channelpad[0])) + gst_pad_push_event (ostream->channelpad[0], + gst_event_new_caps (caps)); + else + gst_pad_send_event (ostream->channelpad[0], + gst_event_new_caps (caps)); + } + + caps = gst_caps_new_empty_simple ("application/x-rtcp"); + + if (ostream->udpsrc[1]) { + gst_element_send_event (ostream->udpsrc[1], + gst_event_new_caps (caps)); + } else if (ostream->channelpad[1]) { + if (GST_PAD_IS_SRC (ostream->channelpad[1])) + gst_pad_push_event (ostream->channelpad[1], + gst_event_new_caps (caps)); + else + gst_pad_send_event (ostream->channelpad[1], + gst_event_new_caps (caps)); + } + + gst_caps_unref (caps); + } + } + } + g_checksum_free (cs); + + gst_rtspsrc_activate_streams (src); + src->need_activate = FALSE; + src->need_segment = TRUE; + } + + if (src->base_time == -1) { + /* Take current running_time. This timestamp will be put on + * the first buffer of each stream because we are a live source and so we + * timestamp with the running_time. When we are dealing with TCP, we also + * only timestamp the first buffer (using the DISCONT flag) because a server + * typically bursts data, for which we don't want to compensate by speeding + * up the media. The other timestamps will be interpollated from this one + * using the RTP timestamps. */ + GST_OBJECT_LOCK (src); + if (GST_ELEMENT_CLOCK (src)) { + GstClockTime now; + GstClockTime base_time; + + now = gst_clock_get_time (GST_ELEMENT_CLOCK (src)); + base_time = GST_ELEMENT_CAST (src)->base_time; + + src->base_time = now - base_time; + + GST_DEBUG_OBJECT (src, "first buffer at time %" GST_TIME_FORMAT ", base %" + GST_TIME_FORMAT, GST_TIME_ARGS (now), GST_TIME_ARGS (base_time)); + } + GST_OBJECT_UNLOCK (src); + } + + /* If needed send a new segment, don't forget we are live and buffer are + * timestamped with running time */ + if (src->need_segment) { + GstSegment segment; + src->need_segment = FALSE; + gst_segment_init (&segment, GST_FORMAT_TIME); + gst_rtspsrc_push_event (src, gst_event_new_segment (&segment)); + } + + if (stream->discont && !is_rtcp) { + /* mark first RTP buffer as discont */ + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + /* first buffer gets the timestamp, other buffers are not timestamped and + * their presentation time will be interpollated from the rtp timestamps. */ + GST_DEBUG_OBJECT (src, "setting timestamp %" GST_TIME_FORMAT, + GST_TIME_ARGS (src->base_time)); + + GST_BUFFER_TIMESTAMP (buf) = src->base_time; + } + + /* chain to the peer pad */ + if (GST_PAD_IS_SINK (outpad)) + ret = gst_pad_chain (outpad, buf); + else + ret = gst_pad_push (outpad, buf); + + if (!is_rtcp) { + /* combine all stream flows for the data transport */ + ret = gst_rtspsrc_combine_flows (src, stream, ret); + } + return ret; + + /* ERRORS */ +unknown_stream: + { + GST_DEBUG_OBJECT (src, "unknown stream on channel %d, ignored", channel); + gst_rtsp_message_unset (message); + return GST_FLOW_OK; + } +invalid_length: + { + GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), + ("Short message received, ignoring.")); + gst_rtsp_message_unset (message); + return GST_FLOW_OK; + } +} + +static GstFlowReturn +gst_rtspsrc_loop_interleaved (GstRTSPSrc * src) +{ + GstRTSPMessage message = { 0 }; + GstRTSPResult res; + GstFlowReturn ret = GST_FLOW_OK; + GTimeVal tv_timeout; + + while (TRUE) { + /* get the next timeout interval */ + gst_rtsp_connection_next_timeout (src->conninfo.connection, &tv_timeout); + + /* see if the timeout period expired */ + if ((tv_timeout.tv_sec | tv_timeout.tv_usec) == 0) { + GST_DEBUG_OBJECT (src, "timout, sending keep-alive"); + /* send keep-alive, only act on interrupt, a warning will be posted for + * other errors. */ + if ((res = gst_rtspsrc_send_keep_alive (src)) == GST_RTSP_EINTR) + goto interrupt; + /* get new timeout */ + gst_rtsp_connection_next_timeout (src->conninfo.connection, &tv_timeout); + } + + GST_DEBUG_OBJECT (src, "doing receive with timeout %ld seconds, %ld usec", + tv_timeout.tv_sec, tv_timeout.tv_usec); + + /* protect the connection with the connection lock so that we can see when + * we are finished doing server communication */ + res = + gst_rtspsrc_connection_receive (src, src->conninfo.connection, + &message, src->ptcp_timeout); + + switch (res) { + case GST_RTSP_OK: + GST_DEBUG_OBJECT (src, "we received a server message"); + break; + case GST_RTSP_EINTR: + /* we got interrupted this means we need to stop */ + goto interrupt; + case GST_RTSP_ETIMEOUT: + /* no reply, send keep alive */ + GST_DEBUG_OBJECT (src, "timeout, sending keep-alive"); + if ((res = gst_rtspsrc_send_keep_alive (src)) == GST_RTSP_EINTR) + goto interrupt; + continue; + case GST_RTSP_EEOF: + /* go EOS when the server closed the connection */ + goto server_eof; + default: + goto receive_error; + } + + switch (message.type) { + case GST_RTSP_MESSAGE_REQUEST: + /* server sends us a request message, handle it */ + res = + gst_rtspsrc_handle_request (src, src->conninfo.connection, + &message); + if (res == GST_RTSP_EEOF) + goto server_eof; + else if (res < 0) + goto handle_request_failed; + break; + case GST_RTSP_MESSAGE_RESPONSE: + /* we ignore response messages */ + GST_DEBUG_OBJECT (src, "ignoring response message"); + if (src->debug) + gst_rtsp_message_dump (&message); + break; + case GST_RTSP_MESSAGE_DATA: + GST_DEBUG_OBJECT (src, "got data message"); + ret = gst_rtspsrc_handle_data (src, &message); + if (ret != GST_FLOW_OK) + goto handle_data_failed; + break; + default: + GST_WARNING_OBJECT (src, "ignoring unknown message type %d", + message.type); + break; + } + } + g_assert_not_reached (); + + /* ERRORS */ +server_eof: + { + GST_DEBUG_OBJECT (src, "we got an eof from the server"); + GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), + ("The server closed the connection.")); + src->conninfo.connected = FALSE; + gst_rtsp_message_unset (&message); + return GST_FLOW_EOS; + } +interrupt: + { + gst_rtsp_message_unset (&message); + GST_DEBUG_OBJECT (src, "got interrupted"); + return GST_FLOW_FLUSHING; + } +receive_error: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Could not receive message. (%s)", str)); + g_free (str); + + gst_rtsp_message_unset (&message); + return GST_FLOW_ERROR; + } +handle_request_failed: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not handle server message. (%s)", str)); + g_free (str); + gst_rtsp_message_unset (&message); + return GST_FLOW_ERROR; + } +handle_data_failed: + { + GST_DEBUG_OBJECT (src, "could no handle data message"); + return ret; + } +} + +static GstFlowReturn +gst_rtspsrc_loop_udp (GstRTSPSrc * src) +{ + GstRTSPResult res; + GstRTSPMessage message = { 0 }; + gint retry = 0; + + while (TRUE) { + GTimeVal tv_timeout; + + /* get the next timeout interval */ + gst_rtsp_connection_next_timeout (src->conninfo.connection, &tv_timeout); + + GST_DEBUG_OBJECT (src, "doing receive with timeout %d seconds", + (gint) tv_timeout.tv_sec); + + gst_rtsp_message_unset (&message); + + /* we should continue reading the TCP socket because the server might + * send us requests. When the session timeout expires, we need to send a + * keep-alive request to keep the session open. */ + res = gst_rtspsrc_connection_receive (src, src->conninfo.connection, + &message, &tv_timeout); + + switch (res) { + case GST_RTSP_OK: + GST_DEBUG_OBJECT (src, "we received a server message"); + break; + case GST_RTSP_EINTR: + /* we got interrupted, see what we have to do */ + goto interrupt; + case GST_RTSP_ETIMEOUT: + /* send keep-alive, ignore the result, a warning will be posted. */ + GST_DEBUG_OBJECT (src, "timeout, sending keep-alive"); + if ((res = gst_rtspsrc_send_keep_alive (src)) == GST_RTSP_EINTR) + goto interrupt; + continue; + case GST_RTSP_EEOF: + /* server closed the connection. not very fatal for UDP, reconnect and + * see what happens. */ + GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), + ("The server closed the connection.")); + if (src->udp_reconnect) { + if ((res = + gst_rtsp_conninfo_reconnect (src, &src->conninfo, FALSE)) < 0) + goto connect_error; + } else { + goto server_eof; + } + continue; + case GST_RTSP_ENET: + GST_DEBUG_OBJECT (src, "An ethernet problem occured."); + default: + GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), + ("Unhandled return value %d.", res)); + goto receive_error; + } + + switch (message.type) { + case GST_RTSP_MESSAGE_REQUEST: + /* server sends us a request message, handle it */ + res = + gst_rtspsrc_handle_request (src, src->conninfo.connection, + &message); + if (res == GST_RTSP_EEOF) + goto server_eof; + else if (res < 0) + goto handle_request_failed; + break; + case GST_RTSP_MESSAGE_RESPONSE: + /* we ignore response and data messages */ + GST_DEBUG_OBJECT (src, "ignoring response message"); + if (src->debug) + gst_rtsp_message_dump (&message); + if (message.type_data.response.code == GST_RTSP_STS_UNAUTHORIZED) { + GST_DEBUG_OBJECT (src, "but is Unauthorized response ..."); + if (gst_rtspsrc_setup_auth (src, &message) && !(retry++)) { + GST_DEBUG_OBJECT (src, "so retrying keep-alive"); + if ((res = gst_rtspsrc_send_keep_alive (src)) == GST_RTSP_EINTR) + goto interrupt; + } + } else { + retry = 0; + } + break; + case GST_RTSP_MESSAGE_DATA: + /* we ignore response and data messages */ + GST_DEBUG_OBJECT (src, "ignoring data message"); + break; + default: + GST_WARNING_OBJECT (src, "ignoring unknown message type %d", + message.type); + break; + } + } + g_assert_not_reached (); + + /* we get here when the connection got interrupted */ +interrupt: + { + gst_rtsp_message_unset (&message); + GST_DEBUG_OBJECT (src, "got interrupted"); + return GST_FLOW_FLUSHING; + } +connect_error: + { + gchar *str = gst_rtsp_strresult (res); + GstFlowReturn ret; + + src->conninfo.connected = FALSE; + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL), + ("Could not connect to server. (%s)", str)); + g_free (str); + ret = GST_FLOW_ERROR; + } else { + ret = GST_FLOW_FLUSHING; + } + return ret; + } +receive_error: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Could not receive message. (%s)", str)); + g_free (str); + return GST_FLOW_ERROR; + } +handle_request_failed: + { + gchar *str = gst_rtsp_strresult (res); + GstFlowReturn ret; + + gst_rtsp_message_unset (&message); + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not handle server message. (%s)", str)); + g_free (str); + ret = GST_FLOW_ERROR; + } else { + ret = GST_FLOW_FLUSHING; + } + return ret; + } +server_eof: + { + GST_DEBUG_OBJECT (src, "we got an eof from the server"); + GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), + ("The server closed the connection.")); + src->conninfo.connected = FALSE; + gst_rtsp_message_unset (&message); + return GST_FLOW_EOS; + } +} + +static GstRTSPResult +gst_rtspsrc_reconnect (GstRTSPSrc * src, gboolean async) +{ + GstRTSPResult res = GST_RTSP_OK; + gboolean restart; + + GST_DEBUG_OBJECT (src, "doing reconnect"); + + GST_OBJECT_LOCK (src); + /* only restart when the pads were not yet activated, else we were + * streaming over UDP */ + restart = src->need_activate; + GST_OBJECT_UNLOCK (src); + + /* no need to restart, we're done */ + if (!restart) + goto done; + + /* we can try only TCP now */ + src->cur_protocols = GST_RTSP_LOWER_TRANS_TCP; + + /* close and cleanup our state */ + if ((res = gst_rtspsrc_close (src, async, FALSE)) < 0) + goto done; + + /* see if we have TCP left to try. Also don't try TCP when we were configured + * with an SDP. */ + if (!(src->protocols & GST_RTSP_LOWER_TRANS_TCP) || src->from_sdp) + goto no_protocols; + + /* We post a warning message now to inform the user + * that nothing happened. It's most likely a firewall thing. */ + GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), + ("Could not receive any UDP packets for %.4f seconds, maybe your " + "firewall is blocking it. Retrying using a TCP connection.", + gst_guint64_to_gdouble (src->udp_timeout / 1000000.0))); + + /* open new connection using tcp */ + if (gst_rtspsrc_open (src, async) < 0) + goto open_failed; + + /* start playback */ + if (gst_rtspsrc_play (src, &src->segment, async) < 0) + goto play_failed; + +done: + return res; + + /* ERRORS */ +no_protocols: + { + src->cur_protocols = 0; + /* no transport possible, post an error and stop */ + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Could not receive any UDP packets for %.4f seconds, maybe your " + "firewall is blocking it. No other protocols to try.", + gst_guint64_to_gdouble (src->udp_timeout / 1000000.0))); + return GST_RTSP_ERROR; + } +open_failed: + { + GST_DEBUG_OBJECT (src, "open failed"); + return GST_RTSP_OK; + } +play_failed: + { + GST_DEBUG_OBJECT (src, "play failed"); + return GST_RTSP_OK; + } +} + +static void +gst_rtspsrc_loop_start_cmd (GstRTSPSrc * src, gint cmd) +{ + switch (cmd) { + case CMD_OPEN: + GST_ELEMENT_PROGRESS (src, START, "open", ("Opening Stream")); + break; + case CMD_PLAY: + GST_ELEMENT_PROGRESS (src, START, "request", ("Sending PLAY request")); + break; + case CMD_PAUSE: + GST_ELEMENT_PROGRESS (src, START, "request", ("Sending PAUSE request")); + break; + case CMD_CLOSE: + GST_ELEMENT_PROGRESS (src, START, "close", ("Closing Stream")); + break; + default: + break; + } +} + +static void +gst_rtspsrc_loop_complete_cmd (GstRTSPSrc * src, gint cmd) +{ + +#ifdef GST_EXT_RTSP_MODIFICATION + GstStructure *s; +#endif + + switch (cmd) { + case CMD_OPEN: +#ifdef GST_EXT_RTSP_MODIFICATION + s = gst_structure_new ("RTSP_duration", "duration", G_TYPE_UINT64, src->segment.duration, NULL); + gst_element_post_message (GST_ELEMENT_CAST (src), gst_message_new_custom(GST_MESSAGE_ELEMENT, GST_OBJECT(src), s)); + GST_DEBUG_OBJECT(src, "GST_MESSAGE_ELEMENT msg posting. duration=%"GST_TIME_FORMAT, GST_TIME_ARGS(src->segment.duration)); +#endif + GST_ELEMENT_PROGRESS (src, COMPLETE, "open", ("Opened Stream")); +#ifdef GST_EXT_RTSP_MODIFICATION + /* rtspsrc PAUSE state should be here for parsing sdp before PAUSE state changed. */ + g_mutex_lock(&(src)->pause_lock); + g_cond_signal (&(src)->open_end); + g_mutex_unlock(&(src)->pause_lock); +#endif + break; + case CMD_PLAY: + GST_ELEMENT_PROGRESS (src, COMPLETE, "request", ("Sent PLAY request")); + break; + case CMD_PAUSE: + GST_ELEMENT_PROGRESS (src, COMPLETE, "request", ("Sent PAUSE request")); + break; + case CMD_CLOSE: + GST_ELEMENT_PROGRESS (src, COMPLETE, "close", ("Closed Stream")); + break; + default: + break; + } +} + +static void +gst_rtspsrc_loop_cancel_cmd (GstRTSPSrc * src, gint cmd) +{ + switch (cmd) { + case CMD_OPEN: + GST_ELEMENT_PROGRESS (src, CANCELED, "open", ("Open canceled")); + break; + case CMD_PLAY: + GST_ELEMENT_PROGRESS (src, CANCELED, "request", ("PLAY canceled")); + break; + case CMD_PAUSE: + GST_ELEMENT_PROGRESS (src, CANCELED, "request", ("PAUSE canceled")); + break; + case CMD_CLOSE: + GST_ELEMENT_PROGRESS (src, CANCELED, "close", ("Close canceled")); + break; + default: + break; + } +} + +static void +gst_rtspsrc_loop_error_cmd (GstRTSPSrc * src, gint cmd) +{ + switch (cmd) { + case CMD_OPEN: + GST_ELEMENT_PROGRESS (src, ERROR, "open", ("Open failed")); + break; + case CMD_PLAY: + GST_ELEMENT_PROGRESS (src, ERROR, "request", ("PLAY failed")); + break; + case CMD_PAUSE: + GST_ELEMENT_PROGRESS (src, ERROR, "request", ("PAUSE failed")); + break; + case CMD_CLOSE: + GST_ELEMENT_PROGRESS (src, ERROR, "close", ("Close failed")); + break; + default: + break; + } +} + +static void +gst_rtspsrc_loop_end_cmd (GstRTSPSrc * src, gint cmd, GstRTSPResult ret) +{ + if (ret == GST_RTSP_OK) + gst_rtspsrc_loop_complete_cmd (src, cmd); + else if (ret == GST_RTSP_EINTR) + gst_rtspsrc_loop_cancel_cmd (src, cmd); + else + gst_rtspsrc_loop_error_cmd (src, cmd); +} + +static gboolean +gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, gint mask) +{ + gint old; + gboolean flushed = FALSE; + + /* start new request */ + gst_rtspsrc_loop_start_cmd (src, cmd); + + GST_DEBUG_OBJECT (src, "sending cmd %s", cmd_to_string (cmd)); + + GST_OBJECT_LOCK (src); + old = src->pending_cmd; + if (old == CMD_RECONNECT) { + GST_DEBUG_OBJECT (src, "ignore, we were reconnecting"); + cmd = CMD_RECONNECT; + } + if (old != CMD_WAIT) { + src->pending_cmd = CMD_WAIT; + GST_OBJECT_UNLOCK (src); + /* cancel previous request */ + GST_DEBUG_OBJECT (src, "cancel previous request %s", cmd_to_string (old)); + gst_rtspsrc_loop_cancel_cmd (src, old); + GST_OBJECT_LOCK (src); + } + src->pending_cmd = cmd; + /* interrupt if allowed */ + if (src->busy_cmd & mask) { + GST_DEBUG_OBJECT (src, "connection flush busy %s", + cmd_to_string (src->busy_cmd)); + gst_rtspsrc_connection_flush (src, TRUE); + flushed = TRUE; + } else { + GST_DEBUG_OBJECT (src, "not interrupting busy cmd %s", + cmd_to_string (src->busy_cmd)); + } + if (src->task) + gst_task_start (src->task); + GST_OBJECT_UNLOCK (src); + + return flushed; +} + +static gboolean +gst_rtspsrc_loop (GstRTSPSrc * src) +{ + GstFlowReturn ret; + + if (!src->conninfo.connection || !src->conninfo.connected) + goto no_connection; + + if (src->interleaved) + ret = gst_rtspsrc_loop_interleaved (src); + else + ret = gst_rtspsrc_loop_udp (src); + + if (ret != GST_FLOW_OK) + goto pause; + + return TRUE; + + /* ERRORS */ +no_connection: + { + GST_WARNING_OBJECT (src, "we are not connected"); + ret = GST_FLOW_FLUSHING; + goto pause; + } +pause: + { + const gchar *reason = gst_flow_get_name (ret); + + GST_DEBUG_OBJECT (src, "pausing task, reason %s", reason); + src->running = FALSE; + if (ret == GST_FLOW_EOS) { + /* perform EOS logic */ + if (src->segment.flags & GST_SEEK_FLAG_SEGMENT) { + gst_element_post_message (GST_ELEMENT_CAST (src), + gst_message_new_segment_done (GST_OBJECT_CAST (src), + src->segment.format, src->segment.position)); + gst_rtspsrc_push_event (src, + gst_event_new_segment_done (src->segment.format, + src->segment.position)); + } else { + gst_rtspsrc_push_event (src, gst_event_new_eos ()); + } + } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) { + /* for fatal errors we post an error message, post the error before the + * EOS so the app knows about the error first. */ + GST_ELEMENT_ERROR (src, STREAM, FAILED, + ("Internal data flow error."), + ("streaming task paused, reason %s (%d)", reason, ret)); + gst_rtspsrc_push_event (src, gst_event_new_eos ()); + } + gst_rtspsrc_loop_send_cmd (src, CMD_WAIT, CMD_LOOP); + return FALSE; + } +} + +#ifndef GST_DISABLE_GST_DEBUG +static const gchar * +gst_rtsp_auth_method_to_string (GstRTSPAuthMethod method) +{ + gint index = 0; + + while (method != 0) { + index++; + method >>= 1; + } + switch (index) { + case 0: + return "None"; + case 1: + return "Basic"; + case 2: + return "Digest"; + } + + return "Unknown"; +} +#endif + +static const gchar * +gst_rtspsrc_skip_lws (const gchar * s) +{ + while (g_ascii_isspace (*s)) + s++; + return s; +} + +static const gchar * +gst_rtspsrc_unskip_lws (const gchar * s, const gchar * start) +{ + while (s > start && g_ascii_isspace (*(s - 1))) + s--; + return s; +} + +static const gchar * +gst_rtspsrc_skip_commas (const gchar * s) +{ + /* The grammar allows for multiple commas */ + while (g_ascii_isspace (*s) || *s == ',') + s++; + return s; +} + +static const gchar * +gst_rtspsrc_skip_item (const gchar * s) +{ + gboolean quoted = FALSE; + const gchar *start = s; + + /* A list item ends at the last non-whitespace character + * before a comma which is not inside a quoted-string. Or at + * the end of the string. + */ + while (*s) { + if (*s == '"') + quoted = !quoted; + else if (quoted) { + if (*s == '\\' && *(s + 1)) + s++; + } else { + if (*s == ',') + break; + } + s++; + } + + return gst_rtspsrc_unskip_lws (s, start); +} + +static void +gst_rtsp_decode_quoted_string (gchar * quoted_string) +{ + gchar *src, *dst; + + src = quoted_string + 1; + dst = quoted_string; + while (*src && *src != '"') { + if (*src == '\\' && *(src + 1)) + src++; + *dst++ = *src++; + } + *dst = '\0'; +} + +/* Extract the authentication tokens that the server provided for each method + * into an array of structures and give those to the connection object. + */ +static void +gst_rtspsrc_parse_digest_challenge (GstRTSPConnection * conn, + const gchar * header, gboolean * stale) +{ + GSList *list = NULL, *iter; + const gchar *end; + gchar *item, *eq, *name_end, *value; + + g_return_if_fail (stale != NULL); + + gst_rtsp_connection_clear_auth_params (conn); + *stale = FALSE; + + /* Parse a header whose content is described by RFC2616 as + * "#something", where "something" does not itself contain commas, + * except as part of quoted-strings, into a list of allocated strings. + */ + header = gst_rtspsrc_skip_commas (header); + while (*header) { + end = gst_rtspsrc_skip_item (header); + list = g_slist_prepend (list, g_strndup (header, end - header)); + header = gst_rtspsrc_skip_commas (end); + } + if (!list) + return; + + list = g_slist_reverse (list); + for (iter = list; iter; iter = iter->next) { + item = iter->data; + + eq = strchr (item, '='); + if (eq) { + name_end = (gchar *) gst_rtspsrc_unskip_lws (eq, item); + if (name_end == item) { + /* That's no good... */ + g_free (item); + continue; + } + + *name_end = '\0'; + + value = (gchar *) gst_rtspsrc_skip_lws (eq + 1); + if (*value == '"') + gst_rtsp_decode_quoted_string (value); + } else + value = NULL; + + if (value && strcmp (item, "stale") == 0 && strcmp (value, "TRUE") == 0) + *stale = TRUE; + gst_rtsp_connection_set_auth_param (conn, item, value); + g_free (item); + } + + g_slist_free (list); +} + +/* Parse a WWW-Authenticate Response header and determine the + * available authentication methods + * + * This code should also cope with the fact that each WWW-Authenticate + * header can contain multiple challenge methods + tokens + * + * At the moment, for Basic auth, we just do a minimal check and don't + * even parse out the realm */ +static void +gst_rtspsrc_parse_auth_hdr (gchar * hdr, GstRTSPAuthMethod * methods, + GstRTSPConnection * conn, gboolean * stale) +{ + gchar *start; + + g_return_if_fail (hdr != NULL); + g_return_if_fail (methods != NULL); + g_return_if_fail (stale != NULL); + + /* Skip whitespace at the start of the string */ + for (start = hdr; start[0] != '\0' && g_ascii_isspace (start[0]); start++); + + if (g_ascii_strncasecmp (start, "basic", 5) == 0) + *methods |= GST_RTSP_AUTH_BASIC; + else if (g_ascii_strncasecmp (start, "digest ", 7) == 0) { + *methods |= GST_RTSP_AUTH_DIGEST; + gst_rtspsrc_parse_digest_challenge (conn, &start[7], stale); + } +} + +/** + * gst_rtspsrc_setup_auth: + * @src: the rtsp source + * + * Configure a username and password and auth method on the + * connection object based on a response we received from the + * peer. + * + * Currently, this requires that a username and password were supplied + * in the uri. In the future, they may be requested on demand by sending + * a message up the bus. + * + * Returns: TRUE if authentication information could be set up correctly. + */ +static gboolean +gst_rtspsrc_setup_auth (GstRTSPSrc * src, GstRTSPMessage * response) +{ + gchar *user = NULL; + gchar *pass = NULL; + GstRTSPAuthMethod avail_methods = GST_RTSP_AUTH_NONE; + GstRTSPAuthMethod method; + GstRTSPResult auth_result; + GstRTSPUrl *url; + GstRTSPConnection *conn; + gchar *hdr; + gboolean stale = FALSE; + + conn = src->conninfo.connection; + + /* Identify the available auth methods and see if any are supported */ + if (gst_rtsp_message_get_header (response, GST_RTSP_HDR_WWW_AUTHENTICATE, + &hdr, 0) == GST_RTSP_OK) { + gst_rtspsrc_parse_auth_hdr (hdr, &avail_methods, conn, &stale); + } + + if (avail_methods == GST_RTSP_AUTH_NONE) + goto no_auth_available; + + /* For digest auth, if the response indicates that the session + * data are stale, we just update them in the connection object and + * return TRUE to retry the request */ + if (stale) + src->tried_url_auth = FALSE; + + url = gst_rtsp_connection_get_url (conn); + + /* Do we have username and password available? */ + if (url != NULL && !src->tried_url_auth && url->user != NULL + && url->passwd != NULL) { + user = url->user; + pass = url->passwd; + src->tried_url_auth = TRUE; + GST_DEBUG_OBJECT (src, + "Attempting authentication using credentials from the URL"); + } else { + user = src->user_id; + pass = src->user_pw; + GST_DEBUG_OBJECT (src, + "Attempting authentication using credentials from the properties"); + } + + /* FIXME: If the url didn't contain username and password or we tried them + * already, request a username and passwd from the application via some kind + * of credentials request message */ + + /* If we don't have a username and passwd at this point, bail out. */ + if (user == NULL || pass == NULL) + goto no_user_pass; + + /* Try to configure for each available authentication method, strongest to + * weakest */ + for (method = GST_RTSP_AUTH_MAX; method != GST_RTSP_AUTH_NONE; method >>= 1) { + /* Check if this method is available on the server */ + if ((method & avail_methods) == 0) + continue; + + /* Pass the credentials to the connection to try on the next request */ + auth_result = gst_rtsp_connection_set_auth (conn, method, user, pass); + /* INVAL indicates an invalid username/passwd were supplied, so we'll just + * ignore it and end up retrying later */ + if (auth_result == GST_RTSP_OK || auth_result == GST_RTSP_EINVAL) { + GST_DEBUG_OBJECT (src, "Attempting %s authentication", + gst_rtsp_auth_method_to_string (method)); + break; + } + } + + if (method == GST_RTSP_AUTH_NONE) + goto no_auth_available; + + return TRUE; + +no_auth_available: + { + /* Output an error indicating that we couldn't connect because there were + * no supported authentication protocols */ + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), + ("No supported authentication protocol was found")); + return FALSE; + } +no_user_pass: + { + /* We don't fire an error message, we just return FALSE and let the + * normal NOT_AUTHORIZED error be propagated */ + return FALSE; + } +} + +static GstRTSPResult +gst_rtspsrc_try_send (GstRTSPSrc * src, GstRTSPConnection * conn, + GstRTSPMessage * request, GstRTSPMessage * response, + GstRTSPStatusCode * code) +{ + GstRTSPResult res; + GstRTSPStatusCode thecode; + gchar *content_base = NULL; + gint try = 0; + +again: + if (!src->short_header) + gst_rtsp_ext_list_before_send (src->extensions, request); + + GST_DEBUG_OBJECT (src, "sending message"); + + if (src->debug) + gst_rtsp_message_dump (request); + + res = gst_rtspsrc_connection_send (src, conn, request, src->ptcp_timeout); + if (res < 0) + goto send_error; + + gst_rtsp_connection_reset_timeout (conn); + +next: + res = gst_rtspsrc_connection_receive (src, conn, response, src->ptcp_timeout); + if (res < 0) + goto receive_error; + + if (src->debug) + gst_rtsp_message_dump (response); + + switch (response->type) { + case GST_RTSP_MESSAGE_REQUEST: + res = gst_rtspsrc_handle_request (src, conn, response); + if (res == GST_RTSP_EEOF) + goto server_eof; + else if (res < 0) + goto handle_request_failed; + goto next; + case GST_RTSP_MESSAGE_RESPONSE: + /* ok, a response is good */ + GST_DEBUG_OBJECT (src, "received response message"); + break; + case GST_RTSP_MESSAGE_DATA: + /* get next response */ + GST_DEBUG_OBJECT (src, "handle data response message"); + gst_rtspsrc_handle_data (src, response); + goto next; + default: + GST_WARNING_OBJECT (src, "ignoring unknown message type %d", + response->type); + goto next; + } + + thecode = response->type_data.response.code; + + GST_DEBUG_OBJECT (src, "got response message %d", thecode); + + /* if the caller wanted the result code, we store it. */ + if (code) + *code = thecode; + + /* If the request didn't succeed, bail out before doing any more */ + if (thecode != GST_RTSP_STS_OK) + return GST_RTSP_OK; + + /* store new content base if any */ + gst_rtsp_message_get_header (response, GST_RTSP_HDR_CONTENT_BASE, + &content_base, 0); + if (content_base) { + g_free (src->content_base); + src->content_base = g_strdup (content_base); + } + gst_rtsp_ext_list_after_send (src->extensions, request, response); + + return GST_RTSP_OK; + + /* ERRORS */ +send_error: + { + gchar *str = gst_rtsp_strresult (res); + + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message. (%s)", str)); + } else { + GST_WARNING_OBJECT (src, "send interrupted"); + } + g_free (str); + return res; + } +receive_error: + { + switch (res) { + case GST_RTSP_EEOF: + GST_WARNING_OBJECT (src, "server closed connection"); + if ((try == 0) && !src->interleaved && src->udp_reconnect) { + try++; + /* if reconnect succeeds, try again */ + if ((res = + gst_rtsp_conninfo_reconnect (src, &src->conninfo, + FALSE)) == 0) + goto again; + } + /* only try once after reconnect, then fallthrough and error out */ + default: + { + gchar *str = gst_rtsp_strresult (res); + + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Could not receive message. (%s)", str)); + } else { + GST_WARNING_OBJECT (src, "receive interrupted"); + } + g_free (str); + break; + } + } + return res; + } +handle_request_failed: + { + /* ERROR was posted */ + gst_rtsp_message_unset (response); + return res; + } +server_eof: + { + GST_DEBUG_OBJECT (src, "we got an eof from the server"); + GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), + ("The server closed the connection.")); + gst_rtsp_message_unset (response); + return res; + } +} + +/** + * gst_rtspsrc_send: + * @src: the rtsp source + * @conn: the connection to send on + * @request: must point to a valid request + * @response: must point to an empty #GstRTSPMessage + * @code: an optional code result + * + * send @request and retrieve the response in @response. optionally @code can be + * non-NULL in which case it will contain the status code of the response. + * + * If This function returns #GST_RTSP_OK, @response will contain a valid response + * message that should be cleaned with gst_rtsp_message_unset() after usage. + * + * If @code is NULL, this function will return #GST_RTSP_ERROR (with an invalid + * @response message) if the response code was not 200 (OK). + * + * If the attempt results in an authentication failure, then this will attempt + * to retrieve authentication credentials via gst_rtspsrc_setup_auth and retry + * the request. + * + * Returns: #GST_RTSP_OK if the processing was successful. + */ +static GstRTSPResult +gst_rtspsrc_send (GstRTSPSrc * src, GstRTSPConnection * conn, + GstRTSPMessage * request, GstRTSPMessage * response, + GstRTSPStatusCode * code) +{ + GstRTSPStatusCode int_code = GST_RTSP_STS_OK; + GstRTSPResult res = GST_RTSP_ERROR; + gint count; + gboolean retry; + GstRTSPMethod method = GST_RTSP_INVALID; + + count = 0; + do { + retry = FALSE; + + /* make sure we don't loop forever */ + if (count++ > 8) + break; + + /* save method so we can disable it when the server complains */ + method = request->type_data.request.method; + + if ((res = + gst_rtspsrc_try_send (src, conn, request, response, &int_code)) < 0) + goto error; + + switch (int_code) { + case GST_RTSP_STS_UNAUTHORIZED: + if (gst_rtspsrc_setup_auth (src, response)) { + /* Try the request/response again after configuring the auth info + * and loop again */ + retry = TRUE; + } + break; + default: + break; + } + } while (retry == TRUE); + + /* If the user requested the code, let them handle errors, otherwise + * post an error below */ + if (code != NULL) + *code = int_code; + else if (int_code != GST_RTSP_STS_OK) + goto error_response; + + return res; + + /* ERRORS */ +error: + { + GST_DEBUG_OBJECT (src, "got error %d", res); + return res; + } +error_response: + { + res = GST_RTSP_ERROR; + + switch (response->type_data.response.code) { + case GST_RTSP_STS_NOT_FOUND: + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), ("%s", + response->type_data.response.reason)); + break; + case GST_RTSP_STS_UNAUTHORIZED: + GST_ELEMENT_ERROR (src, RESOURCE, NOT_AUTHORIZED, (NULL), ("%s", + response->type_data.response.reason)); + break; + case GST_RTSP_STS_MOVED_PERMANENTLY: + case GST_RTSP_STS_MOVE_TEMPORARILY: + { + gchar *new_location; + GstRTSPLowerTrans transports; + + GST_DEBUG_OBJECT (src, "got redirection"); + /* if we don't have a Location Header, we must error */ + if (gst_rtsp_message_get_header (response, GST_RTSP_HDR_LOCATION, + &new_location, 0) < 0) + break; + + /* When we receive a redirect result, we go back to the INIT state after + * parsing the new URI. The caller should do the needed steps to issue + * a new setup when it detects this state change. */ + GST_DEBUG_OBJECT (src, "redirection to %s", new_location); + + /* save current transports */ + if (src->conninfo.url) + transports = src->conninfo.url->transports; + else + transports = GST_RTSP_LOWER_TRANS_UNKNOWN; + + gst_rtspsrc_uri_set_uri (GST_URI_HANDLER (src), new_location, NULL); + + /* set old transports */ + if (src->conninfo.url && transports != GST_RTSP_LOWER_TRANS_UNKNOWN) + src->conninfo.url->transports = transports; + + src->need_redirect = TRUE; + src->state = GST_RTSP_STATE_INIT; + res = GST_RTSP_OK; + break; + } + case GST_RTSP_STS_NOT_ACCEPTABLE: + case GST_RTSP_STS_NOT_IMPLEMENTED: + case GST_RTSP_STS_METHOD_NOT_ALLOWED: + GST_WARNING_OBJECT (src, "got NOT IMPLEMENTED, disable method %s", + gst_rtsp_method_as_text (method)); + src->methods &= ~method; + res = GST_RTSP_OK; + break; + default: + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Got error response: %d (%s).", response->type_data.response.code, + response->type_data.response.reason)); + break; + } + /* if we return ERROR we should unset the response ourselves */ + if (res == GST_RTSP_ERROR) + gst_rtsp_message_unset (response); + + return res; + } +} + +static GstRTSPResult +gst_rtspsrc_send_cb (GstRTSPExtension * ext, GstRTSPMessage * request, + GstRTSPMessage * response, GstRTSPSrc * src) +{ + return gst_rtspsrc_send (src, src->conninfo.connection, request, response, + NULL); +} + + +/* parse the response and collect all the supported methods. We need this + * information so that we don't try to send an unsupported request to the + * server. + */ +static gboolean +gst_rtspsrc_parse_methods (GstRTSPSrc * src, GstRTSPMessage * response) +{ + GstRTSPHeaderField field; + gchar *respoptions; + gint indx = 0; + + /* reset supported methods */ + src->methods = 0; + + /* Try Allow Header first */ + field = GST_RTSP_HDR_ALLOW; + while (TRUE) { + respoptions = NULL; + gst_rtsp_message_get_header (response, field, &respoptions, indx); + if (indx == 0 && !respoptions) { + /* if no Allow header was found then try the Public header... */ + field = GST_RTSP_HDR_PUBLIC; + gst_rtsp_message_get_header (response, field, &respoptions, indx); + } + if (!respoptions) + break; + + src->methods |= gst_rtsp_options_from_text (respoptions); + + indx++; + } + + if (src->methods == 0) { + /* neither Allow nor Public are required, assume the server supports + * at least DESCRIBE, SETUP, we always assume it supports PLAY as + * well. */ + GST_DEBUG_OBJECT (src, "could not get OPTIONS"); + src->methods = GST_RTSP_DESCRIBE | GST_RTSP_SETUP; + } + /* always assume PLAY, FIXME, extensions should be able to override + * this */ + src->methods |= GST_RTSP_PLAY; + /* also assume it will support Range */ + src->seekable = TRUE; + + /* we need describe and setup */ + if (!(src->methods & GST_RTSP_DESCRIBE)) + goto no_describe; + if (!(src->methods & GST_RTSP_SETUP)) + goto no_setup; + + return TRUE; + + /* ERRORS */ +no_describe: + { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), + ("Server does not support DESCRIBE.")); + return FALSE; + } +no_setup: + { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), + ("Server does not support SETUP.")); + return FALSE; + } +} + +/* masks to be kept in sync with the hardcoded protocol order of preference + * in code below */ +static const guint protocol_masks[] = { + GST_RTSP_LOWER_TRANS_UDP, + GST_RTSP_LOWER_TRANS_UDP_MCAST, + GST_RTSP_LOWER_TRANS_TCP, + 0 +}; + +static GstRTSPResult +gst_rtspsrc_create_transports_string (GstRTSPSrc * src, + GstRTSPLowerTrans protocols, GstRTSPProfile profile, gchar ** transports) +{ + GstRTSPResult res; + GString *result; + gboolean add_udp_str; + + *transports = NULL; + + res = + gst_rtsp_ext_list_get_transports (src->extensions, protocols, transports); + + if (res < 0) + goto failed; + + GST_DEBUG_OBJECT (src, "got transports %s", GST_STR_NULL (*transports)); + + /* extension listed transports, use those */ + if (*transports != NULL) + return GST_RTSP_OK; + + /* it's the default */ + add_udp_str = FALSE; + + /* the default RTSP transports */ + result = g_string_new ("RTP"); + + switch (profile) { + case GST_RTSP_PROFILE_AVP: + g_string_append (result, "/AVP"); + break; + case GST_RTSP_PROFILE_SAVP: + g_string_append (result, "/SAVP"); + break; + case GST_RTSP_PROFILE_AVPF: + g_string_append (result, "/AVPF"); + break; + case GST_RTSP_PROFILE_SAVPF: + g_string_append (result, "/SAVPF"); + break; + default: + break; + } + + if (protocols & GST_RTSP_LOWER_TRANS_UDP) { + GST_DEBUG_OBJECT (src, "adding UDP unicast"); + if (add_udp_str) + g_string_append (result, "/UDP"); + g_string_append (result, ";unicast;client_port=%%u1-%%u2"); + } else if (protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST) { + GST_DEBUG_OBJECT (src, "adding UDP multicast"); + /* we don't have to allocate any UDP ports yet, if the selected transport + * turns out to be multicast we can create them and join the multicast + * group indicated in the transport reply */ + if (add_udp_str) + g_string_append (result, "/UDP"); + g_string_append (result, ";multicast"); + if (src->next_port_num != 0) { + if (src->client_port_range.max > 0 && + src->next_port_num >= src->client_port_range.max) + goto no_ports; + + g_string_append_printf (result, ";client_port=%d-%d", + src->next_port_num, src->next_port_num + 1); + } + } else if (protocols & GST_RTSP_LOWER_TRANS_TCP) { + GST_DEBUG_OBJECT (src, "adding TCP"); + + g_string_append (result, "/TCP;unicast;interleaved=%%i1-%%i2"); + } + *transports = g_string_free (result, FALSE); + + GST_DEBUG_OBJECT (src, "prepared transports %s", GST_STR_NULL (*transports)); + + return GST_RTSP_OK; + + /* ERRORS */ +failed: + { + GST_ERROR ("extension gave error %d", res); + return res; + } +no_ports: + { + GST_ERROR ("no more ports available"); + return GST_RTSP_ERROR; + } +} + +static GstRTSPResult +gst_rtspsrc_prepare_transports (GstRTSPStream * stream, gchar ** transports, + gint orig_rtpport, gint orig_rtcpport) +{ + GstRTSPSrc *src; + gint nr_udp, nr_int; + gchar *next, *p; + gint rtpport = 0, rtcpport = 0; + GString *str; + + src = stream->parent; + + /* find number of placeholders first */ + if (strstr (*transports, "%%i2")) + nr_int = 2; + else if (strstr (*transports, "%%i1")) + nr_int = 1; + else + nr_int = 0; + + if (strstr (*transports, "%%u2")) + nr_udp = 2; + else if (strstr (*transports, "%%u1")) + nr_udp = 1; + else + nr_udp = 0; + + if (nr_udp == 0 && nr_int == 0) + goto done; + + if (nr_udp > 0) { + if (!orig_rtpport || !orig_rtcpport) { + if (!gst_rtspsrc_alloc_udp_ports (stream, &rtpport, &rtcpport)) + goto failed; + } else { + rtpport = orig_rtpport; + rtcpport = orig_rtcpport; + } + } + + str = g_string_new (""); + p = *transports; + while ((next = strstr (p, "%%"))) { + g_string_append_len (str, p, next - p); + if (next[2] == 'u') { + if (next[3] == '1') + g_string_append_printf (str, "%d", rtpport); + else if (next[3] == '2') + g_string_append_printf (str, "%d", rtcpport); + } + if (next[2] == 'i') { + if (next[3] == '1') + g_string_append_printf (str, "%d", src->free_channel); + else if (next[3] == '2') + g_string_append_printf (str, "%d", src->free_channel + 1); + } + + p = next + 4; + } + /* append final part */ + g_string_append (str, p); + + g_free (*transports); + *transports = g_string_free (str, FALSE); + +done: + return GST_RTSP_OK; + + /* ERRORS */ +failed: + { + GST_ERROR ("failed to allocate udp ports"); + return GST_RTSP_ERROR; + } +} + +static guint8 +enc_key_length_from_cipher_name (const gchar * cipher) +{ + if (g_strcmp0 (cipher, "aes-128-icm") == 0) + return AES_128_KEY_LEN; + else if (g_strcmp0 (cipher, "aes-256-icm") == 0) + return AES_256_KEY_LEN; + else { + GST_ERROR ("encryption algorithm '%s' not supported", cipher); + return 0; + } +} + +static guint8 +auth_key_length_from_auth_name (const gchar * auth) +{ + if (g_strcmp0 (auth, "hmac-sha1-32") == 0) + return HMAC_32_KEY_LEN; + else if (g_strcmp0 (auth, "hmac-sha1-80") == 0) + return HMAC_80_KEY_LEN; + else { + GST_ERROR ("authentication algorithm '%s' not supported", auth); + return 0; + } +} + +static GstCaps * +signal_get_srtcp_params (GstRTSPSrc * src, GstRTSPStream * stream) +{ + GstCaps *caps = NULL; + + g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_REQUEST_RTCP_KEY], 0, + stream->id, &caps); + + if (caps != NULL) + GST_DEBUG_OBJECT (src, "SRTP parameters received"); + + return caps; +} + +static GstCaps * +default_srtcp_params (void) +{ + guint i; + GstCaps *caps; + GstBuffer *buf; + guint8 *key_data; +#define KEY_SIZE 30 + + /* create a random key */ + key_data = g_malloc (KEY_SIZE); + for (i = 0; i < KEY_SIZE; i += 4) + GST_WRITE_UINT32_BE (key_data + i, g_random_int ()); + + buf = gst_buffer_new_wrapped (key_data, KEY_SIZE); + + caps = gst_caps_new_simple ("application/x-srtp", + "srtp-key", GST_TYPE_BUFFER, buf, + "srtcp-cipher", G_TYPE_STRING, "aes-128-icm", + "srtcp-auth", G_TYPE_STRING, "hmac-sha1-80", NULL); + + gst_buffer_unref (buf); + + return caps; +} + +static gchar * +gst_rtspsrc_stream_make_keymgmt (GstRTSPSrc * src, GstRTSPStream * stream) +{ + GBytes *bytes; + gchar *result, *base64; + const guint8 *data; + gsize size; + GstMIKEYMessage *msg; + GstMIKEYPayload *payload, *pkd; + guint8 byte; + GstStructure *s; + GstMapInfo info; + GstBuffer *srtpkey; + const GValue *val; + const gchar *srtcpcipher, *srtcpauth; + + stream->srtcpparams = signal_get_srtcp_params (src, stream); + if (stream->srtcpparams == NULL) + stream->srtcpparams = default_srtcp_params (); + + s = gst_caps_get_structure (stream->srtcpparams, 0); + + srtcpcipher = gst_structure_get_string (s, "srtcp-cipher"); + srtcpauth = gst_structure_get_string (s, "srtcp-auth"); + val = gst_structure_get_value (s, "srtp-key"); + + if (srtcpcipher == NULL || srtcpauth == NULL || val == NULL) { + GST_ERROR_OBJECT (src, "could not find the right SRTP parameters in caps"); + return NULL; + } + + srtpkey = gst_value_get_buffer (val); + + msg = gst_mikey_message_new (); + /* unencrypted MIKEY message, we send this over TLS so this is allowed */ + gst_mikey_message_set_info (msg, GST_MIKEY_VERSION, GST_MIKEY_TYPE_PSK_INIT, + FALSE, GST_MIKEY_PRF_MIKEY_1, g_random_int (), GST_MIKEY_MAP_TYPE_SRTP); + /* add policy '0' for our SSRC */ + gst_mikey_message_add_cs_srtp (msg, 0, stream->send_ssrc, 0); + /* timestamp is now */ + gst_mikey_message_add_t_now_ntp_utc (msg); + /* add some random data */ + gst_mikey_message_add_rand_len (msg, 16); + + /* the policy '0' is SRTP */ + payload = gst_mikey_payload_new (GST_MIKEY_PT_SP); + gst_mikey_payload_sp_set (payload, 0, GST_MIKEY_SEC_PROTO_SRTP); + + /* only AES-CM is supported */ + byte = 1; + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_ALG, 1, &byte); + /* encryption key length */ + byte = enc_key_length_from_cipher_name (srtcpcipher); + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_KEY_LEN, 1, + &byte); + /* only HMAC-SHA1 */ + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_ALG, 1, + &byte); + /* authentication key length */ + byte = auth_key_length_from_auth_name (srtcpauth); + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_KEY_LEN, 1, + &byte); + /* we enable encryption on RTP and RTCP */ + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_ENC, 1, + &byte); + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTCP_ENC, 1, + &byte); + /* we enable authentication on RTP and RTCP */ + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_AUTH, 1, + &byte); + gst_mikey_message_add_payload (msg, payload); + + /* make unencrypted KEMAC */ + payload = gst_mikey_payload_new (GST_MIKEY_PT_KEMAC); + gst_mikey_payload_kemac_set (payload, GST_MIKEY_ENC_NULL, GST_MIKEY_MAC_NULL); + /* add the key in KEMAC */ + pkd = gst_mikey_payload_new (GST_MIKEY_PT_KEY_DATA); + gst_buffer_map (srtpkey, &info, GST_MAP_READ); + gst_mikey_payload_key_data_set_key (pkd, GST_MIKEY_KD_TEK, info.size, + info.data); + gst_buffer_unmap (srtpkey, &info); + gst_mikey_payload_kemac_add_sub (payload, pkd); + gst_mikey_message_add_payload (msg, payload); + + /* now serialize this to bytes */ + bytes = gst_mikey_message_to_bytes (msg, NULL, NULL); + gst_mikey_message_unref (msg); + /* and make it into base64 */ + data = g_bytes_get_data (bytes, &size); + base64 = g_base64_encode (data, size); + g_bytes_unref (bytes); + + result = g_strdup_printf ("prot=mikey;uri=\"%s\";data=\"%s\"", + stream->conninfo.location, base64); + g_free (base64); + + return result; +} + + +/* Perform the SETUP request for all the streams. + * + * We ask the server for a specific transport, which initially includes all the + * ones we can support (UDP/TCP/MULTICAST). For the UDP transport we allocate + * two local UDP ports that we send to the server. + * + * Once the server replied with a transport, we configure the other streams + * with the same transport. + * + * This function will also configure the stream for the selected transport, + * which basically means creating the pipeline. + */ +static GstRTSPResult +gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) +{ + GList *walk; + GstRTSPResult res = GST_RTSP_ERROR; + GstRTSPMessage request = { 0 }; + GstRTSPMessage response = { 0 }; + GstRTSPStream *stream = NULL; + GstRTSPLowerTrans protocols; + GstRTSPStatusCode code; + gboolean unsupported_real = FALSE; + gint rtpport, rtcpport; + GstRTSPUrl *url; + gchar *hval; + + if (src->conninfo.connection) { + url = gst_rtsp_connection_get_url (src->conninfo.connection); + /* we initially allow all configured lower transports. based on the URL + * transports and the replies from the server we narrow them down. */ + protocols = url->transports & src->cur_protocols; + } else { + url = NULL; + protocols = src->cur_protocols; + } + + if (protocols == 0) + goto no_protocols; + + /* reset some state */ + src->free_channel = 0; + src->interleaved = FALSE; + src->need_activate = FALSE; + /* keep track of next port number, 0 is random */ + src->next_port_num = src->client_port_range.min; + rtpport = rtcpport = 0; + + if (G_UNLIKELY (src->streams == NULL)) + goto no_streams; + + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPConnection *conn; + gchar *transports; + gint retry = 0; + guint mask = 0; + gboolean selected; + GstCaps *caps; + + stream = (GstRTSPStream *) walk->data; + + caps = stream_get_caps_for_pt (stream, stream->default_pt); + if (caps == NULL) { + GST_DEBUG_OBJECT (src, "skipping stream %p, no caps", stream); + continue; + } + + if (stream->skipped) { + GST_DEBUG_OBJECT (src, "skipping stream %p", stream); + continue; + } + + /* see if we need to configure this stream */ + if (!gst_rtsp_ext_list_configure_stream (src->extensions, caps)) { + GST_DEBUG_OBJECT (src, "skipping stream %p, disabled by extension", + stream); + continue; + } + + g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_SELECT_STREAM], 0, + stream->id, caps, &selected); + if (!selected) { + GST_DEBUG_OBJECT (src, "skipping stream %p, disabled by signal", stream); + continue; + } + + /* merge/overwrite global caps */ + if (caps) { + guint j, num; + GstStructure *s; + + s = gst_caps_get_structure (caps, 0); + + num = gst_structure_n_fields (src->props); + for (j = 0; j < num; j++) { + const gchar *name; + const GValue *val; + + name = gst_structure_nth_field_name (src->props, j); + val = gst_structure_get_value (src->props, name); + gst_structure_set_value (s, name, val); + + GST_DEBUG_OBJECT (src, "copied %s", name); + } + } + + /* skip setup if we have no URL for it */ + if (stream->conninfo.location == NULL) { + GST_DEBUG_OBJECT (src, "skipping stream %p, no setup", stream); + continue; + } + + if (src->conninfo.connection == NULL) { + if (!gst_rtsp_conninfo_connect (src, &stream->conninfo, async)) { + GST_DEBUG_OBJECT (src, "skipping stream %p, failed to connect", stream); + continue; + } + conn = stream->conninfo.connection; + } else { + conn = src->conninfo.connection; + } + GST_DEBUG_OBJECT (src, "doing setup of stream %p with %s", stream, + stream->conninfo.location); + + /* if we have a multicast connection, only suggest multicast from now on */ + if (stream->is_multicast) + protocols &= GST_RTSP_LOWER_TRANS_UDP_MCAST; + + next_protocol: + /* first selectable protocol */ + while (protocol_masks[mask] && !(protocols & protocol_masks[mask])) + mask++; + if (!protocol_masks[mask]) + goto no_protocols; + + retry: + GST_DEBUG_OBJECT (src, "protocols = 0x%x, protocol mask = 0x%x", protocols, + protocol_masks[mask]); + /* create a string with first transport in line */ + transports = NULL; + res = gst_rtspsrc_create_transports_string (src, + protocols & protocol_masks[mask], stream->profile, &transports); + if (res < 0 || transports == NULL) + goto setup_transport_failed; + + if (strlen (transports) == 0) { + g_free (transports); + GST_DEBUG_OBJECT (src, "no transports found"); + mask++; + goto next_protocol; + } + + GST_DEBUG_OBJECT (src, "replace ports in %s", GST_STR_NULL (transports)); + + /* replace placeholders with real values, this function will optionally + * allocate UDP ports and other info needed to execute the setup request */ + res = gst_rtspsrc_prepare_transports (stream, &transports, + retry > 0 ? rtpport : 0, retry > 0 ? rtcpport : 0); + if (res < 0) { + g_free (transports); + goto setup_transport_failed; + } + + GST_DEBUG_OBJECT (src, "transport is now %s", GST_STR_NULL (transports)); + + /* create SETUP request */ + res = + gst_rtsp_message_init_request (&request, GST_RTSP_SETUP, + stream->conninfo.location); + if (res < 0) { + g_free (transports); + goto create_request_failed; + } + + /* select transport */ + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_TRANSPORT, transports); + + /* set up keys */ + if (stream->profile == GST_RTSP_PROFILE_SAVP || + stream->profile == GST_RTSP_PROFILE_SAVPF) { + hval = gst_rtspsrc_stream_make_keymgmt (src, stream); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_KEYMGMT, hval); + } + + /* if the user wants a non default RTP packet size we add the blocksize + * parameter */ + if (src->rtp_blocksize > 0) { + hval = g_strdup_printf ("%d", src->rtp_blocksize); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_BLOCKSIZE, hval); + } + + if (async) + GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("SETUP stream %d", + stream->id)); + + /* handle the code ourselves */ + res = gst_rtspsrc_send (src, conn, &request, &response, &code); + if (res < 0) + goto send_error; + + switch (code) { + case GST_RTSP_STS_OK: + break; + case GST_RTSP_STS_UNSUPPORTED_TRANSPORT: + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + /* cleanup of leftover transport */ + gst_rtspsrc_stream_free_udp (stream); + /* MS WMServer RTSP MUST use same UDP pair in all SETUP requests; + * we might be in this case */ + if (stream->container && rtpport && rtcpport && !retry) { + GST_DEBUG_OBJECT (src, "retrying with original port pair %u-%u", + rtpport, rtcpport); + retry++; + goto retry; + } + /* this transport did not go down well, but we may have others to try + * that we did not send yet, try those and only give up then + * but not without checking for lost cause/extension so we can + * post a nicer/more useful error message later */ + if (!unsupported_real) + unsupported_real = stream->is_real; + /* select next available protocol, give up on this stream if none */ + mask++; + while (protocol_masks[mask] && !(protocols & protocol_masks[mask])) + mask++; + if (!protocol_masks[mask] || unsupported_real) + continue; + else + goto retry; + default: + /* cleanup of leftover transport and move to the next stream */ + gst_rtspsrc_stream_free_udp (stream); + goto response_error; + } + + /* parse response transport */ + { + gchar *resptrans = NULL; + GstRTSPTransport transport = { 0 }; + + gst_rtsp_message_get_header (&response, GST_RTSP_HDR_TRANSPORT, + &resptrans, 0); + if (!resptrans) { + gst_rtspsrc_stream_free_udp (stream); + goto no_transport; + } + + /* parse transport, go to next stream on parse error */ + if (gst_rtsp_transport_parse (resptrans, &transport) != GST_RTSP_OK) { + GST_WARNING_OBJECT (src, "failed to parse transport %s", resptrans); + goto next; + } + + /* update allowed transports for other streams. once the transport of + * one stream has been determined, we make sure that all other streams + * are configured in the same way */ + switch (transport.lower_transport) { + case GST_RTSP_LOWER_TRANS_TCP: + GST_DEBUG_OBJECT (src, "stream %p as TCP interleaved", stream); + protocols = GST_RTSP_LOWER_TRANS_TCP; + src->interleaved = TRUE; + /* update free channels */ + src->free_channel = + MAX (transport.interleaved.min, src->free_channel); + src->free_channel = + MAX (transport.interleaved.max, src->free_channel); + src->free_channel++; + break; + case GST_RTSP_LOWER_TRANS_UDP_MCAST: + /* only allow multicast for other streams */ + GST_DEBUG_OBJECT (src, "stream %p as UDP multicast", stream); + protocols = GST_RTSP_LOWER_TRANS_UDP_MCAST; + /* if the server selected our ports, increment our counters so that + * we select a new port later */ + if (src->next_port_num == transport.port.min && + src->next_port_num + 1 == transport.port.max) { + src->next_port_num += 2; + } + break; + case GST_RTSP_LOWER_TRANS_UDP: + /* only allow unicast for other streams */ + GST_DEBUG_OBJECT (src, "stream %p as UDP unicast", stream); + protocols = GST_RTSP_LOWER_TRANS_UDP; + break; + default: + GST_DEBUG_OBJECT (src, "stream %p unknown transport %d", stream, + transport.lower_transport); + break; + } + + if (!src->interleaved || !retry) { + /* now configure the stream with the selected transport */ + if (!gst_rtspsrc_stream_configure_transport (stream, &transport)) { + GST_DEBUG_OBJECT (src, + "could not configure stream %p transport, skipping stream", + stream); + goto next; + } else if (stream->udpsrc[0] && stream->udpsrc[1]) { + /* retain the first allocated UDP port pair */ + g_object_get (G_OBJECT (stream->udpsrc[0]), "port", &rtpport, NULL); + g_object_get (G_OBJECT (stream->udpsrc[1]), "port", &rtcpport, NULL); + } + } + /* we need to activate at least one streams when we detect activity */ + src->need_activate = TRUE; + + /* stream is setup now */ + stream->setup = TRUE; + { + GList *skip = walk; + + while (TRUE) { + GstRTSPStream *sskip; + + skip = g_list_next (skip); + if (skip == NULL) + break; + + sskip = (GstRTSPStream *) skip->data; + + /* skip all streams with the same control url */ + if (g_str_equal (stream->conninfo.location, sskip->conninfo.location)) { + GST_DEBUG_OBJECT (src, "found stream %p with same control %s", + sskip, sskip->conninfo.location); + sskip->skipped = TRUE; + } + } + } + next: + /* clean up our transport struct */ + gst_rtsp_transport_init (&transport); + /* clean up used RTSP messages */ + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + } + } + + /* store the transport protocol that was configured */ + src->cur_protocols = protocols; + + gst_rtsp_ext_list_stream_select (src->extensions, url); + + /* if there is nothing to activate, error out */ + if (!src->need_activate) + goto nothing_to_activate; + + return res; + + /* ERRORS */ +no_protocols: + { + /* no transport possible, post an error and stop */ + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Could not connect to server, no protocols left")); + return GST_RTSP_ERROR; + } +no_streams: + { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("SDP contains no streams")); + return GST_RTSP_ERROR; + } +create_request_failed: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), + ("Could not create request. (%s)", str)); + g_free (str); + goto cleanup_error; + } +setup_transport_failed: + { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("Could not setup transport.")); + res = GST_RTSP_ERROR; + goto cleanup_error; + } +response_error: + { + const gchar *str = gst_rtsp_status_as_text (code); + + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Error (%d): %s", code, GST_STR_NULL (str))); + res = GST_RTSP_ERROR; + goto cleanup_error; + } +send_error: + { + gchar *str = gst_rtsp_strresult (res); + + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message. (%s)", str)); + } else { + GST_WARNING_OBJECT (src, "send interrupted"); + } + g_free (str); + goto cleanup_error; + } +no_transport: + { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("Server did not select transport.")); + res = GST_RTSP_ERROR; + goto cleanup_error; + } +nothing_to_activate: + { + /* none of the available error codes is really right .. */ + if (unsupported_real) { + GST_ELEMENT_ERROR (src, STREAM, CODEC_NOT_FOUND, + (_("No supported stream was found. You might need to install a " + "GStreamer RTSP extension plugin for Real media streams.")), + (NULL)); + } else { + GST_ELEMENT_ERROR (src, STREAM, CODEC_NOT_FOUND, + (_("No supported stream was found. You might need to allow " + "more transport protocols or may otherwise be missing " + "the right GStreamer RTSP extension plugin.")), (NULL)); + } + return GST_RTSP_ERROR; + } +cleanup_error: + { + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + return res; + } +} + +static gboolean +gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range, + GstSegment * segment) +{ + gint64 seconds; + GstRTSPTimeRange *therange; + + if (src->range) + gst_rtsp_range_free (src->range); + + if (gst_rtsp_range_parse (range, &therange) == GST_RTSP_OK) { + GST_DEBUG_OBJECT (src, "parsed range %s", range); + src->range = therange; + } else { + GST_DEBUG_OBJECT (src, "failed to parse range %s", range); + src->range = NULL; + gst_segment_init (segment, GST_FORMAT_TIME); + return FALSE; + } + + GST_DEBUG_OBJECT (src, "range: type %d, min %f - type %d, max %f ", + therange->min.type, therange->min.seconds, therange->max.type, + therange->max.seconds); + + if (therange->min.type == GST_RTSP_TIME_NOW) + seconds = 0; + else if (therange->min.type == GST_RTSP_TIME_END) + seconds = 0; + else + seconds = therange->min.seconds * GST_SECOND; + + GST_DEBUG_OBJECT (src, "range: min %" GST_TIME_FORMAT, + GST_TIME_ARGS (seconds)); + + /* we need to start playback without clipping from the position reported by + * the server */ + segment->start = seconds; + segment->position = seconds; + + if (therange->max.type == GST_RTSP_TIME_NOW) + seconds = -1; + else if (therange->max.type == GST_RTSP_TIME_END) + seconds = -1; + else + seconds = therange->max.seconds * GST_SECOND; + + GST_DEBUG_OBJECT (src, "range: max %" GST_TIME_FORMAT, + GST_TIME_ARGS (seconds)); + + /* live (WMS) server might send overflowed large max as its idea of infinity, + * compensate to prevent problems later on */ + if (seconds != -1 && seconds < 0) { + seconds = -1; + GST_DEBUG_OBJECT (src, "insane range, set to NONE"); + } + + /* live (WMS) might send min == max, which is not worth recording */ + if (segment->duration == -1 && seconds == segment->start) + seconds = -1; + + /* don't change duration with unknown value, we might have a valid value + * there that we want to keep. */ + if (seconds != -1) + segment->duration = seconds; + + return TRUE; +} + +/* Parse clock profived by the server with following syntax: + * + * "GstNetTimeProvider <wrapped-clock> <server-IP:port> <clock-time>" + */ +static gboolean +gst_rtspsrc_parse_gst_clock (GstRTSPSrc * src, const gchar * gstclock) +{ + gboolean res = FALSE; + + if (g_str_has_prefix (gstclock, "GstNetTimeProvider ")) { + gchar **fields = NULL, **parts = NULL; + gchar *remote_ip, *str; + gint port; + GstClockTime base_time; + GstClock *netclock; + + fields = g_strsplit (gstclock, " ", 0); + + /* wrapped clock, not very interesting for now */ + if (fields[1] == NULL) + goto cleanup; + + /* remote IP address and port */ + if ((str = fields[2]) == NULL) + goto cleanup; + + parts = g_strsplit (str, ":", 0); + + if ((remote_ip = parts[0]) == NULL) + goto cleanup; + + if ((str = parts[1]) == NULL) + goto cleanup; + + port = atoi (str); + if (port == 0) + goto cleanup; + + /* base-time */ + if ((str = fields[3]) == NULL) + goto cleanup; + + base_time = g_ascii_strtoull (str, NULL, 10); + + netclock = + gst_net_client_clock_new ((gchar *) "GstRTSPClock", remote_ip, port, + base_time); + + if (src->provided_clock) + gst_object_unref (src->provided_clock); + src->provided_clock = netclock; + + gst_element_post_message (GST_ELEMENT_CAST (src), + gst_message_new_clock_provide (GST_OBJECT_CAST (src), + src->provided_clock, TRUE)); + + res = TRUE; + cleanup: + g_strfreev (fields); + g_strfreev (parts); + } + return res; +} + +/* must be called with the RTSP state lock */ +static GstRTSPResult +gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp, + gboolean async) +{ + GstRTSPResult res; + gint i, n_streams; + + /* prepare global stream caps properties */ + if (src->props) + gst_structure_remove_all_fields (src->props); + else + src->props = gst_structure_new_empty ("RTSPProperties"); + + if (src->debug) + gst_sdp_message_dump (sdp); + + gst_rtsp_ext_list_parse_sdp (src->extensions, sdp, src->props); + + /* let the app inspect and change the SDP */ + g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_ON_SDP], 0, sdp); + + gst_segment_init (&src->segment, GST_FORMAT_TIME); + + /* parse range for duration reporting. */ + { + const gchar *range; + + for (i = 0;; i++) { + range = gst_sdp_message_get_attribute_val_n (sdp, "range", i); + if (range == NULL) + break; + + /* keep track of the range and configure it in the segment */ + if (gst_rtspsrc_parse_range (src, range, &src->segment)) + break; + } + } + /* parse clock information. This is GStreamer specific, a server can tell the + * client what clock it is using and wrap that in a network clock. The + * advantage of that is that we can slave to it. */ + { + const gchar *gstclock; + + for (i = 0;; i++) { + gstclock = gst_sdp_message_get_attribute_val_n (sdp, "x-gst-clock", i); + if (gstclock == NULL) + break; + + /* parse the clock and expose it in the provide_clock method */ + if (gst_rtspsrc_parse_gst_clock (src, gstclock)) + break; + } + } + /* try to find a global control attribute. Note that a '*' means that we should + * do aggregate control with the current url (so we don't do anything and + * leave the current connection as is) */ + { + const gchar *control; + + for (i = 0;; i++) { + control = gst_sdp_message_get_attribute_val_n (sdp, "control", i); + if (control == NULL) + break; + + /* only take fully qualified urls */ + if (g_str_has_prefix (control, "rtsp://")) + break; + } + if (control) { + g_free (src->conninfo.location); + src->conninfo.location = g_strdup (control); + /* make a connection for this, if there was a connection already, nothing + * happens. */ + if (gst_rtsp_conninfo_connect (src, &src->conninfo, async) < 0) { + GST_ERROR_OBJECT (src, "could not connect"); + } + } + /* we need to keep the control url separate from the connection url because + * the rules for constructing the media control url need it */ + g_free (src->control); + src->control = g_strdup (control); + } + + /* create streams */ + n_streams = gst_sdp_message_medias_len (sdp); + for (i = 0; i < n_streams; i++) { + gst_rtspsrc_create_stream (src, sdp, i); + } + + src->state = GST_RTSP_STATE_INIT; + + /* setup streams */ + if ((res = gst_rtspsrc_setup_streams (src, async)) < 0) + goto setup_failed; + + /* reset our state */ + src->need_range = TRUE; + src->skip = FALSE; + + src->state = GST_RTSP_STATE_READY; + + return res; + + /* ERRORS */ +setup_failed: + { + GST_ERROR_OBJECT (src, "setup failed"); + gst_rtspsrc_cleanup (src); + return res; + } +} + +static GstRTSPResult +gst_rtspsrc_retrieve_sdp (GstRTSPSrc * src, GstSDPMessage ** sdp, + gboolean async) +{ + GstRTSPResult res; + GstRTSPMessage request = { 0 }; + GstRTSPMessage response = { 0 }; + guint8 *data; + guint size; + gchar *respcont = NULL; + +restart: + src->need_redirect = FALSE; + + /* can't continue without a valid url */ + if (G_UNLIKELY (src->conninfo.url == NULL)) { + res = GST_RTSP_EINVAL; + goto no_url; + } + src->tried_url_auth = FALSE; + + if ((res = gst_rtsp_conninfo_connect (src, &src->conninfo, async)) < 0) + goto connect_failed; + + /* create OPTIONS */ + GST_DEBUG_OBJECT (src, "create options..."); + res = + gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS, + src->conninfo.url_str); + if (res < 0) + goto create_request_failed; + + /* send OPTIONS */ + GST_DEBUG_OBJECT (src, "send options..."); + + if (async) + GST_ELEMENT_PROGRESS (src, CONTINUE, "open", ("Retrieving server options")); + + if ((res = + gst_rtspsrc_send (src, src->conninfo.connection, &request, &response, + NULL)) < 0) + goto send_error; + + /* parse OPTIONS */ + if (!gst_rtspsrc_parse_methods (src, &response)) + goto methods_error; + + /* create DESCRIBE */ + GST_DEBUG_OBJECT (src, "create describe..."); + res = + gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE, + src->conninfo.url_str); + if (res < 0) + goto create_request_failed; + + /* we only accept SDP for now */ + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_ACCEPT, + "application/sdp"); + + /* send DESCRIBE */ + GST_DEBUG_OBJECT (src, "send describe..."); + + if (async) + GST_ELEMENT_PROGRESS (src, CONTINUE, "open", ("Retrieving media info")); + + if ((res = + gst_rtspsrc_send (src, src->conninfo.connection, &request, &response, + NULL)) < 0) + goto send_error; + + /* we only perform redirect for the describe, currently */ + if (src->need_redirect) { + /* close connection, we don't have to send a TEARDOWN yet, ignore the + * result. */ + gst_rtsp_conninfo_close (src, &src->conninfo, TRUE); + + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + + /* and now retry */ + goto restart; + } + + /* it could be that the DESCRIBE method was not implemented */ + if (!src->methods & GST_RTSP_DESCRIBE) + goto no_describe; + + /* check if reply is SDP */ + gst_rtsp_message_get_header (&response, GST_RTSP_HDR_CONTENT_TYPE, &respcont, + 0); + /* could not be set but since the request returned OK, we assume it + * was SDP, else check it. */ + if (respcont) { + if (g_ascii_strcasecmp (respcont, "application/sdp") != 0) + goto wrong_content_type; + } + + /* get message body and parse as SDP */ + gst_rtsp_message_get_body (&response, &data, &size); + if (data == NULL || size == 0) + goto no_describe; + + GST_DEBUG_OBJECT (src, "parse SDP..."); + gst_sdp_message_new (sdp); + gst_sdp_message_parse_buffer (data, size, *sdp); + + /* clean up any messages */ + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + + return res; + + /* ERRORS */ +no_url: + { + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), + ("No valid RTSP URL was provided")); + goto cleanup_error; + } +connect_failed: + { + gchar *str = gst_rtsp_strresult (res); + + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL), + ("Failed to connect. (%s)", str)); + } else { + GST_WARNING_OBJECT (src, "connect interrupted"); + } + g_free (str); + goto cleanup_error; + } +create_request_failed: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), + ("Could not create request. (%s)", str)); + g_free (str); + goto cleanup_error; + } +send_error: + { + /* Don't post a message - the rtsp_send method will have + * taken care of it because we passed NULL for the response code */ + goto cleanup_error; + } +methods_error: + { + /* error was posted */ + res = GST_RTSP_ERROR; + goto cleanup_error; + } +wrong_content_type: + { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("Server does not support SDP, got %s.", respcont)); + res = GST_RTSP_ERROR; + goto cleanup_error; + } +no_describe: + { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("Server can not provide an SDP.")); + res = GST_RTSP_ERROR; + goto cleanup_error; + } +cleanup_error: + { + if (src->conninfo.connection) { + GST_DEBUG_OBJECT (src, "free connection"); + gst_rtsp_conninfo_close (src, &src->conninfo, TRUE); + } + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + return res; + } +} + +static GstRTSPResult +gst_rtspsrc_open (GstRTSPSrc * src, gboolean async) +{ + GstRTSPResult ret; + + src->methods = + GST_RTSP_SETUP | GST_RTSP_PLAY | GST_RTSP_PAUSE | GST_RTSP_TEARDOWN; + + if (src->sdp == NULL) { + if ((ret = gst_rtspsrc_retrieve_sdp (src, &src->sdp, async)) < 0) + goto no_sdp; + } + + if ((ret = gst_rtspsrc_open_from_sdp (src, src->sdp, async)) < 0) + goto open_failed; + +done: + if (async) + gst_rtspsrc_loop_end_cmd (src, CMD_OPEN, ret); + + return ret; + + /* ERRORS */ +no_sdp: + { + GST_WARNING_OBJECT (src, "can't get sdp"); + src->open_error = TRUE; + goto done; + } +open_failed: + { + GST_WARNING_OBJECT (src, "can't setup streaming from sdp"); + src->open_error = TRUE; + goto done; + } +} + +static GstRTSPResult +gst_rtspsrc_close (GstRTSPSrc * src, gboolean async, gboolean only_close) +{ + GstRTSPMessage request = { 0 }; + GstRTSPMessage response = { 0 }; + GstRTSPResult res = GST_RTSP_OK; + GList *walk; + const gchar *control; + + GST_DEBUG_OBJECT (src, "TEARDOWN..."); + + gst_rtspsrc_set_state (src, GST_STATE_READY); + + if (src->state < GST_RTSP_STATE_READY) { + GST_DEBUG_OBJECT (src, "not ready, doing cleanup"); + goto close; + } + + if (only_close) + goto close; + + /* construct a control url */ + control = get_aggregate_control (src); + + if (!(src->methods & (GST_RTSP_PLAY | GST_RTSP_TEARDOWN))) + goto not_supported; + + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + const gchar *setup_url; + GstRTSPConnInfo *info; + + /* try aggregate control first but do non-aggregate control otherwise */ + if (control) + setup_url = control; + else if ((setup_url = stream->conninfo.location) == NULL) + continue; + + if (src->conninfo.connection) { + info = &src->conninfo; + } else if (stream->conninfo.connection) { + info = &stream->conninfo; + } else { + continue; + } + if (!info->connected) + goto next; + + /* do TEARDOWN */ + res = + gst_rtsp_message_init_request (&request, GST_RTSP_TEARDOWN, setup_url); + if (res < 0) + goto create_request_failed; + + if (async) + GST_ELEMENT_PROGRESS (src, CONTINUE, "close", ("Closing stream")); + + if ((res = + gst_rtspsrc_send (src, info->connection, &request, &response, + NULL)) < 0) + goto send_error; + + /* FIXME, parse result? */ + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + + next: + /* early exit when we did aggregate control */ + if (control) + break; + } + +close: + /* close connections */ + GST_DEBUG_OBJECT (src, "closing connection..."); + gst_rtsp_conninfo_close (src, &src->conninfo, TRUE); + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + gst_rtsp_conninfo_close (src, &stream->conninfo, TRUE); + } + + /* cleanup */ + gst_rtspsrc_cleanup (src); + + src->state = GST_RTSP_STATE_INVALID; + + if (async) + gst_rtspsrc_loop_end_cmd (src, CMD_CLOSE, res); + + return res; + + /* ERRORS */ +create_request_failed: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), + ("Could not create request. (%s)", str)); + g_free (str); + goto close; + } +send_error: + { + gchar *str = gst_rtsp_strresult (res); + + gst_rtsp_message_unset (&request); + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message. (%s)", str)); + } else { + GST_WARNING_OBJECT (src, "TEARDOWN interrupted"); + } + g_free (str); + goto close; + } +not_supported: + { + GST_DEBUG_OBJECT (src, + "TEARDOWN and PLAY not supported, can't do TEARDOWN"); + goto close; + } +} + +/* RTP-Info is of the format: + * + * url=<URL>;[seq=<seqbase>;rtptime=<timebase>] [, url=...] + * + * rtptime corresponds to the timestamp for the NPT time given in the header + * seqbase corresponds to the next sequence number we received. This number + * indicates the first seqnum after the seek and should be used to discard + * packets that are from before the seek. + */ +static gboolean +gst_rtspsrc_parse_rtpinfo (GstRTSPSrc * src, gchar * rtpinfo) +{ + gchar **infos; + gint i, j; + + GST_DEBUG_OBJECT (src, "parsing RTP-Info %s", rtpinfo); + + infos = g_strsplit (rtpinfo, ",", 0); + for (i = 0; infos[i]; i++) { + gchar **fields; + GstRTSPStream *stream; + gint32 seqbase; + gint64 timebase; + + GST_DEBUG_OBJECT (src, "parsing info %s", infos[i]); + + /* init values, types of seqbase and timebase are bigger than needed so we + * can store -1 as uninitialized values */ + stream = NULL; + seqbase = -1; + timebase = -1; + + /* parse url, find stream for url. + * parse seq and rtptime. The seq number should be configured in the rtp + * depayloader or session manager to detect gaps. Same for the rtptime, it + * should be used to create an initial time newsegment. */ + fields = g_strsplit (infos[i], ";", 0); + for (j = 0; fields[j]; j++) { + GST_DEBUG_OBJECT (src, "parsing field %s", fields[j]); + /* remove leading whitespace */ + fields[j] = g_strchug (fields[j]); + if (g_str_has_prefix (fields[j], "url=")) { + /* get the url and the stream */ + stream = + find_stream (src, (fields[j] + 4), (gpointer) find_stream_by_setup); + } else if (g_str_has_prefix (fields[j], "seq=")) { + seqbase = atoi (fields[j] + 4); + } else if (g_str_has_prefix (fields[j], "rtptime=")) { + timebase = g_ascii_strtoll (fields[j] + 8, NULL, 10); + } + } + g_strfreev (fields); + /* now we need to store the values for the caps of the stream */ + if (stream != NULL) { + GST_DEBUG_OBJECT (src, + "found stream %p, setting: seqbase %d, timebase %" G_GINT64_FORMAT, + stream, seqbase, timebase); + + /* we have a stream, configure detected params */ + stream->seqbase = seqbase; + stream->timebase = timebase; + } + } + g_strfreev (infos); + + return TRUE; +} + +static void +gst_rtspsrc_handle_rtcp_interval (GstRTSPSrc * src, gchar * rtcp) +{ + guint64 interval; + GList *walk; + + interval = strtoul (rtcp, NULL, 10); + GST_DEBUG_OBJECT (src, "rtcp interval: %" G_GUINT64_FORMAT " ms", interval); + + if (!interval) + return; + + interval *= GST_MSECOND; + + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + + /* already (optionally) retrieved this when configuring manager */ + if (stream->session) { + GObject *rtpsession = stream->session; + + GST_DEBUG_OBJECT (src, "configure rtcp interval in session %p", + rtpsession); + g_object_set (rtpsession, "rtcp-min-interval", interval, NULL); + } + } + + /* now it happens that (Xenon) server sending this may also provide bogus + * RTCP SR sync data (i.e. with quite some jitter), so never mind those + * and just use RTP-Info to sync */ + if (src->manager) { + GObjectClass *klass; + + klass = G_OBJECT_GET_CLASS (G_OBJECT (src->manager)); + if (g_object_class_find_property (klass, "rtcp-sync")) { + GST_DEBUG_OBJECT (src, "configuring rtp sync method"); + g_object_set (src->manager, "rtcp-sync", RTCP_SYNC_RTP, NULL); + } + } +} + +static gdouble +gst_rtspsrc_get_float (const gchar * dstr) +{ + gchar s[G_ASCII_DTOSTR_BUF_SIZE] = { 0, }; + + /* canonicalise floating point string so we can handle float strings + * in the form "24.930" or "24,930" irrespective of the current locale */ + g_strlcpy (s, dstr, sizeof (s)); + g_strdelimit (s, ",", '.'); + return g_ascii_strtod (s, NULL); +} + +static gchar * +gen_range_header (GstRTSPSrc * src, GstSegment * segment) +{ + gchar val_str[G_ASCII_DTOSTR_BUF_SIZE] = { 0, }; +#ifdef GST_EXT_RTSP_MODIFICATION + if (src->start_position !=0 && segment->position == 0) { + segment->position = src->start_position; + src->start_position = 0; + } +#endif + if (src->range && src->range->min.type == GST_RTSP_TIME_NOW) { + g_strlcpy (val_str, "now", sizeof (val_str)); + } else { + if (segment->position == 0) { + g_strlcpy (val_str, "0", sizeof (val_str)); + } else { + g_ascii_dtostr (val_str, sizeof (val_str), + ((gdouble) segment->position) / GST_SECOND); + } + } + return g_strdup_printf ("npt=%s-", val_str); +} + +static void +clear_rtp_base (GstRTSPSrc * src, GstRTSPStream * stream) +{ + guint i, len; + + stream->timebase = -1; + stream->seqbase = -1; + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + GstStructure *s; + + if (item->caps == NULL) + continue; + + item->caps = gst_caps_make_writable (item->caps); + s = gst_caps_get_structure (item->caps, 0); + gst_structure_remove_fields (s, "clock-base", "seqnum-base", NULL); + } +} + +static GstRTSPResult +gst_rtspsrc_ensure_open (GstRTSPSrc * src, gboolean async) +{ + GstRTSPResult res = GST_RTSP_OK; + + if (src->state < GST_RTSP_STATE_READY) { + res = GST_RTSP_ERROR; + if (src->open_error) { + GST_DEBUG_OBJECT (src, "the stream was in error"); + goto done; + } + if (async) + gst_rtspsrc_loop_start_cmd (src, CMD_OPEN); + + if ((res = gst_rtspsrc_open (src, async)) < 0) { + GST_DEBUG_OBJECT (src, "failed to open stream"); + goto done; + } + } + +done: + return res; +} + +static GstRTSPResult +gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) +{ + GstRTSPMessage request = { 0 }; + GstRTSPMessage response = { 0 }; + GstRTSPResult res = GST_RTSP_OK; + GList *walk; + gchar *hval; + gint hval_idx; + const gchar *control; + + GST_DEBUG_OBJECT (src, "PLAY..."); + + if ((res = gst_rtspsrc_ensure_open (src, async)) < 0) + goto open_failed; + + if (!(src->methods & GST_RTSP_PLAY)) + goto not_supported; + + if (src->state == GST_RTSP_STATE_PLAYING) + goto was_playing; + + if (!src->conninfo.connection || !src->conninfo.connected) + goto done; + + /* send some dummy packets before we activate the receive in the + * udp sources */ + gst_rtspsrc_send_dummy_packets (src); + + /* require new SR packets */ + if (src->manager) + g_signal_emit_by_name (src->manager, "reset-sync", NULL); + + /* construct a control url */ + control = get_aggregate_control (src); + + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + const gchar *setup_url; + GstRTSPConnection *conn; + + /* try aggregate control first but do non-aggregate control otherwise */ + if (control) + setup_url = control; + else if ((setup_url = stream->conninfo.location) == NULL) + continue; + + if (src->conninfo.connection) { + conn = src->conninfo.connection; + } else if (stream->conninfo.connection) { + conn = stream->conninfo.connection; + } else { + continue; + } + + /* do play */ + res = gst_rtsp_message_init_request (&request, GST_RTSP_PLAY, setup_url); + if (res < 0) + goto create_request_failed; + + if (src->need_range) { + hval = gen_range_header (src, segment); + + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_RANGE, hval); + + /* store the newsegment event so it can be sent from the streaming thread. */ + src->need_segment = TRUE; + } + + if (segment->rate != 1.0) { + gchar hval[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_dtostr (hval, sizeof (hval), segment->rate); + if (src->skip) + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SCALE, hval); + else + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, hval); + } + + if (async) + GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PLAY request")); + + if ((res = gst_rtspsrc_send (src, conn, &request, &response, NULL)) < 0) + goto send_error; + + /* seek may have silently failed as it is not supported */ + if (!(src->methods & GST_RTSP_PLAY)) { + GST_DEBUG_OBJECT (src, "PLAY Range not supported; re-enable PLAY"); + /* obviously it is supported as we made it here */ + src->methods |= GST_RTSP_PLAY; + src->seekable = FALSE; + /* but there is nothing to parse in the response, + * so convey we have no idea and not to expect anything particular */ + clear_rtp_base (src, stream); + if (control) { + GList *run; + + /* need to do for all streams */ + for (run = src->streams; run; run = g_list_next (run)) + clear_rtp_base (src, (GstRTSPStream *) run->data); + } + /* NOTE the above also disables npt based eos detection */ + /* and below forces position to 0, + * which is visible feedback we lost the plot */ + segment->start = segment->position = src->last_pos; + } + + gst_rtsp_message_unset (&request); + + /* parse RTP npt field. This is the current position in the stream (Normal + * Play Time) and should be put in the NEWSEGMENT position field. */ + if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RANGE, &hval, + 0) == GST_RTSP_OK) + gst_rtspsrc_parse_range (src, hval, segment); + + /* assume 1.0 rate now, overwrite when the SCALE or SPEED headers are present. */ + segment->rate = 1.0; + + /* parse Speed header. This is the intended playback rate of the stream + * and should be put in the NEWSEGMENT rate field. */ + if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_SPEED, &hval, + 0) == GST_RTSP_OK) { + segment->rate = gst_rtspsrc_get_float (hval); + } else if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_SCALE, + &hval, 0) == GST_RTSP_OK) { + segment->rate = gst_rtspsrc_get_float (hval); + } + + /* parse the RTP-Info header field (if ANY) to get the base seqnum and timestamp + * for the RTP packets. If this is not present, we assume all starts from 0... + * This is info for the RTP session manager that we pass to it in caps. */ + hval_idx = 0; + while (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RTP_INFO, + &hval, hval_idx++) == GST_RTSP_OK) + gst_rtspsrc_parse_rtpinfo (src, hval); + + /* some servers indicate RTCP parameters in PLAY response, + * rather than properly in SDP */ + if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RTCP_INTERVAL, + &hval, 0) == GST_RTSP_OK) + gst_rtspsrc_handle_rtcp_interval (src, hval); + + gst_rtsp_message_unset (&response); + + /* early exit when we did aggregate control */ + if (control) + break; + } + /* configure the caps of the streams after we parsed all headers. Only reset + * the manager object when we set a new Range header (we did a seek) */ + gst_rtspsrc_configure_caps (src, segment, src->need_range); + + /* set to PLAYING after we have configured the caps, otherwise we + * might end up calling request_key (with SRTP) while caps are still + * being configured. */ + gst_rtspsrc_set_state (src, GST_STATE_PLAYING); + + /* set again when needed */ + src->need_range = FALSE; + + src->running = TRUE; + src->base_time = -1; + src->state = GST_RTSP_STATE_PLAYING; + + /* mark discont */ + GST_DEBUG_OBJECT (src, "mark DISCONT, we did a seek to another position"); + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + stream->discont = TRUE; + } + +done: + if (async) + gst_rtspsrc_loop_end_cmd (src, CMD_PLAY, res); + + return res; + + /* ERRORS */ +open_failed: + { + GST_DEBUG_OBJECT (src, "failed to open stream"); + goto done; + } +not_supported: + { + GST_DEBUG_OBJECT (src, "PLAY is not supported"); + goto done; + } +was_playing: + { + GST_DEBUG_OBJECT (src, "we were already PLAYING"); + goto done; + } +create_request_failed: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), + ("Could not create request. (%s)", str)); + g_free (str); + goto done; + } +send_error: + { + gchar *str = gst_rtsp_strresult (res); + + gst_rtsp_message_unset (&request); + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message. (%s)", str)); + } else { + GST_WARNING_OBJECT (src, "PLAY interrupted"); + } + g_free (str); + goto done; + } +} + +static GstRTSPResult +gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPMessage request = { 0 }; + GstRTSPMessage response = { 0 }; + GList *walk; + const gchar *control; + + GST_DEBUG_OBJECT (src, "PAUSE..."); + + if ((res = gst_rtspsrc_ensure_open (src, async)) < 0) + goto open_failed; + + if (!(src->methods & GST_RTSP_PAUSE)) + goto not_supported; + + if (src->state == GST_RTSP_STATE_READY) + goto was_paused; + + if (!src->conninfo.connection || !src->conninfo.connected) + goto no_connection; + + /* construct a control url */ + control = get_aggregate_control (src); + + /* loop over the streams. We might exit the loop early when we could do an + * aggregate control */ + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + GstRTSPConnection *conn; + const gchar *setup_url; + + /* try aggregate control first but do non-aggregate control otherwise */ + if (control) + setup_url = control; + else if ((setup_url = stream->conninfo.location) == NULL) + continue; + + if (src->conninfo.connection) { + conn = src->conninfo.connection; + } else if (stream->conninfo.connection) { + conn = stream->conninfo.connection; + } else { + continue; + } + + if (async) + GST_ELEMENT_PROGRESS (src, CONTINUE, "request", + ("Sending PAUSE request")); + + if ((res = + gst_rtsp_message_init_request (&request, GST_RTSP_PAUSE, + setup_url)) < 0) + goto create_request_failed; + + if ((res = gst_rtspsrc_send (src, conn, &request, &response, NULL)) < 0) + goto send_error; + + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + + /* exit early when we did agregate control */ + if (control) + break; + } + + /* change element states now */ + gst_rtspsrc_set_state (src, GST_STATE_PAUSED); + +no_connection: + src->state = GST_RTSP_STATE_READY; + +done: + if (async) + gst_rtspsrc_loop_end_cmd (src, CMD_PAUSE, res); + + return res; + + /* ERRORS */ +open_failed: + { + GST_DEBUG_OBJECT (src, "failed to open stream"); + goto done; + } +not_supported: + { + GST_DEBUG_OBJECT (src, "PAUSE is not supported"); + goto done; + } +was_paused: + { + GST_DEBUG_OBJECT (src, "we were already PAUSED"); + goto done; + } +create_request_failed: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), + ("Could not create request. (%s)", str)); + g_free (str); + goto done; + } +send_error: + { + gchar *str = gst_rtsp_strresult (res); + + gst_rtsp_message_unset (&request); + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message. (%s)", str)); + } else { + GST_WARNING_OBJECT (src, "PAUSE interrupted"); + } + g_free (str); + goto done; + } +} + +static void +gst_rtspsrc_handle_message (GstBin * bin, GstMessage * message) +{ + GstRTSPSrc *rtspsrc; + + rtspsrc = GST_RTSPSRC (bin); + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + gst_message_unref (message); + break; + case GST_MESSAGE_ELEMENT: + { + const GstStructure *s = gst_message_get_structure (message); + + if (gst_structure_has_name (s, "GstUDPSrcTimeout")) { + gboolean ignore_timeout; + + GST_DEBUG_OBJECT (bin, "timeout on UDP port"); + + GST_OBJECT_LOCK (rtspsrc); + ignore_timeout = rtspsrc->ignore_timeout; + rtspsrc->ignore_timeout = TRUE; + GST_OBJECT_UNLOCK (rtspsrc); + + /* we only act on the first udp timeout message, others are irrelevant + * and can be ignored. */ + if (!ignore_timeout) + gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_RECONNECT, CMD_LOOP); + /* eat and free */ + gst_message_unref (message); + return; + } + GST_BIN_CLASS (parent_class)->handle_message (bin, message); + break; + } + case GST_MESSAGE_ERROR: + { + GstObject *udpsrc; + GstRTSPStream *stream; + GstFlowReturn ret; + + udpsrc = GST_MESSAGE_SRC (message); + + GST_DEBUG_OBJECT (rtspsrc, "got error from %s", + GST_ELEMENT_NAME (udpsrc)); + + stream = find_stream (rtspsrc, udpsrc, (gpointer) find_stream_by_udpsrc); + if (!stream) + goto forward; + + /* we ignore the RTCP udpsrc */ + if (stream->udpsrc[1] == GST_ELEMENT_CAST (udpsrc)) + goto done; + + /* if we get error messages from the udp sources, that's not a problem as + * long as not all of them error out. We also don't really know what the + * problem is, the message does not give enough detail... */ + ret = gst_rtspsrc_combine_flows (rtspsrc, stream, GST_FLOW_NOT_LINKED); + GST_DEBUG_OBJECT (rtspsrc, "combined flows: %s", gst_flow_get_name (ret)); + if (ret != GST_FLOW_OK) + goto forward; + + done: + gst_message_unref (message); + break; + + forward: + /* fatal but not our message, forward */ + GST_BIN_CLASS (parent_class)->handle_message (bin, message); + break; + } + default: + { + GST_BIN_CLASS (parent_class)->handle_message (bin, message); + break; + } + } +} + +/* the thread where everything happens */ +static void +gst_rtspsrc_thread (GstRTSPSrc * src) +{ + gint cmd; + + GST_OBJECT_LOCK (src); + cmd = src->pending_cmd; + if (cmd == CMD_RECONNECT || cmd == CMD_PLAY || cmd == CMD_PAUSE + || cmd == CMD_LOOP || cmd == CMD_OPEN) + src->pending_cmd = CMD_LOOP; + else + src->pending_cmd = CMD_WAIT; + GST_DEBUG_OBJECT (src, "got command %s", cmd_to_string (cmd)); + + /* we got the message command, so ensure communication is possible again */ + gst_rtspsrc_connection_flush (src, FALSE); + + src->busy_cmd = cmd; + GST_OBJECT_UNLOCK (src); + + switch (cmd) { + case CMD_OPEN: + gst_rtspsrc_open (src, TRUE); + break; + case CMD_PLAY: + gst_rtspsrc_play (src, &src->segment, TRUE); + break; + case CMD_PAUSE: + gst_rtspsrc_pause (src, TRUE); + break; + case CMD_CLOSE: + gst_rtspsrc_close (src, TRUE, FALSE); + break; + case CMD_LOOP: + gst_rtspsrc_loop (src); + break; + case CMD_RECONNECT: + gst_rtspsrc_reconnect (src, FALSE); + break; + default: + break; + } + + GST_OBJECT_LOCK (src); + /* and go back to sleep */ + if (src->pending_cmd == CMD_WAIT) { + if (src->task) + gst_task_pause (src->task); + } + /* reset waiting */ + src->busy_cmd = CMD_WAIT; + GST_OBJECT_UNLOCK (src); +} + +static gboolean +gst_rtspsrc_start (GstRTSPSrc * src) +{ + GST_DEBUG_OBJECT (src, "starting"); + + GST_OBJECT_LOCK (src); + + src->pending_cmd = CMD_WAIT; + + if (src->task == NULL) { + src->task = gst_task_new ((GstTaskFunction) gst_rtspsrc_thread, src, NULL); + if (src->task == NULL) + goto task_error; + + gst_task_set_lock (src->task, GST_RTSP_STREAM_GET_LOCK (src)); + } + GST_OBJECT_UNLOCK (src); + + return TRUE; + + /* ERRORS */ +task_error: + { + GST_OBJECT_UNLOCK (src); + GST_ERROR_OBJECT (src, "failed to create task"); + return FALSE; + } +} + +static gboolean +gst_rtspsrc_stop (GstRTSPSrc * src) +{ + GstTask *task; + + GST_DEBUG_OBJECT (src, "stopping"); + + /* also cancels pending task */ + gst_rtspsrc_loop_send_cmd (src, CMD_WAIT, CMD_ALL); + + GST_OBJECT_LOCK (src); + if ((task = src->task)) { + src->task = NULL; + GST_OBJECT_UNLOCK (src); + + gst_task_stop (task); + + /* make sure it is not running */ + GST_RTSP_STREAM_LOCK (src); + GST_RTSP_STREAM_UNLOCK (src); + + /* now wait for the task to finish */ + gst_task_join (task); + + /* and free the task */ + gst_object_unref (GST_OBJECT (task)); + + GST_OBJECT_LOCK (src); + } + GST_OBJECT_UNLOCK (src); + + /* ensure synchronously all is closed and clean */ + gst_rtspsrc_close (src, FALSE, TRUE); + + return TRUE; +} + +static GstStateChangeReturn +gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) +{ + GstRTSPSrc *rtspsrc; + GstStateChangeReturn ret; +#ifdef GST_EXT_RTSP_MODIFICATION + guint64 end_time; +#endif + + rtspsrc = GST_RTSPSRC (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_rtspsrc_start (rtspsrc)) + goto start_failed; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + /* init some state */ + rtspsrc->cur_protocols = rtspsrc->protocols; + /* first attempt, don't ignore timeouts */ + rtspsrc->ignore_timeout = FALSE; + rtspsrc->open_error = FALSE; + gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_OPEN, 0); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + set_manager_buffer_mode (rtspsrc); + /* fall-through */ + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + /* unblock the tcp tasks and make the loop waiting */ + if (gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_WAIT, CMD_LOOP)) { + /* make sure it is waiting before we send PAUSE or PLAY below */ + GST_RTSP_STREAM_LOCK (rtspsrc); + GST_RTSP_STREAM_UNLOCK (rtspsrc); + } + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + goto done; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + ret = GST_STATE_CHANGE_SUCCESS; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: +#ifdef GST_EXT_RTSP_MODIFICATION + /* don't change to PAUSE state before complete stream opend. + see gst_rtspsrc_loop_complete_cmd() */ + g_mutex_lock(&(rtspsrc)->pause_lock); + end_time = g_get_monotonic_time () + 10 * G_TIME_SPAN_SECOND; + if (!g_cond_wait_until (&(rtspsrc)->open_end, &(rtspsrc)->pause_lock, end_time)) { + GST_WARNING_OBJECT(rtspsrc, "time out: stream opend is not completed yet.."); + } + g_mutex_unlock(&(rtspsrc)->pause_lock); +#endif + ret = GST_STATE_CHANGE_NO_PREROLL; + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_PLAY, 0); + ret = GST_STATE_CHANGE_SUCCESS; + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + /* send pause request and keep the idle task around */ + gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_PAUSE, CMD_LOOP); + ret = GST_STATE_CHANGE_NO_PREROLL; + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_CLOSE, CMD_PAUSE); + ret = GST_STATE_CHANGE_SUCCESS; + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_rtspsrc_stop (rtspsrc); + ret = GST_STATE_CHANGE_SUCCESS; + break; + default: + break; + } + +done: + return ret; + +start_failed: + { + GST_DEBUG_OBJECT (rtspsrc, "start failed"); + return GST_STATE_CHANGE_FAILURE; + } +} + +static gboolean +gst_rtspsrc_send_event (GstElement * element, GstEvent * event) +{ + gboolean res; + GstRTSPSrc *rtspsrc; + + rtspsrc = GST_RTSPSRC (element); + + if (GST_EVENT_IS_DOWNSTREAM (event)) { + res = gst_rtspsrc_push_event (rtspsrc, event); + } else { + res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event); + } + + return res; +} + + +/*** GSTURIHANDLER INTERFACE *************************************************/ + +static GstURIType +gst_rtspsrc_uri_get_type (GType type) +{ + return GST_URI_SRC; +} + +static const gchar *const * +gst_rtspsrc_uri_get_protocols (GType type) +{ + static const gchar *protocols[] = + { "rtsp", "rtspu", "rtspt", "rtsph", "rtsp-sdp", + "rtsps", "rtspsu", "rtspst", "rtspsh", NULL + }; + + return protocols; +} + +static gchar * +gst_rtspsrc_uri_get_uri (GstURIHandler * handler) +{ + GstRTSPSrc *src = GST_RTSPSRC (handler); + + /* FIXME: make thread-safe */ + return g_strdup (src->conninfo.location); +} + +static gboolean +gst_rtspsrc_uri_set_uri (GstURIHandler * handler, const gchar * uri, + GError ** error) +{ + GstRTSPSrc *src; + GstRTSPResult res; + GstSDPResult sres; + GstRTSPUrl *newurl = NULL; + GstSDPMessage *sdp = NULL; + + src = GST_RTSPSRC (handler); + + /* same URI, we're fine */ + if (src->conninfo.location && uri && !strcmp (uri, src->conninfo.location)) + goto was_ok; + + if (g_str_has_prefix (uri, "rtsp-sdp://")) { + sres = gst_sdp_message_new (&sdp); + if (sres < 0) + goto sdp_failed; + + GST_DEBUG_OBJECT (src, "parsing SDP message"); + sres = gst_sdp_message_parse_uri (uri, sdp); + if (sres < 0) + goto invalid_sdp; + } else { + /* try to parse */ + GST_DEBUG_OBJECT (src, "parsing URI"); + if ((res = gst_rtsp_url_parse (uri, &newurl)) < 0) + goto parse_error; + } + + /* if worked, free previous and store new url object along with the original + * location. */ + GST_DEBUG_OBJECT (src, "configuring URI"); + g_free (src->conninfo.location); + src->conninfo.location = g_strdup (uri); + gst_rtsp_url_free (src->conninfo.url); + src->conninfo.url = newurl; + g_free (src->conninfo.url_str); + if (newurl) + src->conninfo.url_str = gst_rtsp_url_get_request_uri (src->conninfo.url); + else + src->conninfo.url_str = NULL; + + if (src->sdp) + gst_sdp_message_free (src->sdp); + src->sdp = sdp; + src->from_sdp = sdp != NULL; + + GST_DEBUG_OBJECT (src, "set uri: %s", GST_STR_NULL (uri)); + GST_DEBUG_OBJECT (src, "request uri is: %s", + GST_STR_NULL (src->conninfo.url_str)); + + return TRUE; + + /* Special cases */ +was_ok: + { + GST_DEBUG_OBJECT (src, "URI was ok: '%s'", GST_STR_NULL (uri)); + return TRUE; + } +sdp_failed: + { + GST_ERROR_OBJECT (src, "Could not create new SDP (%d)", sres); + g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, + "Could not create SDP"); + return FALSE; + } +invalid_sdp: + { + GST_ERROR_OBJECT (src, "Not a valid SDP (%d) '%s'", sres, + GST_STR_NULL (uri)); + gst_sdp_message_free (sdp); + g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, + "Invalid SDP"); + return FALSE; + } +parse_error: + { + GST_ERROR_OBJECT (src, "Not a valid RTSP url '%s' (%d)", + GST_STR_NULL (uri), res); + g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, + "Invalid RTSP URI"); + return FALSE; + } +} + +static void +gst_rtspsrc_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = gst_rtspsrc_uri_get_type; + iface->get_protocols = gst_rtspsrc_uri_get_protocols; + iface->get_uri = gst_rtspsrc_uri_get_uri; + iface->set_uri = gst_rtspsrc_uri_set_uri; +} diff --git a/gst/rtsp/gstrtspsrc.h b/gst/rtsp/gstrtspsrc.h new file mode 100755 index 0000000..6309aeb --- /dev/null +++ b/gst/rtsp/gstrtspsrc.h @@ -0,0 +1,283 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * <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. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __GST_RTSPSRC_H__ +#define __GST_RTSPSRC_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +#include <gst/rtsp/rtsp.h> +#include <gio/gio.h> + +#include "gstrtspext.h" + +#define GST_TYPE_RTSPSRC \ + (gst_rtspsrc_get_type()) +#define GST_RTSPSRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTSPSRC,GstRTSPSrc)) +#define GST_RTSPSRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTSPSRC,GstRTSPSrcClass)) +#define GST_IS_RTSPSRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTSPSRC)) +#define GST_IS_RTSPSRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTSPSRC)) +#define GST_RTSPSRC_CAST(obj) \ + ((GstRTSPSrc *)(obj)) + +typedef struct _GstRTSPSrc GstRTSPSrc; +typedef struct _GstRTSPSrcClass GstRTSPSrcClass; + +#define GST_RTSP_STATE_GET_LOCK(rtsp) (&GST_RTSPSRC_CAST(rtsp)->state_rec_lock) +#define GST_RTSP_STATE_LOCK(rtsp) (g_rec_mutex_lock (GST_RTSP_STATE_GET_LOCK(rtsp))) +#define GST_RTSP_STATE_UNLOCK(rtsp) (g_rec_mutex_unlock (GST_RTSP_STATE_GET_LOCK(rtsp))) + +#define GST_RTSP_STREAM_GET_LOCK(rtsp) (&GST_RTSPSRC_CAST(rtsp)->stream_rec_lock) +#define GST_RTSP_STREAM_LOCK(rtsp) (g_rec_mutex_lock (GST_RTSP_STREAM_GET_LOCK(rtsp))) +#define GST_RTSP_STREAM_UNLOCK(rtsp) (g_rec_mutex_unlock (GST_RTSP_STREAM_GET_LOCK(rtsp))) + +typedef struct _GstRTSPConnInfo GstRTSPConnInfo; + +struct _GstRTSPConnInfo { + gchar *location; + GstRTSPUrl *url; + gchar *url_str; + GstRTSPConnection *connection; + gboolean connected; + gboolean flushing; +}; + +typedef struct _GstRTSPStream GstRTSPStream; + +struct _GstRTSPStream { + gint id; + + GstRTSPSrc *parent; /* parent, no extra ref to parent is taken */ + + /* pad we expose or NULL when it does not have an actual pad */ + GstPad *srcpad; + GstFlowReturn last_ret; + gboolean added; + gboolean setup; + gboolean skipped; + gboolean eos; + gboolean discont; + + /* for interleaved mode */ + guint8 channel[2]; + GstPad *channelpad[2]; + + /* our udp sources */ + GstElement *udpsrc[2]; + GstPad *blockedpad; + gulong blockid; + gboolean is_ipv6; + + /* our udp sinks back to the server */ + GstElement *udpsink[2]; + GstPad *rtcppad; + + /* fakesrc for sending dummy data */ + GstElement *fakesrc; + + /* state */ + guint port; + gboolean container; + gboolean is_real; + guint8 default_pt; + GstRTSPProfile profile; + GArray *ptmap; + /* original control url */ + gchar *control_url; + guint32 ssrc; + guint32 seqbase; + guint64 timebase; + GstElement *srtpdec; + GstCaps *srtcpparams; + GstElement *srtpenc; + guint32 send_ssrc; + + /* per stream connection */ + GstRTSPConnInfo conninfo; + + /* session */ + GObject *session; + + /* bandwidth */ + guint as_bandwidth; + guint rs_bandwidth; + guint rr_bandwidth; + + /* destination */ + gchar *destination; + gboolean is_multicast; + guint ttl; + + GstStructure *rtx_pt_map; +}; + +/** + * GstRTSPNatMethod: + * @GST_RTSP_NAT_NONE: none + * @GST_RTSP_NAT_DUMMY: send dummy packets + * + * Different methods for trying to traverse firewalls. + */ +typedef enum +{ + GST_RTSP_NAT_NONE, + GST_RTSP_NAT_DUMMY +} GstRTSPNatMethod; + +struct _GstRTSPSrc { + GstBin parent; + + /* task and mutex for interleaved mode */ + gboolean interleaved; + GstTask *task; + GRecMutex stream_rec_lock; + GstSegment segment; + gboolean running; + gboolean need_range; + gboolean skip; + gint free_channel; + gboolean need_segment; + GstClockTime base_time; + + /* UDP mode loop */ + gint pending_cmd; + gint busy_cmd; + gboolean ignore_timeout; + gboolean open_error; + + /* mutex for protecting state changes */ + GRecMutex state_rec_lock; + + GstSDPMessage *sdp; + gboolean from_sdp; + GList *streams; + GstStructure *props; + gboolean need_activate; + + /* properties */ + GstRTSPLowerTrans protocols; + gboolean debug; + guint retry; + guint64 udp_timeout; + GTimeVal tcp_timeout; + GTimeVal *ptcp_timeout; + guint latency; + gboolean drop_on_latency; + guint64 connection_speed; + GstRTSPNatMethod nat_method; + gboolean do_rtcp; + gboolean do_rtsp_keep_alive; + gchar *proxy_host; + guint proxy_port; + gchar *proxy_user; /* from url or property */ + gchar *proxy_passwd; /* from url or property */ + gchar *prop_proxy_id; /* set via property */ + gchar *prop_proxy_pw; /* set via property */ + guint rtp_blocksize; + gchar *user_id; + gchar *user_pw; + gint buffer_mode; + GstRTSPRange client_port_range; + gint udp_buffer_size; + gboolean short_header; + guint probation; + gboolean udp_reconnect; + gchar *multi_iface; + gboolean ntp_sync; + gboolean use_pipeline_clock; + GstStructure *sdes; + GTlsCertificateFlags tls_validation_flags; + GTlsDatabase *tls_database; + gboolean do_retransmission; + + /* state */ + GstRTSPState state; + gchar *content_base; + GstRTSPLowerTrans cur_protocols; + gboolean tried_url_auth; + gchar *addr; + gboolean need_redirect; + GstRTSPTimeRange *range; + gchar *control; + guint next_port_num; + GstClock *provided_clock; + + /* supported methods */ + gint methods; + + gboolean seekable; + GstClockTime last_pos; + + /* session management */ + GstElement *manager; + gulong manager_sig_id; + gulong manager_ptmap_id; + gboolean use_buffering; + + GstRTSPConnInfo conninfo; + + /* a list of RTSP extensions as GstElement */ + GstRTSPExtensionList *extensions; + +#ifdef GST_EXT_RTSP_MODIFICATION + GCond open_end; + GMutex pause_lock; + guint64 start_position; +#endif +}; + +struct _GstRTSPSrcClass { + GstBinClass parent_class; +}; + +GType gst_rtspsrc_get_type(void); + +G_END_DECLS + +#endif /* __GST_RTSPSRC_H__ */ |