4. VIDIOC_QUERYCAP 实现:填充 v4l2_capability 结构体
VIDIOC_QUERYCAP 是 V4L2 驱动必须实现的第一个 ioctl,也是用户空间应用程序探测设备能力的入口。当应用调用 open() 后,通常立即执行 ioctl(fd, VIDIOC_QUERYCAP, &cap) 来获取设备的基本信息与能力标志。驱动需要正确填充 struct v4l2_capability 结构体,否则应用无法继续后续操作。
4.1 v4l2_capability 结构体定义
该结构体定义在 linux/videodev2.h 中,包含驱动名称、设备名称、总线信息以及能力标志位:
struct v4l2_capability {
__u8 driver[16]; /* 驱动名称,小写,如 "uvcvideo" */
__u8 card[32]; /* 设备名称,可读性强,如 "USB Camera" */
__u8 bus_info[32]; /* 总线信息,如 "usb-0000:00:14.0-1" */
__u32 version; /* 驱动版本号(内核版本已废弃,填 KERNEL_VERSION) */
__u32 capabilities; /* 设备能力标志位 */
__u32 device_caps; /* 设备特定能力(可选,用于复合设备) */
__u32 reserved[3]; /* 保留字段,填 0 */
};
关键约束:
driver、card、bus_info 字段必须是以 '\0' 结尾的 ASCII 字符串,长度不能超过数组大小减 1。内核不会自动截断,溢出会导致用户空间读取越界。
4.2 三个核心字符串字段的填充策略
| 字段 | 长度 | 内容规范 | 典型示例 |
|---|---|---|---|
driver |
16 字节 | 驱动模块名称,全小写,无空格 | "my_v4l2_drv" |
card |
32 字节 | 用户可识别的设备名称,可包含品牌/型号 | "MIPI Camera Module" |
bus_info |
32 字节 | 物理连接位置,用于区分同一驱动下的多设备 | "platform:fe801000.csi" |
填充示例(驱动 probe 或 open 时):
static int my_v4l2_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
struct my_device *dev = video_drvdata(file);
/* driver: 固定为驱动模块名 */
strscpy(cap->driver, "my_v4l2_drv", sizeof(cap->driver));
/* card: 从设备树或平台数据获取 */
strscpy(cap->card, dev->model_name, sizeof(cap->card));
/* bus_info: 使用 dev_name 获取平台/PCI/USB 路径 */
snprintf(cap->bus_info, sizeof(cap->bus_info),
"platform:%s", dev_name(dev->dev));
/* version: 使用内核版本宏 */
cap->version = KERNEL_VERSION(5, 10, 0);
/* capabilities: 见下节 */
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE;
/* device_caps: 如果支持多节点,可细化 */
cap->device_caps = cap->capabilities;
memset(cap->reserved, 0, sizeof(cap->reserved));
return 0;
}
常见错误: 使用
strcpy 或 sprintf 而不检查长度,导致栈溢出;bus_info 填入固定字符串而非真实总线路径,导致多设备无法区分;version 随意填 0 或固定值,应使用 KERNEL_VERSION 宏。
4.3 capabilities 标志位的含义与配置
capabilities 字段是一个位掩码,告诉应用该 V4L2 设备支持哪些功能。应用根据这些标志决定后续调用哪些 ioctl。以下是常用标志位及其含义:
| 标志位 | 值 | 含义 | 适用场景 |
|---|---|---|---|
V4L2_CAP_VIDEO_CAPTURE |
0x00000001 | 支持视频捕获 | 摄像头输入 |
V4L2_CAP_VIDEO_OUTPUT |
0x00000002 | 支持视频输出 | 显示输出 |
V4L2_CAP_VIDEO_OVERLAY |
0x00000004 | 支持视频叠加 | OSD/画中画 |
V4L2_CAP_VBI_CAPTURE |
0x00000010 | 支持 VBI 捕获 | 电视卡图文 |
V4L2_CAP_STREAMING |
0x04000000 | 支持流式 I/O(mmap/DMABUF) | 高效传输 |
V4L2_CAP_READWRITE |
0x01000000 | 支持 read/write 系统调用 | 简单读取 |
V4L2_CAP_EXT_PIX_FORMAT |
0x00200000 | 支持扩展像素格式 | YUV/RAW 格式 |
V4L2_CAP_DEVICE_CAPS |
0x80000000 | 支持 device_caps 字段 |
复合设备 |
配置原则:
- 只设置驱动实际支持的标志位,不要为了兼容性随意添加。
- 如果驱动同时支持 capture 和 output,则同时设置
V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT。 - 如果使用 vb2 框架,通常同时设置
V4L2_CAP_STREAMING和V4L2_CAP_READWRITE。 - 对于本课程涉及的图像裁剪与 YUV 转换场景,必须包含
V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_EXT_PIX_FORMAT。
4.4 capabilities 标志位决策流程
4.5 device_caps 与复合设备场景
当单个 V4L2 设备节点同时支持多种功能(如 capture + output + vbi)时,capabilities 应设置为所有功能的并集,而 device_caps 则只包含当前节点实际支持的能力。应用通过检查 device_caps 来精确了解该节点能做什么。
典型配置示例:
/* 复合设备:video0 节点同时支持 capture 和 output */
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_VIDEO_OUTPUT |
V4L2_CAP_STREAMING |
V4L2_CAP_DEVICE_CAPS;
/* 当前节点只做 capture */
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_STREAMING;
调试检查点: 使用
v4l2-ctl --all 或 v4l2-compliance 工具验证 capabilities 是否正确。常见失败原因:V4L2_CAP_STREAMING 未设置导致 VIDIOC_REQBUFS 返回 ENOTTY;V4L2_CAP_EXT_PIX_FORMAT 缺失导致应用无法枚举 YUV 格式。
4.6 完整实现模板
以下是一个基于 vb2 框架的 vidioc_querycap 完整实现,适用于本课程的图像裁剪与 YUV 转换场景:
static int my_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
struct my_dev *dev = video_drvdata(file);
strscpy(cap->driver, "my_csi_drv", sizeof(cap->driver));
strscpy(cap->card, "MIPI CSI-2 Camera", sizeof(cap->card));
snprintf(cap->bus_info, sizeof(cap->bus_info),
"platform:%s", dev_name(dev->dev));
cap->version = KERNEL_VERSION(5, 15, 0);
/* 本驱动支持:视频捕获 + 流式 I/O + read/write + 扩展像素格式 */
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE |
V4L2_CAP_EXT_PIX_FORMAT;
/* 单节点设备,device_caps 与 capabilities 一致 */
cap->device_caps = cap->capabilities;
memset(cap->reserved, 0, sizeof(cap->reserved));
return 0;
}
将此函数注册到 v4l2_ioctl_ops 结构体中:
static const struct v4l2_ioctl_ops my_ioctl_ops = {
.vidioc_querycap = my_vidioc_querycap,
.vidioc_enum_fmt_vid_cap = my_vidioc_enum_fmt,
.vidioc_s_fmt_vid_cap = my_vidioc_s_fmt,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
/* 其他 ioctl ... */
};
调试技巧: 在
vidioc_querycap 中添加 dev_dbg() 打印,观察用户空间是否成功获取能力信息。如果应用返回 EINVAL,检查 reserved 是否已清零,以及字符串是否以 '\0' 结尾。