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只做控制。
我常用的方法有这几个:
- 使用ION/DMA-BUF直接共享:Camera Sensor输出的数据,直接通过DMA-BUF传给Display或Video编码器。中间不经过CPU。
- 避免格式转换:如果Display支持YUV直接输出,就别转成RGB。很多硬件Composer支持YUV格式,直接送过去就行。
- 使用零拷贝技术:比如通过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,就是靠这三板斧。
嗯,这一章就到这里。下一章咱们聊聊帧率控制和同步机制,那又是另一个坑了。