Gstreamer- 协商(Negotiation)

协商

Capabilities 协商是为 GStreamer pipeline内的数据流决定适当格式的过程。理想情况下,协商(也称为“capsnego”)将信息从pipeline中具有信息的那些部分传输到pipeline的那些易扩展的部分,受pipeline中不易扩展部分的约束。

基本规则

必须遵循这些简单的规则:

  1. 下游建议格式
  2. 上游决定格式

caps协商中使用了 4 种 查询/事件(queries/events ):

  1. GST_QUERY_CAPS:获取可能的格式
  2. GST_QUERY_ACCEPT_CAPS:检查格式是否可行
  3. GST_EVENT_CAPS:配置格式(下游)
  4. 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。
上一篇:Compile opencv 4 with Cuda and GStreamer on windows


下一篇:Kurento实战之三:知识点小导游,华为java面试面经