4、HAL层架构:Camera HAL3中YUV与RAW的数据流路径
好,咱们今天来聊聊HAL3里YUV和RAW到底是怎么走的。说实话,我刚接触HAL3的时候,也被那一堆buffer和stream搞晕过。但后来我发现,只要抓住一条主线——数据从哪来,到哪去,中间经过谁——其实没那么复杂。
4.1 HAL3的整体数据流模型
HAL3的设计思路,说白了就是「一次请求,一次结果」。应用层下发一个capture request,HAL层返回一个或多个result。每个result里可以包含不同格式的buffer,比如YUV、RAW、JPEG等等。
我个人习惯把HAL3的数据流分成三个阶段:
- 请求阶段:App通过CameraService下发request,里面指定了要哪些stream的buffer。
- 处理阶段:HAL层驱动ISP(图像信号处理器)干活,把sensor的原始数据转成各种格式。
- 返回阶段:HAL把填好数据的buffer塞回framework,App拿到后显示或保存。
嗯,这里要注意:HAL3里所有buffer都是提前分配好的,不是拍一张分一张。你想想看,如果每拍一张都去申请内存,那延迟得多大?
4.2 YUV数据流路径
YUV在HAL3里是最常见的输出格式。App预览、录像、拍照,大部分时候用的都是YUV。它的路径大概是这样的:
- Sensor输出Bayer RAW:这是最原始的数据,每个像素只有一种颜色分量。
- ISP做Demosaic:把Bayer格式插值成RGB,再转成YUV。
- HAL拿到YUV buffer:这时候数据已经在内存里了,HAL负责把它填到request指定的buffer中。
- 返回给framework:App拿到YUV数据后,可以显示、编码或者做后处理。
- Sensor输出Bayer RAW:和YUV路径的第一步一样。
- HAL直接拿到RAW buffer:ISP不做任何处理,直接把sensor的数据拷贝到buffer里。
- 返回给framework:App拿到RAW数据后,可以自己写算法做后处理,或者保存成DNG文件。
我在项目中遇到过一个问题:YUV的排列方式搞错了。有的平台用NV12,有的用NV21,还有用YV12的。如果HAL层不匹配,出来的图像颜色就是花的。我曾经因为这个排查了整整两天,最后发现是buffer的stride参数没对齐。
关键点:YUV数据流的核心是「格式匹配」。HAL层输出的YUV格式必须和framework期望的一致,否则图像显示会出问题。
4.3 RAW数据流路径
RAW数据流相对简单一些,因为它跳过了ISP的大部分处理。RAW说白了就是sensor直接吐出来的原始数据,没有经过任何颜色插值、白平衡、降噪等处理。
RAW的路径通常用于专业拍照场景,比如DNG格式输出。它的流程是:
这里有个坑,我提醒一下各位:RAW数据通常很大。一张1200万像素的RAW,如果每个像素10bit,那大小就是1200万×10bit,约15MB。如果连续拍多张,内存压力会很大。
注意:RAW数据流中,HAL层需要特别注意metadata的填充。比如sensor的black level、white balance gains、color correction matrix等,这些信息必须准确,否则App拿到RAW也无法正确还原颜色。
4.4 YUV与RAW的并行路径
实际项目中,YUV和RAW经常是同时走的。比如App既要预览(YUV),又要保存RAW。这时候HAL3需要同时处理多个stream。
我记得有一次做双摄方案,主摄输出RAW用于算法,副摄输出YUV用于预览。两个stream的buffer大小、格式、帧率都不一样。HAL层需要协调好ISP的调度,确保两个stream都能按时填好buffer。
具体实现上,HAL3的processCaptureRequest函数会收到一个request列表,每个request里包含多个stream的buffer。HAL需要遍历这些buffer,逐个填充数据。
// 伪代码示例:HAL3处理多个stream
void processCaptureRequest(CaptureRequest* request) {
for (int i = 0; i < request->num_buffers; i++) {
StreamBuffer* buffer = &request->buffers[i];
if (buffer->format == HAL_PIXEL_FORMAT_YUV) {
// 走YUV路径:ISP处理后填充
fill_yuv_buffer(buffer);
} else if (buffer->format == HAL_PIXEL_FORMAT_RAW) {
// 走RAW路径:直接拷贝sensor数据
fill_raw_buffer(buffer);
}
}
}
你想想看,如果YUV和RAW的帧率不一样,比如YUV要30fps,RAW只要10fps,那HAL层就得做帧率匹配。我一般会在HAL层维护一个帧计数器,根据计数决定是否输出RAW。
4.5 数据流中的关键参数
不管是YUV还是RAW,有几个参数是HAL层必须处理好的:
| 参数 | YUV路径 | RAW路径 |
|---|---|---|
| 分辨率 | 通常和预览尺寸一致 | 通常是sensor全分辨率 |
| 格式 | NV12、NV21、YV12等 | RAW10、RAW12、RAW16等 |
| Stride | 需要对齐到16或32字节 | 通常按行对齐 |
| 帧率 | 30fps或60fps | 可能低于YUV |
嗯,这里要特别说一下stride。很多新手会忽略这个参数,以为分辨率就是buffer大小。其实不是的,硬件为了对齐,每行数据后面可能会补一些无效字节。如果HAL层不按stride填数据,App读出来的图像就是歪的。
小技巧:调试YUV/RAW数据流时,我建议先dump一帧buffer到文件里,用工具(比如7yuv)打开看看。如果图像正常,说明数据流没问题;如果花屏或错位,那大概率是stride或format搞错了。
4.6 总结一下
YUV和RAW的数据流路径,说白了就是两条线:YUV经过ISP处理,RAW直接透传。HAL3的架构设计让这两条线可以并行跑,但需要处理好buffer管理、格式匹配和帧率协调。
我个人觉得,理解数据流的关键在于「站在buffer的角度看问题」。buffer从sensor出来,经过ISP或直接拷贝,最后到App手里。每一步都有对应的参数和约束,搞清楚了这些,HAL3的数据流就没那么神秘了。
下一章我会讲具体的代码实现,包括怎么在HAL层配置stream、怎么填充buffer、怎么处理metadata。到时候咱们再细聊。