3. 实现 V4L2 的 querycap 能力查询

实现 V4L2 的 querycap 能力查询 学习卡片

任务目标

编写一个Linux用户空间程序,通过V4L2接口枚举指定摄像头设备支持的所有像素格式及对应的分辨率,并将结果打印到终端。该程序用于验证摄像头驱动是否正确注册了格式描述符,是后续配置采集参数前的必要检查步骤。

环境与依赖

  • 操作系统:Linux(带V4L2内核驱动)
  • 摄像头设备:/dev/video0(可按需修改)
  • 编译依赖:仅需Linux内核头文件(通常已随libc-dev安装)
  • 编译工具:gcc

关键完整代码

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>

static const char *pix_fmt_desc(unsigned int pf) {
    switch (pf) {
        case V4L2_PIX_FMT_YUYV:  return "YUYV 4:2:2";
        case V4L2_PIX_FMT_MJPEG: return "MJPEG";
        case V4L2_PIX_FMT_H264:  return "H.264";
        case V4L2_PIX_FMT_NV12:  return "NV12";
        case V4L2_PIX_FMT_RGB24: return "RGB24";
        default:                 return "Other/Unknown";
    }
}

static void enum_framesizes(int fd, __u32 pixfmt) {
    struct v4l2_frmsizeenum fsize;
    memset(&fsize, 0, sizeof(fsize));
    fsize.pixel_format = pixfmt;
    fsize.index = 0;

    while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsize) == 0) {
        if (fsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
            printf("    %ux%u\n", fsize.discrete.width, fsize.discrete.height);
        } else if (fsize.type == V4L2_FRMSIZE_TYPE_STEPWISE ||
                   fsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
            printf("    %u-%u x %u-%u (step %ux%u)\n",
                   fsize.stepwise.min_width,  fsize.stepwise.max_width,
                   fsize.stepwise.min_height, fsize.stepwise.max_height,
                   fsize.stepwise.step_width,  fsize.stepwise.step_height);
            break;  // 步进/连续类型只需一条记录
        }
        fsize.index++;
    }
}

int main(int argc, char *argv[]) {
    const char *dev = (argc > 1) ? argv[1] : "/dev/video0";
    int fd = open(dev, O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    struct v4l2_fmtdesc fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.index = 0;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    printf("Device: %s\n", dev);
    printf("Supported formats:\n");

    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) {
        printf("  [%u] 0x%04x %-12s %s\n",
               fmt.index, fmt.pixelformat,
               (char *)fmt.description, pix_fmt_desc(fmt.pixelformat));
        enum_framesizes(fd, fmt.pixelformat);
        fmt.index++;
    }

    close(fd);
    return 0;
}

编译与运行

gcc -o enum_fmt enum_fmt.c
./enum_fmt /dev/video0

若摄像头不是/dev/video0,将设备路径作为命令行参数传入即可。

验证方法与判定标准

  • 正常输出:程序打印设备路径后,逐行列出每个格式索引、四字符码(如YUYV)、驱动描述字符串及该格式支持的分辨率列表。
  • 判定通过:输出中至少包含一个格式条目,且每个格式条目下能看到至少一个分辨率(离散值或范围)。若输出为空或ioctl返回EINVAL,说明驱动未正确实现格式枚举,或设备节点无权限(尝试sudo)。
  • 交叉验证:可运行v4l2-ctl --list-formats-ext -d /dev/video0(若已安装v4l-utils)对比输出,两者格式与分辨率集合应一致。

常见问题

  • 打开设备失败:确认设备节点存在且当前用户有读写权限(可加入video组或使用sudo)。
  • 无任何格式输出:检查驱动是否加载(dmesg | grep video),或设备是否被其他进程独占。
  • 分辨率枚举不完整:部分驱动对步进类型只返回一条范围记录,本程序已做处理;若驱动实现有误,可能只返回离散值,属正常现象。