playbin3流切换源码分析

playbin3 不再依赖 playbin2 中current-audio、current-video 等属性进行流切换。取而代之的是一套更通用、更灵活的异步API。

工作流程涉及两个关键消息和事件:

  1. 流集合发布 (GST_MESSAGE_STREAM_COLLECTION) :playbin3 或 decodebin3 在分析媒体后,会通过 Bus 发送 STREAM_COLLECTION 消息。该消息携带一个 GstStreamCollection 对象,其中包含了媒体中所有可用的流信息,每个流都有一个唯一的 stream-id 字符串标识符。

  2. 流选择事件 (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也只在真正出现"新的无主流"时才生成和下发。

相关推荐
allnlei13 天前
Opus In-Band FEC 详解:原理、实现与开源方案
ffmpeg·音视频·gstreamer
allnlei1 个月前
GStreamer:使用 Meson devenv 管理运行环境
gstreamer
m0_747124534 个月前
多媒体框架 FFmpeg 和 GStreamer
ffmpeg·gstreamer
山西茄子5 个月前
DeepStream9.0 Smart Recording
deepstream·gstreamer
米优6 个月前
qt+gstreamer实现播放功能
qt·gstreamer
山西茄子6 个月前
GstAggregator的aggregate
开发语言·前端·javascript·gstreamer
野指针YZZ7 个月前
GStreamer RKNN 插件自制
linux·音视频·rk3588·gstreamer
xiaohouzi1122331 年前
Python读取视频-硬解和软解
python·opencv·ffmpeg·视频编解码·gstreamer
阿飞__1 年前
Linux开发板(如RK3588)上打开摄像头设备并获取实时画面
c++·gstreamer