/* GStreamer
* Copyright (C) <2006> Edward Hervey <edward@fluendo.com>
* Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2011> Hewlett-Packard Development Company, L.P.
+ * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
#include "config.h"
#endif
+/* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex
+ * with newer GLib versions (>= 2.31.0) */
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+
#include <gst/gst-i18n-plugin.h>
#include <string.h>
#include "gstplayback.h"
#include "gstrawcaps.h"
+#include "gst/glib-compat-private.h"
+
/* generic templates */
static GstStaticPadTemplate decoder_bin_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
#define GST_CAT_DEFAULT gst_decode_bin_debug
typedef struct _GstPendingPad GstPendingPad;
+typedef struct _GstDecodeElement GstDecodeElement;
typedef struct _GstDecodeChain GstDecodeChain;
typedef struct _GstDecodeGroup GstDecodeGroup;
typedef struct _GstDecodePad GstDecodePad;
gboolean have_type; /* if we received the have_type signal */
guint have_type_id; /* signal id for have-type from typefind */
- gboolean async_pending; /* async-start has been emited */
+ gboolean async_pending; /* async-start has been emitted */
GMutex *dyn_lock; /* lock protecting pad blocking */
gboolean shutdown; /* if we are shutting down */
GList *blocked_pads; /* pads that have set to block */
gboolean expose_allstreams; /* Whether to expose unknow type streams or not */
+
+ GList *filtered; /* elements for which error messages are filtered */
};
struct _GstDecodeBinClass
/* automatic sizes, while prerolling we buffer up to 2MB, we ignore time
* and buffers in this case. */
-#define AUTO_PREROLL_SIZE_BYTES 2 * 1024 * 1024
-#define AUTO_PREROLL_SIZE_BUFFERS 0
-#define AUTO_PREROLL_SIZE_TIME 0
+#define AUTO_PREROLL_SIZE_BYTES 2 * 1024 * 1024
+#define AUTO_PREROLL_SIZE_BUFFERS 0
+#define AUTO_PREROLL_NOT_SEEKABLE_SIZE_TIME 10 * GST_SECOND
+#define AUTO_PREROLL_SEEKABLE_SIZE_TIME 0
/* whan playing, keep a max of 2MB of data but try to keep the number of buffers
* as low as possible (try to aim for 5 buffers) */
GstCaps * caps, GstDecodeBin * decode_bin);
static void decodebin_set_queue_size (GstDecodeBin * dbin,
- GstElement * multiqueue, gboolean preroll);
+ GstElement * multiqueue, gboolean preroll, gboolean seekable);
static gboolean gst_decode_bin_autoplug_continue (GstElement * element,
GstPad * pad, GstCaps * caps);
static GstPad *find_sink_pad (GstElement * element);
static GstStateChangeReturn gst_decode_bin_change_state (GstElement * element,
GstStateChange transition);
+static void gst_decode_bin_handle_message (GstBin * bin, GstMessage * message);
+
+static gboolean check_upstream_seekable (GstDecodeBin * dbin, GstPad * pad);
#define EXPOSE_LOCK(dbin) G_STMT_START { \
GST_LOG_OBJECT (dbin, \
gulong event_probe_id;
};
+struct _GstDecodeElement
+{
+ GstElement *element;
+ GstElement *capsfilter; /* Optional capsfilter for Parser/Convert */
+};
+
/* GstDecodeGroup
*
* Streams belonging to the same group/chain of a media file
GstPad *pad; /* srcpad that caused creation of this chain */
gboolean demuxer; /* TRUE if elements->data is a demuxer */
+ gboolean seekable; /* TRUE if this chain ends on a demuxer and is seekable */
GList *elements; /* All elements in this group, first
is the latest and most downstream element */
static GstDecodeGroup *gst_decode_group_new (GstDecodeBin * dbin,
GstDecodeChain * chain);
static gboolean gst_decode_chain_is_complete (GstDecodeChain * chain);
-static void gst_decode_chain_handle_eos (GstDecodeChain * chain);
static gboolean gst_decode_chain_expose (GstDecodeChain * chain,
GList ** endpads, gboolean * missing_plugin);
static gboolean gst_decode_chain_is_drained (GstDecodeChain * chain);
{
GObjectClass *gobject_klass;
GstElementClass *gstelement_klass;
+ GstBinClass *gstbin_klass;
gobject_klass = (GObjectClass *) klass;
gstelement_klass = (GstElementClass *) klass;
+ gstbin_klass = (GstBinClass *) klass;
parent_class = g_type_class_peek_parent (klass);
* @pad: The #GstPad.
* @caps: The #GstCaps found.
*
- * This function is emited when an array of possible factories for @caps on
+ * This function is emitted when an array of possible factories for @caps on
* @pad is needed. Decodebin2 will by default return an array with all
* compatible factories, sorted by rank.
*
* @factories: A #GValueArray of possible #GstElementFactory to use.
*
* Once decodebin2 has found the possible #GstElementFactory objects to try
- * for @caps on @pad, this signal is emited. The purpose of the signal is for
+ * for @caps on @pad, this signal is emitted. The purpose of the signal is for
* the application to perform additional sorting or filtering on the element
* factory array.
*
*
* This signal is emitted once decodebin2 has found all the possible
* #GstElementFactory that can be used to handle the given @caps. For each of
- * those factories, this signal is emited.
+ * those factories, this signal is emitted.
*
* The signal handler should return a #GST_TYPE_AUTOPLUG_SELECT_RESULT enum
* value indicating what decodebin2 should do next.
/**
* GstDecodebin2:max-size-bytes
*
- * Max amount amount of bytes in the queue (0=automatic).
+ * Max amount of bytes in the queue (0=automatic).
*
* Since: 0.10.26
*/
/**
* GstDecodebin2:max-size-buffers
*
- * Max amount amount of buffers in the queue (0=automatic).
+ * Max amount of buffers in the queue (0=automatic).
*
* Since: 0.10.26
*/
/**
* GstDecodebin2:max-size-time
*
- * Max amount amount of time in the queue (in ns, 0=automatic).
+ * Max amount of time in the queue (in ns, 0=automatic).
*
* Since: 0.10.26
*/
klass->autoplug_sort = GST_DEBUG_FUNCPTR (gst_decode_bin_autoplug_sort);
klass->autoplug_select = GST_DEBUG_FUNCPTR (gst_decode_bin_autoplug_select);
- gst_element_class_add_pad_template (gstelement_klass,
- gst_static_pad_template_get (&decoder_bin_sink_template));
- gst_element_class_add_pad_template (gstelement_klass,
- gst_static_pad_template_get (&decoder_bin_src_template));
+ gst_element_class_add_static_pad_template (gstelement_klass,
+ &decoder_bin_sink_template);
+ gst_element_class_add_static_pad_template (gstelement_klass,
+ &decoder_bin_src_template);
gst_element_class_set_details_simple (gstelement_klass,
"Decoder Bin", "Generic/Bin/Decoder",
gstelement_klass->change_state =
GST_DEBUG_FUNCPTR (gst_decode_bin_change_state);
+
+ gstbin_klass->handle_message =
+ GST_DEBUG_FUNCPTR (gst_decode_bin_handle_message);
}
/* Must be called with factories lock! */
gboolean apcontinue = TRUE;
GValueArray *factories = NULL, *result = NULL;
GstDecodePad *dpad;
+ GstElementFactory *factory;
+ const gchar *classification;
+ gboolean is_parser_converter = FALSE;
+ gboolean res;
GST_DEBUG_OBJECT (dbin, "Pad %s:%s caps:%" GST_PTR_FORMAT,
GST_DEBUG_PAD_NAME (pad), caps);
- if (chain->elements && src != chain->elements->data) {
+ if (chain->elements
+ && src != ((GstDecodeElement *) chain->elements->data)->element
+ && src != ((GstDecodeElement *) chain->elements->data)->capsfilter) {
GST_ERROR_OBJECT (dbin, "New pad from not the last element in this chain");
return;
}
dpad = gst_decode_pad_new (dbin, pad, chain);
/* 1. Emit 'autoplug-continue' the result will tell us if this pads needs
- * further autoplugging. */
- g_signal_emit (G_OBJECT (dbin),
- gst_decode_bin_signals[SIGNAL_AUTOPLUG_CONTINUE], 0, dpad, caps,
- &apcontinue);
+ * further autoplugging. Only do this for fixed caps, for unfixed caps
+ * we will later come here again from the notify::caps handler. The
+ * problem with unfixed caps is that we can reliably tell if the output
+ * is e.g. accepted by a sink because only parts of the possible final
+ * caps might be accepted by the sink. */
+ if (gst_caps_is_fixed (caps))
+ g_signal_emit (G_OBJECT (dbin),
+ gst_decode_bin_signals[SIGNAL_AUTOPLUG_CONTINUE], 0, dpad, caps,
+ &apcontinue);
+ else
+ apcontinue = TRUE;
/* 1.a if autoplug-continue is FALSE or caps is a raw format, goto pad_is_final */
if ((!apcontinue) || are_final_caps (dbin, caps))
goto expose_pad;
- /* 1.b when the caps are not fixed yet, we can't be sure what element to
+ /* 1.b For Parser/Converter that can output different stream formats
+ * we insert a capsfilter with the sorted caps of all possible next
+ * elements and continue with the capsfilter srcpad */
+ factory = gst_element_get_factory (src);
+ classification = gst_element_factory_get_klass (factory);
+ is_parser_converter = (strstr (classification, "Parser")
+ && strstr (classification, "Converter"));
+
+ /* 1.c when the caps are not fixed yet, we can't be sure what element to
* connect. We delay autoplugging until the caps are fixed */
- if (!gst_caps_is_fixed (caps))
+ if (!is_parser_converter && !gst_caps_is_fixed (caps))
goto non_fixed;
- /* 1.c else get the factories and if there's no compatible factory goto
+ /* 1.d else get the factories and if there's no compatible factory goto
* unknown_type */
g_signal_emit (G_OBJECT (dbin),
gst_decode_bin_signals[SIGNAL_AUTOPLUG_FACTORIES], 0, dpad, caps,
/* If the caps are raw, this just means we don't want to expose them */
if (gst_caps_can_intersect (raw, caps)) {
+ g_value_array_free (factories);
gst_caps_unref (raw);
gst_object_unref (dpad);
goto discarded_type;
goto unknown_type;
}
- /* 1.d sort some more. */
+ /* 1.e sort some more. */
g_signal_emit (G_OBJECT (dbin),
gst_decode_bin_signals[SIGNAL_AUTOPLUG_SORT], 0, dpad, caps, factories,
&result);
/* At this point we have a potential decoder, but we might not need it
* if it doesn't match the output caps */
- if (!dbin->expose_allstreams) {
+ if (!dbin->expose_allstreams && gst_caps_is_fixed (caps)) {
guint i;
const GList *tmps;
gboolean dontuse = FALSE;
GST_DEBUG ("Checking if we can abort early");
- /* 1.e Do an early check to see if the candidates are potential decoders, but
+ /* 1.f Do an early check to see if the candidates are potential decoders, but
* due to the fact that they decode to a mediatype that is not final we don't
* need them */
for (i = 0; i < factories->n_values && !dontuse; i++) {
GstElementFactory *factory =
- g_value_get_object (g_value_array_get_nth (factories, 0));
+ g_value_get_object (g_value_array_get_nth (factories, i));
GstCaps *tcaps;
/* We are only interested in skipping decoders */
if (dontuse) {
gst_object_unref (dpad);
+ g_value_array_free (factories);
goto discarded_type;
}
}
- /* 1.f else continue autoplugging something from the list. */
+ /* 1.g now get the factory template caps and insert the capsfilter if this
+ * is a parser/converter
+ */
+ if (is_parser_converter) {
+ GstCaps *filter_caps;
+ gint i;
+ GstPad *p;
+ GstDecodeElement *delem;
+
+ g_assert (chain->elements != NULL);
+ delem = (GstDecodeElement *) chain->elements->data;
+
+ filter_caps = gst_caps_new_empty ();
+ for (i = 0; i < factories->n_values; i++) {
+ GstElementFactory *factory =
+ g_value_get_object (g_value_array_get_nth (factories, i));
+ GstCaps *tcaps, *intersection;
+ const GList *tmps;
+
+ GST_DEBUG ("Trying factory %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+
+ if (gst_element_get_factory (src) == factory) {
+ GST_DEBUG ("Skipping factory");
+ continue;
+ }
+
+ for (tmps = gst_element_factory_get_static_pad_templates (factory); tmps;
+ tmps = tmps->next) {
+ GstStaticPadTemplate *st = (GstStaticPadTemplate *) tmps->data;
+ if (st->direction != GST_PAD_SINK || st->presence != GST_PAD_ALWAYS)
+ continue;
+ tcaps = gst_static_pad_template_get_caps (st);
+ intersection =
+ gst_caps_intersect_full (tcaps, caps, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_merge (filter_caps, intersection);
+ gst_caps_unref (tcaps);
+ }
+ }
+
+ /* Append the parser caps to prevent any not-negotiated errors */
+ gst_caps_merge (filter_caps, gst_caps_copy (caps));
+
+ delem->capsfilter = gst_element_factory_make ("capsfilter", NULL);
+ g_object_set (G_OBJECT (delem->capsfilter), "caps", filter_caps, NULL);
+ gst_caps_unref (filter_caps);
+ gst_element_set_state (delem->capsfilter, GST_STATE_PAUSED);
+ gst_bin_add (GST_BIN_CAST (dbin), gst_object_ref (delem->capsfilter));
+
+ gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (dpad), NULL);
+ p = gst_element_get_static_pad (delem->capsfilter, "sink");
+ gst_pad_link_full (pad, p, GST_PAD_LINK_CHECK_NOTHING);
+ gst_object_unref (p);
+ p = gst_element_get_static_pad (delem->capsfilter, "src");
+ gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (dpad), p);
+ pad = p;
+
+ if (!gst_caps_is_fixed (caps)) {
+ g_value_array_free (factories);
+ goto non_fixed;
+ }
+ }
+
+ /* 1.h else continue autoplugging something from the list. */
GST_LOG_OBJECT (pad, "Let's continue discovery on this pad");
- connect_pad (dbin, src, dpad, pad, caps, factories, chain);
+ res = connect_pad (dbin, src, dpad, pad, caps, factories, chain);
+
+ /* Need to unref the capsfilter srcpad here if
+ * we inserted a capsfilter */
+ if (is_parser_converter)
+ gst_object_unref (pad);
gst_object_unref (dpad);
g_value_array_free (factories);
+ if (!res)
+ goto unknown_type;
+
return;
expose_pad:
ppad->event_probe_id =
gst_pad_add_event_probe (pad, (GCallback) pad_event_cb, ppad);
chain->pending_pads = g_list_prepend (chain->pending_pads, ppad);
- CHAIN_MUTEX_UNLOCK (chain);
g_signal_connect (G_OBJECT (pad), "notify::caps",
G_CALLBACK (caps_notify_cb), chain);
+ CHAIN_MUTEX_UNLOCK (chain);
+
+ /* If we're here because we have a Parser/Converter
+ * we have to unref the pad */
+ if (is_parser_converter)
+ gst_object_unref (pad);
+
return;
}
}
+static void
+add_error_filter (GstDecodeBin * dbin, GstElement * element)
+{
+ GST_OBJECT_LOCK (dbin);
+ dbin->filtered = g_list_prepend (dbin->filtered, element);
+ GST_OBJECT_UNLOCK (dbin);
+}
+
+static void
+remove_error_filter (GstDecodeBin * dbin, GstElement * element)
+{
+ GST_OBJECT_LOCK (dbin);
+ dbin->filtered = g_list_remove (dbin->filtered, element);
+ GST_OBJECT_UNLOCK (dbin);
+}
/* connect_pad:
*
while (factories->n_values > 0) {
GstAutoplugSelectResult ret;
GstElementFactory *factory;
+ GstDecodeElement *delem;
GstElement *element;
GstPad *sinkpad;
gboolean subtitle;
CHAIN_MUTEX_LOCK (chain);
for (l = chain->elements; l; l = l->next) {
- GstElement *otherelement = GST_ELEMENT_CAST (l->data);
+ GstDecodeElement *delem = (GstDecodeElement *) l->data;
+ GstElement *otherelement = delem->element;
if (gst_element_get_factory (otherelement) == factory) {
skip = TRUE;
continue;
}
- /* ... activate it ... We do this before adding it to the bin so that we
- * don't accidentally make it post error messages that will stop
- * everything. */
- if ((gst_element_set_state (element,
- GST_STATE_READY)) == GST_STATE_CHANGE_FAILURE) {
- GST_WARNING_OBJECT (dbin, "Couldn't set %s to READY",
+ /* Filter errors, this will prevent the element from causing the pipeline
+ * to error while we test it using READY state. */
+ add_error_filter (dbin, element);
+
+ /* ... add it ... */
+ if (!(gst_bin_add (GST_BIN_CAST (dbin), element))) {
+ GST_WARNING_OBJECT (dbin, "Couldn't add %s to the bin",
GST_ELEMENT_NAME (element));
+ remove_error_filter (dbin, element);
gst_object_unref (element);
continue;
}
- /* 2.3. Find its sink pad, this should work after activating it. */
+ /* Find its sink pad. */
if (!(sinkpad = find_sink_pad (element))) {
GST_WARNING_OBJECT (dbin, "Element %s doesn't have a sink pad",
GST_ELEMENT_NAME (element));
- gst_element_set_state (element, GST_STATE_NULL);
- gst_object_unref (element);
+ remove_error_filter (dbin, element);
+ gst_bin_remove (GST_BIN (dbin), element);
continue;
}
- /* 2.4 add it ... */
- if (!(gst_bin_add (GST_BIN_CAST (dbin), element))) {
- GST_WARNING_OBJECT (dbin, "Couldn't add %s to the bin",
- GST_ELEMENT_NAME (element));
+ /* ... and try to link */
+ if ((gst_pad_link (pad, sinkpad)) != GST_PAD_LINK_OK) {
+ GST_WARNING_OBJECT (dbin, "Link failed on pad %s:%s",
+ GST_DEBUG_PAD_NAME (sinkpad));
+ remove_error_filter (dbin, element);
gst_object_unref (sinkpad);
- gst_element_set_state (element, GST_STATE_NULL);
- gst_object_unref (element);
+ gst_bin_remove (GST_BIN (dbin), element);
continue;
}
- /* 2.5 ...and try to link */
- if ((gst_pad_link (pad, sinkpad)) != GST_PAD_LINK_OK) {
- GST_WARNING_OBJECT (dbin, "Link failed on pad %s:%s",
- GST_DEBUG_PAD_NAME (sinkpad));
- gst_element_set_state (element, GST_STATE_NULL);
+ /* ... activate it ... */
+ if ((gst_element_set_state (element,
+ GST_STATE_READY)) == GST_STATE_CHANGE_FAILURE) {
+ GST_WARNING_OBJECT (dbin, "Couldn't set %s to READY",
+ GST_ELEMENT_NAME (element));
+ remove_error_filter (dbin, element);
gst_object_unref (sinkpad);
gst_bin_remove (GST_BIN (dbin), element);
continue;
}
+
+ /* Stop filtering errors. */
+ remove_error_filter (dbin, element);
+
gst_object_unref (sinkpad);
GST_LOG_OBJECT (dbin, "linked on pad %s:%s", GST_DEBUG_PAD_NAME (pad));
CHAIN_MUTEX_LOCK (chain);
- chain->elements =
- g_list_prepend (chain->elements, gst_object_ref (element));
+ delem = g_slice_new (GstDecodeElement);
+ delem->element = gst_object_ref (element);
+ delem->capsfilter = NULL;
+ chain->elements = g_list_prepend (chain->elements, delem);
chain->demuxer = is_demuxer_element (element);
CHAIN_MUTEX_UNLOCK (chain);
/* Bring the element to the state of the parent */
if ((gst_element_set_state (element,
GST_STATE_PAUSED)) == GST_STATE_CHANGE_FAILURE) {
+ GstDecodeElement *dtmp = NULL;
GstElement *tmp = NULL;
GST_WARNING_OBJECT (dbin, "Couldn't set %s to PAUSED",
do {
GList *l;
- tmp = chain->elements->data;
+ dtmp = chain->elements->data;
+ tmp = dtmp->element;
/* Disconnect any signal handlers that might be connected
* in connect_element() or analyze_pad() */
l = n;
}
+ if (dtmp->capsfilter) {
+ gst_bin_remove (GST_BIN (dbin), dtmp->capsfilter);
+ gst_element_set_state (dtmp->capsfilter, GST_STATE_NULL);
+ gst_object_unref (dtmp->capsfilter);
+ }
+
gst_bin_remove (GST_BIN (dbin), tmp);
gst_element_set_state (tmp, GST_STATE_NULL);
gst_object_unref (tmp);
+ g_slice_free (GstDecodeElement, dtmp);
+
chain->elements = g_list_delete_link (chain->elements, chain->elements);
} while (tmp != element);
CHAIN_MUTEX_UNLOCK (chain);
return;
}
+/* check_upstream_seekable:
+ *
+ * Check if upstream is seekable.
+ */
+static gboolean
+check_upstream_seekable (GstDecodeBin * dbin, GstPad * pad)
+{
+ GstQuery *query;
+ gint64 start = -1, stop = -1;
+ gboolean seekable = FALSE;
+
+ query = gst_query_new_seeking (GST_FORMAT_BYTES);
+ if (!gst_pad_peer_query (pad, query)) {
+ GST_DEBUG_OBJECT (dbin, "seeking query failed");
+ gst_query_unref (query);
+ return FALSE;
+ }
+
+ gst_query_parse_seeking (query, NULL, &seekable, &start, &stop);
+
+ gst_query_unref (query);
+
+ /* try harder to query upstream size if we didn't get it the first time */
+ if (seekable && stop == -1) {
+ GstFormat fmt = GST_FORMAT_BYTES;
+
+ GST_DEBUG_OBJECT (dbin, "doing duration query to fix up unset stop");
+ gst_pad_query_peer_duration (pad, &fmt, &stop);
+ }
+
+ /* if upstream doesn't know the size, it's likely that it's not seekable in
+ * practice even if it technically may be seekable */
+ if (seekable && (start != 0 || stop <= start)) {
+ GST_DEBUG_OBJECT (dbin, "seekable but unknown start/stop -> disable");
+ return FALSE;
+ }
+
+ GST_DEBUG_OBJECT (dbin, "upstream seekable: %d", seekable);
+ return seekable;
+}
+
static void
type_found (GstElement * typefind, guint probability,
GstCaps * caps, GstDecodeBin * decode_bin)
GST_LOG_OBJECT (element, "got no more pads");
CHAIN_MUTEX_LOCK (chain);
- if (!chain->elements || (GstElement *) chain->elements->data != element) {
+ if (!chain->elements
+ || ((GstDecodeElement *) chain->elements->data)->element != element) {
GST_LOG_OBJECT (chain->dbin, "no-more-pads from old chain element '%s'",
GST_OBJECT_NAME (element));
CHAIN_MUTEX_UNLOCK (chain);
* we can probably set its buffering state to playing now */
GST_DEBUG_OBJECT (group->dbin, "Setting group %p multiqueue to "
"'playing' buffering mode", group);
- decodebin_set_queue_size (group->dbin, group->multiqueue, FALSE);
+ decodebin_set_queue_size (group->dbin, group->multiqueue, FALSE,
+ (group->parent ? group->parent->seekable : TRUE));
CHAIN_MUTEX_UNLOCK (chain);
EXPOSE_LOCK (chain->dbin);
chain->pending_pads = NULL;
for (l = chain->elements; l; l = l->next) {
- GstElement *element = GST_ELEMENT (l->data);
+ GstDecodeElement *delem = l->data;
+ GstElement *element = delem->element;
g_signal_handlers_disconnect_by_func (element, pad_added_cb, chain);
g_signal_handlers_disconnect_by_func (element, pad_removed_cb, chain);
g_signal_handlers_disconnect_by_func (element, no_more_pads_cb, chain);
+ if (delem->capsfilter) {
+ if (GST_OBJECT_PARENT (delem->capsfilter) ==
+ GST_OBJECT_CAST (chain->dbin))
+ gst_bin_remove (GST_BIN_CAST (chain->dbin), delem->capsfilter);
+ if (!hide) {
+ gst_element_set_state (delem->capsfilter, GST_STATE_NULL);
+ }
+ }
+
if (GST_OBJECT_PARENT (element) == GST_OBJECT_CAST (chain->dbin))
gst_bin_remove (GST_BIN_CAST (chain->dbin), element);
if (!hide) {
SUBTITLE_UNLOCK (chain->dbin);
if (!hide) {
+ if (delem->capsfilter) {
+ gst_object_unref (delem->capsfilter);
+ delem->capsfilter = NULL;
+ }
+
gst_object_unref (element);
l->data = NULL;
+
+ g_slice_free (GstDecodeElement, delem);
}
}
if (!hide) {
queue);
group->overrun = TRUE;
+ /* this group has prerolled enough to not need more pads,
+ * we can probably set its buffering state to playing now */
+ GST_DEBUG_OBJECT (group->dbin, "Setting group %p multiqueue to "
+ "'playing' buffering mode", group);
+ decodebin_set_queue_size (group->dbin, group->multiqueue, FALSE,
+ (group->parent ? group->parent->seekable : TRUE));
/* FIXME: We should make sure that everything gets exposed now
* even if child chains are not complete because the will never
* playing or prerolling. */
static void
decodebin_set_queue_size (GstDecodeBin * dbin, GstElement * multiqueue,
- gboolean preroll)
+ gboolean preroll, gboolean seekable)
{
guint max_bytes, max_buffers;
guint64 max_time;
if ((max_buffers = dbin->max_size_buffers) == 0)
max_buffers = AUTO_PREROLL_SIZE_BUFFERS;
if ((max_time = dbin->max_size_time) == 0)
- max_time = AUTO_PREROLL_SIZE_TIME;
+ max_time = seekable ? AUTO_PREROLL_SEEKABLE_SIZE_TIME :
+ AUTO_PREROLL_NOT_SEEKABLE_SIZE_TIME;
} else {
/* update runtime limits. At runtime, we try to keep the amount of buffers
* in the queues as low as possible (but at least 5 buffers). */
{
GstDecodeGroup *group = g_slice_new0 (GstDecodeGroup);
GstElement *mq;
+ gboolean seekable;
GST_DEBUG_OBJECT (dbin, "Creating new group %p with parent chain %p", group,
parent);
}
/* configure queue sizes for preroll */
- decodebin_set_queue_size (dbin, mq, TRUE);
+ seekable = FALSE;
+ if (parent && parent->demuxer) {
+ GstElement *element =
+ ((GstDecodeElement *) parent->elements->data)->element;
+ GstPad *pad = gst_element_get_static_pad (element, "sink");
+ if (pad) {
+ seekable = parent->seekable = check_upstream_seekable (dbin, pad);
+ gst_object_unref (pad);
+ }
+ }
+ decodebin_set_queue_size (dbin, mq, TRUE, seekable);
group->overrunsig = g_signal_connect (G_OBJECT (mq), "overrun",
G_CALLBACK (multi_queue_overrun_cb), group);
return complete;
}
-/* check if the group is drained, meaning all pads have seen an EOS
- * event. */
-static void
-gst_decode_pad_handle_eos (GstDecodePad * pad)
+static gboolean
+drain_and_switch_chains (GstDecodeChain * chain, GstDecodePad * drainpad,
+ gboolean * last_group, gboolean * drained, gboolean * switched);
+/* drain_and_switch_chains/groups:
+ *
+ * CALL WITH CHAIN LOCK (or group parent) TAKEN !
+ *
+ * Goes down the chains/groups until it finds the chain
+ * to which the drainpad belongs.
+ *
+ * It marks that pad/chain as drained and then will figure
+ * out which group to switch to or not.
+ *
+ * last_chain will be set to TRUE if the group to which the
+ * pad belongs is the last one.
+ *
+ * drained will be set to TRUE if the chain/group is drained.
+ *
+ * Returns: TRUE if the chain contained the target pad */
+static gboolean
+drain_and_switch_group (GstDecodeGroup * group, GstDecodePad * drainpad,
+ gboolean * last_group, gboolean * drained, gboolean * switched)
{
- GstDecodeChain *chain = pad->chain;
+ gboolean handled = FALSE;
+ gboolean alldrained = TRUE;
+ GList *tmp;
+
+ GST_DEBUG ("Checking group %p (target pad %s:%s)",
+ group, GST_DEBUG_PAD_NAME (drainpad));
- GST_LOG_OBJECT (pad->dbin, "chain : %p, pad %p", chain, pad);
- pad->drained = TRUE;
- gst_decode_chain_handle_eos (chain);
+ /* Definitely can't be in drained groups */
+ if (G_UNLIKELY (group->drained)) {
+ goto beach;
+ }
+
+ /* Figure out if all our chains are drained with the
+ * new information */
+ for (tmp = group->children; tmp; tmp = tmp->next) {
+ GstDecodeChain *chain = (GstDecodeChain *) tmp->data;
+ gboolean subdrained = FALSE;
+
+ handled |=
+ drain_and_switch_chains (chain, drainpad, last_group, &subdrained,
+ switched);
+ if (!subdrained)
+ alldrained = FALSE;
+ }
+
+beach:
+ GST_DEBUG ("group %p (last_group:%d, drained:%d, switched:%d, handled:%d)",
+ group, *last_group, alldrained, *switched, handled);
+ *drained = alldrained;
+ return handled;
}
-/* gst_decode_chain_handle_eos:
- *
- * Checks if there are next groups in any parent chain
- * to which we can switch or if everything is drained.
- *
- * If there are groups to switch to, hide the current active
- * one and expose the new one.
- *
- * MT-safe, don't call with chain lock!
- */
-static void
-gst_decode_chain_handle_eos (GstDecodeChain * eos_chain)
+static gboolean
+drain_and_switch_chains (GstDecodeChain * chain, GstDecodePad * drainpad,
+ gboolean * last_group, gboolean * drained, gboolean * switched)
{
- GstDecodeBin *dbin = eos_chain->dbin;
- GstDecodeGroup *group;
- GstDecodeChain *chain = eos_chain;
- gboolean drained;
+ gboolean handled = FALSE;
+ GstDecodeBin *dbin = chain->dbin;
- g_return_if_fail (eos_chain->endpad);
+ GST_DEBUG ("Checking chain %p (target pad %s:%s)",
+ chain, GST_DEBUG_PAD_NAME (drainpad));
CHAIN_MUTEX_LOCK (chain);
- while ((group = chain->parent)) {
- CHAIN_MUTEX_UNLOCK (chain);
- chain = group->parent;
- CHAIN_MUTEX_LOCK (chain);
- if (gst_decode_group_is_drained (group)) {
- continue;
+ if (chain->endpad) {
+ /* Check if we're reached the target endchain */
+ if (chain == drainpad->chain) {
+ GST_DEBUG ("Found the target chain");
+ drainpad->drained = TRUE;
+ handled = TRUE;
}
- break;
+
+ *drained = chain->endpad->drained;
+ goto beach;
}
- drained = chain->active_group ?
- gst_decode_group_is_drained (chain->active_group) : TRUE;
-
- /* Now either group == NULL and chain == dbin->decode_chain
- * or chain is the lowest chain that has a non-drained group */
- if (chain->active_group && drained && chain->next_groups) {
- GST_DEBUG_OBJECT (dbin, "Hiding current group %p", chain->active_group);
- gst_decode_group_hide (chain->active_group);
- chain->old_groups = g_list_prepend (chain->old_groups, chain->active_group);
- GST_DEBUG_OBJECT (dbin, "Switching to next group %p",
- chain->next_groups->data);
- chain->active_group = chain->next_groups->data;
- chain->next_groups =
- g_list_delete_link (chain->next_groups, chain->next_groups);
- CHAIN_MUTEX_UNLOCK (chain);
+ /* We known there are groups to switch to */
+ if (chain->next_groups)
+ *last_group = FALSE;
+
+ /* Check the active group */
+ if (chain->active_group) {
+ gboolean subdrained = FALSE;
+ handled = drain_and_switch_group (chain->active_group, drainpad,
+ last_group, &subdrained, switched);
+
+ /* The group is drained, see if we can switch to another */
+ if (handled && subdrained && !*switched) {
+ if (chain->next_groups) {
+ /* Switch to next group */
+ GST_DEBUG_OBJECT (dbin, "Hiding current group %p", chain->active_group);
+ gst_decode_group_hide (chain->active_group);
+ chain->old_groups =
+ g_list_prepend (chain->old_groups, chain->active_group);
+ GST_DEBUG_OBJECT (dbin, "Switching to next group %p",
+ chain->next_groups->data);
+ chain->active_group = chain->next_groups->data;
+ chain->next_groups =
+ g_list_delete_link (chain->next_groups, chain->next_groups);
+ *switched = TRUE;
+ *drained = FALSE;
+ } else {
+ GST_DEBUG ("Group %p was the last in chain %p", chain->active_group,
+ chain);
+ *drained = TRUE;
+ /* We're drained ! */
+ }
+ }
+ }
+
+beach:
+ CHAIN_MUTEX_UNLOCK (chain);
+
+ GST_DEBUG ("Chain %p (handled:%d, last_group:%d, drained:%d, switched:%d)",
+ chain, handled, *last_group, *drained, *switched);
+
+ if (*drained)
+ g_signal_emit (dbin, gst_decode_bin_signals[SIGNAL_DRAINED], 0, NULL);
+
+ return handled;
+}
+
+/* check if the group is drained, meaning all pads have seen an EOS
+ * event. */
+static gboolean
+gst_decode_pad_handle_eos (GstDecodePad * pad)
+{
+ gboolean last_group = TRUE;
+ gboolean switched = FALSE;
+ gboolean drained = FALSE;
+ GstDecodeChain *chain = pad->chain;
+ GstDecodeBin *dbin = chain->dbin;
+
+ GST_LOG_OBJECT (dbin, "pad %p", pad);
+ drain_and_switch_chains (dbin->decode_chain, pad, &last_group, &drained,
+ &switched);
+
+ if (switched) {
+ /* If we resulted in a group switch, expose what's needed */
EXPOSE_LOCK (dbin);
if (gst_decode_chain_is_complete (dbin->decode_chain))
gst_decode_bin_expose (dbin);
EXPOSE_UNLOCK (dbin);
- } else if (!chain->active_group || drained) {
- g_assert (chain == dbin->decode_chain);
- CHAIN_MUTEX_UNLOCK (chain);
-
- GST_LOG_OBJECT (dbin, "all groups drained, fire signal");
- g_signal_emit (G_OBJECT (dbin), gst_decode_bin_signals[SIGNAL_DRAINED], 0,
- NULL);
- } else {
- CHAIN_MUTEX_UNLOCK (chain);
- GST_DEBUG_OBJECT (dbin,
- "Current active group in chain %p is not drained yet", chain);
}
+
+ return last_group;
}
/* gst_decode_group_is_drained:
}
static GstCaps *
-_gst_element_get_linked_caps (GstElement * src, GstElement * sink)
+_gst_element_get_linked_caps (GstElement * src, GstElement * sink,
+ GstPad ** srcpad)
{
GstIterator *it;
GstElement *parent;
parent = gst_pad_get_parent_element (peer);
if (parent == sink) {
caps = gst_pad_get_negotiated_caps (pad);
+ if (srcpad) {
+ gst_object_ref (pad);
+ *srcpad = pad;
+ }
done = TRUE;
}
static GQuark topology_caps = 0;
static GQuark topology_next = 0;
static GQuark topology_pad = 0;
+static GQuark topology_element_srcpad = 0;
/* FIXME: Invent gst_structure_take_structure() to prevent all the
* structure copying for nothing
u = gst_structure_id_empty_new (topology_structure_name);
/* Now at the last element */
- if (chain->elements && (chain->endpad || chain->deadend)) {
+ if ((chain->elements || !chain->active_group) &&
+ (chain->endpad || chain->deadend)) {
s = gst_structure_id_empty_new (topology_structure_name);
gst_structure_id_set (u, topology_caps, GST_TYPE_CAPS, chain->endcaps,
NULL);
- if (chain->endpad)
+ if (chain->endpad) {
gst_structure_id_set (u, topology_pad, GST_TYPE_PAD, chain->endpad, NULL);
+ gst_structure_id_set (u, topology_element_srcpad, GST_TYPE_PAD,
+ chain->endpad, NULL);
+ }
gst_structure_id_set (s, topology_next, GST_TYPE_STRUCTURE, u, NULL);
gst_structure_free (u);
u = s;
/* Get caps between all elements in this chain */
l = (chain->elements && chain->elements->next) ? chain->elements : NULL;
for (; l && l->next; l = l->next) {
- GstCaps *caps = _gst_element_get_linked_caps (l->next->data, l->data);
+ GstDecodeElement *delem, *delem_next;
+ GstElement *elem, *elem_next;
+ GstCaps *caps;
+ GstPad *srcpad;
+
+ delem = l->data;
+ elem = delem->element;
+ delem_next = l->next->data;
+ elem_next = delem_next->element;
+ srcpad = NULL;
+
+ caps = _gst_element_get_linked_caps (elem_next, elem, &srcpad);
if (caps) {
s = gst_structure_id_empty_new (topology_structure_name);
gst_structure_free (u);
u = s;
}
+
+ if (srcpad) {
+ gst_structure_id_set (u, topology_element_srcpad, GST_TYPE_PAD, srcpad,
+ NULL);
+ gst_object_unref (srcpad);
+ }
}
/* Caps that resulted in this chain */
caps = NULL;
}
}
- gst_structure_set (u, "caps", GST_TYPE_CAPS, caps, NULL);
+ gst_structure_id_set (u, topology_caps, GST_TYPE_CAPS, caps, NULL);
+ gst_structure_id_set (u, topology_element_srcpad, GST_TYPE_PAD, chain->pad,
+ NULL);
gst_caps_unref (caps);
return u;
/* 4. Signal no-more-pads. This allows the application to hook stuff to the
* exposed pads */
- GST_LOG_OBJECT (dbin, "signalling no-more-pads");
+ GST_LOG_OBJECT (dbin, "signaling no-more-pads");
gst_element_no_more_pads (GST_ELEMENT (dbin));
/* 5. Send a custom element message with the stream topology */
dbin = group->dbin;
/* configure queues for playback */
- decodebin_set_queue_size (dbin, group->multiqueue, FALSE);
+ decodebin_set_queue_size (dbin, group->multiqueue, FALSE, TRUE);
/* we can now disconnect any overrun signal, which is used to expose the
* group. */
static gboolean
source_pad_event_probe (GstPad * pad, GstEvent * event, GstDecodePad * dpad)
{
+ gboolean res = TRUE;
+
GST_LOG_OBJECT (pad, "%s dpad:%p", GST_EVENT_TYPE_NAME (event), dpad);
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
GST_DEBUG_OBJECT (pad, "we received EOS");
- /* Check if all pads are drained. If there is a next group to expose, we
- * will remove the ghostpad of the current group first, which unlinks the
- * peer and so drops the EOS. */
- gst_decode_pad_handle_eos (dpad);
+ /* Check if all pads are drained.
+ * * If there is no next group, we will let the EOS go through.
+ * * If there is a next group but the current group isn't completely
+ * drained, we will drop the EOS event.
+ * * If there is a next group to expose and this was the last non-drained
+ * pad for that group, we will remove the ghostpad of the current group
+ * first, which unlinks the peer and so drops the EOS. */
+ res = gst_decode_pad_handle_eos (dpad);
}
- /* never drop events */
- return TRUE;
+
+ return res;
}
static void
}
}
+static void
+gst_decode_bin_handle_message (GstBin * bin, GstMessage * msg)
+{
+ GstDecodeBin *dbin = GST_DECODE_BIN (bin);
+ gboolean drop = FALSE;
+
+ if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
+ GST_OBJECT_LOCK (dbin);
+ drop = (g_list_find (dbin->filtered, GST_MESSAGE_SRC (msg)) != NULL);
+ GST_OBJECT_UNLOCK (dbin);
+ }
+
+ if (drop)
+ gst_message_unref (msg);
+ else
+ GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
+}
+
gboolean
gst_decode_bin_plugin_init (GstPlugin * plugin)
{
topology_caps = g_quark_from_static_string ("caps");
topology_next = g_quark_from_static_string ("next");
topology_pad = g_quark_from_static_string ("pad");
+ topology_element_srcpad = g_quark_from_static_string ("element-srcpad");
return gst_element_register (plugin, "decodebin2", GST_RANK_NONE,
GST_TYPE_DECODE_BIN);