cap协商是找到一种两个element都能够处理的媒体格式(即GstCaps)的一种行为。Gstreamer中的这种处理,对于整个pipeline,大多数情况下都可以找到最优的解决方案。在这个章节,我们解释了它是怎样工作的。
Caps negotiation basics
在Gstreamer中,媒体格式的协商总是遵循如下的简单规则:
- 一个下游的element在它的sinkpad上建议一种格式,并将此建议格式放至sinkpad CAPS查询的结果中。具体请见下面的Implementing a CAPS query function.
- 一个上游的element决定一种格式。它在sourcepad上,通过CAPS event发送所选定的媒体格式。下游element在sinkpad上,根据CAPS event重新配置以使其能够处理此媒体格式。
- 一个下游element能够通过发送RECONFIGURE event通知上游element,它将建议一个新格式。RECONFIGURE event只是简单的命令上游element重新开始协商步骤。因为此element发送出一个RECONFIGURE event是建议另一种格式,所以pipeline的格式可能改变。
所有的协商都遵循以上这些简单规则。让我们来看一些典型的使用案例及协商是怎样发生的。
Caps negotiation use cases
我们将在下面看一些push调度模式的用例。pull调度模式阶段会在此章节的后面部分讨论,并且它和我所见的很相似。
由于sinkpad只是建议用哪种格式,真正的决定权在sourcepad上,所以最复杂的工作将会在sourcepad上完成。我们可以确定3种sourcepad的cap协商用例
- 固定的协商(Fixed negotiation)。一个element只能输出一种格式。具体请见下文。
- 转换协商(Transform negotiation)。在element的输入和输出之间有一个(固定的)转换,通常是基于一些element的属性。element所产生的caps将依赖于上游的caps,而element所能接受的caps依赖于下游的caps。具体见下文关于转换协商的介绍。
- 动态协商(Dynamic negotiation)。一个element能够输出很多格式。具体见下文关于动态协商的介绍。
Fixed negotiation
在这种情况中,sourcepad只能够产生一个固定格式。通常这种格式被编码到媒体里面。没有下游的element能够要求另一种不同的模式,只有当element决定改变其自身的caps时,source才会重新协商。
能够(在它们的sourcepad上)实现固定caps的elements,通常都 不会重新协商。例子包括:
- 一个类型探测器(typefinder),因为探测到的类型是真实数据流的一部分,所以其不能够重新协商。类型探测器将查看流字节,分辨出类型,发送一个携带caps的CPAS event,并push指明类型的buffers。
- 相当多的分流器,因为所包含基本数据流是在文件头中被定义,因此不会重新协商。
- 一些解码器,格式被嵌入到数据流中,而不是同级cpas的一部分,所以此时的解码器也不会重新协商。
- 一些能够产生固定模式的源。
gst_pad_use_fixed_caps()函数是作用在sourcepad的固定caps上的。只要pad没有协商,默认的CAPS查询将返回padtemplate所提供的caps。当pad已经协商,CAPS查询将返回协商的caps(或什么都不返回)。这里有一些相关的,sourcepad的固定caps的代码片断。
[..]
pad = gst_pad_new_from_static_template (..);
gst_pad_use_fixed_caps (pad);
[..]
接着,固定的caps可通过调用
gst_pad_set_caps()函数,在pad上设置。
[..]
caps = gst_caps_new_simple ("audio/x-raw",
"format", G_TYPE_STRING, GST_AUDIO_NE(F32),
"rate", G_TYPE_INT, <samplerate>,
"channels", G_TYPE_INT, <num-channels>, NULL);
if (!gst_pad_set_caps (pad, caps)) {
GST_ELEMENT_ERROR (element, CORE, NEGOTIATION, (NULL),
("Some debug information here"));
return GST_FLOW_ERROR;
}
[..]
element的这些类型对于输入模式和输出格式之间并不联系,输入的cpas并不简单的包含产生输出caps所需的信息。
所有其它配置格式的elements应该实现完整的caps协商,这将在下文中阐述。
Transform negotiation
在这个协商技术中,element的输入caps和输出caps之间有一个固定的转换。这个转换可通过element的属性,而不是通过流的内容,被参数化。
element能够接受的caps依赖于下游caps(的固定转换)。element能够产生的caps依赖于上游caps(的固定转换)。
element的这种类型,通常可以在sourcepad上设置。sourcepad是在其接收到CAPS event时,设置从sinkpad上的_event()函数传来的caps。这意味着,caps转换函数是转换一个固定caps为另一个固定caps。此element的例子包括:
- Videobox。它根据对象属性在视频帧周围添加可配置边框
- Identity elements。所有element不改变数据的格式,只改变内容。视频和音频就是一个例子。其它例子有检查流的element。
- 一些解码器和编码器,输出的格式是由输入格式定义的,如mulawdec和mulawenc。这些解码器通常没有定义流内容的头。它们更像是转换elements。
[...]
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;
}
[...]
Dynamic negotiation
最后一个是最复杂且最有强大的动态协商。
和转换协商相同的是,动态协商将在下游/上游caps上进行一个转换。和转换协商不同的是,这种转换是将一个固定caps转换成一个非固定caps。这意味着,sinkpad输入的caps能够被转换成非固定的(多种的)格式。sourcepad将必须从这多种可能的格式中选择一种。它通常就像选择一个格式,能够产生的前提下的最少要求,但也不是必须的。格式的选择也应该依赖下游能够接受的caps。
一个典型的流类似于:
- element的sinkpad上接收到的caps。
- 如果element更倾向于passthrough模式,则通过ACCEPT_CAPS查询检查下游是否接受caps。如果接受,我们可以进行协商并以passthrough模式操作。
- 计算sourcepad可能的的caps。
- 查询下游对等pad中可能的caps的列表
- 从下游列表中选择第一个你能转换成的caps,并设置此caps为输出caps。对于一些合理的默认值,你可能需要固定下caps以构成固定的caps。
这类型的element的例子包括:
- converter elements,如videoconvert, audioresample, videoscale, ...
- source elemnts, 如audiotestsrc, videotestsrc, v4l2src, pulsesrc, ...
static gboolean
gst_my_filter_setcaps (GstMyFilter *filter,
GstCaps *caps)
{
if (gst_pad_set_caps (filter->srcpad, caps)) {
filter->passthrough = TRUE;
} else {
GstCaps *othercaps, *newcaps;
GstStructure *s = gst_caps_get_structure (caps, 0), *others;
/* no passthrough, setup internal conversion */
gst_structure_get_int (s, "channels", &filter->channels);
othercaps = gst_pad_get_allowed_caps (filter->srcpad);
others = gst_caps_get_structure (othercaps, 0);
gst_structure_set (others,
"channels", G_TYPE_INT, filter->channels, NULL);
/* now, the samplerate value can optionally have multiple values, so
* we "fixate" it, which means that one fixed value is chosen */
newcaps = gst_caps_copy_nth (othercaps, 0);
gst_caps_unref (othercaps);
gst_pad_fixate_caps (filter->srcpad, newcaps);
if (!gst_pad_set_caps (filter->srcpad, newcaps))
return FALSE;
/* we are now set up, configure internally */
filter->passthrough = FALSE;
gst_structure_get_int (s, "rate", &filter->from_samplerate);
others = gst_caps_get_structure (newcaps, 0);
gst_structure_get_int (others, "rate", &filter->to_samplerate);
}
return TRUE;
}
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;
}
static GstFlowReturn
gst_my_filter_chain (GstPad *pad,
GstObject *parent,
GstBuffer *buf)
{
GstMyFilter *filter = GST_MY_FILTER (parent);
GstBuffer *out;
/* push on if in passthrough mode */
if (filter->passthrough)
return gst_pad_push (filter->srcpad, buf);
/* convert, push */
out = gst_my_filter_convert (filter, buf);
gst_buffer_unref (buf);
return gst_pad_push (filter->srcpad, out);
}
Upstream caps (re)negotiation
上游协商主要是用于重新协商一个已经协商过的pipeline成另一种新格式。一些实际例子包括,由于视频窗口的改变及输出自身不能够重新调节比例,所以需选择一个不同的视频尺寸,或者由于音频通道的配置改变了。