Fix FSF address
[platform/upstream/gstreamer.git] / gst-libs / gst / pbutils / gstdiscoverer.c
index 1aa1f92..6ff01fa 100644 (file)
@@ -14,8 +14,8 @@
  *
  * 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., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
  */
 
 /**
@@ -36,8 +36,6 @@
  * asks for the discovery to begin (through gst_discoverer_start()).
  *
  * All the information is returned in a #GstDiscovererInfo structure.
- *
- * Since: 0.10.31
  */
 
 #ifdef HAVE_CONFIG_H
@@ -45,9 +43,9 @@
 #endif
 
 #include <gst/video/video.h>
+#include <gst/audio/audio.h>
 
 #include "pbutils.h"
-#include "pbutils-marshal.h"
 #include "pbutils-private.h"
 
 GST_DEBUG_CATEGORY_STATIC (discoverer_debug);
@@ -55,6 +53,7 @@ GST_DEBUG_CATEGORY_STATIC (discoverer_debug);
 
 static GQuark _CAPS_QUARK;
 static GQuark _TAGS_QUARK;
+static GQuark _TOC_QUARK;
 static GQuark _MISSING_PLUGIN_QUARK;
 static GQuark _STREAM_TOPOLOGY_QUARK;
 static GQuark _TOPOLOGY_PAD_QUARK;
@@ -67,6 +66,7 @@ typedef struct
   GstElement *queue;
   GstElement *sink;
   GstTagList *tags;
+  GstToc *toc;
 } PrivateStream;
 
 struct _GstDiscovererPrivate
@@ -79,7 +79,7 @@ struct _GstDiscovererPrivate
   /* list of pending URI to process (current excluded) */
   GList *pending_uris;
 
-  GMutex *lock;
+  GMutex lock;
 
   /* TRUE if processing a URI */
   gboolean processing;
@@ -87,6 +87,9 @@ struct _GstDiscovererPrivate
   /* 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;
@@ -95,6 +98,9 @@ struct _GstDiscovererPrivate
   /* 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;
@@ -113,12 +119,13 @@ struct _GstDiscovererPrivate
   /* 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);
+#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)
@@ -127,6 +134,7 @@ _do_init (void)
 
   _CAPS_QUARK = g_quark_from_static_string ("caps");
   _TAGS_QUARK = g_quark_from_static_string ("tags");
+  _TOC_QUARK = g_quark_from_static_string ("toc");
   _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");
@@ -140,6 +148,7 @@ enum
   SIGNAL_FINISHED,
   SIGNAL_STARTING,
   SIGNAL_DISCOVERED,
+  SIGNAL_SOURCE_SETUP,
   LAST_SIGNAL
 };
 
@@ -163,8 +172,11 @@ 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,
@@ -176,6 +188,7 @@ 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;
@@ -184,7 +197,7 @@ gst_discoverer_class_init (GstDiscovererClass * klass)
 
   /* properties */
   /**
-   * GstDiscoverer:timeout
+   * GstDiscoverer:timeout:
    *
    * The duration (in nanoseconds) after which the discovery of an individual
    * URI will timeout.
@@ -225,15 +238,40 @@ gst_discoverer_class_init (GstDiscovererClass * klass)
    * @discoverer: the #GstDiscoverer
    * @info: the results #GstDiscovererInfo
    * @error: (type GLib.Error): #GError, which will be non-NULL if an error
-   *                            occurred during discovery
+   *                            occurred during discovery. You must not
+   *                            free this #GError, it will be freed by
+   *                            the discoverer.
+   *
+   * Will be emitted when all information on a URI could be discovered, or
+   * an error ocurred.
    *
-   * Will be emitted when all information on a URI could be discovered.
+   * 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, pbutils_marshal_VOID__POINTER_BOXED,
-      G_TYPE_NONE, 2, GST_TYPE_DISCOVERER_INFO, GST_TYPE_G_ERROR);
+      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
@@ -259,8 +297,11 @@ gst_discoverer_init (GstDiscoverer * dc)
 
   dc->priv->timeout = DEFAULT_PROP_TIMEOUT;
   dc->priv->async = FALSE;
+  dc->priv->async_done = FALSE;
+
+  g_mutex_init (&dc->priv->lock);
 
-  dc->priv->lock = g_mutex_new ();
+  dc->priv->pending_subtitle_pads = 0;
 
   GST_LOG ("Creating pipeline");
   dc->priv->pipeline = (GstBin *) gst_pipeline_new ("Discoverer");
@@ -280,6 +321,9 @@ gst_discoverer_init (GstDiscoverer * dc)
   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);
@@ -338,6 +382,7 @@ gst_discoverer_dispose (GObject * obj)
     /* 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);
 
@@ -352,11 +397,6 @@ gst_discoverer_dispose (GObject * obj)
 
   gst_discoverer_stop (dc);
 
-  if (dc->priv->lock) {
-    g_mutex_free (dc->priv->lock);
-    dc->priv->lock = NULL;
-  }
-
   if (dc->priv->seeking_query) {
     gst_query_unref (dc->priv->seeking_query);
     dc->priv->seeking_query = NULL;
@@ -366,6 +406,16 @@ gst_discoverer_dispose (GObject * 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)
 {
@@ -410,10 +460,11 @@ gst_discoverer_set_timeout (GstDiscoverer * dc, GstClockTime timeout)
   DISCO_UNLOCK (dc);
 }
 
-static GstProbeReturn
-_event_probe (GstPad * pad, GstProbeType type, GstEvent * event,
-    PrivateStream * ps)
+static GstPadProbeReturn
+_event_probe (GstPad * pad, GstPadProbeInfo * info, PrivateStream * ps)
 {
+  GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+
   if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) {
     GstTagList *tl = NULL, *tmp;
 
@@ -427,7 +478,7 @@ _event_probe (GstPad * pad, GstProbeType type, GstEvent * event,
           ps->tags);
       tmp = gst_tag_list_merge (ps->tags, tl, GST_TAG_MERGE_APPEND);
       if (ps->tags)
-        gst_tag_list_free (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);
@@ -436,22 +487,77 @@ _event_probe (GstPad * pad, GstProbeType type, GstEvent * event,
     DISCO_UNLOCK (ps->dc);
   }
 
-  return GST_PROBE_OK;
+  if (GST_EVENT_TYPE (event) == 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);
+  }
+
+  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");
+
 static gboolean
 is_subtitle_caps (const GstCaps * caps)
 {
-  static GstCaps *subs_caps = NULL;
+  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 (!subs_caps) {
-    subs_caps = gst_caps_from_string ("text/plain; text/x-pango-markup; "
-        "subpicture/x-pgs; subpicture/x-dvb; application/x-subtitle-unknown; "
-        "application/x-ssa; application/x-ass; subtitle/x-kate; "
-        "application/x-kate; video/x-dvd-subpicture; ");
+  if (!(GST_IS_BUFFER (info->data) || (GST_IS_EVENT (info->data)
+              && GST_EVENT_TYPE ((GstEvent *) info->data) == GST_EVENT_GAP)))
+    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;
 
-  return gst_caps_can_intersect (caps, subs_caps);
+}
+
+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
@@ -477,12 +583,21 @@ uridecodebin_pad_added_cb (GstElement * uridecodebin, GstPad * pad,
   g_object_set (ps->sink, "silent", TRUE, NULL);
   g_object_set (ps->queue, "max-size-buffers", 1, "silent", TRUE, NULL);
 
-  caps = gst_pad_get_caps (pad, 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);
@@ -497,16 +612,13 @@ uridecodebin_pad_added_cb (GstElement * uridecodebin, GstPad * pad,
   if (!gst_element_sync_state_with_parent (ps->queue))
     goto error;
 
-  sinkpad = gst_element_get_static_pad (ps->queue, "sink");
-  if (sinkpad == NULL)
-    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_PROBE_TYPE_EVENT,
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
       (GstPadProbeCallback) _event_probe, ps, NULL);
 
   DISCO_LOCK (dc);
@@ -568,7 +680,10 @@ uridecodebin_pad_removed_cb (GstElement * uridecodebin, GstPad * pad,
   gst_bin_remove_many (dc->priv->pipeline, ps->sink, ps->queue, NULL);
 
   if (ps->tags) {
-    gst_tag_list_free (ps->tags);
+    gst_tag_list_unref (ps->tags);
+  }
+  if (ps->toc) {
+    gst_toc_unref (ps->toc);
   }
 
   g_slice_free (PrivateStream, ps);
@@ -584,7 +699,7 @@ collect_stream_information (GstDiscoverer * dc, PrivateStream * ps, guint idx)
   gchar *stname;
 
   stname = g_strdup_printf ("stream-%02d", idx);
-  st = gst_structure_empty_new (stname);
+  st = gst_structure_new_empty (stname);
   g_free (stname);
 
   /* Get caps */
@@ -592,7 +707,7 @@ collect_stream_information (GstDiscoverer * dc, PrivateStream * ps, guint idx)
   if (!caps) {
     GST_WARNING ("Couldn't get negotiated caps from %s:%s",
         GST_DEBUG_PAD_NAME (ps->pad));
-    caps = gst_pad_get_caps (ps->pad, NULL);
+    caps = gst_pad_query_caps (ps->pad, NULL);
   }
   if (caps) {
     GST_DEBUG ("Got caps %" GST_PTR_FORMAT, caps);
@@ -601,11 +716,30 @@ collect_stream_information (GstDiscoverer * dc, PrivateStream * ps, guint idx)
     gst_caps_unref (caps);
   }
   if (ps->tags)
-    gst_structure_id_set (st, _TAGS_QUARK, GST_TYPE_STRUCTURE, ps->tags, NULL);
+    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);
 
   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);
+}
+
 /* Parses a set of caps and tags in st and populates a GstDiscovererStreamInfo
  * structure (parent, if !NULL, otherwise it allocates one)
  */
@@ -614,7 +748,9 @@ collect_information (GstDiscoverer * dc, const GstStructure * st,
     GstDiscovererStreamInfo * parent)
 {
   GstCaps *caps;
-  GstStructure *caps_st, *tags_st;
+  GstStructure *caps_st;
+  GstTagList *tags_st;
+  GstToc *toc_st;
   const gchar *name;
   int tmp;
   guint utmp;
@@ -622,7 +758,7 @@ collect_information (GstDiscoverer * dc, const GstStructure * st,
   if (!st || !gst_structure_id_has_field (st, _CAPS_QUARK)) {
     GST_WARNING ("Couldn't find caps !");
     if (parent)
-      return parent;
+      return gst_discoverer_stream_info_ref (parent);
     else
       return (GstDiscovererStreamInfo *)
           g_object_new (GST_TYPE_DISCOVERER_STREAM_INFO, NULL);
@@ -634,13 +770,14 @@ collect_information (GstDiscoverer * dc, const GstStructure * st,
 
   if (g_str_has_prefix (name, "audio/")) {
     GstDiscovererAudioInfo *info;
+    const gchar *format_str;
 
     if (parent)
-      info = (GstDiscovererAudioInfo *) parent;
+      info = (GstDiscovererAudioInfo *) gst_discoverer_stream_info_ref (parent);
     else {
       info = (GstDiscovererAudioInfo *)
           g_object_new (GST_TYPE_DISCOVERER_AUDIO_INFO, NULL);
-      info->parent.caps = caps;
+      info->parent.caps = gst_caps_ref (caps);
     }
 
     if (gst_structure_get_int (caps_st, "rate", &tmp))
@@ -649,24 +786,34 @@ collect_information (GstDiscoverer * dc, const GstStructure * st,
     if (gst_structure_get_int (caps_st, "channels", &tmp))
       info->channels = (guint) tmp;
 
-    if (gst_structure_get_int (caps_st, "depth", &tmp))
-      info->depth = (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);
+      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_STRUCTURE, &tags_st, NULL);
-      if (gst_structure_get_uint (tags_st, GST_TAG_BITRATE, &utmp) ||
-          gst_structure_get_uint (tags_st, GST_TAG_NOMINAL_BITRATE, &utmp))
+      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_structure_get_uint (tags_st, GST_TAG_MAXIMUM_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? */
-      info->parent.tags = gst_tag_list_merge (info->parent.tags,
-          (GstTagList *) tags_st, GST_TAG_MERGE_REPLACE);
+      gst_discoverer_merge_and_replace_tags (&info->parent.tags, tags_st);
+    }
 
-      gst_structure_free (tags_st);
+    if (gst_structure_id_has_field (st, _TOC_QUARK)) {
+      gst_structure_id_get (st, _TOC_QUARK, GST_TYPE_TOC, &toc_st, NULL);
+      info->parent.toc = toc_st;
     }
 
     if (!info->language && ((GstDiscovererStreamInfo *) info)->tags) {
@@ -677,6 +824,7 @@ collect_information (GstDiscoverer * dc, const GstStructure * st,
       }
     }
 
+    gst_caps_unref (caps);
     return (GstDiscovererStreamInfo *) info;
 
   } else if (g_str_has_prefix (name, "video/") ||
@@ -685,18 +833,14 @@ collect_information (GstDiscoverer * dc, const GstStructure * st,
     GstVideoInfo vinfo;
 
     if (parent)
-      info = (GstDiscovererVideoInfo *) parent;
+      info = (GstDiscovererVideoInfo *) gst_discoverer_stream_info_ref (parent);
     else {
       info = (GstDiscovererVideoInfo *)
           g_object_new (GST_TYPE_DISCOVERER_VIDEO_INFO, NULL);
-      info->parent.caps = caps;
+      info->parent.caps = gst_caps_ref (caps);
     }
 
-    /* FIXME : gst_video_info_from_caps only works with raw caps,
-     * wouldn't we want to get all the info below for non-raw caps ? 
-     */
-    if (g_str_has_prefix (name, "video/x-raw") &&
-        gst_video_info_from_caps (&vinfo, caps)) {
+    if (gst_video_info_from_caps (&vinfo, caps)) {
       info->width = (guint) vinfo.width;
       info->height = (guint) vinfo.height;
 
@@ -708,53 +852,60 @@ collect_information (GstDiscoverer * dc, const GstStructure * st,
       info->framerate_num = vinfo.fps_n;
       info->framerate_denom = vinfo.fps_d;
 
-      info->interlaced = (vinfo.flags & GST_VIDEO_FLAG_INTERLACED) != 0;
+      info->interlaced =
+          vinfo.interlace_mode != GST_VIDEO_INTERLACE_MODE_PROGRESSIVE;
     }
 
     if (gst_structure_id_has_field (st, _TAGS_QUARK)) {
-      gst_structure_id_get (st, _TAGS_QUARK,
-          GST_TYPE_STRUCTURE, &tags_st, NULL);
-      if (gst_structure_get_uint (tags_st, GST_TAG_BITRATE, &utmp) ||
-          gst_structure_get_uint (tags_st, GST_TAG_NOMINAL_BITRATE, &utmp))
+      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_structure_get_uint (tags_st, GST_TAG_MAXIMUM_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? */
-      info->parent.tags = gst_tag_list_merge (info->parent.tags,
-          (GstTagList *) tags_st, GST_TAG_MERGE_REPLACE);
-      gst_structure_free (tags_st);
+      gst_discoverer_merge_and_replace_tags (&info->parent.tags,
+          (GstTagList *) tags_st);
+    }
+
+    if (gst_structure_id_has_field (st, _TOC_QUARK)) {
+      gst_structure_id_get (st, _TOC_QUARK, GST_TYPE_TOC, &toc_st, NULL);
+      info->parent.toc = toc_st;
     }
 
+    gst_caps_unref (caps);
     return (GstDiscovererStreamInfo *) info;
 
   } else if (is_subtitle_caps (caps)) {
     GstDiscovererSubtitleInfo *info;
 
     if (parent)
-      info = (GstDiscovererSubtitleInfo *) parent;
+      info =
+          (GstDiscovererSubtitleInfo *) gst_discoverer_stream_info_ref (parent);
     else {
       info = (GstDiscovererSubtitleInfo *)
           g_object_new (GST_TYPE_DISCOVERER_SUBTITLE_INFO, NULL);
-      info->parent.caps = caps;
+      info->parent.caps = gst_caps_ref (caps);
     }
 
     if (gst_structure_id_has_field (st, _TAGS_QUARK)) {
       const gchar *language;
 
-      gst_structure_id_get (st, _TAGS_QUARK,
-          GST_TYPE_STRUCTURE, &tags_st, NULL);
+      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? */
-      info->parent.tags = gst_tag_list_merge (info->parent.tags,
-          (GstTagList *) tags_st, GST_TAG_MERGE_REPLACE);
-      gst_structure_free (tags_st);
+      gst_discoverer_merge_and_replace_tags (&info->parent.tags, tags_st);
+    }
 
+    if (gst_structure_id_has_field (st, _TOC_QUARK)) {
+      gst_structure_id_get (st, _TOC_QUARK, GST_TYPE_TOC, &toc_st, NULL);
+      info->parent.toc = toc_st;
     }
 
     if (!info->language && ((GstDiscovererStreamInfo *) info)->tags) {
@@ -765,6 +916,7 @@ collect_information (GstDiscoverer * dc, const GstStructure * st,
       }
     }
 
+    gst_caps_unref (caps);
     return (GstDiscovererStreamInfo *) info;
 
   } else {
@@ -772,20 +924,23 @@ collect_information (GstDiscoverer * dc, const GstStructure * st,
     GstDiscovererStreamInfo *info;
 
     if (parent)
-      info = parent;
+      info = gst_discoverer_stream_info_ref (parent);
     else {
       info = (GstDiscovererStreamInfo *)
           g_object_new (GST_TYPE_DISCOVERER_STREAM_INFO, NULL);
-      info->caps = caps;
+      info->caps = gst_caps_ref (caps);
     }
 
-    if (gst_structure_id_get (st, _TAGS_QUARK,
-            GST_TYPE_STRUCTURE, &tags_st, NULL)) {
-      info->tags = gst_tag_list_merge (info->tags, (GstTagList *) tags_st,
-          GST_TAG_MERGE_REPLACE);
-      gst_structure_free (tags_st);
+    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);
     }
 
+    if (gst_structure_id_get (st, _TOC_QUARK, GST_TYPE_TOC, &toc_st, NULL)) {
+      info->toc = toc_st;
+    }
+
+    gst_caps_unref (caps);
     return info;
   }
 
@@ -802,15 +957,17 @@ find_stream_for_node (GstDiscoverer * dc, const GstStructure * topology)
   GList *tmp;
 
   if (!gst_structure_id_has_field (topology, _TOPOLOGY_PAD_QUARK)) {
-    GST_DEBUG ("Could not find pad for node %" GST_PTR_FORMAT "\n", topology);
+    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);
 
-  if (!dc->priv->streams)
+  if (!dc->priv->streams) {
+    gst_object_unref (pad);
     return NULL;
+  }
 
   for (i = 0, tmp = dc->priv->streams; tmp; tmp = tmp->next, i++) {
     ps = (PrivateStream *) tmp->data;
@@ -831,9 +988,9 @@ find_stream_for_node (GstDiscoverer * dc, const GstStructure * topology)
 }
 
 static gboolean
-child_is_raw_stream (GstCaps * parent, GstCaps * child)
+child_is_raw_stream (const GstCaps * parent, const GstCaps * child)
 {
-  GstStructure *st1, *st2;
+  const GstStructure *st1, *st2;
   const gchar *name1, *name2;
 
   st1 = gst_caps_get_structure (parent, 0);
@@ -902,30 +1059,29 @@ parse_stream_topology (GstDiscoverer * dc, const GstStructure * topology,
           /* 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_unref (parent->caps);
-          parent->caps = caps;
+          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;
-          gst_caps_unref (caps);
-
         } 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)) {
@@ -949,16 +1105,16 @@ parse_stream_topology (GstDiscoverer * dc, const GstStructure * topology,
       GstTagList *tmp;
 
       gst_structure_id_get (topology, _TAGS_QUARK,
-          GST_TYPE_STRUCTURE, &tags, NULL);
+          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_free (tags);
+      gst_tag_list_unref (tags);
       if (cont->parent.tags)
-        gst_tag_list_free (cont->parent.tags);
+        gst_tag_list_unref (cont->parent.tags);
       cont->parent.tags = tmp;
       GST_DEBUG ("Container info tags %" GST_PTR_FORMAT, tmp);
     }
@@ -1005,6 +1161,31 @@ discoverer_collect (GstDiscoverer * dc)
       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) {
@@ -1039,12 +1220,14 @@ discoverer_collect (GstDiscoverer * dc)
     if (dc->priv->current_info->duration == 0 &&
         dc->priv->current_info->stream_info != NULL &&
         dc->priv->current_info->stream_info->next == NULL) {
-      GstStructure *st =
-          gst_caps_get_structure (dc->priv->current_info->stream_info->caps, 0);
+      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 *) dc->priv->current_info->stream_info)->
-            is_image = TRUE;
+        ((GstDiscovererVideoInfo *) stream_info)->is_image = TRUE;
     }
   }
 
@@ -1054,6 +1237,7 @@ discoverer_collect (GstDiscoverer * dc)
         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;
   }
 }
 
@@ -1113,8 +1297,15 @@ handle_message (GstDiscoverer * dc, GstMessage * msg)
       /* We need to stop */
       done = TRUE;
 
-      GST_DEBUG ("Setting result to ERROR");
-      dc->priv->current_info->result = GST_DISCOVERER_ERROR;
+      /* 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;
 
@@ -1123,10 +1314,30 @@ handle_message (GstDiscoverer * dc, GstMessage * msg)
       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");
-        done = TRUE;
+        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;
@@ -1144,8 +1355,12 @@ handle_message (GstDiscoverer * dc, GstMessage * msg)
         GST_DEBUG_OBJECT (GST_MESSAGE_SRC (msg),
             "Setting result to MISSING_PLUGINS");
         dc->priv->current_info->result = GST_DISCOVERER_MISSING_PLUGINS;
+        if (dc->priv->current_info->misc)
+          gst_structure_free (dc->priv->current_info->misc);
         dc->priv->current_info->misc = gst_structure_copy (structure);
       } 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);
       }
     }
@@ -1161,15 +1376,27 @@ handle_message (GstDiscoverer * dc, GstMessage * msg)
       tmp =
           gst_tag_list_merge (dc->priv->current_info->tags, tl,
           GST_TAG_MERGE_APPEND);
-      gst_tag_list_free (tl);
+      gst_tag_list_unref (tl);
       if (dc->priv->current_info->tags)
-        gst_tag_list_free (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);
+      dc->priv->current_info->toc = tmp;
+      GST_DEBUG_OBJECT (GST_MESSAGE_SRC (msg), "Current info %p, toc %"
+          GST_PTR_FORMAT, dc->priv->current_info, tmp);
+    }
+      break;
+
     default:
       break;
   }
@@ -1177,7 +1404,6 @@ handle_message (GstDiscoverer * dc, GstMessage * msg)
   return done;
 }
 
-
 static void
 handle_current_sync (GstDiscoverer * dc)
 {
@@ -1196,7 +1422,6 @@ handle_current_sync (GstDiscoverer * dc)
       done = handle_message (dc, msg);
       gst_message_unref (msg);
     }
-
   } while (!done && (g_timer_elapsed (timer, NULL) < deadline));
 
   /* return result */
@@ -1265,6 +1490,9 @@ discoverer_cleanup (GstDiscoverer * dc)
 
   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) {
@@ -1364,8 +1592,6 @@ beach:
  * Allow asynchronous discovering of URIs to take place.
  * A #GMainLoop must be available for #GstDiscoverer to properly work in
  * asynchronous mode.
- *
- * Since: 0.10.31
  */
 void
 gst_discoverer_start (GstDiscoverer * discoverer)
@@ -1406,8 +1632,6 @@ gst_discoverer_start (GstDiscoverer * discoverer)
  *
  * Stop the discovery of any pending URIs and clears the list of
  * pending URIS (if any).
- *
- * Since: 0.10.31
  */
 void
 gst_discoverer_stop (GstDiscoverer * discoverer)
@@ -1468,10 +1692,8 @@ gst_discoverer_stop (GstDiscoverer * discoverer)
  * A copy of @uri will be made internally, so the caller can safely g_free()
  * afterwards.
  *
- * Returns: %TRUE if the @uri was succesfully appended to the list of pending
+ * Returns: %TRUE if the @uri was successfully appended to the list of pending
  * uris, else %FALSE
- *
- * Since: 0.10.31
  */
 gboolean
 gst_discoverer_discover_uri_async (GstDiscoverer * discoverer,
@@ -1508,8 +1730,6 @@ gst_discoverer_discover_uri_async (GstDiscoverer * discoverer,
  *
  * Returns: (transfer full): the result of the scanning. Can be %NULL if an
  * error occurred.
- *
- * Since: 0.10.31
  */
 GstDiscovererInfo *
 gst_discoverer_discover_uri (GstDiscoverer * discoverer, const gchar * uri,
@@ -1565,8 +1785,6 @@ gst_discoverer_discover_uri (GstDiscoverer * discoverer, const gchar * uri,
  * 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().
- *
- * Since: 0.10.31
  */
 GstDiscoverer *
 gst_discoverer_new (GstClockTime timeout, GError ** err)