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 标志位决策流程

capabilities 标志位决策流程 开始填充 capabilities 支持视频捕获? 设置 V4L2_CAP_VIDEO_CAPTURE 支持流式 I/O? 设置 V4L2_CAP_STREAMING 支持 YUV/扩展格式? 设置 V4L2_CAP_EXT_PIX_FORMAT

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' 结尾。