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,帧先变成纹理参与应用内合成。理解这一点,基本就能把"卡顿/黑屏/延迟"问题对症下药。

相关推荐
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·前端·数据库
白菜豆腐花8 小时前
优雅使用ts,解放双手
前端