GStreamer Pad and Capabilities Negotiation

Pad是什么

  • 首先pad是GStreamer Element必不可少的组成部分,你可以把它看作是element和外界(其它element)交换数据的端口。数据从element的一端流入,另一端留出(一般情形,但是,source element只有出没有进, sink只有进没有出),那么一个element就该有两个pad分别位于element的两端。从pad的角度来讲,根据数据的流入和流出方向,就该有source pad和sink pad 的区分了:

    pad 属性之一: direction(方向)
    数据流入端叫sink pad, 数据流出端叫source pad.

  • 其次, 每个element上有几个pad并不是固定的。

    • 有些pad在element的整个生命周期都存在, 这些pad 称为静态pad,也叫always pad, 顾名思义就是常在的意思。
    • 还有一些pad则是根据需要由element自行创建,比如demuxer element需要根据多媒体文件包含的数据源(Stream)动态的创建新的路径:
      GStreamer Pad and Capabilities Negotiation_第1张图片
    • 还有一类pad是由程序员手动创建的,比如tee element可以根据需要拷贝多份数据到不同的分支, 比如, 在下图中ksvideosrc是一个window下的camera source 插件,假设我们需要一边在桌边上实时播放捕捉画面,另外又需要将捕捉到的视频编码保存到本地,这时候我们就需要用到tee element,在它的输出端手动创建两个source pad,分别用来预览和编码。 假如过一段时间,你又要添加捕捉帧到静态图像的功能, 那么你可能又要再手动创建第三个source pad了:
      GStreamer Pad and Capabilities Negotiation_第2张图片

    pad 属性之二: 根据pad创建的方式可以分为:
    静态pad(always pad )
    动态pad (sometimes pad)
    手动pad (on-request pad)

Capabilities of a pad

pad的能力, 这里的能力就是可以流经这个pad的数据流格式。
我们知道GStreamer pipeline是由一系列互相连接的element组成的数据流通道, 当数据从一个element流向另外一个element时,数据类型必须是双方都能够识别的,从这个角度来讲,数据类型当然是越简单越好。 但显然element的实现者们希望它们的 element 能够尽可能的处理更多的数据类型。 鉴于多媒体数据类型的庞杂, 因此需要一套管理element之间数据类型协商的机制。由于GStreamer element之间的连接是基于pad的, 因此处理数据的能力就定义在pad上,而不是element上。

GstPadTemplate

一个element的实现, 需要注册一个或多个GstPadTemplate,然后才能够通过这些template创建pad。 下面的代码是从GStreamer插件指南里面截取的一段代码,用来实现GstMyFilter element:

//GstPadTemplate for sink pad
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

以上我们通过_init构造函数创建了GstMyFilter的两个pad,当然这只是创建pad的一种方式。给element 创建新的pad不一定要在element实现内部完成,在外面也同样可以为一个已经存在的element实例创建pad, 这里暂不关心pad 是怎么被创建出来的, 我们只要知道我们GStreamer Pipeline中的所有elements都链接好了,接下来该是Caps Negotiation粉墨登场了。

Caps Negotiation定义了一套element之间互相询问,回答与事件处理机制。 这里有两个概念:

  • 查询 Query
  • 事件 Event

查询

GStreamer内部的Query可以有很多种, 这里主要涉及到

  • GST_QUERY_CAPS
  • GST_QUERY_ACCEPT_CAPS
GST_QUERY_CAPS

GST_QUERY_CAPS 用来查询相邻pad所支持的caps:

   GstCaps * gst_pad_peer_query_caps (GstPad *pad,GstCaps *filter);

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;
    }
    ...
}

处理的时候要注意以下几点:

  1. element2在创建返回caps列表的时候应该考虑相邻元素,最理想的状态是返回的caps能够被pipeline上所有的element支持。
  2. 假设输入的filter不为NULL,则只能返回与filter匹配的caps
  3. 假设输入的filter包含多个caps, 则 caps 的先后次序反应查询者预期的caps 优先级, 在创建返回列表之时, 应将优先级高的caps放在列表前端。
GST_QUERY_ACCEPT_CAPS

看完了GST_QUERY_CAPS 查询, 再来看看GST_QUERY_ACCEPT_CAPS又是怎么回事呢?

GStreamer Pad and Capabilities Negotiation_第3张图片

当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;
        }
        ...
    }

貌似此时我们的能力协商工作已经完成了, 下图总结了此过程:

GStreamer Pad and Capabilities Negotiation_第4张图片

GST_EVENT_RECONFIGURE

在以上的能力协商中,发起查询,发送事件的都是上游element, 下游element则一直回复和处理这些请求或事件。 最终fixed caps 也是由上游确定的,那么下游element 有没有办法发起一次能力协商或者有自己的意志去选择合适的数据类型呢? 这就是GST_EVENT_RECONFIGURE事件的作用, 它是由下游发送给上游。

一个GST_EVENT_RECONFIGURE事件的发起首先由下游确定一个fixed caps, 然后通过GST_QUERY_ACCEPT_CAPS查询上游是否支持,如果支持的话再发送GST_EVENT_RECONFIGURE事件,上游的处理这是发起一次新的能力协商,在上游查询GstCaps列表时,这次下游这只返回选定的fixed caps.

GStreamer Pad and Capabilities Negotiation_第5张图片

你可能感兴趣的:(gstreamer)