mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-24 18:51:11 +00:00
0dc419197f
Some decoders might introduce quite large latencies, which would result in multiqueue draining out on some streams. In order to avoid that, check the latency of decoders and adjust the minimum interleave time of multiqueue accordingly. Fixes https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/issues/800 Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/931>
3022 lines
97 KiB
C
3022 lines
97 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 "gstplayback.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.
|
|
*
|
|
* > decodebin3 is still experimental API and a technology preview.
|
|
* > Its behaviour and exposed API is subject to change.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Global design
|
|
*
|
|
* 1) From sink pad to elementary streams (GstParseBin)
|
|
*
|
|
* The input sink pads are fed to GstParseBin. GstParseBin will feed them
|
|
* through typefind. When the caps are detected (or changed) we recursively
|
|
* figure out which demuxer, parser or depayloader is needed until we get to
|
|
* elementary streams.
|
|
*
|
|
* All elementary streams (whether decoded or not, whether exposed or not) are
|
|
* fed through multiqueue. There is only *one* multiqueue in decodebin3.
|
|
*
|
|
* => MultiQueue is the cornerstone.
|
|
* => No buffering before multiqueue
|
|
*
|
|
* 2) Elementary streams
|
|
*
|
|
* After GstParseBin, there are 3 main components:
|
|
* 1) Input Streams (provided by GstParseBin)
|
|
* 2) Multiqueue slots
|
|
* 3) Output Streams
|
|
*
|
|
* Input Streams correspond to the stream coming from GstParseBin and that gets
|
|
* fed into a multiqueue slot.
|
|
*
|
|
* Output Streams correspond to the combination of a (optional) decoder and an
|
|
* output ghostpad. Output Streams can be moved from one multiqueue slot to
|
|
* another, can reconfigure itself (different decoders), and can be
|
|
* added/removed depending on the configuration (all streams outputted, only one
|
|
* of each type, ...).
|
|
*
|
|
* Multiqueue slots correspond to a pair of sink/src pad from multiqueue. For
|
|
* each 'active' Input Stream there is a corresponding slot.
|
|
* Slots might have different streams on input and output (due to internal
|
|
* buffering).
|
|
*
|
|
* Due to internal queuing/buffering/..., all those components (might) behave
|
|
* asynchronously. Therefore probes will be used on each component source pad to
|
|
* detect various key-points:
|
|
* * EOS :
|
|
* the stream is done => Mark that component as done, optionally freeing/removing it
|
|
* * STREAM_START :
|
|
* a new stream is starting => link it further if needed
|
|
*
|
|
* 3) Gradual replacement
|
|
*
|
|
* If the caps change at any point in decodebin (input sink pad, demuxer output,
|
|
* multiqueue output, ..), we gradually replace (if needed) the following elements.
|
|
*
|
|
* This is handled by the probes in various locations:
|
|
* a) typefind output
|
|
* b) multiqueue input (source pad of Input Streams)
|
|
* c) multiqueue output (source pad of Multiqueue Slots)
|
|
* d) final output (target of source ghostpads)
|
|
*
|
|
* When CAPS event arrive at those points, one of three things can happen:
|
|
* a) There is no elements downstream yet, just create/link-to following elements
|
|
* b) There are downstream elements, do a ACCEPT_CAPS query
|
|
* b.1) The new CAPS are accepted, keep current configuration
|
|
* b.2) The new CAPS are not accepted, remove following elements then do a)
|
|
*
|
|
* Components:
|
|
*
|
|
* MultiQ Output
|
|
* Input(s) Slots Streams
|
|
* /-------------------------------------------\ /-----\ /------------- \
|
|
*
|
|
* +-------------------------------------------------------------------------+
|
|
* | |
|
|
* | +---------------------------------------------+ |
|
|
* | | GstParseBin(s) | |
|
|
* | | +--------------+ | +-----+ |
|
|
* | | | |---[parser]-[|--| Mul |---[ decoder ]-[|
|
|
* |]--[ typefind ]---| demuxer(s) |------------[| | ti | |
|
|
* | | | (if needed) |---[parser]-[|--| qu | |
|
|
* | | | |---[parser]-[|--| eu |---[ decoder ]-[|
|
|
* | | +--------------+ | +------ ^ |
|
|
* | +---------------------------------------------+ ^ | |
|
|
* | ^ | | |
|
|
* +-----------------------------------------------+--------+-------------+--+
|
|
* | | |
|
|
* | | |
|
|
* Probes --/--------/-------------/
|
|
*
|
|
* ATOMIC SWITCHING
|
|
*
|
|
* We want to ensure we re-use decoders when switching streams. This takes place
|
|
* at the multiqueue output level.
|
|
*
|
|
* MAIN CONCEPTS
|
|
* 1) Activating a stream (i.e. linking a slot to an output) is only done within
|
|
* the streaming thread in the multiqueue_src_probe() and only if the
|
|
stream is in the REQUESTED selection.
|
|
* 2) Deactivating a stream (i.e. unlinking a slot from an output) is also done
|
|
* within the stream thread, but only in a purposefully called IDLE probe
|
|
* that calls reassign_slot().
|
|
*
|
|
* Based on those two principles, 3 "selection" of streams (stream-id) are used:
|
|
* 1) requested_selection
|
|
* All streams within that list should be activated
|
|
* 2) active_selection
|
|
* List of streams that are exposed by decodebin
|
|
* 3) to_activate
|
|
* List of streams that will be moved to requested_selection in the
|
|
* reassign_slot() method (i.e. once a stream was deactivated, and the output
|
|
* was retargetted)
|
|
*/
|
|
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (decodebin3_debug);
|
|
#define GST_CAT_DEFAULT decodebin3_debug
|
|
|
|
#define GST_TYPE_DECODEBIN3 (gst_decodebin3_get_type ())
|
|
|
|
#define EXTRA_DEBUG 1
|
|
|
|
#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;
|
|
|
|
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) */
|
|
/* FIXME : Needs to be reset appropriately (when upstream changes ?) */
|
|
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 */
|
|
|
|
/* List of pending collections.
|
|
* FIXME : Is this really needed ? */
|
|
GList *pending_collection;
|
|
|
|
/* Factories */
|
|
GMutex factories_lock;
|
|
guint32 factories_cookie;
|
|
/* All DECODABLE factories */
|
|
GList *factories;
|
|
/* Only DECODER factories */
|
|
GList *decoder_factories;
|
|
/* DECODABLE but not DECODER factories */
|
|
GList *decodable_factories;
|
|
|
|
/* counters for pads */
|
|
guint32 apadcount, vpadcount, tpadcount, opadcount;
|
|
|
|
/* Properties */
|
|
GstCaps *caps;
|
|
};
|
|
|
|
struct _GstDecodebin3Class
|
|
{
|
|
GstBinClass class;
|
|
|
|
gint (*select_stream) (GstDecodebin3 * dbin,
|
|
GstStreamCollection * collection, GstStream * stream);
|
|
};
|
|
|
|
/* Input of decodebin, controls input pad and parsebin */
|
|
struct _DecodebinInput
|
|
{
|
|
GstDecodebin3 *dbin;
|
|
|
|
gboolean is_main;
|
|
|
|
GstPad *ghost_sink;
|
|
GstPad *parsebin_sink;
|
|
|
|
GstStreamCollection *collection; /* Active collection */
|
|
|
|
guint group_id;
|
|
|
|
GstElement *parsebin;
|
|
|
|
gulong pad_added_sigid;
|
|
gulong pad_removed_sigid;
|
|
gulong drained_sigid;
|
|
|
|
/* TRUE if the input got drained
|
|
* FIXME : When do we reset it if re-used ?
|
|
*/
|
|
gboolean drained;
|
|
|
|
/* HACK : Remove these fields */
|
|
/* List of PendingPad structures */
|
|
GList *pending_pads;
|
|
};
|
|
|
|
/* Multiqueue Slots */
|
|
typedef struct _MultiQueueSlot
|
|
{
|
|
guint id;
|
|
|
|
GstDecodebin3 *dbin;
|
|
/* Type of stream handled by this slot */
|
|
GstStreamType type;
|
|
|
|
/* Linked input and output */
|
|
DecodebinInputStream *input;
|
|
|
|
/* pending => last stream received on sink pad */
|
|
GstStream *pending_stream;
|
|
/* active => last stream outputted on source pad */
|
|
GstStream *active_stream;
|
|
|
|
GstPad *sink_pad, *src_pad;
|
|
|
|
/* id of the MQ src_pad event probe */
|
|
gulong probe_id;
|
|
|
|
gboolean 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;
|
|
};
|
|
|
|
/* Pending pads from parsebin */
|
|
typedef struct _PendingPad
|
|
{
|
|
GstDecodebin3 *dbin;
|
|
DecodebinInput *input;
|
|
GstPad *pad;
|
|
|
|
gulong buffer_probe;
|
|
gulong event_probe;
|
|
gboolean saw_eos;
|
|
} PendingPad;
|
|
|
|
/* 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);
|
|
|
|
static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS);
|
|
|
|
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS_ANY);
|
|
|
|
static GstStaticPadTemplate request_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink_%u",
|
|
GST_PAD_SINK,
|
|
GST_PAD_REQUEST,
|
|
GST_STATIC_CAPS_ANY);
|
|
|
|
static GstStaticPadTemplate video_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("video_%u",
|
|
GST_PAD_SRC,
|
|
GST_PAD_SOMETIMES,
|
|
GST_STATIC_CAPS_ANY);
|
|
|
|
static GstStaticPadTemplate audio_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("audio_%u",
|
|
GST_PAD_SRC,
|
|
GST_PAD_SOMETIMES,
|
|
GST_STATIC_CAPS_ANY);
|
|
|
|
static GstStaticPadTemplate text_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("text_%u",
|
|
GST_PAD_SRC,
|
|
GST_PAD_SOMETIMES,
|
|
GST_STATIC_CAPS_ANY);
|
|
|
|
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u",
|
|
GST_PAD_SRC,
|
|
GST_PAD_SOMETIMES,
|
|
GST_STATIC_CAPS_ANY);
|
|
|
|
|
|
static void gst_decodebin3_dispose (GObject * object);
|
|
static void gst_decodebin3_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_decodebin3_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static gboolean parsebin_autoplug_continue_cb (GstElement *
|
|
parsebin, GstPad * pad, GstCaps * caps, GstDecodebin3 * dbin);
|
|
|
|
static gint
|
|
gst_decodebin3_select_stream (GstDecodebin3 * dbin,
|
|
GstStreamCollection * collection, GstStream * stream)
|
|
{
|
|
GST_LOG_OBJECT (dbin, "default select-stream, returning -1");
|
|
|
|
return -1;
|
|
}
|
|
|
|
static GstPad *gst_decodebin3_request_new_pad (GstElement * element,
|
|
GstPadTemplate * temp, const gchar * name, const GstCaps * caps);
|
|
static void gst_decodebin3_handle_message (GstBin * bin, GstMessage * message);
|
|
static GstStateChangeReturn gst_decodebin3_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
static gboolean gst_decodebin3_send_event (GstElement * element,
|
|
GstEvent * event);
|
|
|
|
static void gst_decode_bin_update_factories_list (GstDecodebin3 * dbin);
|
|
#if 0
|
|
static gboolean have_factory (GstDecodebin3 * dbin, GstCaps * caps,
|
|
GstElementFactoryListType ftype);
|
|
#endif
|
|
|
|
static void free_input (GstDecodebin3 * dbin, DecodebinInput * input);
|
|
static void free_input_async (GstDecodebin3 * dbin, DecodebinInput * input);
|
|
static DecodebinInput *create_new_input (GstDecodebin3 * dbin, gboolean main);
|
|
static gboolean set_input_group_id (DecodebinInput * input, guint32 * group_id);
|
|
|
|
static void reconfigure_output_stream (DecodebinOutputStream * output,
|
|
MultiQueueSlot * slot);
|
|
static void free_output_stream (GstDecodebin3 * dbin,
|
|
DecodebinOutputStream * output);
|
|
static DecodebinOutputStream *create_output_stream (GstDecodebin3 * dbin,
|
|
GstStreamType type);
|
|
|
|
static GstPadProbeReturn slot_unassign_probe (GstPad * pad,
|
|
GstPadProbeInfo * info, MultiQueueSlot * slot);
|
|
static gboolean reassign_slot (GstDecodebin3 * dbin, MultiQueueSlot * slot);
|
|
static MultiQueueSlot *get_slot_for_input (GstDecodebin3 * dbin,
|
|
DecodebinInputStream * input);
|
|
static void link_input_to_slot (DecodebinInputStream * input,
|
|
MultiQueueSlot * slot);
|
|
static void free_multiqueue_slot (GstDecodebin3 * dbin, MultiQueueSlot * slot);
|
|
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);
|
|
|
|
if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
|
|
g_value_set_int (return_accu, res);
|
|
|
|
if (res == -1)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_class_init (GstDecodebin3Class * klass)
|
|
{
|
|
GObjectClass *gobject_klass = (GObjectClass *) klass;
|
|
GstElementClass *element_class = (GstElementClass *) klass;
|
|
GstBinClass *bin_klass = (GstBinClass *) klass;
|
|
|
|
gobject_klass->dispose = gst_decodebin3_dispose;
|
|
gobject_klass->set_property = gst_decodebin3_set_property;
|
|
gobject_klass->get_property = gst_decodebin3_get_property;
|
|
|
|
/* FIXME : ADD PROPERTIES ! */
|
|
g_object_class_install_property (gobject_klass, PROP_CAPS,
|
|
g_param_spec_boxed ("caps", "Caps",
|
|
"The caps on which to stop decoding. (NULL = default)",
|
|
GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/* FIXME : ADD SIGNALS ! */
|
|
/**
|
|
* GstDecodebin3::select-stream
|
|
* @decodebin: a #GstDecodebin3
|
|
* @collection: a #GstStreamCollection
|
|
* @stream: a #GstStream
|
|
*
|
|
* This signal is emitted whenever @decodebin needs to decide whether
|
|
* to expose a @stream of a given @collection.
|
|
*
|
|
* 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);
|
|
|
|
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_dispose (GObject * object)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) object;
|
|
GList *walk, *next;
|
|
|
|
if (dbin->factories)
|
|
gst_plugin_feature_list_free (dbin->factories);
|
|
if (dbin->decoder_factories)
|
|
g_list_free (dbin->decoder_factories);
|
|
if (dbin->decodable_factories)
|
|
g_list_free (dbin->decodable_factories);
|
|
g_list_free_full (dbin->requested_selection, g_free);
|
|
g_list_free (dbin->active_selection);
|
|
g_list_free (dbin->to_activate);
|
|
g_list_free (dbin->pending_select_streams);
|
|
g_clear_object (&dbin->collection);
|
|
|
|
free_input (dbin, dbin->main_input);
|
|
|
|
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);
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) object;
|
|
|
|
/* FIXME : IMPLEMENT */
|
|
switch (prop_id) {
|
|
case PROP_CAPS:
|
|
GST_OBJECT_LOCK (dbin);
|
|
if (dbin->caps)
|
|
gst_caps_unref (dbin->caps);
|
|
dbin->caps = g_value_dup_boxed (value);
|
|
GST_OBJECT_UNLOCK (dbin);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) object;
|
|
|
|
/* FIXME : IMPLEMENT */
|
|
switch (prop_id) {
|
|
case PROP_CAPS:
|
|
GST_OBJECT_LOCK (dbin);
|
|
g_value_set_boxed (value, dbin->caps);
|
|
GST_OBJECT_UNLOCK (dbin);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
parsebin_autoplug_continue_cb (GstElement * parsebin, GstPad * pad,
|
|
GstCaps * caps, GstDecodebin3 * dbin)
|
|
{
|
|
GST_DEBUG_OBJECT (pad, "caps %" GST_PTR_FORMAT, caps);
|
|
|
|
/* If it matches our target caps, expose it */
|
|
if (gst_caps_can_intersect (caps, dbin->caps))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* This method should be called whenever a STREAM_START event
|
|
* comes out of a given parsebin.
|
|
* The caller shall replace the group_id if the function returns TRUE */
|
|
static gboolean
|
|
set_input_group_id (DecodebinInput * input, guint32 * group_id)
|
|
{
|
|
GstDecodebin3 *dbin = input->dbin;
|
|
|
|
if (input->group_id != *group_id) {
|
|
if (input->group_id != 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) {
|
|
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;
|
|
}
|
|
*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);
|
|
}
|
|
}
|
|
|
|
/* 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)) {
|
|
gst_bin_add (GST_BIN (dbin), input->parsebin);
|
|
set_state = TRUE;
|
|
}
|
|
|
|
gst_ghost_pad_set_target (GST_GHOST_PAD (input->ghost_sink),
|
|
input->parsebin_sink);
|
|
if (set_state)
|
|
gst_element_sync_state_with_parent (input->parsebin);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
no_parsebin:
|
|
{
|
|
gst_element_post_message ((GstElement *) dbin,
|
|
gst_missing_element_message_new ((GstElement *) dbin, "parsebin"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GstPadLinkReturn
|
|
gst_decodebin3_input_pad_link (GstPad * pad, GstObject * parent, GstPad * peer)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) parent;
|
|
GstPadLinkReturn res = GST_PAD_LINK_OK;
|
|
DecodebinInput *input;
|
|
|
|
GST_LOG_OBJECT (parent, "Got link on input pad %" GST_PTR_FORMAT
|
|
". Creating parsebin if needed", pad);
|
|
|
|
if ((input = g_object_get_data (G_OBJECT (pad), "decodebin.input")) == NULL)
|
|
goto fail;
|
|
|
|
INPUT_LOCK (dbin);
|
|
if (!ensure_input_parsebin (dbin, input))
|
|
res = GST_PAD_LINK_REFUSED;
|
|
INPUT_UNLOCK (dbin);
|
|
|
|
return res;
|
|
fail:
|
|
GST_ERROR_OBJECT (parent, "Failed to retrieve input state from ghost pad");
|
|
return GST_PAD_LINK_REFUSED;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static void
|
|
gst_decodebin3_input_pad_unlink (GstPad * pad, GstObject * parent)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) parent;
|
|
DecodebinInput *input;
|
|
|
|
GST_LOG_OBJECT (parent, "Got unlink on input pad %" GST_PTR_FORMAT
|
|
". Removing parsebin.", pad);
|
|
|
|
if ((input = g_object_get_data (G_OBJECT (pad), "decodebin.input")) == NULL)
|
|
goto fail;
|
|
|
|
INPUT_LOCK (dbin);
|
|
if (input->parsebin == NULL) {
|
|
INPUT_UNLOCK (dbin);
|
|
return;
|
|
}
|
|
|
|
if (GST_OBJECT_PARENT (GST_OBJECT (input->parsebin)) == GST_OBJECT (dbin)) {
|
|
GstStreamCollection *collection = NULL;
|
|
gulong probe_id = gst_pad_add_probe (input->parsebin_sink,
|
|
GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
|
|
(GstPadProbeCallback) query_duration_drop_probe, input, NULL);
|
|
|
|
/* 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 && collection != dbin->collection) {
|
|
GstMessage *msg;
|
|
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);
|
|
|
|
SELECTION_UNLOCK (dbin);
|
|
gst_element_post_message (GST_ELEMENT_CAST (dbin), msg);
|
|
update_requested_selection (dbin);
|
|
} else
|
|
SELECTION_UNLOCK (dbin);
|
|
|
|
gst_bin_remove (GST_BIN (dbin), input->parsebin);
|
|
gst_element_set_state (input->parsebin, GST_STATE_NULL);
|
|
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_pad_remove_probe (input->parsebin_sink, probe_id);
|
|
gst_object_unref (input->parsebin);
|
|
gst_object_unref (input->parsebin_sink);
|
|
|
|
input->parsebin = NULL;
|
|
input->parsebin_sink = NULL;
|
|
|
|
if (!input->is_main) {
|
|
dbin->other_inputs = g_list_remove (dbin->other_inputs, input);
|
|
free_input_async (dbin, input);
|
|
}
|
|
}
|
|
INPUT_UNLOCK (dbin);
|
|
return;
|
|
|
|
fail:
|
|
GST_ERROR_OBJECT (parent, "Failed to retrieve input state from ghost pad");
|
|
return;
|
|
}
|
|
|
|
static void
|
|
free_input (GstDecodebin3 * dbin, DecodebinInput * input)
|
|
{
|
|
GST_DEBUG ("Freeing input %p", input);
|
|
gst_ghost_pad_set_target (GST_GHOST_PAD (input->ghost_sink), NULL);
|
|
gst_element_remove_pad (GST_ELEMENT (dbin), input->ghost_sink);
|
|
if (input->parsebin) {
|
|
g_signal_handler_disconnect (input->parsebin, input->pad_removed_sigid);
|
|
g_signal_handler_disconnect (input->parsebin, input->pad_added_sigid);
|
|
g_signal_handler_disconnect (input->parsebin, input->drained_sigid);
|
|
gst_element_set_state (input->parsebin, GST_STATE_NULL);
|
|
gst_object_unref (input->parsebin);
|
|
gst_object_unref (input->parsebin_sink);
|
|
}
|
|
if (input->collection)
|
|
gst_object_unref (input->collection);
|
|
g_free (input);
|
|
}
|
|
|
|
static void
|
|
free_input_async (GstDecodebin3 * dbin, DecodebinInput * input)
|
|
{
|
|
GST_LOG_OBJECT (dbin, "pushing input %p on thread pool to free", input);
|
|
gst_element_call_async (GST_ELEMENT_CAST (dbin),
|
|
(GstElementCallAsyncFunc) free_input, input, NULL);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
g_object_set_data (G_OBJECT (input->ghost_sink), "decodebin.input", input);
|
|
gst_pad_set_link_function (input->ghost_sink, gst_decodebin3_input_pad_link);
|
|
gst_pad_set_unlink_function (input->ghost_sink,
|
|
gst_decodebin3_input_pad_unlink);
|
|
|
|
gst_pad_set_active (input->ghost_sink, TRUE);
|
|
gst_element_add_pad ((GstElement *) dbin, input->ghost_sink);
|
|
|
|
return input;
|
|
|
|
}
|
|
|
|
static GstPad *
|
|
gst_decodebin3_request_new_pad (GstElement * element, GstPadTemplate * temp,
|
|
const gchar * name, const GstCaps * caps)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) element;
|
|
DecodebinInput *input;
|
|
GstPad *res = NULL;
|
|
|
|
/* We are ignoring names for the time being, not sure it makes any sense
|
|
* within the context of decodebin3 ... */
|
|
input = 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 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 (!(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:
|
|
/* Finally set the requested selection */
|
|
if (tmp) {
|
|
if (dbin->requested_selection) {
|
|
GST_FIXME_OBJECT (dbin,
|
|
"Replacing non-NULL requested_selection, what should we do ??");
|
|
g_list_free_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;
|
|
if (input->collection) {
|
|
if (res) {
|
|
needs_merge = TRUE;
|
|
break;
|
|
}
|
|
res = input->collection;
|
|
}
|
|
}
|
|
|
|
if (!needs_merge) {
|
|
GST_DEBUG_OBJECT (dbin, "No need to merge, returning %p", res);
|
|
return res ? 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);
|
|
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, GstElement * child)
|
|
{
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
const gchar *upstream_id;
|
|
guint i;
|
|
#endif
|
|
DecodebinInput *input = find_message_parsebin (dbin, child);
|
|
|
|
if (!input) {
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Couldn't find corresponding input, most likely shutting down");
|
|
return;
|
|
}
|
|
|
|
/* Replace collection in input */
|
|
if (input->collection)
|
|
gst_object_unref (input->collection);
|
|
input->collection = 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->pending_collection = */
|
|
/* g_list_append (dbin->pending_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;
|
|
|
|
GST_DEBUG_OBJECT (bin, "Got Message %s", GST_MESSAGE_TYPE_NAME (message));
|
|
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
case GST_MESSAGE_STREAM_COLLECTION:
|
|
{
|
|
GstStreamCollection *collection = NULL;
|
|
gst_message_parse_stream_collection (message, &collection);
|
|
if (collection) {
|
|
INPUT_LOCK (dbin);
|
|
handle_stream_collection (dbin, collection,
|
|
(GstElement *) GST_MESSAGE_SRC (message));
|
|
posting_collection = TRUE;
|
|
INPUT_UNLOCK (dbin);
|
|
}
|
|
|
|
SELECTION_LOCK (dbin);
|
|
if (dbin->collection && collection != dbin->collection) {
|
|
/* Replace collection message, we most likely aggregated it */
|
|
GstMessage *new_msg;
|
|
new_msg =
|
|
gst_message_new_stream_collection ((GstObject *) dbin,
|
|
dbin->collection);
|
|
gst_message_unref (message);
|
|
message = new_msg;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
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");
|
|
|
|
#if 0
|
|
/* FIXME : The idea around this was to avoid activating a stream for
|
|
* which we have no decoder. Unfortunately it is way too
|
|
* expensive. Need to figure out a better solution */
|
|
/* 2. Is there a potential decoder (if one is required) */
|
|
if (!gst_caps_can_intersect (caps, dbin->caps)
|
|
&& !have_factory (dbin, (GstCaps *) caps,
|
|
GST_ELEMENT_FACTORY_TYPE_DECODER)) {
|
|
GST_WARNING_OBJECT (dbin, "Don't have a decoder for %" GST_PTR_FORMAT,
|
|
caps);
|
|
SELECTION_UNLOCK (dbin);
|
|
gst_element_post_message (GST_ELEMENT_CAST (dbin),
|
|
gst_missing_decoder_message_new (GST_ELEMENT_CAST (dbin), caps));
|
|
SELECTION_LOCK (dbin);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/* 3. In default mode check if we should expose */
|
|
id_in_list = (gchar *) stream_in_list (dbin->requested_selection, stream_id);
|
|
if (id_in_list) {
|
|
/* 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;
|
|
dbin->active_selection =
|
|
g_list_append (dbin->active_selection, (gchar *) stream_id);
|
|
} else
|
|
GST_DEBUG ("Not creating any output for slot %p", slot);
|
|
|
|
return output;
|
|
}
|
|
|
|
/* Returns SELECTED_STREAMS message if active_selection is equal to
|
|
* requested_selection, else NULL.
|
|
* Must be called with LOCK taken */
|
|
static GstMessage *
|
|
is_selection_done (GstDecodebin3 * dbin)
|
|
{
|
|
GList *tmp;
|
|
GstMessage *msg;
|
|
|
|
if (!dbin->selection_updated)
|
|
return NULL;
|
|
|
|
GST_LOG_OBJECT (dbin, "Checking");
|
|
|
|
if (dbin->to_activate != NULL) {
|
|
GST_DEBUG ("Still have streams to activate");
|
|
return NULL;
|
|
}
|
|
for (tmp = dbin->requested_selection; tmp; tmp = tmp->next) {
|
|
GST_DEBUG ("Checking requested stream %s", (gchar *) tmp->data);
|
|
if (!stream_in_list (dbin->active_selection, (gchar *) tmp->data)) {
|
|
GST_DEBUG ("Not in active selection, returning");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (dbin, "Selection active, creating message");
|
|
|
|
/* We are completely active */
|
|
msg = gst_message_new_streams_selected ((GstObject *) dbin, dbin->collection);
|
|
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) {
|
|
GST_DEBUG_OBJECT (dbin, "Adding stream %s",
|
|
gst_stream_get_stream_id (output->slot->active_stream));
|
|
|
|
gst_message_streams_selected_add (msg, output->slot->active_stream);
|
|
} else
|
|
GST_WARNING_OBJECT (dbin, "No valid slot for output %p", output);
|
|
}
|
|
dbin->selection_updated = FALSE;
|
|
return msg;
|
|
}
|
|
|
|
/* Must be called with SELECTION_LOCK taken */
|
|
static void
|
|
check_all_slot_for_eos (GstDecodebin3 * dbin)
|
|
{
|
|
gboolean all_drained = TRUE;
|
|
GList *iter;
|
|
|
|
GST_DEBUG_OBJECT (dbin, "check slot for eos");
|
|
|
|
for (iter = dbin->slots; iter; iter = iter->next) {
|
|
MultiQueueSlot *slot = iter->data;
|
|
|
|
if (!slot->output)
|
|
continue;
|
|
|
|
if (slot->is_drained) {
|
|
GST_LOG_OBJECT (slot->sink_pad, "slot %p is drained", slot);
|
|
continue;
|
|
}
|
|
|
|
all_drained = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (all_drained) {
|
|
INPUT_LOCK (dbin);
|
|
if (!pending_pads_are_eos (dbin->main_input))
|
|
all_drained = FALSE;
|
|
|
|
if (all_drained) {
|
|
for (iter = dbin->other_inputs; iter; iter = iter->next) {
|
|
if (!pending_pads_are_eos ((DecodebinInput *) iter->data)) {
|
|
all_drained = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
INPUT_UNLOCK (dbin);
|
|
}
|
|
|
|
if (all_drained) {
|
|
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_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 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) {
|
|
GST_FIXME_OBJECT (pad, "Handle stream changes (%s => %s) !",
|
|
gst_stream_get_stream_id (slot->active_stream),
|
|
gst_stream_get_stream_id (stream));
|
|
gst_object_unref (slot->active_stream);
|
|
slot->active_stream = stream;
|
|
} else
|
|
gst_object_unref (stream);
|
|
#if 0 /* Disabled because stream-start is pushed for every buffer on every unlinked pad */
|
|
{
|
|
gboolean is_active, is_requested;
|
|
/* Quick check to see if we're in the current selection */
|
|
/* FIXME : Re-check all slot<=>output mappings based on requested_selection */
|
|
SELECTION_LOCK (dbin);
|
|
GST_DEBUG_OBJECT (dbin, "Checking active selection");
|
|
is_active = stream_in_list (dbin->active_selection, stream_id);
|
|
GST_DEBUG_OBJECT (dbin, "Checking requested selection");
|
|
is_requested = stream_in_list (dbin->requested_selection, stream_id);
|
|
SELECTION_UNLOCK (dbin);
|
|
if (is_active)
|
|
GST_DEBUG_OBJECT (pad, "Slot in ACTIVE selection (output:%p)",
|
|
slot->output);
|
|
if (is_requested)
|
|
GST_DEBUG_OBJECT (pad, "Slot in REQUESTED selection (output:%p)",
|
|
slot->output);
|
|
else if (slot->output) {
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Slot needs to be deactivated ? It's no longer in requested selection");
|
|
} else if (!is_active)
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Slot in neither active nor requested selection");
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
case GST_EVENT_CAPS:
|
|
{
|
|
/* Configure the output slot if needed */
|
|
DecodebinOutputStream *output;
|
|
GstMessage *msg = NULL;
|
|
SELECTION_LOCK (dbin);
|
|
output = get_output_for_slot (slot);
|
|
if (output) {
|
|
reconfigure_output_stream (output, slot);
|
|
msg = is_selection_done (dbin);
|
|
}
|
|
SELECTION_UNLOCK (dbin);
|
|
if (msg)
|
|
gst_element_post_message ((GstElement *) slot->dbin, msg);
|
|
}
|
|
break;
|
|
case GST_EVENT_EOS:
|
|
{
|
|
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 */
|
|
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_all_slot_for_eos (dbin);
|
|
}
|
|
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, ev);
|
|
gst_object_unref (peer);
|
|
} else {
|
|
gst_event_unref (ev);
|
|
}
|
|
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);
|
|
|
|
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_all_slot_for_eos (dbin);
|
|
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_get_request_pad (dbin->multiqueue, "sink_%u");
|
|
if (slot->sink_pad == NULL)
|
|
goto fail;
|
|
|
|
it = gst_pad_iterate_internal_links (slot->sink_pad);
|
|
if (!it || (gst_iterator_next (it, &item)) != GST_ITERATOR_OK
|
|
|| ((slot->src_pad = g_value_dup_object (&item)) == NULL)) {
|
|
GST_ERROR ("Couldn't get srcpad from multiqueue for sink pad %s:%s",
|
|
GST_DEBUG_PAD_NAME (slot->src_pad));
|
|
goto fail;
|
|
}
|
|
gst_iterator_free (it);
|
|
g_value_reset (&item);
|
|
|
|
g_object_set (slot->sink_pad, "group-id", (guint) type, NULL);
|
|
|
|
/* Add event probe */
|
|
slot->probe_id =
|
|
gst_pad_add_probe (slot->src_pad,
|
|
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
|
|
(GstPadProbeCallback) multiqueue_src_probe, slot, NULL);
|
|
|
|
GST_DEBUG ("Created new slot %u (%p) (%s:%s)", slot->id, slot,
|
|
GST_DEBUG_PAD_NAME (slot->src_pad));
|
|
|
|
dbin->slots = g_list_append (dbin->slots, slot);
|
|
|
|
return slot;
|
|
|
|
/* ERRORS */
|
|
fail:
|
|
{
|
|
if (slot->sink_pad)
|
|
gst_element_release_request_pad (dbin->multiqueue, slot->sink_pad);
|
|
g_free (slot);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
return slot;
|
|
}
|
|
}
|
|
|
|
/* Go amongst all unused slots of the right type and try to find a candidate */
|
|
for (tmp = dbin->slots; tmp; tmp = tmp->next) {
|
|
MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
|
|
if (slot->input == NULL && input_type == slot->type) {
|
|
/* Remember this empty slot for later */
|
|
empty_slot = slot;
|
|
/* Check if available slot is of the same stream_id */
|
|
GST_LOG_OBJECT (dbin, "Checking candidate slot %d (active_stream:%p)",
|
|
slot->id, slot->active_stream);
|
|
if (stream_id && slot->active_stream) {
|
|
gchar *ostream_id =
|
|
(gchar *) gst_stream_get_stream_id (slot->active_stream);
|
|
GST_DEBUG_OBJECT (dbin, "Checking slot %d %s against %s", slot->id,
|
|
ostream_id, stream_id);
|
|
if (!g_strcmp0 (stream_id, ostream_id))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty_slot) {
|
|
GST_DEBUG_OBJECT (dbin, "Re-using existing unused slot %d", empty_slot->id);
|
|
empty_slot->input = input;
|
|
return empty_slot;
|
|
}
|
|
|
|
if (input_type)
|
|
return create_new_slot (dbin, input_type);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
link_input_to_slot (DecodebinInputStream * input, MultiQueueSlot * slot)
|
|
{
|
|
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;
|
|
}
|
|
|
|
#if 0
|
|
static gboolean
|
|
have_factory (GstDecodebin3 * dbin, GstCaps * caps,
|
|
GstElementFactoryListType ftype)
|
|
{
|
|
gboolean ret = FALSE;
|
|
GList *res;
|
|
|
|
g_mutex_lock (&dbin->factories_lock);
|
|
gst_decode_bin_update_factories_list (dbin);
|
|
if (ftype == GST_ELEMENT_FACTORY_TYPE_DECODER)
|
|
res =
|
|
gst_element_factory_list_filter (dbin->decoder_factories,
|
|
caps, GST_PAD_SINK, TRUE);
|
|
else
|
|
res =
|
|
gst_element_factory_list_filter (dbin->decodable_factories,
|
|
caps, GST_PAD_SINK, TRUE);
|
|
g_mutex_unlock (&dbin->factories_lock);
|
|
|
|
if (res) {
|
|
ret = TRUE;
|
|
gst_plugin_feature_list_free (res);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static GstElement *
|
|
create_element (GstDecodebin3 * dbin, GstStream * stream,
|
|
GstElementFactoryListType ftype)
|
|
{
|
|
GList *res;
|
|
GstElement *element = NULL;
|
|
GstCaps *caps;
|
|
|
|
g_mutex_lock (&dbin->factories_lock);
|
|
gst_decode_bin_update_factories_list (dbin);
|
|
caps = gst_stream_get_caps (stream);
|
|
if (ftype == GST_ELEMENT_FACTORY_TYPE_DECODER)
|
|
res =
|
|
gst_element_factory_list_filter (dbin->decoder_factories,
|
|
caps, GST_PAD_SINK, TRUE);
|
|
else
|
|
res =
|
|
gst_element_factory_list_filter (dbin->decodable_factories,
|
|
caps, GST_PAD_SINK, TRUE);
|
|
g_mutex_unlock (&dbin->factories_lock);
|
|
|
|
if (res) {
|
|
element =
|
|
gst_element_factory_create ((GstElementFactory *) res->data, NULL);
|
|
GST_DEBUG ("Created element '%s'", GST_ELEMENT_NAME (element));
|
|
gst_plugin_feature_list_free (res);
|
|
} else {
|
|
GST_DEBUG ("Could not find an element for caps %" GST_PTR_FORMAT, caps);
|
|
}
|
|
|
|
gst_caps_unref (caps);
|
|
return element;
|
|
}
|
|
|
|
/* FIXME : VERY NAIVE. ASSUMING FIRST ONE WILL WORK */
|
|
static GstElement *
|
|
create_decoder (GstDecodebin3 * dbin, GstStream * stream)
|
|
{
|
|
return create_element (dbin, stream, GST_ELEMENT_FACTORY_TYPE_DECODER);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
keyframe_waiter_probe (GstPad * pad, GstPadProbeInfo * info,
|
|
DecodebinOutputStream * output)
|
|
{
|
|
GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER (info);
|
|
/* If we have a keyframe, remove the probe and let all data through */
|
|
/* FIXME : HANDLE HEADER BUFFER ?? */
|
|
if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ||
|
|
GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_HEADER)) {
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Buffer is keyframe or header, letting through and removing probe");
|
|
output->drop_probe_id = 0;
|
|
return GST_PAD_PROBE_REMOVE;
|
|
}
|
|
GST_DEBUG_OBJECT (pad, "Buffer is not a keyframe, dropping");
|
|
return GST_PAD_PROBE_DROP;
|
|
}
|
|
|
|
static void
|
|
reconfigure_output_stream (DecodebinOutputStream * output,
|
|
MultiQueueSlot * slot)
|
|
{
|
|
GstDecodebin3 *dbin = output->dbin;
|
|
GstCaps *new_caps = (GstCaps *) gst_stream_get_caps (slot->active_stream);
|
|
gboolean needs_decoder;
|
|
|
|
needs_decoder = gst_caps_can_intersect (new_caps, dbin->caps) != TRUE;
|
|
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Reconfiguring output %p to slot %p, needs_decoder:%d", output, slot,
|
|
needs_decoder);
|
|
|
|
/* FIXME : Maybe make the output un-hook itself automatically ? */
|
|
if (output->slot != NULL && output->slot != slot) {
|
|
GST_WARNING_OBJECT (dbin,
|
|
"Output still linked to another slot (%p)", output->slot);
|
|
gst_caps_unref (new_caps);
|
|
return;
|
|
}
|
|
|
|
/* Check if existing config is reusable as-is by checking if
|
|
* the existing decoder accepts the new caps, if not delete
|
|
* it and create a new one */
|
|
if (output->decoder) {
|
|
gboolean can_reuse_decoder;
|
|
|
|
if (needs_decoder) {
|
|
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;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (dbin, "Removing old decoder for slot %p", slot);
|
|
|
|
if (output->linked)
|
|
gst_pad_unlink (slot->src_pad, output->decoder_sink);
|
|
output->linked = FALSE;
|
|
if (output->drop_probe_id) {
|
|
gst_pad_remove_probe (slot->src_pad, output->drop_probe_id);
|
|
output->drop_probe_id = 0;
|
|
}
|
|
|
|
if (!gst_ghost_pad_set_target ((GstGhostPad *) output->src_pad, NULL)) {
|
|
GST_ERROR_OBJECT (dbin, "Could not release decoder pad");
|
|
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;
|
|
}
|
|
|
|
gst_caps_unref (new_caps);
|
|
|
|
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) {
|
|
/* If we don't have a decoder yet, instantiate one */
|
|
output->decoder = create_decoder (dbin, slot->active_stream);
|
|
if (output->decoder == NULL) {
|
|
GstCaps *caps;
|
|
|
|
SELECTION_UNLOCK (dbin);
|
|
/* FIXME : Should we be smarter if there's a missing decoder ?
|
|
* Should we deactivate that stream ? */
|
|
caps = gst_stream_get_caps (slot->active_stream);
|
|
gst_element_post_message (GST_ELEMENT_CAST (dbin),
|
|
gst_missing_decoder_message_new (GST_ELEMENT_CAST (dbin), caps));
|
|
gst_caps_unref (caps);
|
|
SELECTION_LOCK (dbin);
|
|
goto cleanup;
|
|
}
|
|
if (!gst_bin_add ((GstBin *) dbin, output->decoder)) {
|
|
GST_ERROR_OBJECT (dbin, "could not add decoder to pipeline");
|
|
goto cleanup;
|
|
}
|
|
output->decoder_sink = gst_element_get_static_pad (output->decoder, "sink");
|
|
output->decoder_src = gst_element_get_static_pad (output->decoder, "src");
|
|
if (output->type & GST_STREAM_TYPE_VIDEO) {
|
|
GST_DEBUG_OBJECT (dbin, "Adding keyframe-waiter probe");
|
|
output->drop_probe_id =
|
|
gst_pad_add_probe (slot->src_pad, GST_PAD_PROBE_TYPE_BUFFER,
|
|
(GstPadProbeCallback) keyframe_waiter_probe, output, NULL);
|
|
}
|
|
if (gst_pad_link_full (slot->src_pad, output->decoder_sink,
|
|
GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK) {
|
|
GST_ERROR_OBJECT (dbin, "could not link to %s:%s",
|
|
GST_DEBUG_PAD_NAME (output->decoder_sink));
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
output->decoder_src = gst_object_ref (slot->src_pad);
|
|
output->decoder_sink = NULL;
|
|
}
|
|
output->linked = TRUE;
|
|
if (!gst_ghost_pad_set_target ((GstGhostPad *) output->src_pad,
|
|
output->decoder_src)) {
|
|
GST_ERROR_OBJECT (dbin, "Could not expose decoder pad");
|
|
goto cleanup;
|
|
}
|
|
if (output->src_exposed == FALSE) {
|
|
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;
|
|
|
|
cleanup:
|
|
{
|
|
GST_DEBUG_OBJECT (dbin, "Cleanup");
|
|
if (output->decoder_sink) {
|
|
gst_object_unref (output->decoder_sink);
|
|
output->decoder_sink = NULL;
|
|
}
|
|
if (output->decoder_src) {
|
|
gst_object_unref (output->decoder_src);
|
|
output->decoder_src = NULL;
|
|
}
|
|
if (output->decoder) {
|
|
gst_element_set_state (output->decoder, GST_STATE_NULL);
|
|
gst_bin_remove ((GstBin *) dbin, output->decoder);
|
|
output->decoder = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
idle_reconfigure (GstPad * pad, GstPadProbeInfo * info, MultiQueueSlot * slot)
|
|
{
|
|
GstMessage *msg = NULL;
|
|
DecodebinOutputStream *output;
|
|
|
|
SELECTION_LOCK (slot->dbin);
|
|
output = get_output_for_slot (slot);
|
|
|
|
GST_DEBUG_OBJECT (pad, "output : %p", output);
|
|
|
|
if (output) {
|
|
reconfigure_output_stream (output, slot);
|
|
msg = is_selection_done (slot->dbin);
|
|
}
|
|
SELECTION_UNLOCK (slot->dbin);
|
|
if (msg)
|
|
gst_element_post_message ((GstElement *) slot->dbin, msg);
|
|
|
|
return GST_PAD_PROBE_REMOVE;
|
|
}
|
|
|
|
static MultiQueueSlot *
|
|
find_slot_for_stream_id (GstDecodebin3 * dbin, const gchar * sid)
|
|
{
|
|
GList *tmp;
|
|
|
|
for (tmp = dbin->slots; tmp; tmp = tmp->next) {
|
|
MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
|
|
const gchar *stream_id;
|
|
if (slot->active_stream) {
|
|
stream_id = gst_stream_get_stream_id (slot->active_stream);
|
|
if (!g_strcmp0 (sid, stream_id))
|
|
return slot;
|
|
}
|
|
if (slot->pending_stream && slot->pending_stream != slot->active_stream) {
|
|
stream_id = gst_stream_get_stream_id (slot->pending_stream);
|
|
if (!g_strcmp0 (sid, stream_id))
|
|
return slot;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* This function handles the reassignment of a slot. Call this from
|
|
* the streaming thread of a slot. */
|
|
static gboolean
|
|
reassign_slot (GstDecodebin3 * dbin, MultiQueueSlot * slot)
|
|
{
|
|
DecodebinOutputStream *output;
|
|
MultiQueueSlot *target_slot = NULL;
|
|
GList *tmp;
|
|
const gchar *sid, *tsid;
|
|
|
|
SELECTION_LOCK (dbin);
|
|
output = slot->output;
|
|
|
|
if (G_UNLIKELY (slot->active_stream == NULL)) {
|
|
GST_DEBUG_OBJECT (slot->src_pad,
|
|
"Called on inactive slot (active_stream == NULL)");
|
|
SELECTION_UNLOCK (dbin);
|
|
return FALSE;
|
|
}
|
|
|
|
if (G_UNLIKELY (output == NULL)) {
|
|
GST_DEBUG_OBJECT (slot->src_pad,
|
|
"Slot doesn't have any output to be removed");
|
|
SELECTION_UNLOCK (dbin);
|
|
return FALSE;
|
|
}
|
|
|
|
sid = gst_stream_get_stream_id (slot->active_stream);
|
|
GST_DEBUG_OBJECT (slot->src_pad, "slot %s %p", sid, slot);
|
|
|
|
/* Recheck whether this stream is still in the list of streams to deactivate */
|
|
if (stream_in_list (dbin->requested_selection, sid)) {
|
|
/* Stream is in the list of requested streams, don't remove */
|
|
SELECTION_UNLOCK (dbin);
|
|
GST_DEBUG_OBJECT (slot->src_pad,
|
|
"Stream '%s' doesn't need to be deactivated", sid);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Unlink slot from output */
|
|
/* FIXME : Handle flushing ? */
|
|
/* FIXME : Handle outputs without decoders */
|
|
GST_DEBUG_OBJECT (slot->src_pad, "Unlinking from decoder %p",
|
|
output->decoder_sink);
|
|
if (output->decoder_sink)
|
|
gst_pad_unlink (slot->src_pad, output->decoder_sink);
|
|
output->linked = FALSE;
|
|
slot->output = NULL;
|
|
output->slot = NULL;
|
|
/* Remove sid from active selection */
|
|
for (tmp = dbin->active_selection; tmp; tmp = tmp->next)
|
|
if (!g_strcmp0 (sid, tmp->data)) {
|
|
dbin->active_selection = g_list_delete_link (dbin->active_selection, tmp);
|
|
break;
|
|
}
|
|
|
|
/* Can we re-assign this output to a requested stream ? */
|
|
GST_DEBUG_OBJECT (slot->src_pad, "Attempting to re-assing output stream");
|
|
for (tmp = dbin->to_activate; tmp; tmp = tmp->next) {
|
|
MultiQueueSlot *tslot = find_slot_for_stream_id (dbin, tmp->data);
|
|
GST_LOG_OBJECT (tslot->src_pad, "Checking slot %p (output:%p , stream:%s)",
|
|
tslot, tslot->output, gst_stream_get_stream_id (tslot->active_stream));
|
|
if (tslot && tslot->type == output->type && tslot->output == NULL) {
|
|
GST_DEBUG_OBJECT (tslot->src_pad, "Using as reassigned slot");
|
|
target_slot = tslot;
|
|
tsid = tmp->data;
|
|
/* Pass target stream id to requested selection */
|
|
dbin->requested_selection =
|
|
g_list_append (dbin->requested_selection, g_strdup (tmp->data));
|
|
dbin->to_activate = g_list_remove (dbin->to_activate, tmp->data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (target_slot) {
|
|
GST_DEBUG_OBJECT (slot->src_pad, "Assigning output to slot %p '%s'",
|
|
target_slot, tsid);
|
|
target_slot->output = output;
|
|
output->slot = target_slot;
|
|
dbin->active_selection =
|
|
g_list_append (dbin->active_selection, (gchar *) tsid);
|
|
SELECTION_UNLOCK (dbin);
|
|
|
|
/* Wakeup the target slot so that it retries to send events/buffers
|
|
* thereby triggering the output reconfiguration codepath */
|
|
gst_pad_add_probe (target_slot->src_pad, GST_PAD_PROBE_TYPE_IDLE,
|
|
(GstPadProbeCallback) idle_reconfigure, target_slot, NULL);
|
|
/* gst_pad_send_event (target_slot->src_pad, gst_event_new_reconfigure ()); */
|
|
} else {
|
|
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);
|
|
|
|
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)
|
|
{
|
|
GST_DEBUG_OBJECT (element, "event %s", GST_EVENT_TYPE_NAME (event));
|
|
if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) {
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) element;
|
|
GList *streams = NULL;
|
|
guint32 seqnum = gst_event_get_seqnum (event);
|
|
|
|
SELECTION_LOCK (dbin);
|
|
if (seqnum == dbin->select_streams_seqnum) {
|
|
SELECTION_UNLOCK (dbin);
|
|
GST_DEBUG_OBJECT (dbin,
|
|
"Already handled/handling that SELECT_STREAMS event");
|
|
return TRUE;
|
|
}
|
|
dbin->select_streams_seqnum = seqnum;
|
|
if (dbin->pending_select_streams != NULL) {
|
|
GST_LOG_OBJECT (dbin, "Replacing pending select streams");
|
|
g_list_free (dbin->pending_select_streams);
|
|
dbin->pending_select_streams = NULL;
|
|
}
|
|
gst_event_parse_select_streams (event, &streams);
|
|
dbin->pending_select_streams = g_list_copy (streams);
|
|
SELECTION_UNLOCK (dbin);
|
|
|
|
/* FIXME : We don't have an upstream ?? */
|
|
#if 0
|
|
/* Send event upstream */
|
|
if ((peer = gst_pad_get_peer (pad))) {
|
|
gst_pad_send_event (peer, event);
|
|
gst_object_unref (peer);
|
|
}
|
|
#endif
|
|
/* Finally handle the switch */
|
|
if (streams) {
|
|
handle_stream_switch (dbin, streams, seqnum);
|
|
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);
|
|
gst_ghost_pad_set_target ((GstGhostPad *) output->src_pad, NULL);
|
|
gst_object_replace ((GstObject **) & output->decoder_src, NULL);
|
|
if (output->src_exposed) {
|
|
gst_element_remove_pad ((GstElement *) dbin, output->src_pad);
|
|
}
|
|
if (output->decoder) {
|
|
gst_element_set_locked_state (output->decoder, TRUE);
|
|
gst_element_set_state (output->decoder, GST_STATE_NULL);
|
|
gst_bin_remove ((GstBin *) dbin, output->decoder);
|
|
}
|
|
g_free (output);
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_decodebin3_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstDecodebin3 *dbin = (GstDecodebin3 *) element;
|
|
GstStateChangeReturn ret;
|
|
|
|
/* Upwards */
|
|
switch (transition) {
|
|
default:
|
|
break;
|
|
}
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
if (ret == GST_STATE_CHANGE_FAILURE)
|
|
goto beach;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
{
|
|
GList *tmp;
|
|
|
|
/* Free output streams */
|
|
for (tmp = dbin->output_streams; tmp; tmp = tmp->next) {
|
|
DecodebinOutputStream *output = (DecodebinOutputStream *) tmp->data;
|
|
free_output_stream (dbin, output);
|
|
}
|
|
g_list_free (dbin->output_streams);
|
|
dbin->output_streams = NULL;
|
|
/* Free multiqueue slots */
|
|
for (tmp = dbin->slots; tmp; tmp = tmp->next) {
|
|
MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
|
|
free_multiqueue_slot (dbin, slot);
|
|
}
|
|
g_list_free (dbin->slots);
|
|
dbin->slots = NULL;
|
|
dbin->current_group_id = GST_GROUP_ID_INVALID;
|
|
/* Free inputs */
|
|
/* Reset the main input group id since it will get a new id on a new stream */
|
|
dbin->main_input->group_id = GST_GROUP_ID_INVALID;
|
|
/* 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;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
beach:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gst_decodebin3_plugin_init (GstPlugin * plugin)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (decodebin3_debug, "decodebin3", 0, "decoder bin");
|
|
|
|
return gst_element_register (plugin, "decodebin3", GST_RANK_NONE,
|
|
GST_TYPE_DECODEBIN3);
|
|
}
|