Gstreamer学习笔记(4):pad定义、连接、流动


pad相当于element的接口,各个element就是通过pad连接进行传输数据,同时pad会通过caps限制特定的数据类型通过,只有当两个pad的caps数据类型一致时才可以建立连接。那么pad在element又是怎么创建以及使用的呢,下面一起来分析一下。

一、pad定义

在理解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连接

在上面我们已经说过,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进行连接,由于过程都类似,就不再进行分析。

三、element间数据传递

上面已经说了,element间的数据的传输都是通过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)函数完成数据的获取。但是实际究竟是如何完成的呢,继续看代码。

PUSH

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

PULL

pull模式比较少用,一般都是demux element会使用pull模式,方便处理数据。pull模式是通过gst_pad_pull_range()函数完成的,与push模式的有点类似,在gst_pad_pull_range()函数中,通过宏GST_PAD_PEER获取上游element的sink pad,然后调用gst_pad_get_range_unchecked()函数,在该函数中再通过以下的方式进行获取数据。

static GstFlowReturn
gst_pad_get_range_unchecked (GstPad * pad, guint64 offset, guint size,
    GstBuffer ** buffer)
{
  GstPadGetRangeFunction getrangefunc;
  ...
  if (G_UNLIKELY ((getrangefunc = GST_PAD_GETRANGEFUNC (pad)) == NULL))
   goto no_function;

  ret = getrangefunc (pad, parent, offset, size, &res_buf);
  ...
}

同样的,通过宏GST_PAD_GETRANGEFUNC获取到的函数,是通过宏gst_pad_set_getrange_function设置的getrangefunc函数,该函数也是在上游element类实例初始化init函数调用宏gst_pad_set_getrange_function设置的,有些element的init函数可能没有设置,但是可以看看它的父类有没有进行相应的设置。通过这样的函数调用,完成了pull模式数据获取。

决定使用模式的关键

通过上面的简单介绍,我们了解element的push和pull模式,但是,又是怎样决定使用什么模式的呢,我们继续看代码。

在看代码之前,首先大概的介绍一下,gstreamer element的四种状态,分别是 GST_STATE_NULL(默认状态)、GST_STATE_READY(准备状态)、GST_STATE_PAUSED(暂停状态)和GST_STATE_PLAYING(运行状态) 。默认状态就是初始状态,没有占用任何资源;在准备状态的时候,element就会得到它所需要的所有资源;然后在暂停状态的时候,element已经可以对数据进行处理,只不过它暂停了而已,这个时候可以设置快进等;而运行状态与暂停状态相比,只是多了时钟开始运行,数据流动。同时需要注意的是,一般情况,在element从READY到PAUSED时,将会进行pad的激活操作,也就是在这里,将会选择使用哪种模式进行数据流通,下面以qtdemux为例,进行简单的介绍。

首先,一般的在每个element class_init函数中,都会有这样的一个函数调用(如果没有,查看继承的父类有没有),如下:

static void
gst_qtdemux_class_init (GstQTDemuxClass * klass)
{
  ...
  gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qtdemux_change_state);
  ...
}

函数指针change_state赋值的是什么函数呢,其实它赋值的就是element状态转换设置函数。当bin(pipeline)进行状态转换时,将会对bin中的element逐个调用change_state函数,改变element的状态。而在大多数的element的change_state函数都将会调用父类的change_state函数,这里,将以父类为GstElement的进行选择模式的简单介绍,其他的,也类似,有兴趣的可以阅读代码跟踪流程。

首先,在qtdemux的change_state函数将会以这样的一种方式调用到父类的change_state函数。

static GstStateChangeReturn
gst_qtdemux_change_state (GstElement * element, GstStateChange transition)
{
  ...
  result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
  ...
}

然后在父类的change_state函数(同样的,在父类的class_init函数赋值),大概如下:

static GstStateChangeReturn
gst_element_change_state_func (GstElement * element, GstStateChange transition)
{
  ...
  switch (transition) {
    case GST_STATE_CHANGE_READY_TO_PAUSED:
      if (!gst_element_pads_activate (element, TRUE)) {
        result = GST_STATE_CHANGE_FAILURE;
      }
      break;
    ... 
  }
}

从上面可以看到,element从READY到PAUSED,将会调用gst_element_pads_activate()函数激活element pad。

static gboolean
gst_element_pads_activate (GstElement * element, gboolean active)
{
  GstIterator *iter;
  gboolean res;

  ...

  iter = gst_element_iterate_src_pads (element);
  res =
      iterator_activate_fold_with_resync (iter,
      (GstIteratorFoldFunction) activate_pads, &active);
  gst_iterator_free (iter);
  if (G_UNLIKELY (!res))
    goto src_failed;

  iter = gst_element_iterate_sink_pads (element);
  res =
      iterator_activate_fold_with_resync (iter,
      (GstIteratorFoldFunction) activate_pads, &active);
  gst_iterator_free (iter);
  if (G_UNLIKELY (!res))
    goto sink_failed;

  ...
  }
}

从gst_element_pads_activate()函数的实现我们可以看到,将会先通过activate_pads()函数逐个激活src pads,再激活sink pads,那么,activate_pads()函数又是如何激活pad的呢,我们继续往下看。

实际在activate_pads()函数中又是通过gst_pad_set_active (pad, *active)函数完成激活操作的,在gst_pad_set_active()函数又将会以(GST_PAD_ACTIVATEFUNC (pad)) (pad, parent)的方式调用pad的激活函数。GST_PAD_ACTIVATEFUNC获取到的函数,是element在实例初始化init函数中,通过下面的方式赋值的:

gst_pad_set_activate_function (qtdemux->sinkpad, qtdemux_sink_activate);

至此,将会通过GST_PAD_ACTIVATEFUNC调用到qtdemux的pad激活函数qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent)。以qtdemux的sink pad为例,进行PUSH和PULL模式选择流程分析。sink pad,接收上游element的接口pad,使用什么模式,不是单个pad就可以决定的,需要和它的PEER pad协商才知道,最终使用什么模式。

static gboolean
qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent)
{
  GstQuery *query;
  gboolean pull_mode;

  query = gst_query_new_scheduling ();

  /* 将会查询pad的相关函数实现信息 */
  if (!gst_pad_peer_query (sinkpad, query)) {
    gst_query_unref (query);
    goto activate_push;
  }

  /* 根据返回的信息,选择模式 */
  pull_mode = gst_query_has_scheduling_mode_with_flags (query,
      GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE);
  gst_query_unref (query);

  /* 应用模式 */
  if (!pull_mode)
    goto activate_push;

  GST_DEBUG_OBJECT (sinkpad, "activating pull");
  return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE);

activate_push:
  {
    GST_DEBUG_OBJECT (sinkpad, "activating push");
    return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE);
  }
}

在gst_pad_peer_query (sinkpad, query)函数中,将会通过宏GST_PAD_PEER取得sink pad的相连pad之后,再调用gst_pad_query (peerpad, query),而在该函数中,也将会通过GST_PAD_QUERYFUNC获取src pad的查询函数并调用。pad的查询函数在element的init函数中将会以宏gst_pad_set_query_function赋值,但是一般的,有些element并没有实现这个赋值操作,这个时候可以查看父类有没有进行这个操作。在GST_PAD_QUERYFUNC函数中,src pad将会检查自己的get_range是否支持,如果支持,将会在query增加GST_PAD_MODE_PULL模式,GST_PAD_MODE_PUSH模式也相应的检查置位。

回到qtdemux_sink_activate()函数,得到pad支持的模式之后,将会通过gst_query_has_scheduling_mode_with_flags()函数检查,是否支持PULL模式,如果支持,则跳到gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE)进行处理,相应的,如果不支持,则运行PUSH模式。最后到gst_pad_activate_mode()函数,也将会通过GST_PAD_ACTIVATEMODEFUNC (pad) (pad, parent, mode, active)函数完成pad的真正激活操作,宏GST_PAD_ACTIVATEMODEFUNC指向的函数,也都是在实例的init函数完成相应的赋值操作的,赋值是通过宏定义gst_pad_set_activatemode_function完成。在相应的activatemode_function中,将会根据push或者pull模式设置相应的标志位或者通过gst_pad_start_task()函数运行循环函数。至此,pad模式选择已经完成,同时进行了数据流通的相应的准备操作。

四、总结

简单的说,一般sink pad都是永久型的,而element的src pad,则大多是随机型的,因为element本身知道它可以处理什么类型的数据,所以sinkpad是永久型的,而输出的数据,则是需要通过解析输入数据之后才知道输出,所以是随机型的。而element link,简单理解就是element的pad保存与之相连的pad信息,然后在传输数据的时候,通过之前link保存的信息而调用到下游element sinkpad,从而完成数据传递与信息交互。

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页