Gstreamer 内存分配协商机制

本文详细介绍了GStreamer中内存分配的协商过程,包括发起问询、响应问询、重协商等内容。阐述了如何通过GST_QUERY_ALLOCATION问询来配置分配器、内存池及元数据,确保数据传输的高效性。

在两个衬垫的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, &params);
  }
  //获取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, &params);
  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

<think> 我们正在讨论GStreamer中动态调整Buffer Pool的方法和原理。根据之前的引用,我们知道Buffer Pool是GStreamer中用于高效管理缓冲区(GstBuffer)的机制。动态调整Buffer Pool指的是在运行时改变Buffer Pool的配置,例如缓冲区的大小、数量等,以适应不同的工作负载或媒体格式变化。 ### 动态调整Buffer Pool的原理 在GStreamer中,Buffer Pool通常由元素(如sink元素)创建和管理。当媒体格式(通过Caps协商确定)发生变化时,可能需要调整Buffer Pool的配置。例如,视频分辨率的变化可能需要改变缓冲区的大小。动态调整的原理如下: 1. **停用Buffer Pool(Deactivate)**:首先需要将当前的Buffer Pool设置为非活动状态(inactive)。在非活动状态下,新的`gst_buffer_pool_acquire_buffer()`调用将返回`GST_FLOW_FLUSHING`,表示无法获取缓冲区。同时,停用操作会等待所有已分配的缓冲区被归还到池中[^3]。 2. **重新配置Buffer Pool**:一旦Buffer Pool被停用,就可以修改其配置。这包括设置新的Caps(媒体格式)、缓冲区大小、最小/最大缓冲区数量等。重新配置通常通过`gst_buffer_pool_set_config()`函数完成。 3. **重新激活Buffer Pool(Activate)**:配置完成后,将Buffer Pool重新设置为活动状态。激活操作会根据新的配置预分配缓冲区(如果配置要求预分配)[^3]。 ### 动态调整的方法 动态调整Buffer Pool通常发生在Caps协商之后,即当元素的`set_caps`方法被调用时。以下是一个典型的流程: 1. **Caps协商**:当两个相连的元素完成Caps协商后,下游元素(如sink)会收到一个确定的Caps(媒体格式)。此时,元素需要检查当前的Buffer Pool是否支持新的Caps,或者是否需要重新配置。 2. **检查并重新配置Buffer Pool**: - 如果元素已经有一个Buffer Pool,它需要停用该池,然后设置新的配置(基于新的Caps),再激活。 - 如果元素还没有Buffer Pool,则创建一个新的Buffer Pool并配置它。 3. **处理缓冲区**:配置完成后,元素可以使用新的Buffer Pool来分配和释放缓冲区。 以下是一个简化的代码示例(基于GStreamer插件开发): ```c static gboolean gst_my_element_set_caps (GstMyElement *self, GstCaps *caps) { /* 1. 解析caps获取需要的参数(如视频宽度、高度等) */ GstVideoInfo info; if (!gst_video_info_from_caps(&info, caps)) { GST_ERROR_OBJECT(self, "Failed to parse caps"); return FALSE; } /* 2. 如果存在已有的Buffer Pool,停用它 */ if (self->pool) { gst_buffer_pool_set_active(self->pool, FALSE); gst_object_unref(self->pool); self->pool = NULL; } /* 3. 创建新的Buffer Pool */ self->pool = gst_video_buffer_pool_new(); /* 4. 配置Buffer Pool */ GstStructure *config = gst_buffer_pool_get_config(self->pool); gst_buffer_pool_config_set_params(config, caps, info.size, 0, 0); if (!gst_buffer_pool_set_config(self->pool, config)) { GST_ERROR_OBJECT(self, "Failed to set config"); return FALSE; } /* 5. 激活Buffer Pool */ if (!gst_buffer_pool_set_active(self->pool, TRUE)) { GST_ERROR_OBJECT(self, "Failed to activate pool"); return FALSE; } return TRUE; } ``` ### 注意事项 - **并发处理**:在停用Buffer Pool时,需要确保所有已分配的缓冲区都被归还。GStreamer内部会等待所有缓冲区归还,因此开发者无需手动等待[^3]。 - **配置参数**:配置Buffer Pool时,通常需要设置以下参数: - Caps:媒体格式。 - 缓冲区大小(size):根据媒体格式计算出的每个缓冲区的大小。 - 最小/最大缓冲区数量:池中缓冲区的最小和最大数量。 - **预分配**:激活Buffer Pool时,如果配置要求预分配(例如设置了最小缓冲区数量),则会预分配这些缓冲区[^3]。 ### 引用说明 - 当缓冲区引用计数归零时,如果它来自Buffer Pool,则会被自动归还到池中[^1]。 - 激活Buffer Pool时会根据配置预分配资源,停用时则在所有缓冲区归还后释放资源[^3]。 ### 总结 动态调整Buffer Pool是GStreamer中处理媒体格式变化的关键机制。通过停用、重新配置和重新激活Buffer Pool,元素能够适应新的媒体格式,并高效地管理缓冲区资源。这一过程通常在Caps协商后触发,并由元素(如sink或解码器)负责执行。 §§相关问题§§ 1. GStreamer中如何自定义Buffer Pool? 2. Caps协商失败时如何影响Buffer Pool? 3. 如何监控Buffer Pool的使用情况(如缓冲区分配和归还)? 4. 动态Pad(如decodebin的输出Pad)如何管理Buffer Pool? </think> ### GStreamer动态调整Buffer Pool的方法与原理 Buffer Pool在GStreamer中用于高效管理媒体数据缓冲区(`GstBuffer`)。动态调整的核心原理是**根据运行时需求重新配置Pool参数**,主要涉及以下流程: #### 动态调整原理 1. **按需激活/停用** Buffer Pool仅在需要时激活(`gst_buffer_pool_set_active(TRUE)`),激活时根据配置预分配资源[^3]: ```c gboolean gst_buffer_pool_set_active(GstBufferPool *pool, gboolean active); ``` - 激活时:预分配缓冲区(根据`min_buffers/max_buffers`参数) - 停用时:释放资源(需等待所有缓冲区归还) 2. **引用计数驱动** 缓冲区使用完毕时(引用计数=0),自动触发归还机制[^1]: ```mermaid graph LR A[Buffer引用计数归零] --> B{来自Pool?} B -- 是 --> C[gst_buffer_pool_release_buffer] B -- 否 --> D[直接释放内存] ``` 3. **动态重配置** 当媒体格式变化时(如分辨率改变),需重新配置Pool: ```c GstStructure *config = gst_buffer_pool_get_config(pool); gst_buffer_pool_config_set_params(config, new_caps, new_size, min_buf, max_buf); gst_buffer_pool_set_config(pool, config); // 应用新配置 ``` #### 动态调整方法 1. **Caps变更触发调整** 在元素的`set_caps()`虚函数中响应格式变化: ```c static gboolean my_element_set_caps(GstBaseTransform *trans, GstCaps *incaps, GstCaps *outcaps) { MyElement *self = MY_ELEMENT(trans); // 停用旧Pool if (self->pool) gst_buffer_pool_set_active(self->pool, FALSE); // 创建新Pool并配置 self->pool = gst_video_buffer_pool_new(); GstVideoInfo info; gst_video_info_from_caps(&info, outcaps); GstStructure *config = gst_buffer_pool_get_config(self->pool); gst_buffer_pool_config_set_params(config, outcaps, info.size, 5, 10); // min=5, max=10 gst_buffer_pool_set_config(self->pool, config); // 激活新Pool gst_buffer_pool_set_active(self->pool, TRUE); return TRUE; } ``` 2. **运行时参数调整** 动态修改Pool参数(需先停用): ```c // 获取当前配置 GstStructure *config = gst_buffer_pool_get_config(pool); // 修改参数(如增加缓冲区数量) gint min_buf, max_buf; gst_buffer_pool_config_get_params(config, NULL, NULL, &min_buf, &max_buf); gst_buffer_pool_config_set_params(config, NULL, -1, min_buf+2, max_buf+5); // 应用新配置 gst_buffer_pool_set_active(pool, FALSE); // 先停用 gst_buffer_pool_set_config(pool, config); gst_buffer_pool_set_active(pool, TRUE); // 重新激活 ``` 3. **自适应分配策略** 通过`gst_buffer_pool_acquire_buffer()`动态获取缓冲区: ```c GstFlowReturn ret; GstBuffer *buffer; ret = gst_buffer_pool_acquire_buffer(pool, &buffer, NULL); if (ret == GST_FLOW_OK) { // 成功获取缓冲区 } else if (ret == GST_FLOW_EOS) { // 资源不足时的处理逻辑 // 可在此触发Pool扩容 } ``` #### 关键特性 1. **零拷贝优化** 通过`GstAllocator`与Pool配合,支持DMA-BUF等零拷贝机制[^3]: ```c GstAllocator *allocator = gst_dmabuf_allocator_new(); gst_buffer_pool_config_set_allocator(config, allocator, NULL); ``` 2. **自动回收机制** 当元素释放缓冲区时(`gst_buffer_unref()`),自动检测来源并归还Pool[^1] 3. **线程安全** 所有Pool操作内部实现锁机制,确保多线程安全 > **应用场景示例**:视频分辨率切换 > 当视频流从1080p切换到4K时: > 1. 触发`set_caps()`接收到新分辨率Caps > 2. 停用旧Pool(等待所有缓冲区归还) > 3. 创建新Pool配置更大缓冲区尺寸 > 4. 激活新Pool继续数据处理
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值