playbin3 不再依赖 playbin2 中current-audio、current-video 等属性进行流切换。取而代之的是一套更通用、更灵活的异步API。
工作流程涉及两个关键消息和事件:
-
流集合发布 (
GST_MESSAGE_STREAM_COLLECTION) :playbin3或decodebin3在分析媒体后,会通过 Bus 发送STREAM_COLLECTION消息。该消息携带一个GstStreamCollection对象,其中包含了媒体中所有可用的流信息,每个流都有一个唯一的stream-id字符串标识符。 -
流选择事件 (
GST_EVENT_SELECT_STREAMS) :应用程序监听到STREAM_COLLECTION消息后,根据需要选择一组stream-id,通过gst_event_new_select_streams()创建一个事件,并将其发送到管道(playbin3元素)。if (stream_list) {
if (is_same_stream_selection (self->current_selection, stream_list)) {
GST_DEBUG_OBJECT (self, "Stream selection did not change");
g_list_free_full (stream_list, g_free);
} else {
GstEvent *ev = gst_event_new_select_streams (stream_list);
g_list_free_full (self->current_selection, g_free);
self->current_selection = stream_list;
self->selection_seqnum = gst_event_get_seqnum (ev);
g_mutex_unlock (&self->lock);
ret = gst_element_send_event (self->playbin, ev);
g_mutex_lock (&self->lock);
if (!ret) {
GST_WARNING_OBJECT (self, "Stream selection failed");
}
}
}static gboolean
gst_play_bin3_send_event (GstElement * element, GstEvent * event)
{
GstPlayBin3 *playbin = GST_PLAY_BIN3 (element);if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) {
gboolean res;GST_PLAY_BIN3_LOCK (playbin); GST_LOG_OBJECT (playbin, "App sent select-streams, we won't do anything ourselves now"); /* This is probably already false, but it doesn't hurt to be sure */ //do_stream_selections = TRUE 表示"应用用的是旧式 current-* 接口,由 playbin3 替它自动完成流选择"。 //它是旧式 API 与新式 collection/SELECT_STREAMS API 之间的兼容开关。但是playbin3外部 //无法设置urrent-* 接口的,内部仅在有combiners使用 playbin->do_stream_selections = 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 */ //有combiners才需要的流程 event = update_select_streams_event (playbin, event); /* Don't reconfigure playsink just yet, until the streams-selected * message(s) tell us as streams become active / available */ GST_PLAY_BIN3_UNLOCK (playbin); /* Send this event directly to collection_source, so it works even * if collection_source didn't add any pads yet */ //直接将消息发送到collection_source //collection_source 是最近一次上报的collection_source(decodebin3) res = gst_element_send_event ((GstElement *) playbin->collection_source, event); return res;}
/* Send event directly to playsink instead of letting GstBin iterate
* over all sink elements. The latter might send the event multiple times
* in case the SEEK causes a reconfiguration of the pipeline, as can easily
* happen with adaptive streaming demuxers.
*
* What would then happen is that the iterator would be reset, we send the
* event again, and on the second time it will fail in the majority of cases
* because the pipeline is still being reconfigured
*/
if (GST_EVENT_IS_UPSTREAM (event)) {
return gst_element_send_event (GST_ELEMENT_CAST (playbin->playsink), event);
}return GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
}
看下collection_source来源:
static void
gst_play_bin3_handle_message (GstBin * bin, GstMessage * msg)
{
GstPlayBin3 *playbin = GST_PLAY_BIN3 (bin);
gboolean do_reset_time = FALSE;
if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_COLLECTION) {
GstStreamCollection *collection = NULL;
gst_message_parse_stream_collection (msg, &collection);
if (collection) {
gboolean pstate = playbin->do_stream_selections;
GST_PLAY_BIN3_LOCK (playbin);
GST_DEBUG_OBJECT (playbin,
"STREAM_COLLECTION: Got a collection from %" GST_PTR_FORMAT,
msg->src);
gst_object_replace ((GstObject **) & playbin->collection,
(GstObject *) collection);
//收到GST_MESSAGE_STREAM_COLLECTION,记录collection_source
gst_object_replace ((GstObject **) & playbin->collection_source,
(GstObject *) GST_MESSAGE_SRC (msg));
update_combiner_info (playbin, playbin->collection);
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);
}
} else if (GST_MESSAGE_TYPE (msg) == GST
static gboolean
gst_decodebin3_send_event (GstElement * element, GstEvent * event)
{
GstDecodebin3 *dbin = (GstDecodebin3 *) element;
GST_DEBUG_OBJECT (element, "event %s", GST_EVENT_TYPE_NAME (event));
if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS
&& handle_select_streams (dbin, event)) {
return TRUE;
}
return GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
}
static gboolean
handle_select_streams (GstDecodebin3 * dbin, GstEvent * event)
{
GList *streams = NULL;
guint32 seqnum = gst_event_get_seqnum (event);
GList *tmp;
DecodebinCollection *collection = NULL;
if (dbin->upstream_handles_selection) {
GST_DEBUG_OBJECT (dbin, "Letting select-streams event flow upstream");
return FALSE;
}
gst_event_parse_select_streams (event, &streams);
if (streams == NULL) {
GST_DEBUG_OBJECT (dbin, "No streams in select streams");
gst_event_unref (event);
return TRUE;
}
SELECTION_LOCK (dbin);
/* Find the collection to which these list of streams apply */
for (tmp = dbin->collections; tmp; tmp = tmp->next) {
DecodebinCollection *cand = tmp->data;
if (are_all_streams_in_collection (cand->collection, streams)) {
collection = cand;
break;
}
}
if (collection == NULL) {
GST_WARNING_OBJECT (dbin, "Requested streams from no known collection");
goto beach;
}
if (seqnum == collection->seqnum) {
GST_DEBUG_OBJECT (dbin,
"Already handled/handling that SELECT_STREAMS event");
goto beach;
}
/* Update the requested list of streams */
if (collection->requested_selection) {
g_list_free_full (collection->requested_selection, g_free);
}
/* We give ownership of the streams to the DecodebinCollection */
collection->requested_selection = streams;
collection->seqnum = seqnum;
collection->posted_streams_selected_msg = FALSE;
/* If the collection is the current output one, handle the switch. */
//请求命中的正是当前输出 collection,立即调用 handle_stream_switch,当场计划并挂 IDLE probe 执行切换。这是正常的运行时切流。
if (collection == dbin->output_collection)
handle_stream_switch (dbin);
//else
//requested_selection 在 if 之前就已写入目标 collection,选择不会因为没进 handle_stream_switch 而丢失。
//不进去只是"推迟":等那份 collection 成为 output_collection(输出侧追上),或等 all_streams_present满足时,再自动执行切换。
beach:
SELECTION_UNLOCK (dbin);
gst_event_unref (event);
return TRUE;
}
之后处理和之前我们分析的是一样的流程了:playbin3源码分析-CSDN博客
GST_EVENT_STREAM_COLLECTION的源头
一般是来自demuxer,如tsdemux:
(1)tsdemux发出GST_EVENT_STREAM_COLLECTION
static void
gst_ts_demux_update_program (MpegTSBase * base, MpegTSBaseProgram * program)
{
if (stream->pad)
gst_pad_push_event (stream->pad,
gst_event_new_stream_collection (program->collection));
}
(2)传递到decodebin3->sink_event_function,然后将新的collection_msg通过gst_element_post_message发送到decodebin3
case GST_EVENT_STREAM_COLLECTION:
{
GstStreamCollection *collection = NULL;
gst_event_parse_stream_collection (event, &collection);
if (collection) {
GstMessage *collection_msg;
collection_msg =
handle_stream_collection_locked (dbin, collection, input);
gst_object_unref (collection);
if (collection_msg) {
GST_DEBUG_OBJECT (sinkpad, "Posting collection");
dbin->posting_collection = TRUE;
INPUT_UNLOCK (dbin);
gst_element_post_message (GST_ELEMENT_CAST (dbin), collection_msg);
INPUT_LOCK (dbin);
dbin->posting_collection = FALSE;
GST_DEBUG_OBJECT (sinkpad, "Done posting collection");
g_cond_broadcast (&dbin->posting_cond);
}
INPUT_UNLOCK (dbin);
}
}
(3)然后经过gst_decodebin3_handle_message ->gst_uri_decode_bin3_handle_message->gst_play_bin3_handle_message
但是有些demxuer也没有GST_EVENT_STREAM_COLLECTION,这时来自parsebin:
fallback_collection 是 parsebin 在上游没有给出 STREAM_COLLECTION时,自己兜底合成的一份流集合。核心含义:有些流没有携带 collection 信息,parsebin 不能让这些流"无主",于是现造一份,保证下游(decodebin3/应用)总能拿到一份 collection。
/* Must only be called if the toplevel chain is complete and blocked! */
/* Not MT-safe, call with ParseBin expose lock! */
static gboolean
gst_parse_bin_expose (GstParseBin * parsebin)
{
GList *tmp, *endpads;
gboolean missing_plugin;
GString *missing_plugin_details;
gboolean already_exposed;
gboolean last_group;
gboolean uncollected_streams;
GstStreamCollection *fallback_collection = NULL;
retry:
endpads = NULL;
missing_plugin = FALSE;
already_exposed = TRUE;
last_group = TRUE;
missing_plugin_details = g_string_new ("");
GST_DEBUG_OBJECT (parsebin, "Exposing currently active chains/groups");
/* 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);
g_string_free (missing_plugin_details, TRUE);
return FALSE;
}
DYN_UNLOCK (parsebin);
/* Get the pads that we're going to expose and mark things as exposed */
uncollected_streams = FALSE;
CHAIN_MUTEX_LOCK (parsebin->parse_chain);
if (!gst_parse_chain_expose (parsebin->parse_chain, &endpads, &missing_plugin,
missing_plugin_details, &last_group, &uncollected_streams)) {
g_list_free_full (endpads, (GDestroyNotify) gst_object_unref);
g_string_free (missing_plugin_details, TRUE);
GST_ERROR_OBJECT (parsebin, "Broken chain/group tree");
CHAIN_MUTEX_UNLOCK (parsebin->parse_chain);
return FALSE;
}
CHAIN_MUTEX_UNLOCK (parsebin->parse_chain);
if (endpads == NULL) {
if (missing_plugin) {
if (missing_plugin_details->len > 0) {
gchar *details = g_string_free (missing_plugin_details, FALSE);
GST_ELEMENT_ERROR (parsebin, CORE, MISSING_PLUGIN, (NULL),
("no suitable plugins found:\n%s", details));
g_free (details);
} else {
g_string_free (missing_plugin_details, TRUE);
GST_ELEMENT_ERROR (parsebin, CORE, MISSING_PLUGIN, (NULL),
("no suitable plugins found"));
}
} else {
/* in this case, the stream ended without buffers,
* just post a warning */
g_string_free (missing_plugin_details, TRUE);
GST_WARNING_OBJECT (parsebin, "All streams finished without buffers. "
"Last group: %d", last_group);
if (last_group) {
GST_ELEMENT_ERROR (parsebin, STREAM, FAILED, (NULL),
("all streams without buffers"));
} else {
gboolean switched = FALSE;
gboolean drained = FALSE;
drain_and_switch_chains (parsebin->parse_chain, NULL, &last_group,
&drained, &switched);
GST_ELEMENT_WARNING (parsebin, STREAM, FAILED, (NULL),
("all streams without buffers"));
if (switched) {
if (gst_parse_chain_is_complete (parsebin->parse_chain))
goto retry;
else
return FALSE;
}
}
}
return FALSE;
}
if (uncollected_streams) {
/* FIXME: Collect and use a stream id from the top chain as
* upstream ID? */
fallback_collection = gst_stream_collection_new (NULL);
build_fallback_collection (parsebin->parse_chain, fallback_collection);
gst_element_post_message (GST_ELEMENT (parsebin),
gst_message_new_stream_collection (GST_OBJECT (parsebin),
fallback_collection));
}
g_string_free (missing_plugin_details, TRUE);
/* Check if this was called when everything was exposed already,
* and see if we need to post a new fallback collection */
for (tmp = endpads; tmp && already_exposed; tmp = tmp->next) {
GstParsePad *parsepad = tmp->data;
already_exposed &= parsepad->exposed;
}
if (already_exposed) {
GST_DEBUG_OBJECT (parsebin, "Everything was exposed already!");
if (fallback_collection)
gst_object_unref (fallback_collection);
g_list_free_full (endpads, (GDestroyNotify) gst_object_unref);
return TRUE;
}
/* Set all already exposed pads to blocked */
for (tmp = endpads; tmp; tmp = tmp->next) {
GstParsePad *parsepad = tmp->data;
if (parsepad->exposed) {
GST_DEBUG_OBJECT (parsepad, "blocking exposed pad");
gst_parse_pad_set_blocked (parsepad, TRUE);
}
}
/* 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)) {
g_list_free_full (endpads, (GDestroyNotify) gst_object_unref);
GST_WARNING_OBJECT (parsebin,
"Currently, shutting down, aborting exposing");
DYN_UNLOCK (parsebin);
if (fallback_collection)
gst_object_unref (fallback_collection);
return FALSE;
}
/* Expose pads */
for (tmp = endpads; tmp; tmp = tmp->next) {
GstParsePad *parsepad = (GstParsePad *) tmp->data;
gchar *padname;
if (parsepad->exposed) {
GST_DEBUG_OBJECT (parsepad, "Pad is already exposed, not doing anything");
continue;
}
/* 1. rewrite name */
padname = g_strdup_printf ("src_%u", parsebin->nbpads);
parsebin->nbpads++;
GST_DEBUG_OBJECT (parsebin, "About to expose parsepad %s as %s",
GST_OBJECT_NAME (parsepad), padname);
gst_object_set_name (GST_OBJECT (parsepad), padname);
g_free (padname);
gst_pad_sticky_events_foreach (GST_PAD_CAST (parsepad), debug_sticky_event,
parsepad);
/* Store the stream-collection event on the pad */
if (parsepad->active_collection == NULL && fallback_collection) {
GstEvent *new_collection =
gst_event_new_stream_collection (fallback_collection);
gst_pad_store_sticky_event (GST_PAD (parsepad), new_collection);
gst_event_unref (new_collection);
}
/* 2. activate and add */
parsepad->exposed = TRUE;
if (!gst_element_add_pad (GST_ELEMENT (parsebin), GST_PAD_CAST (parsepad))) {
/* not really fatal, we can try to add the other pads */
g_warning ("error adding pad to ParseBin");
parsepad->exposed = FALSE;
continue;
}
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) {
GstParsePad *parsepad = (GstParsePad *) tmp->data;
if (parsepad->exposed) {
GST_DEBUG_OBJECT (parsepad, "unblocking");
gst_parse_pad_unblock (parsepad);
GST_DEBUG_OBJECT (parsepad, "unblocked");
}
gst_object_unref (parsepad);
}
g_list_free (endpads);
if (fallback_collection)
gst_object_unref (fallback_collection);
/* Remove old groups */
chain_remove_old_groups (parsebin->parse_chain);
GST_DEBUG_OBJECT (parsebin, "Exposed everything");
return TRUE;
}
chain/group 判定完整 → 触发 gst_parse_bin_expose
│
├─ 遍历 endpad,若有 active_stream 且无 active_collection
│ 且未进过兜底(!in_a_fallback_collection)→ uncollected_streams=TRUE
│
├─ uncollected_streams:
│ new collection → build_fallback_collection(收无主流/猜类型/打标记)
│ → post 消息 +对无主 pad 贴 sticky 事件
│
└─ 收尾:
already_exposed / shutdown → unref,不重复下发
正常 →事件各自持引用,尾部释放本地引用
关键"完成"保障有两点:**in_a_fallback_collection 标记保证同一条无主流只兜底一次、不重复发;already_exposed 判定保证重入 expose 时不重复下发整份兜底 collection。**这样即便 expose 因 group 切换或 retry 多次进入,兜底 collection也只在真正出现"新的无主流"时才生成和下发。