4、Camera Pipeline设计:ISP Pipeline、Sensor驱动交互、V4L2框架在HAL中的应用

好,我们直接进入正题。Camera Pipeline 这个词,说白了就是一张照片从光线打到 Sensor 上,到最后变成你屏幕上那个画面的完整路径。这条路很长,中间要经过 Sensor、ISP、V4L2、HAL,最后才到 Framework。今天我就带你把这几个关键节点拆开揉碎了讲清楚。

4.1 ISP Pipeline:图像处理的核心流水线

ISP 是什么?Image Signal Processor。它负责把 Sensor 输出的原始 RAW 数据,变成人眼看着舒服的 YUV 或 JPEG。我个人习惯把 ISP Pipeline 分成三段:前端处理、中端处理、后端处理。

阶段 主要模块 说明
前端 BLC、LSC、DPC 黑电平校正、镜头阴影校正、坏点校正
中端 Demosaic、AWB、AE、AF 去马赛克、自动白平衡、自动曝光、自动对焦
后端 Gamma、CSC、Sharpen、NR Gamma校正、色彩空间转换、锐化、降噪

我在项目中遇到过一个问题:前端 BLC 没调好,导致整个画面偏色。你想想看,黑电平都没校准,后面的 AWB 再怎么调也是白费力气。所以我的建议是——调试 ISP 时,一定要从前端往后端走,别跳步。

关键点:ISP Pipeline 的每个模块都有依赖关系。前一级的输出质量,直接决定后一级的调优空间。别指望后处理能弥补前端的硬伤。

4.2 Sensor驱动交互:从I2C到MIPI

Sensor 是 Camera 的眼睛。驱动层跟 Sensor 的交互,主要靠两条线:控制通道和数据通道。

  • 控制通道:通常是 I2C 或 SPI。用来配置 Sensor 的寄存器,比如设置曝光时间、增益、帧率。
  • 数据通道:通常是 MIPI CSI-2。用来传输图像数据,RAW10、RAW12 这些格式都在这里跑。

嗯,这里要注意。Sensor 驱动里最容易踩的坑是时序问题。我记得有一次,Sensor 的 VSYNC 信号跟 SoC 的 MIPI 接收端没对齐,导致第一帧数据总是丢。查了两天才发现是驱动里 power-on 序列的 delay 不够。

避坑指南:我曾经在调试 OV5640 时,发现 Sensor 初始化后死活不出图。后来用示波器量了 MIPI 时钟,发现时钟频率配错了。Sensor 的 PLL 配置一定要跟 datasheet 上的参考值严格对齐,差一点都可能不出图。

驱动交互的典型流程是这样的:

// Sensor 驱动初始化流程(伪代码)
sensor_power_on();          // 上电
sensor_reset();             // 复位
sensor_i2c_write(0x0103, 0x01);  // 软件复位
mdelay(10);                 // 等待稳定
sensor_i2c_write(0x3035, 0x11);  // 配置 PLL
sensor_i2c_write(0x3036, 0x46);  // 配置分频
sensor_i2c_write(0x3821, 0x00);  // 配置输出格式
sensor_stream_on();         // 开启流

为什么我要强调这个流程?因为很多新手工程师喜欢把配置一股脑全写进去,中间不加延时。结果 Sensor 还没稳定,下一组配置就来了,直接导致寄存器写入失败。

4.3 V4L2框架在HAL中的应用

V4L2,全称 Video for Linux 2。它是 Linux 内核里处理视频设备的统一框架。在 Camera HAL 里,V4L2 是我们跟内核驱动打交道的标准接口。

说白了,HAL 层通过 V4L2 的 ioctl 来操作设备节点。常见的操作有:

  • VIDIOC_QUERYCAP:查询设备能力
  • VIDIOC_S_FMT:设置图像格式
  • VIDIOC_REQBUFS:申请缓冲区
  • VIDIOC_QBUF / VIDIOC_DQBUF:入队/出队缓冲区
  • VIDIOC_STREAMON / VIDIOC_STREAMOFF:开启/关闭流

我个人习惯把 V4L2 在 HAL 中的应用分成三层:

  1. 设备管理层:负责打开/关闭设备节点,管理多个 video 节点(比如主摄、广角、景深)。
  2. 缓冲区管理层:负责申请、映射、回收 buffer。这里要注意 buffer 的 cache 一致性,我踩过这个坑。
  3. 流控制层:负责 stream on/off,以及帧率的控制。

警告:V4L2 的 buffer 管理在多摄场景下特别容易出问题。我曾经在双摄项目中,两个 Sensor 共用同一个 ISP 硬件,结果 buffer 分配冲突导致系统 OOM。解决方案是给每个 Sensor 独立分配 buffer pool,别共用。

来看一段 HAL 层调用 V4L2 的典型代码:

// HAL 层通过 V4L2 设置图像格式
int fd = open("/dev/video0", O_RDWR);
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1920;
fmt.fmt.pix.height = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
fmt.fmt.pix.field = V4L2_FIELD_NONE;

if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
    // 处理错误
    close(fd);
    return -1;
}

// 申请 buffer
struct v4l2_requestbuffers req = {0};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &req);

你想想看,如果这里 VIDIOC_S_FMT 返回了错误,说明内核驱动不支持你设置的格式。这时候别硬来,先查一下 Sensor 驱动里注册的格式列表。

4.4 三者如何协同工作

Sensor 驱动、V4L2 框架、ISP Pipeline,这三者不是孤立的。它们的关系是这样的:

  • Sensor 驱动负责把硬件数据送上来,通过 V4L2 的 buffer 机制传给上层。
  • V4L2 框架提供统一的接口,让 HAL 层不用关心底层是哪个 Sensor、哪个 ISP。
  • ISP Pipeline 在 HAL 层或内核层处理图像数据,输出最终结果。

我在项目中遇到过一种典型场景:Sensor 输出 RAW10,V4L2 配置了 V4L2_PIX_FMT_SBGGR10,但 ISP 只支持 RAW8 输入。怎么办?要么在驱动里做 packing 转换,要么在 HAL 层加一个格式转换模块。我个人建议在驱动层做,因为效率更高。

总结:Camera Pipeline 的设计核心就是数据流的正确传递。从 Sensor 到 ISP,中间经过 V4L2,每一层都要保证格式、时序、buffer 管理的一致性。任何一个环节出问题,最终画面都会出问题。

好了,这一章的内容就到这里。下一章我们会深入讲一下 HAL 层的 buffer 管理策略,包括如何避免内存抖动和帧率抖动。到时候我会分享一个我在 4K 60fps 项目中踩过的坑,保证让你有收获。