缓冲池
本文档详细介绍了如何在池中分配和管理缓冲区的设计。
缓冲池通过减少分配开销和提高实现零拷贝内存传输的可能性来提高性能。
与 ALLOCATION 查询一起,element可以在它们之间协商分配属性和缓冲池。这也允许element在它们之间协商缓冲区元数据。
要求
-
提供一个 GstBufferPool 基类来帮助高效实现可重用 GstBuffer 对象列表。
-
让上游element发起缓冲池及其配置的协商。允许下游element提供缓冲池属性和/或缓冲池。这包括以下属性:
-
具有最小和最大数量的缓冲区,并可以选择预先分配缓冲区。
-
分配器、对齐和填充支持
-
缓冲区元数据
-
任意额外选项
-
-
与动态caps重新协商集成。
-
通知上游element新的缓冲池可用性。当可以提供缓冲池的新element在下游动态链接时,这一点很重要。
GstBufferPool
缓冲池对象管理具有相同属性(例如大小、填充和对齐)的缓冲区列表。
缓冲池有两种状态:活动和非活动。在非活动状态下,可以使用所需的分配首选项配置缓冲池。在活动状态下,可以从池中检索缓冲区和将其返回到池中。
缓冲池的默认实现能够从具有任意对齐和填充/前缀的任何分配器分配缓冲区。
缓冲池的自定义实现可以覆盖缓冲池中缓冲区的分配和释放算法。这应该允许不同的分配策略,例如使用共享内存或硬件映射内存。
协商
在两个 pad 之间协商了特定的媒体格式后(使用 CAPS 事件),他们必须就如何分配缓冲区达成一致。
srcpad 将始终主动协商分配属性。首先创建一个带有协商caps的 GST_QUERY_ALLOCATION。
srcpad 可以在查询中将 need-pool 标志设置为 TRUE,以选择性地使peer pad 分配缓冲池。只有在能够使用peer方提供的缓冲池时才应该这样做。
然后它将检查返回的结果并在需要时配置返回的池或使用返回的属性创建新池。
缓冲区然后由 srcpad 从协商池中分配,并像往常一样推送到peer pad。
当缓冲区大小不同且无法从池中分配时,分配查询还可以返回分配器对象。
分配查询
分配查询具有以下字段:
-
(in) caps, GST_TYPE_CAPS: 协商的caps
-
(in) need-pool, G_TYPE_BOOLEAN: 如果请求一个 GstBufferPool
-
(out) pool, G_TYPE_ARRAY 的结构:池配置数组:
struct {
GstBufferPool *pool;
guint size;
guint min_buffers;
guint max_buffers;
}
使用 gst_query_parse_nth_allocation_pool() 获取值。
分配器可以包含多个池配置。 如果need-pool 为TRUE,当下游element可以提供一个GstBufferPool 时,池成员可能包含一个GstBufferPool。
Size 包含缓冲池缓冲区的大小,并且永远不会为 0。
min_buffers 和 max_buffers 包含应由池管理的建议的最小和最大缓冲区量。
当没有提供或建议的池不可接受时,上游element可以选择使用提供的池或创建自己的池。
然后可以使用建议的最小和最大缓冲区量配置池,或者下游element可以选择不同的值。
- (out) allocator, G_TYPE_ARRAY of structure:可以使用的分配器参数数组。
struct {
GstAllocator *allocator;
GstAllocationParams params;
}
使用 gst_query_parse_nth_allocation_param() 获取值。
执行查询的element可以使用分配器及其参数为下游element分配内存。 也可以在提供的池中配置分配器。
- (out) metadata, G_TYPE_ARRAY of structure:可以接受的元数据参数数组。
struct {
GType api;
GstStructure *params;
}
使用 gst_query_parse_nth_allocation_meta() 获取值。
当这些元数据项放置在缓冲区上时,下游element可以接受这些元数据项。 还有一个与包含元数据特定选项的元数据关联的任意 GstStructure。
一些缓冲池有选项可以在池分配的缓冲区上启用元数据。
从池中分配
缓冲区是从 pad 的池中分配的:
res = gst_buffer_pool_acquire_buffer (pool, &buffer, ¶ms);
从池中分配的 GstBuffer 将始终是可写的(引用计数为 1),并且它的池成员也将指向创建缓冲区的 GstBufferPool。
缓冲区以通常的方式重新计数。当缓冲区的 refcount 达到 0 时,缓冲区会自动返回到池中。
由于从池中分配的所有缓冲区都保留对池的引用,所以当没有其他对象保存对池的引用计数时,将在池中的所有缓冲区取消引用时结束。通过将池设置为非活动状态,我们可以从池中耗尽所有缓冲区。
当池处于非活动状态时, gst_buffer_pool_acquire_buffer() 将立即返回 GST_FLOW_FLUSHING。
可以为 gst_buffer_pool_acquire_buffer() 方法提供额外的参数来影响分配决策。 GST_BUFFER_POOL_ACQUIRE_FLAG_KEY_UNIT 和 GST_BUFFER_POOL_ACQUIRE_FLAG_DISCONT 作为提示。
当缓冲池配置了最大数量的缓冲区时,当所有的缓冲区都已被分配,分配将会阻塞,直到有一个缓冲区返回到池中。可以通过在参数中指定 GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT 标志来更改此行为。设置此标志后,分配将在池为空时返回 GST_FLOW_EOS。
重新协商
当缓冲池的配置发生变化时,可能需要重新协商缓冲池。更改可以是缓冲区大小(由于caps 更改)、对齐方式或缓冲区数量。
下游
当上游element想要协商新格式时,它可能需要与下游element重新协商新的缓冲池配置。例如,当缓冲区大小发生变化时,就会发生这种情况。
我们不能仅仅重新配置现有的缓冲池,因为pipeline中可能仍然有来自缓冲池的未完成的缓冲区。因此,我们需要为新配置创建一个新的缓冲池,同时让旧池耗尽。
实现可以选择重用相同的缓冲池对象,并在重新配置缓冲池之前等待耗尽完成。
想要重新协商新缓冲池的element使用与刚开始时完全相同的算法。它将首先协商caps,然后使用 ALLOCATION 查询来获取和配置新池。
上游的
当下游element想要协商新格式时,它将向上游发送 RECONFIGURE 事件。这指示上游在需要时重新协商格式和缓冲池。
在pipeline中添加或删除element或pipeline拓扑发生变化时,会发生pipeline重新配置。pipeline重新配置还可能触发缓冲池和caps的重新协商。
当需要重新配置时,RECONFIGURE事件标记它所游历的每个pad。然后,下一次缓冲区分配将需要重新协商或重新配置池。
关闭
在推模式下,source pad负责在流停止时将池设置为非活动状态。非活动状态将取消阻塞任何挂起的分配,以便element可以关闭。
在拉模式下,sink element应该在关闭时将池设置为非活动状态,以便 peer 的_get_range() 函数可以解除阻塞。
在非活动状态下,所有返回到池中的缓冲区将自动被池释放,新的分配将失败。
用例
videotestsrc ! xvimagesink
-
在videotestsrc可以输出缓冲区之前,它需要与下游peer pad协商caps和bufferpool。
-
首先它会根据通用规则与下游协商一个合适的格式。它将使用协商配置向下游发送 CAPS 事件。
-
然后它执行 ALLOCATION 查询。它将使用返回的缓冲池或使用返回的参数配置自己的缓冲池。缓冲池最初处于非活动状态。
-
ALLOCATION 查询列出了下游 xvimagesink 的所需配置,它可以具有特定的对齐方式 和/或 最小/最大缓冲区量。
-
videotestsrc 更新缓冲池的配置,它可能会将最小缓冲区设置为 1 和所需缓冲区的大小。然后使用新属性更新缓冲池配置。
-
当配置成功更新时,videotestsrc 将缓冲池设置为活动状态。这会在池中预先分配缓冲区(如果需要)。当没有足够的可用内存时,此操作可能会失败。由于缓冲池由 xvimagesink 提供,它将分配由 XvImage 支持并指向与 X 服务器共享内存的缓冲区。
-
如果成功激活了缓冲池,videotestsrc 可以从缓冲池中获取一个缓冲区,填充数据并将其推送到 xvimagesink。
-
xvimagesink 可以通过跟踪池成员知道缓冲区源自其池。
-
关闭时,videotestsrc 会将池设置为非活动状态,这将导致进一步的分配失败并释放当前分配的缓冲区。 videotestsrc 然后将释放池并停止流传输。
videotestsrc ! queue ! myvideosink
-
在第二个用例中,我们有一个最多可以分配 3 个视频缓冲区的videosink。
-
同样 videotestsrc 将不得不与peer element协商缓冲池。为此,它将执行 ALLOCATION 查询,并由queue将查询代理到其下游peer element。
-
从 myvideosink 返回的缓冲池的 max_buffers 设置为 3。 queue 和 videotestsrc 可以在此上限下运行,因为这些element都不需要超过该数量的临时存储缓冲区。
-
Myvideosink 的缓冲池将根据协商格式的缓冲区大小并根据填充和对齐规则进行配置。当 videotestsrc 将池设置为活动时,将在池中预分配 3 个视频缓冲区。
-
videotestsrc 从其 srcpad 上的配置池获取缓冲区并将其推送到队列中。当 videotestsrc 已获取并推送 3 帧时,对 gst_buffer_pool_acquire_buffer() 的下一次调用将阻塞(假设未指定 GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT)。
-
当queue推出一个缓冲区并且sink已经渲染它时,缓冲区的引用计数达到 0 并且缓冲区在池中被回收。这将唤醒被阻塞的 videotestsrc,等待更多缓冲区并使其产生下一个缓冲区。
-
在此设置中,pipeline中最多有 3 个活动缓冲区,并且 videotestsrc 的速率受缓冲区在缓冲池中回收的速率的限制。
-
关闭时,videotestsrc 将首先将 srcpad 上的缓冲池设置为非活动状态。这会导致任何挂起(阻塞)的acquire返回一个 FLUSHING 结果并导致流线程暂停。
.. ! myvideodecoder ! queue ! fakesink
-
在这种情况下,myvideodecoder 需要将缓冲区对齐到 128 字节并填充 4096 字节。pipeline从链接到 fakesink 的decoder开始,但我们将动态地将sink更改为可以提供缓冲池的sink。
-
当 myvideodecoder 与下游 fakesink element协商大小时,它会收到一个 NULL 缓冲池,因为 fakesink 不提供缓冲池。然后它会选择自己的自定义缓冲池来开始数据传输。
-
在某些时候,我们阻塞queue 的 srcpad,取消queue 与 fakesink 的链接,链接一个新的 sink,并将新的 sink 设置为 PLAYING 状态。链接新sink将自动向上游发送 RECONFIGURE 事件,并通过队列通知 myvideodecoder 它应该重新协商其缓冲池,因为下游已重新配置。
-
在推送下一个缓冲区之前,myvideodecoder 必须重新协商一个新的缓冲池。为此,它执行通常的缓冲池协商算法。如果它可以从下游获取并配置一个新的缓冲池,它将自己的(旧)池设置为非活动状态并取消引用它。这最终将耗尽并取消引用旧的缓冲池。
-
新缓冲池被设置为queue的srcpad和sinkpad的新缓冲池,并设置为活动状态。
.. ! myvideodecoder ! queue ! myvideosink
-
myvideodecoder 已与下游 myvideosink 协商缓冲池,以处理大小为 320x240 的缓冲区。它现在检测到视频格式发生变化,需要重新协商到 640x480 的分辨率。这需要它协商一个具有更大缓冲区大小的新缓冲池。
-
当 myvideodecoder 需要获得更大的缓冲区时,它开始协商新的缓冲池。它从下游查询缓冲池,使用新配置(包括更大的缓冲区大小)重新配置它,并将缓冲池设置为活动状态。旧池已停用且取消引用,这会导致旧格式的缓冲区耗尽。
-
然后它使用新的缓冲池来分配新尺寸的新缓冲区。
-
如果在某个时候,解码器想要再次切换到较低的分辨率,它可以选择使用当前池(其缓冲区大于所需大小),也可以选择重新协商新的缓冲池。
.. ! myvideodecoder ! videoscale ! myvideosink
-
myvideosink 正在为上游element提供缓冲池,并希望更改分辨率。
-
myvideosink 向上游发送 RECONFIGURE 事件以通知上游需要新格式。上游element尝试在推出新缓冲区之前协商新格式和缓冲池。旧的缓冲池以常规方式耗尽。