2、Buffer申请策略:同步申请 vs 异步申请、申请时机选择、申请数量计算

好,咱们接着聊Buffer申请策略。说实话,这块内容在面试里被问到的频率特别高。很多同学把Buffer申请想得太简单了,觉得不就是调个接口嘛。其实不然,申请策略选错了,轻则掉帧,重则直接黑屏。

我个人习惯把Buffer申请策略拆成三个维度来看:申请方式(同步还是异步)、申请时机(什么时候去要)、申请数量(要多少个才够)。咱们一个一个说。

2.1 同步申请 vs 异步申请

先说说同步申请。同步申请,说白了就是线程阻塞在那里等Buffer。你调用dequeueBuffer,如果当前没有可用的Buffer,线程就挂起了,直到有Buffer回来才继续往下走。

同步申请的特点:

  • 代码逻辑简单,容易理解
  • 延迟可控,但代价是线程阻塞
  • 适合低帧率场景,比如预览30fps以下

我在项目中遇到过一个问题:有个客户做高帧率录像,120fps那种。他们用了同步申请,结果发现帧率死活上不去。为什么?因为每次申请Buffer都要等,等的过程里CPU就空转了。你想想看,120fps意味着每帧只有8.3ms的时间,等个1ms就损失了12%的性能。

那异步申请呢?异步申请就是你先发一个请求出去,然后该干嘛干嘛,等Buffer准备好了再回调通知你。Android的dequeueBuffer其实支持异步模式,通过BufferQueuesetSynchronousMode来控制。

我个人建议:

  • 预览场景:用同步申请,简单可靠
  • 高帧率录像(60fps以上):用异步申请,避免阻塞
  • HDR多帧合成:必须异步,因为要同时处理多个Buffer

嗯,这里要注意一点:异步申请虽然好,但回调处理要小心。我曾经见过一个Case,回调里做了太多耗时操作,结果Buffer处理不过来,反而比同步还慢。异步不是万能药,关键看你怎么用。

2.2 申请时机选择

申请时机这个问题,很多同学容易忽略。什么时候去申请Buffer?是每一帧开始的时候申请?还是提前申请一批?还是用完再申请?

我总结了几种常见的策略:

策略 描述 适用场景
按需申请 每帧开始前申请一个Buffer 普通预览、拍照
预申请 提前申请一批Buffer,放入池中 高帧率录像、连拍
延迟申请 等到真正需要时才申请 低功耗场景、资源紧张时
批量申请 一次申请多个Buffer 多路视频、HDR合成

我个人比较推荐预申请策略。为什么?因为Buffer申请本身是有开销的,涉及到内存分配、ION/ DMA-BUF的映射等等。如果你每帧都去申请,这些开销会累积起来。

我曾经在某个项目中做过测试:预申请3个Buffer vs 每帧按需申请,帧率提升了大约15%。原因很简单,预申请把申请开销平摊到了初始化阶段,运行时只需要从池子里取就行了。

避坑指南:

我曾经踩过一个坑:预申请太多Buffer。当时想着多申请一些,省得不够用。结果内存爆了,系统直接杀了Camera进程。预申请的数量一定要算清楚,不是越多越好。

还有一种情况是延迟申请。这个策略听起来有点反直觉,但在某些场景下很管用。比如低功耗预览模式,你不需要每一帧都处理,可以等用户真正需要拍照时再去申请Buffer。这样平时内存占用很低,功耗也小。

2.3 申请数量计算

好,到了最核心的部分:到底要申请多少个Buffer?

这个问题没有标准答案,但我有一个经验公式:

Buffer数量 = 生产者数量 + 消费者数量 + 1(安全余量)

举个例子:Camera HAL是生产者,SurfaceFlinger是消费者。如果只有一个生产者、一个消费者,那最少需要3个Buffer。为什么?因为生产者正在填一个,消费者正在读一个,还得有一个空闲的等着。

但实际情况更复杂。我整理了一个表格:

场景 推荐Buffer数量 说明
普通预览(30fps) 3-4个 够用,内存占用适中
高帧率录像(120fps) 6-8个 防止生产者等待消费者
HDR多帧合成 8-12个 需要同时持有多个帧
多路Camera同时工作 每路3-4个 总数量要控制,避免内存超限
低功耗模式 2-3个 够用就行,省内存

你可能会问:为什么高帧率录像需要更多Buffer?我解释一下。120fps意味着每8.3ms就要出一帧。如果消费者(比如编码器)处理一帧需要10ms,那它就会落后。Buffer多了,可以缓冲这种速度不匹配。

嗯,这里有个细节:Buffer数量不是越多越好。Buffer多了,内存占用大,而且BufferQueue的维护开销也大。我见过一个项目,申请了16个Buffer,结果内存占用直接飙到200MB+,系统直接OOM了。

我的计算建议:

  1. 先确定生产者和消费者的处理时间
  2. 计算最坏情况下的延迟差
  3. Buffer数量 = 延迟差 / 帧间隔 + 2(安全余量)
  4. 实际测试,调整到最优

举个例子:生产者每8.3ms出一帧,消费者处理一帧需要12ms。延迟差是12ms - 8.3ms = 3.7ms。帧间隔是8.3ms。Buffer数量 = 3.7 / 8.3 + 2 ≈ 2.45,向上取整就是3个。再加上安全余量,建议4-5个。

当然,这只是理论计算。实际项目中,我建议你先按这个公式算,然后实际跑一下看看。因为Buffer申请还涉及到内存碎片、Cache命中率等因素,理论值只能作为参考。

最后说一句:Buffer申请策略没有银弹。不同的场景、不同的硬件、不同的需求,策略都不一样。我的经验是:先理解业务需求,再选策略,最后通过测试验证。别一上来就抄别人的方案,很可能水土不服。