From ae4a216a4be6cea44717cde006be4b15b578945e Mon Sep 17 00:00:00 2001 From: Eunhae Choi Date: Mon, 25 Feb 2019 14:57:13 +0900 Subject: [PATCH] playback: apply the upstream 1.14.4 - apply the latest stable playback code to use playbin3/decodebin3. Change-Id: Ifb3e6600d1b3dec971dbb9c26ef8c3a94de82050 --- gst/playback/Makefile.am | 1 + gst/playback/gstdecodebin3-parse.c | 61 +- gst/playback/gstdecodebin3.c | 302 ++++- gst/playback/gstparsebin.c | 152 ++- gst/playback/gstplayback.c | 1 + gst/playback/gstplayback.h | 1 + gst/playback/gstplaybackutils.c | 8 +- gst/playback/gstplaybin2.c | 19 +- gst/playback/gstplaybin3.c | 2351 ++++++++++++++++-------------------- gst/playback/gstplaysink.c | 66 +- gst/playback/gsturidecodebin.c | 102 +- gst/playback/gsturidecodebin3.c | 1087 +++++++++++++++++ gst/playback/gsturisourcebin.c | 584 ++------- 13 files changed, 2756 insertions(+), 1979 deletions(-) create mode 100644 gst/playback/gsturidecodebin3.c diff --git a/gst/playback/Makefile.am b/gst/playback/Makefile.am index 9603dab..180bf5f 100644 --- a/gst/playback/Makefile.am +++ b/gst/playback/Makefile.am @@ -6,6 +6,7 @@ libgstplayback_la_SOURCES = \ gstdecodebin2.c \ gstdecodebin3.c \ gsturidecodebin.c \ + gsturidecodebin3.c \ gsturisourcebin.c \ gstparsebin.c \ gstplayback.c \ diff --git a/gst/playback/gstdecodebin3-parse.c b/gst/playback/gstdecodebin3-parse.c index 8c51d91..aea0f66 100644 --- a/gst/playback/gstdecodebin3-parse.c +++ b/gst/playback/gstdecodebin3-parse.c @@ -35,6 +35,20 @@ #include "gstplayback.h" #endif +#define CUSTOM_EOS_QUARK _custom_eos_quark_get () +#define CUSTOM_EOS_QUARK_DATA "custom-eos" +static GQuark +_custom_eos_quark_get (void) +{ + static gsize g_quark; + + if (g_once_init_enter (&g_quark)) { + gsize quark = (gsize) g_quark_from_static_string ("decodebin3-custom-eos"); + g_once_init_leave (&g_quark, quark); + } + return g_quark; +} + /* Streams that come from demuxers (input/upstream) */ /* FIXME : All this is hardcoded. Switch to tree of chains */ struct _DecodebinInputStream @@ -63,6 +77,7 @@ static void parsebin_pad_added_cb (GstElement * demux, GstPad * pad, static void parsebin_pad_removed_cb (GstElement * demux, GstPad * pad, DecodebinInput * input); +/* WITH SELECTION_LOCK TAKEN! */ static gboolean pending_pads_are_eos (DecodebinInput * input) { @@ -77,6 +92,7 @@ pending_pads_are_eos (DecodebinInput * input) return TRUE; } +/* WITH SELECTION_LOCK TAKEN! */ static gboolean all_inputs_are_eos (GstDecodebin3 * dbin) { @@ -99,6 +115,7 @@ all_inputs_are_eos (GstDecodebin3 * dbin) return TRUE; } +/* WITH SELECTION_LOCK TAKEN! */ static void check_all_streams_for_eos (GstDecodebin3 * dbin) { @@ -205,7 +222,7 @@ parse_chain_output_probe (GstPad * pad, GstPadProbeInfo * info, case GST_EVENT_STREAM_START: { GstStream *stream = NULL; - guint group_id = G_MAXUINT32; + guint group_id = GST_GROUP_ID_INVALID; if (!gst_event_parse_group_id (ev, &group_id)) { GST_FIXME_OBJECT (pad, @@ -231,10 +248,10 @@ parse_chain_output_probe (GstPad * pad, GstPadProbeInfo * info, gst_object_unref (input->active_stream); input->active_stream = stream; /* We have the beginning of a stream, get a multiqueue slot and link to it */ - g_mutex_lock (&input->dbin->selection_lock); + SELECTION_LOCK (input->dbin); slot = get_slot_for_input (input->dbin, input); link_input_to_slot (input, slot); - g_mutex_unlock (&input->dbin->selection_lock); + SELECTION_UNLOCK (input->dbin); } else gst_object_unref (stream); } @@ -253,18 +270,21 @@ parse_chain_output_probe (GstPad * pad, GstPadProbeInfo * info, input->saw_eos = TRUE; if (all_inputs_are_eos (input->dbin)) { GST_DEBUG_OBJECT (pad, "real input pad, marking as EOS"); + SELECTION_LOCK (input->dbin); check_all_streams_for_eos (input->dbin); + SELECTION_UNLOCK (input->dbin); } else { GstPad *peer = gst_pad_get_peer (input->srcpad); if (peer) { /* Send custom-eos event to multiqueue slot */ - GstStructure *s; GstEvent *event; GST_DEBUG_OBJECT (pad, "Got EOS end of input stream, post custom-eos"); - s = gst_structure_new_empty ("decodebin3-custom-eos"); - event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + event = gst_event_new_eos (); + gst_event_set_seqnum (event, gst_event_get_seqnum (ev)); + gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (event), + CUSTOM_EOS_QUARK, (gchar *) CUSTOM_EOS_QUARK_DATA, NULL); gst_pad_send_event (peer, event); gst_object_unref (peer); } else { @@ -337,12 +357,15 @@ create_input_stream (GstDecodebin3 * dbin, GstStream * stream, GstPad * pad, (GstPadProbeCallback) parse_chain_output_probe, res, NULL); /* Add to list of current input streams */ + SELECTION_LOCK (dbin); dbin->input_streams = g_list_append (dbin->input_streams, res); + SELECTION_UNLOCK (dbin); GST_DEBUG_OBJECT (pad, "Done creating input stream"); return res; } +/* WITH SELECTION_LOCK TAKEN! */ static void remove_input_stream (GstDecodebin3 * dbin, DecodebinInputStream * stream) { @@ -362,9 +385,7 @@ remove_input_stream (GstDecodebin3 * dbin, DecodebinInputStream * stream) } } - g_mutex_lock (&dbin->selection_lock); slot = get_slot_for_input (dbin, stream); - g_mutex_unlock (&dbin->selection_lock); if (slot) { slot->pending_stream = NULL; slot->input = NULL; @@ -390,8 +411,6 @@ parsebin_buffer_probe (GstPad * pad, GstPadProbeInfo * info, GstDecodebin3 *dbin = input->dbin; GList *tmp, *unused_slot = NULL; - GST_FIXME_OBJECT (dbin, "Need a lock !"); - GST_DEBUG_OBJECT (pad, "Got a buffer ! UNBLOCK !"); /* Any data out the demuxer means it's not creating pads @@ -402,11 +421,17 @@ parsebin_buffer_probe (GstPad * pad, GstPadProbeInfo * info, /* 2. Remove unused streams (push EOS) */ GST_DEBUG_OBJECT (dbin, "Removing unused streams"); + SELECTION_LOCK (dbin); tmp = dbin->input_streams; while (tmp != NULL) { DecodebinInputStream *input_stream = (DecodebinInputStream *) tmp->data; GList *next = tmp->next; + if (input_stream->input != input) { + tmp = next; + continue; + } + GST_DEBUG_OBJECT (dbin, "Checking input stream %p", input_stream); if (input_stream->input_buffer_probe_id) { GST_DEBUG_OBJECT (dbin, @@ -423,6 +448,7 @@ parsebin_buffer_probe (GstPad * pad, GstPadProbeInfo * info, } else tmp = next; } + SELECTION_UNLOCK (dbin); GST_DEBUG_OBJECT (dbin, "Creating new streams (if needed)"); /* 3. Create new streams */ @@ -442,10 +468,10 @@ parsebin_buffer_probe (GstPad * pad, GstPadProbeInfo * info, /* See if we can link it straight away */ input_stream->active_stream = stream; - g_mutex_lock (&dbin->selection_lock); + SELECTION_LOCK (dbin); slot = get_slot_for_input (dbin, input_stream); link_input_to_slot (input_stream, slot); - g_mutex_unlock (&dbin->selection_lock); + SELECTION_UNLOCK (dbin); /* Remove the buffer and event probe */ gst_pad_remove_probe (ppad->pad, ppad->buffer_probe); @@ -458,7 +484,7 @@ parsebin_buffer_probe (GstPad * pad, GstPadProbeInfo * info, input->pending_pads = NULL; /* Weed out unused multiqueue slots */ - g_mutex_lock (&dbin->selection_lock); + SELECTION_LOCK (dbin); for (tmp = dbin->slots; tmp; tmp = tmp->next) { MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data; GST_LOG_OBJECT (dbin, "Slot %d input:%p", slot->id, slot->input); @@ -467,7 +493,7 @@ parsebin_buffer_probe (GstPad * pad, GstPadProbeInfo * info, g_list_append (unused_slot, gst_object_ref (slot->sink_pad)); } } - g_mutex_unlock (&dbin->selection_lock); + SELECTION_UNLOCK (dbin); for (tmp = unused_slot; tmp; tmp = tmp->next) { GstPad *sink_pad = (GstPad *) tmp->data; @@ -536,11 +562,11 @@ parsebin_pad_added_cb (GstElement * demux, GstPad * pad, DecodebinInput * input) input->pending_pads = g_list_append (input->pending_pads, ppad); - /* FIXME : ONLY DO FOR THIS PARSEBIN/INPUT ! */ /* Check if all existing input streams have a buffer probe set */ for (tmp = dbin->input_streams; tmp; tmp = tmp->next) { DecodebinInputStream *input_stream = (DecodebinInputStream *) tmp->data; - if (input_stream->input_buffer_probe_id == 0) { + if (input_stream->input == input && + input_stream->input_buffer_probe_id == 0) { GST_DEBUG_OBJECT (input_stream->srcpad, "Adding blocking buffer probe"); input_stream->input_buffer_probe_id = gst_pad_add_probe (input_stream->srcpad, @@ -575,11 +601,8 @@ parsebin_pad_removed_cb (GstElement * demux, GstPad * pad, DecodebinInput * inp) SELECTION_LOCK (dbin); slot = get_slot_for_input (dbin, input); - SELECTION_UNLOCK (dbin); remove_input_stream (dbin, input); - - SELECTION_LOCK (dbin); if (slot && g_list_find (dbin->slots, slot) && slot->is_drained) { /* if slot is still there and already drained, remove it in here */ if (slot->output) { diff --git a/gst/playback/gstdecodebin3.c b/gst/playback/gstdecodebin3.c index 773f624..68b0c76 100644 --- a/gst/playback/gstdecodebin3.c +++ b/gst/playback/gstdecodebin3.c @@ -181,6 +181,21 @@ GST_DEBUG_CATEGORY_STATIC (decodebin3_debug); #define EXTRA_DEBUG 1 +#define CUSTOM_FINAL_EOS_QUARK _custom_final_eos_quark_get () +#define CUSTOM_FINAL_EOS_QUARK_DATA "custom-final-eos" +static GQuark +_custom_final_eos_quark_get (void) +{ + static gsize g_quark; + + if (g_once_init_enter (&g_quark)) { + gsize quark = + (gsize) g_quark_from_static_string ("decodebin3-custom-final-eos"); + g_once_init_leave (&g_quark, quark); + } + return g_quark; +} + typedef struct _GstDecodebin3 GstDecodebin3; typedef struct _GstDecodebin3Class GstDecodebin3Class; @@ -200,23 +215,22 @@ struct _GstDecodebin3 GList *other_inputs; /* counter for input */ guint32 input_counter; - /* Current stream group_id (default : G_MAXUINT32) */ + /* Current stream group_id (default : GST_GROUP_ID_INVALID) */ /* FIXME : Needs to be resetted appropriately (when upstream changes ?) */ guint32 current_group_id; /* End of variables protected by input_lock */ GstElement *multiqueue; - /* FIXME : Mutex for protecting values below */ - GstStreamCollection *collection; /* Active collection */ - + /* selection_lock protects access to following variables */ + GMutex selection_lock; GList *input_streams; /* List of DecodebinInputStream for active collection */ GList *output_streams; /* List of DecodebinOutputStream used for output */ GList *slots; /* List of MultiQueueSlot */ guint slot_id; - /* selection_lock protects access to following variables */ - GMutex selection_lock; + /* Active collection */ + GstStreamCollection *collection; /* requested selection of stream-id to activate post-multiqueue */ GList *requested_selection; /* list of stream-id currently activated in output */ @@ -236,7 +250,6 @@ struct _GstDecodebin3 * FIXME : Is this really needed ? */ GList *pending_collection; - /* Factories */ GMutex factories_lock; guint32 factories_cookie; @@ -280,6 +293,12 @@ struct _DecodebinInput gulong pad_added_sigid; gulong pad_removed_sigid; + gulong drained_sigid; + + /* TRUE if the input got drained + * FIXME : When do we reset it if re-used ? + */ + gboolean drained; /* HACK : Remove these fields */ /* List of PendingPad structures */ @@ -349,8 +368,6 @@ typedef struct _PendingPad } PendingPad; /* properties */ -#define DEFAULT_CAPS (gst_static_caps_get (&default_raw_caps)) - enum { PROP_0, @@ -361,6 +378,7 @@ enum enum { SIGNAL_SELECT_STREAM, + SIGNAL_ABOUT_TO_FINISH, LAST_SIGNAL }; static guint gst_decodebin3_signals[LAST_SIGNAL] = { 0 }; @@ -496,8 +514,7 @@ static void free_multiqueue_slot_async (GstDecodebin3 * dbin, MultiQueueSlot * slot); static GstStreamCollection *get_merged_collection (GstDecodebin3 * dbin); -static void update_requested_selection (GstDecodebin3 * dbin, - GstStreamCollection * collection); +static void update_requested_selection (GstDecodebin3 * dbin); /* FIXME: Really make all the parser stuff a self-contained helper object */ #include "gstdecodebin3-parse.c" @@ -553,6 +570,17 @@ gst_decodebin3_class_init (GstDecodebin3Class * klass) _gst_int_accumulator, NULL, g_cclosure_marshal_generic, G_TYPE_INT, 2, GST_TYPE_STREAM_COLLECTION, GST_TYPE_STREAM); + /** + * GstDecodebin3::about-to-finish: + * + * This signal is emitted when the data for the selected URI is + * entirely buffered and it is safe to specify anothe URI. + */ + gst_decodebin3_signals[SIGNAL_ABOUT_TO_FINISH] = + g_signal_new ("about-to-finish", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, + 0, G_TYPE_NONE); + element_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_decodebin3_request_new_pad); @@ -593,7 +621,7 @@ gst_decodebin3_init (GstDecodebin3 * dbin) "max-size-buffers", 0, "use-interleave", TRUE, NULL); gst_bin_add ((GstBin *) dbin, dbin->multiqueue); - dbin->current_group_id = G_MAXUINT32; + dbin->current_group_id = GST_GROUP_ID_INVALID; g_mutex_init (&dbin->factories_lock); g_mutex_init (&dbin->selection_lock); @@ -698,7 +726,7 @@ set_input_group_id (DecodebinInput * input, guint32 * group_id) GstDecodebin3 *dbin = input->dbin; if (input->group_id != *group_id) { - if (input->group_id != G_MAXUINT32) + if (input->group_id != GST_GROUP_ID_INVALID) GST_WARNING_OBJECT (dbin, "Group id changed (%" G_GUINT32_FORMAT " -> %" G_GUINT32_FORMAT ") on input %p ", input->group_id, *group_id, input); @@ -706,7 +734,7 @@ set_input_group_id (DecodebinInput * input, guint32 * group_id) } if (*group_id != dbin->current_group_id) { - if (dbin->current_group_id == G_MAXUINT32) { + if (dbin->current_group_id == GST_GROUP_ID_INVALID) { GST_DEBUG_OBJECT (dbin, "Setting current group id to %" G_GUINT32_FORMAT, *group_id); dbin->current_group_id = *group_id; @@ -718,6 +746,30 @@ set_input_group_id (DecodebinInput * input, guint32 * group_id) return FALSE; } +static void +parsebin_drained_cb (GstElement * parsebin, DecodebinInput * input) +{ + GstDecodebin3 *dbin = input->dbin; + gboolean all_drained; + GList *tmp; + + GST_WARNING_OBJECT (dbin, "input %p drained", input); + input->drained = TRUE; + + all_drained = dbin->main_input->drained; + for (tmp = dbin->other_inputs; tmp; tmp = tmp->next) { + DecodebinInput *data = (DecodebinInput *) tmp->data; + + all_drained &= data->drained; + } + + if (all_drained) { + GST_WARNING_OBJECT (dbin, "All inputs drained. Posting about-to-finish"); + g_signal_emit (dbin, gst_decodebin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, + NULL); + } +} + /* Call with INPUT_LOCK taken */ static gboolean ensure_input_parsebin (GstDecodebin3 * dbin, DecodebinInput * input) @@ -736,6 +788,9 @@ ensure_input_parsebin (GstDecodebin3 * dbin, DecodebinInput * input) input->pad_removed_sigid = g_signal_connect (input->parsebin, "pad-removed", (GCallback) parsebin_pad_removed_cb, input); + input->drained_sigid = + g_signal_connect (input->parsebin, "drained", + (GCallback) parsebin_drained_cb, input); g_signal_connect (input->parsebin, "autoplug-continue", (GCallback) parsebin_autoplug_continue_cb, dbin); } @@ -834,6 +889,7 @@ gst_decodebin3_input_pad_unlink (GstPad * pad, GstObject * parent) input->collection = NULL; } + SELECTION_LOCK (dbin); collection = get_merged_collection (dbin); if (collection && collection != dbin->collection) { GstMessage *msg; @@ -847,14 +903,17 @@ gst_decodebin3_input_pad_unlink (GstPad * pad, GstObject * parent) gst_message_new_stream_collection ((GstObject *) dbin, dbin->collection); + SELECTION_UNLOCK (dbin); gst_element_post_message (GST_ELEMENT_CAST (dbin), msg); - update_requested_selection (dbin, dbin->collection); - } + update_requested_selection (dbin); + } else + SELECTION_UNLOCK (dbin); gst_bin_remove (GST_BIN (dbin), input->parsebin); gst_element_set_state (input->parsebin, GST_STATE_NULL); g_signal_handler_disconnect (input->parsebin, input->pad_removed_sigid); g_signal_handler_disconnect (input->parsebin, input->pad_added_sigid); + g_signal_handler_disconnect (input->parsebin, input->drained_sigid); gst_pad_remove_probe (input->parsebin_sink, probe_id); gst_object_unref (input->parsebin); gst_object_unref (input->parsebin_sink); @@ -884,6 +943,7 @@ free_input (GstDecodebin3 * dbin, DecodebinInput * input) if (input->parsebin) { g_signal_handler_disconnect (input->parsebin, input->pad_removed_sigid); g_signal_handler_disconnect (input->parsebin, input->pad_added_sigid); + g_signal_handler_disconnect (input->parsebin, input->drained_sigid); gst_element_set_state (input->parsebin, GST_STATE_NULL); gst_object_unref (input->parsebin); gst_object_unref (input->parsebin_sink); @@ -910,7 +970,7 @@ create_new_input (GstDecodebin3 * dbin, gboolean main) input = g_new0 (DecodebinInput, 1); input->dbin = dbin; input->is_main = main; - input->group_id = G_MAXUINT32; + input->group_id = GST_GROUP_ID_INVALID; if (main) input->ghost_sink = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK); else { @@ -940,13 +1000,13 @@ gst_decodebin3_request_new_pad (GstElement * element, GstPadTemplate * temp, /* We are ignoring names for the time being, not sure it makes any sense * within the context of decodebin3 ... */ - INPUT_LOCK (dbin); input = create_new_input (dbin, FALSE); if (input) { + INPUT_LOCK (dbin); dbin->other_inputs = g_list_append (dbin->other_inputs, input); res = input->ghost_sink; + INPUT_UNLOCK (dbin); } - INPUT_UNLOCK (dbin); return res; } @@ -1011,14 +1071,12 @@ stream_in_list (GList * list, const gchar * sid) } static void -update_requested_selection (GstDecodebin3 * dbin, - GstStreamCollection * collection) +update_requested_selection (GstDecodebin3 * dbin) { guint i, nb; GList *tmp = NULL; GstStreamType used_types = 0; - - nb = gst_stream_collection_get_size (collection); + GstStreamCollection *collection; /* 1. Is there a pending SELECT_STREAMS we can return straight away since * the switch handler will take care of the pending selection */ @@ -1029,6 +1087,13 @@ update_requested_selection (GstDecodebin3 * dbin, goto beach; } + collection = dbin->collection; + if (G_UNLIKELY (collection == NULL)) { + GST_DEBUG_OBJECT (dbin, "No current GstStreamCollection"); + goto beach; + } + nb = gst_stream_collection_get_size (collection); + /* 2. If not, are we in EXPOSE_ALL_MODE ? If so, match everything */ GST_FIXME_OBJECT (dbin, "Implement EXPOSE_ALL_MODE"); @@ -1257,6 +1322,7 @@ handle_stream_collection (GstDecodebin3 * dbin, #endif /* Store collection for later usage */ + SELECTION_LOCK (dbin); if (dbin->collection == NULL) { dbin->collection = collection; } else { @@ -1272,6 +1338,7 @@ handle_stream_collection (GstDecodebin3 * dbin, /* dbin->pending_collection = */ /* g_list_append (dbin->pending_collection, collection); */ } + SELECTION_UNLOCK (dbin); } static void @@ -1294,6 +1361,8 @@ gst_decodebin3_handle_message (GstBin * bin, GstMessage * message) posting_collection = TRUE; INPUT_UNLOCK (dbin); } + + SELECTION_LOCK (dbin); if (dbin->collection && collection != dbin->collection) { /* Replace collection message, we most likely aggregated it */ GstMessage *new_msg; @@ -1303,6 +1372,8 @@ gst_decodebin3_handle_message (GstBin * bin, GstMessage * message) gst_message_unref (message); message = new_msg; } + SELECTION_UNLOCK (dbin); + if (collection) gst_object_unref (collection); break; @@ -1315,7 +1386,7 @@ gst_decodebin3_handle_message (GstBin * bin, GstMessage * message) if (posting_collection) { /* Figure out a selection for that collection */ - update_requested_selection (dbin, dbin->collection); + update_requested_selection (dbin); } } @@ -1479,6 +1550,84 @@ is_selection_done (GstDecodebin3 * dbin) return msg; } +/* Must be called with SELECTION_LOCK taken */ +static void +check_all_slot_for_eos (GstDecodebin3 * dbin) +{ + gboolean all_drained = TRUE; + GList *iter; + + GST_DEBUG_OBJECT (dbin, "check slot for eos"); + + for (iter = dbin->slots; iter; iter = iter->next) { + MultiQueueSlot *slot = iter->data; + + if (!slot->output) + continue; + + if (slot->is_drained) { + GST_LOG_OBJECT (slot->sink_pad, "slot %p is drained", slot); + continue; + } + + all_drained = FALSE; + break; + } + + if (all_drained) { + INPUT_LOCK (dbin); + if (!pending_pads_are_eos (dbin->main_input)) + all_drained = FALSE; + + if (all_drained) { + for (iter = dbin->other_inputs; iter; iter = iter->next) { + if (!pending_pads_are_eos ((DecodebinInput *) iter->data)) { + all_drained = FALSE; + break; + } + } + } + INPUT_UNLOCK (dbin); + } + + if (all_drained) { + GST_DEBUG_OBJECT (dbin, + "All active slots are drained, and no pending input, push EOS"); + + for (iter = dbin->input_streams; iter; iter = iter->next) { + DecodebinInputStream *input = (DecodebinInputStream *) iter->data; + GstPad *peer = gst_pad_get_peer (input->srcpad); + + /* Send EOS to all slots */ + if (peer) { + GstEvent *stream_start, *eos; + + stream_start = + gst_pad_get_sticky_event (input->srcpad, GST_EVENT_STREAM_START, 0); + + /* First forward a custom STREAM_START event to reset the EOS status (if any) */ + if (stream_start) { + GstStructure *s; + GstEvent *custom_stream_start = gst_event_copy (stream_start); + gst_event_unref (stream_start); + s = (GstStructure *) gst_event_get_structure (custom_stream_start); + gst_structure_set (s, "decodebin3-flushing-stream-start", + G_TYPE_BOOLEAN, TRUE, NULL); + gst_pad_send_event (peer, custom_stream_start); + } + + eos = gst_event_new_eos (); + gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (eos), + CUSTOM_FINAL_EOS_QUARK, (gchar *) CUSTOM_FINAL_EOS_QUARK_DATA, + NULL); + gst_pad_send_event (peer, eos); + gst_object_unref (peer); + } else + GST_DEBUG_OBJECT (dbin, "no output"); + } + } +} + static GstPadProbeReturn multiqueue_src_probe (GstPad * pad, GstPadProbeInfo * info, MultiQueueSlot * slot) @@ -1494,7 +1643,16 @@ multiqueue_src_probe (GstPad * pad, GstPadProbeInfo * info, case GST_EVENT_STREAM_START: { GstStream *stream = NULL; - const gchar *stream_id; + const GstStructure *s = gst_event_get_structure (ev); + + /* Drop STREAM_START events used to cleanup multiqueue */ + if (s + && gst_structure_has_field (s, + "decodebin3-flushing-stream-start")) { + ret = GST_PAD_PROBE_HANDLED; + gst_event_unref (ev); + break; + } gst_event_parse_stream (ev, &stream); if (stream == NULL) { @@ -1503,8 +1661,8 @@ multiqueue_src_probe (GstPad * pad, GstPadProbeInfo * info, break; } slot->is_drained = FALSE; - stream_id = gst_stream_get_stream_id (stream); - GST_DEBUG_OBJECT (pad, "Stream Start '%s'", stream_id); + GST_DEBUG_OBJECT (pad, "Stream Start '%s'", + gst_stream_get_stream_id (stream)); if (slot->active_stream == NULL) { slot->active_stream = stream; } else if (slot->active_stream != stream) { @@ -1559,10 +1717,44 @@ multiqueue_src_probe (GstPad * pad, GstPadProbeInfo * info, } break; case GST_EVENT_EOS: - /* FIXME : Figure out */ + { + gboolean was_drained = slot->is_drained; + slot->is_drained = TRUE; + + /* Custom EOS handling first */ + if (gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (ev), + CUSTOM_EOS_QUARK)) { + /* remove custom-eos */ + gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (ev), + CUSTOM_EOS_QUARK, NULL, NULL); + GST_LOG_OBJECT (pad, "Received custom EOS"); + ret = GST_PAD_PROBE_HANDLED; + SELECTION_LOCK (dbin); + if (slot->input == NULL) { + GST_DEBUG_OBJECT (pad, + "Got custom-eos from null input stream, remove output stream"); + /* Remove the output */ + if (slot->output) { + DecodebinOutputStream *output = slot->output; + dbin->output_streams = + g_list_remove (dbin->output_streams, output); + free_output_stream (dbin, output); + } + slot->probe_id = 0; + dbin->slots = g_list_remove (dbin->slots, slot); + free_multiqueue_slot_async (dbin, slot); + ret = GST_PAD_PROBE_REMOVE; + } else if (!was_drained) { + check_all_slot_for_eos (dbin); + } + if (ret == GST_PAD_PROBE_HANDLED) + gst_event_unref (ev); + SELECTION_UNLOCK (dbin); + break; + } + GST_FIXME_OBJECT (pad, "EOS on multiqueue source pad. input:%p", slot->input); - slot->is_drained = TRUE; if (slot->input == NULL) { GstPad *peer; GST_DEBUG_OBJECT (pad, @@ -1585,33 +1777,23 @@ multiqueue_src_probe (GstPad * pad, GstPadProbeInfo * info, } slot->probe_id = 0; dbin->slots = g_list_remove (dbin->slots, slot); - free_multiqueue_slot_async (dbin, slot); SELECTION_UNLOCK (dbin); + + free_multiqueue_slot_async (dbin, slot); ret = GST_PAD_PROBE_REMOVE; - } - break; - case GST_EVENT_CUSTOM_DOWNSTREAM: - if (gst_event_has_name (ev, "decodebin3-custom-eos")) { - slot->is_drained = TRUE; + } else if (gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (ev), + CUSTOM_FINAL_EOS_QUARK)) { + GST_DEBUG_OBJECT (pad, "Got final eos, propagating downstream"); + } else { + GST_DEBUG_OBJECT (pad, "Got regular eos (all_inputs_are_eos)"); + /* drop current event as eos will be sent in check_all_slot_for_eos + * when all output streams are also eos */ ret = GST_PAD_PROBE_DROP; SELECTION_LOCK (dbin); - if (slot->input == NULL) { - GST_DEBUG_OBJECT (pad, - "Got custom-eos from null input stream, remove output stream"); - /* Remove the output */ - if (slot->output) { - DecodebinOutputStream *output = slot->output; - dbin->output_streams = - g_list_remove (dbin->output_streams, output); - free_output_stream (dbin, output); - } - slot->probe_id = 0; - dbin->slots = g_list_remove (dbin->slots, slot); - free_multiqueue_slot_async (dbin, slot); - ret = GST_PAD_PROBE_REMOVE; - } + check_all_slot_for_eos (dbin); SELECTION_UNLOCK (dbin); } + } break; default: break; @@ -1658,11 +1840,14 @@ create_new_slot (GstDecodebin3 * dbin, GstStreamType type) gst_stream_type_get_name (type)); slot = g_new0 (MultiQueueSlot, 1); slot->dbin = dbin; + slot->id = dbin->slot_id++; + slot->type = type; slot->sink_pad = gst_element_get_request_pad (dbin->multiqueue, "sink_%u"); if (slot->sink_pad == NULL) goto fail; + it = gst_pad_iterate_internal_links (slot->sink_pad); if (!it || (gst_iterator_next (it, &item)) != GST_ITERATOR_OK || ((slot->src_pad = g_value_dup_object (&item)) == NULL)) { @@ -1683,7 +1868,9 @@ create_new_slot (GstDecodebin3 * dbin, GstStreamType type) GST_DEBUG ("Created new slot %u (%p) (%s:%s)", slot->id, slot, GST_DEBUG_PAD_NAME (slot->src_pad)); + dbin->slots = g_list_append (dbin->slots, slot); + return slot; /* ERRORS */ @@ -1760,7 +1947,6 @@ get_slot_for_input (GstDecodebin3 * dbin, DecodebinInputStream * input) static void link_input_to_slot (DecodebinInputStream * input, MultiQueueSlot * slot) { - GstEvent *event; if (slot->input != NULL && slot->input != input) { GST_ERROR_OBJECT (slot->dbin, "Trying to link input to an already used slot"); @@ -1769,9 +1955,6 @@ link_input_to_slot (DecodebinInputStream * input, MultiQueueSlot * slot) gst_pad_link_full (input->srcpad, slot->sink_pad, GST_PAD_LINK_CHECK_NOTHING); slot->pending_stream = input->active_stream; slot->input = input; - event = gst_pad_get_sticky_event (input->srcpad, GST_EVENT_STREAM_START, 0); - if (event) - gst_pad_send_event (slot->sink_pad, event); } #if 0 @@ -2357,6 +2540,14 @@ handle_stream_switch (GstDecodebin3 * dbin, GList * select_streams, g_list_free (unknown); } + if (to_activate && !slots_to_reassign) { + for (tmp = to_activate; tmp; tmp = tmp->next) { + MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data; + gst_pad_add_probe (slot->src_pad, GST_PAD_PROBE_TYPE_IDLE, + (GstPadProbeCallback) idle_reconfigure, slot, NULL); + } + } + /* For all streams to deactivate, add an idle probe where we will do * the unassignment and switch over */ for (tmp = slots_to_reassign; tmp; tmp = tmp->next) { @@ -2403,6 +2594,8 @@ ghost_pad_event_probe (GstPad * pad, GstPadProbeInfo * info, SELECTION_UNLOCK (dbin); GST_DEBUG_OBJECT (pad, "Already handled/handling that SELECT_STREAMS event"); + gst_event_unref (event); + ret = GST_PAD_PROBE_HANDLED; break; } dbin->select_streams_seqnum = seqnum; @@ -2625,6 +2818,7 @@ gst_decodebin3_change_state (GstElement * element, GstStateChange transition) } g_list_free (dbin->slots); dbin->slots = NULL; + dbin->current_group_id = GST_GROUP_ID_INVALID; /* Free inputs */ } break; diff --git a/gst/playback/gstparsebin.c b/gst/playback/gstparsebin.c index ce3c317..87ee11f 100644 --- a/gst/playback/gstparsebin.c +++ b/gst/playback/gstparsebin.c @@ -106,6 +106,7 @@ #include "gstplay-enum.h" #include "gstplayback.h" #include "gstplaybackutils.h" +#include "gstrawcaps.h" /* generic templates */ static GstStaticPadTemplate parse_bin_sink_template = @@ -169,8 +170,6 @@ struct _GstParseBin 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 emitted */ - GMutex dyn_lock; /* lock protecting pad blocking */ gboolean shutdown; /* if we are shutting down */ GList *blocked_pads; /* pads that have set to block */ @@ -179,6 +178,13 @@ struct _GstParseBin GList *filtered; /* elements for which error messages are filtered */ GList *filtered_errors; /* filtered error messages */ + + GMutex cleanup_lock; /* Mutex used to protect the cleanup thread */ + GThread *cleanup_thread; /* thread used to free chains asynchronously. + * We store it to make sure we end up joining it + * before stopping the element. + * Protected by the object lock */ + }; struct _GstParseBinClass @@ -243,9 +249,6 @@ enum static GstBinClass *parent_class; static guint gst_parse_bin_signals[LAST_SIGNAL] = { 0 }; -static void do_async_start (GstParseBin * parsebin); -static void do_async_done (GstParseBin * parsebin); - static void type_found (GstElement * typefind, guint probability, GstCaps * caps, GstParseBin * parse_bin); @@ -279,6 +282,7 @@ static void gst_parse_pad_update_stream_collection (GstParsePad * parsepad, GstStreamCollection * collection); static GstCaps *get_pad_caps (GstPad * pad); +static GstStreamType guess_stream_type_from_caps (GstCaps * caps); #define EXPOSE_LOCK(parsebin) G_STMT_START { \ GST_LOG_OBJECT (parsebin, \ @@ -650,11 +654,11 @@ gst_parse_bin_class_init (GstParseBinClass * klass) * emitted before looking for any elements that can handle that stream. * * > Invocation of signal handlers stops after the first signal handler - * > returns #FALSE. Signal handlers are invoked in the order they were + * > returns %FALSE. Signal handlers are invoked in the order they were * > connected in. * - * Returns: #TRUE if you wish ParseBin to look for elements that can - * handle the given @caps. If #FALSE, those caps will be considered as + * Returns: %TRUE if you wish ParseBin to look for elements that can + * handle the given @caps. If %FALSE, those caps will be considered as * final and the pad will be exposed as such (see 'pad-added' signal of * #GstElement). */ @@ -706,11 +710,11 @@ gst_parse_bin_class_init (GstParseBinClass * klass) * the application to perform additional sorting or filtering on the element * factory array. * - * The callee should copy and modify @factories or return #NULL if the + * The callee should copy and modify @factories or return %NULL if the * order should not change. * * > Invocation of signal handlers stops after one signal handler has - * > returned something else than #NULL. Signal handlers are invoked in + * > returned something else than %NULL. Signal handlers are invoked in * > the order they were connected in. * > Don't connect signal handlers with the #G_CONNECT_AFTER flag to this * > signal, they will never be invoked! @@ -778,7 +782,7 @@ gst_parse_bin_class_init (GstParseBinClass * klass) * be used to tell the element about the downstream supported caps * for example. * - * Returns: #TRUE if the query was handled, #FALSE otherwise. + * Returns: %TRUE if the query was handled, %FALSE otherwise. */ gst_parse_bin_signals[SIGNAL_AUTOPLUG_QUERY] = g_signal_new ("autoplug-query", G_TYPE_FROM_CLASS (klass), @@ -938,6 +942,9 @@ gst_parse_bin_init (GstParseBin * parse_bin) parse_bin->expose_allstreams = DEFAULT_EXPOSE_ALL_STREAMS; parse_bin->connection_speed = DEFAULT_CONNECTION_SPEED; + g_mutex_init (&parse_bin->cleanup_lock); + parse_bin->cleanup_thread = NULL; + GST_OBJECT_FLAG_SET (parse_bin, GST_BIN_FLAG_STREAMS_AWARE); } @@ -976,6 +983,7 @@ gst_parse_bin_finalize (GObject * object) g_mutex_clear (&parse_bin->dyn_lock); g_mutex_clear (&parse_bin->subtitle_lock); g_mutex_clear (&parse_bin->factories_lock); + g_mutex_clear (&parse_bin->cleanup_lock); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -1099,9 +1107,18 @@ static gboolean gst_parse_bin_autoplug_continue (GstElement * element, GstPad * pad, GstCaps * caps) { + static GstStaticCaps raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS); + + GST_DEBUG_OBJECT (element, "caps %" GST_PTR_FORMAT, caps); + + /* If it matches our target caps, expose it */ + if (gst_caps_can_intersect (caps, gst_static_caps_get (&raw_caps))) { + GST_DEBUG_OBJECT (element, "autoplug-continue returns FALSE"); + return FALSE; + } + GST_DEBUG_OBJECT (element, "autoplug-continue returns TRUE"); - /* by default we always continue */ return TRUE; } @@ -1514,6 +1531,7 @@ unknown_type: chain->deadend_details = deadend_details; chain->deadend = TRUE; + chain->drained = TRUE; chain->endcaps = caps; gst_object_replace ((GstObject **) & chain->current_pad, NULL); @@ -1537,7 +1555,6 @@ unknown_type: GST_ELEMENT_ERROR (parsebin, STREAM, TYPE_NOT_FOUND, (_("Could not determine type of stream")), (NULL)); } - do_async_done (parsebin); } return; } @@ -2944,11 +2961,19 @@ gst_parse_chain_start_free_hidden_groups_thread (GstParseChain * chain) GThread *thread; GError *error = NULL; GList *old_groups; + GstParseBin *parsebin = chain->parsebin; old_groups = chain->old_groups; if (!old_groups) return; + /* If we already have a thread running, wait for it to finish */ + g_mutex_lock (&parsebin->cleanup_lock); + if (parsebin->cleanup_thread) { + g_thread_join (parsebin->cleanup_thread); + parsebin->cleanup_thread = NULL; + } + chain->old_groups = NULL; thread = g_thread_try_new ("free-hidden-groups", (GThreadFunc) gst_parse_chain_free_hidden_groups, old_groups, &error); @@ -2957,11 +2982,14 @@ gst_parse_chain_start_free_hidden_groups_thread (GstParseChain * chain) error ? error->message : "unknown reason"); g_clear_error (&error); chain->old_groups = old_groups; + g_mutex_unlock (&parsebin->cleanup_lock); return; } + + parsebin->cleanup_thread = thread; + g_mutex_unlock (&parsebin->cleanup_lock); + GST_DEBUG_OBJECT (chain->parsebin, "Started free-hidden-groups thread"); - /* We do not need to wait for it or get any results from it */ - g_thread_unref (thread); } /* gst_parse_group_new: @@ -3216,14 +3244,13 @@ drain_and_switch_chains (GstParseChain * chain, GstParsePad * drainpad, beach: CHAIN_MUTEX_UNLOCK (chain); - GST_DEBUG ("Chain %p (handled:%d, last_group:%d, drained:%d, switched:%d)", - chain, handled, *last_group, chain->drained, *switched); + GST_DEBUG + ("Chain %p (%s:%s handled:%d, last_group:%d, drained:%d, switched:%d, deadend:%d)", + chain, GST_DEBUG_PAD_NAME (chain->pad), handled, *last_group, + chain->drained, *switched, chain->deadend); *drained = chain->drained; - if (*drained) - g_signal_emit (parsebin, gst_parse_bin_signals[SIGNAL_DRAINED], 0, NULL); - return handled; } @@ -3244,11 +3271,18 @@ gst_parse_pad_handle_eos (GstParsePad * pad) drain_and_switch_chains (parsebin->parse_chain, pad, &last_group, &drained, &switched); + GST_LOG_OBJECT (parsebin, "drained:%d switched:%d", drained, switched); if (switched) { /* If we resulted in a group switch, expose what's needed */ if (gst_parse_chain_is_complete (parsebin->parse_chain)) gst_parse_bin_expose (parsebin); } + + if (drained) { + GST_DEBUG_OBJECT (parsebin, "We are fully drained, emitting signal"); + g_signal_emit (parsebin, gst_parse_bin_signals[SIGNAL_DRAINED], 0, NULL); + } + } EXPOSE_UNLOCK (parsebin); @@ -3485,7 +3519,6 @@ retry: } } - do_async_done (parsebin); return FALSE; } @@ -3531,6 +3564,15 @@ retry: /* re-order pads : video, then audio, then others */ endpads = g_list_sort (endpads, (GCompareFunc) sort_end_pads); + /* Don't expose if we're currently shutting down */ + DYN_LOCK (parsebin); + if (G_UNLIKELY (parsebin->shutdown)) { + GST_WARNING_OBJECT (parsebin, + "Currently, shutting down, aborting exposing"); + DYN_UNLOCK (parsebin); + return FALSE; + } + /* Expose pads */ for (tmp = endpads; tmp; tmp = tmp->next) { GstParsePad *parsepad = (GstParsePad *) tmp->data; @@ -3570,6 +3612,8 @@ retry: GST_INFO_OBJECT (parsepad, "added new parsed pad"); } + DYN_UNLOCK (parsebin); + /* Unblock internal pads. The application should have connected stuff now * so that streaming can continue. */ for (tmp = endpads; tmp; tmp = tmp->next) { @@ -3597,7 +3641,6 @@ retry: /* Remove old groups */ chain_remove_old_groups (parsebin->parse_chain); - do_async_done (parsebin); GST_DEBUG_OBJECT (parsebin, "Exposed everything"); return TRUE; } @@ -3707,6 +3750,20 @@ build_fallback_collection (GstParseChain * chain, if (p->active_stream != NULL && p->active_collection == NULL) { GST_DEBUG_OBJECT (p, "Adding stream to fallback collection"); + if (G_UNLIKELY (gst_stream_get_stream_type (p->active_stream) == + GST_STREAM_TYPE_UNKNOWN)) { + GstCaps *caps; + caps = get_pad_caps (GST_PAD_CAST (p)); + + if (caps) { + GstStreamType type = guess_stream_type_from_caps (caps); + if (type != GST_STREAM_TYPE_UNKNOWN) { + gst_stream_set_stream_type (p->active_stream, type); + gst_stream_set_caps (p->active_stream, caps); + } + gst_caps_unref (caps); + } + } gst_stream_collection_add_stream (collection, gst_object_ref (p->active_stream)); p->in_a_fallback_collection = TRUE; @@ -4182,32 +4239,6 @@ gst_pending_pad_free (GstPendingPad * ppad) * Element add/remove *****/ -static void -do_async_start (GstParseBin * parsebin) -{ - GstMessage *message; - - parsebin->async_pending = TRUE; - - message = gst_message_new_async_start (GST_OBJECT_CAST (parsebin)); - parent_class->handle_message (GST_BIN_CAST (parsebin), message); -} - -static void -do_async_done (GstParseBin * parsebin) -{ - GstMessage *message; - - if (parsebin->async_pending) { - message = - gst_message_new_async_done (GST_OBJECT_CAST (parsebin), - GST_CLOCK_TIME_NONE); - parent_class->handle_message (GST_BIN_CAST (parsebin), message); - - parsebin->async_pending = FALSE; - } -} - /* call with dyn_lock held */ static void unblock_pads (GstParseBin * parsebin) @@ -4267,8 +4298,6 @@ gst_parse_bin_change_state (GstElement * element, GstStateChange transition) parsebin->shutdown = FALSE; DYN_UNLOCK (parsebin); parsebin->have_type = FALSE; - ret = GST_STATE_CHANGE_ASYNC; - do_async_start (parsebin); /* connect a signal to find out when the typefind element found @@ -4291,20 +4320,12 @@ gst_parse_bin_change_state (GstElement * element, GstStateChange transition) break; } - { - GstStateChangeReturn bret; - - bret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - if (G_UNLIKELY (bret == GST_STATE_CHANGE_FAILURE)) - goto activate_failed; - else if (G_UNLIKELY (bret == GST_STATE_CHANGE_NO_PREROLL)) { - do_async_done (parsebin); - ret = bret; - } - } + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (G_UNLIKELY (ret == GST_STATE_CHANGE_FAILURE)) + goto activate_failed; + switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: - do_async_done (parsebin); EXPOSE_LOCK (parsebin); if (parsebin->parse_chain) { chain_to_free = parsebin->parse_chain; @@ -4316,6 +4337,12 @@ gst_parse_bin_change_state (GstElement * element, GstStateChange transition) gst_parse_chain_free (chain_to_free); break; case GST_STATE_CHANGE_READY_TO_NULL: + g_mutex_lock (&parsebin->cleanup_lock); + if (parsebin->cleanup_thread) { + g_thread_join (parsebin->cleanup_thread); + parsebin->cleanup_thread = NULL; + } + g_mutex_unlock (&parsebin->cleanup_lock); default: break; } @@ -4335,7 +4362,6 @@ activate_failed: { GST_DEBUG_OBJECT (element, "element failed to change states -- activation problem?"); - do_async_done (parsebin); return GST_STATE_CHANGE_FAILURE; } } diff --git a/gst/playback/gstplayback.c b/gst/playback/gstplayback.c index 4789db6..3c3efa5 100644 --- a/gst/playback/gstplayback.c +++ b/gst/playback/gstplayback.c @@ -63,6 +63,7 @@ plugin_init (GstPlugin * plugin) res &= gst_decode_bin_plugin_init (plugin); res &= gst_decodebin3_plugin_init (plugin); res &= gst_uri_decode_bin_plugin_init (plugin); + res &= gst_uri_decode_bin3_plugin_init (plugin); res &= gst_uri_source_bin_plugin_init (plugin); res &= gst_parse_bin_plugin_init (plugin); diff --git a/gst/playback/gstplayback.h b/gst/playback/gstplayback.h index eaa0564..26e3683 100644 --- a/gst/playback/gstplayback.h +++ b/gst/playback/gstplayback.h @@ -26,6 +26,7 @@ gboolean gst_decode_bin_plugin_init (GstPlugin * plugin); gboolean gst_decodebin3_plugin_init (GstPlugin * plugin); gboolean gst_uri_decode_bin_plugin_init (GstPlugin * plugin); +gboolean gst_uri_decode_bin3_plugin_init (GstPlugin * plugin); gboolean gst_uri_source_bin_plugin_init (GstPlugin * plugin); gboolean gst_parse_bin_plugin_init (GstPlugin * plugin); diff --git a/gst/playback/gstplaybackutils.c b/gst/playback/gstplaybackutils.c index 8bf9b08..00becf0 100644 --- a/gst/playback/gstplaybackutils.c +++ b/gst/playback/gstplaybackutils.c @@ -81,10 +81,11 @@ gst_playback_utils_get_n_common_capsfeatures (GstElementFactory * fact1, fact1_tmpl_caps = get_template_caps (fact1, GST_PAD_SRC); fact2_tmpl_caps = get_template_caps (fact2, GST_PAD_SINK); if (!fact1_tmpl_caps || !fact2_tmpl_caps) { - if (fact1_tmpl_caps) gst_caps_unref (fact1_tmpl_caps); - if (fact2_tmpl_caps) gst_caps_unref (fact2_tmpl_caps); - GST_ERROR ("Failed to get template caps from decoder or sink"); + if (fact1_tmpl_caps) + gst_caps_unref (fact1_tmpl_caps); + else if (fact2_tmpl_caps) + gst_caps_unref (fact2_tmpl_caps); return 0; } @@ -131,7 +132,6 @@ gst_playback_utils_get_n_common_capsfeatures (GstElementFactory * fact1, gst_caps_unref (fact1_tmpl_caps); gst_caps_unref (fact2_tmpl_caps); - gst_caps_unref (raw_caps); return n_common_cf; } diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index 04eeb2b..a2e9ed4 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -4570,6 +4570,7 @@ autoplug_select_cb (GstElement * decodebin, GstPad * pad, GstAVElement *ave = NULL; GSequence *ave_seq = NULL; GSequenceIter *seq_iter; + gboolean created_sink = FALSE; playbin = group->playbin; @@ -4635,8 +4636,6 @@ autoplug_select_cb (GstElement * decodebin, GstPad * pad, /* if it is a decoder and we don't have a fixed sink, then find out * the matching audio/video sink from GstAVElements list */ for (l = ave_list; l; l = l->next) { - gboolean created_sink = FALSE; - ave = (GstAVElement *) l->data; if (((isaudiodec && !group->audio_sink) || @@ -4652,6 +4651,10 @@ autoplug_select_cb (GstElement * decodebin, GstPad * pad, gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (ave->sink))); continue; } else { + /* The sink is ours now, don't leak the floating reference in the + * state-changed messages */ + gst_object_ref_sink (*sinkp); + if (!activate_sink (playbin, *sinkp, NULL)) { gst_object_unref (*sinkp); *sinkp = NULL; @@ -4660,7 +4663,6 @@ autoplug_select_cb (GstElement * decodebin, GstPad * pad, gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (ave->sink))); continue; } - gst_object_ref_sink (*sinkp); created_sink = TRUE; } } @@ -4725,6 +4727,7 @@ autoplug_select_cb (GstElement * decodebin, GstPad * pad, gst_element_set_state (*sinkp, GST_STATE_NULL); gst_object_unref (*sinkp); *sinkp = NULL; + created_sink = FALSE; } else { g_mutex_unlock (&playbin->elements_lock); GST_SOURCE_GROUP_UNLOCK (group); @@ -4799,6 +4802,12 @@ autoplug_select_cb (GstElement * decodebin, GstPad * pad, return GST_AUTOPLUG_SELECT_SKIP; } + /* The sink is ours now, don't leak floating references in the state-changed + * messages, but only do that if we didn't just create the sink above and + * already ref_sink'd it there */ + if (!created_sink) + gst_object_ref_sink (*sinkp); + element = *sinkp; if (!activate_sink (playbin, element, NULL)) { @@ -4820,13 +4829,11 @@ autoplug_select_cb (GstElement * decodebin, GstPad * pad, return GST_AUTOPLUG_SELECT_SKIP; } - /* remember the sink in the group now, the element is floating, we take - * ownership now + /* remember the sink in the group now * * store the sink in the group, we will configure it later when we * reconfigure the sink */ GST_DEBUG_OBJECT (playbin, "remember sink"); - gst_object_ref_sink (element); GST_SOURCE_GROUP_UNLOCK (group); /* tell decodebin to expose the pad because we are going to use this diff --git a/gst/playback/gstplaybin3.c b/gst/playback/gstplaybin3.c index f3f5efe..027766b 100644 --- a/gst/playback/gstplaybin3.c +++ b/gst/playback/gstplaybin3.c @@ -20,7 +20,6 @@ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ - /** * SECTION:element-playbin3 * @title: playbin3 @@ -159,16 +158,16 @@ * video sinks implement. See the documentation there for more details. * * ## Specifying which CD/DVD device to use - * The device to use for CDs/DVDs needs to be set on the source element - * playbin3 creates before it is opened. The most generic way of doing this - * is to connect to playbin3's "source-setup" (or "notify::source") signal, - * which will be emitted by playbin3 when it has created the source element - * for a particular URI. In the signal callback you can check if the source - * element has a "device" property and set it appropriately. In some cases - * the device can also be set as part of the URI, but it depends on the - * elements involved if this will work or not. For example, for DVD menu - * playback, the following syntax might work (if the resindvd plugin is used): - * dvd://[/path/to/device] + * + * The device to use for CDs/DVDs needs to be set on the source element playbin3 + * creates before it is opened. The most generic way of doing this is to connect + * to playbin3's "source-setup" signal, which will be emitted by playbin3 when + * it has created the source element for a particular URI. In the signal + * callback you can check if the source element has a "device" property and set + * it appropriately. In some cases the device can also be set as part of the + * URI, but it depends on the elements involved if this will work or not. For + * example, for DVD menu playback, the following syntax might work (if the + * resindvd plugin is used): dvd://[/path/to/device] * * ## Handling redirects * @@ -200,10 +199,6 @@ * */ -/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray - * with newer GLib versions (>= 2.31.0) */ -#define GLIB_DISABLE_DEPRECATION_WARNINGS - #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -243,30 +238,34 @@ typedef struct _GstPlayBin3 GstPlayBin3; typedef struct _GstPlayBin3Class GstPlayBin3Class; typedef struct _GstSourceGroup GstSourceGroup; typedef struct _GstSourceCombine GstSourceCombine; +typedef struct _SourcePad SourcePad; typedef GstCaps *(*SourceCombineGetMediaCapsFunc) (void); -/* has the info for a combiner and provides the link to the sink */ +/* GstSourceCombine controls all the information regarding a certain + * media type. + * + * It can control a custom combiner element (by default none) + */ struct _GstSourceCombine { const gchar *media_type; /* the media type for the combiner */ SourceCombineGetMediaCapsFunc get_media_caps; /* more complex caps for the combiner */ GstPlaySinkType type; /* the sink pad type of the combiner */ + GstStreamType stream_type; /* The GstStreamType of the combiner */ GstElement *combiner; /* the combiner */ - GPtrArray *channels; + GPtrArray *channels; /* Array of GstPad ? */ + GstPad *srcpad; /* the source pad of the combiner */ GstPad *sinkpad; /* the sinkpad of the sink when the combiner - * is linked - */ - gulong block_id; + * is linked */ GPtrArray *streams; /* Sorted array of GstStream for the given type */ - gint current_stream; /* Currently selected GstStream */ gboolean has_active_pad; /* stream combiner has the "active-pad" property */ - gboolean has_always_ok; /* stream combiner's sink pads have the "always-ok" property */ + gboolean is_concat; /* The stream combiner is the 'concat' element */ }; #define GST_SOURCE_GROUP_GET_LOCK(group) (&((GstSourceGroup*)(group))->lock) @@ -286,9 +285,19 @@ static const gchar *stream_type_names[] = { "audio", "video", "text" }; + +#define STREAM_TYPES_FORMAT "s%s%s" +#define STREAM_TYPES_ARGS(s) (s) & GST_STREAM_TYPE_AUDIO ? "audio " : "", \ + (s) & GST_STREAM_TYPE_VIDEO ? "video " : "", \ + (s) & GST_STREAM_TYPE_TEXT ? "text " : "" + + + +#if 0 /* AUTOPLUG DISABLED */ static void avelements_free (gpointer data); static GSequence *avelements_create (GstPlayBin3 * playbin, gboolean isaudioelement); +#endif /* The GstAudioVideoElement structure holding the audio/video decoder * and the audio/video sink factories together with field indicating @@ -300,6 +309,14 @@ typedef struct guint n_comm_cf; /* number of common caps features */ } GstAVElement; +/* a structure to hold information about a uridecodebin pad */ +struct _SourcePad +{ + GstPad *pad; /* The controlled pad */ + GstStreamType stream_type; /* stream type of the controlled pad */ + gulong event_probe_id; +}; + /* a structure to hold the objects for decoding a uri and the subtitle uri */ struct _GstSourceGroup @@ -311,17 +328,30 @@ struct _GstSourceGroup gboolean valid; /* the group has valid info to start playback */ gboolean active; /* the group is active */ + gboolean playing; /* the group is currently playing + * (outputted on the sinks) */ + /* properties */ gchar *uri; gchar *suburi; - GValueArray *streaminfo; - GstElement *source; + /* The currently outputted group_id */ + guint group_id; - /* urisourcebins for uri and subtitle uri */ - /* FIXME: Just make this an array of uris */ - GstElement *urisourcebin; - GstElement *suburisourcebin; + /* Bit-wise set of stream types we have requested from uridecodebin3 */ + GstStreamType selected_stream_types; + + /* Bit-wise set of stream types for which pads are present */ + GstStreamType present_stream_types; + + /* TRUE if a 'about-to-finish' needs to be posted once we have + * got source pads for all requested stream types + * + * FIXME : Move this logic to uridecodebin3 later */ + gboolean pending_about_to_finish; + + /* uridecodebin to handle uri and suburi */ + GstElement *uridecodebin; /* Active sinks for each media type. These are initialized with * the configured or currently used sink, otherwise @@ -331,28 +361,28 @@ struct _GstSourceGroup GstElement *video_sink; GstElement *text_sink; - gint pending; - gboolean sub_pending; + /* List of source pads */ + GList *source_pads; + + /* uridecodebin signals */ + gulong pad_added_id; + gulong pad_removed_id; + gulong select_stream_id; + gulong source_setup_id; + gulong about_to_finish_id; - /* primary uri signals */ - gulong urisrc_pad_added_id; - gulong urisrc_pad_removed_id; - gulong notify_source_id; +#if 0 /* AUTOPLUG DISABLED */ gulong autoplug_factories_id; gulong autoplug_select_id; gulong autoplug_continue_id; gulong autoplug_query_id; +#endif - /* subtitle uri signals */ - gulong sub_pad_added_id; - gulong sub_pad_removed_id; - gulong sub_autoplug_continue_id; - gulong sub_autoplug_query_id; + gboolean stream_changed_pending; - gulong block_id; + /* Active stream collection */ + GstStreamCollection *collection; - GMutex stream_changed_pending_lock; - gboolean stream_changed_pending; /* buffering message stored for after switching */ GstMessage *pending_buffering_msg; @@ -367,20 +397,20 @@ struct _GstSourceGroup #define GST_PLAY_BIN3_DYN_UNLOCK(bin) g_mutex_unlock (&(bin)->dyn_lock) /* lock for shutdown */ -#define GST_PLAY_BIN3_SHUTDOWN_LOCK(bin,label) \ -G_STMT_START { \ - if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown))) \ - goto label; \ - GST_PLAY_BIN3_DYN_LOCK (bin); \ - if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown))) { \ - GST_PLAY_BIN3_DYN_UNLOCK (bin); \ - goto label; \ - } \ -} G_STMT_END +#define GST_PLAY_BIN3_SHUTDOWN_LOCK(bin,label) \ + G_STMT_START { \ + if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown))) \ + goto label; \ + GST_PLAY_BIN3_DYN_LOCK (bin); \ + if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown))) { \ + GST_PLAY_BIN3_DYN_UNLOCK (bin); \ + goto label; \ + } \ + } G_STMT_END /* unlock for shutdown */ -#define GST_PLAY_BIN3_SHUTDOWN_UNLOCK(bin) \ - GST_PLAY_BIN3_DYN_UNLOCK (bin); \ +#define GST_PLAY_BIN3_SHUTDOWN_UNLOCK(bin) \ + GST_PLAY_BIN3_DYN_UNLOCK (bin); \ /** * GstPlayBin3: @@ -398,25 +428,20 @@ struct _GstPlayBin3 GstSourceGroup *curr_group; /* pointer to the currently playing group */ GstSourceGroup *next_group; /* pointer to the next group */ - /* combiners for different streams */ + /* Array of GstPad controlled by each combiner */ GPtrArray *channels[PLAYBIN_STREAM_LAST]; /* links to combiner pads */ + + /* combiners for different streams */ GstSourceCombine combiner[PLAYBIN_STREAM_LAST]; - /* A global decodebin3 that's used to actually do decoding */ - gboolean decodebin_active; - GstElement *decodebin; - /* Bit-wise set of stream types we have - * requested from decodebin vs stream types - * decodebin has provided */ + /* Bit-wise set of stream types we have requested from uridecodebin3. + * Calculated as the combination of the 'selected_stream_types' of + * each sourcegroup */ GstStreamType selected_stream_types; - GstStreamType active_stream_types; - /* Decodebin signals */ - gulong db_pad_added_id; - gulong db_pad_removed_id; - gulong db_no_more_pads_id; - gulong db_drained_id; - gulong db_select_stream_id; + /* Bit-wise set of configured output stream types (i.e. active + playsink inputs and combiners) */ + GstStreamType active_stream_types; /* properties */ guint64 connection_speed; /* connection speed in bits/sec (0 = unknown) */ @@ -438,8 +463,9 @@ struct _GstPlayBin3 /* our play sink */ GstPlaySink *playsink; - /* the last activated source */ - GstElement *source; + /* Task for (de)activating groups, protected by the activation lock */ + GstTask *activation_task; + GRecMutex activation_lock; /* lock protecting dynamic adding/removing */ GMutex dyn_lock; @@ -476,19 +502,7 @@ struct _GstPlayBin3 GSequence *aelements; /* a list of GstAVElements for audio stream */ GSequence *velements; /* a list of GstAVElements for video stream */ - struct - { - gboolean valid; - GstFormat format; - gint64 duration; - } duration[5]; /* cached durations */ - guint64 ring_buffer_max_size; /* 0 means disabled */ - - GList *contexts; - - /* Active stream collection */ - GstStreamCollection *collection; }; struct _GstPlayBin3Class @@ -506,14 +520,12 @@ struct _GstPlayBin3Class /* props */ #define DEFAULT_URI NULL #define DEFAULT_SUBURI NULL -#define DEFAULT_SOURCE NULL #define DEFAULT_FLAGS GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_TEXT | \ GST_PLAY_FLAG_SOFT_VOLUME | GST_PLAY_FLAG_DEINTERLACE | \ GST_PLAY_FLAG_SOFT_COLORBALANCE | GST_PLAY_FLAG_BUFFERING #define DEFAULT_CURRENT_VIDEO -1 #define DEFAULT_CURRENT_AUDIO -1 #define DEFAULT_CURRENT_TEXT -1 -#define DEFAULT_AUTO_SELECT_STREAMS TRUE #define DEFAULT_SUBTITLE_ENCODING NULL #define DEFAULT_AUDIO_SINK NULL #define DEFAULT_VIDEO_SINK NULL @@ -535,7 +547,6 @@ enum PROP_CURRENT_URI, PROP_SUBURI, PROP_CURRENT_SUBURI, - PROP_SOURCE, PROP_FLAGS, PROP_SUBTITLE_ENCODING, PROP_AUDIO_SINK, @@ -571,8 +582,10 @@ enum LAST_SIGNAL }; +#if 0 /* AUTOPLUG DISABLED */ static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS ("audio/x-raw(ANY)"); static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw(ANY)"); +#endif static void gst_play_bin3_class_init (GstPlayBin3Class * klass); static void gst_play_bin3_init (GstPlayBin3 * playbin); @@ -589,37 +602,35 @@ static GstStateChangeReturn gst_play_bin3_change_state (GstElement * element, static void gst_play_bin3_handle_message (GstBin * bin, GstMessage * message); static void gst_play_bin3_deep_element_added (GstBin * playbin, GstBin * sub_bin, GstElement * child); -static gboolean gst_play_bin3_query (GstElement * element, GstQuery * query); -static void gst_play_bin3_set_context (GstElement * element, - GstContext * context); static gboolean gst_play_bin3_send_event (GstElement * element, GstEvent * event); static GstSample *gst_play_bin3_convert_sample (GstPlayBin3 * playbin, GstCaps * caps); -static GstStateChangeReturn setup_next_source (GstPlayBin3 * playbin, - GstState target); +static GstStateChangeReturn setup_next_source (GstPlayBin3 * playbin); -static void no_more_pads_cb (GstElement * decodebin, GstPlayBin3 * playbin); +static void gst_play_bin3_check_group_status (GstPlayBin3 * playbin); +static void emit_about_to_finish (GstPlayBin3 * playbin); +static void reconfigure_output (GstPlayBin3 * playbin); static void pad_removed_cb (GstElement * decodebin, GstPad * pad, - GstPlayBin3 * playbin); + GstSourceGroup * group); static gint select_stream_cb (GstElement * decodebin, GstStreamCollection * collection, GstStream * stream, - GstPlayBin3 * playbin); + GstSourceGroup * group); -static void do_stream_selection (GstPlayBin3 * playbin); +static void do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group); static GstElementClass *parent_class; static guint gst_play_bin3_signals[LAST_SIGNAL] = { 0 }; -#define REMOVE_SIGNAL(obj,id) \ -if (id) { \ - g_signal_handler_disconnect (obj, id); \ - id = 0; \ -} +#define REMOVE_SIGNAL(obj,id) \ + if (id) { \ + g_signal_handler_disconnect (obj, id); \ + id = 0; \ + } static void gst_play_bin3_overlay_init (gpointer g_iface, gpointer g_iface_data); @@ -706,7 +717,7 @@ gst_play_bin3_class_init (GstPlayBin3Class * klass) g_param_spec_string ("uri", "URI", "URI of the media to play", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - /** + /** * GstPlayBin3:current-uri * * The currently playing uri. @@ -736,10 +747,6 @@ gst_play_bin3_class_init (GstPlayBin3Class * klass) "The currently playing URI of a subtitle", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_klass, PROP_SOURCE, - g_param_spec_object ("source", "Source", "Source element", - GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - /** * GstPlayBin3:flags * @@ -801,32 +808,34 @@ gst_play_bin3_class_init (GstPlayBin3Class * klass) /** * GstPlayBin3:video-stream-combiner * - * Get or set the current video stream combiner. By default, an input-selector - * is created and deleted as-needed. + * Get or set the current video stream combiner. By default, no + * element is used and the selected stream is used directly. */ g_object_class_install_property (gobject_klass, PROP_VIDEO_STREAM_COMBINER, g_param_spec_object ("video-stream-combiner", "Video stream combiner", - "Current video stream combiner (NULL = input-selector)", + "Current video stream combiner (default: none)", GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** * GstPlayBin3:audio-stream-combiner * - * Get or set the current audio stream combiner. By default, an input-selector - * is created and deleted as-needed. + * Get or set the current audio stream combiner. By default, no + * element is used and the selected stream is used directly. */ g_object_class_install_property (gobject_klass, PROP_AUDIO_STREAM_COMBINER, g_param_spec_object ("audio-stream-combiner", "Audio stream combiner", - "Current audio stream combiner (NULL = input-selector)", + "Current audio stream combiner (default: none))", GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** * GstPlayBin3:text-stream-combiner * - * Get or set the current text stream combiner. By default, an input-selector - * is created and deleted as-needed. + * Get or set the current text stream combiner. By default, no + * element is used and the selected stream is used directly. */ g_object_class_install_property (gobject_klass, PROP_TEXT_STREAM_COMBINER, g_param_spec_object ("text-stream-combiner", "Text stream combiner", - "Current text stream combiner (NULL = input-selector)", + "Current text stream combiner (default: none)", GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** @@ -1037,8 +1046,6 @@ gst_play_bin3_class_init (GstPlayBin3Class * klass) gstelement_klass->change_state = GST_DEBUG_FUNCPTR (gst_play_bin3_change_state); - gstelement_klass->query = GST_DEBUG_FUNCPTR (gst_play_bin3_query); - gstelement_klass->set_context = GST_DEBUG_FUNCPTR (gst_play_bin3_set_context); gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_bin3_send_event); gstbin_klass->handle_message = @@ -1089,35 +1096,38 @@ init_combiners (GstPlayBin3 * playbin) playbin->combiner[PLAYBIN_STREAM_AUDIO].media_type = "audio"; playbin->combiner[PLAYBIN_STREAM_AUDIO].type = GST_PLAY_SINK_TYPE_AUDIO; + playbin->combiner[PLAYBIN_STREAM_AUDIO].stream_type = GST_STREAM_TYPE_AUDIO; playbin->combiner[PLAYBIN_STREAM_AUDIO].channels = playbin->channels[0]; playbin->combiner[PLAYBIN_STREAM_AUDIO].streams = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); - playbin->combiner[PLAYBIN_STREAM_AUDIO].current_stream = -1; playbin->combiner[PLAYBIN_STREAM_VIDEO].media_type = "video"; playbin->combiner[PLAYBIN_STREAM_VIDEO].type = GST_PLAY_SINK_TYPE_VIDEO; + playbin->combiner[PLAYBIN_STREAM_VIDEO].stream_type = GST_STREAM_TYPE_VIDEO; playbin->combiner[PLAYBIN_STREAM_VIDEO].channels = playbin->channels[1]; playbin->combiner[PLAYBIN_STREAM_VIDEO].streams = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); - playbin->combiner[PLAYBIN_STREAM_VIDEO].current_stream = -1; playbin->combiner[PLAYBIN_STREAM_TEXT].media_type = "text"; playbin->combiner[PLAYBIN_STREAM_TEXT].get_media_caps = gst_subtitle_overlay_create_factory_caps; playbin->combiner[PLAYBIN_STREAM_TEXT].type = GST_PLAY_SINK_TYPE_TEXT; + playbin->combiner[PLAYBIN_STREAM_TEXT].stream_type = GST_STREAM_TYPE_TEXT; playbin->combiner[PLAYBIN_STREAM_TEXT].channels = playbin->channels[2]; playbin->combiner[PLAYBIN_STREAM_TEXT].streams = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); - playbin->combiner[PLAYBIN_STREAM_TEXT].current_stream = -1; } -/* Update the combiner information to be in sync with the current collection */ +/* Update the combiner information to be in sync with the current collection + * + * FIXME : "current" collection doesn't mean anything until we have a "combined" + * collection of all groups */ static void -update_combiner_info (GstPlayBin3 * playbin) +update_combiner_info (GstPlayBin3 * playbin, GstStreamCollection * collection) { guint i, len; - if (playbin->collection == NULL) + if (collection == NULL) return; GST_DEBUG_OBJECT (playbin, "Updating combiner info"); @@ -1128,18 +1138,14 @@ update_combiner_info (GstPlayBin3 * playbin) g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_TEXT].streams, TRUE); playbin->combiner[PLAYBIN_STREAM_AUDIO].streams = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); - playbin->combiner[PLAYBIN_STREAM_AUDIO].current_stream = -1; playbin->combiner[PLAYBIN_STREAM_VIDEO].streams = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); - playbin->combiner[PLAYBIN_STREAM_VIDEO].current_stream = -1; playbin->combiner[PLAYBIN_STREAM_TEXT].streams = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); - playbin->combiner[PLAYBIN_STREAM_TEXT].current_stream = -1; - len = gst_stream_collection_get_size (playbin->collection); + len = gst_stream_collection_get_size (collection); for (i = 0; i < len; i++) { - GstStream *stream = - gst_stream_collection_get_stream (playbin->collection, i); + GstStream *stream = gst_stream_collection_get_stream (collection, i); GstStreamType stype = gst_stream_get_stream_type (stream); if (stype & GST_STREAM_TYPE_AUDIO) { @@ -1162,40 +1168,24 @@ update_combiner_info (GstPlayBin3 * playbin) playbin->combiner[PLAYBIN_STREAM_TEXT].streams->len); } -/* Set the given stream as the selected stream */ -static void -set_selected_stream (GstPlayBin3 * playbin, GstStream * stream) -{ - GstSourceCombine *combine = NULL; - GstStreamType stype = gst_stream_get_stream_type (stream); - - if (stype & GST_STREAM_TYPE_AUDIO) - combine = &playbin->combiner[PLAYBIN_STREAM_AUDIO]; - else if (stype & GST_STREAM_TYPE_VIDEO) - combine = &playbin->combiner[PLAYBIN_STREAM_VIDEO]; - else if (stype & GST_STREAM_TYPE_TEXT) - combine = &playbin->combiner[PLAYBIN_STREAM_TEXT]; - - if (combine) { - if (combine->combiner == NULL) { - guint i, len; - - GST_DEBUG_OBJECT (playbin, "Called for %s (%p)", - gst_stream_get_stream_id (stream), combine->combiner); - - combine->current_stream = -1; - len = combine->streams->len; - for (i = 0; i < len; i++) { - GstStream *cand = g_ptr_array_index (combine->streams, i); - if (cand == stream) { - GST_DEBUG_OBJECT (playbin, "Setting current to %d", i); - combine->current_stream = i; - break; - } - } - } - } -} +#ifndef GST_DISABLE_GST_DEBUG +#define debug_groups(playbin) G_STMT_START { \ + guint i; \ + \ + for (i = 0; i < 2; i++) { \ + GstSourceGroup *group = &playbin->groups[i]; \ + \ + GST_DEBUG ("GstSourceGroup #%d (%s)", i, (group == playbin->curr_group) ? "current" : (group == playbin->next_group) ? "next" : "unused"); \ + GST_DEBUG (" valid:%d , active:%d , playing:%d", group->valid, group->active, group->playing); \ + GST_DEBUG (" uri:%s", group->uri); \ + GST_DEBUG (" suburi:%s", group->suburi); \ + GST_DEBUG (" group_id:%d", group->group_id); \ + GST_DEBUG (" pending_about_to_finish:%d", group->pending_about_to_finish); \ + } \ + } G_STMT_END +#else +#define debug_groups(p) {} +#endif static void init_group (GstPlayBin3 * playbin, GstSourceGroup * group) @@ -1203,7 +1193,7 @@ init_group (GstPlayBin3 * playbin, GstSourceGroup * group) g_mutex_init (&group->lock); group->stream_changed_pending = FALSE; - g_mutex_init (&group->stream_changed_pending_lock); + group->group_id = GST_GROUP_ID_INVALID; group->playbin = playbin; } @@ -1216,12 +1206,13 @@ free_group (GstPlayBin3 * playbin, GstSourceGroup * group) g_mutex_clear (&group->lock); group->stream_changed_pending = FALSE; - g_mutex_clear (&group->stream_changed_pending_lock); if (group->pending_buffering_msg) gst_message_unref (group->pending_buffering_msg); group->pending_buffering_msg = NULL; + gst_object_replace ((GstObject **) & group->collection, NULL); + gst_object_replace ((GstObject **) & group->audio_sink, NULL); gst_object_replace ((GstObject **) & group->video_sink, NULL); gst_object_replace ((GstObject **) & group->text_sink, NULL); @@ -1246,6 +1237,7 @@ colorbalance_value_changed_cb (GstColorBalance * balance, gst_color_balance_value_changed (GST_COLOR_BALANCE (playbin), channel, value); } +#if 0 /* AUTOPLUG DISABLED */ static gint compare_factories_func (gconstpointer p1, gconstpointer p2) { @@ -1320,6 +1312,7 @@ gst_play_bin3_update_elements_list (GstPlayBin3 * playbin) playbin->elements_cookie = cookie; } +#endif static void gst_play_bin3_init (GstPlayBin3 * playbin) @@ -1341,6 +1334,8 @@ gst_play_bin3_init (GstPlayBin3 * playbin) /* first filter out the interesting element factories */ g_mutex_init (&playbin->elements_lock); + g_rec_mutex_init (&playbin->activation_lock); + /* add sink */ playbin->playsink = g_object_new (GST_TYPE_PLAY_SINK, "name", "playsink", "send-event-mode", @@ -1383,9 +1378,6 @@ gst_play_bin3_finalize (GObject * object) for (i = 0; i < PLAYBIN_STREAM_LAST; i++) g_ptr_array_free (playbin->channels[i], TRUE); - if (playbin->source) - gst_object_unref (playbin->source); - /* Setting states to NULL is safe here because playsink * will already be gone and none of these sinks will be * a child of playsink @@ -1420,9 +1412,6 @@ gst_play_bin3_finalize (GObject * object) g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_VIDEO].streams, TRUE); g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_TEXT].streams, TRUE); - if (playbin->decodebin) - gst_object_unref (playbin->decodebin); - if (playbin->elements) gst_plugin_feature_list_free (playbin->elements); @@ -1432,11 +1421,7 @@ gst_play_bin3_finalize (GObject * object) if (playbin->velements) g_sequence_free (playbin->velements); - if (playbin->collection) - gst_object_unref (playbin->collection); - - g_list_free_full (playbin->contexts, (GDestroyNotify) gst_context_unref); - + g_rec_mutex_clear (&playbin->activation_lock); g_rec_mutex_clear (&playbin->lock); g_mutex_clear (&playbin->dyn_lock); g_mutex_clear (&playbin->elements_lock); @@ -1567,43 +1552,6 @@ gst_play_bin3_convert_sample (GstPlayBin3 * playbin, GstCaps * caps) return gst_play_sink_convert_sample (playbin->playsink, caps); } -/* Returns current stream number, or -1 if none has been selected yet */ -static int -get_current_stream_number (GstPlayBin3 * playbin, GstSourceCombine * combine, - GPtrArray * channels) -{ - /* Internal API cleanup would make this easier... */ - int i; - GstPad *pad, *current; - GstObject *combiner = NULL; - int ret = -1; - - if (!combine->has_active_pad) { - GST_WARNING_OBJECT (playbin, - "combiner doesn't have the \"active-pad\" property"); - return ret; - } - - for (i = 0; i < channels->len; i++) { - pad = g_ptr_array_index (channels, i); - if ((combiner = gst_pad_get_parent (pad))) { - g_object_get (combiner, "active-pad", ¤t, NULL); - gst_object_unref (combiner); - - if (pad == current) { - gst_object_unref (current); - ret = i; - break; - } - - if (current) - gst_object_unref (current); - } - } - - return ret; -} - static gboolean gst_play_bin3_send_custom_event (GstObject * combiner, const gchar * event_name) { @@ -1647,10 +1595,10 @@ gst_play_bin3_set_current_stream (GstPlayBin3 * playbin, GST_DEBUG_OBJECT (playbin, "Changing current %s stream %d -> %d", stream_type_names[stream_type], *current_value, stream); - if (combine->combiner == NULL) { + if (combine->combiner == NULL || combine->is_concat) { /* FIXME: Check that the current_value is within range */ *current_value = stream; - do_stream_selection (playbin); + do_stream_selection (playbin, playbin->curr_group); GST_PLAY_BIN3_UNLOCK (playbin); return TRUE; } @@ -1740,35 +1688,6 @@ gst_play_bin3_set_current_text_stream (GstPlayBin3 * playbin, gint stream) &playbin->current_text, stream, &playbin->text_pending_flush_finish); } -static void -source_combine_remove_pads (GstPlayBin3 * playbin, GstSourceCombine * combine) -{ - if (combine->sinkpad) { - GST_LOG_OBJECT (playbin, "unlinking from sink"); - gst_pad_unlink (combine->srcpad, combine->sinkpad); - - /* release back */ - GST_LOG_OBJECT (playbin, "release sink pad"); - gst_play_sink_release_pad (playbin->playsink, combine->sinkpad); - gst_object_unref (combine->sinkpad); - combine->sinkpad = NULL; - } - gst_object_unref (combine->srcpad); - combine->srcpad = NULL; -} - -static GstPadProbeReturn -block_serialized_data_cb (GstPad * pad, GstPadProbeInfo * info, - gpointer user_data) -{ - if (GST_IS_EVENT (info->data) && !GST_EVENT_IS_SERIALIZED (info->data)) { - GST_DEBUG_OBJECT (pad, "Letting non-serialized event %s pass", - GST_EVENT_TYPE_NAME (info->data)); - return GST_PAD_PROBE_PASS; - } - - return GST_PAD_PROBE_OK; -} static void gst_play_bin3_set_sink (GstPlayBin3 * playbin, GstPlaySinkType type, @@ -1810,14 +1729,7 @@ gst_play_bin3_set_stream_combiner (GstPlayBin3 * playbin, GstElement ** elem, static void gst_play_bin3_set_encoding (GstPlayBin3 * playbin, const gchar * encoding) { - GstElement *elem; - GST_PLAY_BIN3_LOCK (playbin); - - /* set subtitles on decodebin. */ - if ((elem = playbin->decodebin)) - g_object_set (G_OBJECT (elem), "subtitle-encoding", encoding, NULL); - gst_play_sink_set_subtitle_encoding (playbin->playsink, encoding); GST_PLAY_BIN3_UNLOCK (playbin); } @@ -1839,8 +1751,8 @@ gst_play_bin3_set_property (GObject * object, guint prop_id, gst_play_bin3_set_flags (playbin, g_value_get_flags (value)); if (playbin->curr_group) { GST_SOURCE_GROUP_LOCK (playbin->curr_group); - if (playbin->curr_group->urisourcebin) { - g_object_set (playbin->curr_group->urisourcebin, "download", + if (playbin->curr_group->uridecodebin) { + g_object_set (playbin->curr_group->uridecodebin, "download", (g_value_get_flags (value) & GST_PLAY_FLAG_DOWNLOAD) != 0, NULL); } GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); @@ -1914,8 +1826,8 @@ gst_play_bin3_set_property (GObject * object, guint prop_id, playbin->ring_buffer_max_size = g_value_get_uint64 (value); if (playbin->curr_group) { GST_SOURCE_GROUP_LOCK (playbin->curr_group); - if (playbin->curr_group->urisourcebin) { - g_object_set (playbin->curr_group->urisourcebin, + if (playbin->curr_group->uridecodebin) { + g_object_set (playbin->curr_group->uridecodebin, "ring-buffer-max-size", playbin->ring_buffer_max_size, NULL); } GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); @@ -1968,7 +1880,10 @@ gst_play_bin3_get_current_stream_combiner (GstPlayBin3 * playbin, GstElement *combiner; GST_PLAY_BIN3_LOCK (playbin); - if ((combiner = playbin->combiner[stream_type].combiner)) + /* The special concat element should never be returned */ + if (playbin->combiner[stream_type].is_concat) + combiner = NULL; + else if ((combiner = playbin->combiner[stream_type].combiner)) gst_object_ref (combiner); else if ((combiner = *elem)) gst_object_ref (combiner); @@ -2024,13 +1939,6 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value, GST_PLAY_BIN3_UNLOCK (playbin); break; } - case PROP_SOURCE: - { - GST_OBJECT_LOCK (playbin); - g_value_set_object (value, playbin->source); - GST_OBJECT_UNLOCK (playbin); - break; - } case PROP_FLAGS: g_value_set_flags (value, gst_play_bin3_get_flags (playbin)); break; @@ -2143,108 +2051,6 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value, } } -static void -gst_play_bin3_update_cached_duration_from_query (GstPlayBin3 * playbin, - gboolean valid, GstQuery * query) -{ - GstFormat fmt; - gint64 duration; - gint i; - - GST_DEBUG_OBJECT (playbin, "Updating cached duration from query"); - gst_query_parse_duration (query, &fmt, &duration); - - for (i = 0; i < G_N_ELEMENTS (playbin->duration); i++) { - if (playbin->duration[i].format == 0 || fmt == playbin->duration[i].format) { - playbin->duration[i].valid = valid; - playbin->duration[i].format = fmt; - playbin->duration[i].duration = valid ? duration : -1; - break; - } - } -} - -static void -gst_play_bin3_update_cached_duration (GstPlayBin3 * playbin) -{ - const GstFormat formats[] = - { GST_FORMAT_TIME, GST_FORMAT_BYTES, GST_FORMAT_DEFAULT }; - gboolean ret; - GstQuery *query; - gint i; - - GST_DEBUG_OBJECT (playbin, "Updating cached durations before group switch"); - for (i = 0; i < G_N_ELEMENTS (formats); i++) { - query = gst_query_new_duration (formats[i]); - ret = - GST_ELEMENT_CLASS (parent_class)->query (GST_ELEMENT_CAST (playbin), - query); - gst_play_bin3_update_cached_duration_from_query (playbin, ret, query); - gst_query_unref (query); - } -} - -static gboolean -gst_play_bin3_query (GstElement * element, GstQuery * query) -{ - GstPlayBin3 *playbin = GST_PLAY_BIN3 (element); - gboolean ret; - - /* During a group switch we shouldn't allow duration queries - * because it's not clear if the old or new group's duration - * is returned and if the sinks are already playing new data - * or old data. See bug #585969 - * - * While we're at it, also don't do any other queries during - * a group switch or any other event that causes topology changes - * by taking the playbin lock in any case. - */ - GST_PLAY_BIN3_LOCK (playbin); - - if (GST_QUERY_TYPE (query) == GST_QUERY_DURATION) { - GstSourceGroup *group = playbin->curr_group; - gboolean pending; - - GST_SOURCE_GROUP_LOCK (group); - - pending = group->pending || group->stream_changed_pending; - - if (pending) { - GstFormat fmt; - gint i; - - ret = FALSE; - gst_query_parse_duration (query, &fmt, NULL); - for (i = 0; i < G_N_ELEMENTS (playbin->duration); i++) { - if (fmt == playbin->duration[i].format) { - ret = playbin->duration[i].valid; - gst_query_set_duration (query, fmt, - (ret ? playbin->duration[i].duration : -1)); - break; - } - } - /* if nothing cached yet, we might as well request duration, - * such as during initial startup */ - if (ret) { - GST_DEBUG_OBJECT (playbin, - "Taking cached duration because of pending group switch: %d", ret); - GST_SOURCE_GROUP_UNLOCK (group); - GST_PLAY_BIN3_UNLOCK (playbin); - return ret; - } - } - GST_SOURCE_GROUP_UNLOCK (group); - } - - ret = GST_ELEMENT_CLASS (parent_class)->query (element, query); - - if (GST_QUERY_TYPE (query) == GST_QUERY_DURATION) - gst_play_bin3_update_cached_duration_from_query (playbin, ret, query); - GST_PLAY_BIN3_UNLOCK (playbin); - - return ret; -} - static gint get_combiner_stream_id (GstPlayBin3 * playbin, GstSourceCombine * combine, GList * full_list) @@ -2252,7 +2058,7 @@ get_combiner_stream_id (GstPlayBin3 * playbin, GstSourceCombine * combine, gint i; GList *tmp; - for (i = 0; combine->streams->len; i++) { + for (i = 0; i < combine->streams->len; i++) { GstStream *stream = (GstStream *) g_ptr_array_index (combine->streams, i); const gchar *sid = gst_stream_get_stream_id (stream); for (tmp = full_list; tmp; tmp = tmp->next) { @@ -2268,17 +2074,16 @@ get_combiner_stream_id (GstPlayBin3 * playbin, GstSourceCombine * combine, static GList * extend_list_of_streams (GstPlayBin3 * playbin, GstStreamType stype, - GList * list) + GList * list, GstStreamCollection * collection) { GList *tmp, *res; gint i, nb; res = list; - nb = gst_stream_collection_get_size (playbin->collection); + nb = gst_stream_collection_get_size (collection); for (i = 0; i < nb; i++) { - GstStream *stream = - gst_stream_collection_get_stream (playbin->collection, i); + GstStream *stream = gst_stream_collection_get_stream (collection, i); GstStreamType curtype = gst_stream_get_stream_type (stream); if (stype == curtype) { gboolean already_there = FALSE; @@ -2301,7 +2106,8 @@ extend_list_of_streams (GstPlayBin3 * playbin, GstStreamType stype, } static GstEvent * -update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event) +update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event, + GstSourceGroup * group) { GList *streams = NULL; GList *to_use; @@ -2315,13 +2121,21 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event) return event; } + if (!group->collection) { + GST_DEBUG_OBJECT (playbin, + "No stream collection for group, no need to modify SELECT_STREAMS event"); + return event; + } + gst_event_parse_select_streams (event, &streams); to_use = g_list_copy_deep (streams, (GCopyFunc) g_strdup, NULL); /* For each combiner, we want to add all streams of that type to the * selection */ if (playbin->audio_stream_combiner) { - to_use = extend_list_of_streams (playbin, GST_STREAM_TYPE_AUDIO, to_use); + to_use = + extend_list_of_streams (playbin, GST_STREAM_TYPE_AUDIO, to_use, + group->collection); combine_id = get_combiner_stream_id (playbin, &playbin->combiner[PLAYBIN_STREAM_AUDIO], streams); @@ -2329,7 +2143,9 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event) gst_play_bin3_set_current_audio_stream (playbin, combine_id); } if (playbin->video_stream_combiner) { - to_use = extend_list_of_streams (playbin, GST_STREAM_TYPE_VIDEO, to_use); + to_use = + extend_list_of_streams (playbin, GST_STREAM_TYPE_VIDEO, to_use, + group->collection); combine_id = get_combiner_stream_id (playbin, &playbin->combiner[PLAYBIN_STREAM_VIDEO], streams); @@ -2337,7 +2153,9 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event) gst_play_bin3_set_current_video_stream (playbin, combine_id); } if (playbin->text_stream_combiner) { - to_use = extend_list_of_streams (playbin, GST_STREAM_TYPE_TEXT, to_use); + to_use = + extend_list_of_streams (playbin, GST_STREAM_TYPE_TEXT, to_use, + group->collection); combine_id = get_combiner_stream_id (playbin, &playbin->combiner[PLAYBIN_STREAM_TEXT], streams); @@ -2356,6 +2174,82 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event) return event; } +/* Returns TRUE if the given list of streams belongs to the stream collection */ +static gboolean +gst_streams_belong_to_collection (GList * streams, + GstStreamCollection * collection) +{ + GList *tmp; + guint i, nb; + + if (streams == NULL || collection == NULL) + return FALSE; + nb = gst_stream_collection_get_size (collection); + if (nb == 0) + return FALSE; + + for (tmp = streams; tmp; tmp = tmp->next) { + const gchar *cand = (const gchar *) tmp->data; + gboolean found = FALSE; + + for (i = 0; i < nb; i++) { + GstStream *stream = gst_stream_collection_get_stream (collection, i); + if (!g_strcmp0 (cand, gst_stream_get_stream_id (stream))) { + found = TRUE; + break; + } + } + if (!found) + return FALSE; + } + return TRUE; +} + +static GstSourceGroup * +get_source_group_for_streams (GstPlayBin3 * playbin, GstEvent * event) +{ + GList *streams; + GstSourceGroup *res = NULL; + + gst_event_parse_select_streams (event, &streams); + if (playbin->curr_group->collection && + gst_streams_belong_to_collection (streams, + playbin->curr_group->collection)) + res = playbin->curr_group; + else if (playbin->next_group->collection && + gst_streams_belong_to_collection (streams, + playbin->next_group->collection)) + res = playbin->next_group; + g_list_free_full (streams, g_free); + + return res; +} + +static GstStreamType +get_stream_type_for_event (GstStreamCollection * collection, GstEvent * event) +{ + GList *stream_list = NULL; + GList *tmp; + GstStreamType res = 0; + guint i, len; + + gst_event_parse_select_streams (event, &stream_list); + len = gst_stream_collection_get_size (collection); + for (tmp = stream_list; tmp; tmp = tmp->next) { + gchar *stid = (gchar *) tmp->data; + + for (i = 0; i < len; i++) { + GstStream *stream = gst_stream_collection_get_stream (collection, i); + if (!g_strcmp0 (stid, gst_stream_get_stream_id (stream))) { + res |= gst_stream_get_stream_type (stream); + } + } + } + g_list_free_full (stream_list, g_free); + + return res; +} + static gboolean gst_play_bin3_send_event (GstElement * element, GstEvent * event) { @@ -2363,6 +2257,7 @@ gst_play_bin3_send_event (GstElement * element, GstEvent * event) if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) { gboolean res; + GstSourceGroup *group; GST_PLAY_BIN3_LOCK (playbin); GST_LOG_OBJECT (playbin, @@ -2370,14 +2265,32 @@ gst_play_bin3_send_event (GstElement * element, GstEvent * event) /* This is probably already false, but it doesn't hurt to be sure */ playbin->do_stream_selections = FALSE; + group = get_source_group_for_streams (playbin, event); + if (group == NULL) { + GST_WARNING_OBJECT (playbin, + "Can't figure out to which uridecodebin the select-streams event should be sent to"); + GST_PLAY_BIN3_UNLOCK (playbin); + return FALSE; + } + /* If we have custom combiners, we need to extend the selection with * the list of all streams for that given type since we will be handling * the selection with that combiner */ - event = update_select_streams_event (playbin, event); + event = update_select_streams_event (playbin, event, group); + + if (group->collection) { + group->selected_stream_types = + get_stream_type_for_event (group->collection, event); + playbin->selected_stream_types = + playbin->groups[0].selected_stream_types | playbin-> + groups[1].selected_stream_types; + if (playbin->active_stream_types != playbin->selected_stream_types) + reconfigure_output (playbin); + } - /* Send this event directly to decodebin, so it works even - * if decodebin didn't add any pads yet */ - res = gst_element_send_event (playbin->decodebin, event); + /* Send this event directly to uridecodebin, so it works even + * if uridecodebin didn't add any pads yet */ + res = gst_element_send_event (group->uridecodebin, event); GST_PLAY_BIN3_UNLOCK (playbin); return res; @@ -2401,7 +2314,7 @@ gst_play_bin3_send_event (GstElement * element, GstEvent * event) /* Called with playbin lock held */ static void -do_stream_selection (GstPlayBin3 * playbin) +do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group) { GstStreamCollection *collection; guint i, nb_streams; @@ -2409,7 +2322,10 @@ do_stream_selection (GstPlayBin3 * playbin) gint nb_video = 0, nb_audio = 0, nb_text = 0; GstStreamType chosen_stream_types = 0; - collection = playbin->collection; + if (group == NULL) + return; + + collection = group->collection; if (collection == NULL) { GST_LOG_OBJECT (playbin, "No stream collection. Not doing stream-select"); return; @@ -2420,6 +2336,9 @@ do_stream_selection (GstPlayBin3 * playbin) GST_INFO_OBJECT (playbin, "Empty collection received! Ignoring"); } + GST_DEBUG_OBJECT (playbin, "Doing selection on collection with %d streams", + nb_streams); + /* Iterate the collection and choose the streams that match * either the current-* setting, or all streams of a type if there's * a combiner for that type */ @@ -2430,6 +2349,8 @@ do_stream_selection (GstPlayBin3 * playbin) gint pb_stream_type = -1; gboolean select_this = FALSE; + GST_LOG_OBJECT (playbin, "Looking at stream #%d : %s", i, stream_id); + if (stream_type & GST_STREAM_TYPE_AUDIO) { pb_stream_type = PLAYBIN_STREAM_AUDIO; /* Select the stream if it's the current one or if there's a custom selector */ @@ -2439,7 +2360,7 @@ do_stream_selection (GstPlayBin3 * playbin) playbin->audio_stream_combiner != NULL); nb_audio++; } else if (stream_type & GST_STREAM_TYPE_VIDEO) { - pb_stream_type = PLAYBIN_STREAM_AUDIO; + pb_stream_type = PLAYBIN_STREAM_VIDEO; select_this = (nb_video == playbin->current_video || (playbin->current_video == -1 && nb_video == 0) || @@ -2470,70 +2391,106 @@ do_stream_selection (GstPlayBin3 * playbin) } if (streams) { - GstEvent *ev = gst_event_new_select_streams (streams); - gst_element_send_event (playbin->decodebin, ev); + if (group->uridecodebin) { + GstEvent *ev = gst_event_new_select_streams (streams); + gst_element_send_event (group->uridecodebin, ev); + } g_list_free (streams); } - playbin->selected_stream_types = chosen_stream_types; + + group->selected_stream_types = chosen_stream_types; + /* Update global selected_stream_types */ + playbin->selected_stream_types = + playbin->groups[0].selected_stream_types | playbin-> + groups[1].selected_stream_types; + if (playbin->active_stream_types != playbin->selected_stream_types) + reconfigure_output (playbin); } -/* mime types we are not handling on purpose right now, don't post a - * missing-plugin message for these */ -static const gchar *blacklisted_mimes[] = { - NULL -}; +/* Return the GstSourceGroup to which this element belongs + * Can be NULL (if it belongs to playsink for example) */ +static GstSourceGroup * +find_source_group_owner (GstPlayBin3 * playbin, GstObject * element) +{ + if (playbin->curr_group->uridecodebin + && gst_object_has_as_ancestor (element, + GST_OBJECT_CAST (playbin->curr_group->uridecodebin))) + return playbin->curr_group; + if (playbin->next_group->uridecodebin + && gst_object_has_as_ancestor (element, + GST_OBJECT_CAST (playbin->next_group->uridecodebin))) + return playbin->next_group; + return NULL; +} static void gst_play_bin3_handle_message (GstBin * bin, GstMessage * msg) { GstPlayBin3 *playbin = GST_PLAY_BIN3 (bin); - if (gst_is_missing_plugin_message (msg)) { - gchar *detail; - guint i; - - detail = gst_missing_plugin_message_get_installer_detail (msg); - for (i = 0; detail != NULL && blacklisted_mimes[i] != NULL; ++i) { - if (strstr (detail, "|decoder-") && strstr (detail, blacklisted_mimes[i])) { - GST_LOG_OBJECT (bin, "suppressing message %" GST_PTR_FORMAT, msg); - gst_message_unref (msg); - g_free (detail); - return; - } - } - g_free (detail); - } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_START) { - GstSourceGroup *new_group = playbin->curr_group; - GstMessage *buffering_msg = NULL; + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_START) { + GstSourceGroup *group = NULL, *other_group = NULL; + gboolean changed = FALSE; + guint group_id; + GstMessage *buffering_msg; - GST_SOURCE_GROUP_LOCK (new_group); - new_group->stream_changed_pending = FALSE; - if (new_group->pending_buffering_msg) { - buffering_msg = new_group->pending_buffering_msg; - new_group->pending_buffering_msg = NULL; + if (!gst_message_parse_group_id (msg, &group_id)) { + GST_ERROR_OBJECT (bin, + "Could not get group_id from STREAM_START message !"); + goto beach; } - GST_SOURCE_GROUP_UNLOCK (new_group); - - GST_DEBUG_OBJECT (playbin, "Stream start from new group %p", new_group); + GST_DEBUG_OBJECT (bin, "STREAM_START group_id:%u", group_id); - if (buffering_msg) { - GST_DEBUG_OBJECT (playbin, "Posting pending buffering message: %" - GST_PTR_FORMAT, buffering_msg); - GST_BIN_CLASS (parent_class)->handle_message (bin, buffering_msg); + /* Figure out to which group this group_id corresponds */ + GST_PLAY_BIN3_LOCK (playbin); + if (playbin->groups[0].group_id == group_id) { + group = &playbin->groups[0]; + other_group = &playbin->groups[1]; + } else if (playbin->groups[1].group_id == group_id) { + group = &playbin->groups[1]; + other_group = &playbin->groups[0]; + } + if (group == NULL) { + GST_ERROR_OBJECT (bin, "group_id %u is not provided by any group !", + group_id); + GST_PLAY_BIN3_UNLOCK (playbin); + goto beach; } - } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_BUFFERING) { - GstSourceGroup *group = playbin->curr_group; - gboolean pending; + debug_groups (playbin); - /* drop buffering messages from child queues while we are switching - * groups (because the application set a new uri in about-to-finish) - * if the playsink queue still has buffers to play */ + /* Do the switch now ! */ + playbin->curr_group = group; + playbin->next_group = other_group; GST_SOURCE_GROUP_LOCK (group); - pending = group->stream_changed_pending; + if (group->playing == FALSE) + changed = TRUE; + group->playing = TRUE; + buffering_msg = group->pending_buffering_msg; + group->pending_buffering_msg = NULL; + GST_SOURCE_GROUP_UNLOCK (group); + + GST_SOURCE_GROUP_LOCK (other_group); + other_group->playing = FALSE; + GST_SOURCE_GROUP_UNLOCK (other_group); + + debug_groups (playbin); + GST_PLAY_BIN3_UNLOCK (playbin); + if (changed) + gst_play_bin3_check_group_status (playbin); + else + GST_DEBUG_OBJECT (bin, "Groups didn't changed"); + /* If there was a pending buffering message to send, do it now */ + if (buffering_msg) + GST_BIN_CLASS (parent_class)->handle_message (bin, buffering_msg); + } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_BUFFERING) { + GstSourceGroup *group; - if (pending) { + /* Only post buffering messages for group which is currently playing */ + group = find_source_group_owner (playbin, msg->src); + GST_SOURCE_GROUP_LOCK (group); + if (!group->playing) { GST_DEBUG_OBJECT (playbin, "Storing buffering message from pending group " "%p %" GST_PTR_FORMAT, group, msg); gst_message_replace (&group->pending_buffering_msg, msg); @@ -2543,60 +2500,36 @@ gst_play_bin3_handle_message (GstBin * bin, GstMessage * msg) GST_SOURCE_GROUP_UNLOCK (group); } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_COLLECTION) { GstStreamCollection *collection = NULL; - GstObject *src = GST_MESSAGE_SRC (msg); - gboolean pstate = playbin->do_stream_selections; gst_message_parse_stream_collection (msg, &collection); if (collection) { + gboolean pstate = playbin->do_stream_selections; + GstSourceGroup *target_group = NULL; + GST_PLAY_BIN3_LOCK (playbin); GST_DEBUG_OBJECT (playbin, - "STREAM_COLLECTION: Got a collection from %" GST_PTR_FORMAT, src); - gst_object_replace ((GstObject **) & playbin->collection, - (GstObject *) collection); - update_combiner_info (playbin); + "STREAM_COLLECTION: Got a collection from %" GST_PTR_FORMAT, + msg->src); + target_group = find_source_group_owner (playbin, msg->src); + if (target_group) + gst_object_replace ((GstObject **) & target_group->collection, + (GstObject *) collection); + /* FIXME: Only do the following if it's the current group? */ + if (target_group == playbin->curr_group) + update_combiner_info (playbin, target_group->collection); if (pstate) playbin->do_stream_selections = FALSE; - do_stream_selection (playbin); + do_stream_selection (playbin, target_group); if (pstate) playbin->do_stream_selections = TRUE; GST_PLAY_BIN3_UNLOCK (playbin); gst_object_unref (collection); } - } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAMS_SELECTED) { - GstStreamCollection *collection = NULL; - GstObject *src = GST_MESSAGE_SRC (msg); - gboolean pstate = playbin->do_stream_selections; - - gst_message_parse_streams_selected (msg, &collection); - if (collection) { - guint i, len; - GST_PLAY_BIN3_LOCK (playbin); - GST_DEBUG_OBJECT (playbin, - "STREAMS_SELECTED: Got a collection from %" GST_PTR_FORMAT, src); - gst_object_replace ((GstObject **) & playbin->collection, - (GstObject *) collection); - update_combiner_info (playbin); - len = gst_message_streams_selected_get_size (msg); - for (i = 0; i < len; i++) { - GstStream *stream; - - stream = gst_message_streams_selected_get_stream (msg, i); - set_selected_stream (playbin, stream); - gst_object_unref (stream); - } - if (pstate) - playbin->do_stream_selections = FALSE; - do_stream_selection (playbin); - if (pstate) - playbin->do_stream_selections = TRUE; - GST_PLAY_BIN3_UNLOCK (playbin); - - gst_object_unref (collection); - } - } + } +beach: if (msg) GST_BIN_CLASS (parent_class)->handle_message (bin, msg); } @@ -2614,11 +2547,47 @@ gst_play_bin3_deep_element_added (GstBin * playbin, GstBin * sub_bin, GST_BIN_CLASS (parent_class)->deep_element_added (playbin, sub_bin, child); } +/* Returns current stream number, or -1 if none has been selected yet */ +static int +get_current_stream_number (GstPlayBin3 * playbin, GstSourceCombine * combine, + GPtrArray * channels) +{ + /* Internal API cleanup would make this easier... */ + int i; + GstPad *pad, *current; + GstObject *combiner = NULL; + int ret = -1; + + if (!combine->has_active_pad) { + GST_WARNING_OBJECT (playbin, + "combiner doesn't have the \"active-pad\" property"); + return ret; + } + + for (i = 0; i < channels->len; i++) { + pad = g_ptr_array_index (channels, i); + if ((combiner = gst_pad_get_parent (pad))) { + g_object_get (combiner, "active-pad", ¤t, NULL); + gst_object_unref (combiner); + + if (pad == current) { + gst_object_unref (current); + ret = i; + break; + } + + if (current) + gst_object_unref (current); + } + } + + return ret; +} + static void combiner_active_pad_changed (GObject * combiner, GParamSpec * pspec, GstPlayBin3 * playbin) { - const gchar *property; GstSourceCombine *combine = NULL; GPtrArray *channels = NULL; int i; @@ -2641,7 +2610,6 @@ combiner_active_pad_changed (GObject * combiner, GParamSpec * pspec, switch (combine->type) { case GST_PLAY_SINK_TYPE_VIDEO: case GST_PLAY_SINK_TYPE_VIDEO_RAW: - property = "current-video"; playbin->current_video = get_current_stream_number (playbin, combine, channels); @@ -2650,12 +2618,10 @@ combiner_active_pad_changed (GObject * combiner, GParamSpec * pspec, GST_PLAY_BIN3_UNLOCK (playbin); gst_play_bin3_send_custom_event (GST_OBJECT (combiner), "playsink-custom-video-flush-finish"); - goto notify; } break; case GST_PLAY_SINK_TYPE_AUDIO: case GST_PLAY_SINK_TYPE_AUDIO_RAW: - property = "current-audio"; playbin->current_audio = get_current_stream_number (playbin, combine, channels); @@ -2664,11 +2630,9 @@ combiner_active_pad_changed (GObject * combiner, GParamSpec * pspec, GST_PLAY_BIN3_UNLOCK (playbin); gst_play_bin3_send_custom_event (GST_OBJECT (combiner), "playsink-custom-audio-flush-finish"); - goto notify; } break; case GST_PLAY_SINK_TYPE_TEXT: - property = "current-text"; playbin->current_text = get_current_stream_number (playbin, combine, channels); @@ -2677,17 +2641,12 @@ combiner_active_pad_changed (GObject * combiner, GParamSpec * pspec, GST_PLAY_BIN3_UNLOCK (playbin); gst_play_bin3_send_custom_event (GST_OBJECT (combiner), "playsink-custom-subtitle-flush-finish"); - goto notify; } break; default: - property = NULL; + break; } GST_PLAY_BIN3_UNLOCK (playbin); - -notify: - if (property) - g_object_notify (G_OBJECT (playbin), property); } static GstCaps * @@ -2736,11 +2695,41 @@ update_video_multiview_caps (GstPlayBin3 * playbin, GstCaps * caps) return out_caps; } +static void +emit_about_to_finish (GstPlayBin3 * playbin) +{ + GST_DEBUG_OBJECT (playbin, "Emitting about-to-finish"); + + /* after this call, we should have a next group to activate or we EOS */ + g_signal_emit (G_OBJECT (playbin), + gst_play_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); + + debug_groups (playbin); + + /* now activate the next group. If the app did not set a uri, this will + * fail and we can do EOS */ + setup_next_source (playbin); +} + +static SourcePad * +find_source_pad (GstSourceGroup * group, GstPad * target) +{ + GList *tmp; + + for (tmp = group->source_pads; tmp; tmp = tmp->next) { + SourcePad *res = (SourcePad *) tmp->data; + if (res->pad == target) + return res; + } + return NULL; +} + static GstPadProbeReturn _decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata) { GstPadProbeReturn ret = GST_PAD_PROBE_OK; - GstPlayBin3 *playbin = (GstPlayBin3 *) udata; + GstSourceGroup *group = (GstSourceGroup *) udata; + GstPlayBin3 *playbin = group->playbin; GstEvent *event = GST_PAD_PROBE_INFO_DATA (info); switch (GST_EVENT_TYPE (event)) { @@ -2764,6 +2753,21 @@ _decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata) } break; } + case GST_EVENT_STREAM_START: + { + guint group_id; + if (gst_event_parse_group_id (event, &group_id)) { + GST_LOG_OBJECT (pad, "STREAM_START group_id:%u", group_id); + if (group->group_id == GST_GROUP_ID_INVALID) + group->group_id = group_id; + else if (group->group_id != group_id) { + GST_DEBUG_OBJECT (pad, "group_id changing from %u to %u", + group->group_id, group_id); + group->group_id = group_id; + } + } + break; + } default: break; } @@ -2771,58 +2775,82 @@ _decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata) return ret; } -/* this function is called when a new pad is added to decodebin. We check the - * type of the pad and add it to the combiner element - */ static void -pad_added_cb (GstElement * decodebin, GstPad * pad, GstPlayBin3 * playbin) +control_source_pad (GstSourceGroup * group, GstPad * pad, + GstStreamType stream_type) { - GstPad *sinkpad; - GstPadLinkReturn res; - GstSourceCombine *combine = NULL; - GstStreamType stream_type; - gint pb_stream_type = -1; - GstElement *custom_combiner = NULL; - gulong event_probe_handler; - gchar *pad_name; + SourcePad *sourcepad = g_slice_new0 (SourcePad); - GST_PLAY_BIN3_SHUTDOWN_LOCK (playbin, shutdown); + sourcepad->pad = pad; + sourcepad->event_probe_id = + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + _decodebin_event_probe, group, NULL); + sourcepad->stream_type = stream_type; + group->source_pads = g_list_append (group->source_pads, sourcepad); +} - pad_name = gst_object_get_name (GST_OBJECT (pad)); +static void +remove_combiner (GstPlayBin3 * playbin, GstSourceCombine * combine) +{ + gint n; - GST_DEBUG_OBJECT (playbin, "decoded pad %s:%s added", - GST_DEBUG_PAD_NAME (pad)); + if (combine->combiner == NULL) { + GST_DEBUG_OBJECT (playbin, "No combiner element to remove"); + return; + } - /* major type of the pad, this determines the combiner to use, - try exact match first */ - if (g_str_has_prefix (pad_name, "video")) { - stream_type = GST_STREAM_TYPE_VIDEO; - pb_stream_type = PLAYBIN_STREAM_VIDEO; + /* Go over all sink pads and release them ! */ + for (n = 0; n < combine->channels->len; n++) { + GstPad *sinkpad = g_ptr_array_index (combine->channels, n); + + gst_element_release_request_pad (combine->combiner, sinkpad); + gst_object_unref (sinkpad); + } + g_ptr_array_set_size (combine->channels, 0); + + gst_element_set_state (combine->combiner, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (playbin), combine->combiner); + combine->combiner = NULL; + +} + +/* Create the combiner element if needed for the given combine */ +static void +create_combiner (GstPlayBin3 * playbin, GstSourceCombine * combine) +{ + GstElement *custom_combiner = NULL; + + if (combine->combiner) { + GST_WARNING_OBJECT (playbin, "Combiner element already exists!"); + return; + } + + if (combine->stream_type == GST_STREAM_TYPE_VIDEO) custom_combiner = playbin->video_stream_combiner; - } else if (g_str_has_prefix (pad_name, "audio")) { - stream_type = GST_STREAM_TYPE_AUDIO; - pb_stream_type = PLAYBIN_STREAM_AUDIO; + else if (combine->stream_type == GST_STREAM_TYPE_AUDIO) custom_combiner = playbin->audio_stream_combiner; - } else if (g_str_has_prefix (pad_name, "text")) { - stream_type = GST_STREAM_TYPE_TEXT; - pb_stream_type = PLAYBIN_STREAM_TEXT; + else if (combine->stream_type == GST_STREAM_TYPE_TEXT) custom_combiner = playbin->text_stream_combiner; - } - g_free (pad_name); + combine->combiner = custom_combiner; - /* no stream type found for the media type, don't bother linking it to a - * combiner. This will leave the pad unlinked and thus ignored. */ - if (pb_stream_type < 0) { - GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin); - goto unknown_type; + if (!combine->combiner) { + gchar *concat_name; + GST_DEBUG_OBJECT (playbin, + "No custom combiner requested, using 'concat' element"); + concat_name = g_strdup_printf ("%s-concat", combine->media_type); + combine->combiner = gst_element_factory_make ("concat", concat_name); + g_object_set (combine->combiner, "adjust-base", FALSE, NULL); + g_free (concat_name); + combine->is_concat = TRUE; } - combine = &playbin->combiner[pb_stream_type]; + combine->srcpad = gst_element_get_static_pad (combine->combiner, "src"); - if (custom_combiner && combine->combiner == NULL) { - combine->combiner = custom_combiner; - /* find out which properties the stream combiner supports */ + /* We only want to use 'active-pad' if it's a regular combiner that + * will consume all streams, and not concat (which is just used for + * gapless) */ + if (!combine->is_concat) { combine->has_active_pad = g_object_class_find_property (G_OBJECT_GET_CLASS (combine->combiner), "active-pad") != NULL; @@ -2830,222 +2858,249 @@ pad_added_cb (GstElement * decodebin, GstPad * pad, GstPlayBin3 * playbin) if (combine->has_active_pad) g_signal_connect (combine->combiner, "notify::active-pad", G_CALLBACK (combiner_active_pad_changed), playbin); - - GST_DEBUG_OBJECT (playbin, "adding new stream combiner %p", - combine->combiner); - gst_element_set_state (combine->combiner, GST_STATE_PAUSED); - gst_bin_add (GST_BIN_CAST (playbin), combine->combiner); } - GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin); + GST_DEBUG_OBJECT (playbin, "adding new stream combiner %" GST_PTR_FORMAT, + combine->combiner); + gst_bin_add (GST_BIN_CAST (playbin), combine->combiner); + gst_element_sync_state_with_parent (combine->combiner); +} - if (combine->srcpad == NULL) { - if (combine->combiner) { - /* save source pad of the combiner */ - combine->srcpad = gst_element_get_static_pad (combine->combiner, "src"); - } else { - /* no combiner, use the pad as the source pad then */ - combine->srcpad = gst_object_ref (pad); - } +static gboolean +combiner_control_pad (GstPlayBin3 * playbin, GstSourceCombine * combine, + GstPad * srcpad) +{ + GstPadLinkReturn res; - /* block the combiner srcpad. It's possible that multiple source elements - * pushing data into the combiners before we have a chance to collect all - * streams and connect the sinks, resulting in not-linked errors. After we - * configure the sinks we will unblock them all. */ - GST_DEBUG_OBJECT (playbin, "blocking %" GST_PTR_FORMAT, combine->srcpad); - combine->block_id = - gst_pad_add_probe (combine->srcpad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, - block_serialized_data_cb, NULL, NULL); - } + GST_DEBUG_OBJECT (playbin, "srcpad %" GST_PTR_FORMAT, srcpad); - /* get sinkpad for the new stream */ if (combine->combiner) { - if ((sinkpad = gst_element_get_request_pad (combine->combiner, "sink_%u"))) { - GST_DEBUG_OBJECT (playbin, "got pad %s:%s from combiner", - GST_DEBUG_PAD_NAME (sinkpad)); + GstPad *sinkpad = + gst_element_get_request_pad (combine->combiner, "sink_%u"); - /* find out which properties the sink pad supports */ - combine->has_always_ok = - g_object_class_find_property (G_OBJECT_GET_CLASS (sinkpad), - "always-ok") != NULL; + if (sinkpad == NULL) + goto request_pad_failed; - /* store the combiner for the pad */ - g_object_set_data (G_OBJECT (sinkpad), "playbin.combine", combine); + GST_DEBUG_OBJECT (playbin, "Got new combiner pad %" GST_PTR_FORMAT, + sinkpad); - /* store the pad in the array */ - GST_DEBUG_OBJECT (playbin, "pad %p added to array", sinkpad); - g_ptr_array_add (combine->channels, sinkpad); + /* store the pad in the array */ + GST_DEBUG_OBJECT (playbin, "pad %" GST_PTR_FORMAT " added to array", + sinkpad); + g_ptr_array_add (combine->channels, sinkpad); - res = gst_pad_link (pad, sinkpad); - if (GST_PAD_LINK_FAILED (res)) - goto link_failed; + res = gst_pad_link (srcpad, sinkpad); + if (GST_PAD_LINK_FAILED (res)) + goto failed_combiner_link; - /* store combiner pad so we can release it */ - g_object_set_data (G_OBJECT (pad), "playbin.sinkpad", sinkpad); + GST_DEBUG_OBJECT (playbin, + "linked pad %" GST_PTR_FORMAT " to combiner %" GST_PTR_FORMAT, srcpad, + combine->combiner); - GST_DEBUG_OBJECT (playbin, "linked pad %s:%s to combiner %p", - GST_DEBUG_PAD_NAME (pad), combine->combiner); - } else { - goto request_pad_failed; - } } else { - /* no combiner, don't configure anything, we'll link the new pad directly to - * the sink. */ - sinkpad = NULL; + GST_LOG_OBJECT (playbin, "combine->sinkpad:%" GST_PTR_FORMAT, + combine->sinkpad); + g_assert (combine->sinkpad != NULL); + /* Connect directly to playsink */ + if (gst_pad_is_linked (combine->sinkpad)) + goto sinkpad_already_linked; - /* store the combiner for the pad */ - g_object_set_data (G_OBJECT (pad), "playbin.combine", combine); - } + GST_DEBUG_OBJECT (playbin, "Linking new pad straight to playsink"); + res = gst_pad_link (srcpad, combine->sinkpad); - event_probe_handler = - gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, - _decodebin_event_probe, playbin, NULL); - g_object_set_data (G_OBJECT (pad), "playbin.event_probe_id", - ULONG_TO_POINTER (event_probe_handler)); - - playbin->active_stream_types |= stream_type; - - /* If we're expecting either audio or video, - * wait for them to appear before configuring playsink */ - if ((playbin->selected_stream_types & ~playbin->active_stream_types & - (GST_STREAM_TYPE_VIDEO | GST_STREAM_TYPE_AUDIO)) - == 0) { - no_more_pads_cb (decodebin, playbin); - } else { - GST_LOG_OBJECT (playbin, "Active stream types 0x%x, want 0x%x. Waiting", - playbin->active_stream_types, playbin->selected_stream_types); + if (res != GST_PAD_LINK_OK) + goto failed_sinkpad_link; } - return; - - /* ERRORS */ -unknown_type: - GST_DEBUG_OBJECT (playbin, "Ignoring pad with unknown type"); - return; + return TRUE; -link_failed: - { - GST_ERROR_OBJECT (playbin, - "failed to link pad %s:%s to combiner, reason %s (%d)", - GST_DEBUG_PAD_NAME (pad), gst_pad_link_get_name (res), res); - return; - } + /* Failure cases */ request_pad_failed: GST_ELEMENT_ERROR (playbin, CORE, PAD, ("Internal playbin error."), ("Failed to get request pad from combiner %p.", combine->combiner)); - return; -shutdown: - { - GST_DEBUG ("ignoring, we are shutting down. Pad will be left unlinked"); - /* not going to done as we didn't request the caps */ - return; + return FALSE; + + +sinkpad_already_linked: + GST_ELEMENT_ERROR (playbin, CORE, PAD, + ("Internal playbin error."), ("playsink pad already used !")); + return FALSE; + +failed_sinkpad_link: + GST_ELEMENT_ERROR (playbin, CORE, PAD, + ("Internal playbin error."), + ("Failed to link pad to sink. Error %d", res)); + return FALSE; + +failed_combiner_link: + GST_ELEMENT_ERROR (playbin, CORE, PAD, + ("Internal playbin error."), + ("Failed to link pad to combiner. Error %d", res)); + return FALSE; +} + +static void +combiner_release_pad (GstPlayBin3 * playbin, GstSourceCombine * combine, + GstPad * pad) +{ + if (combine->combiner) { + GstPad *peer = gst_pad_get_peer (pad); + + if (peer) { + GST_DEBUG_OBJECT (playbin, "Removing combiner pad %" GST_PTR_FORMAT, + peer); + g_ptr_array_remove (combine->channels, peer); + + gst_element_release_request_pad (combine->combiner, peer); + gst_object_unref (peer); + } + } else { + /* Release direct link if present */ + if (combine->sinkpad) { + GST_DEBUG_OBJECT (playbin, "Unlinking pad from playsink sinkpad"); + gst_pad_unlink (pad, combine->sinkpad); + } } } -/* called when a pad is removed from the decodebin. We unlink the pad from - * the combiner. This will make the combiner select a new pad. */ +/* Call after pad was unlinked from (potential) combiner */ static void -pad_removed_cb (GstElement * decodebin, GstPad * pad, GstPlayBin3 * playbin) +release_source_pad (GstPlayBin3 * playbin, GstSourceGroup * group, GstPad * pad) { - GstPad *peer; - GstElement *combiner; - GstSourceCombine *combine; - gulong event_probe_handler; - GstStreamType stream_type = GST_STREAM_TYPE_UNKNOWN; - gchar *pad_name; + SourcePad *sourcepad; + GList *tmp; + GstStreamType alltype = 0; - GST_DEBUG_OBJECT (playbin, - "decoded pad %s:%s removed", GST_DEBUG_PAD_NAME (pad)); + sourcepad = find_source_pad (group, pad); + if (!sourcepad) { + GST_DEBUG_OBJECT (playbin, "Not a pad controlled by us ?"); + return; + } - GST_PLAY_BIN3_LOCK (playbin); + if (sourcepad->event_probe_id) { + gst_pad_remove_probe (pad, sourcepad->event_probe_id); + sourcepad->event_probe_id = 0; + } - if ((event_probe_handler = - POINTER_TO_ULONG (g_object_get_data (G_OBJECT (pad), - "playbin.event_probe_id")))) { - gst_pad_remove_probe (pad, event_probe_handler); - g_object_set_data (G_OBJECT (pad), "playbin.event_probe_id", NULL); + /* Remove from list of controlled pads and check again for EOS status */ + group->source_pads = g_list_remove (group->source_pads, sourcepad); + g_slice_free (SourcePad, sourcepad); + + /* Update present stream types */ + for (tmp = group->source_pads; tmp; tmp = tmp->next) { + SourcePad *cand = (SourcePad *) tmp->data; + alltype |= cand->stream_type; } + group->present_stream_types = alltype; +} + +/* this function is called when a new pad is added to decodebin. We check the + * type of the pad and add it to the combiner element + */ +static void +pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstSourceGroup * group) +{ + GstSourceCombine *combine = NULL; + gint pb_stream_type = -1; + gchar *pad_name; + GstPlayBin3 *playbin = group->playbin; + + GST_PLAY_BIN3_SHUTDOWN_LOCK (playbin, shutdown); pad_name = gst_object_get_name (GST_OBJECT (pad)); + GST_DEBUG_OBJECT (playbin, "decoded pad %s:%s added", + GST_DEBUG_PAD_NAME (pad)); + + /* major type of the pad, this determines the combiner to use, + try exact match first */ if (g_str_has_prefix (pad_name, "video")) { - stream_type = GST_STREAM_TYPE_VIDEO; + pb_stream_type = PLAYBIN_STREAM_VIDEO; } else if (g_str_has_prefix (pad_name, "audio")) { - stream_type = GST_STREAM_TYPE_AUDIO; + pb_stream_type = PLAYBIN_STREAM_AUDIO; } else if (g_str_has_prefix (pad_name, "text")) { - stream_type = GST_STREAM_TYPE_TEXT; + pb_stream_type = PLAYBIN_STREAM_TEXT; } g_free (pad_name); - if ((combine = g_object_get_data (G_OBJECT (pad), "playbin.combine"))) { - g_assert (combine->combiner == NULL); - g_assert (combine->srcpad == pad); - source_combine_remove_pads (playbin, combine); - playbin->active_stream_types &= ~stream_type; - goto exit; + /* no stream type found for the media type, don't bother linking it to a + * combiner. This will leave the pad unlinked and thus ignored. */ + if (pb_stream_type < 0) { + GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin); + goto unknown_type; } - /* get the combiner sinkpad */ - if (!(peer = g_object_get_data (G_OBJECT (pad), "playbin.sinkpad"))) - goto not_linked; - - /* unlink the pad now (can fail, the pad is unlinked before it's removed) */ - gst_pad_unlink (pad, peer); - - /* get combiner */ - combiner = GST_ELEMENT_CAST (gst_pad_get_parent (peer)); - g_assert (combiner != NULL); - - if ((combine = g_object_get_data (G_OBJECT (peer), "playbin.combine"))) { - /* remove the pad from the array */ - g_ptr_array_remove (combine->channels, peer); - GST_DEBUG_OBJECT (playbin, "pad %p removed from array", peer); - - if (!combine->channels->len && combine->combiner) { - GST_DEBUG_OBJECT (playbin, "all combiner sinkpads removed"); - GST_DEBUG_OBJECT (playbin, "removing combiner %p", combine->combiner); - source_combine_remove_pads (playbin, combine); - gst_element_set_state (combine->combiner, GST_STATE_NULL); - gst_bin_remove (GST_BIN_CAST (playbin), combine->combiner); - combine->combiner = NULL; - playbin->active_stream_types &= ~stream_type; - } - } + combine = &playbin->combiner[pb_stream_type]; - /* release the pad to the combiner, this will make the combiner choose a new - * pad. */ - gst_element_release_request_pad (combiner, peer); - gst_object_unref (peer); + combiner_control_pad (playbin, combine, pad); - gst_object_unref (combiner); -exit: - GST_PLAY_BIN3_UNLOCK (playbin); + control_source_pad (group, pad, combine->stream_type); - if ((playbin->selected_stream_types & ~playbin->active_stream_types & - (GST_STREAM_TYPE_VIDEO | GST_STREAM_TYPE_AUDIO)) - == 0) { - no_more_pads_cb (decodebin, playbin); + /* Update present stream_types and check whether we should post a pending about-to-finish */ + group->present_stream_types |= combine->stream_type; + + if (group->playing && group->pending_about_to_finish + && group->present_stream_types == group->selected_stream_types) { + group->pending_about_to_finish = FALSE; + emit_about_to_finish (playbin); } + GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin); + return; /* ERRORS */ -not_linked: +unknown_type: + GST_DEBUG_OBJECT (playbin, "Ignoring pad with unknown type"); + return; + +shutdown: { - GST_DEBUG_OBJECT (playbin, "pad not linked"); - goto exit; + GST_DEBUG ("ignoring, we are shutting down. Pad will be left unlinked"); + /* not going to done as we didn't request the caps */ + return; } } +/* called when a pad is removed from the decodebin. We unlink the pad from + * the combiner. */ +static void +pad_removed_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group) +{ + GstSourceCombine *combine; + GstPlayBin3 *playbin = group->playbin; + + GST_DEBUG_OBJECT (playbin, + "decoded pad %s:%s removed", GST_DEBUG_PAD_NAME (pad)); + + GST_PLAY_BIN3_LOCK (playbin); + + /* Get combiner for pad */ + if (g_str_has_prefix (GST_PAD_NAME (pad), "video")) + combine = &playbin->combiner[PLAYBIN_STREAM_VIDEO]; + else if (g_str_has_prefix (GST_PAD_NAME (pad), "audio")) + combine = &playbin->combiner[PLAYBIN_STREAM_AUDIO]; + else if (g_str_has_prefix (GST_PAD_NAME (pad), "text")) + combine = &playbin->combiner[PLAYBIN_STREAM_TEXT]; + else + return; + + combiner_release_pad (playbin, combine, pad); + release_source_pad (playbin, group, pad); + + GST_PLAY_BIN3_UNLOCK (playbin); +} + static gint select_stream_cb (GstElement * decodebin, GstStreamCollection * collection, - GstStream * stream, GstPlayBin3 * playbin) + GstStream * stream, GstSourceGroup * group) { GstStreamType stype = gst_stream_get_stream_type (stream); GstElement *combiner = NULL; + GstPlayBin3 *playbin = group->playbin; if (stype & GST_STREAM_TYPE_AUDIO) combiner = playbin->audio_stream_combiner; @@ -3063,190 +3118,134 @@ select_stream_cb (GstElement * decodebin, GstStreamCollection * collection, return -1; } -/* we get called when all pads are available and we must connect the sinks to - * them. - * The main purpose of the code is to see if we have video/audio and subtitles - * and pick the right pipelines to display them. - * - * The combiners installed on the group tell us about the presence of - * audio/video and subtitle streams. This allows us to see if we need - * visualisation, video or/and audio. +/* We get called when the selected stream types change and + * reconfiguration of output (i.e. playsink and potential combiners) + * are required. */ static void -no_more_pads_cb (GstElement * decodebin, GstPlayBin3 * playbin) +reconfigure_output (GstPlayBin3 * playbin) { - GstSourceGroup *group; GstPadLinkReturn res; gint i; - gboolean configure; - GST_DEBUG_OBJECT (playbin, "no more pads"); + g_assert (playbin->selected_stream_types != playbin->active_stream_types); - GST_PLAY_BIN3_SHUTDOWN_LOCK (playbin, shutdown); + GST_DEBUG_OBJECT (playbin, "selected_stream_types : %" STREAM_TYPES_FORMAT, + STREAM_TYPES_ARGS (playbin->selected_stream_types)); + GST_DEBUG_OBJECT (playbin, "active_stream_types : %" STREAM_TYPES_FORMAT, + STREAM_TYPES_ARGS (playbin->active_stream_types)); GST_PLAY_BIN3_LOCK (playbin); - group = playbin->curr_group; + /* Make sure combiners/playsink are in sync with selected stream types */ for (i = 0; i < PLAYBIN_STREAM_LAST; i++) { GstSourceCombine *combine = &playbin->combiner[i]; - - /* check if the specific media type was detected and thus has a combiner - * created for it. If there is the media type, get a sinkpad from the sink - * and link it. We only do this if we have not yet requested the sinkpad - * before. */ - if (combine->srcpad && combine->sinkpad == NULL) { - GST_DEBUG_OBJECT (playbin, "requesting new sink pad %d", combine->type); - combine->sinkpad = - gst_play_sink_request_pad (playbin->playsink, combine->type); - gst_object_ref (combine->sinkpad); - } else if (combine->srcpad && combine->sinkpad) { - GST_DEBUG_OBJECT (playbin, "re-using sink pad %d", combine->type); - } else if (combine->sinkpad && combine->srcpad == NULL) { - GST_DEBUG_OBJECT (playbin, "releasing sink pad %d", combine->type); - gst_play_sink_release_pad (playbin->playsink, combine->sinkpad); - gst_object_unref (combine->sinkpad); - combine->sinkpad = NULL; - } - if (combine->sinkpad && combine->srcpad && - !gst_pad_is_linked (combine->srcpad)) { - res = gst_pad_link (combine->srcpad, combine->sinkpad); - GST_DEBUG_OBJECT (playbin, "linked type %s, result: %d", - combine->media_type, res); - if (res != GST_PAD_LINK_OK) { - GST_ELEMENT_ERROR (playbin, CORE, PAD, - ("Internal playbin error."), - ("Failed to link combiner to sink. Error %d", res)); - } - } - } - GST_PLAY_BIN3_UNLOCK (playbin); - - GST_SOURCE_GROUP_LOCK (group); - GST_DEBUG_OBJECT (playbin, "pending %d > %d", group->pending, - group->pending - 1); - - if (group->pending > 0) - group->pending--; - - if (group->pending == 0) { - /* we are the last group to complete, we will configure the output and then - * signal the other waiters. */ - GST_LOG_OBJECT (playbin, "last group complete"); - configure = TRUE; - } else { - GST_LOG_OBJECT (playbin, "have more pending groups"); - configure = FALSE; - } - GST_SOURCE_GROUP_UNLOCK (group); - - if (configure) { - /* if we have custom sinks, configure them now */ - GST_SOURCE_GROUP_LOCK (group); - - if (group->audio_sink) { - GST_INFO_OBJECT (playbin, "setting custom audio sink %" GST_PTR_FORMAT, - group->audio_sink); - gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_AUDIO, - group->audio_sink); - } - - if (group->video_sink) { - GST_INFO_OBJECT (playbin, "setting custom video sink %" GST_PTR_FORMAT, - group->video_sink); - gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_VIDEO, - group->video_sink); - } - - if (group->text_sink) { - GST_INFO_OBJECT (playbin, "setting custom text sink %" GST_PTR_FORMAT, - group->text_sink); - gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_TEXT, - group->text_sink); - } - - GST_SOURCE_GROUP_UNLOCK (group); - - /* signal the other combiners that they can continue now. */ - GST_PLAY_BIN3_LOCK (playbin); - /* unblock all combiners */ - for (i = 0; i < PLAYBIN_STREAM_LAST; i++) { - GstSourceCombine *combine = &playbin->combiner[i]; - + gboolean is_selected = + (combine->stream_type & playbin->selected_stream_types) == + combine->stream_type; + gboolean is_active = + (combine->stream_type & playbin->active_stream_types) == + combine->stream_type; + + GST_DEBUG_OBJECT (playbin, "Stream type status: '%s' %s %s", + combine->media_type, is_selected ? "selected" : "NOT selected", + is_active ? "active" : "NOT active"); + /* FIXME : Remove asserts below once enough testing has been done */ + + if (is_selected && is_active) { + GST_DEBUG_OBJECT (playbin, "Stream type '%s' already active", + combine->media_type); + } else if (is_active && !is_selected) { + GST_DEBUG_OBJECT (playbin, "Stream type '%s' is no longer requested", + combine->media_type); + + /* Unlink combiner from sink */ if (combine->srcpad) { - GST_DEBUG_OBJECT (playbin, "unblocking %" GST_PTR_FORMAT, - combine->srcpad); - if (combine->block_id) { - gst_pad_remove_probe (combine->srcpad, combine->block_id); - combine->block_id = 0; - } + GST_LOG_OBJECT (playbin, "Unlinking from sink"); + if (combine->sinkpad) + gst_pad_unlink (combine->srcpad, combine->sinkpad); + gst_object_unref (combine->srcpad); + combine->srcpad = NULL; } - } - GST_PLAY_BIN3_UNLOCK (playbin); - gst_play_sink_reconfigure (playbin->playsink); - } - GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin); + if (combine->sinkpad) { + /* Release playsink sink pad */ + GST_LOG_OBJECT (playbin, "Releasing playsink pad"); + gst_play_sink_release_pad (playbin->playsink, combine->sinkpad); + gst_object_unref (combine->sinkpad); + combine->sinkpad = NULL; + } - if (configure) { - do_async_done (playbin); - } + /* Release combiner */ + GST_FIXME_OBJECT (playbin, "Release combiner"); + remove_combiner (playbin, combine); + } else if (!is_active && is_selected) { + GST_DEBUG_OBJECT (playbin, "Stream type '%s' is now requested", + combine->media_type); - return; + /* If we are shutting down, do *not* add more combiners */ + if (g_atomic_int_get (&playbin->shutdown)) + continue; -shutdown: - { - GST_DEBUG ("ignoring, we are shutting down"); - /* Request a flushing pad from playsink that we then link to the combiner. - * Then we unblock the combiners so that they stop with a WRONG_STATE - * instead of a NOT_LINKED error. - */ - GST_PLAY_BIN3_LOCK (playbin); - for (i = 0; i < PLAYBIN_STREAM_LAST; i++) { - GstSourceCombine *combine = &playbin->combiner[i]; + g_assert (combine->sinkpad == NULL); - if (combine->srcpad) { - if (combine->sinkpad == NULL) { - GST_DEBUG_OBJECT (playbin, "requesting new flushing sink pad"); - combine->sinkpad = - gst_play_sink_request_pad (playbin->playsink, - GST_PLAY_SINK_TYPE_FLUSHING); - gst_object_ref (combine->sinkpad); - res = gst_pad_link (combine->srcpad, combine->sinkpad); - GST_DEBUG_OBJECT (playbin, "linked flushing, result: %d", res); - } - GST_DEBUG_OBJECT (playbin, "unblocking %" GST_PTR_FORMAT, - combine->srcpad); - if (combine->block_id) { - gst_pad_remove_probe (combine->srcpad, combine->block_id); - combine->block_id = 0; + /* Request playsink sink pad */ + combine->sinkpad = + gst_play_sink_request_pad (playbin->playsink, combine->type); + gst_object_ref (combine->sinkpad); + /* Create combiner if needed and link it */ + create_combiner (playbin, combine); + if (combine->combiner) { + res = gst_pad_link (combine->srcpad, combine->sinkpad); + GST_DEBUG_OBJECT (playbin, "linked type %s, result: %d", + combine->media_type, res); + if (res != GST_PAD_LINK_OK) { + GST_ELEMENT_ERROR (playbin, CORE, PAD, + ("Internal playbin error."), + ("Failed to link combiner to sink. Error %d", res)); } + } } - GST_PLAY_BIN3_UNLOCK (playbin); - return; } + + playbin->active_stream_types = playbin->selected_stream_types; + + GST_PLAY_BIN3_UNLOCK (playbin); + + gst_play_sink_reconfigure (playbin->playsink); + + do_async_done (playbin); + + GST_DEBUG_OBJECT (playbin, "selected_stream_types : %" STREAM_TYPES_FORMAT, + STREAM_TYPES_ARGS (playbin->selected_stream_types)); + GST_DEBUG_OBJECT (playbin, "active_stream_types : %" STREAM_TYPES_FORMAT, + STREAM_TYPES_ARGS (playbin->active_stream_types)); + + return; } -#if 0 static void -drained_cb (GstElement * decodebin, GstSourceGroup * group) +about_to_finish_cb (GstElement * uridecodebin, GstSourceGroup * group) { - GstPlayBin3 *playbin; - - playbin = group->playbin; - + GstPlayBin3 *playbin = group->playbin; GST_DEBUG_OBJECT (playbin, "about to finish in group %p", group); - /* after this call, we should have a next group to activate or we EOS */ - g_signal_emit (G_OBJECT (playbin), - gst_play_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); + GST_LOG_OBJECT (playbin, "selected_stream_types:%" STREAM_TYPES_FORMAT, + STREAM_TYPES_ARGS (group->selected_stream_types)); + GST_LOG_OBJECT (playbin, "present_stream_types:%" STREAM_TYPES_FORMAT, + STREAM_TYPES_ARGS (group->present_stream_types)); - /* now activate the next group. If the app did not set a uri, this will - * fail and we can do EOS */ - setup_next_source (playbin, GST_STATE_PAUSED); + if (group->selected_stream_types == 0 + || (group->selected_stream_types != group->present_stream_types)) { + GST_LOG_OBJECT (playbin, + "Delaying emission of signal until this group is ready"); + group->pending_about_to_finish = TRUE; + } else + emit_about_to_finish (playbin); } -#endif +#if 0 /* AUTOPLUG DISABLED */ /* Like gst_element_factory_can_sink_any_caps() but doesn't * allow ANY caps on the sinkpad template */ static gboolean @@ -3652,64 +3651,7 @@ autoplug_factories_cb (GstElement * decodebin, GstPad * pad, return result; } - -static void -gst_play_bin3_set_context (GstElement * element, GstContext * context) -{ - GstPlayBin3 *playbin = GST_PLAY_BIN3 (element); - - /* Proxy contexts to the sinks, they might not be in playsink yet */ - GST_PLAY_BIN3_LOCK (playbin); - if (playbin->audio_sink) - gst_element_set_context (playbin->audio_sink, context); - if (playbin->video_sink) - gst_element_set_context (playbin->video_sink, context); - if (playbin->text_sink) - gst_element_set_context (playbin->text_sink, context); - - GST_SOURCE_GROUP_LOCK (playbin->curr_group); - - if (playbin->curr_group->audio_sink) - gst_element_set_context (playbin->curr_group->audio_sink, context); - if (playbin->curr_group->video_sink) - gst_element_set_context (playbin->curr_group->video_sink, context); - if (playbin->curr_group->text_sink) - gst_element_set_context (playbin->curr_group->text_sink, context); - - GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); - GST_PLAY_BIN3_UNLOCK (playbin); - - GST_ELEMENT_CLASS (parent_class)->set_context (element, context); -} - -/* Pass sink messages to the application, e.g. NEED_CONTEXT messages */ -static void -gst_play_bin3_update_context (GstPlayBin3 * playbin, GstContext * context) -{ - GList *l; - const gchar *context_type; - - GST_OBJECT_LOCK (playbin); - context_type = gst_context_get_context_type (context); - for (l = playbin->contexts; l; l = l->next) { - GstContext *tmp = l->data; - const gchar *tmp_type = gst_context_get_context_type (tmp); - - /* Always store newest context but never replace - * a persistent one by a non-persistent one */ - if (strcmp (context_type, tmp_type) == 0 && - (gst_context_is_persistent (context) || - !gst_context_is_persistent (tmp))) { - gst_context_replace ((GstContext **) & l->data, context); - break; - } - } - /* Not found? Add */ - if (l == NULL) - playbin->contexts = - g_list_prepend (playbin->contexts, gst_context_ref (context)); - GST_OBJECT_UNLOCK (playbin); -} +#endif static GstBusSyncReply activate_sink_bus_handler (GstBus * bus, GstMessage * msg, @@ -3732,37 +3674,6 @@ activate_sink_bus_handler (GstBus * bus, GstMessage * msg, gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); else gst_message_unref (msg); - } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_NEED_CONTEXT) { - const gchar *context_type; - GList *l; - - gst_message_parse_context_type (msg, &context_type); - GST_OBJECT_LOCK (playbin); - for (l = playbin->contexts; l; l = l->next) { - GstContext *tmp = l->data; - const gchar *tmp_type = gst_context_get_context_type (tmp); - - if (strcmp (context_type, tmp_type) == 0) { - gst_element_set_context (GST_ELEMENT (GST_MESSAGE_SRC (msg)), l->data); - break; - } - } - GST_OBJECT_UNLOCK (playbin); - - /* Forward if we couldn't answer the message */ - if (l == NULL) { - gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); - } else { - gst_message_unref (msg); - } - } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_HAVE_CONTEXT) { - GstContext *context; - - gst_message_parse_have_context (msg, &context); - gst_play_bin3_update_context (playbin, context); - gst_context_unref (context); - - gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); } else { gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); } @@ -3814,6 +3725,7 @@ done: return ret; } +#if 0 /* AUTOPLUG DISABLED */ /* autoplug-continue decides, if a pad has raw caps that can be exposed * directly or if further decoding is necessary. We use this to expose * supported subtitles directly */ @@ -4437,29 +4349,7 @@ autoplug_query_cb (GstElement * uridecodebin, GstPad * pad, return FALSE; } } - -static void -notify_source_cb (GstElement * urisourcebin, GParamSpec * pspec, - GstSourceGroup * group) -{ - GstPlayBin3 *playbin; - GstElement *source; - - playbin = group->playbin; - - g_object_get (urisourcebin, "source", &source, NULL); - - GST_OBJECT_LOCK (playbin); - if (playbin->source) - gst_object_unref (playbin->source); - playbin->source = source; - GST_OBJECT_UNLOCK (playbin); - - g_object_notify (G_OBJECT (playbin), "source"); - - g_signal_emit (playbin, gst_play_bin3_signals[SIGNAL_SOURCE_SETUP], - 0, playbin->source); -} +#endif /* must be called with the group lock */ static gboolean @@ -4468,10 +4358,8 @@ group_set_locked_state_unlocked (GstPlayBin3 * playbin, GstSourceGroup * group, { GST_DEBUG_OBJECT (playbin, "locked_state %d on group %p", locked, group); - if (group->urisourcebin) - gst_element_set_locked_state (group->urisourcebin, locked); - if (group->suburisourcebin) - gst_element_set_locked_state (group->suburisourcebin, locked); + if (group->uridecodebin) + gst_element_set_locked_state (group->uridecodebin, locked); return TRUE; } @@ -4501,160 +4389,20 @@ make_or_reuse_element (GstPlayBin3 * playbin, const gchar * name, return TRUE; } -static void -urisrc_pad_added (GstElement * urisrc, GstPad * pad, GstSourceGroup * group) -{ - GstPadLinkReturn res; - GstPad *sinkpad = NULL; - GstPlayBin3 *playbin; - - GST_SOURCE_GROUP_LOCK (group); - playbin = group->playbin; - if (urisrc == group->urisourcebin) { - /* Primary stream, link to the main pad of decodebin3 */ - sinkpad = gst_element_get_static_pad (playbin->decodebin, "sink"); - if (gst_pad_is_linked (sinkpad)) { - gst_object_unref (GST_OBJECT (sinkpad)); - sinkpad = NULL; - } - } - GST_SOURCE_GROUP_UNLOCK (group); - - if (sinkpad == NULL) { - /* Auxiliary stream, request a new pad from decodebin */ - if ((sinkpad = gst_element_get_request_pad (playbin->decodebin, "sink_%u"))) { - g_object_set_data (G_OBJECT (pad), "playbin.sinkpad", sinkpad); - } - } - if (sinkpad) { - GST_DEBUG_OBJECT (playbin, "New pad %" GST_PTR_FORMAT - " from urisourcebin %" GST_PTR_FORMAT " linking to %" - GST_PTR_FORMAT, pad, urisrc, sinkpad); - - res = gst_pad_link (pad, sinkpad); - gst_object_unref (sinkpad); - - if (GST_PAD_LINK_FAILED (res)) - goto link_failed; - } - return; - -link_failed: - { - GST_ERROR_OBJECT (playbin, - "failed to link pad %s:%s to decodebin, reason %s (%d)", - GST_DEBUG_PAD_NAME (pad), gst_pad_link_get_name (res), res); - return; - } -} static void -urisrc_pad_removed_cb (GstElement * urisrc, GstPad * pad, +source_setup_cb (GstElement * element, GstElement * source, GstSourceGroup * group) { + g_signal_emit (group->playbin, gst_play_bin3_signals[SIGNAL_SOURCE_SETUP], 0, + source); } /* must be called with PLAY_BIN_LOCK */ static GstStateChangeReturn -activate_decodebin (GstPlayBin3 * playbin, GstState target) -{ - GstStateChangeReturn state_ret; - GstElement *decodebin = NULL; - - if (playbin->decodebin_active) - return GST_STATE_CHANGE_SUCCESS; - - GST_LOG_OBJECT (playbin, "Adding and activating decodebin"); - - if (!make_or_reuse_element (playbin, "decodebin3", &playbin->decodebin)) - goto no_decodebin; - decodebin = playbin->decodebin; - - /* connect pads and other things */ - playbin->db_pad_added_id = g_signal_connect (decodebin, "pad-added", - G_CALLBACK (pad_added_cb), playbin); - playbin->db_pad_removed_id = g_signal_connect (decodebin, "pad-removed", - G_CALLBACK (pad_removed_cb), playbin); - playbin->db_no_more_pads_id = g_signal_connect (decodebin, "no-more-pads", - G_CALLBACK (no_more_pads_cb), playbin); - playbin->db_select_stream_id = g_signal_connect (decodebin, "select-stream", - G_CALLBACK (select_stream_cb), playbin); - /* is called when the decodebin is out of data and we can switch to the - * next uri */ -#if 0 - /* FIXME: Re-enable if/when decodebin3 supports 'drained' */ - playbin->db_drained_id = - g_signal_connect (decodebin, "drained", G_CALLBACK (drained_cb), playbin); -#endif - - gst_element_set_locked_state (decodebin, TRUE); - if ((state_ret = - gst_element_set_state (decodebin, - target)) == GST_STATE_CHANGE_FAILURE) - goto decodebin_failure; - gst_element_set_locked_state (decodebin, FALSE); - - playbin->decodebin_active = TRUE; - - return state_ret; - - -no_decodebin: - { - GstMessage *msg; - - msg = - gst_missing_element_message_new (GST_ELEMENT_CAST (playbin), - "decodebin3"); - gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); - - GST_ELEMENT_ERROR (playbin, CORE, MISSING_PLUGIN, - (_("Could not create \"decodebin3\" element.")), (NULL)); - - goto error_cleanup; - } -decodebin_failure: - { - GST_DEBUG_OBJECT (playbin, "failed state change of decodebin"); - goto error_cleanup; - } -error_cleanup:{ - if (decodebin) { - REMOVE_SIGNAL (playbin->decodebin, playbin->db_pad_added_id); - REMOVE_SIGNAL (playbin->decodebin, playbin->db_pad_removed_id); - REMOVE_SIGNAL (playbin->decodebin, playbin->db_no_more_pads_id); - REMOVE_SIGNAL (playbin->decodebin, playbin->db_drained_id); - REMOVE_SIGNAL (playbin->decodebin, playbin->db_select_stream_id); - gst_element_set_state (decodebin, GST_STATE_NULL); - gst_bin_remove (GST_BIN_CAST (playbin), decodebin); - } - return GST_STATE_CHANGE_FAILURE; - } -} - -/* must be called with PLAY_BIN_LOCK */ -static void -deactivate_decodebin (GstPlayBin3 * playbin) -{ - if (playbin->decodebin) { - GST_LOG_OBJECT (playbin, "Deactivating and removing decodebin"); - REMOVE_SIGNAL (playbin->decodebin, playbin->db_pad_added_id); - REMOVE_SIGNAL (playbin->decodebin, playbin->db_pad_removed_id); - REMOVE_SIGNAL (playbin->decodebin, playbin->db_no_more_pads_id); - REMOVE_SIGNAL (playbin->decodebin, playbin->db_drained_id); - REMOVE_SIGNAL (playbin->decodebin, playbin->db_select_stream_id); - gst_bin_remove (GST_BIN_CAST (playbin), playbin->decodebin); - playbin->decodebin_active = FALSE; - playbin->active_stream_types = 0; - } -} - -/* must be called with PLAY_BIN_LOCK */ -static GstStateChangeReturn -activate_group (GstPlayBin3 * playbin, GstSourceGroup * group, GstState target) +activate_group (GstPlayBin3 * playbin, GstSourceGroup * group) { - GstElement *urisrcbin = NULL; - GstElement *suburisrcbin = NULL; + GstElement *uridecodebin = NULL; GstPlayFlags flags; gboolean audio_sink_activated = FALSE; gboolean video_sink_activated = FALSE; @@ -4721,13 +4469,13 @@ activate_group (GstPlayBin3 * playbin, GstSourceGroup * group, GstState target) } - if (!make_or_reuse_element (playbin, "urisourcebin", &group->urisourcebin)) - goto no_urisrcbin; - urisrcbin = group->urisourcebin; + if (!make_or_reuse_element (playbin, "uridecodebin3", &group->uridecodebin)) + goto no_uridecodebin; + uridecodebin = group->uridecodebin; flags = gst_play_sink_get_flags (playbin->playsink); - g_object_set (urisrcbin, + g_object_set (uridecodebin, /* configure connection speed */ "connection-speed", playbin->connection_speed / 1000, /* configure uri */ @@ -4741,99 +4489,30 @@ activate_group (GstPlayBin3 * playbin, GstSourceGroup * group, GstState target) "buffer-size", playbin->buffer_size, "ring-buffer-max-size", playbin->ring_buffer_max_size, NULL); - /* we have 1 pending no-more-pads */ - group->pending = 1; - - group->notify_source_id = g_signal_connect (urisrcbin, "notify::source", - G_CALLBACK (notify_source_cb), group); - - /* will be called when a new media type is found. We return a list of decoders - * including sinks for decodebin to try */ - group->autoplug_factories_id = - g_signal_connect (urisrcbin, "autoplug-factories", - G_CALLBACK (autoplug_factories_cb), group); - group->autoplug_select_id = - g_signal_connect (urisrcbin, "autoplug-select", - G_CALLBACK (autoplug_select_cb), group); - group->autoplug_continue_id = - g_signal_connect (urisrcbin, "autoplug-continue", - G_CALLBACK (autoplug_continue_cb), group); - group->autoplug_query_id = - g_signal_connect (urisrcbin, "autoplug-query", - G_CALLBACK (autoplug_query_cb), group); - - group->urisrc_pad_added_id = g_signal_connect (urisrcbin, "pad-added", - G_CALLBACK (urisrc_pad_added), group); - group->urisrc_pad_removed_id = g_signal_connect (urisrcbin, - "pad-removed", G_CALLBACK (urisrc_pad_removed_cb), group); - - if (group->suburi) { - /* subtitles */ - if (!make_or_reuse_element (playbin, "urisourcebin", - &group->suburisourcebin)) - goto no_urisrcbin; - suburisrcbin = group->suburisourcebin; - - g_object_set (suburisrcbin, - /* configure connection speed */ - "connection-speed", playbin->connection_speed, - /* configure uri */ - "uri", group->suburi, NULL); - - /* connect pads and other things */ - group->sub_pad_added_id = g_signal_connect (suburisrcbin, "pad-added", - G_CALLBACK (urisrc_pad_added), group); - group->sub_pad_removed_id = g_signal_connect (suburisrcbin, - "pad-removed", G_CALLBACK (urisrc_pad_removed_cb), group); - - group->sub_autoplug_continue_id = - g_signal_connect (suburisrcbin, "autoplug-continue", - G_CALLBACK (autoplug_continue_cb), group); - - group->sub_autoplug_query_id = - g_signal_connect (suburisrcbin, "autoplug-query", - G_CALLBACK (autoplug_query_cb), group); - - /* we have 2 pending no-more-pads */ - group->pending = 2; - group->sub_pending = TRUE; - } else { - group->sub_pending = FALSE; - } + group->pad_added_id = g_signal_connect (uridecodebin, "pad-added", + G_CALLBACK (pad_added_cb), group); + group->pad_removed_id = g_signal_connect (uridecodebin, + "pad-removed", G_CALLBACK (pad_removed_cb), group); + group->select_stream_id = g_signal_connect (uridecodebin, "select-stream", + G_CALLBACK (select_stream_cb), group); + group->source_setup_id = g_signal_connect (uridecodebin, "source-setup", + G_CALLBACK (source_setup_cb), group); + group->about_to_finish_id = + g_signal_connect (uridecodebin, "about-to-finish", + G_CALLBACK (about_to_finish_cb), group); + + if (group->suburi) + g_object_set (group->uridecodebin, "suburi", group->suburi, NULL); /* release the group lock before setting the state of the source bins, they * might fire signals in this thread that we need to handle with the * group_lock taken. */ GST_SOURCE_GROUP_UNLOCK (group); - if (suburisrcbin) { - if (gst_element_set_state (suburisrcbin, - target) == GST_STATE_CHANGE_FAILURE) { - GST_DEBUG_OBJECT (playbin, - "failed state change of subtitle urisourcebin"); - GST_SOURCE_GROUP_LOCK (group); - - REMOVE_SIGNAL (suburisrcbin, group->sub_pad_added_id); - REMOVE_SIGNAL (suburisrcbin, group->sub_pad_removed_id); - REMOVE_SIGNAL (suburisrcbin, group->sub_autoplug_continue_id); - REMOVE_SIGNAL (suburisrcbin, group->sub_autoplug_query_id); - /* Might already be removed because of an error message */ - if (GST_OBJECT_PARENT (suburisrcbin) == GST_OBJECT_CAST (playbin)) - gst_bin_remove (GST_BIN_CAST (playbin), suburisrcbin); - if (group->sub_pending) { - group->pending--; - group->sub_pending = FALSE; - } - gst_element_set_state (suburisrcbin, GST_STATE_READY); - g_free (group->suburi); - group->suburi = NULL; - GST_SOURCE_GROUP_UNLOCK (group); - } - } if ((state_ret = - gst_element_set_state (urisrcbin, - target)) == GST_STATE_CHANGE_FAILURE) - goto urisrcbin_failure; + gst_element_set_state (uridecodebin, + GST_STATE_PAUSED)) == GST_STATE_CHANGE_FAILURE) + goto uridecodebin_failure; GST_SOURCE_GROUP_LOCK (group); /* allow state changes of the playbin affect the group elements now */ @@ -4844,26 +4523,26 @@ activate_group (GstPlayBin3 * playbin, GstSourceGroup * group, GstState target) return state_ret; /* ERRORS */ -no_urisrcbin: +no_uridecodebin: { GstMessage *msg; GST_SOURCE_GROUP_UNLOCK (group); msg = gst_missing_element_message_new (GST_ELEMENT_CAST (playbin), - "urisourcebin"); + "uridecodebin3"); gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); GST_ELEMENT_ERROR (playbin, CORE, MISSING_PLUGIN, - (_("Could not create \"urisourcebin\" element.")), (NULL)); + (_("Could not create \"uridecodebin3\" element.")), (NULL)); GST_SOURCE_GROUP_LOCK (group); goto error_cleanup; } -urisrcbin_failure: +uridecodebin_failure: { - GST_DEBUG_OBJECT (playbin, "failed state change of urisrcbin"); + GST_DEBUG_OBJECT (playbin, "failed state change of uridecodebin"); GST_SOURCE_GROUP_LOCK (group); goto error_cleanup; } @@ -4875,6 +4554,8 @@ sink_failure: error_cleanup: { + group->selected_stream_types = 0; + /* delete any custom sinks we might have */ if (group->audio_sink) { /* If this is a automatically created sink set it to NULL */ @@ -4900,17 +4581,21 @@ error_cleanup: } group->text_sink = NULL; - if (urisrcbin) { - REMOVE_SIGNAL (group->urisourcebin, group->urisrc_pad_added_id); - REMOVE_SIGNAL (group->urisourcebin, group->urisrc_pad_removed_id); - REMOVE_SIGNAL (group->urisourcebin, group->notify_source_id); + if (uridecodebin) { + REMOVE_SIGNAL (group->uridecodebin, group->pad_added_id); + REMOVE_SIGNAL (group->uridecodebin, group->pad_removed_id); + REMOVE_SIGNAL (group->uridecodebin, group->select_stream_id); + REMOVE_SIGNAL (group->uridecodebin, group->source_setup_id); + REMOVE_SIGNAL (group->uridecodebin, group->about_to_finish_id); +#if 0 REMOVE_SIGNAL (group->urisourcebin, group->autoplug_factories_id); REMOVE_SIGNAL (group->urisourcebin, group->autoplug_select_id); REMOVE_SIGNAL (group->urisourcebin, group->autoplug_continue_id); REMOVE_SIGNAL (group->urisourcebin, group->autoplug_query_id); +#endif - gst_element_set_state (urisrcbin, GST_STATE_NULL); - gst_bin_remove (GST_BIN_CAST (playbin), urisrcbin); + gst_element_set_state (uridecodebin, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (playbin), uridecodebin); } GST_SOURCE_GROUP_UNLOCK (group); @@ -4919,13 +4604,10 @@ error_cleanup: } } -/* unlink a group of urisrcbin from the decodebin. - * must be called with PLAY_BIN_LOCK */ +/* must be called with PLAY_BIN_LOCK */ static gboolean deactivate_group (GstPlayBin3 * playbin, GstSourceGroup * group) { - gint i; - g_return_val_if_fail (group->active, FALSE); g_return_val_if_fail (group->valid, FALSE); @@ -4933,32 +4615,17 @@ deactivate_group (GstPlayBin3 * playbin, GstSourceGroup * group) GST_SOURCE_GROUP_LOCK (group); group->active = FALSE; - for (i = 0; i < PLAYBIN_STREAM_LAST; i++) { - GstSourceCombine *combine = &playbin->combiner[i]; - - GST_DEBUG_OBJECT (playbin, "unlinking combiner %s", combine->media_type); + group->playing = FALSE; + group->group_id = GST_GROUP_ID_INVALID; - if (combine->srcpad) { - source_combine_remove_pads (playbin, combine); - } - - if (combine->combiner) { - gint n; - - /* release and unref requests pad from the combiner */ - for (n = 0; n < combine->channels->len; n++) { - GstPad *sinkpad = g_ptr_array_index (combine->channels, n); - - gst_element_release_request_pad (combine->combiner, sinkpad); - gst_object_unref (sinkpad); - } - g_ptr_array_set_size (combine->channels, 0); + group->selected_stream_types = 0; + /* Update global selected_stream_types */ + playbin->selected_stream_types = + playbin->groups[0].selected_stream_types | playbin-> + groups[1].selected_stream_types; + if (playbin->active_stream_types != playbin->selected_stream_types) + reconfigure_output (playbin); - gst_element_set_state (combine->combiner, GST_STATE_NULL); - gst_bin_remove (GST_BIN_CAST (playbin), combine->combiner); - combine->combiner = NULL; - } - } #if 0 /* delete any custom sinks we might have. * conditionally set them to null if they aren't inside playsink yet */ @@ -4988,30 +4655,28 @@ deactivate_group (GstPlayBin3 * playbin, GstSourceGroup * group) group->text_sink = NULL; #endif - if (group->urisourcebin) { - REMOVE_SIGNAL (group->urisourcebin, group->urisrc_pad_added_id); - REMOVE_SIGNAL (group->urisourcebin, group->urisrc_pad_removed_id); - REMOVE_SIGNAL (group->urisourcebin, group->notify_source_id); + if (group->uridecodebin) { + REMOVE_SIGNAL (group->uridecodebin, group->select_stream_id); + REMOVE_SIGNAL (group->uridecodebin, group->source_setup_id); + REMOVE_SIGNAL (group->uridecodebin, group->about_to_finish_id); + + gst_element_set_state (group->uridecodebin, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (playbin), group->uridecodebin); + + REMOVE_SIGNAL (group->uridecodebin, group->pad_added_id); + REMOVE_SIGNAL (group->uridecodebin, group->pad_removed_id); +#if 0 REMOVE_SIGNAL (group->urisourcebin, group->autoplug_factories_id); REMOVE_SIGNAL (group->urisourcebin, group->autoplug_select_id); REMOVE_SIGNAL (group->urisourcebin, group->autoplug_continue_id); REMOVE_SIGNAL (group->urisourcebin, group->autoplug_query_id); - gst_bin_remove (GST_BIN_CAST (playbin), group->urisourcebin); - } - - if (group->suburisourcebin) { - REMOVE_SIGNAL (group->suburisourcebin, group->sub_pad_added_id); - REMOVE_SIGNAL (group->suburisourcebin, group->sub_pad_removed_id); - REMOVE_SIGNAL (group->suburisourcebin, group->sub_autoplug_continue_id); - REMOVE_SIGNAL (group->suburisourcebin, group->sub_autoplug_query_id); - - /* Might already be removed because of errors */ - if (GST_OBJECT_PARENT (group->suburisourcebin) == GST_OBJECT_CAST (playbin)) - gst_bin_remove (GST_BIN_CAST (playbin), group->suburisourcebin); +#endif } GST_SOURCE_GROUP_UNLOCK (group); + GST_DEBUG_OBJECT (playbin, "Done"); + return TRUE; } @@ -5019,55 +4684,36 @@ deactivate_group (GstPlayBin3 * playbin, GstSourceGroup * group) * configured. It swaps out the current_group and activates the valid * next_group. */ static GstStateChangeReturn -setup_next_source (GstPlayBin3 * playbin, GstState target) +setup_next_source (GstPlayBin3 * playbin) { - GstSourceGroup *new_group, *old_group; + GstSourceGroup *new_group; GstStateChangeReturn state_ret; - GST_DEBUG_OBJECT (playbin, "setup sources"); + GST_DEBUG_OBJECT (playbin, "setup next source"); + + debug_groups (playbin); /* see if there is a next group */ GST_PLAY_BIN3_LOCK (playbin); new_group = playbin->next_group; - if (!new_group || !new_group->valid) + if (!new_group || !new_group->valid || new_group->active) goto no_next_group; - /* first unlink the current source, if any */ - old_group = playbin->curr_group; - if (old_group && old_group->valid && old_group->active) { - new_group->stream_changed_pending = TRUE; - - gst_play_bin3_update_cached_duration (playbin); - /* unlink our pads with the sink */ - deactivate_group (playbin, old_group); - old_group->valid = FALSE; - } - - /* swap old and new */ - playbin->curr_group = new_group; - playbin->next_group = old_group; - - /* Get decodebin ready now */ - if ((state_ret = - activate_decodebin (playbin, target)) == GST_STATE_CHANGE_FAILURE) - goto activate_failed; - /* activate the new group */ - if ((state_ret = - activate_group (playbin, new_group, - target)) == GST_STATE_CHANGE_FAILURE) + state_ret = activate_group (playbin, new_group); + if (state_ret == GST_STATE_CHANGE_FAILURE) goto activate_failed; GST_PLAY_BIN3_UNLOCK (playbin); + debug_groups (playbin); + return state_ret; /* ERRORS */ no_next_group: { GST_DEBUG_OBJECT (playbin, "no next group"); - if (target == GST_STATE_READY && new_group && new_group->uri == NULL) - GST_ELEMENT_ERROR (playbin, RESOURCE, NOT_FOUND, ("No URI set"), (NULL)); GST_PLAY_BIN3_UNLOCK (playbin); return GST_STATE_CHANGE_FAILURE; } @@ -5126,6 +4772,117 @@ groups_set_locked_state (GstPlayBin3 * playbin, gboolean locked) return TRUE; } +static void +gst_play_bin3_check_group_status (GstPlayBin3 * playbin) +{ + if (playbin->activation_task) + gst_task_start (playbin->activation_task); +} + +static void +gst_play_bin3_activation_thread (GstPlayBin3 * playbin) +{ + GST_DEBUG_OBJECT (playbin, "starting"); + + debug_groups (playbin); + + /* Check if next_group needs to be deactivated */ + GST_PLAY_BIN3_LOCK (playbin); + if (playbin->next_group->active) { + deactivate_group (playbin, playbin->next_group); + playbin->next_group->valid = FALSE; + } + + /* Is there a pending about-to-finish to be emitted ? */ + GST_SOURCE_GROUP_LOCK (playbin->curr_group); + if (playbin->curr_group->pending_about_to_finish) { + GST_LOG_OBJECT (playbin, "Propagating about-to-finish"); + playbin->curr_group->pending_about_to_finish = FALSE; + GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); + /* This will activate the next source afterwards */ + emit_about_to_finish (playbin); + } else + GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); + + GST_LOG_OBJECT (playbin, "Pausing task"); + if (playbin->activation_task) + gst_task_pause (playbin->activation_task); + GST_PLAY_BIN3_UNLOCK (playbin); + + GST_DEBUG_OBJECT (playbin, "done"); + return; +} + +static gboolean +gst_play_bin3_start (GstPlayBin3 * playbin) +{ + GST_DEBUG_OBJECT (playbin, "starting"); + + GST_PLAY_BIN3_LOCK (playbin); + + if (playbin->activation_task == NULL) { + playbin->activation_task = + gst_task_new ((GstTaskFunction) gst_play_bin3_activation_thread, + playbin, NULL); + if (playbin->activation_task == NULL) + goto task_error; + gst_task_set_lock (playbin->activation_task, &playbin->activation_lock); + } + GST_LOG_OBJECT (playbin, "clearing shutdown flag"); + g_atomic_int_set (&playbin->shutdown, 0); + do_async_start (playbin); + + GST_PLAY_BIN3_UNLOCK (playbin); + + return TRUE; + +task_error: + { + GST_PLAY_BIN3_UNLOCK (playbin); + GST_ERROR_OBJECT (playbin, "Failed to create task"); + return FALSE; + } +} + +static void +gst_play_bin3_stop (GstPlayBin3 * playbin) +{ + GstTask *task; + + GST_DEBUG_OBJECT (playbin, "stopping"); + + /* FIXME unlock our waiting groups */ + GST_LOG_OBJECT (playbin, "setting shutdown flag"); + g_atomic_int_set (&playbin->shutdown, 1); + + /* wait for all callbacks to end by taking the lock. + * No dynamic (critical) new callbacks will + * be able to happen as we set the shutdown flag. */ + GST_PLAY_BIN3_DYN_LOCK (playbin); + GST_LOG_OBJECT (playbin, "dynamic lock taken, we can continue shutdown"); + GST_PLAY_BIN3_DYN_UNLOCK (playbin); + + /* Stop the activation task */ + GST_PLAY_BIN3_LOCK (playbin); + if ((task = playbin->activation_task)) { + playbin->activation_task = NULL; + GST_PLAY_BIN3_UNLOCK (playbin); + + gst_task_stop (task); + + /* Make sure task is not running */ + g_rec_mutex_lock (&playbin->activation_lock); + g_rec_mutex_unlock (&playbin->activation_lock); + + /* Wait for task to finish and unref it */ + gst_task_join (task); + gst_object_unref (task); + + GST_PLAY_BIN3_LOCK (playbin); + } + GST_PLAY_BIN3_UNLOCK (playbin); +} + static GstStateChangeReturn gst_play_bin3_change_state (GstElement * element, GstStateChange transition) { @@ -5136,39 +4893,24 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition) playbin = GST_PLAY_BIN3 (element); switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - memset (&playbin->duration, 0, sizeof (playbin->duration)); - break; case GST_STATE_CHANGE_READY_TO_PAUSED: - GST_LOG_OBJECT (playbin, "clearing shutdown flag"); - memset (&playbin->duration, 0, sizeof (playbin->duration)); - g_atomic_int_set (&playbin->shutdown, 0); - do_async_start (playbin); + if (!gst_play_bin3_start (playbin)) + return GST_STATE_CHANGE_FAILURE; break; case GST_STATE_CHANGE_PAUSED_TO_READY: async_down: - /* FIXME unlock our waiting groups */ - GST_LOG_OBJECT (playbin, "setting shutdown flag"); - g_atomic_int_set (&playbin->shutdown, 1); - memset (&playbin->duration, 0, sizeof (playbin->duration)); - - /* wait for all callbacks to end by taking the lock. - * No dynamic (critical) new callbacks will - * be able to happen as we set the shutdown flag. */ - GST_PLAY_BIN3_DYN_LOCK (playbin); - GST_LOG_OBJECT (playbin, "dynamic lock taken, we can continue shutdown"); - GST_PLAY_BIN3_DYN_UNLOCK (playbin); + gst_play_bin3_stop (playbin); if (!do_save) break; case GST_STATE_CHANGE_READY_TO_NULL: /* we go async to PAUSED, so if that fails, we never make it to PAUSED - * and no state change PAUSED to READY passes here, - * though it is a nice-to-have ... */ + * and we will never be called with the GST_STATE_CHANGE_PAUSED_TO_READY. + * Make sure we do go through the same steps (see above) for + * proper cleanup */ if (!g_atomic_int_get (&playbin->shutdown)) { do_save = TRUE; goto async_down; } - memset (&playbin->duration, 0, sizeof (playbin->duration)); /* unlock so that all groups go to NULL */ groups_set_locked_state (playbin, FALSE); @@ -5183,9 +4925,7 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition) switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: - if ((ret = - setup_next_source (playbin, - GST_STATE_PAUSED)) == GST_STATE_CHANGE_FAILURE) + if ((ret = setup_next_source (playbin)) == GST_STATE_CHANGE_FAILURE) goto failure; if (ret == GST_STATE_CHANGE_SUCCESS) ret = GST_STATE_CHANGE_ASYNC; @@ -5201,40 +4941,24 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition) case GST_STATE_CHANGE_READY_TO_NULL: { guint i; - GList *l; /* also do missed state change down to READY */ if (do_save) save_current_group (playbin); - /* Deactive the groups, set the urisrcbins to NULL - * and unref them. - */ + /* Deactive the groups, set uridecodebin to NULL and unref it */ for (i = 0; i < 2; i++) { if (playbin->groups[i].active && playbin->groups[i].valid) { deactivate_group (playbin, &playbin->groups[i]); playbin->groups[i].valid = FALSE; } - if (playbin->groups[i].urisourcebin) { - gst_element_set_state (playbin->groups[i].urisourcebin, + if (playbin->groups[i].uridecodebin) { + gst_element_set_state (playbin->groups[i].uridecodebin, GST_STATE_NULL); - gst_object_unref (playbin->groups[i].urisourcebin); - playbin->groups[i].urisourcebin = NULL; + gst_object_unref (playbin->groups[i].uridecodebin); + playbin->groups[i].uridecodebin = NULL; } - if (playbin->groups[i].suburisourcebin) { - gst_element_set_state (playbin->groups[i].suburisourcebin, - GST_STATE_NULL); - gst_object_unref (playbin->groups[i].suburisourcebin); - playbin->groups[i].suburisourcebin = NULL; - } - } - - deactivate_decodebin (playbin); - if (playbin->decodebin) { - gst_object_unref (playbin->decodebin); - playbin->decodebin = NULL; - playbin->decodebin_active = FALSE; } /* Set our sinks back to NULL, they might not be child of playbin */ @@ -5255,31 +4979,6 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition) /* make sure the groups don't perform a state change anymore until we * enable them again */ groups_set_locked_state (playbin, TRUE); - - /* Remove all non-persistent contexts */ - GST_OBJECT_LOCK (playbin); - for (l = playbin->contexts; l;) { - GstContext *context = l->data; - - if (!gst_context_is_persistent (context)) { - GList *next; - - gst_context_unref (context); - - next = l->next; - playbin->contexts = g_list_delete_link (playbin->contexts, l); - l = next; - } else { - l = l->next; - } - } - - if (playbin->source) { - gst_object_unref (playbin->source); - playbin->source = NULL; - } - - GST_OBJECT_UNLOCK (playbin); break; } default: diff --git a/gst/playback/gstplaysink.c b/gst/playback/gstplaysink.c index ead2e68..89cd225 100644 --- a/gst/playback/gstplaysink.c +++ b/gst/playback/gstplaysink.c @@ -3550,7 +3550,6 @@ gst_play_sink_do_reconfigure (GstPlaySink * playsink) goto no_chain; if (!playsink->audio_sinkpad_stream_synchronizer) { - GstPad *audio_queue_srcpad; GValue item = { 0, }; GstIterator *it; @@ -3565,8 +3564,19 @@ gst_play_sink_do_reconfigure (GstPlaySink * playsink) g_value_unset (&item); g_assert (playsink->audio_srcpad_stream_synchronizer); gst_iterator_free (it); + } + + if (need_vis) { + GstPad *audio_queue_srcpad; + + if (gst_pad_is_linked (playsink->audio_sinkpad_stream_synchronizer)) { + GstPad *peer_pad = + gst_pad_get_peer (playsink->audio_sinkpad_stream_synchronizer); + gst_pad_unlink (peer_pad, playsink->audio_sinkpad_stream_synchronizer); + gst_object_unref (peer_pad); + } - if (need_vis) { + if (!playsink->audio_ssync_queue) { GST_DEBUG_OBJECT (playsink, "adding audio stream synchronizer queue"); playsink->audio_ssync_queue = gst_element_factory_make ("queue", "audiossyncqueue"); @@ -3582,14 +3592,15 @@ gst_play_sink_do_reconfigure (GstPlaySink * playsink) gst_bin_add (GST_BIN_CAST (playsink), playsink->audio_ssync_queue); playsink->audio_ssync_queue_sinkpad = gst_element_get_static_pad (playsink->audio_ssync_queue, "sink"); - audio_queue_srcpad = - gst_element_get_static_pad (playsink->audio_ssync_queue, "src"); - gst_element_sync_state_with_parent (playsink->audio_ssync_queue); - gst_pad_link_full (audio_queue_srcpad, - playsink->audio_sinkpad_stream_synchronizer, - GST_PAD_LINK_CHECK_NOTHING); - gst_object_unref (audio_queue_srcpad); } + + audio_queue_srcpad = + gst_element_get_static_pad (playsink->audio_ssync_queue, "src"); + gst_pad_link_full (audio_queue_srcpad, + playsink->audio_sinkpad_stream_synchronizer, + GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (audio_queue_srcpad); + gst_element_sync_state_with_parent (playsink->audio_ssync_queue); } if (playsink->audiochain) { @@ -3667,7 +3678,26 @@ gst_play_sink_do_reconfigure (GstPlaySink * playsink) if (playsink->vischain) { GST_DEBUG_OBJECT (playsink, "setting up vis chain"); - /* Just change vis plugin or set up chain? */ + /* Lazily add and activate chain */ + if (!playsink->vischain->chain.added) { + srcpad = + gst_element_get_static_pad (playsink->vischain->chain.bin, "src"); + add_chain (GST_PLAY_CHAIN (playsink->vischain), TRUE); + activate_chain (GST_PLAY_CHAIN (playsink->vischain), TRUE); + if (playsink->audio_tee_vissrc == NULL) { + playsink->audio_tee_vissrc = + gst_element_get_request_pad (playsink->audio_tee, "src_%u"); + } + gst_pad_link_full (playsink->audio_tee_vissrc, + playsink->vischain->sinkpad, GST_PAD_LINK_CHECK_NOTHING); + gst_pad_link_full (srcpad, playsink->video_sinkpad_stream_synchronizer, + GST_PAD_LINK_CHECK_NOTHING); + gst_pad_link_full (playsink->video_srcpad_stream_synchronizer, + playsink->videochain->sinkpad, GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (srcpad); + } + + /* Is a reconfiguration required? */ if (playsink->vischain->vis != playsink->visualisation) { /* unlink the old plugin and unghost the pad */ gst_pad_unlink (playsink->vischain->vispeerpad, @@ -3697,22 +3727,6 @@ gst_play_sink_do_reconfigure (GstPlaySink * playsink) playsink->vischain->vissinkpad, GST_PAD_LINK_CHECK_NOTHING); gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->vischain-> srcpad), playsink->vischain->vissrcpad); - } else { - srcpad = - gst_element_get_static_pad (playsink->vischain->chain.bin, "src"); - add_chain (GST_PLAY_CHAIN (playsink->vischain), TRUE); - activate_chain (GST_PLAY_CHAIN (playsink->vischain), TRUE); - if (playsink->audio_tee_vissrc == NULL) { - playsink->audio_tee_vissrc = - gst_element_get_request_pad (playsink->audio_tee, "src_%u"); - } - gst_pad_link_full (playsink->audio_tee_vissrc, - playsink->vischain->sinkpad, GST_PAD_LINK_CHECK_NOTHING); - gst_pad_link_full (srcpad, playsink->video_sinkpad_stream_synchronizer, - GST_PAD_LINK_CHECK_NOTHING); - gst_pad_link_full (playsink->video_srcpad_stream_synchronizer, - playsink->videochain->sinkpad, GST_PAD_LINK_CHECK_NOTHING); - gst_object_unref (srcpad); } } } else { diff --git a/gst/playback/gsturidecodebin.c b/gst/playback/gsturidecodebin.c index 45e9251..0774c73 100644 --- a/gst/playback/gsturidecodebin.c +++ b/gst/playback/gsturidecodebin.c @@ -455,8 +455,6 @@ gst_uri_decode_bin_class_init (GstURIDecodeBinClass * klass) * When download buffering is activated and used for the current media * type, this property does nothing. Otherwise perform buffering on the * demuxed or parsed media. - * - * Since: 0.10.26 */ g_object_class_install_property (gobject_class, PROP_USE_BUFFERING, g_param_spec_boolean ("use-buffering", "Use Buffering", @@ -472,8 +470,6 @@ gst_uri_decode_bin_class_init (GstURIDecodeBinClass * klass) * caps (see 'caps' property) will have a pad exposed. Streams that do not * match those caps but could have been decoded will not have decoder plugged * in internally and will not have a pad exposed. - * - * Since: 0.10.30 */ g_object_class_install_property (gobject_class, PROP_EXPOSE_ALL_STREAMS, g_param_spec_boolean ("expose-all-streams", "Expose All Streams", @@ -486,8 +482,6 @@ gst_uri_decode_bin_class_init (GstURIDecodeBinClass * klass) * * The maximum size of the ring buffer in kilobytes. If set to 0, the ring * buffer is disabled. Default is 0. - * - * Since: 0.10.31 */ g_object_class_install_property (gobject_class, PROP_RING_BUFFER_MAX_SIZE, g_param_spec_uint64 ("ring-buffer-max-size", @@ -522,11 +516,11 @@ gst_uri_decode_bin_class_init (GstURIDecodeBinClass * klass) * emitted before looking for any elements that can handle that stream. * * > Invocation of signal handlers stops after the first signal handler - * > returns #FALSE. Signal handlers are invoked in the order they were + * > returns %FALSE. Signal handlers are invoked in the order they were * > connected in. * - * Returns: #TRUE if you wish uridecodebin to look for elements that can - * handle the given @caps. If #FALSE, those caps will be considered as + * Returns: %TRUE if you wish uridecodebin to look for elements that can + * handle the given @caps. If %FALSE, those caps will be considered as * final and the pad will be exposed as such (see 'pad-added' signal of * #GstElement). */ @@ -579,18 +573,16 @@ gst_uri_decode_bin_class_init (GstURIDecodeBinClass * klass) * the application to perform additional sorting or filtering on the element * factory array. * - * The callee should copy and modify @factories or return #NULL if the + * The callee should copy and modify @factories or return %NULL if the * order should not change. * * > Invocation of signal handlers stops after one signal handler has - * > returned something else than #NULL. Signal handlers are invoked in + * > returned something else than %NULL. Signal handlers are invoked in * > the order they were connected in. * > Don't connect signal handlers with the #G_CONNECT_AFTER flag to this * > signal, they will never be invoked! * * Returns: A new sorted array of #GstElementFactory objects. - * - * Since: 0.10.33 */ gst_uri_decode_bin_signals[SIGNAL_AUTOPLUG_SORT] = g_signal_new ("autoplug-sort", G_TYPE_FROM_CLASS (klass), @@ -652,7 +644,7 @@ gst_uri_decode_bin_class_init (GstURIDecodeBinClass * klass) * be used to tell the element about the downstream supported caps * for example. * - * Returns: #TRUE if the query was handled, #FALSE otherwise. + * Returns: %TRUE if the query was handled, %FALSE otherwise. */ gst_uri_decode_bin_signals[SIGNAL_AUTOPLUG_QUERY] = g_signal_new ("autoplug-query", G_TYPE_FROM_CLASS (klass), @@ -682,8 +674,6 @@ gst_uri_decode_bin_class_init (GstURIDecodeBinClass * klass) * proxy server for an http source, or set the device and read speed for * an audio cd source). This is functionally equivalent to connecting to * the notify::source signal, but more convenient. - * - * Since: 0.10.33 */ gst_uri_decode_bin_signals[SIGNAL_SOURCE_SETUP] = g_signal_new ("source-setup", G_TYPE_FROM_CLASS (klass), @@ -1640,6 +1630,11 @@ remove_decoders (GstURIDecodeBin * bin, gboolean force) GstElement *decoder = GST_ELEMENT_CAST (walk->data); GST_DEBUG_OBJECT (bin, "removing old decoder element"); + + /* Even if we reuse this decodebin, the previous topology will + * be irrelevant */ + g_object_set_data (G_OBJECT (decoder), "uridecodebin-topology", NULL); + if (force) { gst_element_set_state (decoder, GST_STATE_NULL); gst_bin_remove (GST_BIN_CAST (bin), decoder); @@ -2392,6 +2387,54 @@ handle_redirect_message (GstURIDecodeBin * dec, GstMessage * msg) return new_msg; } +static GstMessage * +make_topology_message (GstURIDecodeBin * dec) +{ + GSList *tmp; + GstStructure *aggregated_topology = NULL; + GValue list = G_VALUE_INIT; + GstCaps *caps = NULL; + gchar *name, *proto; + + aggregated_topology = gst_structure_new_empty ("stream-topology"); + g_value_init (&list, GST_TYPE_LIST); + + for (tmp = dec->decodebins; tmp; tmp = tmp->next) { + GValue item = G_VALUE_INIT; + GstStructure *dec_topology = + g_object_get_data (G_OBJECT (tmp->data), "uridecodebin-topology"); + + g_value_init (&item, GST_TYPE_STRUCTURE); + gst_value_set_structure (&item, dec_topology); + gst_value_list_append_and_take_value (&list, &item); + } + + gst_structure_take_value (aggregated_topology, "next", &list); + + /* This is a bit wacky, but that's the best way I can find to express + * uridecodebin 'caps' as subsequently shown by gst-discoverer */ + proto = gst_uri_get_protocol (dec->uri); + name = g_strdup_printf ("application/%s", proto); + g_free (proto); + + caps = gst_caps_new_empty_simple (name); + g_free (name); + + gst_structure_set (aggregated_topology, "caps", GST_TYPE_CAPS, caps, NULL); + gst_caps_unref (caps); + + return gst_message_new_element (GST_OBJECT (dec), aggregated_topology); +} + +static void +check_topology (gpointer data, gpointer user_data) +{ + gboolean *has_topo = user_data; + + if (g_object_get_data (data, "uridecodebin-topology") == NULL) + *has_topo = FALSE; +} + static void handle_message (GstBin * bin, GstMessage * msg) { @@ -2399,7 +2442,32 @@ handle_message (GstBin * bin, GstMessage * msg) switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_ELEMENT:{ - if (gst_message_has_name (msg, "redirect")) { + + if (gst_message_has_name (msg, "stream-topology")) { + GstElement *element = GST_ELEMENT (GST_MESSAGE_SRC (msg)); + gboolean has_all_topo = TRUE; + + if (dec->pending || (dec->decodebins && dec->decodebins->next != NULL)) { + const GstStructure *structure; + + /* If there is only one, just let it through, so this case is if + * there is more than one. + */ + + structure = gst_message_get_structure (msg); + + g_object_set_data_full (G_OBJECT (element), "uridecodebin-topology", + gst_structure_copy (structure), + (GDestroyNotify) gst_structure_free); + + gst_message_unref (msg); + msg = NULL; + + g_slist_foreach (dec->decodebins, check_topology, &has_all_topo); + if (has_all_topo) + msg = make_topology_message (dec); + } + } else if (gst_message_has_name (msg, "redirect")) { /* sort redirect messages based on the connection speed. This simplifies * the user of this element as it can in most cases just pick the first item * of the sorted list as a good redirection candidate. It can of course diff --git a/gst/playback/gsturidecodebin3.c b/gst/playback/gsturidecodebin3.c new file mode 100644 index 0000000..8b1840d --- /dev/null +++ b/gst/playback/gsturidecodebin3.c @@ -0,0 +1,1087 @@ +/* GStreamer + * Copyright (C) <2007> Wim Taymans + * + * 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. + */ + +/* TODO/FIXME: + * + * * BUFFERING MESSAGES + * ** How/Where do we deal with buffering messages from a new/prerolling + * source ? Ideally we want to re-use the same sourcebin ? + * ** Remember last buffering messages per source handler, if the SourceEntry + * group_id is the one being currently outputted on the source ghostpads, + * post the (last) buffering messages. + * If no group_id is being outputted (still prerolling), then output + * the messages directly + * + * * ASYNC HANDLING + * ** URIDECODEBIN3 is not async-aware. + * + * * GAPLESS HANDLING + * ** Correlate group_id and URI to know when/which stream is being outputted/started + */ + +/** + * SECTION:element-uridecodebin3 + * @title: uridecodebin3 + * + * Decodes data from a URI into raw media. It selects a source element that can + * handle the given #GstURIDecodeBin3:uri scheme and connects it to a decodebin. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include +#include +#include + +#include "gstplay-enum.h" +#include "gstrawcaps.h" +#include "gstplayback.h" +#include "gstplaybackutils.h" + +#define GST_TYPE_URI_DECODE_BIN3 \ + (gst_uri_decode_bin3_get_type()) +#define GST_URI_DECODE_BIN3(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_URI_DECODE_BIN3,GstURIDecodeBin3)) +#define GST_URI_DECODE_BIN3_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_URI_DECODE_BIN3,GstURIDecodeBin3Class)) +#define GST_IS_URI_DECODE_BIN3(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_URI_DECODE_BIN3)) +#define GST_IS_URI_DECODE_BIN3_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_URI_DECODE_BIN3)) +#define GST_URI_DECODE_BIN3_CAST(obj) ((GstURIDecodeBin3 *) (obj)) + +typedef struct _GstSourceGroup GstSourceGroup; +typedef struct _GstURIDecodeBin3 GstURIDecodeBin3; +typedef struct _GstURIDecodeBin3Class GstURIDecodeBin3Class; + +#define GST_URI_DECODE_BIN3_LOCK(dec) (g_mutex_lock(&((GstURIDecodeBin3*)(dec))->lock)) +#define GST_URI_DECODE_BIN3_UNLOCK(dec) (g_mutex_unlock(&((GstURIDecodeBin3*)(dec))->lock)) + +typedef struct _GstPlayItem GstPlayItem; +typedef struct _GstSourceItem GstSourceItem; +typedef struct _GstSourceHandler GstSourceHandler; +typedef struct _OutputPad OutputPad; + +/* A structure describing a play item, which travels through the elements + * over time. */ +struct _GstPlayItem +{ + GstURIDecodeBin3 *uridecodebin; + + /* Main URI */ + GstSourceItem *main_item; + + /* Auxiliary URI */ + /* FIXME : Replace by a list later */ + GstSourceItem *sub_item; + + /* The group_id used to identify this play item via STREAM_START events + * This is the group_id which will be used externally (i.e. rewritten + * to outgoing STREAM_START events and in emitted signals). + * The urisourcebin-specific group_id is located in GstSourceItem */ + guint group_id; + + /* Is this play item the one being currently outputted by decodebin3 + * and on our source ghostpads */ + gboolean currently_outputted; +}; + +struct _GstSourceItem +{ + /* The GstPlayItem to which this GstSourceItem belongs to */ + GstPlayItem *play_item; + + gchar *uri; + + /* The urisourcebin controlling this uri + * Can be NULL */ + GstSourceHandler *handler; + + /* Last buffering information */ + gint last_perc; + GstMessage *last_buffering_message; + + /* The groupid created by urisourcebin for this uri */ + guint internal_groupid; + + /* FIXME : Add tag lists and other uri-specific items here ? */ +}; + +/* Structure wrapping everything related to a urisourcebin */ +struct _GstSourceHandler +{ + GstURIDecodeBin3 *uridecodebin; + + GstElement *urisourcebin; + + /* Signal handlers */ + gulong pad_added_id; + gulong pad_removed_id; + gulong source_setup_id; + gulong about_to_finish_id; + + /* TRUE if the controlled urisourcebin was added to uridecodebin */ + gboolean active; + + /* whether urisourcebin is drained or not. + * Reset if/when setting a new URI */ + gboolean drained; + + /* Whether urisourcebin posted EOS on all pads and + * there is no pending entry */ + gboolean is_eos; + + /* TRUE if the urisourcebin handles main item */ + gboolean is_main_source; + + /* buffering message stored for after switching */ + GstMessage *pending_buffering_msg; +}; + +/* Controls an output source pad */ +struct _OutputPad +{ + GstURIDecodeBin3 *uridecodebin; + + GstPad *target_pad; + GstPad *ghost_pad; + + /* Downstream event probe id */ + gulong probe_id; + + /* TRUE if the pad saw EOS. Resetted to FALSE on STREAM_START */ + gboolean is_eos; + + /* The last seen (i.e. current) group_id + * Can be (guint)-1 if no group_id was seen yet */ + guint current_group_id; +}; + +/** + * GstURIDecodeBin3 + * + * uridecodebin3 element struct + */ +struct _GstURIDecodeBin3 +{ + GstBin parent_instance; + + GMutex lock; /* lock for constructing */ + + /* Properties */ + GstElement *source; + guint64 connection_speed; /* In bits/sec (0 = unknown) */ + GstCaps *caps; + guint64 buffer_duration; /* When buffering, buffer duration (ns) */ + guint buffer_size; /* When buffering, buffer size (bytes) */ + gboolean download; + gboolean use_buffering; + guint64 ring_buffer_max_size; + + GList *play_items; /* List of GstPlayItem ordered by time of + * creation. Head of list is therefore the + * current (or pending if initial) one being + * outputted */ + GstPlayItem *current; /* Currently active GstPlayItem. Can be NULL + * if no entry is active yet (i.e. no source + * pads) */ + + /* sources. + * FIXME : Replace by a more modular system later on */ + GstSourceHandler *main_handler; + GstSourceHandler *sub_handler; + + /* URI handling + * FIXME : Switch to a playlist-based API */ + gchar *uri; + gboolean uri_changed; /* TRUE if uri changed */ + gchar *suburi; + gboolean suburi_changed; /* TRUE if suburi changed */ + + /* A global decodebin3 that's used to actually do decoding */ + GstElement *decodebin; + + /* db3 signals */ + gulong db_pad_added_id; + gulong db_pad_removed_id; + gulong db_select_stream_id; + gulong db_about_to_finish_id; + + GList *output_pads; /* List of OutputPad */ + + GList *source_handlers; /* List of SourceHandler */ + + /* Whether we already signalled about-to-finish or not + * FIXME: Track this by group-id ! */ + gboolean posted_about_to_finish; +}; + +struct _GstURIDecodeBin3Class +{ + GstBinClass parent_class; + + gint (*select_stream) (GstURIDecodeBin3 * dbin, + GstStreamCollection * collection, GstStream * stream); +}; + +GST_DEBUG_CATEGORY_STATIC (gst_uri_decode_bin3_debug); +#define GST_CAT_DEFAULT gst_uri_decode_bin3_debug + +/* signals */ +enum +{ + SIGNAL_SELECT_STREAM, + SIGNAL_SOURCE_SETUP, + SIGNAL_ABOUT_TO_FINISH, + LAST_SIGNAL +}; + +#if 0 +static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS ("audio/x-raw(ANY)"); +static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw(ANY)"); +#endif + +/* properties */ +#define DEFAULT_PROP_URI NULL +#define DEFAULT_PROP_SUBURI NULL +#define DEFAULT_PROP_SOURCE NULL +#define DEFAULT_CONNECTION_SPEED 0 +#define DEFAULT_CAPS (gst_static_caps_get (&default_raw_caps)) +#define DEFAULT_BUFFER_DURATION -1 +#define DEFAULT_BUFFER_SIZE -1 +#define DEFAULT_DOWNLOAD FALSE +#define DEFAULT_USE_BUFFERING FALSE +#define DEFAULT_RING_BUFFER_MAX_SIZE 0 + +enum +{ + PROP_0, + PROP_URI, + PROP_CURRENT_URI, + PROP_SUBURI, + PROP_CURRENT_SUBURI, + PROP_SOURCE, + PROP_CONNECTION_SPEED, + PROP_BUFFER_SIZE, + PROP_BUFFER_DURATION, + PROP_DOWNLOAD, + PROP_USE_BUFFERING, + PROP_RING_BUFFER_MAX_SIZE, + PROP_CAPS +}; + +static guint gst_uri_decode_bin3_signals[LAST_SIGNAL] = { 0 }; + +static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS); + +static GstStaticPadTemplate video_src_template = +GST_STATIC_PAD_TEMPLATE ("video_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate audio_src_template = +GST_STATIC_PAD_TEMPLATE ("audio_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate text_src_template = +GST_STATIC_PAD_TEMPLATE ("text_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +GType gst_uri_decode_bin3_get_type (void); +#define gst_uri_decode_bin3_parent_class parent_class +G_DEFINE_TYPE (GstURIDecodeBin3, gst_uri_decode_bin3, GST_TYPE_BIN); + +#define REMOVE_SIGNAL(obj,id) \ +if (id) { \ + g_signal_handler_disconnect (obj, id); \ + id = 0; \ +} + +static void gst_uri_decode_bin3_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_uri_decode_bin3_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_uri_decode_bin3_finalize (GObject * obj); + +static GstStateChangeReturn gst_uri_decode_bin3_change_state (GstElement * + element, GstStateChange transition); + +static gboolean +_gst_int_accumulator (GSignalInvocationHint * ihint, + GValue * return_accu, const GValue * handler_return, gpointer dummy) +{ + gint res = g_value_get_int (handler_return); + + if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP)) + g_value_set_int (return_accu, res); + + if (res == -1) + return TRUE; + + return FALSE; +} + + +static void +gst_uri_decode_bin3_class_init (GstURIDecodeBin3Class * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + + gobject_class->set_property = gst_uri_decode_bin3_set_property; + gobject_class->get_property = gst_uri_decode_bin3_get_property; + gobject_class->finalize = gst_uri_decode_bin3_finalize; + + g_object_class_install_property (gobject_class, PROP_URI, + g_param_spec_string ("uri", "URI", "URI to decode", + DEFAULT_PROP_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_CURRENT_URI, + g_param_spec_string ("current-uri", "Current URI", + "The currently playing URI", NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SUBURI, + g_param_spec_string ("suburi", ".sub-URI", "Optional URI of a subtitle", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_CURRENT_SUBURI, + g_param_spec_string ("current-suburi", "Current .sub-URI", + "The currently playing URI of a subtitle", + NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SOURCE, + g_param_spec_object ("source", "Source", "Source object used", + GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED, + g_param_spec_uint64 ("connection-speed", "Connection Speed", + "Network connection speed in kbps (0 = unknown)", + 0, G_MAXUINT64 / 1000, DEFAULT_CONNECTION_SPEED, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE, + g_param_spec_int ("buffer-size", "Buffer size (bytes)", + "Buffer size when buffering streams (-1 default value)", + -1, G_MAXINT, DEFAULT_BUFFER_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_BUFFER_DURATION, + g_param_spec_int64 ("buffer-duration", "Buffer duration (ns)", + "Buffer duration when buffering streams (-1 default value)", + -1, G_MAXINT64, DEFAULT_BUFFER_DURATION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstURIDecodeBin3::download: + * + * For certain media type, enable download buffering. + */ + g_object_class_install_property (gobject_class, PROP_DOWNLOAD, + g_param_spec_boolean ("download", "Download", + "Attempt download buffering when buffering network streams", + DEFAULT_DOWNLOAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstURIDecodeBin3::use-buffering: + * + * Emit BUFFERING messages based on low-/high-percent thresholds of the + * demuxed or parsed data. + * When download buffering is activated and used for the current media + * type, this property does nothing. Otherwise perform buffering on the + * demuxed or parsed media. + */ + g_object_class_install_property (gobject_class, PROP_USE_BUFFERING, + g_param_spec_boolean ("use-buffering", "Use Buffering", + "Perform buffering on demuxed/parsed media", + DEFAULT_USE_BUFFERING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstURIDecodeBin3::ring-buffer-max-size + * + * The maximum size of the ring buffer in kilobytes. If set to 0, the ring + * buffer is disabled. Default is 0. + */ + g_object_class_install_property (gobject_class, PROP_RING_BUFFER_MAX_SIZE, + g_param_spec_uint64 ("ring-buffer-max-size", + "Max. ring buffer size (bytes)", + "Max. amount of data in the ring buffer (bytes, 0 = ring buffer disabled)", + 0, G_MAXUINT, DEFAULT_RING_BUFFER_MAX_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_CAPS, + g_param_spec_boxed ("caps", "Caps", + "The caps on which to stop decoding. (NULL = default)", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstURIDecodebin3::select-stream + * @decodebin: a #GstURIDecodebin3 + * @collection: a #GstStreamCollection + * @stream: a #GstStream + * + * This signal is emitted whenever @decodebin needs to decide whether + * to expose a @stream of a given @collection. + * + * Returns: 1 if the stream should be selected, 0 if it shouldn't be selected. + * A value of -1 (default) lets @decodebin decide what to do with the stream. + * */ + gst_uri_decode_bin3_signals[SIGNAL_SELECT_STREAM] = + g_signal_new ("select-stream", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURIDecodeBin3Class, select_stream), + _gst_int_accumulator, NULL, g_cclosure_marshal_generic, + G_TYPE_INT, 2, GST_TYPE_STREAM_COLLECTION, GST_TYPE_STREAM); + + /** + * GstURIDecodeBin3::source-setup: + * @bin: the uridecodebin. + * @source: source element + * + * This signal is emitted after a source element has been created, 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). + */ + gst_uri_decode_bin3_signals[SIGNAL_SOURCE_SETUP] = + g_signal_new ("source-setup", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_ELEMENT); + /** + * GstURIDecodeBin3::about-to-finish: + * + * This signal is emitted when the data for the selected URI is + * entirely buffered and it is safe to specify anothe URI. + */ + gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH] = + g_signal_new ("about-to-finish", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, + 0, G_TYPE_NONE); + + + gst_element_class_add_static_pad_template (gstelement_class, + &video_src_template); + gst_element_class_add_static_pad_template (gstelement_class, + &audio_src_template); + gst_element_class_add_static_pad_template (gstelement_class, + &text_src_template); + gst_element_class_add_static_pad_template (gstelement_class, &src_template); + gst_element_class_set_static_metadata (gstelement_class, + "URI Decoder", "Generic/Bin/Decoder", + "Autoplug and decode an URI to raw media", + "Edward Hervey , Jan Schmidt "); + + gstelement_class->change_state = gst_uri_decode_bin3_change_state; + +} + +static GstPadProbeReturn +db_src_probe (GstPad * pad, GstPadProbeInfo * info, OutputPad * output) +{ + /* FIXME : IMPLEMENT */ + + /* EOS : Mark pad as EOS */ + + /* STREAM_START : Store group_id and check if currently active + * PlayEntry changed */ + + return GST_PAD_PROBE_OK; +} + +static OutputPad * +add_output_pad (GstURIDecodeBin3 * dec, GstPad * target_pad) +{ + OutputPad *output; + gchar *pad_name; + + output = g_slice_new0 (OutputPad); + + GST_LOG_OBJECT (dec, "Created output %p", output); + + output->uridecodebin = dec; + output->target_pad = target_pad; + output->current_group_id = (guint) - 1; + pad_name = gst_pad_get_name (target_pad); + output->ghost_pad = gst_ghost_pad_new (pad_name, target_pad); + g_free (pad_name); + + gst_pad_set_active (output->ghost_pad, TRUE); + gst_element_add_pad (GST_ELEMENT (dec), output->ghost_pad); + + output->probe_id = + gst_pad_add_probe (output->target_pad, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, (GstPadProbeCallback) db_src_probe, + output, NULL); + + /* FIXME: LOCK TO PROTECT PAD LIST */ + dec->output_pads = g_list_append (dec->output_pads, output); + + return output; +} + +static void +db_pad_added_cb (GstElement * element, GstPad * pad, GstURIDecodeBin3 * dec) +{ + GST_DEBUG_OBJECT (dec, "Wrapping new pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + if (GST_PAD_IS_SRC (pad)) + add_output_pad (dec, pad); +} + +static void +db_pad_removed_cb (GstElement * element, GstPad * pad, GstURIDecodeBin3 * dec) +{ + GList *tmp; + OutputPad *output = NULL; + + if (!GST_PAD_IS_SRC (pad)) + return; + + GST_DEBUG_OBJECT (dec, "pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + /* FIXME: LOCK for list access */ + + for (tmp = dec->output_pads; tmp; tmp = tmp->next) { + OutputPad *cand = (OutputPad *) tmp->data; + + if (cand->target_pad == pad) { + output = cand; + dec->output_pads = g_list_delete_link (dec->output_pads, tmp); + break; + } + } + + if (output) { + GST_LOG_OBJECT (element, "Removing output %p", output); + /* Remove source ghost pad */ + gst_ghost_pad_set_target ((GstGhostPad *) output->ghost_pad, NULL); + gst_element_remove_pad ((GstElement *) dec, output->ghost_pad); + + /* FIXME : Update global/current PlayEntry group_id (did we switch ?) */ + + /* Remove event probe */ + gst_pad_remove_probe (output->target_pad, output->probe_id); + + g_slice_free (OutputPad, output); + } +} + +static gint +db_select_stream_cb (GstElement * decodebin, + GstStreamCollection * collection, GstStream * stream, + GstURIDecodeBin3 * uridecodebin) +{ + gint response = -1; + + g_signal_emit (uridecodebin, + gst_uri_decode_bin3_signals[SIGNAL_SELECT_STREAM], 0, collection, stream, + &response); + return response; +} + +static void +db_about_to_finish_cb (GstElement * decodebin, GstURIDecodeBin3 * uridecodebin) +{ + if (!uridecodebin->posted_about_to_finish) { + uridecodebin->posted_about_to_finish = TRUE; + g_signal_emit (uridecodebin, + gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); + } +} + +static void +gst_uri_decode_bin3_init (GstURIDecodeBin3 * dec) +{ + g_mutex_init (&dec->lock); + + dec->caps = gst_static_caps_get (&default_raw_caps); + + dec->decodebin = gst_element_factory_make ("decodebin3", NULL); + gst_bin_add (GST_BIN_CAST (dec), dec->decodebin); + dec->db_pad_added_id = + g_signal_connect (dec->decodebin, "pad-added", + G_CALLBACK (db_pad_added_cb), dec); + dec->db_pad_removed_id = + g_signal_connect (dec->decodebin, "pad-removed", + G_CALLBACK (db_pad_removed_cb), dec); + dec->db_select_stream_id = + g_signal_connect (dec->decodebin, "select-stream", + G_CALLBACK (db_select_stream_cb), dec); + dec->db_about_to_finish_id = + g_signal_connect (dec->decodebin, "about-to-finish", + G_CALLBACK (db_about_to_finish_cb), dec); + + GST_OBJECT_FLAG_SET (dec, GST_ELEMENT_FLAG_SOURCE); + gst_bin_set_suppressed_flags (GST_BIN (dec), + GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK); +} + +static void +gst_uri_decode_bin3_finalize (GObject * obj) +{ + GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (obj); + + g_mutex_clear (&dec->lock); + g_free (dec->uri); + g_free (dec->suburi); + + G_OBJECT_CLASS (parent_class)->finalize (obj); +} + +static GstStateChangeReturn +activate_source_item (GstSourceItem * item) +{ + GstSourceHandler *handler = item->handler; + + if (handler == NULL) { + GST_WARNING ("Can't activate item without a handler"); + return GST_STATE_CHANGE_FAILURE; + } + + g_object_set (handler->urisourcebin, "uri", item->uri, NULL); + if (!handler->active) { + gst_bin_add ((GstBin *) handler->uridecodebin, handler->urisourcebin); + /* if (!gst_element_sync_state_with_parent (handler->urisourcebin)) */ + /* return GST_STATE_CHANGE_FAILURE; */ + handler->active = TRUE; + } + + return GST_STATE_CHANGE_SUCCESS; +} + +static void +src_pad_added_cb (GstElement * element, GstPad * pad, + GstSourceHandler * handler) +{ + GstURIDecodeBin3 *uridecodebin; + GstPad *sinkpad = NULL; + GstPadLinkReturn res; + + uridecodebin = handler->uridecodebin; + + GST_DEBUG_OBJECT (uridecodebin, + "New pad %" GST_PTR_FORMAT " from source %" GST_PTR_FORMAT, pad, element); + + /* FIXME: Add probe to unify group_id and detect EOS */ + + /* Try to link to main sink pad only if it's from a main handler */ + if (handler->is_main_source) { + sinkpad = gst_element_get_static_pad (uridecodebin->decodebin, "sink"); + if (gst_pad_is_linked (sinkpad)) { + gst_object_unref (sinkpad); + sinkpad = NULL; + } + } + + if (sinkpad == NULL) + sinkpad = gst_element_get_request_pad (uridecodebin->decodebin, "sink_%u"); + + if (sinkpad) { + GST_DEBUG_OBJECT (uridecodebin, + "Linking %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT, pad, sinkpad); + res = gst_pad_link (pad, sinkpad); + gst_object_unref (sinkpad); + if (GST_PAD_LINK_FAILED (res)) + goto link_failed; + } + return; + +link_failed: + { + GST_ERROR_OBJECT (uridecodebin, + "failed to link pad %s:%s to decodebin, reason %s (%d)", + GST_DEBUG_PAD_NAME (pad), gst_pad_link_get_name (res), res); + return; + } +} + +static void +src_pad_removed_cb (GstElement * element, GstPad * pad, + GstSourceHandler * handler) +{ + /* FIXME : IMPLEMENT */ +} + +static void +src_source_setup_cb (GstElement * element, GstElement * source, + GstSourceHandler * handler) +{ + g_signal_emit (handler->uridecodebin, + gst_uri_decode_bin3_signals[SIGNAL_SOURCE_SETUP], 0, source, NULL); +} + +static void +src_about_to_finish_cb (GstElement * element, GstSourceHandler * handler) +{ + /* FIXME : check if all sources are done */ + if (!handler->uridecodebin->posted_about_to_finish) { + handler->uridecodebin->posted_about_to_finish = TRUE; + g_signal_emit (handler->uridecodebin, + gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); + } +} + +static GstSourceHandler * +new_source_handler (GstURIDecodeBin3 * uridecodebin, gboolean is_main) +{ + GstSourceHandler *handler; + + handler = g_slice_new0 (GstSourceHandler); + + handler->uridecodebin = uridecodebin; + handler->is_main_source = is_main; + handler->urisourcebin = gst_element_factory_make ("urisourcebin", NULL); + /* Set pending properties */ + g_object_set (handler->urisourcebin, + "connection-speed", uridecodebin->connection_speed / 1000, + "download", uridecodebin->download, + "use-buffering", uridecodebin->use_buffering, + "buffer-duration", uridecodebin->buffer_duration, + "buffer-size", uridecodebin->buffer_size, + "ring-buffer-max-size", uridecodebin->ring_buffer_max_size, NULL); + + handler->pad_added_id = + g_signal_connect (handler->urisourcebin, "pad-added", + (GCallback) src_pad_added_cb, handler); + handler->pad_removed_id = + g_signal_connect (handler->urisourcebin, "pad-removed", + (GCallback) src_pad_removed_cb, handler); + handler->source_setup_id = + g_signal_connect (handler->urisourcebin, "source-setup", + (GCallback) src_source_setup_cb, handler); + handler->about_to_finish_id = + g_signal_connect (handler->urisourcebin, "about-to-finish", + (GCallback) src_about_to_finish_cb, handler); + + uridecodebin->source_handlers = + g_list_append (uridecodebin->source_handlers, handler); + + return handler; +} + +static void +gst_uri_decode_bin3_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (object); + + switch (prop_id) { + case PROP_URI: + if (dec->uri) + g_free (dec->uri); + dec->uri = g_value_dup_string (value); + break; + case PROP_SUBURI: + if (dec->suburi) + g_free (dec->suburi); + dec->suburi = g_value_dup_string (value); + break; + case PROP_CONNECTION_SPEED: + GST_URI_DECODE_BIN3_LOCK (dec); + dec->connection_speed = g_value_get_uint64 (value) * 1000; + GST_URI_DECODE_BIN3_UNLOCK (dec); + break; + case PROP_BUFFER_SIZE: + dec->buffer_size = g_value_get_int (value); + break; + case PROP_BUFFER_DURATION: + dec->buffer_duration = g_value_get_int64 (value); + break; + case PROP_DOWNLOAD: + dec->download = g_value_get_boolean (value); + break; + case PROP_USE_BUFFERING: + dec->use_buffering = g_value_get_boolean (value); + break; + case PROP_RING_BUFFER_MAX_SIZE: + dec->ring_buffer_max_size = g_value_get_uint64 (value); + break; + case PROP_CAPS: + GST_OBJECT_LOCK (dec); + if (dec->caps) + gst_caps_unref (dec->caps); + dec->caps = g_value_dup_boxed (value); + GST_OBJECT_UNLOCK (dec); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_uri_decode_bin3_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (object); + + switch (prop_id) { + case PROP_URI: + { + g_value_set_string (value, dec->uri); + break; + } + case PROP_CURRENT_URI: + { + g_value_set_string (value, dec->suburi); + break; + } + case PROP_SUBURI: + { + /* FIXME : Return current uri */ + g_value_set_string (value, dec->uri); + break; + } + case PROP_CURRENT_SUBURI: + { + /* FIXME : Return current suburi */ + g_value_set_string (value, dec->suburi); + break; + } + case PROP_SOURCE: + { + GST_OBJECT_LOCK (dec); + g_value_set_object (value, dec->source); + GST_OBJECT_UNLOCK (dec); + break; + } + case PROP_CONNECTION_SPEED: + GST_URI_DECODE_BIN3_LOCK (dec); + g_value_set_uint64 (value, dec->connection_speed / 1000); + GST_URI_DECODE_BIN3_UNLOCK (dec); + break; + case PROP_BUFFER_SIZE: + GST_OBJECT_LOCK (dec); + g_value_set_int (value, dec->buffer_size); + GST_OBJECT_UNLOCK (dec); + break; + case PROP_BUFFER_DURATION: + GST_OBJECT_LOCK (dec); + g_value_set_int64 (value, dec->buffer_duration); + GST_OBJECT_UNLOCK (dec); + break; + case PROP_DOWNLOAD: + g_value_set_boolean (value, dec->download); + break; + case PROP_USE_BUFFERING: + g_value_set_boolean (value, dec->use_buffering); + break; + case PROP_RING_BUFFER_MAX_SIZE: + g_value_set_uint64 (value, dec->ring_buffer_max_size); + break; + case PROP_CAPS: + GST_OBJECT_LOCK (dec); + g_value_set_boxed (value, dec->caps); + GST_OBJECT_UNLOCK (dec); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +free_source_handler (GstURIDecodeBin3 * uridecodebin, + GstSourceHandler * handler) +{ + GST_LOG_OBJECT (uridecodebin, "source handler %p", handler); + if (handler->active) { + GST_LOG_OBJECT (uridecodebin, "Removing %" GST_PTR_FORMAT, + handler->urisourcebin); + gst_element_set_state (handler->urisourcebin, GST_STATE_NULL); + gst_bin_remove ((GstBin *) uridecodebin, handler->urisourcebin); + } + uridecodebin->source_handlers = + g_list_remove (uridecodebin->source_handlers, handler); + g_slice_free (GstSourceHandler, handler); +} + +static GstSourceItem * +new_source_item (GstURIDecodeBin3 * dec, GstPlayItem * item, gchar * uri) +{ + GstSourceItem *sourceitem = g_slice_new0 (GstSourceItem); + + sourceitem->play_item = item; + sourceitem->uri = uri; + + return sourceitem; +} + +static void +free_source_item (GstURIDecodeBin3 * uridecodebin, GstSourceItem * item) +{ + GST_LOG_OBJECT (uridecodebin, "source item %p", item); + if (item->handler) + free_source_handler (uridecodebin, item->handler); + g_slice_free (GstSourceItem, item); +} + +static GstPlayItem * +new_play_item (GstURIDecodeBin3 * dec, gchar * uri, gchar * suburi) +{ + GstPlayItem *item = g_slice_new0 (GstPlayItem); + + item->uridecodebin = dec; + item->main_item = new_source_item (dec, item, uri); + if (suburi) + item->sub_item = new_source_item (dec, item, suburi); + + return item; +} + +static void +free_play_item (GstURIDecodeBin3 * dec, GstPlayItem * item) +{ + GST_LOG_OBJECT (dec, "play item %p", item); + if (item->main_item) + free_source_item (dec, item->main_item); + if (item->sub_item) + free_source_item (dec, item->sub_item); + + g_slice_free (GstPlayItem, item); +} + +/* Sync source handlers for the given play item. Might require creating/removing some + * and/or configure the handlers accordingly */ +static GstStateChangeReturn +assign_handlers_to_item (GstURIDecodeBin3 * dec, GstPlayItem * item) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + /* FIXME : Go over existing handlers to see if we can assign some to the + * given item */ + + /* Create missing handlers */ + if (item->main_item->handler == NULL) { + item->main_item->handler = new_source_handler (dec, TRUE); + ret = activate_source_item (item->main_item); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + } + + if (item->sub_item && item->sub_item->handler) { + item->sub_item->handler = new_source_handler (dec, FALSE); + ret = activate_source_item (item->sub_item); + } + + return ret; +} + +/* Called to activate the next play item */ +static GstStateChangeReturn +activate_next_play_item (GstURIDecodeBin3 * dec) +{ + GstPlayItem *item; + GstStateChangeReturn ret; + + /* If there is no current play entry, create one from the uri/suburi + * FIXME : Use a playlist API in the future */ + item = new_play_item (dec, dec->uri, dec->suburi); + + ret = assign_handlers_to_item (dec, item); + if (ret == GST_STATE_CHANGE_FAILURE) { + free_play_item (dec, item); + return ret; + } + + dec->play_items = g_list_append (dec->play_items, item); + + return ret; +} + +static void +free_play_items (GstURIDecodeBin3 * dec) +{ + GList *tmp; + + for (tmp = dec->play_items; tmp; tmp = tmp->next) { + GstPlayItem *item = (GstPlayItem *) tmp->data; + free_play_item (dec, item); + } + + g_list_free (dec->play_items); + dec->play_items = NULL; +} + +static GstStateChangeReturn +gst_uri_decode_bin3_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret; + GstURIDecodeBin3 *uridecodebin = (GstURIDecodeBin3 *) element; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + ret = activate_next_play_item (uridecodebin); + if (ret == GST_STATE_CHANGE_FAILURE) + goto failure; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + goto failure; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* FIXME: Cleanup everything */ + free_play_items (uridecodebin); + /* Free play item */ + uridecodebin->posted_about_to_finish = FALSE; + break; + default: + break; + } + + return ret; + + /* ERRORS */ +failure: + { + if (transition == GST_STATE_CHANGE_READY_TO_PAUSED) + free_play_items (uridecodebin); + return ret; + } +} + + +gboolean +gst_uri_decode_bin3_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_uri_decode_bin3_debug, "uridecodebin3", 0, + "URI decoder element 3"); + + return gst_element_register (plugin, "uridecodebin3", GST_RANK_NONE, + GST_TYPE_URI_DECODE_BIN3); +} diff --git a/gst/playback/gsturisourcebin.c b/gst/playback/gsturisourcebin.c index 03da4a9..2713852 100644 --- a/gst/playback/gsturisourcebin.c +++ b/gst/playback/gsturisourcebin.c @@ -151,8 +151,6 @@ struct _GstURISourceBin /* for dynamic sources */ guint src_np_sig_id; /* new-pad signal id */ - gboolean async_pending; /* async-start has been emitted */ - guint64 ring_buffer_max_size; /* 0 means disabled */ GList *pending_pads; /* Pads we have blocked pending assignment @@ -169,28 +167,12 @@ struct _GstURISourceBinClass { GstBinClass parent_class; - /* signal fired when we found a pad that we cannot decode */ - void (*unknown_type) (GstElement * element, GstPad * pad, GstCaps * caps); - - /* signal fired to know if we continue trying to decode the given caps */ - gboolean (*autoplug_continue) (GstElement * element, GstPad * pad, - GstCaps * caps); - /* signal fired to get a list of factories to try to autoplug */ - GValueArray *(*autoplug_factories) (GstElement * element, GstPad * pad, - GstCaps * caps); - /* signal fired to sort the factories */ - GValueArray *(*autoplug_sort) (GstElement * element, GstPad * pad, - GstCaps * caps, GValueArray * factories); - /* signal fired to select from the proposed list of factories */ - GstAutoplugSelectResult (*autoplug_select) (GstElement * element, - GstPad * pad, GstCaps * caps, GstElementFactory * factory); - /* signal fired when a autoplugged element that is not linked downstream - * or exposed wants to query something */ - gboolean (*autoplug_query) (GstElement * element, GstPad * pad, - GstQuery * query); - - /* emitted when all data is decoded */ + /* emitted when all data has been drained out + * FIXME : What do we need this for ?? */ void (*drained) (GstElement * element); + /* emitted when all data has been fed into buffering slots (i.e the + * actual sources are done) */ + void (*about_to_finish) (GstElement * element); }; static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src_%u", @@ -206,13 +188,8 @@ GST_DEBUG_CATEGORY_STATIC (gst_uri_source_bin_debug); /* signals */ enum { - SIGNAL_UNKNOWN_TYPE, - SIGNAL_AUTOPLUG_CONTINUE, - SIGNAL_AUTOPLUG_FACTORIES, - SIGNAL_AUTOPLUG_SELECT, - SIGNAL_AUTOPLUG_SORT, - SIGNAL_AUTOPLUG_QUERY, SIGNAL_DRAINED, + SIGNAL_ABOUT_TO_FINISH, SIGNAL_SOURCE_SETUP, LAST_SIGNAL }; @@ -241,6 +218,21 @@ enum PROP_RING_BUFFER_MAX_SIZE }; +#define CUSTOM_EOS_QUARK _custom_eos_quark_get () +#define CUSTOM_EOS_QUARK_DATA "custom-eos" +static GQuark +_custom_eos_quark_get (void) +{ + static gsize g_quark; + + if (g_once_init_enter (&g_quark)) { + gsize quark = + (gsize) g_quark_from_static_string ("urisourcebin-custom-eos"); + g_once_init_leave (&g_quark, quark); + } + return g_quark; +} + static void post_missing_plugin_error (GstElement * dec, const gchar * element_name); @@ -273,172 +265,6 @@ static void free_output_slot_async (GstURISourceBin * urisrc, static GstPad *create_output_pad (GstURISourceBin * urisrc, GstPad * pad); static void remove_buffering_msgs (GstURISourceBin * bin, GstObject * src); -static gboolean -_gst_boolean_accumulator (GSignalInvocationHint * ihint, - GValue * return_accu, const GValue * handler_return, gpointer dummy) -{ - gboolean myboolean; - - myboolean = g_value_get_boolean (handler_return); - if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP)) - g_value_set_boolean (return_accu, myboolean); - - /* stop emission if FALSE */ - return myboolean; -} - -static gboolean -_gst_boolean_or_accumulator (GSignalInvocationHint * ihint, - GValue * return_accu, const GValue * handler_return, gpointer dummy) -{ - gboolean myboolean; - gboolean retboolean; - - myboolean = g_value_get_boolean (handler_return); - retboolean = g_value_get_boolean (return_accu); - - if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP)) - g_value_set_boolean (return_accu, myboolean || retboolean); - - return TRUE; -} - -static gboolean -_gst_array_accumulator (GSignalInvocationHint * ihint, - GValue * return_accu, const GValue * handler_return, gpointer dummy) -{ - gpointer array; - - array = g_value_get_boxed (handler_return); - if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP)) - g_value_set_boxed (return_accu, array); - - return FALSE; -} - -static gboolean -_gst_select_accumulator (GSignalInvocationHint * ihint, - GValue * return_accu, const GValue * handler_return, gpointer dummy) -{ - GstAutoplugSelectResult res; - - res = g_value_get_enum (handler_return); - if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP)) - g_value_set_enum (return_accu, res); - - /* Call the next handler in the chain (if any) when the current callback - * returns TRY. This makes it possible to register separate autoplug-select - * handlers that implement different TRY/EXPOSE/SKIP strategies. - */ - if (res == GST_AUTOPLUG_SELECT_TRY) - return TRUE; - - return FALSE; -} - -static gboolean -_gst_array_hasvalue_accumulator (GSignalInvocationHint * ihint, - GValue * return_accu, const GValue * handler_return, gpointer dummy) -{ - gpointer array; - - array = g_value_get_boxed (handler_return); - if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP)) - g_value_set_boxed (return_accu, array); - - if (array != NULL) - return FALSE; - - return TRUE; -} - -static gboolean -gst_uri_source_bin_autoplug_continue (GstElement * element, GstPad * pad, - GstCaps * caps) -{ - /* by default we always continue */ - return TRUE; -} - -/* Must be called with factories lock! */ -static void -gst_uri_source_bin_update_factories_list (GstURISourceBin * dec) -{ - guint32 cookie; - - cookie = gst_registry_get_feature_list_cookie (gst_registry_get ()); - if (!dec->factories || dec->factories_cookie != cookie) { - if (dec->factories) - gst_plugin_feature_list_free (dec->factories); - dec->factories = - gst_element_factory_list_get_elements - (GST_ELEMENT_FACTORY_TYPE_DECODABLE, GST_RANK_MARGINAL); - dec->factories = - g_list_sort (dec->factories, gst_playback_utils_compare_factories_func); - dec->factories_cookie = cookie; - } -} - -static GValueArray * -gst_uri_source_bin_autoplug_factories (GstElement * element, GstPad * pad, - GstCaps * caps) -{ - GList *list, *tmp; - GValueArray *result; - GstURISourceBin *dec = GST_URI_SOURCE_BIN_CAST (element); - - GST_DEBUG_OBJECT (element, "finding factories"); - - /* return all compatible factories for caps */ - g_mutex_lock (&dec->factories_lock); - gst_uri_source_bin_update_factories_list (dec); - list = - gst_element_factory_list_filter (dec->factories, caps, GST_PAD_SINK, - gst_caps_is_fixed (caps)); - g_mutex_unlock (&dec->factories_lock); - - result = g_value_array_new (g_list_length (list)); - for (tmp = list; tmp; tmp = tmp->next) { - GstElementFactory *factory = GST_ELEMENT_FACTORY_CAST (tmp->data); - GValue val = { 0, }; - - g_value_init (&val, G_TYPE_OBJECT); - g_value_set_object (&val, factory); - g_value_array_append (result, &val); - g_value_unset (&val); - } - gst_plugin_feature_list_free (list); - - GST_DEBUG_OBJECT (element, "autoplug-factories returns %p", result); - - return result; -} - -static GValueArray * -gst_uri_source_bin_autoplug_sort (GstElement * element, GstPad * pad, - GstCaps * caps, GValueArray * factories) -{ - return NULL; -} - -static GstAutoplugSelectResult -gst_uri_source_bin_autoplug_select (GstElement * element, GstPad * pad, - GstCaps * caps, GstElementFactory * factory) -{ - GST_DEBUG_OBJECT (element, "default autoplug-select returns TRY"); - - /* Try factory. */ - return GST_AUTOPLUG_SELECT_TRY; -} - -static gboolean -gst_uri_source_bin_autoplug_query (GstElement * element, GstPad * pad, - GstQuery * query) -{ - /* No query handled here */ - return FALSE; -} - static void gst_uri_source_bin_class_init (GstURISourceBinClass * klass) { @@ -520,171 +346,6 @@ gst_uri_source_bin_class_init (GstURISourceBinClass * klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** - * GstURISourceBin::unknown-type: - * @bin: The urisourcebin. - * @pad: the new pad containing caps that cannot be resolved to a 'final'. - * stream type. - * @caps: the #GstCaps of the pad that cannot be resolved. - * - * This signal is emitted when a pad for which there is no further possible - * decoding is added to the urisourcebin. - */ - gst_uri_source_bin_signals[SIGNAL_UNKNOWN_TYPE] = - g_signal_new ("unknown-type", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass, unknown_type), - NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, - GST_TYPE_PAD, GST_TYPE_CAPS); - - /** - * GstURISourceBin::autoplug-continue: - * @bin: The urisourcebin. - * @pad: The #GstPad. - * @caps: The #GstCaps found. - * - * This signal is emitted whenever urisourcebin finds a new stream. It is - * emitted before looking for any elements that can handle that stream. - * - * > Invocation of signal handlers stops after the first signal handler - * > returns #FALSE. Signal handlers are invoked in the order they were - * > connected in. - * - * Returns: #TRUE if you wish urisourcebin to look for elements that can - * handle the given @caps. If #FALSE, those caps will be considered as - * final and the pad will be exposed as such (see 'pad-added' signal of - * #GstElement). - */ - gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_CONTINUE] = - g_signal_new ("autoplug-continue", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass, - autoplug_continue), _gst_boolean_accumulator, NULL, - g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 2, GST_TYPE_PAD, - GST_TYPE_CAPS); - - /** - * GstURISourceBin::autoplug-factories: - * @bin: The urisourcebin. - * @pad: The #GstPad. - * @caps: The #GstCaps found. - * - * This function is emitted when an array of possible factories for @caps on - * @pad is needed. urisourcebin will by default return an array with all - * compatible factories, sorted by rank. - * - * If this function returns NULL, @pad will be exposed as a final caps. - * - * If this function returns an empty array, the pad will be considered as - * having an unhandled type media type. - * - * > Only the signal handler that is connected first will ever by invoked. - * > Don't connect signal handlers with the #G_CONNECT_AFTER flag to this - * > signal, they will never be invoked! - * - * Returns: a #GValueArray* with a list of factories to try. The factories are - * by default tried in the returned order or based on the index returned by - * "autoplug-select". - */ - gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_FACTORIES] = - g_signal_new ("autoplug-factories", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass, - autoplug_factories), _gst_array_accumulator, NULL, - g_cclosure_marshal_generic, G_TYPE_VALUE_ARRAY, 2, - GST_TYPE_PAD, GST_TYPE_CAPS); - - /** - * GstURISourceBin::autoplug-sort: - * @bin: The urisourcebin. - * @pad: The #GstPad. - * @caps: The #GstCaps. - * @factories: A #GValueArray of possible #GstElementFactory to use. - * - * Once decodebin has found the possible #GstElementFactory objects to try - * for @caps on @pad, this signal is emited. The purpose of the signal is for - * the application to perform additional sorting or filtering on the element - * factory array. - * - * The callee should copy and modify @factories or return #NULL if the - * order should not change. - * - * > Invocation of signal handlers stops after one signal handler has - * > returned something else than #NULL. Signal handlers are invoked in - * > the order they were connected in. - * > Don't connect signal handlers with the #G_CONNECT_AFTER flag to this - * > signal, they will never be invoked! - * - * Returns: A new sorted array of #GstElementFactory objects. - * - * Since: 0.10.33 - */ - gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_SORT] = - g_signal_new ("autoplug-sort", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass, autoplug_sort), - _gst_array_hasvalue_accumulator, NULL, - g_cclosure_marshal_generic, G_TYPE_VALUE_ARRAY, 3, GST_TYPE_PAD, - GST_TYPE_CAPS, G_TYPE_VALUE_ARRAY | G_SIGNAL_TYPE_STATIC_SCOPE); - - /** - * GstURISourceBin::autoplug-select: - * @bin: The urisourcebin. - * @pad: The #GstPad. - * @caps: The #GstCaps. - * @factory: A #GstElementFactory to use. - * - * This signal is emitted once urisourcebin has found all the possible - * #GstElementFactory that can be used to handle the given @caps. For each of - * those factories, this signal is emitted. - * - * The signal handler should return a #GST_TYPE_AUTOPLUG_SELECT_RESULT enum - * value indicating what decodebin should do next. - * - * A value of #GST_AUTOPLUG_SELECT_TRY will try to autoplug an element from - * @factory. - * - * A value of #GST_AUTOPLUG_SELECT_EXPOSE will expose @pad without plugging - * any element to it. - * - * A value of #GST_AUTOPLUG_SELECT_SKIP will skip @factory and move to the - * next factory. - * - * > The signal handler will not be invoked if any of the previously - * > registered signal handlers (if any) return a value other than - * > GST_AUTOPLUG_SELECT_TRY. Which also means that if you return - * > GST_AUTOPLUG_SELECT_TRY from one signal handler, handlers that get - * > registered next (again, if any) can override that decision. - * - * Returns: a #GST_TYPE_AUTOPLUG_SELECT_RESULT that indicates the required - * operation. The default handler will always return - * #GST_AUTOPLUG_SELECT_TRY. - */ - gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_SELECT] = - g_signal_new ("autoplug-select", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass, - autoplug_select), _gst_select_accumulator, NULL, - g_cclosure_marshal_generic, - GST_TYPE_AUTOPLUG_SELECT_RESULT, 3, GST_TYPE_PAD, GST_TYPE_CAPS, - GST_TYPE_ELEMENT_FACTORY); - - /** - * GstDecodeBin::autoplug-query: - * @bin: The decodebin. - * @child: The child element doing the query - * @pad: The #GstPad. - * @query: The #GstQuery. - * - * This signal is emitted whenever an autoplugged element that is - * not linked downstream yet and not exposed does a query. It can - * be used to tell the element about the downstream supported caps - * for example. - * - * Returns: #TRUE if the query was handled, #FALSE otherwise. - */ - gst_uri_source_bin_signals[SIGNAL_AUTOPLUG_QUERY] = - g_signal_new ("autoplug-query", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURISourceBinClass, autoplug_query), - _gst_boolean_or_accumulator, NULL, g_cclosure_marshal_generic, - G_TYPE_BOOLEAN, 3, GST_TYPE_PAD, GST_TYPE_ELEMENT, - GST_TYPE_QUERY | G_SIGNAL_TYPE_STATIC_SCOPE); - - /** * GstURISourceBin::drained: * * This signal is emitted when the data for the current uri is played. @@ -695,6 +356,17 @@ gst_uri_source_bin_class_init (GstURISourceBinClass * klass) G_STRUCT_OFFSET (GstURISourceBinClass, drained), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); + /** + * GstURISourceBin::about-to-finish: + * + * This signal is emitted when the data for the current uri is played. + */ + gst_uri_source_bin_signals[SIGNAL_ABOUT_TO_FINISH] = + g_signal_new ("about-to-finish", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstURISourceBinClass, about_to_finish), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); + /** * GstURISourceBin::source-setup: * @bin: the urisourcebin. @@ -725,15 +397,6 @@ gst_uri_source_bin_class_init (GstURISourceBinClass * klass) GST_DEBUG_FUNCPTR (gst_uri_source_bin_change_state); gstbin_class->handle_message = GST_DEBUG_FUNCPTR (handle_message); - - klass->autoplug_continue = - GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_continue); - klass->autoplug_factories = - GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_factories); - klass->autoplug_sort = GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_sort); - klass->autoplug_select = - GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_select); - klass->autoplug_query = GST_DEBUG_FUNCPTR (gst_uri_source_bin_autoplug_query); } static void @@ -770,6 +433,8 @@ gst_uri_source_bin_finalize (GObject * obj) remove_demuxer (urisrc); g_mutex_clear (&urisrc->lock); g_mutex_clear (&urisrc->factories_lock); + g_mutex_clear (&urisrc->buffering_lock); + g_mutex_clear (&urisrc->buffering_post_lock); g_free (urisrc->uri); if (urisrc->factories) gst_plugin_feature_list_free (urisrc->factories); @@ -863,32 +528,6 @@ gst_uri_source_bin_get_property (GObject * object, guint prop_id, } } -static void -do_async_start (GstURISourceBin * dbin) -{ - GstMessage *message; - - dbin->async_pending = TRUE; - - message = gst_message_new_async_start (GST_OBJECT_CAST (dbin)); - GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (dbin), message); -} - -static void -do_async_done (GstURISourceBin * dbin) -{ - GstMessage *message; - - if (dbin->async_pending) { - GST_DEBUG_OBJECT (dbin, "posting ASYNC_DONE"); - message = - gst_message_new_async_done (GST_OBJECT_CAST (dbin), - GST_CLOCK_TIME_NONE); - GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (dbin), message); - - dbin->async_pending = FALSE; - } -} #define DEFAULT_QUEUE_SIZE (3 * GST_SECOND) #define DEFAULT_QUEUE_MIN_THRESHOLD ((DEFAULT_QUEUE_SIZE * 30) / 100) @@ -1074,6 +713,7 @@ link_pending_pad_to_output (GstURISourceBin * urisrc, OutputSlotInfo * slot) slot->is_eos = FALSE; BUFFERING_UNLOCK (urisrc); res = TRUE; + slot->is_eos = FALSE; urisrc->pending_pads = g_list_remove (urisrc->pending_pads, out_info->demux_src_pad); } else { @@ -1086,6 +726,20 @@ link_pending_pad_to_output (GstURISourceBin * urisrc, OutputSlotInfo * slot) return res; } +/* Called with lock held */ +static gboolean +all_slots_are_eos (GstURISourceBin * urisrc) +{ + GSList *tmp; + + for (tmp = urisrc->out_slots; tmp; tmp = tmp->next) { + OutputSlotInfo *slot = (OutputSlotInfo *) tmp->data; + if (slot->is_eos == FALSE) + return FALSE; + } + return TRUE; +} + static GstPadProbeReturn demux_pad_events (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) { @@ -1108,36 +762,36 @@ demux_pad_events (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) switch (GST_EVENT_TYPE (ev)) { case GST_EVENT_EOS: { - GstEvent *event; - GstStructure *s; - guint32 seqnum = gst_event_get_seqnum (ev); + gboolean all_streams_eos; GST_LOG_OBJECT (urisrc, "EOS on pad %" GST_PTR_FORMAT, pad); - /* never forward actual EOS to slot */ - ret = GST_PAD_PROBE_DROP; - if ((urisrc->pending_pads && link_pending_pad_to_output (urisrc, child_info->output_slot))) { /* Found a new source pad to give this slot data - no need to send EOS */ GST_URI_SOURCE_BIN_UNLOCK (urisrc); + ret = GST_PAD_PROBE_DROP; goto done; } BUFFERING_LOCK (urisrc); /* Mark that we fed an EOS to this slot */ child_info->output_slot->is_eos = TRUE; + all_streams_eos = all_slots_are_eos (urisrc); BUFFERING_UNLOCK (urisrc); /* EOS means this element is no longer buffering */ remove_buffering_msgs (urisrc, GST_OBJECT_CAST (child_info->output_slot->queue)); - /* Actually feed a custom EOS event to avoid marking pads as EOSed */ - s = gst_structure_new_empty ("urisourcebin-custom-eos"); - event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); - gst_event_set_seqnum (event, seqnum); - gst_pad_send_event (child_info->output_slot->sinkpad, event); + /* Mark this custom EOS */ + gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (ev), CUSTOM_EOS_QUARK, + (gchar *) CUSTOM_EOS_QUARK_DATA, NULL); + if (all_streams_eos) { + GST_DEBUG_OBJECT (urisrc, "POSTING ABOUT TO FINISH"); + g_signal_emit (urisrc, + gst_uri_source_bin_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); + } } break; case GST_EVENT_CAPS: @@ -1163,6 +817,28 @@ done: return ret; } +static GstPadProbeReturn +pre_queue_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstURISourceBin *urisrc = GST_URI_SOURCE_BIN (user_data); + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + GstEvent *ev = GST_PAD_PROBE_INFO_EVENT (info); + + switch (GST_EVENT_TYPE (ev)) { + case GST_EVENT_EOS: + { + GST_LOG_OBJECT (urisrc, "EOS on pad %" GST_PTR_FORMAT, pad); + GST_DEBUG_OBJECT (urisrc, "POSTING ABOUT TO FINISH"); + g_signal_emit (urisrc, + gst_uri_source_bin_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); + } + break; + default: + break; + } + return ret; +} + /* Called with lock held */ static OutputSlotInfo * get_output_slot (GstURISourceBin * urisrc, gboolean do_download, @@ -1186,6 +862,7 @@ get_output_slot (GstURISourceBin * urisrc, gboolean do_download, if (cur_caps == NULL || gst_caps_is_equal (caps, cur_caps)) { GST_LOG_OBJECT (urisrc, "Found existing slot %p to link to", slot); gst_caps_unref (cur_caps); + slot->is_eos = FALSE; return slot; } gst_caps_unref (cur_caps); @@ -1299,11 +976,16 @@ source_pad_event_probe (GstPad * pad, GstPadProbeInfo * info, GST_LOG_OBJECT (pad, "%s, urisrc %p", GST_EVENT_TYPE_NAME (event), event); - if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM && - gst_event_has_name (event, "urisourcebin-custom-eos")) { + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS && + gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (event), + CUSTOM_EOS_QUARK)) { OutputSlotInfo *slot; GST_DEBUG_OBJECT (pad, "we received EOS"); + /* remove custom-eos */ + gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (event), CUSTOM_EOS_QUARK, + NULL, NULL); + GST_URI_SOURCE_BIN_LOCK (urisrc); slot = g_object_get_data (G_OBJECT (pad), "urisourcebin.slotinfo"); @@ -1313,8 +995,17 @@ source_pad_event_probe (GstPad * pad, GstPadProbeInfo * info, guint32 seqnum; if (slot->linked_info) { - /* Do not clear output slot yet. A new input was - * connected. We should just drop this EOS */ + if (slot->is_eos) { + /* linked_info is old input which is stil linked without removal */ + GST_DEBUG_OBJECT (pad, "push actual EOS"); + seqnum = gst_event_get_seqnum (event); + eos = gst_event_new_eos (); + gst_event_set_seqnum (eos, seqnum); + gst_pad_push_event (slot->srcpad, eos); + } else { + /* Do not clear output slot yet. A new input was + * connected. We should just drop this EOS */ + } GST_URI_SOURCE_BIN_UNLOCK (urisrc); return GST_PAD_PROBE_DROP; } @@ -1359,6 +1050,9 @@ create_output_pad (GstURISourceBin * urisrc, GstPad * pad) gst_object_unref (pad_tmpl); g_free (padname); + GST_DEBUG_OBJECT (urisrc, "Created output pad %s:%s for pad %s:%s", + GST_DEBUG_PAD_NAME (newpad), GST_DEBUG_PAD_NAME (pad)); + return newpad; } @@ -1377,9 +1071,6 @@ expose_output_pad (GstURISourceBin * urisrc, GstPad * pad) gst_pad_set_active (pad, TRUE); gst_element_add_pad (GST_ELEMENT_CAST (urisrc), pad); - - /* Once we expose a pad, we're no longer async */ - do_async_done (urisrc); } static void @@ -1431,8 +1122,10 @@ pad_removed_cb (GstElement * element, GstPad * pad, GstURISourceBin * urisrc) GST_LOG_OBJECT (element, "Pad %" GST_PTR_FORMAT " was removed without EOS. Sending.", pad); - s = gst_structure_new_empty ("urisourcebin-custom-eos"); - event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + event = gst_event_new_eos (); + s = gst_event_writable_structure (event); + gst_structure_set (s, "urisourcebin-custom-eos", G_TYPE_BOOLEAN, TRUE, + NULL); gst_pad_send_event (slot->sinkpad, event); } else { GST_LOG_OBJECT (urisrc, "Removed pad has no output slot"); @@ -1696,7 +1389,6 @@ post_missing_plugin_error (GstElement * dec, const gchar * element_name) GST_ELEMENT_ERROR (dec, CORE, MISSING_PLUGIN, (_("Missing element '%s' - check your GStreamer installation."), element_name), (NULL)); - do_async_done (GST_URI_SOURCE_BIN (dec)); } /** @@ -1777,13 +1469,16 @@ analyse_source (GstURISourceBin * urisrc, gboolean * is_raw, gst_object_unref (pad); pad = slot->srcpad; } else { - pad = create_output_pad (urisrc, pad); + GstPad *tmppad = create_output_pad (urisrc, pad); + gst_object_unref (pad); + + pad = tmppad; } GST_URI_SOURCE_BIN_UNLOCK (urisrc); expose_output_pad (urisrc, pad); + } else { gst_object_unref (pad); } - gst_object_unref (pad); g_value_reset (&item); break; } @@ -1906,7 +1601,6 @@ no_demuxer: /* FIXME: Fire the right error */ GST_ELEMENT_ERROR (urisrc, CORE, MISSING_PLUGIN, (NULL), ("No demuxer element, check your installation")); - do_async_done (urisrc); return NULL; } } @@ -1981,7 +1675,7 @@ handle_new_pad (GstURISourceBin * urisrc, GstPad * srcpad, GstCaps * caps) gst_query_unref (query); } - GST_DEBUG_OBJECT (urisrc, "check media-type %s, %d", media_type, + GST_DEBUG_OBJECT (urisrc, "check media-type %s, do_download:%d", media_type, do_download); GST_URI_SOURCE_BIN_LOCK (urisrc); @@ -1990,6 +1684,9 @@ handle_new_pad (GstURISourceBin * urisrc, GstPad * srcpad, GstCaps * caps) if (slot == NULL || gst_pad_link (srcpad, slot->sinkpad) != GST_PAD_LINK_OK) goto could_not_link; + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + pre_queue_event_probe, urisrc, NULL); + expose_output_pad (urisrc, slot->srcpad); GST_URI_SOURCE_BIN_UNLOCK (urisrc); } @@ -2006,7 +1703,6 @@ no_demuxer_sink: { GST_ELEMENT_ERROR (urisrc, CORE, NEGOTIATION, (NULL), ("Adaptive demuxer element has no 'sink' pad")); - do_async_done (urisrc); return; } could_not_link: @@ -2014,7 +1710,6 @@ could_not_link: GST_URI_SOURCE_BIN_UNLOCK (urisrc); GST_ELEMENT_ERROR (urisrc, CORE, NEGOTIATION, (NULL), ("Can't link typefind to adaptive demuxer element")); - do_async_done (urisrc); return; } } @@ -2083,7 +1778,6 @@ no_typefind: post_missing_plugin_error (GST_ELEMENT_CAST (urisrc), "typefind"); GST_ELEMENT_ERROR (urisrc, CORE, MISSING_PLUGIN, (NULL), ("No typefind element, check your installation")); - do_async_done (urisrc); return FALSE; } could_not_link: @@ -2091,7 +1785,6 @@ could_not_link: GST_ELEMENT_ERROR (urisrc, CORE, NEGOTIATION, (NULL), ("Can't link source to typefind element")); gst_bin_remove (GST_BIN_CAST (urisrc), typefind); - do_async_done (urisrc); return FALSE; } } @@ -2259,7 +1952,6 @@ setup_source (GstURISourceBin * urisrc) /* source provides raw data, we added the pads and we can now signal a * no_more pads because we are done. */ gst_element_no_more_pads (GST_ELEMENT_CAST (urisrc)); - do_async_done (urisrc); return TRUE; } if (!have_out && !is_dynamic) { @@ -2893,12 +2585,6 @@ done: return res; } -static void -sync_slot_queue (OutputSlotInfo * slot) -{ - gst_element_sync_state_with_parent (slot->queue); -} - static GstStateChangeReturn gst_uri_source_bin_change_state (GstElement * element, GstStateChange transition) @@ -2908,7 +2594,9 @@ gst_uri_source_bin_change_state (GstElement * element, switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: - do_async_start (urisrc); + GST_DEBUG ("ready to paused"); + if (!setup_source (urisrc)) + goto source_failed; break; default: break; @@ -2917,44 +2605,14 @@ gst_uri_source_bin_change_state (GstElement * element, ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) goto setup_failed; - else if (ret == GST_STATE_CHANGE_NO_PREROLL) - do_async_done (urisrc); switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: - GST_DEBUG ("ready to paused"); - if (!setup_source (urisrc)) - goto source_failed; - - ret = GST_STATE_CHANGE_ASYNC; - - /* And now sync the states of everything we added */ - g_slist_foreach (urisrc->out_slots, (GFunc) sync_slot_queue, NULL); - if (urisrc->typefinds) { - GList *iter; - for (iter = urisrc->typefinds; iter; iter = iter->next) { - GstElement *typefind = iter->data; - ret = gst_element_set_state (typefind, GST_STATE_PAUSED); - if (ret == GST_STATE_CHANGE_FAILURE) - goto setup_failed; - } - } - if (urisrc->source) - ret = gst_element_set_state (urisrc->source, GST_STATE_PAUSED); - if (ret == GST_STATE_CHANGE_FAILURE) - goto setup_failed; - - if (ret == GST_STATE_CHANGE_SUCCESS) - ret = GST_STATE_CHANGE_ASYNC; - else if (ret == GST_STATE_CHANGE_NO_PREROLL) - do_async_done (urisrc); - break; case GST_STATE_CHANGE_PAUSED_TO_READY: GST_DEBUG ("paused to ready"); remove_demuxer (urisrc); remove_source (urisrc); - do_async_done (urisrc); g_list_free_full (urisrc->buffering_status, (GDestroyNotify) gst_message_unref); urisrc->buffering_status = NULL; @@ -2973,13 +2631,11 @@ gst_uri_source_bin_change_state (GstElement * element, /* ERRORS */ source_failed: { - do_async_done (urisrc); return GST_STATE_CHANGE_FAILURE; } setup_failed: { /* clean up leftover groups */ - do_async_done (urisrc); return GST_STATE_CHANGE_FAILURE; } } -- 2.7.4