gstreamer學習筆記---typefind功能流程簡單分析

  使用gstreamer播放音視頻都知道,當我們直接通過playbin播放視頻的時候,playbin會根據當前播放的音視頻數據自動查找相應的element添加到pipeline進行對數據進行下一步處理,那麼,當playbin在解析數據的時候,發現上一個element發現需要某一個caps的時候,是誰來查找,究竟有那個element支持這個caps的處理,然後又選擇它添加到pipeline中?這個,就是我們今天的主角----typefind完成的。下面,我們通過使用playbin播放一個編碼格式爲H264、封裝格式爲MP4的本地文件,瞭解playbin是如何根據播放的內容逐個查找相應的element,它又是如何完成這些操作的呢。

一、typefind的創建

  首先的,你希望可以查找別人,在這個前提,你得是合法的,所以也會將自己註冊到gstreamer系統。首先的,typefind的plugin註冊函數在gstreamer-xxx/plugins/elements目錄下的gstelements.c,在該文件中,就有一個plugin_init()函數將它註冊到gstreamer core。註冊完成之後,gstreamer就可以使用該類型的element。
  playbin,在gstreamer中是一個element,只不過是特殊的element,一個bin,這個bin中還會包含這其他的bin,比如說uridecodebin將會解析源的uri以及解碼,而decodebin又會比它小一層,將會負責解封裝解碼等操作。而typefind又是什麼時候登場的呢,它是包含在decodebin中,在decodebin的init()函數有以下操作。

static void
gst_decode_bin_init (GstDecodeBin * decode_bin)
{
  ...

  /* we create the typefind element only once */
  decode_bin->typefind = gst_element_factory_make ("typefind", "typefind");

  ...
}

  在uridecodebin創建decodebin,將會添加其到uridecodebin,而decodebin也會將typefind添加到自身,接下來就會是bin中的各個element狀態切換。
  在decodebin狀態切換到PAUSED的時候,也會帶動typefind的狀態切換爲PAUSED。在激活pad的時候,有查詢scheduling,確定使用push模式還是pull模式。而typefind將會將會選擇PULL模式,將會創建task運行gst_type_find_element_loop()函數。

二、typefind查找

  typefind的功能,很像一個查找器,是從source讀取到數據,然後從這部分數據解析信息,查找相應的caps,知道相應的caps之後,接着將會把caps信息通過信號發送出去,等待別人的處理。下面來看看在task中是如何查找的。

static void
gst_type_find_element_loop (GstPad * pad)
{
  if (typefind->mode == MODE_TYPEFIND) {
      /* 這個peer一般是proxy pad */
      peer = gst_pad_get_peer (pad);
      if (peer) {
        gchar *ext;
        ...
        /* 查詢文件的大小等信息 */
        if (!gst_pad_query_duration (peer, GST_FORMAT_BYTES, &size)) {
          GST_WARNING_OBJECT (typefind, "Could not query upstream length!");
          gst_object_unref (peer);

          ret = GST_FLOW_ERROR;
          goto pause;
        }
        
        /* 通過上游的uri,查詢source的文件後綴,比如返回mp4 */
        ext = gst_type_find_get_extension (typefind, pad);

        /* 查找caps */
        found_caps =
            gst_type_find_helper_get_range (GST_OBJECT_CAST (peer),
            GST_OBJECT_PARENT (peer),
            (GstTypeFindHelperGetRangeFunction) (GST_PAD_GETRANGEFUNC (peer)),
            (guint64) size, ext, &probability);
         ...
      }
      ...
  }
  ...
}

  gst_type_find_helper_get_range()函數又是如何查找caps的呢,將會是先根據後綴名,對gstreamer支持的caps進行一個排序,方便後續對比,排序之後,將會預讀取數據,分析具體的數據,是否可以使用相應的caps。下面我們將一起來看代碼。

GstCaps *
gst_type_find_helper_get_range (GstObject * obj, GstObject * parent,
    GstTypeFindHelperGetRangeFunction func, guint64 size,
    const gchar * extension, GstTypeFindProbability * prob)
{
  ...
  /* 1. 通過該函數,將會返回註冊到gstreamer core的
   * GST_TYPE_TYPE_FIND_FACTORY型GstPluginFeature */
  type_list = gst_type_find_factory_get_list ();
  
  /* 2. 將會在這裏,根據之前從uri得到的文件後綴,對Feature鏈表
   * 進行排序,文件後綴名與feature的extension信息對比,相同的
    * 將會將feature移動到表頭 */
  if (extension) {
    for (l = type_list; l; l = next) {
      ...
    }
    
  ...
  /* 3. 將會在這裏調用feature的功能探測函數預讀取數據,確認文件數據格式 */
  for (l = type_list; l; l = l->next) {
    helper.factory = GST_TYPE_FIND_FACTORY (l->data);
    gst_type_find_factory_call_function (helper.factory, &find);
    if (helper.best_probability >= GST_TYPE_FIND_MAXIMUM)
      break;
  }
  }
  ...
  
}

  可能看到上面的代碼註釋覺得很困惑,第1和第3步,調用到的究竟是什麼,看完下面就知道了。
  第一步的操作,上面已經說了,該函數將會返回GST_TYPE_TYPE_FIND_FACTORY類型的feature,這些feature都是查找類型的feature。它們在註冊feature的時候,將會註冊相應的探測函數,在探測函數中通過讀取數據,然後判斷類型,是否需要該feature可以解析處理的,就是這樣的一個功能,gstreamer纔會知道,當前數據該用那個caps。
  這些feature的註冊是在gst-plugins-base-1.xx.xx/gst/typefind目錄下的gsttypefindfunctions.c文件註冊的,在該文件的plugin_init()函數中分別通過宏TYPE_FIND_REGISTER_START_WITHTYPE_FIND_REGISTER_RIFFTYPE_FIND_REGISTER向gstreamer core註冊了相應的GST_TYPE_TYPE_FIND_FACTORY類型的feature,在註冊的時候,賦值了相應的探測function、extension等信息。接着,在我們通過gst_type_find_helper_get_range()函數探測caps的時候,就會有第一步的獲取feature,第二步的根據extension排序,第三步的通過功能探測函數確認最終的feature。
  介紹到這裏,小夥伴們應該知道gst_type_find_helper_get_range()返回的是什麼了吧,它返回的就是支持解析、處理輸入數據的caps,比如輸入的是封裝格式爲MOV的mp4文件,那麼返回的是video/quicktime;輸入的是編碼格式爲H264的RAW數據,返回的是video/x-h264,這樣說,應該大家都清楚了吧。
  回到gst_type_find_element_loop()函數,當查找到caps的時候,又將會發送相應的信號,代碼如下:

static void
gst_type_find_element_loop (GstPad * pad)
{
  if (typefind->mode == MODE_TYPEFIND) {
        ...
 
        /* 查找caps */
         ...
      }
      ...
    /* Set to MODE_NORMAL before emitting have-type, in case it triggers a seek */
    typefind->mode = MODE_NORMAL;
    gst_type_find_element_emit_have_type (typefind, probability, found_caps);
  }
  ...
}

  在gst_type_find_element_loop()函數中通過gst_type_find_element_emit_have_type()函數將caps信息生成caps EVENT然後保存在pad,接着發送have-type信號。誰來接收這個信號呢,往下看。

三、have-type信號處理

  上面說到的,typefind發送信號,哪裏接收呢。之前有提到過,這個typefind是包含在decodebin,在decodebin的init()函數創建typefind實例對象。而在decodebin的狀態切換函數,從READY切換到PAUSED的時候將會監聽該信號,接收到該函數將會調用decodebin文件的type_found()函數。

static GstStateChangeReturn
gst_decode_bin_change_state (GstElement * element, GstStateChange transition)
{
  switch (transition) {
    case GST_STATE_CHANGE_READY_TO_PAUSED:
      ...
      /* connect a signal to find out when the typefind element found
       * a type */
      dbin->have_type_id =
          g_signal_connect (dbin->typefind, "have-type",
          G_CALLBACK (type_found), dbin);
     ...
     break;
     ...
  }
  ...
}

  在type_found()函數中,最終將會調用analyze_new_pad()函數。在該函數進行了許多操作,會檢查當前decodebin的elements狀態,檢查現在的數據狀態是流通到什麼位置,typefind傳遞過來的是什麼類型的feature,是Parser還是Converter還是其他,當前的caps是什麼類型的caps,最後確認之後纔會根據caps查找feature。我們重點介紹一下根據caps查找feature,在該函數將會發送autoplug-factories信號。最終的,逐個傳遞將會在playbin真正處理這個信號,處理該信號的函數是autoplug_factories_cb()。

static gboolean
analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad,
    GstCaps * caps, GstDecodeChain * chain, GstDecodeChain ** new_chain)
{
  ...
  /* 1.d else get the factories and if there's no compatible factory goto
   * unknown_type */
  g_signal_emit (G_OBJECT (dbin),
      gst_decode_bin_signals[SIGNAL_AUTOPLUG_FACTORIES], 0, dpad, caps,
      &factories);
  ...
}

  autoplug_factories_cb()函數將會從gstreamer core中獲取所有的feature,然後對比它們的sink pad caps,如果一致,將會把該feature保存到鏈表,進行相應的檢查之後將返回feature鏈表。

static GValueArray *
autoplug_factories_cb (GstElement * decodebin, GstPad * pad,
    GstCaps * caps, GstSourceGroup * group)
{
  /* 將根據caps查找sink pad支持該caps的element返回factory_list */
  g_mutex_lock (&playbin->elements_lock);
  gst_play_bin_update_elements_list (playbin);
  factory_list =
      gst_element_factory_list_filter (playbin->elements, caps, GST_PAD_SINK,
      gst_caps_is_fixed (caps));
  g_mutex_unlock (&playbin->elements_lock);
  ...
}

  得到支持caps的element之後,回到analyze_new_pad()函數,在該函數中將會通過connect_pad()函數創建並連接該element,創建的函數是element = gst_element_factory_create (factory, NULL),得到element之後,將其添加到bin。最後就是element的狀態切換與查詢了。在這個element查找的功能,bin進行了很多工作,會有根據caps查找到的element進行不同的分類查找處理等操作,感興趣的具體可以看看代碼。
  同時的,decodebin還監控pad-added信號,在相應的element添加pad時,發送該信號,調用到decodebin的pad_added_cb()函數,在該函數中最終也還是會調用到analyze_new_pad()函數進行查找element以及link等操作,具體的操作,可以看代碼,這一塊沒有往下跟蹤了。

四、總結

  typefind的功能,並不是它單純就完成相應的element查找功能,與bin交互較多,它更多的是根據註冊的caps探測函數,查找相應的caps,然後將caps返回給bin,bin再根據caps從gstreamer core查找element並創建連接。而typefind則像完成一部分功能一樣,往前推進,繼續查找解析數據的caps。




  以上是個人理解,有理解錯誤的地方,歡迎指出,感謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章