3、HAL与ISP的通信桥梁:V4L2框架、Media Controller拓扑、ISP subdev节点操作实战

好,咱们今天聊点硬核的。HAL和ISP之间怎么传话?说白了,就是靠V4L2这套机制。我刚开始接触这块时,也觉得V4L2不就是个视频采集框架嘛,跟ISP有什么关系?后来踩了坑才明白——V4L2不仅仅是采集,它还是整个Camera系统的“通信总线”。

3.1 V4L2框架:不只是视频采集

V4L2,全称Video for Linux 2。很多人以为它只管“把摄像头数据读出来”。其实不然。在Camera HAL和ISP之间,V4L2扮演了三个角色:

  • 数据通道:把ISP处理完的帧数据送到HAL
  • 控制通道:HAL通过ioctl设置ISP的参数
  • 事件通知:ISP有异常或状态变化,通过V4L2事件通知HAL

我个人习惯把V4L2设备分成三类:

设备类型 节点名称 作用
Video节点 /dev/videoX 数据流传输,比如输出YUV或RAW图
Subdev节点 /dev/v4l-subdevX 控制ISP内部模块,比如AE、AWB、LSC
Media节点 /dev/mediaX 管理拓扑连接,告诉你怎么连的

嗯,这里要注意:很多新人只盯着video节点,忽略了subdev。结果发现调ISP参数时怎么都写不进去——因为你找错门了。

3.2 Media Controller拓扑:一张“电路图”

为什么需要Media Controller?你想想看,一个ISP内部可能有几十个模块:传感器输入、Bayer处理、去马赛克、3A统计、缩放、裁剪……它们之间怎么连接的?

Media Controller就是这张“电路图”。它用entity(实体)pad(端口)来描述每个模块,用link(链接)来描述数据流向。

我在项目中遇到过一个问题:HAL往ISP写参数,结果图像全黑了。查了半天,发现是某个subdev的pad没连上。说白了,数据根本没流过去。

查看拓扑的命令很简单:

# 查看media拓扑
media-ctl -d /dev/media0 -p

# 输出示例:
# - entity 1: sensor (1 pad, 1 link)
#   - pad0: Source [fmt:SRGGB10/1920x1080]
#   - link: pad0 -> isp pad0
# - entity 2: isp (2 pads, 2 links)
#   - pad0: Sink
#   - pad1: Source [fmt:UYVY/1920x1080]

我个人建议,调试时第一步就是打印拓扑。别急着写代码,先看清楚谁连谁。

3.3 ISP subdev节点操作实战

好,到了动手环节。操作ISP subdev,核心就是ioctl。常用的几个命令:

  • VIDIOC_SUBDEV_G_FMT / S_FMT:获取/设置格式
  • VIDIOC_SUBDEV_G_CROP / S_CROP:裁剪区域
  • VIDIOC_SUBDEV_G_SELECTION / S_SELECTION:选择目标区域
  • VIDIOC_SUBDEV_S_FRAME_INTERVAL:帧率控制

举个例子,设置ISP输入格式:

#include <linux/v4l2-subdev.h>

int fd = open("/dev/v4l-subdev0", O_RDWR);
struct v4l2_subdev_format fmt = {0};

fmt.pad = 0;          // 哪个pad
fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;  // 生效格式
fmt.format.width = 1920;
fmt.format.height = 1080;
fmt.format.code = MEDIA_BUS_FMT_SRGGB10_1X10;  // 10-bit RAW

if (ioctl(fd, VIDIOC_SUBDEV_S_FMT, &fmt) < 0) {
    perror("set subdev format failed");
}
close(fd);

我曾经踩过一个坑:which字段忘了设成ACTIVE,结果设了半天都是“试格式”,根本没生效。嗯,这种低级错误,犯一次就记住了。

避坑指南: 操作subdev时,一定要先确认pad号对不对。很多ISP有多个sink pad,写错pad号,参数就写到别的地方去了。我曾经调试一个AE模块,死活调不动,最后发现是pad号写成了1,而AE模块在pad0上。

3.4 HAL与ISP的典型交互流程

实际项目中,HAL和ISP的交互大概是这样的:

  1. 初始化阶段:HAL打开media节点,遍历拓扑,找到所有subdev
  2. 配置阶段:HAL通过subdev节点设置ISP的格式、裁剪、帧率
  3. 启动阶段:HAL通过video节点启动数据流
  4. 运行阶段:HAL通过subdev节点动态调整ISP参数(比如AE、AWB)
  5. 停止阶段:HAL关闭video节点,停止数据流

这里有个细节:启动流时,顺序很重要。我建议先配好所有subdev,再启动video节点。否则ISP还没准备好,数据就来了,容易丢帧。

小技巧: 调试时可以用v4l2-ctlmedia-ctl命令行工具快速验证。比如:
# 设置sensor格式
v4l2-ctl -d /dev/v4l-subdev0 --set-fmt-video=width=1920,height=1080,pixelformat=RG10

# 查看当前格式
v4l2-ctl -d /dev/v4l-subdev0 --get-fmt-video
先命令行调通,再写代码,效率高很多。

3.5 实战中的常见问题

最后,分享几个我实际项目中遇到的问题:

  • 问题1:subdev节点打不开 —— 权限问题,或者驱动没加载。检查dmesg看看有没有probe失败。
  • 问题2:设置格式后图像花屏 —— 格式不匹配。比如ISP输出是UYVY,但HAL配置成了NV12。用media-ctl -p确认一下。
  • 问题3:AE/AWB参数写不进去 —— 确认subdev是否支持该控制。用v4l2-ctl -d /dev/v4l-subdev0 --list-ctrls查看支持的控件列表。
  • 问题4:拓扑连接断开 —— 检查link是否enable。用media-ctl -d /dev/media0 --set-link 'sensor pad0:isp pad0[1]'重新连接。

嗯,V4L2这套东西,说复杂也复杂,说简单也简单。核心就是搞清楚:数据从哪来,到哪去,中间经过谁。把拓扑图画清楚,代码写起来就顺了。

下一章,咱们聊聊HAL层怎么管理这些节点,以及如何做多路并发。到时候见。