pad連接與數據流動

pad定義

  pad相當於element的接口,各個element就是通過pad連接進行傳輸數據,同時pad會通過caps限制特定的數據類型通過,只有當兩個pad的caps數據類型一致時纔可以建立連接。那麼pad在element又是怎麼創建以及使用的呢,下面一起來分析一下。

在理解pad的定義之前,我們先來看看,pad都有那些信息。

Pad Templates:
  SINK template: 'sink'                    ------>sink pad:數據流入
    Availability: Always                   ------>pad時效性:永久型
    Capabilities:                          ------>pad支持的caps
      video/quicktime
      video/mj2
      audio/x-m4a
      application/x-3gp

  SRC template: 'video_%u'                 ------>src pad:數據流出
    Availability: Sometimes                ------>pad時效性:隨機型
    Capabilities:
      ANY

  SRC template: 'audio_%u'
    Availability: Sometimes
    Capabilities:
      ANY

  SRC template: 'subtitle_%u'
    Availability: Sometimes
    Capabilities:
      ANY

從上面可以看到,每個pad,都會有以下屬性:padname、direction、presence、caps。

  • padname:pad名稱
  • direction:pad的輸入輸出方向,有src和sink兩種
  • presence:pad的時效性,有永久型GST_PAD_ALWAYS、隨機型GST_PAD_SOMETIMES、請求型GST_PAD_REQUEST,請求型的僅在gst_element_request_pad()調用,隨機型的則是會根據不同的輸入數據使用不同的pad
  • caps:pad支持的功能

  那麼,一般情況我們是如何定義一個pad呢,我們以qtdemux的sink pad爲例進行說明。在qtdemux.c可以看到以下定義:

 static GstStaticPadTemplate gst_qtdemux_sink_template =
    GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/quicktime; video/mj2; audio/x-m4a; "
        "application/x-3gp")
    );

通過上面我們定義了一個靜態的pad模板,而GST_STATIC_PAD_TEMPLATE宏展開只是簡單的花括號{}。那麼,在這裏定義了pad模板,哪裏使用,與element綁定呢?往下看。
  在qtdemux的class_init()函數中,可以看到以下代碼:

static void
gst_qtdemux_class_init (GstQTDemuxClass * klass)
{
    gobject_class = (GObjectClass *) klass;
    ...
    /* 就是在這裏完成相應的pad添加 */
    gst_element_class_add_static_pad_template (gstelement_class,
      &gst_qtdemux_sink_template);
    ...
 }

 其實在gst_element_class_add_static_pad_template()函數中,也是通過調用gst_element_class_add_pad_template()完成相應的pad添加。pad具體的添加流程,又是怎樣的呢,在GstElementClass中,有一個GList *padtemplates,所以,在gst_element_class_add_pad_template()函數中就是簡單的將pad添加到padtemplates,這樣就完成了elementClass的pad添加。
  同時,在element的實例初始化函數init中,也會通過gst_element_add_pad()函數添加pad到element。

static void
gst_qtdemux_init (GstQTDemux * qtdemux)
{
  ...
  gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), qtdemux->sinkpad);
  ...
}

  在gst_element_add_pad()函數中,將會檢查pad的name是否已經在該element存在,如何沒有,將會把pad的parent設置爲該element,同時根據pad的direction類型添加到element->srcpads或者element->sinkpads,最終都會保存到element->pads。

pad連接

    gst_pad_link_unchecked跟gst_pad_link的區別?一個是不檢查屬性,一個檢查屬性。

    GST_OBJECT_LOCK (srcpad);     多線程的情況,要加鎖。

 在上面我們已經說過,pad相當與element的接口,那麼element間的連接,實質上就是pad間的連接,caps適配,那麼這個連接流程又是怎樣的呢,讓我們來一起探討一下。
  在應用程序中,可以通過gboolean gst_element_link (GstElement * src, GstElement * dest)函數完成element連接,最終會調用到gst_element_link_pads_full (src, srcpadname, dest, destpadname, GST_PAD_LINK_CHECK_DEFAULT)。那麼,下面我們來分析一下gst_element_link_pads_full()函數。
  因爲gst_element_link_pads_full()函數代碼量有點大,就不上代碼,分析一下關鍵函數調用。

gboolean
gst_element_link_pads_full (GstElement * src, const gchar * srcpadname,
    GstElement * dest, const gchar * destpadname, GstPadLinkCheck flags)
{
  /* 一般的,srcpadname爲NULL,所以會獲取src的所有pads,這個就是在實例初始化時添加的,
 1. 如果srcpadname不爲空,則會根據相應的pad name獲取pad */
  srcpads = GST_ELEMENT_PADS (src);
  srcpad = srcpads ? GST_PAD_CAST (srcpads->data) : NULL;
  ...
  /* 同理的,dest進行相應的操作 */
  destpads = GST_ELEMENT_PADS (dest);
  destpad = destpads ? GST_PAD_CAST (destpads->data) : NULL;
  ...
  /* 接下來將會檢查src pads中,是否有pad是還沒有連接的,沒有連接的,將會與dest的pads進行逐一適配,
 2. 如果循環src的發現都沒有,又將會循環dest的pad,進行相同的操作,
 3. 最終都沒有合適的pad相連,將會查看,是否存在請求型pad,再進行嘗試連接 */
  do {
    ...
    if ((GST_PAD_DIRECTION (srcpad) == GST_PAD_SRC) &&
        (GST_PAD_PEER (srcpad) == NULL)) {
      /* 獲取可以相連的pad */
      temp = gst_element_get_compatible_pad (dest, srcpad, NULL);
      ...
      /* 進行pad連接操作 */
      if (temp && pad_link_maybe_ghosting (srcpad, temp, flags)) {
        ...
      }
      ...
    }
    ...
  }while (srcpads);
  ...
}

  在gst_element_get_compatible_pad(GstElement * element, GstPad * pad, GstCaps * caps)中,主要完成以下操作:
   1. 將會通過gst_element_iterate_sink_pads(element)或者gst_element_iterate_src_pads (element)函數element相應的pad;
   2. 通過temp = gst_pad_query_caps (pad, NULL)得到pad的所有caps;
   3. 同樣的,通過temp = gst_pad_query_caps (current, NULL)得到element pad的所有caps;
   4. 通過compatible = gst_caps_can_intersect (temp, intersection)檢查,pad的caps與element pad caps數據類型是否一致;
   5. 如果第4步返回的是一致,則從element中找到了可以與pad連接的element pad,返回element pad;
   6. 如果循環之後都沒有找到,將會嘗試請求型的pad,最終都沒有將會返回NULL。

  在調用gst_element_get_compatible_pad()函數之後,得到dest中可以與srcpad相連的destpad,接下來將會通過pad_link_maybe_ghosting (srcpad, temp, flags)函數進行pad連接,詳細操作如下:
   1. 先通過prepare_link_maybe_ghosting()函數檢查pad的element是否存在同一個parent,需要存在同一個parent纔可以進行下一步連接,也正是這個函數限制了,element需要在同一個pipeline纔可以link;
   2. 通過gst_pad_link_full (src, sink, flags)link src pad和sink pad;
    a. 通過parent發送GST_STRUCTURE_CHANGE_TYPE_PAD_LINK消息,這裏發送的消息,bin(pipelin)將會接收到該消息;

 gst_element_post_message (parent,
          gst_message_new_structure_change (GST_OBJECT_CAST (sinkpad),
              GST_STRUCTURE_CHANGE_TYPE_PAD_LINK, parent, TRUE));

    b. 通過檢查pad可用之後,將會通過GST_PAD_PEER設置srcpad和sinkpad,此時相當於已經link;
    c. 通過schedule_events (srcpad, sinkpad)函數檢查srcpad、sinkpad是否有不一樣的event,sink不存在的event都需要做好標記received = FALSE;
    d. 通過g_signal_emit()函數發送gst_pad_signals[PAD_LINKED]信號,完成連接,這裏發送的信號,又是在實例初始化時,設置各自的pad的接收設置,部分pad並沒有設置接收函數,採用默認的,也將是接收就釋放;
    e. 最後通過gst_pad_send_event (srcpad, gst_event_new_reconfigure ())發送相應的event,srcpad發送的事件,將會在srcpad的event_function處理,這個函數看看相應的element有沒有重載,如果沒有,基本上就是接收到事件就進行事件釋放;

  在gst_pad_link_full()函數完成pad link之後,回到gst_element_link_pads_full()函數,在完成link之後,其實就是釋放之前申請的object,返回link成功,至此,element link完成。上面介紹的,是通過dest element連接srcpad,如果這一步沒有成功的link,又將會進行src element連接destpad,如果以上兩步都沒有完成相應的element link,則是按照之前說的,嘗試通過請求型的pad進行連接,由於過程都類似,就不再進行分析。

PUSH

    ement間的數據的傳輸都是通過pad的,那麼,究竟是如何進行數據傳遞的呢,下面我們來看看。
pad具有兩種模式,分別是PUSH和PULL。PUSH模式,就是由上游element控制傳輸數據的大小與速度,將數據推送到下游element,所以下游的element一般都會設置一個緩衝區來接收數據,PUSH模式一般是通過gst_pad_push (GstPad * pad, GstBuffer * buffer)函數完成數據傳遞的操作;而PULL模式呢,它就是由下游的element告訴上游element需要的數據量,PULL模式通過gst_pad_pull_range (GstPad * pad, guint64 offset, guint size, GstBuffer ** buffer)函數完成數據的獲取。但是實際究竟是如何完成的呢,繼續看代碼。

在element處理完數據之後,需要將數據傳遞給下游,那麼,就是通過gst_pad_push()函數完成這個操作,看看它的具體實現,而gst_pad_push()函數也是通過調用gst_pad_push_data (pad, GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH, buffer)函數完成操作的。 在gst_pad_push_data()函數中,在檢查pad的狀態、模式是否正確之後,通過宏定義GST_PAD_PEER取得pad的下游element sink pad,然後調用gst_pad_chain_data_unchecked (peer, type, data),最後,將會在該函數中,將數據push到下游。
static inline GstFlowReturn
gst_pad_chain_data_unchecked (GstPad * pad, GstPadProbeType type, void *data)
{
  ...
  {
    GstPadChainFunction chainfunc;

    /* 通過宏GST_PAD_CHAINFUNC獲取chainfunc */
    if (G_UNLIKELY ((chainfunc = GST_PAD_CHAINFUNC (pad)) == NULL))
      goto no_function;

    /* 調用chainfunc函數 */
    ret = chainfunc (pad, parent, GST_BUFFER_CAST (data));
  }
  ...
}

具體的是怎麼傳輸數據的呢,GST_PAD_CHAINFUNC獲取到的是什麼函數呢,需要注意的,該函數的pad是peer喔,也就是說,這個是下游的sink pad,通過GST_PAD_CHAINFUNC獲取到的,就是下游element在類實例初始化init函數通過宏gst_pad_set_chain_function設置的GstPadChainFunction。看到這裏,不知道大家明白了沒,PUSH模式,實質就是在上游element的src pad中,通過GST_PAD_PEER獲取到下游element的sink pad,然後調用下游sink pad的chain函數,這樣來達到數據傳遞,簡單來說就是函數指針鏈表,從上往下逐個調用,直至最後,調用之後再逐個返回,push數據完成。

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