第1章:Camera驱动项目实战——从零开始的全链路开发

大家好,我是你们的老朋友。今天咱们聊聊一个完整的Camera驱动项目怎么做。

说实话,我入行那会儿,最怕的就是这种「从零开始」的项目。Sensor型号没见过,datasheet几百页,Android框架像座大山。但后来我发现,只要把流程拆开,一步步来,这事儿其实没那么玄乎。

这一章,咱们就完整走一遍:从需求分析,到架构设计,再到编码调试,最后测试发布。我会把我在项目中踩过的坑、总结的经验,全都抖出来。

1. 需求分析:别急着写代码

很多人拿到新Sensor,第一反应就是「赶紧上电,看看能不能出图」。我劝你冷静。需求分析这一步跳过了,后面有你哭的。

核心三要素:功能、性能、功耗

1.1 功能需求

  • Sensor型号与接口:MIPI CSI几路?几lane?I2C地址多少?
  • 分辨率与帧率:最大支持多少?客户要的是1080p@30fps还是4K@60fps?
  • 输出格式:RAW10?RAW12?YUV?还是JPEG?
  • 控制功能:AE、AWB、AF是否都要支持?有没有特殊模式(HDR、慢动作)?

我记得有一次,客户说「随便一个Sensor就行」。结果做到一半,对方才说「哦,我们要支持4K@60fps」。那个Sensor最大只能跑到4K@30fps……嗯,换Sensor重做。所以,需求一定要白纸黑字写清楚。

1.2 性能需求

指标典型值说明
帧率30fps / 60fps预览和录像分别要求
延迟<100ms从按下快门到取景
图像质量SNR > 40dB暗光下不能太差
AE收敛时间<1s场景切换时不能闪太久

我的经验:性能需求一定要量化。别写「流畅」这种词,要写「30fps不掉帧」。否则测试和开发之间会打架。

1.3 功耗需求

手机摄像头功耗通常控制在200mW以内(预览模式)。Sensor本身、ISP、MIPI走线、甚至I2C上拉电阻,都会影响功耗。

  • Sensor工作模式:active / standby / sleep
  • MIPI时钟频率:能低则低,够用就行
  • 帧率控制:不需要60fps时,切到30fps省电

我曾经在一个项目里,Sensor待机功耗超标。查了半天,发现是驱动里没把MIPI的clock lane关掉。就一行代码的事,功耗降了30mW。细节啊,兄弟们。

2. 架构设计:三层架构怎么搭

Android Camera的架构,说白了就三层:Kernel驱动 → HAL层 → Framework层。咱们一个一个说。

Camera驱动三层架构 Kernel 驱动层 V4L2框架 | Sensor驱动 | I2C/SPI控制 | MIPI CSI接收 HAL 层 Camera HAL3 | 流配置 | Buffer管理 | 3A算法集成 Framework 层 CameraService | CameraDevice | 应用API接口 底层 上层

2.1 Kernel驱动层

这一层直接和硬件打交道。说白了,就是让Linux内核认识你的Sensor。

  • 注册V4L2设备节点(/dev/videoX)
  • 实现s_power、s_fmt、enum_fmt等回调
  • 配置MIPI CSI控制器(D-PHY timing、lane数)
  • 处理中断、DMA传输

注意:Kernel驱动里不要做复杂的图像处理。那是ISP的事。驱动只负责把原始数据从Sensor搬到内存。

2.2 HAL层

HAL层是Kernel和Framework之间的桥梁。Android从HAL3开始,用stream-based的模型。

  • 实现camera_device_t接口
  • 配置stream(preview、capture、video)
  • 管理buffer queue(Gralloc分配)
  • 集成3A算法(AE/AWB/AF)

我个人习惯,HAL层里把Sensor的寄存器配置表单独放一个文件。这样换Sensor时,改配置表就行,不用动逻辑代码。

2.3 Framework层

这一层Android已经帮你做好了。你只需要在HAL层把接口实现好,Framework会自动调用。但你要理解它的流程:

  • CameraService启动时,加载HAL库
  • App打开Camera,Framework创建CameraDevice
  • App配置预览流,Framework下发stream配置到HAL
  • HAL启动Sensor,开始传数据

3. 编码实现与调试

架构设计好了,开始写代码。我建议的顺序是:先Kernel,再HAL,最后调Framework

3.1 Kernel驱动编码

以OV5640为例,核心代码就这几步:

// 1. 注册I2C驱动
static struct i2c_driver ov5640_i2c_driver = {
    .driver = {
        .name = "ov5640",
    },
    .probe = ov5640_probe,
    .id_table = ov5640_id,
};

// 2. probe函数中初始化Sensor
static int ov5640_probe(struct i2c_client *client) {
    // 读取chip ID确认型号
    reg_read(client, 0x300A, &chip_id);
    if (chip_id != 0x5640) return -ENODEV;
    
    // 注册V4L2子设备
    v4l2_i2c_subdev_init(&sensor->sd, client, &ov5640_ops);
}

// 3. 实现s_fmt回调
static int ov5640_s_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt) {
    // 根据分辨率选择寄存器配置表
    // 写入Sensor寄存器
    ov5640_write_reg(client, 0x3808, fmt->width >> 8);
    ov5640_write_reg(client, 0x3809, fmt->width & 0xFF);
    // ...
}

调试技巧:先用i2c-tools在用户空间读写寄存器,确认Sensor能响应。再加载驱动。别一上来就搞V4L2,先确认硬件通不通。

3.2 HAL层编码

HAL层代码量最大。核心是实现这几个函数:

// 打开Camera设备
int hal_camera_open(const struct hw_module_t* module, 
                    const char* id, struct hw_device_t** device) {
    // 分配camera_device结构体
    // 初始化mutex、condition变量
    // 打开Kernel设备节点 /dev/video0
}

// 配置stream
int hal_configure_streams(const struct camera3_device *device,
                          camera3_stream_configuration_t *stream_list) {
    // 检查stream组合是否支持
    // 计算buffer大小
    // 调用Kernel的VIDIOC_S_FMT
}

// 处理request
int hal_process_capture_request(const struct camera3_device *device,
                                camera3_capture_request_t *request) {
    // 解析settings(AE/AWB参数)
    // 下发到Sensor
    // 等待buffer填充完成
    // 回调framework
}

我记得第一次写HAL时,configure_streams里忘了设置buffer的usage flag。结果Gralloc分配出来的buffer格式不对,图像全是花的。查了两天才找到原因……嗯,这种坑踩一次就记住了。

3.3 调试三板斧

  1. dmesg + printk:Kernel层调试,看驱动加载有没有报错
  2. logcat + ALOG:HAL层调试,看stream配置和buffer流转
  3. busybox devmem:直接读寄存器,确认硬件状态

4. 测试与发布

代码写完了,别急着说「搞定了」。测试才是见真章的时候。

4.1 功能测试

  • 预览、拍照、录像三种模式切换
  • 前后摄像头切换
  • 分辨率切换(从VGA到最大分辨率)
  • 特殊模式(HDR、夜景、慢动作)

4.2 性能测试

测试项工具通过标准
帧率adb shell dumpsys media.camera稳定在目标值±2fps
延迟camera latency test app<100ms
功耗Power Monitor / 万用表<200mW
温度热成像仪连续运行30分钟 <45°C

4.3 稳定性测试

这个我吃过亏。有一次项目赶着发布,稳定性测试只跑了2小时。结果出货后,用户反馈用着用着Camera就打不开了。后来复现发现是内存泄漏,跑8小时必挂。

  • Camera打开/关闭循环测试(1000次)
  • 长时间录像测试(4小时)
  • 压力测试(同时预览+拍照+录像)

5. 实战:从零开始为新Sensor完成全链路开发

好了,理论说完了。咱们来真的。

假设你拿到一颗新的Sensor——GC8034。你要做什么?

  1. 读datasheet:找到I2C地址、寄存器map、初始化序列、MIPI timing
  2. 写Kernel驱动:基于已有的ov5640驱动改,替换寄存器配置
  3. 验证驱动:用media-ctl和v4l2-ctl确认设备节点正常
  4. 写HAL层:在camera HAL里添加新的sensor配置表
  5. 集成3A:如果Sensor自带3A,直接通过I2C控制;如果没有,集成第三方算法库
  6. 调优:调整AE速度、AWB色温、AF对焦曲线
  7. 测试:跑完整的功能、性能、稳定性测试
  8. 发布:提交代码、写release notes、打tag

我的建议:第一次做全链路开发,别追求完美。先让Sensor出图,哪怕图像是花的。能出图,说明硬件通路通了。然后再一步步优化。

我曾经带过一个新人,他花了两周调图像质量,结果发现MIPI lane mapping错了,图像本来就是错的。两周白干。所以,先通链路,再调质量。这个顺序不能乱。

好了,这一章的内容就到这儿。从需求分析到架构设计,从编码调试到测试发布,咱们把Camera驱动全链路开发的流程走了一遍。下一章,我会深入Kernel驱动层,手把手教你写一个完整的Sensor驱动。


公众号:蓝海资料掘金营,微信deep3321