tests/examples/Makefile
tests/examples/app/Makefile
tests/examples/audio/Makefile
+tests/examples/decodebin_next/Makefile
tests/examples/dynamic/Makefile
tests/examples/encoding/Makefile
tests/examples/fft/Makefile
libgstplayback_la_SOURCES = \
gstdecodebin2.c \
+ gstdecodebin3.c \
gsturidecodebin.c \
+ gsturidecodebin3.c \
+ gsturisourcebin.c \
+ gstparsebin.c \
gstplayback.c \
gstplaybin2.c \
+ gstplaybin3.c \
gstplaysink.c \
gstplay-enum.c \
gstsubtitleoverlay.c \
$(GST_LIBS)
libgstplayback_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+# FIXME: gstdecodebin3-parse.c isn't really a header,
+# but for now it's included into gstdecodebin3.c directly
noinst_HEADERS = \
+ gstdecodebin3-parse.c \
gstplayback.h \
gstplaysink.h \
gstplay-enum.h \
--- /dev/null
+/* GStreamer
+ *
+ * Copyright (C) <2015> Centricular Ltd
+ * @author: Edward Hervey <edward@centricular.com>
+ * @author: Jan Schmidt <jan@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#if 0
+/* Not needed for now - we're including gstdecodebin3-parse.c into gstdecodebin3.c */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gprintf.h>
+#include <gst/gst.h>
+#include <gst/pbutils/pbutils.h>
+
+#include "gstplayback.h"
+#endif
+
+/* Streams that come from demuxers (input/upstream) */
+/* FIXME : All this is hardcoded. Switch to tree of chains */
+struct _DecodebinInputStream
+{
+ GstDecodebin3 *dbin;
+ GstStream *pending_stream; /* Extra ref */
+ GstStream *active_stream;
+
+ DecodebinInput *input;
+
+ GstPad *srcpad; /* From demuxer */
+
+ /* id of the pad event probe */
+ gulong output_event_probe_id;
+
+ /* id of the buffer blocking probe on the input (demuxer src) pad */
+ gulong input_buffer_probe_id;
+
+ /* Whether we saw an EOS on input. This should be treated accordingly
+ * when the stream is no longer used */
+ gboolean saw_eos;
+ /* TRUE if the EOS being pushed is only for draining and does not represent
+ * the full media EOS */
+ gboolean drain_eos;
+};
+
+static void parsebin_pad_added_cb (GstElement * demux, GstPad * pad,
+ DecodebinInput * input);
+static void parsebin_pad_removed_cb (GstElement * demux, GstPad * pad,
+ DecodebinInput * input);
+
+static gboolean
+pending_pads_are_eos (DecodebinInput * input)
+{
+ GList *tmp;
+
+ for (tmp = input->pending_pads; tmp; tmp = tmp->next) {
+ PendingPad *ppad = (PendingPad *) tmp->data;
+ if (ppad->saw_eos == FALSE)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+all_inputs_are_eos (GstDecodebin3 * dbin)
+{
+ GList *tmp;
+ /* First check input streams */
+ for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
+ DecodebinInputStream *input = (DecodebinInputStream *) tmp->data;
+ if (input->saw_eos == FALSE)
+ return FALSE;
+ }
+
+ /* Check pending pads */
+ if (!pending_pads_are_eos (dbin->main_input))
+ return FALSE;
+ for (tmp = dbin->other_inputs; tmp; tmp = tmp->next)
+ if (!pending_pads_are_eos ((DecodebinInput *) tmp->data))
+ return FALSE;
+
+ GST_DEBUG_OBJECT (dbin, "All streams are EOS");
+ return TRUE;
+}
+
+static void
+check_all_streams_for_eos (GstDecodebin3 * dbin)
+{
+ GList *tmp;
+
+ if (!all_inputs_are_eos (dbin))
+ return;
+
+ /* We know all streams are EOS, properly clean up everything */
+ for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
+ DecodebinInputStream *input = (DecodebinInputStream *) tmp->data;
+ GstPad *peer = gst_pad_get_peer (input->srcpad);
+
+ /* Send EOS and then remove elements */
+ if (peer) {
+ gst_pad_send_event (peer, gst_event_new_eos ());
+ gst_object_unref (peer);
+ }
+ GST_FIXME_OBJECT (input->srcpad, "Remove input stream");
+ }
+}
+
+/* Get the intersection of parser caps and available (sorted) decoders */
+static GstCaps *
+get_parser_caps_filter (GstDecodebin3 * dbin, GstCaps * caps)
+{
+ GList *tmp;
+ GstCaps *filter_caps = gst_caps_new_empty ();
+
+ g_mutex_lock (&dbin->factories_lock);
+ gst_decode_bin_update_factories_list (dbin);
+ for (tmp = dbin->decoder_factories; tmp; tmp = tmp->next) {
+ GstElementFactory *factory = (GstElementFactory *) tmp->data;
+ GstCaps *tcaps, *intersection;
+ const GList *tmps;
+
+ GST_LOG ("Trying factory %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+ for (tmps = gst_element_factory_get_static_pad_templates (factory); tmps;
+ tmps = tmps->next) {
+ GstStaticPadTemplate *st = (GstStaticPadTemplate *) tmps->data;
+ if (st->direction != GST_PAD_SINK || st->presence != GST_PAD_ALWAYS)
+ continue;
+ tcaps = gst_static_pad_template_get_caps (st);
+ intersection =
+ gst_caps_intersect_full (tcaps, caps, GST_CAPS_INTERSECT_FIRST);
+ filter_caps = gst_caps_merge (filter_caps, intersection);
+ gst_caps_unref (tcaps);
+ }
+ }
+ g_mutex_unlock (&dbin->factories_lock);
+ GST_DEBUG_OBJECT (dbin, "Got filter caps %" GST_PTR_FORMAT, filter_caps);
+ return filter_caps;
+}
+
+static gboolean
+check_parser_caps_filter (GstDecodebin3 * dbin, GstCaps * caps)
+{
+ GList *tmp;
+ gboolean res = FALSE;
+
+ g_mutex_lock (&dbin->factories_lock);
+ gst_decode_bin_update_factories_list (dbin);
+ for (tmp = dbin->decoder_factories; tmp; tmp = tmp->next) {
+ GstElementFactory *factory = (GstElementFactory *) tmp->data;
+ GstCaps *tcaps;
+ const GList *tmps;
+
+ GST_LOG ("Trying factory %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+ for (tmps = gst_element_factory_get_static_pad_templates (factory); tmps;
+ tmps = tmps->next) {
+ GstStaticPadTemplate *st = (GstStaticPadTemplate *) tmps->data;
+ if (st->direction != GST_PAD_SINK || st->presence != GST_PAD_ALWAYS)
+ continue;
+ tcaps = gst_static_pad_template_get_caps (st);
+ if (gst_caps_can_intersect (tcaps, caps)) {
+ res = TRUE;
+ gst_caps_unref (tcaps);
+ goto beach;
+ }
+ gst_caps_unref (tcaps);
+ }
+ }
+beach:
+ g_mutex_unlock (&dbin->factories_lock);
+ GST_DEBUG_OBJECT (dbin, "Can intersect : %d", res);
+ return res;
+}
+
+/* Probe on the output of a parser chain (the last
+ * src pad) */
+static GstPadProbeReturn
+parse_chain_output_probe (GstPad * pad, GstPadProbeInfo * info,
+ DecodebinInputStream * input)
+{
+ GstPadProbeReturn ret = GST_PAD_PROBE_OK;
+
+ if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info))) {
+ GstEvent *ev = GST_PAD_PROBE_INFO_EVENT (info);
+
+ GST_DEBUG_OBJECT (pad, "Got event %s", GST_EVENT_TYPE_NAME (ev));
+ switch (GST_EVENT_TYPE (ev)) {
+ case GST_EVENT_STREAM_START:
+ {
+ GstStream *stream = NULL;
+ guint group_id = G_MAXUINT32;
+ gst_event_parse_group_id (ev, &group_id);
+ GST_DEBUG_OBJECT (pad, "Got stream-start, group_id:%d, input %p",
+ group_id, input->input);
+ if (set_input_group_id (input->input, &group_id)) {
+ ev = gst_event_make_writable (ev);
+ gst_event_set_group_id (ev, group_id);
+ GST_PAD_PROBE_INFO_DATA (info) = ev;
+ }
+ input->saw_eos = FALSE;
+
+ gst_event_parse_stream (ev, &stream);
+ /* FIXME : Would we ever end up with a stream already set on the input ?? */
+ if (stream) {
+ if (input->active_stream != stream) {
+ MultiQueueSlot *slot;
+ if (input->active_stream)
+ gst_object_unref (input->active_stream);
+ input->active_stream = stream;
+ /* We have the beginning of a stream, get a multiqueue slot and link to it */
+ slot = get_slot_for_input (input->dbin, input);
+ link_input_to_slot (input, slot);
+ } else
+ gst_object_unref (stream);
+ }
+ }
+ break;
+ case GST_EVENT_CAPS:
+ {
+ GstCaps *caps = NULL;
+ gst_event_parse_caps (ev, &caps);
+ GST_DEBUG_OBJECT (pad, "caps %" GST_PTR_FORMAT, caps);
+ if (caps && input->active_stream)
+ gst_stream_set_caps (input->active_stream, caps);
+ }
+ break;
+ case GST_EVENT_EOS:
+ /* FIXME : Make sure this makes sense ... */
+ if (TRUE) {
+ GST_DEBUG_OBJECT (pad, "real input pad, marking as EOS");
+ input->saw_eos = TRUE;
+ check_all_streams_for_eos (input->dbin);
+ ret = GST_PAD_PROBE_DROP;
+ } else {
+ MultiQueueSlot *slot = get_slot_for_input (input->dbin, input);
+
+ slot->drain_eos = input->drain_eos;
+
+ if (input->drain_eos) {
+ GST_DEBUG_OBJECT (pad,
+ "Got EOS at end of input stream (drain_eos:%d) Dropping.",
+ input->drain_eos);
+ ret = GST_PAD_PROBE_DROP;
+ } else {
+ GST_DEBUG_OBJECT (pad,
+ "Got EOS at end of input stream (drain_eos:%d) Passing.",
+ input->drain_eos);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ } else if (GST_IS_QUERY (GST_PAD_PROBE_INFO_DATA (info))) {
+ GstQuery *q = GST_PAD_PROBE_INFO_QUERY (info);
+ GST_DEBUG_OBJECT (pad, "Seeing query %s", GST_QUERY_TYPE_NAME (q));
+ /* If we have a parser, we want to reply to the caps query */
+ /* FIXME: Set a flag when the input stream is created for
+ * streams where we shouldn't reply to these queries */
+ if (GST_QUERY_TYPE (q) == GST_QUERY_CAPS
+ && (info->type & GST_PAD_PROBE_TYPE_PULL)) {
+ GstCaps *filter = NULL;
+ GstCaps *allowed;
+ gst_query_parse_caps (q, &filter);
+ allowed = get_parser_caps_filter (input->dbin, filter);
+ GST_DEBUG_OBJECT (pad,
+ "Intercepting caps query, setting %" GST_PTR_FORMAT, allowed);
+ gst_query_set_caps_result (q, allowed);
+ gst_caps_unref (allowed);
+ ret = GST_PAD_PROBE_HANDLED;
+ } else if (GST_QUERY_TYPE (q) == GST_QUERY_ACCEPT_CAPS) {
+ GstCaps *prop = NULL;
+ gst_query_parse_accept_caps (q, &prop);
+ /* Fast check against target caps */
+ if (gst_caps_can_intersect (prop, input->dbin->caps))
+ gst_query_set_accept_caps_result (q, TRUE);
+ else {
+ gboolean accepted = check_parser_caps_filter (input->dbin, prop);
+ /* check against caps filter */
+ gst_query_set_accept_caps_result (q, accepted);
+ GST_DEBUG_OBJECT (pad, "ACCEPT_CAPS query, returning %d", accepted);
+ }
+ ret = GST_PAD_PROBE_HANDLED;
+ }
+ }
+
+ return ret;
+}
+
+static DecodebinInputStream *
+create_input_stream (GstDecodebin3 * dbin, GstStream * stream, GstPad * pad,
+ DecodebinInput * input)
+{
+ DecodebinInputStream *res = g_new0 (DecodebinInputStream, 1);
+
+ GST_DEBUG_OBJECT (pad, "Creating input stream for stream %p %s (input:%p)",
+ stream, gst_stream_get_stream_id (stream), input);
+
+ res->dbin = dbin;
+ res->input = input;
+ res->pending_stream = gst_object_ref (stream);
+ res->srcpad = pad;
+
+ /* Put probe on output source pad (for detecting EOS/STREAM_START) */
+ res->output_event_probe_id =
+ gst_pad_add_probe (pad,
+ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
+ (GstPadProbeCallback) parse_chain_output_probe, res, NULL);
+
+ /* Add to list of current input streams */
+ dbin->input_streams = g_list_append (dbin->input_streams, res);
+ GST_DEBUG_OBJECT (pad, "Done creating input stream");
+
+ return res;
+}
+
+static void
+remove_input_stream (GstDecodebin3 * dbin, DecodebinInputStream * stream)
+{
+ MultiQueueSlot *slot;
+
+ GST_DEBUG_OBJECT (dbin, "Removing input stream %p (%s)", stream,
+ stream->active_stream ? gst_stream_get_stream_id (stream->active_stream) :
+ "<NONE>");
+
+ /* Unlink from slot */
+ if (stream->srcpad) {
+ GstPad *peer;
+ peer = gst_pad_get_peer (stream->srcpad);
+ if (peer) {
+ gst_pad_unlink (stream->srcpad, peer);
+ gst_object_unref (peer);
+ }
+ }
+
+ slot = get_slot_for_input (dbin, stream);
+ if (slot) {
+ slot->pending_stream = NULL;
+ slot->input = NULL;
+ GST_DEBUG_OBJECT (dbin, "slot %p cleared", slot);
+ }
+
+ dbin->input_streams = g_list_remove (dbin->input_streams, stream);
+
+ g_free (stream);
+}
+
+
+/* FIXME : HACK, REMOVE, USE INPUT CHAINS */
+static GstPadProbeReturn
+parsebin_buffer_probe (GstPad * pad, GstPadProbeInfo * info,
+ DecodebinInput * input)
+{
+ GstDecodebin3 *dbin = input->dbin;
+ GList *tmp;
+
+ GST_FIXME_OBJECT (dbin, "Need a lock !");
+
+ GST_DEBUG_OBJECT (pad, "Got a buffer ! UNBLOCK !");
+
+ /* Any data out the demuxer means it's not creating pads
+ * any more right now */
+
+ /* 1. Re-use existing streams if/when possible */
+ GST_FIXME_OBJECT (dbin, "Re-use existing input streams if/when possible");
+
+ /* 2. Remove unused streams (push EOS) */
+ GST_DEBUG_OBJECT (dbin, "Removing unused streams");
+ tmp = dbin->input_streams;
+ while (tmp != NULL) {
+ DecodebinInputStream *input_stream = (DecodebinInputStream *) tmp->data;
+ GList *next = tmp->next;
+
+ GST_DEBUG_OBJECT (dbin, "Checking input stream %p", input_stream);
+ if (input_stream->input_buffer_probe_id) {
+ GST_DEBUG_OBJECT (dbin,
+ "Removing pad block on input %p pad %" GST_PTR_FORMAT, input_stream,
+ input_stream->srcpad);
+ gst_pad_remove_probe (input_stream->srcpad,
+ input_stream->input_buffer_probe_id);
+ }
+ input_stream->input_buffer_probe_id = 0;
+
+ if (input_stream->saw_eos) {
+ remove_input_stream (dbin, input_stream);
+ tmp = dbin->input_streams;
+ } else
+ tmp = next;
+ }
+
+ GST_DEBUG_OBJECT (dbin, "Creating new streams (if needed)");
+ /* 3. Create new streams */
+ for (tmp = input->pending_pads; tmp; tmp = tmp->next) {
+ GstStream *stream;
+ PendingPad *ppad = (PendingPad *) tmp->data;
+
+ stream = gst_pad_get_stream (ppad->pad);
+ if (stream == NULL) {
+ GST_ERROR_OBJECT (dbin, "No stream for pad ????");
+ } else {
+ MultiQueueSlot *slot;
+ DecodebinInputStream *input_stream;
+ /* The remaining pads in pending_pads are the ones that require a new
+ * input stream */
+ input_stream = create_input_stream (dbin, stream, ppad->pad, ppad->input);
+ /* See if we can link it straight away */
+ input_stream->active_stream = stream;
+ slot = get_slot_for_input (dbin, input_stream);
+ link_input_to_slot (input_stream, slot);
+ /* Remove the buffer and event probe */
+ gst_pad_remove_probe (ppad->pad, ppad->buffer_probe);
+ gst_pad_remove_probe (ppad->pad, ppad->event_probe);
+ g_free (ppad);
+ }
+ }
+
+ g_list_free (input->pending_pads);
+ input->pending_pads = NULL;
+
+ /* Weed out unused multiqueue slots */
+ for (tmp = dbin->slots; tmp; tmp = tmp->next) {
+ MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
+ GST_LOG_OBJECT (dbin, "Slot %d input:%p drain_eos:%d",
+ slot->id, slot->input, slot->drain_eos);
+ if (slot->input == NULL) {
+ GST_DEBUG_OBJECT (slot->sink_pad, "Sending EOS to unused slot");
+ gst_pad_send_event (slot->sink_pad, gst_event_new_eos ());
+ }
+ }
+
+ return GST_PAD_PROBE_OK;
+}
+
+static GstPadProbeReturn
+parsebin_pending_event_probe (GstPad * pad, GstPadProbeInfo * info,
+ PendingPad * ppad)
+{
+ GstDecodebin3 *dbin = ppad->dbin;
+ /* We drop all events by default */
+ GstPadProbeReturn ret = GST_PAD_PROBE_DROP;
+ GstEvent *ev = GST_PAD_PROBE_INFO_EVENT (info);
+
+ GST_DEBUG_OBJECT (pad, "Got event %p %s", ev, GST_EVENT_TYPE_NAME (ev));
+ switch (GST_EVENT_TYPE (ev)) {
+ case GST_EVENT_EOS:
+ {
+ GST_DEBUG_OBJECT (pad, "Pending pad marked as EOS, removing");
+ ppad->input->pending_pads =
+ g_list_remove (ppad->input->pending_pads, ppad);
+ gst_pad_remove_probe (ppad->pad, ppad->buffer_probe);
+ gst_pad_remove_probe (ppad->pad, ppad->event_probe);
+ g_free (ppad);
+
+ check_all_streams_for_eos (dbin);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void
+parsebin_pad_added_cb (GstElement * demux, GstPad * pad, DecodebinInput * input)
+{
+ GstDecodebin3 *dbin = input->dbin;
+ PendingPad *ppad;
+ GList *tmp;
+
+ GST_DEBUG_OBJECT (dbin, "New pad %s:%s (input:%p)", GST_DEBUG_PAD_NAME (pad),
+ input);
+
+ ppad = g_new0 (PendingPad, 1);
+ ppad->dbin = dbin;
+ ppad->input = input;
+ ppad->pad = pad;
+
+ ppad->event_probe =
+ gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ (GstPadProbeCallback) parsebin_pending_event_probe, ppad, NULL);
+ ppad->buffer_probe =
+ gst_pad_add_probe (pad,
+ GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
+ (GstPadProbeCallback) parsebin_buffer_probe, input, NULL);
+
+ input->pending_pads = g_list_append (input->pending_pads, ppad);
+
+ /* FIXME : ONLY DO FOR THIS PARSEBIN/INPUT ! */
+ /* Check if all existing input streams have a buffer probe set */
+ for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
+ DecodebinInputStream *input_stream = (DecodebinInputStream *) tmp->data;
+ if (input_stream->input_buffer_probe_id == 0) {
+ GST_DEBUG_OBJECT (input_stream->srcpad, "Adding blocking buffer probe");
+ input_stream->input_buffer_probe_id =
+ gst_pad_add_probe (input_stream->srcpad,
+ GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
+ (GstPadProbeCallback) parsebin_buffer_probe, input_stream->input,
+ NULL);
+ }
+ }
+}
+
+static void
+parsebin_pad_removed_cb (GstElement * demux, GstPad * pad, DecodebinInput * inp)
+{
+ GstDecodebin3 *dbin = inp->dbin;
+ DecodebinInputStream *input = NULL;
+ GList *tmp;
+ GST_DEBUG_OBJECT (pad, "removed");
+
+ for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
+ DecodebinInputStream *cand = (DecodebinInputStream *) tmp->data;
+ if (cand->srcpad == pad)
+ input = cand;
+ }
+ /* If there are no pending pads, this means we will definitely not need this
+ * stream anymore */
+ if (input) {
+ GST_DEBUG_OBJECT (pad, "stream %p", input);
+ if (inp->pending_pads == NULL) {
+ GST_DEBUG_OBJECT (pad, "Remove input stream %p", input);
+ remove_input_stream (dbin, input);
+ } else {
+ input->srcpad = NULL;
+ if (input->input_buffer_probe_id)
+ gst_pad_remove_probe (pad, input->input_buffer_probe_id);
+ input->input_buffer_probe_id = 0;
+ }
+ }
+}
--- /dev/null
+/* GStreamer
+ *
+ * Copyright (C) <2015> Centricular Ltd
+ * @author: Edward Hervey <edward@centricular.com>
+ * @author: Jan Schmidt <jan@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gprintf.h>
+#include <gst/gst.h>
+#include <gst/pbutils/pbutils.h>
+
+#include "gstplayback.h"
+#include "gstplay-enum.h"
+#include "gstrawcaps.h"
+
+
+/**
+ * Global design
+ *
+ * 1) From sink pad to elementary streams (GstParseBin)
+ *
+ * The input sink pads are fed to GstParseBin. GstParseBin will feed them
+ * through typefind. When the caps are detected (or changed) we recursively
+ * figure out which demuxer, parser or depayloader is needed until we get to
+ * elementary streams.
+ *
+ * All elementary streams (whether decoded or not, whether exposed or not) are
+ * fed through multiqueue. There is only *one* multiqueue in decodebin3.
+ *
+ * => MultiQueue is the cornerstone.
+ * => No buffering before multiqueue
+ *
+ * 2) Elementary streams
+ *
+ * After GstParseBin, there are 3 main components:
+ * 1) Input Streams (provided by GstParseBin)
+ * 2) Multiqueue slots
+ * 3) Output Streams
+ *
+ * Input Streams correspond to the stream coming from GstParseBin and that gets
+ * fed into a multiqueue slot.
+ *
+ * Output Streams correspond to the combination of a (optional) decoder and an
+ * output ghostpad. Output Streams can be moved from one multiqueue slot to
+ * another, can reconfigure itself (different decoders), and can be
+ * added/removed depending on the configuration (all streams outputted, only one
+ * of each type, ...).
+ *
+ * Multiqueue slots correspond to a pair of sink/src pad from multiqueue. For
+ * each 'active' Input Stream there is a corresponding slot.
+ * Slots might have different streams on input and output (due to internal
+ * buffering).
+ *
+ * Due to internal queuing/buffering/..., all those components (might) behave
+ * asynchronously. Therefore probes will be used on each component source pad to
+ * detect various key-points:
+ * * EOS :
+ * the stream is done => Mark that component as done, optionally freeing/removing it
+ * * STREAM_START :
+ * a new stream is starting => link it further if needed
+ *
+ *
+ * 3) Gradual replacement
+ *
+ * If the caps change at any point in decodebin (input sink pad, demuxer output,
+ * multiqueue output, ..), we gradually replace (if needed) the following elements.
+ *
+ * This is handled by the probes in various locations:
+ * a) typefind output
+ * b) multiqueue input (source pad of Input Streams)
+ * c) multiqueue output (source pad of Multiqueue Slots)
+ * d) final output (target of source ghostpads)
+ *
+ * When CAPS event arrive at those points, one of three things can happen:
+ * a) There is no elements downstream yet, just create/link-to following elements
+ * b) There are downstream elements, do a ACCEPT_CAPS query
+ * b.1) The new CAPS are accepted, keep current configuration
+ * b.2) The new CAPS are not accepted, remove following elements then do a)
+ *
+ *
+ *
+ * Components:
+ *
+ * MultiQ Output
+ * Input(s) Slots Streams
+ * /-------------------------------------------\ /-----\ /------------- \
+ *
+ * +-------------------------------------------------------------------------+
+ * | |
+ * | +---------------------------------------------+ |
+ * | | GstParseBin(s) | |
+ * | | +--------------+ | +-----+ |
+ * | | | |---[parser]-[|--| Mul |---[ decoder ]-[|
+ * |]--[ typefind ]---| demuxer(s) |------------[| | ti | |
+ * | | | (if needed) |---[parser]-[|--| qu | |
+ * | | | |---[parser]-[|--| eu |---[ decoder ]-[|
+ * | | +--------------+ | +------ ^ |
+ * | +---------------------------------------------+ ^ | |
+ * | ^ | | |
+ * +-----------------------------------------------+--------+-------------+--+
+ * | | |
+ * | | |
+ * Probes --/--------/-------------/
+ *
+ * ATOMIC SWITCHING
+ *
+ * We want to ensure we re-use decoders when switching streams. This takes place
+ * at the multiqueue output level.
+ *
+ * MAIN CONCEPTS
+ * 1) Activating a stream (i.e. linking a slot to an output) is only done within
+ * the streaming thread in the multiqueue_src_probe() and only if the
+ stream is in the REQUESTED selection.
+ * 2) Deactivating a stream (i.e. unlinking a slot from an output) is also done
+ * within the stream thread, but only in a purposefully called IDLE probe
+ * that calls reassign_slot().
+ *
+ * Based on those two principles, 3 "selection" of streams (stream-id) are used:
+ * 1) requested_selection
+ * All streams within that list should be activated
+ * 2) active_selection
+ * List of streams that are exposed by decodebin
+ * 3) to_activate
+ * List of streams that will be moved to requested_selection in the
+ * reassign_slot() method (i.e. once a stream was deactivated, and the output
+ * was retargetted)
+ */
+
+
+GST_DEBUG_CATEGORY_STATIC (decodebin3_debug);
+#define GST_CAT_DEFAULT decodebin3_debug
+
+#define GST_TYPE_DECODEBIN3 (gst_decodebin3_get_type ())
+
+#define EXTRA_DEBUG 1
+
+typedef struct _GstDecodebin3 GstDecodebin3;
+typedef struct _GstDecodebin3Class GstDecodebin3Class;
+
+typedef struct _DecodebinInputStream DecodebinInputStream;
+typedef struct _DecodebinInput DecodebinInput;
+typedef struct _DecodebinOutputStream DecodebinOutputStream;
+
+struct _GstDecodebin3
+{
+ GstBin bin;
+
+ /* input_lock protects the following variables */
+ GMutex input_lock;
+ /* Main input (static sink pad) */
+ DecodebinInput *main_input;
+ /* Supplementary input (request sink pads) */
+ GList *other_inputs;
+ /* counter for input */
+ guint32 input_counter;
+ /* Current stream group_id (default : G_MAXUINT32) */
+ /* FIXME : Needs to be resetted appropriately (when upstream changes ?) */
+ guint32 current_group_id;
+ /* End of variables protected by input_lock */
+
+ GstElement *multiqueue;
+
+ /* FIXME : Mutex for protecting values below */
+ GstStreamCollection *collection; /* Active collection */
+
+ GList *input_streams; /* List of DecodebinInputStream for active collection */
+ GList *output_streams; /* List of DecodebinOutputStream used for output */
+ GList *slots; /* List of MultiQueueSlot */
+ guint slot_id;
+
+ /* selection_lock protects access to following variables */
+ GMutex selection_lock;
+ /* requested selection of stream-id to activate post-multiqueue */
+ GList *requested_selection;
+ /* list of stream-id currently activated in output */
+ GList *active_selection;
+ /* List of stream-id that need to be activated (after a stream switch for ex) */
+ GList *to_activate;
+ /* Pending select streams event */
+ guint32 select_streams_seqnum;
+ /* pending list of streams to select (from downstream) */
+ GList *pending_select_streams;
+ /* TRUE if requested_selection was updated, will become FALSE once
+ * it has fully transitioned to active */
+ gboolean selection_updated;
+ /* End of variables protected by selection_lock */
+
+ /* List of pending collections.
+ * FIXME : Is this really needed ? */
+ GList *pending_collection;
+
+
+ /* Factories */
+ GMutex factories_lock;
+ guint32 factories_cookie;
+ /* All DECODABLE factories */
+ GList *factories;
+ /* Only DECODER factories */
+ GList *decoder_factories;
+ /* DECODABLE but not DECODER factories */
+ GList *decodable_factories;
+
+ /* counters for pads */
+ guint32 apadcount, vpadcount, tpadcount, opadcount;
+
+ /* Properties */
+ GstCaps *caps;
+};
+
+struct _GstDecodebin3Class
+{
+ GstBinClass class;
+
+ gint (*select_stream) (GstDecodebin3 * dbin,
+ GstStreamCollection * collection, GstStream * stream);
+};
+
+/* Input of decodebin, controls input pad and parsebin */
+struct _DecodebinInput
+{
+ GstDecodebin3 *dbin;
+
+ gboolean is_main;
+
+ GstPad *ghost_sink;
+ GstPad *parsebin_sink;
+
+ GstStreamCollection *collection; /* Active collection */
+
+ guint group_id;
+
+ GstElement *parsebin;
+
+ gulong pad_added_sigid;
+ gulong pad_removed_sigid;
+
+ /* HACK : Remove these fields */
+ /* List of PendingPad structures */
+ GList *pending_pads;
+};
+
+/* Multiqueue Slots */
+typedef struct _MultiQueueSlot
+{
+ guint id;
+
+ GstDecodebin3 *dbin;
+ /* Type of stream handled by this slot */
+ GstStreamType type;
+
+ /* Linked input and output */
+ DecodebinInputStream *input;
+
+ /* pending => last stream received on sink pad */
+ GstStream *pending_stream;
+ /* active => last stream outputted on source pad */
+ GstStream *active_stream;
+
+ GstPad *sink_pad, *src_pad;
+
+ /* id of the MQ src_pad event probe */
+ gulong probe_id;
+
+ gboolean drain_eos;
+
+ DecodebinOutputStream *output;
+} MultiQueueSlot;
+
+/* Streams that are exposed downstream (i.e. output) */
+struct _DecodebinOutputStream
+{
+ GstDecodebin3 *dbin;
+ /* The type of stream handled by this output stream */
+ GstStreamType type;
+
+ /* The slot to which this output stream is currently connected to */
+ MultiQueueSlot *slot;
+
+ GstElement *decoder; /* Optional */
+ GstPad *decoder_sink, *decoder_src;
+ gboolean linked;
+
+ /* ghostpad */
+ GstPad *src_pad;
+ /* Flag if ghost pad is exposed */
+ gboolean src_exposed;
+
+ /* keyframe dropping probe */
+ gulong drop_probe_id;
+};
+
+/* Pending pads from parsebin */
+typedef struct _PendingPad
+{
+ GstDecodebin3 *dbin;
+ DecodebinInput *input;
+ GstPad *pad;
+
+ gulong buffer_probe;
+ gulong event_probe;
+ gboolean saw_eos;
+} PendingPad;
+
+/* properties */
+#define DEFAULT_CAPS (gst_static_caps_get (&default_raw_caps))
+
+enum
+{
+ PROP_0,
+ PROP_CAPS
+};
+
+/* signals */
+enum
+{
+ SIGNAL_SELECT_STREAM,
+ LAST_SIGNAL
+};
+static guint gst_decodebin3_signals[LAST_SIGNAL] = { 0 };
+
+#define SELECTION_LOCK(dbin) G_STMT_START { \
+ GST_LOG_OBJECT (dbin, \
+ "selection locking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_lock (&dbin->selection_lock); \
+ GST_LOG_OBJECT (dbin, \
+ "selection locked from thread %p", \
+ g_thread_self ()); \
+ } G_STMT_END
+
+#define SELECTION_UNLOCK(dbin) G_STMT_START { \
+ GST_LOG_OBJECT (dbin, \
+ "selection unlocking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_unlock (&dbin->selection_lock); \
+ } G_STMT_END
+
+#define INPUT_LOCK(dbin) G_STMT_START { \
+ GST_LOG_OBJECT (dbin, \
+ "input locking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_lock (&dbin->input_lock); \
+ GST_LOG_OBJECT (dbin, \
+ "input locked from thread %p", \
+ g_thread_self ()); \
+ } G_STMT_END
+
+#define INPUT_UNLOCK(dbin) G_STMT_START { \
+ GST_LOG_OBJECT (dbin, \
+ "input unlocking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_unlock (&dbin->input_lock); \
+ } G_STMT_END
+
+GType gst_decodebin3_get_type (void);
+#define gst_decodebin3_parent_class parent_class
+G_DEFINE_TYPE (GstDecodebin3, gst_decodebin3, GST_TYPE_BIN);
+
+static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS);
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate request_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink_%u",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate video_src_template =
+GST_STATIC_PAD_TEMPLATE ("video_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate audio_src_template =
+GST_STATIC_PAD_TEMPLATE ("audio_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate text_src_template =
+GST_STATIC_PAD_TEMPLATE ("text_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS_ANY);
+
+
+static void gst_decodebin3_dispose (GObject * object);
+static void gst_decodebin3_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_decodebin3_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean parsebin_autoplug_continue_cb (GstElement *
+ parsebin, GstPad * pad, GstCaps * caps, GstDecodebin3 * dbin);
+
+static gint
+gst_decodebin3_select_stream (GstDecodebin3 * dbin,
+ GstStreamCollection * collection, GstStream * stream)
+{
+ GST_LOG_OBJECT (dbin, "default select-stream, returning -1");
+
+ return -1;
+}
+
+static GstPad *gst_decodebin3_request_new_pad (GstElement * element,
+ GstPadTemplate * temp, const gchar * name, const GstCaps * caps);
+static void gst_decodebin3_handle_message (GstBin * bin, GstMessage * message);
+static GstStateChangeReturn gst_decodebin3_change_state (GstElement * element,
+ GstStateChange transition);
+static gboolean gst_decodebin3_send_event (GstElement * element,
+ GstEvent * event);
+
+static void gst_decode_bin_update_factories_list (GstDecodebin3 * dbin);
+#if 0
+static gboolean have_factory (GstDecodebin3 * dbin, GstCaps * caps,
+ GstElementFactoryListType ftype);
+#endif
+
+static void free_input (GstDecodebin3 * dbin, DecodebinInput * input);
+static DecodebinInput *create_new_input (GstDecodebin3 * dbin, gboolean main);
+static gboolean set_input_group_id (DecodebinInput * input, guint32 * group_id);
+
+static void reconfigure_output_stream (DecodebinOutputStream * output,
+ MultiQueueSlot * slot);
+static void free_output_stream (GstDecodebin3 * dbin,
+ DecodebinOutputStream * output);
+static DecodebinOutputStream *create_output_stream (GstDecodebin3 * dbin,
+ GstStreamType type);
+
+static GstPadProbeReturn slot_unassign_probe (GstPad * pad,
+ GstPadProbeInfo * info, MultiQueueSlot * slot);
+static gboolean reassign_slot (GstDecodebin3 * dbin, MultiQueueSlot * slot);
+static MultiQueueSlot *get_slot_for_input (GstDecodebin3 * dbin,
+ DecodebinInputStream * input);
+static void link_input_to_slot (DecodebinInputStream * input,
+ MultiQueueSlot * slot);
+static void free_multiqueue_slot (GstDecodebin3 * dbin, MultiQueueSlot * slot);
+
+/* FIXME: Really make all the parser stuff a self-contained helper object */
+#include "gstdecodebin3-parse.c"
+
+static gboolean
+_gst_int_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gint res = g_value_get_int (handler_return);
+
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_int (return_accu, res);
+
+ if (res == -1)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+gst_decodebin3_class_init (GstDecodebin3Class * klass)
+{
+ GObjectClass *gobject_klass = (GObjectClass *) klass;
+ GstElementClass *element_class = (GstElementClass *) klass;
+ GstBinClass *bin_klass = (GstBinClass *) klass;
+
+ gobject_klass->dispose = gst_decodebin3_dispose;
+ gobject_klass->set_property = gst_decodebin3_set_property;
+ gobject_klass->get_property = gst_decodebin3_get_property;
+
+ /* FIXME : ADD PROPERTIES ! */
+ g_object_class_install_property (gobject_klass, PROP_CAPS,
+ g_param_spec_boxed ("caps", "Caps",
+ "The caps on which to stop decoding. (NULL = default)",
+ GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* FIXME : ADD SIGNALS ! */
+ /**
+ * GstDecodebin3::select-stream
+ * @decodebin: a #GstDecodebin3
+ * @collection: a #GstStreamCollection
+ * @stream: a #GstStream
+ *
+ * This signal is emitted whenever @decodebin needs to decide whether
+ * to expose a @stream of a given @collection.
+ *
+ * Returns: 1 if the stream should be selected, 0 if it shouldn't be selected.
+ * A value of -1 (default) lets @decodebin decide what to do with the stream.
+ * */
+ gst_decodebin3_signals[SIGNAL_SELECT_STREAM] =
+ g_signal_new ("select-stream", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstDecodebin3Class, select_stream),
+ _gst_int_accumulator, NULL, g_cclosure_marshal_generic,
+ G_TYPE_INT, 2, GST_TYPE_STREAM_COLLECTION, GST_TYPE_STREAM);
+
+
+ element_class->request_new_pad =
+ GST_DEBUG_FUNCPTR (gst_decodebin3_request_new_pad);
+ element_class->change_state = GST_DEBUG_FUNCPTR (gst_decodebin3_change_state);
+ element_class->send_event = GST_DEBUG_FUNCPTR (gst_decodebin3_send_event);
+
+ 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 (&request_sink_template));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&video_src_template));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&audio_src_template));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&text_src_template));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&src_template));
+
+ gst_element_class_set_static_metadata (element_class,
+ "Decoder Bin 3", "Generic/Bin/Decoder",
+ "Autoplug and decode to raw media",
+ "Edward Hervey <edward@centricular.com>");
+
+ bin_klass->handle_message = gst_decodebin3_handle_message;
+
+ klass->select_stream = gst_decodebin3_select_stream;
+}
+
+static void
+gst_decodebin3_init (GstDecodebin3 * dbin)
+{
+ /* Create main input */
+ dbin->main_input = create_new_input (dbin, TRUE);
+
+ dbin->multiqueue = gst_element_factory_make ("multiqueue", NULL);
+ g_object_set (dbin->multiqueue, "sync-by-running-time", TRUE,
+ "max-size-buffers", 0, "use-interleave", TRUE, NULL);
+ gst_bin_add ((GstBin *) dbin, dbin->multiqueue);
+
+ dbin->current_group_id = G_MAXUINT32;
+
+ g_mutex_init (&dbin->factories_lock);
+ g_mutex_init (&dbin->selection_lock);
+ g_mutex_init (&dbin->input_lock);
+
+ dbin->caps = gst_static_caps_get (&default_raw_caps);
+}
+
+static void
+gst_decodebin3_dispose (GObject * object)
+{
+ GstDecodebin3 *dbin = (GstDecodebin3 *) object;
+
+ if (dbin->factories)
+ gst_plugin_feature_list_free (dbin->factories);
+ if (dbin->decoder_factories)
+ g_list_free (dbin->decoder_factories);
+ if (dbin->decodable_factories)
+ g_list_free (dbin->decodable_factories);
+ g_list_free (dbin->requested_selection);
+ g_list_free (dbin->active_selection);
+ g_list_free (dbin->to_activate);
+ g_list_free (dbin->pending_select_streams);
+
+ free_input (dbin, dbin->main_input);
+ /* FIXME : GO OVER INPUTS */
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_decodebin3_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstDecodebin3 *dbin = (GstDecodebin3 *) object;
+
+ /* FIXME : IMPLEMENT */
+ switch (prop_id) {
+ case PROP_CAPS:
+ GST_OBJECT_LOCK (dbin);
+ if (dbin->caps)
+ gst_caps_unref (dbin->caps);
+ dbin->caps = g_value_dup_boxed (value);
+ GST_OBJECT_UNLOCK (dbin);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_decodebin3_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstDecodebin3 *dbin = (GstDecodebin3 *) object;
+
+ /* FIXME : IMPLEMENT */
+ switch (prop_id) {
+ case PROP_CAPS:
+ GST_OBJECT_LOCK (dbin);
+ g_value_set_boxed (value, dbin->caps);
+ GST_OBJECT_UNLOCK (dbin);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+parsebin_autoplug_continue_cb (GstElement * parsebin, GstPad * pad,
+ GstCaps * caps, GstDecodebin3 * dbin)
+{
+ GST_DEBUG_OBJECT (pad, "caps %" GST_PTR_FORMAT, caps);
+
+ /* If it matches our target caps, expose it */
+ if (gst_caps_can_intersect (caps, dbin->caps))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* This method should be called whenever a STREAM_START event
+ * comes out of a given parsebin.
+ * The caller shall replace the group_id if the function returns TRUE */
+static gboolean
+set_input_group_id (DecodebinInput * input, guint32 * group_id)
+{
+ GstDecodebin3 *dbin = input->dbin;
+
+ if (input->group_id != *group_id) {
+ if (input->group_id != G_MAXUINT32)
+ GST_WARNING_OBJECT (dbin,
+ "Group id changed (%" G_GUINT32_FORMAT " -> %" G_GUINT32_FORMAT
+ ") on input %p ", input->group_id, *group_id, input);
+ input->group_id = *group_id;
+ }
+
+ if (*group_id != dbin->current_group_id) {
+ if (dbin->current_group_id == G_MAXUINT32) {
+ GST_DEBUG_OBJECT (dbin, "Setting current group id to %" G_GUINT32_FORMAT,
+ *group_id);
+ dbin->current_group_id = *group_id;
+ }
+ *group_id = dbin->current_group_id;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Call with INPUT_LOCK taken */
+static gboolean
+ensure_input_parsebin (GstDecodebin3 * dbin, DecodebinInput * input)
+{
+ gboolean set_state = FALSE;
+
+ if (input->parsebin == NULL) {
+ input->parsebin = gst_element_factory_make ("parsebin", NULL);
+ if (input->parsebin == NULL)
+ goto no_parsebin;
+ input->parsebin = gst_object_ref (input->parsebin);
+ input->parsebin_sink = gst_element_get_static_pad (input->parsebin, "sink");
+ input->pad_added_sigid =
+ g_signal_connect (input->parsebin, "pad-added",
+ (GCallback) parsebin_pad_added_cb, input);
+ input->pad_removed_sigid =
+ g_signal_connect (input->parsebin, "pad-removed",
+ (GCallback) parsebin_pad_removed_cb, input);
+ g_signal_connect (input->parsebin, "autoplug-continue",
+ (GCallback) parsebin_autoplug_continue_cb, dbin);
+ }
+
+ if (GST_OBJECT_PARENT (GST_OBJECT (input->parsebin)) != GST_OBJECT (dbin)) {
+ gst_bin_add (GST_BIN (dbin), input->parsebin);
+ set_state = TRUE;
+ }
+
+ gst_ghost_pad_set_target (GST_GHOST_PAD (input->ghost_sink),
+ input->parsebin_sink);
+ if (set_state)
+ gst_element_sync_state_with_parent (input->parsebin);
+
+ return TRUE;
+
+ /* ERRORS */
+no_parsebin:
+ {
+ gst_element_post_message ((GstElement *) dbin,
+ gst_missing_element_message_new ((GstElement *) dbin, "parsebin"));
+ return FALSE;
+ }
+}
+
+static GstPadLinkReturn
+gst_decodebin3_input_pad_link (GstPad * pad, GstObject * parent, GstPad * peer)
+{
+ GstDecodebin3 *dbin = (GstDecodebin3 *) parent;
+ GstPadLinkReturn res = GST_PAD_LINK_OK;
+ DecodebinInput *input;
+
+ GST_LOG_OBJECT (parent, "Got link on input pad %" GST_PTR_FORMAT
+ ". Creating parsebin if needed", pad);
+
+ if ((input = g_object_get_data (G_OBJECT (pad), "decodebin.input")) == NULL)
+ goto fail;
+
+ INPUT_LOCK (dbin);
+ if (!ensure_input_parsebin (dbin, input))
+ res = GST_PAD_LINK_REFUSED;
+ INPUT_UNLOCK (dbin);
+
+ return res;
+fail:
+ GST_ERROR_OBJECT (parent, "Failed to retrieve input state from ghost pad");
+ return GST_PAD_LINK_REFUSED;
+}
+
+static void
+gst_decodebin3_input_pad_unlink (GstPad * pad, GstObject * parent)
+{
+ GstDecodebin3 *dbin = (GstDecodebin3 *) parent;
+ DecodebinInput *input;
+
+ GST_LOG_OBJECT (parent, "Got unlink on input pad %" GST_PTR_FORMAT
+ ". Removing parsebin.", pad);
+
+ if ((input = g_object_get_data (G_OBJECT (pad), "decodebin.input")) == NULL)
+ goto fail;
+
+ INPUT_LOCK (dbin);
+ if (input->parsebin == NULL) {
+ INPUT_UNLOCK (dbin);
+ return;
+ }
+
+ if (GST_OBJECT_PARENT (GST_OBJECT (input->parsebin)) == GST_OBJECT (dbin)) {
+ gst_bin_remove (GST_BIN (dbin), input->parsebin);
+ gst_element_set_state (input->parsebin, GST_STATE_NULL);
+ }
+ INPUT_UNLOCK (dbin);
+ return;
+
+fail:
+ GST_ERROR_OBJECT (parent, "Failed to retrieve input state from ghost pad");
+ return;
+}
+
+static void
+free_input (GstDecodebin3 * dbin, DecodebinInput * input)
+{
+ GST_DEBUG ("Freeing input %p", input);
+ gst_ghost_pad_set_target (GST_GHOST_PAD (input->ghost_sink), NULL);
+ gst_element_remove_pad (GST_ELEMENT (dbin), input->ghost_sink);
+ if (input->parsebin) {
+ g_signal_handler_disconnect (input->parsebin, input->pad_removed_sigid);
+ g_signal_handler_disconnect (input->parsebin, input->pad_added_sigid);
+ gst_element_set_state (input->parsebin, GST_STATE_NULL);
+ gst_object_unref (input->parsebin);
+ gst_object_unref (input->parsebin_sink);
+ }
+ g_free (input);
+}
+
+/* Call with INPUT_LOCK taken */
+static DecodebinInput *
+create_new_input (GstDecodebin3 * dbin, gboolean main)
+{
+ DecodebinInput *input;
+
+ input = g_new0 (DecodebinInput, 1);
+ input->dbin = dbin;
+ input->is_main = main;
+ input->group_id = G_MAXUINT32;
+ if (main)
+ input->ghost_sink = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK);
+ else {
+ gchar *pad_name = g_strdup_printf ("sink_%u", dbin->input_counter++);
+ input->ghost_sink = gst_ghost_pad_new_no_target (pad_name, GST_PAD_SINK);
+ g_free (pad_name);
+ }
+ g_object_set_data (G_OBJECT (input->ghost_sink), "decodebin.input", input);
+ gst_pad_set_link_function (input->ghost_sink, gst_decodebin3_input_pad_link);
+ gst_pad_set_unlink_function (input->ghost_sink,
+ gst_decodebin3_input_pad_unlink);
+
+ gst_pad_set_active (input->ghost_sink, TRUE);
+ gst_element_add_pad ((GstElement *) dbin, input->ghost_sink);
+
+ return input;
+
+}
+
+static GstPad *
+gst_decodebin3_request_new_pad (GstElement * element, GstPadTemplate * temp,
+ const gchar * name, const GstCaps * caps)
+{
+ GstDecodebin3 *dbin = (GstDecodebin3 *) element;
+ DecodebinInput *input;
+ GstPad *res = NULL;
+
+ /* We are ignoring names for the time being, not sure it makes any sense
+ * within the context of decodebin3 ... */
+ INPUT_LOCK (dbin);
+ input = create_new_input (dbin, FALSE);
+ if (input) {
+ dbin->other_inputs = g_list_append (dbin->other_inputs, input);
+ res = input->ghost_sink;
+ }
+ INPUT_UNLOCK (dbin);
+
+ return res;
+}
+
+/* Must be called with factories lock! */
+static void
+gst_decode_bin_update_factories_list (GstDecodebin3 * dbin)
+{
+ guint cookie;
+
+ cookie = gst_registry_get_feature_list_cookie (gst_registry_get ());
+ if (!dbin->factories || dbin->factories_cookie != cookie) {
+ GList *tmp;
+ if (dbin->factories)
+ gst_plugin_feature_list_free (dbin->factories);
+ if (dbin->decoder_factories)
+ g_list_free (dbin->decoder_factories);
+ if (dbin->decodable_factories)
+ g_list_free (dbin->decodable_factories);
+ dbin->factories =
+ gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_DECODABLE, GST_RANK_MARGINAL);
+ dbin->factories =
+ g_list_sort (dbin->factories, gst_plugin_feature_rank_compare_func);
+ dbin->factories_cookie = cookie;
+
+ /* Filter decoder and other decodables */
+ dbin->decoder_factories = NULL;
+ dbin->decodable_factories = NULL;
+ for (tmp = dbin->factories; tmp; tmp = tmp->next) {
+ GstElementFactory *fact = (GstElementFactory *) tmp->data;
+ if (gst_element_factory_list_is_type (fact,
+ GST_ELEMENT_FACTORY_TYPE_DECODER))
+ dbin->decoder_factories = g_list_append (dbin->decoder_factories, fact);
+ else
+ dbin->decodable_factories =
+ g_list_append (dbin->decodable_factories, fact);
+ }
+ }
+}
+
+/* Must be called with appropriate lock if list is a protected variable */
+static gboolean
+stream_in_list (GList * list, const gchar * sid)
+{
+ GList *tmp;
+
+#if EXTRA_DEBUG
+ for (tmp = list; tmp; tmp = tmp->next) {
+ gchar *osid = (gchar *) tmp->data;
+ GST_DEBUG ("Checking %s against %s", sid, osid);
+ }
+#endif
+
+ for (tmp = list; tmp; tmp = tmp->next) {
+ gchar *osid = (gchar *) tmp->data;
+ if (!g_strcmp0 (sid, osid))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+update_requested_selection (GstDecodebin3 * dbin,
+ GstStreamCollection * collection)
+{
+ guint i, nb;
+ GList *tmp = NULL;
+ GstStreamType used_types = 0;
+
+ nb = gst_stream_collection_get_size (collection);
+
+ /* 1. Is there a pending SELECT_STREAMS we can return straight away since
+ * the switch handler will take care of the pending selection */
+ SELECTION_LOCK (dbin);
+ if (dbin->pending_select_streams) {
+ GST_DEBUG_OBJECT (dbin,
+ "No need to create pending selection, SELECT_STREAMS underway");
+ goto beach;
+ }
+
+ /* 2. If not, are we in EXPOSE_ALL_MODE ? If so, match everything */
+ GST_FIXME_OBJECT (dbin, "Implement EXPOSE_ALL_MODE");
+
+ /* 3. If not, check if we already have some of the streams in the
+ * existing active/requested selection */
+ for (i = 0; i < nb; i++) {
+ GstStream *stream = gst_stream_collection_get_stream (collection, i);
+ const gchar *sid = gst_stream_get_stream_id (stream);
+ gint request = -1;
+ /* Fire select-stream signal to see if outside components want to
+ * hint at which streams should be selected */
+ g_signal_emit (G_OBJECT (dbin),
+ gst_decodebin3_signals[SIGNAL_SELECT_STREAM], 0, collection, stream,
+ &request);
+ GST_DEBUG_OBJECT (dbin, "stream %s , request:%d", sid, request);
+ if (request == 1 || (request == -1
+ && (stream_in_list (dbin->requested_selection, sid)
+ || stream_in_list (dbin->active_selection, sid)))) {
+ GstStreamType curtype = gst_stream_get_stream_type (stream);
+ if (request == 1)
+ GST_DEBUG_OBJECT (dbin,
+ "Using stream requested by 'select-stream' signal : %s", sid);
+ else
+ GST_DEBUG_OBJECT (dbin,
+ "Re-using stream already present in requested or active selection : %s",
+ sid);
+ tmp = g_list_append (tmp, (gchar *) sid);
+ used_types |= curtype;
+ }
+ }
+
+ /* 4. If not, match one stream of each type */
+ for (i = 0; i < nb; i++) {
+ GstStream *stream = gst_stream_collection_get_stream (collection, i);
+ GstStreamType curtype = gst_stream_get_stream_type (stream);
+ if (!(used_types & curtype)) {
+ const gchar *sid = gst_stream_get_stream_id (stream);
+ GST_DEBUG_OBJECT (dbin, "Selecting stream '%s' of type %s",
+ sid, gst_stream_type_get_name (curtype));
+ tmp = g_list_append (tmp, (gchar *) sid);
+ used_types |= curtype;
+ }
+ }
+
+beach:
+ /* Finally set the requested selection */
+ if (tmp) {
+ if (dbin->requested_selection) {
+ GST_FIXME_OBJECT (dbin,
+ "Replacing non-NULL requested_selection, what should we do ??");
+ g_list_free (dbin->requested_selection);
+ }
+ dbin->requested_selection = tmp;
+ dbin->selection_updated = TRUE;
+ }
+ SELECTION_UNLOCK (dbin);
+}
+
+/* Call with INPUT_LOCK taken */
+static GstStreamCollection *
+get_merged_collection (GstDecodebin3 * dbin)
+{
+ gboolean needs_merge = FALSE;
+ GstStreamCollection *res = NULL;
+ GList *tmp;
+ guint i, nb_stream;
+
+ /* First check if we need to do a merge or just return the only collection */
+ res = dbin->main_input->collection;
+
+ for (tmp = dbin->other_inputs; tmp; tmp = tmp->next) {
+ DecodebinInput *input = (DecodebinInput *) tmp->data;
+ if (input->collection) {
+ if (res) {
+ needs_merge = TRUE;
+ break;
+ }
+ res = input->collection;
+ }
+ }
+
+ if (!needs_merge) {
+ GST_DEBUG_OBJECT (dbin, "No need to merge, returning %p", res);
+ return res;
+ }
+
+ /* We really need to create a new collection */
+ /* FIXME : Some numbering scheme maybe ?? */
+ res = gst_stream_collection_new ("decodebin3");
+ if (dbin->main_input->collection) {
+ nb_stream = gst_stream_collection_get_size (dbin->main_input->collection);
+ GST_DEBUG_OBJECT (dbin, "main input %p %d", dbin->main_input, nb_stream);
+ for (i = 0; i < nb_stream; i++) {
+ GstStream *stream =
+ gst_stream_collection_get_stream (dbin->main_input->collection, i);
+ gst_stream_collection_add_stream (res, stream);
+ }
+ }
+
+ for (tmp = dbin->other_inputs; tmp; tmp = tmp->next) {
+ DecodebinInput *input = (DecodebinInput *) tmp->data;
+ GST_DEBUG_OBJECT (dbin, "input %p , collection %p", input,
+ input->collection);
+ if (input->collection) {
+ nb_stream = gst_stream_collection_get_size (input->collection);
+ GST_DEBUG_OBJECT (dbin, "nb_stream : %d", nb_stream);
+ for (i = 0; i < nb_stream; i++) {
+ GstStream *stream =
+ gst_stream_collection_get_stream (input->collection, i);
+ gst_stream_collection_add_stream (res, stream);
+ }
+ }
+ }
+
+ return res;
+}
+
+/* Call with INPUT_LOCK taken */
+static DecodebinInput *
+find_message_parsebin (GstDecodebin3 * dbin, GstElement * child)
+{
+ DecodebinInput *input = NULL;
+ GstElement *parent = gst_object_ref (child);
+ GList *tmp;
+
+ do {
+ GstElement *next_parent;
+
+ GST_DEBUG_OBJECT (dbin, "parent %s",
+ parent ? GST_ELEMENT_NAME (parent) : "<NONE>");
+
+ if (parent == dbin->main_input->parsebin) {
+ input = dbin->main_input;
+ break;
+ }
+ for (tmp = dbin->other_inputs; tmp; tmp = tmp->next) {
+ DecodebinInput *cur = (DecodebinInput *) tmp->data;
+ if (parent == cur->parsebin) {
+ input = cur;
+ break;
+ }
+ }
+ next_parent = (GstElement *) gst_element_get_parent (parent);
+ gst_object_unref (parent);
+ parent = next_parent;
+
+ } while (parent && parent != (GstElement *) dbin);
+
+ if (parent)
+ gst_object_unref (parent);
+
+ return input;
+}
+
+static gboolean
+stream_in_collection (GstDecodebin3 * dbin, gchar * sid)
+{
+ guint i, len;
+
+ if (dbin->collection == NULL)
+ return FALSE;
+ len = gst_stream_collection_get_size (dbin->collection);
+ for (i = 0; i < len; i++) {
+ GstStream *stream = gst_stream_collection_get_stream (dbin->collection, i);
+ const gchar *osid = gst_stream_get_stream_id (stream);
+ if (!g_strcmp0 (sid, osid))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Call with INPUT_LOCK taken */
+static void
+handle_stream_collection (GstDecodebin3 * dbin,
+ GstStreamCollection * collection, GstElement * child)
+{
+#ifndef GST_DISABLE_GST_DEBUG
+ const gchar *upstream_id;
+ guint i;
+#endif
+ DecodebinInput *input = find_message_parsebin (dbin, child);
+
+ if (!input) {
+ GST_DEBUG_OBJECT (dbin,
+ "Couldn't find corresponding input, most likely shutting down");
+ return;
+ }
+
+ /* Replace collection in input */
+ if (input->collection)
+ gst_object_unref (input->collection);
+ input->collection = collection;
+ GST_DEBUG_OBJECT (dbin, "Setting collection %p on input %p", collection,
+ input);
+
+ /* Merge collection if needed */
+ collection = get_merged_collection (dbin);
+
+#ifndef GST_DISABLE_GST_DEBUG
+ /* Just some debugging */
+ upstream_id = gst_stream_collection_get_upstream_id (collection);
+ GST_DEBUG ("Received Stream Collection. Upstream_id : %s", upstream_id);
+ GST_DEBUG ("From input %p", input);
+ GST_DEBUG (" %d streams", gst_stream_collection_get_size (collection));
+ for (i = 0; i < gst_stream_collection_get_size (collection); i++) {
+ GstStream *stream = gst_stream_collection_get_stream (collection, i);
+ const GstTagList *taglist;
+ const GstCaps *caps;
+
+ GST_DEBUG (" Stream '%s'", gst_stream_get_stream_id (stream));
+ GST_DEBUG (" type : %s",
+ gst_stream_type_get_name (gst_stream_get_stream_type (stream)));
+ GST_DEBUG (" flags : 0x%x", gst_stream_get_stream_flags (stream));
+ taglist = gst_stream_get_tags (stream);
+ GST_DEBUG (" tags : %" GST_PTR_FORMAT, taglist);
+ caps = gst_stream_get_caps (stream);
+ GST_DEBUG (" caps : %" GST_PTR_FORMAT, caps);
+ }
+#endif
+
+ /* Store collection for later usage */
+ if (dbin->collection == NULL) {
+ dbin->collection = collection;
+ } else {
+ /* We need to check who emitted this collection (the owner).
+ * If we already had a collection from that user, this one is an update,
+ * that is to say that we need to figure out how we are going to re-use
+ * the streams/slot */
+ GST_FIXME_OBJECT (dbin, "New collection but already had one ...");
+ /* FIXME : When do we switch from pending collection to active collection ?
+ * When all streams from active collection are drained in multiqueue output ? */
+ gst_object_unref (dbin->collection);
+ dbin->collection = collection;
+ /* dbin->pending_collection = */
+ /* g_list_append (dbin->pending_collection, collection); */
+ }
+}
+
+static void
+gst_decodebin3_handle_message (GstBin * bin, GstMessage * message)
+{
+ GstDecodebin3 *dbin = (GstDecodebin3 *) bin;
+ gboolean posting_collection = FALSE;
+
+ GST_DEBUG_OBJECT (bin, "Got Message %s", GST_MESSAGE_TYPE_NAME (message));
+
+ switch (GST_MESSAGE_TYPE (message)) {
+ case GST_MESSAGE_STREAM_COLLECTION:
+ {
+ GstStreamCollection *collection = NULL;
+ gst_message_parse_stream_collection (message, &collection);
+ if (collection) {
+ INPUT_LOCK (dbin);
+ handle_stream_collection (dbin, collection,
+ (GstElement *) GST_MESSAGE_SRC (message));
+ posting_collection = TRUE;
+ INPUT_UNLOCK (dbin);
+ }
+ if (dbin->collection && collection != dbin->collection) {
+ /* Replace collection message, we most likely aggregated it */
+ GstMessage *new_msg;
+ new_msg =
+ gst_message_new_stream_collection ((GstObject *) dbin,
+ dbin->collection);
+ gst_message_unref (message);
+ message = new_msg;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+
+ if (posting_collection) {
+ /* Figure out a selection for that collection */
+ update_requested_selection (dbin, dbin->collection);
+ }
+}
+
+static DecodebinOutputStream *
+find_free_compatible_output (GstDecodebin3 * dbin, GstStream * stream)
+{
+ GList *tmp;
+ GstStreamType stype = gst_stream_get_stream_type (stream);
+
+ for (tmp = dbin->output_streams; tmp; tmp = tmp->next) {
+ DecodebinOutputStream *output = (DecodebinOutputStream *) tmp->data;
+ if (output->type == stype && output->slot && output->slot->active_stream) {
+ GstStream *tstream = output->slot->active_stream;
+ if (!stream_in_list (dbin->requested_selection,
+ (gchar *) gst_stream_get_stream_id (tstream))) {
+ return output;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/* Give a certain slot, figure out if it should be linked to an
+ * output stream
+ * CALL WITH SELECTION LOCK TAKEN !*/
+static DecodebinOutputStream *
+get_output_for_slot (MultiQueueSlot * slot)
+{
+ GstDecodebin3 *dbin = slot->dbin;
+ DecodebinOutputStream *output = NULL;
+ const gchar *stream_id;
+ const GstCaps *caps;
+
+ /* If we already have a configured output, just use it */
+ if (slot->output != NULL)
+ return slot->output;
+
+ /*
+ * FIXME
+ *
+ * This method needs to be split into multiple parts
+ *
+ * 1) Figure out whether stream should be exposed or not
+ * This is based on autoplug-continue, EXPOSE_ALL_MODE, or presence
+ * in the default stream attribution
+ *
+ * 2) Figure out whether an output stream should be created, whether
+ * we can re-use the output stream already linked to the slot, or
+ * whether we need to get re-assigned another (currently used) output
+ * stream.
+ */
+
+ stream_id = gst_stream_get_stream_id (slot->active_stream);
+ caps = gst_stream_get_caps (slot->active_stream);
+ GST_DEBUG_OBJECT (dbin, "stream %s , %" GST_PTR_FORMAT, stream_id, caps);
+
+ /* 0. Emit autoplug-continue signal for pending caps ? */
+ GST_FIXME_OBJECT (dbin, "emit autoplug-continue");
+
+ /* 1. if in EXPOSE_ALL_MODE, just accept */
+ GST_FIXME_OBJECT (dbin, "Handle EXPOSE_ALL_MODE");
+
+#if 0
+ /* FIXME : The idea around this was to avoid activating a stream for
+ * which we have no decoder. Unfortunately it is way too
+ * expensive. Need to figure out a better solution */
+ /* 2. Is there a potential decoder (if one is required) */
+ if (!gst_caps_can_intersect (caps, dbin->caps)
+ && !have_factory (dbin, (GstCaps *) caps,
+ GST_ELEMENT_FACTORY_TYPE_DECODER)) {
+ GST_WARNING_OBJECT (dbin, "Don't have a decoder for %" GST_PTR_FORMAT,
+ caps);
+ SELECTION_UNLOCK (dbin);
+ gst_element_post_message (GST_ELEMENT_CAST (dbin),
+ gst_missing_decoder_message_new (GST_ELEMENT_CAST (dbin), caps));
+ SELECTION_LOCK (dbin);
+ return NULL;
+ }
+#endif
+
+ /* 3. In default mode check if we should expose */
+ if (stream_in_list (dbin->requested_selection, stream_id)) {
+ /* Check if we can steal an existing output stream we could re-use.
+ * that is:
+ * * an output stream whose slot->stream is not in requested
+ * * and is of the same type as this stream
+ */
+ output = find_free_compatible_output (dbin, slot->active_stream);
+ if (output) {
+ /* Move this output from its current slot to this slot */
+ dbin->to_activate =
+ g_list_append (dbin->to_activate, (gchar *) stream_id);
+ dbin->requested_selection =
+ g_list_remove (dbin->requested_selection, stream_id);
+ SELECTION_UNLOCK (dbin);
+ gst_pad_add_probe (output->slot->src_pad, GST_PAD_PROBE_TYPE_IDLE,
+ (GstPadProbeCallback) slot_unassign_probe, output->slot, NULL);
+ SELECTION_LOCK (dbin);
+ return NULL;
+ }
+
+ output = create_output_stream (dbin, slot->type);
+ output->slot = slot;
+ GST_DEBUG ("Linking slot %p to new output %p", slot, output);
+ slot->output = output;
+ dbin->active_selection =
+ g_list_append (dbin->active_selection, (gchar *) stream_id);
+ } else
+ GST_DEBUG ("Not creating any output for slot %p", slot);
+
+ return output;
+}
+
+/* Returns SELECTED_STREAMS message if active_selection is equal to
+ * requested_selection, else NULL.
+ * Must be called with LOCK taken */
+static GstMessage *
+is_selection_done (GstDecodebin3 * dbin)
+{
+ GList *tmp;
+ GstMessage *msg;
+
+ if (!dbin->selection_updated)
+ return NULL;
+
+ GST_LOG_OBJECT (dbin, "Checking");
+
+ if (dbin->to_activate != NULL) {
+ GST_DEBUG ("Still have streams to activate");
+ return NULL;
+ }
+ for (tmp = dbin->requested_selection; tmp; tmp = tmp->next) {
+ GST_DEBUG ("Checking requested stream %s", (gchar *) tmp->data);
+ if (!stream_in_list (dbin->active_selection, (gchar *) tmp->data)) {
+ GST_DEBUG ("Not in active selection, returning");
+ return NULL;
+ }
+ }
+
+ GST_DEBUG_OBJECT (dbin, "Selection active, creating message");
+
+ /* We are completely active */
+ msg = gst_message_new_streams_selected ((GstObject *) dbin, dbin->collection);
+ GST_MESSAGE_SEQNUM (msg) = dbin->select_streams_seqnum;
+ for (tmp = dbin->output_streams; tmp; tmp = tmp->next) {
+ DecodebinOutputStream *output = (DecodebinOutputStream *) tmp->data;
+ if (output->slot) {
+ GST_DEBUG_OBJECT (dbin, "Adding stream %s",
+ gst_stream_get_stream_id (output->slot->active_stream));
+
+ gst_message_streams_selected_add (msg, output->slot->active_stream);
+ } else
+ GST_WARNING_OBJECT (dbin, "No valid slot for output %p", output);
+ }
+ dbin->selection_updated = FALSE;
+ return msg;
+}
+
+static GstPadProbeReturn
+multiqueue_src_probe (GstPad * pad, GstPadProbeInfo * info,
+ MultiQueueSlot * slot)
+{
+ GstPadProbeReturn ret = GST_PAD_PROBE_OK;
+ GstDecodebin3 *dbin = slot->dbin;
+
+ if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info))) {
+ GstEvent *ev = GST_PAD_PROBE_INFO_EVENT (info);
+
+ GST_DEBUG_OBJECT (pad, "Got event %p %s", ev, GST_EVENT_TYPE_NAME (ev));
+ switch (GST_EVENT_TYPE (ev)) {
+ case GST_EVENT_STREAM_START:
+ {
+ GstStream *stream = NULL;
+ const gchar *stream_id;
+
+ gst_event_parse_stream (ev, &stream);
+ if (stream == NULL) {
+ GST_ERROR_OBJECT (pad,
+ "Got a STREAM_START event without a GstStream");
+ break;
+ }
+ stream_id = gst_stream_get_stream_id (stream);
+ GST_DEBUG_OBJECT (pad, "Stream Start '%s'", stream_id);
+ if (slot->active_stream == NULL) {
+ slot->active_stream = stream;
+ } else if (slot->active_stream != stream) {
+ GST_FIXME_OBJECT (pad, "Handle stream changes (%s => %s) !",
+ gst_stream_get_stream_id (slot->active_stream),
+ gst_stream_get_stream_id (stream));
+ gst_object_unref (slot->active_stream);
+ slot->active_stream = stream;
+ } else
+ gst_object_unref (stream);
+#if 0 /* Disabled because stream-start is pushed for every buffer on every unlinked pad */
+ {
+ gboolean is_active, is_requested;
+ /* Quick check to see if we're in the current selection */
+ /* FIXME : Re-check all slot<=>output mappings based on requested_selection */
+ SELECTION_LOCK (dbin);
+ GST_DEBUG_OBJECT (dbin, "Checking active selection");
+ is_active = stream_in_list (dbin->active_selection, stream_id);
+ GST_DEBUG_OBJECT (dbin, "Checking requested selection");
+ is_requested = stream_in_list (dbin->requested_selection, stream_id);
+ SELECTION_UNLOCK (dbin);
+ if (is_active)
+ GST_DEBUG_OBJECT (pad, "Slot in ACTIVE selection (output:%p)",
+ slot->output);
+ if (is_requested)
+ GST_DEBUG_OBJECT (pad, "Slot in REQUESTED selection (output:%p)",
+ slot->output);
+ else if (slot->output) {
+ GST_DEBUG_OBJECT (pad,
+ "Slot needs to be deactivated ? It's no longer in requested selection");
+ } else if (!is_active)
+ GST_DEBUG_OBJECT (pad,
+ "Slot in neither active nor requested selection");
+ }
+#endif
+ }
+ break;
+ case GST_EVENT_CAPS:
+ {
+ /* Configure the output slot if needed */
+ DecodebinOutputStream *output;
+ GstMessage *msg = NULL;
+ SELECTION_LOCK (dbin);
+ output = get_output_for_slot (slot);
+ if (output) {
+ reconfigure_output_stream (output, slot);
+ msg = is_selection_done (dbin);
+ }
+ SELECTION_UNLOCK (dbin);
+ if (msg)
+ gst_element_post_message ((GstElement *) slot->dbin, msg);
+ }
+ break;
+ case GST_EVENT_EOS:
+ /* FIXME : Figure out */
+ GST_FIXME_OBJECT (pad, "EOS on multiqueue source pad. input:%p",
+ slot->input);
+ if (slot->input == NULL) {
+ GstPad *peer;
+ GST_DEBUG_OBJECT (pad,
+ "last EOS for input, forwarding and removing slot");
+ peer = gst_pad_get_peer (pad);
+ if (peer) {
+ gst_pad_send_event (peer, ev);
+ gst_object_unref (peer);
+ }
+ SELECTION_LOCK (dbin);
+ /* FIXME : Shouldn't we try to re-assign the output instead of just
+ * removing it ? */
+ /* Remove the output */
+ if (slot->output) {
+ DecodebinOutputStream *output = slot->output;
+ dbin->output_streams = g_list_remove (dbin->output_streams, output);
+ free_output_stream (dbin, output);
+ }
+ SELECTION_UNLOCK (dbin);
+ ret = GST_PAD_PROBE_HANDLED;
+ }
+ break;
+ default:
+ break;
+ }
+ } else if (GST_IS_QUERY (GST_PAD_PROBE_INFO_DATA (info))) {
+ GstQuery *query = GST_PAD_PROBE_INFO_QUERY (info);
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CAPS:
+ {
+ GST_DEBUG_OBJECT (pad, "Intercepting CAPS query");
+ gst_query_set_caps_result (query, gst_caps_new_any ());
+ ret = GST_PAD_PROBE_HANDLED;
+ }
+ break;
+
+ case GST_QUERY_ACCEPT_CAPS:
+ {
+ GST_DEBUG_OBJECT (pad, "Intercepting Accept Caps query");
+ /* If the current decoder doesn't accept caps, we'll reconfigure
+ * on the actual caps event. So accept any caps. */
+ gst_query_set_accept_caps_result (query, TRUE);
+ ret = GST_PAD_PROBE_HANDLED;
+ }
+ default:
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/* Create a new multiqueue slot for the given type
+ *
+ * It is up to the caller to know whether that slot is needed or not
+ * (and release it when no longer needed) */
+static MultiQueueSlot *
+create_new_slot (GstDecodebin3 * dbin, GstStreamType type)
+{
+ MultiQueueSlot *slot;
+ GstIterator *it = NULL;
+ GValue item = { 0, };
+
+ GST_DEBUG_OBJECT (dbin, "Creating new slot for type %s",
+ gst_stream_type_get_name (type));
+ slot = g_new0 (MultiQueueSlot, 1);
+ slot->dbin = dbin;
+ slot->id = dbin->slot_id++;
+ slot->type = type;
+ slot->sink_pad = gst_element_get_request_pad (dbin->multiqueue, "sink_%u");
+ if (slot->sink_pad == NULL)
+ goto fail;
+ it = gst_pad_iterate_internal_links (slot->sink_pad);
+ if (!it || (gst_iterator_next (it, &item)) != GST_ITERATOR_OK
+ || ((slot->src_pad = g_value_dup_object (&item)) == NULL)) {
+ GST_ERROR ("Couldn't get srcpad from multiqueue for sink pad %s:%s",
+ GST_DEBUG_PAD_NAME (slot->src_pad));
+ goto fail;
+ }
+ gst_iterator_free (it);
+ g_value_reset (&item);
+
+ g_object_set (slot->sink_pad, "group-id", (guint) type, NULL);
+
+ /* Add event probe */
+ slot->probe_id =
+ gst_pad_add_probe (slot->src_pad,
+ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
+ (GstPadProbeCallback) multiqueue_src_probe, slot, NULL);
+
+ GST_DEBUG ("Created new slot %u (%p) (%s:%s)", slot->id, slot,
+ GST_DEBUG_PAD_NAME (slot->src_pad));
+ dbin->slots = g_list_append (dbin->slots, slot);
+ return slot;
+
+ /* ERRORS */
+fail:
+ {
+ if (slot->sink_pad)
+ gst_element_release_request_pad (dbin->multiqueue, slot->sink_pad);
+ g_free (slot);
+ return NULL;
+ }
+}
+
+static MultiQueueSlot *
+get_slot_for_input (GstDecodebin3 * dbin, DecodebinInputStream * input)
+{
+ GList *tmp;
+ MultiQueueSlot *empty_slot = NULL;
+ GstStreamType input_type = 0;
+ gchar *stream_id = NULL;
+
+ GST_DEBUG_OBJECT (dbin, "input %p (stream %p %s)",
+ input, input->active_stream,
+ input->
+ active_stream ? gst_stream_get_stream_id (input->active_stream) : "");
+
+ if (input->active_stream) {
+ input_type = gst_stream_get_stream_type (input->active_stream);
+ stream_id = (gchar *) gst_stream_get_stream_id (input->active_stream);
+ }
+
+ /* Go over existing slots and check if there is already one for it */
+ for (tmp = dbin->slots; tmp; tmp = tmp->next) {
+ MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
+ /* Already used input, return that one */
+ if (slot->input == input) {
+ GST_DEBUG_OBJECT (dbin, "Returning already specified slot %d", slot->id);
+ return slot;
+ }
+ }
+
+ /* Go amongst all unused slots of the right type and try to find a candidate */
+ for (tmp = dbin->slots; tmp; tmp = tmp->next) {
+ MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
+ if (slot->input == NULL && input_type == slot->type) {
+ /* Remember this empty slot for later */
+ empty_slot = slot;
+ /* Check if available slot is of the same stream_id */
+ GST_LOG_OBJECT (dbin, "Checking candidate slot %d (active_stream:%p)",
+ slot->id, slot->active_stream);
+ if (stream_id && slot->active_stream) {
+ gchar *ostream_id =
+ (gchar *) gst_stream_get_stream_id (slot->active_stream);
+ GST_DEBUG_OBJECT (dbin, "Checking slot %d %s against %s", slot->id,
+ ostream_id, stream_id);
+ if (!g_strcmp0 (stream_id, ostream_id))
+ break;
+ }
+ }
+ }
+
+ if (empty_slot) {
+ GST_DEBUG_OBJECT (dbin, "Re-using existing unused slot %d", empty_slot->id);
+ empty_slot->input = input;
+ return empty_slot;
+ }
+
+ if (input_type)
+ return create_new_slot (dbin, input_type);
+
+ return NULL;
+}
+
+static void
+link_input_to_slot (DecodebinInputStream * input, MultiQueueSlot * slot)
+{
+ GstEvent *event;
+ if (slot->input != NULL && slot->input != input) {
+ GST_ERROR_OBJECT (slot->dbin,
+ "Trying to link input to an already used slot");
+ return;
+ }
+ gst_pad_link_full (input->srcpad, slot->sink_pad, GST_PAD_LINK_CHECK_NOTHING);
+ slot->pending_stream = input->active_stream;
+ slot->input = input;
+ event = gst_pad_get_sticky_event (input->srcpad, GST_EVENT_STREAM_START, 0);
+ if (event)
+ gst_pad_send_event (slot->sink_pad, event);
+}
+
+#if 0
+static gboolean
+have_factory (GstDecodebin3 * dbin, GstCaps * caps,
+ GstElementFactoryListType ftype)
+{
+ gboolean ret = FALSE;
+ GList *res;
+
+ g_mutex_lock (&dbin->factories_lock);
+ gst_decode_bin_update_factories_list (dbin);
+ if (ftype == GST_ELEMENT_FACTORY_TYPE_DECODER)
+ res =
+ gst_element_factory_list_filter (dbin->decoder_factories,
+ caps, GST_PAD_SINK, TRUE);
+ else
+ res =
+ gst_element_factory_list_filter (dbin->decodable_factories,
+ caps, GST_PAD_SINK, TRUE);
+ g_mutex_unlock (&dbin->factories_lock);
+
+ if (res) {
+ ret = TRUE;
+ gst_plugin_feature_list_free (res);
+ }
+
+ return ret;
+}
+#endif
+
+static GstElement *
+create_element (GstDecodebin3 * dbin, GstStream * stream,
+ GstElementFactoryListType ftype)
+{
+ GList *res;
+ GstElement *element = NULL;
+
+ g_mutex_lock (&dbin->factories_lock);
+ gst_decode_bin_update_factories_list (dbin);
+ if (ftype == GST_ELEMENT_FACTORY_TYPE_DECODER)
+ res =
+ gst_element_factory_list_filter (dbin->decoder_factories,
+ gst_stream_get_caps (stream), GST_PAD_SINK, TRUE);
+ else
+ res =
+ gst_element_factory_list_filter (dbin->decodable_factories,
+ gst_stream_get_caps (stream), GST_PAD_SINK, TRUE);
+ g_mutex_unlock (&dbin->factories_lock);
+
+ if (res) {
+ element =
+ gst_element_factory_create ((GstElementFactory *) res->data, NULL);
+ GST_DEBUG ("Created element '%s'", GST_ELEMENT_NAME (element));
+ gst_plugin_feature_list_free (res);
+ } else {
+ GST_DEBUG ("Could not find an element for caps %" GST_PTR_FORMAT,
+ gst_stream_get_caps (stream));
+ }
+
+ return element;
+}
+
+/* FIXME : VERY NAIVE. ASSUMING FIRST ONE WILL WORK */
+static GstElement *
+create_decoder (GstDecodebin3 * dbin, GstStream * stream)
+{
+ return create_element (dbin, stream, GST_ELEMENT_FACTORY_TYPE_DECODER);
+}
+
+static GstPadProbeReturn
+keyframe_waiter_probe (GstPad * pad, GstPadProbeInfo * info,
+ DecodebinOutputStream * output)
+{
+ GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER (info);
+ /* If we have a keyframe, remove the probe and let all data through */
+ /* FIXME : HANDLE HEADER BUFFER ?? */
+ if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ||
+ GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_HEADER)) {
+ GST_DEBUG_OBJECT (pad,
+ "Buffer is keyframe or header, letting through and removing probe");
+ output->drop_probe_id = 0;
+ return GST_PAD_PROBE_REMOVE;
+ }
+ GST_DEBUG_OBJECT (pad, "Buffer is not a keyframe, dropping");
+ return GST_PAD_PROBE_DROP;
+}
+
+static void
+reconfigure_output_stream (DecodebinOutputStream * output,
+ MultiQueueSlot * slot)
+{
+ GstDecodebin3 *dbin = output->dbin;
+ GstCaps *new_caps = (GstCaps *) gst_stream_get_caps (slot->active_stream);
+ gboolean needs_decoder;
+
+ needs_decoder = gst_caps_can_intersect (new_caps, dbin->caps) != TRUE;
+
+ GST_DEBUG_OBJECT (dbin,
+ "Reconfiguring output %p to slot %p, needs_decoder:%d", output, slot,
+ needs_decoder);
+
+ /* FIXME : Maybe make the output un-hook itself automatically ? */
+ if (output->slot != NULL && output->slot != slot) {
+ GST_WARNING_OBJECT (dbin,
+ "Output still linked to another slot (%p)", output->slot);
+ return;
+ }
+
+ /* Check if existing config is reusable as-is by checking if
+ * the existing decoder accepts the new caps, if not delete
+ * it and create a new one */
+ if (output->decoder) {
+ gboolean can_reuse_decoder;
+
+ if (needs_decoder) {
+ GstQuery *q = gst_query_new_accept_caps (new_caps);
+ can_reuse_decoder = gst_pad_query (output->decoder_sink, q);
+ } else
+ can_reuse_decoder = FALSE;
+
+ if (can_reuse_decoder) {
+ if (output->type == GST_STREAM_TYPE_VIDEO && output->drop_probe_id == 0) {
+ GST_DEBUG_OBJECT (dbin, "Adding keyframe-waiter probe");
+ output->drop_probe_id =
+ gst_pad_add_probe (slot->src_pad, GST_PAD_PROBE_TYPE_BUFFER,
+ (GstPadProbeCallback) keyframe_waiter_probe, output, NULL);
+ }
+ GST_DEBUG_OBJECT (dbin, "Reusing existing decoder for slot %p", slot);
+ if (output->linked == FALSE) {
+ gst_pad_link_full (slot->src_pad, output->decoder_sink,
+ GST_PAD_LINK_CHECK_NOTHING);
+ output->linked = TRUE;
+ }
+ return;
+ }
+
+ GST_DEBUG_OBJECT (dbin, "Removing old decoder for slot %p", slot);
+
+ if (output->linked)
+ gst_pad_unlink (slot->src_pad, output->decoder_sink);
+ output->linked = FALSE;
+ if (output->drop_probe_id) {
+ gst_pad_remove_probe (slot->src_pad, output->drop_probe_id);
+ output->drop_probe_id = 0;
+ }
+
+ if (!gst_ghost_pad_set_target ((GstGhostPad *) output->src_pad, NULL)) {
+ GST_ERROR_OBJECT (dbin, "Could not release decoder pad");
+ goto cleanup;
+ }
+
+ gst_object_replace ((GstObject **) & output->decoder_sink, NULL);
+ gst_object_replace ((GstObject **) & output->decoder_src, NULL);
+
+ gst_element_set_locked_state (output->decoder, TRUE);
+ gst_element_set_state (output->decoder, GST_STATE_NULL);
+
+ gst_bin_remove ((GstBin *) dbin, output->decoder);
+ output->decoder = NULL;
+ }
+
+ /* If a decoder is required, create one */
+ if (needs_decoder) {
+ /* If we don't have a decoder yet, instantiate one */
+ output->decoder = create_decoder (dbin, slot->active_stream);
+ if (output->decoder == NULL) {
+ SELECTION_UNLOCK (dbin);
+ /* FIXME : Should we be smarter if there's a missing decoder ?
+ * Should we deactivate that stream ? */
+ gst_element_post_message (GST_ELEMENT_CAST (dbin),
+ gst_missing_decoder_message_new (GST_ELEMENT_CAST (dbin),
+ gst_stream_get_caps (slot->active_stream)));
+ SELECTION_LOCK (dbin);
+ goto cleanup;
+ }
+ if (!gst_bin_add ((GstBin *) dbin, output->decoder)) {
+ GST_ERROR_OBJECT (dbin, "could not add decoder to pipeline");
+ goto cleanup;
+ }
+ output->decoder_sink = gst_element_get_static_pad (output->decoder, "sink");
+ output->decoder_src = gst_element_get_static_pad (output->decoder, "src");
+ if (output->type == GST_STREAM_TYPE_VIDEO) {
+ GST_DEBUG_OBJECT (dbin, "Adding keyframe-waiter probe");
+ output->drop_probe_id =
+ gst_pad_add_probe (slot->src_pad, GST_PAD_PROBE_TYPE_BUFFER,
+ (GstPadProbeCallback) keyframe_waiter_probe, output, NULL);
+ }
+ if (gst_pad_link_full (slot->src_pad, output->decoder_sink,
+ GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK) {
+ GST_ERROR_OBJECT (dbin, "could not link to %s:%s",
+ GST_DEBUG_PAD_NAME (output->decoder_sink));
+ goto cleanup;
+ }
+ } else {
+ output->decoder_src = gst_object_ref (slot->src_pad);
+ output->decoder_sink = NULL;
+ }
+ output->linked = TRUE;
+ if (!gst_ghost_pad_set_target ((GstGhostPad *) output->src_pad,
+ output->decoder_src)) {
+ GST_ERROR_OBJECT (dbin, "Could not expose decoder pad");
+ goto cleanup;
+ }
+ if (output->src_exposed == FALSE) {
+ output->src_exposed = TRUE;
+ gst_element_add_pad (GST_ELEMENT_CAST (dbin), output->src_pad);
+ }
+
+ if (output->decoder)
+ gst_element_sync_state_with_parent (output->decoder);
+
+ output->slot = slot;
+ return;
+
+cleanup:
+ {
+ GST_DEBUG_OBJECT (dbin, "Cleanup");
+ if (output->decoder_sink) {
+ gst_object_unref (output->decoder_sink);
+ output->decoder_sink = NULL;
+ }
+ if (output->decoder_src) {
+ gst_object_unref (output->decoder_src);
+ output->decoder_src = NULL;
+ }
+ if (output->decoder) {
+ gst_element_set_state (output->decoder, GST_STATE_NULL);
+ gst_bin_remove ((GstBin *) dbin, output->decoder);
+ output->decoder = NULL;
+ }
+ }
+}
+
+static GstPadProbeReturn
+idle_reconfigure (GstPad * pad, GstPadProbeInfo * info, MultiQueueSlot * slot)
+{
+ GstMessage *msg = NULL;
+ DecodebinOutputStream *output;
+
+ SELECTION_LOCK (slot->dbin);
+ output = get_output_for_slot (slot);
+
+ GST_DEBUG_OBJECT (pad, "output : %p", output);
+
+ if (output) {
+ reconfigure_output_stream (output, slot);
+ msg = is_selection_done (slot->dbin);
+ }
+ SELECTION_UNLOCK (slot->dbin);
+ if (msg)
+ gst_element_post_message ((GstElement *) slot->dbin, msg);
+
+ return GST_PAD_PROBE_REMOVE;
+}
+
+static MultiQueueSlot *
+find_slot_for_stream_id (GstDecodebin3 * dbin, const gchar * sid)
+{
+ GList *tmp;
+
+ for (tmp = dbin->slots; tmp; tmp = tmp->next) {
+ MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
+ const gchar *stream_id;
+ if (slot->active_stream) {
+ stream_id = gst_stream_get_stream_id (slot->active_stream);
+ if (!g_strcmp0 (sid, stream_id))
+ return slot;
+ }
+ if (slot->pending_stream && slot->pending_stream != slot->active_stream) {
+ stream_id = gst_stream_get_stream_id (slot->pending_stream);
+ if (!g_strcmp0 (sid, stream_id))
+ return slot;
+ }
+ }
+
+ return NULL;
+}
+
+/* This function handles the reassignment of a slot. Call this from
+ * the streaming thread of a slot. */
+static gboolean
+reassign_slot (GstDecodebin3 * dbin, MultiQueueSlot * slot)
+{
+ DecodebinOutputStream *output;
+ MultiQueueSlot *target_slot = NULL;
+ GList *tmp;
+ const gchar *sid, *tsid;
+
+ SELECTION_LOCK (dbin);
+ output = slot->output;
+
+ if (G_UNLIKELY (slot->active_stream == NULL)) {
+ GST_DEBUG_OBJECT (slot->src_pad,
+ "Called on inactive slot (active_stream == NULL)");
+ SELECTION_UNLOCK (dbin);
+ return FALSE;
+ }
+
+ if (G_UNLIKELY (output == NULL)) {
+ GST_DEBUG_OBJECT (slot->src_pad,
+ "Slot doesn't have any output to be removed");
+ SELECTION_UNLOCK (dbin);
+ return FALSE;
+ }
+
+ sid = gst_stream_get_stream_id (slot->active_stream);
+ GST_DEBUG_OBJECT (slot->src_pad, "slot %s %p", sid, slot);
+
+ /* Recheck whether this stream is still in the list of streams to deactivate */
+ if (stream_in_list (dbin->requested_selection, sid)) {
+ /* Stream is in the list of requested streams, don't remove */
+ SELECTION_UNLOCK (dbin);
+ GST_DEBUG_OBJECT (slot->src_pad,
+ "Stream '%s' doesn't need to be deactivated", sid);
+ return FALSE;
+ }
+
+ /* Unlink slot from output */
+ /* FIXME : Handle flushing ? */
+ /* FIXME : Handle outputs without decoders */
+ GST_DEBUG_OBJECT (slot->src_pad, "Unlinking from decoder %p",
+ output->decoder_sink);
+ if (output->decoder_sink)
+ gst_pad_unlink (slot->src_pad, output->decoder_sink);
+ output->linked = FALSE;
+ slot->output = NULL;
+ output->slot = NULL;
+ /* Remove sid from active selection */
+ for (tmp = dbin->active_selection; tmp; tmp = tmp->next)
+ if (!g_strcmp0 (sid, tmp->data)) {
+ dbin->active_selection = g_list_delete_link (dbin->active_selection, tmp);
+ break;
+ }
+
+ /* Can we re-assign this output to a requested stream ? */
+ GST_DEBUG_OBJECT (slot->src_pad, "Attempting to re-assing output stream");
+ for (tmp = dbin->to_activate; tmp; tmp = tmp->next) {
+ MultiQueueSlot *tslot = find_slot_for_stream_id (dbin, tmp->data);
+ GST_LOG_OBJECT (tslot->src_pad, "Checking slot %p (output:%p , stream:%s)",
+ tslot, tslot->output, gst_stream_get_stream_id (tslot->active_stream));
+ if (tslot && tslot->type == output->type && tslot->output == NULL) {
+ GST_DEBUG_OBJECT (tslot->src_pad, "Using as reassigned slot");
+ target_slot = tslot;
+ tsid = tmp->data;
+ /* Pass target stream id to requested selection */
+ dbin->requested_selection =
+ g_list_append (dbin->requested_selection, tmp->data);
+ dbin->to_activate = g_list_remove (dbin->to_activate, tmp->data);
+ break;
+ }
+ }
+
+ if (target_slot) {
+ GST_DEBUG_OBJECT (slot->src_pad, "Assigning output to slot %p '%s'",
+ target_slot, tsid);
+ target_slot->output = output;
+ output->slot = target_slot;
+ dbin->active_selection =
+ g_list_append (dbin->active_selection, (gchar *) tsid);
+ SELECTION_UNLOCK (dbin);
+
+ /* Wakeup the target slot so that it retries to send events/buffers
+ * thereby triggering the output reconfiguration codepath */
+ gst_pad_add_probe (target_slot->src_pad, GST_PAD_PROBE_TYPE_IDLE,
+ (GstPadProbeCallback) idle_reconfigure, target_slot, NULL);
+ /* gst_pad_send_event (target_slot->src_pad, gst_event_new_reconfigure ()); */
+ } else {
+ SELECTION_UNLOCK (dbin);
+ /* FIXME : Remove output if no longer needed ? The tricky part is knowing
+ * if it's really no longer needed or not */
+ GST_FIXME_OBJECT (slot->src_pad, "Remove unused output stream ?");
+ }
+
+ return TRUE;
+}
+
+/* Idle probe called when a slot should be unassigned from its output stream.
+ * This is needed to ensure nothing is flowing when unlinking the slot.
+ *
+ * Also, this method will search for a pending stream which could re-use
+ * the output stream. */
+static GstPadProbeReturn
+slot_unassign_probe (GstPad * pad, GstPadProbeInfo * info,
+ MultiQueueSlot * slot)
+{
+ GstDecodebin3 *dbin = slot->dbin;
+
+ reassign_slot (dbin, slot);
+
+ return GST_PAD_PROBE_REMOVE;
+}
+
+static gboolean
+handle_stream_switch (GstDecodebin3 * dbin, GList * select_streams,
+ guint32 seqnum)
+{
+ gboolean ret = TRUE;
+ GList *tmp;
+ /* List of slots to (de)activate. */
+ GList *to_deactivate = NULL;
+ GList *to_activate = NULL;
+ /* List of unknown stream id, most likely means the event
+ * should be sent upstream so that elements can expose the requested stream */
+ GList *unknown = NULL;
+ GList *to_reassign = NULL;
+ GList *future_request_streams = NULL;
+ GList *pending_streams = NULL;
+ GList *slots_to_reassign = NULL;
+
+ SELECTION_LOCK (dbin);
+ if (G_UNLIKELY (seqnum != dbin->select_streams_seqnum)) {
+ GST_DEBUG_OBJECT (dbin, "New SELECT_STREAMS has arrived in the meantime");
+ SELECTION_UNLOCK (dbin);
+ return TRUE;
+ }
+ /* Remove pending select_streams */
+ g_list_free (dbin->pending_select_streams);
+ dbin->pending_select_streams = NULL;
+
+ /* COMPARE the requested streams to the active and requested streams
+ * on multiqueue. */
+
+ /* First check the slots to activate and which ones are unknown */
+ for (tmp = select_streams; tmp; tmp = tmp->next) {
+ const gchar *sid = (const gchar *) tmp->data;
+ MultiQueueSlot *slot;
+ GST_DEBUG_OBJECT (dbin, "Checking stream '%s'", sid);
+ slot = find_slot_for_stream_id (dbin, sid);
+ /* Find the corresponding slot */
+ if (slot == NULL) {
+ if (stream_in_collection (dbin, (gchar *) sid)) {
+ pending_streams = g_list_append (pending_streams, (gchar *) sid);
+ } else {
+ GST_DEBUG_OBJECT (dbin, "We don't have a slot for stream '%s'", sid);
+ unknown = g_list_append (unknown, (gchar *) sid);
+ }
+ } else if (slot->output == NULL) {
+ GST_DEBUG_OBJECT (dbin, "We need to activate slot %p for stream '%s')",
+ slot, sid);
+ to_activate = g_list_append (to_activate, slot);
+ } else {
+ GST_DEBUG_OBJECT (dbin,
+ "Stream '%s' from slot %p is already active on output %p", sid, slot,
+ slot->output);
+ future_request_streams =
+ g_list_append (future_request_streams, (gchar *) sid);
+ }
+ }
+
+ for (tmp = dbin->slots; tmp; tmp = tmp->next) {
+ MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
+ /* For slots that have an output, check if it's part of the streams to
+ * be active */
+ if (slot->output) {
+ gboolean slot_to_deactivate = TRUE;
+
+ if (slot->active_stream) {
+ if (stream_in_list (select_streams,
+ gst_stream_get_stream_id (slot->active_stream)))
+ slot_to_deactivate = FALSE;
+ }
+ if (slot_to_deactivate && slot->pending_stream
+ && slot->pending_stream != slot->active_stream) {
+ if (stream_in_list (select_streams,
+ gst_stream_get_stream_id (slot->pending_stream)))
+ slot_to_deactivate = FALSE;
+ }
+ if (slot_to_deactivate) {
+ GST_DEBUG_OBJECT (dbin,
+ "Slot %p (%s) should be deactivated, no longer used", slot,
+ gst_stream_get_stream_id (slot->active_stream));
+ to_deactivate = g_list_append (to_deactivate, slot);
+ }
+ }
+ }
+
+ if (to_deactivate != NULL) {
+ GST_DEBUG_OBJECT (dbin, "Check if we can reassign slots");
+ /* We need to compare what needs to be activated and deactivated in order
+ * to determine whether there are outputs that can be transferred */
+ /* Take the stream-id of the slots that are to be activated, for which there
+ * is a slot of the same type that needs to be deactivated */
+ tmp = to_deactivate;
+ while (tmp) {
+ MultiQueueSlot *slot_to_deactivate = (MultiQueueSlot *) tmp->data;
+ gboolean removeit = FALSE;
+ GList *tmp2, *next;
+ GST_DEBUG_OBJECT (dbin,
+ "Checking if slot to deactivate (%p) has a candidate slot to activate",
+ slot_to_deactivate);
+ for (tmp2 = to_activate; tmp2; tmp2 = tmp2->next) {
+ MultiQueueSlot *slot_to_activate = (MultiQueueSlot *) tmp2->data;
+ GST_DEBUG_OBJECT (dbin, "Comparing to slot %p", slot_to_activate);
+ if (slot_to_activate->type == slot_to_deactivate->type) {
+ GST_DEBUG_OBJECT (dbin, "Re-using");
+ to_reassign = g_list_append (to_reassign, (gchar *)
+ gst_stream_get_stream_id (slot_to_activate->active_stream));
+ slots_to_reassign =
+ g_list_append (slots_to_reassign, slot_to_deactivate);
+ to_activate = g_list_remove (to_activate, slot_to_activate);
+ removeit = TRUE;
+ break;
+ }
+ }
+ next = tmp->next;
+ if (removeit)
+ to_deactivate = g_list_delete_link (to_deactivate, tmp);
+ tmp = next;
+ }
+ }
+
+ for (tmp = to_deactivate; tmp; tmp = tmp->next) {
+ MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
+ GST_DEBUG_OBJECT (dbin,
+ "Really need to deactivate slot %p, but no available alternative",
+ slot);
+ }
+
+ /* The only slots left to activate are the ones that won't be reassigned and
+ * therefore really need to have a new output created */
+ for (tmp = to_activate; tmp; tmp = tmp->next) {
+ MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
+ if (slot->active_stream)
+ future_request_streams =
+ g_list_append (future_request_streams,
+ (gchar *) gst_stream_get_stream_id (slot->active_stream));
+ else if (slot->pending_stream)
+ future_request_streams =
+ g_list_append (future_request_streams,
+ (gchar *) gst_stream_get_stream_id (slot->pending_stream));
+ else
+ GST_ERROR_OBJECT (dbin, "No stream for slot %p !!", slot);
+ }
+
+ if (to_activate == NULL && pending_streams != NULL) {
+ GST_DEBUG_OBJECT (dbin, "Stream switch requested for future collection");
+ if (dbin->requested_selection)
+ g_list_free (dbin->requested_selection);
+ dbin->requested_selection = select_streams;
+ g_list_free (to_deactivate);
+ g_list_free (pending_streams);
+ to_deactivate = NULL;
+ } else {
+ if (dbin->requested_selection)
+ g_list_free (dbin->requested_selection);
+ dbin->requested_selection = future_request_streams;
+ dbin->requested_selection =
+ g_list_concat (dbin->requested_selection, pending_streams);
+ if (dbin->to_activate)
+ g_list_free (dbin->to_activate);
+ dbin->to_activate = to_reassign;
+ }
+
+ dbin->selection_updated = TRUE;
+ SELECTION_UNLOCK (dbin);
+
+ if (unknown)
+ GST_FIXME_OBJECT (dbin, "Got request for an unknown stream");
+
+ /* For all streams to deactivate, add an idle probe where we will do
+ * the unassignment and switch over */
+ for (tmp = slots_to_reassign; tmp; tmp = tmp->next) {
+ MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
+ gst_pad_add_probe (slot->src_pad, GST_PAD_PROBE_TYPE_IDLE,
+ (GstPadProbeCallback) slot_unassign_probe, slot, NULL);
+ }
+
+ return ret;
+}
+
+static GstPadProbeReturn
+ghost_pad_event_probe (GstPad * pad, GstPadProbeInfo * info,
+ DecodebinOutputStream * output)
+{
+ GstPadProbeReturn ret = GST_PAD_PROBE_OK;
+ GstDecodebin3 *dbin = output->dbin;
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+
+ GST_DEBUG_OBJECT (pad, "Got event %p %s", event, GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_SELECT_STREAMS:
+ {
+ GstPad *peer;
+ GList *streams = NULL;
+ guint32 seqnum = gst_event_get_seqnum (event);
+
+ SELECTION_LOCK (dbin);
+ if (seqnum == dbin->select_streams_seqnum) {
+ SELECTION_UNLOCK (dbin);
+ GST_DEBUG_OBJECT (pad,
+ "Already handled/handling that SELECT_STREAMS event");
+ break;
+ }
+ dbin->select_streams_seqnum = seqnum;
+ if (dbin->pending_select_streams != NULL) {
+ GST_LOG_OBJECT (dbin, "Replacing pending select streams");
+ g_list_free (dbin->pending_select_streams);
+ dbin->pending_select_streams = NULL;
+ }
+ gst_event_parse_select_streams (event, &streams);
+ dbin->pending_select_streams = g_list_copy (streams);
+ SELECTION_UNLOCK (dbin);
+
+ /* Send event upstream */
+ if ((peer = gst_pad_get_peer (pad))) {
+ gst_pad_send_event (peer, event);
+ gst_object_unref (peer);
+ }
+ /* Finally handle the switch */
+ if (streams)
+ handle_stream_switch (dbin, streams, seqnum);
+ ret = GST_PAD_PROBE_HANDLED;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static gboolean
+gst_decodebin3_send_event (GstElement * element, GstEvent * event)
+{
+ GST_DEBUG_OBJECT (element, "event %s", GST_EVENT_TYPE_NAME (event));
+ if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) {
+ GstDecodebin3 *dbin = (GstDecodebin3 *) element;
+ GList *streams = NULL;
+ guint32 seqnum = gst_event_get_seqnum (event);
+
+ SELECTION_LOCK (dbin);
+ if (seqnum == dbin->select_streams_seqnum) {
+ SELECTION_UNLOCK (dbin);
+ GST_DEBUG_OBJECT (dbin,
+ "Already handled/handling that SELECT_STREAMS event");
+ return TRUE;
+ }
+ dbin->select_streams_seqnum = seqnum;
+ if (dbin->pending_select_streams != NULL) {
+ GST_LOG_OBJECT (dbin, "Replacing pending select streams");
+ g_list_free (dbin->pending_select_streams);
+ dbin->pending_select_streams = NULL;
+ }
+ gst_event_parse_select_streams (event, &streams);
+ dbin->pending_select_streams = g_list_copy (streams);
+ SELECTION_UNLOCK (dbin);
+
+ /* FIXME : We don't have an upstream ?? */
+#if 0
+ /* Send event upstream */
+ if ((peer = gst_pad_get_peer (pad))) {
+ gst_pad_send_event (peer, event);
+ gst_object_unref (peer);
+ }
+#endif
+ /* Finally handle the switch */
+ if (streams)
+ handle_stream_switch (dbin, streams, seqnum);
+
+ return TRUE;
+ }
+ return GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
+}
+
+
+static void
+free_multiqueue_slot (GstDecodebin3 * dbin, MultiQueueSlot * slot)
+{
+ if (slot->probe_id)
+ gst_pad_remove_probe (slot->src_pad, slot->probe_id);
+ if (slot->input) {
+ if (slot->input->srcpad)
+ gst_pad_unlink (slot->input->srcpad, slot->sink_pad);
+ }
+
+ gst_element_release_request_pad (dbin->multiqueue, slot->sink_pad);
+ gst_object_replace ((GstObject **) & slot->sink_pad, NULL);
+ gst_object_replace ((GstObject **) & slot->src_pad, NULL);
+ g_free (slot);
+}
+
+/* Create a DecodebinOutputStream for a given type
+ * Note: It will be empty initially, it needs to be configured
+ * afterwards */
+static DecodebinOutputStream *
+create_output_stream (GstDecodebin3 * dbin, GstStreamType type)
+{
+ DecodebinOutputStream *res = g_new0 (DecodebinOutputStream, 1);
+ gchar *pad_name;
+ const gchar *prefix;
+ GstStaticPadTemplate *templ;
+ GstPadTemplate *ptmpl;
+ guint32 *counter;
+ GstPad *internal_pad;
+
+ GST_DEBUG_OBJECT (dbin, "Created new output stream %p for type %s",
+ res, gst_stream_type_get_name (type));
+
+ res->type = type;
+ res->dbin = dbin;
+
+ switch (type) {
+ case GST_STREAM_TYPE_VIDEO:
+ templ = &video_src_template;
+ counter = &dbin->vpadcount;
+ prefix = "video";
+ break;
+ case GST_STREAM_TYPE_AUDIO:
+ templ = &audio_src_template;
+ counter = &dbin->apadcount;
+ prefix = "audio";
+ break;
+ case GST_STREAM_TYPE_TEXT:
+ templ = &text_src_template;
+ counter = &dbin->tpadcount;
+ prefix = "text";
+ break;
+ default:
+ templ = &src_template;
+ counter = &dbin->opadcount;
+ prefix = "src";
+ break;
+ }
+
+ pad_name = g_strdup_printf ("%s_%u", prefix, *counter);
+ *counter += 1;
+ ptmpl = gst_static_pad_template_get (templ);
+ res->src_pad = gst_ghost_pad_new_no_target_from_template (pad_name, ptmpl);
+ gst_object_unref (ptmpl);
+ g_free (pad_name);
+ gst_pad_set_active (res->src_pad, TRUE);
+ /* Put an event probe on the internal proxy pad to detect upstream
+ * events */
+ internal_pad =
+ (GstPad *) gst_proxy_pad_get_internal ((GstProxyPad *) res->src_pad);
+ gst_pad_add_probe (internal_pad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
+ (GstPadProbeCallback) ghost_pad_event_probe, res, NULL);
+ gst_object_unref (internal_pad);
+
+ dbin->output_streams = g_list_append (dbin->output_streams, res);
+
+ return res;
+}
+
+static void
+free_output_stream (GstDecodebin3 * dbin, DecodebinOutputStream * output)
+{
+ if (output->slot) {
+ if (output->decoder_sink && output->decoder)
+ gst_pad_unlink (output->slot->src_pad, output->decoder_sink);
+
+ output->slot->output = NULL;
+ output->slot = NULL;
+ }
+ gst_object_replace ((GstObject **) & output->decoder_sink, NULL);
+ gst_ghost_pad_set_target ((GstGhostPad *) output->src_pad, NULL);
+ gst_object_replace ((GstObject **) & output->decoder_src, NULL);
+ if (output->src_exposed) {
+ gst_element_remove_pad ((GstElement *) dbin, output->src_pad);
+ }
+ if (output->decoder) {
+ gst_element_set_locked_state (output->decoder, TRUE);
+ gst_element_set_state (output->decoder, GST_STATE_NULL);
+ gst_bin_remove ((GstBin *) dbin, output->decoder);
+ }
+ g_free (output);
+}
+
+static GstStateChangeReturn
+gst_decodebin3_change_state (GstElement * element, GstStateChange transition)
+{
+ GstDecodebin3 *dbin = (GstDecodebin3 *) element;
+ GstStateChangeReturn ret;
+
+ /* Upwards */
+ switch (transition) {
+ default:
+ break;
+ }
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto beach;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ {
+ GList *tmp;
+
+ /* Free output streams */
+ for (tmp = dbin->output_streams; tmp; tmp = tmp->next) {
+ DecodebinOutputStream *output = (DecodebinOutputStream *) tmp->data;
+ free_output_stream (dbin, output);
+ }
+ g_list_free (dbin->output_streams);
+ dbin->output_streams = NULL;
+ /* Free multiqueue slots */
+ for (tmp = dbin->slots; tmp; tmp = tmp->next) {
+ MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
+ free_multiqueue_slot (dbin, slot);
+ }
+ g_list_free (dbin->slots);
+ dbin->slots = NULL;
+ /* Free inputs */
+ }
+ break;
+ default:
+ break;
+ }
+beach:
+ return ret;
+}
+
+gboolean
+gst_decodebin3_plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (decodebin3_debug, "decodebin3", 0, "decoder bin");
+
+ return gst_element_register (plugin, "decodebin3", GST_RANK_NONE,
+ GST_TYPE_DECODEBIN3);
+}
--- /dev/null
+/* GStreamer
+ * Copyright (C) <2006> Edward Hervey <edward@fluendo.com>
+ * Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2011> Hewlett-Packard Development Company, L.P.
+ * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, Collabora Ltd.
+ * Copyright (C) <2013> Collabora Ltd.
+ * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-ParseBin
+ *
+ * #GstBin that auto-magically constructs a parsing pipeline
+ * using available parsers and demuxers via auto-plugging.
+ *
+ */
+
+/* Implementation notes:
+ *
+ * The following section describes how ParseBin works internally.
+ *
+ * The first part of ParseBin is its typefind element, which tries
+ * to determine the media type of the input stream. If the type is found
+ * autoplugging starts.
+ *
+ * ParseBin internally organizes the elements it autoplugged into
+ * GstParseChains and GstParseGroups. A parse chain is a single chain
+ * of parsing, this
+ * means that if ParseBin ever autoplugs an element with two+ srcpads
+ * (e.g. a demuxer) this will end the chain and everything following this
+ * demuxer will be put into parse groups below the chain. Otherwise,
+ * if an element has a single srcpad that outputs raw data the parse chain
+ * is ended too and a GstParsePad is stored and blocked.
+ *
+ * A parse group combines a number of chains that are created by a
+ * demuxer element.
+ *
+ * This continues until the top-level parse chain is complete. A parse
+ * chain is complete if it either ends with a blocked elementary stream,
+ * if autoplugging stopped because no suitable plugins could be found
+ * or if the active group is complete. A parse group on the other hand
+ * is complete if all child chains are complete.
+ *
+ * If this happens at some point, all end pads of all active groups are exposed.
+ * For this ParseBin adds the end pads, and then unblocks them. Now playback starts.
+ *
+ * If one of the chains that end on a endpad receives EOS ParseBin checks
+ * if all chains and groups are drained. In that case everything goes into EOS.
+ * If there is a chain where the active group is drained but there exist next
+ * groups, the active group is hidden (endpads are removed) and the next group
+ * is exposed. This means that in some cases more pads may be created even
+ * after the initial no-more-pads signal. This happens for example with
+ * so-called "chained oggs", most commonly found among ogg/vorbis internet
+ * radio streams.
+ *
+ * Note 1: If we're talking about blocked endpads this really means that the
+ * *target* pads of the endpads are blocked. Pads that are exposed to the outside
+ * should never ever be blocked!
+ *
+ * Note 2: If a group is complete and the parent's chain demuxer adds new pads
+ * but never signaled no-more-pads this additional pads will be ignored!
+ *
+ */
+
+/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
+ * with newer GLib versions (>= 2.31.0) */
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst-i18n-plugin.h>
+
+#include <string.h>
+#include <gst/gst.h>
+#include <gst/pbutils/pbutils.h>
+
+#include "gstplay-enum.h"
+#include "gstplayback.h"
+
+/* Also used by gsturidecodebin.c */
+gint _parse_bin_compare_factories_func (gconstpointer p1, gconstpointer p2);
+
+/* generic templates */
+static GstStaticPadTemplate decoder_bin_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate decoder_bin_src_template =
+GST_STATIC_PAD_TEMPLATE ("src_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (gst_parse_bin_debug);
+#define GST_CAT_DEFAULT gst_parse_bin_debug
+
+typedef struct _GstPendingPad GstPendingPad;
+typedef struct _GstParseElement GstParseElement;
+typedef struct _GstParseChain GstParseChain;
+typedef struct _GstParseGroup GstParseGroup;
+typedef struct _GstParsePad GstParsePad;
+typedef GstGhostPadClass GstParsePadClass;
+typedef struct _GstParseBin GstParseBin;
+typedef struct _GstParseBinClass GstParseBinClass;
+
+#define GST_TYPE_PARSE_BIN (gst_parse_bin_get_type())
+#define GST_PARSE_BIN_CAST(obj) ((GstParseBin*)(obj))
+#define GST_PARSE_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PARSE_BIN,GstParseBin))
+#define GST_PARSE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PARSE_BIN,GstParseBinClass))
+#define GST_IS_parse_bin(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PARSE_BIN))
+#define GST_IS_parse_bin_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PARSE_BIN))
+
+/**
+ * GstParseBin:
+ *
+ * The opaque #GstParseBin data structure
+ */
+struct _GstParseBin
+{
+ GstBin bin; /* we extend GstBin */
+
+ /* properties */
+ gchar *encoding; /* encoding of subtitles */
+ guint64 connection_speed;
+
+ GstElement *typefind; /* this holds the typefind object */
+
+ GMutex expose_lock; /* Protects exposal and removal of groups */
+ GstParseChain *parse_chain; /* Top level parse chain */
+ guint nbpads; /* unique identifier for source pads */
+
+ GMutex factories_lock;
+ guint32 factories_cookie; /* Cookie from last time when factories was updated */
+ GList *factories; /* factories we can use for selecting elements */
+
+ GMutex subtitle_lock; /* Protects changes to subtitles and encoding */
+ GList *subtitles; /* List of elements with subtitle-encoding,
+ * protected by above mutex! */
+
+ gboolean have_type; /* if we received the have_type signal */
+ guint have_type_id; /* signal id for have-type from typefind */
+
+ gboolean async_pending; /* async-start has been emitted */
+
+ GMutex dyn_lock; /* lock protecting pad blocking */
+ gboolean shutdown; /* if we are shutting down */
+ GList *blocked_pads; /* pads that have set to block */
+
+ gboolean expose_allstreams; /* Whether to expose unknow type streams or not */
+
+ GList *filtered; /* elements for which error messages are filtered */
+ GList *filtered_errors; /* filtered error messages */
+};
+
+struct _GstParseBinClass
+{
+ GstBinClass parent_class;
+
+ /* signal fired when we found a pad that we cannot decode */
+ void (*unknown_type) (GstElement * element, GstPad * pad, GstCaps * caps);
+
+ /* signal fired to know if we continue trying to decode the given caps */
+ gboolean (*autoplug_continue) (GstElement * element, GstPad * pad,
+ GstCaps * caps);
+ /* signal fired to get a list of factories to try to autoplug */
+ GValueArray *(*autoplug_factories) (GstElement * element, GstPad * pad,
+ GstCaps * caps);
+ /* signal fired to sort the factories */
+ GValueArray *(*autoplug_sort) (GstElement * element, GstPad * pad,
+ GstCaps * caps, GValueArray * factories);
+ /* signal fired to select from the proposed list of factories */
+ GstAutoplugSelectResult (*autoplug_select) (GstElement * element,
+ GstPad * pad, GstCaps * caps, GstElementFactory * factory);
+ /* signal fired when a autoplugged element that is not linked downstream
+ * or exposed wants to query something */
+ gboolean (*autoplug_query) (GstElement * element, GstPad * pad,
+ GstQuery * query);
+
+ /* fired when the last group is drained */
+ void (*drained) (GstElement * element);
+};
+
+/* signals */
+enum
+{
+ SIGNAL_UNKNOWN_TYPE,
+ SIGNAL_AUTOPLUG_CONTINUE,
+ SIGNAL_AUTOPLUG_FACTORIES,
+ SIGNAL_AUTOPLUG_SELECT,
+ SIGNAL_AUTOPLUG_SORT,
+ SIGNAL_AUTOPLUG_QUERY,
+ SIGNAL_DRAINED,
+ LAST_SIGNAL
+};
+
+#define DEFAULT_SUBTITLE_ENCODING NULL
+#define DEFAULT_USE_BUFFERING FALSE
+#define DEFAULT_LOW_PERCENT 10
+#define DEFAULT_HIGH_PERCENT 99
+/* by default we use the automatic values above */
+#define DEFAULT_EXPOSE_ALL_STREAMS TRUE
+#define DEFAULT_CONNECTION_SPEED 0
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_SUBTITLE_ENCODING,
+ PROP_SINK_CAPS,
+ PROP_EXPOSE_ALL_STREAMS,
+ PROP_CONNECTION_SPEED
+};
+
+static GstBinClass *parent_class;
+static guint gst_parse_bin_signals[LAST_SIGNAL] = { 0 };
+
+static void do_async_start (GstParseBin * parsebin);
+static void do_async_done (GstParseBin * parsebin);
+
+static void type_found (GstElement * typefind, guint probability,
+ GstCaps * caps, GstParseBin * parse_bin);
+
+static gboolean gst_parse_bin_autoplug_continue (GstElement * element,
+ GstPad * pad, GstCaps * caps);
+static GValueArray *gst_parse_bin_autoplug_factories (GstElement *
+ element, GstPad * pad, GstCaps * caps);
+static GValueArray *gst_parse_bin_autoplug_sort (GstElement * element,
+ GstPad * pad, GstCaps * caps, GValueArray * factories);
+static GstAutoplugSelectResult gst_parse_bin_autoplug_select (GstElement *
+ element, GstPad * pad, GstCaps * caps, GstElementFactory * factory);
+static gboolean gst_parse_bin_autoplug_query (GstElement * element,
+ GstPad * pad, GstQuery * query);
+
+static void gst_parse_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_parse_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void caps_notify_cb (GstPad * pad, GParamSpec * unused,
+ GstParseChain * chain);
+
+static GstStateChangeReturn gst_parse_bin_change_state (GstElement * element,
+ GstStateChange transition);
+static void gst_parse_bin_handle_message (GstBin * bin, GstMessage * message);
+static void gst_parse_pad_update_caps (GstParsePad * parsepad, GstCaps * caps);
+static void gst_parse_pad_update_tags (GstParsePad * parsepad,
+ GstTagList * tags);
+static GstEvent *gst_parse_pad_stream_start_event (GstParsePad * parsepad,
+ GstEvent * event);
+static void gst_parse_pad_update_stream_collection (GstParsePad * parsepad,
+ GstStreamCollection * collection);
+
+static GstCaps *get_pad_caps (GstPad * pad);
+
+#define EXPOSE_LOCK(parsebin) G_STMT_START { \
+ GST_LOG_OBJECT (parsebin, \
+ "expose locking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_lock (&GST_PARSE_BIN_CAST(parsebin)->expose_lock); \
+ GST_LOG_OBJECT (parsebin, \
+ "expose locked from thread %p", \
+ g_thread_self ()); \
+} G_STMT_END
+
+#define EXPOSE_UNLOCK(parsebin) G_STMT_START { \
+ GST_LOG_OBJECT (parsebin, \
+ "expose unlocking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_unlock (&GST_PARSE_BIN_CAST(parsebin)->expose_lock); \
+} G_STMT_END
+
+#define DYN_LOCK(parsebin) G_STMT_START { \
+ GST_LOG_OBJECT (parsebin, \
+ "dynlocking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_lock (&GST_PARSE_BIN_CAST(parsebin)->dyn_lock); \
+ GST_LOG_OBJECT (parsebin, \
+ "dynlocked from thread %p", \
+ g_thread_self ()); \
+} G_STMT_END
+
+#define DYN_UNLOCK(parsebin) G_STMT_START { \
+ GST_LOG_OBJECT (parsebin, \
+ "dynunlocking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_unlock (&GST_PARSE_BIN_CAST(parsebin)->dyn_lock); \
+} G_STMT_END
+
+#define SUBTITLE_LOCK(parsebin) G_STMT_START { \
+ GST_LOG_OBJECT (parsebin, \
+ "subtitle locking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_lock (&GST_PARSE_BIN_CAST(parsebin)->subtitle_lock); \
+ GST_LOG_OBJECT (parsebin, \
+ "subtitle lock from thread %p", \
+ g_thread_self ()); \
+} G_STMT_END
+
+#define SUBTITLE_UNLOCK(parsebin) G_STMT_START { \
+ GST_LOG_OBJECT (parsebin, \
+ "subtitle unlocking from thread %p", \
+ g_thread_self ()); \
+ g_mutex_unlock (&GST_PARSE_BIN_CAST(parsebin)->subtitle_lock); \
+} G_STMT_END
+
+struct _GstPendingPad
+{
+ GstPad *pad;
+ GstParseChain *chain;
+ gulong event_probe_id;
+ gulong notify_caps_id;
+};
+
+struct _GstParseElement
+{
+ GstElement *element;
+ GstElement *capsfilter; /* Optional capsfilter for Parser/Convert */
+ gulong pad_added_id;
+ gulong pad_removed_id;
+ gulong no_more_pads_id;
+};
+
+/* GstParseGroup
+ *
+ * Streams belonging to the same group/chain of a media file
+ *
+ * When changing something here lock the parent chain!
+ */
+struct _GstParseGroup
+{
+ GstParseBin *parsebin;
+ GstParseChain *parent;
+
+ gboolean no_more_pads; /* TRUE if the demuxer signaled no-more-pads */
+ gboolean drained; /* TRUE if the all children are drained */
+
+ GList *children; /* List of GstParseChains in this group */
+};
+
+struct _GstParseChain
+{
+ GstParseGroup *parent;
+ GstParseBin *parsebin;
+
+ GMutex lock; /* Protects this chain and its groups */
+
+ GstPad *pad; /* srcpad that caused creation of this chain */
+ GstCaps *start_caps; /* The initial caps of this chain */
+
+ gboolean drained; /* TRUE if the all children are drained */
+ gboolean demuxer; /* TRUE if elements->data is a demuxer */
+ gboolean parsed; /* TRUE if any elements are a parser */
+ GList *elements; /* All elements in this group, first
+ is the latest and most downstream element */
+
+ /* Note: there are only groups if the last element of this chain
+ * is a demuxer, otherwise the chain will end with an endpad.
+ * The other way around this means, that endpad only exists if this
+ * chain doesn't end with a demuxer! */
+
+ GstParseGroup *active_group; /* Currently active group */
+ GList *next_groups; /* head is newest group, tail is next group.
+ a new group will be created only if the head
+ group had no-more-pads. If it's only exposed
+ all new pads will be ignored! */
+ GList *pending_pads; /* Pads that have no fixed caps yet */
+
+ GstParsePad *current_pad; /* Current ending pad of the chain that can't
+ * be exposed yet but would be the same as endpad
+ * once it can be exposed */
+ GstParsePad *endpad; /* Pad of this chain that could be exposed */
+ gboolean deadend; /* This chain is incomplete and can't be completed,
+ e.g. no suitable decoder could be found
+ e.g. stream got EOS without buffers
+ */
+ gchar *deadend_details;
+ GstCaps *endcaps; /* Caps that were used when linking to the endpad
+ or that resulted in the deadend
+ */
+
+ /* FIXME: This should be done directly via a thread! */
+ GList *old_groups; /* Groups that should be freed later */
+};
+
+static void gst_parse_chain_free (GstParseChain * chain);
+static GstParseChain *gst_parse_chain_new (GstParseBin * parsebin,
+ GstParseGroup * group, GstPad * pad, GstCaps * start_caps);
+static void gst_parse_group_hide (GstParseGroup * group);
+static void gst_parse_group_free (GstParseGroup * group);
+static GstParseGroup *gst_parse_group_new (GstParseBin * parsebin,
+ GstParseChain * chain);
+static gboolean gst_parse_chain_is_complete (GstParseChain * chain);
+static gboolean gst_parse_chain_expose (GstParseChain * chain,
+ GList ** endpads, gboolean * missing_plugin,
+ GString * missing_plugin_details, gboolean * last_group,
+ gboolean * uncollected_streams);
+static void build_fallback_collection (GstParseChain * chain,
+ GstStreamCollection * collection);
+static gboolean gst_parse_chain_is_drained (GstParseChain * chain);
+static gboolean gst_parse_group_is_complete (GstParseGroup * group);
+static gboolean gst_parse_group_is_drained (GstParseGroup * group);
+
+static gboolean gst_parse_bin_expose (GstParseBin * parsebin);
+
+#define CHAIN_MUTEX_LOCK(chain) G_STMT_START { \
+ GST_LOG_OBJECT (chain->parsebin, \
+ "locking chain %p from thread %p", \
+ chain, g_thread_self ()); \
+ g_mutex_lock (&chain->lock); \
+ GST_LOG_OBJECT (chain->parsebin, \
+ "locked chain %p from thread %p", \
+ chain, g_thread_self ()); \
+} G_STMT_END
+
+#define CHAIN_MUTEX_UNLOCK(chain) G_STMT_START { \
+ GST_LOG_OBJECT (chain->parsebin, \
+ "unlocking chain %p from thread %p", \
+ chain, g_thread_self ()); \
+ g_mutex_unlock (&chain->lock); \
+} G_STMT_END
+
+/* GstParsePad
+ *
+ * GstPad private used for source pads of chains
+ */
+struct _GstParsePad
+{
+ GstGhostPad parent;
+ GstParseBin *parsebin;
+ GstParseChain *chain;
+
+ gboolean blocked; /* the *target* pad is blocked */
+ gboolean exposed; /* the pad is exposed */
+ gboolean drained; /* an EOS has been seen on the pad */
+
+ gulong block_id;
+
+ gboolean in_a_fallback_collection;
+ GstStreamCollection *active_collection;
+ GstStream *active_stream;
+};
+
+GType gst_parse_pad_get_type (void);
+G_DEFINE_TYPE (GstParsePad, gst_parse_pad, GST_TYPE_GHOST_PAD);
+#define GST_TYPE_PARSE_PAD (gst_parse_pad_get_type ())
+#define GST_PARSE_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PARSE_PAD,GstParsePad))
+
+static GstParsePad *gst_parse_pad_new (GstParseBin * parsebin,
+ GstParseChain * chain);
+static void gst_parse_pad_activate (GstParsePad * parsepad,
+ GstParseChain * chain);
+static void gst_parse_pad_unblock (GstParsePad * parsepad);
+static void gst_parse_pad_set_blocked (GstParsePad * parsepad,
+ gboolean blocked);
+static gboolean gst_parse_pad_query (GstPad * pad, GstObject * parent,
+ GstQuery * query);
+static GstPadProbeReturn
+gst_parse_pad_event (GstPad * pad, GstPadProbeInfo * info, gpointer user_data);
+
+
+static void gst_pending_pad_free (GstPendingPad * ppad);
+static GstPadProbeReturn pad_event_cb (GstPad * pad, GstPadProbeInfo * info,
+ gpointer data);
+
+/********************************
+ * Standard GObject boilerplate *
+ ********************************/
+
+static void gst_parse_bin_class_init (GstParseBinClass * klass);
+static void gst_parse_bin_init (GstParseBin * parse_bin);
+static void gst_parse_bin_dispose (GObject * object);
+static void gst_parse_bin_finalize (GObject * object);
+
+static GType
+gst_parse_bin_get_type (void)
+{
+ static GType gst_parse_bin_type = 0;
+
+ if (!gst_parse_bin_type) {
+ static const GTypeInfo gst_parse_bin_info = {
+ sizeof (GstParseBinClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gst_parse_bin_class_init,
+ NULL,
+ NULL,
+ sizeof (GstParseBin),
+ 0,
+ (GInstanceInitFunc) gst_parse_bin_init,
+ NULL
+ };
+
+ gst_parse_bin_type =
+ g_type_register_static (GST_TYPE_BIN, "GstParseBin",
+ &gst_parse_bin_info, 0);
+ }
+
+ return gst_parse_bin_type;
+}
+
+static gboolean
+_gst_boolean_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gboolean myboolean;
+
+ myboolean = g_value_get_boolean (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boolean (return_accu, myboolean);
+
+ /* stop emission if FALSE */
+ return myboolean;
+}
+
+static gboolean
+_gst_boolean_or_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gboolean myboolean;
+ gboolean retboolean;
+
+ myboolean = g_value_get_boolean (handler_return);
+ retboolean = g_value_get_boolean (return_accu);
+
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boolean (return_accu, myboolean || retboolean);
+
+ return TRUE;
+}
+
+/* we collect the first result */
+static gboolean
+_gst_array_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gpointer array;
+
+ array = g_value_get_boxed (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boxed (return_accu, array);
+
+ return FALSE;
+}
+
+static gboolean
+_gst_select_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ GstAutoplugSelectResult res;
+
+ res = g_value_get_enum (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_enum (return_accu, res);
+
+ /* Call the next handler in the chain (if any) when the current callback
+ * returns TRY. This makes it possible to register separate autoplug-select
+ * handlers that implement different TRY/EXPOSE/SKIP strategies.
+ */
+ if (res == GST_AUTOPLUG_SELECT_TRY)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+_gst_array_hasvalue_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gpointer array;
+
+ array = g_value_get_boxed (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boxed (return_accu, array);
+
+ if (array != NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+gst_parse_bin_class_init (GstParseBinClass * klass)
+{
+ GObjectClass *gobject_klass;
+ GstElementClass *gstelement_klass;
+ GstBinClass *gstbin_klass;
+
+ gobject_klass = (GObjectClass *) klass;
+ gstelement_klass = (GstElementClass *) klass;
+ gstbin_klass = (GstBinClass *) klass;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ gobject_klass->dispose = gst_parse_bin_dispose;
+ gobject_klass->finalize = gst_parse_bin_finalize;
+ gobject_klass->set_property = gst_parse_bin_set_property;
+ gobject_klass->get_property = gst_parse_bin_get_property;
+
+ /**
+ * GstParseBin::unknown-type:
+ * @bin: The ParseBin.
+ * @pad: The new pad containing caps that cannot be resolved to a 'final'
+ * stream type.
+ * @caps: The #GstCaps of the pad that cannot be resolved.
+ *
+ * This signal is emitted when a pad for which there is no further possible
+ * decoding is added to the ParseBin.
+ */
+ gst_parse_bin_signals[SIGNAL_UNKNOWN_TYPE] =
+ g_signal_new ("unknown-type", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstParseBinClass, unknown_type),
+ NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2,
+ GST_TYPE_PAD, GST_TYPE_CAPS);
+
+ /**
+ * GstParseBin::autoplug-continue:
+ * @bin: The ParseBin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps found.
+ *
+ * This signal is emitted whenever ParseBin finds a new stream. It is
+ * emitted before looking for any elements that can handle that stream.
+ *
+ * <note>
+ * Invocation of signal handlers stops after the first signal handler
+ * returns #FALSE. Signal handlers are invoked in the order they were
+ * connected in.
+ * </note>
+ *
+ * Returns: #TRUE if you wish ParseBin to look for elements that can
+ * handle the given @caps. If #FALSE, those caps will be considered as
+ * final and the pad will be exposed as such (see 'pad-added' signal of
+ * #GstElement).
+ */
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_CONTINUE] =
+ g_signal_new ("autoplug-continue", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstParseBinClass, autoplug_continue),
+ _gst_boolean_accumulator, NULL, g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 2, GST_TYPE_PAD, GST_TYPE_CAPS);
+
+ /**
+ * GstParseBin::autoplug-factories:
+ * @bin: The ParseBin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps found.
+ *
+ * This function is emited when an array of possible factories for @caps on
+ * @pad is needed. ParseBin will by default return an array with all
+ * compatible factories, sorted by rank.
+ *
+ * If this function returns NULL, @pad will be exposed as a final caps.
+ *
+ * If this function returns an empty array, the pad will be considered as
+ * having an unhandled type media type.
+ *
+ * <note>
+ * Only the signal handler that is connected first will ever by invoked.
+ * Don't connect signal handlers with the #G_CONNECT_AFTER flag to this
+ * signal, they will never be invoked!
+ * </note>
+ *
+ * Returns: a #GValueArray* with a list of factories to try. The factories are
+ * by default tried in the returned order or based on the index returned by
+ * "autoplug-select".
+ */
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_FACTORIES] =
+ g_signal_new ("autoplug-factories", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstParseBinClass,
+ autoplug_factories), _gst_array_accumulator, NULL,
+ g_cclosure_marshal_generic, G_TYPE_VALUE_ARRAY, 2,
+ GST_TYPE_PAD, GST_TYPE_CAPS);
+
+ /**
+ * GstParseBin::autoplug-sort:
+ * @bin: The ParseBin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps.
+ * @factories: A #GValueArray of possible #GstElementFactory to use.
+ *
+ * Once ParseBin has found the possible #GstElementFactory objects to try
+ * for @caps on @pad, this signal is emited. The purpose of the signal is for
+ * the application to perform additional sorting or filtering on the element
+ * factory array.
+ *
+ * The callee should copy and modify @factories or return #NULL if the
+ * order should not change.
+ *
+ * <note>
+ * Invocation of signal handlers stops after one signal handler has
+ * returned something else than #NULL. Signal handlers are invoked in
+ * the order they were connected in.
+ * Don't connect signal handlers with the #G_CONNECT_AFTER flag to this
+ * signal, they will never be invoked!
+ * </note>
+ *
+ * Returns: A new sorted array of #GstElementFactory objects.
+ */
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_SORT] =
+ g_signal_new ("autoplug-sort", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstParseBinClass, autoplug_sort),
+ _gst_array_hasvalue_accumulator, NULL,
+ g_cclosure_marshal_generic, G_TYPE_VALUE_ARRAY, 3, GST_TYPE_PAD,
+ GST_TYPE_CAPS, G_TYPE_VALUE_ARRAY | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ /**
+ * GstParseBin::autoplug-select:
+ * @bin: The ParseBin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps.
+ * @factory: A #GstElementFactory to use.
+ *
+ * This signal is emitted once ParseBin has found all the possible
+ * #GstElementFactory that can be used to handle the given @caps. For each of
+ * those factories, this signal is emitted.
+ *
+ * The signal handler should return a #GST_TYPE_AUTOPLUG_SELECT_RESULT enum
+ * value indicating what ParseBin should do next.
+ *
+ * A value of #GST_AUTOPLUG_SELECT_TRY will try to autoplug an element from
+ * @factory.
+ *
+ * A value of #GST_AUTOPLUG_SELECT_EXPOSE will expose @pad without plugging
+ * any element to it.
+ *
+ * A value of #GST_AUTOPLUG_SELECT_SKIP will skip @factory and move to the
+ * next factory.
+ *
+ * <note>
+ * The signal handler will not be invoked if any of the previously
+ * registered signal handlers (if any) return a value other than
+ * GST_AUTOPLUG_SELECT_TRY. Which also means that if you return
+ * GST_AUTOPLUG_SELECT_TRY from one signal handler, handlers that get
+ * registered next (again, if any) can override that decision.
+ * </note>
+ *
+ * Returns: a #GST_TYPE_AUTOPLUG_SELECT_RESULT that indicates the required
+ * operation. the default handler will always return
+ * #GST_AUTOPLUG_SELECT_TRY.
+ */
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_SELECT] =
+ g_signal_new ("autoplug-select", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstParseBinClass, autoplug_select),
+ _gst_select_accumulator, NULL,
+ g_cclosure_marshal_generic,
+ GST_TYPE_AUTOPLUG_SELECT_RESULT, 3, GST_TYPE_PAD, GST_TYPE_CAPS,
+ GST_TYPE_ELEMENT_FACTORY);
+
+ /**
+ * GstParseBin::autoplug-query:
+ * @bin: The ParseBin.
+ * @child: The child element doing the query
+ * @pad: The #GstPad.
+ * @element: The #GstElement.
+ * @query: The #GstQuery.
+ *
+ * This signal is emitted whenever an autoplugged element that is
+ * not linked downstream yet and not exposed does a query. It can
+ * be used to tell the element about the downstream supported caps
+ * for example.
+ *
+ * Returns: #TRUE if the query was handled, #FALSE otherwise.
+ */
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_QUERY] =
+ g_signal_new ("autoplug-query", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstParseBinClass, autoplug_query),
+ _gst_boolean_or_accumulator, NULL, g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 3, GST_TYPE_PAD, GST_TYPE_ELEMENT,
+ GST_TYPE_QUERY | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ /**
+ * GstParseBin::drained
+ * @bin: The ParseBin
+ *
+ * This signal is emitted once ParseBin has finished decoding all the data.
+ */
+ gst_parse_bin_signals[SIGNAL_DRAINED] =
+ g_signal_new ("drained", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstParseBinClass, drained),
+ NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ g_object_class_install_property (gobject_klass, PROP_SUBTITLE_ENCODING,
+ g_param_spec_string ("subtitle-encoding", "subtitle encoding",
+ "Encoding to assume if input subtitles are not in UTF-8 encoding. "
+ "If not set, the GST_SUBTITLE_ENCODING environment variable will "
+ "be checked for an encoding to use. If that is not set either, "
+ "ISO-8859-15 will be assumed.", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_SINK_CAPS,
+ g_param_spec_boxed ("sink-caps", "Sink Caps",
+ "The caps of the input data. (NULL = use typefind element)",
+ GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstParseBin::expose-all-streams
+ *
+ * Expose streams of unknown type.
+ *
+ * If set to %FALSE, then only the streams that can be decoded to the final
+ * caps (see 'caps' property) will have a pad exposed. Streams that do not
+ * match those caps but could have been decoded will not have decoder plugged
+ * in internally and will not have a pad exposed.
+ */
+ g_object_class_install_property (gobject_klass, PROP_EXPOSE_ALL_STREAMS,
+ g_param_spec_boolean ("expose-all-streams", "Expose All Streams",
+ "Expose all streams, including those of unknown type or that don't match the 'caps' property",
+ DEFAULT_EXPOSE_ALL_STREAMS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstParseBin2::connection-speed
+ *
+ * Network connection speed in kbps (0 = unknownw)
+ */
+ g_object_class_install_property (gobject_klass, PROP_CONNECTION_SPEED,
+ g_param_spec_uint64 ("connection-speed", "Connection Speed",
+ "Network connection speed in kbps (0 = unknown)",
+ 0, G_MAXUINT64 / 1000, DEFAULT_CONNECTION_SPEED,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+
+
+ klass->autoplug_continue =
+ GST_DEBUG_FUNCPTR (gst_parse_bin_autoplug_continue);
+ klass->autoplug_factories =
+ GST_DEBUG_FUNCPTR (gst_parse_bin_autoplug_factories);
+ klass->autoplug_sort = GST_DEBUG_FUNCPTR (gst_parse_bin_autoplug_sort);
+ klass->autoplug_select = GST_DEBUG_FUNCPTR (gst_parse_bin_autoplug_select);
+ klass->autoplug_query = GST_DEBUG_FUNCPTR (gst_parse_bin_autoplug_query);
+
+ gst_element_class_add_pad_template (gstelement_klass,
+ gst_static_pad_template_get (&decoder_bin_sink_template));
+ gst_element_class_add_pad_template (gstelement_klass,
+ gst_static_pad_template_get (&decoder_bin_src_template));
+
+ gst_element_class_set_static_metadata (gstelement_klass,
+ "Decoder Bin", "Generic/Bin/Decoder",
+ "Autoplug and decode to raw media",
+ "Edward Hervey <edward.hervey@collabora.co.uk>, "
+ "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
+
+ gstelement_klass->change_state =
+ GST_DEBUG_FUNCPTR (gst_parse_bin_change_state);
+
+ gstbin_klass->handle_message =
+ GST_DEBUG_FUNCPTR (gst_parse_bin_handle_message);
+
+ g_type_class_ref (GST_TYPE_PARSE_PAD);
+}
+
+gint
+_parse_bin_compare_factories_func (gconstpointer p1, gconstpointer p2)
+{
+ GstPluginFeature *f1, *f2;
+ gboolean is_parser1, is_parser2;
+
+ f1 = (GstPluginFeature *) p1;
+ f2 = (GstPluginFeature *) p2;
+
+ is_parser1 = gst_element_factory_list_is_type (GST_ELEMENT_FACTORY_CAST (f1),
+ GST_ELEMENT_FACTORY_TYPE_PARSER);
+ is_parser2 = gst_element_factory_list_is_type (GST_ELEMENT_FACTORY_CAST (f2),
+ GST_ELEMENT_FACTORY_TYPE_PARSER);
+
+
+ /* We want all parsers first as we always want to plug parsers
+ * before decoders */
+ if (is_parser1 && !is_parser2)
+ return -1;
+ else if (!is_parser1 && is_parser2)
+ return 1;
+
+ /* And if it's a both a parser we first sort by rank
+ * and then by factory name */
+ return gst_plugin_feature_rank_compare_func (p1, p2);
+}
+
+/* Must be called with factories lock! */
+static void
+gst_parse_bin_update_factories_list (GstParseBin * parsebin)
+{
+ guint cookie;
+
+ cookie = gst_registry_get_feature_list_cookie (gst_registry_get ());
+ if (!parsebin->factories || parsebin->factories_cookie != cookie) {
+ if (parsebin->factories)
+ gst_plugin_feature_list_free (parsebin->factories);
+ parsebin->factories =
+ gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_DECODABLE, GST_RANK_MARGINAL);
+ parsebin->factories =
+ g_list_sort (parsebin->factories, _parse_bin_compare_factories_func);
+ parsebin->factories_cookie = cookie;
+ }
+}
+
+static void
+gst_parse_bin_init (GstParseBin * parse_bin)
+{
+ /* first filter out the interesting element factories */
+ g_mutex_init (&parse_bin->factories_lock);
+
+ /* we create the typefind element only once */
+ parse_bin->typefind = gst_element_factory_make ("typefind", "typefind");
+ if (!parse_bin->typefind) {
+ g_warning ("can't find typefind element, ParseBin will not work");
+ } else {
+ GstPad *pad;
+ GstPad *gpad;
+ GstPadTemplate *pad_tmpl;
+
+ /* add the typefind element */
+ if (!gst_bin_add (GST_BIN (parse_bin), parse_bin->typefind)) {
+ g_warning ("Could not add typefind element, ParseBin will not work");
+ gst_object_unref (parse_bin->typefind);
+ parse_bin->typefind = NULL;
+ }
+
+ /* get the sinkpad */
+ pad = gst_element_get_static_pad (parse_bin->typefind, "sink");
+
+ /* get the pad template */
+ pad_tmpl = gst_static_pad_template_get (&decoder_bin_sink_template);
+
+ /* ghost the sink pad to ourself */
+ gpad = gst_ghost_pad_new_from_template ("sink", pad, pad_tmpl);
+ gst_pad_set_active (gpad, TRUE);
+ gst_element_add_pad (GST_ELEMENT (parse_bin), gpad);
+
+ gst_object_unref (pad_tmpl);
+ gst_object_unref (pad);
+ }
+
+ g_mutex_init (&parse_bin->expose_lock);
+ parse_bin->parse_chain = NULL;
+
+ g_mutex_init (&parse_bin->dyn_lock);
+ parse_bin->shutdown = FALSE;
+ parse_bin->blocked_pads = NULL;
+
+ g_mutex_init (&parse_bin->subtitle_lock);
+
+ parse_bin->encoding = g_strdup (DEFAULT_SUBTITLE_ENCODING);
+
+ parse_bin->expose_allstreams = DEFAULT_EXPOSE_ALL_STREAMS;
+ parse_bin->connection_speed = DEFAULT_CONNECTION_SPEED;
+}
+
+static void
+gst_parse_bin_dispose (GObject * object)
+{
+ GstParseBin *parse_bin;
+
+ parse_bin = GST_PARSE_BIN (object);
+
+ if (parse_bin->factories)
+ gst_plugin_feature_list_free (parse_bin->factories);
+ parse_bin->factories = NULL;
+
+ if (parse_bin->parse_chain)
+ gst_parse_chain_free (parse_bin->parse_chain);
+ parse_bin->parse_chain = NULL;
+
+ g_free (parse_bin->encoding);
+ parse_bin->encoding = NULL;
+
+ g_list_free (parse_bin->subtitles);
+ parse_bin->subtitles = NULL;
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_parse_bin_finalize (GObject * object)
+{
+ GstParseBin *parse_bin;
+
+ parse_bin = GST_PARSE_BIN (object);
+
+ g_mutex_clear (&parse_bin->expose_lock);
+ g_mutex_clear (&parse_bin->dyn_lock);
+ g_mutex_clear (&parse_bin->subtitle_lock);
+ g_mutex_clear (&parse_bin->factories_lock);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_parse_bin_set_sink_caps (GstParseBin * parsebin, GstCaps * caps)
+{
+ GST_DEBUG_OBJECT (parsebin, "Setting new caps: %" GST_PTR_FORMAT, caps);
+
+ g_object_set (parsebin->typefind, "force-caps", caps, NULL);
+}
+
+static GstCaps *
+gst_parse_bin_get_sink_caps (GstParseBin * parsebin)
+{
+ GstCaps *caps;
+
+ GST_DEBUG_OBJECT (parsebin, "Getting currently set caps");
+
+ g_object_get (parsebin->typefind, "force-caps", &caps, NULL);
+
+ return caps;
+}
+
+static void
+gst_parse_bin_set_subs_encoding (GstParseBin * parsebin, const gchar * encoding)
+{
+ GList *walk;
+
+ GST_DEBUG_OBJECT (parsebin, "Setting new encoding: %s",
+ GST_STR_NULL (encoding));
+
+ SUBTITLE_LOCK (parsebin);
+ g_free (parsebin->encoding);
+ parsebin->encoding = g_strdup (encoding);
+
+ /* set the subtitle encoding on all added elements */
+ for (walk = parsebin->subtitles; walk; walk = g_list_next (walk)) {
+ g_object_set (G_OBJECT (walk->data), "subtitle-encoding",
+ parsebin->encoding, NULL);
+ }
+ SUBTITLE_UNLOCK (parsebin);
+}
+
+static gchar *
+gst_parse_bin_get_subs_encoding (GstParseBin * parsebin)
+{
+ gchar *encoding;
+
+ GST_DEBUG_OBJECT (parsebin, "Getting currently set encoding");
+
+ SUBTITLE_LOCK (parsebin);
+ encoding = g_strdup (parsebin->encoding);
+ SUBTITLE_UNLOCK (parsebin);
+
+ return encoding;
+}
+
+static void
+gst_parse_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstParseBin *parsebin;
+
+ parsebin = GST_PARSE_BIN (object);
+
+ switch (prop_id) {
+ case PROP_SUBTITLE_ENCODING:
+ gst_parse_bin_set_subs_encoding (parsebin, g_value_get_string (value));
+ break;
+ case PROP_SINK_CAPS:
+ gst_parse_bin_set_sink_caps (parsebin, g_value_get_boxed (value));
+ break;
+ case PROP_EXPOSE_ALL_STREAMS:
+ parsebin->expose_allstreams = g_value_get_boolean (value);
+ break;
+ case PROP_CONNECTION_SPEED:
+ GST_OBJECT_LOCK (parsebin);
+ parsebin->connection_speed = g_value_get_uint64 (value) * 1000;
+ GST_OBJECT_UNLOCK (parsebin);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_parse_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstParseBin *parsebin;
+
+ parsebin = GST_PARSE_BIN (object);
+ switch (prop_id) {
+ case PROP_SUBTITLE_ENCODING:
+ g_value_take_string (value, gst_parse_bin_get_subs_encoding (parsebin));
+ break;
+ case PROP_SINK_CAPS:
+ g_value_take_boxed (value, gst_parse_bin_get_sink_caps (parsebin));
+ break;
+ case PROP_EXPOSE_ALL_STREAMS:
+ g_value_set_boolean (value, parsebin->expose_allstreams);
+ break;
+ case PROP_CONNECTION_SPEED:
+ GST_OBJECT_LOCK (parsebin);
+ g_value_set_uint64 (value, parsebin->connection_speed / 1000);
+ GST_OBJECT_UNLOCK (parsebin);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+/*****
+ * Default autoplug signal handlers
+ *****/
+static gboolean
+gst_parse_bin_autoplug_continue (GstElement * element, GstPad * pad,
+ GstCaps * caps)
+{
+ GST_DEBUG_OBJECT (element, "autoplug-continue returns TRUE");
+
+ /* by default we always continue */
+ return TRUE;
+}
+
+static GValueArray *
+gst_parse_bin_autoplug_factories (GstElement * element, GstPad * pad,
+ GstCaps * caps)
+{
+ GList *list, *tmp;
+ GValueArray *result;
+ GstParseBin *parsebin = GST_PARSE_BIN_CAST (element);
+
+ GST_DEBUG_OBJECT (element, "finding factories");
+
+ /* return all compatible factories for caps */
+ g_mutex_lock (&parsebin->factories_lock);
+ gst_parse_bin_update_factories_list (parsebin);
+ list =
+ gst_element_factory_list_filter (parsebin->factories, caps, GST_PAD_SINK,
+ gst_caps_is_fixed (caps));
+ g_mutex_unlock (&parsebin->factories_lock);
+
+ result = g_value_array_new (g_list_length (list));
+ for (tmp = list; tmp; tmp = tmp->next) {
+ GstElementFactory *factory = GST_ELEMENT_FACTORY_CAST (tmp->data);
+ GValue val = { 0, };
+
+ g_value_init (&val, G_TYPE_OBJECT);
+ g_value_set_object (&val, factory);
+ g_value_array_append (result, &val);
+ g_value_unset (&val);
+ }
+ gst_plugin_feature_list_free (list);
+
+ GST_DEBUG_OBJECT (element, "autoplug-factories returns %p", result);
+
+ return result;
+}
+
+static GValueArray *
+gst_parse_bin_autoplug_sort (GstElement * element, GstPad * pad,
+ GstCaps * caps, GValueArray * factories)
+{
+ return NULL;
+}
+
+static GstAutoplugSelectResult
+gst_parse_bin_autoplug_select (GstElement * element, GstPad * pad,
+ GstCaps * caps, GstElementFactory * factory)
+{
+ /* Try factory. */
+ return GST_AUTOPLUG_SELECT_TRY;
+}
+
+static gboolean
+gst_parse_bin_autoplug_query (GstElement * element, GstPad * pad,
+ GstQuery * query)
+{
+ /* No query handled here */
+ return FALSE;
+}
+
+/********
+ * Discovery methods
+ *****/
+
+static gboolean is_demuxer_element (GstElement * srcelement);
+
+static gboolean connect_pad (GstParseBin * parsebin, GstElement * src,
+ GstParsePad * parsepad, GstPad * pad, GstCaps * caps,
+ GValueArray * factories, GstParseChain * chain, gchar ** deadend_details);
+static GList *connect_element (GstParseBin * parsebin, GstParseElement * delem,
+ GstParseChain * chain);
+static void expose_pad (GstParseBin * parsebin, GstElement * src,
+ GstParsePad * parsepad, GstPad * pad, GstCaps * caps,
+ GstParseChain * chain);
+
+static void pad_added_cb (GstElement * element, GstPad * pad,
+ GstParseChain * chain);
+static void pad_removed_cb (GstElement * element, GstPad * pad,
+ GstParseChain * chain);
+static void no_more_pads_cb (GstElement * element, GstParseChain * chain);
+
+static GstParseGroup *gst_parse_chain_get_current_group (GstParseChain * chain);
+
+static gboolean
+clear_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
+{
+ GST_DEBUG_OBJECT (pad, "clearing sticky event %" GST_PTR_FORMAT, *event);
+ gst_event_unref (*event);
+ *event = NULL;
+ return TRUE;
+}
+
+static gboolean
+copy_sticky_events (GstPad * pad, GstEvent ** eventptr, gpointer user_data)
+{
+ GstParsePad *ppad = GST_PARSE_PAD (user_data);
+ GstEvent *event = gst_event_ref (*eventptr);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_CAPS:{
+ GstCaps *caps = NULL;
+ gst_event_parse_caps (event, &caps);
+ gst_parse_pad_update_caps (ppad, caps);
+ break;
+ }
+ case GST_EVENT_STREAM_START:{
+ event = gst_parse_pad_stream_start_event (ppad, event);
+ break;
+ }
+ case GST_EVENT_STREAM_COLLECTION:{
+ GstStreamCollection *collection = NULL;
+ gst_event_parse_stream_collection (event, &collection);
+ gst_parse_pad_update_stream_collection (ppad, collection);
+ break;
+ }
+ default:
+ break;
+ }
+
+ GST_DEBUG_OBJECT (ppad, "store sticky event %" GST_PTR_FORMAT, event);
+ gst_pad_store_sticky_event (GST_PAD_CAST (ppad), event);
+ gst_event_unref (event);
+
+ return TRUE;
+}
+
+static void
+parse_pad_set_target (GstParsePad * parsepad, GstPad * target)
+{
+ GstPad *old_target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (parsepad));
+ if (old_target)
+ gst_object_unref (old_target);
+
+ if (old_target == target)
+ return;
+
+ gst_pad_sticky_events_foreach (GST_PAD_CAST (parsepad),
+ clear_sticky_events, NULL);
+ gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (parsepad), target);
+
+ if (target == NULL) {
+ GST_LOG_OBJECT (parsepad->parsebin, "Setting pad %" GST_PTR_FORMAT
+ " target to NULL", parsepad);
+ } else {
+ GST_LOG_OBJECT (parsepad->parsebin, "Setting pad %" GST_PTR_FORMAT
+ " target to %" GST_PTR_FORMAT, parsepad, target);
+ gst_pad_sticky_events_foreach (target, copy_sticky_events, parsepad);
+ }
+}
+
+/* called when a new pad is discovered. It will perform some basic actions
+ * before trying to link something to it.
+ *
+ * - Check the caps, don't do anything when there are no caps or when they have
+ * no good type.
+ * - signal AUTOPLUG_CONTINUE to check if we need to continue autoplugging this
+ * pad.
+ * - if the caps are non-fixed, setup a handler to continue autoplugging when
+ * the caps become fixed (connect to notify::caps).
+ * - get list of factories to autoplug.
+ * - continue autoplugging to one of the factories.
+ */
+static void
+analyze_new_pad (GstParseBin * parsebin, GstElement * src, GstPad * pad,
+ GstCaps * caps, GstParseChain * chain)
+{
+ gboolean apcontinue = TRUE;
+ GValueArray *factories = NULL, *result = NULL;
+ GstParsePad *parsepad;
+ GstElementFactory *factory;
+ const gchar *classification;
+ gboolean is_parser_converter = FALSE;
+ gboolean res;
+ gchar *deadend_details = NULL;
+
+ GST_DEBUG_OBJECT (parsebin, "Pad %s:%s caps:%" GST_PTR_FORMAT,
+ GST_DEBUG_PAD_NAME (pad), caps);
+
+ if (chain->elements
+ && src != ((GstParseElement *) chain->elements->data)->element
+ && src != ((GstParseElement *) chain->elements->data)->capsfilter) {
+ GST_ERROR_OBJECT (parsebin,
+ "New pad from not the last element in this chain");
+ return;
+ }
+
+ if (chain->demuxer) {
+ GstParseGroup *group;
+ GstParseChain *oldchain = chain;
+ GstParseElement *demux = (chain->elements ? chain->elements->data : NULL);
+
+ if (chain->current_pad)
+ gst_object_unref (chain->current_pad);
+ chain->current_pad = NULL;
+
+ /* we are adding a new pad for a demuxer (see is_demuxer_element(),
+ * start a new chain for it */
+ CHAIN_MUTEX_LOCK (oldchain);
+ group = gst_parse_chain_get_current_group (chain);
+ if (group && !g_list_find (group->children, chain)) {
+ chain = gst_parse_chain_new (parsebin, group, pad, caps);
+ group->children = g_list_prepend (group->children, chain);
+ }
+ CHAIN_MUTEX_UNLOCK (oldchain);
+ if (!group) {
+ GST_WARNING_OBJECT (parsebin, "No current group");
+ return;
+ }
+
+ /* If this is not a dynamic pad demuxer, we're no-more-pads
+ * already before anything else happens
+ */
+ if (demux == NULL || !demux->no_more_pads_id)
+ group->no_more_pads = TRUE;
+ }
+
+ /* From here on we own a reference to the caps as
+ * we might create new caps below and would need
+ * to unref them later */
+ if (caps)
+ gst_caps_ref (caps);
+
+ if ((caps == NULL) || gst_caps_is_empty (caps))
+ goto unknown_type;
+
+ if (gst_caps_is_any (caps))
+ goto any_caps;
+
+ if (!chain->current_pad)
+ chain->current_pad = gst_parse_pad_new (parsebin, chain);
+
+ parsepad = gst_object_ref (chain->current_pad);
+ gst_pad_set_active (GST_PAD_CAST (parsepad), TRUE);
+ parse_pad_set_target (parsepad, pad);
+
+ /* 1. Emit 'autoplug-continue' the result will tell us if this pads needs
+ * further autoplugging. Only do this for fixed caps, for unfixed caps
+ * we will later come here again from the notify::caps handler. The
+ * problem with unfixed caps is that, we can't reliably tell if the output
+ * is e.g. accepted by a sink because only parts of the possible final
+ * caps might be accepted by the sink. */
+ if (gst_caps_is_fixed (caps))
+ g_signal_emit (G_OBJECT (parsebin),
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_CONTINUE], 0, parsepad, caps,
+ &apcontinue);
+ else
+ apcontinue = TRUE;
+
+ /* 1.a if autoplug-continue is FALSE or caps is a raw format, goto pad_is_final */
+ if (!apcontinue)
+ goto expose_pad;
+
+ /* 1.b For Parser/Converter that can output different stream formats
+ * we insert a capsfilter with the sorted caps of all possible next
+ * elements and continue with the capsfilter srcpad */
+ factory = gst_element_get_factory (src);
+ classification =
+ gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS);
+ is_parser_converter = (strstr (classification, "Parser")
+ && strstr (classification, "Converter"));
+
+ /* FIXME: We just need to be sure that the next element is not a parser */
+ /* 1.c when the caps are not fixed yet, we can't be sure what element to
+ * connect. We delay autoplugging until the caps are fixed */
+ if (!is_parser_converter && !gst_caps_is_fixed (caps)) {
+ goto non_fixed;
+ } else if (!is_parser_converter) {
+ gst_caps_unref (caps);
+ caps = gst_pad_get_current_caps (pad);
+ if (!caps) {
+ GST_DEBUG_OBJECT (parsebin,
+ "No final caps set yet, delaying autoplugging");
+ gst_object_unref (parsepad);
+ goto setup_caps_delay;
+ }
+ }
+
+ /* 1.d else get the factories and if there's no compatible factory goto
+ * unknown_type */
+ g_signal_emit (G_OBJECT (parsebin),
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_FACTORIES], 0, parsepad, caps,
+ &factories);
+
+ /* NULL means that we can expose the pad */
+ if (factories == NULL)
+ goto expose_pad;
+
+ /* if the array is empty, we have a type for which we have no decoder */
+ if (factories->n_values == 0) {
+ /* if not we have a unhandled type with no compatible factories */
+ g_value_array_free (factories);
+ gst_object_unref (parsepad);
+ goto unknown_type;
+ }
+
+ /* 1.e sort some more. */
+ g_signal_emit (G_OBJECT (parsebin),
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_SORT], 0, parsepad, caps, factories,
+ &result);
+ if (result) {
+ g_value_array_free (factories);
+ factories = result;
+ }
+
+ /* 1.g now get the factory template caps and insert the capsfilter if this
+ * is a parser/converter
+ */
+ if (is_parser_converter) {
+ GstCaps *filter_caps;
+ gint i;
+ GstPad *p;
+ GstParseElement *delem;
+
+ g_assert (chain->elements != NULL);
+ delem = (GstParseElement *) chain->elements->data;
+
+ filter_caps = gst_caps_new_empty ();
+ for (i = 0; i < factories->n_values; i++) {
+ GstElementFactory *factory =
+ g_value_get_object (g_value_array_get_nth (factories, i));
+ GstCaps *tcaps, *intersection;
+ const GList *tmps;
+
+ GST_DEBUG ("Trying factory %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+
+ if (gst_element_get_factory (src) == factory ||
+ gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_PARSER)) {
+ GST_DEBUG ("Skipping factory");
+ continue;
+ }
+
+ for (tmps = gst_element_factory_get_static_pad_templates (factory); tmps;
+ tmps = tmps->next) {
+ GstStaticPadTemplate *st = (GstStaticPadTemplate *) tmps->data;
+ if (st->direction != GST_PAD_SINK || st->presence != GST_PAD_ALWAYS)
+ continue;
+ tcaps = gst_static_pad_template_get_caps (st);
+ intersection =
+ gst_caps_intersect_full (tcaps, caps, GST_CAPS_INTERSECT_FIRST);
+ filter_caps = gst_caps_merge (filter_caps, intersection);
+ gst_caps_unref (tcaps);
+ }
+ }
+
+ /* Append the parser caps to prevent any not-negotiated errors */
+ filter_caps = gst_caps_merge (filter_caps, gst_caps_ref (caps));
+
+ delem->capsfilter = gst_element_factory_make ("capsfilter", NULL);
+ g_object_set (G_OBJECT (delem->capsfilter), "caps", filter_caps, NULL);
+ gst_caps_unref (filter_caps);
+ gst_element_set_state (delem->capsfilter, GST_STATE_PAUSED);
+ gst_bin_add (GST_BIN_CAST (parsebin), gst_object_ref (delem->capsfilter));
+
+ parse_pad_set_target (parsepad, NULL);
+ p = gst_element_get_static_pad (delem->capsfilter, "sink");
+ gst_pad_link_full (pad, p, GST_PAD_LINK_CHECK_NOTHING);
+ gst_object_unref (p);
+ p = gst_element_get_static_pad (delem->capsfilter, "src");
+ parse_pad_set_target (parsepad, p);
+ pad = p;
+
+ gst_caps_unref (caps);
+
+ caps = gst_pad_get_current_caps (pad);
+ if (!caps) {
+ GST_DEBUG_OBJECT (parsebin,
+ "No final caps set yet, delaying autoplugging");
+ gst_object_unref (parsepad);
+ g_value_array_free (factories);
+ goto setup_caps_delay;
+ }
+ }
+
+ /* 1.h else continue autoplugging something from the list. */
+ GST_LOG_OBJECT (pad, "Let's continue discovery on this pad");
+ res =
+ connect_pad (parsebin, src, parsepad, pad, caps, factories, chain,
+ &deadend_details);
+
+ /* Need to unref the capsfilter srcpad here if
+ * we inserted a capsfilter */
+ if (is_parser_converter)
+ gst_object_unref (pad);
+
+ gst_object_unref (parsepad);
+ g_value_array_free (factories);
+
+ if (!res)
+ goto unknown_type;
+
+ gst_caps_unref (caps);
+
+ return;
+
+expose_pad:
+ {
+ GST_LOG_OBJECT (parsebin, "Pad is final. autoplug-continue:%d", apcontinue);
+ expose_pad (parsebin, src, parsepad, pad, caps, chain);
+ gst_object_unref (parsepad);
+ gst_caps_unref (caps);
+ return;
+ }
+
+unknown_type:
+ {
+ GST_LOG_OBJECT (pad, "Unknown type, posting message and firing signal");
+
+ chain->deadend_details = deadend_details;
+ chain->deadend = TRUE;
+ chain->endcaps = caps;
+ gst_object_replace ((GstObject **) & chain->current_pad, NULL);
+
+ gst_element_post_message (GST_ELEMENT_CAST (parsebin),
+ gst_missing_decoder_message_new (GST_ELEMENT_CAST (parsebin), caps));
+
+ g_signal_emit (G_OBJECT (parsebin),
+ gst_parse_bin_signals[SIGNAL_UNKNOWN_TYPE], 0, pad, caps);
+
+ /* Try to expose anything */
+ EXPOSE_LOCK (parsebin);
+ if (parsebin->parse_chain) {
+ if (gst_parse_chain_is_complete (parsebin->parse_chain)) {
+ gst_parse_bin_expose (parsebin);
+ }
+ }
+ EXPOSE_UNLOCK (parsebin);
+
+ if (src == parsebin->typefind) {
+ if (!caps || gst_caps_is_empty (caps)) {
+ GST_ELEMENT_ERROR (parsebin, STREAM, TYPE_NOT_FOUND,
+ (_("Could not determine type of stream")), (NULL));
+ }
+ do_async_done (parsebin);
+ }
+ return;
+ }
+#if 1
+non_fixed:
+ {
+ GST_DEBUG_OBJECT (pad, "pad has non-fixed caps delay autoplugging");
+ gst_object_unref (parsepad);
+ goto setup_caps_delay;
+ }
+#endif
+any_caps:
+ {
+ GST_DEBUG_OBJECT (pad, "pad has ANY caps, delaying auto-plugging");
+ goto setup_caps_delay;
+ }
+setup_caps_delay:
+ {
+ GstPendingPad *ppad;
+
+ /* connect to caps notification */
+ CHAIN_MUTEX_LOCK (chain);
+ GST_LOG_OBJECT (parsebin, "Chain %p has now %d dynamic pads", chain,
+ g_list_length (chain->pending_pads));
+ ppad = g_slice_new0 (GstPendingPad);
+ ppad->pad = gst_object_ref (pad);
+ ppad->chain = chain;
+ ppad->event_probe_id =
+ gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ pad_event_cb, ppad, NULL);
+ chain->pending_pads = g_list_prepend (chain->pending_pads, ppad);
+ ppad->notify_caps_id = g_signal_connect (pad, "notify::caps",
+ G_CALLBACK (caps_notify_cb), chain);
+ CHAIN_MUTEX_UNLOCK (chain);
+
+ /* If we're here because we have a Parser/Converter
+ * we have to unref the pad */
+ if (is_parser_converter)
+ gst_object_unref (pad);
+ if (caps)
+ gst_caps_unref (caps);
+
+ return;
+ }
+}
+
+static void
+add_error_filter (GstParseBin * parsebin, GstElement * element)
+{
+ GST_OBJECT_LOCK (parsebin);
+ parsebin->filtered = g_list_prepend (parsebin->filtered, element);
+ GST_OBJECT_UNLOCK (parsebin);
+}
+
+static void
+remove_error_filter (GstParseBin * parsebin, GstElement * element,
+ GstMessage ** error)
+{
+ GList *l;
+
+ GST_OBJECT_LOCK (parsebin);
+ parsebin->filtered = g_list_remove (parsebin->filtered, element);
+
+ if (error)
+ *error = NULL;
+
+ l = parsebin->filtered_errors;
+ while (l) {
+ GstMessage *msg = l->data;
+
+ if (GST_MESSAGE_SRC (msg) == GST_OBJECT_CAST (element)) {
+ /* Get the last error of this element, i.e. the earliest */
+ if (error)
+ gst_message_replace (error, msg);
+ gst_message_unref (msg);
+ l = parsebin->filtered_errors =
+ g_list_delete_link (parsebin->filtered_errors, l);
+ } else {
+ l = l->next;
+ }
+ }
+ GST_OBJECT_UNLOCK (parsebin);
+}
+
+typedef struct
+{
+ gboolean ret;
+ GstPad *peer;
+} SendStickyEventsData;
+
+static gboolean
+send_sticky_event (GstPad * pad, GstEvent ** event, gpointer user_data)
+{
+ SendStickyEventsData *data = user_data;
+ gboolean ret;
+
+ ret = gst_pad_send_event (data->peer, gst_event_ref (*event));
+ if (!ret)
+ data->ret = FALSE;
+
+ return data->ret;
+}
+
+static gboolean
+send_sticky_events (GstParseBin * parsebin, GstPad * pad)
+{
+ SendStickyEventsData data;
+
+ data.ret = TRUE;
+ data.peer = gst_pad_get_peer (pad);
+
+ gst_pad_sticky_events_foreach (pad, send_sticky_event, &data);
+
+ gst_object_unref (data.peer);
+
+ return data.ret;
+}
+
+static gchar *
+error_message_to_string (GstMessage * msg)
+{
+ GError *err;
+ gchar *debug, *message, *full_message;
+
+ gst_message_parse_error (msg, &err, &debug);
+
+ message = gst_error_get_message (err->domain, err->code);
+
+ if (debug)
+ full_message = g_strdup_printf ("%s\n%s\n%s", message, err->message, debug);
+ else
+ full_message = g_strdup_printf ("%s\n%s", message, err->message);
+
+ g_free (message);
+ g_free (debug);
+ g_clear_error (&err);
+
+ return full_message;
+}
+
+/* We consider elements as "simple demuxer" when they are a demuxer
+ * with one and only one ALWAYS source pad.
+ */
+static gboolean
+is_simple_demuxer_factory (GstElementFactory * factory)
+{
+ if (strstr (gst_element_factory_get_metadata (factory,
+ GST_ELEMENT_METADATA_KLASS), "Demuxer")) {
+ const GList *tmp;
+ gint num_alway_srcpads = 0;
+
+ for (tmp = gst_element_factory_get_static_pad_templates (factory);
+ tmp; tmp = tmp->next) {
+ GstStaticPadTemplate *template = tmp->data;
+
+ if (template->direction == GST_PAD_SRC) {
+ if (template->presence == GST_PAD_ALWAYS) {
+ if (num_alway_srcpads >= 0)
+ num_alway_srcpads++;
+ } else {
+ num_alway_srcpads = -1;
+ }
+ }
+
+ }
+
+ if (num_alway_srcpads == 1)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* connect_pad:
+ *
+ * Try to connect the given pad to an element created from one of the factories,
+ * and recursively.
+ *
+ * Note that parsepad is ghosting pad, and so pad is linked; be sure to unset parsepad's
+ * target before trying to link pad.
+ *
+ * Returns TRUE if an element was properly created and linked
+ */
+static gboolean
+connect_pad (GstParseBin * parsebin, GstElement * src, GstParsePad * parsepad,
+ GstPad * pad, GstCaps * caps, GValueArray * factories,
+ GstParseChain * chain, gchar ** deadend_details)
+{
+ gboolean res = FALSE;
+ GString *error_details = NULL;
+
+ g_return_val_if_fail (factories != NULL, FALSE);
+ g_return_val_if_fail (factories->n_values > 0, FALSE);
+
+ GST_DEBUG_OBJECT (parsebin,
+ "pad %s:%s , chain:%p, %d factories, caps %" GST_PTR_FORMAT,
+ GST_DEBUG_PAD_NAME (pad), chain, factories->n_values, caps);
+
+ error_details = g_string_new ("");
+
+ /* 2. Try to create an element and link to it */
+ while (factories->n_values > 0) {
+ GstAutoplugSelectResult ret;
+ GstElementFactory *factory;
+ GstParseElement *delem;
+ GstElement *element;
+ GstPad *sinkpad;
+ GParamSpec *pspec;
+ gboolean subtitle;
+ GList *to_connect = NULL;
+ gboolean is_parser_converter = FALSE, is_simple_demuxer = FALSE;
+
+ /* Set parsepad target to pad again, it might've been unset
+ * below but we came back here because something failed
+ */
+ parse_pad_set_target (parsepad, pad);
+
+ /* take first factory */
+ factory = g_value_get_object (g_value_array_get_nth (factories, 0));
+ /* Remove selected factory from the list. */
+ g_value_array_remove (factories, 0);
+
+ GST_LOG_OBJECT (src, "trying factory %" GST_PTR_FORMAT, factory);
+
+ /* Check if the caps are really supported by the factory. The
+ * factory list is non-empty-subset filtered while caps
+ * are only accepted by a pad if they are a subset of the
+ * pad caps.
+ *
+ * FIXME: Only do this for fixed caps here. Non-fixed caps
+ * can happen if a Parser/Converter was autoplugged before
+ * this. We then assume that it will be able to convert to
+ * everything that the decoder would want.
+ *
+ * A subset check will fail here because the parser caps
+ * will be generic and while the decoder will only
+ * support a subset of the parser caps.
+ */
+ if (gst_caps_is_fixed (caps)) {
+ const GList *templs;
+ gboolean skip = FALSE;
+
+ templs = gst_element_factory_get_static_pad_templates (factory);
+
+ while (templs) {
+ GstStaticPadTemplate *templ = (GstStaticPadTemplate *) templs->data;
+
+ if (templ->direction == GST_PAD_SINK) {
+ GstCaps *templcaps = gst_static_caps_get (&templ->static_caps);
+
+ if (!gst_caps_is_subset (caps, templcaps)) {
+ GST_DEBUG_OBJECT (src,
+ "caps %" GST_PTR_FORMAT " not subset of %" GST_PTR_FORMAT, caps,
+ templcaps);
+ gst_caps_unref (templcaps);
+ skip = TRUE;
+ break;
+ }
+
+ gst_caps_unref (templcaps);
+ }
+ templs = g_list_next (templs);
+ }
+ if (skip)
+ continue;
+ }
+
+ /* If the factory is for a parser we first check if the factory
+ * was already used for the current chain. If it was used already
+ * we would otherwise create an infinite loop here because the
+ * parser apparently accepts its own output as input.
+ * This is only done for parsers because it's perfectly valid
+ * to have other element classes after each other because a
+ * parser is the only one that does not change the data. A
+ * valid example for this would be multiple id3demux in a row.
+ */
+ is_parser_converter = strstr (gst_element_factory_get_metadata (factory,
+ GST_ELEMENT_METADATA_KLASS), "Parser") != NULL;
+ is_simple_demuxer = is_simple_demuxer_factory (factory);
+
+ if (is_parser_converter) {
+ gboolean skip = FALSE;
+ GList *l;
+
+ CHAIN_MUTEX_LOCK (chain);
+ for (l = chain->elements; l; l = l->next) {
+ GstParseElement *delem = (GstParseElement *) l->data;
+ GstElement *otherelement = delem->element;
+
+ if (gst_element_get_factory (otherelement) == factory) {
+ skip = TRUE;
+ break;
+ }
+ }
+
+ if (!skip && chain->parent && chain->parent->parent) {
+ GstParseChain *parent_chain = chain->parent->parent;
+ GstParseElement *pelem =
+ parent_chain->elements ? parent_chain->elements->data : NULL;
+
+ if (pelem && gst_element_get_factory (pelem->element) == factory)
+ skip = TRUE;
+ }
+ CHAIN_MUTEX_UNLOCK (chain);
+ if (skip) {
+ GST_DEBUG_OBJECT (parsebin,
+ "Skipping factory '%s' because it was already used in this chain",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory)));
+ continue;
+ }
+
+ }
+
+ /* Expose pads if the next factory is a decoder */
+ if (gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_DECODER)) {
+ ret = GST_AUTOPLUG_SELECT_EXPOSE;
+ } else {
+ /* emit autoplug-select to see what we should do with it. */
+ g_signal_emit (G_OBJECT (parsebin),
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_SELECT],
+ 0, parsepad, caps, factory, &ret);
+ }
+
+ switch (ret) {
+ case GST_AUTOPLUG_SELECT_TRY:
+ GST_DEBUG_OBJECT (parsebin, "autoplug select requested try");
+ break;
+ case GST_AUTOPLUG_SELECT_EXPOSE:
+ GST_DEBUG_OBJECT (parsebin, "autoplug select requested expose");
+ /* expose the pad, we don't have the source element */
+ expose_pad (parsebin, src, parsepad, pad, caps, chain);
+ res = TRUE;
+ goto beach;
+ case GST_AUTOPLUG_SELECT_SKIP:
+ GST_DEBUG_OBJECT (parsebin, "autoplug select requested skip");
+ continue;
+ default:
+ GST_WARNING_OBJECT (parsebin, "autoplug select returned unhandled %d",
+ ret);
+ break;
+ }
+
+ /* 2.0. Unlink pad */
+ parse_pad_set_target (parsepad, NULL);
+
+ /* 2.1. Try to create an element */
+ if ((element = gst_element_factory_create (factory, NULL)) == NULL) {
+ GST_WARNING_OBJECT (parsebin, "Could not create an element from %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+ g_string_append_printf (error_details,
+ "Could not create an element from %s\n",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+ continue;
+ }
+
+ /* Filter errors, this will prevent the element from causing the pipeline
+ * to error while we test it using READY state. */
+ add_error_filter (parsebin, element);
+
+ /* We don't yet want the bin to control the element's state */
+ gst_element_set_locked_state (element, TRUE);
+
+ /* ... add it ... */
+ if (!(gst_bin_add (GST_BIN_CAST (parsebin), element))) {
+ GST_WARNING_OBJECT (parsebin, "Couldn't add %s to the bin",
+ GST_ELEMENT_NAME (element));
+ remove_error_filter (parsebin, element, NULL);
+ g_string_append_printf (error_details, "Couldn't add %s to the bin\n",
+ GST_ELEMENT_NAME (element));
+ gst_object_unref (element);
+ continue;
+ }
+
+ /* Find its sink pad. */
+ sinkpad = NULL;
+ GST_OBJECT_LOCK (element);
+ if (element->sinkpads != NULL)
+ sinkpad = gst_object_ref (element->sinkpads->data);
+ GST_OBJECT_UNLOCK (element);
+
+ if (sinkpad == NULL) {
+ GST_WARNING_OBJECT (parsebin, "Element %s doesn't have a sink pad",
+ GST_ELEMENT_NAME (element));
+ remove_error_filter (parsebin, element, NULL);
+ g_string_append_printf (error_details,
+ "Element %s doesn't have a sink pad", GST_ELEMENT_NAME (element));
+ gst_bin_remove (GST_BIN (parsebin), element);
+ continue;
+ }
+
+ /* ... and try to link */
+ if ((gst_pad_link_full (pad, sinkpad,
+ GST_PAD_LINK_CHECK_NOTHING)) != GST_PAD_LINK_OK) {
+ GST_WARNING_OBJECT (parsebin, "Link failed on pad %s:%s",
+ GST_DEBUG_PAD_NAME (sinkpad));
+ remove_error_filter (parsebin, element, NULL);
+ g_string_append_printf (error_details, "Link failed on pad %s:%s",
+ GST_DEBUG_PAD_NAME (sinkpad));
+ gst_object_unref (sinkpad);
+ gst_bin_remove (GST_BIN (parsebin), element);
+ continue;
+ }
+
+ /* ... activate it ... */
+ if ((gst_element_set_state (element,
+ GST_STATE_READY)) == GST_STATE_CHANGE_FAILURE) {
+ GstMessage *error_msg;
+
+ GST_WARNING_OBJECT (parsebin, "Couldn't set %s to READY",
+ GST_ELEMENT_NAME (element));
+ remove_error_filter (parsebin, element, &error_msg);
+
+ if (error_msg) {
+ gchar *error_string = error_message_to_string (error_msg);
+ g_string_append_printf (error_details, "Couldn't set %s to READY:\n%s",
+ GST_ELEMENT_NAME (element), error_string);
+ gst_message_unref (error_msg);
+ g_free (error_string);
+ } else {
+ g_string_append_printf (error_details, "Couldn't set %s to READY",
+ GST_ELEMENT_NAME (element));
+ }
+ gst_object_unref (sinkpad);
+ gst_bin_remove (GST_BIN (parsebin), element);
+ continue;
+ }
+
+ /* check if we still accept the caps on the pad after setting
+ * the element to READY */
+ if (!gst_pad_query_accept_caps (sinkpad, caps)) {
+ GstMessage *error_msg;
+
+ GST_WARNING_OBJECT (parsebin, "Element %s does not accept caps",
+ GST_ELEMENT_NAME (element));
+
+ remove_error_filter (parsebin, element, &error_msg);
+
+ if (error_msg) {
+ gchar *error_string = error_message_to_string (error_msg);
+ g_string_append_printf (error_details,
+ "Element %s does not accept caps:\n%s", GST_ELEMENT_NAME (element),
+ error_string);
+ gst_message_unref (error_msg);
+ g_free (error_string);
+ } else {
+ g_string_append_printf (error_details,
+ "Element %s does not accept caps", GST_ELEMENT_NAME (element));
+ }
+
+ gst_element_set_state (element, GST_STATE_NULL);
+ gst_object_unref (sinkpad);
+ gst_bin_remove (GST_BIN (parsebin), element);
+ continue;
+ }
+
+ gst_object_unref (sinkpad);
+ GST_LOG_OBJECT (parsebin, "linked on pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+ CHAIN_MUTEX_LOCK (chain);
+ delem = g_slice_new0 (GstParseElement);
+ delem->element = gst_object_ref (element);
+ delem->capsfilter = NULL;
+ chain->elements = g_list_prepend (chain->elements, delem);
+ chain->demuxer = is_demuxer_element (element);
+
+ /* If we plugging a parser, mark the chain as parsed */
+ chain->parsed |= is_parser_converter;
+
+ CHAIN_MUTEX_UNLOCK (chain);
+
+ /* Set connection-speed property if needed */
+ if (chain->demuxer) {
+ GParamSpec *pspec;
+
+ if ((pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (element),
+ "connection-speed"))) {
+ guint64 speed = parsebin->connection_speed / 1000;
+ gboolean wrong_type = FALSE;
+
+ if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_UINT) {
+ GParamSpecUInt *pspecuint = G_PARAM_SPEC_UINT (pspec);
+
+ speed = CLAMP (speed, pspecuint->minimum, pspecuint->maximum);
+ } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT) {
+ GParamSpecInt *pspecint = G_PARAM_SPEC_INT (pspec);
+
+ speed = CLAMP (speed, pspecint->minimum, pspecint->maximum);
+ } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_UINT64) {
+ GParamSpecUInt64 *pspecuint = G_PARAM_SPEC_UINT64 (pspec);
+
+ speed = CLAMP (speed, pspecuint->minimum, pspecuint->maximum);
+ } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT64) {
+ GParamSpecInt64 *pspecint = G_PARAM_SPEC_INT64 (pspec);
+
+ speed = CLAMP (speed, pspecint->minimum, pspecint->maximum);
+ } else {
+ GST_WARNING_OBJECT (parsebin,
+ "The connection speed property %" G_GUINT64_FORMAT " of type %s"
+ " is not usefull not setting it", speed,
+ g_type_name (G_PARAM_SPEC_TYPE (pspec)));
+ wrong_type = TRUE;
+ }
+
+ if (!wrong_type) {
+ GST_DEBUG_OBJECT (parsebin,
+ "setting connection-speed=%" G_GUINT64_FORMAT
+ " to demuxer element", speed);
+
+ g_object_set (element, "connection-speed", speed, NULL);
+ }
+ }
+ }
+
+ /* try to configure the subtitle encoding property when we can */
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (element),
+ "subtitle-encoding");
+ if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_STRING) {
+ SUBTITLE_LOCK (parsebin);
+ GST_DEBUG_OBJECT (parsebin,
+ "setting subtitle-encoding=%s to element", parsebin->encoding);
+ g_object_set (G_OBJECT (element), "subtitle-encoding", parsebin->encoding,
+ NULL);
+ SUBTITLE_UNLOCK (parsebin);
+ subtitle = TRUE;
+ } else {
+ subtitle = FALSE;
+ }
+
+ /* link this element further */
+ to_connect = connect_element (parsebin, delem, chain);
+
+ if ((is_simple_demuxer || is_parser_converter) && to_connect) {
+ GList *l;
+ for (l = to_connect; l; l = g_list_next (l)) {
+ GstPad *opad = GST_PAD_CAST (l->data);
+ GstCaps *ocaps;
+
+ ocaps = get_pad_caps (opad);
+ analyze_new_pad (parsebin, delem->element, opad, ocaps, chain);
+ if (ocaps)
+ gst_caps_unref (ocaps);
+
+ gst_object_unref (opad);
+ }
+ g_list_free (to_connect);
+ to_connect = NULL;
+ }
+
+ /* Bring the element to the state of the parent */
+
+ /* First lock element's sinkpad stream lock so no data reaches
+ * the possible new element added when caps are sent by element
+ * while we're still sending sticky events */
+ GST_PAD_STREAM_LOCK (sinkpad);
+
+ if ((gst_element_set_state (element,
+ GST_STATE_PAUSED)) == GST_STATE_CHANGE_FAILURE ||
+ !send_sticky_events (parsebin, pad)) {
+ GstParseElement *dtmp = NULL;
+ GstElement *tmp = NULL;
+ GstMessage *error_msg;
+
+ GST_PAD_STREAM_UNLOCK (sinkpad);
+
+ GST_WARNING_OBJECT (parsebin, "Couldn't set %s to PAUSED",
+ GST_ELEMENT_NAME (element));
+
+ g_list_free_full (to_connect, (GDestroyNotify) gst_object_unref);
+ to_connect = NULL;
+
+ remove_error_filter (parsebin, element, &error_msg);
+
+ if (error_msg) {
+ gchar *error_string = error_message_to_string (error_msg);
+ g_string_append_printf (error_details, "Couldn't set %s to PAUSED:\n%s",
+ GST_ELEMENT_NAME (element), error_string);
+ gst_message_unref (error_msg);
+ g_free (error_string);
+ } else {
+ g_string_append_printf (error_details, "Couldn't set %s to PAUSED",
+ GST_ELEMENT_NAME (element));
+ }
+
+ /* Remove all elements in this chain that were just added. No
+ * other thread could've added elements in the meantime */
+ CHAIN_MUTEX_LOCK (chain);
+ do {
+ GList *l;
+
+ dtmp = chain->elements->data;
+ tmp = dtmp->element;
+
+ /* Disconnect any signal handlers that might be connected
+ * in connect_element() or analyze_pad() */
+ if (dtmp->pad_added_id)
+ g_signal_handler_disconnect (tmp, dtmp->pad_added_id);
+ if (dtmp->pad_removed_id)
+ g_signal_handler_disconnect (tmp, dtmp->pad_removed_id);
+ if (dtmp->no_more_pads_id)
+ g_signal_handler_disconnect (tmp, dtmp->no_more_pads_id);
+
+ for (l = chain->pending_pads; l;) {
+ GstPendingPad *pp = l->data;
+ GList *n;
+
+ if (GST_PAD_PARENT (pp->pad) != tmp) {
+ l = l->next;
+ continue;
+ }
+
+ gst_pending_pad_free (pp);
+
+ /* Remove element from the list, update list head and go to the
+ * next element in the list */
+ n = l->next;
+ chain->pending_pads = g_list_delete_link (chain->pending_pads, l);
+ l = n;
+ }
+
+ if (dtmp->capsfilter) {
+ gst_bin_remove (GST_BIN (parsebin), dtmp->capsfilter);
+ gst_element_set_state (dtmp->capsfilter, GST_STATE_NULL);
+ gst_object_unref (dtmp->capsfilter);
+ }
+
+ gst_bin_remove (GST_BIN (parsebin), tmp);
+ gst_element_set_state (tmp, GST_STATE_NULL);
+
+ gst_object_unref (tmp);
+ g_slice_free (GstParseElement, dtmp);
+
+ chain->elements = g_list_delete_link (chain->elements, chain->elements);
+ } while (tmp != element);
+ CHAIN_MUTEX_UNLOCK (chain);
+
+ continue;
+ } else {
+ /* Everything went well, the spice must flow now */
+ GST_PAD_STREAM_UNLOCK (sinkpad);
+ }
+
+ /* Remove error filter now, from now on we can't gracefully
+ * handle errors of the element anymore */
+ remove_error_filter (parsebin, element, NULL);
+
+ /* Now let the bin handle the state */
+ gst_element_set_locked_state (element, FALSE);
+
+ if (subtitle) {
+ SUBTITLE_LOCK (parsebin);
+ /* we added the element now, add it to the list of subtitle-encoding
+ * elements when we can set the property */
+ parsebin->subtitles = g_list_prepend (parsebin->subtitles, element);
+ SUBTITLE_UNLOCK (parsebin);
+ }
+
+ if (to_connect) {
+ GList *l;
+ for (l = to_connect; l; l = g_list_next (l)) {
+ GstPad *opad = GST_PAD_CAST (l->data);
+ GstCaps *ocaps;
+
+ ocaps = get_pad_caps (opad);
+ analyze_new_pad (parsebin, delem->element, opad, ocaps, chain);
+ if (ocaps)
+ gst_caps_unref (ocaps);
+
+ gst_object_unref (opad);
+ }
+ g_list_free (to_connect);
+ to_connect = NULL;
+ }
+
+ res = TRUE;
+ break;
+ }
+
+beach:
+ if (error_details)
+ *deadend_details = g_string_free (error_details, (error_details->len == 0
+ || res));
+ else
+ *deadend_details = NULL;
+
+ return res;
+}
+
+static GstCaps *
+get_pad_caps (GstPad * pad)
+{
+ GstCaps *caps;
+
+ /* first check the pad caps, if this is set, we are positively sure it is
+ * fixed and exactly what the element will produce. */
+ caps = gst_pad_get_current_caps (pad);
+
+ /* then use the getcaps function if we don't have caps. These caps might not
+ * be fixed in some cases, in which case analyze_new_pad will set up a
+ * notify::caps signal to continue autoplugging. */
+ if (caps == NULL)
+ caps = gst_pad_query_caps (pad, NULL);
+
+ return caps;
+}
+
+/* Returns a list of pads that can be connected to already and
+ * connects to pad-added and related signals */
+static GList *
+connect_element (GstParseBin * parsebin, GstParseElement * delem,
+ GstParseChain * chain)
+{
+ GstElement *element = delem->element;
+ GList *pads;
+ gboolean dynamic = FALSE;
+ GList *to_connect = NULL;
+
+ GST_DEBUG_OBJECT (parsebin,
+ "Attempting to connect element %s [chain:%p] further",
+ GST_ELEMENT_NAME (element), chain);
+
+ /* 1. Loop over pad templates, grabbing existing pads along the way */
+ for (pads = GST_ELEMENT_GET_CLASS (element)->padtemplates; pads;
+ pads = g_list_next (pads)) {
+ GstPadTemplate *templ = GST_PAD_TEMPLATE (pads->data);
+ const gchar *templ_name;
+
+ /* we are only interested in source pads */
+ if (GST_PAD_TEMPLATE_DIRECTION (templ) != GST_PAD_SRC)
+ continue;
+
+ templ_name = GST_PAD_TEMPLATE_NAME_TEMPLATE (templ);
+ GST_DEBUG_OBJECT (parsebin, "got a source pad template %s", templ_name);
+
+ /* figure out what kind of pad this is */
+ switch (GST_PAD_TEMPLATE_PRESENCE (templ)) {
+ case GST_PAD_ALWAYS:
+ {
+ /* get the pad that we need to autoplug */
+ GstPad *pad = gst_element_get_static_pad (element, templ_name);
+
+ if (pad) {
+ GST_DEBUG_OBJECT (parsebin, "got the pad for always template %s",
+ templ_name);
+ /* here is the pad, we need to autoplug it */
+ to_connect = g_list_prepend (to_connect, pad);
+ } else {
+ /* strange, pad is marked as always but it's not
+ * there. Fix the element */
+ GST_WARNING_OBJECT (parsebin,
+ "could not get the pad for always template %s", templ_name);
+ }
+ break;
+ }
+ case GST_PAD_SOMETIMES:
+ {
+ /* try to get the pad to see if it is already created or
+ * not */
+ GstPad *pad = gst_element_get_static_pad (element, templ_name);
+
+ if (pad) {
+ GST_DEBUG_OBJECT (parsebin, "got the pad for sometimes template %s",
+ templ_name);
+ /* the pad is created, we need to autoplug it */
+ to_connect = g_list_prepend (to_connect, pad);
+ } else {
+ GST_DEBUG_OBJECT (parsebin,
+ "did not get the sometimes pad of template %s", templ_name);
+ /* we have an element that will create dynamic pads */
+ dynamic = TRUE;
+ }
+ break;
+ }
+ case GST_PAD_REQUEST:
+ /* ignore request pads */
+ GST_DEBUG_OBJECT (parsebin, "ignoring request padtemplate %s",
+ templ_name);
+ break;
+ }
+ }
+
+ /* 2. if there are more potential pads, connect to relevant signals */
+ if (dynamic) {
+ GST_LOG_OBJECT (parsebin, "Adding signals to element %s in chain %p",
+ GST_ELEMENT_NAME (element), chain);
+ delem->pad_added_id = g_signal_connect (element, "pad-added",
+ G_CALLBACK (pad_added_cb), chain);
+ delem->pad_removed_id = g_signal_connect (element, "pad-removed",
+ G_CALLBACK (pad_removed_cb), chain);
+ delem->no_more_pads_id = g_signal_connect (element, "no-more-pads",
+ G_CALLBACK (no_more_pads_cb), chain);
+ }
+
+ /* 3. return all pads that can be connected to already */
+
+ return to_connect;
+}
+
+/* expose_pad:
+ *
+ * Expose the given pad on the chain as a decoded pad.
+ */
+static void
+expose_pad (GstParseBin * parsebin, GstElement * src, GstParsePad * parsepad,
+ GstPad * pad, GstCaps * caps, GstParseChain * chain)
+{
+ GST_DEBUG_OBJECT (parsebin, "pad %s:%s, chain:%p",
+ GST_DEBUG_PAD_NAME (pad), chain);
+
+ gst_parse_pad_activate (parsepad, chain);
+ chain->endpad = gst_object_ref (parsepad);
+ if (caps)
+ chain->endcaps = gst_caps_ref (caps);
+ else
+ chain->endcaps = NULL;
+}
+
+static void
+type_found (GstElement * typefind, guint probability,
+ GstCaps * caps, GstParseBin * parse_bin)
+{
+ GstPad *pad, *sink_pad;
+
+ GST_DEBUG_OBJECT (parse_bin, "typefind found caps %" GST_PTR_FORMAT, caps);
+
+ /* If the typefinder (but not something else) finds text/plain - i.e. that's
+ * the top-level type of the file - then error out.
+ */
+ if (gst_structure_has_name (gst_caps_get_structure (caps, 0), "text/plain")) {
+ GST_ELEMENT_ERROR (parse_bin, STREAM, WRONG_TYPE,
+ (_("This appears to be a text file")),
+ ("ParseBin cannot decode plain text files"));
+ goto exit;
+ }
+
+ /* FIXME: we can only deal with one type, we don't yet support dynamically changing
+ * caps from the typefind element */
+ if (parse_bin->have_type || parse_bin->parse_chain)
+ goto exit;
+
+ parse_bin->have_type = TRUE;
+
+ pad = gst_element_get_static_pad (typefind, "src");
+ sink_pad = gst_element_get_static_pad (typefind, "sink");
+
+ /* need some lock here to prevent race with shutdown state change
+ * which might yank away e.g. parse_chain while building stuff here.
+ * In typical cases, STREAM_LOCK is held and handles that, it need not
+ * be held (if called from a proxied setcaps), so grab it anyway */
+ GST_PAD_STREAM_LOCK (sink_pad);
+ parse_bin->parse_chain = gst_parse_chain_new (parse_bin, NULL, pad, caps);
+ analyze_new_pad (parse_bin, typefind, pad, caps, parse_bin->parse_chain);
+ GST_PAD_STREAM_UNLOCK (sink_pad);
+
+ gst_object_unref (sink_pad);
+ gst_object_unref (pad);
+
+exit:
+ return;
+}
+
+static GstPadProbeReturn
+pad_event_cb (GstPad * pad, GstPadProbeInfo * info, gpointer data)
+{
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+ GstPendingPad *ppad = (GstPendingPad *) data;
+ GstParseChain *chain = ppad->chain;
+ GstParseBin *parsebin = chain->parsebin;
+
+ g_assert (ppad);
+ g_assert (chain);
+ g_assert (parsebin);
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_EOS:
+ GST_DEBUG_OBJECT (pad, "Received EOS on a non final pad, this stream "
+ "ended too early");
+ chain->deadend = TRUE;
+ chain->drained = TRUE;
+ gst_object_replace ((GstObject **) & chain->current_pad, NULL);
+ /* we don't set the endcaps because NULL endcaps means early EOS */
+
+ EXPOSE_LOCK (parsebin);
+ if (parsebin->parse_chain)
+ if (gst_parse_chain_is_complete (parsebin->parse_chain))
+ gst_parse_bin_expose (parsebin);
+ EXPOSE_UNLOCK (parsebin);
+ break;
+ default:
+ break;
+ }
+ return GST_PAD_PROBE_OK;
+}
+
+static void
+pad_added_cb (GstElement * element, GstPad * pad, GstParseChain * chain)
+{
+ GstCaps *caps;
+ GstParseBin *parsebin;
+
+ parsebin = chain->parsebin;
+
+ GST_DEBUG_OBJECT (pad, "pad added, chain:%p", chain);
+
+ caps = get_pad_caps (pad);
+ analyze_new_pad (parsebin, element, pad, caps, chain);
+ if (caps)
+ gst_caps_unref (caps);
+
+ EXPOSE_LOCK (parsebin);
+ if (parsebin->parse_chain) {
+ if (gst_parse_chain_is_complete (parsebin->parse_chain)) {
+ GST_LOG_OBJECT (parsebin,
+ "That was the last dynamic object, now attempting to expose the group");
+ if (!gst_parse_bin_expose (parsebin))
+ GST_WARNING_OBJECT (parsebin, "Couldn't expose group");
+ }
+ } else {
+ GST_DEBUG_OBJECT (parsebin, "No parse chain, new pad ignored");
+ }
+ EXPOSE_UNLOCK (parsebin);
+}
+
+static void
+pad_removed_cb (GstElement * element, GstPad * pad, GstParseChain * chain)
+{
+ GList *l;
+
+ GST_LOG_OBJECT (pad, "pad removed, chain:%p", chain);
+
+ /* In fact, we don't have to do anything here, the active group will be
+ * removed when the group's multiqueue is drained */
+ CHAIN_MUTEX_LOCK (chain);
+ for (l = chain->pending_pads; l; l = l->next) {
+ GstPendingPad *ppad = l->data;
+ GstPad *opad = ppad->pad;
+
+ if (pad == opad) {
+ gst_pending_pad_free (ppad);
+ chain->pending_pads = g_list_delete_link (chain->pending_pads, l);
+ break;
+ }
+ }
+ CHAIN_MUTEX_UNLOCK (chain);
+}
+
+static void
+no_more_pads_cb (GstElement * element, GstParseChain * chain)
+{
+ GstParseGroup *group = NULL;
+
+ GST_LOG_OBJECT (element, "got no more pads");
+
+ CHAIN_MUTEX_LOCK (chain);
+ if (!chain->elements
+ || ((GstParseElement *) chain->elements->data)->element != element) {
+ GST_LOG_OBJECT (chain->parsebin, "no-more-pads from old chain element '%s'",
+ GST_OBJECT_NAME (element));
+ CHAIN_MUTEX_UNLOCK (chain);
+ return;
+ } else if (!chain->demuxer) {
+ GST_LOG_OBJECT (chain->parsebin,
+ "no-more-pads from a non-demuxer element '%s'",
+ GST_OBJECT_NAME (element));
+ CHAIN_MUTEX_UNLOCK (chain);
+ return;
+ }
+
+ /* when we received no_more_pads, we can complete the pads of the chain */
+ if (!chain->next_groups && chain->active_group) {
+ group = chain->active_group;
+ } else if (chain->next_groups) {
+ GList *iter;
+ for (iter = chain->next_groups; iter; iter = g_list_next (iter)) {
+ group = iter->data;
+ if (!group->no_more_pads)
+ break;
+ }
+ }
+ if (!group) {
+ GST_ERROR_OBJECT (chain->parsebin, "can't find group for element");
+ CHAIN_MUTEX_UNLOCK (chain);
+ return;
+ }
+
+ GST_DEBUG_OBJECT (element, "Setting group %p to complete", group);
+
+ group->no_more_pads = TRUE;
+ CHAIN_MUTEX_UNLOCK (chain);
+
+ EXPOSE_LOCK (chain->parsebin);
+ if (chain->parsebin->parse_chain) {
+ if (gst_parse_chain_is_complete (chain->parsebin->parse_chain)) {
+ gst_parse_bin_expose (chain->parsebin);
+ }
+ }
+ EXPOSE_UNLOCK (chain->parsebin);
+}
+
+static void
+caps_notify_cb (GstPad * pad, GParamSpec * unused, GstParseChain * chain)
+{
+ GstElement *element;
+ GList *l;
+
+ GST_LOG_OBJECT (pad, "Notified caps for pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+ /* Disconnect this; if we still need it, we'll reconnect to this in
+ * analyze_new_pad */
+ element = GST_ELEMENT_CAST (gst_pad_get_parent (pad));
+
+ CHAIN_MUTEX_LOCK (chain);
+ for (l = chain->pending_pads; l; l = l->next) {
+ GstPendingPad *ppad = l->data;
+ if (ppad->pad == pad) {
+ gst_pending_pad_free (ppad);
+ chain->pending_pads = g_list_delete_link (chain->pending_pads, l);
+ break;
+ }
+ }
+ CHAIN_MUTEX_UNLOCK (chain);
+
+ pad_added_cb (element, pad, chain);
+
+ gst_object_unref (element);
+}
+
+/* Decide whether an element is a demuxer based on the
+ * klass and number/type of src pad templates it has */
+static gboolean
+is_demuxer_element (GstElement * srcelement)
+{
+ GstElementFactory *srcfactory;
+ GstElementClass *elemclass;
+ GList *walk;
+ const gchar *klass;
+ gint potential_src_pads = 0;
+
+ srcfactory = gst_element_get_factory (srcelement);
+ klass =
+ gst_element_factory_get_metadata (srcfactory, GST_ELEMENT_METADATA_KLASS);
+
+ /* Can't be a demuxer unless it has Demux in the klass name */
+ if (!strstr (klass, "Demux"))
+ return FALSE;
+
+ /* Walk the src pad templates and count how many the element
+ * might produce */
+ elemclass = GST_ELEMENT_GET_CLASS (srcelement);
+
+ walk = gst_element_class_get_pad_template_list (elemclass);
+ while (walk != NULL) {
+ GstPadTemplate *templ;
+
+ templ = (GstPadTemplate *) walk->data;
+ if (GST_PAD_TEMPLATE_DIRECTION (templ) == GST_PAD_SRC) {
+ switch (GST_PAD_TEMPLATE_PRESENCE (templ)) {
+ case GST_PAD_ALWAYS:
+ case GST_PAD_SOMETIMES:
+ if (strstr (GST_PAD_TEMPLATE_NAME_TEMPLATE (templ), "%"))
+ potential_src_pads += 2; /* Might make multiple pads */
+ else
+ potential_src_pads += 1;
+ break;
+ case GST_PAD_REQUEST:
+ potential_src_pads += 2;
+ break;
+ }
+ }
+ walk = g_list_next (walk);
+ }
+
+ if (potential_src_pads < 2)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* gst_parse_chain_get_current_group:
+ *
+ * Returns the current group of this chain, to which
+ * new chains should be attached or NULL if the last
+ * group didn't have no-more-pads.
+ *
+ * Not MT-safe: Call with parent chain lock!
+ */
+static GstParseGroup *
+gst_parse_chain_get_current_group (GstParseChain * chain)
+{
+ GstParseGroup *group;
+
+ /* Now we know that we can really return something useful */
+ if (!chain->active_group) {
+ chain->active_group = group = gst_parse_group_new (chain->parsebin, chain);
+ } else if (!chain->active_group->no_more_pads) {
+ group = chain->active_group;
+ } else {
+ GList *iter;
+ group = NULL;
+ for (iter = chain->next_groups; iter; iter = g_list_next (iter)) {
+ GstParseGroup *next_group = iter->data;
+
+ if (!next_group->no_more_pads) {
+ group = next_group;
+ break;
+ }
+ }
+ }
+ if (!group) {
+ group = gst_parse_group_new (chain->parsebin, chain);
+ chain->next_groups = g_list_append (chain->next_groups, group);
+ }
+
+ return group;
+}
+
+static void gst_parse_group_free_internal (GstParseGroup * group,
+ gboolean hide);
+
+static void
+gst_parse_chain_free_internal (GstParseChain * chain, gboolean hide)
+{
+ GList *l, *set_to_null = NULL;
+
+ CHAIN_MUTEX_LOCK (chain);
+
+ GST_DEBUG_OBJECT (chain->parsebin, "%s chain %p",
+ (hide ? "Hiding" : "Freeing"), chain);
+
+ if (chain->active_group) {
+ gst_parse_group_free_internal (chain->active_group, hide);
+ if (!hide)
+ chain->active_group = NULL;
+ }
+
+ for (l = chain->next_groups; l; l = l->next) {
+ gst_parse_group_free_internal ((GstParseGroup *) l->data, hide);
+ if (!hide)
+ l->data = NULL;
+ }
+ if (!hide) {
+ g_list_free (chain->next_groups);
+ chain->next_groups = NULL;
+ }
+
+ if (!hide) {
+ for (l = chain->old_groups; l; l = l->next) {
+ GstParseGroup *group = l->data;
+
+ gst_parse_group_free (group);
+ }
+ g_list_free (chain->old_groups);
+ chain->old_groups = NULL;
+ }
+
+ gst_object_replace ((GstObject **) & chain->current_pad, NULL);
+
+ for (l = chain->pending_pads; l; l = l->next) {
+ GstPendingPad *ppad = l->data;
+ gst_pending_pad_free (ppad);
+ l->data = NULL;
+ }
+ g_list_free (chain->pending_pads);
+ chain->pending_pads = NULL;
+
+ for (l = chain->elements; l; l = l->next) {
+ GstParseElement *delem = l->data;
+ GstElement *element = delem->element;
+
+ if (delem->pad_added_id)
+ g_signal_handler_disconnect (element, delem->pad_added_id);
+ delem->pad_added_id = 0;
+ if (delem->pad_removed_id)
+ g_signal_handler_disconnect (element, delem->pad_removed_id);
+ delem->pad_removed_id = 0;
+ if (delem->no_more_pads_id)
+ g_signal_handler_disconnect (element, delem->no_more_pads_id);
+ delem->no_more_pads_id = 0;
+
+ if (delem->capsfilter) {
+ if (GST_OBJECT_PARENT (delem->capsfilter) ==
+ GST_OBJECT_CAST (chain->parsebin))
+ gst_bin_remove (GST_BIN_CAST (chain->parsebin), delem->capsfilter);
+ if (!hide) {
+ set_to_null =
+ g_list_append (set_to_null, gst_object_ref (delem->capsfilter));
+ }
+ }
+
+ if (GST_OBJECT_PARENT (element) == GST_OBJECT_CAST (chain->parsebin))
+ gst_bin_remove (GST_BIN_CAST (chain->parsebin), element);
+ if (!hide) {
+ set_to_null = g_list_append (set_to_null, gst_object_ref (element));
+ }
+
+ SUBTITLE_LOCK (chain->parsebin);
+ /* remove possible subtitle element */
+ chain->parsebin->subtitles =
+ g_list_remove (chain->parsebin->subtitles, element);
+ SUBTITLE_UNLOCK (chain->parsebin);
+
+ if (!hide) {
+ if (delem->capsfilter) {
+ gst_object_unref (delem->capsfilter);
+ delem->capsfilter = NULL;
+ }
+
+ gst_object_unref (element);
+ l->data = NULL;
+
+ g_slice_free (GstParseElement, delem);
+ }
+ }
+ if (!hide) {
+ g_list_free (chain->elements);
+ chain->elements = NULL;
+ }
+
+ if (chain->endpad) {
+ if (chain->endpad->exposed) {
+ GstPad *endpad = GST_PAD_CAST (chain->endpad);
+ GST_DEBUG_OBJECT (chain->parsebin, "Removing pad %s:%s",
+ GST_DEBUG_PAD_NAME (endpad));
+ gst_pad_push_event (endpad, gst_event_new_eos ());
+ gst_element_remove_pad (GST_ELEMENT_CAST (chain->parsebin), endpad);
+ }
+
+ parse_pad_set_target (chain->endpad, NULL);
+ chain->endpad->exposed = FALSE;
+ if (!hide) {
+ gst_object_unref (chain->endpad);
+ chain->endpad = NULL;
+ }
+ }
+
+ if (!hide && chain->current_pad) {
+ gst_object_unref (chain->current_pad);
+ chain->current_pad = NULL;
+ }
+
+ if (chain->pad) {
+ gst_object_unref (chain->pad);
+ chain->pad = NULL;
+ }
+ if (chain->start_caps) {
+ gst_caps_unref (chain->start_caps);
+ chain->start_caps = NULL;
+ }
+
+ if (chain->endcaps) {
+ gst_caps_unref (chain->endcaps);
+ chain->endcaps = NULL;
+ }
+ g_free (chain->deadend_details);
+ chain->deadend_details = NULL;
+
+ GST_DEBUG_OBJECT (chain->parsebin, "%s chain %p", (hide ? "Hidden" : "Freed"),
+ chain);
+ CHAIN_MUTEX_UNLOCK (chain);
+
+ while (set_to_null) {
+ GstElement *element = set_to_null->data;
+ set_to_null = g_list_delete_link (set_to_null, set_to_null);
+ gst_element_set_state (element, GST_STATE_NULL);
+ gst_object_unref (element);
+ }
+
+ if (!hide) {
+ g_mutex_clear (&chain->lock);
+ g_slice_free (GstParseChain, chain);
+ }
+}
+
+/* gst_parse_chain_free:
+ *
+ * Completely frees and removes the chain and all
+ * child groups from ParseBin.
+ *
+ * MT-safe, don't hold the chain lock or any child chain's lock
+ * when calling this!
+ */
+static void
+gst_parse_chain_free (GstParseChain * chain)
+{
+ gst_parse_chain_free_internal (chain, FALSE);
+}
+
+/* gst_parse_chain_new:
+ *
+ * Creates a new parse chain and initializes it.
+ *
+ * It's up to the caller to add it to the list of child chains of
+ * a group!
+ */
+static GstParseChain *
+gst_parse_chain_new (GstParseBin * parsebin, GstParseGroup * parent,
+ GstPad * pad, GstCaps * start_caps)
+{
+ GstParseChain *chain = g_slice_new0 (GstParseChain);
+
+ GST_DEBUG_OBJECT (parsebin, "Creating new chain %p with parent group %p",
+ chain, parent);
+
+ chain->parsebin = parsebin;
+ chain->parent = parent;
+ g_mutex_init (&chain->lock);
+ chain->pad = gst_object_ref (pad);
+ if (start_caps)
+ chain->start_caps = gst_caps_ref (start_caps);
+
+ return chain;
+}
+
+/****
+ * GstParseGroup functions
+ ****/
+
+static void
+gst_parse_group_free_internal (GstParseGroup * group, gboolean hide)
+{
+ GList *l;
+
+ GST_DEBUG_OBJECT (group->parsebin, "%s group %p",
+ (hide ? "Hiding" : "Freeing"), group);
+ for (l = group->children; l; l = l->next) {
+ GstParseChain *chain = (GstParseChain *) l->data;
+
+ gst_parse_chain_free_internal (chain, hide);
+ if (!hide)
+ l->data = NULL;
+ }
+ if (!hide) {
+ g_list_free (group->children);
+ group->children = NULL;
+ }
+
+ GST_DEBUG_OBJECT (group->parsebin, "%s group %p", (hide ? "Hid" : "Freed"),
+ group);
+ if (!hide)
+ g_slice_free (GstParseGroup, group);
+}
+
+/* gst_parse_group_free:
+ *
+ * Completely frees and removes the parse group and all
+ * it's children.
+ *
+ * Never call this from any streaming thread!
+ *
+ * Not MT-safe, call with parent's chain lock!
+ */
+static void
+gst_parse_group_free (GstParseGroup * group)
+{
+ gst_parse_group_free_internal (group, FALSE);
+}
+
+/* gst_parse_group_hide:
+ *
+ * Hide the parse group only, this means that
+ * all child endpads are removed from ParseBin
+ * and all signals are unconnected.
+ *
+ * No element is set to NULL state and completely
+ * unrefed here.
+ *
+ * Can be called from streaming threads.
+ *
+ * Not MT-safe, call with parent's chain lock!
+ */
+static void
+gst_parse_group_hide (GstParseGroup * group)
+{
+ gst_parse_group_free_internal (group, TRUE);
+}
+
+/* gst_parse_chain_free_hidden_groups:
+ *
+ * Frees any parse groups that were hidden previously.
+ * This allows keeping memory use from ballooning when
+ * switching chains repeatedly.
+ *
+ * A new throwaway thread will be created to free the
+ * groups, so any delay does not block the setup of a
+ * new group.
+ *
+ * Not MT-safe, call with parent's chain lock!
+ */
+static void
+gst_parse_chain_free_hidden_groups (GList * old_groups)
+{
+ GList *l;
+
+ for (l = old_groups; l; l = l->next) {
+ GstParseGroup *group = l->data;
+
+ gst_parse_group_free (group);
+ }
+ g_list_free (old_groups);
+}
+
+static void
+gst_parse_chain_start_free_hidden_groups_thread (GstParseChain * chain)
+{
+ GThread *thread;
+ GError *error = NULL;
+ GList *old_groups;
+
+ old_groups = chain->old_groups;
+ if (!old_groups)
+ return;
+
+ chain->old_groups = NULL;
+ thread = g_thread_try_new ("free-hidden-groups",
+ (GThreadFunc) gst_parse_chain_free_hidden_groups, old_groups, &error);
+ if (!thread || error) {
+ GST_ERROR ("Failed to start free-hidden-groups thread: %s",
+ error ? error->message : "unknown reason");
+ g_clear_error (&error);
+ chain->old_groups = old_groups;
+ return;
+ }
+ GST_DEBUG_OBJECT (chain->parsebin, "Started free-hidden-groups thread");
+ /* We do not need to wait for it or get any results from it */
+ g_thread_unref (thread);
+}
+
+/* gst_parse_group_new:
+ * @parsebin: Parent ParseBin
+ * @parent: Parent chain or %NULL
+ *
+ * Creates a new GstParseGroup. It is up to the caller to add it to the list
+ * of groups.
+ */
+static GstParseGroup *
+gst_parse_group_new (GstParseBin * parsebin, GstParseChain * parent)
+{
+ GstParseGroup *group = g_slice_new0 (GstParseGroup);
+
+ GST_DEBUG_OBJECT (parsebin, "Creating new group %p with parent chain %p",
+ group, parent);
+
+ group->parsebin = parsebin;
+ group->parent = parent;
+
+ return group;
+}
+
+/* gst_parse_group_is_complete:
+ *
+ * Checks if the group is complete, this means that
+ * a) no-more-pads happened
+ * b) all child chains are complete
+ *
+ * Not MT-safe, always call with ParseBin expose lock
+ */
+static gboolean
+gst_parse_group_is_complete (GstParseGroup * group)
+{
+ GList *l;
+ gboolean complete = TRUE;
+
+ if (!group->no_more_pads) {
+ complete = FALSE;
+ goto out;
+ }
+
+ for (l = group->children; l; l = l->next) {
+ GstParseChain *chain = l->data;
+
+ /* Any blocked chain requires we complete this group
+ * since everything is synchronous, we can't proceed otherwise */
+ if (chain->endpad && chain->endpad->blocked)
+ goto out;
+
+ if (!gst_parse_chain_is_complete (chain)) {
+ complete = FALSE;
+ goto out;
+ }
+ }
+
+out:
+ GST_DEBUG_OBJECT (group->parsebin, "Group %p is complete: %d", group,
+ complete);
+ return complete;
+}
+
+/* gst_parse_chain_is_complete:
+ *
+ * Returns TRUE if the chain is complete, this means either
+ * a) This chain is a dead end, i.e. we have no suitable plugins
+ * b) This chain ends in an endpad and this is blocked or exposed
+ * c) The chain has gotten far enough to have plugged 1 parser at least.
+ *
+ * Not MT-safe, always call with ParseBin expose lock
+ */
+static gboolean
+gst_parse_chain_is_complete (GstParseChain * chain)
+{
+ gboolean complete = FALSE;
+
+ CHAIN_MUTEX_LOCK (chain);
+ if (chain->parsebin->shutdown)
+ goto out;
+
+ if (chain->deadend) {
+ complete = TRUE;
+ goto out;
+ }
+
+ if (chain->endpad && (chain->endpad->blocked || chain->endpad->exposed)) {
+ complete = TRUE;
+ goto out;
+ }
+
+ if (chain->demuxer) {
+ if (chain->active_group
+ && gst_parse_group_is_complete (chain->active_group)) {
+ complete = TRUE;
+ goto out;
+ }
+ }
+
+ if (chain->parsed) {
+ complete = TRUE;
+ goto out;
+ }
+
+out:
+ CHAIN_MUTEX_UNLOCK (chain);
+ GST_DEBUG_OBJECT (chain->parsebin, "Chain %p is complete: %d", chain,
+ complete);
+ return complete;
+}
+
+static void
+chain_remove_old_groups (GstParseChain * chain)
+{
+ GList *tmp;
+
+ /* First go in child */
+ if (chain->active_group) {
+ for (tmp = chain->active_group->children; tmp; tmp = tmp->next) {
+ GstParseChain *child = (GstParseChain *) tmp->data;
+ chain_remove_old_groups (child);
+ }
+ }
+
+ if (chain->old_groups) {
+ gst_parse_group_hide (chain->old_groups->data);
+ gst_parse_chain_start_free_hidden_groups_thread (chain);
+ }
+}
+
+static gboolean
+drain_and_switch_chains (GstParseChain * chain, GstParsePad * drainpad,
+ gboolean * last_group, gboolean * drained, gboolean * switched);
+/* drain_and_switch_chains/groups:
+ *
+ * CALL WITH CHAIN LOCK (or group parent) TAKEN !
+ *
+ * Goes down the chains/groups until it finds the chain
+ * to which the drainpad belongs.
+ *
+ * It marks that pad/chain as drained and then will figure
+ * out which group to switch to or not.
+ *
+ * last_chain will be set to TRUE if the group to which the
+ * pad belongs is the last one.
+ *
+ * drained will be set to TRUE if the chain/group is drained.
+ *
+ * Returns: TRUE if the chain contained the target pad */
+static gboolean
+drain_and_switch_group (GstParseGroup * group, GstParsePad * drainpad,
+ gboolean * last_group, gboolean * drained, gboolean * switched)
+{
+ gboolean handled = FALSE;
+ GList *tmp;
+
+ GST_DEBUG ("Checking group %p (target pad %s:%s)",
+ group, GST_DEBUG_PAD_NAME (drainpad));
+
+ /* Definitely can't be in drained groups */
+ if (G_UNLIKELY (group->drained)) {
+ goto beach;
+ }
+
+ /* Figure out if all our chains are drained with the
+ * new information */
+ group->drained = TRUE;
+ for (tmp = group->children; tmp; tmp = tmp->next) {
+ GstParseChain *chain = (GstParseChain *) tmp->data;
+ gboolean subdrained = FALSE;
+
+ handled |=
+ drain_and_switch_chains (chain, drainpad, last_group, &subdrained,
+ switched);
+ if (!subdrained)
+ group->drained = FALSE;
+ }
+
+beach:
+ GST_DEBUG ("group %p (last_group:%d, drained:%d, switched:%d, handled:%d)",
+ group, *last_group, group->drained, *switched, handled);
+ *drained = group->drained;
+ return handled;
+}
+
+static gboolean
+drain_and_switch_chains (GstParseChain * chain, GstParsePad * drainpad,
+ gboolean * last_group, gboolean * drained, gboolean * switched)
+{
+ gboolean handled = FALSE;
+ GstParseBin *parsebin = chain->parsebin;
+
+ GST_DEBUG ("Checking chain %p %s:%s (target pad %s:%s)",
+ chain, GST_DEBUG_PAD_NAME (chain->pad), GST_DEBUG_PAD_NAME (drainpad));
+
+ CHAIN_MUTEX_LOCK (chain);
+
+ /* Definitely can't be in drained chains */
+ if (G_UNLIKELY (chain->drained)) {
+ goto beach;
+ }
+
+ if (chain->endpad) {
+ /* Check if we're reached the target endchain */
+ if (drainpad != NULL && chain == drainpad->chain) {
+ GST_DEBUG ("Found the target chain");
+ drainpad->drained = TRUE;
+ handled = TRUE;
+ }
+
+ chain->drained = chain->endpad->drained;
+ goto beach;
+ }
+
+ /* We known there are groups to switch to */
+ if (chain->next_groups)
+ *last_group = FALSE;
+
+ /* Check the active group */
+ if (chain->active_group) {
+ gboolean subdrained = FALSE;
+ handled = drain_and_switch_group (chain->active_group, drainpad,
+ last_group, &subdrained, switched);
+
+ /* The group is drained, see if we can switch to another */
+ if ((handled || drainpad == NULL) && subdrained && !*switched) {
+ if (chain->next_groups) {
+ /* Switch to next group, the actual removal of the current group will
+ * be done when the next one is activated */
+ GST_DEBUG_OBJECT (parsebin, "Moving current group %p to old groups",
+ chain->active_group);
+ chain->old_groups =
+ g_list_prepend (chain->old_groups, chain->active_group);
+ GST_DEBUG_OBJECT (parsebin, "Switching to next group %p",
+ chain->next_groups->data);
+ chain->active_group = chain->next_groups->data;
+ chain->next_groups =
+ g_list_delete_link (chain->next_groups, chain->next_groups);
+ *switched = TRUE;
+ chain->drained = FALSE;
+ } else {
+ GST_DEBUG ("Group %p was the last in chain %p", chain->active_group,
+ chain);
+ chain->drained = TRUE;
+ /* We're drained ! */
+ }
+ } else {
+ if (subdrained && !chain->next_groups)
+ *drained = TRUE;
+ }
+ }
+
+beach:
+ CHAIN_MUTEX_UNLOCK (chain);
+
+ GST_DEBUG ("Chain %p (handled:%d, last_group:%d, drained:%d, switched:%d)",
+ chain, handled, *last_group, chain->drained, *switched);
+
+ *drained = chain->drained;
+
+ if (*drained)
+ g_signal_emit (parsebin, gst_parse_bin_signals[SIGNAL_DRAINED], 0, NULL);
+
+ return handled;
+}
+
+/* check if the group is drained, meaning all pads have seen an EOS
+ * event. */
+static gboolean
+gst_parse_pad_handle_eos (GstParsePad * pad)
+{
+ gboolean last_group = TRUE;
+ gboolean switched = FALSE;
+ gboolean drained = FALSE;
+ GstParseChain *chain = pad->chain;
+ GstParseBin *parsebin = chain->parsebin;
+
+ GST_LOG_OBJECT (parsebin, "pad %p", pad);
+ EXPOSE_LOCK (parsebin);
+ if (parsebin->parse_chain) {
+ drain_and_switch_chains (parsebin->parse_chain, pad, &last_group, &drained,
+ &switched);
+
+ if (switched) {
+ /* If we resulted in a group switch, expose what's needed */
+ if (gst_parse_chain_is_complete (parsebin->parse_chain))
+ gst_parse_bin_expose (parsebin);
+ }
+ }
+ EXPOSE_UNLOCK (parsebin);
+
+ return last_group;
+}
+
+/* gst_parse_group_is_drained:
+ *
+ * Check is this group is drained and cache this result.
+ * The group is drained if all child chains are drained.
+ *
+ * Not MT-safe, call with group->parent's lock */
+static gboolean
+gst_parse_group_is_drained (GstParseGroup * group)
+{
+ GList *l;
+ gboolean drained = TRUE;
+
+ if (group->drained) {
+ drained = TRUE;
+ goto out;
+ }
+
+ for (l = group->children; l; l = l->next) {
+ GstParseChain *chain = l->data;
+
+ CHAIN_MUTEX_LOCK (chain);
+ if (!gst_parse_chain_is_drained (chain))
+ drained = FALSE;
+ CHAIN_MUTEX_UNLOCK (chain);
+ if (!drained)
+ goto out;
+ }
+ group->drained = drained;
+
+out:
+ GST_DEBUG_OBJECT (group->parsebin, "Group %p is drained: %d", group, drained);
+ return drained;
+}
+
+/* gst_parse_chain_is_drained:
+ *
+ * Check is the chain is drained, which means that
+ * either
+ *
+ * a) it's endpad is drained
+ * b) there are no pending pads, the active group is drained
+ * and there are no next groups
+ *
+ * Not MT-safe, call with chain lock
+ */
+static gboolean
+gst_parse_chain_is_drained (GstParseChain * chain)
+{
+ gboolean drained = FALSE;
+
+ if (chain->endpad) {
+ drained = chain->endpad->drained;
+ goto out;
+ }
+
+ if (chain->pending_pads) {
+ drained = FALSE;
+ goto out;
+ }
+
+ if (chain->active_group && gst_parse_group_is_drained (chain->active_group)
+ && !chain->next_groups) {
+ drained = TRUE;
+ goto out;
+ }
+
+out:
+ GST_DEBUG_OBJECT (chain->parsebin, "Chain %p is drained: %d", chain, drained);
+ return drained;
+}
+
+/* sort_end_pads:
+ * GCompareFunc to use with lists of GstPad.
+ * Sorts pads by mime type.
+ * First video (raw, then non-raw), then audio (raw, then non-raw),
+ * then others.
+ *
+ * Return: negative if a<b, 0 if a==b, positive if a>b
+ */
+static gint
+sort_end_pads (GstParsePad * da, GstParsePad * db)
+{
+ gint va, vb;
+ GstCaps *capsa, *capsb;
+ GstStructure *sa, *sb;
+ const gchar *namea, *nameb;
+ gchar *ida, *idb;
+ gint ret;
+
+ capsa = get_pad_caps (GST_PAD_CAST (da));
+ capsb = get_pad_caps (GST_PAD_CAST (db));
+
+ sa = gst_caps_get_structure ((const GstCaps *) capsa, 0);
+ sb = gst_caps_get_structure ((const GstCaps *) capsb, 0);
+
+ namea = gst_structure_get_name (sa);
+ nameb = gst_structure_get_name (sb);
+
+ if (g_strrstr (namea, "video/x-raw"))
+ va = 0;
+ else if (g_strrstr (namea, "video/"))
+ va = 1;
+ else if (g_strrstr (namea, "image/"))
+ va = 2;
+ else if (g_strrstr (namea, "audio/x-raw"))
+ va = 3;
+ else if (g_strrstr (namea, "audio/"))
+ va = 4;
+ else
+ va = 5;
+
+ if (g_strrstr (nameb, "video/x-raw"))
+ vb = 0;
+ else if (g_strrstr (nameb, "video/"))
+ vb = 1;
+ else if (g_strrstr (nameb, "image/"))
+ vb = 2;
+ else if (g_strrstr (nameb, "audio/x-raw"))
+ vb = 3;
+ else if (g_strrstr (nameb, "audio/"))
+ vb = 4;
+ else
+ vb = 5;
+
+ gst_caps_unref (capsa);
+ gst_caps_unref (capsb);
+
+ if (va != vb)
+ return va - vb;
+
+ /* if otherwise the same, sort by stream-id */
+ ida = gst_pad_get_stream_id (GST_PAD_CAST (da));
+ idb = gst_pad_get_stream_id (GST_PAD_CAST (db));
+ ret = (ida) ? ((idb) ? strcmp (ida, idb) : -1) : 1;
+ g_free (ida);
+ g_free (idb);
+
+ return ret;
+}
+
+static gboolean
+debug_sticky_event (GstPad * pad, GstEvent ** event, gpointer user_data)
+{
+ GST_DEBUG_OBJECT (pad, "sticky event %s (%p)", GST_EVENT_TYPE_NAME (*event),
+ *event);
+ return TRUE;
+}
+
+/* Must only be called if the toplevel chain is complete and blocked! */
+/* Not MT-safe, call with ParseBin expose lock! */
+static gboolean
+gst_parse_bin_expose (GstParseBin * parsebin)
+{
+ GList *tmp, *endpads;
+ gboolean missing_plugin;
+ GString *missing_plugin_details;
+ gboolean already_exposed;
+ gboolean last_group;
+ gboolean uncollected_streams;
+ GstStreamCollection *fallback_collection = NULL;
+
+retry:
+ endpads = NULL;
+ missing_plugin = FALSE;
+ already_exposed = TRUE;
+ last_group = TRUE;
+
+ missing_plugin_details = g_string_new ("");
+
+ GST_DEBUG_OBJECT (parsebin, "Exposing currently active chains/groups");
+
+ /* Don't expose if we're currently shutting down */
+ DYN_LOCK (parsebin);
+ if (G_UNLIKELY (parsebin->shutdown)) {
+ GST_WARNING_OBJECT (parsebin,
+ "Currently, shutting down, aborting exposing");
+ DYN_UNLOCK (parsebin);
+ return FALSE;
+ }
+ DYN_UNLOCK (parsebin);
+
+ /* Get the pads that we're going to expose and mark things as exposed */
+ uncollected_streams = FALSE;
+ if (!gst_parse_chain_expose (parsebin->parse_chain, &endpads, &missing_plugin,
+ missing_plugin_details, &last_group, &uncollected_streams)) {
+ g_list_free_full (endpads, (GDestroyNotify) gst_object_unref);
+ g_string_free (missing_plugin_details, TRUE);
+ GST_ERROR_OBJECT (parsebin, "Broken chain/group tree");
+ g_return_val_if_reached (FALSE);
+ return FALSE;
+ }
+ if (endpads == NULL) {
+ if (missing_plugin) {
+ if (missing_plugin_details->len > 0) {
+ gchar *details = g_string_free (missing_plugin_details, FALSE);
+ GST_ELEMENT_ERROR (parsebin, CORE, MISSING_PLUGIN, (NULL),
+ ("no suitable plugins found:\n%s", details));
+ g_free (details);
+ } else {
+ g_string_free (missing_plugin_details, TRUE);
+ GST_ELEMENT_ERROR (parsebin, CORE, MISSING_PLUGIN, (NULL),
+ ("no suitable plugins found"));
+ }
+ } else {
+ /* in this case, the stream ended without buffers,
+ * just post a warning */
+ g_string_free (missing_plugin_details, TRUE);
+
+ GST_WARNING_OBJECT (parsebin, "All streams finished without buffers. "
+ "Last group: %d", last_group);
+ if (last_group) {
+ GST_ELEMENT_ERROR (parsebin, STREAM, FAILED, (NULL),
+ ("all streams without buffers"));
+ } else {
+ gboolean switched = FALSE;
+ gboolean drained = FALSE;
+
+ drain_and_switch_chains (parsebin->parse_chain, NULL, &last_group,
+ &drained, &switched);
+ GST_ELEMENT_WARNING (parsebin, STREAM, FAILED, (NULL),
+ ("all streams without buffers"));
+ if (switched) {
+ if (gst_parse_chain_is_complete (parsebin->parse_chain))
+ goto retry;
+ else
+ return FALSE;
+ }
+ }
+ }
+
+ do_async_done (parsebin);
+ return FALSE;
+ }
+
+ if (uncollected_streams) {
+ /* FIXME: Collect and use a stream id from the top chain as
+ * upstream ID? */
+ fallback_collection = gst_stream_collection_new (NULL);
+
+ build_fallback_collection (parsebin->parse_chain, fallback_collection);
+
+ gst_element_post_message (GST_ELEMENT (parsebin),
+ gst_message_new_stream_collection (GST_OBJECT (parsebin),
+ fallback_collection));
+ }
+
+ g_string_free (missing_plugin_details, TRUE);
+
+ /* Check if this was called when everything was exposed already,
+ * and see if we need to post a new fallback collection */
+ for (tmp = endpads; tmp && already_exposed; tmp = tmp->next) {
+ GstParsePad *parsepad = tmp->data;
+
+ already_exposed &= parsepad->exposed;
+ }
+ if (already_exposed) {
+ GST_DEBUG_OBJECT (parsebin, "Everything was exposed already!");
+ if (fallback_collection)
+ gst_object_unref (fallback_collection);
+ g_list_free_full (endpads, (GDestroyNotify) gst_object_unref);
+ return TRUE;
+ }
+
+ /* Set all already exposed pads to blocked */
+ for (tmp = endpads; tmp; tmp = tmp->next) {
+ GstParsePad *parsepad = tmp->data;
+
+ if (parsepad->exposed) {
+ GST_DEBUG_OBJECT (parsepad, "blocking exposed pad");
+ gst_parse_pad_set_blocked (parsepad, TRUE);
+ }
+ }
+
+ /* re-order pads : video, then audio, then others */
+ endpads = g_list_sort (endpads, (GCompareFunc) sort_end_pads);
+
+ /* Expose pads */
+ for (tmp = endpads; tmp; tmp = tmp->next) {
+ GstParsePad *parsepad = (GstParsePad *) tmp->data;
+ gchar *padname;
+
+ //if (!parsepad->blocked)
+ //continue;
+
+ /* 1. rewrite name */
+ padname = g_strdup_printf ("src_%u", parsebin->nbpads);
+ parsebin->nbpads++;
+ GST_DEBUG_OBJECT (parsebin, "About to expose parsepad %s as %s",
+ GST_OBJECT_NAME (parsepad), padname);
+ gst_object_set_name (GST_OBJECT (parsepad), padname);
+ g_free (padname);
+
+ gst_pad_sticky_events_foreach (GST_PAD_CAST (parsepad), debug_sticky_event,
+ parsepad);
+
+ /* 2. activate and add */
+ if (!parsepad->exposed) {
+ parsepad->exposed = TRUE;
+ if (!gst_element_add_pad (GST_ELEMENT (parsebin),
+ GST_PAD_CAST (parsepad))) {
+ /* not really fatal, we can try to add the other pads */
+ g_warning ("error adding pad to ParseBin");
+ parsepad->exposed = FALSE;
+ continue;
+ }
+#if 0
+ /* HACK: Send an empty gap event to push sticky events */
+ gst_pad_push_event (GST_PAD (parsepad),
+ gst_event_new_gap (0, GST_CLOCK_TIME_NONE));
+#endif
+ }
+
+ GST_INFO_OBJECT (parsepad, "added new decoded pad");
+ }
+
+ /* Unblock internal pads. The application should have connected stuff now
+ * so that streaming can continue. */
+ for (tmp = endpads; tmp; tmp = tmp->next) {
+ GstParsePad *parsepad = (GstParsePad *) tmp->data;
+
+ if (parsepad->exposed) {
+ GST_DEBUG_OBJECT (parsepad, "unblocking");
+ gst_parse_pad_unblock (parsepad);
+ GST_DEBUG_OBJECT (parsepad, "unblocked");
+ }
+
+ /* Send stream-collection events for any pads that don't have them,
+ * and post a stream-collection onto the bus */
+ if (parsepad->active_collection == NULL && fallback_collection) {
+ gst_pad_push_event (GST_PAD (parsepad),
+ gst_event_new_stream_collection (gst_object_ref
+ (fallback_collection)));
+ }
+ gst_object_unref (parsepad);
+ }
+ g_list_free (endpads);
+
+ if (fallback_collection)
+ gst_object_unref (fallback_collection);
+
+ /* Remove old groups */
+ chain_remove_old_groups (parsebin->parse_chain);
+
+ do_async_done (parsebin);
+ GST_DEBUG_OBJECT (parsebin, "Exposed everything");
+ return TRUE;
+}
+
+/* gst_parse_chain_expose:
+ *
+ * Check if the chain can be exposed and add all endpads
+ * to the endpads list.
+ *
+ * Not MT-safe, call with ParseBin expose lock! *
+ */
+static gboolean
+gst_parse_chain_expose (GstParseChain * chain, GList ** endpads,
+ gboolean * missing_plugin, GString * missing_plugin_details,
+ gboolean * last_group, gboolean * uncollected_streams)
+{
+ GstParseGroup *group;
+ GList *l;
+ gboolean ret = FALSE;
+
+ if (chain->deadend) {
+ if (chain->endcaps) {
+ if (chain->deadend_details) {
+ g_string_append (missing_plugin_details, chain->deadend_details);
+ g_string_append_c (missing_plugin_details, '\n');
+ } else {
+ gchar *desc = gst_pb_utils_get_codec_description (chain->endcaps);
+ gchar *caps_str = gst_caps_to_string (chain->endcaps);
+ g_string_append_printf (missing_plugin_details,
+ "Missing decoder: %s (%s)\n", desc, caps_str);
+ g_free (caps_str);
+ g_free (desc);
+ }
+ *missing_plugin = TRUE;
+ }
+ return TRUE;
+ }
+
+ if (chain->endpad == NULL && chain->parsed && chain->pending_pads) {
+ /* The chain has a pending pad from a parser, let's just
+ * expose that now as the endpad */
+ GList *cur = chain->pending_pads;
+ GstPendingPad *ppad = (GstPendingPad *) (cur->data);
+ GstPad *endpad = gst_object_ref (ppad->pad);
+ GstElement *elem =
+ GST_ELEMENT (gst_object_get_parent (GST_OBJECT (endpad)));
+
+ chain->pending_pads = g_list_remove (chain->pending_pads, ppad);
+
+ gst_pending_pad_free (ppad);
+
+ GST_DEBUG_OBJECT (chain->parsebin,
+ "Exposing pad %" GST_PTR_FORMAT " with incomplete caps "
+ "because it's parsed", endpad);
+
+ expose_pad (chain->parsebin, elem, chain->current_pad, endpad, NULL, chain);
+ gst_object_unref (endpad);
+ gst_object_unref (elem);
+ }
+
+ if (chain->endpad) {
+ GstParsePad *p = chain->endpad;
+
+ if (p->active_stream && p->active_collection == NULL
+ && !p->in_a_fallback_collection)
+ *uncollected_streams = TRUE;
+
+ *endpads = g_list_prepend (*endpads, gst_object_ref (p));
+ return TRUE;
+ }
+
+ if (chain->next_groups)
+ *last_group = FALSE;
+
+ group = chain->active_group;
+ if (!group) {
+ GstParsePad *p = chain->current_pad;
+
+ if (p->active_stream && p->active_collection == NULL
+ && !p->in_a_fallback_collection)
+ *uncollected_streams = TRUE;
+
+ return FALSE;
+ }
+
+ for (l = group->children; l; l = l->next) {
+ GstParseChain *childchain = l->data;
+
+ ret |= gst_parse_chain_expose (childchain, endpads, missing_plugin,
+ missing_plugin_details, last_group, uncollected_streams);
+ }
+
+ return ret;
+}
+
+static void
+build_fallback_collection (GstParseChain * chain,
+ GstStreamCollection * collection)
+{
+ GstParseGroup *group = chain->active_group;
+ GList *l;
+
+ /* If it's an end pad, or a not-finished chain that's
+ * not a group, put it in the collection */
+ if (chain->endpad || (chain->current_pad && group == NULL)) {
+ GstParsePad *p = chain->current_pad;
+
+ if (p->active_stream != NULL && p->active_collection == NULL) {
+ GST_DEBUG_OBJECT (p, "Adding stream to fallback collection");
+ gst_stream_collection_add_stream (collection,
+ gst_object_ref (p->active_stream));
+ p->in_a_fallback_collection = TRUE;
+ }
+ return;
+ }
+
+ if (!group)
+ return;
+ for (l = group->children; l; l = l->next) {
+ GstParseChain *childchain = l->data;
+
+ build_fallback_collection (childchain, collection);
+ }
+}
+
+/*************************
+ * GstParsePad functions
+ *************************/
+
+static void gst_parse_pad_dispose (GObject * object);
+
+static void
+gst_parse_pad_class_init (GstParsePadClass * klass)
+{
+ GObjectClass *gobject_klass;
+
+ gobject_klass = (GObjectClass *) klass;
+
+ gobject_klass->dispose = gst_parse_pad_dispose;
+}
+
+static void
+gst_parse_pad_init (GstParsePad * pad)
+{
+ pad->chain = NULL;
+ pad->blocked = FALSE;
+ pad->exposed = FALSE;
+ pad->drained = FALSE;
+ gst_object_ref_sink (pad);
+}
+
+static void
+gst_parse_pad_dispose (GObject * object)
+{
+ GstParsePad *parsepad = (GstParsePad *) (object);
+ parse_pad_set_target (parsepad, NULL);
+
+ gst_object_replace ((GstObject **) & parsepad->active_collection, NULL);
+ gst_object_replace ((GstObject **) & parsepad->active_stream, NULL);
+
+ G_OBJECT_CLASS (gst_parse_pad_parent_class)->dispose (object);
+}
+
+static GstPadProbeReturn
+source_pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ GstParsePad *parsepad = user_data;
+ GstParseChain *chain;
+ GstParseBin *parsebin;
+ GstPadProbeReturn ret = GST_PAD_PROBE_OK;
+
+ if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) {
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+
+ GST_LOG_OBJECT (pad, "Seeing event '%s'", GST_EVENT_TYPE_NAME (event));
+
+ if (!GST_EVENT_IS_SERIALIZED (event)) {
+ /* do not block on sticky or out of band events otherwise the allocation query
+ from demuxer might block the loop thread */
+ GST_LOG_OBJECT (pad, "Letting OOB event through");
+ return GST_PAD_PROBE_PASS;
+ }
+
+ if (GST_EVENT_IS_STICKY (event) && GST_EVENT_TYPE (event) != GST_EVENT_EOS) {
+ GstPad *peer;
+
+ /* manually push sticky events to ghost pad to avoid exposing pads
+ * that don't have the sticky events. Handle EOS separately as we
+ * want to block the pad on it if we didn't get any buffers before
+ * EOS and expose the pad then. */
+ peer = gst_pad_get_peer (pad);
+ gst_pad_send_event (peer, gst_event_ref (event));
+ gst_object_unref (peer);
+ GST_LOG_OBJECT (pad, "Manually pushed sticky event through");
+ ret = GST_PAD_PROBE_HANDLED;
+ goto done;
+ }
+ } else if (GST_PAD_PROBE_INFO_TYPE (info) &
+ GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM) {
+ GstQuery *query = GST_PAD_PROBE_INFO_QUERY (info);
+
+ if (!GST_QUERY_IS_SERIALIZED (query)) {
+ /* do not block on non-serialized queries */
+ GST_LOG_OBJECT (pad, "Letting non-serialized query through");
+ return GST_PAD_PROBE_PASS;
+ }
+ if (!gst_pad_has_current_caps (pad)) {
+ /* do not block on allocation queries before we have caps,
+ * this would deadlock because we are doing no autoplugging
+ * without caps.
+ * TODO: Try to do autoplugging based on the query caps
+ */
+ GST_LOG_OBJECT (pad, "Letting serialized query before caps through");
+ return GST_PAD_PROBE_PASS;
+ }
+ }
+ chain = parsepad->chain;
+ parsebin = chain->parsebin;
+
+ GST_LOG_OBJECT (parsepad, "blocked: parsepad->chain:%p", chain);
+
+ parsepad->blocked = TRUE;
+
+ EXPOSE_LOCK (parsebin);
+ if (parsebin->parse_chain) {
+ if (!gst_parse_bin_expose (parsebin))
+ GST_WARNING_OBJECT (parsebin, "Couldn't expose group");
+ }
+ EXPOSE_UNLOCK (parsebin);
+
+done:
+ return ret;
+}
+
+/* FIXME: We can probably do some cleverer things, and maybe move this into
+ * pbutils. Ideas:
+ * if there are tags look if it's got an AUDIO_CODEC VIDEO_CODEC CONTAINER_FORMAT tag
+ * Look at the factory klass designation of parsers in the chain
+ * Consider demuxer pad names as well, sometimes they give the type away
+ */
+static GstStreamType
+guess_stream_type_from_caps (GstCaps * caps)
+{
+ GstStructure *s;
+ const gchar *name;
+
+ if (gst_caps_get_size (caps) < 1)
+ return GST_STREAM_TYPE_UNKNOWN;
+
+ s = gst_caps_get_structure (caps, 0);
+ name = gst_structure_get_name (s);
+
+ if (g_str_has_prefix (name, "video/") || g_str_has_prefix (name, "image/"))
+ return GST_STREAM_TYPE_VIDEO;
+ if (g_str_has_prefix (name, "audio/"))
+ return GST_STREAM_TYPE_AUDIO;
+ if (g_str_has_prefix (name, "text/") ||
+ g_str_has_prefix (name, "subpicture/"))
+ return GST_STREAM_TYPE_TEXT;
+
+ return GST_STREAM_TYPE_UNKNOWN;
+}
+
+static void
+gst_parse_pad_update_caps (GstParsePad * parsepad, GstCaps * caps)
+{
+ if (caps && parsepad->active_stream) {
+ GST_DEBUG_OBJECT (parsepad, "Storing caps %" GST_PTR_FORMAT
+ " on stream %" GST_PTR_FORMAT, caps, parsepad->active_stream);
+
+ if (gst_caps_is_fixed (caps))
+ gst_stream_set_caps (parsepad->active_stream, caps);
+ /* intuit a type */
+ if (gst_stream_get_stream_type (parsepad->active_stream) ==
+ GST_STREAM_TYPE_UNKNOWN) {
+ GstStreamType new_type = guess_stream_type_from_caps (caps);
+ if (new_type != GST_STREAM_TYPE_UNKNOWN)
+ gst_stream_set_stream_type (parsepad->active_stream, new_type);
+ }
+ }
+}
+
+static void
+gst_parse_pad_update_tags (GstParsePad * parsepad, GstTagList * tags)
+{
+ if (tags && parsepad->active_stream) {
+ GST_DEBUG_OBJECT (parsepad, "Storing new tags %" GST_PTR_FORMAT
+ " on stream %" GST_PTR_FORMAT, tags, parsepad->active_stream);
+ gst_stream_set_tags (parsepad->active_stream, tags);
+ }
+}
+
+static GstEvent *
+gst_parse_pad_stream_start_event (GstParsePad * parsepad, GstEvent * event)
+{
+ GstStream *stream = NULL;
+ const gchar *stream_id = NULL;
+ gboolean repeat_event = FALSE;
+
+ gst_event_parse_stream_start (event, &stream_id);
+
+ if (parsepad->active_stream != NULL &&
+ g_str_equal (parsepad->active_stream->stream_id, stream_id))
+ repeat_event = TRUE;
+ else {
+ /* A new stream requires a new collection event, or else
+ * we'll place it in a fallback collection later */
+ gst_object_replace ((GstObject **) & parsepad->active_collection, NULL);
+ parsepad->in_a_fallback_collection = FALSE;
+ }
+
+ gst_event_parse_stream (event, &stream);
+ if (stream == NULL) {
+ GstCaps *caps = gst_pad_get_current_caps (GST_PAD_CAST (parsepad));
+ if (caps == NULL) {
+ /* Try and get caps from the parsepad peer */
+ GstPad *peer = gst_ghost_pad_get_target (GST_GHOST_PAD (parsepad));
+ caps = gst_pad_get_current_caps (peer);
+ gst_object_unref (peer);
+ }
+ if (caps == NULL && parsepad->chain && parsepad->chain->start_caps) {
+ /* Still no caps, use the chain start caps */
+ caps = gst_caps_ref (parsepad->chain->start_caps);
+ }
+
+ GST_DEBUG_OBJECT (parsepad,
+ "Saw stream_start with no GstStream. Adding one. Caps %"
+ GST_PTR_FORMAT, caps);
+
+ if (repeat_event) {
+ stream = gst_object_ref (parsepad->active_stream);
+ } else {
+ stream =
+ gst_stream_new (stream_id, NULL, GST_STREAM_TYPE_UNKNOWN,
+ GST_STREAM_FLAG_NONE);
+ gst_object_replace ((GstObject **) & parsepad->active_stream,
+ (GstObject *) stream);
+ }
+ if (caps)
+ gst_parse_pad_update_caps (parsepad, caps);
+
+ event = gst_event_make_writable (event);
+ gst_event_set_stream (event, stream);
+ }
+ gst_object_unref (stream);
+ GST_LOG_OBJECT (parsepad, "Saw stream %s (GstStream %p)",
+ stream->stream_id, stream);
+
+ return event;
+}
+
+static void
+gst_parse_pad_update_stream_collection (GstParsePad * parsepad,
+ GstStreamCollection * collection)
+{
+ GST_LOG_OBJECT (parsepad, "Got new stream collection %p", collection);
+ gst_object_replace ((GstObject **) & parsepad->active_collection,
+ (GstObject *) collection);
+ parsepad->in_a_fallback_collection = FALSE;
+}
+
+static GstPadProbeReturn
+gst_parse_pad_event (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+ GstObject *parent = gst_pad_get_parent (pad);
+ GstParsePad *parsepad = GST_PARSE_PAD (parent);
+ gboolean forwardit = TRUE;
+
+ GST_LOG_OBJECT (pad, "%s parsepad:%p", GST_EVENT_TYPE_NAME (event), parsepad);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_CAPS:{
+ GstCaps *caps = NULL;
+ gst_event_parse_caps (event, &caps);
+ gst_parse_pad_update_caps (parsepad, caps);
+ break;
+ }
+ case GST_EVENT_TAG:{
+ GstTagList *tags;
+ gst_event_parse_tag (event, &tags);
+ gst_parse_pad_update_tags (parsepad, tags);
+ break;
+ }
+ case GST_EVENT_STREAM_START:{
+ GST_PAD_PROBE_INFO_DATA (info) =
+ gst_parse_pad_stream_start_event (parsepad, event);
+ break;
+ }
+ case GST_EVENT_STREAM_COLLECTION:{
+ GstStreamCollection *collection = NULL;
+ gst_event_parse_stream_collection (event, &collection);
+ gst_parse_pad_update_stream_collection (parsepad, collection);
+ break;
+ }
+ case GST_EVENT_EOS:{
+ GST_DEBUG_OBJECT (pad, "we received EOS");
+
+ /* Check if all pads are drained.
+ * * If there is no next group, we will let the EOS go through.
+ * * If there is a next group but the current group isn't completely
+ * drained, we will drop the EOS event.
+ * * If there is a next group to expose and this was the last non-drained
+ * pad for that group, we will remove the ghostpad of the current group
+ * first, which unlinks the peer and so drops the EOS. */
+ forwardit = gst_parse_pad_handle_eos (parsepad);
+ }
+ default:
+ break;
+ }
+ gst_object_unref (parent);
+ if (forwardit)
+ return GST_PAD_PROBE_OK;
+ else
+ return GST_PAD_PROBE_DROP;
+}
+
+static void
+gst_parse_pad_set_blocked (GstParsePad * parsepad, gboolean blocked)
+{
+ GstParseBin *parsebin = parsepad->parsebin;
+ GstPad *opad;
+
+ DYN_LOCK (parsebin);
+
+ GST_DEBUG_OBJECT (parsepad, "blocking pad: %d", blocked);
+
+ opad = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (parsepad));
+ if (!opad)
+ goto out;
+
+ /* do not block if shutting down.
+ * we do not consider/expect it blocked further below, but use other trick */
+ if (!blocked || !parsebin->shutdown) {
+ if (blocked) {
+ if (parsepad->block_id == 0)
+ parsepad->block_id =
+ gst_pad_add_probe (opad,
+ GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM |
+ GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM, source_pad_blocked_cb,
+ gst_object_ref (parsepad), (GDestroyNotify) gst_object_unref);
+ } else {
+ if (parsepad->block_id != 0) {
+ gst_pad_remove_probe (opad, parsepad->block_id);
+ parsepad->block_id = 0;
+ }
+ parsepad->blocked = FALSE;
+ }
+ }
+
+ if (blocked) {
+ if (parsebin->shutdown) {
+ /* deactivate to force flushing state to prevent NOT_LINKED errors */
+ gst_pad_set_active (GST_PAD_CAST (parsepad), FALSE);
+ /* note that deactivating the target pad would have no effect here,
+ * since elements are typically connected first (and pads exposed),
+ * and only then brought to PAUSED state (so pads activated) */
+ } else {
+ gst_object_ref (parsepad);
+ parsebin->blocked_pads =
+ g_list_prepend (parsebin->blocked_pads, parsepad);
+ }
+ } else {
+ GList *l;
+
+ if ((l = g_list_find (parsebin->blocked_pads, parsepad))) {
+ gst_object_unref (parsepad);
+ parsebin->blocked_pads = g_list_delete_link (parsebin->blocked_pads, l);
+ }
+ }
+ gst_object_unref (opad);
+out:
+ DYN_UNLOCK (parsebin);
+}
+
+static void
+gst_parse_pad_activate (GstParsePad * parsepad, GstParseChain * chain)
+{
+ g_return_if_fail (chain != NULL);
+
+ parsepad->chain = chain;
+ gst_pad_set_active (GST_PAD_CAST (parsepad), TRUE);
+ gst_parse_pad_set_blocked (parsepad, TRUE);
+}
+
+static void
+gst_parse_pad_unblock (GstParsePad * parsepad)
+{
+ gst_parse_pad_set_blocked (parsepad, FALSE);
+}
+
+static gboolean
+gst_parse_pad_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+ GstParsePad *parsepad = GST_PARSE_PAD (parent);
+ gboolean ret = FALSE;
+
+ CHAIN_MUTEX_LOCK (parsepad->chain);
+ if (!parsepad->exposed && !parsepad->parsebin->shutdown
+ && !parsepad->chain->deadend && parsepad->chain->elements) {
+ GstParseElement *delem = parsepad->chain->elements->data;
+
+ ret = FALSE;
+ GST_DEBUG_OBJECT (parsepad->parsebin,
+ "calling autoplug-query for %s (element %s): %" GST_PTR_FORMAT,
+ GST_PAD_NAME (parsepad), GST_ELEMENT_NAME (delem->element), query);
+ g_signal_emit (G_OBJECT (parsepad->parsebin),
+ gst_parse_bin_signals[SIGNAL_AUTOPLUG_QUERY], 0, parsepad,
+ delem->element, query, &ret);
+
+ if (ret)
+ GST_DEBUG_OBJECT (parsepad->parsebin,
+ "autoplug-query returned %d: %" GST_PTR_FORMAT, ret, query);
+ else
+ GST_DEBUG_OBJECT (parsepad->parsebin, "autoplug-query returned %d", ret);
+ }
+ CHAIN_MUTEX_UNLOCK (parsepad->chain);
+
+ /* If exposed or nothing handled the query use the default handler */
+ if (!ret)
+ ret = gst_pad_query_default (pad, parent, query);
+
+ return ret;
+}
+
+/*gst_parse_pad_new:
+ *
+ * Creates a new GstParsePad for the given pad.
+ */
+static GstParsePad *
+gst_parse_pad_new (GstParseBin * parsebin, GstParseChain * chain)
+{
+ GstParsePad *parsepad;
+ GstProxyPad *ppad;
+ GstPadTemplate *pad_tmpl;
+
+ GST_DEBUG_OBJECT (parsebin, "making new decodepad");
+ pad_tmpl = gst_static_pad_template_get (&decoder_bin_src_template);
+ parsepad =
+ g_object_new (GST_TYPE_PARSE_PAD, "direction", GST_PAD_SRC,
+ "template", pad_tmpl, NULL);
+ gst_ghost_pad_construct (GST_GHOST_PAD_CAST (parsepad));
+ parsepad->chain = chain;
+ parsepad->parsebin = parsebin;
+ gst_object_unref (pad_tmpl);
+
+ ppad = gst_proxy_pad_get_internal (GST_PROXY_PAD (parsepad));
+ gst_pad_set_query_function (GST_PAD_CAST (ppad), gst_parse_pad_query);
+
+ /* Add downstream event probe */
+ GST_LOG_OBJECT (parsepad, "Adding event probe on internal pad %"
+ GST_PTR_FORMAT, ppad);
+ gst_pad_add_probe (GST_PAD_CAST (ppad),
+ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, gst_parse_pad_event, parsepad, NULL);
+ gst_object_unref (ppad);
+
+ return parsepad;
+}
+
+static void
+gst_pending_pad_free (GstPendingPad * ppad)
+{
+ g_assert (ppad);
+ g_assert (ppad->pad);
+
+ if (ppad->event_probe_id != 0)
+ gst_pad_remove_probe (ppad->pad, ppad->event_probe_id);
+ if (ppad->notify_caps_id)
+ g_signal_handler_disconnect (ppad->pad, ppad->notify_caps_id);
+ gst_object_unref (ppad->pad);
+ g_slice_free (GstPendingPad, ppad);
+}
+
+/*****
+ * Element add/remove
+ *****/
+
+static void
+do_async_start (GstParseBin * parsebin)
+{
+ GstMessage *message;
+
+ parsebin->async_pending = TRUE;
+
+ message = gst_message_new_async_start (GST_OBJECT_CAST (parsebin));
+ parent_class->handle_message (GST_BIN_CAST (parsebin), message);
+}
+
+static void
+do_async_done (GstParseBin * parsebin)
+{
+ GstMessage *message;
+
+ if (parsebin->async_pending) {
+ message =
+ gst_message_new_async_done (GST_OBJECT_CAST (parsebin),
+ GST_CLOCK_TIME_NONE);
+ parent_class->handle_message (GST_BIN_CAST (parsebin), message);
+
+ parsebin->async_pending = FALSE;
+ }
+}
+
+/* call with dyn_lock held */
+static void
+unblock_pads (GstParseBin * parsebin)
+{
+ GList *tmp;
+
+ GST_LOG_OBJECT (parsebin, "unblocking pads");
+
+ for (tmp = parsebin->blocked_pads; tmp; tmp = tmp->next) {
+ GstParsePad *parsepad = (GstParsePad *) tmp->data;
+ GstPad *opad;
+
+ opad = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (parsepad));
+ if (!opad)
+ continue;
+
+ GST_DEBUG_OBJECT (parsepad, "unblocking");
+ if (parsepad->block_id != 0) {
+ gst_pad_remove_probe (opad, parsepad->block_id);
+ parsepad->block_id = 0;
+ }
+ parsepad->blocked = FALSE;
+ /* make flushing, prevent NOT_LINKED */
+ gst_pad_set_active (GST_PAD_CAST (parsepad), FALSE);
+ gst_object_unref (parsepad);
+ gst_object_unref (opad);
+ GST_DEBUG_OBJECT (parsepad, "unblocked");
+ }
+
+ /* clear, no more blocked pads */
+ g_list_free (parsebin->blocked_pads);
+ parsebin->blocked_pads = NULL;
+}
+
+static GstStateChangeReturn
+gst_parse_bin_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+ GstParseBin *parsebin = GST_PARSE_BIN (element);
+ GstParseChain *chain_to_free = NULL;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (parsebin->typefind == NULL)
+ goto missing_typefind;
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ /* Make sure we've cleared all existing chains */
+ EXPOSE_LOCK (parsebin);
+ if (parsebin->parse_chain) {
+ gst_parse_chain_free (parsebin->parse_chain);
+ parsebin->parse_chain = NULL;
+ }
+ EXPOSE_UNLOCK (parsebin);
+ DYN_LOCK (parsebin);
+ GST_LOG_OBJECT (parsebin, "clearing shutdown flag");
+ parsebin->shutdown = FALSE;
+ DYN_UNLOCK (parsebin);
+ parsebin->have_type = FALSE;
+ ret = GST_STATE_CHANGE_ASYNC;
+ do_async_start (parsebin);
+
+
+ /* connect a signal to find out when the typefind element found
+ * a type */
+ parsebin->have_type_id =
+ g_signal_connect (parsebin->typefind, "have-type",
+ G_CALLBACK (type_found), parsebin);
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ if (parsebin->have_type_id)
+ g_signal_handler_disconnect (parsebin->typefind,
+ parsebin->have_type_id);
+ parsebin->have_type_id = 0;
+ DYN_LOCK (parsebin);
+ GST_LOG_OBJECT (parsebin, "setting shutdown flag");
+ parsebin->shutdown = TRUE;
+ unblock_pads (parsebin);
+ DYN_UNLOCK (parsebin);
+ default:
+ break;
+ }
+
+ {
+ GstStateChangeReturn bret;
+
+ bret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (G_UNLIKELY (bret == GST_STATE_CHANGE_FAILURE))
+ goto activate_failed;
+ else if (G_UNLIKELY (bret == GST_STATE_CHANGE_NO_PREROLL)) {
+ do_async_done (parsebin);
+ ret = bret;
+ }
+ }
+ switch (transition) {
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ do_async_done (parsebin);
+ EXPOSE_LOCK (parsebin);
+ if (parsebin->parse_chain) {
+ chain_to_free = parsebin->parse_chain;
+ gst_parse_chain_free_internal (parsebin->parse_chain, TRUE);
+ parsebin->parse_chain = NULL;
+ }
+ EXPOSE_UNLOCK (parsebin);
+ if (chain_to_free)
+ gst_parse_chain_free (chain_to_free);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ default:
+ break;
+ }
+
+ return ret;
+
+/* ERRORS */
+missing_typefind:
+ {
+ gst_element_post_message (element,
+ gst_missing_element_message_new (element, "typefind"));
+ GST_ELEMENT_ERROR (parsebin, CORE, MISSING_PLUGIN, (NULL),
+ ("no typefind!"));
+ return GST_STATE_CHANGE_FAILURE;
+ }
+activate_failed:
+ {
+ GST_DEBUG_OBJECT (element,
+ "element failed to change states -- activation problem?");
+ do_async_done (parsebin);
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
+
+static void
+gst_parse_bin_handle_message (GstBin * bin, GstMessage * msg)
+{
+ GstParseBin *parsebin = GST_PARSE_BIN (bin);
+ gboolean drop = FALSE;
+
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_ERROR:{
+ GST_OBJECT_LOCK (parsebin);
+ drop = (g_list_find (parsebin->filtered, GST_MESSAGE_SRC (msg)) != NULL);
+ if (drop)
+ parsebin->filtered_errors =
+ g_list_prepend (parsebin->filtered_errors, gst_message_ref (msg));
+ GST_OBJECT_UNLOCK (parsebin);
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (drop)
+ gst_message_unref (msg);
+ else
+ GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
+}
+
+gboolean
+gst_parse_bin_plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_parse_bin_debug, "parsebin", 0, "parser bin");
+
+ return gst_element_register (plugin, "parsebin", GST_RANK_NONE,
+ GST_TYPE_PARSE_BIN);
+}
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
#endif /* ENABLE_NLS */
- res = gst_play_bin2_plugin_init (plugin);
+ /* Swap in playbin3 as 'playbin' if USE_PLAYBIN3=1 */
+ {
+ const gchar *env = g_getenv ("USE_PLAYBIN3");
+ if (env && g_str_has_prefix (env, "1"))
+ res = gst_play_bin3_plugin_init (plugin, TRUE);
+ else
+ res = gst_play_bin2_plugin_init (plugin);
+ }
+
+ res &= gst_play_bin3_plugin_init (plugin, FALSE);
res &= gst_play_sink_plugin_init (plugin);
res &= gst_subtitle_overlay_plugin_init (plugin);
res &= gst_stream_synchronizer_plugin_init (plugin);
res &= gst_decode_bin_plugin_init (plugin);
+ res &= gst_decodebin3_plugin_init (plugin);
res &= gst_uri_decode_bin_plugin_init (plugin);
+ res &= gst_uri_decode_bin3_plugin_init (plugin);
+ res &= gst_uri_source_bin_plugin_init (plugin);
+ res &= gst_parse_bin_plugin_init (plugin);
return res;
}
#include <gst/gst.h>
gboolean gst_decode_bin_plugin_init (GstPlugin * plugin);
+gboolean gst_decodebin3_plugin_init (GstPlugin * plugin);
gboolean gst_uri_decode_bin_plugin_init (GstPlugin * plugin);
+gboolean gst_uri_decode_bin3_plugin_init (GstPlugin * plugin);
+gboolean gst_uri_source_bin_plugin_init (GstPlugin * plugin);
+gboolean gst_parse_bin_plugin_init (GstPlugin * plugin);
gboolean gst_play_bin_plugin_init (GstPlugin * plugin);
gboolean gst_play_bin2_plugin_init (GstPlugin * plugin);
+gboolean gst_play_bin3_plugin_init (GstPlugin * plugin, gboolean as_playbin);
#endif /* __GST_PLAY_BACK_H__ */
--- /dev/null
+/* GStreamer
+ * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
+ * Copyright (C) <2011> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2013> Collabora Ltd.
+ * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2015> Jan Schmidt <jan@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-playbin3
+ *
+ * Playbin provides a stand-alone everything-in-one abstraction for an
+ * audio and/or video player.
+ *
+ * Playbin can handle both audio and video files and features
+ * <itemizedlist>
+ * <listitem>
+ * automatic file type recognition and based on that automatic
+ * selection and usage of the right audio/video/subtitle demuxers/decoders
+ * </listitem>
+ * <listitem>
+ * visualisations for audio files
+ * </listitem>
+ * <listitem>
+ * subtitle support for video files. Subtitles can be store in external
+ * files.
+ * </listitem>
+ * <listitem>
+ * stream selection between different video/audio/subtitles streams
+ * </listitem>
+ * <listitem>
+ * meta info (tag) extraction
+ * </listitem>
+ * <listitem>
+ * easy access to the last video sample
+ * </listitem>
+ * <listitem>
+ * buffering when playing streams over a network
+ * </listitem>
+ * <listitem>
+ * volume control with mute option
+ * </listitem>
+ * </itemizedlist>
+ *
+ * <refsect2>
+ * <title>Usage</title>
+ * <para>
+ * A playbin element can be created just like any other element using
+ * gst_element_factory_make(). The file/URI to play should be set via the #GstPlayBin3:uri
+ * property. This must be an absolute URI, relative file paths are not allowed.
+ * Example URIs are file:///home/joe/movie.avi or http://www.joedoe.com/foo.ogg
+ *
+ * Playbin is a #GstPipeline. It will notify the application of everything
+ * that's happening (errors, end of stream, tags found, state changes, etc.)
+ * by posting messages on its #GstBus. The application needs to watch the
+ * bus.
+ *
+ * Playback can be initiated by setting the element to PLAYING state using
+ * gst_element_set_state(). Note that the state change will take place in
+ * the background in a separate thread, when the function returns playback
+ * is probably not happening yet and any errors might not have occured yet.
+ * Applications using playbin should ideally be written to deal with things
+ * completely asynchroneous.
+ *
+ * When playback has finished (an EOS message has been received on the bus)
+ * or an error has occured (an ERROR message has been received on the bus) or
+ * the user wants to play a different track, playbin should be set back to
+ * READY or NULL state, then the #GstPlayBin3:uri property should be set to the
+ * new location and then playbin be set to PLAYING state again.
+ *
+ * Seeking can be done using gst_element_seek_simple() or gst_element_seek()
+ * on the playbin element. Again, the seek will not be executed
+ * instantaneously, but will be done in a background thread. When the seek
+ * call returns the seek will most likely still be in process. An application
+ * may wait for the seek to finish (or fail) using gst_element_get_state() with
+ * -1 as the timeout, but this will block the user interface and is not
+ * recommended at all.
+ *
+ * Applications may query the current position and duration of the stream
+ * via gst_element_query_position() and gst_element_query_duration() and
+ * setting the format passed to GST_FORMAT_TIME. If the query was successful,
+ * the duration or position will have been returned in units of nanoseconds.
+ * </para>
+ * </refsect2>
+ * <refsect2>
+ * <title>Advanced Usage: specifying the audio and video sink</title>
+ * <para>
+ * By default, if no audio sink or video sink has been specified via the
+ * #GstPlayBin3:audio-sink or #GstPlayBin3:video-sink property, playbin will use the autoaudiosink
+ * and autovideosink elements to find the first-best available output method.
+ * This should work in most cases, but is not always desirable. Often either
+ * the user or application might want to specify more explicitly what to use
+ * for audio and video output.
+ *
+ * If the application wants more control over how audio or video should be
+ * output, it may create the audio/video sink elements itself (for example
+ * using gst_element_factory_make()) and provide them to playbin using the
+ * #GstPlayBin3:audio-sink or #GstPlayBin3:video-sink property.
+ *
+ * GNOME-based applications, for example, will usually want to create
+ * gconfaudiosink and gconfvideosink elements and make playbin use those,
+ * so that output happens to whatever the user has configured in the GNOME
+ * Multimedia System Selector configuration dialog.
+ *
+ * The sink elements do not necessarily need to be ready-made sinks. It is
+ * possible to create container elements that look like a sink to playbin,
+ * but in reality contain a number of custom elements linked together. This
+ * can be achieved by creating a #GstBin and putting elements in there and
+ * linking them, and then creating a sink #GstGhostPad for the bin and pointing
+ * it to the sink pad of the first element within the bin. This can be used
+ * for a number of purposes, for example to force output to a particular
+ * format or to modify or observe the data before it is output.
+ *
+ * It is also possible to 'suppress' audio and/or video output by using
+ * 'fakesink' elements (or capture it from there using the fakesink element's
+ * "handoff" signal, which, nota bene, is fired from the streaming thread!).
+ * </para>
+ * </refsect2>
+ * <refsect2>
+ * <title>Retrieving Tags and Other Meta Data</title>
+ * <para>
+ * Most of the common meta data (artist, title, etc.) can be retrieved by
+ * watching for TAG messages on the pipeline's bus (see above).
+ *
+ * Other more specific meta information like width/height/framerate of video
+ * streams or samplerate/number of channels of audio streams can be obtained
+ * from the negotiated caps on the sink pads of the sinks.
+ * </para>
+ * </refsect2>
+ * <refsect2>
+ * <title>Buffering</title>
+ * Playbin handles buffering automatically for the most part, but applications
+ * need to handle parts of the buffering process as well. Whenever playbin is
+ * buffering, it will post BUFFERING messages on the bus with a percentage
+ * value that shows the progress of the buffering process. Applications need
+ * to set playbin to PLAYING or PAUSED state in response to these messages.
+ * They may also want to convey the buffering progress to the user in some
+ * way. Here is how to extract the percentage information from the message:
+ * |[
+ * switch (GST_MESSAGE_TYPE (msg)) {
+ * case GST_MESSAGE_BUFFERING: {
+ * gint percent = 0;
+ * gst_message_parse_buffering (msg, &percent);
+ * g_print ("Buffering (%u percent done)", percent);
+ * break;
+ * }
+ * ...
+ * }
+ * ]|
+ * Note that applications should keep/set the pipeline in the PAUSED state when
+ * a BUFFERING message is received with a buffer percent value < 100 and set
+ * the pipeline back to PLAYING state when a BUFFERING message with a value
+ * of 100 percent is received (if PLAYING is the desired state, that is).
+ * </refsect2>
+ * <refsect2>
+ * <title>Embedding the video window in your application</title>
+ * By default, playbin (or rather the video sinks used) will create their own
+ * window. Applications will usually want to force output to a window of their
+ * own, however. This can be done using the #GstVideoOverlay interface, which most
+ * video sinks implement. See the documentation there for more details.
+ * </refsect2>
+ * <refsect2>
+ * <title>Specifying which CD/DVD device to use</title>
+ * The device to use for CDs/DVDs needs to be set on the source element
+ * playbin creates before it is opened. The most generic way of doing this
+ * is to connect to playbin's "source-setup" (or "notify::source") signal,
+ * which will be emitted by playbin when it has created the source element
+ * for a particular URI. In the signal callback you can check if the source
+ * element has a "device" property and set it appropriately. In some cases
+ * the device can also be set as part of the URI, but it depends on the
+ * elements involved if this will work or not. For example, for DVD menu
+ * playback, the following syntax might work (if the resindvd plugin is used):
+ * dvd://[/path/to/device]
+ * </refsect2>
+ * <refsect2>
+ * <title>Handling redirects</title>
+ * <para>
+ * Some elements may post 'redirect' messages on the bus to tell the
+ * application to open another location. These are element messages containing
+ * a structure named 'redirect' along with a 'new-location' field of string
+ * type. The new location may be a relative or an absolute URI. Examples
+ * for such redirects can be found in many quicktime movie trailers.
+ * </para>
+ * </refsect2>
+ * <refsect2>
+ * <title>Examples</title>
+ * |[
+ * gst-launch-1.0 -v playbin uri=file:///path/to/somefile.mp4
+ * ]| This will play back the given AVI video file, given that the video and
+ * audio decoders required to decode the content are installed. Since no
+ * special audio sink or video sink is supplied (via playbin's audio-sink or
+ * video-sink properties) playbin will try to find a suitable audio and
+ * video sink automatically using the autoaudiosink and autovideosink elements.
+ * |[
+ * gst-launch-1.0 -v playbin uri=cdda://4
+ * ]| This will play back track 4 on an audio CD in your disc drive (assuming
+ * the drive is detected automatically by the plugin).
+ * |[
+ * gst-launch-1.0 -v playbin uri=dvd://
+ * ]| This will play back the DVD in your disc drive (assuming
+ * the drive is detected automatically by the plugin).
+ * </refsect2>
+ */
+
+/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
+ * with newer GLib versions (>= 2.31.0) */
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <gst/gst.h>
+
+#include <gst/gst-i18n-plugin.h>
+#include <gst/pbutils/pbutils.h>
+#include <gst/audio/streamvolume.h>
+#include <gst/video/video-info.h>
+#include <gst/video/video-multiview.h>
+#include <gst/video/videooverlay.h>
+#include <gst/video/navigation.h>
+#include <gst/video/colorbalance.h>
+#include "gstplay-enum.h"
+#include "gstplayback.h"
+#include "gstplaysink.h"
+#include "gstsubtitleoverlay.h"
+#include "gstplaybackutils.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_play_bin3_debug);
+#define GST_CAT_DEFAULT gst_play_bin3_debug
+
+#define GST_TYPE_PLAY_BIN (gst_play_bin3_get_type())
+#define GST_PLAY_BIN3(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_BIN,GstPlayBin3))
+#define GST_PLAY_BIN3_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_BIN,GstPlayBin3Class))
+#define GST_IS_PLAY_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_BIN))
+#define GST_IS_PLAY_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_BIN))
+
+#define ULONG_TO_POINTER(number) ((gpointer) (guintptr) (number))
+#define POINTER_TO_ULONG(number) ((guintptr) (number))
+
+#define VOLUME_MAX_DOUBLE 10.0
+
+typedef struct _GstPlayBin3 GstPlayBin3;
+typedef struct _GstPlayBin3Class GstPlayBin3Class;
+typedef struct _GstSourceGroup GstSourceGroup;
+typedef struct _GstSourceCombine GstSourceCombine;
+
+typedef GstCaps *(*SourceCombineGetMediaCapsFunc) (void);
+
+/* has the info for a combiner and provides the link to the sink */
+struct _GstSourceCombine
+{
+ const gchar *media_type; /* the media type for the combiner */
+ SourceCombineGetMediaCapsFunc get_media_caps; /* more complex caps for the combiner */
+ GstPlaySinkType type; /* the sink pad type of the combiner */
+
+ GstElement *combiner; /* the combiner */
+ GPtrArray *channels;
+ GstPad *srcpad; /* the source pad of the combiner */
+ GstPad *sinkpad; /* the sinkpad of the sink when the combiner
+ * is linked
+ */
+ gulong block_id;
+
+ GPtrArray *streams; /* Sorted array of GstStream for the given type */
+ gint current_stream; /* Currently selected GstStream */
+
+ gboolean has_active_pad; /* stream combiner has the "active-pad" property */
+
+ gboolean has_always_ok; /* stream combiner's sink pads have the "always-ok" property */
+};
+
+#define GST_SOURCE_GROUP_GET_LOCK(group) (&((GstSourceGroup*)(group))->lock)
+#define GST_SOURCE_GROUP_LOCK(group) (g_mutex_lock (GST_SOURCE_GROUP_GET_LOCK(group)))
+#define GST_SOURCE_GROUP_UNLOCK(group) (g_mutex_unlock (GST_SOURCE_GROUP_GET_LOCK(group)))
+
+enum
+{
+ PLAYBIN_STREAM_AUDIO = 0,
+ PLAYBIN_STREAM_VIDEO,
+ PLAYBIN_STREAM_TEXT,
+ PLAYBIN_STREAM_LAST
+};
+
+/* names matching the enum above */
+static const gchar *stream_type_names[] = {
+ "audio", "video", "text"
+};
+
+static void avelements_free (gpointer data);
+static GSequence *avelements_create (GstPlayBin3 * playbin,
+ gboolean isaudioelement);
+
+/* The GstAudioVideoElement structure holding the audio/video decoder
+ * and the audio/video sink factories together with field indicating
+ * the number of common caps features */
+typedef struct
+{
+ GstElementFactory *dec; /* audio:video decoder */
+ GstElementFactory *sink; /* audio:video sink */
+ guint n_comm_cf; /* number of common caps features */
+} GstAVElement;
+
+/* a structure to hold the objects for decoding a uri and the subtitle uri
+ */
+struct _GstSourceGroup
+{
+ GstPlayBin3 *playbin;
+
+ GMutex lock;
+
+ gboolean valid; /* the group has valid info to start playback */
+ gboolean active; /* the group is active */
+
+ /* properties */
+ gchar *uri;
+ gchar *suburi;
+ GValueArray *streaminfo;
+ GstElement *source;
+
+
+ /* urisourcebins for uri and subtitle uri */
+ /* FIXME: Just make this an array of uris */
+ GstElement *urisourcebin;
+ GstElement *suburisourcebin;
+
+ /* Active sinks for each media type. These are initialized with
+ * the configured or currently used sink, otherwise
+ * left as NULL and playbin tries to automatically
+ * select a good sink */
+ GstElement *audio_sink;
+ GstElement *video_sink;
+ GstElement *text_sink;
+
+ gint pending;
+ gboolean sub_pending;
+
+ /* primary uri signals */
+ gulong urisrc_pad_added_id;
+ gulong urisrc_pad_removed_id;
+ gulong notify_source_id;
+ gulong autoplug_factories_id;
+ gulong autoplug_select_id;
+ gulong autoplug_continue_id;
+ gulong autoplug_query_id;
+
+ /* subtitle uri signals */
+ gulong sub_pad_added_id;
+ gulong sub_pad_removed_id;
+ gulong sub_autoplug_continue_id;
+ gulong sub_autoplug_query_id;
+
+ gulong block_id;
+
+ GMutex stream_changed_pending_lock;
+ gboolean stream_changed_pending;
+
+ /* buffering message stored for after switching */
+ GstMessage *pending_buffering_msg;
+};
+
+#define GST_PLAY_BIN3_GET_LOCK(bin) (&((GstPlayBin3*)(bin))->lock)
+#define GST_PLAY_BIN3_LOCK(bin) (g_rec_mutex_lock (GST_PLAY_BIN3_GET_LOCK(bin)))
+#define GST_PLAY_BIN3_UNLOCK(bin) (g_rec_mutex_unlock (GST_PLAY_BIN3_GET_LOCK(bin)))
+
+/* lock to protect dynamic callbacks, like no-more-pads */
+#define GST_PLAY_BIN3_DYN_LOCK(bin) g_mutex_lock (&(bin)->dyn_lock)
+#define GST_PLAY_BIN3_DYN_UNLOCK(bin) g_mutex_unlock (&(bin)->dyn_lock)
+
+/* lock for shutdown */
+#define GST_PLAY_BIN3_SHUTDOWN_LOCK(bin,label) \
+G_STMT_START { \
+ if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown))) \
+ goto label; \
+ GST_PLAY_BIN3_DYN_LOCK (bin); \
+ if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown))) { \
+ GST_PLAY_BIN3_DYN_UNLOCK (bin); \
+ goto label; \
+ } \
+} G_STMT_END
+
+/* unlock for shutdown */
+#define GST_PLAY_BIN3_SHUTDOWN_UNLOCK(bin) \
+ GST_PLAY_BIN3_DYN_UNLOCK (bin); \
+
+/**
+ * GstPlayBin3:
+ *
+ * playbin element structure
+ */
+struct _GstPlayBin3
+{
+ GstPipeline parent;
+
+ GRecMutex lock; /* to protect group switching */
+
+ /* the input groups, we use a double buffer to switch between current and next */
+ GstSourceGroup groups[2]; /* array with group info */
+ GstSourceGroup *curr_group; /* pointer to the currently playing group */
+ GstSourceGroup *next_group; /* pointer to the next group */
+
+ /* combiners for different streams */
+ GPtrArray *channels[PLAYBIN_STREAM_LAST]; /* links to combiner pads */
+ GstSourceCombine combiner[PLAYBIN_STREAM_LAST];
+
+ /* A global decodebin3 that's used to actually do decoding */
+ gboolean decodebin_active;
+ GstElement *decodebin;
+ /* Bit-wise set of stream types we have
+ * requested from decodebin vs stream types
+ * decodebin has provided */
+ GstStreamType selected_stream_types;
+ GstStreamType active_stream_types;
+
+ /* Decodebin signals */
+ gulong db_pad_added_id;
+ gulong db_pad_removed_id;
+ gulong db_no_more_pads_id;
+ gulong db_drained_id;
+ gulong db_select_stream_id;
+
+ /* properties */
+ guint64 connection_speed; /* connection speed in bits/sec (0 = unknown) */
+ gint current_video; /* the currently selected stream */
+ gint current_audio; /* the currently selected stream */
+ gint current_text; /* the currently selected stream */
+
+ gboolean do_stream_selections; /* Set to TRUE when any of current-{video|audio|text} are set to
+ say playbin should do backwards-compatibility behaviours */
+
+ guint64 buffer_duration; /* When buffering, the max buffer duration (ns) */
+ guint buffer_size; /* When buffering, the max buffer size (bytes) */
+ gboolean force_aspect_ratio;
+
+ /* Multiview/stereoscopic overrides */
+ GstVideoMultiviewFramePacking multiview_mode;
+ GstVideoMultiviewFlags multiview_flags;
+
+ /* our play sink */
+ GstPlaySink *playsink;
+
+ /* the last activated source */
+ GstElement *source;
+
+ /* lock protecting dynamic adding/removing */
+ GMutex dyn_lock;
+ /* if we are shutting down or not */
+ gint shutdown;
+ gboolean async_pending; /* async-start has been emitted */
+
+ GMutex elements_lock;
+ guint32 elements_cookie;
+ GList *elements; /* factories we can use for selecting elements */
+
+ gboolean have_selector; /* set to FALSE when we fail to create an
+ * input-selector, so that we only post a
+ * warning once */
+
+ gboolean video_pending_flush_finish; /* whether we are pending to send a custom
+ * custom-video-flush-finish event
+ * on pad activation */
+ gboolean audio_pending_flush_finish; /* whether we are pending to send a custom
+ * custom-audio-flush-finish event
+ * on pad activation */
+ gboolean text_pending_flush_finish; /* whether we are pending to send a custom
+ * custom-subtitle-flush-finish event
+ * on pad activation */
+
+ GstElement *audio_sink; /* configured audio sink, or NULL */
+ GstElement *video_sink; /* configured video sink, or NULL */
+ GstElement *text_sink; /* configured text sink, or NULL */
+
+ GstElement *audio_stream_combiner; /* configured audio stream combiner, or NULL */
+ GstElement *video_stream_combiner; /* configured video stream combiner, or NULL */
+ GstElement *text_stream_combiner; /* configured text stream combiner, or NULL */
+
+ GSequence *aelements; /* a list of GstAVElements for audio stream */
+ GSequence *velements; /* a list of GstAVElements for video stream */
+
+ struct
+ {
+ gboolean valid;
+ GstFormat format;
+ gint64 duration;
+ } duration[5]; /* cached durations */
+
+ guint64 ring_buffer_max_size; /* 0 means disabled */
+
+ GList *contexts;
+
+ /* Active stream collection */
+ GstStreamCollection *collection;
+ guint collection_notify_id;
+};
+
+struct _GstPlayBin3Class
+{
+ GstPipelineClass parent_class;
+
+ /* notify app that the current uri finished decoding and it is possible to
+ * queue a new one for gapless playback */
+ void (*about_to_finish) (GstPlayBin3 * playbin);
+
+ /* notify app that number of audio/video/text streams changed */
+ void (*video_changed) (GstPlayBin3 * playbin);
+ void (*audio_changed) (GstPlayBin3 * playbin);
+ void (*text_changed) (GstPlayBin3 * playbin);
+
+ /* notify app that the tags of audio/video/text streams changed */
+ void (*video_tags_changed) (GstPlayBin3 * playbin, gint stream);
+ void (*audio_tags_changed) (GstPlayBin3 * playbin, gint stream);
+ void (*text_tags_changed) (GstPlayBin3 * playbin, gint stream);
+
+ /* get audio/video/text tags for a stream */
+ GstTagList *(*get_video_tags) (GstPlayBin3 * playbin, gint stream);
+ GstTagList *(*get_audio_tags) (GstPlayBin3 * playbin, gint stream);
+ GstTagList *(*get_text_tags) (GstPlayBin3 * playbin, gint stream);
+
+ /* get the last video sample and convert it to the given caps */
+ GstSample *(*convert_sample) (GstPlayBin3 * playbin, GstCaps * caps);
+
+ /* get audio/video/text pad for a stream */
+ GstPad *(*get_video_pad) (GstPlayBin3 * playbin, gint stream);
+ GstPad *(*get_audio_pad) (GstPlayBin3 * playbin, gint stream);
+ GstPad *(*get_text_pad) (GstPlayBin3 * playbin, gint stream);
+};
+
+/* props */
+#define DEFAULT_URI NULL
+#define DEFAULT_SUBURI NULL
+#define DEFAULT_SOURCE NULL
+#define DEFAULT_FLAGS GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_TEXT | \
+ GST_PLAY_FLAG_SOFT_VOLUME | GST_PLAY_FLAG_DEINTERLACE | \
+ GST_PLAY_FLAG_SOFT_COLORBALANCE
+#define DEFAULT_N_VIDEO 0
+#define DEFAULT_CURRENT_VIDEO -1
+#define DEFAULT_N_AUDIO 0
+#define DEFAULT_CURRENT_AUDIO -1
+#define DEFAULT_N_TEXT 0
+#define DEFAULT_CURRENT_TEXT -1
+#define DEFAULT_AUTO_SELECT_STREAMS TRUE
+#define DEFAULT_SUBTITLE_ENCODING NULL
+#define DEFAULT_AUDIO_SINK NULL
+#define DEFAULT_VIDEO_SINK NULL
+#define DEFAULT_VIS_PLUGIN NULL
+#define DEFAULT_TEXT_SINK NULL
+#define DEFAULT_VOLUME 1.0
+#define DEFAULT_MUTE FALSE
+#define DEFAULT_FRAME NULL
+#define DEFAULT_FONT_DESC NULL
+#define DEFAULT_CONNECTION_SPEED 0
+#define DEFAULT_BUFFER_DURATION -1
+#define DEFAULT_BUFFER_SIZE -1
+#define DEFAULT_RING_BUFFER_MAX_SIZE 0
+
+enum
+{
+ PROP_0,
+ PROP_URI,
+ PROP_CURRENT_URI,
+ PROP_SUBURI,
+ PROP_CURRENT_SUBURI,
+ PROP_SOURCE,
+ PROP_FLAGS,
+ PROP_N_VIDEO,
+ PROP_CURRENT_VIDEO,
+ PROP_N_AUDIO,
+ PROP_CURRENT_AUDIO,
+ PROP_N_TEXT,
+ PROP_CURRENT_TEXT,
+ PROP_AUTO_SELECT_STREAMS,
+ PROP_SUBTITLE_ENCODING,
+ PROP_AUDIO_SINK,
+ PROP_VIDEO_SINK,
+ PROP_VIS_PLUGIN,
+ PROP_TEXT_SINK,
+ PROP_VIDEO_STREAM_COMBINER,
+ PROP_AUDIO_STREAM_COMBINER,
+ PROP_TEXT_STREAM_COMBINER,
+ PROP_VOLUME,
+ PROP_MUTE,
+ PROP_SAMPLE,
+ PROP_FONT_DESC,
+ PROP_CONNECTION_SPEED,
+ PROP_BUFFER_SIZE,
+ PROP_BUFFER_DURATION,
+ PROP_AV_OFFSET,
+ PROP_RING_BUFFER_MAX_SIZE,
+ PROP_FORCE_ASPECT_RATIO,
+ PROP_AUDIO_FILTER,
+ PROP_VIDEO_FILTER,
+ PROP_MULTIVIEW_MODE,
+ PROP_MULTIVIEW_FLAGS
+};
+
+/* signals */
+enum
+{
+ SIGNAL_ABOUT_TO_FINISH,
+ SIGNAL_CONVERT_SAMPLE,
+ SIGNAL_VIDEO_CHANGED,
+ SIGNAL_AUDIO_CHANGED,
+ SIGNAL_TEXT_CHANGED,
+ SIGNAL_VIDEO_TAGS_CHANGED,
+ SIGNAL_AUDIO_TAGS_CHANGED,
+ SIGNAL_TEXT_TAGS_CHANGED,
+ SIGNAL_GET_VIDEO_TAGS,
+ SIGNAL_GET_AUDIO_TAGS,
+ SIGNAL_GET_TEXT_TAGS,
+ SIGNAL_GET_VIDEO_PAD,
+ SIGNAL_GET_AUDIO_PAD,
+ SIGNAL_GET_TEXT_PAD,
+ SIGNAL_SOURCE_SETUP,
+ LAST_SIGNAL
+};
+
+static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS ("audio/x-raw(ANY)");
+static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw(ANY)");
+
+static void gst_play_bin3_class_init (GstPlayBin3Class * klass);
+static void gst_play_bin3_init (GstPlayBin3 * playbin);
+static void gst_play_bin3_finalize (GObject * object);
+
+static void gst_play_bin3_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * spec);
+static void gst_play_bin3_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * spec);
+
+static GstStateChangeReturn gst_play_bin3_change_state (GstElement * element,
+ GstStateChange transition);
+
+static void gst_play_bin3_handle_message (GstBin * bin, GstMessage * message);
+static gboolean gst_play_bin3_query (GstElement * element, GstQuery * query);
+static void gst_play_bin3_set_context (GstElement * element,
+ GstContext * context);
+static gboolean gst_play_bin3_send_event (GstElement * element,
+ GstEvent * event);
+
+static GstTagList *gst_play_bin3_get_video_tags (GstPlayBin3 * playbin,
+ gint stream);
+static GstTagList *gst_play_bin3_get_audio_tags (GstPlayBin3 * playbin,
+ gint stream);
+static GstTagList *gst_play_bin3_get_text_tags (GstPlayBin3 * playbin,
+ gint stream);
+
+static GstSample *gst_play_bin3_convert_sample (GstPlayBin3 * playbin,
+ GstCaps * caps);
+
+static GstPad *gst_play_bin3_get_video_pad (GstPlayBin3 * playbin, gint stream);
+static GstPad *gst_play_bin3_get_audio_pad (GstPlayBin3 * playbin, gint stream);
+static GstPad *gst_play_bin3_get_text_pad (GstPlayBin3 * playbin, gint stream);
+
+static GstStateChangeReturn setup_next_source (GstPlayBin3 * playbin,
+ GstState target);
+
+static void no_more_pads_cb (GstElement * decodebin, GstPlayBin3 * playbin);
+static void pad_removed_cb (GstElement * decodebin, GstPad * pad,
+ GstPlayBin3 * playbin);
+
+static gint select_stream_cb (GstElement * decodebin,
+ GstStreamCollection * collection, GstStream * stream,
+ GstPlayBin3 * playbin);
+
+static void do_stream_selection (GstPlayBin3 * playbin);
+static void notify_tags_cb (GstStreamCollection * collection,
+ GstStream * stream, GParamSpec * pspec, GstPlayBin3 * playbin);
+static void notify_tags_for_stream (GstPlayBin3 * playbin,
+ GstStreamCollection * collection, GstStream * stream);
+
+static GstElementClass *parent_class;
+
+static guint gst_play_bin3_signals[LAST_SIGNAL] = { 0 };
+
+#define REMOVE_SIGNAL(obj,id) \
+if (id) { \
+ g_signal_handler_disconnect (obj, id); \
+ id = 0; \
+}
+
+static void gst_play_bin3_overlay_init (gpointer g_iface,
+ gpointer g_iface_data);
+static void gst_play_bin3_navigation_init (gpointer g_iface,
+ gpointer g_iface_data);
+static void gst_play_bin3_colorbalance_init (gpointer g_iface,
+ gpointer g_iface_data);
+
+static GType
+gst_play_bin3_get_type (void)
+{
+ static GType gst_play_bin3_type = 0;
+
+ if (!gst_play_bin3_type) {
+ static const GTypeInfo gst_play_bin3_info = {
+ sizeof (GstPlayBin3Class),
+ NULL,
+ NULL,
+ (GClassInitFunc) gst_play_bin3_class_init,
+ NULL,
+ NULL,
+ sizeof (GstPlayBin3),
+ 0,
+ (GInstanceInitFunc) gst_play_bin3_init,
+ NULL
+ };
+ static const GInterfaceInfo svol_info = {
+ NULL, NULL, NULL
+ };
+ static const GInterfaceInfo ov_info = {
+ gst_play_bin3_overlay_init,
+ NULL, NULL
+ };
+ static const GInterfaceInfo nav_info = {
+ gst_play_bin3_navigation_init,
+ NULL, NULL
+ };
+ static const GInterfaceInfo col_info = {
+ gst_play_bin3_colorbalance_init,
+ NULL, NULL
+ };
+
+ gst_play_bin3_type = g_type_register_static (GST_TYPE_PIPELINE,
+ "GstPlayBin3", &gst_play_bin3_info, 0);
+
+ g_type_add_interface_static (gst_play_bin3_type, GST_TYPE_STREAM_VOLUME,
+ &svol_info);
+ g_type_add_interface_static (gst_play_bin3_type, GST_TYPE_VIDEO_OVERLAY,
+ &ov_info);
+ g_type_add_interface_static (gst_play_bin3_type, GST_TYPE_NAVIGATION,
+ &nav_info);
+ g_type_add_interface_static (gst_play_bin3_type, GST_TYPE_COLOR_BALANCE,
+ &col_info);
+ }
+
+ return gst_play_bin3_type;
+}
+
+static void
+gst_play_bin3_class_init (GstPlayBin3Class * klass)
+{
+ GObjectClass *gobject_klass;
+ GstElementClass *gstelement_klass;
+ GstBinClass *gstbin_klass;
+
+ gobject_klass = (GObjectClass *) klass;
+ gstelement_klass = (GstElementClass *) klass;
+ gstbin_klass = (GstBinClass *) klass;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ gobject_klass->set_property = gst_play_bin3_set_property;
+ gobject_klass->get_property = gst_play_bin3_get_property;
+
+ gobject_klass->finalize = gst_play_bin3_finalize;
+
+ /**
+ * GstPlayBin3:uri
+ *
+ * Set the next URI that playbin will play. This property can be set from the
+ * about-to-finish signal to queue the next media file.
+ */
+ g_object_class_install_property (gobject_klass, PROP_URI,
+ g_param_spec_string ("uri", "URI", "URI of the media to play",
+ NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3:current-uri
+ *
+ * The currently playing uri.
+ */
+ g_object_class_install_property (gobject_klass, PROP_CURRENT_URI,
+ g_param_spec_string ("current-uri", "Current URI",
+ "The currently playing URI", NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3:suburi
+ *
+ * Set the next subtitle URI that playbin will play. This property can be
+ * set from the about-to-finish signal to queue the next subtitle media file.
+ */
+ g_object_class_install_property (gobject_klass, PROP_SUBURI,
+ g_param_spec_string ("suburi", ".sub-URI", "Optional URI of a subtitle",
+ NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3:current-suburi
+ *
+ * The currently playing subtitle uri.
+ */
+ g_object_class_install_property (gobject_klass, PROP_CURRENT_SUBURI,
+ g_param_spec_string ("current-suburi", "Current .sub-URI",
+ "The currently playing URI of a subtitle",
+ NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_SOURCE,
+ g_param_spec_object ("source", "Source", "Source element",
+ GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3:flags
+ *
+ * Control the behaviour of playbin.
+ */
+ g_object_class_install_property (gobject_klass, PROP_FLAGS,
+ g_param_spec_flags ("flags", "Flags", "Flags to control behaviour",
+ GST_TYPE_PLAY_FLAGS, DEFAULT_FLAGS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3:n-video
+ *
+ * Get the total number of available video streams.
+ */
+ g_object_class_install_property (gobject_klass, PROP_N_VIDEO,
+ g_param_spec_int ("n-video", "Number Video",
+ "Total number of video streams", 0, G_MAXINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstPlayBin3:current-video
+ *
+ * Get or set the currently playing video stream. By default the first video
+ * stream with data is played.
+ */
+ g_object_class_install_property (gobject_klass, PROP_CURRENT_VIDEO,
+ g_param_spec_int ("current-video", "Current Video",
+ "Currently playing video stream (-1 = auto)",
+ -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstPlayBin3:n-audio
+ *
+ * Get the total number of available audio streams.
+ */
+ g_object_class_install_property (gobject_klass, PROP_N_AUDIO,
+ g_param_spec_int ("n-audio", "Number Audio",
+ "Total number of audio streams", 0, G_MAXINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstPlayBin3:current-audio
+ *
+ * Get or set the currently playing audio stream. By default the first audio
+ * stream with data is played.
+ */
+ g_object_class_install_property (gobject_klass, PROP_CURRENT_AUDIO,
+ g_param_spec_int ("current-audio", "Current audio",
+ "Currently playing audio stream (-1 = auto)",
+ -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstPlayBin3:n-text
+ *
+ * Get the total number of available subtitle streams.
+ */
+ g_object_class_install_property (gobject_klass, PROP_N_TEXT,
+ g_param_spec_int ("n-text", "Number Text",
+ "Total number of text streams", 0, G_MAXINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstPlayBin3:current-text:
+ *
+ * Get or set the currently playing subtitle stream. By default the first
+ * subtitle stream with data is played.
+ */
+ g_object_class_install_property (gobject_klass, PROP_CURRENT_TEXT,
+ g_param_spec_int ("current-text", "Current Text",
+ "Currently playing text stream (-1 = auto)",
+ -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3::auto-select-streams:
+ *
+ * If TRUE the playbin will respond to stream-collection messages
+ * by sending a SELECT_STREAMS event to decodebin. Set to FALSE
+ * if the application will manage stream selection. This property
+ * will automatically be set to FALSE if playbin receives a select-streams
+ * event from the application, but setting it explicitly avoids any
+ * races where playbin mind send a select-streams event before the
+ * application.
+ */
+ g_object_class_install_property (gobject_klass, PROP_AUTO_SELECT_STREAMS,
+ g_param_spec_boolean ("auto-select-streams", "Automatic Select-Streams",
+ "Whether playbin should respond to stream-collection messags with select-streams events",
+ DEFAULT_AUTO_SELECT_STREAMS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_SUBTITLE_ENCODING,
+ g_param_spec_string ("subtitle-encoding", "subtitle encoding",
+ "Encoding to assume if input subtitles are not in UTF-8 encoding. "
+ "If not set, the GST_SUBTITLE_ENCODING environment variable will "
+ "be checked for an encoding to use. If that is not set either, "
+ "ISO-8859-15 will be assumed.", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_VIDEO_FILTER,
+ g_param_spec_object ("video-filter", "Video filter",
+ "the video filter(s) to apply, if possible",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_klass, PROP_AUDIO_FILTER,
+ g_param_spec_object ("audio-filter", "Audio filter",
+ "the audio filter(s) to apply, if possible",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_klass, PROP_VIDEO_SINK,
+ g_param_spec_object ("video-sink", "Video Sink",
+ "the video output element to use (NULL = default sink)",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_klass, PROP_AUDIO_SINK,
+ g_param_spec_object ("audio-sink", "Audio Sink",
+ "the audio output element to use (NULL = default sink)",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_klass, PROP_VIS_PLUGIN,
+ g_param_spec_object ("vis-plugin", "Vis plugin",
+ "the visualization element to use (NULL = default)",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_klass, PROP_TEXT_SINK,
+ g_param_spec_object ("text-sink", "Text plugin",
+ "the text output element to use (NULL = default subtitleoverlay)",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstPlayBin3:video-stream-combiner
+ *
+ * Get or set the current video stream combiner. By default, an input-selector
+ * is created and deleted as-needed.
+ */
+ g_object_class_install_property (gobject_klass, PROP_VIDEO_STREAM_COMBINER,
+ g_param_spec_object ("video-stream-combiner", "Video stream combiner",
+ "Current video stream combiner (NULL = input-selector)",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstPlayBin3:audio-stream-combiner
+ *
+ * Get or set the current audio stream combiner. By default, an input-selector
+ * is created and deleted as-needed.
+ */
+ g_object_class_install_property (gobject_klass, PROP_AUDIO_STREAM_COMBINER,
+ g_param_spec_object ("audio-stream-combiner", "Audio stream combiner",
+ "Current audio stream combiner (NULL = input-selector)",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstPlayBin3:text-stream-combiner
+ *
+ * Get or set the current text stream combiner. By default, an input-selector
+ * is created and deleted as-needed.
+ */
+ g_object_class_install_property (gobject_klass, PROP_TEXT_STREAM_COMBINER,
+ g_param_spec_object ("text-stream-combiner", "Text stream combiner",
+ "Current text stream combiner (NULL = input-selector)",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3:volume:
+ *
+ * Get or set the current audio stream volume. 1.0 means 100%,
+ * 0.0 means mute. This uses a linear volume scale.
+ *
+ */
+ g_object_class_install_property (gobject_klass, PROP_VOLUME,
+ g_param_spec_double ("volume", "Volume", "The audio volume, 1.0=100%",
+ 0.0, VOLUME_MAX_DOUBLE, 1.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_klass, PROP_MUTE,
+ g_param_spec_boolean ("mute", "Mute",
+ "Mute the audio channel without changing the volume", FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3:sample:
+ * @playbin: a #GstPlayBin3
+ *
+ * Get the currently rendered or prerolled sample in the video sink.
+ * The #GstCaps in the sample will describe the format of the buffer.
+ */
+ g_object_class_install_property (gobject_klass, PROP_SAMPLE,
+ g_param_spec_boxed ("sample", "Sample",
+ "The last sample (NULL = no video available)",
+ GST_TYPE_SAMPLE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_FONT_DESC,
+ g_param_spec_string ("subtitle-font-desc",
+ "Subtitle font description",
+ "Pango font description of font "
+ "to be used for subtitle rendering", NULL,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_CONNECTION_SPEED,
+ g_param_spec_uint64 ("connection-speed", "Connection Speed",
+ "Network connection speed in kbps (0 = unknown)",
+ 0, G_MAXUINT64 / 1000, DEFAULT_CONNECTION_SPEED,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_BUFFER_SIZE,
+ g_param_spec_int ("buffer-size", "Buffer size (bytes)",
+ "Buffer size when buffering network streams",
+ -1, G_MAXINT, DEFAULT_BUFFER_SIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_klass, PROP_BUFFER_DURATION,
+ g_param_spec_int64 ("buffer-duration", "Buffer duration (ns)",
+ "Buffer duration when buffering network streams",
+ -1, G_MAXINT64, DEFAULT_BUFFER_DURATION,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstPlayBin3:av-offset:
+ *
+ * Control the synchronisation offset between the audio and video streams.
+ * Positive values make the audio ahead of the video and negative values make
+ * the audio go behind the video.
+ */
+ g_object_class_install_property (gobject_klass, PROP_AV_OFFSET,
+ g_param_spec_int64 ("av-offset", "AV Offset",
+ "The synchronisation offset between audio and video in nanoseconds",
+ G_MININT64, G_MAXINT64, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3:ring-buffer-max-size
+ *
+ * The maximum size of the ring buffer in bytes. If set to 0, the ring
+ * buffer is disabled. Default 0.
+ */
+ g_object_class_install_property (gobject_klass, PROP_RING_BUFFER_MAX_SIZE,
+ g_param_spec_uint64 ("ring-buffer-max-size",
+ "Max. ring buffer size (bytes)",
+ "Max. amount of data in the ring buffer (bytes, 0 = ring buffer disabled)",
+ 0, G_MAXUINT, DEFAULT_RING_BUFFER_MAX_SIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3::force-aspect-ratio:
+ *
+ * Requests the video sink to enforce the video display aspect ratio.
+ */
+ g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO,
+ g_param_spec_boolean ("force-aspect-ratio", "Force Aspect Ratio",
+ "When enabled, scaling will respect original aspect ratio", TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3::video-multiview-mode:
+ *
+ * Set the stereoscopic mode for video streams that don't contain
+ * any information in the stream, so they can be correctly played
+ * as 3D streams. If a video already has multiview information
+ * encoded, this property can override other modes in the set,
+ * but cannot be used to re-interpret MVC or mixed-mono streams.
+ *
+ * See Also: The #GstPlayBin3::video-multiview-flags property
+ *
+ */
+ g_object_class_install_property (gobject_klass, PROP_MULTIVIEW_MODE,
+ g_param_spec_enum ("video-multiview-mode",
+ "Multiview Mode Override",
+ "Re-interpret a video stream as one of several frame-packed stereoscopic modes.",
+ GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING,
+ GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3::video-multiview-flags:
+ *
+ * When overriding the multiview mode of an input stream,
+ * these flags modify details of the view layout.
+ *
+ * See Also: The #GstPlayBin3::video-multiview-mode property
+ */
+ g_object_class_install_property (gobject_klass, PROP_MULTIVIEW_FLAGS,
+ g_param_spec_flags ("video-multiview-flags",
+ "Multiview Flags Override",
+ "Override details of the multiview frame layout",
+ GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstPlayBin3::about-to-finish
+ * @playbin: a #GstPlayBin3
+ *
+ * This signal is emitted when the current uri is about to finish. You can
+ * set the uri and suburi to make sure that playback continues.
+ *
+ * This signal is emitted from the context of a GStreamer streaming thread.
+ */
+ gst_play_bin3_signals[SIGNAL_ABOUT_TO_FINISH] =
+ g_signal_new ("about-to-finish", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstPlayBin3Class, about_to_finish), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ /**
+ * GstPlayBin3::video-changed
+ * @playbin: a #GstPlayBin3
+ *
+ * This signal is emitted whenever the number or order of the video
+ * streams has changed. The application will most likely want to select
+ * a new video stream.
+ *
+ * This signal is usually emitted from the context of a GStreamer streaming
+ * thread. You can use gst_message_new_application() and
+ * gst_element_post_message() to notify your application's main thread.
+ */
+ /* FIXME 0.11: turn video-changed signal into message? */
+ gst_play_bin3_signals[SIGNAL_VIDEO_CHANGED] =
+ g_signal_new ("video-changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstPlayBin3Class, video_changed), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+ /**
+ * GstPlayBin3::audio-changed
+ * @playbin: a #GstPlayBin3
+ *
+ * This signal is emitted whenever the number or order of the audio
+ * streams has changed. The application will most likely want to select
+ * a new audio stream.
+ *
+ * This signal may be emitted from the context of a GStreamer streaming thread.
+ * You can use gst_message_new_application() and gst_element_post_message()
+ * to notify your application's main thread.
+ */
+ /* FIXME 0.11: turn audio-changed signal into message? */
+ gst_play_bin3_signals[SIGNAL_AUDIO_CHANGED] =
+ g_signal_new ("audio-changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstPlayBin3Class, audio_changed), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+ /**
+ * GstPlayBin3::text-changed
+ * @playbin: a #GstPlayBin3
+ *
+ * This signal is emitted whenever the number or order of the text
+ * streams has changed. The application will most likely want to select
+ * a new text stream.
+ *
+ * This signal may be emitted from the context of a GStreamer streaming thread.
+ * You can use gst_message_new_application() and gst_element_post_message()
+ * to notify your application's main thread.
+ */
+ /* FIXME 0.11: turn text-changed signal into message? */
+ gst_play_bin3_signals[SIGNAL_TEXT_CHANGED] =
+ g_signal_new ("text-changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstPlayBin3Class, text_changed), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ /**
+ * GstPlayBin3::video-tags-changed
+ * @playbin: a #GstPlayBin3
+ * @stream: stream index with changed tags
+ *
+ * This signal is emitted whenever the tags of a video stream have changed.
+ * The application will most likely want to get the new tags.
+ *
+ * This signal may be emitted from the context of a GStreamer streaming thread.
+ * You can use gst_message_new_application() and gst_element_post_message()
+ * to notify your application's main thread.
+ */
+ gst_play_bin3_signals[SIGNAL_VIDEO_TAGS_CHANGED] =
+ g_signal_new ("video-tags-changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstPlayBin3Class, video_tags_changed), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_INT);
+
+ /**
+ * GstPlayBin3::audio-tags-changed
+ * @playbin: a #GstPlayBin3
+ * @stream: stream index with changed tags
+ *
+ * This signal is emitted whenever the tags of an audio stream have changed.
+ * The application will most likely want to get the new tags.
+ *
+ * This signal may be emitted from the context of a GStreamer streaming thread.
+ * You can use gst_message_new_application() and gst_element_post_message()
+ * to notify your application's main thread.
+ */
+ gst_play_bin3_signals[SIGNAL_AUDIO_TAGS_CHANGED] =
+ g_signal_new ("audio-tags-changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstPlayBin3Class, audio_tags_changed), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_INT);
+
+ /**
+ * GstPlayBin3::text-tags-changed
+ * @playbin: a #GstPlayBin3
+ * @stream: stream index with changed tags
+ *
+ * This signal is emitted whenever the tags of a text stream have changed.
+ * The application will most likely want to get the new tags.
+ *
+ * This signal may be emitted from the context of a GStreamer streaming thread.
+ * You can use gst_message_new_application() and gst_element_post_message()
+ * to notify your application's main thread.
+ */
+ gst_play_bin3_signals[SIGNAL_TEXT_TAGS_CHANGED] =
+ g_signal_new ("text-tags-changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstPlayBin3Class, text_tags_changed), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_INT);
+
+ /**
+ * GstPlayBin3::source-setup:
+ * @playbin: a #GstPlayBin3
+ * @source: source element
+ *
+ * This signal is emitted after the source element has been created, so
+ * it can be configured by setting additional properties (e.g. set a
+ * proxy server for an http source, or set the device and read speed for
+ * an audio cd source). This is functionally equivalent to connecting to
+ * the notify::source signal, but more convenient.
+ *
+ * This signal is usually emitted from the context of a GStreamer streaming
+ * thread.
+ */
+ gst_play_bin3_signals[SIGNAL_SOURCE_SETUP] =
+ g_signal_new ("source-setup", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+ /**
+ * GstPlayBin3::get-video-tags
+ * @playbin: a #GstPlayBin3
+ * @stream: a video stream number
+ *
+ * Action signal to retrieve the tags of a specific video stream number.
+ * This information can be used to select a stream.
+ *
+ * Returns: a GstTagList with tags or NULL when the stream number does not
+ * exist.
+ */
+ gst_play_bin3_signals[SIGNAL_GET_VIDEO_TAGS] =
+ g_signal_new ("get-video-tags", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GstPlayBin3Class, get_video_tags), NULL, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_TAG_LIST, 1, G_TYPE_INT);
+ /**
+ * GstPlayBin3::get-audio-tags
+ * @playbin: a #GstPlayBin3
+ * @stream: an audio stream number
+ *
+ * Action signal to retrieve the tags of a specific audio stream number.
+ * This information can be used to select a stream.
+ *
+ * Returns: a GstTagList with tags or NULL when the stream number does not
+ * exist.
+ */
+ gst_play_bin3_signals[SIGNAL_GET_AUDIO_TAGS] =
+ g_signal_new ("get-audio-tags", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GstPlayBin3Class, get_audio_tags), NULL, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_TAG_LIST, 1, G_TYPE_INT);
+ /**
+ * GstPlayBin3::get-text-tags
+ * @playbin: a #GstPlayBin3
+ * @stream: a text stream number
+ *
+ * Action signal to retrieve the tags of a specific text stream number.
+ * This information can be used to select a stream.
+ *
+ * Returns: a GstTagList with tags or NULL when the stream number does not
+ * exist.
+ */
+ gst_play_bin3_signals[SIGNAL_GET_TEXT_TAGS] =
+ g_signal_new ("get-text-tags", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GstPlayBin3Class, get_text_tags), NULL, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_TAG_LIST, 1, G_TYPE_INT);
+ /**
+ * GstPlayBin3::convert-sample
+ * @playbin: a #GstPlayBin3
+ * @caps: the target format of the frame
+ *
+ * Action signal to retrieve the currently playing video frame in the format
+ * specified by @caps.
+ * If @caps is %NULL, no conversion will be performed and this function is
+ * equivalent to the #GstPlayBin3::frame property.
+ *
+ * Returns: a #GstSample of the current video frame converted to #caps.
+ * The caps on the sample will describe the final layout of the buffer data.
+ * %NULL is returned when no current buffer can be retrieved or when the
+ * conversion failed.
+ */
+ gst_play_bin3_signals[SIGNAL_CONVERT_SAMPLE] =
+ g_signal_new ("convert-sample", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GstPlayBin3Class, convert_sample), NULL, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_SAMPLE, 1, GST_TYPE_CAPS);
+
+ /**
+ * GstPlayBin3::get-video-pad
+ * @playbin: a #GstPlayBin3
+ * @stream: a video stream number
+ *
+ * Action signal to retrieve the stream-combiner sinkpad for a specific
+ * video stream.
+ * This pad can be used for notifications of caps changes, stream-specific
+ * queries, etc.
+ *
+ * Returns: a #GstPad, or NULL when the stream number does not exist.
+ */
+ gst_play_bin3_signals[SIGNAL_GET_VIDEO_PAD] =
+ g_signal_new ("get-video-pad", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GstPlayBin3Class, get_video_pad), NULL, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_PAD, 1, G_TYPE_INT);
+ /**
+ * GstPlayBin3::get-audio-pad
+ * @playbin: a #GstPlayBin3
+ * @stream: an audio stream number
+ *
+ * Action signal to retrieve the stream-combiner sinkpad for a specific
+ * audio stream.
+ * This pad can be used for notifications of caps changes, stream-specific
+ * queries, etc.
+ *
+ * Returns: a #GstPad, or NULL when the stream number does not exist.
+ */
+ gst_play_bin3_signals[SIGNAL_GET_AUDIO_PAD] =
+ g_signal_new ("get-audio-pad", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GstPlayBin3Class, get_audio_pad), NULL, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_PAD, 1, G_TYPE_INT);
+ /**
+ * GstPlayBin3::get-text-pad
+ * @playbin: a #GstPlayBin3
+ * @stream: a text stream number
+ *
+ * Action signal to retrieve the stream-combiner sinkpad for a specific
+ * text stream.
+ * This pad can be used for notifications of caps changes, stream-specific
+ * queries, etc.
+ *
+ * Returns: a #GstPad, or NULL when the stream number does not exist.
+ */
+ gst_play_bin3_signals[SIGNAL_GET_TEXT_PAD] =
+ g_signal_new ("get-text-pad", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GstPlayBin3Class, get_text_pad), NULL, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_PAD, 1, G_TYPE_INT);
+
+ klass->get_video_tags = gst_play_bin3_get_video_tags;
+ klass->get_audio_tags = gst_play_bin3_get_audio_tags;
+ klass->get_text_tags = gst_play_bin3_get_text_tags;
+
+ klass->convert_sample = gst_play_bin3_convert_sample;
+
+ klass->get_video_pad = gst_play_bin3_get_video_pad;
+ klass->get_audio_pad = gst_play_bin3_get_audio_pad;
+ klass->get_text_pad = gst_play_bin3_get_text_pad;
+
+ gst_element_class_set_static_metadata (gstelement_klass,
+ "Player Bin 3", "Generic/Bin/Player",
+ "Autoplug and play media from an uri",
+ "Wim Taymans <wim.taymans@gmail.com>");
+
+ gstelement_klass->change_state =
+ GST_DEBUG_FUNCPTR (gst_play_bin3_change_state);
+ gstelement_klass->query = GST_DEBUG_FUNCPTR (gst_play_bin3_query);
+ gstelement_klass->set_context = GST_DEBUG_FUNCPTR (gst_play_bin3_set_context);
+ gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_bin3_send_event);
+
+ gstbin_klass->handle_message =
+ GST_DEBUG_FUNCPTR (gst_play_bin3_handle_message);
+}
+
+static void
+do_async_start (GstPlayBin3 * playbin)
+{
+ GstMessage *message;
+
+ playbin->async_pending = TRUE;
+
+ message = gst_message_new_async_start (GST_OBJECT_CAST (playbin));
+ GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (playbin),
+ message);
+}
+
+static void
+do_async_done (GstPlayBin3 * playbin)
+{
+ GstMessage *message;
+
+ if (playbin->async_pending) {
+ GST_DEBUG_OBJECT (playbin, "posting ASYNC_DONE");
+ message =
+ gst_message_new_async_done (GST_OBJECT_CAST (playbin),
+ GST_CLOCK_TIME_NONE);
+ GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (playbin),
+ message);
+
+ playbin->async_pending = FALSE;
+ }
+}
+
+/* init combiners. The combiner is found by finding the first prefix that
+ * matches the media. */
+static void
+init_combiners (GstPlayBin3 * playbin)
+{
+ gint i;
+
+ /* store the array for the different channels */
+ for (i = 0; i < PLAYBIN_STREAM_LAST; i++)
+ playbin->channels[i] = g_ptr_array_new ();
+
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].media_type = "audio";
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].type = GST_PLAY_SINK_TYPE_AUDIO;
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].channels = playbin->channels[0];
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].streams =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].current_stream = -1;
+
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].media_type = "video";
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].type = GST_PLAY_SINK_TYPE_VIDEO;
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].channels = playbin->channels[1];
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].streams =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].current_stream = -1;
+
+ playbin->combiner[PLAYBIN_STREAM_TEXT].media_type = "text";
+ playbin->combiner[PLAYBIN_STREAM_TEXT].get_media_caps =
+ gst_subtitle_overlay_create_factory_caps;
+ playbin->combiner[PLAYBIN_STREAM_TEXT].type = GST_PLAY_SINK_TYPE_TEXT;
+ playbin->combiner[PLAYBIN_STREAM_TEXT].channels = playbin->channels[2];
+ playbin->combiner[PLAYBIN_STREAM_TEXT].streams =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
+ playbin->combiner[PLAYBIN_STREAM_TEXT].current_stream = -1;
+}
+
+/* Update the combiner information to be in sync with the current collection */
+static void
+update_combiner_info (GstPlayBin3 * playbin)
+{
+ guint i, len;
+
+ if (playbin->collection == NULL)
+ return;
+
+ GST_DEBUG_OBJECT (playbin, "Updating combiner info");
+
+ /* Wipe current combiner streams */
+ g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_AUDIO].streams, TRUE);
+ g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_VIDEO].streams, TRUE);
+ g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_TEXT].streams, TRUE);
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].streams =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].current_stream = -1;
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].streams =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].current_stream = -1;
+ playbin->combiner[PLAYBIN_STREAM_TEXT].streams =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
+ playbin->combiner[PLAYBIN_STREAM_TEXT].current_stream = -1;
+
+ len = gst_stream_collection_get_size (playbin->collection);
+ for (i = 0; i < len; i++) {
+ GstStream *stream =
+ gst_stream_collection_get_stream (playbin->collection, i);
+ GstStreamType stype = gst_stream_get_stream_type (stream);
+
+ switch (stype) {
+ case GST_STREAM_TYPE_AUDIO:
+ g_ptr_array_add (playbin->combiner[PLAYBIN_STREAM_AUDIO].streams,
+ gst_object_ref (stream));
+ break;
+ case GST_STREAM_TYPE_VIDEO:
+ g_ptr_array_add (playbin->combiner[PLAYBIN_STREAM_VIDEO].streams,
+ gst_object_ref (stream));
+ break;
+ case GST_STREAM_TYPE_TEXT:
+ g_ptr_array_add (playbin->combiner[PLAYBIN_STREAM_TEXT].streams,
+ gst_object_ref (stream));
+ break;
+ default:
+ break;
+ }
+ }
+
+ GST_DEBUG_OBJECT (playbin, "There are %d audio streams",
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].streams->len);
+ GST_DEBUG_OBJECT (playbin, "There are %d video streams",
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].streams->len);
+ GST_DEBUG_OBJECT (playbin, "There are %d text streams",
+ playbin->combiner[PLAYBIN_STREAM_TEXT].streams->len);
+}
+
+/* Set the given stream as the selected stream */
+static void
+set_selected_stream (GstPlayBin3 * playbin, GstStream * stream)
+{
+ GstSourceCombine *combine = NULL;
+
+ switch (gst_stream_get_stream_type (stream)) {
+ case GST_STREAM_TYPE_AUDIO:
+ combine = &playbin->combiner[PLAYBIN_STREAM_AUDIO];
+ break;
+ case GST_STREAM_TYPE_VIDEO:
+ combine = &playbin->combiner[PLAYBIN_STREAM_VIDEO];
+ break;
+ case GST_STREAM_TYPE_TEXT:
+ combine = &playbin->combiner[PLAYBIN_STREAM_TEXT];
+ break;
+ default:
+ break;
+ }
+
+ if (combine) {
+ if (combine->combiner == NULL) {
+ guint i, len;
+
+ GST_DEBUG_OBJECT (playbin, "Called for %s (%p)",
+ gst_stream_get_stream_id (stream), combine->combiner);
+
+ combine->current_stream = -1;
+ len = combine->streams->len;
+ for (i = 0; i < len; i++) {
+ GstStream *cand = g_ptr_array_index (combine->streams, i);
+ if (cand == stream) {
+ GST_DEBUG_OBJECT (playbin, "Setting current to %d", i);
+ combine->current_stream = i;
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void
+init_group (GstPlayBin3 * playbin, GstSourceGroup * group)
+{
+ g_mutex_init (&group->lock);
+
+ group->stream_changed_pending = FALSE;
+ g_mutex_init (&group->stream_changed_pending_lock);
+
+ group->playbin = playbin;
+}
+
+static void
+free_group (GstPlayBin3 * playbin, GstSourceGroup * group)
+{
+ g_free (group->uri);
+ g_free (group->suburi);
+
+ g_mutex_clear (&group->lock);
+ group->stream_changed_pending = FALSE;
+ g_mutex_clear (&group->stream_changed_pending_lock);
+
+ if (group->pending_buffering_msg)
+ gst_message_unref (group->pending_buffering_msg);
+ group->pending_buffering_msg = NULL;
+
+ gst_object_replace ((GstObject **) & group->audio_sink, NULL);
+ gst_object_replace ((GstObject **) & group->video_sink, NULL);
+ gst_object_replace ((GstObject **) & group->text_sink, NULL);
+}
+
+static void
+notify_volume_cb (GObject * combiner, GParamSpec * pspec, GstPlayBin3 * playbin)
+{
+ g_object_notify (G_OBJECT (playbin), "volume");
+}
+
+static void
+notify_mute_cb (GObject * combiner, GParamSpec * pspec, GstPlayBin3 * playbin)
+{
+ g_object_notify (G_OBJECT (playbin), "mute");
+}
+
+static void
+colorbalance_value_changed_cb (GstColorBalance * balance,
+ GstColorBalanceChannel * channel, gint value, GstPlayBin3 * playbin)
+{
+ gst_color_balance_value_changed (GST_COLOR_BALANCE (playbin), channel, value);
+}
+
+static gint
+compare_factories_func (gconstpointer p1, gconstpointer p2)
+{
+ GstPluginFeature *f1, *f2;
+ gboolean is_sink1, is_sink2;
+ gboolean is_parser1, is_parser2;
+
+ f1 = (GstPluginFeature *) p1;
+ f2 = (GstPluginFeature *) p2;
+
+ is_sink1 = gst_element_factory_list_is_type (GST_ELEMENT_FACTORY_CAST (f1),
+ GST_ELEMENT_FACTORY_TYPE_SINK);
+ is_sink2 = gst_element_factory_list_is_type (GST_ELEMENT_FACTORY_CAST (f2),
+ GST_ELEMENT_FACTORY_TYPE_SINK);
+ is_parser1 = gst_element_factory_list_is_type (GST_ELEMENT_FACTORY_CAST (f1),
+ GST_ELEMENT_FACTORY_TYPE_PARSER);
+ is_parser2 = gst_element_factory_list_is_type (GST_ELEMENT_FACTORY_CAST (f2),
+ GST_ELEMENT_FACTORY_TYPE_PARSER);
+
+ /* First we want all sinks as we prefer a sink if it directly
+ * supports the current caps */
+ if (is_sink1 && !is_sink2)
+ return -1;
+ else if (!is_sink1 && is_sink2)
+ return 1;
+
+ /* Then we want all parsers as we always want to plug parsers
+ * before decoders */
+ if (is_parser1 && !is_parser2)
+ return -1;
+ else if (!is_parser1 && is_parser2)
+ return 1;
+
+ /* And if it's a both a parser or sink we first sort by rank
+ * and then by factory name */
+ return gst_plugin_feature_rank_compare_func (p1, p2);
+}
+
+/* Must be called with elements lock! */
+static void
+gst_play_bin3_update_elements_list (GstPlayBin3 * playbin)
+{
+ GList *res, *tmp;
+ guint cookie;
+
+ cookie = gst_registry_get_feature_list_cookie (gst_registry_get ());
+
+ if (!playbin->elements || playbin->elements_cookie != cookie) {
+ if (playbin->elements)
+ gst_plugin_feature_list_free (playbin->elements);
+ res =
+ gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_DECODABLE, GST_RANK_MARGINAL);
+ tmp =
+ gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_AUDIOVIDEO_SINKS, GST_RANK_MARGINAL);
+ playbin->elements = g_list_concat (res, tmp);
+ playbin->elements = g_list_sort (playbin->elements, compare_factories_func);
+ }
+
+ if (!playbin->aelements || playbin->elements_cookie != cookie) {
+ if (playbin->aelements)
+ g_sequence_free (playbin->aelements);
+ playbin->aelements = avelements_create (playbin, TRUE);
+ }
+
+ if (!playbin->velements || playbin->elements_cookie != cookie) {
+ if (playbin->velements)
+ g_sequence_free (playbin->velements);
+ playbin->velements = avelements_create (playbin, FALSE);
+ }
+
+ playbin->elements_cookie = cookie;
+}
+
+static void
+gst_play_bin3_init (GstPlayBin3 * playbin)
+{
+ g_rec_mutex_init (&playbin->lock);
+ g_mutex_init (&playbin->dyn_lock);
+
+ /* assume we can create an input-selector */
+ playbin->have_selector = TRUE;
+
+ playbin->do_stream_selections = DEFAULT_AUTO_SELECT_STREAMS;
+
+ init_combiners (playbin);
+
+ /* init groups */
+ playbin->curr_group = &playbin->groups[0];
+ playbin->next_group = &playbin->groups[1];
+ init_group (playbin, &playbin->groups[0]);
+ init_group (playbin, &playbin->groups[1]);
+
+ /* first filter out the interesting element factories */
+ g_mutex_init (&playbin->elements_lock);
+
+ /* add sink */
+ playbin->playsink =
+ g_object_new (GST_TYPE_PLAY_SINK, "name", "playsink", "send-event-mode",
+ 1, NULL);
+ gst_bin_add (GST_BIN_CAST (playbin), GST_ELEMENT_CAST (playbin->playsink));
+ gst_play_sink_set_flags (playbin->playsink, DEFAULT_FLAGS);
+ /* Connect to notify::volume and notify::mute signals for proxying */
+ g_signal_connect (playbin->playsink, "notify::volume",
+ G_CALLBACK (notify_volume_cb), playbin);
+ g_signal_connect (playbin->playsink, "notify::mute",
+ G_CALLBACK (notify_mute_cb), playbin);
+ g_signal_connect (playbin->playsink, "value-changed",
+ G_CALLBACK (colorbalance_value_changed_cb), playbin);
+
+ playbin->current_video = DEFAULT_CURRENT_VIDEO;
+ playbin->current_audio = DEFAULT_CURRENT_AUDIO;
+ playbin->current_text = DEFAULT_CURRENT_TEXT;
+
+ playbin->buffer_duration = DEFAULT_BUFFER_DURATION;
+ playbin->buffer_size = DEFAULT_BUFFER_SIZE;
+ playbin->ring_buffer_max_size = DEFAULT_RING_BUFFER_MAX_SIZE;
+
+ playbin->force_aspect_ratio = TRUE;
+
+ playbin->multiview_mode = GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE;
+ playbin->multiview_flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
+}
+
+static void
+gst_play_bin3_finalize (GObject * object)
+{
+ GstPlayBin3 *playbin;
+ gint i;
+
+ playbin = GST_PLAY_BIN3 (object);
+
+ free_group (playbin, &playbin->groups[0]);
+ free_group (playbin, &playbin->groups[1]);
+
+ for (i = 0; i < PLAYBIN_STREAM_LAST; i++)
+ g_ptr_array_free (playbin->channels[i], TRUE);
+
+ if (playbin->source)
+ gst_object_unref (playbin->source);
+
+ /* Setting states to NULL is safe here because playsink
+ * will already be gone and none of these sinks will be
+ * a child of playsink
+ */
+ if (playbin->video_sink) {
+ gst_element_set_state (playbin->video_sink, GST_STATE_NULL);
+ gst_object_unref (playbin->video_sink);
+ }
+ if (playbin->audio_sink) {
+ gst_element_set_state (playbin->audio_sink, GST_STATE_NULL);
+ gst_object_unref (playbin->audio_sink);
+ }
+ if (playbin->text_sink) {
+ gst_element_set_state (playbin->text_sink, GST_STATE_NULL);
+ gst_object_unref (playbin->text_sink);
+ }
+
+ if (playbin->video_stream_combiner) {
+ gst_element_set_state (playbin->video_stream_combiner, GST_STATE_NULL);
+ gst_object_unref (playbin->video_stream_combiner);
+ }
+ if (playbin->audio_stream_combiner) {
+ gst_element_set_state (playbin->audio_stream_combiner, GST_STATE_NULL);
+ gst_object_unref (playbin->audio_stream_combiner);
+ }
+ if (playbin->text_stream_combiner) {
+ gst_element_set_state (playbin->text_stream_combiner, GST_STATE_NULL);
+ gst_object_unref (playbin->text_stream_combiner);
+ }
+
+ g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_AUDIO].streams, TRUE);
+ g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_VIDEO].streams, TRUE);
+ g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_TEXT].streams, TRUE);
+
+ if (playbin->decodebin)
+ gst_object_unref (playbin->decodebin);
+
+ if (playbin->elements)
+ gst_plugin_feature_list_free (playbin->elements);
+
+ if (playbin->aelements)
+ g_sequence_free (playbin->aelements);
+
+ if (playbin->velements)
+ g_sequence_free (playbin->velements);
+
+ g_list_free_full (playbin->contexts, (GDestroyNotify) gst_context_unref);
+
+ g_rec_mutex_clear (&playbin->lock);
+ g_mutex_clear (&playbin->dyn_lock);
+ g_mutex_clear (&playbin->elements_lock);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_playbin_uri_is_valid (GstPlayBin3 * playbin, const gchar * uri)
+{
+ const gchar *c;
+
+ GST_LOG_OBJECT (playbin, "checking uri '%s'", uri);
+
+ /* this just checks the protocol */
+ if (!gst_uri_is_valid (uri))
+ return FALSE;
+
+ for (c = uri; *c != '\0'; ++c) {
+ if (!g_ascii_isprint (*c))
+ goto invalid;
+ if (*c == ' ')
+ goto invalid;
+ }
+
+ return TRUE;
+
+invalid:
+ {
+ GST_WARNING_OBJECT (playbin, "uri '%s' not valid, character #%u",
+ uri, (guint) ((guintptr) c - (guintptr) uri));
+ return FALSE;
+ }
+}
+
+static void
+gst_play_bin3_set_uri (GstPlayBin3 * playbin, const gchar * uri)
+{
+ GstSourceGroup *group;
+
+ if (uri == NULL) {
+ g_warning ("cannot set NULL uri");
+ return;
+ }
+
+ if (!gst_playbin_uri_is_valid (playbin, uri)) {
+ if (g_str_has_prefix (uri, "file:")) {
+ GST_WARNING_OBJECT (playbin, "not entirely correct file URI '%s' - make "
+ "sure to escape spaces and non-ASCII characters properly and specify "
+ "an absolute path. Use gst_filename_to_uri() to convert filenames "
+ "to URIs", uri);
+ } else {
+ /* GST_ERROR_OBJECT (playbin, "malformed URI '%s'", uri); */
+ }
+ }
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ group = playbin->next_group;
+
+ GST_SOURCE_GROUP_LOCK (group);
+ /* store the uri in the next group we will play */
+ g_free (group->uri);
+ group->uri = g_strdup (uri);
+ group->valid = TRUE;
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ GST_DEBUG ("set new uri to %s", uri);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+}
+
+static void
+gst_play_bin3_set_suburi (GstPlayBin3 * playbin, const gchar * suburi)
+{
+ GstSourceGroup *group;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ group = playbin->next_group;
+
+ GST_SOURCE_GROUP_LOCK (group);
+ g_free (group->suburi);
+ group->suburi = g_strdup (suburi);
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ GST_DEBUG ("setting new .sub uri to %s", suburi);
+
+ GST_PLAY_BIN3_UNLOCK (playbin);
+}
+
+static void
+gst_play_bin3_set_flags (GstPlayBin3 * playbin, GstPlayFlags flags)
+{
+ GstPlayFlags old_flags;
+ old_flags = gst_play_sink_get_flags (playbin->playsink);
+
+ if (flags != old_flags) {
+ gst_play_sink_set_flags (playbin->playsink, flags);
+ gst_play_sink_reconfigure (playbin->playsink);
+ }
+}
+
+static GstPlayFlags
+gst_play_bin3_get_flags (GstPlayBin3 * playbin)
+{
+ GstPlayFlags flags;
+
+ flags = gst_play_sink_get_flags (playbin->playsink);
+
+ return flags;
+}
+
+/* get the currently playing group or if nothing is playing, the next
+ * group. Must be called with the PLAY_BIN_LOCK. */
+static GstSourceGroup *
+get_group (GstPlayBin3 * playbin)
+{
+ GstSourceGroup *result;
+
+ if (!(result = playbin->curr_group))
+ result = playbin->next_group;
+
+ return result;
+}
+
+static GstPad *
+gst_play_bin3_get_pad_of_type (GstPlayBin3 * playbin, gint stream_type,
+ gint stream)
+{
+ GstPad *sinkpad = NULL;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ if (playbin->combiner[stream_type].combiner == NULL) {
+ GST_DEBUG_OBJECT (playbin,
+ "get-pad of type %d w/o custom-combiner. Returning playsink pad",
+ stream_type);
+ sinkpad = playbin->combiner[stream_type].sinkpad;
+ if (sinkpad) {
+ sinkpad = gst_object_ref (sinkpad);
+ goto done;
+ }
+ }
+ if (stream < playbin->channels[stream_type]->len) {
+ sinkpad = g_ptr_array_index (playbin->channels[stream_type], stream);
+ gst_object_ref (sinkpad);
+ }
+
+done:
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return sinkpad;
+}
+
+static GstPad *
+gst_play_bin3_get_video_pad (GstPlayBin3 * playbin, gint stream)
+{
+ return gst_play_bin3_get_pad_of_type (playbin, PLAYBIN_STREAM_VIDEO, stream);
+}
+
+static GstPad *
+gst_play_bin3_get_audio_pad (GstPlayBin3 * playbin, gint stream)
+{
+ return gst_play_bin3_get_pad_of_type (playbin, PLAYBIN_STREAM_AUDIO, stream);
+}
+
+static GstPad *
+gst_play_bin3_get_text_pad (GstPlayBin3 * playbin, gint stream)
+{
+ return gst_play_bin3_get_pad_of_type (playbin, PLAYBIN_STREAM_TEXT, stream);
+}
+
+
+static GstTagList *
+get_tags (GstPlayBin3 * playbin, GstStreamType type, gint stream_num)
+{
+ GstTagList *result = NULL;
+ gint nb_streams = gst_stream_collection_get_size (playbin->collection);
+ gint i, cur_idx = 0;
+
+ /* Count the streams of the type we want to find the one numbered 'stream' */
+ for (i = 0; i < nb_streams; i++) {
+ GstStream *stream =
+ gst_stream_collection_get_stream (playbin->collection, i);
+ GstStreamType stream_type = gst_stream_get_stream_type (stream);
+ if (stream_type != type)
+ continue;
+ if (cur_idx == stream_num)
+ return gst_stream_get_tags (stream);
+ cur_idx++;
+ }
+
+ return result;
+}
+
+static GstTagList *
+gst_play_bin3_get_video_tags (GstPlayBin3 * playbin, gint stream)
+{
+ GstTagList *result;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ result = get_tags (playbin, GST_STREAM_TYPE_VIDEO, stream);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return result;
+}
+
+static GstTagList *
+gst_play_bin3_get_audio_tags (GstPlayBin3 * playbin, gint stream)
+{
+ GstTagList *result;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ result = get_tags (playbin, GST_STREAM_TYPE_AUDIO, stream);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return result;
+}
+
+static GstTagList *
+gst_play_bin3_get_text_tags (GstPlayBin3 * playbin, gint stream)
+{
+ GstTagList *result;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ result = get_tags (playbin, GST_STREAM_TYPE_TEXT, stream);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return result;
+}
+
+static GstSample *
+gst_play_bin3_convert_sample (GstPlayBin3 * playbin, GstCaps * caps)
+{
+ return gst_play_sink_convert_sample (playbin->playsink, caps);
+}
+
+/* Returns current stream number, or -1 if none has been selected yet */
+static int
+get_current_stream_number (GstPlayBin3 * playbin, GstSourceCombine * combine,
+ GPtrArray * channels)
+{
+ /* Internal API cleanup would make this easier... */
+ int i;
+ GstPad *pad, *current;
+ GstObject *combiner = NULL;
+ int ret = -1;
+
+ if (!combine->has_active_pad) {
+ GST_WARNING_OBJECT (playbin,
+ "combiner doesn't have the \"active-pad\" property");
+ return ret;
+ }
+
+ for (i = 0; i < channels->len; i++) {
+ pad = g_ptr_array_index (channels, i);
+ if ((combiner = gst_pad_get_parent (pad))) {
+ g_object_get (combiner, "active-pad", ¤t, NULL);
+ gst_object_unref (combiner);
+
+ if (pad == current) {
+ gst_object_unref (current);
+ ret = i;
+ break;
+ }
+
+ if (current)
+ gst_object_unref (current);
+ }
+ }
+
+ return ret;
+}
+
+static gboolean
+gst_play_bin3_send_custom_event (GstObject * combiner, const gchar * event_name)
+{
+ GstPad *src;
+ GstPad *peer;
+ GstStructure *s;
+ GstEvent *event;
+ gboolean ret = FALSE;
+
+ src = gst_element_get_static_pad (GST_ELEMENT_CAST (combiner), "src");
+ peer = gst_pad_get_peer (src);
+ if (peer) {
+ s = gst_structure_new_empty (event_name);
+ event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB, s);
+ gst_pad_send_event (peer, event);
+ gst_object_unref (peer);
+ ret = TRUE;
+ }
+ gst_object_unref (src);
+ return ret;
+}
+
+static gboolean
+gst_play_bin3_set_current_stream (GstPlayBin3 * playbin,
+ gint stream_type, gint * current_value, gint stream,
+ gboolean * flush_marker)
+{
+ GstSourceCombine *combine;
+ GPtrArray *channels;
+ GstPad *sinkpad;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ /* This function is only called if the app sets
+ * one of the current-* properties, which means it doesn't
+ * handle collections or select-streams yet */
+ playbin->do_stream_selections = TRUE;
+
+ combine = playbin->combiner + stream_type;
+ channels = playbin->channels[stream_type];
+
+ GST_DEBUG_OBJECT (playbin, "Changing current %s stream %d -> %d",
+ stream_type_names[stream_type], *current_value, stream);
+
+ if (combine->combiner == NULL) {
+ /* FIXME: Check that the current_value is within range */
+ *current_value = stream;
+ do_stream_selection (playbin);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ return TRUE;
+ }
+
+ GST_DEBUG_OBJECT (playbin, "Using old style combiner");
+
+ if (!combine->has_active_pad)
+ goto no_active_pad;
+ if (channels == NULL)
+ goto no_channels;
+
+ if (stream == -1 || channels->len <= stream) {
+ sinkpad = NULL;
+ } else {
+ /* take channel from selected stream */
+ sinkpad = g_ptr_array_index (channels, stream);
+ }
+
+ if (sinkpad)
+ gst_object_ref (sinkpad);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ if (sinkpad) {
+ GstObject *combiner;
+
+ if ((combiner = gst_pad_get_parent (sinkpad))) {
+ GstPad *old_sinkpad;
+
+ g_object_get (combiner, "active-pad", &old_sinkpad, NULL);
+
+ if (old_sinkpad != sinkpad) {
+ /* FIXME: Is there actually any reason playsink
+ * needs special names for each type of stream we flush? */
+ gchar *flush_event_name = g_strdup_printf ("playsink-custom-%s-flush",
+ stream_type_names[stream_type]);
+ if (gst_play_bin3_send_custom_event (combiner, flush_event_name))
+ *flush_marker = TRUE;
+ g_free (flush_event_name);
+
+ /* activate the selected pad */
+ g_object_set (combiner, "active-pad", sinkpad, NULL);
+ }
+
+ if (old_sinkpad)
+ gst_object_unref (old_sinkpad);
+
+ gst_object_unref (combiner);
+ }
+ gst_object_unref (sinkpad);
+ }
+ return TRUE;
+
+no_active_pad:
+ {
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ GST_WARNING_OBJECT (playbin,
+ "can't switch %s, the stream combiner's sink pads don't have the \"active-pad\" property",
+ stream_type_names[stream_type]);
+ return FALSE;
+ }
+no_channels:
+ {
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ GST_DEBUG_OBJECT (playbin, "can't switch video, we have no channels");
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_play_bin3_set_current_video_stream (GstPlayBin3 * playbin, gint stream)
+{
+ return gst_play_bin3_set_current_stream (playbin, PLAYBIN_STREAM_VIDEO,
+ &playbin->current_video, stream, &playbin->video_pending_flush_finish);
+}
+
+static gboolean
+gst_play_bin3_set_current_audio_stream (GstPlayBin3 * playbin, gint stream)
+{
+ return gst_play_bin3_set_current_stream (playbin, PLAYBIN_STREAM_AUDIO,
+ &playbin->current_audio, stream, &playbin->audio_pending_flush_finish);
+}
+
+static gboolean
+gst_play_bin3_set_current_text_stream (GstPlayBin3 * playbin, gint stream)
+{
+ return gst_play_bin3_set_current_stream (playbin, PLAYBIN_STREAM_TEXT,
+ &playbin->current_text, stream, &playbin->text_pending_flush_finish);
+}
+
+static void
+source_combine_remove_pads (GstPlayBin3 * playbin, GstSourceCombine * combine)
+{
+ if (combine->sinkpad) {
+ GST_LOG_OBJECT (playbin, "unlinking from sink");
+ gst_pad_unlink (combine->srcpad, combine->sinkpad);
+
+ /* release back */
+ GST_LOG_OBJECT (playbin, "release sink pad");
+ gst_play_sink_release_pad (playbin->playsink, combine->sinkpad);
+ gst_object_unref (combine->sinkpad);
+ combine->sinkpad = NULL;
+ }
+ gst_object_unref (combine->srcpad);
+ combine->srcpad = NULL;
+}
+
+static GstPadProbeReturn
+block_serialized_data_cb (GstPad * pad, GstPadProbeInfo * info,
+ gpointer user_data)
+{
+ if (GST_IS_EVENT (info->data) && !GST_EVENT_IS_SERIALIZED (info->data)) {
+ GST_DEBUG_OBJECT (pad, "Letting non-serialized event %s pass",
+ GST_EVENT_TYPE_NAME (info->data));
+ return GST_PAD_PROBE_PASS;
+ }
+
+ return GST_PAD_PROBE_OK;
+}
+
+static void
+gst_play_bin3_set_sink (GstPlayBin3 * playbin, GstPlaySinkType type,
+ const gchar * dbg, GstElement ** elem, GstElement * sink)
+{
+ GST_INFO_OBJECT (playbin, "Setting %s sink to %" GST_PTR_FORMAT, dbg, sink);
+
+ gst_play_sink_set_sink (playbin->playsink, type, sink);
+
+ if (*elem)
+ gst_object_unref (*elem);
+ *elem = sink ? gst_object_ref (sink) : NULL;
+}
+
+static void
+gst_play_bin3_set_stream_combiner (GstPlayBin3 * playbin, GstElement ** elem,
+ const gchar * dbg, GstElement * combiner)
+{
+ GST_INFO_OBJECT (playbin, "Setting %s stream combiner to %" GST_PTR_FORMAT,
+ dbg, combiner);
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ if (*elem != combiner) {
+ GstElement *old;
+
+ old = *elem;
+ if (combiner)
+ gst_object_ref_sink (combiner);
+
+ *elem = combiner;
+ if (old)
+ gst_object_unref (old);
+ }
+ GST_LOG_OBJECT (playbin, "%s stream combiner now %" GST_PTR_FORMAT, dbg,
+ *elem);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+}
+
+static void
+gst_play_bin3_set_encoding (GstPlayBin3 * playbin, const gchar * encoding)
+{
+ GstElement *elem;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+
+ /* set subtitles on decodebin. */
+ if ((elem = playbin->decodebin))
+ g_object_set (G_OBJECT (elem), "subtitle-encoding", encoding, NULL);
+
+ gst_play_sink_set_subtitle_encoding (playbin->playsink, encoding);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+}
+
+static void
+gst_play_bin3_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (object);
+
+ switch (prop_id) {
+ case PROP_URI:
+ gst_play_bin3_set_uri (playbin, g_value_get_string (value));
+ break;
+ case PROP_SUBURI:
+ gst_play_bin3_set_suburi (playbin, g_value_get_string (value));
+ break;
+ case PROP_FLAGS:
+ gst_play_bin3_set_flags (playbin, g_value_get_flags (value));
+ if (playbin->curr_group) {
+ GST_SOURCE_GROUP_LOCK (playbin->curr_group);
+ if (playbin->curr_group->urisourcebin) {
+ g_object_set (playbin->curr_group->urisourcebin, "download",
+ (g_value_get_flags (value) & GST_PLAY_FLAG_DOWNLOAD) != 0, NULL);
+ }
+ GST_SOURCE_GROUP_UNLOCK (playbin->curr_group);
+ }
+ break;
+ case PROP_CURRENT_VIDEO:
+ gst_play_bin3_set_current_video_stream (playbin, g_value_get_int (value));
+ break;
+ case PROP_CURRENT_AUDIO:
+ gst_play_bin3_set_current_audio_stream (playbin, g_value_get_int (value));
+ break;
+ case PROP_CURRENT_TEXT:
+ gst_play_bin3_set_current_text_stream (playbin, g_value_get_int (value));
+ break;
+ case PROP_AUTO_SELECT_STREAMS:
+ GST_PLAY_BIN3_LOCK (playbin);
+ playbin->do_stream_selections = g_value_get_boolean (value);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ case PROP_SUBTITLE_ENCODING:
+ gst_play_bin3_set_encoding (playbin, g_value_get_string (value));
+ break;
+ case PROP_VIDEO_FILTER:
+ gst_play_sink_set_filter (playbin->playsink, GST_PLAY_SINK_TYPE_VIDEO,
+ GST_ELEMENT (g_value_get_object (value)));
+ break;
+ case PROP_AUDIO_FILTER:
+ gst_play_sink_set_filter (playbin->playsink, GST_PLAY_SINK_TYPE_AUDIO,
+ GST_ELEMENT (g_value_get_object (value)));
+ break;
+ case PROP_VIDEO_SINK:
+ gst_play_bin3_set_sink (playbin, GST_PLAY_SINK_TYPE_VIDEO, "video",
+ &playbin->video_sink, g_value_get_object (value));
+ break;
+ case PROP_AUDIO_SINK:
+ gst_play_bin3_set_sink (playbin, GST_PLAY_SINK_TYPE_AUDIO, "audio",
+ &playbin->audio_sink, g_value_get_object (value));
+ break;
+ case PROP_VIS_PLUGIN:
+ gst_play_sink_set_vis_plugin (playbin->playsink,
+ g_value_get_object (value));
+ break;
+ case PROP_TEXT_SINK:
+ gst_play_bin3_set_sink (playbin, GST_PLAY_SINK_TYPE_TEXT, "text",
+ &playbin->text_sink, g_value_get_object (value));
+ break;
+ case PROP_VIDEO_STREAM_COMBINER:
+ gst_play_bin3_set_stream_combiner (playbin,
+ &playbin->video_stream_combiner, "video", g_value_get_object (value));
+ break;
+ case PROP_AUDIO_STREAM_COMBINER:
+ gst_play_bin3_set_stream_combiner (playbin,
+ &playbin->audio_stream_combiner, "audio", g_value_get_object (value));
+ break;
+ case PROP_TEXT_STREAM_COMBINER:
+ gst_play_bin3_set_stream_combiner (playbin,
+ &playbin->text_stream_combiner, "text", g_value_get_object (value));
+ break;
+ case PROP_VOLUME:
+ gst_play_sink_set_volume (playbin->playsink, g_value_get_double (value));
+ break;
+ case PROP_MUTE:
+ gst_play_sink_set_mute (playbin->playsink, g_value_get_boolean (value));
+ break;
+ case PROP_FONT_DESC:
+ gst_play_sink_set_font_desc (playbin->playsink,
+ g_value_get_string (value));
+ break;
+ case PROP_CONNECTION_SPEED:
+ GST_PLAY_BIN3_LOCK (playbin);
+ playbin->connection_speed = g_value_get_uint64 (value) * 1000;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ case PROP_BUFFER_SIZE:
+ playbin->buffer_size = g_value_get_int (value);
+ break;
+ case PROP_BUFFER_DURATION:
+ playbin->buffer_duration = g_value_get_int64 (value);
+ break;
+ case PROP_AV_OFFSET:
+ gst_play_sink_set_av_offset (playbin->playsink,
+ g_value_get_int64 (value));
+ break;
+ case PROP_RING_BUFFER_MAX_SIZE:
+ playbin->ring_buffer_max_size = g_value_get_uint64 (value);
+ if (playbin->curr_group) {
+ GST_SOURCE_GROUP_LOCK (playbin->curr_group);
+ if (playbin->curr_group->urisourcebin) {
+ g_object_set (playbin->curr_group->urisourcebin,
+ "ring-buffer-max-size", playbin->ring_buffer_max_size, NULL);
+ }
+ GST_SOURCE_GROUP_UNLOCK (playbin->curr_group);
+ }
+ break;
+ case PROP_FORCE_ASPECT_RATIO:
+ g_object_set (playbin->playsink, "force-aspect-ratio",
+ g_value_get_boolean (value), NULL);
+ break;
+ case PROP_MULTIVIEW_MODE:
+ GST_PLAY_BIN3_LOCK (playbin);
+ playbin->multiview_mode = g_value_get_enum (value);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ case PROP_MULTIVIEW_FLAGS:
+ GST_PLAY_BIN3_LOCK (playbin);
+ playbin->multiview_flags = g_value_get_flags (value);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GstElement *
+gst_play_bin3_get_current_sink (GstPlayBin3 * playbin, GstElement ** elem,
+ const gchar * dbg, GstPlaySinkType type)
+{
+ GstElement *sink = gst_play_sink_get_sink (playbin->playsink, type);
+
+ GST_LOG_OBJECT (playbin, "play_sink_get_sink() returned %s sink %"
+ GST_PTR_FORMAT ", the originally set %s sink is %" GST_PTR_FORMAT,
+ dbg, sink, dbg, *elem);
+
+ if (sink == NULL) {
+ GST_PLAY_BIN3_LOCK (playbin);
+ if ((sink = *elem))
+ gst_object_ref (sink);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ }
+
+ return sink;
+}
+
+static GstElement *
+gst_play_bin3_get_current_stream_combiner (GstPlayBin3 * playbin,
+ GstElement ** elem, const gchar * dbg, int stream_type)
+{
+ GstElement *combiner;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ if ((combiner = playbin->combiner[stream_type].combiner))
+ gst_object_ref (combiner);
+ else if ((combiner = *elem))
+ gst_object_ref (combiner);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return combiner;
+}
+
+static void
+gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (object);
+
+ switch (prop_id) {
+ case PROP_URI:
+ {
+ GstSourceGroup *group;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ group = playbin->next_group;
+ g_value_set_string (value, group->uri);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ }
+ case PROP_CURRENT_URI:
+ {
+ GstSourceGroup *group;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ group = get_group (playbin);
+ g_value_set_string (value, group->uri);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ }
+ case PROP_SUBURI:
+ {
+ GstSourceGroup *group;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ group = playbin->next_group;
+ g_value_set_string (value, group->suburi);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ }
+ case PROP_CURRENT_SUBURI:
+ {
+ GstSourceGroup *group;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ group = get_group (playbin);
+ g_value_set_string (value, group->suburi);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ }
+ case PROP_SOURCE:
+ {
+ GST_OBJECT_LOCK (playbin);
+ g_value_set_object (value, playbin->source);
+ GST_OBJECT_UNLOCK (playbin);
+ break;
+ }
+ case PROP_FLAGS:
+ g_value_set_flags (value, gst_play_bin3_get_flags (playbin));
+ break;
+ case PROP_N_VIDEO:
+ {
+ gint n_video;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ n_video =
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].
+ streams ? playbin->combiner[PLAYBIN_STREAM_VIDEO].streams->len : 0;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ g_value_set_int (value, n_video);
+ break;
+ }
+ case PROP_CURRENT_VIDEO:
+ GST_PLAY_BIN3_LOCK (playbin);
+ if (playbin->combiner[PLAYBIN_STREAM_VIDEO].current_stream != -1)
+ g_value_set_int (value,
+ playbin->combiner[PLAYBIN_STREAM_VIDEO].current_stream);
+ else
+ g_value_set_int (value, playbin->current_video);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ case PROP_N_AUDIO:
+ {
+ gint n_audio;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ n_audio =
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].
+ streams ? playbin->combiner[PLAYBIN_STREAM_AUDIO].streams->len : 0;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ g_value_set_int (value, n_audio);
+ break;
+ }
+ case PROP_CURRENT_AUDIO:
+ GST_PLAY_BIN3_LOCK (playbin);
+ if (playbin->combiner[PLAYBIN_STREAM_AUDIO].current_stream != -1)
+ g_value_set_int (value,
+ playbin->combiner[PLAYBIN_STREAM_AUDIO].current_stream);
+ else
+ g_value_set_int (value, playbin->current_audio);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ case PROP_N_TEXT:
+ {
+ gint n_text;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ n_text =
+ playbin->combiner[PLAYBIN_STREAM_TEXT].
+ streams ? playbin->combiner[PLAYBIN_STREAM_TEXT].streams->len : 0;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ g_value_set_int (value, n_text);
+ break;
+ }
+ case PROP_CURRENT_TEXT:
+ GST_PLAY_BIN3_LOCK (playbin);
+ if (playbin->combiner[PLAYBIN_STREAM_TEXT].current_stream != -1)
+ g_value_set_int (value,
+ playbin->combiner[PLAYBIN_STREAM_TEXT].current_stream);
+ else
+ g_value_set_int (value, playbin->current_text);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ case PROP_AUTO_SELECT_STREAMS:
+ GST_PLAY_BIN3_LOCK (playbin);
+ g_value_set_boolean (value, playbin->do_stream_selections);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ case PROP_SUBTITLE_ENCODING:
+ GST_PLAY_BIN3_LOCK (playbin);
+ g_value_take_string (value,
+ gst_play_sink_get_subtitle_encoding (playbin->playsink));
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ case PROP_VIDEO_FILTER:
+ g_value_take_object (value,
+ gst_play_sink_get_filter (playbin->playsink,
+ GST_PLAY_SINK_TYPE_VIDEO));
+ break;
+ case PROP_AUDIO_FILTER:
+ g_value_take_object (value,
+ gst_play_sink_get_filter (playbin->playsink,
+ GST_PLAY_SINK_TYPE_AUDIO));
+ break;
+ case PROP_VIDEO_SINK:
+ g_value_take_object (value,
+ gst_play_bin3_get_current_sink (playbin, &playbin->video_sink,
+ "video", GST_PLAY_SINK_TYPE_VIDEO));
+ break;
+ case PROP_AUDIO_SINK:
+ g_value_take_object (value,
+ gst_play_bin3_get_current_sink (playbin, &playbin->audio_sink,
+ "audio", GST_PLAY_SINK_TYPE_AUDIO));
+ break;
+ case PROP_VIS_PLUGIN:
+ g_value_take_object (value,
+ gst_play_sink_get_vis_plugin (playbin->playsink));
+ break;
+ case PROP_TEXT_SINK:
+ g_value_take_object (value,
+ gst_play_bin3_get_current_sink (playbin, &playbin->text_sink,
+ "text", GST_PLAY_SINK_TYPE_TEXT));
+ break;
+ case PROP_VIDEO_STREAM_COMBINER:
+ g_value_take_object (value,
+ gst_play_bin3_get_current_stream_combiner (playbin,
+ &playbin->video_stream_combiner, "video", PLAYBIN_STREAM_VIDEO));
+ break;
+ case PROP_AUDIO_STREAM_COMBINER:
+ g_value_take_object (value,
+ gst_play_bin3_get_current_stream_combiner (playbin,
+ &playbin->audio_stream_combiner, "audio", PLAYBIN_STREAM_AUDIO));
+ break;
+ case PROP_TEXT_STREAM_COMBINER:
+ g_value_take_object (value,
+ gst_play_bin3_get_current_stream_combiner (playbin,
+ &playbin->text_stream_combiner, "text", PLAYBIN_STREAM_TEXT));
+ break;
+ case PROP_VOLUME:
+ g_value_set_double (value, gst_play_sink_get_volume (playbin->playsink));
+ break;
+ case PROP_MUTE:
+ g_value_set_boolean (value, gst_play_sink_get_mute (playbin->playsink));
+ break;
+ case PROP_SAMPLE:
+ gst_value_take_sample (value,
+ gst_play_sink_get_last_sample (playbin->playsink));
+ break;
+ case PROP_FONT_DESC:
+ g_value_take_string (value,
+ gst_play_sink_get_font_desc (playbin->playsink));
+ break;
+ case PROP_CONNECTION_SPEED:
+ GST_PLAY_BIN3_LOCK (playbin);
+ g_value_set_uint64 (value, playbin->connection_speed / 1000);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ break;
+ case PROP_BUFFER_SIZE:
+ GST_OBJECT_LOCK (playbin);
+ g_value_set_int (value, playbin->buffer_size);
+ GST_OBJECT_UNLOCK (playbin);
+ break;
+ case PROP_BUFFER_DURATION:
+ GST_OBJECT_LOCK (playbin);
+ g_value_set_int64 (value, playbin->buffer_duration);
+ GST_OBJECT_UNLOCK (playbin);
+ break;
+ case PROP_AV_OFFSET:
+ g_value_set_int64 (value,
+ gst_play_sink_get_av_offset (playbin->playsink));
+ break;
+ case PROP_RING_BUFFER_MAX_SIZE:
+ g_value_set_uint64 (value, playbin->ring_buffer_max_size);
+ break;
+ case PROP_FORCE_ASPECT_RATIO:{
+ gboolean v;
+
+ g_object_get (playbin->playsink, "force-aspect-ratio", &v, NULL);
+ g_value_set_boolean (value, v);
+ break;
+ }
+ case PROP_MULTIVIEW_MODE:
+ GST_OBJECT_LOCK (playbin);
+ g_value_set_enum (value, playbin->multiview_mode);
+ GST_OBJECT_UNLOCK (playbin);
+ break;
+ case PROP_MULTIVIEW_FLAGS:
+ GST_OBJECT_LOCK (playbin);
+ g_value_set_flags (value, playbin->multiview_flags);
+ GST_OBJECT_UNLOCK (playbin);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_play_bin3_update_cached_duration_from_query (GstPlayBin3 * playbin,
+ gboolean valid, GstQuery * query)
+{
+ GstFormat fmt;
+ gint64 duration;
+ gint i;
+
+ GST_DEBUG_OBJECT (playbin, "Updating cached duration from query");
+ gst_query_parse_duration (query, &fmt, &duration);
+
+ for (i = 0; i < G_N_ELEMENTS (playbin->duration); i++) {
+ if (playbin->duration[i].format == 0 || fmt == playbin->duration[i].format) {
+ playbin->duration[i].valid = valid;
+ playbin->duration[i].format = fmt;
+ playbin->duration[i].duration = valid ? duration : -1;
+ break;
+ }
+ }
+}
+
+static void
+gst_play_bin3_update_cached_duration (GstPlayBin3 * playbin)
+{
+ const GstFormat formats[] =
+ { GST_FORMAT_TIME, GST_FORMAT_BYTES, GST_FORMAT_DEFAULT };
+ gboolean ret;
+ GstQuery *query;
+ gint i;
+
+ GST_DEBUG_OBJECT (playbin, "Updating cached durations before group switch");
+ for (i = 0; i < G_N_ELEMENTS (formats); i++) {
+ query = gst_query_new_duration (formats[i]);
+ ret =
+ GST_ELEMENT_CLASS (parent_class)->query (GST_ELEMENT_CAST (playbin),
+ query);
+ gst_play_bin3_update_cached_duration_from_query (playbin, ret, query);
+ gst_query_unref (query);
+ }
+}
+
+static gboolean
+gst_play_bin3_query (GstElement * element, GstQuery * query)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (element);
+ gboolean ret;
+
+ /* During a group switch we shouldn't allow duration queries
+ * because it's not clear if the old or new group's duration
+ * is returned and if the sinks are already playing new data
+ * or old data. See bug #585969
+ *
+ * While we're at it, also don't do any other queries during
+ * a group switch or any other event that causes topology changes
+ * by taking the playbin lock in any case.
+ */
+ GST_PLAY_BIN3_LOCK (playbin);
+
+ if (GST_QUERY_TYPE (query) == GST_QUERY_DURATION) {
+ GstSourceGroup *group = playbin->curr_group;
+ gboolean pending;
+
+ GST_SOURCE_GROUP_LOCK (group);
+
+ pending = group->pending || group->stream_changed_pending;
+
+ if (pending) {
+ GstFormat fmt;
+ gint i;
+
+ ret = FALSE;
+ gst_query_parse_duration (query, &fmt, NULL);
+ for (i = 0; i < G_N_ELEMENTS (playbin->duration); i++) {
+ if (fmt == playbin->duration[i].format) {
+ ret = playbin->duration[i].valid;
+ gst_query_set_duration (query, fmt,
+ (ret ? playbin->duration[i].duration : -1));
+ break;
+ }
+ }
+ /* if nothing cached yet, we might as well request duration,
+ * such as during initial startup */
+ if (ret) {
+ GST_DEBUG_OBJECT (playbin,
+ "Taking cached duration because of pending group switch: %d", ret);
+ GST_SOURCE_GROUP_UNLOCK (group);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ return ret;
+ }
+ }
+ GST_SOURCE_GROUP_UNLOCK (group);
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->query (element, query);
+
+ if (GST_QUERY_TYPE (query) == GST_QUERY_DURATION)
+ gst_play_bin3_update_cached_duration_from_query (playbin, ret, query);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return ret;
+}
+
+static gint
+get_combiner_stream_id (GstPlayBin3 * playbin, GstSourceCombine * combine,
+ GList * full_list)
+{
+ gint i;
+ GList *tmp;
+
+ for (i = 0; combine->streams->len; i++) {
+ GstStream *stream = (GstStream *) g_ptr_array_index (combine->streams, i);
+ const gchar *sid = gst_stream_get_stream_id (stream);
+ for (tmp = full_list; tmp; tmp = tmp->next) {
+ gchar *orig = (gchar *) tmp->data;
+ if (!g_strcmp0 (orig, sid))
+ return i;
+ }
+ }
+
+ /* Fallback */
+ return -1;
+}
+
+static GList *
+extend_list_of_streams (GstPlayBin3 * playbin, GstStreamType stype,
+ GList * list)
+{
+ GList *tmp, *res;
+ gint i, nb;
+
+ res = list;
+
+ nb = gst_stream_collection_get_size (playbin->collection);
+ for (i = 0; i < nb; i++) {
+ GstStream *stream =
+ gst_stream_collection_get_stream (playbin->collection, i);
+ GstStreamType curtype = gst_stream_get_stream_type (stream);
+ if (stype == curtype) {
+ gboolean already_there = FALSE;
+ const gchar *sid = gst_stream_get_stream_id (stream);
+ for (tmp = res; tmp; tmp = tmp->next) {
+ const gchar *other = (const gchar *) tmp->data;
+ if (!g_strcmp0 (sid, other)) {
+ already_there = TRUE;
+ break;
+ }
+ }
+ if (!already_there) {
+ GST_DEBUG_OBJECT (playbin, "Adding stream %s", sid);
+ res = g_list_append (res, g_strdup (sid));
+ }
+ }
+ }
+
+ return res;
+}
+
+static GstEvent *
+update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event)
+{
+ GList *streams = NULL;
+ GList *to_use;
+ gint combine_id;
+
+ if (!playbin->audio_stream_combiner && !playbin->video_stream_combiner &&
+ !playbin->text_stream_combiner) {
+ /* Nothing to do */
+ GST_DEBUG_OBJECT (playbin,
+ "No custom combiners, no need to modify SELECT_STREAMS event");
+ return event;
+ }
+
+ gst_event_parse_select_streams (event, &streams);
+ to_use = g_list_copy_deep (streams, (GCopyFunc) g_strdup, NULL);
+
+ /* For each combiner, we want to add all streams of that type to the
+ * selection */
+ if (playbin->audio_stream_combiner) {
+ to_use = extend_list_of_streams (playbin, GST_STREAM_TYPE_AUDIO, to_use);
+ combine_id =
+ get_combiner_stream_id (playbin,
+ &playbin->combiner[PLAYBIN_STREAM_AUDIO], streams);
+ if (combine_id != -1)
+ gst_play_bin3_set_current_audio_stream (playbin, combine_id);
+ }
+ if (playbin->video_stream_combiner) {
+ to_use = extend_list_of_streams (playbin, GST_STREAM_TYPE_VIDEO, to_use);
+ combine_id =
+ get_combiner_stream_id (playbin,
+ &playbin->combiner[PLAYBIN_STREAM_VIDEO], streams);
+ if (combine_id != -1)
+ gst_play_bin3_set_current_video_stream (playbin, combine_id);
+ }
+ if (playbin->text_stream_combiner) {
+ to_use = extend_list_of_streams (playbin, GST_STREAM_TYPE_TEXT, to_use);
+ combine_id =
+ get_combiner_stream_id (playbin,
+ &playbin->combiner[PLAYBIN_STREAM_TEXT], streams);
+ if (combine_id != -1)
+ gst_play_bin3_set_current_text_stream (playbin, combine_id);
+ }
+
+ gst_event_unref (event);
+ return gst_event_new_select_streams (to_use);
+}
+
+static gboolean
+gst_play_bin3_send_event (GstElement * element, GstEvent * event)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (element);
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) {
+ gboolean res;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ GST_LOG_OBJECT (playbin,
+ "App sent select-streams, we won't do anything ourselves now");
+ /* This is probably already false, but it doesn't hurt to be sure */
+ playbin->do_stream_selections = FALSE;
+
+ /* If we have custom combiners, we need to extend the selection with
+ * the list of all streams for that given type since we will be handling
+ * the selection with that combiner */
+ event = update_select_streams_event (playbin, event);
+
+ /* Send this event directly to decodebin, so it works even
+ * if decodebin didn't add any pads yet */
+ res = gst_element_send_event (playbin->decodebin, event);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return res;
+ }
+
+ /* Send event directly to playsink instead of letting GstBin iterate
+ * over all sink elements. The latter might send the event multiple times
+ * in case the SEEK causes a reconfiguration of the pipeline, as can easily
+ * happen with adaptive streaming demuxers.
+ *
+ * What would then happen is that the iterator would be reset, we send the
+ * event again, and on the second time it will fail in the majority of cases
+ * because the pipeline is still being reconfigured
+ */
+ if (GST_EVENT_IS_UPSTREAM (event)) {
+ return gst_element_send_event (GST_ELEMENT_CAST (playbin->playsink), event);
+ }
+
+ return GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
+}
+
+/* Called with playbin lock held */
+static void
+do_stream_selection (GstPlayBin3 * playbin)
+{
+ GstStreamCollection *collection;
+ guint i, nb_streams;
+ GList *streams = NULL;
+ gint nb_video = 0, nb_audio = 0, nb_text = 0;
+ GstStreamType chosen_stream_types = 0;
+
+ collection = playbin->collection;
+ if (collection == NULL) {
+ GST_LOG_OBJECT (playbin, "No stream collection. Not doing stream-select");
+ return;
+ }
+
+ nb_streams = gst_stream_collection_get_size (collection);
+ if (nb_streams == 0) {
+ GST_INFO_OBJECT (playbin, "Empty collection received! Ignoring");
+ }
+
+ /* Iterate the collection and choose the streams that match
+ * either the current-* setting, or all streams of a type if there's
+ * a combiner for that type */
+ for (i = 0; i < nb_streams; i++) {
+ GstStream *stream = gst_stream_collection_get_stream (collection, i);
+ GstStreamType stream_type = gst_stream_get_stream_type (stream);
+ const gchar *stream_id = gst_stream_get_stream_id (stream);
+ gint pb_stream_type = -1;
+ gboolean select_this = FALSE;
+
+ switch (stream_type) {
+ case GST_STREAM_TYPE_AUDIO:
+ pb_stream_type = PLAYBIN_STREAM_AUDIO;
+ /* Select the stream if it's the current one or if there's a custom selector */
+ select_this =
+ (nb_audio == playbin->current_audio ||
+ (playbin->current_audio == -1 && nb_audio == 0) ||
+ playbin->audio_stream_combiner != NULL);
+ nb_audio++;
+ break;
+ case GST_STREAM_TYPE_VIDEO:
+ pb_stream_type = PLAYBIN_STREAM_AUDIO;
+ select_this =
+ (nb_video == playbin->current_video ||
+ (playbin->current_video == -1 && nb_video == 0) ||
+ playbin->video_stream_combiner != NULL);
+ nb_video++;
+ break;
+ case GST_STREAM_TYPE_TEXT:
+ pb_stream_type = PLAYBIN_STREAM_TEXT;
+ select_this =
+ (nb_text == playbin->current_text ||
+ (playbin->current_text == -1 && nb_text == 0) ||
+ playbin->text_stream_combiner != NULL);
+ nb_text++;
+ break;
+ default:
+ break;
+ }
+ if (pb_stream_type < 0) {
+ GST_DEBUG_OBJECT (playbin,
+ "Stream %d (id %s) of unhandled type %s. Ignoring", i, stream_id,
+ gst_stream_type_get_name (stream_type));
+ continue;
+ }
+ if (select_this) {
+ GST_DEBUG_OBJECT (playbin, "Selecting stream %s of type %s",
+ stream_id, gst_stream_type_get_name (stream_type));
+ /* Don't build the list if we're not in charge of stream selection */
+ if (playbin->do_stream_selections)
+ streams = g_list_append (streams, (gpointer) stream_id);
+ chosen_stream_types |= stream_type;
+ }
+ }
+
+ if (streams) {
+ GstEvent *ev = gst_event_new_select_streams (streams);
+ gst_element_send_event (playbin->decodebin, ev);
+ g_list_free (streams);
+ }
+ playbin->selected_stream_types = chosen_stream_types;
+}
+
+/* mime types we are not handling on purpose right now, don't post a
+ * missing-plugin message for these */
+static const gchar *blacklisted_mimes[] = {
+ NULL
+};
+
+static void
+notify_all_streams (GstPlayBin3 * playbin, GstStreamCollection * collection)
+{
+ gint i, nb_streams;
+ nb_streams = gst_stream_collection_get_size (collection);
+ for (i = 0; i < nb_streams; i++)
+ notify_tags_for_stream (playbin, collection,
+ gst_stream_collection_get_stream (collection, i));
+}
+
+static void
+gst_play_bin3_handle_message (GstBin * bin, GstMessage * msg)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (bin);
+
+ if (gst_is_missing_plugin_message (msg)) {
+ gchar *detail;
+ guint i;
+
+ detail = gst_missing_plugin_message_get_installer_detail (msg);
+ for (i = 0; detail != NULL && blacklisted_mimes[i] != NULL; ++i) {
+ if (strstr (detail, "|decoder-") && strstr (detail, blacklisted_mimes[i])) {
+ GST_LOG_OBJECT (bin, "suppressing message %" GST_PTR_FORMAT, msg);
+ gst_message_unref (msg);
+ g_free (detail);
+ return;
+ }
+ }
+ g_free (detail);
+ } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_START) {
+ GstSourceGroup *new_group = playbin->curr_group;
+ GstMessage *buffering_msg = NULL;
+
+ GST_SOURCE_GROUP_LOCK (new_group);
+ new_group->stream_changed_pending = FALSE;
+ if (new_group->pending_buffering_msg) {
+ buffering_msg = new_group->pending_buffering_msg;
+ new_group->pending_buffering_msg = NULL;
+ }
+ GST_SOURCE_GROUP_UNLOCK (new_group);
+
+ GST_DEBUG_OBJECT (playbin, "Stream start from new group %p", new_group);
+
+ if (buffering_msg) {
+ GST_DEBUG_OBJECT (playbin, "Posting pending buffering message: %"
+ GST_PTR_FORMAT, buffering_msg);
+ GST_BIN_CLASS (parent_class)->handle_message (bin, buffering_msg);
+ }
+
+ } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_BUFFERING) {
+ GstSourceGroup *group = playbin->curr_group;
+ gboolean pending;
+
+ /* drop buffering messages from child queues while we are switching
+ * groups (because the application set a new uri in about-to-finish)
+ * if the playsink queue still has buffers to play */
+
+ GST_SOURCE_GROUP_LOCK (group);
+ pending = group->stream_changed_pending;
+
+ if (pending) {
+ GST_DEBUG_OBJECT (playbin, "Storing buffering message from pending group "
+ "%p %" GST_PTR_FORMAT, group, msg);
+ gst_message_replace (&group->pending_buffering_msg, msg);
+ gst_message_unref (msg);
+ msg = NULL;
+ }
+ GST_SOURCE_GROUP_UNLOCK (group);
+ } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_COLLECTION) {
+ GstStreamCollection *collection = NULL;
+ GstObject *src = GST_MESSAGE_SRC (msg);
+ gboolean pstate = playbin->do_stream_selections;
+
+ gst_message_parse_stream_collection (msg, &collection);
+
+ if (collection) {
+ GST_PLAY_BIN3_LOCK (playbin);
+ GST_DEBUG_OBJECT (playbin,
+ "STREAM_COLLECTION: Got a collection from %" GST_PTR_FORMAT, src);
+ if (playbin->collection && playbin->collection_notify_id) {
+ g_signal_handler_disconnect (playbin->collection,
+ playbin->collection_notify_id);
+ playbin->collection_notify_id = 0;
+ }
+ gst_object_replace ((GstObject **) & playbin->collection,
+ (GstObject *) collection);
+ playbin->collection_notify_id =
+ g_signal_connect (collection, "stream-notify::tags",
+ (GCallback) notify_tags_cb, playbin);
+ update_combiner_info (playbin);
+ if (pstate)
+ playbin->do_stream_selections = FALSE;
+ do_stream_selection (playbin);
+ if (pstate)
+ playbin->do_stream_selections = TRUE;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ notify_all_streams (playbin, collection);
+ }
+ } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAMS_SELECTED) {
+ GstStreamCollection *collection = NULL;
+ GstObject *src = GST_MESSAGE_SRC (msg);
+ gboolean pstate = playbin->do_stream_selections;
+
+ gst_message_parse_streams_selected (msg, &collection);
+ if (collection) {
+ guint i, len;
+ GST_PLAY_BIN3_LOCK (playbin);
+ GST_DEBUG_OBJECT (playbin,
+ "STREAMS_SELECTED: Got a collection from %" GST_PTR_FORMAT, src);
+ if (playbin->collection && playbin->collection_notify_id) {
+ g_signal_handler_disconnect (playbin->collection,
+ playbin->collection_notify_id);
+ playbin->collection_notify_id = 0;
+ }
+ gst_object_replace ((GstObject **) & playbin->collection,
+ (GstObject *) collection);
+ playbin->collection_notify_id =
+ g_signal_connect (collection, "stream-notify::tags",
+ (GCallback) notify_tags_cb, playbin);
+ update_combiner_info (playbin);
+ len = gst_message_streams_selected_get_size (msg);
+ for (i = 0; i < len; i++) {
+ set_selected_stream (playbin,
+ gst_message_streams_selected_get_stream (msg, i));
+ }
+ if (pstate)
+ playbin->do_stream_selections = FALSE;
+ do_stream_selection (playbin);
+ if (pstate)
+ playbin->do_stream_selections = TRUE;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ notify_all_streams (playbin, collection);
+ }
+ }
+
+ if (msg)
+ GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
+}
+
+static void
+combiner_active_pad_changed (GObject * combiner, GParamSpec * pspec,
+ GstPlayBin3 * playbin)
+{
+ const gchar *property;
+ GstSourceCombine *combine = NULL;
+ GPtrArray *channels = NULL;
+ int i;
+
+ GST_PLAY_BIN3_LOCK (playbin);
+
+ for (i = 0; i < PLAYBIN_STREAM_LAST; i++) {
+ if (combiner == G_OBJECT (playbin->combiner[i].combiner)) {
+ combine = &playbin->combiner[i];
+ channels = playbin->channels[i];
+ }
+ }
+
+ /* We got a pad-change after our group got switched out; no need to notify */
+ if (!combine) {
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ return;
+ }
+
+ switch (combine->type) {
+ case GST_PLAY_SINK_TYPE_VIDEO:
+ case GST_PLAY_SINK_TYPE_VIDEO_RAW:
+ property = "current-video";
+ playbin->current_video = get_current_stream_number (playbin,
+ combine, channels);
+
+ if (playbin->video_pending_flush_finish) {
+ playbin->video_pending_flush_finish = FALSE;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ gst_play_bin3_send_custom_event (GST_OBJECT (combiner),
+ "playsink-custom-video-flush-finish");
+ goto notify;
+ }
+ break;
+ case GST_PLAY_SINK_TYPE_AUDIO:
+ case GST_PLAY_SINK_TYPE_AUDIO_RAW:
+ property = "current-audio";
+ playbin->current_audio = get_current_stream_number (playbin,
+ combine, channels);
+
+ if (playbin->audio_pending_flush_finish) {
+ playbin->audio_pending_flush_finish = FALSE;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ gst_play_bin3_send_custom_event (GST_OBJECT (combiner),
+ "playsink-custom-audio-flush-finish");
+ goto notify;
+ }
+ break;
+ case GST_PLAY_SINK_TYPE_TEXT:
+ property = "current-text";
+ playbin->current_text = get_current_stream_number (playbin,
+ combine, channels);
+
+ if (playbin->text_pending_flush_finish) {
+ playbin->text_pending_flush_finish = FALSE;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ gst_play_bin3_send_custom_event (GST_OBJECT (combiner),
+ "playsink-custom-subtitle-flush-finish");
+ goto notify;
+ }
+ break;
+ default:
+ property = NULL;
+ }
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+notify:
+ if (property)
+ g_object_notify (G_OBJECT (playbin), property);
+}
+
+static GstCaps *
+update_video_multiview_caps (GstPlayBin3 * playbin, GstCaps * caps)
+{
+ GstVideoMultiviewMode mv_mode;
+ GstVideoMultiviewMode cur_mv_mode;
+ GstVideoMultiviewFlags mv_flags, cur_mv_flags;
+ GstStructure *s;
+ const gchar *mview_mode_str;
+ GstCaps *out_caps;
+
+ GST_OBJECT_LOCK (playbin);
+ mv_mode = (GstVideoMultiviewMode) playbin->multiview_mode;
+ mv_flags = playbin->multiview_flags;
+ GST_OBJECT_UNLOCK (playbin);
+
+ if (mv_mode == GST_VIDEO_MULTIVIEW_MODE_NONE)
+ return NULL;
+
+ cur_mv_mode = GST_VIDEO_MULTIVIEW_MODE_NONE;
+ cur_mv_flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
+
+ s = gst_caps_get_structure (caps, 0);
+
+ gst_structure_get_flagset (s, "multiview-flags", &cur_mv_flags, NULL);
+ if ((mview_mode_str = gst_structure_get_string (s, "multiview-mode")))
+ cur_mv_mode = gst_video_multiview_mode_from_caps_string (mview_mode_str);
+
+ /* We can't override an existing annotated multiview mode, except
+ * maybe (in the future) we could change some flags. */
+ if ((gint) cur_mv_mode > GST_VIDEO_MULTIVIEW_MAX_FRAME_PACKING) {
+ GST_INFO_OBJECT (playbin, "Cannot override existing multiview mode");
+ return NULL;
+ }
+
+ mview_mode_str = gst_video_multiview_mode_to_caps_string (mv_mode);
+ g_assert (mview_mode_str != NULL);
+ out_caps = gst_caps_copy (caps);
+ s = gst_caps_get_structure (out_caps, 0);
+
+ gst_structure_set (s, "multiview-mode", G_TYPE_STRING, mview_mode_str,
+ "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, mv_flags,
+ GST_FLAG_SET_MASK_EXACT, NULL);
+
+ return out_caps;
+}
+
+static GstPadProbeReturn
+_decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata)
+{
+ GstPadProbeReturn ret = GST_PAD_PROBE_OK;
+ GstPlayBin3 *playbin = (GstPlayBin3 *) udata;
+ GstEvent *event = GST_PAD_PROBE_INFO_DATA (info);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_CAPS:{
+ GstCaps *caps = NULL;
+ const GstStructure *s;
+ const gchar *name;
+
+ gst_event_parse_caps (event, &caps);
+ /* If video caps, check if we should override multiview flags */
+ s = gst_caps_get_structure (caps, 0);
+ name = gst_structure_get_name (s);
+ if (g_str_has_prefix (name, "video/")) {
+ caps = update_video_multiview_caps (playbin, caps);
+ if (caps) {
+ gst_event_unref (event);
+ event = gst_event_new_caps (caps);
+ GST_PAD_PROBE_INFO_DATA (info) = event;
+ gst_caps_unref (caps);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static gint
+find_index_for_stream_by_type (GstStreamCollection * collection,
+ GstStream * stream)
+{
+ gint nb_streams = gst_stream_collection_get_size (collection);
+ gint i, cur_idx = 0;
+ GstStreamType target_type = gst_stream_get_stream_type (stream);
+
+ /* Count the streams of the type we want to find the index of the one we want */
+ for (i = 0; i < nb_streams; i++) {
+ GstStream *cur_stream = gst_stream_collection_get_stream (collection, i);
+ if (stream == cur_stream)
+ return cur_idx;
+ if (gst_stream_get_stream_type (cur_stream) == target_type)
+ cur_idx++;
+ }
+
+ return -1;
+}
+
+static void
+notify_tags_for_stream (GstPlayBin3 * playbin, GstStreamCollection * collection,
+ GstStream * stream)
+{
+ GstStreamType stream_type = gst_stream_get_stream_type (stream);
+ gint stream_idx = find_index_for_stream_by_type (collection, stream);
+ gint signal;
+
+ GST_DEBUG_OBJECT (playbin, "Tags on stream %" GST_PTR_FORMAT
+ " with stream idx %d and type %d have changed",
+ stream, stream_idx, stream_type);
+
+ switch (stream_type) {
+ case GST_STREAM_TYPE_VIDEO:
+ signal = SIGNAL_VIDEO_TAGS_CHANGED;
+ break;
+ case GST_STREAM_TYPE_AUDIO:
+ signal = SIGNAL_AUDIO_TAGS_CHANGED;
+ break;
+ case GST_STREAM_TYPE_TEXT:
+ signal = SIGNAL_TEXT_TAGS_CHANGED;
+ break;
+ default:
+ signal = -1;
+ break;
+ }
+
+ if (signal >= 0)
+ g_signal_emit (G_OBJECT (playbin), gst_play_bin3_signals[signal], 0,
+ stream_idx);
+}
+
+static void
+notify_tags_cb (GstStreamCollection * collection, GstStream * stream,
+ GParamSpec * pspec, GstPlayBin3 * playbin)
+{
+ notify_tags_for_stream (playbin, collection, stream);
+}
+
+/* this function is called when a new pad is added to decodebin. We check the
+ * type of the pad and add it to the combiner element
+ */
+static void
+pad_added_cb (GstElement * decodebin, GstPad * pad, GstPlayBin3 * playbin)
+{
+ GstPad *sinkpad;
+ GstPadLinkReturn res;
+ GstSourceCombine *combine = NULL;
+ GstStreamType stream_type;
+ gint pb_stream_type = -1;
+ gboolean changed = FALSE;
+ GstElement *custom_combiner = NULL;
+ gulong event_probe_handler;
+ gchar *pad_name;
+
+ GST_PLAY_BIN3_SHUTDOWN_LOCK (playbin, shutdown);
+
+ pad_name = gst_object_get_name (GST_OBJECT (pad));
+
+ GST_DEBUG_OBJECT (playbin, "decoded pad %s:%s added",
+ GST_DEBUG_PAD_NAME (pad));
+
+ /* major type of the pad, this determines the combiner to use,
+ try exact match first */
+ if (g_str_has_prefix (pad_name, "video")) {
+ stream_type = GST_STREAM_TYPE_VIDEO;
+ pb_stream_type = PLAYBIN_STREAM_VIDEO;
+ custom_combiner = playbin->video_stream_combiner;
+ } else if (g_str_has_prefix (pad_name, "audio")) {
+ stream_type = GST_STREAM_TYPE_AUDIO;
+ pb_stream_type = PLAYBIN_STREAM_AUDIO;
+ custom_combiner = playbin->audio_stream_combiner;
+ } else if (g_str_has_prefix (pad_name, "text")) {
+ stream_type = GST_STREAM_TYPE_TEXT;
+ pb_stream_type = PLAYBIN_STREAM_TEXT;
+ custom_combiner = playbin->text_stream_combiner;
+ }
+
+ g_free (pad_name);
+
+ /* no stream type found for the media type, don't bother linking it to a
+ * combiner. This will leave the pad unlinked and thus ignored. */
+ if (pb_stream_type < 0) {
+ GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin);
+ goto unknown_type;
+ }
+
+ combine = &playbin->combiner[pb_stream_type];
+
+ if (custom_combiner && combine->combiner == NULL) {
+ combine->combiner = custom_combiner;
+ /* find out which properties the stream combiner supports */
+ combine->has_active_pad =
+ g_object_class_find_property (G_OBJECT_GET_CLASS (combine->combiner),
+ "active-pad") != NULL;
+
+ if (!custom_combiner) {
+ /* sync-mode=1, use clock */
+ if (combine->type == GST_PLAY_SINK_TYPE_TEXT)
+ g_object_set (combine->combiner, "sync-streams", TRUE,
+ "sync-mode", 1, "cache-buffers", TRUE, NULL);
+ else
+ g_object_set (combine->combiner, "sync-streams", TRUE, NULL);
+ }
+
+ if (combine->has_active_pad)
+ g_signal_connect (combine->combiner, "notify::active-pad",
+ G_CALLBACK (combiner_active_pad_changed), playbin);
+
+ GST_DEBUG_OBJECT (playbin, "adding new stream combiner %p",
+ combine->combiner);
+ gst_element_set_state (combine->combiner, GST_STATE_PAUSED);
+ gst_bin_add (GST_BIN_CAST (playbin), combine->combiner);
+ }
+
+ GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin);
+
+ if (combine->srcpad == NULL) {
+ if (combine->combiner) {
+ /* save source pad of the combiner */
+ combine->srcpad = gst_element_get_static_pad (combine->combiner, "src");
+ } else {
+ /* no combiner, use the pad as the source pad then */
+ combine->srcpad = gst_object_ref (pad);
+ }
+
+ /* block the combiner srcpad. It's possible that multiple source elements
+ * pushing data into the combiners before we have a chance to collect all
+ * streams and connect the sinks, resulting in not-linked errors. After we
+ * configure the sinks we will unblock them all. */
+ GST_DEBUG_OBJECT (playbin, "blocking %" GST_PTR_FORMAT, combine->srcpad);
+ combine->block_id =
+ gst_pad_add_probe (combine->srcpad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+ block_serialized_data_cb, NULL, NULL);
+ }
+
+ /* get sinkpad for the new stream */
+ if (combine->combiner) {
+ if ((sinkpad = gst_element_get_request_pad (combine->combiner, "sink_%u"))) {
+ GST_DEBUG_OBJECT (playbin, "got pad %s:%s from combiner",
+ GST_DEBUG_PAD_NAME (sinkpad));
+
+ /* find out which properties the sink pad supports */
+ combine->has_always_ok =
+ g_object_class_find_property (G_OBJECT_GET_CLASS (sinkpad),
+ "always-ok") != NULL;
+
+ /* store the combiner for the pad */
+ g_object_set_data (G_OBJECT (sinkpad), "playbin.combine", combine);
+
+ /* store the pad in the array */
+ GST_DEBUG_OBJECT (playbin, "pad %p added to array", sinkpad);
+ g_ptr_array_add (combine->channels, sinkpad);
+
+ res = gst_pad_link (pad, sinkpad);
+ if (GST_PAD_LINK_FAILED (res))
+ goto link_failed;
+
+ /* store combiner pad so we can release it */
+ g_object_set_data (G_OBJECT (pad), "playbin.sinkpad", sinkpad);
+
+ changed = TRUE;
+ GST_DEBUG_OBJECT (playbin, "linked pad %s:%s to combiner %p",
+ GST_DEBUG_PAD_NAME (pad), combine->combiner);
+ } else {
+ goto request_pad_failed;
+ }
+ } else {
+ /* no combiner, don't configure anything, we'll link the new pad directly to
+ * the sink. */
+ changed = FALSE;
+ sinkpad = NULL;
+
+ /* store the combiner for the pad */
+ g_object_set_data (G_OBJECT (pad), "playbin.combine", combine);
+ }
+
+ event_probe_handler =
+ gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ _decodebin_event_probe, playbin, NULL);
+ g_object_set_data (G_OBJECT (pad), "playbin.event_probe_id",
+ ULONG_TO_POINTER (event_probe_handler));
+
+ if (changed) {
+ int signal;
+
+ switch (combine->type) {
+ case GST_PLAY_SINK_TYPE_VIDEO:
+ case GST_PLAY_SINK_TYPE_VIDEO_RAW:
+ signal = SIGNAL_VIDEO_CHANGED;
+ break;
+ case GST_PLAY_SINK_TYPE_AUDIO:
+ case GST_PLAY_SINK_TYPE_AUDIO_RAW:
+ signal = SIGNAL_AUDIO_CHANGED;
+ break;
+ case GST_PLAY_SINK_TYPE_TEXT:
+ signal = SIGNAL_TEXT_CHANGED;
+ break;
+ default:
+ signal = -1;
+ }
+
+ if (signal >= 0) {
+ g_signal_emit (G_OBJECT (playbin), gst_play_bin3_signals[signal], 0,
+ NULL);
+ }
+ }
+
+ playbin->active_stream_types |= stream_type;
+
+ /* If we're expecting either audio or video,
+ * wait for them to appear before configuring playsink */
+ if ((playbin->selected_stream_types & ~playbin->active_stream_types &
+ (GST_STREAM_TYPE_VIDEO | GST_STREAM_TYPE_AUDIO))
+ == 0) {
+ no_more_pads_cb (decodebin, playbin);
+ } else {
+ GST_LOG_OBJECT (playbin, "Active stream types 0x%x, want 0x%x. Waiting",
+ playbin->active_stream_types, playbin->selected_stream_types);
+ }
+
+ return;
+
+ /* ERRORS */
+unknown_type:
+ GST_DEBUG_OBJECT (playbin, "Ignoring pad with unknown type");
+ return;
+
+link_failed:
+ {
+ GST_ERROR_OBJECT (playbin,
+ "failed to link pad %s:%s to combiner, reason %s (%d)",
+ GST_DEBUG_PAD_NAME (pad), gst_pad_link_get_name (res), res);
+ return;
+ }
+request_pad_failed:
+ GST_ELEMENT_ERROR (playbin, CORE, PAD,
+ ("Internal playbin error."),
+ ("Failed to get request pad from combiner %p.", combine->combiner));
+ return;
+shutdown:
+ {
+ GST_DEBUG ("ignoring, we are shutting down. Pad will be left unlinked");
+ /* not going to done as we didn't request the caps */
+ return;
+ }
+}
+
+/* called when a pad is removed from the decodebin. We unlink the pad from
+ * the combiner. This will make the combiner select a new pad. */
+static void
+pad_removed_cb (GstElement * decodebin, GstPad * pad, GstPlayBin3 * playbin)
+{
+ GstPad *peer;
+ GstElement *combiner;
+ GstSourceCombine *combine;
+ int signal = -1;
+ gulong event_probe_handler;
+
+ GST_DEBUG_OBJECT (playbin,
+ "decoded pad %s:%s removed", GST_DEBUG_PAD_NAME (pad));
+
+ GST_PLAY_BIN3_LOCK (playbin);
+
+ if ((event_probe_handler =
+ POINTER_TO_ULONG (g_object_get_data (G_OBJECT (pad),
+ "playbin.event_probe_id")))) {
+ gst_pad_remove_probe (pad, event_probe_handler);
+ g_object_set_data (G_OBJECT (pad), "playbin.event_probe_id", NULL);
+ }
+
+ if ((combine = g_object_get_data (G_OBJECT (pad), "playbin.combine"))) {
+ g_assert (combine->combiner == NULL);
+ g_assert (combine->srcpad == pad);
+ source_combine_remove_pads (playbin, combine);
+ goto exit;
+ }
+
+ /* get the combiner sinkpad */
+ if (!(peer = g_object_get_data (G_OBJECT (pad), "playbin.sinkpad")))
+ goto not_linked;
+
+ /* unlink the pad now (can fail, the pad is unlinked before it's removed) */
+ gst_pad_unlink (pad, peer);
+
+ /* get combiner */
+ combiner = GST_ELEMENT_CAST (gst_pad_get_parent (peer));
+ g_assert (combiner != NULL);
+
+ if ((combine = g_object_get_data (G_OBJECT (peer), "playbin.combine"))) {
+ /* remove the pad from the array */
+ g_ptr_array_remove (combine->channels, peer);
+ GST_DEBUG_OBJECT (playbin, "pad %p removed from array", peer);
+
+ /* get the correct type-changed signal */
+ switch (combine->type) {
+ case GST_PLAY_SINK_TYPE_VIDEO:
+ case GST_PLAY_SINK_TYPE_VIDEO_RAW:
+ signal = SIGNAL_VIDEO_CHANGED;
+ break;
+ case GST_PLAY_SINK_TYPE_AUDIO:
+ case GST_PLAY_SINK_TYPE_AUDIO_RAW:
+ signal = SIGNAL_AUDIO_CHANGED;
+ break;
+ case GST_PLAY_SINK_TYPE_TEXT:
+ signal = SIGNAL_TEXT_CHANGED;
+ break;
+ default:
+ signal = -1;
+ }
+
+ if (!combine->channels->len && combine->combiner) {
+ GST_DEBUG_OBJECT (playbin, "all combiner sinkpads removed");
+ GST_DEBUG_OBJECT (playbin, "removing combiner %p", combine->combiner);
+ source_combine_remove_pads (playbin, combine);
+ gst_element_set_state (combine->combiner, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (playbin), combine->combiner);
+ combine->combiner = NULL;
+ }
+ }
+
+ /* release the pad to the combiner, this will make the combiner choose a new
+ * pad. */
+ gst_element_release_request_pad (combiner, peer);
+ gst_object_unref (peer);
+
+ gst_object_unref (combiner);
+exit:
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ if (signal >= 0)
+ g_signal_emit (G_OBJECT (playbin), gst_play_bin3_signals[signal], 0, NULL);
+
+ return;
+
+ /* ERRORS */
+not_linked:
+ {
+ GST_DEBUG_OBJECT (playbin, "pad not linked");
+ goto exit;
+ }
+}
+
+
+static gint
+select_stream_cb (GstElement * decodebin, GstStreamCollection * collection,
+ GstStream * stream, GstPlayBin3 * playbin)
+{
+ GstStreamType stype = gst_stream_get_stream_type (stream);
+ GstElement *combiner = NULL;
+
+ switch (stype) {
+ case GST_STREAM_TYPE_AUDIO:
+ combiner = playbin->audio_stream_combiner;
+ break;
+ case GST_STREAM_TYPE_VIDEO:
+ combiner = playbin->video_stream_combiner;
+ break;
+ case GST_STREAM_TYPE_TEXT:
+ combiner = playbin->text_stream_combiner;
+ break;
+ default:
+ break;
+ }
+
+ if (combiner) {
+ GST_DEBUG_OBJECT (playbin, "Got a combiner, requesting stream activation");
+ return 1;
+ }
+
+ /* Let decodebin3 decide otherwise */
+ return -1;
+}
+
+/* we get called when all pads are available and we must connect the sinks to
+ * them.
+ * The main purpose of the code is to see if we have video/audio and subtitles
+ * and pick the right pipelines to display them.
+ *
+ * The combiners installed on the group tell us about the presence of
+ * audio/video and subtitle streams. This allows us to see if we need
+ * visualisation, video or/and audio.
+ */
+static void
+no_more_pads_cb (GstElement * decodebin, GstPlayBin3 * playbin)
+{
+ GstSourceGroup *group;
+ GstPadLinkReturn res;
+ gint i;
+ gboolean configure;
+
+ GST_DEBUG_OBJECT (playbin, "no more pads");
+
+ GST_PLAY_BIN3_SHUTDOWN_LOCK (playbin, shutdown);
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ group = playbin->curr_group;
+
+ for (i = 0; i < PLAYBIN_STREAM_LAST; i++) {
+ GstSourceCombine *combine = &playbin->combiner[i];
+
+ /* check if the specific media type was detected and thus has a combiner
+ * created for it. If there is the media type, get a sinkpad from the sink
+ * and link it. We only do this if we have not yet requested the sinkpad
+ * before. */
+ if (combine->srcpad && combine->sinkpad == NULL) {
+ GST_DEBUG_OBJECT (playbin, "requesting new sink pad %d", combine->type);
+ combine->sinkpad =
+ gst_play_sink_request_pad (playbin->playsink, combine->type);
+ gst_object_ref (combine->sinkpad);
+ } else if (combine->srcpad && combine->sinkpad) {
+ GST_DEBUG_OBJECT (playbin, "re-using sink pad %d", combine->type);
+ } else if (combine->sinkpad && combine->srcpad == NULL) {
+ GST_DEBUG_OBJECT (playbin, "releasing sink pad %d", combine->type);
+ gst_play_sink_release_pad (playbin->playsink, combine->sinkpad);
+ gst_object_unref (combine->sinkpad);
+ combine->sinkpad = NULL;
+ }
+ if (combine->sinkpad && combine->srcpad &&
+ !gst_pad_is_linked (combine->srcpad)) {
+ res = gst_pad_link (combine->srcpad, combine->sinkpad);
+ GST_DEBUG_OBJECT (playbin, "linked type %s, result: %d",
+ combine->media_type, res);
+ if (res != GST_PAD_LINK_OK) {
+ GST_ELEMENT_ERROR (playbin, CORE, PAD,
+ ("Internal playbin error."),
+ ("Failed to link combiner to sink. Error %d", res));
+ }
+ }
+ }
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ GST_SOURCE_GROUP_LOCK (group);
+ GST_DEBUG_OBJECT (playbin, "pending %d > %d", group->pending,
+ group->pending - 1);
+
+ if (group->pending > 0)
+ group->pending--;
+
+ if (group->pending == 0) {
+ /* we are the last group to complete, we will configure the output and then
+ * signal the other waiters. */
+ GST_LOG_OBJECT (playbin, "last group complete");
+ configure = TRUE;
+ } else {
+ GST_LOG_OBJECT (playbin, "have more pending groups");
+ configure = FALSE;
+ }
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ if (configure) {
+ /* if we have custom sinks, configure them now */
+ GST_SOURCE_GROUP_LOCK (group);
+
+ if (group->audio_sink) {
+ GST_INFO_OBJECT (playbin, "setting custom audio sink %" GST_PTR_FORMAT,
+ group->audio_sink);
+ gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_AUDIO,
+ group->audio_sink);
+ }
+
+ if (group->video_sink) {
+ GST_INFO_OBJECT (playbin, "setting custom video sink %" GST_PTR_FORMAT,
+ group->video_sink);
+ gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_VIDEO,
+ group->video_sink);
+ }
+
+ if (group->text_sink) {
+ GST_INFO_OBJECT (playbin, "setting custom text sink %" GST_PTR_FORMAT,
+ group->text_sink);
+ gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_TEXT,
+ group->text_sink);
+ }
+
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ /* signal the other combiners that they can continue now. */
+ GST_PLAY_BIN3_LOCK (playbin);
+ /* unblock all combiners */
+ for (i = 0; i < PLAYBIN_STREAM_LAST; i++) {
+ GstSourceCombine *combine = &playbin->combiner[i];
+
+ if (combine->srcpad) {
+ GST_DEBUG_OBJECT (playbin, "unblocking %" GST_PTR_FORMAT,
+ combine->srcpad);
+ if (combine->block_id) {
+ gst_pad_remove_probe (combine->srcpad, combine->block_id);
+ combine->block_id = 0;
+ }
+ }
+ }
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ gst_play_sink_reconfigure (playbin->playsink);
+ }
+
+ GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin);
+
+ if (configure) {
+ do_async_done (playbin);
+ }
+
+ return;
+
+shutdown:
+ {
+ GST_DEBUG ("ignoring, we are shutting down");
+ /* Request a flushing pad from playsink that we then link to the combiner.
+ * Then we unblock the combiners so that they stop with a WRONG_STATE
+ * instead of a NOT_LINKED error.
+ */
+ GST_PLAY_BIN3_LOCK (playbin);
+ for (i = 0; i < PLAYBIN_STREAM_LAST; i++) {
+ GstSourceCombine *combine = &playbin->combiner[i];
+
+ if (combine->srcpad) {
+ if (combine->sinkpad == NULL) {
+ GST_DEBUG_OBJECT (playbin, "requesting new flushing sink pad");
+ combine->sinkpad =
+ gst_play_sink_request_pad (playbin->playsink,
+ GST_PLAY_SINK_TYPE_FLUSHING);
+ gst_object_ref (combine->sinkpad);
+ res = gst_pad_link (combine->srcpad, combine->sinkpad);
+ GST_DEBUG_OBJECT (playbin, "linked flushing, result: %d", res);
+ }
+ GST_DEBUG_OBJECT (playbin, "unblocking %" GST_PTR_FORMAT,
+ combine->srcpad);
+ if (combine->block_id) {
+ gst_pad_remove_probe (combine->srcpad, combine->block_id);
+ combine->block_id = 0;
+ }
+ }
+ }
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ return;
+ }
+}
+
+#if 0
+static void
+drained_cb (GstElement * decodebin, GstSourceGroup * group)
+{
+ GstPlayBin3 *playbin;
+
+ playbin = group->playbin;
+
+ GST_DEBUG_OBJECT (playbin, "about to finish in group %p", group);
+
+ /* after this call, we should have a next group to activate or we EOS */
+ g_signal_emit (G_OBJECT (playbin),
+ gst_play_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL);
+
+ /* now activate the next group. If the app did not set a uri, this will
+ * fail and we can do EOS */
+ setup_next_source (playbin, GST_STATE_PAUSED);
+}
+#endif
+
+/* Like gst_element_factory_can_sink_any_caps() but doesn't
+ * allow ANY caps on the sinkpad template */
+static gboolean
+_factory_can_sink_caps (GstElementFactory * factory, GstCaps * caps)
+{
+ const GList *templs;
+
+ templs = gst_element_factory_get_static_pad_templates (factory);
+
+ while (templs) {
+ GstStaticPadTemplate *templ = (GstStaticPadTemplate *) templs->data;
+
+ if (templ->direction == GST_PAD_SINK) {
+ GstCaps *templcaps = gst_static_caps_get (&templ->static_caps);
+
+ if (!gst_caps_is_any (templcaps)
+ && gst_caps_is_subset (caps, templcaps)) {
+ gst_caps_unref (templcaps);
+ return TRUE;
+ }
+ gst_caps_unref (templcaps);
+ }
+ templs = g_list_next (templs);
+ }
+
+ return FALSE;
+}
+
+static void
+avelements_free (gpointer avelement)
+{
+ GstAVElement *elm = (GstAVElement *) avelement;
+
+ if (elm->dec)
+ gst_object_unref (elm->dec);
+ if (elm->sink)
+ gst_object_unref (elm->sink);
+ g_slice_free (GstAVElement, elm);
+}
+
+static gint
+avelement_compare_decoder (gconstpointer p1, gconstpointer p2,
+ gpointer user_data)
+{
+ GstAVElement *v1, *v2;
+
+ v1 = (GstAVElement *) p1;
+ v2 = (GstAVElement *) p2;
+
+ return strcmp (GST_OBJECT_NAME (v1->dec), GST_OBJECT_NAME (v2->dec));
+}
+
+static gint
+avelement_lookup_decoder (gconstpointer p1, gconstpointer p2,
+ gpointer user_data)
+{
+ GstAVElement *v1;
+ GstElementFactory *f2;
+
+ v1 = (GstAVElement *) p1;
+ f2 = (GstElementFactory *) p2;
+
+ return strcmp (GST_OBJECT_NAME (v1->dec), GST_OBJECT_NAME (f2));
+}
+
+static gint
+avelement_compare (gconstpointer p1, gconstpointer p2)
+{
+ GstAVElement *v1, *v2;
+ GstPluginFeature *fd1, *fd2, *fs1, *fs2;
+ gint64 diff, v1_rank, v2_rank;
+
+ v1 = (GstAVElement *) p1;
+ v2 = (GstAVElement *) p2;
+
+ fd1 = (GstPluginFeature *) v1->dec;
+ fd2 = (GstPluginFeature *) v2->dec;
+
+ /* If both have a sink, we also compare their ranks */
+ if (v1->sink && v2->sink) {
+ fs1 = (GstPluginFeature *) v1->sink;
+ fs2 = (GstPluginFeature *) v2->sink;
+ v1_rank =
+ gst_plugin_feature_get_rank (fd1) * gst_plugin_feature_get_rank (fs1);
+ v2_rank =
+ gst_plugin_feature_get_rank (fd2) * gst_plugin_feature_get_rank (fs2);
+ } else {
+ v1_rank = gst_plugin_feature_get_rank (fd1);
+ v2_rank = gst_plugin_feature_get_rank (fd2);
+ fs1 = fs2 = NULL;
+ }
+
+ /* comparison based on the rank */
+ diff = v2_rank - v1_rank;
+ if (diff < 0)
+ return -1;
+ else if (diff > 0)
+ return 1;
+
+ /* comparison based on number of common caps features */
+ diff = v2->n_comm_cf - v1->n_comm_cf;
+ if (diff != 0)
+ return diff;
+
+ if (fs1 && fs2) {
+ /* comparison based on the name of sink elements */
+ diff = strcmp (GST_OBJECT_NAME (fs1), GST_OBJECT_NAME (fs2));
+ if (diff != 0)
+ return diff;
+ }
+
+ /* comparison based on the name of decoder elements */
+ return strcmp (GST_OBJECT_NAME (fd1), GST_OBJECT_NAME (fd2));
+}
+
+static GSequence *
+avelements_create (GstPlayBin3 * playbin, gboolean isaudioelement)
+{
+ GstElementFactory *d_factory, *s_factory;
+ GList *dec_list, *sink_list, *dl, *sl;
+ GSequence *ave_seq = NULL;
+ GstAVElement *ave;
+ guint n_common_cf = 0;
+
+ if (isaudioelement) {
+ sink_list = gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_SINK |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, GST_RANK_MARGINAL);
+ dec_list =
+ gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DECODER
+ | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, GST_RANK_MARGINAL);
+ } else {
+ sink_list = gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_SINK |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE, GST_RANK_MARGINAL);
+
+ dec_list =
+ gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DECODER
+ | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE, GST_RANK_MARGINAL);
+ }
+
+ /* create a list of audio/video elements. Each element in the list
+ * is holding an audio/video decoder and an audio/video sink in which
+ * the decoders srcpad template caps and sink element's sinkpad template
+ * caps are compatible */
+ dl = dec_list;
+ sl = sink_list;
+
+ ave_seq = g_sequence_new ((GDestroyNotify) avelements_free);
+
+ for (; dl; dl = dl->next) {
+ d_factory = (GstElementFactory *) dl->data;
+ for (; sl; sl = sl->next) {
+ s_factory = (GstElementFactory *) sl->data;
+
+ n_common_cf =
+ gst_playback_utils_get_n_common_capsfeatures (d_factory, s_factory,
+ gst_play_bin3_get_flags (playbin), isaudioelement);
+ if (n_common_cf < 1)
+ continue;
+
+ ave = g_slice_new (GstAVElement);
+ ave->dec = gst_object_ref (d_factory);
+ ave->sink = gst_object_ref (s_factory);
+ ave->n_comm_cf = n_common_cf;
+ g_sequence_append (ave_seq, ave);
+ }
+ sl = sink_list;
+ }
+ g_sequence_sort (ave_seq, (GCompareDataFunc) avelement_compare_decoder, NULL);
+
+ gst_plugin_feature_list_free (dec_list);
+ gst_plugin_feature_list_free (sink_list);
+
+ return ave_seq;
+}
+
+static gboolean
+avelement_iter_is_equal (GSequenceIter * iter, GstElementFactory * factory)
+{
+ GstAVElement *ave;
+
+ if (!iter)
+ return FALSE;
+
+ ave = g_sequence_get (iter);
+ if (!ave)
+ return FALSE;
+
+ return strcmp (GST_OBJECT_NAME (ave->dec), GST_OBJECT_NAME (factory)) == 0;
+}
+
+static GList *
+create_decoders_list (GList * factory_list, GSequence * avelements)
+{
+ GList *dec_list = NULL, *tmp;
+ GList *ave_list = NULL;
+ GList *ave_free_list = NULL;
+ GstAVElement *ave, *best_ave;
+
+ g_return_val_if_fail (factory_list != NULL, NULL);
+ g_return_val_if_fail (avelements != NULL, NULL);
+
+ for (tmp = factory_list; tmp; tmp = tmp->next) {
+ GstElementFactory *factory = (GstElementFactory *) tmp->data;
+
+ /* if there are parsers or sink elements, add them first */
+ if (gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_PARSER) ||
+ gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_SINK)) {
+ dec_list = g_list_prepend (dec_list, gst_object_ref (factory));
+ } else {
+ GSequenceIter *seq_iter;
+
+ seq_iter =
+ g_sequence_lookup (avelements, factory,
+ (GCompareDataFunc) avelement_lookup_decoder, NULL);
+ if (!seq_iter) {
+ GstAVElement *ave = g_slice_new0 (GstAVElement);
+
+ ave->dec = factory;
+ ave->sink = NULL;
+ /* There's at least raw */
+ ave->n_comm_cf = 1;
+
+ ave_list = g_list_prepend (ave_list, ave);
+
+ /* We need to free these later */
+ ave_free_list = g_list_prepend (ave_free_list, ave);
+ continue;
+ }
+
+ /* Go to first iter with that decoder */
+ do {
+ GSequenceIter *tmp_seq_iter;
+
+ tmp_seq_iter = g_sequence_iter_prev (seq_iter);
+ if (!avelement_iter_is_equal (tmp_seq_iter, factory))
+ break;
+ seq_iter = tmp_seq_iter;
+ } while (!g_sequence_iter_is_begin (seq_iter));
+
+ /* Get the best ranked GstAVElement for that factory */
+ best_ave = NULL;
+ while (!g_sequence_iter_is_end (seq_iter)
+ && avelement_iter_is_equal (seq_iter, factory)) {
+ ave = g_sequence_get (seq_iter);
+
+ if (!best_ave || avelement_compare (ave, best_ave) < 0)
+ best_ave = ave;
+
+ seq_iter = g_sequence_iter_next (seq_iter);
+ }
+ ave_list = g_list_prepend (ave_list, best_ave);
+ }
+ }
+
+ /* Sort all GstAVElements by their relative ranks and insert
+ * into the decoders list */
+ ave_list = g_list_sort (ave_list, (GCompareFunc) avelement_compare);
+ for (tmp = ave_list; tmp; tmp = tmp->next) {
+ ave = (GstAVElement *) tmp->data;
+ dec_list = g_list_prepend (dec_list, gst_object_ref (ave->dec));
+ }
+ g_list_free (ave_list);
+ gst_plugin_feature_list_free (factory_list);
+
+ for (tmp = ave_free_list; tmp; tmp = tmp->next)
+ g_slice_free (GstAVElement, tmp->data);
+ g_list_free (ave_free_list);
+
+ dec_list = g_list_reverse (dec_list);
+
+ return dec_list;
+}
+
+/* Called when we must provide a list of factories to plug to @pad with @caps.
+ * We first check if we have a sink that can handle the format and if we do, we
+ * return NULL, to expose the pad. If we have no sink (or the sink does not
+ * work), we return the list of elements that can connect. */
+static GValueArray *
+autoplug_factories_cb (GstElement * decodebin, GstPad * pad,
+ GstCaps * caps, GstSourceGroup * group)
+{
+ GstPlayBin3 *playbin;
+ GList *factory_list, *tmp;
+ GValueArray *result;
+ gboolean unref_caps = FALSE;
+ gboolean isaudiodeclist = FALSE;
+ gboolean isvideodeclist = FALSE;
+
+ if (!caps) {
+ caps = gst_caps_new_any ();
+ unref_caps = TRUE;
+ }
+
+ playbin = group->playbin;
+
+ GST_DEBUG_OBJECT (playbin, "factories group %p for %s:%s, %" GST_PTR_FORMAT,
+ group, GST_DEBUG_PAD_NAME (pad), caps);
+
+ /* filter out the elements based on the caps. */
+ g_mutex_lock (&playbin->elements_lock);
+ gst_play_bin3_update_elements_list (playbin);
+ factory_list =
+ gst_element_factory_list_filter (playbin->elements, caps, GST_PAD_SINK,
+ gst_caps_is_fixed (caps));
+ g_mutex_unlock (&playbin->elements_lock);
+
+ GST_DEBUG_OBJECT (playbin, "found factories %p", factory_list);
+ GST_PLUGIN_FEATURE_LIST_DEBUG (factory_list);
+
+ /* check whether the caps are asking for a list of audio/video decoders */
+ tmp = factory_list;
+ if (!gst_caps_is_any (caps)) {
+ for (; tmp; tmp = tmp->next) {
+ GstElementFactory *factory = (GstElementFactory *) tmp->data;
+
+ isvideodeclist = gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_DECODER |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE);
+ isaudiodeclist = gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_DECODER |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO);
+
+ if (isaudiodeclist || isvideodeclist)
+ break;
+ }
+ }
+
+ if (isaudiodeclist || isvideodeclist) {
+ GSequence **ave_list;
+ if (isaudiodeclist)
+ ave_list = &playbin->aelements;
+ else
+ ave_list = &playbin->velements;
+
+ g_mutex_lock (&playbin->elements_lock);
+ /* sort factory_list based on the GstAVElement list priority */
+ factory_list = create_decoders_list (factory_list, *ave_list);
+ g_mutex_unlock (&playbin->elements_lock);
+ }
+
+ /* 2 additional elements for the already set audio/video sinks */
+ result = g_value_array_new (g_list_length (factory_list) + 2);
+
+ /* Check if we already have an audio/video sink and if this is the case
+ * put it as the first element of the array */
+ if (group->audio_sink) {
+ GstElementFactory *factory = gst_element_get_factory (group->audio_sink);
+
+ if (factory && _factory_can_sink_caps (factory, caps)) {
+ GValue val = { 0, };
+
+ g_value_init (&val, G_TYPE_OBJECT);
+ g_value_set_object (&val, factory);
+ result = g_value_array_append (result, &val);
+ g_value_unset (&val);
+ }
+ }
+
+ if (group->video_sink) {
+ GstElementFactory *factory = gst_element_get_factory (group->video_sink);
+
+ if (factory && _factory_can_sink_caps (factory, caps)) {
+ GValue val = { 0, };
+
+ g_value_init (&val, G_TYPE_OBJECT);
+ g_value_set_object (&val, factory);
+ result = g_value_array_append (result, &val);
+ g_value_unset (&val);
+ }
+ }
+
+ for (tmp = factory_list; tmp; tmp = tmp->next) {
+ GstElementFactory *factory = GST_ELEMENT_FACTORY_CAST (tmp->data);
+ GValue val = { 0, };
+
+ if (group->audio_sink && gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_SINK |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) {
+ continue;
+ }
+ if (group->video_sink && gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO
+ | GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE)) {
+ continue;
+ }
+
+ g_value_init (&val, G_TYPE_OBJECT);
+ g_value_set_object (&val, factory);
+ g_value_array_append (result, &val);
+ g_value_unset (&val);
+ }
+ gst_plugin_feature_list_free (factory_list);
+
+ if (unref_caps)
+ gst_caps_unref (caps);
+
+ return result;
+}
+
+static void
+gst_play_bin3_set_context (GstElement * element, GstContext * context)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (element);
+
+ /* Proxy contexts to the sinks, they might not be in playsink yet */
+ GST_PLAY_BIN3_LOCK (playbin);
+ if (playbin->audio_sink)
+ gst_element_set_context (playbin->audio_sink, context);
+ if (playbin->video_sink)
+ gst_element_set_context (playbin->video_sink, context);
+ if (playbin->text_sink)
+ gst_element_set_context (playbin->text_sink, context);
+
+ GST_SOURCE_GROUP_LOCK (playbin->curr_group);
+
+ if (playbin->curr_group->audio_sink)
+ gst_element_set_context (playbin->curr_group->audio_sink, context);
+ if (playbin->curr_group->video_sink)
+ gst_element_set_context (playbin->curr_group->video_sink, context);
+ if (playbin->curr_group->text_sink)
+ gst_element_set_context (playbin->curr_group->text_sink, context);
+
+ GST_SOURCE_GROUP_UNLOCK (playbin->curr_group);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
+/* Pass sink messages to the application, e.g. NEED_CONTEXT messages */
+static void
+gst_play_bin3_update_context (GstPlayBin3 * playbin, GstContext * context)
+{
+ GList *l;
+ const gchar *context_type;
+
+ GST_OBJECT_LOCK (playbin);
+ context_type = gst_context_get_context_type (context);
+ for (l = playbin->contexts; l; l = l->next) {
+ GstContext *tmp = l->data;
+ const gchar *tmp_type = gst_context_get_context_type (tmp);
+
+ /* Always store newest context but never replace
+ * a persistent one by a non-persistent one */
+ if (strcmp (context_type, tmp_type) == 0 &&
+ (gst_context_is_persistent (context) ||
+ !gst_context_is_persistent (tmp))) {
+ gst_context_replace ((GstContext **) & l->data, context);
+ break;
+ }
+ }
+ /* Not found? Add */
+ if (l == NULL)
+ playbin->contexts =
+ g_list_prepend (playbin->contexts, gst_context_ref (context));
+ GST_OBJECT_UNLOCK (playbin);
+}
+
+static GstBusSyncReply
+activate_sink_bus_handler (GstBus * bus, GstMessage * msg,
+ GstPlayBin3 * playbin)
+{
+ if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
+ /* Only proxy errors from a fixed sink. If that fails we can just error out
+ * early as stuff will fail later anyway */
+ if (playbin->audio_sink
+ && gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg),
+ GST_OBJECT_CAST (playbin->audio_sink)))
+ gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
+ else if (playbin->video_sink
+ && gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg),
+ GST_OBJECT_CAST (playbin->video_sink)))
+ gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
+ else if (playbin->text_sink
+ && gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg),
+ GST_OBJECT_CAST (playbin->text_sink)))
+ gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
+ else
+ gst_message_unref (msg);
+ } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_NEED_CONTEXT) {
+ const gchar *context_type;
+ GList *l;
+
+ gst_message_parse_context_type (msg, &context_type);
+ GST_OBJECT_LOCK (playbin);
+ for (l = playbin->contexts; l; l = l->next) {
+ GstContext *tmp = l->data;
+ const gchar *tmp_type = gst_context_get_context_type (tmp);
+
+ if (strcmp (context_type, tmp_type) == 0) {
+ gst_element_set_context (GST_ELEMENT (GST_MESSAGE_SRC (msg)), l->data);
+ break;
+ }
+ }
+ GST_OBJECT_UNLOCK (playbin);
+
+ /* Forward if we couldn't answer the message */
+ if (l == NULL) {
+ gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
+ } else {
+ gst_message_unref (msg);
+ }
+ } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_HAVE_CONTEXT) {
+ GstContext *context;
+
+ gst_message_parse_have_context (msg, &context);
+ gst_play_bin3_update_context (playbin, context);
+ gst_context_unref (context);
+
+ gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
+ } else {
+ gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
+ }
+
+ /* Doesn't really matter, nothing is using this bus */
+ return GST_BUS_DROP;
+}
+
+static gboolean
+activate_sink (GstPlayBin3 * playbin, GstElement * sink, gboolean * activated)
+{
+ GstState state;
+ GstBus *bus = NULL;
+ GstStateChangeReturn sret;
+ gboolean ret = FALSE;
+
+ if (activated)
+ *activated = FALSE;
+
+ GST_OBJECT_LOCK (sink);
+ state = GST_STATE (sink);
+ GST_OBJECT_UNLOCK (sink);
+ if (state >= GST_STATE_READY) {
+ ret = TRUE;
+ goto done;
+ }
+
+ if (!GST_OBJECT_PARENT (sink)) {
+ bus = gst_bus_new ();
+ gst_bus_set_sync_handler (bus,
+ (GstBusSyncHandler) activate_sink_bus_handler, playbin, NULL);
+ gst_element_set_bus (sink, bus);
+ }
+
+ sret = gst_element_set_state (sink, GST_STATE_READY);
+ if (sret == GST_STATE_CHANGE_FAILURE)
+ goto done;
+
+ if (activated)
+ *activated = TRUE;
+ ret = TRUE;
+
+done:
+ if (bus) {
+ gst_element_set_bus (sink, NULL);
+ gst_object_unref (bus);
+ }
+
+ return ret;
+}
+
+/* autoplug-continue decides, if a pad has raw caps that can be exposed
+ * directly or if further decoding is necessary. We use this to expose
+ * supported subtitles directly */
+
+/* FIXME 0.11: Remove the checks for ANY caps, a sink should specify
+ * explicitly the caps it supports and if it claims to support ANY
+ * caps it really should support everything */
+static gboolean
+autoplug_continue_cb (GstElement * element, GstPad * pad, GstCaps * caps,
+ GstSourceGroup * group)
+{
+ gboolean ret = TRUE;
+ GstPad *sinkpad = NULL;
+ gboolean activated_sink;
+
+ GST_SOURCE_GROUP_LOCK (group);
+
+ if (group->text_sink &&
+ activate_sink (group->playbin, group->text_sink, &activated_sink)) {
+ sinkpad = gst_element_get_static_pad (group->text_sink, "sink");
+ if (sinkpad) {
+ GstCaps *sinkcaps;
+
+ sinkcaps = gst_pad_query_caps (sinkpad, NULL);
+ if (!gst_caps_is_any (sinkcaps))
+ ret = !gst_pad_query_accept_caps (sinkpad, caps);
+ gst_caps_unref (sinkcaps);
+ gst_object_unref (sinkpad);
+ }
+ if (activated_sink)
+ gst_element_set_state (group->text_sink, GST_STATE_NULL);
+ } else {
+ GstCaps *subcaps = gst_subtitle_overlay_create_factory_caps ();
+ ret = !gst_caps_is_subset (caps, subcaps);
+ gst_caps_unref (subcaps);
+ }
+ /* If autoplugging can stop don't do additional checks */
+ if (!ret)
+ goto done;
+
+ if (group->audio_sink &&
+ activate_sink (group->playbin, group->audio_sink, &activated_sink)) {
+
+ sinkpad = gst_element_get_static_pad (group->audio_sink, "sink");
+ if (sinkpad) {
+ GstCaps *sinkcaps;
+
+ sinkcaps = gst_pad_query_caps (sinkpad, NULL);
+ if (!gst_caps_is_any (sinkcaps))
+ ret = !gst_pad_query_accept_caps (sinkpad, caps);
+ gst_caps_unref (sinkcaps);
+ gst_object_unref (sinkpad);
+ }
+ if (activated_sink)
+ gst_element_set_state (group->audio_sink, GST_STATE_NULL);
+ }
+ if (!ret)
+ goto done;
+
+ if (group->video_sink
+ && activate_sink (group->playbin, group->video_sink, &activated_sink)) {
+ sinkpad = gst_element_get_static_pad (group->video_sink, "sink");
+ if (sinkpad) {
+ GstCaps *sinkcaps;
+
+ sinkcaps = gst_pad_query_caps (sinkpad, NULL);
+ if (!gst_caps_is_any (sinkcaps))
+ ret = !gst_pad_query_accept_caps (sinkpad, caps);
+ gst_caps_unref (sinkcaps);
+ gst_object_unref (sinkpad);
+ }
+ if (activated_sink)
+ gst_element_set_state (group->video_sink, GST_STATE_NULL);
+ }
+
+done:
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ GST_DEBUG_OBJECT (group->playbin,
+ "continue autoplugging group %p for %s:%s, %" GST_PTR_FORMAT ": %d",
+ group, GST_DEBUG_PAD_NAME (pad), caps, ret);
+
+ return ret;
+}
+
+static gboolean
+sink_accepts_caps (GstPlayBin3 * playbin, GstElement * sink, GstCaps * caps)
+{
+ GstPad *sinkpad;
+
+ if ((sinkpad = gst_element_get_static_pad (sink, "sink"))) {
+ /* Got the sink pad, now let's see if the element actually does accept the
+ * caps that we have */
+ if (!gst_pad_query_accept_caps (sinkpad, caps)) {
+ gst_object_unref (sinkpad);
+ return FALSE;
+ }
+ gst_object_unref (sinkpad);
+ }
+
+ return TRUE;
+}
+
+/* We are asked to select an element. See if the next element to check
+ * is a sink. If this is the case, we see if the sink works by setting it to
+ * READY. If the sink works, we return SELECT_EXPOSE to make decodebin
+ * expose the raw pad so that we can setup the mixers. */
+static GstAutoplugSelectResult
+autoplug_select_cb (GstElement * decodebin, GstPad * pad,
+ GstCaps * caps, GstElementFactory * factory, GstSourceGroup * group)
+{
+ GstPlayBin3 *playbin;
+ GstElement *element;
+ const gchar *klass;
+ GstPlaySinkType type;
+ GstElement **sinkp;
+ GList *ave_list = NULL, *l;
+ GstAVElement *ave = NULL;
+ GSequence *ave_seq = NULL;
+ GSequenceIter *seq_iter;
+
+ playbin = group->playbin;
+
+ GST_DEBUG_OBJECT (playbin, "select group %p for %s:%s, %" GST_PTR_FORMAT,
+ group, GST_DEBUG_PAD_NAME (pad), caps);
+
+ GST_DEBUG_OBJECT (playbin, "checking factory %s", GST_OBJECT_NAME (factory));
+
+ /* if it's not a sink, we make sure the element is compatible with
+ * the fixed sink */
+ if (!gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_SINK)) {
+ gboolean isvideodec = gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_DECODER |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE);
+ gboolean isaudiodec = gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_DECODER |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO);
+
+ if (!isvideodec && !isaudiodec)
+ return GST_AUTOPLUG_SELECT_TRY;
+
+ GST_SOURCE_GROUP_LOCK (group);
+ g_mutex_lock (&playbin->elements_lock);
+
+ if (isaudiodec) {
+ ave_seq = playbin->aelements;
+ sinkp = &group->audio_sink;
+ } else {
+ ave_seq = playbin->velements;
+ sinkp = &group->video_sink;
+ }
+
+ seq_iter =
+ g_sequence_lookup (ave_seq, factory,
+ (GCompareDataFunc) avelement_lookup_decoder, NULL);
+ if (seq_iter) {
+ /* Go to first iter with that decoder */
+ do {
+ GSequenceIter *tmp_seq_iter;
+
+ tmp_seq_iter = g_sequence_iter_prev (seq_iter);
+ if (!avelement_iter_is_equal (tmp_seq_iter, factory))
+ break;
+ seq_iter = tmp_seq_iter;
+ } while (!g_sequence_iter_is_begin (seq_iter));
+
+ while (!g_sequence_iter_is_end (seq_iter)
+ && avelement_iter_is_equal (seq_iter, factory)) {
+ ave = g_sequence_get (seq_iter);
+ ave_list = g_list_prepend (ave_list, ave);
+ seq_iter = g_sequence_iter_next (seq_iter);
+ }
+
+ /* Sort all GstAVElements by their relative ranks and insert
+ * into the decoders list */
+ ave_list = g_list_sort (ave_list, (GCompareFunc) avelement_compare);
+ } else {
+ ave_list = g_list_prepend (ave_list, NULL);
+ }
+
+ /* if it is a decoder and we don't have a fixed sink, then find out
+ * the matching audio/video sink from GstAVElements list */
+ for (l = ave_list; l; l = l->next) {
+ gboolean created_sink = FALSE;
+
+ ave = (GstAVElement *) l->data;
+
+ if (((isaudiodec && !group->audio_sink) ||
+ (isvideodec && !group->video_sink))) {
+ if (ave && ave->sink) {
+ GST_DEBUG_OBJECT (playbin,
+ "Trying to create sink '%s' for decoder '%s'",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (ave->sink)),
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+ if ((*sinkp = gst_element_factory_create (ave->sink, NULL)) == NULL) {
+ GST_WARNING_OBJECT (playbin,
+ "Could not create an element from %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (ave->sink)));
+ continue;
+ } else {
+ if (!activate_sink (playbin, *sinkp, NULL)) {
+ gst_object_unref (*sinkp);
+ *sinkp = NULL;
+ GST_WARNING_OBJECT (playbin,
+ "Could not activate sink %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (ave->sink)));
+ continue;
+ }
+ gst_object_ref_sink (*sinkp);
+ created_sink = TRUE;
+ }
+ }
+ }
+
+ /* If it is a decoder and we have a fixed sink for the media
+ * type it outputs, check that the decoder is compatible with this sink */
+ if ((isaudiodec && group->audio_sink) || (isvideodec
+ && group->video_sink)) {
+ gboolean compatible = FALSE;
+ GstPad *sinkpad;
+ GstCaps *caps;
+ GstElement *sink;
+
+ sink = *sinkp;
+
+ if ((sinkpad = gst_element_get_static_pad (sink, "sink"))) {
+ GstPlayFlags flags = gst_play_bin3_get_flags (playbin);
+ GstCaps *raw_caps =
+ (isaudiodec) ? gst_static_caps_get (&raw_audio_caps) :
+ gst_static_caps_get (&raw_video_caps);
+
+ caps = gst_pad_query_caps (sinkpad, NULL);
+
+ /* If the sink supports raw audio/video, we first check
+ * if the decoder could output any raw audio/video format
+ * and assume it is compatible with the sink then. We don't
+ * do a complete compatibility check here if converters
+ * are plugged between the decoder and the sink because
+ * the converters will convert between raw formats and
+ * even if the decoder format is not supported by the decoder
+ * a converter will convert it.
+ *
+ * We assume here that the converters can convert between
+ * any raw format.
+ */
+ if ((isaudiodec && !(flags & GST_PLAY_FLAG_NATIVE_AUDIO)
+ && gst_caps_can_intersect (caps, raw_caps)) || (!isaudiodec
+ && !(flags & GST_PLAY_FLAG_NATIVE_VIDEO)
+ && gst_caps_can_intersect (caps, raw_caps))) {
+ compatible =
+ gst_element_factory_can_src_any_caps (factory, raw_caps)
+ || gst_element_factory_can_src_any_caps (factory, caps);
+ } else {
+ compatible = gst_element_factory_can_src_any_caps (factory, caps);
+ }
+
+ gst_object_unref (sinkpad);
+ gst_caps_unref (caps);
+ }
+
+ if (compatible)
+ break;
+
+ GST_DEBUG_OBJECT (playbin, "%s not compatible with the fixed sink",
+ GST_OBJECT_NAME (factory));
+
+ /* If it is not compatible, either continue with the next possible
+ * sink or if we have a fixed sink, skip the decoder */
+ if (created_sink) {
+ gst_element_set_state (*sinkp, GST_STATE_NULL);
+ gst_object_unref (*sinkp);
+ *sinkp = NULL;
+ } else {
+ g_mutex_unlock (&playbin->elements_lock);
+ GST_SOURCE_GROUP_UNLOCK (group);
+ return GST_AUTOPLUG_SELECT_SKIP;
+ }
+ }
+ }
+ g_list_free (ave_list);
+ g_mutex_unlock (&playbin->elements_lock);
+ GST_SOURCE_GROUP_UNLOCK (group);
+ return GST_AUTOPLUG_SELECT_TRY;
+ }
+
+ /* it's a sink, see if an instance of it actually works */
+ GST_DEBUG_OBJECT (playbin, "we found a sink '%s'", GST_OBJECT_NAME (factory));
+
+ klass =
+ gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS);
+
+ /* figure out the klass */
+ if (strstr (klass, "Audio")) {
+ GST_DEBUG_OBJECT (playbin, "we found an audio sink");
+ type = GST_PLAY_SINK_TYPE_AUDIO;
+ sinkp = &group->audio_sink;
+ } else if (strstr (klass, "Video")) {
+ GST_DEBUG_OBJECT (playbin, "we found a video sink");
+ type = GST_PLAY_SINK_TYPE_VIDEO;
+ sinkp = &group->video_sink;
+ } else {
+ /* unknown klass, skip this element */
+ GST_WARNING_OBJECT (playbin, "unknown sink klass %s found", klass);
+ return GST_AUTOPLUG_SELECT_SKIP;
+ }
+
+ /* if we are asked to do visualisations and it's an audio sink, skip the
+ * element. We can only do visualisations with raw sinks */
+ if (gst_play_sink_get_flags (playbin->playsink) & GST_PLAY_FLAG_VIS) {
+ if (type == GST_PLAY_SINK_TYPE_AUDIO) {
+ GST_DEBUG_OBJECT (playbin, "skip audio sink because of vis");
+ return GST_AUTOPLUG_SELECT_SKIP;
+ }
+ }
+
+ /* now see if we already have a sink element */
+ GST_SOURCE_GROUP_LOCK (group);
+ if (*sinkp && GST_STATE (*sinkp) >= GST_STATE_READY) {
+ GstElement *sink = gst_object_ref (*sinkp);
+
+ if (sink_accepts_caps (playbin, sink, caps)) {
+ GST_DEBUG_OBJECT (playbin,
+ "Existing sink '%s' accepts caps: %" GST_PTR_FORMAT,
+ GST_ELEMENT_NAME (sink), caps);
+ gst_object_unref (sink);
+ GST_SOURCE_GROUP_UNLOCK (group);
+ return GST_AUTOPLUG_SELECT_EXPOSE;
+ } else {
+ GST_DEBUG_OBJECT (playbin,
+ "Existing sink '%s' does not accept caps: %" GST_PTR_FORMAT,
+ GST_ELEMENT_NAME (sink), caps);
+ gst_object_unref (sink);
+ GST_SOURCE_GROUP_UNLOCK (group);
+ return GST_AUTOPLUG_SELECT_SKIP;
+ }
+ }
+ GST_DEBUG_OBJECT (playbin, "we have no pending sink, try to create '%s'",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+
+ if ((*sinkp = gst_element_factory_create (factory, NULL)) == NULL) {
+ GST_WARNING_OBJECT (playbin, "Could not create an element from %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+ GST_SOURCE_GROUP_UNLOCK (group);
+ return GST_AUTOPLUG_SELECT_SKIP;
+ }
+
+ element = *sinkp;
+
+ if (!activate_sink (playbin, element, NULL)) {
+ GST_WARNING_OBJECT (playbin, "Could not activate sink %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+ *sinkp = NULL;
+ gst_object_unref (element);
+ GST_SOURCE_GROUP_UNLOCK (group);
+ return GST_AUTOPLUG_SELECT_SKIP;
+ }
+
+ /* Check if the selected sink actually supports the
+ * caps and can be set to READY*/
+ if (!sink_accepts_caps (playbin, element, caps)) {
+ *sinkp = NULL;
+ gst_element_set_state (element, GST_STATE_NULL);
+ gst_object_unref (element);
+ GST_SOURCE_GROUP_UNLOCK (group);
+ return GST_AUTOPLUG_SELECT_SKIP;
+ }
+
+ /* remember the sink in the group now, the element is floating, we take
+ * ownership now
+ *
+ * store the sink in the group, we will configure it later when we
+ * reconfigure the sink */
+ GST_DEBUG_OBJECT (playbin, "remember sink");
+ gst_object_ref_sink (element);
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ /* tell decodebin to expose the pad because we are going to use this
+ * sink */
+ GST_DEBUG_OBJECT (playbin, "we found a working sink, expose pad");
+
+ return GST_AUTOPLUG_SELECT_EXPOSE;
+}
+
+#define GST_PLAY_BIN3_FILTER_CAPS(filter,caps) G_STMT_START { \
+ if ((filter)) { \
+ GstCaps *intersection = \
+ gst_caps_intersect_full ((filter), (caps), GST_CAPS_INTERSECT_FIRST); \
+ gst_caps_unref ((caps)); \
+ (caps) = intersection; \
+ } \
+} G_STMT_END
+
+static gboolean
+autoplug_query_caps (GstElement * uridecodebin, GstPad * pad,
+ GstElement * element, GstQuery * query, GstSourceGroup * group)
+{
+ GstCaps *filter, *result = NULL;
+ GstElement *sink;
+ GstPad *sinkpad = NULL;
+ GstElementFactory *factory;
+ GstElementFactoryListType factory_type;
+ gboolean have_sink = FALSE;
+
+ GST_SOURCE_GROUP_LOCK (group);
+ gst_query_parse_caps (query, &filter);
+
+ factory = gst_element_get_factory (element);
+ if (!factory)
+ goto done;
+
+ if (gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE)) {
+ factory_type =
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE;
+
+ if ((sink = group->video_sink)) {
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ if (sinkpad) {
+ GstCaps *sinkcaps;
+
+ sinkcaps = gst_pad_query_caps (sinkpad, filter);
+ if (!gst_caps_is_any (sinkcaps)) {
+ if (!result)
+ result = sinkcaps;
+ else
+ result = gst_caps_merge (result, sinkcaps);
+ } else {
+ gst_caps_unref (sinkcaps);
+ }
+ gst_object_unref (sinkpad);
+ }
+ have_sink = TRUE;
+ }
+ } else if (gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) {
+ factory_type = GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO;
+
+ if ((sink = group->audio_sink)) {
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ if (sinkpad) {
+ GstCaps *sinkcaps;
+
+ sinkcaps = gst_pad_query_caps (sinkpad, filter);
+ if (!gst_caps_is_any (sinkcaps)) {
+ if (!result)
+ result = sinkcaps;
+ else
+ result = gst_caps_merge (result, sinkcaps);
+ } else {
+ gst_caps_unref (sinkcaps);
+ }
+ gst_object_unref (sinkpad);
+ }
+ have_sink = TRUE;
+ }
+ } else if (gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_SUBTITLE)) {
+ factory_type = GST_ELEMENT_FACTORY_TYPE_MEDIA_SUBTITLE;
+
+ if ((sink = group->playbin->text_sink)) {
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ if (sinkpad) {
+ GstCaps *sinkcaps;
+
+ sinkcaps = gst_pad_query_caps (sinkpad, filter);
+ if (!gst_caps_is_any (sinkcaps)) {
+ if (!result)
+ result = sinkcaps;
+ else
+ result = gst_caps_merge (result, sinkcaps);
+ } else {
+ gst_caps_unref (sinkcaps);
+ }
+ gst_object_unref (sinkpad);
+ }
+ have_sink = TRUE;
+ } else {
+ GstCaps *subcaps = gst_subtitle_overlay_create_factory_caps ();
+ GST_PLAY_BIN3_FILTER_CAPS (filter, subcaps);
+ if (!result)
+ result = subcaps;
+ else
+ result = gst_caps_merge (result, subcaps);
+ }
+ } else {
+ goto done;
+ }
+
+ if (!have_sink) {
+ GValueArray *factories;
+ gint i, n;
+
+ factories = autoplug_factories_cb (uridecodebin, pad, NULL, group);
+ n = factories->n_values;
+ for (i = 0; i < n; i++) {
+ GValue *v = g_value_array_get_nth (factories, i);
+ GstElementFactory *f = g_value_get_object (v);
+ const GList *templates;
+ const GList *l;
+ GstCaps *templ_caps;
+
+ if (!gst_element_factory_list_is_type (f, factory_type))
+ continue;
+
+ templates = gst_element_factory_get_static_pad_templates (f);
+
+ for (l = templates; l; l = l->next) {
+ templ_caps = gst_static_pad_template_get_caps (l->data);
+
+ if (!gst_caps_is_any (templ_caps)) {
+ GST_PLAY_BIN3_FILTER_CAPS (filter, templ_caps);
+ if (!result)
+ result = templ_caps;
+ else
+ result = gst_caps_merge (result, templ_caps);
+ } else {
+ gst_caps_unref (templ_caps);
+ }
+ }
+ }
+ g_value_array_free (factories);
+ }
+
+done:
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ if (!result)
+ return FALSE;
+
+ /* Add the actual decoder/parser/etc caps at the very end to
+ * make sure we don't cause empty caps to be returned, e.g.
+ * if a parser asks us but a decoder is required after it
+ * because no sink can handle the format directly.
+ */
+ {
+ GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
+
+ if (target) {
+ GstCaps *target_caps = gst_pad_get_pad_template_caps (target);
+ GST_PLAY_BIN3_FILTER_CAPS (filter, target_caps);
+ result = gst_caps_merge (result, target_caps);
+ gst_object_unref (target);
+ }
+ }
+
+
+ gst_query_set_caps_result (query, result);
+ gst_caps_unref (result);
+
+ return TRUE;
+}
+
+static gboolean
+autoplug_query_context (GstElement * uridecodebin, GstPad * pad,
+ GstElement * element, GstQuery * query, GstSourceGroup * group)
+{
+ GstElement *sink;
+ GstPad *sinkpad = NULL;
+ GstElementFactory *factory;
+ gboolean res = FALSE;
+
+ GST_SOURCE_GROUP_LOCK (group);
+
+ factory = gst_element_get_factory (element);
+ if (!factory)
+ goto done;
+
+ if (gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO |
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE)) {
+ if ((sink = group->video_sink)) {
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ if (sinkpad) {
+ res = gst_pad_query (sinkpad, query);
+ gst_object_unref (sinkpad);
+ }
+ }
+ } else if (gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) {
+ if ((sink = group->audio_sink)) {
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ if (sinkpad) {
+ res = gst_pad_query (sinkpad, query);
+ gst_object_unref (sinkpad);
+ }
+ }
+ } else if (gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_MEDIA_SUBTITLE)) {
+ if ((sink = group->playbin->text_sink)) {
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ if (sinkpad) {
+ res = gst_pad_query (sinkpad, query);
+ gst_object_unref (sinkpad);
+ }
+ }
+ } else {
+ goto done;
+ }
+
+done:
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ return res;
+}
+
+static gboolean
+autoplug_query_cb (GstElement * uridecodebin, GstPad * pad,
+ GstElement * element, GstQuery * query, GstSourceGroup * group)
+{
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CAPS:
+ return autoplug_query_caps (uridecodebin, pad, element, query, group);
+ case GST_QUERY_CONTEXT:
+ return autoplug_query_context (uridecodebin, pad, element, query, group);
+ default:
+ return FALSE;
+ }
+}
+
+static void
+notify_source_cb (GstElement * urisourcebin, GParamSpec * pspec,
+ GstSourceGroup * group)
+{
+ GstPlayBin3 *playbin;
+ GstElement *source;
+
+ playbin = group->playbin;
+
+ g_object_get (urisourcebin, "source", &source, NULL);
+
+ GST_OBJECT_LOCK (playbin);
+ if (playbin->source)
+ gst_object_unref (playbin->source);
+ playbin->source = source;
+ GST_OBJECT_UNLOCK (playbin);
+
+ g_object_notify (G_OBJECT (playbin), "source");
+
+ g_signal_emit (playbin, gst_play_bin3_signals[SIGNAL_SOURCE_SETUP],
+ 0, playbin->source);
+}
+
+/* must be called with the group lock */
+static gboolean
+group_set_locked_state_unlocked (GstPlayBin3 * playbin, GstSourceGroup * group,
+ gboolean locked)
+{
+ GST_DEBUG_OBJECT (playbin, "locked_state %d on group %p", locked, group);
+
+ if (group->urisourcebin)
+ gst_element_set_locked_state (group->urisourcebin, locked);
+ if (group->suburisourcebin)
+ gst_element_set_locked_state (group->suburisourcebin, locked);
+
+ return TRUE;
+}
+
+static gboolean
+make_or_reuse_element (GstPlayBin3 * playbin, const gchar * name,
+ GstElement ** elem)
+{
+ if (*elem) {
+ GST_DEBUG_OBJECT (playbin, "reusing existing %s", name);
+ gst_element_set_state (*elem, GST_STATE_READY);
+ /* no need to take extra ref, we already have one
+ * and the bin will add one since it is no longer floating,
+ * as we added a non-floating ref when removing it from the
+ * bin earlier */
+ } else {
+ GstElement *new_elem;
+ GST_DEBUG_OBJECT (playbin, "making new %s", name);
+ new_elem = gst_element_factory_make (name, NULL);
+ if (!new_elem)
+ return FALSE;
+ *elem = gst_object_ref (new_elem);
+ }
+
+ if (GST_OBJECT_PARENT (*elem) != GST_OBJECT_CAST (playbin))
+ gst_bin_add (GST_BIN_CAST (playbin), *elem);
+ return TRUE;
+}
+
+static void
+urisrc_pad_added (GstElement * urisrc, GstPad * pad, GstSourceGroup * group)
+{
+ GstPadLinkReturn res;
+ GstPad *sinkpad = NULL;
+ GstPlayBin3 *playbin;
+
+ GST_SOURCE_GROUP_LOCK (group);
+ playbin = group->playbin;
+ if (urisrc == group->urisourcebin) {
+ /* Primary stream, link to the main pad of decodebin3 */
+ sinkpad = gst_element_get_static_pad (playbin->decodebin, "sink");
+ if (gst_pad_is_linked (sinkpad)) {
+ gst_object_unref (GST_OBJECT (sinkpad));
+ sinkpad = NULL;
+ }
+ }
+
+ if (sinkpad == NULL) {
+ /* Auxiliary stream, request a new pad from decodebin */
+ if ((sinkpad = gst_element_get_request_pad (playbin->decodebin, "sink_%u"))) {
+ g_object_set_data (G_OBJECT (pad), "playbin.sinkpad", sinkpad);
+ }
+ }
+ if (sinkpad) {
+ GST_DEBUG_OBJECT (playbin, "New pad %" GST_PTR_FORMAT
+ " from urisourcebin %" GST_PTR_FORMAT " linking to %"
+ GST_PTR_FORMAT, pad, urisrc, sinkpad);
+
+ res = gst_pad_link (pad, sinkpad);
+ gst_object_unref (sinkpad);
+
+ if (GST_PAD_LINK_FAILED (res))
+ goto link_failed;
+ }
+ GST_SOURCE_GROUP_UNLOCK (group);
+ return;
+
+link_failed:
+ {
+ GST_ERROR_OBJECT (playbin,
+ "failed to link pad %s:%s to decodebin, reason %s (%d)",
+ GST_DEBUG_PAD_NAME (pad), gst_pad_link_get_name (res), res);
+ GST_SOURCE_GROUP_UNLOCK (group);
+ return;
+ }
+}
+
+static void
+urisrc_pad_removed_cb (GstElement * urisrc, GstPad * pad,
+ GstSourceGroup * group)
+{
+}
+
+/* must be called with PLAY_BIN_LOCK */
+static GstStateChangeReturn
+activate_decodebin (GstPlayBin3 * playbin, GstState target)
+{
+ GstStateChangeReturn state_ret;
+ GstElement *decodebin = NULL;
+
+ if (playbin->decodebin_active)
+ return GST_STATE_CHANGE_SUCCESS;
+
+ GST_LOG_OBJECT (playbin, "Adding and activating decodebin");
+
+ if (!make_or_reuse_element (playbin, "decodebin3", &playbin->decodebin))
+ goto no_decodebin;
+ decodebin = playbin->decodebin;
+
+ /* connect pads and other things */
+ playbin->db_pad_added_id = g_signal_connect (decodebin, "pad-added",
+ G_CALLBACK (pad_added_cb), playbin);
+ playbin->db_pad_removed_id = g_signal_connect (decodebin, "pad-removed",
+ G_CALLBACK (pad_removed_cb), playbin);
+ playbin->db_no_more_pads_id = g_signal_connect (decodebin, "no-more-pads",
+ G_CALLBACK (no_more_pads_cb), playbin);
+ playbin->db_select_stream_id = g_signal_connect (decodebin, "select-stream",
+ G_CALLBACK (select_stream_cb), playbin);
+ /* is called when the decodebin is out of data and we can switch to the
+ * next uri */
+#if 0
+ /* FIXME: Re-enable if/when decodebin3 supports 'drained' */
+ playbin->db_drained_id =
+ g_signal_connect (decodebin, "drained", G_CALLBACK (drained_cb), playbin);
+#endif
+
+ gst_element_set_locked_state (decodebin, TRUE);
+ if ((state_ret =
+ gst_element_set_state (decodebin,
+ target)) == GST_STATE_CHANGE_FAILURE)
+ goto decodebin_failure;
+ gst_element_set_locked_state (decodebin, FALSE);
+
+ playbin->decodebin_active = TRUE;
+
+ return state_ret;
+
+
+no_decodebin:
+ {
+ GstMessage *msg;
+
+ msg =
+ gst_missing_element_message_new (GST_ELEMENT_CAST (playbin),
+ "decodebin3");
+ gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
+
+ GST_ELEMENT_ERROR (playbin, CORE, MISSING_PLUGIN,
+ (_("Could not create \"decodebin3\" element.")), (NULL));
+
+ goto error_cleanup;
+ }
+decodebin_failure:
+ {
+ GST_DEBUG_OBJECT (playbin, "failed state change of decodebin");
+ goto error_cleanup;
+ }
+error_cleanup:{
+ if (decodebin) {
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_pad_added_id);
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_pad_removed_id);
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_no_more_pads_id);
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_drained_id);
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_select_stream_id);
+ gst_element_set_state (decodebin, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (playbin), decodebin);
+ }
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
+
+/* must be called with PLAY_BIN_LOCK */
+static void
+deactivate_decodebin (GstPlayBin3 * playbin)
+{
+ if (playbin->decodebin) {
+ GST_LOG_OBJECT (playbin, "Deactivating and removing decodebin");
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_pad_added_id);
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_pad_removed_id);
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_no_more_pads_id);
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_drained_id);
+ REMOVE_SIGNAL (playbin->decodebin, playbin->db_select_stream_id);
+ gst_bin_remove (GST_BIN_CAST (playbin), playbin->decodebin);
+ playbin->decodebin_active = FALSE;
+ playbin->active_stream_types = 0;
+ }
+}
+
+/* must be called with PLAY_BIN_LOCK */
+static GstStateChangeReturn
+activate_group (GstPlayBin3 * playbin, GstSourceGroup * group, GstState target)
+{
+ GstElement *urisrcbin = NULL;
+ GstElement *suburisrcbin = NULL;
+ GstPlayFlags flags;
+ gboolean audio_sink_activated = FALSE;
+ gboolean video_sink_activated = FALSE;
+ gboolean text_sink_activated = FALSE;
+ GstStateChangeReturn state_ret;
+
+ g_return_val_if_fail (group->valid, GST_STATE_CHANGE_FAILURE);
+ g_return_val_if_fail (!group->active, GST_STATE_CHANGE_FAILURE);
+
+ GST_DEBUG_OBJECT (playbin, "activating group %p", group);
+
+ GST_SOURCE_GROUP_LOCK (group);
+
+ /* First set up the custom sinks */
+ if (playbin->audio_sink)
+ group->audio_sink = gst_object_ref (playbin->audio_sink);
+ else
+ group->audio_sink =
+ gst_play_sink_get_sink (playbin->playsink, GST_PLAY_SINK_TYPE_AUDIO);
+
+ if (group->audio_sink) {
+ if (!activate_sink (playbin, group->audio_sink, &audio_sink_activated)) {
+ if (group->audio_sink == playbin->audio_sink) {
+ goto sink_failure;
+ } else {
+ gst_object_unref (group->audio_sink);
+ group->audio_sink = NULL;
+ }
+ }
+ }
+
+ if (playbin->video_sink)
+ group->video_sink = gst_object_ref (playbin->video_sink);
+ else
+ group->video_sink =
+ gst_play_sink_get_sink (playbin->playsink, GST_PLAY_SINK_TYPE_VIDEO);
+
+ if (group->video_sink) {
+ if (!activate_sink (playbin, group->video_sink, &video_sink_activated)) {
+ if (group->video_sink == playbin->video_sink) {
+ goto sink_failure;
+ } else {
+ gst_object_unref (group->video_sink);
+ group->video_sink = NULL;
+ }
+ }
+ }
+
+ if (playbin->text_sink)
+ group->text_sink = gst_object_ref (playbin->text_sink);
+ else
+ group->text_sink =
+ gst_play_sink_get_sink (playbin->playsink, GST_PLAY_SINK_TYPE_TEXT);
+
+ if (group->text_sink) {
+ if (!activate_sink (playbin, group->text_sink, &text_sink_activated)) {
+ if (group->text_sink == playbin->text_sink) {
+ goto sink_failure;
+ } else {
+ gst_object_unref (group->text_sink);
+ group->text_sink = NULL;
+ }
+ }
+ }
+
+
+ if (!make_or_reuse_element (playbin, "urisourcebin", &group->urisourcebin))
+ goto no_urisrcbin;
+ urisrcbin = group->urisourcebin;
+
+ flags = gst_play_sink_get_flags (playbin->playsink);
+
+ g_object_set (urisrcbin,
+ /* configure connection speed */
+ "connection-speed", playbin->connection_speed / 1000,
+ /* configure uri */
+ "uri", group->uri,
+ /* configure download buffering */
+ "download", ((flags & GST_PLAY_FLAG_DOWNLOAD) != 0),
+ /* configure buffering of demuxed/parsed data */
+ "use-buffering", ((flags & GST_PLAY_FLAG_BUFFERING) != 0),
+ /* configure buffering parameters */
+ "buffer-duration", playbin->buffer_duration,
+ "buffer-size", playbin->buffer_size,
+ "ring-buffer-max-size", playbin->ring_buffer_max_size, NULL);
+
+ /* we have 1 pending no-more-pads */
+ group->pending = 1;
+
+ group->notify_source_id = g_signal_connect (urisrcbin, "notify::source",
+ G_CALLBACK (notify_source_cb), group);
+
+ /* will be called when a new media type is found. We return a list of decoders
+ * including sinks for decodebin to try */
+ group->autoplug_factories_id =
+ g_signal_connect (urisrcbin, "autoplug-factories",
+ G_CALLBACK (autoplug_factories_cb), group);
+ group->autoplug_select_id =
+ g_signal_connect (urisrcbin, "autoplug-select",
+ G_CALLBACK (autoplug_select_cb), group);
+ group->autoplug_continue_id =
+ g_signal_connect (urisrcbin, "autoplug-continue",
+ G_CALLBACK (autoplug_continue_cb), group);
+ group->autoplug_query_id =
+ g_signal_connect (urisrcbin, "autoplug-query",
+ G_CALLBACK (autoplug_query_cb), group);
+
+ group->urisrc_pad_added_id = g_signal_connect (urisrcbin, "pad-added",
+ G_CALLBACK (urisrc_pad_added), group);
+ group->urisrc_pad_removed_id = g_signal_connect (urisrcbin,
+ "pad-removed", G_CALLBACK (urisrc_pad_removed_cb), group);
+
+ if (group->suburi) {
+ /* subtitles */
+ if (!make_or_reuse_element (playbin, "urisourcebin",
+ &group->suburisourcebin))
+ goto no_urisrcbin;
+ suburisrcbin = group->suburisourcebin;
+
+ g_object_set (suburisrcbin,
+ /* configure connection speed */
+ "connection-speed", playbin->connection_speed,
+ /* configure uri */
+ "uri", group->suburi, NULL);
+
+ /* connect pads and other things */
+ group->sub_pad_added_id = g_signal_connect (suburisrcbin, "pad-added",
+ G_CALLBACK (urisrc_pad_added), group);
+ group->sub_pad_removed_id = g_signal_connect (suburisrcbin,
+ "pad-removed", G_CALLBACK (urisrc_pad_removed_cb), group);
+
+ group->sub_autoplug_continue_id =
+ g_signal_connect (suburisrcbin, "autoplug-continue",
+ G_CALLBACK (autoplug_continue_cb), group);
+
+ group->sub_autoplug_query_id =
+ g_signal_connect (suburisrcbin, "autoplug-query",
+ G_CALLBACK (autoplug_query_cb), group);
+
+ /* we have 2 pending no-more-pads */
+ group->pending = 2;
+ group->sub_pending = TRUE;
+ } else {
+ group->sub_pending = FALSE;
+ }
+
+ /* release the group lock before setting the state of the source bins, they
+ * might fire signals in this thread that we need to handle with the
+ * group_lock taken. */
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ if (suburisrcbin) {
+ if (gst_element_set_state (suburisrcbin,
+ target) == GST_STATE_CHANGE_FAILURE) {
+ GST_DEBUG_OBJECT (playbin,
+ "failed state change of subtitle urisourcebin");
+ GST_SOURCE_GROUP_LOCK (group);
+
+ REMOVE_SIGNAL (suburisrcbin, group->sub_pad_added_id);
+ REMOVE_SIGNAL (suburisrcbin, group->sub_pad_removed_id);
+ REMOVE_SIGNAL (suburisrcbin, group->sub_autoplug_continue_id);
+ REMOVE_SIGNAL (suburisrcbin, group->sub_autoplug_query_id);
+ /* Might already be removed because of an error message */
+ if (GST_OBJECT_PARENT (suburisrcbin) == GST_OBJECT_CAST (playbin))
+ gst_bin_remove (GST_BIN_CAST (playbin), suburisrcbin);
+ if (group->sub_pending) {
+ group->pending--;
+ group->sub_pending = FALSE;
+ }
+ gst_element_set_state (suburisrcbin, GST_STATE_READY);
+ g_free (group->suburi);
+ group->suburi = NULL;
+ GST_SOURCE_GROUP_UNLOCK (group);
+ }
+ }
+ if ((state_ret =
+ gst_element_set_state (urisrcbin,
+ target)) == GST_STATE_CHANGE_FAILURE)
+ goto urisrcbin_failure;
+
+ GST_SOURCE_GROUP_LOCK (group);
+ /* allow state changes of the playbin affect the group elements now */
+ group_set_locked_state_unlocked (playbin, group, FALSE);
+ group->active = TRUE;
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ return state_ret;
+
+ /* ERRORS */
+no_urisrcbin:
+ {
+ GstMessage *msg;
+
+ GST_SOURCE_GROUP_UNLOCK (group);
+ msg =
+ gst_missing_element_message_new (GST_ELEMENT_CAST (playbin),
+ "urisourcebin");
+ gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
+
+ GST_ELEMENT_ERROR (playbin, CORE, MISSING_PLUGIN,
+ (_("Could not create \"urisourcebin\" element.")), (NULL));
+
+ GST_SOURCE_GROUP_LOCK (group);
+
+ goto error_cleanup;
+ }
+urisrcbin_failure:
+ {
+ GST_DEBUG_OBJECT (playbin, "failed state change of urisrcbin");
+ GST_SOURCE_GROUP_LOCK (group);
+ goto error_cleanup;
+ }
+sink_failure:
+ {
+ GST_ERROR_OBJECT (playbin, "failed to activate sinks");
+ goto error_cleanup;
+ }
+
+error_cleanup:
+ {
+ /* delete any custom sinks we might have */
+ if (group->audio_sink) {
+ /* If this is a automatically created sink set it to NULL */
+ if (audio_sink_activated)
+ gst_element_set_state (group->audio_sink, GST_STATE_NULL);
+ gst_object_unref (group->audio_sink);
+ }
+ group->audio_sink = NULL;
+
+ if (group->video_sink) {
+ /* If this is a automatically created sink set it to NULL */
+ if (video_sink_activated)
+ gst_element_set_state (group->video_sink, GST_STATE_NULL);
+ gst_object_unref (group->video_sink);
+ }
+ group->video_sink = NULL;
+
+ if (group->text_sink) {
+ /* If this is a automatically created sink set it to NULL */
+ if (text_sink_activated)
+ gst_element_set_state (group->text_sink, GST_STATE_NULL);
+ gst_object_unref (group->text_sink);
+ }
+ group->text_sink = NULL;
+
+ if (urisrcbin) {
+ REMOVE_SIGNAL (group->urisourcebin, group->urisrc_pad_added_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->urisrc_pad_removed_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->notify_source_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->autoplug_factories_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->autoplug_select_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->autoplug_continue_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->autoplug_query_id);
+
+ gst_element_set_state (urisrcbin, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (playbin), urisrcbin);
+ }
+
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
+
+/* unlink a group of urisrcbin from the decodebin.
+ * must be called with PLAY_BIN_LOCK */
+static gboolean
+deactivate_group (GstPlayBin3 * playbin, GstSourceGroup * group)
+{
+ gint i;
+
+ g_return_val_if_fail (group->active, FALSE);
+ g_return_val_if_fail (group->valid, FALSE);
+
+ GST_DEBUG_OBJECT (playbin, "unlinking group %p", group);
+
+ GST_SOURCE_GROUP_LOCK (group);
+ group->active = FALSE;
+ for (i = 0; i < PLAYBIN_STREAM_LAST; i++) {
+ GstSourceCombine *combine = &playbin->combiner[i];
+
+ GST_DEBUG_OBJECT (playbin, "unlinking combiner %s", combine->media_type);
+
+ if (combine->srcpad) {
+ source_combine_remove_pads (playbin, combine);
+ }
+
+ if (combine->combiner) {
+ gint n;
+
+ /* release and unref requests pad from the combiner */
+ for (n = 0; n < combine->channels->len; n++) {
+ GstPad *sinkpad = g_ptr_array_index (combine->channels, n);
+
+ gst_element_release_request_pad (combine->combiner, sinkpad);
+ gst_object_unref (sinkpad);
+ }
+ g_ptr_array_set_size (combine->channels, 0);
+
+ gst_element_set_state (combine->combiner, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (playbin), combine->combiner);
+ combine->combiner = NULL;
+ }
+ }
+#if 0
+ /* delete any custom sinks we might have.
+ * conditionally set them to null if they aren't inside playsink yet */
+ if (group->audio_sink) {
+ if (!gst_object_has_as_ancestor (GST_OBJECT_CAST (group->audio_sink),
+ GST_OBJECT_CAST (playbin->playsink))) {
+ gst_element_set_state (group->audio_sink, GST_STATE_NULL);
+ }
+ gst_object_unref (group->audio_sink);
+ }
+ group->audio_sink = NULL;
+ if (group->video_sink) {
+ if (!gst_object_has_as_ancestor (GST_OBJECT_CAST (group->video_sink),
+ GST_OBJECT_CAST (playbin->playsink))) {
+ gst_element_set_state (group->video_sink, GST_STATE_NULL);
+ }
+ gst_object_unref (group->video_sink);
+ }
+ group->video_sink = NULL;
+ if (group->text_sink) {
+ if (!gst_object_has_as_ancestor (GST_OBJECT_CAST (group->text_sink),
+ GST_OBJECT_CAST (playbin->playsink))) {
+ gst_element_set_state (group->text_sink, GST_STATE_NULL);
+ }
+ gst_object_unref (group->text_sink);
+ }
+ group->text_sink = NULL;
+#endif
+
+ if (group->urisourcebin) {
+ REMOVE_SIGNAL (group->urisourcebin, group->urisrc_pad_added_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->urisrc_pad_removed_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->notify_source_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->autoplug_factories_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->autoplug_select_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->autoplug_continue_id);
+ REMOVE_SIGNAL (group->urisourcebin, group->autoplug_query_id);
+ gst_bin_remove (GST_BIN_CAST (playbin), group->urisourcebin);
+ }
+
+ if (group->suburisourcebin) {
+ REMOVE_SIGNAL (group->suburisourcebin, group->sub_pad_added_id);
+ REMOVE_SIGNAL (group->suburisourcebin, group->sub_pad_removed_id);
+ REMOVE_SIGNAL (group->suburisourcebin, group->sub_autoplug_continue_id);
+ REMOVE_SIGNAL (group->suburisourcebin, group->sub_autoplug_query_id);
+
+ /* Might already be removed because of errors */
+ if (GST_OBJECT_PARENT (group->suburisourcebin) == GST_OBJECT_CAST (playbin))
+ gst_bin_remove (GST_BIN_CAST (playbin), group->suburisourcebin);
+ }
+
+ GST_SOURCE_GROUP_UNLOCK (group);
+
+ return TRUE;
+}
+
+/* setup the next group to play, this assumes the next_group is valid and
+ * configured. It swaps out the current_group and activates the valid
+ * next_group. */
+static GstStateChangeReturn
+setup_next_source (GstPlayBin3 * playbin, GstState target)
+{
+ GstSourceGroup *new_group, *old_group;
+ GstStateChangeReturn state_ret;
+
+ GST_DEBUG_OBJECT (playbin, "setup sources");
+
+ /* see if there is a next group */
+ GST_PLAY_BIN3_LOCK (playbin);
+ new_group = playbin->next_group;
+ if (!new_group || !new_group->valid)
+ goto no_next_group;
+
+ /* first unlink the current source, if any */
+ old_group = playbin->curr_group;
+ if (old_group && old_group->valid && old_group->active) {
+ new_group->stream_changed_pending = TRUE;
+
+ gst_play_bin3_update_cached_duration (playbin);
+ /* unlink our pads with the sink */
+ deactivate_group (playbin, old_group);
+ old_group->valid = FALSE;
+ }
+
+ /* swap old and new */
+ playbin->curr_group = new_group;
+ playbin->next_group = old_group;
+
+ /* Get decodebin ready now */
+ if ((state_ret =
+ activate_decodebin (playbin, target)) == GST_STATE_CHANGE_FAILURE)
+ goto activate_failed;
+
+ /* activate the new group */
+ if ((state_ret =
+ activate_group (playbin, new_group,
+ target)) == GST_STATE_CHANGE_FAILURE)
+ goto activate_failed;
+
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return state_ret;
+
+ /* ERRORS */
+no_next_group:
+ {
+ GST_DEBUG_OBJECT (playbin, "no next group");
+ if (target == GST_STATE_READY && new_group && new_group->uri == NULL)
+ GST_ELEMENT_ERROR (playbin, RESOURCE, NOT_FOUND, ("No URI set"), (NULL));
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ return GST_STATE_CHANGE_FAILURE;
+ }
+activate_failed:
+ {
+ new_group->stream_changed_pending = FALSE;
+ GST_DEBUG_OBJECT (playbin, "activate failed");
+ new_group->valid = FALSE;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
+
+/* The group that is currently playing is copied again to the
+ * next_group so that it will start playing the next time.
+ */
+static gboolean
+save_current_group (GstPlayBin3 * playbin)
+{
+ GstSourceGroup *curr_group;
+
+ GST_DEBUG_OBJECT (playbin, "save current group");
+
+ /* see if there is a current group */
+ GST_PLAY_BIN3_LOCK (playbin);
+ curr_group = playbin->curr_group;
+ if (curr_group && curr_group->valid && curr_group->active) {
+ /* unlink our pads with the sink */
+ deactivate_group (playbin, curr_group);
+ }
+ /* swap old and new */
+ playbin->curr_group = playbin->next_group;
+ playbin->next_group = curr_group;
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return TRUE;
+}
+
+/* clear the locked state from all groups. This function is called before a
+ * state change to NULL is performed on them. */
+static gboolean
+groups_set_locked_state (GstPlayBin3 * playbin, gboolean locked)
+{
+ GST_DEBUG_OBJECT (playbin, "setting locked state to %d on all groups",
+ locked);
+
+ GST_PLAY_BIN3_LOCK (playbin);
+ GST_SOURCE_GROUP_LOCK (playbin->curr_group);
+ group_set_locked_state_unlocked (playbin, playbin->curr_group, locked);
+ GST_SOURCE_GROUP_UNLOCK (playbin->curr_group);
+ GST_SOURCE_GROUP_LOCK (playbin->next_group);
+ group_set_locked_state_unlocked (playbin, playbin->next_group, locked);
+ GST_SOURCE_GROUP_UNLOCK (playbin->next_group);
+ GST_PLAY_BIN3_UNLOCK (playbin);
+
+ return TRUE;
+}
+
+static GstStateChangeReturn
+gst_play_bin3_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ GstPlayBin3 *playbin;
+ gboolean do_save = FALSE;
+
+ playbin = GST_PLAY_BIN3 (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ memset (&playbin->duration, 0, sizeof (playbin->duration));
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ GST_LOG_OBJECT (playbin, "clearing shutdown flag");
+ memset (&playbin->duration, 0, sizeof (playbin->duration));
+ g_atomic_int_set (&playbin->shutdown, 0);
+ do_async_start (playbin);
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ async_down:
+ /* FIXME unlock our waiting groups */
+ GST_LOG_OBJECT (playbin, "setting shutdown flag");
+ g_atomic_int_set (&playbin->shutdown, 1);
+ memset (&playbin->duration, 0, sizeof (playbin->duration));
+
+ /* wait for all callbacks to end by taking the lock.
+ * No dynamic (critical) new callbacks will
+ * be able to happen as we set the shutdown flag. */
+ GST_PLAY_BIN3_DYN_LOCK (playbin);
+ GST_LOG_OBJECT (playbin, "dynamic lock taken, we can continue shutdown");
+ GST_PLAY_BIN3_DYN_UNLOCK (playbin);
+ if (!do_save)
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ /* we go async to PAUSED, so if that fails, we never make it to PAUSED
+ * and no state change PAUSED to READY passes here,
+ * though it is a nice-to-have ... */
+ if (!g_atomic_int_get (&playbin->shutdown)) {
+ do_save = TRUE;
+ goto async_down;
+ }
+ memset (&playbin->duration, 0, sizeof (playbin->duration));
+
+ /* unlock so that all groups go to NULL */
+ groups_set_locked_state (playbin, FALSE);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto failure;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ if ((ret =
+ setup_next_source (playbin,
+ GST_STATE_PAUSED)) == GST_STATE_CHANGE_FAILURE)
+ goto failure;
+ if (ret == GST_STATE_CHANGE_SUCCESS)
+ ret = GST_STATE_CHANGE_ASYNC;
+
+ break;
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ do_async_done (playbin);
+ /* FIXME Release audio device when we implement that */
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ save_current_group (playbin);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ {
+ guint i;
+ GList *l;
+
+ /* also do missed state change down to READY */
+ if (do_save)
+ save_current_group (playbin);
+ /* Deactive the groups, set the urisrcbins to NULL
+ * and unref them.
+ */
+ for (i = 0; i < 2; i++) {
+ if (playbin->groups[i].active && playbin->groups[i].valid) {
+ deactivate_group (playbin, &playbin->groups[i]);
+ playbin->groups[i].valid = FALSE;
+ }
+
+ if (playbin->groups[i].urisourcebin) {
+ gst_element_set_state (playbin->groups[i].urisourcebin,
+ GST_STATE_NULL);
+ gst_object_unref (playbin->groups[i].urisourcebin);
+ playbin->groups[i].urisourcebin = NULL;
+ }
+
+ if (playbin->groups[i].suburisourcebin) {
+ gst_element_set_state (playbin->groups[i].suburisourcebin,
+ GST_STATE_NULL);
+ gst_object_unref (playbin->groups[i].suburisourcebin);
+ playbin->groups[i].suburisourcebin = NULL;
+ }
+ }
+
+ deactivate_decodebin (playbin);
+ if (playbin->decodebin) {
+ gst_object_unref (playbin->decodebin);
+ playbin->decodebin = NULL;
+ playbin->decodebin_active = FALSE;
+ }
+
+ /* Set our sinks back to NULL, they might not be child of playbin */
+ if (playbin->audio_sink)
+ gst_element_set_state (playbin->audio_sink, GST_STATE_NULL);
+ if (playbin->video_sink)
+ gst_element_set_state (playbin->video_sink, GST_STATE_NULL);
+ if (playbin->text_sink)
+ gst_element_set_state (playbin->text_sink, GST_STATE_NULL);
+
+ if (playbin->video_stream_combiner)
+ gst_element_set_state (playbin->video_stream_combiner, GST_STATE_NULL);
+ if (playbin->audio_stream_combiner)
+ gst_element_set_state (playbin->audio_stream_combiner, GST_STATE_NULL);
+ if (playbin->text_stream_combiner)
+ gst_element_set_state (playbin->text_stream_combiner, GST_STATE_NULL);
+
+ /* make sure the groups don't perform a state change anymore until we
+ * enable them again */
+ groups_set_locked_state (playbin, TRUE);
+
+ /* Remove all non-persistent contexts */
+ GST_OBJECT_LOCK (playbin);
+ for (l = playbin->contexts; l;) {
+ GstContext *context = l->data;
+
+ if (!gst_context_is_persistent (context)) {
+ GList *next;
+
+ gst_context_unref (context);
+
+ next = l->next;
+ playbin->contexts = g_list_delete_link (playbin->contexts, l);
+ l = next;
+ } else {
+ l = l->next;
+ }
+ }
+
+ if (playbin->source) {
+ gst_object_unref (playbin->source);
+ playbin->source = NULL;
+ }
+
+ GST_OBJECT_UNLOCK (playbin);
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (ret == GST_STATE_CHANGE_NO_PREROLL)
+ do_async_done (playbin);
+
+ return ret;
+
+ /* ERRORS */
+failure:
+ {
+ do_async_done (playbin);
+
+ if (transition == GST_STATE_CHANGE_READY_TO_PAUSED) {
+ GstSourceGroup *curr_group;
+
+ curr_group = playbin->curr_group;
+ if (curr_group) {
+ if (curr_group->active && curr_group->valid) {
+ /* unlink our pads with the sink */
+ deactivate_group (playbin, curr_group);
+ }
+ curr_group->valid = FALSE;
+ }
+
+ /* Swap current and next group back */
+ playbin->curr_group = playbin->next_group;
+ playbin->next_group = curr_group;
+ }
+ return ret;
+ }
+}
+
+static void
+gst_play_bin3_overlay_expose (GstVideoOverlay * overlay)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (overlay);
+
+ gst_video_overlay_expose (GST_VIDEO_OVERLAY (playbin->playsink));
+}
+
+static void
+gst_play_bin3_overlay_handle_events (GstVideoOverlay * overlay,
+ gboolean handle_events)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (overlay);
+
+ gst_video_overlay_handle_events (GST_VIDEO_OVERLAY (playbin->playsink),
+ handle_events);
+}
+
+static void
+gst_play_bin3_overlay_set_render_rectangle (GstVideoOverlay * overlay, gint x,
+ gint y, gint width, gint height)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (overlay);
+
+ gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (playbin->playsink),
+ x, y, width, height);
+}
+
+static void
+gst_play_bin3_overlay_set_window_handle (GstVideoOverlay * overlay,
+ guintptr handle)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (overlay);
+
+ gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (playbin->playsink),
+ handle);
+}
+
+static void
+gst_play_bin3_overlay_init (gpointer g_iface, gpointer g_iface_data)
+{
+ GstVideoOverlayInterface *iface = (GstVideoOverlayInterface *) g_iface;
+ iface->expose = gst_play_bin3_overlay_expose;
+ iface->handle_events = gst_play_bin3_overlay_handle_events;
+ iface->set_render_rectangle = gst_play_bin3_overlay_set_render_rectangle;
+ iface->set_window_handle = gst_play_bin3_overlay_set_window_handle;
+}
+
+static void
+gst_play_bin3_navigation_send_event (GstNavigation * navigation,
+ GstStructure * structure)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (navigation);
+
+ gst_navigation_send_event (GST_NAVIGATION (playbin->playsink), structure);
+}
+
+static void
+gst_play_bin3_navigation_init (gpointer g_iface, gpointer g_iface_data)
+{
+ GstNavigationInterface *iface = (GstNavigationInterface *) g_iface;
+
+ iface->send_event = gst_play_bin3_navigation_send_event;
+}
+
+static const GList *
+gst_play_bin3_colorbalance_list_channels (GstColorBalance * balance)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (balance);
+
+ return
+ gst_color_balance_list_channels (GST_COLOR_BALANCE (playbin->playsink));
+}
+
+static void
+gst_play_bin3_colorbalance_set_value (GstColorBalance * balance,
+ GstColorBalanceChannel * channel, gint value)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (balance);
+
+ gst_color_balance_set_value (GST_COLOR_BALANCE (playbin->playsink), channel,
+ value);
+}
+
+static gint
+gst_play_bin3_colorbalance_get_value (GstColorBalance * balance,
+ GstColorBalanceChannel * channel)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (balance);
+
+ return gst_color_balance_get_value (GST_COLOR_BALANCE (playbin->playsink),
+ channel);
+}
+
+static GstColorBalanceType
+gst_play_bin3_colorbalance_get_balance_type (GstColorBalance * balance)
+{
+ GstPlayBin3 *playbin = GST_PLAY_BIN3 (balance);
+
+ return
+ gst_color_balance_get_balance_type (GST_COLOR_BALANCE
+ (playbin->playsink));
+}
+
+static void
+gst_play_bin3_colorbalance_init (gpointer g_iface, gpointer g_iface_data)
+{
+ GstColorBalanceInterface *iface = (GstColorBalanceInterface *) g_iface;
+
+ iface->list_channels = gst_play_bin3_colorbalance_list_channels;
+ iface->set_value = gst_play_bin3_colorbalance_set_value;
+ iface->get_value = gst_play_bin3_colorbalance_get_value;
+ iface->get_balance_type = gst_play_bin3_colorbalance_get_balance_type;
+}
+
+gboolean
+gst_play_bin3_plugin_init (GstPlugin * plugin, gboolean as_playbin)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_play_bin3_debug, "playbin3", 0, "play bin");
+
+ if (as_playbin)
+ return gst_element_register (plugin, "playbin", GST_RANK_NONE,
+ GST_TYPE_PLAY_BIN);
+
+ return gst_element_register (plugin, "playbin3", GST_RANK_NONE,
+ GST_TYPE_PLAY_BIN);
+}
--- /dev/null
+/* GStreamer
+ * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-uridecodebin
+ *
+ * Decodes data from a URI into raw media. It selects a source element that can
+ * handle the given #GstURIDecodeBin3:uri scheme and connects it to a decodebin.
+ */
+
+/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
+ * with newer GLib versions (>= 2.31.0) */
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+
+#include <gst/gst.h>
+#include <gst/gst-i18n-plugin.h>
+#include <gst/pbutils/missing-plugins.h>
+
+#include "gstplay-enum.h"
+#include "gstrawcaps.h"
+#include "gstplayback.h"
+
+/* From gstdecodebin2.c */
+gint _decode_bin_compare_factories_func (gconstpointer p1, gconstpointer p2);
+
+#define GST_TYPE_URI_DECODE_BIN3 \
+ (gst_uri_decode_bin3_get_type())
+#define GST_URI_DECODE_BIN3(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_URI_DECODE_BIN3,GstURIDecodeBin3))
+#define GST_URI_DECODE_BIN3_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_URI_DECODE_BIN3,GstURIDecodeBin3Class))
+#define GST_IS_URI_DECODE_BIN3(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_URI_DECODE_BIN3))
+#define GST_IS_URI_DECODE_BIN3_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_URI_DECODE_BIN3))
+#define GST_URI_DECODE_BIN3_CAST(obj) ((GstURIDecodeBin3 *) (obj))
+
+typedef struct _GstURIDecodeBin3 GstURIDecodeBin3;
+typedef struct _GstURIDecodeBin3Class GstURIDecodeBin3Class;
+
+#define GST_URI_DECODE_BIN3_LOCK(dec) (g_mutex_lock(&((GstURIDecodeBin3*)(dec))->lock))
+#define GST_URI_DECODE_BIN3_UNLOCK(dec) (g_mutex_unlock(&((GstURIDecodeBin3*)(dec))->lock))
+
+typedef struct _GstURIDecodeBin3Stream
+{
+ gulong probe_id;
+ guint bitrate;
+} GstURIDecodeBin3Stream;
+
+/**
+ * GstURIDecodeBin3
+ *
+ * uridecodebin element struct
+ */
+struct _GstURIDecodeBin3
+{
+ GstBin parent_instance;
+
+ GMutex lock; /* lock for constructing */
+
+ GMutex factories_lock;
+ guint32 factories_cookie;
+ GList *factories; /* factories we can use for selecting elements */
+
+ gchar *uri;
+ guint64 connection_speed;
+ GstCaps *caps;
+ gchar *encoding;
+
+ gboolean is_stream;
+ gboolean is_adaptive;
+ gboolean need_queue;
+ guint64 buffer_duration; /* When buffering, buffer duration (ns) */
+ guint buffer_size; /* When buffering, buffer size (bytes) */
+ gboolean download;
+ gboolean use_buffering;
+
+ GstElement *source;
+ GstElement *queue;
+ GstElement *typefind;
+ guint have_type_id; /* have-type signal id from typefind */
+ GSList *decodebins;
+ GSList *pending_decodebins;
+ GHashTable *streams;
+ guint numpads;
+
+ /* for dynamic sources */
+ guint src_np_sig_id; /* new-pad signal id */
+ guint src_nmp_sig_id; /* no-more-pads signal id */
+ gint pending;
+ GList *missing_plugin_errors;
+
+ gboolean async_pending; /* async-start has been emitted */
+
+ gboolean expose_allstreams; /* Whether to expose unknow type streams or not */
+
+ guint64 ring_buffer_max_size; /* 0 means disabled */
+};
+
+struct _GstURIDecodeBin3Class
+{
+ GstBinClass parent_class;
+
+ /* signal fired when we found a pad that we cannot decode */
+ void (*unknown_type) (GstElement * element, GstPad * pad, GstCaps * caps);
+
+ /* signal fired to know if we continue trying to decode the given caps */
+ gboolean (*autoplug_continue) (GstElement * element, GstPad * pad,
+ GstCaps * caps);
+ /* signal fired to get a list of factories to try to autoplug */
+ GValueArray *(*autoplug_factories) (GstElement * element, GstPad * pad,
+ GstCaps * caps);
+ /* signal fired to sort the factories */
+ GValueArray *(*autoplug_sort) (GstElement * element, GstPad * pad,
+ GstCaps * caps, GValueArray * factories);
+ /* signal fired to select from the proposed list of factories */
+ GstAutoplugSelectResult (*autoplug_select) (GstElement * element,
+ GstPad * pad, GstCaps * caps, GstElementFactory * factory);
+ /* signal fired when a autoplugged element that is not linked downstream
+ * or exposed wants to query something */
+ gboolean (*autoplug_query) (GstElement * element, GstPad * pad,
+ GstQuery * query);
+
+ /* emitted when all data is decoded */
+ void (*drained) (GstElement * element);
+};
+
+static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS);
+
+GST_DEBUG_CATEGORY_STATIC (gst_uri_decode_bin3_debug);
+#define GST_CAT_DEFAULT gst_uri_decode_bin3_debug
+
+/* signals */
+enum
+{
+ SIGNAL_UNKNOWN_TYPE,
+ SIGNAL_AUTOPLUG_CONTINUE,
+ SIGNAL_AUTOPLUG_FACTORIES,
+ SIGNAL_AUTOPLUG_SELECT,
+ SIGNAL_AUTOPLUG_SORT,
+ SIGNAL_AUTOPLUG_QUERY,
+ SIGNAL_DRAINED,
+ SIGNAL_SOURCE_SETUP,
+ LAST_SIGNAL
+};
+
+/* properties */
+#define DEFAULT_PROP_URI NULL
+#define DEFAULT_PROP_SOURCE NULL
+#define DEFAULT_CONNECTION_SPEED 0
+#define DEFAULT_CAPS (gst_static_caps_get (&default_raw_caps))
+#define DEFAULT_SUBTITLE_ENCODING NULL
+#define DEFAULT_BUFFER_DURATION -1
+#define DEFAULT_BUFFER_SIZE -1
+#define DEFAULT_DOWNLOAD FALSE
+#define DEFAULT_USE_BUFFERING FALSE
+#define DEFAULT_EXPOSE_ALL_STREAMS TRUE
+#define DEFAULT_RING_BUFFER_MAX_SIZE 0
+
+enum
+{
+ PROP_0,
+ PROP_URI,
+ PROP_SOURCE,
+ PROP_CONNECTION_SPEED,
+ PROP_CAPS,
+ PROP_SUBTITLE_ENCODING,
+ PROP_BUFFER_SIZE,
+ PROP_BUFFER_DURATION,
+ PROP_DOWNLOAD,
+ PROP_USE_BUFFERING,
+ PROP_EXPOSE_ALL_STREAMS,
+ PROP_RING_BUFFER_MAX_SIZE
+};
+
+static guint gst_uri_decode_bin3_signals[LAST_SIGNAL] = { 0 };
+
+GType gst_uri_decode_bin3_get_type (void);
+#define gst_uri_decode_bin3_parent_class parent_class
+G_DEFINE_TYPE (GstURIDecodeBin3, gst_uri_decode_bin3, GST_TYPE_BIN);
+
+static void remove_decoders (GstURIDecodeBin3 * bin, gboolean force);
+static void gst_uri_decode_bin3_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_uri_decode_bin3_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_uri_decode_bin3_finalize (GObject * obj);
+
+static void handle_message (GstBin * bin, GstMessage * msg);
+
+static gboolean gst_uri_decode_bin3_query (GstElement * element,
+ GstQuery * query);
+static GstStateChangeReturn gst_uri_decode_bin3_change_state (GstElement *
+ element, GstStateChange transition);
+
+static gboolean
+_gst_boolean_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gboolean myboolean;
+
+ myboolean = g_value_get_boolean (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boolean (return_accu, myboolean);
+
+ /* stop emission if FALSE */
+ return myboolean;
+}
+
+static gboolean
+_gst_boolean_or_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gboolean myboolean;
+ gboolean retboolean;
+
+ myboolean = g_value_get_boolean (handler_return);
+ retboolean = g_value_get_boolean (return_accu);
+
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boolean (return_accu, myboolean || retboolean);
+
+ return TRUE;
+}
+
+static gboolean
+_gst_array_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gpointer array;
+
+ array = g_value_get_boxed (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boxed (return_accu, array);
+
+ return FALSE;
+}
+
+static gboolean
+_gst_select_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ GstAutoplugSelectResult res;
+
+ res = g_value_get_enum (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_enum (return_accu, res);
+
+ /* Call the next handler in the chain (if any) when the current callback
+ * returns TRY. This makes it possible to register separate autoplug-select
+ * handlers that implement different TRY/EXPOSE/SKIP strategies.
+ */
+ if (res == GST_AUTOPLUG_SELECT_TRY)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+_gst_array_hasvalue_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gpointer array;
+
+ array = g_value_get_boxed (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boxed (return_accu, array);
+
+ if (array != NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+gst_uri_decode_bin3_autoplug_continue (GstElement * element, GstPad * pad,
+ GstCaps * caps)
+{
+ /* by default we always continue */
+ return TRUE;
+}
+
+/* Must be called with factories lock! */
+static void
+gst_uri_decode_bin3_update_factories_list (GstURIDecodeBin3 * dec)
+{
+ guint32 cookie;
+
+ cookie = gst_registry_get_feature_list_cookie (gst_registry_get ());
+ if (!dec->factories || dec->factories_cookie != cookie) {
+ if (dec->factories)
+ gst_plugin_feature_list_free (dec->factories);
+ dec->factories =
+ gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_DECODABLE, GST_RANK_MARGINAL);
+ dec->factories =
+ g_list_sort (dec->factories, _decode_bin_compare_factories_func);
+ dec->factories_cookie = cookie;
+ }
+}
+
+static GValueArray *
+gst_uri_decode_bin3_autoplug_factories (GstElement * element, GstPad * pad,
+ GstCaps * caps)
+{
+ GList *list, *tmp;
+ GValueArray *result;
+ GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3_CAST (element);
+
+ GST_DEBUG_OBJECT (element, "finding factories");
+
+ /* return all compatible factories for caps */
+ g_mutex_lock (&dec->factories_lock);
+ gst_uri_decode_bin3_update_factories_list (dec);
+ list =
+ gst_element_factory_list_filter (dec->factories, caps, GST_PAD_SINK,
+ gst_caps_is_fixed (caps));
+ g_mutex_unlock (&dec->factories_lock);
+
+ result = g_value_array_new (g_list_length (list));
+ for (tmp = list; tmp; tmp = tmp->next) {
+ GstElementFactory *factory = GST_ELEMENT_FACTORY_CAST (tmp->data);
+ GValue val = { 0, };
+
+ g_value_init (&val, G_TYPE_OBJECT);
+ g_value_set_object (&val, factory);
+ g_value_array_append (result, &val);
+ g_value_unset (&val);
+ }
+ gst_plugin_feature_list_free (list);
+
+ GST_DEBUG_OBJECT (element, "autoplug-factories returns %p", result);
+
+ return result;
+}
+
+static GValueArray *
+gst_uri_decode_bin3_autoplug_sort (GstElement * element, GstPad * pad,
+ GstCaps * caps, GValueArray * factories)
+{
+ return NULL;
+}
+
+static GstAutoplugSelectResult
+gst_uri_decode_bin3_autoplug_select (GstElement * element, GstPad * pad,
+ GstCaps * caps, GstElementFactory * factory)
+{
+ GST_DEBUG_OBJECT (element, "default autoplug-select returns TRY");
+
+ /* Try factory. */
+ return GST_AUTOPLUG_SELECT_TRY;
+}
+
+static gboolean
+gst_uri_decode_bin3_autoplug_query (GstElement * element, GstPad * pad,
+ GstQuery * query)
+{
+ /* No query handled here */
+ return FALSE;
+}
+
+static void
+gst_uri_decode_bin3_class_init (GstURIDecodeBin3Class * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBinClass *gstbin_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gstelement_class = GST_ELEMENT_CLASS (klass);
+ gstbin_class = GST_BIN_CLASS (klass);
+
+ gobject_class->set_property = gst_uri_decode_bin3_set_property;
+ gobject_class->get_property = gst_uri_decode_bin3_get_property;
+ gobject_class->finalize = gst_uri_decode_bin3_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_URI,
+ g_param_spec_string ("uri", "URI", "URI to decode",
+ DEFAULT_PROP_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_SOURCE,
+ g_param_spec_object ("source", "Source", "Source object used",
+ GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED,
+ g_param_spec_uint64 ("connection-speed", "Connection Speed",
+ "Network connection speed in kbps (0 = unknown)",
+ 0, G_MAXUINT64 / 1000, DEFAULT_CONNECTION_SPEED,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_CAPS,
+ g_param_spec_boxed ("caps", "Caps",
+ "The caps on which to stop decoding. (NULL = default)",
+ GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_SUBTITLE_ENCODING,
+ g_param_spec_string ("subtitle-encoding", "subtitle encoding",
+ "Encoding to assume if input subtitles are not in UTF-8 encoding. "
+ "If not set, the GST_SUBTITLE_ENCODING environment variable will "
+ "be checked for an encoding to use. If that is not set either, "
+ "ISO-8859-15 will be assumed.", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
+ g_param_spec_int ("buffer-size", "Buffer size (bytes)",
+ "Buffer size when buffering streams (-1 default value)",
+ -1, G_MAXINT, DEFAULT_BUFFER_SIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BUFFER_DURATION,
+ g_param_spec_int64 ("buffer-duration", "Buffer duration (ns)",
+ "Buffer duration when buffering streams (-1 default value)",
+ -1, G_MAXINT64, DEFAULT_BUFFER_DURATION,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstURIDecodeBin3::download:
+ *
+ * For certain media type, enable download buffering.
+ */
+ g_object_class_install_property (gobject_class, PROP_DOWNLOAD,
+ g_param_spec_boolean ("download", "Download",
+ "Attempt download buffering when buffering network streams",
+ DEFAULT_DOWNLOAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstURIDecodeBin3::use-buffering:
+ *
+ * Emit BUFFERING messages based on low-/high-percent thresholds of the
+ * demuxed or parsed data.
+ * When download buffering is activated and used for the current media
+ * type, this property does nothing. Otherwise perform buffering on the
+ * demuxed or parsed media.
+ *
+ * Since: 0.10.26
+ */
+ g_object_class_install_property (gobject_class, PROP_USE_BUFFERING,
+ g_param_spec_boolean ("use-buffering", "Use Buffering",
+ "Perform buffering on demuxed/parsed media",
+ DEFAULT_USE_BUFFERING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstURIDecodeBin3::expose-all-streams
+ *
+ * Expose streams of unknown type.
+ *
+ * If set to %FALSE, then only the streams that can be decoded to the final
+ * caps (see 'caps' property) will have a pad exposed. Streams that do not
+ * match those caps but could have been decoded will not have decoder plugged
+ * in internally and will not have a pad exposed.
+ *
+ * Since: 0.10.30
+ */
+ g_object_class_install_property (gobject_class, PROP_EXPOSE_ALL_STREAMS,
+ g_param_spec_boolean ("expose-all-streams", "Expose All Streams",
+ "Expose all streams, including those of unknown type or that don't match the 'caps' property",
+ DEFAULT_EXPOSE_ALL_STREAMS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstURIDecodeBin3::ring-buffer-max-size
+ *
+ * The maximum size of the ring buffer in kilobytes. If set to 0, the ring
+ * buffer is disabled. Default is 0.
+ *
+ * Since: 0.10.31
+ */
+ g_object_class_install_property (gobject_class, PROP_RING_BUFFER_MAX_SIZE,
+ g_param_spec_uint64 ("ring-buffer-max-size",
+ "Max. ring buffer size (bytes)",
+ "Max. amount of data in the ring buffer (bytes, 0 = ring buffer disabled)",
+ 0, G_MAXUINT, DEFAULT_RING_BUFFER_MAX_SIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstURIDecodeBin3::unknown-type:
+ * @bin: The uridecodebin.
+ * @pad: the new pad containing caps that cannot be resolved to a 'final'.
+ * stream type.
+ * @caps: the #GstCaps of the pad that cannot be resolved.
+ *
+ * This signal is emitted when a pad for which there is no further possible
+ * decoding is added to the uridecodebin.
+ */
+ gst_uri_decode_bin3_signals[SIGNAL_UNKNOWN_TYPE] =
+ g_signal_new ("unknown-type", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURIDecodeBin3Class, unknown_type),
+ NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2,
+ GST_TYPE_PAD, GST_TYPE_CAPS);
+
+ /**
+ * GstURIDecodeBin3::autoplug-continue:
+ * @bin: The uridecodebin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps found.
+ *
+ * This signal is emitted whenever uridecodebin finds a new stream. It is
+ * emitted before looking for any elements that can handle that stream.
+ *
+ * <note>
+ * Invocation of signal handlers stops after the first signal handler
+ * returns #FALSE. Signal handlers are invoked in the order they were
+ * connected in.
+ * </note>
+ *
+ * Returns: #TRUE if you wish uridecodebin to look for elements that can
+ * handle the given @caps. If #FALSE, those caps will be considered as
+ * final and the pad will be exposed as such (see 'pad-added' signal of
+ * #GstElement).
+ */
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_CONTINUE] =
+ g_signal_new ("autoplug-continue", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURIDecodeBin3Class,
+ autoplug_continue), _gst_boolean_accumulator, NULL,
+ g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 2, GST_TYPE_PAD,
+ GST_TYPE_CAPS);
+
+ /**
+ * GstURIDecodeBin3::autoplug-factories:
+ * @bin: The uridecodebin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps found.
+ *
+ * This function is emitted when an array of possible factories for @caps on
+ * @pad is needed. Uridecodebin will by default return an array with all
+ * compatible factories, sorted by rank.
+ *
+ * If this function returns NULL, @pad will be exposed as a final caps.
+ *
+ * If this function returns an empty array, the pad will be considered as
+ * having an unhandled type media type.
+ *
+ * <note>
+ * Only the signal handler that is connected first will ever by invoked.
+ * Don't connect signal handlers with the #G_CONNECT_AFTER flag to this
+ * signal, they will never be invoked!
+ * </note>
+ *
+ * Returns: a #GValueArray* with a list of factories to try. The factories are
+ * by default tried in the returned order or based on the index returned by
+ * "autoplug-select".
+ */
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_FACTORIES] =
+ g_signal_new ("autoplug-factories", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURIDecodeBin3Class,
+ autoplug_factories), _gst_array_accumulator, NULL,
+ g_cclosure_marshal_generic, G_TYPE_VALUE_ARRAY, 2,
+ GST_TYPE_PAD, GST_TYPE_CAPS);
+
+ /**
+ * GstURIDecodeBin3::autoplug-sort:
+ * @bin: The uridecodebin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps.
+ * @factories: A #GValueArray of possible #GstElementFactory to use.
+ *
+ * Once decodebin has found the possible #GstElementFactory objects to try
+ * for @caps on @pad, this signal is emited. The purpose of the signal is for
+ * the application to perform additional sorting or filtering on the element
+ * factory array.
+ *
+ * The callee should copy and modify @factories or return #NULL if the
+ * order should not change.
+ *
+ * <note>
+ * Invocation of signal handlers stops after one signal handler has
+ * returned something else than #NULL. Signal handlers are invoked in
+ * the order they were connected in.
+ * Don't connect signal handlers with the #G_CONNECT_AFTER flag to this
+ * signal, they will never be invoked!
+ * </note>
+ *
+ * Returns: A new sorted array of #GstElementFactory objects.
+ *
+ * Since: 0.10.33
+ */
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_SORT] =
+ g_signal_new ("autoplug-sort", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURIDecodeBin3Class, autoplug_sort),
+ _gst_array_hasvalue_accumulator, NULL,
+ g_cclosure_marshal_generic, G_TYPE_VALUE_ARRAY, 3, GST_TYPE_PAD,
+ GST_TYPE_CAPS, G_TYPE_VALUE_ARRAY | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ /**
+ * GstURIDecodeBin3::autoplug-select:
+ * @bin: The uridecodebin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps.
+ * @factory: A #GstElementFactory to use.
+ *
+ * This signal is emitted once uridecodebin has found all the possible
+ * #GstElementFactory that can be used to handle the given @caps. For each of
+ * those factories, this signal is emitted.
+ *
+ * The signal handler should return a #GST_TYPE_AUTOPLUG_SELECT_RESULT enum
+ * value indicating what decodebin should do next.
+ *
+ * A value of #GST_AUTOPLUG_SELECT_TRY will try to autoplug an element from
+ * @factory.
+ *
+ * A value of #GST_AUTOPLUG_SELECT_EXPOSE will expose @pad without plugging
+ * any element to it.
+ *
+ * A value of #GST_AUTOPLUG_SELECT_SKIP will skip @factory and move to the
+ * next factory.
+ *
+ * <note>
+ * The signal handler will not be invoked if any of the previously
+ * registered signal handlers (if any) return a value other than
+ * GST_AUTOPLUG_SELECT_TRY. Which also means that if you return
+ * GST_AUTOPLUG_SELECT_TRY from one signal handler, handlers that get
+ * registered next (again, if any) can override that decision.
+ * </note>
+ *
+ * Returns: a #GST_TYPE_AUTOPLUG_SELECT_RESULT that indicates the required
+ * operation. The default handler will always return
+ * #GST_AUTOPLUG_SELECT_TRY.
+ */
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_SELECT] =
+ g_signal_new ("autoplug-select", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURIDecodeBin3Class,
+ autoplug_select), _gst_select_accumulator, NULL,
+ g_cclosure_marshal_generic,
+ GST_TYPE_AUTOPLUG_SELECT_RESULT, 3, GST_TYPE_PAD, GST_TYPE_CAPS,
+ GST_TYPE_ELEMENT_FACTORY);
+
+ /**
+ * GstDecodeBin::autoplug-query:
+ * @bin: The decodebin.
+ * @child: The child element doing the query
+ * @pad: The #GstPad.
+ * @query: The #GstQuery.
+ *
+ * This signal is emitted whenever an autoplugged element that is
+ * not linked downstream yet and not exposed does a query. It can
+ * be used to tell the element about the downstream supported caps
+ * for example.
+ *
+ * Returns: #TRUE if the query was handled, #FALSE otherwise.
+ */
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_QUERY] =
+ g_signal_new ("autoplug-query", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURIDecodeBin3Class,
+ autoplug_query), _gst_boolean_or_accumulator, NULL,
+ g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 3, GST_TYPE_PAD,
+ GST_TYPE_ELEMENT, GST_TYPE_QUERY | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ /**
+ * GstURIDecodeBin3::drained:
+ *
+ * This signal is emitted when the data for the current uri is played.
+ */
+ gst_uri_decode_bin3_signals[SIGNAL_DRAINED] =
+ g_signal_new ("drained", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstURIDecodeBin3Class, drained), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ /**
+ * GstURIDecodeBin3::source-setup:
+ * @bin: the uridecodebin.
+ * @source: source element
+ *
+ * This signal is emitted after the source element has been created, so
+ * it can be configured by setting additional properties (e.g. set a
+ * proxy server for an http source, or set the device and read speed for
+ * an audio cd source). This is functionally equivalent to connecting to
+ * the notify::source signal, but more convenient.
+ *
+ * Since: 0.10.33
+ */
+ gst_uri_decode_bin3_signals[SIGNAL_SOURCE_SETUP] =
+ g_signal_new ("source-setup", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&srctemplate));
+ gst_element_class_set_static_metadata (gstelement_class,
+ "URI Decoder 3", "Generic/Bin/Decoder",
+ "Autoplug and decode an URI to raw media",
+ "Wim Taymans <wim.taymans@gmail.com>");
+
+ gstelement_class->query = GST_DEBUG_FUNCPTR (gst_uri_decode_bin3_query);
+ gstelement_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_uri_decode_bin3_change_state);
+
+ gstbin_class->handle_message = GST_DEBUG_FUNCPTR (handle_message);
+
+ klass->autoplug_continue =
+ GST_DEBUG_FUNCPTR (gst_uri_decode_bin3_autoplug_continue);
+ klass->autoplug_factories =
+ GST_DEBUG_FUNCPTR (gst_uri_decode_bin3_autoplug_factories);
+ klass->autoplug_sort = GST_DEBUG_FUNCPTR (gst_uri_decode_bin3_autoplug_sort);
+ klass->autoplug_select =
+ GST_DEBUG_FUNCPTR (gst_uri_decode_bin3_autoplug_select);
+ klass->autoplug_query =
+ GST_DEBUG_FUNCPTR (gst_uri_decode_bin3_autoplug_query);
+}
+
+static void
+gst_uri_decode_bin3_init (GstURIDecodeBin3 * dec)
+{
+ /* first filter out the interesting element factories */
+ g_mutex_init (&dec->factories_lock);
+
+ g_mutex_init (&dec->lock);
+
+ dec->uri = g_strdup (DEFAULT_PROP_URI);
+ dec->connection_speed = DEFAULT_CONNECTION_SPEED;
+ dec->caps = DEFAULT_CAPS;
+ dec->encoding = g_strdup (DEFAULT_SUBTITLE_ENCODING);
+
+ dec->buffer_duration = DEFAULT_BUFFER_DURATION;
+ dec->buffer_size = DEFAULT_BUFFER_SIZE;
+ dec->download = DEFAULT_DOWNLOAD;
+ dec->use_buffering = DEFAULT_USE_BUFFERING;
+ dec->expose_allstreams = DEFAULT_EXPOSE_ALL_STREAMS;
+ dec->ring_buffer_max_size = DEFAULT_RING_BUFFER_MAX_SIZE;
+
+ GST_OBJECT_FLAG_SET (dec, GST_ELEMENT_FLAG_SOURCE);
+}
+
+static void
+gst_uri_decode_bin3_finalize (GObject * obj)
+{
+ GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (obj);
+
+ remove_decoders (dec, TRUE);
+ g_mutex_clear (&dec->lock);
+ g_mutex_clear (&dec->factories_lock);
+ g_free (dec->uri);
+ g_free (dec->encoding);
+ if (dec->factories)
+ gst_plugin_feature_list_free (dec->factories);
+ if (dec->caps)
+ gst_caps_unref (dec->caps);
+
+ G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+gst_uri_decode_bin3_set_encoding (GstURIDecodeBin3 * dec,
+ const gchar * encoding)
+{
+ GSList *walk;
+
+ GST_URI_DECODE_BIN3_LOCK (dec);
+
+ /* set property first */
+ GST_OBJECT_LOCK (dec);
+ g_free (dec->encoding);
+ dec->encoding = g_strdup (encoding);
+ GST_OBJECT_UNLOCK (dec);
+
+ /* set the property on all decodebins now */
+ for (walk = dec->decodebins; walk; walk = g_slist_next (walk)) {
+ GObject *decodebin = G_OBJECT (walk->data);
+
+ g_object_set (decodebin, "subtitle-encoding", encoding, NULL);
+ }
+ GST_URI_DECODE_BIN3_UNLOCK (dec);
+}
+
+static void
+gst_uri_decode_bin3_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (object);
+
+ switch (prop_id) {
+ case PROP_URI:
+ GST_OBJECT_LOCK (dec);
+ g_free (dec->uri);
+ dec->uri = g_value_dup_string (value);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_CONNECTION_SPEED:
+ GST_OBJECT_LOCK (dec);
+ dec->connection_speed = g_value_get_uint64 (value) * 1000;
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_CAPS:
+ GST_OBJECT_LOCK (dec);
+ if (dec->caps)
+ gst_caps_unref (dec->caps);
+ dec->caps = g_value_dup_boxed (value);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_SUBTITLE_ENCODING:
+ gst_uri_decode_bin3_set_encoding (dec, g_value_get_string (value));
+ break;
+ case PROP_BUFFER_SIZE:
+ dec->buffer_size = g_value_get_int (value);
+ break;
+ case PROP_BUFFER_DURATION:
+ dec->buffer_duration = g_value_get_int64 (value);
+ break;
+ case PROP_DOWNLOAD:
+ dec->download = g_value_get_boolean (value);
+ break;
+ case PROP_USE_BUFFERING:
+ dec->use_buffering = g_value_get_boolean (value);
+ break;
+ case PROP_EXPOSE_ALL_STREAMS:
+ dec->expose_allstreams = g_value_get_boolean (value);
+ break;
+ case PROP_RING_BUFFER_MAX_SIZE:
+ dec->ring_buffer_max_size = g_value_get_uint64 (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_uri_decode_bin3_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (object);
+
+ switch (prop_id) {
+ case PROP_URI:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_string (value, dec->uri);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_SOURCE:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_object (value, dec->source);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_CONNECTION_SPEED:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_uint64 (value, dec->connection_speed / 1000);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_CAPS:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_boxed (value, dec->caps);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_SUBTITLE_ENCODING:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_string (value, dec->encoding);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_BUFFER_SIZE:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_int (value, dec->buffer_size);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_BUFFER_DURATION:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_int64 (value, dec->buffer_duration);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_DOWNLOAD:
+ g_value_set_boolean (value, dec->download);
+ break;
+ case PROP_USE_BUFFERING:
+ g_value_set_boolean (value, dec->use_buffering);
+ break;
+ case PROP_EXPOSE_ALL_STREAMS:
+ g_value_set_boolean (value, dec->expose_allstreams);
+ break;
+ case PROP_RING_BUFFER_MAX_SIZE:
+ g_value_set_uint64 (value, dec->ring_buffer_max_size);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+do_async_start (GstURIDecodeBin3 * dbin)
+{
+ GstMessage *message;
+
+ dbin->async_pending = TRUE;
+
+ message = gst_message_new_async_start (GST_OBJECT_CAST (dbin));
+ GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (dbin), message);
+}
+
+static void
+do_async_done (GstURIDecodeBin3 * dbin)
+{
+ GstMessage *message;
+
+ if (dbin->async_pending) {
+ GST_DEBUG_OBJECT (dbin, "posting ASYNC_DONE");
+ message =
+ gst_message_new_async_done (GST_OBJECT_CAST (dbin),
+ GST_CLOCK_TIME_NONE);
+ GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (dbin), message);
+
+ dbin->async_pending = FALSE;
+ }
+}
+
+#define DEFAULT_QUEUE_SIZE (3 * GST_SECOND)
+#define DEFAULT_QUEUE_MIN_THRESHOLD ((DEFAULT_QUEUE_SIZE * 30) / 100)
+#define DEFAULT_QUEUE_THRESHOLD ((DEFAULT_QUEUE_SIZE * 95) / 100)
+
+static void
+unknown_type_cb (GstElement * element, GstPad * pad, GstCaps * caps,
+ GstURIDecodeBin3 * decoder)
+{
+ gchar *capsstr;
+
+ capsstr = gst_caps_to_string (caps);
+ GST_ELEMENT_WARNING (decoder, STREAM, CODEC_NOT_FOUND,
+ (_("No decoder available for type \'%s\'."), capsstr), (NULL));
+ g_free (capsstr);
+}
+
+/* add a streaminfo that indicates that the stream is handled by the
+ * given element. This usually means that a stream without actual data is
+ * produced but one that is sunken by an element. Examples of this are:
+ * cdaudio, a hardware decoder/sink, dvd meta bins etc...
+ */
+static void
+add_element_stream (GstElement * element, GstURIDecodeBin3 * decoder)
+{
+ g_warning ("add element stream");
+}
+
+/* when the decoder element signals that no more pads will be generated, we
+ * can commit the current group.
+ */
+static void
+no_more_pads_full (GstElement * element, gboolean subs,
+ GstURIDecodeBin3 * decoder)
+{
+ gboolean final;
+
+ /* setup phase */
+ GST_DEBUG_OBJECT (element, "no more pads, %d pending", decoder->pending);
+
+ GST_URI_DECODE_BIN3_LOCK (decoder);
+ final = (decoder->pending == 0);
+
+ /* nothing pending, we can exit */
+ if (final)
+ goto done;
+
+ /* the object has no pending no_more_pads */
+ if (!g_object_get_data (G_OBJECT (element), "pending"))
+ goto done;
+ g_object_set_data (G_OBJECT (element), "pending", NULL);
+
+ decoder->pending--;
+ final = (decoder->pending == 0);
+
+done:
+ GST_URI_DECODE_BIN3_UNLOCK (decoder);
+
+ if (final) {
+ /* If we got not a single stream yet, that means that all
+ * decodebins had missing plugins for all of their streams!
+ */
+ if (!decoder->streams || g_hash_table_size (decoder->streams) == 0) {
+ if (decoder->missing_plugin_errors) {
+ GString *str = g_string_new ("");
+ GList *l;
+
+ for (l = decoder->missing_plugin_errors; l; l = l->next) {
+ GstMessage *msg = l->data;
+ gchar *debug;
+
+ gst_message_parse_error (msg, NULL, &debug);
+ g_string_append (str, debug);
+ g_free (debug);
+ gst_message_unref (msg);
+ }
+ g_list_free (decoder->missing_plugin_errors);
+ decoder->missing_plugin_errors = NULL;
+
+ GST_ELEMENT_ERROR (decoder, CORE, MISSING_PLUGIN, (NULL),
+ ("no suitable plugins found:\n%s", str->str));
+ g_string_free (str, TRUE);
+ } else {
+ GST_ELEMENT_ERROR (decoder, CORE, MISSING_PLUGIN, (NULL),
+ ("no suitable plugins found"));
+ }
+ } else {
+ gst_element_no_more_pads (GST_ELEMENT_CAST (decoder));
+ }
+ do_async_done (decoder);
+ }
+
+ return;
+}
+
+static void
+no_more_pads (GstElement * element, GstURIDecodeBin3 * decoder)
+{
+ no_more_pads_full (element, FALSE, decoder);
+}
+
+static void
+source_no_more_pads (GstElement * element, GstURIDecodeBin3 * bin)
+{
+ GST_DEBUG_OBJECT (bin, "No more pads in source element %s.",
+ GST_ELEMENT_NAME (element));
+
+ g_signal_handler_disconnect (element, bin->src_np_sig_id);
+ bin->src_np_sig_id = 0;
+ g_signal_handler_disconnect (element, bin->src_nmp_sig_id);
+ bin->src_nmp_sig_id = 0;
+
+ no_more_pads_full (element, FALSE, bin);
+}
+
+static void
+configure_stream_buffering (GstURIDecodeBin3 * decoder)
+{
+ GstElement *queue = NULL;
+ GHashTableIter iter;
+ gpointer key, value;
+ gint bitrate = 0;
+
+ /* automatic configuration enabled ? */
+ if (decoder->buffer_size != -1)
+ return;
+
+ GST_URI_DECODE_BIN3_LOCK (decoder);
+ if (decoder->queue)
+ queue = gst_object_ref (decoder->queue);
+
+ g_hash_table_iter_init (&iter, decoder->streams);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ GstURIDecodeBin3Stream *stream = value;
+
+ if (stream->bitrate && bitrate >= 0)
+ bitrate += stream->bitrate;
+ else
+ bitrate = -1;
+ }
+ GST_URI_DECODE_BIN3_UNLOCK (decoder);
+
+ GST_DEBUG_OBJECT (decoder, "overall bitrate %d", bitrate);
+ if (!queue)
+ return;
+
+ if (bitrate > 0) {
+ guint64 time;
+ guint bytes;
+
+ /* all streams have a bitrate;
+ * configure queue size based on queue duration using combined bitrate */
+ g_object_get (queue, "max-size-time", &time, NULL);
+ GST_DEBUG_OBJECT (decoder, "queue buffering time %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (time));
+ if (time > 0) {
+ bytes = gst_util_uint64_scale (time, bitrate, 8 * GST_SECOND);
+ GST_DEBUG_OBJECT (decoder, "corresponds to buffer size %d", bytes);
+ g_object_set (queue, "max-size-bytes", bytes, NULL);
+ }
+ }
+
+ gst_object_unref (queue);
+}
+
+static GstPadProbeReturn
+decoded_pad_event_probe (GstPad * pad, GstPadProbeInfo * info,
+ gpointer user_data)
+{
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+ GstURIDecodeBin3 *decoder = user_data;
+
+ GST_LOG_OBJECT (pad, "%s, decoder %p", GST_EVENT_TYPE_NAME (event), decoder);
+
+ /* look for a bitrate tag */
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_TAG:
+ {
+ GstTagList *list;
+ guint bitrate = 0;
+ GstURIDecodeBin3Stream *stream;
+
+ gst_event_parse_tag (event, &list);
+ if (!gst_tag_list_get_uint_index (list, GST_TAG_NOMINAL_BITRATE, 0,
+ &bitrate)) {
+ gst_tag_list_get_uint_index (list, GST_TAG_BITRATE, 0, &bitrate);
+ }
+ GST_DEBUG_OBJECT (pad, "found bitrate %u", bitrate);
+ if (bitrate) {
+ GST_URI_DECODE_BIN3_LOCK (decoder);
+ stream = g_hash_table_lookup (decoder->streams, pad);
+ GST_URI_DECODE_BIN3_UNLOCK (decoder);
+ if (stream) {
+ stream->bitrate = bitrate;
+ /* no longer need this probe now */
+ gst_pad_remove_probe (pad, stream->probe_id);
+ /* configure buffer if possible */
+ configure_stream_buffering (decoder);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ /* never drop */
+ return GST_PAD_PROBE_OK;
+}
+
+
+static gboolean
+copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
+{
+ GstPad *gpad = GST_PAD_CAST (user_data);
+
+ GST_DEBUG_OBJECT (gpad, "store sticky event %" GST_PTR_FORMAT, *event);
+ gst_pad_store_sticky_event (gpad, *event);
+
+ return TRUE;
+}
+
+/* Called by the signal handlers when a decodebin has found a new raw pad */
+static void
+new_decoded_pad_added_cb (GstElement * element, GstPad * pad,
+ GstURIDecodeBin3 * decoder)
+{
+ GstPad *newpad;
+ GstPadTemplate *pad_tmpl;
+ gchar *padname;
+ GstURIDecodeBin3Stream *stream;
+
+ GST_DEBUG_OBJECT (element, "new decoded pad, name: <%s>", GST_PAD_NAME (pad));
+
+ GST_URI_DECODE_BIN3_LOCK (decoder);
+ padname = g_strdup_printf ("src_%u", decoder->numpads);
+ decoder->numpads++;
+ GST_URI_DECODE_BIN3_UNLOCK (decoder);
+
+ pad_tmpl = gst_static_pad_template_get (&srctemplate);
+ newpad = gst_ghost_pad_new_from_template (padname, pad, pad_tmpl);
+ gst_object_unref (pad_tmpl);
+ g_free (padname);
+
+ /* store ref to the ghostpad so we can remove it */
+ g_object_set_data (G_OBJECT (pad), "uridecodebin.ghostpad", newpad);
+
+ /* add event probe to monitor tags */
+ stream = g_slice_alloc0 (sizeof (GstURIDecodeBin3Stream));
+ stream->probe_id =
+ gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ decoded_pad_event_probe, decoder, NULL);
+ GST_URI_DECODE_BIN3_LOCK (decoder);
+ g_hash_table_insert (decoder->streams, pad, stream);
+ GST_URI_DECODE_BIN3_UNLOCK (decoder);
+
+ gst_pad_set_active (newpad, TRUE);
+ gst_pad_sticky_events_foreach (pad, copy_sticky_events, newpad);
+ gst_element_add_pad (GST_ELEMENT_CAST (decoder), newpad);
+
+ /* Let decodebin handle async-done from here on out */
+ do_async_done (decoder);
+}
+
+static GstPadProbeReturn
+source_pad_event_probe (GstPad * pad, GstPadProbeInfo * info,
+ gpointer user_data)
+{
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+ GstURIDecodeBin3 *decoder = user_data;
+
+ GST_LOG_OBJECT (pad, "%s, decoder %p", GST_EVENT_TYPE_NAME (event), decoder);
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
+ GST_DEBUG_OBJECT (pad, "we received EOS");
+
+ g_signal_emit (decoder,
+ gst_uri_decode_bin3_signals[SIGNAL_DRAINED], 0, NULL);
+ }
+ /* never drop events */
+ return GST_PAD_PROBE_OK;
+}
+
+/* called when we found a raw pad on the source element. We need to set up a
+ * padprobe to detect EOS before exposing the pad. */
+static void
+expose_decoded_pad (GstElement * element, GstPad * pad,
+ GstURIDecodeBin3 * decoder)
+{
+ gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ source_pad_event_probe, decoder, NULL);
+
+ new_decoded_pad_added_cb (element, pad, decoder);
+}
+
+static void
+pad_removed_cb (GstElement * element, GstPad * pad, GstURIDecodeBin3 * decoder)
+{
+ GstPad *ghost;
+
+ GST_DEBUG_OBJECT (element, "pad removed name: <%s:%s>",
+ GST_DEBUG_PAD_NAME (pad));
+
+ /* we only care about srcpads */
+ if (!GST_PAD_IS_SRC (pad))
+ return;
+
+ if (!(ghost = g_object_get_data (G_OBJECT (pad), "uridecodebin.ghostpad")))
+ goto no_ghost;
+
+ /* unghost the pad */
+ gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (ghost), NULL);
+
+ /* deactivate and remove */
+ gst_pad_set_active (pad, FALSE);
+ gst_element_remove_pad (GST_ELEMENT_CAST (decoder), ghost);
+
+ return;
+
+ /* ERRORS */
+no_ghost:
+ {
+ GST_WARNING_OBJECT (element, "no ghost pad found");
+ return;
+ }
+}
+
+/* helper function to lookup stuff in lists */
+static gboolean
+array_has_value (const gchar * values[], const gchar * value)
+{
+ gint i;
+
+ for (i = 0; values[i]; i++) {
+ if (g_str_has_prefix (value, values[i]))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+array_has_uri_value (const gchar * values[], const gchar * value)
+{
+ gint i;
+
+ for (i = 0; values[i]; i++) {
+ if (!g_ascii_strncasecmp (value, values[i], strlen (values[i])))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* list of URIs that we consider to be streams and that need buffering.
+ * We have no mechanism yet to figure this out with a query. */
+static const gchar *stream_uris[] = { "http://", "https://", "mms://",
+ "mmsh://", "mmsu://", "mmst://", "fd://", "myth://", "ssh://",
+ "ftp://", "sftp://",
+ NULL
+};
+
+/* list of URIs that need a queue because they are pretty bursty */
+static const gchar *queue_uris[] = { "cdda://", NULL };
+
+/* blacklisted URIs, we know they will always fail. */
+static const gchar *blacklisted_uris[] = { NULL };
+
+/* media types that use adaptive streaming */
+static const gchar *adaptive_media[] = {
+ "application/x-hls", "application/vnd.ms-sstr+xml",
+ "application/dash+xml", NULL
+};
+
+#define IS_STREAM_URI(uri) (array_has_uri_value (stream_uris, uri))
+#define IS_QUEUE_URI(uri) (array_has_uri_value (queue_uris, uri))
+#define IS_BLACKLISTED_URI(uri) (array_has_uri_value (blacklisted_uris, uri))
+#define IS_ADAPTIVE_MEDIA(media) (array_has_value (adaptive_media, media))
+
+/*
+ * Generate and configure a source element.
+ */
+static GstElement *
+gen_source_element (GstURIDecodeBin3 * decoder)
+{
+ GObjectClass *source_class;
+ GstElement *source;
+ GParamSpec *pspec;
+ GstQuery *query;
+ GstSchedulingFlags flags;
+ GError *err = NULL;
+
+ if (!decoder->uri)
+ goto no_uri;
+
+ GST_LOG_OBJECT (decoder, "finding source for %s", decoder->uri);
+
+ if (!gst_uri_is_valid (decoder->uri))
+ goto invalid_uri;
+
+ if (IS_BLACKLISTED_URI (decoder->uri))
+ goto uri_blacklisted;
+
+ source =
+ gst_element_make_from_uri (GST_URI_SRC, decoder->uri, "source", &err);
+ if (!source)
+ goto no_source;
+
+ GST_LOG_OBJECT (decoder, "found source type %s", G_OBJECT_TYPE_NAME (source));
+
+ query = gst_query_new_scheduling ();
+ if (gst_element_query (source, query)) {
+ gst_query_parse_scheduling (query, &flags, NULL, NULL, NULL);
+ decoder->is_stream = flags & GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED;
+ } else
+ decoder->is_stream = IS_STREAM_URI (decoder->uri);
+ gst_query_unref (query);
+
+ GST_LOG_OBJECT (decoder, "source is stream: %d", decoder->is_stream);
+
+ decoder->need_queue = IS_QUEUE_URI (decoder->uri);
+ GST_LOG_OBJECT (decoder, "source needs queue: %d", decoder->need_queue);
+
+ source_class = G_OBJECT_GET_CLASS (source);
+
+ pspec = g_object_class_find_property (source_class, "connection-speed");
+ if (pspec != NULL) {
+ guint64 speed = decoder->connection_speed / 1000;
+ gboolean wrong_type = FALSE;
+
+ if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_UINT) {
+ GParamSpecUInt *pspecuint = G_PARAM_SPEC_UINT (pspec);
+
+ speed = CLAMP (speed, pspecuint->minimum, pspecuint->maximum);
+ } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT) {
+ GParamSpecInt *pspecint = G_PARAM_SPEC_INT (pspec);
+
+ speed = CLAMP (speed, pspecint->minimum, pspecint->maximum);
+ } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_UINT64) {
+ GParamSpecUInt64 *pspecuint = G_PARAM_SPEC_UINT64 (pspec);
+
+ speed = CLAMP (speed, pspecuint->minimum, pspecuint->maximum);
+ } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT64) {
+ GParamSpecInt64 *pspecint = G_PARAM_SPEC_INT64 (pspec);
+
+ speed = CLAMP (speed, pspecint->minimum, pspecint->maximum);
+ } else {
+ GST_WARNING_OBJECT (decoder,
+ "The connection speed property %" G_GUINT64_FORMAT
+ " of type %s is not usefull not setting it", speed,
+ g_type_name (G_PARAM_SPEC_TYPE (pspec)));
+ wrong_type = TRUE;
+ }
+
+ if (!wrong_type) {
+ g_object_set (source, "connection-speed", speed, NULL);
+
+ GST_DEBUG_OBJECT (decoder,
+ "setting connection-speed=%" G_GUINT64_FORMAT " to source element",
+ speed);
+ }
+ }
+
+ pspec = g_object_class_find_property (source_class, "subtitle-encoding");
+ if (pspec != NULL && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_STRING) {
+ GST_DEBUG_OBJECT (decoder,
+ "setting subtitle-encoding=%s to source element", decoder->encoding);
+ g_object_set (source, "subtitle-encoding", decoder->encoding, NULL);
+ }
+ return source;
+
+ /* ERRORS */
+no_uri:
+ {
+ GST_ELEMENT_ERROR (decoder, RESOURCE, NOT_FOUND,
+ (_("No URI specified to play from.")), (NULL));
+ return NULL;
+ }
+invalid_uri:
+ {
+ GST_ELEMENT_ERROR (decoder, RESOURCE, NOT_FOUND,
+ (_("Invalid URI \"%s\"."), decoder->uri), (NULL));
+ g_clear_error (&err);
+ return NULL;
+ }
+uri_blacklisted:
+ {
+ GST_ELEMENT_ERROR (decoder, RESOURCE, FAILED,
+ (_("This stream type cannot be played yet.")), (NULL));
+ return NULL;
+ }
+no_source:
+ {
+ /* whoops, could not create the source element, dig a little deeper to
+ * figure out what might be wrong. */
+ if (err != NULL && err->code == GST_URI_ERROR_UNSUPPORTED_PROTOCOL) {
+ gchar *prot;
+
+ prot = gst_uri_get_protocol (decoder->uri);
+ if (prot == NULL)
+ goto invalid_uri;
+
+ gst_element_post_message (GST_ELEMENT_CAST (decoder),
+ gst_missing_uri_source_message_new (GST_ELEMENT (decoder), prot));
+
+ GST_ELEMENT_ERROR (decoder, CORE, MISSING_PLUGIN,
+ (_("No URI handler implemented for \"%s\"."), prot), (NULL));
+
+ g_free (prot);
+ } else {
+ GST_ELEMENT_ERROR (decoder, RESOURCE, NOT_FOUND,
+ ("%s", (err) ? err->message : "URI was not accepted by any element"),
+ ("No element accepted URI '%s'", decoder->uri));
+ }
+
+ g_clear_error (&err);
+ return NULL;
+ }
+}
+
+/**
+ * has_all_raw_caps:
+ * @pad: a #GstPad
+ * @all_raw: pointer to hold the result
+ *
+ * check if the caps of the pad are all raw. The caps are all raw if
+ * all of its structures contain audio/x-raw or video/x-raw.
+ *
+ * Returns: %FALSE @pad has no caps. Else TRUE and @all_raw set t the result.
+ */
+static gboolean
+has_all_raw_caps (GstPad * pad, GstCaps * rawcaps, gboolean * all_raw)
+{
+ GstCaps *caps, *intersection;
+ gint capssize;
+ gboolean res = FALSE;
+
+ caps = gst_pad_query_caps (pad, NULL);
+ if (caps == NULL)
+ return FALSE;
+
+ GST_DEBUG_OBJECT (pad, "have caps %" GST_PTR_FORMAT, caps);
+
+ capssize = gst_caps_get_size (caps);
+ /* no caps, skip and move to the next pad */
+ if (capssize == 0 || gst_caps_is_empty (caps) || gst_caps_is_any (caps))
+ goto done;
+
+ intersection = gst_caps_intersect (caps, rawcaps);
+ *all_raw = !gst_caps_is_empty (intersection)
+ && (gst_caps_get_size (intersection) == capssize);
+ gst_caps_unref (intersection);
+
+ res = TRUE;
+
+done:
+ gst_caps_unref (caps);
+ return res;
+}
+
+static void
+post_missing_plugin_error (GstElement * dec, const gchar * element_name)
+{
+ GstMessage *msg;
+
+ msg = gst_missing_element_message_new (dec, element_name);
+ gst_element_post_message (dec, msg);
+
+ GST_ELEMENT_ERROR (dec, CORE, MISSING_PLUGIN,
+ (_("Missing element '%s' - check your GStreamer installation."),
+ element_name), (NULL));
+ do_async_done (GST_URI_DECODE_BIN3 (dec));
+}
+
+/**
+ * analyse_source:
+ * @decoder: a #GstURIDecodeBin3
+ * @is_raw: are all pads raw data
+ * @have_out: does the source have output
+ * @is_dynamic: is this a dynamic source
+ * @use_queue: put a queue before raw output pads
+ *
+ * Check the source of @decoder and collect information about it.
+ *
+ * @is_raw will be set to TRUE if the source only produces raw pads. When this
+ * function returns, all of the raw pad of the source will be added
+ * to @decoder.
+ *
+ * @have_out: will be set to TRUE if the source has output pads.
+ *
+ * @is_dynamic: TRUE if the element will create (more) pads dynamically later
+ * on.
+ *
+ * Returns: FALSE if a fatal error occured while scanning.
+ */
+static gboolean
+analyse_source (GstURIDecodeBin3 * decoder, gboolean * is_raw,
+ gboolean * have_out, gboolean * is_dynamic, gboolean use_queue)
+{
+ GstIterator *pads_iter;
+ gboolean done = FALSE;
+ gboolean res = TRUE;
+ GstCaps *rawcaps;
+ GstPad *pad;
+ GValue item = { 0, };
+
+ *have_out = FALSE;
+ *is_raw = FALSE;
+ *is_dynamic = FALSE;
+
+ g_object_get (decoder, "caps", &rawcaps, NULL);
+ if (!rawcaps)
+ rawcaps = DEFAULT_CAPS;
+
+ pads_iter = gst_element_iterate_src_pads (decoder->source);
+ while (!done) {
+ switch (gst_iterator_next (pads_iter, &item)) {
+ case GST_ITERATOR_ERROR:
+ res = FALSE;
+ /* FALLTROUGH */
+ case GST_ITERATOR_DONE:
+ done = TRUE;
+ break;
+ case GST_ITERATOR_RESYNC:
+ /* reset results and resync */
+ *have_out = FALSE;
+ *is_raw = FALSE;
+ *is_dynamic = FALSE;
+ gst_iterator_resync (pads_iter);
+ break;
+ case GST_ITERATOR_OK:
+ pad = g_value_dup_object (&item);
+ /* we now officially have an ouput pad */
+ *have_out = TRUE;
+
+ /* if FALSE, this pad has no caps and we continue with the next pad. */
+ if (!has_all_raw_caps (pad, rawcaps, is_raw)) {
+ gst_object_unref (pad);
+ g_value_reset (&item);
+ break;
+ }
+
+ /* caps on source pad are all raw, we can add the pad */
+ if (*is_raw) {
+ GstElement *outelem;
+
+ if (use_queue) {
+ GstPad *sinkpad;
+
+ /* insert a queue element right before the raw pad */
+ outelem = gst_element_factory_make ("queue2", NULL);
+ if (!outelem)
+ goto no_queue2;
+
+ gst_bin_add (GST_BIN_CAST (decoder), outelem);
+
+ sinkpad = gst_element_get_static_pad (outelem, "sink");
+ gst_pad_link (pad, sinkpad);
+ gst_object_unref (sinkpad);
+
+ /* save queue pointer so we can remove it later */
+ decoder->queue = outelem;
+
+ /* get the new raw srcpad */
+ gst_object_unref (pad);
+ pad = gst_element_get_static_pad (outelem, "src");
+ } else {
+ outelem = decoder->source;
+ }
+ expose_decoded_pad (outelem, pad, decoder);
+ }
+ gst_object_unref (pad);
+ g_value_reset (&item);
+ break;
+ }
+ }
+ g_value_unset (&item);
+ gst_iterator_free (pads_iter);
+ gst_caps_unref (rawcaps);
+
+ if (!*have_out) {
+ GstElementClass *elemclass;
+ GList *walk;
+
+ /* element has no output pads, check for padtemplates that list SOMETIMES
+ * pads. */
+ elemclass = GST_ELEMENT_GET_CLASS (decoder->source);
+
+ walk = gst_element_class_get_pad_template_list (elemclass);
+ while (walk != NULL) {
+ GstPadTemplate *templ;
+
+ templ = (GstPadTemplate *) walk->data;
+ if (GST_PAD_TEMPLATE_DIRECTION (templ) == GST_PAD_SRC) {
+ if (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_SOMETIMES)
+ *is_dynamic = TRUE;
+ break;
+ }
+ walk = g_list_next (walk);
+ }
+ }
+
+ return res;
+no_queue2:
+ {
+ post_missing_plugin_error (GST_ELEMENT_CAST (decoder), "queue2");
+
+ gst_object_unref (pad);
+ g_value_unset (&item);
+ gst_iterator_free (pads_iter);
+ gst_caps_unref (rawcaps);
+
+ return FALSE;
+ }
+}
+
+/* Remove all decodebin from ourself
+ * If force is FALSE, then the decodebin instances will be stored in
+ * pending_decodebins for re-use later on.
+ * If force is TRUE, then all decodebin instances will be unreferenced
+ * and cleared, including the pending ones. */
+static void
+remove_decoders (GstURIDecodeBin3 * bin, gboolean force)
+{
+ GSList *walk;
+
+ for (walk = bin->decodebins; walk; walk = g_slist_next (walk)) {
+ GstElement *decoder = GST_ELEMENT_CAST (walk->data);
+
+ GST_DEBUG_OBJECT (bin, "removing old decoder element");
+ if (force) {
+ gst_element_set_state (decoder, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (bin), decoder);
+ } else {
+ GstCaps *caps;
+
+ gst_element_set_state (decoder, GST_STATE_READY);
+ /* add it to our list of pending decodebins */
+ g_object_ref (decoder);
+ gst_bin_remove (GST_BIN_CAST (bin), decoder);
+ /* restore some properties we might have changed */
+ g_object_set (decoder, "sink-caps", NULL, NULL);
+ caps = DEFAULT_CAPS;
+ g_object_set (decoder, "caps", caps, NULL);
+ gst_caps_unref (caps);
+ /* make it freshly floating again */
+ g_object_force_floating (G_OBJECT (decoder));
+
+ bin->pending_decodebins =
+ g_slist_prepend (bin->pending_decodebins, decoder);
+ }
+ }
+ g_slist_free (bin->decodebins);
+ bin->decodebins = NULL;
+ if (force) {
+ GSList *tmp;
+
+ for (tmp = bin->pending_decodebins; tmp; tmp = tmp->next) {
+ gst_element_set_state ((GstElement *) tmp->data, GST_STATE_NULL);
+ gst_object_unref ((GstElement *) tmp->data);
+ }
+ g_slist_free (bin->pending_decodebins);
+ bin->pending_decodebins = NULL;
+
+ }
+
+ /* Don't loose the SOURCE flag */
+ GST_OBJECT_FLAG_SET (bin, GST_ELEMENT_FLAG_SOURCE);
+}
+
+static void
+proxy_unknown_type_signal (GstElement * decodebin, GstPad * pad, GstCaps * caps,
+ GstURIDecodeBin3 * dec)
+{
+ GST_DEBUG_OBJECT (dec, "unknown-type signaled");
+
+ g_signal_emit (dec,
+ gst_uri_decode_bin3_signals[SIGNAL_UNKNOWN_TYPE], 0, pad, caps);
+}
+
+static gboolean
+proxy_autoplug_continue_signal (GstElement * decodebin, GstPad * pad,
+ GstCaps * caps, GstURIDecodeBin3 * dec)
+{
+ gboolean result;
+
+ g_signal_emit (dec,
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_CONTINUE], 0, pad, caps,
+ &result);
+
+ GST_DEBUG_OBJECT (dec, "autoplug-continue returned %d", result);
+
+ return result;
+}
+
+static GValueArray *
+proxy_autoplug_factories_signal (GstElement * decodebin, GstPad * pad,
+ GstCaps * caps, GstURIDecodeBin3 * dec)
+{
+ GValueArray *result;
+
+ g_signal_emit (dec,
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_FACTORIES], 0, pad, caps,
+ &result);
+
+ GST_DEBUG_OBJECT (dec, "autoplug-factories returned %p", result);
+
+ return result;
+}
+
+static GValueArray *
+proxy_autoplug_sort_signal (GstElement * decodebin, GstPad * pad,
+ GstCaps * caps, GValueArray * factories, GstURIDecodeBin3 * dec)
+{
+ GValueArray *result;
+
+ g_signal_emit (dec,
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_SORT], 0, pad, caps,
+ factories, &result);
+
+ GST_DEBUG_OBJECT (dec, "autoplug-sort returned %p", result);
+
+ return result;
+}
+
+static GstAutoplugSelectResult
+proxy_autoplug_select_signal (GstElement * decodebin, GstPad * pad,
+ GstCaps * caps, GstElementFactory * factory, GstURIDecodeBin3 * dec)
+{
+ GstAutoplugSelectResult result;
+
+ g_signal_emit (dec,
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_SELECT], 0, pad, caps,
+ factory, &result);
+
+ GST_DEBUG_OBJECT (dec, "autoplug-select returned %d", result);
+
+ return result;
+}
+
+static gboolean
+proxy_autoplug_query_signal (GstElement * decodebin, GstPad * pad,
+ GstElement * element, GstQuery * query, GstURIDecodeBin3 * dec)
+{
+ gboolean ret = FALSE;
+
+ g_signal_emit (dec,
+ gst_uri_decode_bin3_signals[SIGNAL_AUTOPLUG_QUERY], 0, pad, element,
+ query, &ret);
+
+ GST_DEBUG_OBJECT (dec, "autoplug-query returned %d", ret);
+
+ return ret;
+}
+
+static void
+proxy_drained_signal (GstElement * decodebin, GstURIDecodeBin3 * dec)
+{
+ GST_DEBUG_OBJECT (dec, "drained signaled");
+
+ g_signal_emit (dec, gst_uri_decode_bin3_signals[SIGNAL_DRAINED], 0, NULL);
+}
+
+/* make a decodebin and connect to all the signals */
+static GstElement *
+make_decoder (GstURIDecodeBin3 * decoder)
+{
+ GstElement *decodebin;
+
+ /* re-use pending decodebin */
+ if (decoder->pending_decodebins) {
+ GSList *first = decoder->pending_decodebins;
+ GST_LOG_OBJECT (decoder, "re-using pending decodebin");
+ decodebin = (GstElement *) first->data;
+ decoder->pending_decodebins =
+ g_slist_delete_link (decoder->pending_decodebins, first);
+ } else {
+ GST_LOG_OBJECT (decoder, "making new decodebin");
+
+ /* now create the decoder element */
+ decodebin = gst_element_factory_make ("decodebin3", NULL);
+
+ if (!decodebin)
+ goto no_decodebin;
+
+ /* sanity check */
+ if (decodebin->numsinkpads == 0)
+ goto no_typefind;
+
+ /* connect signals to proxy */
+ g_signal_connect (decodebin, "unknown-type",
+ G_CALLBACK (proxy_unknown_type_signal), decoder);
+ g_signal_connect (decodebin, "autoplug-continue",
+ G_CALLBACK (proxy_autoplug_continue_signal), decoder);
+ g_signal_connect (decodebin, "autoplug-factories",
+ G_CALLBACK (proxy_autoplug_factories_signal), decoder);
+ g_signal_connect (decodebin, "autoplug-sort",
+ G_CALLBACK (proxy_autoplug_sort_signal), decoder);
+ g_signal_connect (decodebin, "autoplug-select",
+ G_CALLBACK (proxy_autoplug_select_signal), decoder);
+ g_signal_connect (decodebin, "autoplug-query",
+ G_CALLBACK (proxy_autoplug_query_signal), decoder);
+ g_signal_connect (decodebin, "drained",
+ G_CALLBACK (proxy_drained_signal), decoder);
+
+ /* set up callbacks to create the links between decoded data
+ * and video/audio/subtitle rendering/output. */
+ g_signal_connect (decodebin,
+ "pad-added", G_CALLBACK (new_decoded_pad_added_cb), decoder);
+ g_signal_connect (decodebin,
+ "pad-removed", G_CALLBACK (pad_removed_cb), decoder);
+ g_signal_connect (decodebin, "no-more-pads",
+ G_CALLBACK (no_more_pads), decoder);
+ g_signal_connect (decodebin,
+ "unknown-type", G_CALLBACK (unknown_type_cb), decoder);
+ }
+
+ /* configure caps if we have any */
+ if (decoder->caps)
+ g_object_set (decodebin, "caps", decoder->caps, NULL);
+
+ /* Propagate expose-all-streams and connection-speed properties */
+ g_object_set (decodebin, "expose-all-streams", decoder->expose_allstreams,
+ "connection-speed", decoder->connection_speed / 1000, NULL);
+
+ if (!decoder->is_stream || decoder->is_adaptive) {
+ /* propagate the use-buffering property but only when we are not already
+ * doing stream buffering with queue2. FIXME, we might want to do stream
+ * buffering with the multiqueue buffering instead of queue2. */
+ g_object_set (decodebin, "use-buffering", decoder->use_buffering
+ || decoder->is_adaptive, NULL);
+
+ if (decoder->use_buffering || decoder->is_adaptive) {
+ guint max_bytes;
+ guint64 max_time;
+
+ /* configure sizes when buffering */
+ if ((max_bytes = decoder->buffer_size) == -1)
+ max_bytes = 2 * 1024 * 1024;
+ if ((max_time = decoder->buffer_duration) == -1)
+ max_time = 5 * GST_SECOND;
+
+ g_object_set (decodebin, "max-size-bytes", max_bytes, "max-size-buffers",
+ (guint) 0, "max-size-time", max_time, NULL);
+ }
+ }
+
+ g_object_set_data (G_OBJECT (decodebin), "pending", GINT_TO_POINTER (1));
+ g_object_set (decodebin, "subtitle-encoding", decoder->encoding, NULL);
+ decoder->pending++;
+ GST_LOG_OBJECT (decoder, "have %d pending dynamic objects", decoder->pending);
+
+ gst_bin_add (GST_BIN_CAST (decoder), decodebin);
+
+ decoder->decodebins = g_slist_prepend (decoder->decodebins, decodebin);
+
+ return decodebin;
+
+ /* ERRORS */
+no_decodebin:
+ {
+ post_missing_plugin_error (GST_ELEMENT_CAST (decoder), "decodebin3");
+ GST_ELEMENT_ERROR (decoder, CORE, MISSING_PLUGIN, (NULL),
+ ("No decodebin3 element, check your installation"));
+ do_async_done (decoder);
+ return NULL;
+ }
+no_typefind:
+ {
+ gst_object_unref (decodebin);
+ GST_ELEMENT_ERROR (decoder, CORE, MISSING_PLUGIN, (NULL),
+ ("No typefind element, decodebin is unusable, check your installation"));
+ do_async_done (decoder);
+ return NULL;
+ }
+}
+
+/* signaled when we have a stream and we need to configure the download
+ * buffering or regular buffering */
+static void
+type_found (GstElement * typefind, guint probability,
+ GstCaps * caps, GstURIDecodeBin3 * decoder)
+{
+ GstElement *src_elem, *dec_elem, *queue = NULL;
+ GstStructure *s;
+ const gchar *media_type, *elem_name;
+ gboolean do_download = FALSE;
+
+ GST_DEBUG_OBJECT (decoder, "typefind found caps %" GST_PTR_FORMAT, caps);
+
+ s = gst_caps_get_structure (caps, 0);
+ media_type = gst_structure_get_name (s);
+
+ decoder->is_adaptive = IS_ADAPTIVE_MEDIA (media_type);
+
+ /* only enable download buffering if the upstream duration is known */
+ if (decoder->download) {
+ gint64 dur;
+
+ do_download = (gst_element_query_duration (typefind, GST_FORMAT_BYTES, &dur)
+ && dur != -1);
+ }
+
+ dec_elem = make_decoder (decoder);
+ if (!dec_elem)
+ goto no_decodebin;
+
+ if (decoder->is_adaptive) {
+ src_elem = typefind;
+ } else {
+ if (do_download) {
+ elem_name = "downloadbuffer";
+ } else {
+ elem_name = "queue2";
+ }
+ queue = gst_element_factory_make (elem_name, NULL);
+ if (!queue)
+ goto no_buffer_element;
+
+ decoder->queue = queue;
+
+ GST_DEBUG_OBJECT (decoder, "check media-type %s, %d", media_type,
+ do_download);
+
+ if (do_download) {
+ gchar *temp_template, *filename;
+ const gchar *tmp_dir, *prgname;
+
+ tmp_dir = g_get_user_cache_dir ();
+ prgname = g_get_prgname ();
+ if (prgname == NULL)
+ prgname = "GStreamer";
+
+ filename = g_strdup_printf ("%s-XXXXXX", prgname);
+
+ /* build our filename */
+ temp_template = g_build_filename (tmp_dir, filename, NULL);
+
+ GST_DEBUG_OBJECT (decoder, "enable download buffering in %s (%s, %s, %s)",
+ temp_template, tmp_dir, prgname, filename);
+
+ /* configure progressive download for selected media types */
+ g_object_set (queue, "temp-template", temp_template, NULL);
+
+ g_free (filename);
+ g_free (temp_template);
+ } else {
+ g_object_set (queue, "use-buffering", TRUE, NULL);
+ g_object_set (queue, "ring-buffer-max-size",
+ decoder->ring_buffer_max_size, NULL);
+ /* Disable max-size-buffers */
+ g_object_set (queue, "max-size-buffers", 0, NULL);
+ }
+
+ /* If buffer size or duration are set, set them on the element */
+ if (decoder->buffer_size != -1)
+ g_object_set (queue, "max-size-bytes", decoder->buffer_size, NULL);
+ if (decoder->buffer_duration != -1)
+ g_object_set (queue, "max-size-time", decoder->buffer_duration, NULL);
+
+ gst_bin_add (GST_BIN_CAST (decoder), queue);
+
+ if (!gst_element_link_pads (typefind, "src", queue, "sink"))
+ goto could_not_link;
+ src_elem = queue;
+ }
+
+ /* to force caps on the decodebin element and avoid reparsing stuff by
+ * typefind. It also avoids a deadlock in the way typefind activates pads in
+ * the state change */
+ g_object_set (dec_elem, "sink-caps", caps, NULL);
+
+ if (!gst_element_link_pads (src_elem, "src", dec_elem, "sink"))
+ goto could_not_link;
+
+ /* PLAYING in one go might fail (see bug #632782) */
+ gst_element_set_state (dec_elem, GST_STATE_PAUSED);
+ gst_element_sync_state_with_parent (dec_elem);
+ if (queue)
+ gst_element_sync_state_with_parent (queue);
+
+ return;
+
+ /* ERRORS */
+no_decodebin:
+ {
+ /* error was posted */
+ return;
+ }
+could_not_link:
+ {
+ GST_ELEMENT_ERROR (decoder, CORE, NEGOTIATION,
+ (NULL), ("Can't link typefind to decodebin element"));
+ do_async_done (decoder);
+ return;
+ }
+no_buffer_element:
+ {
+ post_missing_plugin_error (GST_ELEMENT_CAST (decoder), elem_name);
+ return;
+ }
+}
+
+/* setup a streaming source. This will first plug a typefind element to the
+ * source. After we find the type, we decide to plug a queue2 and continue to
+ * plug a decodebin starting from the found caps */
+static gboolean
+setup_streaming (GstURIDecodeBin3 * decoder)
+{
+ GstElement *typefind;
+
+ /* now create the decoder element */
+ typefind = gst_element_factory_make ("typefind", NULL);
+ if (!typefind)
+ goto no_typefind;
+
+ gst_bin_add (GST_BIN_CAST (decoder), typefind);
+
+ if (!gst_element_link_pads (decoder->source, NULL, typefind, "sink"))
+ goto could_not_link;
+
+ decoder->typefind = typefind;
+
+ /* connect a signal to find out when the typefind element found
+ * a type */
+ decoder->have_type_id =
+ g_signal_connect (decoder->typefind, "have-type",
+ G_CALLBACK (type_found), decoder);
+
+ return TRUE;
+
+ /* ERRORS */
+no_typefind:
+ {
+ post_missing_plugin_error (GST_ELEMENT_CAST (decoder), "typefind");
+ GST_ELEMENT_ERROR (decoder, CORE, MISSING_PLUGIN, (NULL),
+ ("No typefind element, check your installation"));
+ do_async_done (decoder);
+ return FALSE;
+ }
+could_not_link:
+ {
+ GST_ELEMENT_ERROR (decoder, CORE, NEGOTIATION,
+ (NULL), ("Can't link source to typefind element"));
+ gst_bin_remove (GST_BIN_CAST (decoder), typefind);
+ /* Don't loose the SOURCE flag */
+ GST_OBJECT_FLAG_SET (decoder, GST_ELEMENT_FLAG_SOURCE);
+ do_async_done (decoder);
+ return FALSE;
+ }
+}
+
+static void
+free_stream (gpointer value)
+{
+ g_slice_free (GstURIDecodeBin3Stream, value);
+}
+
+/* remove source and all related elements */
+static void
+remove_source (GstURIDecodeBin3 * bin)
+{
+ GstElement *source = bin->source;
+
+ if (source) {
+ GST_DEBUG_OBJECT (bin, "removing old src element");
+ gst_element_set_state (source, GST_STATE_NULL);
+
+ if (bin->src_np_sig_id) {
+ g_signal_handler_disconnect (source, bin->src_np_sig_id);
+ bin->src_np_sig_id = 0;
+ }
+ if (bin->src_nmp_sig_id) {
+ g_signal_handler_disconnect (source, bin->src_nmp_sig_id);
+ bin->src_nmp_sig_id = 0;
+ }
+ gst_bin_remove (GST_BIN_CAST (bin), source);
+ bin->source = NULL;
+ }
+ if (bin->queue) {
+ GST_DEBUG_OBJECT (bin, "removing old queue element");
+ gst_element_set_state (bin->queue, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (bin), bin->queue);
+ bin->queue = NULL;
+ }
+ if (bin->typefind) {
+ GST_DEBUG_OBJECT (bin, "removing old typefind element");
+ gst_element_set_state (bin->typefind, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (bin), bin->typefind);
+ bin->typefind = NULL;
+ }
+ if (bin->streams) {
+ g_hash_table_destroy (bin->streams);
+ bin->streams = NULL;
+ }
+ /* Don't loose the SOURCE flag */
+ GST_OBJECT_FLAG_SET (bin, GST_ELEMENT_FLAG_SOURCE);
+}
+
+/* is called when a dynamic source element created a new pad. */
+static void
+source_new_pad (GstElement * element, GstPad * pad, GstURIDecodeBin3 * bin)
+{
+ GstElement *decoder;
+ gboolean is_raw;
+ GstCaps *rawcaps;
+
+ GST_URI_DECODE_BIN3_LOCK (bin);
+ GST_DEBUG_OBJECT (bin, "Found new pad %s.%s in source element %s",
+ GST_DEBUG_PAD_NAME (pad), GST_ELEMENT_NAME (element));
+
+ g_object_get (bin, "caps", &rawcaps, NULL);
+ if (!rawcaps)
+ rawcaps = DEFAULT_CAPS;
+
+ /* if this is a pad with all raw caps, we can expose it */
+ if (has_all_raw_caps (pad, rawcaps, &is_raw) && is_raw) {
+ /* it's all raw, create output pads. */
+ GST_URI_DECODE_BIN3_UNLOCK (bin);
+ gst_caps_unref (rawcaps);
+ expose_decoded_pad (element, pad, bin);
+ return;
+ }
+ gst_caps_unref (rawcaps);
+
+ /* not raw, create decoder */
+ decoder = make_decoder (bin);
+ if (!decoder)
+ goto no_decodebin;
+
+ /* and link to decoder */
+ if (!gst_element_link_pads (bin->source, NULL, decoder, "sink"))
+ goto could_not_link;
+
+ GST_DEBUG_OBJECT (bin, "linked decoder to new pad");
+
+ gst_element_sync_state_with_parent (decoder);
+ GST_URI_DECODE_BIN3_UNLOCK (bin);
+
+ return;
+
+ /* ERRORS */
+no_decodebin:
+ {
+ /* error was posted */
+ GST_URI_DECODE_BIN3_UNLOCK (bin);
+ return;
+ }
+could_not_link:
+ {
+ GST_ELEMENT_ERROR (bin, CORE, NEGOTIATION,
+ (NULL), ("Can't link source to decoder element"));
+ GST_URI_DECODE_BIN3_UNLOCK (bin);
+ do_async_done (bin);
+ return;
+ }
+}
+
+static gboolean
+is_live_source (GstElement * source)
+{
+ GObjectClass *source_class = NULL;
+ gboolean is_live = FALSE;
+ GParamSpec *pspec;
+
+ source_class = G_OBJECT_GET_CLASS (source);
+ pspec = g_object_class_find_property (source_class, "is-live");
+ if (!pspec || G_PARAM_SPEC_VALUE_TYPE (pspec) != G_TYPE_BOOLEAN)
+ return FALSE;
+
+ g_object_get (G_OBJECT (source), "is-live", &is_live, NULL);
+
+ return is_live;
+}
+
+/* construct and run the source and decoder elements until we found
+ * all the streams or until a preroll queue has been filled.
+*/
+static gboolean
+setup_source (GstURIDecodeBin3 * decoder)
+{
+ gboolean is_raw, have_out, is_dynamic;
+
+ GST_DEBUG_OBJECT (decoder, "setup source");
+
+ /* delete old src */
+ remove_source (decoder);
+
+ decoder->pending = 0;
+
+ /* create and configure an element that can handle the uri */
+ if (!(decoder->source = gen_source_element (decoder)))
+ goto no_source;
+
+ /* state will be merged later - if file is not found, error will be
+ * handled by the application right after. */
+ gst_bin_add (GST_BIN_CAST (decoder), decoder->source);
+
+ /* notify of the new source used */
+ g_object_notify (G_OBJECT (decoder), "source");
+
+ g_signal_emit (decoder, gst_uri_decode_bin3_signals[SIGNAL_SOURCE_SETUP],
+ 0, decoder->source);
+
+ if (is_live_source (decoder->source))
+ decoder->is_stream = FALSE;
+
+ /* remove the old decoders now, if any */
+ remove_decoders (decoder, FALSE);
+
+ /* stream admin setup */
+ decoder->streams = g_hash_table_new_full (NULL, NULL, NULL, free_stream);
+
+ /* see if the source element emits raw audio/video all by itself,
+ * if so, we can create streams for the pads and be done with it.
+ * Also check that is has source pads, if not, we assume it will
+ * do everything itself. */
+ if (!analyse_source (decoder, &is_raw, &have_out, &is_dynamic,
+ decoder->need_queue))
+ goto invalid_source;
+
+ if (is_raw) {
+ GST_DEBUG_OBJECT (decoder, "Source provides all raw data");
+ /* source provides raw data, we added the pads and we can now signal a
+ * no_more pads because we are done. */
+ gst_element_no_more_pads (GST_ELEMENT_CAST (decoder));
+ do_async_done (decoder);
+ return TRUE;
+ }
+ if (!have_out && !is_dynamic) {
+ GST_DEBUG_OBJECT (decoder, "Source has no output pads");
+ /* create a stream to indicate that this uri is handled by a self
+ * contained element. We are now done. */
+ add_element_stream (decoder->source, decoder);
+ return TRUE;
+ }
+ if (is_dynamic) {
+ GST_DEBUG_OBJECT (decoder, "Source has dynamic output pads");
+ /* connect a handler for the new-pad signal */
+ decoder->src_np_sig_id =
+ g_signal_connect (decoder->source, "pad-added",
+ G_CALLBACK (source_new_pad), decoder);
+ decoder->src_nmp_sig_id =
+ g_signal_connect (decoder->source, "no-more-pads",
+ G_CALLBACK (source_no_more_pads), decoder);
+ g_object_set_data (G_OBJECT (decoder->source), "pending",
+ GINT_TO_POINTER (1));
+ decoder->pending++;
+ } else {
+ if (decoder->is_stream) {
+ GST_DEBUG_OBJECT (decoder, "Setting up streaming");
+ /* do the stream things here */
+ if (!setup_streaming (decoder))
+ goto streaming_failed;
+ } else {
+ GstElement *dec_elem;
+
+ /* no streaming source, we can link now */
+ GST_DEBUG_OBJECT (decoder, "Plugging decodebin to source");
+
+ dec_elem = make_decoder (decoder);
+ if (!dec_elem)
+ goto no_decoder;
+
+ if (!gst_element_link_pads (decoder->source, NULL, dec_elem, "sink"))
+ goto could_not_link;
+ }
+ }
+ return TRUE;
+
+ /* ERRORS */
+no_source:
+ {
+ /* error message was already posted */
+ return FALSE;
+ }
+invalid_source:
+ {
+ GST_ELEMENT_ERROR (decoder, CORE, FAILED,
+ (_("Source element is invalid.")), (NULL));
+ return FALSE;
+ }
+no_decoder:
+ {
+ /* message was posted */
+ return FALSE;
+ }
+streaming_failed:
+ {
+ /* message was posted */
+ return FALSE;
+ }
+could_not_link:
+ {
+ GST_ELEMENT_ERROR (decoder, CORE, NEGOTIATION,
+ (NULL), ("Can't link source to decoder element"));
+ return FALSE;
+ }
+}
+
+static void
+value_list_append_structure_list (GValue * list_val, GstStructure ** first,
+ GList * structure_list)
+{
+ GList *l;
+
+ for (l = structure_list; l != NULL; l = l->next) {
+ GValue val = { 0, };
+
+ if (*first == NULL)
+ *first = gst_structure_copy ((GstStructure *) l->data);
+
+ g_value_init (&val, GST_TYPE_STRUCTURE);
+ g_value_take_boxed (&val, gst_structure_copy ((GstStructure *) l->data));
+ gst_value_list_append_value (list_val, &val);
+ g_value_unset (&val);
+ }
+}
+
+/* if it's a redirect message with multiple redirect locations we might
+ * want to pick a different 'best' location depending on the required
+ * bitrates and the connection speed */
+static GstMessage *
+handle_redirect_message (GstURIDecodeBin3 * dec, GstMessage * msg)
+{
+ const GValue *locations_list, *location_val;
+ GstMessage *new_msg;
+ GstStructure *new_structure = NULL;
+ GList *l_good = NULL, *l_neutral = NULL, *l_bad = NULL;
+ GValue new_list = { 0, };
+ guint size, i;
+ const GstStructure *structure;
+
+ GST_DEBUG_OBJECT (dec, "redirect message: %" GST_PTR_FORMAT, msg);
+ GST_DEBUG_OBJECT (dec, "connection speed: %" G_GUINT64_FORMAT,
+ dec->connection_speed);
+
+ structure = gst_message_get_structure (msg);
+ if (dec->connection_speed == 0 || structure == NULL)
+ return msg;
+
+ locations_list = gst_structure_get_value (structure, "locations");
+ if (locations_list == NULL)
+ return msg;
+
+ size = gst_value_list_get_size (locations_list);
+ if (size < 2)
+ return msg;
+
+ /* maintain existing order as much as possible, just sort references
+ * with too high a bitrate to the end (the assumption being that if
+ * bitrates are given they are given for all interesting streams and
+ * that the you-need-at-least-version-xyz redirect has the same bitrate
+ * as the lowest referenced redirect alternative) */
+ for (i = 0; i < size; ++i) {
+ const GstStructure *s;
+ gint bitrate = 0;
+
+ location_val = gst_value_list_get_value (locations_list, i);
+ s = (const GstStructure *) g_value_get_boxed (location_val);
+ if (!gst_structure_get_int (s, "minimum-bitrate", &bitrate) || bitrate <= 0) {
+ GST_DEBUG_OBJECT (dec, "no bitrate: %" GST_PTR_FORMAT, s);
+ l_neutral = g_list_append (l_neutral, (gpointer) s);
+ } else if (bitrate > dec->connection_speed) {
+ GST_DEBUG_OBJECT (dec, "bitrate too high: %" GST_PTR_FORMAT, s);
+ l_bad = g_list_append (l_bad, (gpointer) s);
+ } else if (bitrate <= dec->connection_speed) {
+ GST_DEBUG_OBJECT (dec, "bitrate OK: %" GST_PTR_FORMAT, s);
+ l_good = g_list_append (l_good, (gpointer) s);
+ }
+ }
+
+ g_value_init (&new_list, GST_TYPE_LIST);
+ value_list_append_structure_list (&new_list, &new_structure, l_good);
+ value_list_append_structure_list (&new_list, &new_structure, l_neutral);
+ value_list_append_structure_list (&new_list, &new_structure, l_bad);
+ gst_structure_take_value (new_structure, "locations", &new_list);
+
+ g_list_free (l_good);
+ g_list_free (l_neutral);
+ g_list_free (l_bad);
+
+ new_msg = gst_message_new_element (msg->src, new_structure);
+ gst_message_unref (msg);
+
+ GST_DEBUG_OBJECT (dec, "new redirect message: %" GST_PTR_FORMAT, new_msg);
+ return new_msg;
+}
+
+static void
+handle_message (GstBin * bin, GstMessage * msg)
+{
+ GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (bin);
+
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_ELEMENT:{
+ if (gst_message_has_name (msg, "redirect")) {
+ /* sort redirect messages based on the connection speed. This simplifies
+ * the user of this element as it can in most cases just pick the first item
+ * of the sorted list as a good redirection candidate. It can of course
+ * choose something else from the list if it has a better way. */
+ msg = handle_redirect_message (dec, msg);
+ }
+ break;
+ }
+ case GST_MESSAGE_ERROR:{
+ GError *err = NULL;
+
+ /* Filter out missing plugin error messages from the decodebins. Only if
+ * all decodebins exposed no streams we will report a missing plugin
+ * error from no_more_pads_full()
+ */
+ gst_message_parse_error (msg, &err, NULL);
+ if (g_error_matches (err, GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN)
+ || g_error_matches (err, GST_STREAM_ERROR,
+ GST_STREAM_ERROR_CODEC_NOT_FOUND)) {
+ dec->missing_plugin_errors =
+ g_list_prepend (dec->missing_plugin_errors, gst_message_ref (msg));
+
+ no_more_pads_full (GST_ELEMENT (GST_MESSAGE_SRC (msg)), FALSE,
+ GST_URI_DECODE_BIN3 (bin));
+ gst_message_unref (msg);
+ msg = NULL;
+ }
+ g_clear_error (&err);
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (msg)
+ GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
+}
+
+/* generic struct passed to all query fold methods
+ * FIXME, move to core.
+ */
+typedef struct
+{
+ GstQuery *query;
+ gint64 min;
+ gint64 max;
+ gboolean seekable;
+ gboolean live;
+} QueryFold;
+
+typedef void (*QueryInitFunction) (GstURIDecodeBin3 * dec, QueryFold * fold);
+typedef void (*QueryDoneFunction) (GstURIDecodeBin3 * dec, QueryFold * fold);
+
+/* for duration/position we collect all durations/positions and take
+ * the MAX of all valid results */
+static void
+decoder_query_init (GstURIDecodeBin3 * dec, QueryFold * fold)
+{
+ fold->min = 0;
+ fold->max = -1;
+ fold->seekable = TRUE;
+ fold->live = 0;
+}
+
+static gboolean
+decoder_query_duration_fold (const GValue * item, GValue * ret,
+ QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+
+ if (gst_pad_query (pad, fold->query)) {
+ gint64 duration;
+
+ g_value_set_boolean (ret, TRUE);
+
+ gst_query_parse_duration (fold->query, NULL, &duration);
+
+ GST_DEBUG_OBJECT (item, "got duration %" G_GINT64_FORMAT, duration);
+
+ if (duration > fold->max)
+ fold->max = duration;
+ }
+ return TRUE;
+}
+
+static void
+decoder_query_duration_done (GstURIDecodeBin3 * dec, QueryFold * fold)
+{
+ GstFormat format;
+
+ gst_query_parse_duration (fold->query, &format, NULL);
+ /* store max in query result */
+ gst_query_set_duration (fold->query, format, fold->max);
+
+ GST_DEBUG ("max duration %" G_GINT64_FORMAT, fold->max);
+}
+
+static gboolean
+decoder_query_position_fold (const GValue * item, GValue * ret,
+ QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+
+ if (gst_pad_query (pad, fold->query)) {
+ gint64 position;
+
+ g_value_set_boolean (ret, TRUE);
+
+ gst_query_parse_position (fold->query, NULL, &position);
+
+ GST_DEBUG_OBJECT (item, "got position %" G_GINT64_FORMAT, position);
+
+ if (position > fold->max)
+ fold->max = position;
+ }
+
+ return TRUE;
+}
+
+static void
+decoder_query_position_done (GstURIDecodeBin3 * dec, QueryFold * fold)
+{
+ GstFormat format;
+
+ gst_query_parse_position (fold->query, &format, NULL);
+ /* store max in query result */
+ gst_query_set_position (fold->query, format, fold->max);
+
+ GST_DEBUG_OBJECT (dec, "max position %" G_GINT64_FORMAT, fold->max);
+}
+
+static gboolean
+decoder_query_latency_fold (const GValue * item, GValue * ret, QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+
+ if (gst_pad_query (pad, fold->query)) {
+ GstClockTime min, max;
+ gboolean live;
+
+ gst_query_parse_latency (fold->query, &live, &min, &max);
+
+ GST_DEBUG_OBJECT (pad,
+ "got latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT
+ ", live %d", GST_TIME_ARGS (min), GST_TIME_ARGS (max), live);
+
+ if (live) {
+ /* for the combined latency we collect the MAX of all min latencies and
+ * the MIN of all max latencies */
+ if (min > fold->min)
+ fold->min = min;
+ if (fold->max == -1)
+ fold->max = max;
+ else if (max < fold->max)
+ fold->max = max;
+
+ fold->live = TRUE;
+ }
+ } else {
+ GST_LOG_OBJECT (pad, "latency query failed");
+ g_value_set_boolean (ret, FALSE);
+ }
+
+ return TRUE;
+}
+
+static void
+decoder_query_latency_done (GstURIDecodeBin3 * dec, QueryFold * fold)
+{
+ /* store max in query result */
+ gst_query_set_latency (fold->query, fold->live, fold->min, fold->max);
+
+ GST_DEBUG_OBJECT (dec,
+ "latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT
+ ", live %d", GST_TIME_ARGS (fold->min), GST_TIME_ARGS (fold->max),
+ fold->live);
+}
+
+/* we are seekable if all srcpads are seekable */
+static gboolean
+decoder_query_seeking_fold (const GValue * item, GValue * ret, QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+
+ if (gst_pad_query (pad, fold->query)) {
+ gboolean seekable;
+
+ g_value_set_boolean (ret, TRUE);
+ gst_query_parse_seeking (fold->query, NULL, &seekable, NULL, NULL);
+
+ GST_DEBUG_OBJECT (item, "got seekable %d", seekable);
+
+ if (fold->seekable)
+ fold->seekable = seekable;
+ }
+
+ return TRUE;
+}
+
+static void
+decoder_query_seeking_done (GstURIDecodeBin3 * dec, QueryFold * fold)
+{
+ GstFormat format;
+
+ gst_query_parse_seeking (fold->query, &format, NULL, NULL, NULL);
+ gst_query_set_seeking (fold->query, format, fold->seekable, 0, -1);
+
+ GST_DEBUG_OBJECT (dec, "seekable %d", fold->seekable);
+}
+
+/* generic fold, return first valid result */
+static gboolean
+decoder_query_generic_fold (const GValue * item, GValue * ret, QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+ gboolean res;
+
+ if ((res = gst_pad_query (pad, fold->query))) {
+ g_value_set_boolean (ret, TRUE);
+ GST_DEBUG_OBJECT (item, "answered query %p", fold->query);
+ }
+
+ /* and stop as soon as we have a valid result */
+ return !res;
+}
+
+
+/* we're a bin, the default query handler iterates sink elements, which we don't
+ * have normally. We should just query all source pads.
+ */
+static gboolean
+gst_uri_decode_bin3_query (GstElement * element, GstQuery * query)
+{
+ GstURIDecodeBin3 *decoder;
+ gboolean res = FALSE;
+ GstIterator *iter;
+ GstIteratorFoldFunction fold_func;
+ QueryInitFunction fold_init = NULL;
+ QueryDoneFunction fold_done = NULL;
+ QueryFold fold_data;
+ GValue ret = { 0 };
+ gboolean default_ret = FALSE;
+
+ decoder = GST_URI_DECODE_BIN3 (element);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_DURATION:
+ /* iterate and collect durations */
+ fold_func = (GstIteratorFoldFunction) decoder_query_duration_fold;
+ fold_init = decoder_query_init;
+ fold_done = decoder_query_duration_done;
+ break;
+ case GST_QUERY_POSITION:
+ /* iterate and collect durations */
+ fold_func = (GstIteratorFoldFunction) decoder_query_position_fold;
+ fold_init = decoder_query_init;
+ fold_done = decoder_query_position_done;
+ break;
+ case GST_QUERY_LATENCY:
+ /* iterate and collect durations */
+ fold_func = (GstIteratorFoldFunction) decoder_query_latency_fold;
+ fold_init = decoder_query_init;
+ fold_done = decoder_query_latency_done;
+ default_ret = TRUE;
+ break;
+ case GST_QUERY_SEEKING:
+ /* iterate and collect durations */
+ fold_func = (GstIteratorFoldFunction) decoder_query_seeking_fold;
+ fold_init = decoder_query_init;
+ fold_done = decoder_query_seeking_done;
+ break;
+ default:
+ fold_func = (GstIteratorFoldFunction) decoder_query_generic_fold;
+ break;
+ }
+
+ fold_data.query = query;
+
+ g_value_init (&ret, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&ret, default_ret);
+
+ iter = gst_element_iterate_src_pads (element);
+ GST_DEBUG_OBJECT (element, "Sending query %p (type %d) to src pads",
+ query, GST_QUERY_TYPE (query));
+
+ if (fold_init)
+ fold_init (decoder, &fold_data);
+
+ while (TRUE) {
+ GstIteratorResult ires;
+
+ ires = gst_iterator_fold (iter, fold_func, &ret, &fold_data);
+
+ switch (ires) {
+ case GST_ITERATOR_RESYNC:
+ gst_iterator_resync (iter);
+ if (fold_init)
+ fold_init (decoder, &fold_data);
+ g_value_set_boolean (&ret, default_ret);
+ break;
+ case GST_ITERATOR_OK:
+ case GST_ITERATOR_DONE:
+ res = g_value_get_boolean (&ret);
+ if (fold_done != NULL && res)
+ fold_done (decoder, &fold_data);
+ goto done;
+ default:
+ res = FALSE;
+ goto done;
+ }
+ }
+done:
+ gst_iterator_free (iter);
+
+ return res;
+}
+
+static GstStateChangeReturn
+gst_uri_decode_bin3_change_state (GstElement * element,
+ GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ GstURIDecodeBin3 *decoder;
+
+ decoder = GST_URI_DECODE_BIN3 (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ do_async_start (decoder);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto setup_failed;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ GST_DEBUG ("ready to paused");
+ if (!setup_source (decoder))
+ goto source_failed;
+
+ ret = GST_STATE_CHANGE_ASYNC;
+
+ /* And now sync the states of everything we added */
+ g_slist_foreach (decoder->decodebins,
+ (GFunc) gst_element_sync_state_with_parent, NULL);
+ if (decoder->typefind)
+ ret = gst_element_set_state (decoder->typefind, GST_STATE_PAUSED);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto setup_failed;
+ if (decoder->queue)
+ ret = gst_element_set_state (decoder->queue, GST_STATE_PAUSED);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto setup_failed;
+ if (decoder->source)
+ ret = gst_element_set_state (decoder->source, GST_STATE_PAUSED);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto setup_failed;
+ if (ret == GST_STATE_CHANGE_SUCCESS)
+ ret = GST_STATE_CHANGE_ASYNC;
+
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ GST_DEBUG ("paused to ready");
+ remove_decoders (decoder, FALSE);
+ remove_source (decoder);
+ do_async_done (decoder);
+ g_list_free_full (decoder->missing_plugin_errors,
+ (GDestroyNotify) gst_message_unref);
+ decoder->missing_plugin_errors = NULL;
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ GST_DEBUG ("ready to null");
+ remove_decoders (decoder, TRUE);
+ remove_source (decoder);
+ break;
+ default:
+ break;
+ }
+
+ if (ret == GST_STATE_CHANGE_NO_PREROLL)
+ do_async_done (decoder);
+
+ return ret;
+
+ /* ERRORS */
+source_failed:
+ {
+ do_async_done (decoder);
+ return GST_STATE_CHANGE_FAILURE;
+ }
+setup_failed:
+ {
+ /* clean up leftover groups */
+ do_async_done (decoder);
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
+
+gboolean
+gst_uri_decode_bin3_plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_uri_decode_bin3_debug, "uridecodebin3", 0,
+ "URI decoder element");
+
+ return gst_element_register (plugin, "uridecodebin3", GST_RANK_NONE,
+ GST_TYPE_URI_DECODE_BIN3);
+}
--- /dev/null
+/* GStreamer
+ * Copyright (C) <2015> Jan Schmidt <jan@centricular.com>
+ * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-urisourcebin
+ *
+ * Handles selecting a URI source element and potentially buffering/download
+ * for network sources. Produces one or more source pads for feeding to
+ * decoding chains or decodebin.
+ */
+
+/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
+ * with newer GLib versions (>= 2.31.0) */
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+
+#include <gst/gst.h>
+#include <gst/gst-i18n-plugin.h>
+#include <gst/pbutils/missing-plugins.h>
+
+#include "gstplay-enum.h"
+#include "gstrawcaps.h"
+#include "gstplayback.h"
+
+/* From gstdecodebin2.c */
+gint _decode_bin_compare_factories_func (gconstpointer p1, gconstpointer p2);
+
+#define GST_TYPE_URI_DECODE_BIN \
+ (gst_uri_source_bin_get_type())
+#define GST_URI_SOURCE_BIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_URI_DECODE_BIN,GstURISourceBin))
+#define GST_URI_SOURCE_BIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_URI_DECODE_BIN,GstURISourceBinClass))
+#define GST_IS_URI_SOURCE_BIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_URI_DECODE_BIN))
+#define GST_IS_URI_SOURCE_BIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_URI_DECODE_BIN))
+#define GST_URI_SOURCE_BIN_CAST(obj) ((GstURISourceBin *) (obj))
+
+typedef struct _GstURISourceBin GstURISourceBin;
+typedef struct _GstURISourceBinClass GstURISourceBinClass;
+typedef struct _ChildSrcPadInfo ChildSrcPadInfo;
+typedef struct _OutputSlotInfo OutputSlotInfo;
+
+#define GST_URI_SOURCE_BIN_LOCK(dec) (g_mutex_lock(&((GstURISourceBin*)(dec))->lock))
+#define GST_URI_SOURCE_BIN_UNLOCK(dec) (g_mutex_unlock(&((GstURISourceBin*)(dec))->lock))
+
+/* Track a source pad from a child that
+ * is linked or needs linking to an output
+ * slot */
+struct _ChildSrcPadInfo
+{
+ guint blocking_probe_id;
+ guint event_probe_id;
+ GstPad *demux_src_pad;
+ GstCaps *cur_caps; /* holds ref */
+
+ /* Configured output slot, if any */
+ OutputSlotInfo *output_slot;
+};
+
+struct _OutputSlotInfo
+{
+ ChildSrcPadInfo *linked_info; /* demux source pad info feeding this slot, if any */
+ GstElement *queue; /* queue2 or downloadbuffer */
+ GstPad *sinkpad; /* Sink pad of the queue eleemnt */
+ GstPad *srcpad; /* Output ghost pad */
+ gboolean is_eos; /* Did EOS get fed into the buffering element */
+};
+
+/**
+ * GstURISourceBin
+ *
+ * urisourcebin element struct
+ */
+struct _GstURISourceBin
+{
+ GstBin parent_instance;
+
+ GMutex lock; /* lock for constructing */
+
+ GMutex factories_lock;
+ guint32 factories_cookie;
+ GList *factories; /* factories we can use for selecting elements */
+
+ gchar *uri;
+ guint64 connection_speed;
+
+ gboolean is_stream;
+ gboolean is_adaptive;
+ gboolean need_queue;
+ guint64 buffer_duration; /* When buffering, buffer duration (ns) */
+ guint buffer_size; /* When buffering, buffer size (bytes) */
+ gboolean download;
+ gboolean use_buffering;
+
+ GstElement *source;
+ GstElement *typefind;
+ guint have_type_id; /* have-type signal id from typefind */
+
+ GstElement *demuxer; /* Adaptive demuxer if any */
+ GSList *out_slots;
+
+ GHashTable *streams;
+ guint numpads;
+
+ /* for dynamic sources */
+ guint src_np_sig_id; /* new-pad signal id */
+
+ gboolean async_pending; /* async-start has been emitted */
+
+ guint64 ring_buffer_max_size; /* 0 means disabled */
+
+ GList *pending_pads; /* Pads we have blocked pending assignment
+ to an output source pad */
+ GList *inactive_output_pads; /* output pads that were unghosted */
+
+ GList *buffering_status; /* element currently buffering messages */
+ gint last_buffering_pct; /* Avoid sending buffering over and over */
+};
+
+struct _GstURISourceBinClass
+{
+ GstBinClass parent_class;
+
+ /* signal fired when we found a pad that we cannot decode */
+ void (*unknown_type) (GstElement * element, GstPad * pad, GstCaps * caps);
+
+ /* signal fired to know if we continue trying to decode the given caps */
+ gboolean (*autoplug_continue) (GstElement * element, GstPad * pad,
+ GstCaps * caps);
+ /* signal fired to get a list of factories to try to autoplug */
+ GValueArray *(*autoplug_factories) (GstElement * element, GstPad * pad,
+ GstCaps * caps);
+ /* signal fired to sort the factories */
+ GValueArray *(*autoplug_sort) (GstElement * element, GstPad * pad,
+ GstCaps * caps, GValueArray * factories);
+ /* signal fired to select from the proposed list of factories */
+ GstAutoplugSelectResult (*autoplug_select) (GstElement * element,
+ GstPad * pad, GstCaps * caps, GstElementFactory * factory);
+ /* signal fired when a autoplugged element that is not linked downstream
+ * or exposed wants to query something */
+ gboolean (*autoplug_query) (GstElement * element, GstPad * pad,
+ GstQuery * query);
+
+ /* emitted when all data is decoded */
+ void (*drained) (GstElement * element);
+};
+
+static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS);
+
+GST_DEBUG_CATEGORY_STATIC (gst_uri_source_bin_debug);
+#define GST_CAT_DEFAULT gst_uri_source_bin_debug
+
+/* signals */
+enum
+{
+ SIGNAL_UNKNOWN_TYPE,
+ SIGNAL_AUTOPLUG_CONTINUE,
+ SIGNAL_AUTOPLUG_FACTORIES,
+ SIGNAL_AUTOPLUG_SELECT,
+ SIGNAL_AUTOPLUG_SORT,
+ SIGNAL_AUTOPLUG_QUERY,
+ SIGNAL_DRAINED,
+ SIGNAL_SOURCE_SETUP,
+ LAST_SIGNAL
+};
+
+/* properties */
+#define DEFAULT_PROP_URI NULL
+#define DEFAULT_PROP_SOURCE NULL
+#define DEFAULT_CONNECTION_SPEED 0
+#define DEFAULT_BUFFER_DURATION -1
+#define DEFAULT_BUFFER_SIZE -1
+#define DEFAULT_DOWNLOAD FALSE
+#define DEFAULT_USE_BUFFERING FALSE
+#define DEFAULT_RING_BUFFER_MAX_SIZE 0
+
+#define DEFAULT_CAPS (gst_static_caps_get (&default_raw_caps))
+enum
+{
+ PROP_0,
+ PROP_URI,
+ PROP_SOURCE,
+ PROP_CONNECTION_SPEED,
+ PROP_BUFFER_SIZE,
+ PROP_BUFFER_DURATION,
+ PROP_DOWNLOAD,
+ PROP_USE_BUFFERING,
+ PROP_RING_BUFFER_MAX_SIZE
+};
+
+static void post_missing_plugin_error (GstElement * dec,
+ const gchar * element_name);
+
+static guint gst_uri_source_bin_signals[LAST_SIGNAL] = { 0 };
+
+GType gst_uri_source_bin_get_type (void);
+#define gst_uri_source_bin_parent_class parent_class
+G_DEFINE_TYPE (GstURISourceBin, gst_uri_source_bin, GST_TYPE_BIN);
+
+static void gst_uri_source_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_uri_source_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_uri_source_bin_finalize (GObject * obj);
+
+static void handle_message (GstBin * bin, GstMessage * msg);
+
+static gboolean gst_uri_source_bin_query (GstElement * element,
+ GstQuery * query);
+static GstStateChangeReturn gst_uri_source_bin_change_state (GstElement *
+ element, GstStateChange transition);
+
+static void remove_demuxer (GstURISourceBin * bin);
+static void expose_output_pad (GstURISourceBin * urisrc, GstPad * pad);
+static OutputSlotInfo *get_output_slot (GstURISourceBin * urisrc,
+ gboolean do_download, gboolean is_adaptive, GstCaps * caps);
+static void free_output_slot (OutputSlotInfo * slot, GstURISourceBin * urisrc);
+static GstPad *create_output_pad (GstURISourceBin * urisrc, GstPad * pad);
+static void remove_buffering_msgs (GstURISourceBin * bin, GstObject * src);
+
+static gboolean
+_gst_boolean_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gboolean myboolean;
+
+ myboolean = g_value_get_boolean (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boolean (return_accu, myboolean);
+
+ /* stop emission if FALSE */
+ return myboolean;
+}
+
+static gboolean
+_gst_boolean_or_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gboolean myboolean;
+ gboolean retboolean;
+
+ myboolean = g_value_get_boolean (handler_return);
+ retboolean = g_value_get_boolean (return_accu);
+
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boolean (return_accu, myboolean || retboolean);
+
+ return TRUE;
+}
+
+static gboolean
+_gst_array_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gpointer array;
+
+ array = g_value_get_boxed (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boxed (return_accu, array);
+
+ return FALSE;
+}
+
+static gboolean
+_gst_select_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ GstAutoplugSelectResult res;
+
+ res = g_value_get_enum (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_enum (return_accu, res);
+
+ /* Call the next handler in the chain (if any) when the current callback
+ * returns TRY. This makes it possible to register separate autoplug-select
+ * handlers that implement different TRY/EXPOSE/SKIP strategies.
+ */
+ if (res == GST_AUTOPLUG_SELECT_TRY)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+_gst_array_hasvalue_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer dummy)
+{
+ gpointer array;
+
+ array = g_value_get_boxed (handler_return);
+ if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
+ g_value_set_boxed (return_accu, array);
+
+ if (array != NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+gst_uri_source_bin_autoplug_continue (GstElement * element, GstPad * pad,
+ GstCaps * caps)
+{
+ /* by default we always continue */
+ return TRUE;
+}
+
+/* Must be called with factories lock! */
+static void
+gst_uri_source_bin_update_factories_list (GstURISourceBin * dec)
+{
+ guint32 cookie;
+
+ cookie = gst_registry_get_feature_list_cookie (gst_registry_get ());
+ if (!dec->factories || dec->factories_cookie != cookie) {
+ if (dec->factories)
+ gst_plugin_feature_list_free (dec->factories);
+ dec->factories =
+ gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_DECODABLE, GST_RANK_MARGINAL);
+ dec->factories =
+ g_list_sort (dec->factories, _decode_bin_compare_factories_func);
+ dec->factories_cookie = cookie;
+ }
+}
+
+static GValueArray *
+gst_uri_source_bin_autoplug_factories (GstElement * element, GstPad * pad,
+ GstCaps * caps)
+{
+ GList *list, *tmp;
+ GValueArray *result;
+ GstURISourceBin *dec = GST_URI_SOURCE_BIN_CAST (element);
+
+ GST_DEBUG_OBJECT (element, "finding factories");
+
+ /* return all compatible factories for caps */
+ g_mutex_lock (&dec->factories_lock);
+ gst_uri_source_bin_update_factories_list (dec);
+ list =
+ gst_element_factory_list_filter (dec->factories, caps, GST_PAD_SINK,
+ gst_caps_is_fixed (caps));
+ g_mutex_unlock (&dec->factories_lock);
+
+ result = g_value_array_new (g_list_length (list));
+ for (tmp = list; tmp; tmp = tmp->next) {
+ GstElementFactory *factory = GST_ELEMENT_FACTORY_CAST (tmp->data);
+ GValue val = { 0, };
+
+ g_value_init (&val, G_TYPE_OBJECT);
+ g_value_set_object (&val, factory);
+ g_value_array_append (result, &val);
+ g_value_unset (&val);
+ }
+ gst_plugin_feature_list_free (list);
+
+ GST_DEBUG_OBJECT (element, "autoplug-factories returns %p", result);
+
+ return result;
+}
+
+static GValueArray *
+gst_uri_source_bin_autoplug_sort (GstElement * element, GstPad * pad,
+ GstCaps * caps, GValueArray * factories)
+{
+ return NULL;
+}
+
+static GstAutoplugSelectResult
+gst_uri_source_bin_autoplug_select (GstElement * element, GstPad * pad,
+ GstCaps * caps, GstElementFactory * factory)
+{
+ GST_DEBUG_OBJECT (element, "default autoplug-select returns TRY");
+
+ /* Try factory. */
+ return GST_AUTOPLUG_SELECT_TRY;
+}
+
+static gboolean
+gst_uri_source_bin_autoplug_query (GstElement * element, GstPad * pad,
+ GstQuery * query)
+{
+ /* No query handled here */
+ return FALSE;
+}
+
+static void
+gst_uri_source_bin_class_init (GstURISourceBinClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBinClass *gstbin_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gstelement_class = GST_ELEMENT_CLASS (klass);
+ gstbin_class = GST_BIN_CLASS (klass);
+
+ gobject_class->set_property = gst_uri_source_bin_set_property;
+ gobject_class->get_property = gst_uri_source_bin_get_property;
+ gobject_class->finalize = gst_uri_source_bin_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_URI,
+ g_param_spec_string ("uri", "URI", "URI to decode",
+ DEFAULT_PROP_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_SOURCE,
+ g_param_spec_object ("source", "Source", "Source object used",
+ GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED,
+ g_param_spec_uint64 ("connection-speed", "Connection Speed",
+ "Network connection speed in kbps (0 = unknown)",
+ 0, G_MAXUINT64 / 1000, DEFAULT_CONNECTION_SPEED,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
+ g_param_spec_int ("buffer-size", "Buffer size (bytes)",
+ "Buffer size when buffering streams (-1 default value)",
+ -1, G_MAXINT, DEFAULT_BUFFER_SIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BUFFER_DURATION,
+ g_param_spec_int64 ("buffer-duration", "Buffer duration (ns)",
+ "Buffer duration when buffering streams (-1 default value)",
+ -1, G_MAXINT64, DEFAULT_BUFFER_DURATION,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstURISourceBin::download:
+ *
+ * For certain media type, enable download buffering.
+ */
+ g_object_class_install_property (gobject_class, PROP_DOWNLOAD,
+ g_param_spec_boolean ("download", "Download",
+ "Attempt download buffering when buffering network streams",
+ DEFAULT_DOWNLOAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstURISourceBin::use-buffering:
+ *
+ * Perform buffering using a queue2 element, and emit BUFFERING
+ * messages based on low-/high-percent thresholds of streaming data,
+ * such as adaptive-demuxer streams.
+ *
+ * When download buffering is activated and used for the current media
+ * type, this property does nothing.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_USE_BUFFERING,
+ g_param_spec_boolean ("use-buffering", "Use Buffering",
+ "Perform buffering on demuxed/parsed media",
+ DEFAULT_USE_BUFFERING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstURISourceBin::ring-buffer-max-size
+ *
+ * The maximum size of the ring buffer in kilobytes. If set to 0, the ring
+ * buffer is disabled. Default is 0.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_RING_BUFFER_MAX_SIZE,
+ g_param_spec_uint64 ("ring-buffer-max-size",
+ "Max. ring buffer size (bytes)",
+ "Max. amount of data in the ring buffer (bytes, 0 = ring buffer disabled)",
+ 0, G_MAXUINT, DEFAULT_RING_BUFFER_MAX_SIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstURISourceBin::unknown-type:
+ * @bin: The urisourcebin.
+ * @pad: the new pad containing caps that cannot be resolved to a 'final'.
+ * stream type.
+ * @caps: the #GstCaps of the pad that cannot be resolved.
+ *
+ * This signal is emitted when a pad for which there is no further possible
+ * decoding is added to the urisourcebin.
+ */
+ gst_uri_source_bin_signals[SIGNAL_UNKNOWN_TYPE] =
+ g_signal_new ("unknown-type", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass, unknown_type),
+ NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2,
+ GST_TYPE_PAD, GST_TYPE_CAPS);
+
+ /**
+ * GstURISourceBin::autoplug-continue:
+ * @bin: The urisourcebin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps found.
+ *
+ * This signal is emitted whenever urisourcebin finds a new stream. It is
+ * emitted before looking for any elements that can handle that stream.
+ *
+ * <note>
+ * Invocation of signal handlers stops after the first signal handler
+ * returns #FALSE. Signal handlers are invoked in the order they were
+ * connected in.
+ * </note>
+ *
+ * Returns: #TRUE if you wish urisourcebin to look for elements that can
+ * handle the given @caps. If #FALSE, those caps will be considered as
+ * final and the pad will be exposed as such (see 'pad-added' signal of
+ * #GstElement).
+ */
+ gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_CONTINUE] =
+ g_signal_new ("autoplug-continue", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass,
+ autoplug_continue), _gst_boolean_accumulator, NULL,
+ g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 2, GST_TYPE_PAD,
+ GST_TYPE_CAPS);
+
+ /**
+ * GstURISourceBin::autoplug-factories:
+ * @bin: The urisourcebin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps found.
+ *
+ * This function is emitted when an array of possible factories for @caps on
+ * @pad is needed. urisourcebin will by default return an array with all
+ * compatible factories, sorted by rank.
+ *
+ * If this function returns NULL, @pad will be exposed as a final caps.
+ *
+ * If this function returns an empty array, the pad will be considered as
+ * having an unhandled type media type.
+ *
+ * <note>
+ * Only the signal handler that is connected first will ever by invoked.
+ * Don't connect signal handlers with the #G_CONNECT_AFTER flag to this
+ * signal, they will never be invoked!
+ * </note>
+ *
+ * Returns: a #GValueArray* with a list of factories to try. The factories are
+ * by default tried in the returned order or based on the index returned by
+ * "autoplug-select".
+ */
+ gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_FACTORIES] =
+ g_signal_new ("autoplug-factories", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass,
+ autoplug_factories), _gst_array_accumulator, NULL,
+ g_cclosure_marshal_generic, G_TYPE_VALUE_ARRAY, 2,
+ GST_TYPE_PAD, GST_TYPE_CAPS);
+
+ /**
+ * GstURISourceBin::autoplug-sort:
+ * @bin: The urisourcebin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps.
+ * @factories: A #GValueArray of possible #GstElementFactory to use.
+ *
+ * Once decodebin has found the possible #GstElementFactory objects to try
+ * for @caps on @pad, this signal is emited. The purpose of the signal is for
+ * the application to perform additional sorting or filtering on the element
+ * factory array.
+ *
+ * The callee should copy and modify @factories or return #NULL if the
+ * order should not change.
+ *
+ * <note>
+ * Invocation of signal handlers stops after one signal handler has
+ * returned something else than #NULL. Signal handlers are invoked in
+ * the order they were connected in.
+ * Don't connect signal handlers with the #G_CONNECT_AFTER flag to this
+ * signal, they will never be invoked!
+ * </note>
+ *
+ * Returns: A new sorted array of #GstElementFactory objects.
+ *
+ * Since: 0.10.33
+ */
+ gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_SORT] =
+ g_signal_new ("autoplug-sort", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass, autoplug_sort),
+ _gst_array_hasvalue_accumulator, NULL,
+ g_cclosure_marshal_generic, G_TYPE_VALUE_ARRAY, 3, GST_TYPE_PAD,
+ GST_TYPE_CAPS, G_TYPE_VALUE_ARRAY | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ /**
+ * GstURISourceBin::autoplug-select:
+ * @bin: The urisourcebin.
+ * @pad: The #GstPad.
+ * @caps: The #GstCaps.
+ * @factory: A #GstElementFactory to use.
+ *
+ * This signal is emitted once urisourcebin has found all the possible
+ * #GstElementFactory that can be used to handle the given @caps. For each of
+ * those factories, this signal is emitted.
+ *
+ * The signal handler should return a #GST_TYPE_AUTOPLUG_SELECT_RESULT enum
+ * value indicating what decodebin should do next.
+ *
+ * A value of #GST_AUTOPLUG_SELECT_TRY will try to autoplug an element from
+ * @factory.
+ *
+ * A value of #GST_AUTOPLUG_SELECT_EXPOSE will expose @pad without plugging
+ * any element to it.
+ *
+ * A value of #GST_AUTOPLUG_SELECT_SKIP will skip @factory and move to the
+ * next factory.
+ *
+ * <note>
+ * The signal handler will not be invoked if any of the previously
+ * registered signal handlers (if any) return a value other than
+ * GST_AUTOPLUG_SELECT_TRY. Which also means that if you return
+ * GST_AUTOPLUG_SELECT_TRY from one signal handler, handlers that get
+ * registered next (again, if any) can override that decision.
+ * </note>
+ *
+ * Returns: a #GST_TYPE_AUTOPLUG_SELECT_RESULT that indicates the required
+ * operation. The default handler will always return
+ * #GST_AUTOPLUG_SELECT_TRY.
+ */
+ gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_SELECT] =
+ g_signal_new ("autoplug-select", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass,
+ autoplug_select), _gst_select_accumulator, NULL,
+ g_cclosure_marshal_generic,
+ GST_TYPE_AUTOPLUG_SELECT_RESULT, 3, GST_TYPE_PAD, GST_TYPE_CAPS,
+ GST_TYPE_ELEMENT_FACTORY);
+
+ /**
+ * GstDecodeBin::autoplug-query:
+ * @bin: The decodebin.
+ * @child: The child element doing the query
+ * @pad: The #GstPad.
+ * @query: The #GstQuery.
+ *
+ * This signal is emitted whenever an autoplugged element that is
+ * not linked downstream yet and not exposed does a query. It can
+ * be used to tell the element about the downstream supported caps
+ * for example.
+ *
+ * Returns: #TRUE if the query was handled, #FALSE otherwise.
+ */
+ gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_QUERY] =
+ g_signal_new ("autoplug-query", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass, autoplug_query),
+ _gst_boolean_or_accumulator, NULL, g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 3, GST_TYPE_PAD, GST_TYPE_ELEMENT,
+ GST_TYPE_QUERY | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ /**
+ * GstURISourceBin::drained:
+ *
+ * This signal is emitted when the data for the current uri is played.
+ */
+ gst_uri_source_bin_signals[SIGNAL_DRAINED] =
+ g_signal_new ("drained", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstURISourceBinClass, drained), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ /**
+ * GstURISourceBin::source-setup:
+ * @bin: the urisourcebin.
+ * @source: source element
+ *
+ * This signal is emitted after the source element has been created, so
+ * it can be configured by setting additional properties (e.g. set a
+ * proxy server for an http source, or set the device and read speed for
+ * an audio cd source). This is functionally equivalent to connecting to
+ * the notify::source signal, but more convenient.
+ *
+ * Since: 1.6.1
+ */
+ gst_uri_source_bin_signals[SIGNAL_SOURCE_SETUP] =
+ g_signal_new ("source-setup", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&srctemplate));
+ gst_element_class_set_static_metadata (gstelement_class,
+ "URI reader", "Generic/Bin/Source",
+ "Download and buffer a URI as needed",
+ "Jan Schmidt <jan@centricular.com>");
+
+ gstelement_class->query = GST_DEBUG_FUNCPTR (gst_uri_source_bin_query);
+ gstelement_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_uri_source_bin_change_state);
+
+ gstbin_class->handle_message = GST_DEBUG_FUNCPTR (handle_message);
+
+ klass->autoplug_continue =
+ GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_continue);
+ klass->autoplug_factories =
+ GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_factories);
+ klass->autoplug_sort = GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_sort);
+ klass->autoplug_select =
+ GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_select);
+ klass->autoplug_query = GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_query);
+}
+
+static void
+gst_uri_source_bin_init (GstURISourceBin * urisrc)
+{
+ /* first filter out the interesting element factories */
+ g_mutex_init (&urisrc->factories_lock);
+
+ g_mutex_init (&urisrc->lock);
+
+ urisrc->uri = g_strdup (DEFAULT_PROP_URI);
+ urisrc->connection_speed = DEFAULT_CONNECTION_SPEED;
+
+ urisrc->buffer_duration = DEFAULT_BUFFER_DURATION;
+ urisrc->buffer_size = DEFAULT_BUFFER_SIZE;
+ urisrc->download = DEFAULT_DOWNLOAD;
+ urisrc->ring_buffer_max_size = DEFAULT_RING_BUFFER_MAX_SIZE;
+ urisrc->last_buffering_pct = -1;
+
+ GST_OBJECT_FLAG_SET (urisrc, GST_ELEMENT_FLAG_SOURCE);
+}
+
+static void
+gst_uri_source_bin_finalize (GObject * obj)
+{
+ GstURISourceBin *urisrc = GST_URI_SOURCE_BIN (obj);
+
+ remove_demuxer (urisrc);
+ g_mutex_clear (&urisrc->lock);
+ g_mutex_clear (&urisrc->factories_lock);
+ g_free (urisrc->uri);
+ if (urisrc->factories)
+ gst_plugin_feature_list_free (urisrc->factories);
+
+ G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+gst_uri_source_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstURISourceBin *dec = GST_URI_SOURCE_BIN (object);
+
+ switch (prop_id) {
+ case PROP_URI:
+ GST_OBJECT_LOCK (dec);
+ g_free (dec->uri);
+ dec->uri = g_value_dup_string (value);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_CONNECTION_SPEED:
+ GST_OBJECT_LOCK (dec);
+ dec->connection_speed = g_value_get_uint64 (value) * 1000;
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_BUFFER_SIZE:
+ dec->buffer_size = g_value_get_int (value);
+ break;
+ case PROP_BUFFER_DURATION:
+ dec->buffer_duration = g_value_get_int64 (value);
+ break;
+ case PROP_DOWNLOAD:
+ dec->download = g_value_get_boolean (value);
+ break;
+ case PROP_USE_BUFFERING:
+ dec->use_buffering = g_value_get_boolean (value);
+ break;
+ case PROP_RING_BUFFER_MAX_SIZE:
+ dec->ring_buffer_max_size = g_value_get_uint64 (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_uri_source_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstURISourceBin *dec = GST_URI_SOURCE_BIN (object);
+
+ switch (prop_id) {
+ case PROP_URI:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_string (value, dec->uri);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_SOURCE:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_object (value, dec->source);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_CONNECTION_SPEED:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_uint64 (value, dec->connection_speed / 1000);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_BUFFER_SIZE:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_int (value, dec->buffer_size);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_BUFFER_DURATION:
+ GST_OBJECT_LOCK (dec);
+ g_value_set_int64 (value, dec->buffer_duration);
+ GST_OBJECT_UNLOCK (dec);
+ break;
+ case PROP_DOWNLOAD:
+ g_value_set_boolean (value, dec->download);
+ break;
+ case PROP_USE_BUFFERING:
+ g_value_set_boolean (value, dec->use_buffering);
+ break;
+ case PROP_RING_BUFFER_MAX_SIZE:
+ g_value_set_uint64 (value, dec->ring_buffer_max_size);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+do_async_start (GstURISourceBin * dbin)
+{
+ GstMessage *message;
+
+ dbin->async_pending = TRUE;
+
+ message = gst_message_new_async_start (GST_OBJECT_CAST (dbin));
+ GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (dbin), message);
+}
+
+static void
+do_async_done (GstURISourceBin * dbin)
+{
+ GstMessage *message;
+
+ if (dbin->async_pending) {
+ GST_DEBUG_OBJECT (dbin, "posting ASYNC_DONE");
+ message =
+ gst_message_new_async_done (GST_OBJECT_CAST (dbin),
+ GST_CLOCK_TIME_NONE);
+ GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (dbin), message);
+
+ dbin->async_pending = FALSE;
+ }
+}
+
+#define DEFAULT_QUEUE_SIZE (3 * GST_SECOND)
+#define DEFAULT_QUEUE_MIN_THRESHOLD ((DEFAULT_QUEUE_SIZE * 30) / 100)
+#define DEFAULT_QUEUE_THRESHOLD ((DEFAULT_QUEUE_SIZE * 95) / 100)
+
+static gboolean
+copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
+{
+ GstPad *gpad = GST_PAD_CAST (user_data);
+
+ GST_DEBUG_OBJECT (gpad, "store sticky event %" GST_PTR_FORMAT, *event);
+ gst_pad_store_sticky_event (gpad, *event);
+
+ return TRUE;
+}
+
+static GstPadProbeReturn
+pending_pad_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data);
+
+static GstPadProbeReturn
+demux_pad_events (GstPad * pad, GstPadProbeInfo * info, gpointer user_data);
+
+static void
+free_child_src_pad_info (ChildSrcPadInfo * info)
+{
+ if (info->cur_caps)
+ gst_caps_unref (info->cur_caps);
+ g_free (info);
+}
+
+/* Called by the signal handlers when a demuxer has produced a new stream */
+static void
+new_demuxer_pad_added_cb (GstElement * element, GstPad * pad,
+ GstURISourceBin * urisrc)
+{
+ ChildSrcPadInfo *info;
+
+ info = g_new0 (ChildSrcPadInfo, 1);
+ info->demux_src_pad = pad;
+ info->cur_caps = gst_pad_get_current_caps (pad);
+ if (info->cur_caps == NULL)
+ info->cur_caps = gst_pad_query_caps (pad, NULL);
+
+ g_object_set_data_full (G_OBJECT (pad), "urisourcebin.srcpadinfo",
+ info, (GDestroyNotify) free_child_src_pad_info);
+
+ GST_DEBUG_OBJECT (element, "new demuxer pad, name: <%s>. "
+ "Added as pending pad with caps %" GST_PTR_FORMAT,
+ GST_PAD_NAME (pad), info->cur_caps);
+
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+ urisrc->pending_pads = g_list_prepend (urisrc->pending_pads, pad);
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+
+ /* Block the pad. On the first data on that pad if it hasn't
+ * been linked to an output slot, we'll create one */
+ info->blocking_probe_id =
+ gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+ pending_pad_blocked, urisrc, NULL);
+ info->event_probe_id =
+ gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ demux_pad_events, urisrc, NULL);
+}
+
+static GstPadProbeReturn
+pending_pad_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ ChildSrcPadInfo *child_info;
+ OutputSlotInfo *slot;
+ GstURISourceBin *urisrc = GST_URI_SOURCE_BIN (user_data);
+ GstCaps *caps;
+
+ if (!(child_info =
+ g_object_get_data (G_OBJECT (pad), "urisourcebin.srcpadinfo")))
+ goto done;
+
+ GST_LOG_OBJECT (urisrc, "Removing pad %" GST_PTR_FORMAT " from pending list",
+ pad);
+
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+
+ /* Once blocked, this pad is no longer pending, one way or another */
+ urisrc->pending_pads = g_list_remove (urisrc->pending_pads, pad);
+
+ /* If already linked to a slot, nothing more to do */
+ if (child_info->output_slot) {
+ GST_LOG_OBJECT (urisrc, "Pad %" GST_PTR_FORMAT " is linked to slot %p",
+ pad, child_info->output_slot);
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ goto done;
+ }
+
+ caps = gst_pad_get_current_caps (pad);
+ if (caps == NULL)
+ caps = gst_pad_query_caps (pad, NULL);
+
+ /* FIXME: Don't do buffering if use_buffering is FALSE */
+ slot = get_output_slot (urisrc, FALSE, TRUE, caps);
+
+ gst_caps_unref (caps);
+
+ if (slot == NULL) {
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ goto done;
+ }
+
+ GST_LOG_OBJECT (urisrc, "Pad %" GST_PTR_FORMAT " linked to slot %p", pad,
+ slot);
+
+ child_info->output_slot = slot;
+ slot->linked_info = child_info;
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+
+ gst_pad_link (pad, slot->sinkpad);
+
+ expose_output_pad (urisrc, slot->srcpad);
+
+done:
+ return GST_PAD_PROBE_REMOVE;
+}
+
+/* Called with LOCK held */
+/* Looks for a suitable pending pad to connect onto this
+ * finishing output slot that's about to EOS */
+static gboolean
+link_pending_pad_to_output (GstURISourceBin * urisrc, OutputSlotInfo * slot)
+{
+ GList *cur;
+ ChildSrcPadInfo *in_info = slot->linked_info;
+ ChildSrcPadInfo *out_info = NULL;
+ gboolean res = FALSE;
+ GstCaps *cur_caps;
+
+ /* Look for a suitable pending pad */
+ cur_caps = gst_pad_get_current_caps (slot->sinkpad);
+
+ for (cur = urisrc->pending_pads; cur != NULL; cur = g_list_next (cur)) {
+ GstPad *pending = (GstPad *) (cur->data);
+ ChildSrcPadInfo *cur_info = NULL;
+ if ((cur_info =
+ g_object_get_data (G_OBJECT (pending),
+ "urisourcebin.srcpadinfo"))) {
+ /* Don't re-link to the same pad in case of EOS while still pending */
+ if (in_info == cur_info)
+ continue;
+ if (cur_caps == NULL || gst_caps_is_equal (cur_caps, cur_info->cur_caps)) {
+ GST_DEBUG_OBJECT (urisrc, "Found suitable pending pad %" GST_PTR_FORMAT
+ " with caps %" GST_PTR_FORMAT " to link to this output slot",
+ cur_info->demux_src_pad, cur_info->cur_caps);
+ out_info = cur_info;
+ break;
+ }
+ }
+ }
+
+ if (cur_caps)
+ gst_caps_unref (cur_caps);
+
+ if (out_info) {
+ /* Block any upstream stuff while we switch out the pad */
+ guint block_id =
+ gst_pad_add_probe (slot->sinkpad, GST_PAD_PROBE_TYPE_BLOCK_UPSTREAM,
+ NULL, NULL, NULL);
+ GST_DEBUG_OBJECT (urisrc, "Linking pending pad to existing output slot %p",
+ slot);
+
+ if (in_info) {
+ gst_pad_unlink (in_info->demux_src_pad, slot->sinkpad);
+ in_info->output_slot = NULL;
+ slot->linked_info = NULL;
+ }
+
+ if (gst_pad_link (out_info->demux_src_pad,
+ slot->sinkpad) == GST_PAD_LINK_OK) {
+ out_info->output_slot = slot;
+ slot->linked_info = out_info;
+ res = TRUE;
+ } else {
+ GST_ERROR_OBJECT (urisrc,
+ "Failed to link new demuxer pad to the output slot we tried");
+ }
+ gst_pad_remove_probe (slot->sinkpad, block_id);
+ }
+
+ return res;
+}
+
+static GstPadProbeReturn
+demux_pad_events (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ GstURISourceBin *urisrc = GST_URI_SOURCE_BIN (user_data);
+ ChildSrcPadInfo *child_info;
+
+ if (!(child_info =
+ g_object_get_data (G_OBJECT (pad), "urisourcebin.srcpadinfo")))
+ goto done;
+
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+ /* If not linked to a slot, nothing more to do */
+ if (child_info->output_slot == NULL) {
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ goto done;
+ }
+
+ if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info))) {
+ GstEvent *ev = GST_PAD_PROBE_INFO_EVENT (info);
+ if (GST_EVENT_TYPE (ev) == GST_EVENT_EOS && urisrc->pending_pads) {
+ GST_LOG_OBJECT (urisrc, "EOS on pad %" GST_PTR_FORMAT, pad);
+ if (!link_pending_pad_to_output (urisrc, child_info->output_slot)) {
+ GstEvent *event;
+ GstStructure *s;
+
+ /* Mark that we fed an EOS to this slot */
+ child_info->output_slot->is_eos = TRUE;
+
+ /* Actually feed a custom EOS event to avoid marking pads as EOSed */
+ s = gst_structure_new_empty ("urisourcebin-custom-eos");
+ event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s);
+ gst_pad_send_event (child_info->output_slot->sinkpad, event);
+ }
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ return GST_PAD_PROBE_HANDLED;
+ } else if (GST_EVENT_TYPE (ev) == GST_EVENT_CAPS) {
+ GstCaps *caps;
+ gst_event_parse_caps (ev, &caps);
+ gst_caps_replace (&child_info->cur_caps, caps);
+ }
+ }
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+
+done:
+ return GST_PAD_PROBE_OK;
+}
+
+/* Called with lock held */
+static OutputSlotInfo *
+get_output_slot (GstURISourceBin * urisrc, gboolean do_download,
+ gboolean is_adaptive, GstCaps * caps)
+{
+ OutputSlotInfo *slot;
+ GstPad *srcpad;
+ GstElement *queue;
+ const gchar *elem_name;
+
+ /* If we have caps, iterate the existing slots and look for an
+ * unlinked one that can be used */
+ if (caps && gst_caps_is_fixed (caps)) {
+ GSList *cur;
+ GstCaps *cur_caps;
+
+ for (cur = urisrc->out_slots; cur != NULL; cur = g_slist_next (cur)) {
+ slot = (OutputSlotInfo *) (cur->data);
+ if (slot->linked_info == NULL) {
+ cur_caps = gst_pad_get_current_caps (slot->sinkpad);
+ if (cur_caps == NULL || gst_caps_is_equal (caps, cur_caps)) {
+ GST_LOG_OBJECT (urisrc, "Found existing slot %p to link to", slot);
+ gst_caps_unref (cur_caps);
+ return slot;
+ }
+ gst_caps_unref (cur_caps);
+ }
+ }
+ }
+
+ /* Otherwise create the new slot */
+#if 0 /* There's no downloadbuffer in 1.2 */
+ if (do_download)
+ elem_name = "downloadbuffer";
+ else
+#endif
+ elem_name = "queue2";
+
+ queue = gst_element_factory_make (elem_name, NULL);
+ if (!queue)
+ goto no_buffer_element;
+
+ slot = g_new0 (OutputSlotInfo, 1);
+ slot->queue = queue;
+
+ if (do_download) {
+ gchar *temp_template, *filename;
+ const gchar *tmp_dir, *prgname;
+
+ tmp_dir = g_get_user_cache_dir ();
+ prgname = g_get_prgname ();
+ if (prgname == NULL)
+ prgname = "GStreamer";
+
+ filename = g_strdup_printf ("%s-XXXXXX", prgname);
+
+ /* build our filename */
+ temp_template = g_build_filename (tmp_dir, filename, NULL);
+
+ GST_DEBUG_OBJECT (urisrc, "enable download buffering in %s (%s, %s, %s)",
+ temp_template, tmp_dir, prgname, filename);
+
+ /* configure progressive download for selected media types */
+ g_object_set (queue, "temp-template", temp_template, NULL);
+
+ g_free (filename);
+ g_free (temp_template);
+ } else {
+ if (is_adaptive) {
+ GST_LOG_OBJECT (urisrc, "Adding queue for adaptive streaming stream");
+ g_object_set (queue, "use-buffering", TRUE, "use-tags-bitrate", TRUE,
+ "use-rate-estimate", FALSE, NULL);
+ } else {
+ GST_LOG_OBJECT (urisrc, "Adding queue for buffering");
+ g_object_set (queue, "use-buffering", TRUE, NULL);
+ }
+ g_object_set (queue, "ring-buffer-max-size",
+ urisrc->ring_buffer_max_size, NULL);
+ /* Disable max-size-buffers - queue based on data rate to the default time limit */
+ g_object_set (queue, "max-size-buffers", 0, NULL);
+ }
+
+ /* If buffer size or duration are set, set them on the element */
+ if (urisrc->buffer_size != -1)
+ g_object_set (queue, "max-size-bytes", urisrc->buffer_size, NULL);
+ if (urisrc->buffer_duration != -1)
+ g_object_set (queue, "max-size-time", urisrc->buffer_duration, NULL);
+ else {
+ /* Buffer 4 seconds by default - some extra headroom over the
+ * core default, because we trigger playback sooner */
+ g_object_set (queue, "max-size-time", 4 * GST_SECOND, NULL);
+ }
+
+ /* Don't start buffering until the queue is empty (< 1%).
+ * Start playback when the queue is 60% full, leaving a bit more room
+ * for upstream to push more without getting bursty */
+ g_object_set (queue, "low-percent", 1, "high-percent", 60, NULL);
+
+ /* save queue pointer so we can remove it later */
+ urisrc->out_slots = g_slist_prepend (urisrc->out_slots, slot);
+
+ gst_bin_add (GST_BIN_CAST (urisrc), queue);
+ gst_element_sync_state_with_parent (queue);
+
+ slot->sinkpad = gst_element_get_static_pad (queue, "sink");
+
+ /* get the new raw srcpad */
+ srcpad = gst_element_get_static_pad (queue, "src");
+ g_object_set_data (G_OBJECT (srcpad), "urisourcebin.slotinfo", slot);
+
+ slot->srcpad = create_output_pad (urisrc, srcpad);
+
+ gst_object_unref (srcpad);
+
+ return slot;
+
+no_buffer_element:
+ {
+ post_missing_plugin_error (GST_ELEMENT_CAST (urisrc), elem_name);
+ return NULL;
+ }
+}
+
+static GstPadProbeReturn
+source_pad_event_probe (GstPad * pad, GstPadProbeInfo * info,
+ gpointer user_data)
+{
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+ GstURISourceBin *urisrc = user_data;
+
+ GST_LOG_OBJECT (pad, "%s, urisrc %p", GST_EVENT_TYPE_NAME (event), urisrc);
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM &&
+ gst_event_has_name (event, "urisourcebin-custom-eos")) {
+ OutputSlotInfo *slot;
+ GST_DEBUG_OBJECT (pad, "we received EOS");
+
+ /* Check the slot is still unlinked - maybe it got
+ * re-linked and we should drop this EOS */
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+ slot = g_object_get_data (G_OBJECT (pad), "urisourcebin.slotinfo");
+ if (slot && slot->linked_info) {
+ GST_DEBUG_OBJECT (pad,
+ "EOS pad was re-linked to pending pad, so removing EOS status");
+ slot->is_eos = FALSE;
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ return GST_PAD_PROBE_HANDLED;
+ }
+
+ /* Otherwise it's time to send EOS and clean up this pad */
+ gst_pad_push_event (slot->srcpad, gst_event_new_eos ());
+
+ /* FIXME: Can't clean the pad up from the streaming thread... */
+ urisrc->out_slots = g_slist_remove (urisrc->out_slots, slot);
+#if 0
+ free_output_slot (slot, urisrc);
+ slot = NULL;
+#endif
+
+ /* FIXME: Only emit drained if all output pads are done and there's no
+ * pending pads */
+ g_signal_emit (urisrc, gst_uri_source_bin_signals[SIGNAL_DRAINED], 0, NULL);
+
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ return GST_PAD_PROBE_HANDLED;
+ }
+ /* never drop events */
+ return GST_PAD_PROBE_OK;
+}
+
+/* called when we found a raw pad to expose. We set up a
+ * padprobe to detect EOS before exposing the pad.
+ * Called with LOCK held. */
+static GstPad *
+create_output_pad (GstURISourceBin * urisrc, GstPad * pad)
+{
+ GstPad *newpad;
+ GstPadTemplate *pad_tmpl;
+ gchar *padname;
+
+ gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ source_pad_event_probe, urisrc, NULL);
+
+ pad_tmpl = gst_static_pad_template_get (&srctemplate);
+
+ padname = g_strdup_printf ("src_%u", urisrc->numpads);
+ urisrc->numpads++;
+
+ newpad = gst_ghost_pad_new_from_template (padname, pad, pad_tmpl);
+ gst_object_unref (pad_tmpl);
+ g_free (padname);
+
+ return newpad;
+}
+
+static void
+expose_output_pad (GstURISourceBin * urisrc, GstPad * pad)
+{
+ GstPad *target;
+
+ if (gst_object_has_as_parent (GST_OBJECT (pad), GST_OBJECT (urisrc)))
+ return; /* Pad is already exposed */
+
+ target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
+
+ gst_pad_sticky_events_foreach (target, copy_sticky_events, pad);
+ gst_object_unref (target);
+
+ gst_pad_set_active (pad, TRUE);
+ gst_element_add_pad (GST_ELEMENT_CAST (urisrc), pad);
+
+ /* Once we expose a pad, we're no longer async */
+ do_async_done (urisrc);
+}
+
+static void
+pad_removed_cb (GstElement * element, GstPad * pad, GstURISourceBin * urisrc)
+{
+ ChildSrcPadInfo *info;
+
+ GST_DEBUG_OBJECT (element, "pad removed name: <%s:%s>",
+ GST_DEBUG_PAD_NAME (pad));
+
+ /* we only care about srcpads */
+ if (!GST_PAD_IS_SRC (pad))
+ return;
+
+ if (!(info = g_object_get_data (G_OBJECT (pad), "urisourcebin.srcpadinfo")))
+ goto no_info;
+
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+ /* Make sure this isn't in the pending pads list */
+ urisrc->pending_pads = g_list_remove (urisrc->pending_pads, pad);
+
+ /* Send EOS to the output slot if the demuxer didn't already */
+ if (info->output_slot) {
+ if (!info->output_slot->is_eos) {
+ GstStructure *s;
+ GstEvent *event;
+
+ GST_LOG_OBJECT (element,
+ "Pad %" GST_PTR_FORMAT " was removed without EOS. Sending.", pad);
+
+ s = gst_structure_new_empty ("urisourcebin-custom-eos");
+ event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s);
+ gst_pad_send_event (info->output_slot->sinkpad, event);
+ info->output_slot->is_eos = TRUE;
+ }
+ /* After the pad goes away, the slot is free to reuse */
+ info->output_slot->linked_info = NULL;
+ info->output_slot = NULL;
+ } else {
+ GST_LOG_OBJECT (urisrc, "Removed pad has no output slot");
+ }
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+
+ return;
+
+ /* ERRORS */
+no_info:
+ {
+ GST_WARNING_OBJECT (element, "no info found for pad");
+ return;
+ }
+}
+
+/* helper function to lookup stuff in lists */
+static gboolean
+array_has_value (const gchar * values[], const gchar * value)
+{
+ gint i;
+
+ for (i = 0; values[i]; i++) {
+ if (g_str_has_prefix (value, values[i]))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+array_has_uri_value (const gchar * values[], const gchar * value)
+{
+ gint i;
+
+ for (i = 0; values[i]; i++) {
+ if (!g_ascii_strncasecmp (value, values[i], strlen (values[i])))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* list of URIs that we consider to be streams and that need buffering.
+ * We have no mechanism yet to figure this out with a query. */
+static const gchar *stream_uris[] = { "http://", "https://", "mms://",
+ "mmsh://", "mmsu://", "mmst://", "fd://", "myth://", "ssh://",
+ "ftp://", "sftp://",
+ NULL
+};
+
+/* list of URIs that need a queue because they are pretty bursty */
+static const gchar *queue_uris[] = { "cdda://", NULL };
+
+/* blacklisted URIs, we know they will always fail. */
+static const gchar *blacklisted_uris[] = { NULL };
+
+/* media types that use adaptive streaming */
+static const gchar *adaptive_media[] = {
+ "application/x-hls", "application/vnd.ms-sstr+xml",
+ "application/dash+xml", NULL
+};
+
+#define IS_STREAM_URI(uri) (array_has_uri_value (stream_uris, uri))
+#define IS_QUEUE_URI(uri) (array_has_uri_value (queue_uris, uri))
+#define IS_BLACKLISTED_URI(uri) (array_has_uri_value (blacklisted_uris, uri))
+#define IS_ADAPTIVE_MEDIA(media) (array_has_value (adaptive_media, media))
+
+/*
+ * Generate and configure a source element.
+ */
+static GstElement *
+gen_source_element (GstURISourceBin * urisrc)
+{
+ GObjectClass *source_class;
+ GstElement *source;
+ GParamSpec *pspec;
+ GstQuery *query;
+ GstSchedulingFlags flags;
+ GError *err = NULL;
+
+ if (!urisrc->uri)
+ goto no_uri;
+
+ GST_LOG_OBJECT (urisrc, "finding source for %s", urisrc->uri);
+
+ if (!gst_uri_is_valid (urisrc->uri))
+ goto invalid_uri;
+
+ if (IS_BLACKLISTED_URI (urisrc->uri))
+ goto uri_blacklisted;
+
+ source = gst_element_make_from_uri (GST_URI_SRC, urisrc->uri, "source", &err);
+ if (!source)
+ goto no_source;
+
+ GST_LOG_OBJECT (urisrc, "found source type %s", G_OBJECT_TYPE_NAME (source));
+
+ query = gst_query_new_scheduling ();
+ if (gst_element_query (source, query)) {
+ gst_query_parse_scheduling (query, &flags, NULL, NULL, NULL);
+ urisrc->is_stream = flags & GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED;
+ } else
+ urisrc->is_stream = IS_STREAM_URI (urisrc->uri);
+ gst_query_unref (query);
+
+ GST_LOG_OBJECT (urisrc, "source is stream: %d", urisrc->is_stream);
+
+ urisrc->need_queue = IS_QUEUE_URI (urisrc->uri);
+ GST_LOG_OBJECT (urisrc, "source needs queue: %d", urisrc->need_queue);
+
+ source_class = G_OBJECT_GET_CLASS (source);
+
+ pspec = g_object_class_find_property (source_class, "connection-speed");
+ if (pspec != NULL) {
+ guint64 speed = urisrc->connection_speed / 1000;
+ gboolean wrong_type = FALSE;
+
+ if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_UINT) {
+ GParamSpecUInt *pspecuint = G_PARAM_SPEC_UINT (pspec);
+
+ speed = CLAMP (speed, pspecuint->minimum, pspecuint->maximum);
+ } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT) {
+ GParamSpecInt *pspecint = G_PARAM_SPEC_INT (pspec);
+
+ speed = CLAMP (speed, pspecint->minimum, pspecint->maximum);
+ } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_UINT64) {
+ GParamSpecUInt64 *pspecuint = G_PARAM_SPEC_UINT64 (pspec);
+
+ speed = CLAMP (speed, pspecuint->minimum, pspecuint->maximum);
+ } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT64) {
+ GParamSpecInt64 *pspecint = G_PARAM_SPEC_INT64 (pspec);
+
+ speed = CLAMP (speed, pspecint->minimum, pspecint->maximum);
+ } else {
+ GST_WARNING_OBJECT (urisrc,
+ "The connection speed property %" G_GUINT64_FORMAT
+ " of type %s is not useful. Not setting it", speed,
+ g_type_name (G_PARAM_SPEC_TYPE (pspec)));
+ wrong_type = TRUE;
+ }
+
+ if (!wrong_type) {
+ g_object_set (source, "connection-speed", speed, NULL);
+
+ GST_DEBUG_OBJECT (urisrc,
+ "setting connection-speed=%" G_GUINT64_FORMAT " to source element",
+ speed);
+ }
+ }
+
+ return source;
+
+ /* ERRORS */
+no_uri:
+ {
+ GST_ELEMENT_ERROR (urisrc, RESOURCE, NOT_FOUND,
+ (_("No URI specified to play from.")), (NULL));
+ return NULL;
+ }
+invalid_uri:
+ {
+ GST_ELEMENT_ERROR (urisrc, RESOURCE, NOT_FOUND,
+ (_("Invalid URI \"%s\"."), urisrc->uri), (NULL));
+ g_clear_error (&err);
+ return NULL;
+ }
+uri_blacklisted:
+ {
+ GST_ELEMENT_ERROR (urisrc, RESOURCE, FAILED,
+ (_("This stream type cannot be played yet.")), (NULL));
+ return NULL;
+ }
+no_source:
+ {
+ /* whoops, could not create the source element, dig a little deeper to
+ * figure out what might be wrong. */
+ if (err != NULL && err->code == GST_URI_ERROR_UNSUPPORTED_PROTOCOL) {
+ gchar *prot;
+
+ prot = gst_uri_get_protocol (urisrc->uri);
+ if (prot == NULL)
+ goto invalid_uri;
+
+ gst_element_post_message (GST_ELEMENT_CAST (urisrc),
+ gst_missing_uri_source_message_new (GST_ELEMENT (urisrc), prot));
+
+ GST_ELEMENT_ERROR (urisrc, CORE, MISSING_PLUGIN,
+ (_("No URI handler implemented for \"%s\"."), prot), (NULL));
+
+ g_free (prot);
+ } else {
+ GST_ELEMENT_ERROR (urisrc, RESOURCE, NOT_FOUND,
+ ("%s", (err) ? err->message : "URI was not accepted by any element"),
+ ("No element accepted URI '%s'", urisrc->uri));
+ }
+
+ g_clear_error (&err);
+ return NULL;
+ }
+}
+
+static gboolean
+is_all_raw_caps (GstCaps * caps, GstCaps * rawcaps, gboolean * all_raw)
+{
+ GstCaps *intersection;
+ gint capssize;
+ gboolean res = FALSE;
+
+ if (caps == NULL)
+ return FALSE;
+
+ capssize = gst_caps_get_size (caps);
+ /* no caps, skip and move to the next pad */
+ if (capssize == 0 || gst_caps_is_empty (caps) || gst_caps_is_any (caps))
+ goto done;
+
+ intersection = gst_caps_intersect (caps, rawcaps);
+ *all_raw = !gst_caps_is_empty (intersection)
+ && (gst_caps_get_size (intersection) == capssize);
+ gst_caps_unref (intersection);
+
+ res = TRUE;
+
+done:
+ return res;
+}
+
+/**
+ * has_all_raw_caps:
+ * @pad: a #GstPad
+ * @all_raw: pointer to hold the result
+ *
+ * check if the caps of the pad are all raw. The caps are all raw if
+ * all of its structures contain audio/x-raw or video/x-raw.
+ *
+ * Returns: %FALSE @pad has no caps. Else TRUE and @all_raw set t the result.
+ */
+static gboolean
+has_all_raw_caps (GstPad * pad, GstCaps * rawcaps, gboolean * all_raw)
+{
+ GstCaps *caps;
+ gboolean res = FALSE;
+
+ caps = gst_pad_query_caps (pad, NULL);
+
+ GST_DEBUG_OBJECT (pad, "have caps %" GST_PTR_FORMAT, caps);
+
+ res = is_all_raw_caps (caps, rawcaps, all_raw);
+
+ gst_caps_unref (caps);
+ return res;
+}
+
+static void
+post_missing_plugin_error (GstElement * dec, const gchar * element_name)
+{
+ GstMessage *msg;
+
+ msg = gst_missing_element_message_new (dec, element_name);
+ gst_element_post_message (dec, msg);
+
+ GST_ELEMENT_ERROR (dec, CORE, MISSING_PLUGIN,
+ (_("Missing element '%s' - check your GStreamer installation."),
+ element_name), (NULL));
+ do_async_done (GST_URI_SOURCE_BIN (dec));
+}
+
+/**
+ * analyse_source:
+ * @urisrc: a #GstURISourceBin
+ * @is_raw: are all pads raw data
+ * @have_out: does the source have output
+ * @is_dynamic: is this a dynamic source
+ * @use_queue: put a queue before raw output pads
+ *
+ * Check the source of @urisrc and collect information about it.
+ *
+ * @is_raw will be set to TRUE if the source only produces raw pads. When this
+ * function returns, all of the raw pad of the source will be added
+ * to @urisrc
+ *
+ * @have_out: will be set to TRUE if the source has output pads.
+ *
+ * @is_dynamic: TRUE if the element will create (more) pads dynamically later
+ * on.
+ *
+ * Returns: FALSE if a fatal error occured while scanning.
+ */
+static gboolean
+analyse_source (GstURISourceBin * urisrc, gboolean * is_raw,
+ gboolean * have_out, gboolean * is_dynamic, gboolean use_queue)
+{
+ GstIterator *pads_iter;
+ gboolean done = FALSE;
+ gboolean res = TRUE;
+ GstPad *pad;
+ GValue item = { 0, };
+ GstCaps *rawcaps = DEFAULT_CAPS;
+
+ *have_out = FALSE;
+ *is_raw = FALSE;
+ *is_dynamic = FALSE;
+
+ pads_iter = gst_element_iterate_src_pads (urisrc->source);
+ while (!done) {
+ switch (gst_iterator_next (pads_iter, &item)) {
+ case GST_ITERATOR_ERROR:
+ res = FALSE;
+ /* FALLTROUGH */
+ case GST_ITERATOR_DONE:
+ done = TRUE;
+ break;
+ case GST_ITERATOR_RESYNC:
+ /* reset results and resync */
+ *have_out = FALSE;
+ *is_raw = FALSE;
+ *is_dynamic = FALSE;
+ gst_iterator_resync (pads_iter);
+ break;
+ case GST_ITERATOR_OK:
+ pad = g_value_dup_object (&item);
+ /* we now officially have an ouput pad */
+ *have_out = TRUE;
+
+ /* if FALSE, this pad has no caps and we continue with the next pad. */
+ if (!has_all_raw_caps (pad, rawcaps, is_raw)) {
+ gst_object_unref (pad);
+ g_value_reset (&item);
+ break;
+ }
+
+ /* caps on source pad are all raw, we can add the pad */
+ if (*is_raw) {
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+ if (use_queue) {
+ OutputSlotInfo *slot = get_output_slot (urisrc, FALSE, FALSE, NULL);
+ if (slot)
+ goto no_slot;
+
+ gst_pad_link (pad, slot->sinkpad);
+
+ /* get the new raw srcpad */
+ gst_object_unref (pad);
+ pad = slot->srcpad;
+ } else {
+ pad = create_output_pad (urisrc, pad);
+ }
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ expose_output_pad (urisrc, pad);
+ gst_object_unref (pad);
+ }
+ gst_object_unref (pad);
+ g_value_reset (&item);
+ break;
+ }
+ }
+ g_value_unset (&item);
+ gst_iterator_free (pads_iter);
+ gst_caps_unref (rawcaps);
+
+ if (!*have_out) {
+ GstElementClass *elemclass;
+ GList *walk;
+
+ /* element has no output pads, check for padtemplates that list SOMETIMES
+ * pads. */
+ elemclass = GST_ELEMENT_GET_CLASS (urisrc->source);
+
+ walk = gst_element_class_get_pad_template_list (elemclass);
+ while (walk != NULL) {
+ GstPadTemplate *templ;
+
+ templ = (GstPadTemplate *) walk->data;
+ if (GST_PAD_TEMPLATE_DIRECTION (templ) == GST_PAD_SRC) {
+ if (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_SOMETIMES)
+ *is_dynamic = TRUE;
+ break;
+ }
+ walk = g_list_next (walk);
+ }
+ }
+
+ return res;
+no_slot:
+ {
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ gst_object_unref (pad);
+ g_value_unset (&item);
+ gst_iterator_free (pads_iter);
+ gst_caps_unref (rawcaps);
+
+ return FALSE;
+ }
+}
+
+/* Remove any adaptive demuxer element */
+static void
+remove_demuxer (GstURISourceBin * bin)
+{
+ if (bin->demuxer) {
+ GST_DEBUG_OBJECT (bin, "removing old demuxer element");
+ gst_element_set_state (bin->demuxer, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (bin), bin->demuxer);
+ bin->demuxer = NULL;
+ }
+}
+
+/* make a demuxer and connect to all the signals */
+static GstElement *
+make_demuxer (GstURISourceBin * urisrc, GstCaps * caps)
+{
+ GList *factories, *eligible, *cur;
+ GstElement *demuxer = NULL;
+
+ GST_LOG_OBJECT (urisrc, "making new adaptive demuxer");
+
+ /* now create the demuxer element */
+
+ /* FIXME: Fire a signal to get the demuxer? */
+ factories = gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_DEMUXER, GST_RANK_MARGINAL);
+ eligible =
+ gst_element_factory_list_filter (factories, caps, GST_PAD_SINK,
+ gst_caps_is_fixed (caps));
+ gst_plugin_feature_list_free (factories);
+
+ if (eligible == NULL)
+ goto no_demuxer;
+
+ eligible = g_list_sort (eligible, gst_plugin_feature_rank_compare_func);
+
+ for (cur = eligible; cur != NULL; cur = g_list_next (cur)) {
+ GstElementFactory *factory = (GstElementFactory *) (cur->data);
+ const gchar *klass =
+ gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS);
+
+ /* Can't be a demuxer unless it has Demux in the klass name */
+ if (!strstr (klass, "Demux") || !strstr (klass, "Adaptive"))
+ continue;
+
+ demuxer = gst_element_factory_create (factory, NULL);
+ break;
+ }
+ gst_plugin_feature_list_free (eligible);
+
+ if (!demuxer)
+ goto no_demuxer;
+
+ GST_DEBUG_OBJECT (urisrc, "Created adaptive demuxer %" GST_PTR_FORMAT,
+ demuxer);
+
+ /* set up callbacks to create the links between
+ * demuxer streams and output */
+ g_signal_connect (demuxer,
+ "pad-added", G_CALLBACK (new_demuxer_pad_added_cb), urisrc);
+ g_signal_connect (demuxer,
+ "pad-removed", G_CALLBACK (pad_removed_cb), urisrc);
+
+ /* Propagate connection-speed property */
+ /* FIXME: Check the property exists on the demuxer */
+ g_object_set (demuxer,
+ "connection-speed", urisrc->connection_speed / 1000, NULL);
+
+ return demuxer;
+
+ /* ERRORS */
+no_demuxer:
+ {
+ /* FIXME: Fire the right error */
+ GST_ELEMENT_ERROR (urisrc, CORE, MISSING_PLUGIN, (NULL),
+ ("No demuxer element, check your installation"));
+ do_async_done (urisrc);
+ return NULL;
+ }
+}
+
+static void
+handle_new_pad (GstURISourceBin * urisrc, GstPad * srcpad, GstCaps * caps)
+{
+ gboolean is_raw;
+ GstStructure *s;
+ const gchar *media_type;
+ gboolean do_download = FALSE;
+
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+
+ /* if this is a pad with all raw caps, we can expose it */
+ if (is_all_raw_caps (caps, DEFAULT_CAPS, &is_raw) && is_raw) {
+ GstPad *pad;
+
+ GST_DEBUG_OBJECT (urisrc, "Found pad with raw caps %" GST_PTR_FORMAT
+ ", exposing", caps);
+ pad = create_output_pad (urisrc, srcpad);
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+
+ expose_output_pad (urisrc, pad);
+ return;
+ }
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+
+ s = gst_caps_get_structure (caps, 0);
+ media_type = gst_structure_get_name (s);
+
+ urisrc->is_adaptive = IS_ADAPTIVE_MEDIA (media_type);
+
+ if (urisrc->is_adaptive) {
+ GstElement *demux_elem;
+ GstPad *sinkpad;
+ GstPadLinkReturn link_res;
+
+ demux_elem = make_demuxer (urisrc, caps);
+ if (!demux_elem)
+ goto no_demuxer;
+ gst_bin_add (GST_BIN_CAST (urisrc), demux_elem);
+
+ sinkpad = gst_element_get_static_pad (demux_elem, "sink");
+ if (sinkpad == NULL)
+ goto no_demuxer_sink;
+
+ link_res = gst_pad_link (srcpad, sinkpad);
+
+ gst_object_unref (sinkpad);
+ if (link_res != GST_PAD_LINK_OK)
+ goto could_not_link;
+
+ gst_element_sync_state_with_parent (demux_elem);
+ } else {
+ OutputSlotInfo *slot;
+
+ /* only enable download buffering if the upstream duration is known */
+ if (urisrc->download) {
+ GstQuery *query = gst_query_new_duration (GST_FORMAT_BYTES);
+ if (gst_pad_query (srcpad, query)) {
+ gint64 dur;
+ gst_query_parse_duration (query, NULL, &dur);
+ do_download = (dur != -1);
+ }
+ gst_object_unref (query);
+ }
+
+ GST_DEBUG_OBJECT (urisrc, "check media-type %s, %d", media_type,
+ do_download);
+
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+ slot = get_output_slot (urisrc, do_download, FALSE, NULL);
+
+ if (slot == NULL || gst_pad_link (srcpad, slot->sinkpad) != GST_PAD_LINK_OK)
+ goto could_not_link;
+
+ expose_output_pad (urisrc, slot->srcpad);
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ }
+
+ return;
+
+ /* ERRORS */
+no_demuxer:
+ {
+ /* error was posted */
+ return;
+ }
+no_demuxer_sink:
+ {
+ GST_ELEMENT_ERROR (urisrc, CORE, NEGOTIATION,
+ (NULL), ("Adaptive demuxer element has no 'sink' pad"));
+ do_async_done (urisrc);
+ return;
+ }
+could_not_link:
+ {
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ GST_ELEMENT_ERROR (urisrc, CORE, NEGOTIATION,
+ (NULL), ("Can't link typefind to adaptive demuxer element"));
+ do_async_done (urisrc);
+ return;
+ }
+}
+
+/* signaled when we have a stream and we need to configure the download
+ * buffering or regular buffering */
+static void
+type_found (GstElement * typefind, guint probability,
+ GstCaps * caps, GstURISourceBin * urisrc)
+{
+ GstPad *srcpad = gst_element_get_static_pad (typefind, "src");
+
+ GST_DEBUG_OBJECT (urisrc, "typefind found caps %" GST_PTR_FORMAT
+ " on pad %" GST_PTR_FORMAT, caps, srcpad);
+ handle_new_pad (urisrc, srcpad, caps);
+
+ gst_object_unref (GST_OBJECT (srcpad));
+}
+
+/* setup a streaming source. This will first plug a typefind element to the
+ * source. After we find the type, we decide to whether to plug an adaptive
+ * demuxer, or just link through queue2 and expose the data. */
+static gboolean
+setup_streaming (GstURISourceBin * urisrc)
+{
+ GstElement *typefind;
+
+ /* now create the typefind element */
+ typefind = gst_element_factory_make ("typefind", NULL);
+ if (!typefind)
+ goto no_typefind;
+
+ gst_bin_add (GST_BIN_CAST (urisrc), typefind);
+
+ if (!gst_element_link_pads (urisrc->source, NULL, typefind, "sink"))
+ goto could_not_link;
+
+ urisrc->typefind = typefind;
+
+ /* connect a signal to find out when the typefind element found
+ * a type */
+ urisrc->have_type_id =
+ g_signal_connect (urisrc->typefind, "have-type",
+ G_CALLBACK (type_found), urisrc);
+
+ return TRUE;
+
+ /* ERRORS */
+no_typefind:
+ {
+ post_missing_plugin_error (GST_ELEMENT_CAST (urisrc), "typefind");
+ GST_ELEMENT_ERROR (urisrc, CORE, MISSING_PLUGIN, (NULL),
+ ("No typefind element, check your installation"));
+ do_async_done (urisrc);
+ return FALSE;
+ }
+could_not_link:
+ {
+ GST_ELEMENT_ERROR (urisrc, CORE, NEGOTIATION,
+ (NULL), ("Can't link source to typefind element"));
+ gst_bin_remove (GST_BIN_CAST (urisrc), typefind);
+ /* Don't lose the SOURCE flag */
+ GST_OBJECT_FLAG_SET (urisrc, GST_ELEMENT_FLAG_SOURCE);
+ do_async_done (urisrc);
+ return FALSE;
+ }
+}
+
+static void
+free_output_slot (OutputSlotInfo * slot, GstURISourceBin * urisrc)
+{
+ GST_DEBUG_OBJECT (urisrc, "removing old queue element and freeing slot %p",
+ slot);
+ gst_element_set_locked_state (slot->queue, TRUE);
+ gst_element_set_state (slot->queue, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (urisrc), slot->queue);
+
+ gst_object_unref (slot->sinkpad);
+
+ remove_buffering_msgs (urisrc, GST_OBJECT_CAST (slot->queue));
+
+ /* deactivate and remove the srcpad */
+ gst_pad_set_active (slot->srcpad, FALSE);
+ gst_element_remove_pad (GST_ELEMENT_CAST (urisrc), slot->srcpad);
+
+ g_free (slot);
+}
+
+/* remove source and all related elements */
+static void
+remove_source (GstURISourceBin * urisrc)
+{
+ GstElement *source = urisrc->source;
+
+ if (source) {
+ GST_DEBUG_OBJECT (urisrc, "removing old src element");
+ gst_element_set_state (source, GST_STATE_NULL);
+
+ if (urisrc->src_np_sig_id) {
+ g_signal_handler_disconnect (source, urisrc->src_np_sig_id);
+ urisrc->src_np_sig_id = 0;
+ }
+ gst_bin_remove (GST_BIN_CAST (urisrc), source);
+ urisrc->source = NULL;
+ }
+ if (urisrc->typefind) {
+ GST_DEBUG_OBJECT (urisrc, "removing old typefind element");
+ gst_element_set_state (urisrc->typefind, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (urisrc), urisrc->typefind);
+ urisrc->typefind = NULL;
+ }
+
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+ g_slist_foreach (urisrc->out_slots, (GFunc) free_output_slot, urisrc);
+ g_slist_free (urisrc->out_slots);
+ urisrc->out_slots = NULL;
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+
+ if (urisrc->demuxer) {
+ GST_DEBUG_OBJECT (urisrc, "removing old adaptive demux element");
+ gst_element_set_state (urisrc->demuxer, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (urisrc), urisrc->demuxer);
+ urisrc->demuxer = NULL;
+ }
+ /* Don't lose the SOURCE flag */
+ GST_OBJECT_FLAG_SET (urisrc, GST_ELEMENT_FLAG_SOURCE);
+}
+
+/* is called when a dynamic source element created a new pad. */
+static void
+source_new_pad (GstElement * element, GstPad * pad, GstURISourceBin * urisrc)
+{
+ GstCaps *caps;
+
+ GST_DEBUG_OBJECT (urisrc, "Found new pad %s.%s in source element %s",
+ GST_DEBUG_PAD_NAME (pad), GST_ELEMENT_NAME (element));
+ caps = gst_pad_get_current_caps (pad);
+ if (caps == NULL)
+ caps = gst_pad_query_caps (pad, NULL);
+ handle_new_pad (urisrc, pad, caps);
+ gst_caps_unref (caps);
+}
+
+static gboolean
+is_live_source (GstElement * source)
+{
+ GObjectClass *source_class = NULL;
+ gboolean is_live = FALSE;
+ GParamSpec *pspec;
+
+ source_class = G_OBJECT_GET_CLASS (source);
+ pspec = g_object_class_find_property (source_class, "is-live");
+ if (!pspec || G_PARAM_SPEC_VALUE_TYPE (pspec) != G_TYPE_BOOLEAN)
+ return FALSE;
+
+ g_object_get (G_OBJECT (source), "is-live", &is_live, NULL);
+
+ return is_live;
+}
+
+/* construct and run the source and demuxer elements until we found
+ * all the streams or until a preroll queue has been filled.
+*/
+static gboolean
+setup_source (GstURISourceBin * urisrc)
+{
+ gboolean is_raw, have_out, is_dynamic;
+
+ GST_DEBUG_OBJECT (urisrc, "setup source");
+
+ /* delete old src */
+ remove_source (urisrc);
+
+ /* create and configure an element that can handle the uri */
+ if (!(urisrc->source = gen_source_element (urisrc)))
+ goto no_source;
+
+ /* state will be merged later - if file is not found, error will be
+ * handled by the application right after. */
+ gst_bin_add (GST_BIN_CAST (urisrc), urisrc->source);
+
+ /* notify of the new source used */
+ g_object_notify (G_OBJECT (urisrc), "source");
+
+ g_signal_emit (urisrc, gst_uri_source_bin_signals[SIGNAL_SOURCE_SETUP],
+ 0, urisrc->source);
+
+ if (is_live_source (urisrc->source))
+ urisrc->is_stream = FALSE;
+
+ /* remove the old demuxer now, if any */
+ remove_demuxer (urisrc);
+
+ /* see if the source element emits raw audio/video all by itself,
+ * if so, we can create streams for the pads and be done with it.
+ * Also check that is has source pads, if not, we assume it will
+ * do everything itself. */
+ if (!analyse_source (urisrc, &is_raw, &have_out, &is_dynamic,
+ urisrc->need_queue && urisrc->use_buffering))
+ goto invalid_source;
+
+ if (is_raw) {
+ GST_DEBUG_OBJECT (urisrc, "Source provides all raw data");
+ /* source provides raw data, we added the pads and we can now signal a
+ * no_more pads because we are done. */
+ gst_element_no_more_pads (GST_ELEMENT_CAST (urisrc));
+ do_async_done (urisrc);
+ return TRUE;
+ }
+ if (!have_out && !is_dynamic) {
+ GST_DEBUG_OBJECT (urisrc, "Source has no output pads");
+ return TRUE;
+ }
+ if (is_dynamic) {
+ GST_DEBUG_OBJECT (urisrc, "Source has dynamic output pads");
+ /* connect a handler for the new-pad signal */
+ urisrc->src_np_sig_id =
+ g_signal_connect (urisrc->source, "pad-added",
+ G_CALLBACK (source_new_pad), urisrc);
+ } else {
+ if (urisrc->is_stream) {
+ GST_DEBUG_OBJECT (urisrc, "Setting up streaming");
+ /* do the stream things here */
+ if (!setup_streaming (urisrc))
+ goto streaming_failed;
+ } else {
+ GstIterator *pads_iter;
+ gboolean done = FALSE;
+ pads_iter = gst_element_iterate_src_pads (urisrc->source);
+ while (!done) {
+ GValue item = { 0, };
+ GstPad *pad;
+
+ switch (gst_iterator_next (pads_iter, &item)) {
+ case GST_ITERATOR_ERROR:
+ GST_WARNING_OBJECT (urisrc,
+ "Error iterating pads on source element");
+ /* FALLTROUGH */
+ case GST_ITERATOR_DONE:
+ done = TRUE;
+ break;
+ case GST_ITERATOR_RESYNC:
+ /* reset results and resync */
+ gst_iterator_resync (pads_iter);
+ break;
+ case GST_ITERATOR_OK:
+ pad = g_value_get_object (&item);
+ /* no streaming source, expose pads directly */
+ GST_URI_SOURCE_BIN_LOCK (urisrc);
+ pad = create_output_pad (urisrc, pad);
+ GST_URI_SOURCE_BIN_UNLOCK (urisrc);
+ expose_output_pad (urisrc, pad);
+ g_value_reset (&item);
+ break;
+ }
+ }
+ gst_iterator_free (pads_iter);
+ gst_element_no_more_pads (GST_ELEMENT_CAST (urisrc));
+ do_async_done (urisrc);
+ }
+ }
+ return TRUE;
+
+ /* ERRORS */
+no_source:
+ {
+ /* error message was already posted */
+ return FALSE;
+ }
+invalid_source:
+ {
+ GST_ELEMENT_ERROR (urisrc, CORE, FAILED,
+ (_("Source element is invalid.")), (NULL));
+ return FALSE;
+ }
+streaming_failed:
+ {
+ /* message was posted */
+ return FALSE;
+ }
+}
+
+static void
+value_list_append_structure_list (GValue * list_val, GstStructure ** first,
+ GList * structure_list)
+{
+ GList *l;
+
+ for (l = structure_list; l != NULL; l = l->next) {
+ GValue val = { 0, };
+
+ if (*first == NULL)
+ *first = gst_structure_copy ((GstStructure *) l->data);
+
+ g_value_init (&val, GST_TYPE_STRUCTURE);
+ g_value_take_boxed (&val, gst_structure_copy ((GstStructure *) l->data));
+ gst_value_list_append_value (list_val, &val);
+ g_value_unset (&val);
+ }
+}
+
+/* if it's a redirect message with multiple redirect locations we might
+ * want to pick a different 'best' location depending on the required
+ * bitrates and the connection speed */
+static GstMessage *
+handle_redirect_message (GstURISourceBin * dec, GstMessage * msg)
+{
+ const GValue *locations_list, *location_val;
+ GstMessage *new_msg;
+ GstStructure *new_structure = NULL;
+ GList *l_good = NULL, *l_neutral = NULL, *l_bad = NULL;
+ GValue new_list = { 0, };
+ guint size, i;
+ const GstStructure *structure;
+
+ GST_DEBUG_OBJECT (dec, "redirect message: %" GST_PTR_FORMAT, msg);
+ GST_DEBUG_OBJECT (dec, "connection speed: %" G_GUINT64_FORMAT,
+ dec->connection_speed);
+
+ structure = gst_message_get_structure (msg);
+ if (dec->connection_speed == 0 || structure == NULL)
+ return msg;
+
+ locations_list = gst_structure_get_value (structure, "locations");
+ if (locations_list == NULL)
+ return msg;
+
+ size = gst_value_list_get_size (locations_list);
+ if (size < 2)
+ return msg;
+
+ /* maintain existing order as much as possible, just sort references
+ * with too high a bitrate to the end (the assumption being that if
+ * bitrates are given they are given for all interesting streams and
+ * that the you-need-at-least-version-xyz redirect has the same bitrate
+ * as the lowest referenced redirect alternative) */
+ for (i = 0; i < size; ++i) {
+ const GstStructure *s;
+ gint bitrate = 0;
+
+ location_val = gst_value_list_get_value (locations_list, i);
+ s = (const GstStructure *) g_value_get_boxed (location_val);
+ if (!gst_structure_get_int (s, "minimum-bitrate", &bitrate) || bitrate <= 0) {
+ GST_DEBUG_OBJECT (dec, "no bitrate: %" GST_PTR_FORMAT, s);
+ l_neutral = g_list_append (l_neutral, (gpointer) s);
+ } else if (bitrate > dec->connection_speed) {
+ GST_DEBUG_OBJECT (dec, "bitrate too high: %" GST_PTR_FORMAT, s);
+ l_bad = g_list_append (l_bad, (gpointer) s);
+ } else if (bitrate <= dec->connection_speed) {
+ GST_DEBUG_OBJECT (dec, "bitrate OK: %" GST_PTR_FORMAT, s);
+ l_good = g_list_append (l_good, (gpointer) s);
+ }
+ }
+
+ g_value_init (&new_list, GST_TYPE_LIST);
+ value_list_append_structure_list (&new_list, &new_structure, l_good);
+ value_list_append_structure_list (&new_list, &new_structure, l_neutral);
+ value_list_append_structure_list (&new_list, &new_structure, l_bad);
+ gst_structure_take_value (new_structure, "locations", &new_list);
+
+ g_list_free (l_good);
+ g_list_free (l_neutral);
+ g_list_free (l_bad);
+
+ new_msg = gst_message_new_element (msg->src, new_structure);
+ gst_message_unref (msg);
+
+ GST_DEBUG_OBJECT (dec, "new redirect message: %" GST_PTR_FORMAT, new_msg);
+ return new_msg;
+}
+
+static GstMessage *
+handle_buffering_message (GstURISourceBin * urisrc, GstMessage * msg)
+{
+ gint perc, msg_perc;
+ gint smaller_perc = 100;
+ GstMessage *smaller = NULL;
+ GList *found = NULL;
+ GList *iter;
+
+ /* buffering messages must be aggregated as there might be multiple
+ * multiqueue in the pipeline and their independent buffering messages
+ * will confuse the application
+ *
+ * urisourcebin keeps a list of messages received from elements that are
+ * buffering.
+ * Rules are:
+ * 0) Ignore buffering from elements that are draining (is_eos == TRUE)
+ * 1) Always post the smaller buffering %
+ * 2) If an element posts a 100% buffering message, remove it from the list
+ * 3) When there are no more messages on the list, post 100% message
+ * 4) When an element posts a new buffering message, update the one
+ * on the list to this new value
+ */
+ gst_message_parse_buffering (msg, &msg_perc);
+ GST_LOG_OBJECT (urisrc, "Got buffering msg from %" GST_PTR_FORMAT
+ " with %d%%", GST_MESSAGE_SRC (msg), msg_perc);
+
+ GST_OBJECT_LOCK (urisrc);
+ /*
+ * Single loop for 2 things:
+ * 1) Look for a message with the same source
+ * 1.1) If the received message is 100%, remove it from the list
+ * 2) Find the minimum buffering from the list from elements that aren't EOS
+ */
+ for (iter = urisrc->buffering_status; iter;) {
+ GstMessage *bufstats = iter->data;
+ OutputSlotInfo *slot =
+ g_object_get_data (G_OBJECT (GST_MESSAGE_SRC (bufstats)),
+ "urisourcebin.slotinfo");
+ gboolean is_eos = FALSE;
+
+ if (slot)
+ is_eos = slot->is_eos;
+
+ if (GST_MESSAGE_SRC (bufstats) == GST_MESSAGE_SRC (msg)) {
+ found = iter;
+ if (msg_perc < 100) {
+ gst_message_unref (iter->data);
+ bufstats = iter->data = gst_message_ref (msg);
+ } else {
+ GList *current = iter;
+
+ /* remove the element here and avoid confusing the loop */
+ iter = g_list_next (iter);
+
+ gst_message_unref (current->data);
+ urisrc->buffering_status =
+ g_list_delete_link (urisrc->buffering_status, current);
+
+ continue;
+ }
+ }
+
+ /* only update minimum stat for non-EOS slots */
+ if (!is_eos) {
+ gst_message_parse_buffering (bufstats, &perc);
+ if (perc < smaller_perc) {
+ smaller_perc = perc;
+ smaller = bufstats;
+ }
+ } else {
+ GST_LOG_OBJECT (urisrc, "Ignoring buffering from EOS element");
+ }
+ iter = g_list_next (iter);
+ }
+
+ if (found == NULL && msg_perc < 100) {
+ if (msg_perc < smaller_perc) {
+ smaller_perc = msg_perc;
+ smaller = msg;
+ }
+ urisrc->buffering_status =
+ g_list_prepend (urisrc->buffering_status, gst_message_ref (msg));
+ }
+
+ if (smaller_perc == urisrc->last_buffering_pct) {
+ /* Don't repeat our last buffering status */
+ gst_message_replace (&msg, NULL);
+ } else {
+ urisrc->last_buffering_pct = smaller_perc;
+
+ /* now compute the buffering message that should be posted */
+ if (smaller_perc == 100) {
+ g_assert (urisrc->buffering_status == NULL);
+ /* we are posting the original received msg */
+ } else {
+ gst_message_replace (&msg, smaller);
+ }
+ }
+ GST_OBJECT_UNLOCK (urisrc);
+
+ if (msg) {
+ GST_LOG_OBJECT (urisrc, "Sending buffering msg from %" GST_PTR_FORMAT
+ " with %d%%", GST_MESSAGE_SRC (msg), smaller_perc);
+ } else {
+ GST_LOG_OBJECT (urisrc, "Dropped buffering msg as a repeat of %d%%",
+ smaller_perc);
+ }
+ return msg;
+}
+
+/* Remove any buffering message from the given source */
+static void
+remove_buffering_msgs (GstURISourceBin * urisrc, GstObject * src)
+{
+ GList *iter;
+
+ GST_OBJECT_LOCK (urisrc);
+ for (iter = urisrc->buffering_status; iter;) {
+ GstMessage *bufstats = iter->data;
+ if (GST_MESSAGE_SRC (bufstats) == src) {
+ gst_message_unref (bufstats);
+ urisrc->buffering_status =
+ g_list_delete_link (urisrc->buffering_status, iter);
+ break;
+ }
+ iter = g_list_next (iter);
+ }
+ GST_OBJECT_UNLOCK (urisrc);
+}
+
+static void
+handle_message (GstBin * bin, GstMessage * msg)
+{
+ GstURISourceBin *urisrc = GST_URI_SOURCE_BIN (bin);
+
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_ELEMENT:{
+ if (gst_message_has_name (msg, "redirect")) {
+ /* sort redirect messages based on the connection speed. This simplifies
+ * the user of this element as it can in most cases just pick the first item
+ * of the sorted list as a good redirection candidate. It can of course
+ * choose something else from the list if it has a better way. */
+ msg = handle_redirect_message (urisrc, msg);
+ }
+ break;
+ }
+ case GST_MESSAGE_BUFFERING:
+ msg = handle_buffering_message (urisrc, msg);
+ break;
+ default:
+ break;
+ }
+
+ if (msg)
+ GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
+}
+
+/* generic struct passed to all query fold methods
+ * FIXME, move to core.
+ */
+typedef struct
+{
+ GstQuery *query;
+ gint64 min;
+ gint64 max;
+ gboolean seekable;
+ gboolean live;
+} QueryFold;
+
+typedef void (*QueryInitFunction) (GstURISourceBin * urisrc, QueryFold * fold);
+typedef void (*QueryDoneFunction) (GstURISourceBin * urisrc, QueryFold * fold);
+
+/* for duration/position we collect all durations/positions and take
+ * the MAX of all valid results */
+static void
+decoder_query_init (GstURISourceBin * dec, QueryFold * fold)
+{
+ fold->min = 0;
+ fold->max = -1;
+ fold->seekable = TRUE;
+ fold->live = 0;
+}
+
+static gboolean
+decoder_query_duration_fold (const GValue * item, GValue * ret,
+ QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+
+ if (gst_pad_query (pad, fold->query)) {
+ gint64 duration;
+
+ g_value_set_boolean (ret, TRUE);
+
+ gst_query_parse_duration (fold->query, NULL, &duration);
+
+ GST_DEBUG_OBJECT (item, "got duration %" G_GINT64_FORMAT, duration);
+
+ if (duration > fold->max)
+ fold->max = duration;
+ }
+ return TRUE;
+}
+
+static void
+decoder_query_duration_done (GstURISourceBin * dec, QueryFold * fold)
+{
+ GstFormat format;
+
+ gst_query_parse_duration (fold->query, &format, NULL);
+ /* store max in query result */
+ gst_query_set_duration (fold->query, format, fold->max);
+
+ GST_DEBUG ("max duration %" G_GINT64_FORMAT, fold->max);
+}
+
+static gboolean
+decoder_query_position_fold (const GValue * item, GValue * ret,
+ QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+
+ if (gst_pad_query (pad, fold->query)) {
+ gint64 position;
+
+ g_value_set_boolean (ret, TRUE);
+
+ gst_query_parse_position (fold->query, NULL, &position);
+
+ GST_DEBUG_OBJECT (item, "got position %" G_GINT64_FORMAT, position);
+
+ if (position > fold->max)
+ fold->max = position;
+ }
+
+ return TRUE;
+}
+
+static void
+decoder_query_position_done (GstURISourceBin * dec, QueryFold * fold)
+{
+ GstFormat format;
+
+ gst_query_parse_position (fold->query, &format, NULL);
+ /* store max in query result */
+ gst_query_set_position (fold->query, format, fold->max);
+
+ GST_DEBUG_OBJECT (dec, "max position %" G_GINT64_FORMAT, fold->max);
+}
+
+static gboolean
+decoder_query_latency_fold (const GValue * item, GValue * ret, QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+
+ if (gst_pad_query (pad, fold->query)) {
+ GstClockTime min, max;
+ gboolean live;
+
+ gst_query_parse_latency (fold->query, &live, &min, &max);
+
+ GST_DEBUG_OBJECT (pad,
+ "got latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT
+ ", live %d", GST_TIME_ARGS (min), GST_TIME_ARGS (max), live);
+
+ if (live) {
+ /* for the combined latency we collect the MAX of all min latencies and
+ * the MIN of all max latencies */
+ if (min > fold->min)
+ fold->min = min;
+ if (fold->max == -1)
+ fold->max = max;
+ else if (max < fold->max)
+ fold->max = max;
+
+ fold->live = TRUE;
+ }
+ } else {
+ GST_LOG_OBJECT (pad, "latency query failed");
+ g_value_set_boolean (ret, FALSE);
+ }
+
+ return TRUE;
+}
+
+static void
+decoder_query_latency_done (GstURISourceBin * dec, QueryFold * fold)
+{
+ /* store max in query result */
+ gst_query_set_latency (fold->query, fold->live, fold->min, fold->max);
+
+ GST_DEBUG_OBJECT (dec,
+ "latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT
+ ", live %d", GST_TIME_ARGS (fold->min), GST_TIME_ARGS (fold->max),
+ fold->live);
+}
+
+/* we are seekable if all srcpads are seekable */
+static gboolean
+decoder_query_seeking_fold (const GValue * item, GValue * ret, QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+
+ if (gst_pad_query (pad, fold->query)) {
+ gboolean seekable;
+
+ g_value_set_boolean (ret, TRUE);
+ gst_query_parse_seeking (fold->query, NULL, &seekable, NULL, NULL);
+
+ GST_DEBUG_OBJECT (item, "got seekable %d", seekable);
+
+ if (fold->seekable)
+ fold->seekable = seekable;
+ }
+
+ return TRUE;
+}
+
+static void
+decoder_query_seeking_done (GstURISourceBin * dec, QueryFold * fold)
+{
+ GstFormat format;
+
+ gst_query_parse_seeking (fold->query, &format, NULL, NULL, NULL);
+ gst_query_set_seeking (fold->query, format, fold->seekable, 0, -1);
+
+ GST_DEBUG_OBJECT (dec, "seekable %d", fold->seekable);
+}
+
+/* generic fold, return first valid result */
+static gboolean
+decoder_query_generic_fold (const GValue * item, GValue * ret, QueryFold * fold)
+{
+ GstPad *pad = g_value_get_object (item);
+ gboolean res;
+
+ if ((res = gst_pad_query (pad, fold->query))) {
+ g_value_set_boolean (ret, TRUE);
+ GST_DEBUG_OBJECT (item, "answered query %p", fold->query);
+ }
+
+ /* and stop as soon as we have a valid result */
+ return !res;
+}
+
+/* we're a bin, the default query handler iterates sink elements, which we don't
+ * have normally. We should just query all source pads.
+ */
+static gboolean
+gst_uri_source_bin_query (GstElement * element, GstQuery * query)
+{
+ GstURISourceBin *decoder;
+ gboolean res = FALSE;
+ GstIterator *iter;
+ GstIteratorFoldFunction fold_func;
+ QueryInitFunction fold_init = NULL;
+ QueryDoneFunction fold_done = NULL;
+ QueryFold fold_data;
+ GValue ret = { 0 };
+ gboolean default_ret = FALSE;
+
+ decoder = GST_URI_SOURCE_BIN (element);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_DURATION:
+ /* iterate and collect durations */
+ fold_func = (GstIteratorFoldFunction) decoder_query_duration_fold;
+ fold_init = decoder_query_init;
+ fold_done = decoder_query_duration_done;
+ break;
+ case GST_QUERY_POSITION:
+ /* iterate and collect durations */
+ fold_func = (GstIteratorFoldFunction) decoder_query_position_fold;
+ fold_init = decoder_query_init;
+ fold_done = decoder_query_position_done;
+ break;
+ case GST_QUERY_LATENCY:
+ /* iterate and collect durations */
+ fold_func = (GstIteratorFoldFunction) decoder_query_latency_fold;
+ fold_init = decoder_query_init;
+ fold_done = decoder_query_latency_done;
+ default_ret = TRUE;
+ break;
+ case GST_QUERY_SEEKING:
+ /* iterate and collect durations */
+ fold_func = (GstIteratorFoldFunction) decoder_query_seeking_fold;
+ fold_init = decoder_query_init;
+ fold_done = decoder_query_seeking_done;
+ break;
+ default:
+ fold_func = (GstIteratorFoldFunction) decoder_query_generic_fold;
+ break;
+ }
+
+ fold_data.query = query;
+
+ g_value_init (&ret, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&ret, default_ret);
+
+ iter = gst_element_iterate_src_pads (element);
+ GST_DEBUG_OBJECT (element, "Sending query %p (type %d) to src pads",
+ query, GST_QUERY_TYPE (query));
+
+ if (fold_init)
+ fold_init (decoder, &fold_data);
+
+ while (TRUE) {
+ GstIteratorResult ires;
+
+ ires = gst_iterator_fold (iter, fold_func, &ret, &fold_data);
+
+ switch (ires) {
+ case GST_ITERATOR_RESYNC:
+ gst_iterator_resync (iter);
+ if (fold_init)
+ fold_init (decoder, &fold_data);
+ g_value_set_boolean (&ret, default_ret);
+ break;
+ case GST_ITERATOR_OK:
+ case GST_ITERATOR_DONE:
+ res = g_value_get_boolean (&ret);
+ if (fold_done != NULL && res)
+ fold_done (decoder, &fold_data);
+ goto done;
+ default:
+ res = FALSE;
+ goto done;
+ }
+ }
+done:
+ gst_iterator_free (iter);
+
+ return res;
+}
+
+static void
+sync_slot_queue (OutputSlotInfo * slot)
+{
+ gst_element_sync_state_with_parent (slot->queue);
+}
+
+static GstStateChangeReturn
+gst_uri_source_bin_change_state (GstElement * element,
+ GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ GstURISourceBin *urisrc = GST_URI_SOURCE_BIN (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ do_async_start (urisrc);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto setup_failed;
+ else if (ret == GST_STATE_CHANGE_NO_PREROLL)
+ do_async_done (urisrc);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ GST_DEBUG ("ready to paused");
+ if (!setup_source (urisrc))
+ goto source_failed;
+
+ ret = GST_STATE_CHANGE_ASYNC;
+
+ /* And now sync the states of everything we added */
+ g_slist_foreach (urisrc->out_slots, (GFunc) sync_slot_queue, NULL);
+ if (urisrc->typefind)
+ ret = gst_element_set_state (urisrc->typefind, GST_STATE_PAUSED);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto setup_failed;
+ if (urisrc->source)
+ ret = gst_element_set_state (urisrc->source, GST_STATE_PAUSED);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto setup_failed;
+ if (ret == GST_STATE_CHANGE_SUCCESS)
+ ret = GST_STATE_CHANGE_ASYNC;
+
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ GST_DEBUG ("paused to ready");
+ remove_demuxer (urisrc);
+ remove_source (urisrc);
+ do_async_done (urisrc);
+ g_list_free_full (urisrc->buffering_status,
+ (GDestroyNotify) gst_message_unref);
+ urisrc->buffering_status = NULL;
+ urisrc->last_buffering_pct = -1;
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ GST_DEBUG ("ready to null");
+ remove_demuxer (urisrc);
+ remove_source (urisrc);
+ break;
+ default:
+ break;
+ }
+ return ret;
+
+ /* ERRORS */
+source_failed:
+ {
+ do_async_done (urisrc);
+ return GST_STATE_CHANGE_FAILURE;
+ }
+setup_failed:
+ {
+ /* clean up leftover groups */
+ do_async_done (urisrc);
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
+
+gboolean
+gst_uri_source_bin_plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_uri_source_bin_debug, "urisourcebin", 0,
+ "URI source element");
+
+ return gst_element_register (plugin, "urisourcebin", GST_RANK_NONE,
+ GST_TYPE_URI_DECODE_BIN);
+}
GTK_SUBDIRS = playback seek snapshot
endif
-SUBDIRS = app audio dynamic fft $(GTK_SUBDIRS) gio overlay playrec encoding
-DIST_SUBDIRS = app audio dynamic fft gio playback overlay seek snapshot playrec encoding
+SUBDIRS = app audio decodebin_next dynamic fft $(GTK_SUBDIRS) gio overlay playrec encoding
+DIST_SUBDIRS = app audio dynamic decodebin_next fft gio playback overlay seek snapshot playrec encoding
include $(top_srcdir)/common/parallel-subdirs.mak
--- /dev/null
+decodebin3
+playbin-test
--- /dev/null
+noinst_PROGRAMS = decodebin3 playbin-test
+
+LDADD = $(GST_LIBS)
+
+AM_CFLAGS = -I$(top_builddir)/gst-libs $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS)
--- /dev/null
+/* sample application for testing decodebin3
+ *
+ * Copyright (C) 2015 Centricular Ltd
+ * @author: Edward Hervey <edward@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gprintf.h>
+#include <gst/gst.h>
+
+/* Global structure */
+
+typedef struct _MyDataStruct
+{
+ GMainLoop *mainloop;
+ GstElement *pipeline;
+ GstBus *demux_bus;
+
+ GstElement *decodebin;
+
+ GstElement *src;
+ GList *other_src;
+ GstElement *playsink;
+
+ /* Current collection */
+ GstStreamCollection *collection;
+ guint notify_id;
+
+ guint current_audio;
+ guint current_video;
+ guint current_text;
+
+ glong timeout_id;
+} MyDataStruct;
+
+static void
+print_tag_foreach (const GstTagList * tags, const gchar * tag,
+ gpointer user_data)
+{
+ GValue val = { 0, };
+ gchar *str;
+ gint depth = GPOINTER_TO_INT (user_data);
+
+ if (!gst_tag_list_copy_value (&val, tags, tag))
+ return;
+
+ if (G_VALUE_HOLDS_STRING (&val))
+ str = g_value_dup_string (&val);
+ else
+ str = gst_value_serialize (&val);
+
+ g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
+ g_free (str);
+
+ g_value_unset (&val);
+}
+
+static void
+dump_collection (GstStreamCollection * collection)
+{
+ guint i;
+ const GstTagList *tags;
+ const GstCaps *caps;
+
+ for (i = 0; i < gst_stream_collection_get_size (collection); i++) {
+ GstStream *stream = gst_stream_collection_get_stream (collection, i);
+ g_print (" Stream %u type %s flags 0x%x\n", i,
+ gst_stream_type_get_name (gst_stream_get_stream_type (stream)),
+ gst_stream_get_stream_flags (stream));
+ g_print (" ID: %s\n", gst_stream_get_stream_id (stream));
+
+ caps = gst_stream_get_caps (stream);
+ if (caps) {
+ gchar *caps_str = gst_caps_to_string (caps);
+ g_print (" caps: %s\n", caps_str);
+ g_free (caps_str);
+ }
+
+ tags = gst_stream_get_tags (stream);
+ if (tags) {
+ g_print (" tags:\n");
+ gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (3));
+ }
+ }
+}
+
+static gboolean
+switch_streams (MyDataStruct * data)
+{
+ guint i, nb_streams;
+ gint nb_video = 0, nb_audio = 0, nb_text = 0;
+ GstStream *videos[256], *audios[256], *texts[256];
+ GList *streams = NULL;
+ GstEvent *ev;
+
+ g_print ("Switching Streams...\n");
+
+ /* Calculate the number of streams of each type */
+ nb_streams = gst_stream_collection_get_size (data->collection);
+ for (i = 0; i < nb_streams; i++) {
+ GstStream *stream = gst_stream_collection_get_stream (data->collection, i);
+ GstStreamType stype = gst_stream_get_stream_type (stream);
+ if (stype == GST_STREAM_TYPE_VIDEO) {
+ videos[nb_video] = stream;
+ nb_video += 1;
+ } else if (stype == GST_STREAM_TYPE_AUDIO) {
+ audios[nb_audio] = stream;
+ nb_audio += 1;
+ } else if (stype == GST_STREAM_TYPE_TEXT) {
+ texts[nb_text] = stream;
+ nb_text += 1;
+ }
+ }
+
+ if (nb_video) {
+ data->current_video = (data->current_video + 1) % nb_video;
+ streams =
+ g_list_append (streams,
+ (gchar *) gst_stream_get_stream_id (videos[data->current_video]));
+ g_print (" Selecting video channel #%d : %s\n", data->current_video,
+ gst_stream_get_stream_id (videos[data->current_video]));
+ }
+ if (nb_audio) {
+ data->current_audio = (data->current_audio + 1) % nb_audio;
+ streams =
+ g_list_append (streams,
+ (gchar *) gst_stream_get_stream_id (audios[data->current_audio]));
+ g_print (" Selecting audio channel #%d : %s\n", data->current_audio,
+ gst_stream_get_stream_id (audios[data->current_audio]));
+ }
+ if (nb_text) {
+ data->current_text = (data->current_text + 1) % nb_text;
+ streams =
+ g_list_append (streams,
+ (gchar *) gst_stream_get_stream_id (texts[data->current_text]));
+ g_print (" Selecting text channel #%d : %s\n", data->current_text,
+ gst_stream_get_stream_id (texts[data->current_text]));
+ }
+
+ ev = gst_event_new_select_streams (streams);
+ gst_element_send_event (data->pipeline, ev);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
+ GParamSpec * pspec, guint * val)
+{
+ g_print ("Got stream-notify from stream %s for %s (collection %p)\n",
+ stream->stream_id, pspec->name, collection);
+ if (g_str_equal (pspec->name, "caps")) {
+ GstCaps *caps = gst_stream_get_caps (stream);
+ gchar *caps_str = gst_caps_to_string (caps);
+ g_print (" New caps: %s\n", caps_str);
+ g_free (caps_str);
+ gst_caps_unref (caps);
+ }
+}
+
+static GstBusSyncReply
+_on_bus_message (GstBus * bus, GstMessage * message, MyDataStruct * data)
+{
+ GstObject *src = GST_MESSAGE_SRC (message);
+ switch (GST_MESSAGE_TYPE (message)) {
+ case GST_MESSAGE_ERROR:{
+ GError *err = NULL;
+ gchar *name = gst_object_get_path_string (GST_MESSAGE_SRC (message));
+ gst_message_parse_error (message, &err, NULL);
+
+ g_printerr ("ERROR: from element %s: %s\n", name, err->message);
+ g_error_free (err);
+ g_free (name);
+
+ g_printf ("Stopping\n");
+ g_main_loop_quit (data->mainloop);
+ break;
+ }
+ case GST_MESSAGE_EOS:
+ g_printf ("EOS ! Stopping \n");
+ g_main_loop_quit (data->mainloop);
+ break;
+ case GST_MESSAGE_STREAM_COLLECTION:
+ {
+ GstStreamCollection *collection = NULL;
+ gst_message_parse_stream_collection (message, &collection);
+ if (collection) {
+ g_printf ("Got a collection from %s:\n",
+ src ? GST_OBJECT_NAME (src) : "Unknown");
+ dump_collection (collection);
+ if (data->collection && data->notify_id) {
+ g_signal_handler_disconnect (data->collection, data->notify_id);
+ data->notify_id = 0;
+ }
+ gst_object_replace ((GstObject **) & data->collection,
+ (GstObject *) collection);
+ if (data->collection) {
+ data->notify_id =
+ g_signal_connect (data->collection, "stream-notify",
+ (GCallback) stream_notify_cb, data);
+ }
+ if (data->timeout_id == 0)
+ /* In 5s try to change streams */
+ data->timeout_id =
+ g_timeout_add_seconds (5, (GSourceFunc) switch_streams, data);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return GST_BUS_PASS;
+}
+
+static void
+decodebin_pad_added_cb (GstElement * dbin, GstPad * pad, MyDataStruct * data)
+{
+ gchar *pad_name = gst_pad_get_name (pad);
+ const gchar *sink_pad = NULL;
+
+ GST_DEBUG_OBJECT (pad, "New pad ! Link to playsink !");
+ if (!g_ascii_strncasecmp (pad_name, "video_", 6))
+ sink_pad = "video_sink";
+ else if (!g_ascii_strncasecmp (pad_name, "audio_", 6))
+ sink_pad = "audio_sink";
+ else if (!g_ascii_strncasecmp (pad_name, "text_", 5))
+ sink_pad = "text_sink";
+ else
+ GST_WARNING_OBJECT (pad, "non audio/video/text pad");
+
+ g_free (pad_name);
+
+ if (sink_pad) {
+ GstPad *playsink_pad;
+
+ playsink_pad = gst_element_get_request_pad (data->playsink, sink_pad);
+ if (playsink_pad)
+ gst_pad_link (pad, playsink_pad);
+ }
+}
+
+int
+main (int argc, gchar ** argv)
+{
+ GError *error = NULL;
+ GstBus *bus;
+ MyDataStruct *data;
+ int i;
+
+ gst_init (&argc, &argv);
+
+ data = g_new0 (MyDataStruct, 1);
+
+ if (argc < 2) {
+ g_print ("Usage: decodebin3 URI\n");
+ return 1;
+ }
+
+ data->pipeline = gst_pipeline_new ("pipeline");
+ data->decodebin = gst_element_factory_make ("decodebin3", NULL);
+
+ data->src =
+ gst_element_make_from_uri (GST_URI_SRC, argv[1], "source", &error);
+ if (error) {
+ g_printf ("pipeline could not be constructed: %s\n", error->message);
+ g_error_free (error);
+ return 1;
+ }
+ data->playsink = gst_element_factory_make ("playsink", NULL);
+
+#if 0
+ {
+ GstElement *sink = gst_element_factory_make ("fakesink", NULL);
+ g_object_set (sink, "sync", FALSE, NULL);
+ g_object_set (data->playsink, "video-sink", sink, NULL);
+
+ sink = gst_element_factory_make ("fakesink", NULL);
+ g_object_set (sink, "sync", FALSE, NULL);
+ g_object_set (data->playsink, "audio-sink", sink, NULL);
+ }
+#endif
+
+ gst_bin_add_many ((GstBin *) data->pipeline, data->src, data->decodebin,
+ data->playsink, NULL);
+ if (!gst_element_link (data->src, (GstElement *) data->decodebin)) {
+ g_printf ("Could not link source to demuxer\n");
+ return 1;
+ }
+
+ /* Handle other inputs if specified */
+ if (argc > 2) {
+ for (i = 2; i < argc; i++) {
+ GstElement *new_src =
+ gst_element_make_from_uri (GST_URI_SRC, argv[i], NULL, &error);
+ GstPad *src_pad, *sink_pad;
+ if (error) {
+ g_printf ("pipeline could not be constructed: %s\n", error->message);
+ g_error_free (error);
+ return 1;
+ }
+ data->other_src = g_list_append (data->other_src, new_src);
+ gst_bin_add ((GstBin *) data->pipeline, new_src);
+ src_pad = gst_element_get_static_pad (new_src, "src");
+ sink_pad = gst_element_get_request_pad (data->decodebin, "sink_%u");
+ if (gst_pad_link (src_pad, sink_pad) != GST_PAD_LINK_OK) {
+ g_printf ("Could not link new source to decodebin : %s\n", argv[i]);
+ return 1;
+ }
+ }
+ }
+
+
+ g_signal_connect (data->decodebin, "pad-added",
+ (GCallback) decodebin_pad_added_cb, data);
+ data->mainloop = g_main_loop_new (NULL, FALSE);
+
+ /* Put a bus handler */
+ bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline));
+ gst_bus_set_sync_handler (bus, (GstBusSyncHandler) _on_bus_message, data,
+ NULL);
+
+ /* Start pipeline */
+ gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
+ g_main_loop_run (data->mainloop);
+
+ gst_element_set_state (data->pipeline, GST_STATE_NULL);
+
+ gst_object_unref (data->pipeline);
+ gst_object_unref (bus);
+
+ return 0;
+}
--- /dev/null
+/* sample application for testing decodebin3 w/ playbin
+ *
+ * Copyright (C) 2015 Centricular Ltd
+ * @author: Edward Hervey <edward@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gprintf.h>
+#include <gst/gst.h>
+
+/* Global structure */
+
+typedef struct _MyDataStruct
+{
+ GMainLoop *mainloop;
+ GstElement *pipeline;
+ GstBus *bus;
+
+ /* Current collection */
+ GstStreamCollection *collection;
+ guint notify_id;
+
+ guint current_audio;
+ guint current_video;
+ guint current_text;
+
+ glong timeout_id;
+} MyDataStruct;
+
+static void
+print_tag_foreach (const GstTagList * tags, const gchar * tag,
+ gpointer user_data)
+{
+ GValue val = { 0, };
+ gchar *str;
+ gint depth = GPOINTER_TO_INT (user_data);
+
+ if (!gst_tag_list_copy_value (&val, tags, tag))
+ return;
+
+ if (G_VALUE_HOLDS_STRING (&val))
+ str = g_value_dup_string (&val);
+ else
+ str = gst_value_serialize (&val);
+
+ g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
+ g_free (str);
+
+ g_value_unset (&val);
+}
+
+static void
+dump_collection (GstStreamCollection * collection)
+{
+ guint i;
+ const GstTagList *tags;
+ const GstCaps *caps;
+
+ for (i = 0; i < gst_stream_collection_get_size (collection); i++) {
+ GstStream *stream = gst_stream_collection_get_stream (collection, i);
+ g_print (" Stream %u type %s flags 0x%x\n", i,
+ gst_stream_type_get_name (gst_stream_get_stream_type (stream)),
+ gst_stream_get_stream_flags (stream));
+ g_print (" ID: %s\n", gst_stream_get_stream_id (stream));
+
+ caps = gst_stream_get_caps (stream);
+ if (caps) {
+ gchar *caps_str = gst_caps_to_string (caps);
+ g_print (" caps: %s\n", caps_str);
+ g_free (caps_str);
+ }
+
+ tags = gst_stream_get_tags (stream);
+ if (tags) {
+ g_print (" tags:\n");
+ gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (3));
+ }
+ }
+}
+
+static gboolean
+switch_streams (MyDataStruct * data)
+{
+ guint i, nb_streams;
+ gint nb_video = 0, nb_audio = 0, nb_text = 0;
+ GstStream *videos[256], *audios[256], *texts[256];
+ GList *streams = NULL;
+ GstEvent *ev;
+
+ g_print ("Switching Streams...\n");
+
+ /* Calculate the number of streams of each type */
+ nb_streams = gst_stream_collection_get_size (data->collection);
+ for (i = 0; i < nb_streams; i++) {
+ GstStream *stream = gst_stream_collection_get_stream (data->collection, i);
+ GstStreamType stype = gst_stream_get_stream_type (stream);
+ if (stype == GST_STREAM_TYPE_VIDEO) {
+ videos[nb_video] = stream;
+ nb_video += 1;
+ } else if (stype == GST_STREAM_TYPE_AUDIO) {
+ audios[nb_audio] = stream;
+ nb_audio += 1;
+ } else if (stype == GST_STREAM_TYPE_TEXT) {
+ texts[nb_text] = stream;
+ nb_text += 1;
+ }
+ }
+
+ if (nb_video) {
+ data->current_video = (data->current_video + 1) % nb_video;
+ streams =
+ g_list_append (streams,
+ (gchar *) gst_stream_get_stream_id (videos[data->current_video]));
+ g_print (" Selecting video channel #%d : %s\n", data->current_video,
+ gst_stream_get_stream_id (videos[data->current_video]));
+ }
+ if (nb_audio) {
+ data->current_audio = (data->current_audio + 1) % nb_audio;
+ streams =
+ g_list_append (streams,
+ (gchar *) gst_stream_get_stream_id (audios[data->current_audio]));
+ g_print (" Selecting audio channel #%d : %s\n", data->current_audio,
+ gst_stream_get_stream_id (audios[data->current_audio]));
+ }
+ if (nb_text) {
+ data->current_text = (data->current_text + 1) % nb_text;
+ streams =
+ g_list_append (streams,
+ (gchar *) gst_stream_get_stream_id (texts[data->current_text]));
+ g_print (" Selecting text channel #%d : %s\n", data->current_text,
+ gst_stream_get_stream_id (texts[data->current_text]));
+ }
+
+ ev = gst_event_new_select_streams (streams);
+ gst_element_send_event (data->pipeline, ev);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
+ GParamSpec * pspec, guint * val)
+{
+ g_print ("Got stream-notify from stream %s for %s (collection %p)\n",
+ stream->stream_id, pspec->name, collection);
+ if (g_str_equal (pspec->name, "caps")) {
+ GstCaps *caps = gst_stream_get_caps (stream);
+ gchar *caps_str = gst_caps_to_string (caps);
+ g_print (" New caps: %s\n", caps_str);
+ g_free (caps_str);
+ gst_caps_unref (caps);
+ }
+}
+
+static GstBusSyncReply
+_on_bus_message (GstBus * bus, GstMessage * message, MyDataStruct * data)
+{
+ GstObject *src = GST_MESSAGE_SRC (message);
+ switch (GST_MESSAGE_TYPE (message)) {
+ case GST_MESSAGE_ERROR:{
+ GError *err = NULL;
+ gchar *name = gst_object_get_path_string (GST_MESSAGE_SRC (message));
+ gst_message_parse_error (message, &err, NULL);
+
+ g_printerr ("ERROR: from element %s: %s\n", name, err->message);
+ g_error_free (err);
+ g_free (name);
+
+ g_printf ("Stopping\n");
+ g_main_loop_quit (data->mainloop);
+ break;
+ }
+ case GST_MESSAGE_EOS:
+ g_printf ("EOS ! Stopping \n");
+ g_main_loop_quit (data->mainloop);
+ break;
+ case GST_MESSAGE_STREAM_COLLECTION:
+ {
+ GstStreamCollection *collection = NULL;
+ gst_message_parse_stream_collection (message, &collection);
+ if (collection) {
+ g_printf ("Got a collection from %s:\n",
+ src ? GST_OBJECT_NAME (src) : "Unknown");
+ dump_collection (collection);
+ if (data->collection && data->notify_id) {
+ g_signal_handler_disconnect (data->collection, data->notify_id);
+ data->notify_id = 0;
+ }
+ gst_object_replace ((GstObject **) & data->collection,
+ (GstObject *) collection);
+ if (data->collection) {
+ data->notify_id =
+ g_signal_connect (data->collection, "stream-notify",
+ (GCallback) stream_notify_cb, data);
+ }
+ if (data->timeout_id == 0)
+ /* In 5s try to change streams */
+ data->timeout_id =
+ g_timeout_add_seconds (5, (GSourceFunc) switch_streams, data);
+ }
+ break;
+ }
+ case GST_MESSAGE_STREAMS_SELECTED:
+ {
+ GstStreamCollection *collection = NULL;
+ gst_message_parse_streams_selected (message, &collection);
+ if (collection) {
+ guint i, len;
+ g_printf ("Got a STREAMS_SELECTED message from %s (seqnum:%"
+ G_GUINT32_FORMAT "):\n", src ? GST_OBJECT_NAME (src) : "unknown",
+ GST_MESSAGE_SEQNUM (message));
+ len = gst_message_streams_selected_get_size (message);
+ for (i = 0; i < len; i++) {
+ GstStream *stream =
+ gst_message_streams_selected_get_stream (message, i);
+ g_printf (" Stream #%d : %s\n", i,
+ gst_stream_get_stream_id (stream));
+ gst_object_unref (stream);
+ }
+ gst_object_unref (collection);
+ }
+ }
+ default:
+ break;
+ }
+
+ return GST_BUS_PASS;
+}
+
+static gchar *
+cmdline_to_uri (const gchar * arg)
+{
+ if (gst_uri_is_valid (arg))
+ return g_strdup (arg);
+
+ return gst_filename_to_uri (arg, NULL);
+}
+
+int
+main (int argc, gchar ** argv)
+{
+ GstBus *bus;
+ MyDataStruct *data;
+ gchar *uri;
+
+ gst_init (&argc, &argv);
+
+ data = g_new0 (MyDataStruct, 1);
+
+ uri = cmdline_to_uri (argv[1]);
+
+ if (argc < 2 || uri == NULL) {
+ g_print ("Usage: %s URI\n", argv[0]);
+ return 1;
+ }
+
+ data->pipeline = gst_element_factory_make ("playbin3", NULL);
+ if (data->pipeline == NULL) {
+ g_printerr ("Failed to create playbin element. Aborting");
+ return 1;
+ }
+
+ g_object_set (data->pipeline, "uri", uri, "auto-select-streams", FALSE, NULL);
+ g_free (uri);
+
+#if 0
+ {
+ GstElement *sink = gst_element_factory_make ("fakesink", NULL);
+ g_object_set (sink, "sync", FALSE, NULL);
+ g_object_set (data->pipeline, "video-sink", sink, NULL);
+
+ sink = gst_element_factory_make ("fakesink", NULL);
+ g_object_set (sink, "sync", FALSE, NULL);
+ g_object_set (data->pipeline, "audio-sink", sink, NULL);
+ }
+#endif
+
+ /* Handle other input if specified */
+ if (argc > 2) {
+ uri = cmdline_to_uri (argv[2]);
+ if (uri != NULL) {
+ g_object_set (data->pipeline, "suburi", uri, NULL);
+ g_free (uri);
+ } else {
+ g_warning ("Could not parse auxilliary file argument. Ignoring");
+ }
+ }
+
+ data->mainloop = g_main_loop_new (NULL, FALSE);
+
+ /* Put a bus handler */
+ bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline));
+ gst_bus_set_sync_handler (bus, (GstBusSyncHandler) _on_bus_message, data,
+ NULL);
+
+ /* Start pipeline */
+ gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
+ g_main_loop_run (data->mainloop);
+
+ gst_element_set_state (data->pipeline, GST_STATE_NULL);
+
+ gst_object_unref (data->pipeline);
+ gst_object_unref (bus);
+
+ return 0;
+}