第三章:Gralloc与GraphicBuffer——内存分配的幕后推手
各位好,欢迎来到第三章。这一章我们聊聊Gralloc和GraphicBuffer。说实话,这两个东西是Camera HAL里最绕不开的模块。你想想看,每一帧图像数据从Sensor出来,最终都要落到一块内存里,这块内存怎么来、怎么用、怎么还,全得靠它们。
我在项目中遇到过不少次,明明算法流程没问题,但就是卡顿、掉帧,最后查来查去,问题出在内存分配上。嗯,这一章我们就把它彻底讲透。
3.1 Gralloc HAL接口详解
Gralloc,全称是Graphics Memory Allocator。说白了,它就是Android系统里专门管图形缓冲区分配的那个硬件抽象层。Camera HAL要拿内存,得通过它。
Gralloc HAL的接口定义在hardware/libhardware/include/hardware/gralloc.h里。核心结构体是gralloc_module_t和alloc_device_t。我挑几个关键函数说说:
- alloc():分配一块图形缓冲区。你得告诉它宽、高、像素格式、使用标志。
- free():释放缓冲区。别漏了,漏了就是内存泄漏。
- lock() / unlock():锁定缓冲区,拿到CPU可访问的地址。解锁后才能给硬件用。
- registerBuffer() / unregisterBuffer():跨进程传递时,需要注册/注销缓冲区。
重点:alloc()里的usage标志位非常关键。比如GRALLOC_USAGE_HW_CAMERA_WRITE表示这块内存是给Camera硬件写的,GRALLOC_USAGE_SW_READ_OFTEN表示CPU会频繁读。选错了,性能直接打折扣。
我曾经在一个项目里,因为忘了加GRALLOC_USAGE_HW_CAMERA_WRITE,结果Sensor写数据时走的是CPU路径,帧率直接掉了一半。嗯,这种坑踩过一次就记住了。
3.2 GraphicBuffer的分配与映射
GraphicBuffer是Android上层对Gralloc的封装。你在Camera HAL里看到的sp<GraphicBuffer>,其实就是它。
分配一个GraphicBuffer很简单:
sp<GraphicBuffer> buffer = new GraphicBuffer(
width, height,
HAL_PIXEL_FORMAT_YCbCr_420_888,
GRALLOC_USAGE_HW_CAMERA_WRITE | GRALLOC_USAGE_SW_READ_OFTEN
);
但这里有个细节:GraphicBuffer的构造函数并不会立即分配物理内存。它只是创建了一个轻量级的对象,真正的内存分配发生在第一次调用lock()的时候。这叫延迟分配(Lazy Allocation),好处是节省资源,坏处是如果你忘了lock,内存根本没到位。
映射呢?说白了就是把物理内存映射到进程的虚拟地址空间。Camera HAL里常用的做法是:
void *cpuAddr = nullptr;
buffer->lock(
GRALLOC_USAGE_SW_READ_OFTEN,
&cpuAddr
);
// 现在cpuAddr就是你可以直接读写的内存地址
// 处理完数据后
buffer->unlock();
我的习惯:在分配GraphicBuffer时,我会先预估一下需要的buffer池大小。比如预览场景,我一般分配3-4个buffer做循环。太少容易丢帧,太多浪费内存。这个数字我建议你根据实际帧率和处理延迟来调。
3.3 BufferQueue与生产者消费者模型
BufferQueue,这个名字起得很直白——就是缓冲区的队列。它实现了一个经典的生产者-消费者模型。
在Camera HAL的场景里:
- 生产者:Camera HAL本身。它从Sensor拿到数据,填入缓冲区。
- 消费者:上层应用或显示系统。它从缓冲区读取数据,用于预览、拍照或编码。
- BufferQueue:中间那个协调者。它管理着一组缓冲区,谁用完了就还给队列,谁需要就从队列取。
流程大概是这样的:
- 消费者通过
dequeueBuffer()从队列里取一个空闲缓冲区。 - 消费者把缓冲区交给生产者(比如通过
queueBuffer()的变种)。 - 生产者往缓冲区里写数据,写完后调用
queueBuffer()还回去。 - 消费者再从队列里取,如此循环。
注意:BufferQueue是有容量上限的。默认一般是64个,但Camera场景下我建议你设小一点,比如8-16个。为什么呢?因为Camera数据量大,队列太长会导致延迟飙升。我曾经在一个4K60fps的项目里,队列设了32个,结果从拍照到回显延迟了将近半秒。后来改成8个,延迟降到了100ms以内。
还有一个容易踩的坑:死锁。如果生产者和消费者都在等对方释放缓冲区,那就卡死了。我记得有一次调试,发现预览画面卡住不动,查了半天,原来是消费者线程在等dequeue,而生产者线程在等queue,两个互相等,谁也不让谁。解决方案是加超时机制,或者用异步回调。
3.4 实战中的内存管理要点
好了,理论讲完了,我总结几个实战中一定要记住的点:
| 要点 | 说明 | 我的建议 |
|---|---|---|
| usage标志位 | 必须准确设置,否则性能下降 | Camera写+CPU读是标配 |
| buffer池大小 | 太少丢帧,太多延迟 | 预览3-4个,录像4-6个 |
| lock/unlock配对 | 漏了unlock会导致内存泄漏 | 用RAII封装,自动管理 |
| 跨进程传递 | 要用registerBuffer/unregisterBuffer | 别忘了在远端注册 |
| 死锁预防 | 生产者和消费者互相等待 | 加超时或异步回调 |
核心思想:内存管理不是越多越好,也不是越快越好,而是刚刚好。你要根据实际的帧率、分辨率、处理延迟来动态调整。我一般会在HAL里加一个调试接口,可以实时查看buffer池的使用情况,这样调优起来就方便多了。
嗯,这一章就到这里。Gralloc和GraphicBuffer是Camera HAL的基石,BufferQueue是协调的枢纽。搞懂了它们,你就能理解为什么有时候换一块内存分配策略,整个系统的流畅度就完全不一样了。下一章我们聊聊更具体的Buffer管理策略,到时候见。