第二章:HAL层设计哲学

各位好,欢迎来到第二章。今天咱们聊聊HAL层——这个在RK平台里承上启下的关键角色。说实话,我刚开始接触RK平台时,也被这层抽象搞得有点晕。但干久了你会发现,HAL层的设计哲学,说白了就是一句话:让上层不用关心底层怎么干活。

2.1 HAL层在RK平台中的定位

先说说HAL层到底站在哪。你想想看,整个相机系统从硬件到应用,大概分这么几层:

  • 硬件层:Sensor、ISP芯片、MIPI接口这些物理器件
  • Kernel驱动层:负责寄存器读写、中断处理、DMA传输
  • HAL层:我们今天的主角,封装底层细节
  • Framework层:Android Camera API、CameraService
  • 应用层:你手机里的相机App

HAL层就夹在Kernel驱动和Framework中间。它像是一个翻译官——把Framework那些高大上的API调用,翻译成驱动能听懂的寄存器操作。我在项目中遇到过不少新人,上来就跳过HAL层直接调驱动,结果代码耦合得一塌糊涂,换个Sensor就得重写一半逻辑。

核心定位:HAL层是硬件抽象层,它的存在就是为了让上层代码不依赖具体硬件。你换Sensor也好,换ISP芯片也好,只要HAL接口不变,Framework和应用层代码一行都不用改。

嗯,这里要注意:RK平台的HAL层不是简单的函数封装。它里面包含了大量的图像处理算法、3A(自动曝光、自动对焦、自动白平衡)策略、以及buffer管理逻辑。说白了,它才是真正干活的。

2.2 与Kernel ISP驱动的接口

HAL层往下走,就是Kernel驱动。这个接口怎么设计的?我画个图你就明白了。

Framework层 (CameraService / Camera API) open() → configureStreams() → processCaptureRequest() HAL层 (Camera HAL / ISP HAL) rk_isp_ioctl() / rk_isp_start() / rk_isp_stop() 3A算法、Buffer管理、参数转换 Kernel ISP驱动层 ioctl() / mmap() / DMA-BUF / V4L2 关键接口函数 rk_isp_set_params() rk_isp_get_frame() rk_isp_enum_formats()

从图里你能看到,HAL层通过标准的ioctl和mmap跟驱动打交道。我个人习惯把驱动接口封装成一组内部函数,比如:

// HAL层内部封装的驱动接口
static int rk_isp_set_params(int fd, struct isp_params *params) {
    return ioctl(fd, RK_ISP_SET_PARAMS, params);
}

static int rk_isp_get_frame(int fd, struct frame_buffer *buf) {
    return ioctl(fd, RK_ISP_GET_FRAME, buf);
}

static int rk_isp_start_capture(int fd) {
    return ioctl(fd, RK_ISP_START_CAPTURE, NULL);
}

为什么要这么封装?因为驱动接口可能会变。我记得RK3588的驱动和RK3399的驱动,ioctl命令码就不完全一样。如果你在HAL层到处裸调ioctl,换平台时改代码能改到你怀疑人生。

我的经验:在HAL层和驱动之间再加一层薄薄的适配层。每个平台只需要改适配层,HAL核心逻辑纹丝不动。这个习惯帮我省了至少两个月的调试时间。

2.3 与上层Framework的交互

HAL层往上走,面对的是Android Framework。这里用的是标准的Camera HAL接口——camera3_device_ops_t结构体。你想想看,Framework根本不知道底层是RK ISP还是高通ISP,它只认这组函数指针。

核心接口就这几个:

Framework调用 HAL层实现 说明
open() 初始化ISP、Sensor、分配buffer 我习惯在这里做一次硬件自检
configureStreams() 配置输出流格式、分辨率、fps 注意检查硬件是否支持
processCaptureRequest() 下发拍照/预览请求,触发ISP处理 这是最核心的函数
flush() 清空所有待处理请求 关闭相机时一定要调用

这里有个坑,我踩过好几次:processCaptureRequest()是异步的。Framework下发请求后,HAL层需要尽快返回,然后在后台线程里处理ISP数据。处理完了通过回调函数通知Framework。如果你在processCaptureRequest()里同步等待ISP出图,那整个相机系统就卡死了。

警告:千万不要在processCaptureRequest()里做耗时操作!我曾经因为在这里加了3A统计计算,导致相机预览掉到5帧。正确的做法是:把请求扔到工作队列,立刻返回,后台慢慢算。

再说说buffer管理。Framework和HAL层之间通过camera3_stream_buffer传递图像数据。这里的设计哲学是:谁分配,谁释放。Framework分配buffer给HAL用,HAL填完数据后还回去。但RK平台有个特殊之处——ISP输出的raw图可能跟Framework要求的格式不一样。这时候HAL层就得做格式转换,或者用DMA-BUF零拷贝。

// HAL层处理buffer的典型流程
int process_capture_request(camera3_capture_request_t *request) {
    // 1. 从request中取出输出buffer
    camera3_stream_buffer_t *out_buf = &request->output_buffers[0];
    
    // 2. 把buffer信息传给ISP驱动
    struct isp_buffer_info buf_info;
    buf_info.fd = out_buf->buffer->planes[0].fd;
    buf_info.size = out_buf->buffer->planes[0].size;
    
    // 3. 启动ISP处理(异步)
    enqueue_isp_job(&buf_info);
    
    // 4. 立即返回,不等待
    return 0;
}

// ISP处理完成后的回调
void on_isp_frame_done(struct isp_buffer_info *buf) {
    camera3_capture_result_t result;
    result.num_output_buffers = 1;
    result.output_buffers = ...;  // 填好buffer状态
    
    // 通知Framework取图
    callback->process_capture_result(callback, &result);
}

嗯,这里要注意:process_capture_result回调里要填好acquire_fence和release_fence。这两个fence是Framework用来同步buffer访问的。我见过有人忘了填fence,结果画面撕裂、buffer竞争,各种诡异问题。

2.4 设计哲学总结

说了这么多,总结一下HAL层的设计哲学,其实就三条:

  1. 抽象隔离:把硬件细节关在HAL层里面,外面只看到标准接口
  2. 异步处理:所有耗时操作都异步化,别阻塞Framework
  3. buffer零拷贝:尽量用DMA-BUF共享内存,少做数据搬运

最后说句实在的:HAL层写得好不好,直接决定了你后面调试顺不顺。我见过太多项目,HAL层写得乱七八糟,结果联调时天天崩,最后不得不重构。所以,花时间把HAL层的架构设计好,是值得的。

一句话记住:HAL层就是硬件的「代理人」——对上提供标准服务,对下管理具体硬件。代理人靠谱,整个系统就稳了。

可可精品资料库