在两个衬垫的caps协商完成之后,元件之间需要确认如何分配buffer。本文梳理Gstreamer 内存协商机制,比如当某元件不能自己分配内存时,如何使用其他元件的分配器。
场景和目的
一般而言,内存分配的协商是在caps协商之后。根据需求匹配到对应的参数,获取到分配器和内存池,然后才能开始数据传输。
在sink_event函数的GST_EVENT_CAPS分支,通过解析到上游的caps信息,来设置自己的caps。紧接着就会发起内存分配的协商。
因此我们在Gstreamer 源码中,很多协商都是在元件的gst_xxx_set_caps函数中进行的。
对于很多基础类,我们会看到两个相关的虚函数:
- propose_allocation
在收到GST_QUERY_ALLOCATION问询时,一般时候直接调用该函数来处理,为上游元件提供建议值。 - decide_allocation
根据下游的建议参数,来决定最终的参数值。一般会判断当前query是否具有有效值,补齐query中的参数。该函数可能在默认的negotiate函数中调用,也可能在set caps之后调用(通常的element)。
发起问询
协商总是由源衬垫src pad来发起,srcpad给出想要的参数,最终由下游节点判断是否能满足要求。
下游的sinkpad可以返回以下三类信息:
- GstAllocator
- GstBufferPool
- GstMeta
请求端——创建问询
首先创建一个问询:GST_QUERY_ALLOCATION,携带需要存放的caps格式 。如果需要使用内存池,可以把need-pool的标志位置一。
和其他问询一样,在实际代码中,并不直接调用GST_QUERY_ALLOCATION ,而是通过封装的接口gst_query_new_allocation来进行的:
GstQuery *query;
query = gst_query_new_allocation (outcaps, TRUE);
gst_query_new_allocation 其实现如下:
GstQuery *
gst_query_new_allocation (GstCaps * caps, gboolean need_pool)
{
GstQuery *query;
GstStructure *structure;
structure = gst_structure_new_id (GST_QUARK (QUERY_ALLOCATION),
GST_QUARK (CAPS), GST_TYPE_CAPS, caps,
GST_QUARK (NEED_POOL), G_TYPE_BOOLEAN, need_pool, NULL);
query = gst_query_new_custom (GST_QUERY_ALLOCATION, structure);
return query;
}
请求端——发送问询
之后我们往对等节点发送这个问询:
if (!gst_pad_peer_query (scope->priv->srcpad, query)) {
/* not a problem, we use the query defaults */
GST_DEBUG_OBJECT (scope, "allocation query failed");
}
请求端——检查问询结果
接下来解析结果,获取分配器、参数、pool信息:
GstAllocator *allocator;
GstAllocationParams params;
GstBufferPool *pool = NULL;
//获取params和allocator
if (gst_query_get_n_allocation_params (query) > 0) {
gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms);
}
//获取pool
if (gst_query_get_n_allocation_pools (query) > 0)
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
//接下来可以按正常流程使用pool和allocator
config = gst_buffer_pool_get_config (pool);
//查询meta,并在buffer中添加video meta选项
if (gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL))
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
//设置并激活pool
gst_buffer_pool_config_set_params (config, caps, size, min, max);
gst_buffer_pool_config_set_allocator (config, allocator, ¶ms);
gst_buffer_pool_set_config (pool, config);
/* and activate */
gst_buffer_pool_set_active (pool, TRUE);
响应问询
和其他问询一样,问询的响应都是在sink_query函数中完成的。一般是调用propose_allocation函数来直接处理。
响应端——解析问询
我们可以通过gst_query_parse_allocation来解析问询结果,拿到caps和pool的需求信息。
GstCaps *outcaps;
gboolean need_pool;
gst_query_parse_allocation (query, &caps, &need_pool);
响应端——配置问询
根据解析到信息,创建对应资源,并添加到query:
//1. 添加allocator和params:
gst_query_add_allocation_param(query, priv->propose_allocator, &priv->propose_allocation_params);
//2. 创建pool
if (need_pool) {
pool = gst_video_buffer_pool_new ();
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
if (!gst_buffer_pool_set_config (pool, config))
goto config_failed;
//添加pool
gst_query_add_allocation_pool (query, pool, size, 2, 0);
}
//3. 添加meta,可以添加多个meta */
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
之后,src端就可以在自己的decide_allocation函数来解析这些设定值了。
重协商
当配置发生变化的时候,可能会执行再协商一个新的pool,比如视频分辨率发生变化。
上游变化
上游发生变化时,这时候我们不能马上把当前的pool重新配置,因为pipeline中可能还在使用其中的buffer,所以需要创建新的pool。当然也可以等待当前pool耗尽之后,重新配置当前的pool。
重新协商的过程和之前一样,在协商caps之后发起同样的流程。
下游变化
当下游想要修改配置时,需要发送一个GST_EVENT_RECONFIGURE的事件,命令上游重新协商格式和pool。
当pipeline出现元件的增删,或者拓扑结构变化时,会触发RECONFIGURE事件。pipeline的重新配置也会触发caps和pool的重新协商。
GST_EVENT_RECONFIGURE事件标记每一个需要重新配置的pad。然后,下一次缓冲区分配将需要重新协商或重新配置pool。
对于GST_EVENT_RECONFIGURE的caps相关流程可以参考《capabilities negociation 规格协商》。
参考
插件开发教程之内存分配
Gstreamer 设计文档之bufferpool
Gstreamer API之bufferpool