Gstreamer学习笔记(5):GStreamer Pad and Capabilities Negotiation


1、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)动态的创建新的路径:
      这里写图片描述
    • 还有一类pad是由程序员手动创建的,比如tee element可以根据需要拷贝多份数据到不同的分支, 比如, 在下图中ksvideosrc是一个window下的camera source 插件,假设我们需要一边在桌边上实时播放捕捉画面,另外又需要将捕捉到的视频编码保存到本地,这时候我们就需要用到tee element,在它的输出端手动创建两个source pad,分别用来预览和编码。 假如过一段时间,你又要添加捕捉帧到静态图像的功能, 那么你可能又要再手动创建第三个source pad了:
      这里写图片描述

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


2、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), 非常直观的表述,目的就是要确定最终的格式和取值。


3、Caps Negotiation

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

Caps协商是在它们可以处理的element之间寻找媒体格式(GstCaps)的行为。GStreamer中的这个过程在大多数情况下可以为整个管道找到一个最佳解决方案。在本节中,我们将解释其工作原理。

在GStreamer中,媒体格式的协商总是遵循以下简单规则:

  • downstream的element可以通过其sinkpad给出建议的格式, 在其sinkpad收到CAPS query后,将其建议的格式作为结果返回。参见CAPS查询函数的实现。

  • 一个upstream element可以决定某种格式,该element可以将其选定的格式以CAPS event的方式通过其source pad发送向downstream。Downstream的element在其sinkpad收到CAPS event所发送的选定格式后,对自己做重新配置。

  • 一个downstream的element可以通过发送RECONFIGURE event给upstream,通过这种方式提出自己的建议格式。RECONFIGURE event命令upstream element重启negotiation。因为发送RECONFIGURE事件的元素现在建议使用另一种格式,所以管道中的格式可能会改变。

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

  • 查询 Query
  • 事件 Event

3.1 查询

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又是怎么回事呢?ACCEPT_CAPS查询,用于快速检查某个element是否可以接受特定的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内部筛选出的结果。

3.2 事件

这里也同样涉及两个事件:

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

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

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.

这里写图片描述

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我会尽力回答你的问题。关于通过UDP传输音视频,我了解一些相关的知识,下面是一些学习笔记: 1. gstreamer是一个流媒体框架,用于创建、处理和播放多媒体流。它支持多种音视频格式,可以通过插件扩展功能。 2. 通过gstreamer可以使用UDP协议传输音视频数据。UDP协议是一种无连接的协议,不保证数据传输的可靠性和顺序性,但是传输效率高。 3. 首先需要创建一个gstreamer的pipeline,包括音视频源、编码器、UDP发送端等组件。例如: ``` gst-launch-1.0 -v filesrc location=test.mp4 ! decodebin ! x264enc ! rtph264pay ! udpsink host=192.168.1.100 port=5000 ``` 这个pipeline的作用是从test.mp4文件读取音视频流,解码后使用x264编码器进行压缩,然后使用rtph264pay将数据打包成RTP数据包,最后通过udpsink发送到指定的IP地址和端口。 4. 接收端需要创建一个gstreamer的pipeline,包括UDP接收端、解包器、解码器等组件。例如: ``` gst-launch-1.0 -v udpsrc port=5000 ! application/x-rtp, payload=96 ! rtpjitterbuffer ! rtph264depay ! avdec_h264 ! autovideosink ``` 这个pipeline的作用是从UDP端口5000接收音视频数据,使用rtpjitterbuffer解决网络抖动问题,使用rtph264depay将RTP数据包解包成原始的H.264数据流,然后使用avdec_h264解码器进行解码,最后使用autovideosink播放视频。 5. 在实际使用过程中,还需要考虑数据的带宽限制、网络延迟等问题,以保证音视频传输的效果。 希望这些笔记能对你有帮助。如果你还有其他问题,可以继续问我。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值