BufferQueue的环形队列是什么设计的

1. 总览:固定槽位池 + 有界 FIFO

BufferQueue 的"环形"不是单块连续内存做取模指针,而是:

  • 一组固定大小的槽位数组(BufferSlot slots[NUM_BUFFER_SLOTS],AOSP 定义通常为 64 上限)存放 GraphicBuffer 指针与元数据;

  • 一条有界 FIFO 队列(mQueue,存 BufferItem,按帧序号入队出队)。

  • 槽位反复复用 + 队列首尾推进 ⇒ 形成逻辑上的环

常见参与方:

  • Producer(相机/MediaCodec/GL 等):dequeueBuffer() → 写图 → queueBuffer()

  • Consumer(SurfaceFlinger/TextureView 等):acquireBuffer() → 用图 → releaseBuffer()

2. 核心数据结构

  • BufferSlot[] :每个 slot 里保存:

    • sp(显存句柄)
    • 状态(见下一节)
    • fences、crop、transform、timestamp、damage 等元数据
  • mQueue (std::list / deque) :排队的可消费帧(指向某个 slot + 提交参数)

  • free 列表 / 索引集合:可 dequeue 的空闲 slot

  • 计数器:frameNumber(单调递增),maxBufferCount,maxAcquiredBufferCount

3. 槽位状态机(Slot State)

典型四态(不同版本略有扩展):

rust 复制代码
FREE -> DEQUEUED -> QUEUED -> ACQUIRED -> FREE
  • FREE:空闲,可被 Producer dequeueBuffer

  • DEQUEUED:Producer 已拿到,正在写

  • QUEUED:写完已入队,等待 Consumer 取

  • ACQUIRED:Consumer 正在用

  • releaseBuffer() 把 ACQUIRED 还为 FREE,进入下一轮复用

4. "环形"的本质

  • 槽位是固定池:数量有限,周而复始复用(不像无界分配/释放)。

  • mQueue 是有界 FIFO :队尾入、队首出;当容量用尽,Producer 会背压(dequeueBuffer 阻塞/失败),直到 Consumer 释放。

  • 因为有限容量 + 循环复用,所以从运行形态上呈现"环形队列"。

一个简单示意(3 缓冲典型):

less 复制代码
[FREE] [FREE] [FREE]        // 初始
  |dequeue→写
[DEQUEUED] [FREE] [FREE]
  |queue→入队
[QUEUED] [FREE] [FREE]
         |acquire→消费
[ACQUIRED] [FREE] [FREE]
         |release
[FREE] [FREE] [FREE]        // 再次回到开头(循环)

5. 关键流程时序(简化)

Producer 侧

  1. dequeueBuffer(slot, fence):找一个 FREE slot,返回写入前需要等待的 fence(保证上游 GPU/Consumer 已经不再使用该缓冲)

  2. 写图(GL/CPU/Codec)

  3. queueBuffer(slot, params):把该帧(BufferItem)压入 mQueue 尾部并标记 QUEUED

Consumer 侧

  1. acquireBuffer(&item, &fence):从队首取 BufferItem,变 ACQUIRED,拿到acquire fence 等待生产者完成写入

  2. 合成/渲染

  3. releaseBuffer(slot, releaseFence):归还该 slot,回到 FREE

6. Fence 同步与零拷贝

  • 通过 sync fence 保证读写时序:Producer 在 dequeue 时等 "写前 fence";Consumer 在 acquire 时等 "写完 fence";释放时给 Producer 返回 "release fence"。

  • 典型是零拷贝 共享(gralloc/allocator 分配的显存缓冲),跨进程只传递 引用 + fence

7. 缓冲数量与背压

  • maxBufferCount(通常 2/3 起步):决定环的"周长"。三缓冲常见,可减少 Producer 等待,平滑抖动。

  • maxAcquiredBufferCount(Consumer 设置,通常为 1):限制 Consumer 同时持有的帧数。

  • 当所有 slot 都处于 DEQUEUED/QUEUED/ACQUIRED(无 FREE)时,Producer 的 dequeueBuffer 会被阻塞或返回应对码,形成背压,防止无限堆积。

8. 与"真正环形数组"的区别

  • 不是单一 head/tail + 取模索引的数据结构实现;

  • 实际实现是槽位池 + FIFO 列表 ,但行为等价于固定容量的环形队列:队列深度有限、首尾推进、元素释放后回到池中再次被复用。

9. 常见调参与模式(概念)

  • Async/Non-async 模式:异步模式允许队列中保留更多已排队帧,减轻 Producer 阻塞,但可能增加延时。

  • Shared/Persistent Buffer(部分版本/场景):特定模式下共享单一缓冲或持久分配,减少分配次数。

  • 裁剪/变换/时间戳/损伤区:随 queueBuffer 一起入队,Consumer 合成时使用,降低带宽与重绘成本。

10. 一句话总结

BufferQueue 的"环形队列"= 固定数量槽位的循环复用 (Slot 池) + 有界 FIFO 帧队列 (mQueue),配合 fence 同步 + 背压 ,在 Producer/Consumer 之间实现零拷贝、低延时、可控容量的图像缓冲传递。

如果你要定位卡顿/延迟/撕裂等问题,优先检查:

  • 当前 maxBufferCount / maxAcquiredBufferCount 是否匹配场景;
  • dequeueBuffer 是否经常超时(背压过强);
  • fence 等待是否过长(GPU/合成拥堵);
  • Consumer 是否长期 ACQUIRED 不释放(忘记 releaseBuffer)。
相关推荐
南北是北北6 小时前
Surface中的BufferQueue
前端·面试
willlzq6 小时前
Swift高级特性深度解析:@dynamicMemberLookup与@dynamicCallable在Builder库中的应用
前端
张旭超7 小时前
vue3 上传插件vue-file-agent-next
前端·vue.js
xianxin_7 小时前
CSS Opacity(透明度)
前端
渐行渐远君489017 小时前
AI Agent智能体与MCP Server开发实践
前端·人工智能
在未来等你7 小时前
Elasticsearch面试精讲 Day 4:集群发现与节点角色
大数据·分布式·elasticsearch·搜索引擎·面试
OEC小胖胖7 小时前
构建单页应用:React Router v6 核心概念与实战
前端·react.js·前端框架·web
ss2737 小时前
手写MyBatis第46弹:多插件责任链模式的实现原理与执行顺序奥秘--MyBatis插件架构深度解析
前端·javascript·html
VIP_CQCRE7 小时前
身份证识别及信息核验 API 对接说明
java·前端·数据库