caps指pad的capabilities,翻译为该pad支持的规格。每一个组件都有自己可以处理的数据规格,规定了当前元素的功能和支持的参数,比如常见的音视频格式、比特率等。
在元素连接的过程中,相邻的元素需要协商彼此可以衔接的数据格式,以便将对应的数据传给对方。比如解码器支持输出RGB和YUV,显示组件支持YUV,两者协商,决定使用YUV的数据格式。
caps协商主要通过问询和事件机制来实现. caps规格协商分为三类:固定协商、传输协商、动态协商。
1.固定协商
src pad只提供一种规格,下游元素不能请求其它的规格,则其实就直接设定死了,并不需要协商过程。比如typefinder或者filesrc,只是读取二进制数据,不存在更多的格式。
主要流程:
- gst_pad_use_fixed_caps (src_pad),标记规格是固定
- gst_pad_set_caps,设定src_pad的规格,通知到下游节点
/* capsnego */
caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, GST_MPC_FORMAT, "layout", G_TYPE_STRING, "interleaved",
"channels", G_TYPE_INT, i.channels, "rate", G_TYPE_INT, i.sample_freq, NULL);
gst_pad_use_fixed_caps (musepackdec->srcpad);
if (!gst_pad_set_caps (musepackdec->srcpad, caps)) {
GST_ELEMENT_ERROR (musepackdec, CORE, NEGOTIATION, (NULL), (NULL));
return FALSE;
}
gst_pad_set_caps函数会向对等节点发送GST_EVENT_CAPS消息,下游的对等节点收到之后就会进行对应的传输协商。此时下游节点会在event函数中收到GST_EVENT_CAPS事件,解析到该规格,然后往更下游设定。
2. 传输协商
传输协商是单向地决定某个规格,sink pad上收到上游的GST_EVENT_CAPS,解析caps的内容,然后将其设置到src pad。
上面提到,固定协商中,上游节点使用了gst_pad_set_caps函数来设定了固定的规格,下游的节点sink_pad就会从event GST_EVENT_CAPS收到这个固定的规格,然后将这个规格设定到自己的src pad。
主要流程:
- gst_xxx_sink_event,收到GST_EVENT_CAPS事件
- gst_pad_set_caps,解析并设定caps,将caps通知到下游节点
static gboolean gst_my_filter_setcaps (GstMyFilter *filter, GstCaps *caps)
{
GstStructure *structure;
int rate, channels;
gboolean ret;
GstCaps *outcaps;
structure = gst_caps_get_structure (caps, 0);
ret = gst_structure_get_int (structure, "rate", &rate);
ret = ret && gst_structure_get_int (structure, "channels", &channels);
if (!ret)
return FALSE;
outcaps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, GST_AUDIO_NE(S16),
"rate", G_TYPE_INT, rate, "channels", G_TYPE_INT, channels, NULL);
ret = gst_pad_set_caps (filter->srcpad, outcaps);
gst_caps_unref (outcaps);
return ret;
}
static gboolean gst_my_filter_sink_event (GstPad *pad, GstObject *parent, GstEvent *event)
{
gboolean ret;
GstMyFilter *filter = GST_MY_FILTER (parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
ret = gst_my_filter_setcaps (filter, caps);
break;
}
default:
ret = gst_pad_event_default (pad, parent, event);
break;
}
return ret;
}
3. 动态协商
动态协商和上述的传输协商类似,不过这里收到的规格可能和下游元素不匹配,因此需要询问和协商。此时需要把收到的规格,同下游节点确认,选择一个彼此都可用的规格来使用。
主要流程:
- gst_xxx_sink_event : 收到上游节点GST_EVENT_CAPS事件和对应的caps
- gst_pad_get_allowed_caps :获取下游节点支持的caps(GST_QUERY_CAPS问询)。
- 根据收到的caps和下游返回的caps,选择一个固定的caps。如果两者的caps没有交集,可以内部做格式转换(否则连接失败)。
- gst_pad_set_caps,解析并设定caps,将caps通知到下游节点
static gboolean gst_my_filter_setcaps (GstMyFilter *filter, GstCaps *caps)
{
if (gst_pad_set_caps (filter->srcpad, caps)) {
filter->passthrough = TRUE;
} else {
othercaps = gst_pad_get_allowed_caps (filter->srcpad);//获取下游对等节点支持的caps
newcaps = gst_caps_copy_nth (othercaps, 0);
gst_pad_fixate_caps (filter->srcpad, newcaps);//选定一个caps
if (!gst_pad_set_caps (filter->srcpad, newcaps))
return FALSE;
...
static gboolean gst_my_filter_sink_event (GstPad *pad, GstObject *parent, GstEvent *event)
{
gboolean ret;
GstMyFilter *filter = GST_MY_FILTER (parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
ret = gst_my_filter_setcaps (filter, caps);
...
4. 重配置GST_EVENT_RECONFIGURE
当下游元素遇到状态变化,需要上游改变数据规格的时候,可以通过发送GST_EVENT_RECONFIGURE事件,请求重新配置格式。比如用户改变了窗口大小,需要同步对视频尺寸进行裁剪,接受新的图像尺寸。
该事件只是一个通知,下游节点在环境变化时,更改自己的规格,然后请求上游节点重新进行一次协商。之后上游节点拿到的caps规格就是下游节点重新调整的参数。当然,最终规格还是要由上游节点来决定。
当需要时,直接向对方pad发送该事件即可。
gst_pad_push_event (srcpad, gst_event_new_reconfigure ());
5. 交互事件实现
上述过程中,协商主要涉及GST_EVENT_CAPS、GST_QUERY_CAPS、GST_QUERY_ACCEPT_CAPS。
当前sink pad收到上游的GST_EVENT_CAPS,然后让src pad通过GST_QUERY_CAPS询问下游元素sink pad支持的格式,从中选择一个共同支持的格式。
另外,下游也可以在规格需要更改的时候,发送重新配置的事件GST_EVENT_RECONFIGURE,之后会重新走一遍协商流程。
无论怎样,都是上游的src pad来决定最终的格式。
GstBaseSrcClass中有一个默认的协商函数gst_base_src_default_negotiate,一般的插件不需要重新定义协商函数。该函数获取当前src的caps,再获取对等pad的caps,两者比较选择一个固定的caps,并通知对等节点。这里单独用gst_base_src_default_negotiate代码为例,追溯对应逻辑流程。
5.1. GST_QUERY_CAPS
src pad调用gst_pad_peer_query_caps函数,发出GST_QUERY_CAPS查询,目标是下游的sink pad:
gst_base_src_default_negotiate
gst_pad_query_caps //获取当前caps
gst_pad_peer_query_caps //获取对等pad的caps
--->gst_query_new_caps//新建问询 GST_QUERY_CAPS
--->gst_query_new_custom
--->gst_pad_peer_query//发出问询 GST_QUERY_CAPS
gst_base_src_fixate //匹配caps
gst_base_src_set_caps //设置caps
GST_QUERY_CAPS有默认的处理函数,继续往下游传输。需要当前的下游元素需要自己来处理,就在query函数来实现自己处理相关流程,否则不用关心。下游元素需要将支持的caps作为问询结果,用gst_query_set_caps_result (query, caps)设置caps为问询的结果,回传给上游。如果当前无法决定,可以通过gst_pad_query_default由下游节点来处理。
static gboolean gst_my_filter_sink_query( GstPad *pad, GstObject *parent, GstQuery *query)
{
GstMyFilter* filter = GST_MYFILTER(parent);
GST_LOG_OBJECT ( filter, "gst_my_filter_sink_query received %s query: %" GST_PTR_FORMAT,GST_QUERY_TYPE_NAME (query), query);
switch( GST_QUERY_TYPE( query ) )
{
case GST_QUERY_CAPS:
GstCaps *filter, *caps;
gst_query_parse_caps (query, &filter);
caps = gst_ass_render_get_videosink_caps (pad, (GstAssRender *) parent, filter);
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
res = TRUE;
break;
default:
res = gst_pad_query_default (pad, parent, query);
break;
注意问询和事件的所有处理都需要考虑更加下游的元素,gst_pad_query_default一定不能漏掉,否则链条断掉,下游元件就收不到应该处理的消息。
gst pad也将GST_QUERY_CAPS和返回值封装成了gst_pad_get_allowed_caps (GstPad * pad)函数,可以直接调用拿到下游节点的规格。
5.2 GST_QUERY_ACCEPT_CAPS
gst_base_src_default_negotiate函数调用gst_base_src_set_caps,根据问询结果创建一个新的caps,并推送对应事件。在事件处理流程中,会调用gst_pad_query_accept_caps来发出问询,并检查结果。
gst_base_src_default_negotiate--->
gst_base_src_set_caps--->
gst_pad_push_event---> //发送事件
gst_pad_send_event_unchecked--->
pre_eventfunc_check--->
gst_pad_query_accept_caps--->//发送accept
gst_query_new_accept_caps---> //新建GST_QUERY_ACCEPT_CAPS
gst_pad_query---> //发送GST_QUERY_ACCEPT_CAPS
gst_query_parse_accept_caps_result--->//检查问询结果
GST_QUERY_ACCEPT_CAPS 也是在问询处理函数处理,因为这里是通过上一阶段返回的caps来选择的,一般默认返回正确,可以不用再做确认。
static gboolean gst_my_filter_sink_query( GstPad *pad, GstObject *parent, GstQuery *query)
{
GstMyFilter* filter = GST_MYFILTER(parent);
GST_LOG_OBJECT ( filter, "gst_my_filter_sink_query received %s query: %" GST_PTR_FORMAT,GST_QUERY_TYPE_NAME (query), query);
switch( GST_QUERY_TYPE( query ) )
{
case GST_QUERY_CAPS:
{
gst_pad_query_default( pad, parent, query );
}
break;
case GST_QUERY_ACCEPT_CAPS:
break;
default:
gst_pad_query_default( pad, parent, query );
break;
}
}
5.3 GST_EVENT_CAPS
GST_EVENT_CAPS是告诉对方,将要发送对应规格的数据了。该事件在确认caps accept之后,直接发送的。流程和上述一样,在pre_eventfunc_check之后,就会拿到pad的event处理函数,然后进行调用。
gst_base_src_default_negotiate--->
gst_base_src_set_caps--->
gst_pad_push_event--->//发送事件
gst_pad_send_event_unchecked--->
eventfullfunc = GST_PAD_EVENTFULLFUNC (pad);
eventfunc = GST_PAD_EVENTFUNC (pad);
pre_eventfunc_check //发送accept 问询
if (eventfullfunc) //调用pad event handle函数
ret = eventfullfunc (pad, parent, event);
else
ret = eventfunc (pad, parent, event))
这里调用的eventfunc就是我们初始化时候设置的事件处理函数:
static gboolean gst_my_filter_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstMyFilter *filter;
gboolean ret;
filter = GST_MYFILTER (parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
/* do something with the caps */
/* and forward */
ret = gst_pad_event_default (pad, parent, event);
break;
}
同样地,在代码中一般使用接口gst_pad_set_caps来设置规格,该函数封装了GST_EVENT_CAPS的创建和发送。
5.4 总结
利用gst_base_src_default_negotiate总结整个流程如下:
gst_base_src_default_negotiate
gst_pad_query_caps //获取当前caps
gst_pad_peer_query_caps //获取对等pad的caps
--->gst_query_new_caps//新建问询 GST_QUERY_CAPS
--->gst_query_new_custom
--->gst_pad_peer_query//发出问询 GST_QUERY_CAPS
gst_base_src_fixate //匹配caps
gst_base_src_set_caps //设置caps--->
gst_pad_push_event---> //发送GST_EVENT_CAPS事件
gst_pad_send_event_unchecked--->
pre_eventfunc_check--->
gst_pad_query_accept_caps--->//发送accept 问询
gst_query_new_accept_caps---> //新建GST_QUERY_ACCEPT_CAPS
gst_pad_query---> //发送GST_QUERY_ACCEPT_CAPS
gst_query_parse_accept_caps_result--->//检查问询结果
ret = eventfunc (pad, parent, event) //调用事件处理函数GST_EVENT_CAPS