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)。
相关推荐
一只小风华~14 分钟前
学习笔记:Vue Router 中的链接匹配机制与样式控制
前端·javascript·vue.js·笔记·学习·ecmascript
Jerry_Rod25 分钟前
react+umijs 项目快速学习
前端·react.js
京东云开发者30 分钟前
浅析cef在win和mac上的适配
前端
uhakadotcom37 分钟前
在chrome浏览器插件之中,options.html和options.js常用来做什么事情
前端·javascript·面试
想想就想想37 分钟前
线程池执行流程详解
面试
西瓜树枝41 分钟前
Chrome 扩展开发从入门到实践:以 Cookie 跨页提取工具为例,拆解核心模块与交互逻辑
前端·javascript·chrome
gplitems1231 小时前
Download:Blaxcut - Barbershop & Hair Salon WordPress Theme
前端
拜无忧1 小时前
【DEMO】互动信息墙 - 无限流动版-点击放大
前端
AliPaPa1 小时前
你可能忽略了useSyncExternalStore + useOptimistic + useTransition
前端·react.js