23、摄像头系统:Camera HAL 与车载摄像头(环视、DMS),视频流处理

车载摄像头,说白了就是汽车的「眼睛」。环视让你看清周围,DMS盯着驾驶员有没有走神。但要把这些画面流畅地送到屏幕上,背后是一整套复杂的流水线。

我个人习惯把摄像头系统分成三层:硬件驱动层、HAL抽象层、应用框架层。今天咱们重点聊HAL层,因为它是连接硬件和系统的桥梁,也是坑最多的地方。

23.1 车载摄像头的特殊之处

手机摄像头和车载摄像头,虽然都叫Camera,但完全是两码事。我在项目中遇到过最头疼的问题——车载摄像头对实时性的要求极其苛刻。

举个例子:环视系统要求4路摄像头画面拼接后延迟不超过100ms。一旦超了,驾驶员看到的就是「过去」的画面,倒车时很容易出事。

对比项 手机摄像头 车载摄像头
帧率要求 30fps 够用 环视30fps,DMS 15-30fps
延迟容忍 200-300ms 可接受 环视<100ms,DMS<150ms
工作温度 0~40°C -40~85°C
安全等级 无要求 ASIL-B 或更高

核心要点:车载摄像头系统必须满足功能安全(ISO 26262)要求。HAL层需要支持故障检测、超时监控、数据完整性校验。

23.2 Camera HAL 架构解析

Android Automotive 的 Camera HAL 基于 HIDL(Treble架构)AIDL 实现。我个人更推荐新项目直接用AIDL,因为HIDL已经被Google标记为废弃了。

HAL层主要干三件事:

  • 设备管理:打开/关闭摄像头,配置分辨率、帧率、曝光参数
  • 数据流控制:启动/停止视频流,处理buffer队列
  • 元数据传递:把摄像头特性(如视场角、畸变参数)传给上层

来看一个典型的HAL接口定义:

// ICameraDevice.hal
interface ICameraDevice {
  // 打开摄像头,返回一个流通道
  open(StreamConfig config) generates (IStreamChannel channel);
  
  // 获取摄像头特性
  getCameraCharacteristics() generates (CameraMetadata metadata);
  
  // 关闭摄像头
  close();
};

interface IStreamChannel {
  // 开始流传输
  startStream();
  
  // 停止流传输
  stopStream();
  
  // 注册buffer回调
  setBufferCallback(IBufferCallback cb);
};

我的经验:车载摄像头通常有多个物理接口(FPD-Link、GMSL、MIPI CSI-2)。HAL层最好抽象出一个「物理端口」的概念,方便适配不同硬件。

23.3 环视系统:4路画面的同步难题

环视系统需要4个广角摄像头同时工作。你想想看,如果4路画面不同步,拼接出来的俯视图就会出现「鬼影」——车已经动了,某个角度的画面还停留在上一帧。

我曾经在调试环视系统时,发现左摄像头总是比右摄像头慢2帧。查了半天,原来是左摄像头的I2C时钟配置错了,导致曝光时间偏长。

解决同步问题的常用方法:

  1. 硬件同步(推荐):所有摄像头共用同一个帧同步信号(VSYNC)
  2. 软件时间戳:在HAL层给每帧打上硬件时间戳,上层根据时间戳对齐
  3. Buffer队列同步:使用同步栅栏(Sync Fence)确保4路buffer同时就绪

环视系统的典型数据流:

// 伪代码:环视数据流处理
void processSurroundView() {
  // 1. 从4个摄像头获取原始帧
  Buffer buf_left = cameraLeft->getFrame();
  Buffer buf_right = cameraRight->getFrame();
  Buffer buf_front = cameraFront->getFrame();
  Buffer buf_rear = cameraRear->getFrame();
  
  // 2. 等待所有buffer就绪(同步点)
  syncFence->waitAll({buf_left, buf_right, buf_front, buf_rear});
  
  // 3. 畸变校正 + 透视变换
  buf_left = undistort(buf_left, calibData.left);
  buf_right = undistort(buf_right, calibData.right);
  // ... 其他摄像头同理
  
  // 4. 拼接成俯视图
  Image stitched = stitch({buf_left, buf_right, buf_front, buf_rear});
  
  // 5. 送显示
  display->show(stitched);
}

23.4 DMS:驾驶员监控的特殊要求

DMS(Driver Monitoring System)和环视完全不同。它不需要广角,但需要红外补光高灵敏度,因为要在夜间也能看清驾驶员的脸。

嗯,这里要注意:DMS摄像头通常安装在方向盘后方或A柱上,视角要覆盖驾驶员面部。HAL层需要支持ROI(感兴趣区域)裁剪,因为上层算法只关心人脸区域,不需要整帧画面。

DMS视频流处理的关键点:

  • 低延迟编码:如果DMS需要上传云端分析,HAL层要支持硬件编码(如H.264)
  • 隐私保护:DMS画面不能随意存储,HAL层最好提供「安全buffer」机制,防止应用层非法读取
  • 红外LED控制:通过GPIO或I2C控制红外补光灯,HAL层要暴露这个接口

避坑指南:我曾经遇到过DMS摄像头在强光下画面过曝的问题。原因是HAL层没有做自动曝光(AE)的快速收敛。车载场景光照变化剧烈(进出隧道),AE算法必须能在1-2帧内调整到位。

23.5 视频流处理的性能优化

车载系统资源有限,不能像手机那样随便开4K 60fps。我建议遵循以下原则:

场景 推荐分辨率 推荐帧率 编码格式
环视 1280x720 x4 30fps 原始YUV(不编码)
DMS 640x480 15-30fps H.264(如需上传)
行车记录 1920x1080 30fps H.265

性能优化的几个实战技巧:

  • Buffer复用:不要每帧都申请新buffer,用环形缓冲区(Ring Buffer)循环使用
  • 零拷贝:通过ION/DMA-BUF让摄像头直接写数据到GPU/显示内存,绕过CPU
  • 多线程流水线:采集、处理、显示分别用不同线程,避免互相阻塞

下面这张图展示了整个视频流处理的核心逻辑:

车载摄像头视频流处理架构 环视摄像头 x4 FPD-Link / GMSL DMS 摄像头 x1 红外 + ROI裁剪 行车记录 x1 H.265编码 Camera HAL 层 设备管理 Buffer队列同步 元数据传递 故障检测 视频流处理 畸变校正 透视变换 图像拼接 编码/压缩 显示 / 存储 / 上传

我的建议:调试摄像头系统时,先在HAL层加一个「环回测试」模式——让摄像头采集的画面直接显示,不经过任何处理。这样可以快速判断问题是出在硬件、驱动还是上层算法。

最后说一句,车载摄像头系统最考验人的不是技术有多深,而是对细节的把控。一个像素的偏移、一帧的延迟、一个I2C时序的抖动,都可能导致整个系统不可用。嗯,慢慢调,别急。


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