mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-19 16:21:17 +00:00
1afeef0e8b
When switching from a raw stream to an encoded stream we need to make sure the slot is unlinked, there is code in place for this but it wasn't triggered because the slot being reconfigured wasn't advertised as linked beforehand. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5126>
3716 lines
119 KiB
C
3716 lines
119 KiB
C
/* GStreamer
|
|
*
|
|
* Copyright (C) <2015> Centricular Ltd
|
|
* @author: Edward Hervey <edward@centricular.com>
|
|
* @author: Jan Schmidt <jan@centricular.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
#include <glib-object.h>
|
|
#include <glib/gprintf.h>
|
|
#include <gst/gst.h>
|
|
#include <gst/pbutils/pbutils.h>
|
|
|
|
#include "gstplaybackelements.h"
|
|
#include "gstplay-enum.h"
|
|
#include "gstrawcaps.h"
|
|
|
|
/**
|
|
* SECTION:element-decodebin3
|
|
* @title: decodebin3
|
|
*
|
|
* #GstBin that auto-magically constructs a decoding pipeline using available
|
|
* decoders and demuxers via auto-plugging. The output is raw audio, video
|
|
* or subtitle streams.
|
|
*
|
|
* decodebin3 differs from the previous decodebin (decodebin2) in important ways:
|
|
*
|
|
* * supports publication and selection of stream information via
|
|
* GstStreamCollection messages and #GST_EVENT_SELECT_STREAMS events.
|
|
*
|
|
* * dynamically switches stream connections internally, and
|
|
* reuses decoder elements when stream selections change, so that in
|
|
* the normal case it maintains 1 decoder of each type (video/audio/subtitle)
|
|
* and only creates new elements when streams change and an existing decoder
|
|
* is not capable of handling the new format.
|
|
*
|
|
* * supports multiple input pads for the parallel decoding of auxiliary streams
|
|
* not muxed with the primary stream.
|
|
*
|
|
* * does not handle network stream buffering. decodebin3 expects that network stream
|
|
* buffering is handled upstream, before data is passed to it.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Global design
|
|
*
|
|
* 1) From sink pad to elementary streams (GstParseBin or identity)
|
|
*
|
|
* Note : If the incoming streams are push-based-only and are compatible with
|
|
* either the output caps or a potential decoder, the usage of parsebin is
|
|
* replaced by a simple passthrough identity element.
|
|
*
|
|
* 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
|
|
|
|
#define CUSTOM_FINAL_EOS_QUARK _custom_final_eos_quark_get ()
|
|
#define CUSTOM_FINAL_EOS_QUARK_DATA "custom-final-eos"
|
|
static GQuark
|
|
_custom_final_eos_quark_get (void)
|
|
{
|
|
static gsize g_quark;
|
|
|
|
if (g_once_init_enter (&g_quark)) {
|
|
gsize quark =
|
|
(gsize) g_quark_from_static_string ("decodebin3-custom-final-eos");
|
|
g_once_init_leave (&g_quark, quark);
|
|
}
|
|
return g_quark;
|
|
}
|
|
|
|
typedef struct _GstDecodebin3 GstDecodebin3;
|
|
typedef struct _GstDecodebin3Class GstDecodebin3Class;
|
|
|
|
typedef struct _DecodebinInputStream DecodebinInputStream;
|
|
typedef struct _DecodebinInput DecodebinInput;
|
|
typedef struct _DecodebinOutputStream DecodebinOutputStream;
|
|
|
|
typedef struct
|
|
{
|
|
GstElement *element;
|
|
GstMessage *error; // Last error message seen for that element
|
|
GstMessage *latency; // Last latency message seen for that element
|
|
} CandidateDecoder;
|
|
|
|
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 : GST_GROUP_ID_INVALID) */
|
|
guint32 current_group_id;
|
|
/* End of variables protected by input_lock */
|
|
|
|
GstElement *multiqueue;
|
|
GstClockTime default_mq_min_interleave;
|
|
GstClockTime current_mq_min_interleave;
|
|
|
|
/* selection_lock protects access to following variables */
|
|
GMutex selection_lock;
|
|
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;
|
|
|
|
/* Active collection */
|
|
GstStreamCollection *collection;
|
|
/* 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 */
|
|
gboolean upstream_selected;
|
|
|
|
/* 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;
|
|
|
|
GList *candidate_decoders;
|
|
};
|
|
|
|
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 */
|
|
gboolean upstream_selected;
|
|
|
|
guint group_id;
|
|
|
|
/* Either parsebin or identity is used */
|
|
GstElement *parsebin;
|
|
GstElement *identity;
|
|
|
|
gulong pad_added_sigid;
|
|
gulong pad_removed_sigid;
|
|
gulong drained_sigid;
|
|
|
|
/* TRUE if the input got drained */
|
|
gboolean drained;
|
|
|
|
/* TEMPORARY HACK for knowing if upstream is already parsed and identity can
|
|
* be avoided */
|
|
gboolean input_is_parsed;
|
|
};
|
|
|
|
/* 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;
|
|
|
|
/* TRUE if EOS was pushed out by multiqueue */
|
|
gboolean is_drained;
|
|
|
|
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;
|
|
|
|
/* Reported decoder latency */
|
|
GstClockTime decoder_latency;
|
|
|
|
/* keyframe dropping probe */
|
|
gulong drop_probe_id;
|
|
};
|
|
|
|
/* properties */
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CAPS
|
|
};
|
|
|
|
/* signals */
|
|
enum
|
|
{
|
|
SIGNAL_SELECT_STREAM,
|
|
SIGNAL_ABOUT_TO_FINISH,
|
|
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);
|
|
#define _do_init \
|
|
GST_DEBUG_CATEGORY_INIT (decodebin3_debug, "decodebin3", 0, "decoder bin");\
|
|
playback_element_init (plugin);
|
|
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (decodebin3, "decodebin3", GST_RANK_NONE,
|
|
GST_TYPE_DECODEBIN3, _do_init);
|
|
|
|
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_finalize (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_release_pad (GstElement * element, GstPad * pad);
|
|
static void handle_stream_collection (GstDecodebin3 * dbin,
|
|
GstStreamCollection * collection, DecodebinInput * input);
|
|
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);
|
|
|
|
static void reset_input (GstDecodebin3 * dbin, DecodebinInput * input);
|
|
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 gboolean reconfigure_output_stream (DecodebinOutputStream * output,
|
|
MultiQueueSlot * slot, GstMessage ** msg);
|
|
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);
|
|
static void free_multiqueue_slot_async (GstDecodebin3 * dbin,
|
|
MultiQueueSlot * slot);
|
|
|
|
static GstStreamCollection *get_merged_collection (GstDecodebin3 * dbin);
|
|
static void update_requested_selection (GstDecodebin3 * dbin);
|
|
|
|
/* 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);
|
|
|
|
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->finalize = gst_decodebin3_finalize;
|
|
gobject_klass->set_property = gst_decodebin3_set_property;
|
|
gobject_klass->get_property = gst_decodebin3_get_property;
|
|
|
|
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));
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Note that the prefered way to select streams is to listen to
|
|
* GST_MESSAGE_STREAM_COLLECTION on the bus and send a
|
|
* GST_EVENT_SELECT_STREAMS with the streams the user wants.
|
|
*
|
|
* 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, NULL,
|
|
G_TYPE_INT, 2, GST_TYPE_STREAM_COLLECTION, GST_TYPE_STREAM);
|
|
|
|
/**
|
|
* GstDecodebin3::about-to-finish:
|
|
*
|
|
* This signal is emitted when the data for the selected URI is
|
|
* entirely buffered and it is safe to specify another URI.
|
|
*/
|
|
gst_decodebin3_signals[SIGNAL_ABOUT_TO_FINISH] =
|
|
g_signal_new ("about-to-finish", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
|
|
|
|
|
|
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);
|
|
element_class->release_pad = GST_DEBUG_FUNCPTR (gst_decodebin3_release_pad);
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&sink_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&request_sink_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&video_src_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&audio_src_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&text_src_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&src_template));
|
|
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"Decoder Bin 3", "Generic/Bin/Decoder",
|
|
"Autoplug and decode to raw media",
|
|
"Edward Hervey <edward@centricular.com>");
|
|
|
|
bin_klass->handle_message = gst_decodebin3_handle_message;
|
|
|
|
klass->select_stream = gst_decodebin3_select_stream;
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_init (GstDecodebin3 * dbin)
|
|
{
|
|
/* Create main input */
|
|
dbin->main_input = create_new_input (dbin, TRUE);
|
|
|
|
dbin->multiqueue = gst_element_factory_make ("multiqueue", NULL);
|
|
g_object_get (dbin->multiqueue, "min-interleave-time",
|
|
&dbin->default_mq_min_interleave, NULL);
|
|
dbin->current_mq_min_interleave = dbin->default_mq_min_interleave;
|
|
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 = GST_GROUP_ID_INVALID;
|
|
|
|
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);
|
|
|
|
GST_OBJECT_FLAG_SET (dbin, GST_BIN_FLAG_STREAMS_AWARE);
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_reset (GstDecodebin3 * dbin)
|
|
{
|
|
GList *tmp;
|
|
|
|
GST_DEBUG_OBJECT (dbin, "Resetting");
|
|
|
|
/* 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;
|
|
dbin->current_group_id = GST_GROUP_ID_INVALID;
|
|
|
|
/* Reset the inputs */
|
|
reset_input (dbin, dbin->main_input);
|
|
for (tmp = dbin->other_inputs; tmp; tmp = tmp->next) {
|
|
reset_input (dbin, tmp->data);
|
|
}
|
|
|
|
/* Reset multiqueue to default interleave */
|
|
g_object_set (dbin->multiqueue, "min-interleave-time",
|
|
dbin->default_mq_min_interleave, NULL);
|
|
dbin->current_mq_min_interleave = dbin->default_mq_min_interleave;
|
|
dbin->upstream_selected = FALSE;
|
|
|
|
g_list_free_full (dbin->requested_selection, g_free);
|
|
dbin->requested_selection = NULL;
|
|
|
|
g_list_free_full (dbin->active_selection, g_free);
|
|
dbin->active_selection = NULL;
|
|
|
|
g_list_free (dbin->to_activate);
|
|
dbin->to_activate = NULL;
|
|
|
|
g_list_free (dbin->pending_select_streams);
|
|
dbin->pending_select_streams = NULL;
|
|
|
|
dbin->selection_updated = FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_dispose (GObject * object)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) object;
|
|
GList *walk, *next;
|
|
|
|
gst_decodebin3_reset (dbin);
|
|
|
|
if (dbin->factories) {
|
|
gst_plugin_feature_list_free (dbin->factories);
|
|
dbin->factories = NULL;
|
|
}
|
|
if (dbin->decoder_factories) {
|
|
g_list_free (dbin->decoder_factories);
|
|
dbin->decoder_factories = NULL;
|
|
}
|
|
if (dbin->decodable_factories) {
|
|
g_list_free (dbin->decodable_factories);
|
|
dbin->decodable_factories = NULL;
|
|
}
|
|
|
|
gst_clear_object (&dbin->collection);
|
|
|
|
INPUT_LOCK (dbin);
|
|
if (dbin->main_input) {
|
|
free_input (dbin, dbin->main_input);
|
|
dbin->main_input = NULL;
|
|
}
|
|
|
|
for (walk = dbin->other_inputs; walk; walk = next) {
|
|
DecodebinInput *input = walk->data;
|
|
|
|
next = g_list_next (walk);
|
|
|
|
free_input (dbin, input);
|
|
dbin->other_inputs = g_list_delete_link (dbin->other_inputs, walk);
|
|
}
|
|
INPUT_UNLOCK (dbin);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_finalize (GObject * object)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) object;
|
|
|
|
g_mutex_clear (&dbin->factories_lock);
|
|
g_mutex_clear (&dbin->selection_lock);
|
|
g_mutex_clear (&dbin->input_lock);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) object;
|
|
|
|
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;
|
|
|
|
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 != GST_GROUP_ID_INVALID)
|
|
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) {
|
|
/* The input is being re-used with a different incoming stream, we do want
|
|
* to change/unify to this new group-id */
|
|
if (dbin->current_group_id == GST_GROUP_ID_INVALID) {
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Setting current group id to %" G_GUINT32_FORMAT, *group_id);
|
|
dbin->current_group_id = *group_id;
|
|
} else {
|
|
GST_DEBUG_OBJECT (dbin, "Returning global group id %" G_GUINT32_FORMAT,
|
|
dbin->current_group_id);
|
|
}
|
|
*group_id = dbin->current_group_id;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
parsebin_drained_cb (GstElement * parsebin, DecodebinInput * input)
|
|
{
|
|
GstDecodebin3 *dbin = input->dbin;
|
|
gboolean all_drained;
|
|
GList *tmp;
|
|
|
|
GST_INFO_OBJECT (dbin, "input %p drained", input);
|
|
input->drained = TRUE;
|
|
|
|
all_drained = dbin->main_input->drained;
|
|
for (tmp = dbin->other_inputs; tmp; tmp = tmp->next) {
|
|
DecodebinInput *data = (DecodebinInput *) tmp->data;
|
|
|
|
all_drained &= data->drained;
|
|
}
|
|
|
|
if (all_drained) {
|
|
GST_INFO_OBJECT (dbin, "All inputs drained. Posting about-to-finish");
|
|
g_signal_emit (dbin, gst_decodebin3_signals[SIGNAL_ABOUT_TO_FINISH], 0,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
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 ** 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 gboolean
|
|
decode_pad_set_target (GstGhostPad * pad, GstPad * target)
|
|
{
|
|
gboolean res = gst_ghost_pad_set_target (pad, target);
|
|
if (!res)
|
|
return res;
|
|
|
|
if (target == NULL)
|
|
gst_pad_sticky_events_foreach (GST_PAD_CAST (pad), clear_sticky_events,
|
|
NULL);
|
|
else
|
|
gst_pad_sticky_events_foreach (target, copy_sticky_events, pad);
|
|
|
|
return res;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
gboolean ret;
|
|
GstPad *peer;
|
|
} SendStickyEventsData;
|
|
|
|
static gboolean
|
|
send_sticky_event (GstPad * pad, GstEvent ** event, gpointer user_data)
|
|
{
|
|
SendStickyEventsData *data = user_data;
|
|
|
|
data->ret &= gst_pad_send_event (data->peer, gst_event_ref (*event));
|
|
|
|
return data->ret;
|
|
}
|
|
|
|
static gboolean
|
|
send_sticky_events (GstDecodebin3 * dbin, 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 CandidateDecoder *
|
|
add_candidate_decoder (GstDecodebin3 * dbin, GstElement * element)
|
|
{
|
|
GST_OBJECT_LOCK (dbin);
|
|
CandidateDecoder *candidate;
|
|
candidate = g_new0 (CandidateDecoder, 1);
|
|
candidate->element = element;
|
|
dbin->candidate_decoders =
|
|
g_list_prepend (dbin->candidate_decoders, candidate);
|
|
GST_OBJECT_UNLOCK (dbin);
|
|
return candidate;
|
|
}
|
|
|
|
static void
|
|
remove_candidate_decoder (GstDecodebin3 * dbin, CandidateDecoder * candidate)
|
|
{
|
|
GST_OBJECT_LOCK (dbin);
|
|
dbin->candidate_decoders =
|
|
g_list_remove (dbin->candidate_decoders, candidate);
|
|
if (candidate->error)
|
|
gst_message_unref (candidate->error);
|
|
g_free (candidate);
|
|
GST_OBJECT_UNLOCK (dbin);
|
|
}
|
|
|
|
/* 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);
|
|
input->drained_sigid =
|
|
g_signal_connect (input->parsebin, "drained",
|
|
(GCallback) parsebin_drained_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)) {
|
|
/* The state lock is taken so that we ensure we are the one (de)activating
|
|
* parsebin. We need to do this to ensure any activation taking place in
|
|
* parsebin (including by elements doing upstream activation) are done
|
|
* within the same thread. */
|
|
GST_STATE_LOCK (input->parsebin);
|
|
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);
|
|
GST_STATE_UNLOCK (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;
|
|
GstQuery *query;
|
|
gboolean pull_mode = FALSE;
|
|
gboolean has_caps = TRUE;
|
|
GstPadLinkReturn res = GST_PAD_LINK_OK;
|
|
DecodebinInput *input = g_object_get_data (G_OBJECT (pad), "decodebin.input");
|
|
|
|
g_return_val_if_fail (input, GST_PAD_LINK_REFUSED);
|
|
|
|
GST_LOG_OBJECT (parent, "Got link on input pad %" GST_PTR_FORMAT, pad);
|
|
|
|
query = gst_query_new_scheduling ();
|
|
if (gst_pad_query (peer, query)
|
|
&& gst_query_has_scheduling_mode_with_flags (query, GST_PAD_MODE_PULL,
|
|
GST_SCHEDULING_FLAG_SEEKABLE))
|
|
pull_mode = TRUE;
|
|
gst_query_unref (query);
|
|
|
|
GST_DEBUG_OBJECT (dbin, "Upstream can do pull-based : %d", pull_mode);
|
|
|
|
if (!pull_mode) {
|
|
/* If push-based, query if it will provide some caps */
|
|
query = gst_query_new_caps (NULL);
|
|
if (gst_pad_query (peer, query)) {
|
|
GstCaps *rescaps = NULL;
|
|
gst_query_parse_caps_result (query, &rescaps);
|
|
if (!rescaps || gst_caps_is_any (rescaps) || gst_caps_is_empty (rescaps)) {
|
|
GST_DEBUG_OBJECT (dbin, "Upstream can't provide caps");
|
|
has_caps = FALSE;
|
|
}
|
|
}
|
|
gst_query_unref (query);
|
|
}
|
|
|
|
/* If upstream *can* do pull-based OR it doesn't have any caps, we always use
|
|
* a parsebin. If not, we will delay that decision to a later stage
|
|
* (caps/stream/collection event processing) to figure out if one is really
|
|
* needed or whether an identity element will be enough */
|
|
INPUT_LOCK (dbin);
|
|
if (pull_mode || !has_caps) {
|
|
if (!ensure_input_parsebin (dbin, input))
|
|
res = GST_PAD_LINK_REFUSED;
|
|
else if (input->identity) {
|
|
GST_ERROR_OBJECT (dbin,
|
|
"Can't reconfigure input from push-based to pull-based");
|
|
res = GST_PAD_LINK_REFUSED;
|
|
}
|
|
}
|
|
|
|
/* Clear stream-collection corresponding to current INPUT. We do not
|
|
* recalculate the global one yet, it will be done when at least one
|
|
* collection is received/computed for this input.
|
|
*/
|
|
if (input->collection) {
|
|
GST_DEBUG_OBJECT (pad, "Clearing input collection");
|
|
gst_object_unref (input->collection);
|
|
input->collection = NULL;
|
|
}
|
|
|
|
INPUT_UNLOCK (dbin);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Drop duration query during _input_pad_unlink */
|
|
static GstPadProbeReturn
|
|
query_duration_drop_probe (GstPad * pad, GstPadProbeInfo * info,
|
|
DecodebinInput * input)
|
|
{
|
|
GstPadProbeReturn ret = GST_PAD_PROBE_OK;
|
|
|
|
if (GST_IS_QUERY (GST_PAD_PROBE_INFO_DATA (info))) {
|
|
GstQuery *query = GST_PAD_PROBE_INFO_QUERY (info);
|
|
if (GST_QUERY_TYPE (query) == GST_QUERY_DURATION) {
|
|
GST_LOG_OBJECT (pad, "stop forwarding query duration");
|
|
ret = GST_PAD_PROBE_HANDLED;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* CALL with INPUT LOCK */
|
|
static void
|
|
recalculate_group_id (GstDecodebin3 * dbin)
|
|
{
|
|
guint32 common_group_id;
|
|
GList *iter;
|
|
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"recalculating, current global group_id: %" G_GUINT32_FORMAT,
|
|
dbin->current_group_id);
|
|
|
|
common_group_id = dbin->main_input->group_id;
|
|
|
|
for (iter = dbin->other_inputs; iter; iter = iter->next) {
|
|
DecodebinInput *input = iter->data;
|
|
|
|
if (input->group_id != common_group_id) {
|
|
if (common_group_id != GST_GROUP_ID_INVALID)
|
|
return;
|
|
|
|
common_group_id = input->group_id;
|
|
}
|
|
}
|
|
|
|
if (common_group_id == dbin->current_group_id) {
|
|
GST_DEBUG_OBJECT (dbin, "Global group_id hasn't changed");
|
|
} else {
|
|
GST_DEBUG_OBJECT (dbin, "Updating global group_id to %" G_GUINT32_FORMAT,
|
|
common_group_id);
|
|
dbin->current_group_id = common_group_id;
|
|
}
|
|
}
|
|
|
|
/* CALL with INPUT LOCK */
|
|
static void
|
|
reset_input_parsebin (GstDecodebin3 * dbin, DecodebinInput * input)
|
|
{
|
|
GList *iter;
|
|
|
|
if (input->parsebin == NULL)
|
|
return;
|
|
|
|
GST_DEBUG_OBJECT (dbin, "Resetting %" GST_PTR_FORMAT, input->parsebin);
|
|
|
|
GST_STATE_LOCK (dbin);
|
|
gst_element_set_state (input->parsebin, GST_STATE_NULL);
|
|
input->drained = FALSE;
|
|
input->group_id = GST_GROUP_ID_INVALID;
|
|
recalculate_group_id (dbin);
|
|
for (iter = dbin->input_streams; iter; iter = iter->next) {
|
|
DecodebinInputStream *istream = iter->data;
|
|
if (istream->input == input)
|
|
istream->saw_eos = TRUE;
|
|
}
|
|
gst_element_sync_state_with_parent (input->parsebin);
|
|
GST_STATE_UNLOCK (dbin);
|
|
}
|
|
|
|
|
|
static void
|
|
gst_decodebin3_input_pad_unlink (GstPad * pad, GstPad * peer,
|
|
DecodebinInput * input)
|
|
{
|
|
GstDecodebin3 *dbin = input->dbin;
|
|
|
|
g_return_if_fail (input);
|
|
|
|
GST_LOG_OBJECT (dbin, "Got unlink on input pad %" GST_PTR_FORMAT, pad);
|
|
|
|
INPUT_LOCK (dbin);
|
|
|
|
if (input->parsebin && GST_PAD_MODE (pad) == GST_PAD_MODE_PULL) {
|
|
GST_DEBUG_OBJECT (dbin, "Resetting parsebin since it's pull-based");
|
|
reset_input_parsebin (dbin, input);
|
|
}
|
|
/* In all cases we will be receiving new stream-start and data */
|
|
input->group_id = GST_GROUP_ID_INVALID;
|
|
input->drained = FALSE;
|
|
recalculate_group_id (dbin);
|
|
|
|
INPUT_UNLOCK (dbin);
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_release_pad (GstElement * element, GstPad * pad)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) element;
|
|
DecodebinInput *input = g_object_get_data (G_OBJECT (pad), "decodebin.input");
|
|
GstStreamCollection *collection = NULL;
|
|
gulong probe_id = 0;
|
|
GstMessage *msg;
|
|
|
|
g_return_if_fail (input);
|
|
GST_LOG_OBJECT (dbin, "Releasing pad %" GST_PTR_FORMAT, pad);
|
|
|
|
INPUT_LOCK (dbin);
|
|
|
|
/* Clear stream-collection corresponding to current INPUT and post new
|
|
* stream-collection message, if needed */
|
|
if (input->collection) {
|
|
gst_object_unref (input->collection);
|
|
input->collection = NULL;
|
|
}
|
|
|
|
SELECTION_LOCK (dbin);
|
|
collection = get_merged_collection (dbin);
|
|
if (!collection) {
|
|
SELECTION_UNLOCK (dbin);
|
|
goto beach;
|
|
}
|
|
if (collection == dbin->collection) {
|
|
SELECTION_UNLOCK (dbin);
|
|
gst_object_unref (collection);
|
|
goto beach;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (dbin, "Update Stream Collection");
|
|
|
|
if (dbin->collection)
|
|
gst_object_unref (dbin->collection);
|
|
dbin->collection = collection;
|
|
dbin->select_streams_seqnum = GST_SEQNUM_INVALID;
|
|
|
|
msg =
|
|
gst_message_new_stream_collection ((GstObject *) dbin, dbin->collection);
|
|
|
|
if (input->parsebin)
|
|
/* Drop duration queries that the application might be doing while this message is posted */
|
|
probe_id = gst_pad_add_probe (input->parsebin_sink,
|
|
GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
|
|
(GstPadProbeCallback) query_duration_drop_probe, input, NULL);
|
|
|
|
SELECTION_UNLOCK (dbin);
|
|
gst_element_post_message (GST_ELEMENT_CAST (dbin), msg);
|
|
update_requested_selection (dbin);
|
|
|
|
if (input->parsebin) {
|
|
gst_pad_remove_probe (input->parsebin_sink, probe_id);
|
|
}
|
|
|
|
beach:
|
|
if (!input->is_main) {
|
|
dbin->other_inputs = g_list_remove (dbin->other_inputs, input);
|
|
free_input (dbin, input);
|
|
} else
|
|
reset_input (dbin, input);
|
|
|
|
INPUT_UNLOCK (dbin);
|
|
return;
|
|
}
|
|
|
|
/* Call with INPUT LOCK */
|
|
static void
|
|
reset_input (GstDecodebin3 * dbin, DecodebinInput * input)
|
|
{
|
|
GST_LOG_OBJECT (dbin, "Resetting input %p", input);
|
|
|
|
gst_ghost_pad_set_target (GST_GHOST_PAD (input->ghost_sink), NULL);
|
|
|
|
if (input->parsebin) {
|
|
g_signal_handler_disconnect (input->parsebin, input->pad_removed_sigid);
|
|
g_signal_handler_disconnect (input->parsebin, input->pad_added_sigid);
|
|
g_signal_handler_disconnect (input->parsebin, input->drained_sigid);
|
|
gst_element_set_state (input->parsebin, GST_STATE_NULL);
|
|
gst_bin_remove (GST_BIN (dbin), input->parsebin);
|
|
gst_clear_object (&input->parsebin);
|
|
gst_clear_object (&input->parsebin_sink);
|
|
}
|
|
if (input->identity) {
|
|
GstPad *idpad = gst_element_get_static_pad (input->identity, "src");
|
|
DecodebinInputStream *stream = find_input_stream_for_pad (dbin, idpad);
|
|
gst_object_unref (idpad);
|
|
remove_input_stream (dbin, stream);
|
|
gst_element_set_state (input->identity, GST_STATE_NULL);
|
|
gst_bin_remove (GST_BIN (dbin), input->identity);
|
|
gst_clear_object (&input->identity);
|
|
}
|
|
if (input->collection)
|
|
gst_clear_object (&input->collection);
|
|
|
|
input->group_id = GST_GROUP_ID_INVALID;
|
|
}
|
|
|
|
/* Call with INPUT LOCK */
|
|
static void
|
|
free_input (GstDecodebin3 * dbin, DecodebinInput * input)
|
|
{
|
|
reset_input (dbin, input);
|
|
|
|
GST_LOG_OBJECT (dbin, "Freeing input %p", input);
|
|
|
|
INPUT_UNLOCK (dbin);
|
|
gst_element_remove_pad (GST_ELEMENT (dbin), input->ghost_sink);
|
|
INPUT_LOCK (dbin);
|
|
|
|
g_free (input);
|
|
}
|
|
|
|
static gboolean
|
|
sink_query_function (GstPad * sinkpad, GstDecodebin3 * dbin, GstQuery * query)
|
|
{
|
|
DecodebinInput *input =
|
|
g_object_get_data (G_OBJECT (sinkpad), "decodebin.input");
|
|
|
|
g_return_val_if_fail (input, FALSE);
|
|
|
|
GST_DEBUG_OBJECT (sinkpad, "query %" GST_PTR_FORMAT, query);
|
|
|
|
/* We accept any caps, since we will reconfigure ourself internally if the new
|
|
* stream is incompatible */
|
|
if (GST_QUERY_TYPE (query) == GST_QUERY_ACCEPT_CAPS) {
|
|
GST_DEBUG_OBJECT (dbin, "Accepting ACCEPT_CAPS query");
|
|
gst_query_set_accept_caps_result (query, TRUE);
|
|
return TRUE;
|
|
}
|
|
return gst_pad_query_default (sinkpad, GST_OBJECT (dbin), query);
|
|
}
|
|
|
|
static gboolean
|
|
is_parsebin_required_for_input (GstDecodebin3 * dbin, DecodebinInput * input,
|
|
GstCaps * newcaps, GstPad * sinkpad)
|
|
{
|
|
gboolean parsebin_needed = TRUE;
|
|
GstStream *stream;
|
|
|
|
stream = gst_pad_get_stream (sinkpad);
|
|
|
|
if (stream == NULL) {
|
|
/* If upstream didn't provide a `GstStream` we will need to create a
|
|
* parsebin to handle that stream */
|
|
GST_DEBUG_OBJECT (sinkpad,
|
|
"Need to create parsebin since upstream doesn't provide GstStream");
|
|
} else if (gst_caps_can_intersect (newcaps, dbin->caps)) {
|
|
/* If the incoming caps match decodebin3 output, no processing is needed */
|
|
GST_FIXME_OBJECT (sinkpad, "parsebin not needed (matches output caps) !");
|
|
parsebin_needed = FALSE;
|
|
} else if (input->input_is_parsed) {
|
|
GST_DEBUG_OBJECT (sinkpad, "input is parsed, no parsebin needed");
|
|
parsebin_needed = FALSE;
|
|
} else {
|
|
GList *decoder_list;
|
|
/* If the incoming caps are compatible with a decoder, we don't need to
|
|
* process it before */
|
|
g_mutex_lock (&dbin->factories_lock);
|
|
gst_decode_bin_update_factories_list (dbin);
|
|
decoder_list =
|
|
gst_element_factory_list_filter (dbin->decoder_factories, newcaps,
|
|
GST_PAD_SINK, TRUE);
|
|
g_mutex_unlock (&dbin->factories_lock);
|
|
if (decoder_list) {
|
|
GST_FIXME_OBJECT (sinkpad, "parsebin not needed (available decoders) !");
|
|
gst_plugin_feature_list_free (decoder_list);
|
|
parsebin_needed = FALSE;
|
|
}
|
|
}
|
|
if (stream)
|
|
gst_object_unref (stream);
|
|
|
|
return parsebin_needed;
|
|
}
|
|
|
|
static void
|
|
setup_identify_for_input (GstDecodebin3 * dbin, DecodebinInput * input,
|
|
GstPad * sinkpad)
|
|
{
|
|
GstPad *idsrc, *idsink;
|
|
DecodebinInputStream *inputstream;
|
|
|
|
GST_DEBUG_OBJECT (sinkpad, "Adding identity for new input stream");
|
|
|
|
input->identity = gst_element_factory_make ("identity", NULL);
|
|
/* We drop allocation queries due to our usage of multiqueue just
|
|
* afterwards. It is just too dangerous.
|
|
*
|
|
* If application users want to have optimal raw source <=> sink allocations
|
|
* they should not use decodebin3
|
|
*/
|
|
g_object_set (input->identity, "drop-allocation", TRUE, NULL);
|
|
input->identity = gst_object_ref (input->identity);
|
|
idsink = gst_element_get_static_pad (input->identity, "sink");
|
|
idsrc = gst_element_get_static_pad (input->identity, "src");
|
|
gst_bin_add (GST_BIN (dbin), input->identity);
|
|
|
|
SELECTION_LOCK (dbin);
|
|
inputstream = create_input_stream (dbin, idsrc, input);
|
|
/* Forward any existing GstStream directly on the input stream */
|
|
inputstream->active_stream = gst_pad_get_stream (sinkpad);
|
|
SELECTION_UNLOCK (dbin);
|
|
|
|
gst_object_unref (idsrc);
|
|
gst_object_unref (idsink);
|
|
gst_ghost_pad_set_target (GST_GHOST_PAD (input->ghost_sink), idsink);
|
|
gst_element_sync_state_with_parent (input->identity);
|
|
}
|
|
|
|
static gboolean
|
|
sink_event_function (GstPad * sinkpad, GstDecodebin3 * dbin, GstEvent * event)
|
|
{
|
|
DecodebinInput *input =
|
|
g_object_get_data (G_OBJECT (sinkpad), "decodebin.input");
|
|
|
|
g_return_val_if_fail (input, FALSE);
|
|
|
|
GST_DEBUG_OBJECT (sinkpad, "event %" GST_PTR_FORMAT, event);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_STREAM_START:
|
|
{
|
|
GstQuery *q = gst_query_new_selectable ();
|
|
const GstStructure *s = gst_event_get_structure (event);
|
|
|
|
/* Query whether upstream can handle stream selection or not */
|
|
if (gst_pad_peer_query (sinkpad, q)) {
|
|
gst_query_parse_selectable (q, &input->upstream_selected);
|
|
GST_DEBUG_OBJECT (sinkpad, "Upstream is selectable : %d",
|
|
input->upstream_selected);
|
|
} else {
|
|
input->upstream_selected = FALSE;
|
|
GST_DEBUG_OBJECT (sinkpad, "Upstream does not handle SELECTABLE query");
|
|
}
|
|
gst_query_unref (q);
|
|
|
|
/* FIXME : We force `decodebin3` to upstream selection mode if *any* of the
|
|
inputs is. This means things might break if there's a mix */
|
|
if (input->upstream_selected)
|
|
dbin->upstream_selected = TRUE;
|
|
|
|
input->input_is_parsed = s
|
|
&& gst_structure_has_field (s, "urisourcebin-parsed-data");
|
|
|
|
/* Make sure group ids will be recalculated */
|
|
input->group_id = GST_GROUP_ID_INVALID;
|
|
INPUT_LOCK (dbin);
|
|
recalculate_group_id (dbin);
|
|
INPUT_UNLOCK (dbin);
|
|
break;
|
|
}
|
|
case GST_EVENT_STREAM_COLLECTION:
|
|
{
|
|
GstStreamCollection *collection = NULL;
|
|
|
|
gst_event_parse_stream_collection (event, &collection);
|
|
if (collection) {
|
|
INPUT_LOCK (dbin);
|
|
handle_stream_collection (dbin, collection, input);
|
|
gst_object_unref (collection);
|
|
INPUT_UNLOCK (dbin);
|
|
SELECTION_LOCK (dbin);
|
|
/* Post the (potentially) updated collection */
|
|
if (dbin->collection) {
|
|
GstMessage *msg;
|
|
msg =
|
|
gst_message_new_stream_collection ((GstObject *) dbin,
|
|
dbin->collection);
|
|
SELECTION_UNLOCK (dbin);
|
|
gst_element_post_message (GST_ELEMENT_CAST (dbin), msg);
|
|
update_requested_selection (dbin);
|
|
} else
|
|
SELECTION_UNLOCK (dbin);
|
|
}
|
|
|
|
/* If we are waiting to create an identity passthrough, do it now */
|
|
if (!input->parsebin && !input->identity)
|
|
setup_identify_for_input (dbin, input, sinkpad);
|
|
break;
|
|
}
|
|
case GST_EVENT_CAPS:
|
|
{
|
|
GstCaps *newcaps = NULL;
|
|
|
|
gst_event_parse_caps (event, &newcaps);
|
|
if (!newcaps)
|
|
break;
|
|
GST_DEBUG_OBJECT (sinkpad, "new caps %" GST_PTR_FORMAT, newcaps);
|
|
|
|
/* No parsebin or identity present, check if we can avoid creating one */
|
|
if (!input->parsebin && !input->identity) {
|
|
if (is_parsebin_required_for_input (dbin, input, newcaps, sinkpad)) {
|
|
GST_DEBUG_OBJECT (sinkpad, "parsebin is required for input");
|
|
ensure_input_parsebin (dbin, input);
|
|
break;
|
|
}
|
|
GST_DEBUG_OBJECT (sinkpad,
|
|
"parsebin not required. Will create identity passthrough element once we get the collection");
|
|
break;
|
|
}
|
|
|
|
if (input->identity) {
|
|
if (is_parsebin_required_for_input (dbin, input, newcaps, sinkpad)) {
|
|
GST_ERROR_OBJECT (sinkpad,
|
|
"Switching from passthrough to parsebin on inputs is not supported !");
|
|
gst_event_unref (event);
|
|
return FALSE;
|
|
}
|
|
/* Nothing else to do here */
|
|
break;
|
|
}
|
|
|
|
/* Check if the parsebin present can handle the new caps */
|
|
g_assert (input->parsebin);
|
|
GST_DEBUG_OBJECT (sinkpad,
|
|
"New caps, checking if they are compatible with existing parsebin");
|
|
if (!gst_pad_query_accept_caps (input->parsebin_sink, newcaps)) {
|
|
GST_DEBUG_OBJECT (sinkpad,
|
|
"Parsebin doesn't accept the new caps %" GST_PTR_FORMAT, newcaps);
|
|
/* Reset parsebin so that it reconfigures itself for the new stream format */
|
|
INPUT_LOCK (dbin);
|
|
reset_input_parsebin (dbin, input);
|
|
INPUT_UNLOCK (dbin);
|
|
} else {
|
|
GST_DEBUG_OBJECT (sinkpad, "Parsebin accepts new caps");
|
|
}
|
|
break;
|
|
}
|
|
case GST_EVENT_SEGMENT:
|
|
{
|
|
const GstSegment *segment = NULL;
|
|
gst_event_parse_segment (event, &segment);
|
|
|
|
/* All data reaching multiqueue must be in time format. If it's not, we
|
|
* need to use a parsebin on the incoming stream.
|
|
*/
|
|
if (segment && segment->format != GST_FORMAT_TIME && !input->parsebin) {
|
|
GST_DEBUG_OBJECT (sinkpad,
|
|
"Got a non-time segment, forcing parsebin handling");
|
|
ensure_input_parsebin (dbin, input);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Chain to parent function */
|
|
return gst_pad_event_default (sinkpad, GST_OBJECT (dbin), event);
|
|
}
|
|
|
|
/* 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 = GST_GROUP_ID_INVALID;
|
|
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);
|
|
}
|
|
input->upstream_selected = FALSE;
|
|
g_object_set_data (G_OBJECT (input->ghost_sink), "decodebin.input", input);
|
|
gst_pad_set_event_function (input->ghost_sink,
|
|
(GstPadEventFunction) sink_event_function);
|
|
gst_pad_set_query_function (input->ghost_sink,
|
|
(GstPadQueryFunction) sink_query_function);
|
|
gst_pad_set_link_function (input->ghost_sink, gst_decodebin3_input_pad_link);
|
|
g_signal_connect (input->ghost_sink, "unlinked",
|
|
(GCallback) gst_decodebin3_input_pad_unlink, input);
|
|
|
|
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 = create_new_input (dbin, FALSE);
|
|
if (input) {
|
|
INPUT_LOCK (dbin);
|
|
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 const gchar *
|
|
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) {
|
|
const gchar *osid = (gchar *) tmp->data;
|
|
if (!g_strcmp0 (sid, osid))
|
|
return osid;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GList *
|
|
remove_from_list (GList * list, const gchar * sid)
|
|
{
|
|
GList *tmp;
|
|
|
|
for (tmp = list; tmp; tmp = tmp->next) {
|
|
gchar *osid = tmp->data;
|
|
if (!g_strcmp0 (sid, osid)) {
|
|
g_free (osid);
|
|
return g_list_delete_link (list, tmp);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
static gboolean
|
|
stream_list_equal (GList * lista, GList * listb)
|
|
{
|
|
GList *tmp;
|
|
|
|
if (g_list_length (lista) != g_list_length (listb))
|
|
return FALSE;
|
|
|
|
for (tmp = lista; tmp; tmp = tmp->next) {
|
|
gchar *osid = tmp->data;
|
|
if (!stream_in_list (listb, osid))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
update_requested_selection (GstDecodebin3 * dbin)
|
|
{
|
|
guint i, nb;
|
|
GList *tmp = NULL;
|
|
gboolean all_user_selected = TRUE;
|
|
GstStreamType used_types = 0;
|
|
GstStreamCollection *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;
|
|
}
|
|
|
|
collection = dbin->collection;
|
|
if (G_UNLIKELY (collection == NULL)) {
|
|
GST_DEBUG_OBJECT (dbin, "No current GstStreamCollection");
|
|
goto beach;
|
|
}
|
|
nb = gst_stream_collection_get_size (collection);
|
|
|
|
/* 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)
|
|
all_user_selected = FALSE;
|
|
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 the user didn't explicitly selected all streams, match one stream of each type */
|
|
if (!all_user_selected && dbin->select_streams_seqnum == GST_SEQNUM_INVALID) {
|
|
for (i = 0; i < nb; i++) {
|
|
GstStream *stream = gst_stream_collection_get_stream (collection, i);
|
|
GstStreamType curtype = gst_stream_get_stream_type (stream);
|
|
if (curtype != GST_STREAM_TYPE_UNKNOWN && !(used_types & curtype)) {
|
|
const gchar *sid = gst_stream_get_stream_id (stream);
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Automatically selecting stream '%s' of type %s", sid,
|
|
gst_stream_type_get_name (curtype));
|
|
tmp = g_list_append (tmp, (gchar *) sid);
|
|
used_types |= curtype;
|
|
}
|
|
}
|
|
}
|
|
|
|
beach:
|
|
if (stream_list_equal (tmp, dbin->requested_selection)) {
|
|
/* If the selection is equal, there is nothign to do */
|
|
GST_DEBUG_OBJECT (dbin, "Dropping duplicate selection");
|
|
g_list_free (tmp);
|
|
tmp = NULL;
|
|
}
|
|
|
|
if (tmp) {
|
|
/* Finally set the requested selection */
|
|
if (dbin->requested_selection) {
|
|
GST_FIXME_OBJECT (dbin,
|
|
"Replacing non-NULL requested_selection, what should we do ??");
|
|
g_list_free_full (dbin->requested_selection, g_free);
|
|
}
|
|
dbin->requested_selection =
|
|
g_list_copy_deep (tmp, (GCopyFunc) g_strdup, NULL);
|
|
dbin->selection_updated = TRUE;
|
|
g_list_free (tmp);
|
|
}
|
|
SELECTION_UNLOCK (dbin);
|
|
}
|
|
|
|
/* sort_streams:
|
|
* GCompareFunc to use with lists of GstStream.
|
|
* Sorts GstStreams by stream type and SELECT flag and stream-id
|
|
* First video, then audio, then others.
|
|
*
|
|
* Return: negative if a<b, 0 if a==b, positive if a>b
|
|
*/
|
|
static gint
|
|
sort_streams (GstStream * sa, GstStream * sb)
|
|
{
|
|
GstStreamType typea, typeb;
|
|
GstStreamFlags flaga, flagb;
|
|
const gchar *ida, *idb;
|
|
gint ret = 0;
|
|
|
|
typea = gst_stream_get_stream_type (sa);
|
|
typeb = gst_stream_get_stream_type (sb);
|
|
|
|
GST_LOG ("sa(%s), sb(%s)", gst_stream_get_stream_id (sa),
|
|
gst_stream_get_stream_id (sb));
|
|
|
|
/* Sort by stream type. First video, then audio, then others(text, container, unknown) */
|
|
if (typea != typeb) {
|
|
if (typea & GST_STREAM_TYPE_VIDEO)
|
|
ret = -1;
|
|
else if (typea & GST_STREAM_TYPE_AUDIO)
|
|
ret = (!(typeb & GST_STREAM_TYPE_VIDEO)) ? -1 : 1;
|
|
else if (typea & GST_STREAM_TYPE_TEXT)
|
|
ret = (!(typeb & GST_STREAM_TYPE_VIDEO)
|
|
&& !(typeb & GST_STREAM_TYPE_AUDIO)) ? -1 : 1;
|
|
else if (typea & GST_STREAM_TYPE_CONTAINER)
|
|
ret = (typeb & GST_STREAM_TYPE_UNKNOWN) ? -1 : 1;
|
|
else
|
|
ret = 1;
|
|
|
|
if (ret != 0) {
|
|
GST_LOG ("Sort by stream-type: %d", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Sort by SELECT flag, if stream type is same. */
|
|
flaga = gst_stream_get_stream_flags (sa);
|
|
flagb = gst_stream_get_stream_flags (sb);
|
|
|
|
ret =
|
|
(flaga & GST_STREAM_FLAG_SELECT) ? ((flagb & GST_STREAM_FLAG_SELECT) ? 0 :
|
|
-1) : ((flagb & GST_STREAM_FLAG_SELECT) ? 1 : 0);
|
|
|
|
if (ret != 0) {
|
|
GST_LOG ("Sort by SELECT flag: %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Sort by stream-id, if otherwise the same. */
|
|
ida = gst_stream_get_stream_id (sa);
|
|
idb = gst_stream_get_stream_id (sb);
|
|
ret = g_strcmp0 (ida, idb);
|
|
|
|
GST_LOG ("Sort by stream-id: %d", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Call with INPUT_LOCK taken */
|
|
static GstStreamCollection *
|
|
get_merged_collection (GstDecodebin3 * dbin)
|
|
{
|
|
gboolean needs_merge = FALSE;
|
|
GstStreamCollection *res = NULL;
|
|
GList *tmp;
|
|
GList *unsorted_streams = NULL;
|
|
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;
|
|
GST_LOG_OBJECT (dbin, "Comparing res %p input->collection %p", res,
|
|
input->collection);
|
|
if (input->collection && input->collection != res) {
|
|
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 ? gst_object_ref (res) : NULL;
|
|
}
|
|
|
|
/* 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);
|
|
unsorted_streams = g_list_append (unsorted_streams, 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);
|
|
/* Only add if not already present in the list */
|
|
if (!g_list_find (unsorted_streams, stream))
|
|
unsorted_streams = g_list_append (unsorted_streams, stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* re-order streams : video, then audio, then others */
|
|
unsorted_streams =
|
|
g_list_sort (unsorted_streams, (GCompareFunc) sort_streams);
|
|
for (tmp = unsorted_streams; tmp; tmp = tmp->next) {
|
|
GstStream *stream = (GstStream *) tmp->data;
|
|
GST_DEBUG_OBJECT (dbin, "Adding #stream(%s) to collection",
|
|
gst_stream_get_stream_id (stream));
|
|
gst_stream_collection_add_stream (res, gst_object_ref (stream));
|
|
}
|
|
|
|
if (unsorted_streams)
|
|
g_list_free (unsorted_streams);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Call with INPUT_LOCK taken */
|
|
static DecodebinInput *
|
|
find_message_parsebin (GstDecodebin3 * dbin, GstElement * child)
|
|
{
|
|
DecodebinInput *input = NULL;
|
|
GstElement *parent = gst_object_ref (child);
|
|
GList *tmp;
|
|
|
|
do {
|
|
GstElement *next_parent;
|
|
|
|
GST_DEBUG_OBJECT (dbin, "parent %s",
|
|
parent ? GST_ELEMENT_NAME (parent) : "<NONE>");
|
|
|
|
if (parent == dbin->main_input->parsebin) {
|
|
input = dbin->main_input;
|
|
break;
|
|
}
|
|
for (tmp = dbin->other_inputs; tmp; tmp = tmp->next) {
|
|
DecodebinInput *cur = (DecodebinInput *) tmp->data;
|
|
if (parent == cur->parsebin) {
|
|
input = cur;
|
|
break;
|
|
}
|
|
}
|
|
next_parent = (GstElement *) gst_element_get_parent (parent);
|
|
gst_object_unref (parent);
|
|
parent = next_parent;
|
|
|
|
} while (parent && parent != (GstElement *) dbin);
|
|
|
|
if (parent)
|
|
gst_object_unref (parent);
|
|
|
|
return input;
|
|
}
|
|
|
|
static const gchar *
|
|
stream_in_collection (GstDecodebin3 * dbin, gchar * sid)
|
|
{
|
|
guint i, len;
|
|
|
|
if (dbin->collection == NULL)
|
|
return NULL;
|
|
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 osid;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Call with INPUT_LOCK taken */
|
|
static void
|
|
handle_stream_collection (GstDecodebin3 * dbin,
|
|
GstStreamCollection * collection, DecodebinInput * input)
|
|
{
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
const gchar *upstream_id;
|
|
guint i;
|
|
#endif
|
|
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 = gst_object_ref (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);
|
|
GstTagList *taglist;
|
|
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);
|
|
if (taglist)
|
|
gst_tag_list_unref (taglist);
|
|
if (caps)
|
|
gst_caps_unref (caps);
|
|
}
|
|
#endif
|
|
|
|
/* Store collection for later usage */
|
|
SELECTION_LOCK (dbin);
|
|
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->select_streams_seqnum = GST_SEQNUM_INVALID;
|
|
SELECTION_UNLOCK (dbin);
|
|
}
|
|
|
|
/* Must be called with the selection lock taken */
|
|
static void
|
|
gst_decodebin3_update_min_interleave (GstDecodebin3 * dbin)
|
|
{
|
|
GstClockTime max_latency = GST_CLOCK_TIME_NONE;
|
|
GList *tmp;
|
|
|
|
GST_DEBUG_OBJECT (dbin, "Recalculating max latency of decoders");
|
|
for (tmp = dbin->output_streams; tmp; tmp = tmp->next) {
|
|
DecodebinOutputStream *out = (DecodebinOutputStream *) tmp->data;
|
|
if (GST_CLOCK_TIME_IS_VALID (out->decoder_latency)) {
|
|
if (max_latency == GST_CLOCK_TIME_NONE
|
|
|| out->decoder_latency > max_latency)
|
|
max_latency = out->decoder_latency;
|
|
}
|
|
}
|
|
GST_DEBUG_OBJECT (dbin, "max latency of all decoders: %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (max_latency));
|
|
|
|
if (!GST_CLOCK_TIME_IS_VALID (max_latency))
|
|
return;
|
|
|
|
/* Make sure we keep an extra overhead */
|
|
max_latency += 100 * GST_MSECOND;
|
|
if (max_latency == dbin->current_mq_min_interleave)
|
|
return;
|
|
|
|
dbin->current_mq_min_interleave = max_latency;
|
|
GST_DEBUG_OBJECT (dbin, "Setting mq min-interleave to %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (dbin->current_mq_min_interleave));
|
|
g_object_set (dbin->multiqueue, "min-interleave-time",
|
|
dbin->current_mq_min_interleave, NULL);
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_handle_message (GstBin * bin, GstMessage * message)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) bin;
|
|
gboolean posting_collection = FALSE;
|
|
GList *l;
|
|
|
|
GST_DEBUG_OBJECT (bin, "Got Message %s", GST_MESSAGE_TYPE_NAME (message));
|
|
|
|
GST_OBJECT_LOCK (dbin);
|
|
for (l = dbin->candidate_decoders; l; l = l->next) {
|
|
CandidateDecoder *candidate = l->data;
|
|
if (GST_OBJECT_CAST (candidate->element) == GST_MESSAGE_SRC (message)) {
|
|
if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
|
|
if (candidate->error)
|
|
gst_message_unref (candidate->error);
|
|
candidate->error = message;
|
|
GST_OBJECT_UNLOCK (dbin);
|
|
return;
|
|
} else if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_LATENCY) {
|
|
if (candidate->latency)
|
|
gst_message_unref (candidate->latency);
|
|
GST_DEBUG_OBJECT (bin, "store latency message for %" GST_PTR_FORMAT,
|
|
candidate->element);
|
|
candidate->latency = message;
|
|
GST_OBJECT_UNLOCK (dbin);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
GST_OBJECT_UNLOCK (dbin);
|
|
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
case GST_MESSAGE_STREAM_COLLECTION:
|
|
{
|
|
GstStreamCollection *collection = NULL;
|
|
DecodebinInput *input;
|
|
|
|
INPUT_LOCK (dbin);
|
|
input =
|
|
find_message_parsebin (dbin,
|
|
(GstElement *) GST_MESSAGE_SRC (message));
|
|
if (input == NULL) {
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Couldn't find corresponding input, most likely shutting down");
|
|
INPUT_UNLOCK (dbin);
|
|
break;
|
|
}
|
|
if (input->upstream_selected) {
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Upstream handles selection, not using/forwarding collection");
|
|
INPUT_UNLOCK (dbin);
|
|
goto drop_message;
|
|
}
|
|
gst_message_parse_stream_collection (message, &collection);
|
|
if (collection) {
|
|
handle_stream_collection (dbin, collection, input);
|
|
posting_collection = TRUE;
|
|
}
|
|
INPUT_UNLOCK (dbin);
|
|
|
|
SELECTION_LOCK (dbin);
|
|
if (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;
|
|
}
|
|
SELECTION_UNLOCK (dbin);
|
|
|
|
if (collection)
|
|
gst_object_unref (collection);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_LATENCY:
|
|
{
|
|
GList *tmp;
|
|
/* Check if this is from one of our decoders */
|
|
SELECTION_LOCK (dbin);
|
|
for (tmp = dbin->output_streams; tmp; tmp = tmp->next) {
|
|
DecodebinOutputStream *out = (DecodebinOutputStream *) tmp->data;
|
|
if (out->decoder == (GstElement *) GST_MESSAGE_SRC (message)) {
|
|
GstClockTime min, max;
|
|
if (GST_IS_VIDEO_DECODER (out->decoder)) {
|
|
gst_video_decoder_get_latency (GST_VIDEO_DECODER (out->decoder),
|
|
&min, &max);
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Got latency update from one of our decoders. min: %"
|
|
GST_TIME_FORMAT " max: %" GST_TIME_FORMAT, GST_TIME_ARGS (min),
|
|
GST_TIME_ARGS (max));
|
|
out->decoder_latency = min;
|
|
/* Trigger recalculation */
|
|
gst_decodebin3_update_min_interleave (dbin);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
SELECTION_UNLOCK (dbin);
|
|
}
|
|
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);
|
|
}
|
|
|
|
return;
|
|
|
|
drop_message:
|
|
{
|
|
GST_DEBUG_OBJECT (bin, "dropping message");
|
|
gst_message_unref (message);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_stored_latency_message (GstDecodebin3 * dbin,
|
|
DecodebinOutputStream * output, CandidateDecoder * candidate)
|
|
{
|
|
GstClockTime min, max;
|
|
if (candidate->latency && GST_IS_VIDEO_DECODER (candidate->element)) {
|
|
gst_video_decoder_get_latency (GST_VIDEO_DECODER (candidate->element),
|
|
&min, &max);
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Got latency update from %" GST_PTR_FORMAT ". min: %"
|
|
GST_TIME_FORMAT " max: %" GST_TIME_FORMAT, candidate->element,
|
|
GST_TIME_ARGS (min), GST_TIME_ARGS (max));
|
|
output->decoder_latency = min;
|
|
/* Trigger recalculation */
|
|
gst_decodebin3_update_min_interleave (dbin);
|
|
|
|
GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (dbin),
|
|
candidate->latency);
|
|
}
|
|
}
|
|
|
|
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;
|
|
GstCaps *caps;
|
|
gchar *id_in_list = NULL;
|
|
|
|
/* 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);
|
|
gst_caps_unref (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");
|
|
|
|
/* 3. In default mode check if we should expose */
|
|
id_in_list = (gchar *) stream_in_list (dbin->requested_selection, stream_id);
|
|
if (id_in_list || dbin->upstream_selected) {
|
|
/* 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, id_in_list);
|
|
g_free (id_in_list);
|
|
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;
|
|
GST_DEBUG ("Adding '%s' to active_selection", stream_id);
|
|
dbin->active_selection =
|
|
g_list_append (dbin->active_selection, (gchar *) g_strdup (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->upstream_selected) {
|
|
GST_DEBUG ("Upstream handles stream selection, returning");
|
|
return NULL;
|
|
}
|
|
|
|
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);
|
|
if (dbin->select_streams_seqnum != GST_SEQNUM_INVALID) {
|
|
gst_message_set_seqnum (msg, dbin->select_streams_seqnum);
|
|
}
|
|
for (tmp = dbin->output_streams; tmp; tmp = tmp->next) {
|
|
DecodebinOutputStream *output = (DecodebinOutputStream *) tmp->data;
|
|
if (output->slot) {
|
|
const gchar *output_streamid =
|
|
gst_stream_get_stream_id (output->slot->active_stream);
|
|
GST_DEBUG_OBJECT (dbin, "Adding stream %s", output_streamid);
|
|
if (stream_in_list (dbin->requested_selection, output_streamid))
|
|
gst_message_streams_selected_add (msg, output->slot->active_stream);
|
|
else
|
|
GST_WARNING_OBJECT (dbin,
|
|
"Output slot still active for old selection ?");
|
|
} else
|
|
GST_WARNING_OBJECT (dbin, "No valid slot for output %p", output);
|
|
}
|
|
dbin->selection_updated = FALSE;
|
|
return msg;
|
|
}
|
|
|
|
/* Must be called with SELECTION_LOCK taken
|
|
*
|
|
* This code is used to propagate the final EOS if all slots and inputs are
|
|
* drained.
|
|
**/
|
|
static void
|
|
check_inputs_and_slots_for_eos (GstDecodebin3 * dbin, GstEvent * ev)
|
|
{
|
|
GList *iter;
|
|
|
|
GST_DEBUG_OBJECT (dbin, "checking slots for eos");
|
|
|
|
for (iter = dbin->slots; iter; iter = iter->next) {
|
|
MultiQueueSlot *slot = iter->data;
|
|
|
|
if (slot->output && !slot->is_drained) {
|
|
GST_LOG_OBJECT (slot->sink_pad, "Not drained, not all slots are done");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Also check with the inputs, data might be pending */
|
|
if (!all_inputs_are_eos (dbin))
|
|
return;
|
|
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"All active slots are drained, and no pending input, push EOS");
|
|
|
|
for (iter = dbin->input_streams; iter; iter = iter->next) {
|
|
DecodebinInputStream *input = (DecodebinInputStream *) iter->data;
|
|
GstPad *peer = gst_pad_get_peer (input->srcpad);
|
|
|
|
/* Send EOS to all slots */
|
|
if (peer) {
|
|
GstEvent *stream_start, *eos;
|
|
|
|
stream_start =
|
|
gst_pad_get_sticky_event (input->srcpad, GST_EVENT_STREAM_START, 0);
|
|
|
|
/* First forward a custom STREAM_START event to reset the EOS status (if
|
|
* any) */
|
|
if (stream_start) {
|
|
GstStructure *s;
|
|
GstEvent *custom_stream_start = gst_event_copy (stream_start);
|
|
gst_event_unref (stream_start);
|
|
s = (GstStructure *) gst_event_get_structure (custom_stream_start);
|
|
gst_structure_set (s, "decodebin3-flushing-stream-start",
|
|
G_TYPE_BOOLEAN, TRUE, NULL);
|
|
gst_pad_send_event (peer, custom_stream_start);
|
|
}
|
|
|
|
eos = gst_event_new_eos ();
|
|
gst_event_set_seqnum (eos, gst_event_get_seqnum (ev));
|
|
gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (eos),
|
|
CUSTOM_FINAL_EOS_QUARK, (gchar *) CUSTOM_FINAL_EOS_QUARK_DATA, NULL);
|
|
gst_pad_send_event (peer, eos);
|
|
gst_object_unref (peer);
|
|
} else
|
|
GST_DEBUG_OBJECT (dbin, "no output");
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_slot_reconfiguration (GstDecodebin3 * dbin, MultiQueueSlot * slot)
|
|
{
|
|
DecodebinOutputStream *output;
|
|
GstMessage *msg = NULL;
|
|
|
|
SELECTION_LOCK (dbin);
|
|
output = get_output_for_slot (slot);
|
|
if (!output) {
|
|
SELECTION_UNLOCK (dbin);
|
|
return;
|
|
}
|
|
|
|
if (!reconfigure_output_stream (output, slot, &msg)) {
|
|
GST_DEBUG_OBJECT (dbin, "Removing failing stream from selection: %s ",
|
|
gst_stream_get_stream_id (slot->active_stream));
|
|
slot->dbin->requested_selection =
|
|
remove_from_list (slot->dbin->requested_selection,
|
|
gst_stream_get_stream_id (slot->active_stream));
|
|
dbin->selection_updated = TRUE;
|
|
SELECTION_UNLOCK (dbin);
|
|
if (msg)
|
|
gst_element_post_message ((GstElement *) slot->dbin, msg);
|
|
reassign_slot (dbin, slot);
|
|
} else {
|
|
GstMessage *selection_msg = is_selection_done (dbin);
|
|
SELECTION_UNLOCK (dbin);
|
|
if (selection_msg)
|
|
gst_element_post_message ((GstElement *) slot->dbin, selection_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 GstStructure *s = gst_event_get_structure (ev);
|
|
|
|
/* Drop STREAM_START events used to cleanup multiqueue */
|
|
if (s
|
|
&& gst_structure_has_field (s,
|
|
"decodebin3-flushing-stream-start")) {
|
|
ret = GST_PAD_PROBE_HANDLED;
|
|
gst_event_unref (ev);
|
|
break;
|
|
}
|
|
|
|
gst_event_parse_stream (ev, &stream);
|
|
if (stream == NULL) {
|
|
GST_ERROR_OBJECT (pad,
|
|
"Got a STREAM_START event without a GstStream");
|
|
break;
|
|
}
|
|
slot->is_drained = FALSE;
|
|
GST_DEBUG_OBJECT (pad, "Stream Start '%s'",
|
|
gst_stream_get_stream_id (stream));
|
|
if (slot->active_stream == NULL) {
|
|
slot->active_stream = stream;
|
|
} else if (slot->active_stream != stream) {
|
|
gboolean stream_type_changed =
|
|
gst_stream_get_stream_type (stream) !=
|
|
gst_stream_get_stream_type (slot->active_stream);
|
|
|
|
GST_DEBUG_OBJECT (pad, "Stream change (%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;
|
|
|
|
if (stream_type_changed) {
|
|
/* The stream type has changed, we get rid of the current output. A
|
|
* new one (targetting the new stream type) will be created once the
|
|
* caps are received. */
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Stream type change, discarding current output stream");
|
|
if (slot->output) {
|
|
DecodebinOutputStream *output = slot->output;
|
|
SELECTION_LOCK (dbin);
|
|
dbin->output_streams =
|
|
g_list_remove (dbin->output_streams, output);
|
|
free_output_stream (dbin, output);
|
|
SELECTION_UNLOCK (dbin);
|
|
}
|
|
}
|
|
} else
|
|
gst_object_unref (stream);
|
|
}
|
|
break;
|
|
case GST_EVENT_CAPS:
|
|
{
|
|
/* Configure the output slot if needed */
|
|
check_slot_reconfiguration (dbin, slot);
|
|
}
|
|
break;
|
|
case GST_EVENT_EOS:
|
|
{
|
|
gboolean was_drained = slot->is_drained;
|
|
slot->is_drained = TRUE;
|
|
|
|
/* Custom EOS handling first */
|
|
if (gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (ev),
|
|
CUSTOM_EOS_QUARK)) {
|
|
/* remove custom-eos */
|
|
ev = gst_event_make_writable (ev);
|
|
GST_PAD_PROBE_INFO_DATA (info) = ev;
|
|
gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (ev),
|
|
CUSTOM_EOS_QUARK, NULL, NULL);
|
|
|
|
GST_LOG_OBJECT (pad, "Received custom EOS");
|
|
ret = GST_PAD_PROBE_HANDLED;
|
|
SELECTION_LOCK (dbin);
|
|
if (slot->input == NULL) {
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Got custom-eos from null input stream, remove output stream");
|
|
/* 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);
|
|
/* Reacalculate min interleave */
|
|
gst_decodebin3_update_min_interleave (dbin);
|
|
}
|
|
slot->probe_id = 0;
|
|
dbin->slots = g_list_remove (dbin->slots, slot);
|
|
free_multiqueue_slot_async (dbin, slot);
|
|
ret = GST_PAD_PROBE_REMOVE;
|
|
} else if (!was_drained) {
|
|
check_inputs_and_slots_for_eos (dbin, ev);
|
|
}
|
|
if (ret == GST_PAD_PROBE_HANDLED)
|
|
gst_event_unref (ev);
|
|
SELECTION_UNLOCK (dbin);
|
|
break;
|
|
}
|
|
|
|
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, gst_event_ref (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);
|
|
}
|
|
slot->probe_id = 0;
|
|
dbin->slots = g_list_remove (dbin->slots, slot);
|
|
SELECTION_UNLOCK (dbin);
|
|
|
|
/* FIXME: Removing the slot is async, which means actually
|
|
* unlinking the pad is async. Other things like stream-start
|
|
* might flow through this (now unprobed) link before it actually
|
|
* gets released */
|
|
free_multiqueue_slot_async (dbin, slot);
|
|
ret = GST_PAD_PROBE_REMOVE;
|
|
} else if (gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (ev),
|
|
CUSTOM_FINAL_EOS_QUARK)) {
|
|
GST_DEBUG_OBJECT (pad, "Got final eos, propagating downstream");
|
|
} else {
|
|
GST_DEBUG_OBJECT (pad, "Got regular eos (all_inputs_are_eos)");
|
|
/* drop current event as eos will be sent in check_all_slot_for_eos
|
|
* when all output streams are also eos */
|
|
ret = GST_PAD_PROBE_DROP;
|
|
SELECTION_LOCK (dbin);
|
|
check_inputs_and_slots_for_eos (dbin, ev);
|
|
SELECTION_UNLOCK (dbin);
|
|
}
|
|
}
|
|
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_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_request_pad_simple (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;
|
|
}
|
|
}
|
|
|
|
/* Must be called with SELECTION_LOCK */
|
|
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);
|
|
if (input_type && slot->type != input_type) {
|
|
/* The input stream type has changed. It is the responsibility of the
|
|
* user of decodebin3 to ensure that the inputs are coherent.
|
|
*
|
|
* The only case where the stream type will change is when switching
|
|
* between sources which have non-intersecting stream types (ex:
|
|
* switching from audio-only file to video-only file)
|
|
*
|
|
* NOTE : We need to change the slot type here, since it is notified as
|
|
* soon as the *input* of the slot changes.
|
|
*/
|
|
GST_DEBUG_OBJECT (dbin, "Changing multiqueue slot stream type");
|
|
slot->type = input_type;
|
|
}
|
|
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);
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
static GList *
|
|
create_decoder_factory_list (GstDecodebin3 * dbin, GstCaps * caps)
|
|
{
|
|
GList *res;
|
|
|
|
g_mutex_lock (&dbin->factories_lock);
|
|
gst_decode_bin_update_factories_list (dbin);
|
|
res = gst_element_factory_list_filter (dbin->decoder_factories,
|
|
caps, GST_PAD_SINK, TRUE);
|
|
g_mutex_unlock (&dbin->factories_lock);
|
|
return res;
|
|
}
|
|
|
|
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 */
|
|
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
|
|
remove_decoder_link (DecodebinOutputStream * output, MultiQueueSlot * slot)
|
|
{
|
|
GstDecodebin3 *dbin = output->dbin;
|
|
|
|
gst_pad_unlink (slot->src_pad, output->decoder_sink);
|
|
if (output->drop_probe_id) {
|
|
gst_pad_remove_probe (slot->src_pad, output->drop_probe_id);
|
|
output->drop_probe_id = 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Returns FALSE if the output couldn't be properly configured and the
|
|
* associated GstStreams should be disabled */
|
|
static gboolean
|
|
reconfigure_output_stream (DecodebinOutputStream * output,
|
|
MultiQueueSlot * slot, GstMessage ** msg)
|
|
{
|
|
GstDecodebin3 *dbin = output->dbin;
|
|
GstCaps *new_caps = (GstCaps *) gst_stream_get_caps (slot->active_stream);
|
|
gboolean needs_decoder;
|
|
gboolean ret = TRUE;
|
|
|
|
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);
|
|
gst_caps_unref (new_caps);
|
|
return ret;
|
|
}
|
|
|
|
/* 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) {
|
|
can_reuse_decoder =
|
|
gst_pad_query_accept_caps (output->decoder_sink, new_caps);
|
|
} 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;
|
|
}
|
|
gst_caps_unref (new_caps);
|
|
return ret;
|
|
}
|
|
|
|
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 (!decode_pad_set_target ((GstGhostPad *) output->src_pad, NULL)) {
|
|
GST_ERROR_OBJECT (dbin, "Could not release decoder pad");
|
|
gst_caps_unref (new_caps);
|
|
goto cleanup;
|
|
}
|
|
|
|
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;
|
|
output->decoder_latency = GST_CLOCK_TIME_NONE;
|
|
} else if (output->linked) {
|
|
/* Otherwise if we have no decoder yet but the output is linked make
|
|
* sure that the ghost pad is really unlinked in case no decoder was
|
|
* needed previously */
|
|
if (!decode_pad_set_target ((GstGhostPad *) output->src_pad, NULL)) {
|
|
GST_ERROR_OBJECT (dbin, "Could not release ghost pad");
|
|
gst_caps_unref (new_caps);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
gst_object_replace ((GstObject **) & output->decoder_sink, NULL);
|
|
gst_object_replace ((GstObject **) & output->decoder_src, NULL);
|
|
|
|
/* If a decoder is required, create one */
|
|
if (needs_decoder) {
|
|
GList *factories, *next_factory;
|
|
|
|
factories = next_factory = create_decoder_factory_list (dbin, new_caps);
|
|
if (!next_factory) {
|
|
GST_DEBUG ("Could not find an element for caps %" GST_PTR_FORMAT,
|
|
new_caps);
|
|
g_assert (output->decoder == NULL);
|
|
ret = FALSE;
|
|
goto missing_decoder;
|
|
}
|
|
|
|
while (next_factory) {
|
|
gboolean decoder_failed = FALSE;
|
|
CandidateDecoder *candidate = NULL;
|
|
|
|
/* If we don't have a decoder yet, instantiate one */
|
|
output->decoder = gst_element_factory_create ((GstElementFactory *)
|
|
next_factory->data, NULL);
|
|
GST_DEBUG ("Trying decoder %" GST_PTR_FORMAT, output->decoder);
|
|
|
|
if (output->decoder == NULL)
|
|
goto try_next;
|
|
|
|
if (!gst_bin_add ((GstBin *) dbin, output->decoder)) {
|
|
GST_WARNING_OBJECT (dbin, "could not add decoder '%s' to pipeline",
|
|
GST_ELEMENT_NAME (output->decoder));
|
|
goto try_next;
|
|
}
|
|
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);
|
|
}
|
|
|
|
candidate = add_candidate_decoder (dbin, output->decoder);
|
|
if (gst_pad_link_full (slot->src_pad, output->decoder_sink,
|
|
GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK) {
|
|
GST_WARNING_OBJECT (dbin, "could not link to %s:%s",
|
|
GST_DEBUG_PAD_NAME (output->decoder_sink));
|
|
goto try_next;
|
|
}
|
|
|
|
if (gst_element_set_state (output->decoder,
|
|
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) {
|
|
GST_WARNING_OBJECT (dbin,
|
|
"Decoder '%s' failed to reach READY state",
|
|
GST_ELEMENT_NAME (output->decoder));
|
|
decoder_failed = TRUE;
|
|
goto try_next;
|
|
}
|
|
|
|
if (!gst_pad_query_accept_caps (output->decoder_sink, new_caps)) {
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Decoder '%s' did not accept the caps, trying the next type",
|
|
GST_ELEMENT_NAME (output->decoder));
|
|
decoder_failed = TRUE;
|
|
goto try_next;
|
|
}
|
|
|
|
/* 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 (output->decoder_sink);
|
|
|
|
if (gst_element_set_state (output->decoder,
|
|
GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE ||
|
|
!send_sticky_events (dbin, slot->src_pad)) {
|
|
|
|
GST_PAD_STREAM_UNLOCK (output->decoder_sink);
|
|
GST_WARNING_OBJECT (dbin,
|
|
"Decoder '%s' failed to reach PAUSED state",
|
|
GST_ELEMENT_NAME (output->decoder));
|
|
decoder_failed = TRUE;
|
|
goto try_next;
|
|
} else {
|
|
/* Everything went well */
|
|
GST_PAD_STREAM_UNLOCK (output->decoder_sink);
|
|
output->linked = TRUE;
|
|
GST_DEBUG ("created decoder %" GST_PTR_FORMAT, output->decoder);
|
|
|
|
handle_stored_latency_message (dbin, output, candidate);
|
|
remove_candidate_decoder (dbin, candidate);
|
|
}
|
|
|
|
break;
|
|
|
|
try_next:
|
|
{
|
|
if (decoder_failed)
|
|
remove_decoder_link (output, slot);
|
|
if (candidate)
|
|
remove_candidate_decoder (dbin, candidate);
|
|
|
|
if (!next_factory->next) {
|
|
ret = FALSE;
|
|
if (!decoder_failed)
|
|
goto cleanup;
|
|
if (output->decoder == NULL)
|
|
goto missing_decoder;
|
|
} else {
|
|
next_factory = next_factory->next;
|
|
}
|
|
}
|
|
}
|
|
gst_plugin_feature_list_free (factories);
|
|
} else {
|
|
output->decoder_src = gst_object_ref (slot->src_pad);
|
|
output->decoder_sink = NULL;
|
|
}
|
|
gst_caps_unref (new_caps);
|
|
|
|
if (!decode_pad_set_target ((GstGhostPad *) output->src_pad,
|
|
output->decoder_src)) {
|
|
GST_ERROR_OBJECT (dbin, "Could not expose decoder pad");
|
|
ret = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
output->linked = TRUE;
|
|
|
|
if (output->src_exposed == FALSE) {
|
|
GstEvent *stream_start;
|
|
|
|
stream_start = gst_pad_get_sticky_event (slot->src_pad,
|
|
GST_EVENT_STREAM_START, 0);
|
|
|
|
/* Ensure GstStream is accesiable from pad-added callback */
|
|
if (stream_start) {
|
|
gst_pad_store_sticky_event (output->src_pad, stream_start);
|
|
gst_event_unref (stream_start);
|
|
} else {
|
|
GST_WARNING_OBJECT (slot->src_pad,
|
|
"Pad has no stored stream-start event");
|
|
}
|
|
|
|
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 ret;
|
|
|
|
missing_decoder:
|
|
{
|
|
GstCaps *caps;
|
|
caps = gst_stream_get_caps (slot->active_stream);
|
|
*msg = gst_missing_decoder_message_new (GST_ELEMENT_CAST (dbin), caps);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
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;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
idle_reconfigure (GstPad * pad, GstPadProbeInfo * info, MultiQueueSlot * slot)
|
|
{
|
|
GstDecodebin3 *dbin = slot->dbin;
|
|
|
|
check_slot_reconfiguration (dbin, slot);
|
|
|
|
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 */
|
|
GST_DEBUG ("Removing '%s' from active_selection", sid);
|
|
for (tmp = dbin->active_selection; tmp; tmp = tmp->next)
|
|
if (!g_strcmp0 (sid, tmp->data)) {
|
|
g_free (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, g_strdup (tmp->data));
|
|
dbin->to_activate = g_list_delete_link (dbin->to_activate, tmp);
|
|
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;
|
|
GST_DEBUG ("Adding '%s' to active_selection", tsid);
|
|
dbin->active_selection =
|
|
g_list_append (dbin->active_selection, (gchar *) g_strdup (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 {
|
|
GstMessage *msg;
|
|
|
|
dbin->output_streams = g_list_remove (dbin->output_streams, output);
|
|
free_output_stream (dbin, output);
|
|
msg = is_selection_done (slot->dbin);
|
|
SELECTION_UNLOCK (dbin);
|
|
|
|
if (msg)
|
|
gst_element_post_message ((GstElement *) slot->dbin, msg);
|
|
}
|
|
|
|
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,
|
|
slot->
|
|
active_stream ? gst_stream_get_stream_id (slot->active_stream) :
|
|
"NULL");
|
|
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);
|
|
|
|
slots_to_reassign = g_list_append (slots_to_reassign, 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_full (dbin->requested_selection, g_free);
|
|
dbin->requested_selection =
|
|
g_list_copy_deep (select_streams, (GCopyFunc) g_strdup, NULL);
|
|
g_list_free (to_deactivate);
|
|
g_list_free (pending_streams);
|
|
to_deactivate = NULL;
|
|
pending_streams = NULL;
|
|
} else {
|
|
if (dbin->requested_selection)
|
|
g_list_free_full (dbin->requested_selection, g_free);
|
|
dbin->requested_selection =
|
|
g_list_copy_deep (future_request_streams, (GCopyFunc) g_strdup, NULL);
|
|
dbin->requested_selection =
|
|
g_list_concat (dbin->requested_selection,
|
|
g_list_copy_deep (pending_streams, (GCopyFunc) g_strdup, NULL));
|
|
if (dbin->to_activate)
|
|
g_list_free (dbin->to_activate);
|
|
dbin->to_activate = g_list_copy (to_reassign);
|
|
}
|
|
|
|
dbin->selection_updated = TRUE;
|
|
SELECTION_UNLOCK (dbin);
|
|
|
|
if (unknown) {
|
|
GST_FIXME_OBJECT (dbin, "Got request for an unknown stream");
|
|
g_list_free (unknown);
|
|
}
|
|
|
|
if (to_activate && !slots_to_reassign) {
|
|
for (tmp = to_activate; tmp; tmp = tmp->next) {
|
|
MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
|
|
gst_pad_add_probe (slot->src_pad, GST_PAD_PROBE_TYPE_IDLE,
|
|
(GstPadProbeCallback) idle_reconfigure, slot, NULL);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
if (to_deactivate)
|
|
g_list_free (to_deactivate);
|
|
if (to_activate)
|
|
g_list_free (to_activate);
|
|
if (to_reassign)
|
|
g_list_free (to_reassign);
|
|
if (future_request_streams)
|
|
g_list_free (future_request_streams);
|
|
if (pending_streams)
|
|
g_list_free (pending_streams);
|
|
if (slots_to_reassign)
|
|
g_list_free (slots_to_reassign);
|
|
|
|
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);
|
|
|
|
if (dbin->upstream_selected) {
|
|
GST_DEBUG_OBJECT (pad, "Letting select-streams event flow upstream");
|
|
break;
|
|
}
|
|
|
|
SELECTION_LOCK (dbin);
|
|
if (seqnum == dbin->select_streams_seqnum) {
|
|
SELECTION_UNLOCK (dbin);
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Already handled/handling that SELECT_STREAMS event");
|
|
gst_event_unref (event);
|
|
ret = GST_PAD_PROBE_HANDLED;
|
|
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);
|
|
} else {
|
|
gst_event_unref (event);
|
|
}
|
|
/* Finally handle the switch */
|
|
if (streams) {
|
|
handle_stream_switch (dbin, streams, seqnum);
|
|
g_list_free_full (streams, g_free);
|
|
}
|
|
ret = GST_PAD_PROBE_HANDLED;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decodebin3_send_event (GstElement * element, GstEvent * event)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) element;
|
|
|
|
GST_DEBUG_OBJECT (element, "event %s", GST_EVENT_TYPE_NAME (event));
|
|
if (!dbin->upstream_selected
|
|
&& GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) {
|
|
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);
|
|
|
|
/* Finally handle the switch */
|
|
if (streams) {
|
|
handle_stream_switch (dbin, streams, seqnum);
|
|
g_list_free_full (streams, g_free);
|
|
}
|
|
|
|
gst_event_unref (event);
|
|
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);
|
|
gst_object_replace ((GstObject **) & slot->active_stream, NULL);
|
|
g_free (slot);
|
|
}
|
|
|
|
static void
|
|
free_multiqueue_slot_async (GstDecodebin3 * dbin, MultiQueueSlot * slot)
|
|
{
|
|
GST_LOG_OBJECT (dbin, "pushing multiqueue slot on thread pool to free");
|
|
gst_element_call_async (GST_ELEMENT_CAST (dbin),
|
|
(GstElementCallAsyncFunc) free_multiqueue_slot, slot, NULL);
|
|
}
|
|
|
|
/* 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;
|
|
res->decoder_latency = GST_CLOCK_TIME_NONE;
|
|
|
|
if (type & GST_STREAM_TYPE_VIDEO) {
|
|
templ = &video_src_template;
|
|
counter = &dbin->vpadcount;
|
|
prefix = "video";
|
|
} else if (type & GST_STREAM_TYPE_AUDIO) {
|
|
templ = &audio_src_template;
|
|
counter = &dbin->apadcount;
|
|
prefix = "audio";
|
|
} else if (type & GST_STREAM_TYPE_TEXT) {
|
|
templ = &text_src_template;
|
|
counter = &dbin->tpadcount;
|
|
prefix = "text";
|
|
} else {
|
|
templ = &src_template;
|
|
counter = &dbin->opadcount;
|
|
prefix = "src";
|
|
}
|
|
|
|
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);
|
|
decode_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:
|
|
gst_decodebin3_reset (dbin);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
beach:
|
|
return ret;
|
|
}
|