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)
中,主要完成以下操作:
-
将会通过
gst_element_iterate_sink_pads(element)
或者gst_element_iterate_src_pads (element)
函数element相应的pad; -
通过
temp = gst_pad_query_caps (pad, NULL)
得到pad的所有caps; -
同样的,通过
temp = gst_pad_query_caps (current, NULL)
得到element pad的所有caps; -
通过
compatible = gst_caps_can_intersect (temp, intersection)
检查,pad的caps与element pad caps数据类型是否一致; -
如果第4步返回的是一致,则从element中找到了可以与pad连接的element pad,返回element pad;
-
如果循环之后都没有找到,将会尝试请求型的pad,最终都没有将会返回NULL。
在调用gst_element_get_compatible_pad()函数之后,得到dest中可以与srcpad相连的destpad,接下来将会通过pad_link_maybe_ghosting (srcpad, temp, flags)
函数进行pad连接,详细操作如下:
-
先通过
prepare_link_maybe_ghosting()
函数检查pad的element是否存在同一个parent,需要存在同一个parent才可以进行下一步连接,也正是这个函数限制了,element需要在同一个pipeline才可以link; -
通过
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,从而完成数据传递与信息交互。