mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 01:00:37 +00:00
15123c0455
The caps put into the stream topology by decodebin are the caps at the moment the pads are exposed on it. This is usually before decoders received any buffers. In discoverer we however wait for pre-roll, which ensures that each decoder handled buffers already. At this point, there might be more information known about the caps already that we could make use of. One example here is extra information stored in the SEI of H264, like the multiview-mode. This will be known if there is a SEI before the first keyframe, but decodebin won't put this into the topology as it only waits for the initial caps of h264parse (which come directly after SPS/PPS). With this change, the multiview-mode is in the caps reported by discoverer in many cases.
2338 lines
68 KiB
C
2338 lines
68 KiB
C
/* GStreamer
|
|
* Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
|
|
* 2009 Nokia Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:gstdiscoverer
|
|
* @short_description: Utility for discovering information on URIs.
|
|
*
|
|
* The #GstDiscoverer is a utility object which allows to get as much
|
|
* information as possible from one or many URIs.
|
|
*
|
|
* It provides two APIs, allowing usage in blocking or non-blocking mode.
|
|
*
|
|
* The blocking mode just requires calling gst_discoverer_discover_uri()
|
|
* with the URI one wishes to discover.
|
|
*
|
|
* The non-blocking mode requires a running #GMainLoop iterating a
|
|
* #GMainContext, where one connects to the various signals, appends the
|
|
* URIs to be processed (through gst_discoverer_discover_uri_async()) and then
|
|
* asks for the discovery to begin (through gst_discoverer_start()).
|
|
* By default this will use the GLib default main context unless you have
|
|
* set a custom context using g_main_context_push_thread_default().
|
|
*
|
|
* All the information is returned in a #GstDiscovererInfo structure.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <gst/video/video.h>
|
|
#include <gst/audio/audio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "pbutils.h"
|
|
#include "pbutils-private.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (discoverer_debug);
|
|
#define GST_CAT_DEFAULT discoverer_debug
|
|
|
|
static GQuark _CAPS_QUARK;
|
|
static GQuark _TAGS_QUARK;
|
|
static GQuark _ELEMENT_SRCPAD_QUARK;
|
|
static GQuark _TOC_QUARK;
|
|
static GQuark _STREAM_ID_QUARK;
|
|
static GQuark _MISSING_PLUGIN_QUARK;
|
|
static GQuark _STREAM_TOPOLOGY_QUARK;
|
|
static GQuark _TOPOLOGY_PAD_QUARK;
|
|
|
|
|
|
typedef struct
|
|
{
|
|
GstDiscoverer *dc;
|
|
GstPad *pad;
|
|
GstElement *queue;
|
|
GstElement *sink;
|
|
GstTagList *tags;
|
|
GstToc *toc;
|
|
gchar *stream_id;
|
|
} PrivateStream;
|
|
|
|
struct _GstDiscovererPrivate
|
|
{
|
|
gboolean async;
|
|
|
|
/* allowed time to discover each uri in nanoseconds */
|
|
GstClockTime timeout;
|
|
|
|
/* list of pending URI to process (current excluded) */
|
|
GList *pending_uris;
|
|
|
|
GMutex lock;
|
|
|
|
/* TRUE if processing a URI */
|
|
gboolean processing;
|
|
|
|
/* TRUE if discoverer has been started */
|
|
gboolean running;
|
|
|
|
/* TRUE if ASYNC_DONE has been received (need to check for subtitle tags) */
|
|
gboolean async_done;
|
|
|
|
/* current items */
|
|
GstDiscovererInfo *current_info;
|
|
GError *current_error;
|
|
GstStructure *current_topology;
|
|
|
|
/* List of private streams */
|
|
GList *streams;
|
|
|
|
/* List of these sinks and their handler IDs (to remove the probe) */
|
|
guint pending_subtitle_pads;
|
|
|
|
/* Global elements */
|
|
GstBin *pipeline;
|
|
GstElement *uridecodebin;
|
|
GstBus *bus;
|
|
|
|
GType decodebin_type;
|
|
|
|
/* Custom main context variables */
|
|
GMainContext *ctx;
|
|
guint sourceid;
|
|
guint timeoutid;
|
|
|
|
/* reusable queries */
|
|
GstQuery *seeking_query;
|
|
|
|
/* Handler ids for various callbacks */
|
|
gulong pad_added_id;
|
|
gulong pad_remove_id;
|
|
gulong source_chg_id;
|
|
gulong element_added_id;
|
|
gulong bus_cb_id;
|
|
};
|
|
|
|
#define DISCO_LOCK(dc) g_mutex_lock (&dc->priv->lock);
|
|
#define DISCO_UNLOCK(dc) g_mutex_unlock (&dc->priv->lock);
|
|
|
|
static void
|
|
_do_init (void)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (discoverer_debug, "discoverer", 0, "Discoverer");
|
|
|
|
_CAPS_QUARK = g_quark_from_static_string ("caps");
|
|
_ELEMENT_SRCPAD_QUARK = g_quark_from_static_string ("element-srcpad");
|
|
_TAGS_QUARK = g_quark_from_static_string ("tags");
|
|
_TOC_QUARK = g_quark_from_static_string ("toc");
|
|
_STREAM_ID_QUARK = g_quark_from_static_string ("stream-id");
|
|
_MISSING_PLUGIN_QUARK = g_quark_from_static_string ("missing-plugin");
|
|
_STREAM_TOPOLOGY_QUARK = g_quark_from_static_string ("stream-topology");
|
|
_TOPOLOGY_PAD_QUARK = g_quark_from_static_string ("pad");
|
|
};
|
|
|
|
G_DEFINE_TYPE_EXTENDED (GstDiscoverer, gst_discoverer, G_TYPE_OBJECT, 0,
|
|
_do_init ());
|
|
|
|
enum
|
|
{
|
|
SIGNAL_FINISHED,
|
|
SIGNAL_STARTING,
|
|
SIGNAL_DISCOVERED,
|
|
SIGNAL_SOURCE_SETUP,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
#define DEFAULT_PROP_TIMEOUT 15 * GST_SECOND
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_TIMEOUT
|
|
};
|
|
|
|
static guint gst_discoverer_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static void gst_discoverer_set_timeout (GstDiscoverer * dc,
|
|
GstClockTime timeout);
|
|
static gboolean async_timeout_cb (GstDiscoverer * dc);
|
|
|
|
static void discoverer_bus_cb (GstBus * bus, GstMessage * msg,
|
|
GstDiscoverer * dc);
|
|
static void uridecodebin_pad_added_cb (GstElement * uridecodebin, GstPad * pad,
|
|
GstDiscoverer * dc);
|
|
static void uridecodebin_pad_removed_cb (GstElement * uridecodebin,
|
|
GstPad * pad, GstDiscoverer * dc);
|
|
static void uridecodebin_source_changed_cb (GstElement * uridecodebin,
|
|
GParamSpec * pspec, GstDiscoverer * dc);
|
|
|
|
static void gst_discoverer_dispose (GObject * dc);
|
|
static void gst_discoverer_finalize (GObject * dc);
|
|
static void gst_discoverer_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_discoverer_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static void
|
|
gst_discoverer_class_init (GstDiscovererClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
|
|
gobject_class->dispose = gst_discoverer_dispose;
|
|
gobject_class->finalize = gst_discoverer_finalize;
|
|
|
|
gobject_class->set_property = gst_discoverer_set_property;
|
|
gobject_class->get_property = gst_discoverer_get_property;
|
|
|
|
g_type_class_add_private (klass, sizeof (GstDiscovererPrivate));
|
|
|
|
/* properties */
|
|
/**
|
|
* GstDiscoverer:timeout:
|
|
*
|
|
* The duration (in nanoseconds) after which the discovery of an individual
|
|
* URI will timeout.
|
|
*
|
|
* If the discovery of a URI times out, the %GST_DISCOVERER_TIMEOUT will be
|
|
* set on the result flags.
|
|
*/
|
|
g_object_class_install_property (gobject_class, PROP_TIMEOUT,
|
|
g_param_spec_uint64 ("timeout", "timeout", "Timeout",
|
|
GST_SECOND, 3600 * GST_SECOND, DEFAULT_PROP_TIMEOUT,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
|
|
|
/* signals */
|
|
/**
|
|
* GstDiscoverer::finished:
|
|
* @discoverer: the #GstDiscoverer
|
|
*
|
|
* Will be emitted in async mode when all pending URIs have been processed.
|
|
*/
|
|
gst_discoverer_signals[SIGNAL_FINISHED] =
|
|
g_signal_new ("finished", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GstDiscovererClass, finished),
|
|
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE);
|
|
|
|
/**
|
|
* GstDiscoverer::starting:
|
|
* @discoverer: the #GstDiscoverer
|
|
*
|
|
* Will be emitted when the discover starts analyzing the pending URIs
|
|
*/
|
|
gst_discoverer_signals[SIGNAL_STARTING] =
|
|
g_signal_new ("starting", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GstDiscovererClass, starting),
|
|
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE);
|
|
|
|
/**
|
|
* GstDiscoverer::discovered:
|
|
* @discoverer: the #GstDiscoverer
|
|
* @info: the results #GstDiscovererInfo
|
|
* @error: (type GLib.Error): #GError, which will be non-NULL if an error
|
|
* occurred during discovery. You must not
|
|
* free this #GError, it will be freed by
|
|
* the discoverer.
|
|
*
|
|
* Will be emitted in async mode when all information on a URI could be
|
|
* discovered, or an error occurred.
|
|
*
|
|
* When an error occurs, @info might still contain some partial information,
|
|
* depending on the circumstances of the error.
|
|
*/
|
|
gst_discoverer_signals[SIGNAL_DISCOVERED] =
|
|
g_signal_new ("discovered", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GstDiscovererClass, discovered),
|
|
NULL, NULL, g_cclosure_marshal_generic,
|
|
G_TYPE_NONE, 2, GST_TYPE_DISCOVERER_INFO,
|
|
G_TYPE_ERROR | G_SIGNAL_TYPE_STATIC_SCOPE);
|
|
|
|
/**
|
|
* GstDiscoverer::source-setup:
|
|
* @discoverer: the #GstDiscoverer
|
|
* @source: source element
|
|
*
|
|
* This signal is emitted after the source element has been created for, so
|
|
* the URI being discovered, so it can be configured by setting additional
|
|
* properties (e.g. set a proxy server for an http source, or set the device
|
|
* and read speed for an audio cd source).
|
|
*
|
|
* This signal is usually emitted from the context of a GStreamer streaming
|
|
* thread.
|
|
*/
|
|
gst_discoverer_signals[SIGNAL_SOURCE_SETUP] =
|
|
g_signal_new ("source-setup", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstDiscovererClass, source_setup),
|
|
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
|
|
}
|
|
|
|
static void
|
|
uridecodebin_element_added_cb (GstElement * uridecodebin,
|
|
GstElement * child, GstDiscoverer * dc)
|
|
{
|
|
GST_DEBUG ("New element added to uridecodebin : %s",
|
|
GST_ELEMENT_NAME (child));
|
|
|
|
if (G_OBJECT_TYPE (child) == dc->priv->decodebin_type) {
|
|
g_object_set (child, "post-stream-topology", TRUE, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_discoverer_init (GstDiscoverer * dc)
|
|
{
|
|
GstElement *tmp;
|
|
GstFormat format = GST_FORMAT_TIME;
|
|
|
|
dc->priv = G_TYPE_INSTANCE_GET_PRIVATE (dc, GST_TYPE_DISCOVERER,
|
|
GstDiscovererPrivate);
|
|
|
|
dc->priv->timeout = DEFAULT_PROP_TIMEOUT;
|
|
dc->priv->async = FALSE;
|
|
dc->priv->async_done = FALSE;
|
|
|
|
g_mutex_init (&dc->priv->lock);
|
|
|
|
dc->priv->pending_subtitle_pads = 0;
|
|
|
|
GST_LOG ("Creating pipeline");
|
|
dc->priv->pipeline = (GstBin *) gst_pipeline_new ("Discoverer");
|
|
GST_LOG_OBJECT (dc, "Creating uridecodebin");
|
|
dc->priv->uridecodebin =
|
|
gst_element_factory_make ("uridecodebin", "discoverer-uri");
|
|
if (G_UNLIKELY (dc->priv->uridecodebin == NULL)) {
|
|
GST_ERROR ("Can't create uridecodebin");
|
|
return;
|
|
}
|
|
GST_LOG_OBJECT (dc, "Adding uridecodebin to pipeline");
|
|
gst_bin_add (dc->priv->pipeline, dc->priv->uridecodebin);
|
|
|
|
dc->priv->pad_added_id =
|
|
g_signal_connect_object (dc->priv->uridecodebin, "pad-added",
|
|
G_CALLBACK (uridecodebin_pad_added_cb), dc, 0);
|
|
dc->priv->pad_remove_id =
|
|
g_signal_connect_object (dc->priv->uridecodebin, "pad-removed",
|
|
G_CALLBACK (uridecodebin_pad_removed_cb), dc, 0);
|
|
dc->priv->source_chg_id =
|
|
g_signal_connect_object (dc->priv->uridecodebin, "notify::source",
|
|
G_CALLBACK (uridecodebin_source_changed_cb), dc, 0);
|
|
|
|
GST_LOG_OBJECT (dc, "Getting pipeline bus");
|
|
dc->priv->bus = gst_pipeline_get_bus ((GstPipeline *) dc->priv->pipeline);
|
|
|
|
dc->priv->bus_cb_id =
|
|
g_signal_connect_object (dc->priv->bus, "message",
|
|
G_CALLBACK (discoverer_bus_cb), dc, 0);
|
|
|
|
GST_DEBUG_OBJECT (dc, "Done initializing Discoverer");
|
|
|
|
/* This is ugly. We get the GType of decodebin so we can quickly detect
|
|
* when a decodebin is added to uridecodebin so we can set the
|
|
* post-stream-topology setting to TRUE */
|
|
dc->priv->element_added_id =
|
|
g_signal_connect_object (dc->priv->uridecodebin, "element-added",
|
|
G_CALLBACK (uridecodebin_element_added_cb), dc, 0);
|
|
tmp = gst_element_factory_make ("decodebin", NULL);
|
|
dc->priv->decodebin_type = G_OBJECT_TYPE (tmp);
|
|
gst_object_unref (tmp);
|
|
|
|
/* create queries */
|
|
dc->priv->seeking_query = gst_query_new_seeking (format);
|
|
}
|
|
|
|
static void
|
|
discoverer_reset (GstDiscoverer * dc)
|
|
{
|
|
GST_DEBUG_OBJECT (dc, "Resetting");
|
|
|
|
if (dc->priv->pending_uris) {
|
|
g_list_foreach (dc->priv->pending_uris, (GFunc) g_free, NULL);
|
|
g_list_free (dc->priv->pending_uris);
|
|
dc->priv->pending_uris = NULL;
|
|
}
|
|
|
|
if (dc->priv->pipeline)
|
|
gst_element_set_state ((GstElement *) dc->priv->pipeline, GST_STATE_NULL);
|
|
}
|
|
|
|
#define DISCONNECT_SIGNAL(o,i) G_STMT_START{ \
|
|
if ((i) && g_signal_handler_is_connected ((o), (i))) \
|
|
g_signal_handler_disconnect ((o), (i)); \
|
|
(i) = 0; \
|
|
}G_STMT_END
|
|
|
|
static void
|
|
gst_discoverer_dispose (GObject * obj)
|
|
{
|
|
GstDiscoverer *dc = (GstDiscoverer *) obj;
|
|
|
|
GST_DEBUG_OBJECT (dc, "Disposing");
|
|
|
|
discoverer_reset (dc);
|
|
|
|
if (G_LIKELY (dc->priv->pipeline)) {
|
|
/* Workaround for bug #118536 */
|
|
DISCONNECT_SIGNAL (dc->priv->uridecodebin, dc->priv->pad_added_id);
|
|
DISCONNECT_SIGNAL (dc->priv->uridecodebin, dc->priv->pad_remove_id);
|
|
DISCONNECT_SIGNAL (dc->priv->uridecodebin, dc->priv->source_chg_id);
|
|
DISCONNECT_SIGNAL (dc->priv->uridecodebin, dc->priv->element_added_id);
|
|
DISCONNECT_SIGNAL (dc->priv->bus, dc->priv->bus_cb_id);
|
|
|
|
/* pipeline was set to NULL in _reset */
|
|
gst_object_unref (dc->priv->pipeline);
|
|
if (dc->priv->bus)
|
|
gst_object_unref (dc->priv->bus);
|
|
|
|
dc->priv->pipeline = NULL;
|
|
dc->priv->uridecodebin = NULL;
|
|
dc->priv->bus = NULL;
|
|
}
|
|
|
|
gst_discoverer_stop (dc);
|
|
|
|
if (dc->priv->seeking_query) {
|
|
gst_query_unref (dc->priv->seeking_query);
|
|
dc->priv->seeking_query = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (gst_discoverer_parent_class)->dispose (obj);
|
|
}
|
|
|
|
static void
|
|
gst_discoverer_finalize (GObject * obj)
|
|
{
|
|
GstDiscoverer *dc = (GstDiscoverer *) obj;
|
|
|
|
g_mutex_clear (&dc->priv->lock);
|
|
|
|
G_OBJECT_CLASS (gst_discoverer_parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
gst_discoverer_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstDiscoverer *dc = (GstDiscoverer *) object;
|
|
|
|
switch (prop_id) {
|
|
case PROP_TIMEOUT:
|
|
gst_discoverer_set_timeout (dc, g_value_get_uint64 (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_discoverer_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstDiscoverer *dc = (GstDiscoverer *) object;
|
|
|
|
switch (prop_id) {
|
|
case PROP_TIMEOUT:
|
|
DISCO_LOCK (dc);
|
|
g_value_set_uint64 (value, dc->priv->timeout);
|
|
DISCO_UNLOCK (dc);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_discoverer_set_timeout (GstDiscoverer * dc, GstClockTime timeout)
|
|
{
|
|
GST_DEBUG_OBJECT (dc, "timeout : %" GST_TIME_FORMAT, GST_TIME_ARGS (timeout));
|
|
|
|
/* FIXME : update current pending timeout if we're running */
|
|
DISCO_LOCK (dc);
|
|
dc->priv->timeout = timeout;
|
|
DISCO_UNLOCK (dc);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
_event_probe (GstPad * pad, GstPadProbeInfo * info, PrivateStream * ps)
|
|
{
|
|
GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_TAG:{
|
|
GstTagList *tl = NULL, *tmp;
|
|
|
|
gst_event_parse_tag (event, &tl);
|
|
GST_DEBUG_OBJECT (pad, "tags %" GST_PTR_FORMAT, tl);
|
|
DISCO_LOCK (ps->dc);
|
|
/* If preroll is complete, drop these tags - the collected information is
|
|
* possibly already being processed and adding more tags would be racy */
|
|
if (G_LIKELY (ps->dc->priv->processing)) {
|
|
GST_DEBUG_OBJECT (pad, "private stream %p old tags %" GST_PTR_FORMAT,
|
|
ps, ps->tags);
|
|
tmp = gst_tag_list_merge (ps->tags, tl, GST_TAG_MERGE_APPEND);
|
|
if (ps->tags)
|
|
gst_tag_list_unref (ps->tags);
|
|
ps->tags = tmp;
|
|
GST_DEBUG_OBJECT (pad, "private stream %p new tags %" GST_PTR_FORMAT,
|
|
ps, tmp);
|
|
} else
|
|
GST_DEBUG_OBJECT (pad, "Dropping tags since preroll is done");
|
|
DISCO_UNLOCK (ps->dc);
|
|
break;
|
|
}
|
|
case GST_EVENT_TOC:{
|
|
GstToc *tmp;
|
|
|
|
gst_event_parse_toc (event, &tmp, NULL);
|
|
GST_DEBUG_OBJECT (pad, "toc %" GST_PTR_FORMAT, tmp);
|
|
DISCO_LOCK (ps->dc);
|
|
ps->toc = tmp;
|
|
if (G_LIKELY (ps->dc->priv->processing)) {
|
|
GST_DEBUG_OBJECT (pad, "private stream %p toc %" GST_PTR_FORMAT, ps,
|
|
tmp);
|
|
} else
|
|
GST_DEBUG_OBJECT (pad, "Dropping toc since preroll is done");
|
|
DISCO_UNLOCK (ps->dc);
|
|
break;
|
|
}
|
|
case GST_EVENT_STREAM_START:{
|
|
const gchar *stream_id;
|
|
|
|
gst_event_parse_stream_start (event, &stream_id);
|
|
|
|
g_free (ps->stream_id);
|
|
ps->stream_id = stream_id ? g_strdup (stream_id) : NULL;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static GstStaticCaps subtitle_caps = GST_STATIC_CAPS ("text/x-raw; "
|
|
"subpicture/x-pgs; subpicture/x-dvb; subpicture/x-dvd; "
|
|
"application/x-subtitle-unknown; application/x-ssa; application/x-ass; "
|
|
"subtitle/x-kate; application/x-kate; subpicture/x-xsub");
|
|
|
|
static gboolean
|
|
is_subtitle_caps (const GstCaps * caps)
|
|
{
|
|
GstCaps *subs_caps;
|
|
gboolean ret;
|
|
|
|
subs_caps = gst_static_caps_get (&subtitle_caps);
|
|
ret = gst_caps_can_intersect (caps, subs_caps);
|
|
gst_caps_unref (subs_caps);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
got_subtitle_data (GstPad * pad, GstPadProbeInfo * info, GstDiscoverer * dc)
|
|
{
|
|
|
|
if (!(GST_IS_BUFFER (info->data) || (GST_IS_EVENT (info->data)
|
|
&& (GST_EVENT_TYPE ((GstEvent *) info->data) == GST_EVENT_GAP
|
|
|| GST_EVENT_TYPE ((GstEvent *) info->data) ==
|
|
GST_EVENT_EOS))))
|
|
return GST_PAD_PROBE_OK;
|
|
|
|
|
|
DISCO_LOCK (dc);
|
|
|
|
dc->priv->pending_subtitle_pads--;
|
|
|
|
if (dc->priv->pending_subtitle_pads == 0) {
|
|
GstMessage *msg = gst_message_new_application (NULL,
|
|
gst_structure_new_empty ("DiscovererDone"));
|
|
gst_element_post_message ((GstElement *) dc->priv->pipeline, msg);
|
|
}
|
|
DISCO_UNLOCK (dc);
|
|
|
|
return GST_PAD_PROBE_REMOVE;
|
|
|
|
}
|
|
|
|
static void
|
|
uridecodebin_source_changed_cb (GstElement * uridecodebin,
|
|
GParamSpec * pspec, GstDiscoverer * dc)
|
|
{
|
|
GstElement *src;
|
|
/* get a handle to the source */
|
|
g_object_get (uridecodebin, pspec->name, &src, NULL);
|
|
|
|
GST_DEBUG_OBJECT (dc, "got a new source %p", src);
|
|
|
|
g_signal_emit (dc, gst_discoverer_signals[SIGNAL_SOURCE_SETUP], 0, src);
|
|
gst_object_unref (src);
|
|
}
|
|
|
|
static void
|
|
uridecodebin_pad_added_cb (GstElement * uridecodebin, GstPad * pad,
|
|
GstDiscoverer * dc)
|
|
{
|
|
PrivateStream *ps;
|
|
GstPad *sinkpad = NULL;
|
|
GstCaps *caps;
|
|
|
|
GST_DEBUG_OBJECT (dc, "pad %s:%s", GST_DEBUG_PAD_NAME (pad));
|
|
|
|
ps = g_slice_new0 (PrivateStream);
|
|
|
|
ps->dc = dc;
|
|
ps->pad = pad;
|
|
ps->queue = gst_element_factory_make ("queue", NULL);
|
|
ps->sink = gst_element_factory_make ("fakesink", NULL);
|
|
|
|
if (G_UNLIKELY (ps->queue == NULL || ps->sink == NULL))
|
|
goto error;
|
|
|
|
g_object_set (ps->sink, "silent", TRUE, NULL);
|
|
g_object_set (ps->queue, "max-size-buffers", 1, "silent", TRUE, NULL);
|
|
|
|
caps = gst_pad_query_caps (pad, NULL);
|
|
|
|
sinkpad = gst_element_get_static_pad (ps->queue, "sink");
|
|
if (sinkpad == NULL)
|
|
goto error;
|
|
|
|
if (is_subtitle_caps (caps)) {
|
|
/* Subtitle streams are sparse and may not provide any information - don't
|
|
* wait for data to preroll */
|
|
gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
|
|
(GstPadProbeCallback) got_subtitle_data, dc, NULL);
|
|
g_object_set (ps->sink, "async", FALSE, NULL);
|
|
DISCO_LOCK (dc);
|
|
dc->priv->pending_subtitle_pads++;
|
|
DISCO_UNLOCK (dc);
|
|
}
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
gst_bin_add_many (dc->priv->pipeline, ps->queue, ps->sink, NULL);
|
|
|
|
if (!gst_element_link_pads_full (ps->queue, "src", ps->sink, "sink",
|
|
GST_PAD_LINK_CHECK_NOTHING))
|
|
goto error;
|
|
if (!gst_element_sync_state_with_parent (ps->sink))
|
|
goto error;
|
|
if (!gst_element_sync_state_with_parent (ps->queue))
|
|
goto error;
|
|
|
|
if (gst_pad_link_full (pad, sinkpad,
|
|
GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)
|
|
goto error;
|
|
gst_object_unref (sinkpad);
|
|
|
|
/* Add an event probe */
|
|
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
|
|
(GstPadProbeCallback) _event_probe, ps, NULL);
|
|
|
|
DISCO_LOCK (dc);
|
|
dc->priv->streams = g_list_append (dc->priv->streams, ps);
|
|
DISCO_UNLOCK (dc);
|
|
|
|
GST_DEBUG_OBJECT (dc, "Done handling pad");
|
|
|
|
return;
|
|
|
|
error:
|
|
GST_ERROR_OBJECT (dc, "Error while handling pad");
|
|
if (sinkpad)
|
|
gst_object_unref (sinkpad);
|
|
if (ps->queue)
|
|
gst_object_unref (ps->queue);
|
|
if (ps->sink)
|
|
gst_object_unref (ps->sink);
|
|
g_slice_free (PrivateStream, ps);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
uridecodebin_pad_removed_cb (GstElement * uridecodebin, GstPad * pad,
|
|
GstDiscoverer * dc)
|
|
{
|
|
GList *tmp;
|
|
PrivateStream *ps;
|
|
GstPad *sinkpad;
|
|
|
|
GST_DEBUG_OBJECT (dc, "pad %s:%s", GST_DEBUG_PAD_NAME (pad));
|
|
|
|
/* Find the PrivateStream */
|
|
DISCO_LOCK (dc);
|
|
for (tmp = dc->priv->streams; tmp; tmp = tmp->next) {
|
|
ps = (PrivateStream *) tmp->data;
|
|
if (ps->pad == pad)
|
|
break;
|
|
}
|
|
|
|
if (tmp == NULL) {
|
|
DISCO_UNLOCK (dc);
|
|
GST_DEBUG ("The removed pad wasn't controlled by us !");
|
|
return;
|
|
}
|
|
|
|
dc->priv->streams = g_list_delete_link (dc->priv->streams, tmp);
|
|
DISCO_UNLOCK (dc);
|
|
|
|
gst_element_set_state (ps->sink, GST_STATE_NULL);
|
|
gst_element_set_state (ps->queue, GST_STATE_NULL);
|
|
gst_element_unlink (ps->queue, ps->sink);
|
|
|
|
sinkpad = gst_element_get_static_pad (ps->queue, "sink");
|
|
gst_pad_unlink (pad, sinkpad);
|
|
gst_object_unref (sinkpad);
|
|
|
|
/* references removed here */
|
|
gst_bin_remove_many (dc->priv->pipeline, ps->sink, ps->queue, NULL);
|
|
|
|
if (ps->tags) {
|
|
gst_tag_list_unref (ps->tags);
|
|
}
|
|
if (ps->toc) {
|
|
gst_toc_unref (ps->toc);
|
|
}
|
|
g_free (ps->stream_id);
|
|
|
|
g_slice_free (PrivateStream, ps);
|
|
|
|
GST_DEBUG ("Done handling pad");
|
|
}
|
|
|
|
static GstStructure *
|
|
collect_stream_information (GstDiscoverer * dc, PrivateStream * ps, guint idx)
|
|
{
|
|
GstCaps *caps;
|
|
GstStructure *st;
|
|
gchar *stname;
|
|
|
|
stname = g_strdup_printf ("stream-%02d", idx);
|
|
st = gst_structure_new_empty (stname);
|
|
g_free (stname);
|
|
|
|
/* Get caps */
|
|
caps = gst_pad_get_current_caps (ps->pad);
|
|
if (!caps) {
|
|
GST_WARNING ("Couldn't get negotiated caps from %s:%s",
|
|
GST_DEBUG_PAD_NAME (ps->pad));
|
|
caps = gst_pad_query_caps (ps->pad, NULL);
|
|
}
|
|
if (caps) {
|
|
GST_DEBUG ("stream-%02d, got caps %" GST_PTR_FORMAT, idx, caps);
|
|
gst_structure_id_set (st, _CAPS_QUARK, GST_TYPE_CAPS, caps, NULL);
|
|
gst_caps_unref (caps);
|
|
}
|
|
if (ps->tags)
|
|
gst_structure_id_set (st, _TAGS_QUARK, GST_TYPE_TAG_LIST, ps->tags, NULL);
|
|
if (ps->toc)
|
|
gst_structure_id_set (st, _TOC_QUARK, GST_TYPE_TOC, ps->toc, NULL);
|
|
if (ps->stream_id)
|
|
gst_structure_id_set (st, _STREAM_ID_QUARK, G_TYPE_STRING, ps->stream_id,
|
|
NULL);
|
|
|
|
return st;
|
|
}
|
|
|
|
/* takes ownership of new_tags, may replace *taglist with a new one */
|
|
static void
|
|
gst_discoverer_merge_and_replace_tags (GstTagList ** taglist,
|
|
GstTagList * new_tags)
|
|
{
|
|
if (new_tags == NULL)
|
|
return;
|
|
|
|
if (*taglist == NULL) {
|
|
*taglist = new_tags;
|
|
return;
|
|
}
|
|
|
|
gst_tag_list_insert (*taglist, new_tags, GST_TAG_MERGE_REPLACE);
|
|
gst_tag_list_unref (new_tags);
|
|
}
|
|
|
|
static void
|
|
collect_common_information (GstDiscovererStreamInfo * info,
|
|
const GstStructure * st)
|
|
{
|
|
if (gst_structure_id_has_field (st, _TOC_QUARK)) {
|
|
gst_structure_id_get (st, _TOC_QUARK, GST_TYPE_TOC, &info->toc, NULL);
|
|
}
|
|
|
|
if (gst_structure_id_has_field (st, _STREAM_ID_QUARK)) {
|
|
gst_structure_id_get (st, _STREAM_ID_QUARK, G_TYPE_STRING, &info->stream_id,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
static GstDiscovererStreamInfo *
|
|
make_info (GstDiscovererStreamInfo * parent, GType type, GstCaps * caps)
|
|
{
|
|
GstDiscovererStreamInfo *info;
|
|
|
|
if (parent)
|
|
info = gst_discoverer_stream_info_ref (parent);
|
|
else {
|
|
info = g_object_new (type, NULL);
|
|
if (caps)
|
|
info->caps = gst_caps_ref (caps);
|
|
}
|
|
return info;
|
|
}
|
|
|
|
/* Parses a set of caps and tags in st and populates a GstDiscovererStreamInfo
|
|
* structure (parent, if !NULL, otherwise it allocates one)
|
|
*/
|
|
static GstDiscovererStreamInfo *
|
|
collect_information (GstDiscoverer * dc, const GstStructure * st,
|
|
GstDiscovererStreamInfo * parent)
|
|
{
|
|
GstPad *srcpad;
|
|
GstCaps *caps = NULL;
|
|
GstStructure *caps_st;
|
|
GstTagList *tags_st;
|
|
const gchar *name;
|
|
gint tmp, tmp2;
|
|
guint utmp;
|
|
|
|
if (!st || (!gst_structure_id_has_field (st, _CAPS_QUARK)
|
|
&& !gst_structure_id_has_field (st, _ELEMENT_SRCPAD_QUARK))) {
|
|
GST_WARNING ("Couldn't find caps !");
|
|
return make_info (parent, GST_TYPE_DISCOVERER_STREAM_INFO, NULL);
|
|
}
|
|
|
|
if (gst_structure_id_get (st, _ELEMENT_SRCPAD_QUARK, GST_TYPE_PAD, &srcpad,
|
|
NULL)) {
|
|
caps = gst_pad_get_current_caps (srcpad);
|
|
gst_object_unref (srcpad);
|
|
}
|
|
if (!caps) {
|
|
gst_structure_id_get (st, _CAPS_QUARK, GST_TYPE_CAPS, &caps, NULL);
|
|
}
|
|
|
|
if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
|
|
GST_WARNING ("Couldn't find caps !");
|
|
if (caps)
|
|
gst_caps_unref (caps);
|
|
return make_info (parent, GST_TYPE_DISCOVERER_STREAM_INFO, NULL);
|
|
}
|
|
|
|
caps_st = gst_caps_get_structure (caps, 0);
|
|
name = gst_structure_get_name (caps_st);
|
|
|
|
if (g_str_has_prefix (name, "audio/")) {
|
|
GstDiscovererAudioInfo *info;
|
|
const gchar *format_str;
|
|
|
|
info = (GstDiscovererAudioInfo *) make_info (parent,
|
|
GST_TYPE_DISCOVERER_AUDIO_INFO, caps);
|
|
|
|
if (gst_structure_get_int (caps_st, "rate", &tmp))
|
|
info->sample_rate = (guint) tmp;
|
|
|
|
if (gst_structure_get_int (caps_st, "channels", &tmp))
|
|
info->channels = (guint) tmp;
|
|
|
|
/* FIXME: we only want to extract depth if raw audio is what's in the
|
|
* container (i.e. not if there is a decoder involved) */
|
|
format_str = gst_structure_get_string (caps_st, "format");
|
|
if (format_str != NULL) {
|
|
const GstAudioFormatInfo *finfo;
|
|
GstAudioFormat format;
|
|
|
|
format = gst_audio_format_from_string (format_str);
|
|
finfo = gst_audio_format_get_info (format);
|
|
if (finfo)
|
|
info->depth = GST_AUDIO_FORMAT_INFO_DEPTH (finfo);
|
|
}
|
|
|
|
if (gst_structure_id_has_field (st, _TAGS_QUARK)) {
|
|
gst_structure_id_get (st, _TAGS_QUARK, GST_TYPE_TAG_LIST, &tags_st, NULL);
|
|
if (gst_tag_list_get_uint (tags_st, GST_TAG_BITRATE, &utmp) ||
|
|
gst_tag_list_get_uint (tags_st, GST_TAG_NOMINAL_BITRATE, &utmp))
|
|
info->bitrate = utmp;
|
|
|
|
if (gst_tag_list_get_uint (tags_st, GST_TAG_MAXIMUM_BITRATE, &utmp))
|
|
info->max_bitrate = utmp;
|
|
|
|
/* FIXME: Is it worth it to remove the tags we've parsed? */
|
|
gst_discoverer_merge_and_replace_tags (&info->parent.tags, tags_st);
|
|
}
|
|
|
|
collect_common_information (&info->parent, st);
|
|
|
|
if (!info->language && ((GstDiscovererStreamInfo *) info)->tags) {
|
|
gchar *language;
|
|
if (gst_tag_list_get_string (((GstDiscovererStreamInfo *) info)->tags,
|
|
GST_TAG_LANGUAGE_CODE, &language)) {
|
|
info->language = language;
|
|
}
|
|
}
|
|
|
|
gst_caps_unref (caps);
|
|
return (GstDiscovererStreamInfo *) info;
|
|
|
|
} else if (g_str_has_prefix (name, "video/") ||
|
|
g_str_has_prefix (name, "image/")) {
|
|
GstDiscovererVideoInfo *info;
|
|
const gchar *caps_str;
|
|
|
|
info = (GstDiscovererVideoInfo *) make_info (parent,
|
|
GST_TYPE_DISCOVERER_VIDEO_INFO, caps);
|
|
|
|
if (gst_structure_get_int (caps_st, "width", &tmp))
|
|
info->width = (guint) tmp;
|
|
if (gst_structure_get_int (caps_st, "height", &tmp))
|
|
info->height = (guint) tmp;
|
|
|
|
if (gst_structure_get_fraction (caps_st, "framerate", &tmp, &tmp2)) {
|
|
info->framerate_num = (guint) tmp;
|
|
info->framerate_denom = (guint) tmp2;
|
|
} else {
|
|
info->framerate_num = 0;
|
|
info->framerate_denom = 1;
|
|
}
|
|
|
|
if (gst_structure_get_fraction (caps_st, "pixel-aspect-ratio", &tmp, &tmp2)) {
|
|
info->par_num = (guint) tmp;
|
|
info->par_denom = (guint) tmp2;
|
|
} else {
|
|
info->par_num = 1;
|
|
info->par_denom = 1;
|
|
}
|
|
|
|
/* FIXME: we only want to extract depth if raw video is what's in the
|
|
* container (i.e. not if there is a decoder involved) */
|
|
caps_str = gst_structure_get_string (caps_st, "format");
|
|
if (caps_str != NULL) {
|
|
const GstVideoFormatInfo *finfo;
|
|
GstVideoFormat format;
|
|
|
|
format = gst_video_format_from_string (caps_str);
|
|
finfo = gst_video_format_get_info (format);
|
|
if (finfo)
|
|
info->depth = finfo->bits * finfo->n_components;
|
|
}
|
|
|
|
caps_str = gst_structure_get_string (caps_st, "interlace-mode");
|
|
if (!caps_str || strcmp (caps_str, "progressive") == 0)
|
|
info->interlaced = FALSE;
|
|
else
|
|
info->interlaced = TRUE;
|
|
|
|
if (gst_structure_id_has_field (st, _TAGS_QUARK)) {
|
|
gst_structure_id_get (st, _TAGS_QUARK, GST_TYPE_TAG_LIST, &tags_st, NULL);
|
|
if (gst_tag_list_get_uint (tags_st, GST_TAG_BITRATE, &utmp) ||
|
|
gst_tag_list_get_uint (tags_st, GST_TAG_NOMINAL_BITRATE, &utmp))
|
|
info->bitrate = utmp;
|
|
|
|
if (gst_tag_list_get_uint (tags_st, GST_TAG_MAXIMUM_BITRATE, &utmp))
|
|
info->max_bitrate = utmp;
|
|
|
|
/* FIXME: Is it worth it to remove the tags we've parsed? */
|
|
gst_discoverer_merge_and_replace_tags (&info->parent.tags, tags_st);
|
|
}
|
|
|
|
collect_common_information (&info->parent, st);
|
|
|
|
gst_caps_unref (caps);
|
|
return (GstDiscovererStreamInfo *) info;
|
|
|
|
} else if (is_subtitle_caps (caps)) {
|
|
GstDiscovererSubtitleInfo *info;
|
|
|
|
info = (GstDiscovererSubtitleInfo *) make_info (parent,
|
|
GST_TYPE_DISCOVERER_SUBTITLE_INFO, caps);
|
|
|
|
if (gst_structure_id_has_field (st, _TAGS_QUARK)) {
|
|
const gchar *language;
|
|
|
|
gst_structure_id_get (st, _TAGS_QUARK, GST_TYPE_TAG_LIST, &tags_st, NULL);
|
|
|
|
language = gst_structure_get_string (caps_st, GST_TAG_LANGUAGE_CODE);
|
|
if (language)
|
|
info->language = g_strdup (language);
|
|
|
|
/* FIXME: Is it worth it to remove the tags we've parsed? */
|
|
gst_discoverer_merge_and_replace_tags (&info->parent.tags, tags_st);
|
|
}
|
|
|
|
collect_common_information (&info->parent, st);
|
|
|
|
if (!info->language && ((GstDiscovererStreamInfo *) info)->tags) {
|
|
gchar *language;
|
|
if (gst_tag_list_get_string (((GstDiscovererStreamInfo *) info)->tags,
|
|
GST_TAG_LANGUAGE_CODE, &language)) {
|
|
info->language = language;
|
|
}
|
|
}
|
|
|
|
gst_caps_unref (caps);
|
|
return (GstDiscovererStreamInfo *) info;
|
|
|
|
} else {
|
|
/* None of the above - populate what information we can */
|
|
GstDiscovererStreamInfo *info;
|
|
|
|
info = make_info (parent, GST_TYPE_DISCOVERER_STREAM_INFO, caps);
|
|
|
|
if (gst_structure_id_get (st, _TAGS_QUARK, GST_TYPE_TAG_LIST, &tags_st,
|
|
NULL)) {
|
|
gst_discoverer_merge_and_replace_tags (&info->tags, tags_st);
|
|
}
|
|
|
|
collect_common_information (info, st);
|
|
|
|
gst_caps_unref (caps);
|
|
return info;
|
|
}
|
|
|
|
}
|
|
|
|
static GstStructure *
|
|
find_stream_for_node (GstDiscoverer * dc, const GstStructure * topology)
|
|
{
|
|
GstPad *pad;
|
|
GstPad *target_pad = NULL;
|
|
GstStructure *st = NULL;
|
|
PrivateStream *ps;
|
|
guint i;
|
|
GList *tmp;
|
|
|
|
if (!dc->priv->streams) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!gst_structure_id_has_field (topology, _TOPOLOGY_PAD_QUARK)) {
|
|
GST_DEBUG ("Could not find pad for node %" GST_PTR_FORMAT, topology);
|
|
return NULL;
|
|
}
|
|
|
|
gst_structure_id_get (topology, _TOPOLOGY_PAD_QUARK,
|
|
GST_TYPE_PAD, &pad, NULL);
|
|
|
|
for (i = 0, tmp = dc->priv->streams; tmp; tmp = tmp->next, i++) {
|
|
ps = (PrivateStream *) tmp->data;
|
|
|
|
target_pad = gst_ghost_pad_get_target (GST_GHOST_PAD (ps->pad));
|
|
gst_object_unref (target_pad);
|
|
|
|
if (target_pad == pad)
|
|
break;
|
|
}
|
|
|
|
if (tmp)
|
|
st = collect_stream_information (dc, ps, i);
|
|
|
|
gst_object_unref (pad);
|
|
|
|
return st;
|
|
}
|
|
|
|
/* this can fail due to {framed,parsed}={TRUE,FALSE} differences, thus we filter
|
|
* the parent */
|
|
static gboolean
|
|
child_is_same_stream (const GstCaps * _parent, const GstCaps * child)
|
|
{
|
|
GstCaps *parent;
|
|
guint i, size;
|
|
gboolean res;
|
|
|
|
if (_parent == child)
|
|
return TRUE;
|
|
if (!_parent)
|
|
return FALSE;
|
|
if (!child)
|
|
return FALSE;
|
|
|
|
parent = gst_caps_copy (_parent);
|
|
size = gst_caps_get_size (parent);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
gst_structure_remove_field (gst_caps_get_structure (parent, i), "parsed");
|
|
gst_structure_remove_field (gst_caps_get_structure (parent, i), "framed");
|
|
}
|
|
res = gst_caps_can_intersect (parent, child);
|
|
gst_caps_unref (parent);
|
|
return res;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
child_is_raw_stream (const GstCaps * parent, const GstCaps * child)
|
|
{
|
|
const GstStructure *st1, *st2;
|
|
const gchar *name1, *name2;
|
|
|
|
if (parent == child)
|
|
return TRUE;
|
|
if (!parent)
|
|
return FALSE;
|
|
if (!child)
|
|
return FALSE;
|
|
|
|
st1 = gst_caps_get_structure (parent, 0);
|
|
name1 = gst_structure_get_name (st1);
|
|
st2 = gst_caps_get_structure (child, 0);
|
|
name2 = gst_structure_get_name (st2);
|
|
|
|
if ((g_str_has_prefix (name1, "audio/") &&
|
|
g_str_has_prefix (name2, "audio/x-raw")) ||
|
|
((g_str_has_prefix (name1, "video/") ||
|
|
g_str_has_prefix (name1, "image/")) &&
|
|
g_str_has_prefix (name2, "video/x-raw"))) {
|
|
/* child is the "raw" sub-stream corresponding to parent */
|
|
return TRUE;
|
|
}
|
|
|
|
if (is_subtitle_caps (parent))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* If a parent is non-NULL, collected stream information will be appended to it
|
|
* (and where the information exists, it will be overriden)
|
|
*/
|
|
static GstDiscovererStreamInfo *
|
|
parse_stream_topology (GstDiscoverer * dc, const GstStructure * topology,
|
|
GstDiscovererStreamInfo * parent)
|
|
{
|
|
GstDiscovererStreamInfo *res = NULL;
|
|
GstCaps *caps = NULL;
|
|
const GValue *nval = NULL;
|
|
|
|
GST_DEBUG ("parsing: %" GST_PTR_FORMAT, topology);
|
|
|
|
nval = gst_structure_get_value (topology, "next");
|
|
|
|
if (nval == NULL || GST_VALUE_HOLDS_STRUCTURE (nval)) {
|
|
GstStructure *st = find_stream_for_node (dc, topology);
|
|
gboolean add_to_list = TRUE;
|
|
|
|
if (st) {
|
|
res = collect_information (dc, st, parent);
|
|
gst_structure_free (st);
|
|
} else {
|
|
/* Didn't find a stream structure, so let's just use the caps we have */
|
|
res = collect_information (dc, topology, parent);
|
|
}
|
|
|
|
if (nval == NULL) {
|
|
/* FIXME : aggregate with information from main streams */
|
|
GST_DEBUG ("Coudn't find 'next' ! might be the last entry");
|
|
} else {
|
|
GstPad *srcpad;
|
|
|
|
st = (GstStructure *) gst_value_get_structure (nval);
|
|
|
|
GST_DEBUG ("next is a structure %" GST_PTR_FORMAT, st);
|
|
|
|
if (!parent)
|
|
parent = res;
|
|
|
|
if (gst_structure_id_get (st, _ELEMENT_SRCPAD_QUARK, GST_TYPE_PAD,
|
|
&srcpad, NULL)) {
|
|
caps = gst_pad_get_current_caps (srcpad);
|
|
gst_object_unref (srcpad);
|
|
}
|
|
if (!caps) {
|
|
gst_structure_id_get (st, _CAPS_QUARK, GST_TYPE_CAPS, &caps, NULL);
|
|
}
|
|
|
|
if (caps) {
|
|
if (child_is_same_stream (parent->caps, caps)) {
|
|
/* We sometimes get an extra sub-stream from the parser. If this is
|
|
* the case, we just replace the parent caps with this stream's caps
|
|
* since they might contain more information */
|
|
gst_caps_replace (&parent->caps, caps);
|
|
|
|
parse_stream_topology (dc, st, parent);
|
|
add_to_list = FALSE;
|
|
} else if (child_is_raw_stream (parent->caps, caps)) {
|
|
/* This is the "raw" stream corresponding to the parent. This
|
|
* contains more information than the parent, tags etc. */
|
|
parse_stream_topology (dc, st, parent);
|
|
add_to_list = FALSE;
|
|
} else {
|
|
GstDiscovererStreamInfo *next = parse_stream_topology (dc, st, NULL);
|
|
res->next = next;
|
|
next->previous = res;
|
|
}
|
|
gst_caps_unref (caps);
|
|
}
|
|
}
|
|
|
|
if (add_to_list) {
|
|
dc->priv->current_info->stream_list =
|
|
g_list_append (dc->priv->current_info->stream_list, res);
|
|
} else {
|
|
gst_discoverer_stream_info_unref (res);
|
|
}
|
|
|
|
} else if (GST_VALUE_HOLDS_LIST (nval)) {
|
|
guint i, len;
|
|
GstDiscovererContainerInfo *cont;
|
|
GstTagList *tags;
|
|
GstPad *srcpad;
|
|
|
|
if (gst_structure_id_get (topology, _ELEMENT_SRCPAD_QUARK, GST_TYPE_PAD,
|
|
&srcpad, NULL)) {
|
|
caps = gst_pad_get_current_caps (srcpad);
|
|
gst_object_unref (srcpad);
|
|
}
|
|
if (!caps) {
|
|
gst_structure_id_get (topology, _CAPS_QUARK, GST_TYPE_CAPS, &caps, NULL);
|
|
}
|
|
|
|
if (!caps)
|
|
GST_WARNING ("Couldn't find caps !");
|
|
|
|
len = gst_value_list_get_size (nval);
|
|
GST_DEBUG ("next is a list of %d entries", len);
|
|
|
|
cont = (GstDiscovererContainerInfo *)
|
|
g_object_new (GST_TYPE_DISCOVERER_CONTAINER_INFO, NULL);
|
|
cont->parent.caps = caps;
|
|
res = (GstDiscovererStreamInfo *) cont;
|
|
|
|
if (gst_structure_id_has_field (topology, _TAGS_QUARK)) {
|
|
GstTagList *tmp;
|
|
|
|
gst_structure_id_get (topology, _TAGS_QUARK,
|
|
GST_TYPE_TAG_LIST, &tags, NULL);
|
|
|
|
GST_DEBUG ("Merge tags %" GST_PTR_FORMAT, tags);
|
|
|
|
tmp =
|
|
gst_tag_list_merge (cont->parent.tags, (GstTagList *) tags,
|
|
GST_TAG_MERGE_APPEND);
|
|
gst_tag_list_unref (tags);
|
|
if (cont->parent.tags)
|
|
gst_tag_list_unref (cont->parent.tags);
|
|
cont->parent.tags = tmp;
|
|
GST_DEBUG ("Container info tags %" GST_PTR_FORMAT, tmp);
|
|
}
|
|
|
|
for (i = 0; i < len; i++) {
|
|
const GValue *subv = gst_value_list_get_value (nval, i);
|
|
const GstStructure *subst = gst_value_get_structure (subv);
|
|
GstDiscovererStreamInfo *substream;
|
|
|
|
GST_DEBUG ("%d %" GST_PTR_FORMAT, i, subst);
|
|
|
|
substream = parse_stream_topology (dc, subst, NULL);
|
|
|
|
substream->previous = res;
|
|
cont->streams =
|
|
g_list_append (cont->streams,
|
|
gst_discoverer_stream_info_ref (substream));
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Called when pipeline is pre-rolled */
|
|
static void
|
|
discoverer_collect (GstDiscoverer * dc)
|
|
{
|
|
GST_DEBUG ("Collecting information");
|
|
|
|
/* Stop the timeout handler if present */
|
|
if (dc->priv->timeoutid) {
|
|
g_source_remove (dc->priv->timeoutid);
|
|
dc->priv->timeoutid = 0;
|
|
}
|
|
|
|
if (dc->priv->streams) {
|
|
/* FIXME : Make this querying optional */
|
|
if (TRUE) {
|
|
GstElement *pipeline = (GstElement *) dc->priv->pipeline;
|
|
gint64 dur;
|
|
|
|
GST_DEBUG ("Attempting to query duration");
|
|
|
|
if (gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur)) {
|
|
GST_DEBUG ("Got duration %" GST_TIME_FORMAT, GST_TIME_ARGS (dur));
|
|
dc->priv->current_info->duration = (guint64) dur;
|
|
} else {
|
|
GstStateChangeReturn sret;
|
|
|
|
/* Some parsers may not even return a rough estimate right away, e.g.
|
|
* because they've only processed a single frame so far, so if we
|
|
* didn't get a duration the first time, spin a bit and try again.
|
|
* Ugly, but still better than making parsers or other elements return
|
|
* completely bogus values. We need some API extensions to solve this
|
|
* better. */
|
|
GST_INFO ("No duration yet, try a bit harder..");
|
|
sret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
|
if (sret != GST_STATE_CHANGE_FAILURE) {
|
|
int i;
|
|
|
|
for (i = 0; i < 2; ++i) {
|
|
g_usleep (G_USEC_PER_SEC / 20);
|
|
if (gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur)
|
|
&& dur > 0) {
|
|
GST_DEBUG ("Got duration %" GST_TIME_FORMAT, GST_TIME_ARGS (dur));
|
|
dc->priv->current_info->duration = (guint64) dur;
|
|
break;
|
|
}
|
|
}
|
|
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
|
}
|
|
}
|
|
|
|
if (dc->priv->seeking_query) {
|
|
if (gst_element_query (pipeline, dc->priv->seeking_query)) {
|
|
GstFormat format;
|
|
gboolean seekable;
|
|
|
|
gst_query_parse_seeking (dc->priv->seeking_query, &format,
|
|
&seekable, NULL, NULL);
|
|
if (format == GST_FORMAT_TIME) {
|
|
GST_DEBUG ("Got seekable %d", seekable);
|
|
dc->priv->current_info->seekable = seekable;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dc->priv->current_topology)
|
|
dc->priv->current_info->stream_info = parse_stream_topology (dc,
|
|
dc->priv->current_topology, NULL);
|
|
|
|
/*
|
|
* Images need some special handling. They do not have a duration, have
|
|
* caps named image/<foo> (th exception being MJPEG video which is also
|
|
* type image/jpeg), and should consist of precisely one stream (actually
|
|
* initially there are 2, the image and raw stream, but we squash these
|
|
* while parsing the stream topology). At some point, if we find that these
|
|
* conditions are not sufficient, we can count the number of decoders and
|
|
* parsers in the chain, and if there's more than one decoder, or any
|
|
* parser at all, we should not mark this as an image.
|
|
*/
|
|
if (dc->priv->current_info->duration == 0 &&
|
|
dc->priv->current_info->stream_info != NULL &&
|
|
dc->priv->current_info->stream_info->next == NULL) {
|
|
GstDiscovererStreamInfo *stream_info;
|
|
GstStructure *st;
|
|
|
|
stream_info = dc->priv->current_info->stream_info;
|
|
st = gst_caps_get_structure (stream_info->caps, 0);
|
|
|
|
if (g_str_has_prefix (gst_structure_get_name (st), "image/"))
|
|
((GstDiscovererVideoInfo *) stream_info)->is_image = TRUE;
|
|
}
|
|
}
|
|
|
|
if (dc->priv->async) {
|
|
GST_DEBUG ("Emitting 'discoverered'");
|
|
g_signal_emit (dc, gst_discoverer_signals[SIGNAL_DISCOVERED], 0,
|
|
dc->priv->current_info, dc->priv->current_error);
|
|
/* Clients get a copy of current_info since it is a boxed type */
|
|
gst_discoverer_info_unref (dc->priv->current_info);
|
|
dc->priv->current_info = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_async_cb (gpointer cb_data, GSource * source, GSourceFunc * func,
|
|
gpointer * data)
|
|
{
|
|
*func = (GSourceFunc) async_timeout_cb;
|
|
*data = cb_data;
|
|
}
|
|
|
|
/* Wrapper since GSourceCallbackFuncs don't expect a return value from ref() */
|
|
static void
|
|
_void_g_object_ref (gpointer object)
|
|
{
|
|
g_object_ref (G_OBJECT (object));
|
|
}
|
|
|
|
static void
|
|
handle_current_async (GstDiscoverer * dc)
|
|
{
|
|
GSource *source;
|
|
static GSourceCallbackFuncs cb_funcs = {
|
|
_void_g_object_ref,
|
|
g_object_unref,
|
|
get_async_cb,
|
|
};
|
|
|
|
/* Attach a timeout to the main context */
|
|
source = g_timeout_source_new (dc->priv->timeout / GST_MSECOND);
|
|
g_source_set_callback_indirect (source, g_object_ref (dc), &cb_funcs);
|
|
dc->priv->timeoutid = g_source_attach (source, dc->priv->ctx);
|
|
g_source_unref (source);
|
|
}
|
|
|
|
|
|
/* Returns TRUE if processing should stop */
|
|
static gboolean
|
|
handle_message (GstDiscoverer * dc, GstMessage * msg)
|
|
{
|
|
gboolean done = FALSE;
|
|
|
|
GST_DEBUG_OBJECT (GST_MESSAGE_SRC (msg), "got a %s message",
|
|
GST_MESSAGE_TYPE_NAME (msg));
|
|
|
|
switch (GST_MESSAGE_TYPE (msg)) {
|
|
case GST_MESSAGE_ERROR:{
|
|
GError *gerr;
|
|
gchar *debug;
|
|
|
|
gst_message_parse_error (msg, &gerr, &debug);
|
|
GST_WARNING_OBJECT (GST_MESSAGE_SRC (msg),
|
|
"Got an error [debug:%s], [message:%s]", debug, gerr->message);
|
|
dc->priv->current_error = gerr;
|
|
g_free (debug);
|
|
|
|
/* We need to stop */
|
|
done = TRUE;
|
|
|
|
/* Don't override missing plugin result code for missing plugin errors */
|
|
if (dc->priv->current_info->result != GST_DISCOVERER_MISSING_PLUGINS ||
|
|
(!g_error_matches (gerr, GST_CORE_ERROR,
|
|
GST_CORE_ERROR_MISSING_PLUGIN) &&
|
|
!g_error_matches (gerr, GST_STREAM_ERROR,
|
|
GST_STREAM_ERROR_CODEC_NOT_FOUND))) {
|
|
GST_DEBUG ("Setting result to ERROR");
|
|
dc->priv->current_info->result = GST_DISCOVERER_ERROR;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GST_MESSAGE_EOS:
|
|
GST_DEBUG ("Got EOS !");
|
|
done = TRUE;
|
|
break;
|
|
|
|
case GST_MESSAGE_APPLICATION:{
|
|
const gchar *name;
|
|
gboolean async_done;
|
|
name = gst_structure_get_name (gst_message_get_structure (msg));
|
|
/* Maybe ASYNC_DONE is received & we're just waiting for subtitle tags */
|
|
DISCO_LOCK (dc);
|
|
async_done = dc->priv->async_done;
|
|
DISCO_UNLOCK (dc);
|
|
if (g_str_equal (name, "DiscovererDone") && async_done)
|
|
return TRUE;
|
|
break;
|
|
}
|
|
|
|
case GST_MESSAGE_ASYNC_DONE:
|
|
if (GST_MESSAGE_SRC (msg) == (GstObject *) dc->priv->pipeline) {
|
|
GST_DEBUG ("Finished changing state asynchronously");
|
|
DISCO_LOCK (dc);
|
|
if (dc->priv->pending_subtitle_pads == 0) {
|
|
done = TRUE;
|
|
} else {
|
|
/* Remember that ASYNC_DONE has been received, wait for subtitles */
|
|
dc->priv->async_done = TRUE;
|
|
}
|
|
DISCO_UNLOCK (dc);
|
|
|
|
}
|
|
break;
|
|
|
|
case GST_MESSAGE_ELEMENT:
|
|
{
|
|
GQuark sttype;
|
|
const GstStructure *structure;
|
|
|
|
structure = gst_message_get_structure (msg);
|
|
sttype = gst_structure_get_name_id (structure);
|
|
GST_DEBUG_OBJECT (GST_MESSAGE_SRC (msg),
|
|
"structure %" GST_PTR_FORMAT, structure);
|
|
if (sttype == _MISSING_PLUGIN_QUARK) {
|
|
GST_DEBUG_OBJECT (GST_MESSAGE_SRC (msg),
|
|
"Setting result to MISSING_PLUGINS");
|
|
dc->priv->current_info->result = GST_DISCOVERER_MISSING_PLUGINS;
|
|
/* FIXME 2.0 Remove completely the ->misc
|
|
* Keep the old behaviour for now.
|
|
*/
|
|
if (dc->priv->current_info->misc)
|
|
gst_structure_free (dc->priv->current_info->misc);
|
|
dc->priv->current_info->misc = gst_structure_copy (structure);
|
|
g_ptr_array_add (dc->priv->current_info->missing_elements_details,
|
|
gst_missing_plugin_message_get_installer_detail (msg));
|
|
} else if (sttype == _STREAM_TOPOLOGY_QUARK) {
|
|
if (dc->priv->current_topology)
|
|
gst_structure_free (dc->priv->current_topology);
|
|
dc->priv->current_topology = gst_structure_copy (structure);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GST_MESSAGE_TAG:
|
|
{
|
|
GstTagList *tl, *tmp;
|
|
|
|
gst_message_parse_tag (msg, &tl);
|
|
GST_DEBUG_OBJECT (GST_MESSAGE_SRC (msg), "Got tags %" GST_PTR_FORMAT, tl);
|
|
/* Merge with current tags */
|
|
tmp =
|
|
gst_tag_list_merge (dc->priv->current_info->tags, tl,
|
|
GST_TAG_MERGE_APPEND);
|
|
gst_tag_list_unref (tl);
|
|
if (dc->priv->current_info->tags)
|
|
gst_tag_list_unref (dc->priv->current_info->tags);
|
|
dc->priv->current_info->tags = tmp;
|
|
GST_DEBUG_OBJECT (GST_MESSAGE_SRC (msg), "Current info %p, tags %"
|
|
GST_PTR_FORMAT, dc->priv->current_info, tmp);
|
|
}
|
|
break;
|
|
|
|
case GST_MESSAGE_TOC:
|
|
{
|
|
GstToc *tmp;
|
|
|
|
gst_message_parse_toc (msg, &tmp, NULL);
|
|
GST_DEBUG_OBJECT (GST_MESSAGE_SRC (msg), "Got toc %" GST_PTR_FORMAT, tmp);
|
|
if (dc->priv->current_info->toc)
|
|
gst_toc_unref (dc->priv->current_info->toc);
|
|
dc->priv->current_info->toc = tmp; /* transfer ownership */
|
|
GST_DEBUG_OBJECT (GST_MESSAGE_SRC (msg), "Current info %p, toc %"
|
|
GST_PTR_FORMAT, dc->priv->current_info, tmp);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return done;
|
|
}
|
|
|
|
static void
|
|
handle_current_sync (GstDiscoverer * dc)
|
|
{
|
|
GTimer *timer;
|
|
gdouble deadline = ((gdouble) dc->priv->timeout) / GST_SECOND;
|
|
GstMessage *msg;
|
|
gboolean done = FALSE;
|
|
|
|
timer = g_timer_new ();
|
|
g_timer_start (timer);
|
|
|
|
do {
|
|
/* poll bus with timeout */
|
|
/* FIXME : make the timeout more fine-tuned */
|
|
if ((msg = gst_bus_timed_pop (dc->priv->bus, GST_SECOND / 2))) {
|
|
done = handle_message (dc, msg);
|
|
gst_message_unref (msg);
|
|
}
|
|
} while (!done && (g_timer_elapsed (timer, NULL) < deadline));
|
|
|
|
/* return result */
|
|
if (!done) {
|
|
GST_DEBUG ("we timed out! Setting result to TIMEOUT");
|
|
dc->priv->current_info->result = GST_DISCOVERER_TIMEOUT;
|
|
}
|
|
|
|
DISCO_LOCK (dc);
|
|
dc->priv->processing = FALSE;
|
|
DISCO_UNLOCK (dc);
|
|
|
|
|
|
GST_DEBUG ("Done");
|
|
|
|
g_timer_stop (timer);
|
|
g_timer_destroy (timer);
|
|
}
|
|
|
|
static void
|
|
_setup_locked (GstDiscoverer * dc)
|
|
{
|
|
GstStateChangeReturn ret;
|
|
|
|
GST_DEBUG ("Setting up");
|
|
|
|
/* Pop URI off the pending URI list */
|
|
dc->priv->current_info =
|
|
(GstDiscovererInfo *) g_object_new (GST_TYPE_DISCOVERER_INFO, NULL);
|
|
dc->priv->current_info->uri = (gchar *) dc->priv->pending_uris->data;
|
|
dc->priv->pending_uris =
|
|
g_list_delete_link (dc->priv->pending_uris, dc->priv->pending_uris);
|
|
|
|
/* set uri on uridecodebin */
|
|
g_object_set (dc->priv->uridecodebin, "uri", dc->priv->current_info->uri,
|
|
NULL);
|
|
|
|
GST_DEBUG ("Current is now %s", dc->priv->current_info->uri);
|
|
|
|
dc->priv->processing = TRUE;
|
|
|
|
/* set pipeline to PAUSED */
|
|
DISCO_UNLOCK (dc);
|
|
GST_DEBUG ("Setting pipeline to PAUSED");
|
|
ret =
|
|
gst_element_set_state ((GstElement *) dc->priv->pipeline,
|
|
GST_STATE_PAUSED);
|
|
if (ret == GST_STATE_CHANGE_NO_PREROLL) {
|
|
GST_DEBUG ("Source is live, switching to PLAYING");
|
|
ret =
|
|
gst_element_set_state ((GstElement *) dc->priv->pipeline,
|
|
GST_STATE_PLAYING);
|
|
}
|
|
DISCO_LOCK (dc);
|
|
|
|
GST_DEBUG_OBJECT (dc, "Pipeline going to PAUSED : %s",
|
|
gst_element_state_change_return_get_name (ret));
|
|
}
|
|
|
|
static void
|
|
discoverer_cleanup (GstDiscoverer * dc)
|
|
{
|
|
GST_DEBUG ("Cleaning up");
|
|
|
|
gst_bus_set_flushing (dc->priv->bus, TRUE);
|
|
|
|
DISCO_LOCK (dc);
|
|
if (dc->priv->current_error) {
|
|
g_error_free (dc->priv->current_error);
|
|
DISCO_UNLOCK (dc);
|
|
gst_element_set_state ((GstElement *) dc->priv->pipeline, GST_STATE_NULL);
|
|
} else {
|
|
DISCO_UNLOCK (dc);
|
|
}
|
|
|
|
gst_element_set_state ((GstElement *) dc->priv->pipeline, GST_STATE_READY);
|
|
gst_bus_set_flushing (dc->priv->bus, FALSE);
|
|
|
|
DISCO_LOCK (dc);
|
|
dc->priv->current_error = NULL;
|
|
if (dc->priv->current_topology) {
|
|
gst_structure_free (dc->priv->current_topology);
|
|
dc->priv->current_topology = NULL;
|
|
}
|
|
|
|
dc->priv->current_info = NULL;
|
|
|
|
dc->priv->pending_subtitle_pads = 0;
|
|
dc->priv->async_done = FALSE;
|
|
|
|
/* Try popping the next uri */
|
|
if (dc->priv->async) {
|
|
if (dc->priv->pending_uris != NULL) {
|
|
_setup_locked (dc);
|
|
DISCO_UNLOCK (dc);
|
|
/* Start timeout */
|
|
handle_current_async (dc);
|
|
} else {
|
|
/* We're done ! */
|
|
DISCO_UNLOCK (dc);
|
|
g_signal_emit (dc, gst_discoverer_signals[SIGNAL_FINISHED], 0);
|
|
}
|
|
} else
|
|
DISCO_UNLOCK (dc);
|
|
|
|
GST_DEBUG ("out");
|
|
}
|
|
|
|
static void
|
|
discoverer_bus_cb (GstBus * bus, GstMessage * msg, GstDiscoverer * dc)
|
|
{
|
|
if (dc->priv->processing) {
|
|
if (handle_message (dc, msg)) {
|
|
GST_DEBUG ("Stopping asynchronously");
|
|
/* Serialise with _event_probe() */
|
|
DISCO_LOCK (dc);
|
|
dc->priv->processing = FALSE;
|
|
DISCO_UNLOCK (dc);
|
|
discoverer_collect (dc);
|
|
discoverer_cleanup (dc);
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
async_timeout_cb (GstDiscoverer * dc)
|
|
{
|
|
if (!g_source_is_destroyed (g_main_current_source ())) {
|
|
dc->priv->timeoutid = 0;
|
|
GST_DEBUG ("Setting result to TIMEOUT");
|
|
dc->priv->current_info->result = GST_DISCOVERER_TIMEOUT;
|
|
dc->priv->processing = FALSE;
|
|
discoverer_collect (dc);
|
|
discoverer_cleanup (dc);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* If there is a pending URI, it will pop it from the list of pending
|
|
* URIs and start the discovery on it.
|
|
*
|
|
* Returns GST_DISCOVERER_OK if the next URI was popped and is processing,
|
|
* else a error flag.
|
|
*/
|
|
static GstDiscovererResult
|
|
start_discovering (GstDiscoverer * dc)
|
|
{
|
|
GstDiscovererResult res = GST_DISCOVERER_OK;
|
|
|
|
GST_DEBUG ("Starting");
|
|
|
|
DISCO_LOCK (dc);
|
|
if (dc->priv->pending_uris == NULL) {
|
|
GST_WARNING ("No URI to process");
|
|
res = GST_DISCOVERER_URI_INVALID;
|
|
DISCO_UNLOCK (dc);
|
|
goto beach;
|
|
}
|
|
|
|
if (dc->priv->current_info != NULL) {
|
|
GST_WARNING ("Already processing a file");
|
|
res = GST_DISCOVERER_BUSY;
|
|
DISCO_UNLOCK (dc);
|
|
goto beach;
|
|
}
|
|
|
|
g_signal_emit (dc, gst_discoverer_signals[SIGNAL_STARTING], 0);
|
|
|
|
_setup_locked (dc);
|
|
|
|
DISCO_UNLOCK (dc);
|
|
|
|
if (dc->priv->async)
|
|
handle_current_async (dc);
|
|
else
|
|
handle_current_sync (dc);
|
|
|
|
beach:
|
|
return res;
|
|
}
|
|
|
|
/* Serializing code */
|
|
|
|
static GVariant *
|
|
_serialize_common_stream_info (GstDiscovererStreamInfo * sinfo,
|
|
GstDiscovererSerializeFlags flags)
|
|
{
|
|
GVariant *common;
|
|
gchar *caps_str = NULL, *tags_str = NULL, *misc_str = NULL;
|
|
|
|
if (sinfo->caps && (flags & GST_DISCOVERER_SERIALIZE_CAPS))
|
|
caps_str = gst_caps_to_string (sinfo->caps);
|
|
|
|
if (sinfo->tags && (flags & GST_DISCOVERER_SERIALIZE_TAGS))
|
|
tags_str = gst_tag_list_to_string (sinfo->tags);
|
|
|
|
if (sinfo->misc && (flags & GST_DISCOVERER_SERIALIZE_MISC))
|
|
misc_str = gst_structure_to_string (sinfo->misc);
|
|
|
|
common =
|
|
g_variant_new ("(msmsmsms)", sinfo->stream_id, caps_str, tags_str,
|
|
misc_str);
|
|
|
|
g_free (caps_str);
|
|
g_free (tags_str);
|
|
g_free (misc_str);
|
|
|
|
return common;
|
|
}
|
|
|
|
static GVariant *
|
|
_serialize_info (GstDiscovererInfo * info, GstDiscovererSerializeFlags flags)
|
|
{
|
|
gchar *tags_str = NULL;
|
|
GVariant *ret;
|
|
|
|
if (info->tags && (flags & GST_DISCOVERER_SERIALIZE_TAGS))
|
|
tags_str = gst_tag_list_to_string (info->tags);
|
|
|
|
ret =
|
|
g_variant_new ("(mstbms)", info->uri, info->duration, info->seekable,
|
|
tags_str);
|
|
|
|
g_free (tags_str);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GVariant *
|
|
_serialize_audio_stream_info (GstDiscovererAudioInfo * ainfo)
|
|
{
|
|
return g_variant_new ("(uuuuums)",
|
|
ainfo->channels,
|
|
ainfo->sample_rate,
|
|
ainfo->bitrate, ainfo->max_bitrate, ainfo->depth, ainfo->language);
|
|
}
|
|
|
|
static GVariant *
|
|
_serialize_video_stream_info (GstDiscovererVideoInfo * vinfo)
|
|
{
|
|
return g_variant_new ("(uuuuuuubuub)",
|
|
vinfo->width,
|
|
vinfo->height,
|
|
vinfo->depth,
|
|
vinfo->framerate_num,
|
|
vinfo->framerate_denom,
|
|
vinfo->par_num,
|
|
vinfo->par_denom,
|
|
vinfo->interlaced, vinfo->bitrate, vinfo->max_bitrate, vinfo->is_image);
|
|
}
|
|
|
|
static GVariant *
|
|
_serialize_subtitle_stream_info (GstDiscovererSubtitleInfo * sinfo)
|
|
{
|
|
return g_variant_new ("ms", sinfo->language);
|
|
}
|
|
|
|
static GVariant *
|
|
gst_discoverer_info_to_variant_recurse (GstDiscovererStreamInfo * sinfo,
|
|
GstDiscovererSerializeFlags flags)
|
|
{
|
|
GVariant *stream_variant = NULL;
|
|
GVariant *common_stream_variant =
|
|
_serialize_common_stream_info (sinfo, flags);
|
|
|
|
if (GST_IS_DISCOVERER_CONTAINER_INFO (sinfo)) {
|
|
GList *tmp;
|
|
GList *streams =
|
|
gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO
|
|
(sinfo));
|
|
|
|
if (g_list_length (streams) > 0) {
|
|
GVariantBuilder children;
|
|
GVariant *child_variant;
|
|
g_variant_builder_init (&children, G_VARIANT_TYPE_ARRAY);
|
|
|
|
for (tmp = streams; tmp; tmp = tmp->next) {
|
|
child_variant =
|
|
gst_discoverer_info_to_variant_recurse (tmp->data, flags);
|
|
g_variant_builder_add (&children, "v", child_variant);
|
|
}
|
|
stream_variant =
|
|
g_variant_new ("(yvav)", 'c', common_stream_variant, &children);
|
|
} else {
|
|
stream_variant =
|
|
g_variant_new ("(yvav)", 'c', common_stream_variant, NULL);
|
|
}
|
|
|
|
gst_discoverer_stream_info_list_free (streams);
|
|
} else if (GST_IS_DISCOVERER_AUDIO_INFO (sinfo)) {
|
|
GVariant *audio_stream_info =
|
|
_serialize_audio_stream_info (GST_DISCOVERER_AUDIO_INFO (sinfo));
|
|
stream_variant =
|
|
g_variant_new ("(yvv)", 'a', common_stream_variant, audio_stream_info);
|
|
} else if (GST_IS_DISCOVERER_VIDEO_INFO (sinfo)) {
|
|
GVariant *video_stream_info =
|
|
_serialize_video_stream_info (GST_DISCOVERER_VIDEO_INFO (sinfo));
|
|
stream_variant =
|
|
g_variant_new ("(yvv)", 'v', common_stream_variant, video_stream_info);
|
|
} else if (GST_IS_DISCOVERER_SUBTITLE_INFO (sinfo)) {
|
|
GVariant *subtitle_stream_info =
|
|
_serialize_subtitle_stream_info (GST_DISCOVERER_SUBTITLE_INFO (sinfo));
|
|
stream_variant =
|
|
g_variant_new ("(yvv)", 's', common_stream_variant,
|
|
subtitle_stream_info);
|
|
}
|
|
|
|
return stream_variant;
|
|
}
|
|
|
|
/* Parsing code */
|
|
|
|
#define GET_FROM_TUPLE(v, t, n, val) G_STMT_START{ \
|
|
GVariant *child = g_variant_get_child_value (v, n); \
|
|
*val = g_variant_get_##t(child); \
|
|
g_variant_unref (child); \
|
|
}G_STMT_END
|
|
|
|
static const gchar *
|
|
_maybe_get_string_from_tuple (GVariant * tuple, guint index)
|
|
{
|
|
const gchar *ret = NULL;
|
|
GVariant *maybe;
|
|
GET_FROM_TUPLE (tuple, maybe, index, &maybe);
|
|
if (maybe) {
|
|
ret = g_variant_get_string (maybe, NULL);
|
|
g_variant_unref (maybe);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
_parse_info (GstDiscovererInfo * info, GVariant * info_variant)
|
|
{
|
|
const gchar *str;
|
|
|
|
str = _maybe_get_string_from_tuple (info_variant, 0);
|
|
if (str)
|
|
info->uri = g_strdup (str);
|
|
|
|
GET_FROM_TUPLE (info_variant, uint64, 1, &info->duration);
|
|
GET_FROM_TUPLE (info_variant, boolean, 2, &info->seekable);
|
|
|
|
str = _maybe_get_string_from_tuple (info_variant, 3);
|
|
if (str)
|
|
info->tags = gst_tag_list_new_from_string (str);
|
|
}
|
|
|
|
static void
|
|
_parse_common_stream_info (GstDiscovererStreamInfo * sinfo, GVariant * common)
|
|
{
|
|
const gchar *str;
|
|
|
|
str = _maybe_get_string_from_tuple (common, 0);
|
|
if (str)
|
|
sinfo->stream_id = g_strdup (str);
|
|
|
|
str = _maybe_get_string_from_tuple (common, 1);
|
|
if (str)
|
|
sinfo->caps = gst_caps_from_string (str);
|
|
|
|
str = _maybe_get_string_from_tuple (common, 2);
|
|
if (str)
|
|
sinfo->tags = gst_tag_list_new_from_string (str);
|
|
|
|
str = _maybe_get_string_from_tuple (common, 3);
|
|
if (str)
|
|
sinfo->misc = gst_structure_new_from_string (str);
|
|
|
|
g_variant_unref (common);
|
|
}
|
|
|
|
static void
|
|
_parse_audio_stream_info (GstDiscovererAudioInfo * ainfo, GVariant * specific)
|
|
{
|
|
const gchar *str;
|
|
|
|
GET_FROM_TUPLE (specific, uint32, 0, &ainfo->channels);
|
|
GET_FROM_TUPLE (specific, uint32, 1, &ainfo->sample_rate);
|
|
GET_FROM_TUPLE (specific, uint32, 2, &ainfo->bitrate);
|
|
GET_FROM_TUPLE (specific, uint32, 3, &ainfo->max_bitrate);
|
|
GET_FROM_TUPLE (specific, uint32, 4, &ainfo->depth);
|
|
|
|
str = _maybe_get_string_from_tuple (specific, 5);
|
|
|
|
if (str)
|
|
ainfo->language = g_strdup (str);
|
|
|
|
g_variant_unref (specific);
|
|
}
|
|
|
|
static void
|
|
_parse_video_stream_info (GstDiscovererVideoInfo * vinfo, GVariant * specific)
|
|
{
|
|
GET_FROM_TUPLE (specific, uint32, 0, &vinfo->width);
|
|
GET_FROM_TUPLE (specific, uint32, 1, &vinfo->height);
|
|
GET_FROM_TUPLE (specific, uint32, 2, &vinfo->depth);
|
|
GET_FROM_TUPLE (specific, uint32, 3, &vinfo->framerate_num);
|
|
GET_FROM_TUPLE (specific, uint32, 4, &vinfo->framerate_denom);
|
|
GET_FROM_TUPLE (specific, uint32, 5, &vinfo->par_num);
|
|
GET_FROM_TUPLE (specific, uint32, 6, &vinfo->par_denom);
|
|
GET_FROM_TUPLE (specific, boolean, 7, &vinfo->interlaced);
|
|
GET_FROM_TUPLE (specific, uint32, 8, &vinfo->bitrate);
|
|
GET_FROM_TUPLE (specific, uint32, 9, &vinfo->max_bitrate);
|
|
GET_FROM_TUPLE (specific, boolean, 10, &vinfo->is_image);
|
|
|
|
g_variant_unref (specific);
|
|
}
|
|
|
|
static void
|
|
_parse_subtitle_stream_info (GstDiscovererSubtitleInfo * sinfo,
|
|
GVariant * specific)
|
|
{
|
|
GVariant *maybe;
|
|
|
|
maybe = g_variant_get_maybe (specific);
|
|
if (maybe) {
|
|
sinfo->language = g_strdup (g_variant_get_string (maybe, NULL));
|
|
g_variant_unref (maybe);
|
|
}
|
|
|
|
g_variant_unref (specific);
|
|
}
|
|
|
|
static GstDiscovererStreamInfo *
|
|
_parse_discovery (GVariant * variant, GstDiscovererInfo * info)
|
|
{
|
|
gchar type;
|
|
GVariant *common = g_variant_get_child_value (variant, 1);
|
|
GVariant *specific = g_variant_get_child_value (variant, 2);
|
|
GstDiscovererStreamInfo *sinfo = NULL;
|
|
|
|
GET_FROM_TUPLE (variant, byte, 0, &type);
|
|
switch (type) {
|
|
case 'c':
|
|
sinfo = g_object_new (GST_TYPE_DISCOVERER_CONTAINER_INFO, NULL);
|
|
break;
|
|
case 'a':
|
|
sinfo = g_object_new (GST_TYPE_DISCOVERER_AUDIO_INFO, NULL);
|
|
_parse_audio_stream_info (GST_DISCOVERER_AUDIO_INFO (sinfo),
|
|
g_variant_get_child_value (specific, 0));
|
|
break;
|
|
case 'v':
|
|
sinfo = g_object_new (GST_TYPE_DISCOVERER_VIDEO_INFO, NULL);
|
|
_parse_video_stream_info (GST_DISCOVERER_VIDEO_INFO (sinfo),
|
|
g_variant_get_child_value (specific, 0));
|
|
break;
|
|
case 's':
|
|
sinfo = g_object_new (GST_TYPE_DISCOVERER_SUBTITLE_INFO, NULL);
|
|
_parse_subtitle_stream_info (GST_DISCOVERER_SUBTITLE_INFO (sinfo),
|
|
g_variant_get_child_value (specific, 0));
|
|
break;
|
|
default:
|
|
GST_WARNING ("Unexpected discoverer info type %d", type);
|
|
goto out;
|
|
}
|
|
|
|
_parse_common_stream_info (sinfo, g_variant_get_child_value (common, 0));
|
|
|
|
info->stream_list = g_list_append (info->stream_list, sinfo);
|
|
|
|
if (!info->stream_info) {
|
|
info->stream_info = sinfo;
|
|
}
|
|
|
|
if (GST_IS_DISCOVERER_CONTAINER_INFO (sinfo)) {
|
|
GVariantIter iter;
|
|
GVariant *child;
|
|
|
|
GstDiscovererContainerInfo *cinfo = GST_DISCOVERER_CONTAINER_INFO (sinfo);
|
|
g_variant_iter_init (&iter, specific);
|
|
while ((child = g_variant_iter_next_value (&iter))) {
|
|
GstDiscovererStreamInfo *child_info;
|
|
child_info = _parse_discovery (g_variant_get_variant (child), info);
|
|
if (child_info != NULL) {
|
|
cinfo->streams =
|
|
g_list_append (cinfo->streams,
|
|
gst_discoverer_stream_info_ref (child_info));
|
|
}
|
|
g_variant_unref (child);
|
|
}
|
|
}
|
|
|
|
out:
|
|
|
|
g_variant_unref (common);
|
|
g_variant_unref (specific);
|
|
g_variant_unref (variant);
|
|
return sinfo;
|
|
}
|
|
|
|
/**
|
|
* gst_discoverer_start:
|
|
* @discoverer: A #GstDiscoverer
|
|
*
|
|
* Allow asynchronous discovering of URIs to take place.
|
|
* A #GMainLoop must be available for #GstDiscoverer to properly work in
|
|
* asynchronous mode.
|
|
*/
|
|
void
|
|
gst_discoverer_start (GstDiscoverer * discoverer)
|
|
{
|
|
GSource *source;
|
|
GMainContext *ctx = NULL;
|
|
|
|
g_return_if_fail (GST_IS_DISCOVERER (discoverer));
|
|
|
|
GST_DEBUG_OBJECT (discoverer, "Starting...");
|
|
|
|
if (discoverer->priv->async) {
|
|
GST_DEBUG_OBJECT (discoverer, "We were already started");
|
|
return;
|
|
}
|
|
|
|
discoverer->priv->async = TRUE;
|
|
discoverer->priv->running = TRUE;
|
|
|
|
ctx = g_main_context_get_thread_default ();
|
|
|
|
/* Connect to bus signals */
|
|
if (ctx == NULL)
|
|
ctx = g_main_context_default ();
|
|
|
|
source = gst_bus_create_watch (discoverer->priv->bus);
|
|
g_source_set_callback (source, (GSourceFunc) gst_bus_async_signal_func,
|
|
NULL, NULL);
|
|
discoverer->priv->sourceid = g_source_attach (source, ctx);
|
|
g_source_unref (source);
|
|
discoverer->priv->ctx = g_main_context_ref (ctx);
|
|
|
|
start_discovering (discoverer);
|
|
GST_DEBUG_OBJECT (discoverer, "Started");
|
|
}
|
|
|
|
/**
|
|
* gst_discoverer_stop:
|
|
* @discoverer: A #GstDiscoverer
|
|
*
|
|
* Stop the discovery of any pending URIs and clears the list of
|
|
* pending URIS (if any).
|
|
*/
|
|
void
|
|
gst_discoverer_stop (GstDiscoverer * discoverer)
|
|
{
|
|
g_return_if_fail (GST_IS_DISCOVERER (discoverer));
|
|
|
|
GST_DEBUG_OBJECT (discoverer, "Stopping...");
|
|
|
|
if (!discoverer->priv->async) {
|
|
GST_DEBUG_OBJECT (discoverer,
|
|
"We were already stopped, or running synchronously");
|
|
return;
|
|
}
|
|
|
|
DISCO_LOCK (discoverer);
|
|
if (discoverer->priv->processing) {
|
|
/* We prevent any further processing by setting the bus to
|
|
* flushing and setting the pipeline to READY.
|
|
* _reset() will take care of the rest of the cleanup */
|
|
if (discoverer->priv->bus)
|
|
gst_bus_set_flushing (discoverer->priv->bus, TRUE);
|
|
if (discoverer->priv->pipeline)
|
|
gst_element_set_state ((GstElement *) discoverer->priv->pipeline,
|
|
GST_STATE_READY);
|
|
}
|
|
discoverer->priv->running = FALSE;
|
|
DISCO_UNLOCK (discoverer);
|
|
|
|
/* Remove timeout handler */
|
|
if (discoverer->priv->timeoutid) {
|
|
g_source_remove (discoverer->priv->timeoutid);
|
|
discoverer->priv->timeoutid = 0;
|
|
}
|
|
/* Remove signal watch */
|
|
if (discoverer->priv->sourceid) {
|
|
g_source_remove (discoverer->priv->sourceid);
|
|
discoverer->priv->sourceid = 0;
|
|
}
|
|
/* Unref main context */
|
|
if (discoverer->priv->ctx) {
|
|
g_main_context_unref (discoverer->priv->ctx);
|
|
discoverer->priv->ctx = NULL;
|
|
}
|
|
discoverer_reset (discoverer);
|
|
|
|
discoverer->priv->async = FALSE;
|
|
|
|
GST_DEBUG_OBJECT (discoverer, "Stopped");
|
|
}
|
|
|
|
/**
|
|
* gst_discoverer_discover_uri_async:
|
|
* @discoverer: A #GstDiscoverer
|
|
* @uri: the URI to add.
|
|
*
|
|
* Appends the given @uri to the list of URIs to discoverer. The actual
|
|
* discovery of the @uri will only take place if gst_discoverer_start() has
|
|
* been called.
|
|
*
|
|
* A copy of @uri will be made internally, so the caller can safely g_free()
|
|
* afterwards.
|
|
*
|
|
* Returns: %TRUE if the @uri was successfully appended to the list of pending
|
|
* uris, else %FALSE
|
|
*/
|
|
gboolean
|
|
gst_discoverer_discover_uri_async (GstDiscoverer * discoverer,
|
|
const gchar * uri)
|
|
{
|
|
gboolean can_run;
|
|
|
|
g_return_val_if_fail (GST_IS_DISCOVERER (discoverer), FALSE);
|
|
|
|
GST_DEBUG_OBJECT (discoverer, "uri : %s", uri);
|
|
|
|
DISCO_LOCK (discoverer);
|
|
can_run = (discoverer->priv->pending_uris == NULL);
|
|
discoverer->priv->pending_uris =
|
|
g_list_append (discoverer->priv->pending_uris, g_strdup (uri));
|
|
DISCO_UNLOCK (discoverer);
|
|
|
|
if (can_run)
|
|
start_discovering (discoverer);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Synchronous mode */
|
|
/**
|
|
* gst_discoverer_discover_uri:
|
|
* @discoverer: A #GstDiscoverer
|
|
* @uri: The URI to run on.
|
|
* @err: (out) (allow-none): If an error occurred, this field will be filled in.
|
|
*
|
|
* Synchronously discovers the given @uri.
|
|
*
|
|
* A copy of @uri will be made internally, so the caller can safely g_free()
|
|
* afterwards.
|
|
*
|
|
* Returns: (transfer full): the result of the scanning. Can be %NULL if an
|
|
* error occurred.
|
|
*/
|
|
GstDiscovererInfo *
|
|
gst_discoverer_discover_uri (GstDiscoverer * discoverer, const gchar * uri,
|
|
GError ** err)
|
|
{
|
|
GstDiscovererResult res = 0;
|
|
GstDiscovererInfo *info;
|
|
|
|
g_return_val_if_fail (GST_IS_DISCOVERER (discoverer), NULL);
|
|
g_return_val_if_fail (uri, NULL);
|
|
|
|
GST_DEBUG_OBJECT (discoverer, "uri:%s", uri);
|
|
|
|
DISCO_LOCK (discoverer);
|
|
if (G_UNLIKELY (discoverer->priv->current_info)) {
|
|
DISCO_UNLOCK (discoverer);
|
|
GST_WARNING_OBJECT (discoverer, "Already handling a uri");
|
|
if (err)
|
|
*err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_FAILED,
|
|
"Already handling a uri");
|
|
return NULL;
|
|
}
|
|
|
|
discoverer->priv->pending_uris =
|
|
g_list_append (discoverer->priv->pending_uris, g_strdup (uri));
|
|
DISCO_UNLOCK (discoverer);
|
|
|
|
res = start_discovering (discoverer);
|
|
discoverer_collect (discoverer);
|
|
|
|
/* Get results */
|
|
if (err) {
|
|
if (discoverer->priv->current_error)
|
|
*err = g_error_copy (discoverer->priv->current_error);
|
|
else
|
|
*err = NULL;
|
|
}
|
|
if (res != GST_DISCOVERER_OK) {
|
|
GST_DEBUG ("Setting result to %d (was %d)", res,
|
|
discoverer->priv->current_info->result);
|
|
discoverer->priv->current_info->result = res;
|
|
}
|
|
info = discoverer->priv->current_info;
|
|
|
|
discoverer_cleanup (discoverer);
|
|
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* gst_discoverer_new:
|
|
* @timeout: timeout per file, in nanoseconds. Allowed are values between
|
|
* one second (#GST_SECOND) and one hour (3600 * #GST_SECOND)
|
|
* @err: a pointer to a #GError. can be %NULL
|
|
*
|
|
* Creates a new #GstDiscoverer with the provided timeout.
|
|
*
|
|
* Returns: (transfer full): The new #GstDiscoverer.
|
|
* If an error occurred when creating the discoverer, @err will be set
|
|
* accordingly and %NULL will be returned. If @err is set, the caller must
|
|
* free it when no longer needed using g_error_free().
|
|
*/
|
|
GstDiscoverer *
|
|
gst_discoverer_new (GstClockTime timeout, GError ** err)
|
|
{
|
|
GstDiscoverer *res;
|
|
|
|
res = g_object_new (GST_TYPE_DISCOVERER, "timeout", timeout, NULL);
|
|
if (res->priv->uridecodebin == NULL) {
|
|
if (err)
|
|
*err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN,
|
|
"Couldn't create 'uridecodebin' element");
|
|
gst_object_unref (res);
|
|
res = NULL;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_discoverer_info_to_variant:
|
|
* @info: A #GstDiscovererInfo
|
|
* @flags: A combination of #GstDiscovererSerializeFlags to specify
|
|
* what needs to be serialized.
|
|
*
|
|
* Serializes @info to a #GVariant that can be parsed again
|
|
* through gst_discoverer_info_from_variant().
|
|
*
|
|
* Note that any #GstToc (s) that might have been discovered will not be serialized
|
|
* for now.
|
|
*
|
|
* Returns: (transfer full): A newly-allocated #GVariant representing @info.
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
GVariant *
|
|
gst_discoverer_info_to_variant (GstDiscovererInfo * info,
|
|
GstDiscovererSerializeFlags flags)
|
|
{
|
|
/* FIXME: implement TOC support */
|
|
GVariant *stream_variant;
|
|
GVariant *variant;
|
|
GstDiscovererStreamInfo *sinfo;
|
|
GVariant *wrapper;
|
|
|
|
g_return_val_if_fail (GST_IS_DISCOVERER_INFO (info), NULL);
|
|
g_return_val_if_fail (gst_discoverer_info_get_result (info) ==
|
|
GST_DISCOVERER_OK, NULL);
|
|
|
|
sinfo = gst_discoverer_info_get_stream_info (info);
|
|
stream_variant = gst_discoverer_info_to_variant_recurse (sinfo, flags);
|
|
variant =
|
|
g_variant_new ("(vv)", _serialize_info (info, flags), stream_variant);
|
|
|
|
/* Returning a wrapper implies some small overhead, but simplifies
|
|
* deserializing from bytes */
|
|
wrapper = g_variant_new_variant (variant);
|
|
|
|
gst_discoverer_stream_info_unref (sinfo);
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* gst_discoverer_info_from_variant:
|
|
* @variant: A #GVariant to deserialize into a #GstDiscovererInfo.
|
|
*
|
|
* Parses a #GVariant as produced by gst_discoverer_info_to_variant()
|
|
* back to a #GstDiscovererInfo.
|
|
*
|
|
* Returns: (transfer full): A newly-allocated #GstDiscovererInfo.
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
GstDiscovererInfo *
|
|
gst_discoverer_info_from_variant (GVariant * variant)
|
|
{
|
|
GstDiscovererInfo *info = g_object_new (GST_TYPE_DISCOVERER_INFO, NULL);
|
|
GVariant *info_variant = g_variant_get_variant (variant);
|
|
GVariant *info_specific_variant;
|
|
GVariant *wrapped;
|
|
|
|
GET_FROM_TUPLE (info_variant, variant, 0, &info_specific_variant);
|
|
GET_FROM_TUPLE (info_variant, variant, 1, &wrapped);
|
|
|
|
_parse_info (info, info_specific_variant);
|
|
_parse_discovery (wrapped, info);
|
|
g_variant_unref (info_specific_variant);
|
|
g_variant_unref (info_variant);
|
|
|
|
return info;
|
|
}
|