diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am index 3702520032..38d9fb298d 100644 --- a/docs/plugins/Makefile.am +++ b/docs/plugins/Makefile.am @@ -13,33 +13,13 @@ FORMATS=html html: html-build.stamp include $(top_srcdir)/common/upload-doc.mak -# generated basefiles -#basefiles = \ -## $(DOC_MODULE).types \ -# $(DOC_MODULE)-sections.txt \ -# $(DOC_MODULE)-docs.sgml - -# ugly hack to make -unused.sgml work -#unused-build.stamp: -# BUILDDIR=`pwd` && \ -# cd $(srcdir)/tmpl && \ -# ln -sf gstreamer-libs-unused.sgml \ -# $$BUILDDIR/tmpl/gstreamer-libs-@GST_MAJORMINOR@-unused.sgml -# touch unused-build.stamp - -# these rules are added to create parallel docs using GST_MAJORMINOR -#$(basefiles): gstreamer-libs-@GST_MAJORMINOR@%: gstreamer-libs% -# cp $< $@ - -#CLEANFILES = $(basefiles) - # The top-level SGML file. Change it if you want. DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml -# The directory containing the source code. Relative to $(top_srcdir). +# The directory containing the source code. # gtk-doc will search all .c & .h files beneath here for inline comments # documenting functions and macros. -DOC_SOURCE_DIR = $(top_srcdir) +DOC_SOURCE_DIR = $(top_srcdir)/gst $(top_srcdir)/ext $(top_srcdir)/sys # Extra options to supply to gtkdoc-scan. SCAN_OPTIONS= @@ -53,14 +33,11 @@ FIXXREF_OPTIONS=--extra-dir=$(GLIB_PREFIX)/share/gtk-doc/html \ --extra-dir=$(GSTPB_PREFIX)/share/gtk-doc/html # Used for dependencies. -HFILE_GLOB=$(DOC_SOURCE_DIR)/*/*/*.h -CFILE_GLOB=$(DOC_SOURCE_DIR)/*/*/*.c $(DOC_SOURCE_DIR)/*/*/*.cc $(DOC_SOURCE_DIR)/*/*/*.m - -# this is a wingo addition -# thomasvs: another nice wingo addition would be an explanation on why -# this is useful ;) - -SCANOBJ_DEPS = +HFILE_GLOB= \ + $(top_srcdir)/gst/*/*.h $(top_srcdir)/ext/*/*.h $(top_srcdir)/sys/*/*.h +CFILE_GLOB= \ + $(top_srcdir)/gst/*/*.c $(top_srcdir)/ext/*/*.c $(top_srcdir)/sys/*/*.c \ + $(top_srcdir)/ext/*/*.cc $(top_srcdir)/sys/*/*.m # Header files to ignore when scanning. IGNORE_HFILES = @@ -253,7 +230,7 @@ extra_files = # CFLAGS and LDFLAGS for compiling scan program. Only needed if your app/lib # contains GtkObjects/GObjects and you want to document signals and properties. GTKDOC_CFLAGS = $(GST_BASE_CFLAGS) -I$(top_builddir) -GTKDOC_LIBS = $(SCANOBJ_DEPS) $(GST_BASE_LIBS) +GTKDOC_LIBS = $(GST_BASE_LIBS) GTKDOC_CC=$(LIBTOOL) --tag=CC --mode=compile $(CC) GTKDOC_LD=$(LIBTOOL) --tag=CC --mode=link $(CC) diff --git a/ext/pulse/Makefile.am b/ext/pulse/Makefile.am index 9c0d6b7171..2438f5efc3 100644 --- a/ext/pulse/Makefile.am +++ b/ext/pulse/Makefile.am @@ -7,12 +7,14 @@ libgstpulse_la_SOURCES = \ pulsemixertrack.c \ pulseprobe.c \ pulsesink.c \ + pulseaudiosink.c \ pulsesrc.c \ pulseutil.c libgstpulse_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(PULSE_CFLAGS) libgstpulse_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_MAJORMINOR) \ - -lgstinterfaces-$(GST_MAJORMINOR) $(GST_BASE_LIBS) $(GST_LIBS) $(PULSE_LIBS) + -lgstinterfaces-$(GST_MAJORMINOR) -lgstpbutils-$(GST_MAJORMINOR) \ + $(GST_BASE_LIBS) $(GST_LIBS) $(PULSE_LIBS) libgstpulse_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstpulse_la_LIBTOOLFLAGS = --tag=disable-static diff --git a/ext/pulse/plugin.c b/ext/pulse/plugin.c index 3abc26fc96..6b2e6b4bf7 100644 --- a/ext/pulse/plugin.c +++ b/ext/pulse/plugin.c @@ -49,6 +49,12 @@ plugin_init (GstPlugin * plugin) GST_TYPE_PULSESRC)) return FALSE; +#ifdef HAVE_PULSE_1_0 + if (!gst_element_register (plugin, "pulseaudiosink", GST_RANK_PRIMARY + 11, + GST_TYPE_PULSE_AUDIO_SINK)) + return FALSE; +#endif + if (!gst_element_register (plugin, "pulsemixer", GST_RANK_NONE, GST_TYPE_PULSEMIXER)) return FALSE; diff --git a/ext/pulse/pulseaudiosink.c b/ext/pulse/pulseaudiosink.c new file mode 100644 index 0000000000..b7e1a07254 --- /dev/null +++ b/ext/pulse/pulseaudiosink.c @@ -0,0 +1,927 @@ +/*-*- Mode: C; c-basic-offset: 2 -*-*/ + +/* GStreamer pulseaudio plugin + * + * Copyright (c) 2011 Intel Corporation + * 2011 Collabora + * 2011 Arun Raghavan + * 2011 Sebastian Dröge + * + * gst-pulse is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * gst-pulse is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with gst-pulse; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + */ + +/** + * SECTION:element-pulseaudiosink + * @see_also: pulsesink, pulsesrc, pulsemixer + * + * This element outputs audio to a + * PulseAudio sound server via + * the @pulsesink element. It transparently takes care of passing compressed + * format as-is if the sink supports it, decoding if necessary, and changes + * to supported formats at runtime. + * + * + * Example pipelines + * |[ + * gst-launch -v filesrc location=sine.ogg ! oggdemux ! vorbisdec ! pulseaudiosink + * ]| Decode and play an Ogg/Vorbis file. + * |[ + * gst-launch -v filesrc location=test.mp3 ! mp3parse ! pulseaudiosink stream-properties="props,media.title=test" + * ]| Play an MP3 file on a sink that supports decoding directly, plug in a + * decoder if/when required. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_PULSE_1_0 + +#include +#include + +#include +#include "pulsesink.h" + +GST_DEBUG_CATEGORY (pulseaudiosink_debug); +#define GST_CAT_DEFAULT (pulseaudiosink_debug) + +#define GST_PULSE_AUDIO_SINK_LOCK(obj) G_STMT_START { \ + GST_LOG_OBJECT (obj, \ + "locking from thread %p", \ + g_thread_self ()); \ + g_mutex_lock (GST_PULSE_AUDIO_SINK_CAST(obj)->lock); \ + GST_LOG_OBJECT (obj, \ + "locked from thread %p", \ + g_thread_self ()); \ +} G_STMT_END + +#define GST_PULSE_AUDIO_SINK_UNLOCK(obj) G_STMT_START { \ + GST_LOG_OBJECT (obj, \ + "unlocking from thread %p", \ + g_thread_self ()); \ + g_mutex_unlock (GST_PULSE_AUDIO_SINK_CAST(obj)->lock); \ +} G_STMT_END + +typedef struct +{ + GstBin parent; + GMutex *lock; + + GstPad *sinkpad; + GstPad *sink_proxypad; + GstPadEventFunction sinkpad_old_eventfunc; + GstPadEventFunction proxypad_old_eventfunc; + + GstPulseSink *psink; + GstElement *dbin2; + + GstSegment segment; + + guint event_probe_id; + gulong pad_added_id; + + gboolean format_lost; +} GstPulseAudioSink; + +typedef struct +{ + GstBinClass parent_class; + guint n_prop_own; + guint n_prop_total; +} GstPulseAudioSinkClass; + +static void gst_pulse_audio_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_pulse_audio_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_pulse_audio_sink_dispose (GObject * object); +static gboolean gst_pulse_audio_sink_src_event (GstPad * pad, GstEvent * event); +static gboolean gst_pulse_audio_sink_sink_event (GstPad * pad, + GstEvent * event); +static gboolean gst_pulse_audio_sink_sink_acceptcaps (GstPad * pad, + GstCaps * caps); +static gboolean gst_pulse_audio_sink_sink_setcaps (GstPad * pad, + GstCaps * caps); +static GstStateChangeReturn +gst_pulse_audio_sink_change_state (GstElement * element, + GstStateChange transition); + +static void +gst_pulse_audio_sink_do_init (GType type) +{ + GST_DEBUG_CATEGORY_INIT (pulseaudiosink_debug, "pulseaudiosink", 0, + "Bin that wraps pulsesink for handling compressed formats"); +} + +GST_BOILERPLATE_FULL (GstPulseAudioSink, gst_pulse_audio_sink, GstBin, + GST_TYPE_BIN, gst_pulse_audio_sink_do_init); + +static GstStaticPadTemplate sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS (PULSE_SINK_TEMPLATE_CAPS)); + +static void +gst_pulse_audio_sink_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + + gst_element_class_set_details_simple (element_class, + "Bin wrapping pulsesink", "Sink/Audio/Bin", + "Correctly handles sink changes when streaming compressed formats to " + "pulsesink", "Arun Raghavan "); +} + +static GParamSpec * +param_spec_copy (GParamSpec * spec) +{ + const char *name, *nick, *blurb; + GParamFlags flags; + + name = g_param_spec_get_name (spec); + nick = g_param_spec_get_nick (spec); + blurb = g_param_spec_get_blurb (spec); + flags = spec->flags; + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_BOOLEAN) { + return g_param_spec_boolean (name, nick, blurb, + G_PARAM_SPEC_BOOLEAN (spec)->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_BOXED) { + return g_param_spec_boxed (name, nick, blurb, spec->value_type, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_CHAR) { + GParamSpecChar *cspec = G_PARAM_SPEC_CHAR (spec); + return g_param_spec_char (name, nick, blurb, cspec->minimum, + cspec->maximum, cspec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_DOUBLE) { + GParamSpecDouble *dspec = G_PARAM_SPEC_DOUBLE (spec); + return g_param_spec_double (name, nick, blurb, dspec->minimum, + dspec->maximum, dspec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_ENUM) { + return g_param_spec_enum (name, nick, blurb, spec->value_type, + G_PARAM_SPEC_ENUM (spec)->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_FLAGS) { + return g_param_spec_flags (name, nick, blurb, spec->value_type, + G_PARAM_SPEC_ENUM (spec)->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_FLOAT) { + GParamSpecFloat *fspec = G_PARAM_SPEC_FLOAT (spec); + return g_param_spec_double (name, nick, blurb, fspec->minimum, + fspec->maximum, fspec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_GTYPE) { + return g_param_spec_gtype (name, nick, blurb, + G_PARAM_SPEC_GTYPE (spec)->is_a_type, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_INT) { + GParamSpecInt *ispec = G_PARAM_SPEC_INT (spec); + return g_param_spec_int (name, nick, blurb, ispec->minimum, + ispec->maximum, ispec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_INT64) { + GParamSpecInt64 *ispec = G_PARAM_SPEC_INT64 (spec); + return g_param_spec_int64 (name, nick, blurb, ispec->minimum, + ispec->maximum, ispec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_LONG) { + GParamSpecLong *lspec = G_PARAM_SPEC_LONG (spec); + return g_param_spec_long (name, nick, blurb, lspec->minimum, + lspec->maximum, lspec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_OBJECT) { + return g_param_spec_object (name, nick, blurb, spec->value_type, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_PARAM) { + return g_param_spec_param (name, nick, blurb, spec->value_type, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_POINTER) { + return g_param_spec_pointer (name, nick, blurb, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_STRING) { + return g_param_spec_string (name, nick, blurb, + G_PARAM_SPEC_STRING (spec)->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_UCHAR) { + GParamSpecUChar *cspec = G_PARAM_SPEC_UCHAR (spec); + return g_param_spec_uchar (name, nick, blurb, cspec->minimum, + cspec->maximum, cspec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_UINT) { + GParamSpecUInt *ispec = G_PARAM_SPEC_UINT (spec); + return g_param_spec_uint (name, nick, blurb, ispec->minimum, + ispec->maximum, ispec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_UINT64) { + GParamSpecUInt64 *ispec = G_PARAM_SPEC_UINT64 (spec); + return g_param_spec_uint64 (name, nick, blurb, ispec->minimum, + ispec->maximum, ispec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_ULONG) { + GParamSpecULong *lspec = G_PARAM_SPEC_ULONG (spec); + return g_param_spec_ulong (name, nick, blurb, lspec->minimum, + lspec->maximum, lspec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_UNICHAR) { + return g_param_spec_unichar (name, nick, blurb, + G_PARAM_SPEC_UNICHAR (spec)->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == G_TYPE_PARAM_VARIANT) { + GParamSpecVariant *vspec = G_PARAM_SPEC_VARIANT (spec); + return g_param_spec_variant (name, nick, blurb, vspec->type, + vspec->default_value, flags); + } + + if (G_PARAM_SPEC_TYPE (spec) == GST_TYPE_PARAM_MINI_OBJECT) { + return gst_param_spec_mini_object (name, nick, blurb, spec->value_type, + flags); + } + + g_warning ("Unknown param type %ld for '%s'", + (long) G_PARAM_SPEC_TYPE (spec), name); + g_assert_not_reached (); +} + +static void +gst_pulse_audio_sink_class_init (GstPulseAudioSinkClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *element_class = (GstElementClass *) klass; + GstPulseSinkClass *psink_class = + GST_PULSESINK_CLASS (g_type_class_ref (GST_TYPE_PULSESINK)); + GParamSpec **specs; + guint n, i, j; + + gobject_class->get_property = gst_pulse_audio_sink_get_property; + gobject_class->set_property = gst_pulse_audio_sink_set_property; + gobject_class->dispose = gst_pulse_audio_sink_dispose; + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_pulse_audio_sink_change_state); + + /* Find out how many properties we already have */ + specs = g_object_class_list_properties (gobject_class, &klass->n_prop_own); + g_free (specs); + + /* Proxy pulsesink's properties */ + specs = g_object_class_list_properties (G_OBJECT_CLASS (psink_class), &n); + for (i = 0, j = klass->n_prop_own; i < n; i++) { + if (g_object_class_find_property (gobject_class, + g_param_spec_get_name (specs[i]))) { + /* We already inherited this property from a parent, skip */ + j--; + } else { + g_object_class_install_property (gobject_class, i + j + 1, + param_spec_copy (specs[i])); + } + } + + klass->n_prop_total = i + j; + + g_free (specs); + g_type_class_unref (psink_class); +} + +static GstPad * +get_proxypad (GstPad * sinkpad) +{ + GstIterator *iter = NULL; + GstPad *proxypad = NULL; + + iter = gst_pad_iterate_internal_links (sinkpad); + if (iter) { + if (gst_iterator_next (iter, (gpointer) & proxypad) != GST_ITERATOR_OK) + proxypad = NULL; + gst_iterator_free (iter); + } + + return proxypad; +} + +static void +post_missing_element_message (GstPulseAudioSink * pbin, const gchar * name) +{ + GstMessage *msg; + + msg = gst_missing_element_message_new (GST_ELEMENT_CAST (pbin), name); + gst_element_post_message (GST_ELEMENT_CAST (pbin), msg); +} + +static void +notify_cb (GObject * selector, GParamSpec * pspec, GstPulseAudioSink * pbin) +{ + g_object_notify (G_OBJECT (pbin), g_param_spec_get_name (pspec)); +} + +static void +gst_pulse_audio_sink_init (GstPulseAudioSink * pbin, + GstPulseAudioSinkClass * klass) +{ + GstPad *pad = NULL; + GParamSpec **specs; + GString *prop; + guint i; + + pbin->lock = g_mutex_new (); + + gst_segment_init (&pbin->segment, GST_FORMAT_UNDEFINED); + + pbin->psink = GST_PULSESINK (gst_element_factory_make ("pulsesink", + "pulseaudiosink-sink")); + g_assert (pbin->psink != NULL); + + if (!gst_bin_add (GST_BIN (pbin), GST_ELEMENT (pbin->psink))) { + GST_ERROR_OBJECT (pbin, "Failed to add pulsesink to bin"); + goto error; + } + + pad = gst_element_get_static_pad (GST_ELEMENT (pbin->psink), "sink"); + pbin->sinkpad = gst_ghost_pad_new_from_template ("sink", pad, + gst_static_pad_template_get (&sink_template)); + + pbin->sinkpad_old_eventfunc = GST_PAD_EVENTFUNC (pbin->sinkpad); + gst_pad_set_event_function (pbin->sinkpad, + GST_DEBUG_FUNCPTR (gst_pulse_audio_sink_sink_event)); + gst_pad_set_setcaps_function (pbin->sinkpad, + GST_DEBUG_FUNCPTR (gst_pulse_audio_sink_sink_setcaps)); + gst_pad_set_acceptcaps_function (pbin->sinkpad, + GST_DEBUG_FUNCPTR (gst_pulse_audio_sink_sink_acceptcaps)); + + gst_element_add_pad (GST_ELEMENT (pbin), pbin->sinkpad); + + if (!(pbin->sink_proxypad = get_proxypad (pbin->sinkpad))) + GST_ERROR_OBJECT (pbin, "Failed to get proxypad of srcpad"); + else { + pbin->proxypad_old_eventfunc = GST_PAD_EVENTFUNC (pbin->sink_proxypad); + gst_pad_set_event_function (pbin->sink_proxypad, + GST_DEBUG_FUNCPTR (gst_pulse_audio_sink_src_event)); + } + + /* Now proxy all the notify::* signals */ + specs = g_object_class_list_properties (G_OBJECT_CLASS (klass), &i); + prop = g_string_sized_new (30); + + for (i--; i >= klass->n_prop_own; i--) { + g_string_printf (prop, "notify::%s", g_param_spec_get_name (specs[i])); + g_signal_connect (pbin->psink, prop->str, G_CALLBACK (notify_cb), pbin); + } + + g_string_free (prop, TRUE); + g_free (specs); + + pbin->format_lost = FALSE; + +out: + if (pad) + gst_object_unref (pad); + + return; + +error: + if (pbin->psink) + gst_object_unref (pbin->psink); + goto out; +} + +static void +gst_pulse_audio_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (object); + GstPulseAudioSinkClass *klass = + GST_PULSE_AUDIO_SINK_CLASS (G_OBJECT_GET_CLASS (object)); + + g_return_if_fail (prop_id <= klass->n_prop_total); + + g_object_set_property (G_OBJECT (pbin->psink), g_param_spec_get_name (pspec), + value); +} + +static void +gst_pulse_audio_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (object); + GstPulseAudioSinkClass *klass = + GST_PULSE_AUDIO_SINK_CLASS (G_OBJECT_GET_CLASS (object)); + + g_return_if_fail (prop_id <= klass->n_prop_total); + + g_object_get_property (G_OBJECT (pbin->psink), g_param_spec_get_name (pspec), + value); +} + +static void +gst_pulse_audio_sink_free_dbin2 (GstPulseAudioSink * pbin) +{ + g_signal_handler_disconnect (pbin->dbin2, pbin->pad_added_id); + gst_element_set_state (pbin->dbin2, GST_STATE_NULL); + + gst_bin_remove (GST_BIN (pbin), pbin->dbin2); + + pbin->dbin2 = NULL; +} + +static void +gst_pulse_audio_sink_dispose (GObject * object) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (object); + + if (pbin->lock) { + g_mutex_free (pbin->lock); + pbin->lock = NULL; + } + + if (pbin->sink_proxypad) { + gst_object_unref (pbin->sink_proxypad); + pbin->sink_proxypad = NULL; + } + + if (pbin->dbin2) { + g_signal_handler_disconnect (pbin->dbin2, pbin->pad_added_id); + pbin->dbin2 = NULL; + } + + pbin->sinkpad = NULL; + pbin->psink = NULL; +} + +static gboolean +gst_pulse_audio_sink_update_sinkpad (GstPulseAudioSink * pbin, GstPad * sinkpad) +{ + gboolean ret; + + ret = gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (pbin->sinkpad), sinkpad); + + if (!ret) + GST_WARNING_OBJECT (pbin, "Could not update ghostpad target"); + + return ret; +} + +static void +distribute_running_time (GstElement * element, const GstSegment * segment) +{ + GstEvent *event; + GstPad *pad; + + pad = gst_element_get_static_pad (element, "sink"); + + /* FIXME: Some decoders collect newsegments and send them out at once, making + * them lose accumulator events (and thus making dbin2_event_probe() hard to + * do right if we're sending these as well. We can get away with not sending + * these at the moment, but this should be fixed! */ +#if 0 + if (segment->accum) { + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, 0, segment->accum, 0); + gst_pad_send_event (pad, event); + } +#endif + + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, + segment->start, segment->stop, segment->time); + gst_pad_send_event (pad, event); + + gst_object_unref (pad); +} + +static gboolean +dbin2_event_probe (GstPad * pad, GstMiniObject * obj, gpointer data) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (data); + GstEvent *event = GST_EVENT (obj); + + if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + GST_DEBUG_OBJECT (pbin, "Got newsegment - dropping"); + gst_pad_remove_event_probe (pad, pbin->event_probe_id); + gst_object_unref (pbin); + return FALSE; + } + + return TRUE; +} + +static void +pad_added_cb (GstElement * dbin2, GstPad * pad, gpointer * data) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (data); + GstPad *sinkpad = NULL; + + pbin = GST_PULSE_AUDIO_SINK (data); + sinkpad = gst_element_get_static_pad (GST_ELEMENT (pbin->psink), "sink"); + + GST_PULSE_AUDIO_SINK_LOCK (pbin); + if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK) + GST_ERROR_OBJECT (pbin, "Failed to link decodebin2 to pulsesink"); + else + GST_DEBUG_OBJECT (pbin, "Linked new pad to pulsesink"); + GST_PULSE_AUDIO_SINK_UNLOCK (pbin); + + gst_object_unref (sinkpad); +} + +/* Called with pbin lock held */ +static void +gst_pulse_audio_sink_add_dbin2 (GstPulseAudioSink * pbin) +{ + GstPad *sinkpad = NULL; + + g_assert (pbin->dbin2 == NULL); + + pbin->dbin2 = gst_element_factory_make ("decodebin2", "pulseaudiosink-dbin2"); + + if (!pbin->dbin2) { + post_missing_element_message (pbin, "decodebin2"); + GST_ELEMENT_WARNING (pbin, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "decodebin2"), ("audio playback might fail")); + goto out; + } + + if (!gst_bin_add (GST_BIN (pbin), pbin->dbin2)) { + GST_ERROR_OBJECT (pbin, "Failed to add decodebin2 to bin"); + goto out; + } + + pbin->pad_added_id = g_signal_connect (pbin->dbin2, "pad-added", + G_CALLBACK (pad_added_cb), pbin); + + if (!gst_element_sync_state_with_parent (pbin->dbin2)) { + GST_ERROR_OBJECT (pbin, "Failed to set decodebin2 to parent state"); + goto out; + } + + /* Trap the newsegment events that we feed the decodebin and discard them */ + sinkpad = gst_element_get_static_pad (GST_ELEMENT (pbin->psink), "sink"); + pbin->event_probe_id = gst_pad_add_event_probe (sinkpad, + G_CALLBACK (dbin2_event_probe), gst_object_ref (pbin)); + gst_object_unref (sinkpad); + sinkpad = NULL; + + GST_DEBUG_OBJECT (pbin, "Distributing running time to decodebin"); + distribute_running_time (pbin->dbin2, &pbin->segment); + + sinkpad = gst_element_get_static_pad (pbin->dbin2, "sink"); + + gst_pulse_audio_sink_update_sinkpad (pbin, sinkpad); + +out: + if (sinkpad) + gst_object_unref (sinkpad); +} + +static void +update_eac3_alignment (GstPulseAudioSink * pbin) +{ + GstCaps *caps = gst_pad_peer_get_caps_reffed (pbin->sinkpad); + GstStructure *st; + + if (!caps) + return; + + st = gst_caps_get_structure (caps, 0); + + if (g_str_equal (gst_structure_get_name (st), "audio/x-eac3")) { + GstStructure *event_st = gst_structure_new ("ac3parse-set-alignment", + "alignment", G_TYPE_STRING, pbin->dbin2 ? "frame" : "iec61937", NULL); + + if (!gst_pad_push_event (pbin->sinkpad, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, event_st))) + GST_WARNING_OBJECT (pbin->sinkpad, "Could not update alignment"); + } + + gst_caps_unref (caps); +} + +static void +proxypad_blocked_cb (GstPad * pad, gboolean blocked, gpointer data) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (data); + GstCaps *caps; + GstPad *sinkpad = NULL; + + if (!blocked) { + /* Unblocked, don't need to do anything */ + GST_DEBUG_OBJECT (pbin, "unblocked"); + return; + } + + GST_DEBUG_OBJECT (pbin, "blocked"); + + GST_PULSE_AUDIO_SINK_LOCK (pbin); + + if (!pbin->format_lost) { + sinkpad = gst_element_get_static_pad (GST_ELEMENT (pbin->psink), "sink"); + caps = gst_pad_get_caps_reffed (pad); + + if (gst_pad_accept_caps (sinkpad, caps)) { + if (pbin->dbin2) { + GST_DEBUG_OBJECT (pbin, "Removing decodebin"); + gst_pulse_audio_sink_free_dbin2 (pbin); + gst_pulse_audio_sink_update_sinkpad (pbin, sinkpad); + } else + GST_DEBUG_OBJECT (pbin, "Doing nothing"); + + gst_caps_unref (caps); + gst_object_unref (sinkpad); + goto done; + } + /* pulsesink doesn't accept the incoming caps, so add a decodebin + * (potentially after removing the existing once, since decodebin2 can't + * renegotiate). */ + } else { + /* Format lost, proceed to try plugging a decodebin */ + pbin->format_lost = FALSE; + } + + if (pbin->dbin2 != NULL) { + /* decodebin2 doesn't support reconfiguration, so throw this one away and + * create a new one. */ + gst_pulse_audio_sink_free_dbin2 (pbin); + } + + GST_DEBUG_OBJECT (pbin, "Adding decodebin"); + gst_pulse_audio_sink_add_dbin2 (pbin); + +done: + update_eac3_alignment (pbin); + + gst_pad_set_blocked_async_full (pad, FALSE, proxypad_blocked_cb, + gst_object_ref (pbin), (GDestroyNotify) gst_object_unref); + + GST_PULSE_AUDIO_SINK_UNLOCK (pbin); +} + +static gboolean +gst_pulse_audio_sink_src_event (GstPad * pad, GstEvent * event) +{ + GstPulseAudioSink *pbin = NULL; + GstPad *ghostpad = NULL; + gboolean ret = FALSE; + + ghostpad = GST_PAD_CAST (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!ghostpad)) { + GST_WARNING_OBJECT (pad, "Could not get ghostpad"); + goto out; + } + + pbin = GST_PULSE_AUDIO_SINK (gst_pad_get_parent (ghostpad)); + if (G_UNLIKELY (!pbin)) { + GST_WARNING_OBJECT (pad, "Could not get pulseaudiosink"); + goto out; + } + + if (G_UNLIKELY (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_UPSTREAM) && + (gst_event_has_name (event, "pulse-format-lost") || + gst_event_has_name (event, "pulse-sink-changed"))) { + g_return_val_if_fail (pad->mode != GST_ACTIVATE_PULL, FALSE); + + GST_PULSE_AUDIO_SINK_LOCK (pbin); + if (gst_event_has_name (event, "pulse-format-lost")) + pbin->format_lost = TRUE; + + if (!gst_pad_is_blocked (pad)) + gst_pad_set_blocked_async_full (pad, TRUE, proxypad_blocked_cb, + gst_object_ref (pbin), (GDestroyNotify) gst_object_unref); + GST_PULSE_AUDIO_SINK_UNLOCK (pbin); + + ret = TRUE; + } else if (pbin->proxypad_old_eventfunc) { + ret = pbin->proxypad_old_eventfunc (pad, event); + event = NULL; + } + +out: + if (ghostpad) + gst_object_unref (ghostpad); + if (pbin) + gst_object_unref (pbin); + if (event) + gst_event_unref (event); + + return ret; +} + +static gboolean +gst_pulse_audio_sink_sink_event (GstPad * pad, GstEvent * event) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (gst_pad_get_parent (pad)); + gboolean ret; + + ret = pbin->sinkpad_old_eventfunc (pad, gst_event_ref (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + gboolean update; + + GST_PULSE_AUDIO_SINK_LOCK (pbin); + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + GST_DEBUG_OBJECT (pbin, + "newsegment: update %d, rate %g, arate %g, start %" GST_TIME_FORMAT + ", stop %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, + update, rate, arate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop), + GST_TIME_ARGS (time)); + + if (format == GST_FORMAT_TIME) { + /* Store the values for feeding to sub-elements */ + gst_segment_set_newsegment_full (&pbin->segment, update, + rate, arate, format, start, stop, time); + } else { + GST_WARNING_OBJECT (pbin, "Got a non-TIME format segment"); + gst_segment_init (&pbin->segment, GST_FORMAT_TIME); + } + GST_PULSE_AUDIO_SINK_UNLOCK (pbin); + + break; + } + + case GST_EVENT_FLUSH_STOP: + GST_PULSE_AUDIO_SINK_LOCK (pbin); + gst_segment_init (&pbin->segment, GST_FORMAT_UNDEFINED); + GST_PULSE_AUDIO_SINK_UNLOCK (pbin); + break; + + default: + break; + } + + gst_object_unref (pbin); + gst_event_unref (event); + + return ret; +} + +/* The bin's acceptcaps should be exactly equivalent to a pulsesink that is + * connected to a sink that supports all the formats in template caps. This + * means that upstream will have to have everything possibly upto a parser + * plugged and we plugin a decoder whenever required. */ +static gboolean +gst_pulse_audio_sink_sink_acceptcaps (GstPad * pad, GstCaps * caps) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (gst_pad_get_parent (pad)); + GstRingBufferSpec spec = { 0 }; + const GstStructure *st; + GstCaps *pad_caps = NULL; + gboolean ret = FALSE; + + pad_caps = gst_pad_get_caps_reffed (pad); + if (!pad_caps || !gst_caps_can_intersect (pad_caps, caps)) + goto out; + + /* If we've not got fixed caps, creating a stream might fail, so let's just + * return from here with default acceptcaps behaviour */ + if (!gst_caps_is_fixed (caps)) + goto out; + + spec.latency_time = GST_BASE_AUDIO_SINK (pbin->psink)->latency_time; + if (!gst_ring_buffer_parse_caps (&spec, caps)) + goto out; + + /* Make sure non-raw input is framed (one frame per buffer) and can be + * payloaded */ + st = gst_caps_get_structure (caps, 0); + + if (!g_str_has_prefix (gst_structure_get_name (st), "audio/x-raw")) { + gboolean framed = FALSE, parsed = FALSE; + + gst_structure_get_boolean (st, "framed", &framed); + gst_structure_get_boolean (st, "parsed", &parsed); + if ((!framed && !parsed) || gst_audio_iec61937_frame_size (&spec) <= 0) + goto out; + } + + ret = TRUE; + +out: + if (pad_caps) + gst_caps_unref (pad_caps); + + gst_object_unref (pbin); + + return ret; +} + +static gboolean +gst_pulse_audio_sink_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (gst_pad_get_parent (pad)); + gboolean ret = TRUE; + + GST_PULSE_AUDIO_SINK_LOCK (pbin); + + if (!gst_pad_is_blocked (pbin->sinkpad)) + gst_pad_set_blocked_async_full (pbin->sink_proxypad, TRUE, + proxypad_blocked_cb, gst_object_ref (pbin), + (GDestroyNotify) gst_object_unref); + + GST_PULSE_AUDIO_SINK_UNLOCK (pbin); + + gst_object_unref (pbin); + + return ret; +} + +static GstStateChangeReturn +gst_pulse_audio_sink_change_state (GstElement * element, + GstStateChange transition) +{ + GstPulseAudioSink *pbin = GST_PULSE_AUDIO_SINK (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + /* Nothing to do for upward transitions */ + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_PULSE_AUDIO_SINK_LOCK (pbin); + if (gst_pad_is_blocked (pbin->sinkpad)) { + gst_pad_set_blocked_async_full (pbin->sink_proxypad, FALSE, + proxypad_blocked_cb, gst_object_ref (pbin), + (GDestroyNotify) gst_object_unref); + } + GST_PULSE_AUDIO_SINK_UNLOCK (pbin); + break; + + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret != GST_STATE_CHANGE_SUCCESS) { + GST_DEBUG_OBJECT (pbin, "Base class returned %d on state change", ret); + goto out; + } + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_PULSE_AUDIO_SINK_LOCK (pbin); + gst_segment_init (&pbin->segment, GST_FORMAT_UNDEFINED); + + if (pbin->dbin2) { + GstPad *pad = gst_element_get_static_pad (GST_ELEMENT (pbin->psink), + "sink"); + + gst_pulse_audio_sink_free_dbin2 (pbin); + gst_pulse_audio_sink_update_sinkpad (pbin, pad); + + gst_object_unref (pad); + + } + GST_PULSE_AUDIO_SINK_UNLOCK (pbin); + + break; + + default: + break; + } + +out: + return ret; +} + +#endif /* HAVE_PULSE_1_0 */ diff --git a/ext/pulse/pulsesink.c b/ext/pulse/pulsesink.c index b4c8a54cbe..13723fc7b3 100644 --- a/ext/pulse/pulsesink.c +++ b/ext/pulse/pulsesink.c @@ -420,6 +420,27 @@ gst_pulsering_context_subscribe_cb (pa_context * c, if (idx != pa_stream_get_index (pbuf->stream)) continue; +#ifdef HAVE_PULSE_1_0 + if (psink->device && pa_format_info_is_pcm (pbuf->format) && + !g_str_equal (psink->device, + pa_stream_get_device_name (pbuf->stream))) { + /* Underlying sink changed. And this is not a passthrough stream. Let's + * see if someone upstream wants to try to renegotiate. */ + GstEvent *renego; + + g_free (psink->device); + psink->device = g_strdup (pa_stream_get_device_name (pbuf->stream)); + + GST_INFO_OBJECT (psink, "emitting sink-changed"); + + renego = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_new ("pulse-sink-changed", NULL)); + + if (!gst_pad_push_event (GST_BASE_SINK (psink)->sinkpad, renego)) + GST_DEBUG_OBJECT (psink, "Emitted sink-changed - nobody was listening"); + } +#endif + /* Actually this event is also triggered when other properties of * the stream change that are unrelated to the volume. However it is * probably cheaper to signal the change here and check for the @@ -1716,35 +1737,10 @@ static gboolean gst_pulsesink_event (GstBaseSink * sink, GstEvent * event); static GstStateChangeReturn gst_pulsesink_change_state (GstElement * element, GstStateChange transition); -#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) -# define FORMATS "{ S16LE, S16BE, F32LE, F32BE, S32LE, S32BE, " \ - "S24LE, S24BE, S24_32LE, S24_32BE, S8 }" -#else -# define FORMATS "{ S16BE, S16LE, F32BE, F32LE, S32BE, S32LE, " \ - "S24BE, S24LE, S24_32BE, S24_32LE, S8 }" -#endif - static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-raw, " - "format = (string) " FORMATS ", " - "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 32 ];" - "audio/x-alaw, " - "rate = (int) [ 1, MAX], " - "channels = (int) [ 1, 32 ];" - "audio/x-mulaw, " - "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 32 ];" -#ifdef HAVE_PULSE_1_0 - "audio/x-ac3, framed = (boolean) true;" - "audio/x-eac3, framed = (boolean) true; " - "audio/x-dts, framed = (boolean) true, " - " block_size = (int) { 512, 1024, 2048 }; " - "audio/mpeg, mpegversion = (int)1, " - " mpegaudioversion = (int) [ 1, 2 ], parsed = (boolean) true; " -#endif - )); + GST_STATIC_CAPS (PULSE_SINK_TEMPLATE_CAPS)); GST_IMPLEMENT_PULSEPROBE_METHODS (GstPulseSink, gst_pulsesink); @@ -2002,6 +1998,8 @@ done: } #ifdef HAVE_PULSE_1_0 +/* NOTE: If you're making changes here, see if pulseaudiosink acceptcaps also + * needs to be changed accordingly. */ static gboolean gst_pulsesink_pad_acceptcaps (GstPad * pad, GstCaps * caps) { diff --git a/ext/pulse/pulsesink.h b/ext/pulse/pulsesink.h index ad8831f393..34796e8b27 100644 --- a/ext/pulse/pulsesink.h +++ b/ext/pulse/pulsesink.h @@ -24,6 +24,10 @@ #ifndef __GST_PULSESINK_H__ #define __GST_PULSESINK_H__ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include #include @@ -88,6 +92,60 @@ struct _GstPulseSinkClass GType gst_pulsesink_get_type (void); +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) +# define FORMATS "{ S16LE, S16BE, F32LE, F32BE, S32LE, S32BE, " \ + "S24LE, S24BE, S24_32LE, S24_32BE, S8 }" +#else +# define FORMATS "{ S16BE, S16LE, F32BE, F32LE, S32BE, S32LE, " \ + "S24BE, S24LE, S24_32BE, S24_32LE, S8 }" +#endif + +#define _PULSE_SINK_CAPS_COMMON \ + "audio/x-raw, " \ + "format = (string) " FORMATS ", " \ + "rate = (int) [ 1, MAX ], " \ + "channels = (int) [ 1, 32 ];" \ + "audio/x-alaw, " \ + "rate = (int) [ 1, MAX], " \ + "channels = (int) [ 1, 32 ];" \ + "audio/x-mulaw, " \ + "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 32 ];" + +#ifdef HAVE_PULSE_1_0 +#define _PULSE_SINK_CAPS_1_0 \ + "audio/x-ac3, framed = (boolean) true;" \ + "audio/x-eac3, framed = (boolean) true; " \ + "audio/x-dts, framed = (boolean) true, " \ + "block-size = (int) { 512, 1024, 2048 }; " \ + "audio/mpeg, mpegversion = (int) 1, " \ + "mpegaudioversion = (int) [ 1, 2 ], parsed = (boolean) true;" +#else +#define _PULSE_SINK_CAPS_1_0 "" +#endif + +#define PULSE_SINK_TEMPLATE_CAPS \ + _PULSE_SINK_CAPS_COMMON \ + _PULSE_SINK_CAPS_1_0 + +#ifdef HAVE_PULSE_1_0 + +#define GST_TYPE_PULSE_AUDIO_SINK \ + (gst_pulse_audio_sink_get_type()) +#define GST_PULSE_AUDIO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PULSE_AUDIO_SINK,GstPulseAudioSink)) +#define GST_PULSE_AUDIO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSE_AUDIO_SINK,GstPulseAudioSinkClass)) +#define GST_IS_PULSE_AUDIO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PULSE_AUDIO_SINK)) +#define GST_IS_PULSE_AUDIO_SINK_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PULSE_AUDIO_SINK)) +#define GST_PULSE_AUDIO_SINK_CAST(obj) \ + ((GstPulseAudioSink *)(obj)) + +GType gst_pulse_audio_sink_get_type (void); + +#endif /* HAVE_PULSE_1_0 */ + G_END_DECLS #endif /* __GST_PULSESINK_H__ */ diff --git a/ext/pulse/pulseutil.h b/ext/pulse/pulseutil.h index 91c8502e39..4adfeb1ed4 100644 --- a/ext/pulse/pulseutil.h +++ b/ext/pulse/pulseutil.h @@ -22,6 +22,10 @@ #ifndef __GST_PULSEUTIL_H__ #define __GST_PULSEUTIL_H__ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include #include #include diff --git a/ext/soup/Makefile.am b/ext/soup/Makefile.am index 29564063cb..6916b174a8 100644 --- a/ext/soup/Makefile.am +++ b/ext/soup/Makefile.am @@ -1,10 +1,10 @@ plugin_LTLIBRARIES = libgstsouphttpsrc.la -libgstsouphttpsrc_la_SOURCES = gstsouphttpsrc.c gstsouphttpsink.c gstsoup.c +libgstsouphttpsrc_la_SOURCES = gstsouphttpsrc.c gstsouphttpclientsink.c gstsoup.c libgstsouphttpsrc_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(SOUP_CFLAGS) libgstsouphttpsrc_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgsttag-@GST_MAJORMINOR@ $(GST_BASE_LIBS) $(SOUP_LIBS) libgstsouphttpsrc_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstsouphttpsrc_la_LIBTOOLFLAGS = --tag=disable-static -noinst_HEADERS = gstsouphttpsrc.h gstsouphttpsink.h +noinst_HEADERS = gstsouphttpsrc.h gstsouphttpclientsink.h diff --git a/ext/soup/gstsoup.c b/ext/soup/gstsoup.c index d01ed9b86e..de71df821b 100644 --- a/ext/soup/gstsoup.c +++ b/ext/soup/gstsoup.c @@ -19,7 +19,7 @@ #include #include "gstsouphttpsrc.h" -#include "gstsouphttpsink.h" +#include "gstsouphttpclientsink.h" static gboolean @@ -34,8 +34,8 @@ plugin_init (GstPlugin * plugin) gst_element_register (plugin, "souphttpsrc", GST_RANK_PRIMARY, GST_TYPE_SOUP_HTTP_SRC); - gst_element_register (plugin, "souphttpsink", GST_RANK_NONE, - GST_TYPE_SOUP_HTTP_SINK); + gst_element_register (plugin, "souphttpclientsink", GST_RANK_NONE, + GST_TYPE_SOUP_HTTP_CLIENT_SINK); return TRUE; } diff --git a/ext/soup/gstsouphttpsink.c b/ext/soup/gstsouphttpclientsink.c similarity index 76% rename from ext/soup/gstsouphttpsink.c rename to ext/soup/gstsouphttpclientsink.c index 5292c81ab6..df704769ed 100644 --- a/ext/soup/gstsouphttpsink.c +++ b/ext/soup/gstsouphttpclientsink.c @@ -17,16 +17,16 @@ * Boston, MA 02110-1335, USA. */ /** - * SECTION:element-gstsouphttpsink + * SECTION:element-gstsouphttpclientsink * - * The souphttpsink element sends pipeline data to an HTTP server + * The souphttpclientsink element sends pipeline data to an HTTP server * using HTTP PUT commands. * * * Example launch line * |[ * gst-launch -v videotestsrc num-buffers=300 ! theoraenc ! oggmux ! - * souphttpsink location=http://server/filename.ogv + * souphttpclientsink location=http://server/filename.ogv * ]| * * This example encodes 10 seconds of video and sends it to the HTTP @@ -40,43 +40,44 @@ #include #include -#include "gstsouphttpsink.h" +#include "gstsouphttpclientsink.h" -GST_DEBUG_CATEGORY_STATIC (gst_soup_http_sink_debug_category); -#define GST_CAT_DEFAULT gst_soup_http_sink_debug_category +GST_DEBUG_CATEGORY_STATIC (souphttpclientsink_dbg); +#define GST_CAT_DEFAULT souphttpclientsink_dbg /* prototypes */ -static void gst_soup_http_sink_set_property (GObject * object, +static void gst_soup_http_client_sink_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec); -static void gst_soup_http_sink_get_property (GObject * object, +static void gst_soup_http_client_sink_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec); -static void gst_soup_http_sink_dispose (GObject * object); -static void gst_soup_http_sink_finalize (GObject * object); +static void gst_soup_http_client_sink_dispose (GObject * object); +static void gst_soup_http_client_sink_finalize (GObject * object); -static gboolean gst_soup_http_sink_set_caps (GstBaseSink * sink, +static gboolean gst_soup_http_client_sink_set_caps (GstBaseSink * sink, GstCaps * caps); -static void gst_soup_http_sink_get_times (GstBaseSink * sink, +static void gst_soup_http_client_sink_get_times (GstBaseSink * sink, GstBuffer * buffer, GstClockTime * start, GstClockTime * end); -static gboolean gst_soup_http_sink_start (GstBaseSink * sink); -static gboolean gst_soup_http_sink_stop (GstBaseSink * sink); -static gboolean gst_soup_http_sink_unlock (GstBaseSink * sink); -static gboolean gst_soup_http_sink_event (GstBaseSink * sink, GstEvent * event); -static GstFlowReturn -gst_soup_http_sink_preroll (GstBaseSink * sink, GstBuffer * buffer); -static GstFlowReturn -gst_soup_http_sink_render (GstBaseSink * sink, GstBuffer * buffer); +static gboolean gst_soup_http_client_sink_start (GstBaseSink * sink); +static gboolean gst_soup_http_client_sink_stop (GstBaseSink * sink); +static gboolean gst_soup_http_client_sink_unlock (GstBaseSink * sink); +static gboolean gst_soup_http_client_sink_event (GstBaseSink * sink, + GstEvent * event); +static GstFlowReturn gst_soup_http_client_sink_preroll (GstBaseSink * sink, + GstBuffer * buffer); +static GstFlowReturn gst_soup_http_client_sink_render (GstBaseSink * sink, + GstBuffer * buffer); static void free_buffer_list (GList * list); -static void gst_soup_http_sink_reset (GstSoupHttpSink * souphttpsink); +static void gst_soup_http_client_sink_reset (GstSoupHttpClientSink * + souphttpsink); static void authenticate (SoupSession * session, SoupMessage * msg, SoupAuth * auth, gboolean retrying, gpointer user_data); -static void -callback (SoupSession * session, SoupMessage * msg, gpointer user_data); -static gboolean -gst_soup_http_sink_set_proxy (GstSoupHttpSink * souphttpsink, - const gchar * uri); +static void callback (SoupSession * session, SoupMessage * msg, + gpointer user_data); +static gboolean gst_soup_http_client_sink_set_proxy (GstSoupHttpClientSink * + souphttpsink, const gchar * uri); enum { @@ -93,11 +94,11 @@ enum PROP_SESSION }; -#define DEFAULT_USER_AGENT "GStreamer souphttpsink " +#define DEFAULT_USER_AGENT "GStreamer souphttpclientsink " /* pad templates */ -static GstStaticPadTemplate gst_soup_http_sink_sink_template = +static GstStaticPadTemplate gst_soup_http_client_sink_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, @@ -106,20 +107,21 @@ GST_STATIC_PAD_TEMPLATE ("sink", /* class initialization */ -#define gst_soup_http_sink_parent_class parent_class -G_DEFINE_TYPE (GstSoupHttpSink, gst_soup_http_sink, GST_TYPE_BASE_SINK); +#define gst_soup_http_client_sink_parent_class parent_class +G_DEFINE_TYPE (GstSoupHttpClientSink, gst_soup_http_client_sink, + GST_TYPE_BASE_SINK); static void -gst_soup_http_sink_class_init (GstSoupHttpSinkClass * klass) +gst_soup_http_client_sink_class_init (GstSoupHttpClientSinkClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS (klass); - gobject_class->set_property = gst_soup_http_sink_set_property; - gobject_class->get_property = gst_soup_http_sink_get_property; - gobject_class->dispose = gst_soup_http_sink_dispose; - gobject_class->finalize = gst_soup_http_sink_finalize; + gobject_class->set_property = gst_soup_http_client_sink_set_property; + gobject_class->get_property = gst_soup_http_client_sink_get_property; + gobject_class->dispose = gst_soup_http_client_sink_dispose; + gobject_class->finalize = gst_soup_http_client_sink_finalize; g_object_class_install_property (gobject_class, PROP_LOCATION, @@ -166,37 +168,38 @@ gst_soup_http_sink_class_init (GstSoupHttpSinkClass * klass) G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&gst_soup_http_sink_sink_template)); + gst_static_pad_template_get (&gst_soup_http_client_sink_sink_template)); gst_element_class_set_details_simple (gstelement_class, "HTTP client sink", "Generic", "Sends streams to HTTP server via PUT", "David Schleef "); - base_sink_class->set_caps = GST_DEBUG_FUNCPTR (gst_soup_http_sink_set_caps); + base_sink_class->set_caps = + GST_DEBUG_FUNCPTR (gst_soup_http_client_sink_set_caps); if (0) base_sink_class->get_times = - GST_DEBUG_FUNCPTR (gst_soup_http_sink_get_times); - base_sink_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_sink_start); - base_sink_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_sink_stop); - base_sink_class->unlock = GST_DEBUG_FUNCPTR (gst_soup_http_sink_unlock); - base_sink_class->event = GST_DEBUG_FUNCPTR (gst_soup_http_sink_event); + GST_DEBUG_FUNCPTR (gst_soup_http_client_sink_get_times); + base_sink_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_client_sink_start); + base_sink_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_client_sink_stop); + base_sink_class->unlock = + GST_DEBUG_FUNCPTR (gst_soup_http_client_sink_unlock); + base_sink_class->event = GST_DEBUG_FUNCPTR (gst_soup_http_client_sink_event); if (0) - base_sink_class->preroll = GST_DEBUG_FUNCPTR (gst_soup_http_sink_preroll); - base_sink_class->render = GST_DEBUG_FUNCPTR (gst_soup_http_sink_render); + base_sink_class->preroll = + GST_DEBUG_FUNCPTR (gst_soup_http_client_sink_preroll); + base_sink_class->render = + GST_DEBUG_FUNCPTR (gst_soup_http_client_sink_render); + + GST_DEBUG_CATEGORY_INIT (souphttpclientsink_dbg, "souphttpclientsink", 0, + "souphttpclientsink element"); - GST_DEBUG_CATEGORY_INIT (gst_soup_http_sink_debug_category, "souphttpsink", 0, - "debug category for souphttpsink element"); } static void -gst_soup_http_sink_init (GstSoupHttpSink * souphttpsink) +gst_soup_http_client_sink_init (GstSoupHttpClientSink * souphttpsink) { const char *proxy; - souphttpsink->sinkpad = - gst_pad_new_from_static_template (&gst_soup_http_sink_sink_template, - "sink"); - souphttpsink->mutex = g_mutex_new (); souphttpsink->cond = g_cond_new (); @@ -210,17 +213,17 @@ gst_soup_http_sink_init (GstSoupHttpSink * souphttpsink) souphttpsink->prop_session = NULL; souphttpsink->timeout = 1; proxy = g_getenv ("http_proxy"); - if (proxy && !gst_soup_http_sink_set_proxy (souphttpsink, proxy)) { + if (proxy && !gst_soup_http_client_sink_set_proxy (souphttpsink, proxy)) { GST_WARNING_OBJECT (souphttpsink, "The proxy in the http_proxy env var (\"%s\") cannot be parsed.", proxy); } - gst_soup_http_sink_reset (souphttpsink); + gst_soup_http_client_sink_reset (souphttpsink); } static void -gst_soup_http_sink_reset (GstSoupHttpSink * souphttpsink) +gst_soup_http_client_sink_reset (GstSoupHttpClientSink * souphttpsink) { g_free (souphttpsink->reason_phrase); souphttpsink->reason_phrase = NULL; @@ -230,7 +233,8 @@ gst_soup_http_sink_reset (GstSoupHttpSink * souphttpsink) } static gboolean -gst_soup_http_sink_set_proxy (GstSoupHttpSink * souphttpsink, const gchar * uri) +gst_soup_http_client_sink_set_proxy (GstSoupHttpClientSink * souphttpsink, + const gchar * uri) { if (souphttpsink->proxy) { soup_uri_free (souphttpsink->proxy); @@ -249,10 +253,10 @@ gst_soup_http_sink_set_proxy (GstSoupHttpSink * souphttpsink, const gchar * uri) } void -gst_soup_http_sink_set_property (GObject * object, guint property_id, +gst_soup_http_client_sink_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (object); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (object); g_mutex_lock (souphttpsink->mutex); switch (property_id) { @@ -300,7 +304,7 @@ gst_soup_http_sink_set_property (GObject * object, guint property_id, GST_WARNING ("proxy property cannot be NULL"); goto done; } - if (!gst_soup_http_sink_set_proxy (souphttpsink, proxy)) { + if (!gst_soup_http_client_sink_set_proxy (souphttpsink, proxy)) { GST_WARNING ("badly formatted proxy URI"); goto done; } @@ -319,10 +323,10 @@ done: } void -gst_soup_http_sink_get_property (GObject * object, guint property_id, +gst_soup_http_client_sink_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (object); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (object); switch (property_id) { case PROP_SESSION: @@ -369,9 +373,9 @@ gst_soup_http_sink_get_property (GObject * object, guint property_id, } void -gst_soup_http_sink_dispose (GObject * object) +gst_soup_http_client_sink_dispose (GObject * object) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (object); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (object); /* clean up as possible. may be called multiple times */ if (souphttpsink->prop_session) @@ -382,9 +386,9 @@ gst_soup_http_sink_dispose (GObject * object) } void -gst_soup_http_sink_finalize (GObject * object) +gst_soup_http_client_sink_finalize (GObject * object) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (object); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (object); /* clean up object here */ @@ -406,9 +410,9 @@ gst_soup_http_sink_finalize (GObject * object) static gboolean -gst_soup_http_sink_set_caps (GstBaseSink * sink, GstCaps * caps) +gst_soup_http_client_sink_set_caps (GstBaseSink * sink, GstCaps * caps) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (sink); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (sink); GstStructure *structure; const GValue *value_array; int i, n; @@ -435,7 +439,7 @@ gst_soup_http_sink_set_caps (GstBaseSink * sink, GstCaps * caps) } static void -gst_soup_http_sink_get_times (GstBaseSink * sink, GstBuffer * buffer, +gst_soup_http_client_sink_get_times (GstBaseSink * sink, GstBuffer * buffer, GstClockTime * start, GstClockTime * end) { @@ -444,7 +448,7 @@ gst_soup_http_sink_get_times (GstBaseSink * sink, GstBuffer * buffer, static gpointer thread_func (gpointer ptr) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (ptr); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (ptr); GST_DEBUG ("thread start"); @@ -457,9 +461,9 @@ thread_func (gpointer ptr) } static gboolean -gst_soup_http_sink_start (GstBaseSink * sink) +gst_soup_http_client_sink_start (GstBaseSink * sink) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (sink); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (sink); if (souphttpsink->prop_session) { souphttpsink->session = souphttpsink->prop_session; @@ -488,9 +492,9 @@ gst_soup_http_sink_start (GstBaseSink * sink) } static gboolean -gst_soup_http_sink_stop (GstBaseSink * sink) +gst_soup_http_client_sink_stop (GstBaseSink * sink) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (sink); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (sink); GST_DEBUG ("stop"); @@ -510,13 +514,13 @@ gst_soup_http_sink_stop (GstBaseSink * sink) souphttpsink->context = NULL; } - gst_soup_http_sink_reset (souphttpsink); + gst_soup_http_client_sink_reset (souphttpsink); return TRUE; } static gboolean -gst_soup_http_sink_unlock (GstBaseSink * sink) +gst_soup_http_client_sink_unlock (GstBaseSink * sink) { GST_DEBUG ("unlock"); @@ -524,9 +528,9 @@ gst_soup_http_sink_unlock (GstBaseSink * sink) } static gboolean -gst_soup_http_sink_event (GstBaseSink * sink, GstEvent * event) +gst_soup_http_client_sink_event (GstBaseSink * sink, GstEvent * event) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (sink); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (sink); GST_DEBUG_OBJECT (souphttpsink, "event"); @@ -545,7 +549,7 @@ gst_soup_http_sink_event (GstBaseSink * sink, GstEvent * event) } static GstFlowReturn -gst_soup_http_sink_preroll (GstBaseSink * sink, GstBuffer * buffer) +gst_soup_http_client_sink_preroll (GstBaseSink * sink, GstBuffer * buffer) { GST_DEBUG ("preroll"); @@ -564,7 +568,7 @@ free_buffer_list (GList * list) } static void -send_message_locked (GstSoupHttpSink * souphttpsink) +send_message_locked (GstSoupHttpClientSink * souphttpsink) { GList *g; guint64 n; @@ -643,7 +647,7 @@ send_message_locked (GstSoupHttpSink * souphttpsink) } static gboolean -send_message (GstSoupHttpSink * souphttpsink) +send_message (GstSoupHttpClientSink * souphttpsink) { g_mutex_lock (souphttpsink->mutex); send_message_locked (souphttpsink); @@ -655,7 +659,7 @@ send_message (GstSoupHttpSink * souphttpsink) static void callback (SoupSession * session, SoupMessage * msg, gpointer user_data) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (user_data); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (user_data); GST_DEBUG_OBJECT (souphttpsink, "callback status=%d %s", msg->status_code, msg->reason_phrase); @@ -679,9 +683,9 @@ callback (SoupSession * session, SoupMessage * msg, gpointer user_data) } static GstFlowReturn -gst_soup_http_sink_render (GstBaseSink * sink, GstBuffer * buffer) +gst_soup_http_client_sink_render (GstBaseSink * sink, GstBuffer * buffer) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (sink); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (sink); GSource *source; gboolean wake; @@ -717,7 +721,7 @@ static void authenticate (SoupSession * session, SoupMessage * msg, SoupAuth * auth, gboolean retrying, gpointer user_data) { - GstSoupHttpSink *souphttpsink = GST_SOUP_HTTP_SINK (user_data); + GstSoupHttpClientSink *souphttpsink = GST_SOUP_HTTP_CLIENT_SINK (user_data); if (!retrying) { if (souphttpsink->user_id && souphttpsink->user_pw) { diff --git a/ext/soup/gstsouphttpsink.h b/ext/soup/gstsouphttpclientsink.h similarity index 60% rename from ext/soup/gstsouphttpsink.h rename to ext/soup/gstsouphttpclientsink.h index f78272946e..fab143051d 100644 --- a/ext/soup/gstsouphttpsink.h +++ b/ext/soup/gstsouphttpclientsink.h @@ -1,5 +1,5 @@ /* GStreamer - * Copyright (C) 2011 FIXME + * Copyright (C) 2011 David Schleef * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -17,29 +17,27 @@ * Boston, MA 02111-1307, USA. */ -#ifndef _GST_SOUP_HTTP_SINK_H_ -#define _GST_SOUP_HTTP_SINK_H_ +#ifndef _GST_SOUP_HTTP_CLIENT_SINK_H_ +#define _GST_SOUP_HTTP_CLIENT_SINK_H_ #include #include G_BEGIN_DECLS -#define GST_TYPE_SOUP_HTTP_SINK (gst_soup_http_sink_get_type()) -#define GST_SOUP_HTTP_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SOUP_HTTP_SINK,GstSoupHttpSink)) -#define GST_SOUP_HTTP_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SOUP_HTTP_SINK,GstSoupHttpSinkClass)) -#define GST_IS_SOUP_HTTP_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SOUP_HTTP_SINK)) -#define GST_IS_SOUP_HTTP_SINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SOUP_HTTP_SINK)) +#define GST_TYPE_SOUP_HTTP_CLIENT_SINK (gst_soup_http_client_sink_get_type()) +#define GST_SOUP_HTTP_CLIENT_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SOUP_HTTP_CLIENT_SINK,GstSoupHttpClientSink)) +#define GST_SOUP_HTTP_CLIENT_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SOUP_HTTP_CLIENT_SINK,GstSoupHttpClientSinkClass)) +#define GST_IS_SOUP_HTTP_CLIENT_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SOUP_HTTP_CLIENT_SINK)) +#define GST_IS_SOUP_HTTP_CLIENT_SINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SOUP_HTTP_CLIENT_SINK)) -typedef struct _GstSoupHttpSink GstSoupHttpSink; -typedef struct _GstSoupHttpSinkClass GstSoupHttpSinkClass; +typedef struct _GstSoupHttpClientSink GstSoupHttpClientSink; +typedef struct _GstSoupHttpClientSinkClass GstSoupHttpClientSinkClass; -struct _GstSoupHttpSink +struct _GstSoupHttpClientSink { GstBaseSink base_souphttpsink; - GstPad *sinkpad; - GMutex *mutex; GCond *cond; GMainContext *context; @@ -71,12 +69,12 @@ struct _GstSoupHttpSink }; -struct _GstSoupHttpSinkClass +struct _GstSoupHttpClientSinkClass { GstBaseSinkClass base_souphttpsink_class; }; -GType gst_soup_http_sink_get_type (void); +GType gst_soup_http_client_sink_get_type (void); G_END_DECLS diff --git a/ext/soup/gstsouphttpsrc.c b/ext/soup/gstsouphttpsrc.c index 8a828dc56e..46de62251d 100644 --- a/ext/soup/gstsouphttpsrc.c +++ b/ext/soup/gstsouphttpsrc.c @@ -720,6 +720,9 @@ gst_soup_http_src_got_headers_cb (SoupMessage * msg, GstSoupHTTPSrc * src) soup_message_headers_foreach (msg->response_headers, gst_soup_http_src_headers_foreach, src); + if (msg->status_code == 407 && src->proxy_id && src->proxy_pw) + return; + if (src->automatic_redirect && SOUP_STATUS_IS_REDIRECTION (msg->status_code)) { GST_DEBUG_OBJECT (src, "%u redirect to \"%s\"", msg->status_code, soup_message_headers_get (msg->response_headers, "Location")); diff --git a/ext/speex/gstspeexenc.c b/ext/speex/gstspeexenc.c index 3b26866ff0..e202d9224b 100644 --- a/ext/speex/gstspeexenc.c +++ b/ext/speex/gstspeexenc.c @@ -1034,6 +1034,7 @@ gst_speex_enc_chain (GstPad * pad, GstBuffer * buf) /* Check if we have a continous stream, if not drop some samples or the buffer or * insert some silence samples */ if (enc->next_ts != GST_CLOCK_TIME_NONE && + GST_BUFFER_TIMESTAMP_IS_VALID (buf) && GST_BUFFER_TIMESTAMP (buf) < enc->next_ts) { guint64 diff = enc->next_ts - GST_BUFFER_TIMESTAMP (buf); guint64 diff_bytes; diff --git a/gst/audioparsers/gstaacparse.c b/gst/audioparsers/gstaacparse.c index 070e94997d..aaed5f8422 100644 --- a/gst/audioparsers/gstaacparse.c +++ b/gst/audioparsers/gstaacparse.c @@ -57,8 +57,7 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/mpeg, " - "framed = (boolean) false, " "mpegversion = (int) { 2, 4 };")); + GST_STATIC_CAPS ("audio/mpeg, mpegversion = (int) { 2, 4 };")); GST_DEBUG_CATEGORY_STATIC (aacparse_debug); #define GST_CAT_DEFAULT aacparse_debug @@ -252,10 +251,12 @@ gst_aac_parse_sink_setcaps (GstBaseParse * parse, GstCaps * caps) aacparse->channels = (data[1] & 0x78) >> 3; aacparse->header_type = DSPAAC_HEADER_NONE; aacparse->mpegversion = 4; + aacparse->frame_samples = (data[1] & 4) ? 960 : 1024; gst_buffer_unmap (buf, data, size); - GST_DEBUG ("codec_data: object_type=%d, sample_rate=%d, channels=%d", - aacparse->object_type, aacparse->sample_rate, aacparse->channels); + GST_DEBUG ("codec_data: object_type=%d, sample_rate=%d, channels=%d, " + "samples=%d", aacparse->object_type, aacparse->sample_rate, + aacparse->channels, aacparse->frame_samples); /* arrange for metadata and get out of the way */ gst_aac_parse_set_src_caps (aacparse, caps); @@ -451,7 +452,8 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse, gst_aac_parse_parse_adts_header (aacparse, data, &rate, &channels, &aacparse->object_type, &aacparse->mpegversion); - gst_base_parse_set_frame_rate (GST_BASE_PARSE (aacparse), rate, 1024, 2, 2); + gst_base_parse_set_frame_rate (GST_BASE_PARSE (aacparse), rate, + aacparse->frame_samples, 2, 2); GST_DEBUG ("ADTS: samplerate %d, channels %d, objtype %d, version %d", rate, channels, aacparse->object_type, aacparse->mpegversion); @@ -683,7 +685,7 @@ gst_aac_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame) gst_caps_unref (sinkcaps); gst_base_parse_set_frame_rate (GST_BASE_PARSE (aacparse), - aacparse->sample_rate, 1024, 2, 2); + aacparse->sample_rate, aacparse->frame_samples, 2, 2); } return ret; @@ -705,6 +707,7 @@ gst_aac_parse_start (GstBaseParse * parse) aacparse = GST_AAC_PARSE (parse); GST_DEBUG ("start"); + aacparse->frame_samples = 1024; gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse), ADTS_MAX_SIZE); return TRUE; } diff --git a/gst/audioparsers/gstaacparse.h b/gst/audioparsers/gstaacparse.h index 4020d8fc75..1907c2e440 100644 --- a/gst/audioparsers/gstaacparse.h +++ b/gst/audioparsers/gstaacparse.h @@ -63,19 +63,6 @@ typedef struct _GstAacParseClass GstAacParseClass; /** * GstAacParse: - * @element: the parent element. - * @object_type: AAC object type of the stream. - * @bitrate: Current media bitrate. - * @sample_rate: Current media samplerate. - * @channels: Current media channel count. - * @frames_per_sec: FPS value of the current stream. - * @header_type: #GstAacHeaderType indicating the current stream type. - * @framecount: The amount of frames that has been processed this far. - * @bytecount: The amount of bytes that has been processed this far. - * @sync: Tells whether the parser is in sync (a.k.a. not searching for header) - * @eos: End-of-Stream indicator. Set when EOS event arrives. - * @duration: Duration of the current stream. - * @ts: Current stream timestamp. * * The opaque GstAacParse data structure. */ @@ -88,6 +75,7 @@ struct _GstAacParse { gint sample_rate; gint channels; gint mpegversion; + gint frame_samples; GstAacHeaderType header_type; }; diff --git a/gst/audioparsers/gstac3parse.c b/gst/audioparsers/gstac3parse.c index aa3351f49b..644a939077 100644 --- a/gst/audioparsers/gstac3parse.c +++ b/gst/audioparsers/gstac3parse.c @@ -144,16 +144,16 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-ac3, framed = (boolean) true, " - " channels = (int) [ 1, 6 ], rate = (int) [ 32000, 48000 ]; " + " channels = (int) [ 1, 6 ], rate = (int) [ 8000, 48000 ], " + " alignment = (string) { iec61937, frame}; " "audio/x-eac3, framed = (boolean) true, " - " channels = (int) [ 1, 6 ], rate = (int) [ 32000, 48000 ] ")); + " channels = (int) [ 1, 6 ], rate = (int) [ 8000, 48000 ], " + " alignment = (string) { iec61937, frame}; ")); static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-ac3, framed = (boolean) false; " - "audio/x-eac3, framed = (boolean) false; " - "audio/ac3, framed = (boolean) false ")); + GST_STATIC_CAPS ("audio/x-ac3; " "audio/x-eac3; " "audio/ac3")); static void gst_ac3_parse_finalize (GObject * object); @@ -187,7 +187,7 @@ gst_ac3_parse_class_init (GstAc3ParseClass * klass) gst_static_pad_template_get (&src_template)); gst_element_class_set_details_simple (element_class, - "AC3 audio stream parser", "Codec/Parser/Audio", + "AC3 audio stream parser", "Codec/Parser/Converter/Audio", "AC3 parser", "Tim-Philipp MĂ¼ller "); parse_class->start = GST_DEBUG_FUNCPTR (gst_ac3_parse_start); @@ -298,7 +298,7 @@ gst_ac3_parse_frame_header_ac3 (GstAc3Parse * ac3parse, GstBuffer * buf, GstBitReader bits; gpointer data; gsize size; - guint8 fscod, frmsizcod, bsid, acmod, lfe_on; + guint8 fscod, frmsizcod, bsid, acmod, lfe_on, rate_scale; gboolean ret = FALSE; GST_LOG_OBJECT (ac3parse, "parsing ac3"); @@ -338,10 +338,14 @@ gst_ac3_parse_frame_header_ac3 (GstAc3Parse * ac3parse, GstBuffer * buf, lfe_on = gst_bit_reader_get_bits_uint8_unchecked (&bits, 1); + /* 6/8->0, 9->1, 10->2, + see http://matroska.org/technical/specs/codecid/index.html */ + rate_scale = (CLAMP (bsid, 8, 10) - 8); + if (frame_size) *frame_size = frmsizcod_table[frmsizcod].frame_size[fscod] * 2; if (rate) - *rate = fscod_rates[fscod]; + *rate = fscod_rates[fscod] >> rate_scale; if (chans) *chans = acmod_chans[acmod] + lfe_on; if (blks) @@ -631,7 +635,7 @@ gst_ac3_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame) } if (G_UNLIKELY (ac3parse->sample_rate != rate || ac3parse->channels != chans - || ac3parse->eac != ac3parse->eac)) { + || ac3parse->eac != eac)) { GstCaps *caps = gst_caps_new_simple (eac ? "audio/x-eac3" : "audio/x-ac3", "framed", G_TYPE_BOOLEAN, TRUE, "rate", G_TYPE_INT, rate, "channels", G_TYPE_INT, chans, NULL); diff --git a/gst/audioparsers/gstdcaparse.c b/gst/audioparsers/gstdcaparse.c index b4bbe87245..7ebbc701fe 100644 --- a/gst/audioparsers/gstdcaparse.c +++ b/gst/audioparsers/gstdcaparse.c @@ -62,12 +62,13 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", " channels = (int) [ 1, 8 ]," " rate = (int) [ 8000, 192000 ]," " depth = (int) { 14, 16 }," - " endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }")); + " endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, " + " block-size = (int) [ 1, MAX], " " frame-size = (int) [ 1, MAX]")); static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-dts, framed = (boolean) false")); + GST_STATIC_CAPS ("audio/x-dts")); static void gst_dca_parse_finalize (GObject * object); diff --git a/gst/audioparsers/gstflacparse.c b/gst/audioparsers/gstflacparse.c index 9764dce766..8bc5344246 100644 --- a/gst/audioparsers/gstflacparse.c +++ b/gst/audioparsers/gstflacparse.c @@ -181,7 +181,7 @@ static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-flac, framed = (boolean) false") + GST_STATIC_CAPS ("audio/x-flac") ); static void gst_flac_parse_finalize (GObject * object); diff --git a/gst/audioparsers/gstmpegaudioparse.c b/gst/audioparsers/gstmpegaudioparse.c index c454ec8336..8ac1e2bc6d 100644 --- a/gst/audioparsers/gstmpegaudioparse.c +++ b/gst/audioparsers/gstmpegaudioparse.c @@ -76,14 +76,15 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_STATIC_CAPS ("audio/mpeg, " "mpegversion = (int) 1, " "layer = (int) [ 1, 3 ], " - "rate = (int) [ 8000, 48000 ], channels = (int) [ 1, 2 ]," - "parsed=(boolean) true") + "mpegaudioversion = (int) [ 1, 3], " + "rate = (int) [ 8000, 48000 ], " + "channels = (int) [ 1, 2 ], " "parsed=(boolean) true") ); static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/mpeg, mpegversion = (int) 1, parsed=(boolean)false") + GST_STATIC_CAPS ("audio/mpeg, mpegversion = (int) 1") ); static void gst_mpeg_audio_parse_finalize (GObject * object); diff --git a/gst/debugutils/gstnavseek.c b/gst/debugutils/gstnavseek.c index 9678a92935..13f2b72846 100644 --- a/gst/debugutils/gstnavseek.c +++ b/gst/debugutils/gstnavseek.c @@ -205,6 +205,23 @@ gst_navseek_segseek (GstNavSeek * navseek) gst_object_unref (peer_pad); } +static void +gst_navseek_toggle_play_pause (GstNavSeek * navseek) +{ + GstStateChangeReturn sret; + GstState current, pending, state; + + sret = gst_element_get_state (GST_ELEMENT (navseek), ¤t, &pending, 0); + if (sret == GST_STATE_CHANGE_FAILURE) + return; + + state = (pending != GST_STATE_VOID_PENDING) ? pending : current; + + gst_element_post_message (GST_ELEMENT (navseek), + gst_message_new_request_state (GST_OBJECT (navseek), + (state == GST_STATE_PLAYING) ? GST_STATE_PAUSED : GST_STATE_PLAYING)); +} + static gboolean gst_navseek_src_event (GstBaseTransform * trans, GstEvent * event) { @@ -257,6 +274,8 @@ gst_navseek_src_event (GstBaseTransform * trans, GstEvent * event) } else if (strcmp (key, "n") == 0) { /* normal speed */ gst_navseek_change_playback_rate (navseek, 1.0); + } else if (strcmp (key, "space") == 0) { + gst_navseek_toggle_play_pause (navseek); } } else { break; diff --git a/gst/flv/gstflvmux.c b/gst/flv/gstflvmux.c index 69e26220d2..aac52fdd8d 100644 --- a/gst/flv/gstflvmux.c +++ b/gst/flv/gstflvmux.c @@ -107,6 +107,8 @@ static GstStateChangeReturn gst_flv_mux_change_state (GstElement * element, GstStateChange transition); static void gst_flv_mux_reset (GstElement * element); +static void gst_flv_mux_reset_pad (GstFlvMux * mux, GstFlvPad * pad, + gboolean video); typedef struct { @@ -228,10 +230,10 @@ gst_flv_mux_reset (GstElement * element) GstFlvMux *mux = GST_FLV_MUX (element); GSList *sl; - while ((sl = mux->collect->data) != NULL) { + for (sl = mux->collect->data; sl != NULL; sl = g_slist_next (sl)) { GstFlvPad *cpad = (GstFlvPad *) sl->data; - gst_element_release_request_pad (element, cpad->collect.pad); + gst_flv_mux_reset_pad (mux, cpad, cpad->video); } g_list_foreach (mux->index, (GFunc) gst_flv_mux_index_entry_free, NULL); @@ -503,6 +505,26 @@ gst_flv_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) return ret; } +static void +gst_flv_mux_reset_pad (GstFlvMux * mux, GstFlvPad * cpad, gboolean video) +{ + cpad->video = video; + + if (cpad->audio_codec_data) + gst_buffer_unref (cpad->audio_codec_data); + cpad->audio_codec_data = NULL; + cpad->audio_codec = G_MAXUINT; + cpad->rate = G_MAXUINT; + cpad->width = G_MAXUINT; + cpad->channels = G_MAXUINT; + + if (cpad->video_codec_data) + gst_buffer_unref (cpad->video_codec_data); + cpad->video_codec_data = NULL; + cpad->video_codec = G_MAXUINT; + cpad->last_timestamp = 0; +} + static GstPad * gst_flv_mux_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps) @@ -544,18 +566,9 @@ gst_flv_mux_request_new_pad (GstElement * element, cpad = (GstFlvPad *) gst_collect_pads_add_pad (mux->collect, pad, sizeof (GstFlvPad)); - cpad->video = video; - - cpad->audio_codec = G_MAXUINT; - cpad->rate = G_MAXUINT; - cpad->width = G_MAXUINT; - cpad->channels = G_MAXUINT; cpad->audio_codec_data = NULL; - - cpad->video_codec = G_MAXUINT; cpad->video_codec_data = NULL; - - cpad->last_timestamp = 0; + gst_flv_mux_reset_pad (mux, cpad, video); /* FIXME: hacked way to override/extend the event function of * GstCollectPads; because it sets its own event function giving the @@ -577,11 +590,7 @@ gst_flv_mux_release_pad (GstElement * element, GstPad * pad) GstFlvMux *mux = GST_FLV_MUX (GST_PAD_PARENT (pad)); GstFlvPad *cpad = (GstFlvPad *) gst_pad_get_element_private (pad); - if (cpad && cpad->audio_codec_data) - gst_buffer_unref (cpad->audio_codec_data); - if (cpad && cpad->video_codec_data) - gst_buffer_unref (cpad->video_codec_data); - + gst_flv_mux_reset_pad (mux, cpad, cpad->video); gst_collect_pads_remove_pad (mux->collect, pad); gst_element_remove_pad (element, pad); } diff --git a/gst/goom2k1/goom_core.c b/gst/goom2k1/goom_core.c index d66ffe25de..ee893f4076 100644 --- a/gst/goom2k1/goom_core.c +++ b/gst/goom2k1/goom_core.c @@ -66,9 +66,8 @@ goom_set_resolution (GoomData * goomdata, guint32 resx, guint32 resy) goomdata->back = (guint32 *) malloc (buffsize * sizeof (guint32) + 128); goomdata->buffsize = buffsize; - goomdata->p1 = - (void *) (((unsigned long) goomdata->pixel + 0x7f) & (~0x7f)); - goomdata->p2 = (void *) (((unsigned long) goomdata->back + 0x7f) & (~0x7f)); + goomdata->p1 = (void *) (((guintptr) goomdata->pixel + 0x7f) & (~0x7f)); + goomdata->p2 = (void *) (((guintptr) goomdata->back + 0x7f) & (~0x7f)); } goomdata->resolx = resx; diff --git a/gst/isomp4/gstqtmux.c b/gst/isomp4/gstqtmux.c index 3e210f7bd4..572956d419 100644 --- a/gst/isomp4/gstqtmux.c +++ b/gst/isomp4/gstqtmux.c @@ -1757,15 +1757,25 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) GstCollectData *cdata = (GstCollectData *) walk->data; GstQTPad *qtpad = (GstQTPad *) cdata; - /* send last buffer */ + /* avoid add_buffer complaining if not negotiated + * in which case no buffers either, so skipping */ + if (!qtpad->fourcc) { + GST_DEBUG_OBJECT (qtmux, "Pad %s has never had buffers", + GST_PAD_NAME (qtpad->collect.pad)); + continue; + } + + /* send last buffer; also flushes possibly queued buffers/ts */ GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s", GST_PAD_NAME (qtpad->collect.pad)); ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL); - if (ret != GST_FLOW_OK) + if (ret != GST_FLOW_OK) { GST_WARNING_OBJECT (qtmux, "Failed to send last buffer for %s, " "flow return: %s", GST_PAD_NAME (qtpad->collect.pad), gst_flow_get_name (ret)); + } + /* having flushed above, can check for buffers now */ if (!GST_CLOCK_TIME_IS_VALID (qtpad->first_ts)) { GST_DEBUG_OBJECT (qtmux, "Pad %s has no buffers", GST_PAD_NAME (qtpad->collect.pad)); @@ -2048,6 +2058,39 @@ gst_qt_mux_push_ts (GstQTMux * qtmux, GstQTPad * pad, GstClockTime ts) pad->ts_n_entries++; } +static void +check_and_subtract_ts (GstQTMux * qtmux, GstClockTime * ts_a, GstClockTime ts_b) +{ + if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (*ts_a))) { + if (G_LIKELY (*ts_a > ts_b)) { + *ts_a -= ts_b; + } else { + *ts_a = 0; + GST_WARNING_OBJECT (qtmux, "Subtraction would result in negative value, " + "using 0 as result"); + } + } +} + +/* subtract ts from all buffers enqueued on the pad */ +static void +gst_qt_mux_subtract_ts (GstQTMux * qtmux, GstQTPad * pad, GstClockTime ts) +{ + gint i; + + for (i = 0; (i < QTMUX_NO_OF_TS) && (i < pad->ts_n_entries); i++) { + check_and_subtract_ts (qtmux, &pad->ts_entries[i], ts); + } + for (i = 0; i < G_N_ELEMENTS (pad->buf_entries); i++) { + if (pad->buf_entries[i]) { + check_and_subtract_ts (qtmux, &GST_BUFFER_TIMESTAMP (pad->buf_entries[i]), + ts); + check_and_subtract_ts (qtmux, + &GST_BUFFER_OFFSET_END (pad->buf_entries[i]), ts); + } + } +} + /* takes ownership of @buf */ static GstBuffer * gst_qt_mux_get_asc_buffer_ts (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) @@ -2104,9 +2147,16 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) buf = pad->prepare_buf_func (pad, buf, qtmux); } + if (G_LIKELY (buf != NULL && GST_CLOCK_TIME_IS_VALID (pad->first_ts))) { + buf = gst_buffer_make_writable (buf); + check_and_subtract_ts (qtmux, &GST_BUFFER_TIMESTAMP (buf), pad->first_ts); + } + /* when we obtain the first_ts we subtract from all stored buffers we have, + * after that we can subtract on input */ + again: last_buf = pad->last_buf; - if (G_UNLIKELY (qtmux->dts_method == DTS_METHOD_REORDER)) { + if (qtmux->dts_method == DTS_METHOD_REORDER) { buf = gst_qt_mux_get_asc_buffer_ts (qtmux, pad, buf); if (!buf && !last_buf) { GST_DEBUG_OBJECT (qtmux, "no reordered buffer"); @@ -2177,6 +2227,31 @@ again: goto no_order; } + /* if this is the first buffer, store the timestamp */ + if (G_UNLIKELY (pad->first_ts == GST_CLOCK_TIME_NONE) && last_buf) { + if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (last_buf))) { + pad->first_ts = GST_BUFFER_TIMESTAMP (last_buf); + } else { + GST_DEBUG_OBJECT (qtmux, "First buffer for pad %s has no timestamp, " + "using 0 as first timestamp", GST_PAD_NAME (pad->collect.pad)); + pad->first_ts = 0; + } + GST_DEBUG_OBJECT (qtmux, "Stored first timestamp for pad %s %" + GST_TIME_FORMAT, GST_PAD_NAME (pad->collect.pad), + GST_TIME_ARGS (pad->first_ts)); + + gst_qt_mux_subtract_ts (qtmux, pad, pad->first_ts); + + GST_BUFFER_TIMESTAMP (last_buf) = 0; + check_and_subtract_ts (qtmux, &GST_BUFFER_OFFSET_END (last_buf), + pad->first_ts); + if (buf) { + check_and_subtract_ts (qtmux, &GST_BUFFER_TIMESTAMP (buf), pad->first_ts); + check_and_subtract_ts (qtmux, &GST_BUFFER_OFFSET_END (buf), + pad->first_ts); + } + } + /* fall back to duration if last buffer or * out-of-order (determined previously), otherwise use input ts */ if (buf == NULL || @@ -2336,20 +2411,6 @@ again: qtmux->longest_chunk = duration; } - /* if this is the first buffer, store the timestamp */ - if (G_UNLIKELY (pad->first_ts == GST_CLOCK_TIME_NONE) && last_buf) { - if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (last_buf))) { - pad->first_ts = GST_BUFFER_TIMESTAMP (last_buf); - } else { - GST_DEBUG_OBJECT (qtmux, "First buffer for pad %s has no timestamp, " - "using 0 as first timestamp", GST_PAD_NAME (pad->collect.pad)); - pad->first_ts = 0; - } - GST_DEBUG_OBJECT (qtmux, "Stored first timestamp for pad %s %" - GST_TIME_FORMAT, GST_PAD_NAME (pad->collect.pad), - GST_TIME_ARGS (pad->first_ts)); - } - /* now we go and register this buffer/sample all over */ /* note that a new chunk is started each time (not fancy but works) */ if (qtmux->moov_recov_file) { diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c index 23adee23f4..ca36aa82b4 100644 --- a/gst/isomp4/qtdemux.c +++ b/gst/isomp4/qtdemux.c @@ -8381,6 +8381,41 @@ unknown_tag: } } +static void +qtdemux_tag_add_id32 (GstQTDemux * demux, const char *tag, + const char *tag_bis, GNode * node) +{ + guint8 *data; + GstBuffer *buf; + guint len; + GstTagList *taglist = NULL; + + GST_LOG_OBJECT (demux, "parsing ID32"); + + data = node->data; + len = GST_READ_UINT32_BE (data); + + /* need at least full box and language tag */ + if (len < 12 + 2) + return; + + buf = gst_buffer_new_allocate (NULL, len - 14, 0); + gst_buffer_fill (buf, 0, data + 14, len - 14); + + taglist = gst_tag_list_from_id3v2_tag (buf); + if (taglist) { + GST_LOG_OBJECT (demux, "parsing ok"); + gst_tag_list_insert (demux->tag_list, taglist, GST_TAG_MERGE_KEEP); + } else { + GST_LOG_OBJECT (demux, "parsing failed"); + } + + if (taglist) + gst_tag_list_free (taglist); + + gst_buffer_unref (buf); +} + typedef void (*GstQTDemuxAddTagFunc) (GstQTDemux * demux, const char *tag, const char *tag_bis, GNode * node); @@ -8450,7 +8485,9 @@ static const struct * http://atomicparsley.sourceforge.net/mpeg-4files.html and * bug #614471 */ - FOURCC_____, "", NULL, qtdemux_tag_add_revdns} + FOURCC_____, "", NULL, qtdemux_tag_add_revdns}, { + /* see http://www.mp4ra.org/specs.html for ID32 in meta box */ + FOURCC_ID32, "", NULL, qtdemux_tag_add_id32} }; static void @@ -8865,6 +8902,15 @@ qtdemux_parse_tree (GstQTDemux * qtdemux) GST_LOG_OBJECT (qtdemux, "No udta node found."); } + /* maybe also some tags in meta box */ + udta = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_meta); + if (udta) { + GST_DEBUG_OBJECT (qtdemux, "Parsing meta box for tags."); + qtdemux_parse_udta (qtdemux, udta); + } else { + GST_LOG_OBJECT (qtdemux, "No meta node found."); + } + qtdemux->tag_list = qtdemux_add_container_format (qtdemux, qtdemux->tag_list); return TRUE; diff --git a/gst/isomp4/qtdemux_fourcc.h b/gst/isomp4/qtdemux_fourcc.h index 29ad155a3f..6666a94e55 100644 --- a/gst/isomp4/qtdemux_fourcc.h +++ b/gst/isomp4/qtdemux_fourcc.h @@ -189,6 +189,9 @@ G_BEGIN_DECLS #define FOURCC_albm GST_MAKE_FOURCC('a','l','b','m') #define FOURCC_yrrc GST_MAKE_FOURCC('y','r','r','c') +/* misc tag stuff */ +#define FOURCC_ID32 GST_MAKE_FOURCC('I', 'D','3','2') + /* ISO Motion JPEG 2000 fourcc */ #define FOURCC_mjp2 GST_MAKE_FOURCC('m','j','p','2') #define FOURCC_jp2h GST_MAKE_FOURCC('j','p','2','h') diff --git a/gst/matroska/ebml-read.c b/gst/matroska/ebml-read.c index c2dfa522a4..f6bf1348c9 100644 --- a/gst/matroska/ebml-read.c +++ b/gst/matroska/ebml-read.c @@ -60,6 +60,7 @@ gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, guint * _needed, gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; guint64 total; guint8 b; + GstFlowReturn ret; g_return_val_if_fail (_id != NULL, GST_FLOW_ERROR); g_return_val_if_fail (_length != NULL, GST_FLOW_ERROR); @@ -71,10 +72,9 @@ gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, guint * _needed, /* read element id */ needed = 2; - buf = peek (ctx, needed); - if (!buf) - goto not_enough_data; - + ret = peek (ctx, needed, &buf); + if (ret != GST_FLOW_OK) + goto peek_error; b = GST_READ_UINT8 (buf); total = (guint64) b; while (read <= 4 && !(total & len_mask)) { @@ -86,10 +86,9 @@ gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, guint * _needed, /* need id and at least something for subsequent length */ needed = read + 1; - buf = peek (ctx, needed); - if (!buf) - goto not_enough_data; - + ret = peek (ctx, needed, &buf); + if (ret != GST_FLOW_OK) + goto peek_error; while (n < read) { b = GST_READ_UINT8 (buf + n); total = (total << 8) | b; @@ -112,10 +111,9 @@ gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, guint * _needed, num_ffs++; needed += read - 1; - buf = peek (ctx, needed); - if (!buf) - goto not_enough_data; - + ret = peek (ctx, needed, &buf); + if (ret != GST_FLOW_OK) + goto peek_error; buf += (needed - read); n = 1; while (n < read) { @@ -137,10 +135,11 @@ gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, guint * _needed, return GST_FLOW_OK; /* ERRORS */ -not_enough_data: +peek_error: { + GST_WARNING_OBJECT (el, "peek failed, ret = %d", ret); *_needed = needed; - return GST_FLOW_UNEXPECTED; + return ret; } invalid_id: { @@ -190,15 +189,13 @@ gst_ebml_read_clear (GstEbmlRead * ebml) ebml->el = NULL; } -static const guint8 * -gst_ebml_read_peek (GstByteReader * br, guint peek) +static GstFlowReturn +gst_ebml_read_peek (GstByteReader * br, guint peek, const guint8 ** data) { - const guint8 *data = NULL; - - if (G_LIKELY (gst_byte_reader_peek_data (br, peek, &data))) - return data; + if (G_LIKELY (gst_byte_reader_peek_data (br, peek, data))) + return GST_FLOW_OK; else - return NULL; + return GST_FLOW_UNEXPECTED; } static GstFlowReturn diff --git a/gst/matroska/ebml-read.h b/gst/matroska/ebml-read.h index b40c31d352..9db38f54ca 100644 --- a/gst/matroska/ebml-read.h +++ b/gst/matroska/ebml-read.h @@ -59,7 +59,7 @@ typedef struct _GstEbmlRead { GArray *readers; } GstEbmlRead; -typedef const guint8 * (*GstPeekData) (gpointer * context, guint peek); +typedef GstFlowReturn (*GstPeekData) (gpointer * context, guint peek, const guint8 ** data); /* returns UNEXPECTED if not enough data */ GstFlowReturn gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, diff --git a/gst/matroska/matroska-demux.c b/gst/matroska/matroska-demux.c index 5a0b90a584..f4b710d98c 100644 --- a/gst/matroska/matroska-demux.c +++ b/gst/matroska/matroska-demux.c @@ -81,9 +81,12 @@ enum { ARG_0, ARG_METADATA, - ARG_STREAMINFO + ARG_STREAMINFO, + ARG_MAX_GAP_TIME }; +#define DEFAULT_MAX_GAP_TIME (2 * GST_SECOND) + static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, @@ -168,6 +171,12 @@ static void gst_matroska_demux_reset (GstElement * element); static gboolean perform_seek_to_offset (GstMatroskaDemux * demux, guint64 offset); +/* gobject functions */ +static void gst_matroska_demux_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_matroska_demux_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + GType gst_matroska_demux_get_type (void); GST_BOILERPLATE (GstMatroskaDemux, gst_matroska_demux, GstElement, GST_TYPE_ELEMENT); @@ -223,6 +232,15 @@ gst_matroska_demux_class_init (GstMatroskaDemuxClass * klass) gobject_class->finalize = gst_matroska_demux_finalize; + gobject_class->get_property = gst_matroska_demux_get_property; + gobject_class->set_property = gst_matroska_demux_set_property; + + g_object_class_install_property (gobject_class, ARG_MAX_GAP_TIME, + g_param_spec_uint64 ("max-gap-time", "Maximum gap time", + "The demuxer sends out newsegment events for skipping " + "gaps longer than this (0 = disabled).", 0, G_MAXUINT64, + DEFAULT_MAX_GAP_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_matroska_demux_change_state); gstelement_class->send_event = @@ -262,6 +280,9 @@ gst_matroska_demux_init (GstMatroskaDemux * demux, demux->common.adapter = gst_adapter_new (); + /* property defaults */ + demux->max_gap_time = DEFAULT_MAX_GAP_TIME; + /* finish off */ gst_matroska_demux_reset (GST_ELEMENT (demux)); } @@ -3364,7 +3385,8 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, /* handle gaps, e.g. non-zero start-time, or an cue index entry * that landed us with timestamps not quite intended */ GST_OBJECT_LOCK (demux); - if (GST_CLOCK_TIME_IS_VALID (demux->common.segment.last_stop) && + if (demux->max_gap_time && + GST_CLOCK_TIME_IS_VALID (demux->last_stop_end) && demux->common.segment.rate > 0.0) { GstClockTimeDiff diff; GstEvent *event1, *event2; @@ -3372,8 +3394,9 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, /* only send newsegments with increasing start times, * otherwise if these go back and forth downstream (sinks) increase * accumulated time and running_time */ - diff = GST_CLOCK_DIFF (demux->common.segment.last_stop, lace_time); - if (diff > 2 * GST_SECOND && lace_time > demux->common.segment.start + diff = GST_CLOCK_DIFF (demux->last_stop_end, lace_time); + if (diff > 0 && diff > demux->max_gap_time + && lace_time > demux->common.segment.start && (!GST_CLOCK_TIME_IS_VALID (demux->common.segment.stop) || lace_time < demux->common.segment.stop)) { GST_DEBUG_OBJECT (demux, @@ -3386,11 +3409,9 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, * accum time, hence running_time */ /* close ahead of gap */ event1 = gst_event_new_new_segment (TRUE, - demux->common.segment.rate, - demux->common.segment.format, - demux->common.segment.last_stop, - demux->common.segment.last_stop, - demux->common.segment.last_stop); + demux->common.segment.rate, demux->common.segment.format, + demux->last_stop_end, demux->last_stop_end, + demux->last_stop_end); /* skip gap */ event2 = gst_event_new_new_segment (FALSE, demux->common.segment.rate, @@ -5414,6 +5435,48 @@ gst_matroska_demux_change_state (GstElement * element, return ret; } +static void +gst_matroska_demux_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstMatroskaDemux *demux; + + g_return_if_fail (GST_IS_MATROSKA_DEMUX (object)); + demux = GST_MATROSKA_DEMUX (object); + + switch (prop_id) { + case ARG_MAX_GAP_TIME: + GST_OBJECT_LOCK (demux); + demux->max_gap_time = g_value_get_uint64 (value); + GST_OBJECT_UNLOCK (demux); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_matroska_demux_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstMatroskaDemux *demux; + + g_return_if_fail (GST_IS_MATROSKA_DEMUX (object)); + demux = GST_MATROSKA_DEMUX (object); + + switch (prop_id) { + case ARG_MAX_GAP_TIME: + GST_OBJECT_LOCK (demux); + g_value_set_uint64 (value, demux->max_gap_time); + GST_OBJECT_UNLOCK (demux); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + gboolean gst_matroska_demux_plugin_init (GstPlugin * plugin) { diff --git a/gst/matroska/matroska-demux.h b/gst/matroska/matroska-demux.h index c9ab2fc8e1..192189f13b 100644 --- a/gst/matroska/matroska-demux.h +++ b/gst/matroska/matroska-demux.h @@ -91,6 +91,9 @@ typedef struct _GstMatroskaDemux { /* reverse playback */ GArray *seek_index; gint seek_entry; + + /* gap handling */ + guint64 max_gap_time; } GstMatroskaDemux; typedef struct _GstMatroskaDemuxClass { diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c index ed98df1162..2398d78e5e 100644 --- a/gst/matroska/matroska-mux.c +++ b/gst/matroska/matroska-mux.c @@ -391,6 +391,8 @@ gst_matroska_mux_finalize (GObject * object) { GstMatroskaMux *mux = GST_MATROSKA_MUX (object); + gst_event_replace (&mux->force_key_unit_event, NULL); + gst_object_unref (mux->collect); gst_object_unref (mux->ebml_write); if (mux->writing_app) @@ -662,6 +664,17 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event) gst_event_unref (event); event = NULL; break; + case GST_EVENT_CUSTOM_DOWNSTREAM:{ + const GstStructure *structure; + + structure = gst_event_get_structure (event); + if (gst_structure_has_name (structure, "GstForceKeyUnit")) { + gst_event_replace (&mux->force_key_unit_event, NULL); + mux->force_key_unit_event = event; + event = NULL; + } + break; + } default: break; } @@ -2692,13 +2705,20 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) } if (mux->cluster) { - /* start a new cluster at every keyframe or when we may be reaching the - * limit of the relative timestamp */ + /* start a new cluster at every keyframe, at every GstForceKeyUnit event, + * or when we may be reaching the limit of the relative timestamp */ if (mux->cluster_time + mux->max_cluster_duration < GST_BUFFER_TIMESTAMP (buf) - || is_video_keyframe) { + || is_video_keyframe || mux->force_key_unit_event) { if (!mux->streamable) gst_ebml_write_master_finish (ebml, mux->cluster); + + /* Forward the GstForceKeyUnit event after finishing the cluster */ + if (mux->force_key_unit_event) { + gst_pad_push_event (mux->srcpad, mux->force_key_unit_event); + mux->force_key_unit_event = NULL; + } + mux->prev_cluster_size = ebml->pos - mux->cluster_pos; mux->cluster_pos = ebml->pos; gst_ebml_write_set_cache (ebml, 0x20); diff --git a/gst/matroska/matroska-mux.h b/gst/matroska/matroska-mux.h index e5074a7c08..73bdc09ac3 100644 --- a/gst/matroska/matroska-mux.h +++ b/gst/matroska/matroska-mux.h @@ -126,6 +126,9 @@ typedef struct _GstMatroskaMux { cluster_pos, prev_cluster_size; + /* GstForceKeyUnit event */ + GstEvent *force_key_unit_event; + } GstMatroskaMux; typedef struct _GstMatroskaMuxClass { diff --git a/gst/matroska/matroska-read-common.c b/gst/matroska/matroska-read-common.c index 32b513e12b..672dcc3fc6 100644 --- a/gst/matroska/matroska-read-common.c +++ b/gst/matroska/matroska-read-common.c @@ -1639,14 +1639,12 @@ gst_matroska_read_common_peek_bytes (GstMatroskaReadCommon * common, guint64 return GST_FLOW_OK; } -static const guint8 * -gst_matroska_read_common_peek_pull (GstMatroskaReadCommon * common, guint peek) +static GstFlowReturn +gst_matroska_read_common_peek_pull (GstMatroskaReadCommon * common, guint peek, + guint8 ** data) { - guint8 *data = NULL; - - gst_matroska_read_common_peek_bytes (common, common->offset, peek, NULL, - &data); - return data; + return gst_matroska_read_common_peek_bytes (common, common->offset, peek, + NULL, data); } GstFlowReturn diff --git a/gst/rtp/gstrtph264depay.c b/gst/rtp/gstrtph264depay.c index 35078f94d8..c3c08ff822 100644 --- a/gst/rtp/gstrtph264depay.c +++ b/gst/rtp/gstrtph264depay.c @@ -501,7 +501,7 @@ gst_rtp_h264_complete_au (GstRtpH264Depay * rtph264depay, * so downstream waiting for keyframe can pick up at SPS/PPS/IDR */ #define NAL_TYPE_IS_KEY(nt) (((nt) == 5) || ((nt) == 7) || ((nt) == 8)) -static gboolean +static GstBuffer * gst_rtp_h264_depay_handle_nal (GstRtpH264Depay * rtph264depay, GstBuffer * nal, GstClockTime in_timestamp, gboolean marker) { @@ -584,11 +584,9 @@ gst_rtp_h264_depay_handle_nal (GstRtpH264Depay * rtph264depay, GstBuffer * nal, GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); else GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); - - gst_base_rtp_depayload_push (depayload, outbuf); } - return TRUE; + return outbuf; /* ERRORS */ short_nal: @@ -596,12 +594,13 @@ short_nal: GST_WARNING_OBJECT (depayload, "dropping short NAL"); gst_buffer_unmap (nal, data, size); gst_buffer_unref (nal); - return FALSE; + return NULL; } } -static void -gst_rtp_h264_push_fragmentation_unit (GstRtpH264Depay * rtph264depay) +static GstBuffer * +gst_rtp_h264_push_fragmentation_unit (GstRtpH264Depay * rtph264depay, + gboolean send) { guint outsize; guint8 *outdata; @@ -624,17 +623,26 @@ gst_rtp_h264_push_fragmentation_unit (GstRtpH264Depay * rtph264depay) } gst_buffer_unmap (outbuf, outdata, -1); - gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, - rtph264depay->fu_timestamp, rtph264depay->fu_marker); - rtph264depay->current_fu_type = 0; + + if (send) { + outbuf = gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, + rtph264depay->fu_timestamp, rtph264depay->fu_marker); + if (outbuf) + gst_base_rtp_depayload_push (GST_BASE_RTP_DEPAYLOAD (rtph264depay), + outbuf); + return NULL; + } else { + return gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, + rtph264depay->fu_timestamp, rtph264depay->fu_marker); + } } static GstBuffer * gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) { GstRtpH264Depay *rtph264depay; - GstBuffer *outbuf; + GstBuffer *outbuf = NULL; guint8 nal_unit_type; GstRTPBuffer rtp = { NULL }; @@ -692,7 +700,7 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) * when the FU ended) and send out what we gathered thusfar */ if (G_UNLIKELY (rtph264depay->current_fu_type != 0 && nal_unit_type != rtph264depay->current_fu_type)) - gst_rtp_h264_push_fragmentation_unit (rtph264depay); + gst_rtp_h264_push_fragmentation_unit (rtph264depay, TRUE); switch (nal_unit_type) { case 0: @@ -755,7 +763,8 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) outsize = gst_adapter_available (rtph264depay->adapter); outbuf = gst_adapter_take_buffer (rtph264depay->adapter, outsize); - gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, timestamp, marker); + outbuf = gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, timestamp, + marker); break; } case 26: @@ -798,7 +807,7 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) * Assume that the remote payloader is buggy (doesn't set the end * bit) and send out what we've gathered thusfar */ if (G_UNLIKELY (rtph264depay->current_fu_type != 0)) - gst_rtp_h264_push_fragmentation_unit (rtph264depay); + gst_rtp_h264_push_fragmentation_unit (rtph264depay, TRUE); rtph264depay->current_fu_type = nal_unit_type; rtph264depay->fu_timestamp = timestamp; @@ -844,11 +853,12 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) gst_adapter_push (rtph264depay->adapter, outbuf); } + outbuf = NULL; rtph264depay->fu_marker = marker; /* if NAL unit ends, flush the adapter */ if (E) - gst_rtp_h264_push_fragmentation_unit (rtph264depay); + outbuf = gst_rtp_h264_push_fragmentation_unit (rtph264depay, FALSE); break; } default: @@ -872,14 +882,15 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) memcpy (outdata + sizeof (sync_bytes), payload, nalu_size); gst_buffer_unmap (outbuf, outdata, outsize); - gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, timestamp, marker); + outbuf = gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, timestamp, + marker); break; } } gst_rtp_buffer_unmap (&rtp); } - return NULL; + return outbuf; /* ERRORS */ undefined_type: diff --git a/gst/rtpmanager/gstrtpbin.c b/gst/rtpmanager/gstrtpbin.c index 88e6f6b2f6..df925df822 100644 --- a/gst/rtpmanager/gstrtpbin.c +++ b/gst/rtpmanager/gstrtpbin.c @@ -213,6 +213,9 @@ struct _GstRtpBinPrivate gint shutdown; gboolean autoremove; + + /* UNIX (ntp) time of last SR sync used */ + guint64 last_unix; }; /* signals and args */ @@ -245,6 +248,8 @@ enum #define DEFAULT_AUTOREMOVE FALSE #define DEFAULT_BUFFER_MODE RTP_JITTER_BUFFER_MODE_SLAVE #define DEFAULT_USE_PIPELINE_CLOCK FALSE +#define DEFAULT_RTCP_SYNC GST_RTP_BIN_RTCP_SYNC_ALWAYS +#define DEFAULT_RTCP_SYNC_INTERVAL 0 enum { @@ -254,12 +259,39 @@ enum PROP_DO_LOST, PROP_IGNORE_PT, PROP_NTP_SYNC, + PROP_RTCP_SYNC, + PROP_RTCP_SYNC_INTERVAL, PROP_AUTOREMOVE, PROP_BUFFER_MODE, PROP_USE_PIPELINE_CLOCK, PROP_LAST }; +enum +{ + GST_RTP_BIN_RTCP_SYNC_ALWAYS, + GST_RTP_BIN_RTCP_SYNC_INITIAL, + GST_RTP_BIN_RTCP_SYNC_RTP +}; + +#define GST_RTP_BIN_RTCP_SYNC_TYPE (gst_rtp_bin_rtcp_sync_get_type()) +static GType +gst_rtp_bin_rtcp_sync_get_type (void) +{ + static GType rtcp_sync_type = 0; + static const GEnumValue rtcp_sync_types[] = { + {GST_RTP_BIN_RTCP_SYNC_ALWAYS, "always", "always"}, + {GST_RTP_BIN_RTCP_SYNC_INITIAL, "initial", "initial"}, + {GST_RTP_BIN_RTCP_SYNC_RTP, "rtp-info", "rtp-info"}, + {0, NULL, NULL}, + }; + + if (!rtcp_sync_type) { + rtcp_sync_type = g_enum_register_static ("GstRTCPSync", rtcp_sync_types); + } + return rtcp_sync_type; +} + /* helper objects */ typedef struct _GstRtpBinSession GstRtpBinSession; typedef struct _GstRtpBinStream GstRtpBinStream; @@ -310,6 +342,9 @@ struct _GstRtpBinStream gboolean have_sync; /* mapping to local RTP and NTP time */ gint64 rt_delta; + gint64 rtp_delta; + /* base rtptime in gst time */ + gint64 clock_base; }; #define GST_RTP_SESSION_LOCK(sess) g_mutex_lock ((sess)->lock) @@ -775,6 +810,8 @@ gst_rtp_bin_reset_sync (GstRtpBin * rtpbin) * lip-sync */ stream->have_sync = FALSE; stream->rt_delta = 0; + stream->rtp_delta = 0; + stream->clock_base = -100 * GST_SECOND; } } GST_RTP_BIN_UNLOCK (rtpbin); @@ -979,7 +1016,8 @@ stream_set_ts_offset (GstRtpBin * bin, GstRtpBinStream * stream, static void gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len, guint8 * data, guint64 ntptime, guint64 last_extrtptime, - guint64 base_rtptime, guint64 base_time, guint clock_rate) + guint64 base_rtptime, guint64 base_time, guint clock_rate, + gint64 rtp_clock_base) { GstRtpBinClient *client; gboolean created; @@ -1014,6 +1052,19 @@ gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len, stream->ssrc, client, client->cname); } + if (!GST_CLOCK_TIME_IS_VALID (last_extrtptime)) { + GST_DEBUG_OBJECT (bin, "invalidated sync data"); + if (bin->rtcp_sync == GST_RTP_BIN_RTCP_SYNC_RTP) { + /* we don't need that data, so carry on, + * but make some values look saner */ + last_extrtptime = base_rtptime; + } else { + /* nothing we can do with this data in this case */ + GST_DEBUG_OBJECT (bin, "bailing out"); + return; + } + } + /* Take the extended rtptime we found in the SR packet and map it to the * local rtptime. The local rtp time is used to construct timestamps on the * buffers so we will calculate what running_time corresponds to the RTP @@ -1022,8 +1073,9 @@ gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len, GST_DEBUG_OBJECT (bin, "base %" G_GUINT64_FORMAT ", extrtptime %" G_GUINT64_FORMAT - ", local RTP %" G_GUINT64_FORMAT ", clock-rate %d", base_rtptime, - last_extrtptime, local_rtp, clock_rate); + ", local RTP %" G_GUINT64_FORMAT ", clock-rate %d, " + "clock-base %" G_GINT64_FORMAT, base_rtptime, + last_extrtptime, local_rtp, clock_rate, rtp_clock_base); /* calculate local RTP time in gstreamer timestamp, we essentially perform the * same conversion that a jitterbuffer would use to convert an rtp timestamp @@ -1070,8 +1122,10 @@ gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len, stream->rt_delta = rtdiff - ntpdiff; stream_set_ts_offset (bin, stream, stream->rt_delta); - } else if (client->nstreams > 1) { - gint64 min; + } else { + gint64 min, rtp_min, clock_base = stream->clock_base; + gboolean all_sync, use_rtp; + gboolean rtcp_sync = g_atomic_int_get (&bin->rtcp_sync); /* calculate delta between server and receiver. last_unix is created by * converting the ntptime in the last SR packet to a gstreamer timestamp. This @@ -1089,19 +1143,114 @@ gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len, * latencies). * The stream that has the smallest diff is selected as the reference stream, * all other streams will have a positive offset to this difference. */ - min = G_MAXINT64; + + /* some alternative setting allow ignoring RTCP as much as possible, + * for servers generating bogus ntp timeline */ + min = rtp_min = G_MAXINT64; + use_rtp = FALSE; + if (rtcp_sync == GST_RTP_BIN_RTCP_SYNC_RTP) { + guint64 ext_base; + + use_rtp = TRUE; + /* signed version for convienience */ + clock_base = base_rtptime; + /* deal with possible wrap-around */ + ext_base = base_rtptime; + rtp_clock_base = gst_rtp_buffer_ext_timestamp (&ext_base, rtp_clock_base); + /* sanity check; base rtp and provided clock_base should be close */ + if (rtp_clock_base >= clock_base) { + if (rtp_clock_base - clock_base < 10 * clock_rate) { + rtp_clock_base = base_time + + gst_util_uint64_scale_int (rtp_clock_base - clock_base, + GST_SECOND, clock_rate); + } else { + use_rtp = FALSE; + } + } else { + if (clock_base - rtp_clock_base < 10 * clock_rate) { + rtp_clock_base = base_time - + gst_util_uint64_scale_int (clock_base - rtp_clock_base, + GST_SECOND, clock_rate); + } else { + use_rtp = FALSE; + } + } + /* warn and bail for clarity out if no sane values */ + if (!use_rtp) { + GST_WARNING_OBJECT (bin, "unable to sync to provided rtptime"); + return; + } + /* store to track changes */ + clock_base = rtp_clock_base; + /* generate a fake as before, + * now equating rtptime obtained from RTP-Info, + * where the large time represent the otherwise irrelevant npt/ntp time */ + stream->rtp_delta = (GST_SECOND << 28) - rtp_clock_base; + } + for (walk = client->streams; walk; walk = g_slist_next (walk)) { GstRtpBinStream *ostream = (GstRtpBinStream *) walk->data; - if (!ostream->have_sync) + if (!ostream->have_sync) { + all_sync = FALSE; continue; + } + + /* change in current stream's base from previously init'ed value + * leads to reset of all stream's base */ + if (stream != ostream && stream->clock_base >= 0 && + (stream->clock_base != clock_base)) { + GST_DEBUG_OBJECT (bin, "reset upon clock base change"); + ostream->clock_base = -100 * GST_SECOND; + ostream->rtp_delta = 0; + } if (ostream->rt_delta < min) min = ostream->rt_delta; + if (ostream->rtp_delta < rtp_min) + rtp_min = ostream->rtp_delta; } - GST_DEBUG_OBJECT (bin, "client %p min delta %" G_GINT64_FORMAT, client, - min); + /* arrange to re-sync for each stream upon significant change, + * e.g. post-seek */ + all_sync = (stream->clock_base == clock_base); + stream->clock_base = clock_base; + + /* may need init performed above later on, but nothing more to do now */ + if (client->nstreams <= 1) + return; + + GST_DEBUG_OBJECT (bin, "client %p min delta %" G_GINT64_FORMAT + " all sync %d", client, min, all_sync); + GST_DEBUG_OBJECT (bin, "rtcp sync mode %d, use_rtp %d", rtcp_sync, use_rtp); + + switch (rtcp_sync) { + case GST_RTP_BIN_RTCP_SYNC_RTP: + if (!use_rtp) + break; + GST_DEBUG_OBJECT (bin, "using rtp generated reports; " + "client %p min rtp delta %" G_GINT64_FORMAT, client, rtp_min); + /* fall-through */ + case GST_RTP_BIN_RTCP_SYNC_INITIAL: + /* if all have been synced already, do not bother further */ + if (all_sync) { + GST_DEBUG_OBJECT (bin, "all streams already synced; done"); + return; + } + break; + default: + break; + } + + /* bail out if we adjusted recently enough */ + if (all_sync && (last_unix - bin->priv->last_unix) < + bin->rtcp_sync_interval * GST_MSECOND) { + GST_DEBUG_OBJECT (bin, "discarding RTCP sender packet for sync; " + "previous sender info too recent " + "(previous UNIX %" G_GUINT64_FORMAT ")", bin->priv->last_unix); + return; + } + bin->priv->last_unix = last_unix; /* calculate offsets for each stream */ for (walk = client->streams; walk; walk = g_slist_next (walk)) { @@ -1116,7 +1265,10 @@ gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len, /* calculate offset to our reference stream, this should always give a * positive number. */ - ts_offset = ostream->rt_delta - min; + if (use_rtp) + ts_offset = ostream->rtp_delta - rtp_min; + else + ts_offset = ostream->rt_delta - min; stream_set_ts_offset (bin, ostream, ts_offset); } @@ -1149,6 +1301,7 @@ gst_rtp_bin_handle_sync (GstElement * jitterbuffer, GstStructure * s, guint64 base_rtptime; guint64 base_time; guint clock_rate; + guint64 clock_base; guint64 extrtptime; GstBuffer *buffer; GstRTCPBuffer rtcp = { NULL }; @@ -1165,6 +1318,7 @@ gst_rtp_bin_handle_sync (GstElement * jitterbuffer, GstStructure * s, g_value_get_uint64 (gst_structure_get_value (s, "base-rtptime")); base_time = g_value_get_uint64 (gst_structure_get_value (s, "base-time")); clock_rate = g_value_get_uint (gst_structure_get_value (s, "clock-rate")); + clock_base = g_value_get_uint64 (gst_structure_get_value (s, "clock-base")); extrtptime = g_value_get_uint64 (gst_structure_get_value (s, "sr-ext-rtptime")); buffer = gst_value_get_buffer (gst_structure_get_value (s, "sr-buffer")); @@ -1220,7 +1374,8 @@ gst_rtp_bin_handle_sync (GstElement * jitterbuffer, GstStructure * s, GST_RTP_BIN_LOCK (bin); /* associate the stream to CNAME */ gst_rtp_bin_associate (bin, stream, len, data, - ntptime, extrtptime, base_rtptime, base_time, clock_rate); + ntptime, extrtptime, base_rtptime, base_time, clock_rate, + clock_base); GST_RTP_BIN_UNLOCK (bin); } } @@ -1265,7 +1420,9 @@ create_stream (GstRtpBinSession * session, guint32 ssrc) stream->have_sync = FALSE; stream->rt_delta = 0; + stream->rtp_delta = 0; stream->percent = 100; + stream->clock_base = -100 * GST_SECOND; session->streams = g_slist_prepend (session->streams, stream); /* provide clock_rate to the jitterbuffer when needed */ @@ -1656,6 +1813,32 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass) "Synchronize received streams to the NTP clock", DEFAULT_NTP_SYNC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRtpBin::rtcp-sync: + * + * If not synchronizing (directly) to the NTP clock, determines how to sync + * the various streams. + * + * Since: 0.10.31 + */ + g_object_class_install_property (gobject_class, PROP_RTCP_SYNC, + g_param_spec_enum ("rtcp-sync", "RTCP Sync", + "Use of RTCP SR in synchronization", GST_RTP_BIN_RTCP_SYNC_TYPE, + DEFAULT_RTCP_SYNC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRtpBin::rtcp-sync-interval: + * + * Determines how often to sync streams using RTCP data. + * + * Since: 0.10.31 + */ + g_object_class_install_property (gobject_class, PROP_RTCP_SYNC_INTERVAL, + g_param_spec_uint ("rtcp-sync-interval", "RTCP Sync Interval", + "RTCP SR interval synchronization (ms) (0 = always)", + 0, G_MAXUINT, DEFAULT_RTCP_SYNC_INTERVAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_rtp_bin_change_state); gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_rtp_bin_request_new_pad); @@ -1706,6 +1889,8 @@ gst_rtp_bin_init (GstRtpBin * rtpbin) rtpbin->do_lost = DEFAULT_DO_LOST; rtpbin->ignore_pt = DEFAULT_IGNORE_PT; rtpbin->ntp_sync = DEFAULT_NTP_SYNC; + rtpbin->rtcp_sync = DEFAULT_RTCP_SYNC; + rtpbin->rtcp_sync_interval = DEFAULT_RTCP_SYNC_INTERVAL; rtpbin->priv->autoremove = DEFAULT_AUTOREMOVE; rtpbin->buffer_mode = DEFAULT_BUFFER_MODE; rtpbin->use_pipeline_clock = DEFAULT_USE_PIPELINE_CLOCK; @@ -1821,6 +2006,12 @@ gst_rtp_bin_set_property (GObject * object, guint prop_id, case PROP_NTP_SYNC: rtpbin->ntp_sync = g_value_get_boolean (value); break; + case PROP_RTCP_SYNC: + g_atomic_int_set (&rtpbin->rtcp_sync, g_value_get_enum (value)); + break; + case PROP_RTCP_SYNC_INTERVAL: + rtpbin->rtcp_sync_interval = g_value_get_uint (value); + break; case PROP_IGNORE_PT: rtpbin->ignore_pt = g_value_get_boolean (value); break; @@ -1883,6 +2074,12 @@ gst_rtp_bin_get_property (GObject * object, guint prop_id, case PROP_NTP_SYNC: g_value_set_boolean (value, rtpbin->ntp_sync); break; + case PROP_RTCP_SYNC: + g_value_set_enum (value, g_atomic_int_get (&rtpbin->rtcp_sync)); + break; + case PROP_RTCP_SYNC_INTERVAL: + g_value_set_uint (value, rtpbin->rtcp_sync_interval); + break; case PROP_AUTOREMOVE: g_value_set_boolean (value, rtpbin->priv->autoremove); break; @@ -2020,6 +2217,7 @@ gst_rtp_bin_handle_message (GstBin * bin, GstMessage * message) now = gst_clock_get_time (clock); base_time = gst_element_get_base_time (GST_ELEMENT_CAST (bin)); running_time = now - base_time; + gst_object_unref (clock); } GST_DEBUG_OBJECT (bin, "running time now %" GST_TIME_FORMAT, @@ -2108,6 +2306,7 @@ gst_rtp_bin_change_state (GstElement * element, GstStateChange transition) case GST_STATE_CHANGE_NULL_TO_READY: break; case GST_STATE_CHANGE_READY_TO_PAUSED: + priv->last_unix = 0; GST_LOG_OBJECT (rtpbin, "clearing shutdown flag"); g_atomic_int_set (&priv->shutdown, 0); break; diff --git a/gst/rtpmanager/gstrtpbin.h b/gst/rtpmanager/gstrtpbin.h index 74aaac2453..a9157871d3 100644 --- a/gst/rtpmanager/gstrtpbin.h +++ b/gst/rtpmanager/gstrtpbin.h @@ -50,6 +50,8 @@ struct _GstRtpBin { gboolean do_lost; gboolean ignore_pt; gboolean ntp_sync; + gint rtcp_sync; + guint rtcp_sync_interval; RTPJitterBufferMode buffer_mode; gboolean buffering; gboolean use_pipeline_clock; diff --git a/gst/rtpmanager/gstrtpjitterbuffer.c b/gst/rtpmanager/gstrtpjitterbuffer.c index 3584d145bb..a9f6913ff4 100644 --- a/gst/rtpmanager/gstrtpjitterbuffer.c +++ b/gst/rtpmanager/gstrtpjitterbuffer.c @@ -664,6 +664,11 @@ gst_rtp_jitter_buffer_clear_pt_map (GstRtpJitterBuffer * jitterbuffer) JBUF_LOCK (priv); priv->clock_rate = -1; + /* do not clear current content, but refresh state for new arrival */ + GST_DEBUG_OBJECT (jitterbuffer, "reset jitterbuffer"); + rtp_jitter_buffer_reset_skew (priv->jbuf); + priv->last_popped_seqnum = -1; + priv->next_seqnum = -1; JBUF_UNLOCK (priv); } @@ -1953,6 +1958,7 @@ gst_rtp_jitter_buffer_chain_rtcp (GstPad * pad, GstBuffer * buffer) guint32 rtptime; gboolean drop = FALSE; GstRTCPBuffer rtcp = { NULL }; + guint64 clock_base; jitterbuffer = GST_RTP_JITTER_BUFFER (gst_pad_get_parent (pad)); @@ -1989,9 +1995,12 @@ gst_rtp_jitter_buffer_chain_rtcp (GstPad * pad, GstBuffer * buffer) rtp_jitter_buffer_get_sync (priv->jbuf, &base_rtptime, &base_time, &clock_rate, &last_rtptime); + clock_base = priv->clock_base; + GST_DEBUG_OBJECT (jitterbuffer, "ext SR %" G_GUINT64_FORMAT ", base %" - G_GUINT64_FORMAT ", clock-rate %" G_GUINT32_FORMAT, - ext_rtptime, base_rtptime, clock_rate); + G_GUINT64_FORMAT ", clock-rate %" G_GUINT32_FORMAT + ", clock-base %" G_GUINT64_FORMAT, + ext_rtptime, base_rtptime, clock_rate, clock_base); if (base_rtptime == -1 || clock_rate == -1 || base_time == -1) { GST_DEBUG_OBJECT (jitterbuffer, "dropping, no RTP values"); @@ -2009,8 +2018,12 @@ gst_rtp_jitter_buffer_chain_rtcp (GstPad * pad, GstBuffer * buffer) diff = ext_rtptime - last_rtptime; /* if bigger than 1 second, we drop it */ if (diff > clock_rate) { - GST_DEBUG_OBJECT (jitterbuffer, "dropping, too far ahead"); - drop = TRUE; + GST_DEBUG_OBJECT (jitterbuffer, "too far ahead"); + /* should drop this, but some RTSP servers end up with bogus + * way too ahead RTCP packet when repeated PAUSE/PLAY, + * so still trigger rptbin sync but invalidate RTCP data + * (sync might use other methods) */ + ext_rtptime = -1; } GST_DEBUG_OBJECT (jitterbuffer, "ext last %" G_GUINT64_FORMAT ", diff %" G_GUINT64_FORMAT, last_rtptime, diff); @@ -2026,6 +2039,7 @@ gst_rtp_jitter_buffer_chain_rtcp (GstPad * pad, GstBuffer * buffer) "base-rtptime", G_TYPE_UINT64, base_rtptime, "base-time", G_TYPE_UINT64, base_time, "clock-rate", G_TYPE_UINT, clock_rate, + "clock-base", G_TYPE_UINT64, clock_base, "sr-ext-rtptime", G_TYPE_UINT64, ext_rtptime, "sr-buffer", GST_TYPE_BUFFER, buffer, NULL); diff --git a/gst/rtpmanager/gstrtpsession.c b/gst/rtpmanager/gstrtpsession.c index 4b112fa643..1244783df6 100644 --- a/gst/rtpmanager/gstrtpsession.c +++ b/gst/rtpmanager/gstrtpsession.c @@ -835,6 +835,10 @@ rtcp_thread (GstRtpSession * rtpsession) session = rtpsession->priv->session; + GST_DEBUG_OBJECT (rtpsession, "starting at %" GST_TIME_FORMAT, + GST_TIME_ARGS (current_time)); + session->start_time = current_time; + while (!rtpsession->priv->stop_thread) { GstClockReturn res; diff --git a/gst/rtpmanager/rtpjitterbuffer.c b/gst/rtpmanager/rtpjitterbuffer.c index 73b09d4a6c..a02145b658 100644 --- a/gst/rtpmanager/rtpjitterbuffer.c +++ b/gst/rtpmanager/rtpjitterbuffer.c @@ -640,6 +640,26 @@ rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, GstBuffer * buf, } rtptime = gst_rtp_buffer_get_timestamp (&rtp); + /* rtp time jumps are checked for during skew calculation, but bypassed + * in other mode, so mind those here and reset jb if needed. + * Only reset if valid input time, which is likely for UDP input + * where we expect this might happen due to async thread effects + * (in seek and state change cycles), but not so much for TCP input */ + if (GST_CLOCK_TIME_IS_VALID (time) && + jbuf->mode != RTP_JITTER_BUFFER_MODE_SLAVE && + jbuf->base_time != -1 && jbuf->last_rtptime != -1) { + GstClockTime ext_rtptime = jbuf->ext_rtptime; + + ext_rtptime = gst_rtp_buffer_ext_timestamp (&ext_rtptime, rtptime); + if (ext_rtptime > jbuf->last_rtptime + 3 * clock_rate || + ext_rtptime + 3 * clock_rate < jbuf->last_rtptime) { + /* reset even if we don't have valid incoming time; + * still better than producing possibly very bogus output timestamp */ + GST_WARNING ("rtp delta too big, reset skew"); + rtp_jitter_buffer_reset_skew (jbuf); + } + } + switch (jbuf->mode) { case RTP_JITTER_BUFFER_MODE_NONE: case RTP_JITTER_BUFFER_MODE_BUFFER: diff --git a/gst/rtpmanager/rtpsession.c b/gst/rtpmanager/rtpsession.c index 9e4163cb95..89dafb88e7 100644 --- a/gst/rtpmanager/rtpsession.c +++ b/gst/rtpmanager/rtpsession.c @@ -543,6 +543,8 @@ rtp_session_init (RTPSession * sess) sess->source->internal = TRUE; sess->stats.active_sources++; INIT_AVG (sess->stats.avg_rtcp_packet_size, 100); + sess->source->stats.prev_rtcptime = 0; + sess->source->stats.last_rtcptime = 1; rtp_stats_set_min_interval (&sess->stats, (gdouble) DEFAULT_RTCP_MIN_INTERVAL / GST_SECOND); @@ -664,6 +666,12 @@ rtp_session_set_property (GObject * object, guint prop_id, case PROP_RTCP_MIN_INTERVAL: rtp_stats_set_min_interval (&sess->stats, (gdouble) g_value_get_uint64 (value) / GST_SECOND); + /* trigger reconsideration */ + RTP_SESSION_LOCK (sess); + sess->next_rtcp_check_time = 0; + RTP_SESSION_UNLOCK (sess); + if (sess->callbacks.reconsider) + sess->callbacks.reconsider (sess, sess->reconsider_user_data); break; case PROP_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD: sess->rtcp_immediate_feedback_threshold = g_value_get_uint (value); @@ -2767,11 +2775,35 @@ session_cleanup (const gchar * key, RTPSource * source, ReportData * data) gboolean sendertimeout = FALSE; gboolean is_sender, is_active; RTPSession *sess = data->sess; - GstClockTime interval; + GstClockTime interval, binterval; + GstClockTime btime; is_sender = RTP_SOURCE_IS_SENDER (source); is_active = RTP_SOURCE_IS_ACTIVE (source); + /* our own rtcp interval may have been forced low by secondary configuration, + * while sender side may still operate with higher interval, + * so do not just take our interval to decide on timing out sender, + * but take (if data->interval <= 5 * GST_SECOND): + * interval = CLAMP (sender_interval, data->interval, 5 * GST_SECOND) + * where sender_interval is difference between last 2 received RTCP reports + */ + if (data->interval >= 5 * GST_SECOND || (source == sess->source)) { + binterval = data->interval; + } else { + GST_LOG ("prev_rtcp %" GST_TIME_FORMAT ", last_rtcp %" GST_TIME_FORMAT, + GST_TIME_ARGS (source->stats.prev_rtcptime), + GST_TIME_ARGS (source->stats.last_rtcptime)); + /* if not received enough yet, fallback to larger default */ + if (source->stats.last_rtcptime > source->stats.prev_rtcptime) + binterval = source->stats.last_rtcptime - source->stats.prev_rtcptime; + else + binterval = 5 * GST_SECOND; + binterval = CLAMP (binterval, data->interval, 5 * GST_SECOND); + } + GST_LOG ("timeout base interval %" GST_TIME_FORMAT, + GST_TIME_ARGS (binterval)); + /* check for our own source, we don't want to delete our own source. */ if (!(source == sess->source)) { if (source->received_bye) { @@ -2786,11 +2818,13 @@ session_cleanup (const gchar * key, RTPSource * source, ReportData * data) } /* sources that were inactive for more than 5 times the deterministic reporting * interval get timed out. the min timeout is 5 seconds. */ - if (data->current_time > source->last_activity) { - interval = MAX (data->interval * 5, 5 * GST_SECOND); - if (data->current_time - source->last_activity > interval) { + /* mind old time that might pre-date last time going to PLAYING */ + btime = MAX (source->last_activity, sess->start_time); + if (data->current_time > btime) { + interval = MAX (binterval * 5, 5 * GST_SECOND); + if (data->current_time - btime > interval) { GST_DEBUG ("removing timeout source %08x, last %" GST_TIME_FORMAT, - source->ssrc, GST_TIME_ARGS (source->last_activity)); + source->ssrc, GST_TIME_ARGS (btime)); remove = TRUE; } } @@ -2799,12 +2833,13 @@ session_cleanup (const gchar * key, RTPSource * source, ReportData * data) /* senders that did not send for a long time become a receiver, this also * holds for our own source. */ if (is_sender) { - if (data->current_time > source->last_rtp_activity) { - interval = MAX (data->interval * 2, 5 * GST_SECOND); - if (data->current_time - source->last_rtp_activity > interval) { + /* mind old time that might pre-date last time going to PLAYING */ + btime = MAX (source->last_rtp_activity, sess->start_time); + if (data->current_time > btime) { + interval = MAX (binterval * 2, 5 * GST_SECOND); + if (data->current_time - btime > interval) { GST_DEBUG ("sender source %08x timed out and became receiver, last %" - GST_TIME_FORMAT, source->ssrc, - GST_TIME_ARGS (source->last_rtp_activity)); + GST_TIME_FORMAT, source->ssrc, GST_TIME_ARGS (btime)); source->is_sender = FALSE; sess->stats.sender_sources--; sendertimeout = TRUE; diff --git a/gst/rtpmanager/rtpsession.h b/gst/rtpmanager/rtpsession.h index 30b74bb5db..6b827f60a1 100644 --- a/gst/rtpmanager/rtpsession.h +++ b/gst/rtpmanager/rtpsession.h @@ -209,6 +209,7 @@ struct _RTPSession { GstClockTime next_rtcp_check_time; GstClockTime last_rtcp_send_time; + GstClockTime start_time; gboolean first_rtcp; gboolean allow_early; diff --git a/gst/rtpmanager/rtpsource.c b/gst/rtpmanager/rtpsource.c index 2912708c23..15ba4f8706 100644 --- a/gst/rtpmanager/rtpsource.c +++ b/gst/rtpmanager/rtpsource.c @@ -1368,6 +1368,9 @@ rtp_source_process_sr (RTPSource * src, GstClockTime time, guint64 ntptime, /* make current */ src->stats.curr_sr = curridx; + + src->stats.prev_rtcptime = src->stats.last_rtcptime; + src->stats.last_rtcptime = time; } /** diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c index dec7c2518f..17acf81048 100644 --- a/gst/rtsp/gstrtspsrc.c +++ b/gst/rtsp/gstrtspsrc.c @@ -128,6 +128,13 @@ enum LAST_SIGNAL }; +enum _GstRtspSrcRtcpSyncMode +{ + RTCP_SYNC_ALWAYS, + RTCP_SYNC_INITIAL, + RTCP_SYNC_RTP +}; + enum _GstRtspSrcBufferMode { BUFFER_MODE_NONE, @@ -1651,7 +1658,7 @@ cleanup: } static void -gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush) +gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing) { GstEvent *event; gint cmd, i; @@ -1667,9 +1674,12 @@ gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush) state = GST_STATE_PAUSED; } else { event = gst_event_new_flush_stop (TRUE); - GST_DEBUG_OBJECT (src, "stop flush"); + GST_DEBUG_OBJECT (src, "stop flush; playing %d", playing); cmd = CMD_LOOP; - state = GST_STATE_PLAYING; + if (playing) + state = GST_STATE_PLAYING; + else + state = GST_STATE_PAUSED; clock = gst_element_get_clock (GST_ELEMENT_CAST (src)); if (clock) { base_time = gst_clock_get_time (clock); @@ -1818,7 +1828,7 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) * blocking in preroll). */ if (flush) { GST_DEBUG_OBJECT (src, "starting flush"); - gst_rtspsrc_flush (src, TRUE); + gst_rtspsrc_flush (src, TRUE, FALSE); } else { if (src->task) { gst_task_pause (src->task); @@ -1867,7 +1877,7 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) if (flush) { /* if we started flush, we stop now */ GST_DEBUG_OBJECT (src, "stopping flush"); - gst_rtspsrc_flush (src, FALSE); + gst_rtspsrc_flush (src, FALSE, playing); } /* now we did the seek and can activate the new segment values */ @@ -4052,6 +4062,8 @@ gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, gboolean flush) /* start new request */ gst_rtspsrc_loop_start_cmd (src, cmd); + GST_DEBUG_OBJECT (src, "sending cmd %d", cmd); + GST_OBJECT_LOCK (src); old = src->loop_cmd; if (old != CMD_WAIT) { @@ -5900,6 +5912,47 @@ gst_rtspsrc_parse_rtpinfo (GstRTSPSrc * src, gchar * rtpinfo) 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) { @@ -6108,6 +6161,12 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) &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 */ @@ -6401,10 +6460,6 @@ gst_rtspsrc_thread (GstRTSPSrc * src) switch (cmd) { case CMD_OPEN: - src->cur_protocols = src->protocols; - /* first attempt, don't ignore timeouts */ - src->ignore_timeout = FALSE; - src->open_error = FALSE; ret = gst_rtspsrc_open (src, TRUE); break; case CMD_PLAY: @@ -6524,6 +6579,11 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) 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, FALSE); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: diff --git a/gst/udp/gstudpnetutils.c b/gst/udp/gstudpnetutils.c index f488fcc418..bdbffbceca 100644 --- a/gst/udp/gstudpnetutils.c +++ b/gst/udp/gstudpnetutils.c @@ -374,7 +374,10 @@ gst_udp_parse_uri (const gchar * uristr, GstUDPUri * uri) gchar *location, *location_end; gchar *colptr; + /* consider no protocol to be udp:// */ protocol = gst_uri_get_protocol (uristr); + if (!protocol) + goto no_protocol; if (strcmp (protocol, "udp") != 0) goto wrong_protocol; g_free (protocol); @@ -425,6 +428,11 @@ gst_udp_parse_uri (const gchar * uristr, GstUDPUri * uri) return 0; /* ERRORS */ +no_protocol: + { + GST_ERROR ("error parsing uri %s: no protocol", uristr); + return -1; + } wrong_protocol: { GST_ERROR ("error parsing uri %s: wrong protocol (%s != udp)", uristr, diff --git a/sys/v4l2/gstv4l2object.c b/sys/v4l2/gstv4l2object.c index 7be11b8838..2e558bf454 100644 --- a/sys/v4l2/gstv4l2object.c +++ b/sys/v4l2/gstv4l2object.c @@ -2358,11 +2358,20 @@ get_fmt_failed: } set_fmt_failed: { - GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, SETTINGS, - (_("Device '%s' cannot capture at %dx%d"), - v4l2object->videodev, width, height), - ("Call to S_FMT failed for %" GST_FOURCC_FORMAT " @ %dx%d: %s", - GST_FOURCC_ARGS (pixelformat), width, height, g_strerror (errno))); + if (errno == EBUSY) { + GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, BUSY, + (_("Device '%s' is busy"), v4l2object->videodev), + ("Call to S_FMT failed for %" GST_FOURCC_FORMAT " @ %dx%d: %s", + GST_FOURCC_ARGS (pixelformat), width, height, + g_strerror (errno))); + } else { + GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, SETTINGS, + (_("Device '%s' cannot capture at %dx%d"), + v4l2object->videodev, width, height), + ("Call to S_FMT failed for %" GST_FOURCC_FORMAT " @ %dx%d: %s", + GST_FOURCC_ARGS (pixelformat), width, height, + g_strerror (errno))); + } return FALSE; } invalid_dimensions: diff --git a/sys/ximage/gstximagesrc.c b/sys/ximage/gstximagesrc.c index 60d8e8c7fb..b286cfbd7b 100644 --- a/sys/ximage/gstximagesrc.c +++ b/sys/ximage/gstximagesrc.c @@ -71,6 +71,8 @@ enum PROP_ENDX, PROP_ENDY, PROP_REMOTE, + PROP_XID, + PROP_XNAME, }; #define gst_ximage_src_parent_class parent_class @@ -105,6 +107,35 @@ gst_ximage_src_return_buf (GstXImageSrc * ximagesrc, GstBuffer * ximage) } } +static Window +gst_ximage_src_find_window (GstXImageSrc * src, Window root, const char *name) +{ + Window *children; + Window window = 0, root_return, parent_return; + unsigned int nchildren; + char *tmpname; + int n, status; + + status = XFetchName (src->xcontext->disp, root, &tmpname); + if (status && !strcmp (name, tmpname)) + return root; + + status = + XQueryTree (src->xcontext->disp, root, &root_return, &parent_return, + &children, &nchildren); + if (!status || !children) + return (Window) 0; + + for (n = 0; n < nchildren; ++n) { + window = gst_ximage_src_find_window (src, children[n], name); + if (window != 0) + break; + } + + XFree (children); + return window; +} + static gboolean gst_ximage_src_open_display (GstXImageSrc * s, const gchar * name) { @@ -125,8 +156,49 @@ gst_ximage_src_open_display (GstXImageSrc * s, const gchar * name) s->width = s->xcontext->width; s->height = s->xcontext->height; - /* Always capture root window, for now */ s->xwindow = s->xcontext->root; + if (s->xid != 0 || s->xname) { + int status; + XWindowAttributes attrs; + Window window; + + if (s->xid != 0) { + status = XGetWindowAttributes (s->xcontext->disp, s->xid, &attrs); + if (status) { + GST_DEBUG_OBJECT (s, "Found window XID %p", s->xid); + s->xwindow = s->xid; + goto window_found; + } else { + GST_WARNING_OBJECT (s, "Failed to get window %p attributes", s->xid); + } + } + + if (s->xname) { + GST_DEBUG_OBJECT (s, "Looking for window %s", s->xname); + window = gst_ximage_src_find_window (s, s->xcontext->root, s->xname); + if (window != 0) { + GST_DEBUG_OBJECT (s, "Found window named %s as %p, ", s->xname, window); + status = XGetWindowAttributes (s->xcontext->disp, window, &attrs); + if (status) { + s->xwindow = window; + goto window_found; + } else { + GST_WARNING_OBJECT (s, "Failed to get window %p attributes", window); + } + } + } + + GST_INFO_OBJECT (s, "Using root window"); + goto use_root_window; + + window_found: + g_assert (s->xwindow != 0); + s->width = attrs.width; + s->height = attrs.height; + GST_INFO_OBJECT (s, "Using default window %p, size of %dx%d", s->xwindow, + s->width, s->height); + } +use_root_window: #ifdef HAVE_XFIXES /* check if xfixes supported */ @@ -603,7 +675,8 @@ gst_ximage_src_ximage_get (GstXImageSrc * ximagesrc) } else #endif /* HAVE_XSHM */ { - GST_DEBUG_OBJECT (ximagesrc, "Retrieving screen using XGetImage"); + GST_DEBUG_OBJECT (ximagesrc, + "Retrieving screen using XGetImage, window %p", ximagesrc->xwindow); if (ximagesrc->remote) { XGetSubImage (ximagesrc->xcontext->disp, ximagesrc->xwindow, ximagesrc->startx, ximagesrc->starty, ximagesrc->width, @@ -847,6 +920,21 @@ gst_ximage_src_set_property (GObject * object, guint prop_id, case PROP_REMOTE: src->remote = g_value_get_boolean (value); break; + case PROP_XID: + if (src->xcontext != NULL) { + g_warning ("ximagesrc window ID must be set before opening display"); + break; + } + src->xid = g_value_get_uint64 (value); + break; + case PROP_XNAME: + if (src->xcontext != NULL) { + g_warning ("ximagesrc window name must be set before opening display"); + break; + } + g_free (src->xname); + src->xname = g_strdup (g_value_get_string (value)); + break; default: break; } @@ -890,6 +978,12 @@ gst_ximage_src_get_property (GObject * object, guint prop_id, GValue * value, case PROP_REMOTE: g_value_set_boolean (value, src->remote); break; + case PROP_XID: + g_value_set_uint64 (value, src->xid); + break; + case PROP_XNAME: + g_value_set_string (value, src->xname); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -928,6 +1022,7 @@ gst_ximage_src_finalize (GObject * object) if (src->xcontext) ximageutil_xcontext_clear (src->xcontext); + g_free (src->xname); g_mutex_free (src->pool_lock); g_mutex_free (src->x_lock); @@ -952,9 +1047,16 @@ gst_ximage_src_get_caps (GstBaseSrc * bs, GstCaps * filter) (s)->srcpad)); xcontext = s->xcontext; - - width = xcontext->width; - height = xcontext->height; + width = s->xcontext->width; + height = s->xcontext->height; + if (s->xwindow != 0) { + XWindowAttributes attrs; + int status = XGetWindowAttributes (s->xcontext->disp, s->xwindow, &attrs); + if (status) { + width = attrs.width; + height = attrs.height; + } + } /* property comments say 0 means right/bottom, means we can't capture the top left pixel alone */ @@ -1135,6 +1237,28 @@ gst_ximage_src_class_init (GstXImageSrcClass * klass) g_param_spec_boolean ("remote", "Remote dispay", "Whether the display is remote", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstXImageSrc:xid + * + * The XID of the window to capture. 0 for the root window (default). + * + * Since: 0.10.31 + **/ + g_object_class_install_property (gc, PROP_XID, + g_param_spec_uint64 ("xid", "Window XID", + "Window XID to capture from", 0, G_MAXUINT64, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstXImageSrc:xname + * + * The name of the window to capture, if any. + * + * Since: 0.10.31 + **/ + g_object_class_install_property (gc, PROP_XNAME, + g_param_spec_string ("xname", "Window name", + "Window name to capture from", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gst_element_class_set_details_simple (ec, "Ximage video source", "Source/Video", diff --git a/sys/ximage/gstximagesrc.h b/sys/ximage/gstximagesrc.h index 732e7af95e..a5f9d79201 100644 --- a/sys/ximage/gstximagesrc.h +++ b/sys/ximage/gstximagesrc.h @@ -56,6 +56,10 @@ struct _GstXImageSrc gchar *display_name; guint screen_num; + /* Window selection */ + guint64 xid; + gchar *xname; + /* Desired output framerate */ gint fps_n; gint fps_d; diff --git a/tests/check/elements/cmmldec.c b/tests/check/elements/cmmldec.c index 1ba8fb4a1b..a6b8559d5e 100644 --- a/tests/check/elements/cmmldec.c +++ b/tests/check/elements/cmmldec.c @@ -25,7 +25,7 @@ #include #define SINK_CAPS "text/x-cmml" -#define SRC_CAPS "text/x-cmml" +#define SRC_CAPS "text/x-cmml, encoded=(boolean)TRUE" #define IDENT_HEADER \ "CMML\x00\x00\x00\x00"\ diff --git a/tests/check/elements/cmmlenc.c b/tests/check/elements/cmmlenc.c index 4424a72a77..4b95d713ec 100644 --- a/tests/check/elements/cmmlenc.c +++ b/tests/check/elements/cmmlenc.c @@ -26,7 +26,7 @@ #include #define SINK_CAPS "text/x-cmml" -#define SRC_CAPS "text/x-cmml" +#define SRC_CAPS "text/x-cmml,encoded=(boolean)FALSE" #define IDENT_HEADER \ "CMML\x00\x00\x00\x00"\ diff --git a/tests/check/elements/flvmux.c b/tests/check/elements/flvmux.c index 6930ac71e7..fe10282898 100644 --- a/tests/check/elements/flvmux.c +++ b/tests/check/elements/flvmux.c @@ -50,6 +50,7 @@ static void mux_pcm_audio (guint num_buffers, guint repeat) { GstElement *src, *sink, *flvmux, *conv, *pipeline; + GstPad *sinkpad, *srcpad; gint counter; GST_LOG ("num_buffers = %u", num_buffers); @@ -83,25 +84,24 @@ mux_pcm_audio (guint num_buffers, guint repeat) fail_unless (gst_element_link (src, conv)); fail_unless (gst_element_link (flvmux, sink)); + /* now link the elements */ + sinkpad = gst_element_get_request_pad (flvmux, "audio"); + fail_unless (sinkpad != NULL, "Could not get audio request pad"); + + srcpad = gst_element_get_static_pad (conv, "src"); + fail_unless (srcpad != NULL, "Could not get audioconvert's source pad"); + + fail_unless_equals_int (gst_pad_link (srcpad, sinkpad), GST_PAD_LINK_OK); + + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + do { GstStateChangeReturn state_ret; GstMessage *msg; - GstPad *sinkpad, *srcpad; GST_LOG ("repeat=%d", repeat); - /* now link the elements */ - sinkpad = gst_element_get_request_pad (flvmux, "audio"); - fail_unless (sinkpad != NULL, "Could not get audio request pad"); - - srcpad = gst_element_get_static_pad (conv, "src"); - fail_unless (srcpad != NULL, "Could not get audioconvert's source pad"); - - fail_unless_equals_int (gst_pad_link (srcpad, sinkpad), GST_PAD_LINK_OK); - - gst_object_unref (srcpad); - gst_object_unref (sinkpad); - counter = 0; state_ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); diff --git a/tests/check/elements/qtmux.c b/tests/check/elements/qtmux.c index a70e34564d..9a8ede4b3b 100644 --- a/tests/check/elements/qtmux.c +++ b/tests/check/elements/qtmux.c @@ -43,6 +43,7 @@ static GstPad *mysrcpad, *mysinkpad; "rate = (int) 48000" #define VIDEO_CAPS_STRING "video/mpeg, " \ "mpegversion = (int) 4, " \ + "systemstream = (boolean) false, " \ "width = (int) 384, " \ "height = (int) 288, " \ "framerate = (fraction) 25/1" @@ -517,7 +518,7 @@ create_qtmux_profile (const gchar * variant) return cprof; } -GST_START_TEST (test_encodebin) +GST_START_TEST (test_encodebin_qtmux) { GstEncodingContainerProfile *cprof; GstElement *enc; @@ -553,6 +554,136 @@ GST_START_TEST (test_encodebin) GST_END_TEST; +/* Fake mp3 encoder for test */ +typedef GstElement TestMp3Enc; +typedef GstElementClass TestMp3EncClass; + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg, mpegversion=1, layer=[1,3]") + ); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int") + ); + +static GType test_mp3_enc_get_type (void); + +GST_BOILERPLATE (TestMp3Enc, test_mp3_enc, GstElement, GST_TYPE_ELEMENT); + +static void +test_mp3_enc_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_details_simple (element_class, "MPEG1 Audio Encoder", + "Codec/Encoder/Audio", "Pretends to encode mp3", "Foo Bar "); +} + +static void +test_mp3_enc_class_init (TestMp3EncClass * klass) +{ + /* doesn't actually need to do anything for this test */ +} + +static void +test_mp3_enc_init (TestMp3Enc * mp3enc, TestMp3EncClass * klass) +{ + GstPad *pad; + + pad = gst_pad_new_from_static_template (&sink_template, "sink"); + gst_element_add_pad (mp3enc, pad); + + pad = gst_pad_new_from_static_template (&src_template, "src"); + gst_element_add_pad (mp3enc, pad); +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "testmp3enc", GST_RANK_NONE, + test_mp3_enc_get_type ()); +} + +static GstEncodingContainerProfile * +create_mp4mux_profile (void) +{ + GstEncodingContainerProfile *cprof; + GstCaps *caps; + + caps = gst_caps_new_simple ("video/quicktime", + "variant", G_TYPE_STRING, "iso", NULL); + + cprof = gst_encoding_container_profile_new ("Name", "blah", caps, NULL); + gst_caps_unref (caps); + + caps = gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 1, + "layer", G_TYPE_INT, 3, "channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, + 44100, NULL); + gst_encoding_container_profile_add_profile (cprof, + GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (caps, NULL, NULL, + 1))); + gst_caps_unref (caps); + + return cprof; +} + +GST_START_TEST (test_encodebin_mp4mux) +{ + GstEncodingContainerProfile *cprof; + GstPluginFeature *feature; + GstElement *enc, *mux; + GstPad *pad; + + /* need a fake mp3 encoder because mp4 only accepts encoded formats */ + gst_plugin_register_static (GST_VERSION_MAJOR, GST_VERSION_MINOR, + "fakemp3enc", "fakemp3enc", plugin_init, VERSION, "LGPL", + "gst-plugins-good", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); + + feature = gst_default_registry_find_feature ("testmp3enc", + GST_TYPE_ELEMENT_FACTORY); + gst_plugin_feature_set_rank (feature, GST_RANK_PRIMARY + 100); + + enc = gst_element_factory_make ("encodebin", NULL); + if (enc == NULL) + return; + + /* Make sure encodebin finds mp4mux even though qtmux outputs a superset */ + cprof = create_mp4mux_profile (); + g_object_set (enc, "profile", cprof, NULL); + gst_encoding_profile_unref (cprof); + + /* should have created a pad after setting the profile */ + pad = gst_element_get_static_pad (enc, "audio_0"); + fail_unless (pad != NULL); + gst_object_unref (pad); + + mux = gst_bin_get_by_interface (GST_BIN (enc), GST_TYPE_TAG_SETTER); + fail_unless (mux != NULL); + { + GstElementFactory *f = gst_element_get_factory (mux); + + /* make sure we got mp4mux for variant=iso */ + GST_INFO ("muxer: %s", G_OBJECT_TYPE_NAME (mux)); + fail_unless_equals_string (GST_PLUGIN_FEATURE_NAME (f), "mp4mux"); + } + gst_object_unref (mux); + gst_object_unref (enc); + + gst_plugin_feature_set_rank (feature, GST_RANK_NONE); + gst_object_unref (feature); +} + +GST_END_TEST; + static Suite * qtmux_suite (void) { @@ -582,7 +713,8 @@ qtmux_suite (void) tcase_add_test (tc_chain, test_audio_pad_frag_asc_streamable); tcase_add_test (tc_chain, test_reuse); - tcase_add_test (tc_chain, test_encodebin); + tcase_add_test (tc_chain, test_encodebin_qtmux); + tcase_add_test (tc_chain, test_encodebin_mp4mux); return s; }