4、Buffer管理优化:Gralloc Buffer分配策略、减少CPU/GPU拷贝、使用Hardware Composer直通

好,咱们进入第四章。这一章聊的是Buffer管理,说白了就是数据怎么在内存里流转。

做Camera HAL最头疼的问题之一,就是延迟。而延迟的罪魁祸首,十有八九是Buffer拷贝。我见过太多团队,辛辛苦苦优化了算法,结果被Buffer拷贝拖了后腿。你想想看,每一帧数据从Sensor出来,到最终显示到屏幕上,中间要经过多少次内存搬运?每一次搬运都是延迟,都是功耗。

4.1 Gralloc Buffer分配策略

Gralloc是Android图形系统的内存分配器。它负责给Camera、Display、Video等模块分配共享内存。分配策略选对了,延迟能降一大截。

核心原则:尽量使用硬件专用内存,避免使用系统堆内存。

我个人的习惯是,在HAL层初始化时,就根据场景预分配好Buffer池。而不是每次请求来了再分配。为什么?因为分配内存本身就有开销,而且容易产生内存碎片。

关键参数:

  • usage flags:决定内存类型。比如GRALLOC_USAGE_HW_CAMERA_WRITE表示Camera写入,GRALLOC_USAGE_HW_COMPOSER表示给Composer用。
  • format:像素格式。比如HAL_PIXEL_FORMAT_YCbCr_420_888是YUV格式,HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED让厂商自己选最优格式。
  • width/height:分辨率。注意对齐要求,很多硬件要求16或32字节对齐。

举个例子,分配一个预览用的Buffer:

// 分配一个预览Buffer
buffer_handle_t buf;
int stride;
int err = gralloc->alloc(gralloc, 
    width, height, 
    HAL_PIXEL_FORMAT_YCbCr_420_888,
    GRALLOC_USAGE_HW_CAMERA_WRITE | GRALLOC_USAGE_HW_COMPOSER,
    &buf, &stride);
if (err != 0) {
    // 处理错误
    return -ENOMEM;
}

这里有个坑。我曾经在某个项目上,直接用了GRALLOC_USAGE_SW_READ_OFTEN。结果呢?内存分配到了系统堆上,每次CPU读写都要经过Cache,延迟直接飙到30ms以上。后来改成GRALLOC_USAGE_HW_CAMERA_WRITE,延迟降到了5ms以内。

我的建议:分配Buffer时,尽量指定HW usage。如果必须CPU访问,用GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,但要做好Cache同步。

4.2 减少CPU/GPU拷贝

CPU拷贝是延迟的元凶。每一帧数据从Sensor到ISP,再到内存,如果中间经过CPU做格式转换、旋转、缩放,那延迟就上去了。

怎么减少?核心思路是:让硬件做硬件的事,CPU只做控制。

我常用的方法有这几个:

  1. 使用ION/DMA-BUF直接共享:Camera Sensor输出的数据,直接通过DMA-BUF传给Display或Video编码器。中间不经过CPU。
  2. 避免格式转换:如果Display支持YUV直接输出,就别转成RGB。很多硬件Composer支持YUV格式,直接送过去就行。
  3. 使用零拷贝技术:比如通过dma_buf的fd传递,而不是memcpy。

举个例子,我之前优化一个项目的预览延迟。原始流程是:

Sensor -> ISP -> CPU(YUV转RGB) -> GPU(旋转) -> Display

这个流程里,CPU和GPU各做了一次拷贝。延迟大概25ms。

优化后:

Sensor -> ISP -> DMA-BUF -> Display(硬件Composer直接处理)

延迟降到了8ms。说白了,就是让数据在硬件层面直接流转,CPU只负责发命令。

注意:零拷贝不是万能的。如果Display不支持YUV格式,或者需要做旋转、裁剪,那还是得走CPU。但尽量在硬件层面完成这些操作。

4.3 使用Hardware Composer直通

Hardware Composer(HWC)是Android的显示合成模块。它可以把多个图层合成一帧显示到屏幕上。但对我们Camera来说,更重要的是它的“直通”模式。

直通模式是什么?就是Camera输出的Buffer,不经过任何合成,直接送到Display。这样延迟最低。

我做过一个项目,客户要求预览延迟低于10ms。一开始用默认的HWC合成,延迟在15ms左右。后来我改成直通模式,延迟降到了5ms。

怎么实现?在HAL层设置Buffer的usage flags时,加上GRALLOC_USAGE_HW_COMPOSER。然后在HWC的prepare阶段,把Camera的图层标记为“直接显示”。

// HWC prepare阶段
hwc_layer_t *layer = &layers[camera_layer_index];
layer->compositionType = HWC_FRAMEBUFFER_TARGET;
// 或者直接设为 HWC_OVERLAY,表示硬件叠加
layer->hints = HWC_HINT_CLEAR_FB;
layer->flags = HWC_SKIP_COMPOSITION;

这里要注意,直通模式对硬件有要求。Display控制器必须支持直接接收Camera输出的格式。如果格式不匹配,HWC会fallback到GPU合成,那延迟反而更高。

避坑指南:我曾经遇到一个情况,直通模式打开了,但画面是绿的。查了半天,发现是YUV的色域空间不匹配。Camera输出的是BT.601,Display期望的是BT.709。后来在HAL层做了色域转换,问题解决。

4.4 实战总结

Buffer管理优化,说白了就是三件事:

  • 分配策略:预分配、用HW内存、对齐好。
  • 减少拷贝:用DMA-BUF、避免格式转换、零拷贝。
  • 硬件直通:让HWC直接显示Camera Buffer。

这三件事做好了,预览延迟至少能降50%。我见过最极端的案例,从40ms优化到8ms,就是靠这三板斧。

嗯,这一章就到这里。下一章咱们聊聊帧率控制和同步机制,那又是另一个坑了。