pad连接与数据流动

gst_pad_push_data函数分析

gst_pad_push_data{

  gst_pad_chain_data_unchecke(GstPad * pad, gboolean is_buffer, void *data,

    GstPadPushCache * cache){

      caps = gst_pad_data_get_caps (is_buffer, data);

      caps_changed = caps && caps != GST_PAD_CAPS (pad);

       //查看caps跟pad的caps是否一致。

        if (G_UNLIKELY (caps_changed)) {

           gst_pad_configure_sinkGstPad * pad, GstCaps * caps{

              /* set caps on pad if call succeeds */

               gst_pad_set_caps(pad, caps{

                /* call setcaps function to configure the pad only if the

                * caps is not NULL */

                 xxx_set_caps(){

                 }

              }     

           }

        }

  }

}

 

pad定义

  pads的功能通过GstCaps 对象来进行描述。一个GstCaps对象会包含一个或多个 GstStructure。一个 GstStructure描述一种媒体类型。一个被数据流通过的pad(negotiated pad)存在功能集(capabilities set),每种功能只包含一个GstStructure结构。结构中只包含固定的值。但上述约束并不对尚未有数据流通过的pads(unnegotiated pads)或pads模板有效。

根据条目多少和属性,caps可分为:
简单caps:只含一条structure
固定caps:含一条structure,并且属性值没有range之类可变化的
任意caps和空caps是两种特殊cap一个pads能够有多个功能。功能(GstCaps)可以用一个包含一个或多个 GstStructures 的数组来表示。每个GstStructures由一个名字字符串(比如说 "width")和相应的值(类型可能为G_TYPE_INT或GST_TYPE_INT_RANGE)构成。

        值得注意的是,这里有三种不同的pads的功能(capabilities)需要区分:pads的可能功能(possible capabilities)(通常是通过对pads模板使用gst-inspect 得到),pads的允许功能(allowed caps)(它是pads模板的功能的子集,具体取决于每对交互pads的可能功能), pads的最后协商功能(lastly negotiated caps)(准确的流或缓存格式,只包含一个结构,以及没有像范围值或列表值这种不定变量)。

        你可以通过查询每个功能的结构得到一个pads功能集的所有功能。你可以通过gst_caps_get_structure()得到一个功能的 GstStructure,通过gst_caps_get_size()得到一个GstCaps对象中的GstStructure数量。

        简易pads的功能(capabilities)(simple caps )是指仅有一个GstStructure,固定衬垫的功能(capabilities)(fixed caps)指其仅有一个GstStructure,且没有可变的数据类型(像范围或列表等)。另外还有两种特殊的功能 - 任意pads的功能(capabilities)(ANY caps)和空pads的功能(capabilities)(empty caps)。

下面的例子演示了如何从一个固定的视频功能提取出宽度和高度信息:

static void  read_video_props (GstCaps *caps)
{
  gint width, height;
  const GstStructure *str;
  g_return_if_fail (gst_caps_is_fixed (caps));
  str = gst_caps_get_structure (caps, 0);
  if (!gst_structure_get_int (str, "width", &width) ||
      !gst_structure_get_int (str, "height", &height)) {
    g_print ("No width/height available\n");
    return;
  }
  g_print ("The video size of this set of capabilities is %dx%d\n",
  width, height);
}
        由于pads的功能(capabilities)常被包含于插件(plugin)中,且用来描述pads支持的媒体类型,所以程序员在为了在插件(plugin)间 进行交互时,尤其是使用过滤功能(filtered caps)时,通常需要对pads功能有着基本的理解。当你使用过滤功能(filtered caps)或固定功能(fixation)时,你就对交互的pads间所允许的媒体类型做了限制,限制其为交互的pads所支持的媒体类型的一个子集。你可以通过 在管道中使用capsfilter element实现上述功能,而为了做这些,你需要创建你自己的GstCaps。这里我们给出最容易的方法是,你可以通过gst_caps_new_simple()函数来创建你自己的GstCaps。

static gboolean  link_elements_with_filter (GstElement *element1, GstElement *element2)
{
  gboolean link_ok;
  GstCaps *caps;
  caps = gst_caps_new_simple ("video/x-raw-yuv",
      "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC (‘I‘, ‘4‘, ‘2‘, ‘0‘),
      "width", G_TYPE_INT, 384,
      "height", G_TYPE_INT, 288,
      "framerate", GST_TYPE_FRACTION, 25, 1,
      NULL);
  link_ok = gst_element_link_filtered (element1, element2, caps);
  gst_caps_unref (caps);
  if (!link_ok) {
    g_warning ("Failed to link element1 and element2!");
  }
  return link_ok;
}

        上述代码会将两个elment间交互的数据限制为特定的视频格式、宽度、高度以及帧率(如果没达到这些限制条件,两个 elemment会连接失败)。请记住:当你使用 gst_element_link_filtered()时,Gstreamer会自动创建一个capsfilter element, 将其加入Bins或管道中, 并插入到你想要交互的两个element间。(当你想要断开两个element的连接时,你需要注意到这一点)。

在某些情况下,当你想要在两个pads间创建一个更精确的带过滤连接的功能集时,你可以用到一个更精简的函数- gst_caps_new_full ():
static gboolean ink_elements_with_filter (GstElement *element1, GstElement *element2)
{
  gboolean link_ok;
  GstCaps *caps;
  caps = gst_caps_new_full (
      gst_structure_new ("video/x-raw-yuv",
"width", G_TYPE_INT, 384,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, 25, 1,
NULL),
      gst_structure_new ("video/x-raw-rgb",
"width", G_TYPE_INT, 384,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, 25, 1,
NULL),
 NULL);
  link_ok = gst_element_link_filtered (element1, element2, caps);
  gst_caps_unref (caps);
  if (!link_ok) {
    g_warning ("Failed to link element1 and element2!");
  }
  return link_ok;
}

        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(pipeline)将会接收到该消息;

 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数据完成。

BUF的处理 Processing

A transform has 2 main processing functions:
transform(): Transform the input buffer to the output buffer. The output buffer is guaranteed to be writable and different from the input buffer.
transform_ip(): Transform the input buffer in-place. The input buffer is writable and of bigger or equal size than the output buffer.

A transform can operate in the following modes:
passthrough: The element will not make changes to the buffers, buffers are pushed straight through, caps on both sides need to be the same. The element can optionally implement a transform_ip() function to take a look at the data, the buffer does not have to be writable.
in-place: Changes can be made to the input buffer directly to obtain the output buffer. The transform must implement a transform_ip() function.
copy-transform: The transform is performed by copying and transforming the input buffer to a new output buffer. The transform must implement a transform() function.


When no transform() function is provided, only in-place and passthrough operation is allowed, this means that source and destination caps must be equal or that the source buffer size is bigger or equal than the destination buffer.
When no transform_ip() function is provided, only passthrough and copy-transforms are supported. Providing this function is an optimisation that can avoid a buffer copy.
When no functions are provided, we can only process in passthrough mode.

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值