gs--插件高級

插件的註冊

GST_PLUGIN_DEFINE

GST_PLUGIN_DEFINE_STATIC
gst_plugin_register_static

PAD

 

CAP協商

插件的協商在gst_pad_alloc_buffer裏。

 

三大類:靜態pad(always pad ),動態pad (sometimes pad),手動pad (on-request pad)
#每個element上有幾個pad並不是固定的。
#有些pad在element的整個生命週期都存在, 這些pad 稱爲靜態pad,也叫always pad, 顧名思義就是常在的意思。
#還有一些pad則是根據需要由element自行創建,比如demuxer element需要根據多媒體文件包含的數據源(Stream)動態的創建新的路徑:
#還有一類pad是由程序員手動創建的,比如tee element可以根據需要拷貝多份數據到不同的分支, 比如, 在下圖中ksvideosrc是一個window下的camera source 插件,假設我們需要一邊在桌邊上實時播放捕捉畫面,另外又需要將捕捉到的視頻編碼保存到本地,這時候我們就需要用到tee element,在它的輸出端手動創建兩個source pad,分別用來預覽和編碼。 假如過一段時間,你又要添加捕捉幀到靜態圖像的功能, 那麼你可能又要再手動創建第三個source pad了:
    pad的能力, 這裏的能力就是可以流經這個pad的數據流格式。
我們知道GStreamer pipeline是由一系列互相連接的element組成的數據流通道, 當數據從一個element流向另外一個element時,數據類型必須是雙方都能夠識別的,從這個角度來講,數據類型當然是越簡單越好。 但顯然element的實現者們希望它們的 element 能夠儘可能的處理更多的數據類型。 鑑於多媒體數據類型的龐雜, 因此需要一套管理element之間數據類型協商的機制。由於GStreamer element之間的連接是基於pad的, 因此處理數據的能力就定義在pad上,而不是element上。
GstPadTemplate
一個element的實現, 需要註冊一個或多個GstPadTemplate,然後才能夠通過這些template創建pad。 下面的代碼是從GStreamer插件指南里面截取的一段代碼,用來實現GstMyFilter element:
static GstStaticPadTemplate sink_factory =
   GST_STATIC_PAD_TEMPLATE(
      "sink",        //GstPadTemplate的名字
      GST_PAD_SINK,  //可從該template創建的pad 方向屬性 (sink, source)
      GST_PAD_AWAYS, //可從該template創建的pad 創建方式屬性(always, sometimes, on-request)
      GST_STATIC_CAPS(
         "audio/x-raw, "
         "format = (string) " GST_AUDIO_NE(S16) ", "
         "channels = (int) { 1, 2}, "
         "rate = (int) [ 8000, 96000 ]"  ) //可從該template創建的pad支持數據類型列表
     );
 
//GstPadTemplate for src pad
static GstStaticPadTemplate src_factory =
   GST_STATIC_PAD_TEMPLATE(...);
 
//這裏註冊GstPadTemplate  
static void gst_my_filter_class_init( GstMyFilterClass* klass)
{
     GstElementClass* element_class = GST_ELEMENT_CLASS(klass);
     ...
    gst_element_class_add_pad_template( element_class, gst_static_pad_template_get(&sink_factory));
    gst_element_class_add_pad_template( element_class, gst_static_pad_template_get(&src_factory));
}
這裏可以看出GstMyFilter element註冊了兩個GstPadTemplate.
每個GstPadTemplate有4項構成,其中我們最關注的當然是最後一項,包含了從該template創建的pad所能夠支持的數據類型列表。我們抽取這部分進行分析
 "audio/x-raw, "
 "format = (string) " GST_AUDIO_NE(S16) ", "
 "channels = (int) { 1, 2}, "
 "rate = (int) [ 8000, 96000 ]"
這是列表裏面的一項, 當然列表可能包含不僅僅一項,而有可能包含多項, 每項用一個字符串表示,每項之間用”;“隔開。 每項內部屬性與屬性之間則用”,“分隔。

    "audio/x-raw": 支持未經壓縮的raw audio輸入
    "format = (string) "GST_AUDIO_NE(S16) ", ": 整形16bit audio數據
    "channels = (int) { 1, 2}, ": 單聲道或者立體聲道, {}: 列表
    "rate = (int) [ 8000, 96000 ]": 支持範圍在8000-96000bps之間的傳輸率, [ ]: 範圍

另外創建方式是GST_PAD_AWAYS, 這就說明這兩個pad是在GstMyFilter被創建的時候就已經存在並且在GstMyFilter實例的整個生命週期都將存在,因此在GstMyFilter的_init函數中:
    static void gst_my_filter_init( GstMyFilter* filter)
    {
        filter->sinkpad = gst_pad_new_from_static_template(&sink_factory , "sink");
        gst_pad_set_event_function(filter->sinkpad, gst_my_filter_sink_event);
        gst_pad_set_query_function(filter->sinkpad, gst_my_filter_sink_query);
        gst_element_add_pad( GST_ELEMENT(filter), filter->sinkpad);
     
        filter->srcpad = gst_pad_new_from_static_template(&src_factory, "src");
        gst_element_add_pad( GST_ELEMENT(filter), filter->srcpad);
    }
_init函數相當於GstMyFilter的構造函數,對每個實例,從註冊的template創建一個sink pad, 一個src pad.

到此爲止, 我們通過 gst_element_class_add_pad_template 註冊了GstPadTemplate, 再在構造函數裏面通過這些template創建pad的實例。但事情還沒完, 我們看到我們註冊的template,它們可能支持多種媒體格式,對於每個格式某些屬性的取值範圍也不是固定的,那當我們將兩個pad連接起來的時候,到底怎麼確定最終的媒體格式和確定屬性的最終值呢? 這就涉及到了GStreamer中另外一個重要概念: 能力協商(Caps Negotiation), 非常直觀的表述,目的就是要確定最終的格式和取值。

Caps Negotiation

Caps Negotiation定義了一套element之間互相詢問,回答與事件處理機制。 這裏有兩個概念:
    查詢 Query
    事件 Event
查詢
GStreamer內部的Query可以有很多種, 這裏主要涉及到
    GST_QUERY_CAPS
    GST_QUERY_ACCEPT_CAPS
gst_pad_peer_query_caps(srcpad, filter)是srcpad向sinkpad發出的一個GST_QUERY_CAPS查詢,旨在獲取element2 sinkpad端所支持的所有符合條件的caps集合。 這裏符合條件是指能夠通過filter 過濾的caps, 但是如果filter = NULL, 則會返回所有支持的caps.
那麼element2收到GST_QUERY_CAPS查詢是怎麼返回caps的呢? 不知道大家有沒有注意到上面我們在GstMyFilter _init函數裏面有這麼一行:
gst_pad_set_query_function(filter->sinkpad, gst_my_filter_sink_query);
這裏註冊一個query回調函數 gst_my_filter_sink_query, element2可以在這個函數裏面處理所有對element2的查詢:
static gboolean gst_my_filter_sink_query( GstPad *pad, GstObject *parent, GstQuery *query)
{   
    ...
    GstMyFilter* filter = GST_MY_FILTER(parent);
 
    switch( GST_QUERY_TYPE( query ) )
    {
        case GST_QUERY_CAPS:
         //這裏是處理的地方 ...
         //並將查詢轉發給下游
         GstCaps* filter;
         gst_query_parse_caps( query, &filter);
         gst_pad_peer_query_caps(filter->srcpad, filter);
         break;
        case GST_QUERY_ACCEPT_CAPS:
         //處理GST_QUERY_ACCEPT_CAPS ...
         break;
        default:
         gst_pad_query_default( pad, parent, query );
         break;
    }
    ...
}

處理的時候要注意以下幾點:
    element2在創建返回caps列表的時候應該考慮相鄰元素,最理想的狀態是返回的caps能夠被pipeline上所有的element支持。
    假設輸入的filter不爲NULL,則只能返回與filter匹配的caps
    假設輸入的filter包含多個caps, 則 caps 的先後次序反應查詢者預期的caps 優先級, 在創建返回列表之時, 應將優先級高的caps放在列表前端。
GST_QUERY_ACCEPT_CAPS
看完了GST_QUERY_CAPS 查詢, 再來看看GST_QUERY_ACCEPT_CAPS又是怎麼回事呢?
當element2返回caps列表之後, element1 需要遍歷列表並選出最優選線(a fixed caps), 並再次向element2發出查詢,以下代碼來自element1:
    GstCaps* elem1_src_caps = gst_pad_query_caps( srcpad ); //首先獲取srcpad自身caps
    GstCaps* elem2_sink_candidates = gst_pad_peer_query_caps(srcpad, elem1_src_caps);
     
    foreach( GstCaps* candidate, elem2_sink_candidates )
    {
        //這裏將candidate內部屬性取值固定
        GstCaps* fixed_caps = gst_pad_fixate_caps( srcpad, candidate );
     
       //再次發送GST_QUERY_ACCEPT_CAPS
       if( gst_pad_peer_query_accept_caps(srcpad, fixed_caps) )
       {
           ...
       }
     
    }
element2 對於 GST_QUERY_ACCEPT_CAPS查詢的處理可以不必轉發給下游,而是根據自身情況決定返回值。因爲此時上游發過來的fixed caps已經是從element2返回的caps內部篩選出的結果。
事件

這裏也同樣涉及兩個事件:

    GST_EVENT_CAPS
    GST_EVENT_RECONFIGURE
GST_EVENT_CAPS
這是上游element通知下游element,一個fixed caps 已經被選定,各位應該做好相應的準備,等着接受由上游傳遞的buffer來處理吧 ,buffer的格式則由這個指定的fixed caps限定了。
從element1發送GST_EVENT_CAPS:
       if( gst_pad_peer_query_accept_caps(srcpad, fixed_caps) )
       {
          gst_pad_push_event( srcpad, gst_event_new_caps( fixed_caps ));
       }
element2的處理則放在_init 註冊的事件處理函數中:
    gst_pad_set_event_function(filter->sinkpad, gst_my_filter_sink_event);
 
    static gboolean gst_my_filter_sink_event( GstPad *pad, GstObject *parent, GstEvent *event)
    {
        ...
        GstMyFilter* filter = GST_MY_FILTER(parent);
 
        switch( GST_EVENT_TYPE(event))
        {
           case GST_EVENT_CAPS:
             //這裏做好處理
             //並將事件按轉發給下游
             gst_pad_push_event( filter->srcpad, event );
             break;
           default:
             gst_pad_event_default( pad, parent, event);
             break;
        }
        ...
    }
貌似此時我們的能力協商工作已經完成了, 下圖總結了此過程:

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