Surface中的BufferQueue

1. BufferQueue 是什么

  • 本质 :一个环形缓冲队列 (slots + fences),承接"生产者 Producer 写帧 → 消费者 Consumer 取帧"的流。

  • 关键目标零拷贝/少拷贝传递图像帧,降低延迟与功耗。

  • 典型 Producer:MediaCodec(硬解)、MediaPlayer/ExoPlayer、OpenGL(EGL)、Canvas CPU 绘制。

  • 典型 Consumer

    • SurfaceView:SurfaceFlinger(系统合成器,独立图层)

    • TextureView:SurfaceTexture(把帧变为 GPU 纹理参与应用内合成)

简图:

scss 复制代码
Producer(解码/GL/Canvas) ──> Surface(IGraphicBufferProducer)
     │                                  │
     │                           [BufferQueueCore]
     ▼                                  │
  queueBuffer()                   acquireBuffer()
                                     ▼
   -----fence---->           Consumer(SF/SurfaceTexture)

2. 内部构成(你需要知道的名词)

  • Slots:固定数量的缓冲槽位(Android 源码里 NUM_BUFFER_SLOTS=64 的上限,实际使用远小于此,一般 2~4)。
  • GraphicBuffer:每个 slot 上的图像缓冲(显存/共享内存)。
  • Fence(栅栏) :同步原语,标记"GPU/硬件写入/读取完成"。Producer queue 时附release fence ;Consumer acquire 时拿到acquire fence
  • IGraphicBufferProducer / Consumer:BufferQueue 的 Binder 两端接口;Java Surface → native Surface.cpp → IGBP。
  • BufferQueueProducer/Consumer/Core:C++ 里三分体实现(队列核心 + 生产/消费端包装)。

3. 状态机与典型时序

3.1 Producer 侧(写帧)

  1. dequeueBuffer() :向队列申请一个空闲 slot

    • 若没有可用 slot,会阻塞/失败
  2. 请求/复用缓冲 :如果大小/格式/usage 不匹配,触发重新分配(避免每帧分配!)。

  3. 渲染写入

    • CPU:ANativeWindow_lock() → 绘制 → unlockAndPost()
    • GL:向 egl window surface 画 → eglSwapBuffers()
    • MediaCodec:解码器把帧直接写入绑定的 Surface
  4. queueBuffer() :把填好的帧入队,并附上 release fence、时间戳、裁剪、变换等元数据。

    • 若队列满,可能阻塞或丢帧(async 模式)。

3.2 Consumer 侧(读帧)

  1. acquireBuffer() :取队首可用帧;等待 acquire fence 完成再读(保证数据有效)。

  2. 消费

    • SurfaceView:SurfaceFlinger 在下一次合成周期把这帧与其它图层进行硬件合成/混合
    • TextureView:SurfaceTexture 把帧更新到 GL 纹理,在应用进程的 ViewRoot 绘制时采样参与合成。
  3. releaseBuffer() :用完把 slot 归还队列(可再次被 Producer 复用)。

关键点双/三缓冲常见。三缓冲可降低生产/消费不同步导致的阻塞,但会增加一帧延迟。


4. 容量/阻塞规则(为什么会"卡住/掉帧")

  • 队列允许 Producer 同时"借出"的 buffer 数量受 maxDequeuedBufferCount 限制(默认根据 Consumer 能同时持有的数量决定)。

  • 当 Producer 已借出过多Consumer 持有未释放 ,再 dequeueBuffer() 可能阻塞

  • 当 Producer queueBuffer() 太快(消费者来不及合成/采样):

    • 同步模式:可能在 queueBuffer() 阻塞等待。
    • 异步模式 (setAsyncMode(true)):不阻塞,丢弃旧帧保留最新帧(视频预览常用)。
  • 尺寸/格式改变 会导致老缓冲失配并重分配,频繁 reallocate = 抖动/卡顿

经验:视频/相机建议 3 缓冲 (Producer 可 deque 2 个,Consumer 同时 hold 1 个),并开启异步模式以避免阻塞主渲染。


5. SurfaceView vs TextureView:BufferQueue 的去向不同

维度 SurfaceView 管线 TextureView 管线
Consumer SurfaceFlinger(系统) SurfaceTexture(应用)
合成 独立图层,与其它窗口一起合成 作为纹理参与 ViewRoot 的 GPU 合成
延迟/功耗 更低 略高(多一次纹理采样与应用内合成)
动画/裁剪 受限 灵活(透明/圆角/Matrix)

相同点:两者 Producer 都是对一个 Surface 写帧;BufferQueue 机制一致,只是"另一端是谁"不同。


6. 关键参数与调优点(Producer 侧常用)

  • 尺寸:ANativeWindow_setBuffersGeometry() / MediaCodec format.setInteger(KEY_WIDTH/HEIGHT, ...)

    只在必要时改,否则频繁重分配。

  • 格式:PIXEL_FORMAT_RGBA_8888 等;视频解码一般 NV12/NV21/YUV420(由 Codec & HWC 决定)。

  • Usage:GRALLOC_USAGE_SW_READ/WRITE, HW_TEXTURE, HW_COMPOSER, PROTECTED(DRM)等;决定缓冲放在哪、能否被哪类硬件高效访问。

  • Dataspace/ColorSpace:BT.709/BT.2020/HDR;和 HWC/GL 管线匹配,否则会偏色/错 gamma。

  • Scaling/Transform/Crop:通过 queueBuffer() 的元数据携带。SurfaceView 通常用 HWC 处理,TextureView 用 setTransform()。


7. 常见问题 & 对策

  1. 卡住在 dequeue/queue

    • 增加缓冲数(setBufferCount / setMaxDequeuedBufferCount);
    • 开启 async 模式(允许丢旧帧保最新);
    • 检查 Consumer 是否长期不 releaseBuffer()(例如合成阻塞、GL 卡帧)。
  2. 频繁重分配(alloc churn)

    • 避免每帧改分辨率/格式;先 detach 再统一 setBuffersGeometry,或在场景切换时一次性变更。
  3. 黑屏/花屏/绿屏

    • TextureView 某些机型/解码器组合不稳 → 验证改用 SurfaceView;
    • 确认 setSurface(null) 的时序,销毁前解绑定;
    • 检查颜色空间/stride 对齐问题(尤其自研 GL)。
  4. 高延迟

    • 三缓冲 + async;降低渲染分辨率;减少在应用进程的二次合成(TextureView->考虑 SurfaceView)。
  5. 截图/录屏差异

    • SurfaceView 不参与应用内 View.draw() 截屏;系统录屏一般可见;DRM 可 setSecure(true) 禁止截屏。

8. 调试与观测

  • adb shell dumpsys SurfaceFlinger --list / --displays / --latency:看图层、刷新、合成信息。
  • Systrace/Perfetto:Graphics、View、Sched、Freq 轨道看 queue/dequeue、合成耗时与丢帧。
  • dumpsys gfxinfo:帧时间分布、Janky 统计。
  • 开启 debug.sf.* 系列系统属性(开发版 ROM)可见更详细日志(谨慎)。

9. 最小落地骨架(Player → Surface)

SurfaceView

kotlin 复制代码
sv.holder.addCallback(object : SurfaceHolder.Callback {
    override fun surfaceCreated(h: SurfaceHolder) {
        player.setVideoSurface(h.surface)        // Media3/ExoPlayer API
        player.playWhenReady = true
    }
    override fun surfaceDestroyed(h: SurfaceHolder) {
        player.setVideoSurface(null)             // 先解绑再 release
    }
})

TextureView

kotlin 复制代码
tv.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
    override fun onSurfaceTextureAvailable(st: SurfaceTexture, w: Int, h: Int) {
        player.setVideoSurface(Surface(st))
        player.playWhenReady = true
    }
    override fun onSurfaceTextureDestroyed(st: SurfaceTexture) = true.also {
        player.setVideoSurface(null)
    }
}

若用 EGL/GL 自绘:以 holder.surface 或 Surface(st) 创建 EGL window surface,再 eglSwapBuffers()。


一句话总结

BufferQueue 是 Surface 的"帧中转站":Producer 通过 dequeue→渲染→queue 写帧;Consumer acquire→显示→release 读帧。

SurfaceView ,帧被 SurfaceFlinger 直接合成;在 TextureView,帧先变成纹理参与应用内合成。理解这一点,基本就能把"卡顿/黑屏/延迟"问题对症下药。

相关推荐
用户1563068103511 小时前
Day01 | 什么是Agent?
面试
Momo__1 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富1 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇1 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇1 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆1 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马2 小时前
Verilog开发常见问题汇总解析
前端
子兮曰2 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端
weedsfly2 小时前
语法糖褪去之后——Babel 转译产物中的 JavaScript 本貌
前端·javascript