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

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

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

  • 一组固定大小的槽位数组(BufferSlot slotsNUM_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)。
相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
晨米酱1 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
彧azz1 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
汉堡大王95271 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大1 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端