HAL层基础:Camera HAL架构解析
做图像质量调优,绕不开HAL层。说白了,HAL就是操作系统和硬件之间的翻译官。你上层App发一个「拍照」指令,经过Framework层层传递,最后到HAL这里,它得告诉硬件:「嘿,该干活了」。
我个人习惯把HAL层比作一个黑盒的接口层。你不需要知道硬件怎么实现的,但你必须知道它提供了什么能力。今天我们就从Android HAL3开始聊起。
Android HAL3架构
Android的Camera HAL经历了HAL1到HAL3的演进。HAL1太老了,现在主流都是HAL3。为什么?因为HAL3支持更精细的控制。
HAL3的核心设计理念是「管道模型」。你想想看,数据从传感器出来,经过ISP处理,最后送到应用层,这不就是一条流水线吗?
HAL3架构中,主要有这几个角色:
- CameraDevice:代表一个物理摄像头设备
- CameraSession:管理数据流的会话
- CaptureRequest:每一次捕获的请求描述
- CaptureResult:捕获完成后的结果反馈
我在项目中遇到过一个问题:某个平台HAL3实现不完整,导致预览和拍照的帧率不一致。查了半天,发现是CaptureRequest里的控制参数没传对。嗯,这里要注意,HAL3的接口定义很严格,少一个参数都可能出问题。
核心要点:HAL3的架构设计让应用层可以同时发送多个请求,并且每个请求可以独立配置参数。这就是为什么你可以一边预览一边拍照,还能同时做视频录制。
HAL层数据流
数据流是HAL层最直观的部分。我们分三种场景来看:Preview、Video、Snapshot。
Preview数据流
预览是最基础的需求。流程大概是这样的:
- App创建CameraCaptureSession
- App提交重复的CaptureRequest(设置TEMPLATE_PREVIEW)
- HAL层从Sensor获取帧数据
- 经过ISP处理后,送到Surface上显示
这里有个坑:预览的帧率一般要求30fps,但有些低端平台只能跑到24fps。我曾经调试过一个项目,预览画面明显卡顿,最后发现是Sensor的输出分辨率设置太高了。降低到1080p后,帧率就稳了。
Video数据流
视频录制和预览其实共享同一条数据流。区别在于:
- 预览走的是显示Surface
- 录制走的是编码器Surface
HAL3支持同时输出多个Stream。你可以把预览和录制配置成两个不同的Stream,一个给显示,一个给编码器。这样互不干扰。
经验之谈:视频录制时,我建议把预览的帧率和录制的帧率保持一致。否则会出现预览流畅但录出来的视频卡顿的情况。别问我怎么知道的,都是泪。
Snapshot数据流
拍照是最高质量的需求。Snapshot的流程和预览不同:
- App提交单次CaptureRequest(设置TEMPLATE_STILL_CAPTURE)
- HAL层使用最高质量参数处理
- 输出全分辨率JPEG或YUV
为什么拍照要单独走一个请求?因为拍照需要更长的曝光时间、更复杂的后处理。你想想看,预览要30fps,每帧只有33ms的处理时间。但拍照可以花几百毫秒甚至几秒来优化画质。
我曾经调试过一个bug:拍照后照片偏暗。排查发现是HAL层在Snapshot模式下没有正确应用AE补偿。说白了,就是预览和拍照的曝光参数没同步。
Buffer管理机制
Buffer管理是HAL层的核心难点。数据在HAL层流转,本质上就是Buffer在流转。
Android使用Gralloc来管理图形Buffer。HAL3引入了BufferQueue机制,生产者(HAL)把Buffer填好数据,消费者(App/显示/编码器)消费Buffer。
Buffer管理的关键点:
| 概念 | 说明 |
|---|---|
| Buffer分配 | 由Gralloc分配,支持多种格式(NV12、NV21、YV12等) |
| Buffer流转 | 通过dequeue_buffer和queue_buffer操作 |
| Buffer复用 | 消费完成后,Buffer回到池中等待下次使用 |
| 内存优化 | 避免频繁分配释放,使用Buffer池 |
注意:Buffer泄漏是HAL层最常见的bug之一。如果Buffer没有正确归还到池中,很快就会出现「out of memory」错误。我见过一个项目,连续拍照10张后系统就崩了,最后发现是JPEG编码完成后没有release Buffer。
Buffer管理的代码示例(简化版):
// HAL层处理Buffer的典型流程
void processCaptureRequest(capture_request_t *request) {
// 1. 从Buffer池中获取一个空Buffer
buffer_handle_t buffer = gralloc_alloc(width, height, format);
// 2. 填充图像数据
fill_image_data(buffer, sensor_data);
// 3. 将Buffer交给消费者
buffer_queue.queue_buffer(buffer);
// 4. 消费者处理完成后,Buffer自动回池
// 注意:这里不能手动释放,否则会double free
}
Metadata传递机制
Metadata是HAL3的灵魂。它负责传递控制参数和状态信息。
Metadata的结构是key-value对。Android定义了一套标准的Metadata key,比如:
ANDROID_SENSOR_EXPOSURE_TIME:曝光时间ANDROID_SENSOR_SENSITIVITY:ISO感光度ANDROID_LENS_FOCUS_DISTANCE:对焦距离ANDROID_CONTROL_AE_MODE:自动曝光模式
Metadata的传递流程:
- App在CaptureRequest中设置控制参数
- HAL层解析Request中的Metadata
- HAL层配置硬件参数
- 捕获完成后,HAL层填充Result Metadata
- App通过CaptureResult获取实际参数
这里有个容易忽略的点:Request中的参数是「期望值」,Result中的参数是「实际值」。比如你设置曝光时间为10ms,但硬件可能只能做到10.5ms。Result里会告诉你实际用了多少。
避坑指南:我曾经调试过一个3A(AE/AWB/AF)不收敛的问题。查了两天,最后发现是Metadata里的AE补偿值没有正确传递到HAL层。App设置了补偿值,但HAL层根本没读到。所以,调试时一定要打印出完整的Metadata内容,看看参数到底传没传过去。
Metadata的序列化格式通常是二进制的,但调试时可以转成可读的JSON格式。我习惯在HAL层加一个dump函数,把每次Request和Result的Metadata都打印出来。这样问题定位快很多。
知识体系总览
下面这张图展示了HAL层核心知识的关系:
这张图把HAL3的核心模块串起来了。从上到下是数据流的方向,Metadata则在各个模块之间双向传递。Buffer管理贯穿整个流程,是性能的关键。
好了,HAL层的基础就聊到这里。记住一句话:HAL3的核心是「请求-响应」模型,理解了这个,后面的调优就好办了。