第26章:Camera与车载系统:Android Automotive Camera架构、车载摄像头驱动与环视系统实战

各位同学,今天我们来聊一个非常硬核的话题——车载Camera系统。说实话,我做了这么多年嵌入式,真正让我觉得「这玩意儿出问题是要命的」,就是车载摄像头。手机拍照糊了可以重拍,车载摄像头延迟高了或者画面拼接错了,那可能就是一场事故。

这一章,我会带大家深入Android Automotive的Camera架构,聊聊环视、后视、DMS这些车载摄像头的驱动实现,还有ISO 26262安全标准对驱动开发的影响。最后,咱们手把手在Android Automotive上实现一个环视系统。

26.1 Android Automotive Camera架构概览

Android Automotive和手机上的Android,Camera架构其实大同小异。但有一个关键区别——车载系统对实时性和安全性的要求高得多。

我给大家画了一张架构图,先看整体脉络:

Android Automotive Camera 架构 应用层 环视应用 | 后视应用 | DMS应用 | 行车记录应用 Android Camera Framework CameraManager | CameraDevice | CameraCaptureSession Camera HAL (CameraProvider) HAL 3.x | 多Camera设备管理 | Buffer管理 Kernel Driver (V4L2 / 私有驱动) CSI-2 | I2C配置 | ISP控制 | 中断处理

你看,从应用层到内核驱动,每一层都有它的职责。但车载系统里,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做隔离。
⚠️ 重要提醒:ISO 26262不是写写文档就完事的。驱动代码的每一行都要有对应的安全分析。我曾经在一个项目中,因为驱动里一个变量没有做边界检查,被安全评审打回来三次。别想着偷懒,安全评审的眼睛是雪亮的。

26.4 车载Camera延迟要求(<100ms)

100ms,这是后视摄像头的硬指标。从传感器曝光到画面显示在屏幕上,整个pipeline不能超过100ms。环视系统稍微宽松一些,但通常也要求在150ms以内。

怎么做到?我给大家拆解一下延迟构成:

  1. 传感器曝光时间:一般1~16ms,取决于光照条件。
  2. ISP处理时间:包括去噪、色彩校正、畸变校正。硬件ISP通常5~10ms,软件ISP可能20~30ms。
  3. 传输时间:MIPI CSI-2传输一帧1080p数据,大约3~5ms。
  4. Buffer排队时间:这是最容易忽略的。如果HAL层用了双缓冲,排队时间可能增加16ms。
  5. 显示刷新时间:屏幕刷新周期,通常16ms(60Hz)。

加起来,硬件ISP方案大概40~50ms,软件ISP方案可能到70~80ms。所以,后视摄像头必须用硬件ISP,这是铁律。

💡 实战技巧:我在做延迟优化时,发现一个「坑」——Android Camera Framework默认会做Buffer转换(比如从NV12转YUV420)。这个转换在手机上无所谓,但在车载系统里可能吃掉10~20ms。我的做法是:在HAL层直接输出显示设备支持的格式,跳过Framework的格式转换。这样能省下宝贵的十几毫秒。

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