协商
Capabilities 协商是为 GStreamer pipeline内的数据流决定适当格式的过程。理想情况下,协商(也称为“capsnego”)将信息从pipeline中具有信息的那些部分传输到pipeline的那些易扩展的部分,受pipeline中不易扩展部分的约束。
基本规则
必须遵循这些简单的规则:
- 下游建议格式
- 上游决定格式
caps协商中使用了 4 种 查询/事件(queries/events ):
- GST_QUERY_CAPS:获取可能的格式
- GST_QUERY_ACCEPT_CAPS:检查格式是否可行
- GST_EVENT_CAPS:配置格式(下游)
- GST_EVENT_RECONFIGURE:通知上游可能的新caps
查询
pad可以向peer pad询问其支持的 GstCaps。它通过 CAPS 查询完成此操作。支持的caps列表可用于为数据传输选择合适的 GstCap。 CAPS 查询以递归方式工作,element在构建可能的caps时应考虑其 peer element。由于结果caps可能非常大,因此可以使用过滤器来限制caps。只有与过滤器匹配的caps才会作为结果caps返回。过滤器caps的顺序给出了调用者的优先顺序,并且应该考虑到返回的caps。
- filter (in) GST_TYPE_CAPS (default NULL): - 一个用于过滤结果的 GstCaps
- caps (out) GST_TYPE_CAPS (default NULL): - 结果caps
pad可以询问peer pad 是否支持给定的caps。它通过 ACCEPT_CAPS 查询完成此操作。caps必须固定。 ACCEPT_CAPS 查询不需要递归工作,如果带有这些caps的后续 CAPS 事件返回成功,它可以简单地返回 TRUE。
- caps (in) GST_TYPE_CAPS: - 要检查的 GstCaps,必须固定
- result (输出)G_TYPE_BOOLEAN(默认为 FALSE): - 如果接受caps则为 TRUE
事件
当协商媒体格式时,通过 CAPS 事件将 GstCaps 通知给peer element。caps必须固定。
- caps GST_TYPE_CAPS: - 协商的 GstCaps,必须是固定的
操作
GStreamer 的两种调度模式,push 模式和 pull 模式,适用于不同的机制来实现这个目标。由于更常见,我们首先描述推模式协商。
推模式协商
当element想要推送缓冲区并需要决定格式时,会发生推模式协商。这称为下游协商,因为上游element决定下游element的格式。这是最常见的情况。
当下游element想要从上游element接收另一种数据格式时,也会发生协商。这称为上游协商。
协商的基本原则如下:
-
GstCaps(参见 caps)在它们作为事件推送之前被引用以描述随后的缓冲区的内容。
-
在处理随后的缓冲区之前,element应将自身重新配置为作为 CAPS 事件接收的新格式。如果 caps 事件中的数据类型不可接受,则element应拒绝该事件。该element还应通过从链函数返回适当的 GST_FLOW_NOT_NEGOTIATED 返回值来拒绝下一个缓冲区。
-
下游element可以通过向上游发送 RECONFIGURE 事件来请求流的格式更改。上游element在收到 RECONFIGURE 事件时将重新协商新格式。
source pad开始协商的一般流程。
src sink
| |
| querycaps? |
|---------------->|
| caps |
select caps |< - - - - - - - -|
from the | |
candidates | |
| |-.
| accepts? | |
type A |---------------->| | optional
| yes | |
|< - - - - - - - -| |
| |-'
| send_event() |
send CAPS |---------------->| Receive type A, reconfigure to
event A | | process type A.
| |
| push |
push buffer |---------------->| Process buffer of type A
| |
一种可能的实现方式的伪代码:
[element wants to create a buffer]
if not format
# see what we can do
ourcaps = gst_pad_query_caps (srcpad)
# see what the peer can do filtered against our caps
candidates = gst_pad_peer_query_caps (srcpad, ourcaps)
foreach candidate in candidates
# make sure the caps is fixed
fixedcaps = gst_pad_fixate_caps (srcpad, candidate)
# see if the peer accepts it
if gst_pad_peer_accept_caps (srcpad, fixedcaps)
# store the caps as the negotiated caps, this will
# call the setcaps function on the pad
gst_pad_push_event (srcpad, gst_event_new_caps (fixedcaps))
break
endif
done
endif
使用ALLOCATION 查询 协商 allocator/bufferpool
buffer = gst_buffer_new_allocate (NULL, size, 0);
# fill buffer and push
sink pad 开始重新协商的通用流程.
src sink
| |
| accepts? |
|<----------------| type B
| yes |
|- - - - - - - - >|-.
| | | suggest B caps next
| |<'
| |
| push_event() |
mark .-|<----------------| send RECONFIGURE event
renegotiate| | |
'>| |
| querycaps() |
renegotiate |---------------->|
| suggest B |
|< - - - - - - - -|
| |
| send_event() |
send CAPS |---------------->| Receive type B, reconfigure to
event B | | process type B.
| |
| push |
push buffer |---------------->| Process buffer of type B
| |
用例:
videotestsrc ! xvimagesink
-
谁决定使用什么格式?
- 按照惯例, 总是由 src pad 决定。 sinkpad 可以通过将其放在 caps 查询结果 GstCaps 的高位来建议格式。
- 由于由src 决定,它总是可以选择它可以做到的事情,所以只有当 sinkpad 声明它可以接受某些东西而稍后它又不能时,这一步才会失败。
-
什么时候进行协商?
- 在 srcpad 执行推送之前,它会计算出 1) 中所述的类型,然后推送具有该类型的 caps 事件。sink 检查媒体类型并为此类型配置自己。
- 然后source通常会执行 ALLOCATION 查询以与sink协商缓冲池。然后它从池中分配一个缓冲区并将其推送到sink。由于sink接受了caps,因此它可以为对应的格式创建一个池。
- 由于 1) 中所述的sink可以接受该类型,因此它将能够处理它。
-
sink如何请求另一种格式?
- sink 询问source是否可以使用新格式。
- sink 向上游推送 RECONFIGURE 事件
- src 接收 RECONFIGURE 事件并标记重新协商
- 在下一次缓冲区推送时,source重新协商caps和缓冲池。接收器会将新的首选格式放在它从其 caps 查询返回的 caps 列表中的高位。
videotestsrc ! queue ! xvimagesink
- queue 代理所有的accept和caps 查询到另一个peer pad。
- queue 代理缓冲池
- queue 代理 RECONFIGURE 事件
- queue 将 CAPS 事件存储在queue中。这意味着queue可以包含不同类型的缓冲区。
拉模式协商
拉模式下的pipeline与推模式下激活的pipeline具有不同的协商需求。推模式针对两个用例进行了优化:
-
媒体文件的播放,其中分流器和解码器是应该将格式信息应传播到pipeline其余部分的点;和
-
从实时源录制,其中用户习惯于在source element之后直接放置一个 capsfilter;因此,caps信息流从用户开始,通过source的潜在caps,到达pipeline的sink。
相比之下,拉模式还有其他典型用例:
-
从有损源(例如 RTP)播放,在这种源中,更多地了解pipeline的延迟可以提高质量;或者
-
音频合成,其中音频 API 被调整为仅产生必要数量的采样,通常由硬件中断驱动以填充 DMA 缓冲区或 Jack[0] 端口缓冲区。
-
低延迟效果处理,当数据从环形缓冲区传输到sink应该应用过滤器,而不是传输之前。例如, wavsrc ! volume ! alsasink 不应在推模式 中使用内部 alsasink ringbuffer 线程,而是通过 wavsrc ! audioringbuffer ! volume ! alsasink 将volume 放在声卡写入器线程中 。
[0] http://jackit.sf.net
拉模式的问题是sink必须知道格式才能知道通过 gst_pad_pull_range() 拉多少字节。这意味着在拉取之前,sink必须发起协商以决定格式。
回顾 capsnego 的原则,即信息必须从拥有它的部分流向没有它的部分,我们看到三个具名的用例具有不同的协商要求:
-
RTP 和低延迟播放都类似于正常播放情况,其中信息流向下游。
-
在音频合成中,拥有最多信息的pipeline部分是sink,受提供给它的能力图的限制(constrained by the capabilities of the graph that feeds it)。然而,没有完全指定caps;在某些时候,用户必须至少进行干预以选择采样率。这可以在 gstreamer 外部完成,就像在 jack element中一样,也可以通过 capsfilter 在内部完成,就像实时源一样。
鉴于sink可能需要source的输入,就像在RTP的情况下一样,至少在合成的情况下作为过滤器,在拉线程被激活之前必须有一个协商阶段。此外,考虑到拉模式提供的低延迟,我们希望避免从拉线程内进行 capsnego,以防它导致我们错过我们的调度的最后期限。
拉线程一般是在PAUSED→PLAYING状态变化下开始的。我们必须能够在这种状态改变发生之前完成协商。
那么,执行 capsnego 的时间是在 SCHEDULING 查询成功之后,但在 sink 产生拉线程之前。
机制
sink通过执行 SCHEDULING 查询来确定上游element支持基于拉的调度。
sink通过将gst_pad_query_caps()从sink pad和 peer src pad获得的结果进行相交来启动协商过程。这个操作是由gst_pad_get_allowed_caps()执行。在简单的传递情况下,peer pad的caps查询应该返回在其所有sink pad上调用get_allowed_caps()的交集。通过这种方式,sink element知道整个pipeline的能力。
如有必要,sink element然后将结果caps固定,从而产生流的caps。从现在开始,sinkpad 的 caps 查询将只返回这些固定的 caps,这意味着上游element只能生成这种格式的数据。
如果sink element 无法在其sink pad上设置caps,则它应该在总线上发布一条错误消息,指示无法进行协商。
当协商成功时,sinkpad 和所有上游内部链接的pad 在pull 模式下被激活。通常,此操作将触发下游element的协商,现在将*协商到sink pad的最终固定所需caps。
在这些步骤之后,sink element从状态改变函数返回 ASYNC。当sink中接收到第一个缓冲区时,状态将提交到 PAUSED。这需要为期望从sink返回ASYNC值的应用程序提供一致的API ,但它也允许我们在拉动线程的上下文之外执行其余的协商。
模式
我们可以在协商中确定 3 种模式:
-
Fixed :无法选择输出格式
- caps在流中已编码
- 视频/音频解码器
- 通常使用 gst_pad_use_fixed_caps()
-
Transform
- 未修改的caps(直通(passthrough))
- 可以根据element属性做caps变换
- 固定的caps转变为固定的caps
- videobox
-
Dynamic :可以选择输出格式
- 一个转换器element
- 取决于下游caps,需要做一个caps查询来找到转换。
- 通常更喜欢使用恒等变换
- 固定的caps可以转换为非固定的caps。