From: Edward Hervey Date: Wed, 29 Jun 2016 16:14:51 +0000 (+0200) Subject: playback: New elements X-Git-Tag: 1.19.3~511^2~2794 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=d514e79beed868067e1f936bc0159a4a6a8564e4;p=platform%2Fupstream%2Fgstreamer.git playback: New elements With contributions from Jan Schmidt * decodebin3 and playbin3 have the same purpose as the decodebin and playbin elements, except make usage of more 1.x features and the new GstStream API. This allows them to be more memory/cpu efficient. * parsebin is a new element that demuxers/depayloads/parses an incoming stream and exposes elementary streams. It is used by decodebin3. It also automatically creates GstStream and GstStreamCollection for elements that don't natively create them and sends the corresponding events and messages * Any application using playbin can use playbin3 by setting the env variable USE_PLAYBIN3=1 without reconfiguration/recompilation. --- diff --git a/configure.ac b/configure.ac index 519aaca..cc6bb36 100644 --- a/configure.ac +++ b/configure.ac @@ -918,6 +918,7 @@ tests/check/Makefile 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 diff --git a/gst/playback/Makefile.am b/gst/playback/Makefile.am index 081b224..180bf5f 100644 --- a/gst/playback/Makefile.am +++ b/gst/playback/Makefile.am @@ -4,9 +4,14 @@ csp_cflags = -DCOLORSPACE=\"videoconvert\" 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 \ @@ -26,7 +31,10 @@ libgstplayback_la_LIBADD = \ $(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 \ diff --git a/gst/playback/gstdecodebin3-parse.c b/gst/playback/gstdecodebin3-parse.c new file mode 100644 index 0000000..0f1a17d --- /dev/null +++ b/gst/playback/gstdecodebin3-parse.c @@ -0,0 +1,551 @@ +/* GStreamer + * + * Copyright (C) <2015> Centricular Ltd + * @author: Edward Hervey + * @author: Jan Schmidt + * + * 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 +#include +#include +#include +#include + +#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) : + ""); + + /* 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; + } + } +} diff --git a/gst/playback/gstdecodebin3.c b/gst/playback/gstdecodebin3.c new file mode 100644 index 0000000..1cdcc83 --- /dev/null +++ b/gst/playback/gstdecodebin3.c @@ -0,0 +1,2438 @@ +/* GStreamer + * + * Copyright (C) <2015> Centricular Ltd + * @author: Edward Hervey + * @author: Jan Schmidt + * + * 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 +#include +#include +#include +#include + +#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 "); + + 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) : ""); + + 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); +} diff --git a/gst/playback/gstparsebin.c b/gst/playback/gstparsebin.c new file mode 100644 index 0000000..f67eda8 --- /dev/null +++ b/gst/playback/gstparsebin.c @@ -0,0 +1,4393 @@ +/* GStreamer + * Copyright (C) <2006> Edward Hervey + * Copyright (C) <2009> Sebastian Dröge + * Copyright (C) <2011> Hewlett-Packard Development Company, L.P. + * Author: Sebastian Dröge , Collabora Ltd. + * Copyright (C) <2013> Collabora Ltd. + * Author: Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 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 + +#include +#include +#include + +#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. + * + * + * Invocation of signal handlers stops after the first signal handler + * returns #FALSE. Signal handlers are invoked in the order they were + * connected in. + * + * + * 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. + * + * + * 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! + * + * + * 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. + * + * + * 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! + * + * + * 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. + * + * + * 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. + * + * + * 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 , " + "Sebastian Dröge "); + + 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 ab + */ +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); +} diff --git a/gst/playback/gstplayback.c b/gst/playback/gstplayback.c index 882a7ec..3c3efa5 100644 --- a/gst/playback/gstplayback.c +++ b/gst/playback/gstplayback.c @@ -46,13 +46,26 @@ plugin_init (GstPlugin * plugin) 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; } diff --git a/gst/playback/gstplayback.h b/gst/playback/gstplayback.h index b4309c7..26e3683 100644 --- a/gst/playback/gstplayback.h +++ b/gst/playback/gstplayback.h @@ -24,10 +24,15 @@ #include 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__ */ diff --git a/gst/playback/gstplaybin3.c b/gst/playback/gstplaybin3.c new file mode 100644 index 0000000..7a3f163 --- /dev/null +++ b/gst/playback/gstplaybin3.c @@ -0,0 +1,6079 @@ +/* GStreamer + * Copyright (C) <2007> Wim Taymans + * Copyright (C) <2011> Sebastian Dröge + * Copyright (C) <2013> Collabora Ltd. + * Author: Sebastian Dröge + * Copyright (C) <2015> Jan Schmidt + * + * 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 + * + * + * automatic file type recognition and based on that automatic + * selection and usage of the right audio/video/subtitle demuxers/decoders + * + * + * visualisations for audio files + * + * + * subtitle support for video files. Subtitles can be store in external + * files. + * + * + * stream selection between different video/audio/subtitles streams + * + * + * meta info (tag) extraction + * + * + * easy access to the last video sample + * + * + * buffering when playing streams over a network + * + * + * volume control with mute option + * + * + * + * + * Usage + * + * 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. + * + * + * + * Advanced Usage: specifying the audio and video sink + * + * 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!). + * + * + * + * Retrieving Tags and Other Meta Data + * + * 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. + * + * + * + * Buffering + * 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). + * + * + * Embedding the video window in your application + * 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. + * + * + * Specifying which CD/DVD device to use + * 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] + * + * + * Handling redirects + * + * 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. + * + * + * + * Examples + * |[ + * 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). + * + */ + +/* 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#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 "); + + 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); +} diff --git a/gst/playback/gsturidecodebin3.c b/gst/playback/gsturidecodebin3.c new file mode 100644 index 0000000..af857e6 --- /dev/null +++ b/gst/playback/gsturidecodebin3.c @@ -0,0 +1,2833 @@ +/* GStreamer + * Copyright (C) <2007> Wim Taymans + * + * 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 + +#include +#include +#include + +#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. + * + * + * Invocation of signal handlers stops after the first signal handler + * returns #FALSE. Signal handlers are invoked in the order they were + * connected in. + * + * + * 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. + * + * + * 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! + * + * + * 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. + * + * + * 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! + * + * + * 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. + * + * + * 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. + * + * + * 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 "); + + 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); +} diff --git a/gst/playback/gsturisourcebin.c b/gst/playback/gsturisourcebin.c new file mode 100644 index 0000000..c72e922 --- /dev/null +++ b/gst/playback/gsturisourcebin.c @@ -0,0 +1,2823 @@ +/* GStreamer + * Copyright (C) <2015> Jan Schmidt + * Copyright (C) <2007> Wim Taymans + * + * 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 + +#include +#include +#include + +#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. + * + * + * Invocation of signal handlers stops after the first signal handler + * returns #FALSE. Signal handlers are invoked in the order they were + * connected in. + * + * + * 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. + * + * + * 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! + * + * + * 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. + * + * + * 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! + * + * + * 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. + * + * + * 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. + * + * + * 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 "); + + 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); +} diff --git a/tests/examples/Makefile.am b/tests/examples/Makefile.am index 90171e9..330ea8f 100644 --- a/tests/examples/Makefile.am +++ b/tests/examples/Makefile.am @@ -2,8 +2,8 @@ if HAVE_GTK 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 diff --git a/tests/examples/decodebin_next/.gitignore b/tests/examples/decodebin_next/.gitignore new file mode 100644 index 0000000..12d704b --- /dev/null +++ b/tests/examples/decodebin_next/.gitignore @@ -0,0 +1,2 @@ +decodebin3 +playbin-test diff --git a/tests/examples/decodebin_next/Makefile.am b/tests/examples/decodebin_next/Makefile.am new file mode 100644 index 0000000..5270bec --- /dev/null +++ b/tests/examples/decodebin_next/Makefile.am @@ -0,0 +1,5 @@ +noinst_PROGRAMS = decodebin3 playbin-test + +LDADD = $(GST_LIBS) + +AM_CFLAGS = -I$(top_builddir)/gst-libs $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) diff --git a/tests/examples/decodebin_next/decodebin3.c b/tests/examples/decodebin_next/decodebin3.c new file mode 100644 index 0000000..2eb2a68 --- /dev/null +++ b/tests/examples/decodebin_next/decodebin3.c @@ -0,0 +1,353 @@ +/* sample application for testing decodebin3 + * + * Copyright (C) 2015 Centricular Ltd + * @author: Edward Hervey + * + * 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 +#include +#include +#include + +/* 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; +} diff --git a/tests/examples/decodebin_next/playbin-test.c b/tests/examples/decodebin_next/playbin-test.c new file mode 100644 index 0000000..d8e9a88 --- /dev/null +++ b/tests/examples/decodebin_next/playbin-test.c @@ -0,0 +1,326 @@ +/* sample application for testing decodebin3 w/ playbin + * + * Copyright (C) 2015 Centricular Ltd + * @author: Edward Hervey + * + * 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 +#include +#include +#include + +/* 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; +}