第26章:Camera与车载系统:Android Automotive Camera架构、车载摄像头驱动与环视系统实战
各位同学,今天我们来聊一个非常硬核的话题——车载Camera系统。说实话,我做了这么多年嵌入式,真正让我觉得「这玩意儿出问题是要命的」,就是车载摄像头。手机拍照糊了可以重拍,车载摄像头延迟高了或者画面拼接错了,那可能就是一场事故。
这一章,我会带大家深入Android Automotive的Camera架构,聊聊环视、后视、DMS这些车载摄像头的驱动实现,还有ISO 26262安全标准对驱动开发的影响。最后,咱们手把手在Android Automotive上实现一个环视系统。
26.1 Android Automotive Camera架构概览
Android Automotive和手机上的Android,Camera架构其实大同小异。但有一个关键区别——车载系统对实时性和安全性的要求高得多。
我给大家画了一张架构图,先看整体脉络:
你看,从应用层到内核驱动,每一层都有它的职责。但车载系统里,HAL层和内核层之间往往需要更紧密的配合。为什么?因为延迟要求太苛刻了。
26.2 车载摄像头类型与驱动特点
车载摄像头主要有三类:环视摄像头、后视摄像头、DMS(驾驶员监控)摄像头。每种摄像头的驱动侧重点都不一样。
| 摄像头类型 | 典型数量 | 分辨率 | 帧率要求 | 驱动关键点 |
|---|---|---|---|---|
| 环视摄像头 | 4颗(前后左右) | 1280x720 ~ 1920x1080 | 30fps | 多路同步、ISP参数统一、畸变校正 |
| 后视摄像头 | 1颗 | 1280x720 | 30fps | 低延迟、宽动态、倒车触发 |
| DMS摄像头 | 1~2颗 | 640x480 ~ 1280x720 | 15~30fps | 红外补光、人脸检测、隐私保护 |
我个人习惯把环视摄像头叫做「四眼系统」。为什么?因为四颗摄像头必须精确同步,差一帧画面拼接就会错位。我在项目中遇到过,四颗摄像头用的都是OV10640传感器,但其中一颗的帧同步信号线虚焊了,结果环视画面每隔几秒就「跳」一下。排查了整整两天才找到问题。
26.3 车载Camera安全要求(ISO 26262)
ISO 26262,搞车载的朋友应该不陌生。它把功能安全等级分为ASIL-A到ASIL-D。摄像头系统通常要求ASIL-B或ASIL-C。
这对驱动开发意味着什么?我给大家列几条硬性要求:
- 故障检测:驱动必须能检测传感器故障、通信故障、数据异常。比如CRC校验失败要能上报。
- 超时监控:帧数据必须在规定时间内到达,否则触发安全机制。后视摄像头延迟超过100ms,系统要能降级或报警。
- 冗余设计:关键路径不能单点故障。比如I2C配置通道坏了,驱动要能切换到备用通道。
- 内存保护:DMA缓冲区不能被其他进程篡改。这需要内核驱动配合IOMMU做隔离。
26.4 车载Camera延迟要求(<100ms)
100ms,这是后视摄像头的硬指标。从传感器曝光到画面显示在屏幕上,整个pipeline不能超过100ms。环视系统稍微宽松一些,但通常也要求在150ms以内。
怎么做到?我给大家拆解一下延迟构成:
- 传感器曝光时间:一般1~16ms,取决于光照条件。
- ISP处理时间:包括去噪、色彩校正、畸变校正。硬件ISP通常5~10ms,软件ISP可能20~30ms。
- 传输时间:MIPI CSI-2传输一帧1080p数据,大约3~5ms。
- Buffer排队时间:这是最容易忽略的。如果HAL层用了双缓冲,排队时间可能增加16ms。
- 显示刷新时间:屏幕刷新周期,通常16ms(60Hz)。
加起来,硬件ISP方案大概40~50ms,软件ISP方案可能到70~80ms。所以,后视摄像头必须用硬件ISP,这是铁律。
26.5 实战:在Android Automotive上实现环视系统
好,理论讲完了,咱们来点实际的。下面是一个简化版的环视系统实现方案。
26.5.1 硬件配置
假设我们用的是四颗OV10640传感器,通过一个四通道解串器(比如MAX9286)连接到SoC的CSI-2接口。SoC端用Qualcomm SA8155或NXP i.MX8。
26.5.2 内核驱动层
内核驱动需要做几件事:初始化传感器、配置解串器、注册V4L2设备。我给大家看一段简化的驱动初始化代码:
// 环视摄像头驱动初始化(简化版)
static int surround_cam_probe(struct platform_device *pdev)
{
struct surround_cam_dev *cam;
int ret, i;
cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL);
if (!cam)
return -ENOMEM;
// 1. 初始化四颗传感器
for (i = 0; i < 4; i++) {
ret = ov10640_init(&cam->sensor[i], i);
if (ret) {
dev_err(&pdev->dev, "Sensor %d init failed\n", i);
return ret;
}
}
// 2. 配置解串器(同步模式)
ret = max9286_config(&cam->deser, MAX9286_MODE_SYNC);
if (ret) {
dev_err(&pdev->dev, "Deserializer config failed\n");
return ret;
}
// 3. 注册V4L2设备
ret = v4l2_device_register(&pdev->dev, &cam->v4l2_dev);
if (ret) {
dev_err(&pdev->dev, "V4L2 device register failed\n");
return ret;
}
// 4. 为每颗摄像头创建video设备
for (i = 0; i < 4; i++) {
cam->vdev[i] = video_device_alloc();
if (!cam->vdev[i]) {
ret = -ENOMEM;
goto err_free_vdev;
}
snprintf(cam->vdev[i]->name, sizeof(cam->vdev[i]->name),
"surround_cam_%d", i);
cam->vdev[i]->v4l2_dev = &cam->v4l2_dev;
cam->vdev[i]->fops = &surround_cam_fops;
cam->vdev[i]->ioctl_ops = &surround_cam_ioctl_ops;
video_set_drvdata(cam->vdev[i], cam);
ret = video_register_device(cam->vdev[i], VFL_TYPE_VIDEO, -1);
if (ret) {
dev_err(&pdev->dev, "Video device %d register failed\n", i);
goto err_unreg_vdev;
}
}
dev_info(&pdev->dev, "Surround camera system initialized\n");
return 0;
err_unreg_vdev:
while (--i >= 0)
video_unregister_device(cam->vdev[i]);
err_free_vdev:
for (i = 0; i < 4; i++)
if (cam->vdev[i])
video_device_release(cam->vdev[i]);
return ret;
}
这段代码看起来简单,但有几个关键点:
- 四颗传感器必须用同一个PCLK和VSYNC信号,否则帧同步会出问题。
- 解串器要配置成同步模式,确保四路数据同时到达SoC。
- 每个video设备独立注册,这样HAL层可以分别打开和关闭。
26.5.3 HAL层实现
HAL层需要实现一个「虚拟Camera设备」,把四颗物理摄像头的数据合并成一个逻辑设备。这样上层应用只需要打开一个Camera,就能拿到四路画面。
// HAL层环视Camera设备实现(伪代码)
class SurroundCameraDevice : public camera3_device_t {
public:
// 初始化四路物理Camera
int initialize() {
for (int i = 0; i < 4; i++) {
mPhysicalCameras[i] = new PhysicalCamera(i);
mPhysicalCameras[i]->open();
}
// 创建Buffer池,用于拼接后的画面
mOutputBufferPool = new BufferPool(4, 1920 * 1080 * 3 / 2);
return 0;
}
// 处理请求:从四路Camera取帧,拼接后返回
int processCaptureRequest(camera3_capture_request_t *request) {
// 1. 从四路Camera获取帧数据
for (int i = 0; i < 4; i++) {
mPhysicalCameras[i]->capture(&mRawBuffers[i]);
}
// 2. 执行畸变校正和透视变换
for (int i = 0; i < 4; i++) {
undistort(&mRawBuffers[i], &mUndistortedBuffers[i]);
perspectiveTransform(&mUndistortedBuffers[i], i);
}
// 3. 拼接成环视画面
stitch(&mUndistortedBuffers, mOutputBuffer);
// 4. 填充输出buffer
memcpy(request->output_buffers[0].buffer,
mOutputBuffer, mOutputBufferPool->bufferSize());
return 0;
}
private:
PhysicalCamera *mPhysicalCameras[4];
BufferPool *mOutputBufferPool;
uint8_t *mRawBuffers[4];
uint8_t *mUndistortedBuffers[4];
uint8_t *mOutputBuffer;
};
这里有个性能关键点:畸变校正和透视变换必须在硬件ISP或GPU上做,不能在CPU上跑。我见过一个项目,工程师用CPU做畸变校正,结果四路画面处理完,延迟已经超过200ms了。后来改成GPU compute shader,延迟降到了50ms以内。
26.5.4 应用层调用
应用层就简单了,打开环视Camera设备,设置好输出尺寸和格式,然后循环取帧显示即可。这部分和普通Camera应用没什么区别,我就不赘述了。
26.6 避坑指南
最后,我给大家总结几个实战中容易踩的坑:
- 帧同步问题:四颗摄像头的VSYNC必须严格对齐。我曾经用示波器量过,发现其中一颗的VSYNC有2us的偏移,结果画面拼接处每隔几帧就出现撕裂。解决办法是检查硬件走线,确保四路MIPI时钟线等长。
- ISP参数一致性:四颗摄像头的ISP参数(曝光、白平衡、增益)必须一致,否则拼接出来的画面会有明显的色差。我建议用硬件同步模式,让四颗传感器共享同一组ISP参数。
- 内存带宽:四路1080p@30fps的数据量大约是4 × 1920 × 1080 × 2 × 30 = 约500MB/s。如果SoC的内存带宽不够,会出现丢帧。我在SA8155上遇到过这个问题,后来把输出分辨率降到720p才解决。
- 安全认证:ISO 26262要求驱动代码有完整的单元测试和故障注入测试。别等到最后才补测试,那会累死人的。我建议在写驱动的同时就写测试用例,每提交一次代码就跑一遍。
好了,这一章的内容就到这里。环视系统是个系统工程,从硬件选型到驱动开发,再到应用集成,每一步都有坑。但只要你把基础打牢了,这些坑都能填平。
公众号:蓝海资料掘金营,微信deep3321