3. 视频格式查询与打印

任务目标

编写一个V4L2用户态程序,打开指定摄像头设备,查询其支持的像素格式(YUYV、NV12、MJPG等)以及每种格式对应的分辨率列表,并将全部信息格式化打印到终端。

环境与依赖

  • Linux系统(含V4L2框架),摄像头设备节点如 /dev/video0
  • 内核已加载 videodev 模块,摄像头驱动支持标准V4L2查询接口
  • 编译依赖:Linux内核头文件(提供 <linux/videodev2.h>),gcc编译器

核心实现

程序通过 VIDIOC_ENUM_FMT 遍历所有像素格式,对每种格式再通过 VIDIOC_ENUM_FRAMESIZES 遍历其支持的分辨率。使用 ioctl 系统调用与驱动交互,全程无额外依赖库。

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

static const char *fourcc_to_str(unsigned int fourcc) {
    static char buf[5];
    buf[0] = (fourcc >>  0) & 0xFF;
    buf[1] = (fourcc >>  8) & 0xFF;
    buf[2] = (fourcc >> 16) & 0xFF;
    buf[3] = (fourcc >> 24) & 0xFF;
    buf[4] = '\0';
    return buf;
}

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.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    printf("设备: %s\n", dev);
    printf("========================================\n");

    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) {
        printf("格式[%d]: %s (0x%08X) %s\n",
               fmt.index, fourcc_to_str(fmt.pixelformat),
               fmt.pixelformat, fmt.description);

        struct v4l2_frmsizeenum frmsize;
        memset(&frmsize, 0, sizeof(frmsize));
        frmsize.pixel_format = fmt.pixelformat;
        frmsize.index = 0;

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

    close(fd);
    return 0;
}

编译与运行

# 编译
gcc -o v4l2_list_fmt v4l2_list_fmt.c

# 运行(默认查询 /dev/video0,也可指定其他设备)
./v4l2_list_fmt /dev/video0

验证方法

运行程序后,观察输出是否包含摄像头实际支持的格式与分辨率。可通过以下方式交叉验证:

  • 使用 v4l2-ctl --list-formats-ext(若已安装v4l-utils)对比输出结果,两者应一致。
  • 若摄像头支持YUYV且最大分辨率1920x1080,输出中应出现 YUYV 条目且包含 1920x1080。
  • 程序正常退出且无 Invalid argument 错误,说明所有格式枚举完成。

通过条件:程序能完整列出摄像头所有像素格式及对应分辨率,且与 v4l2-ctl 查询结果一致。

常见问题

  • 打开设备失败:确认设备节点存在且当前用户有读写权限(可尝试 sudo)。
  • 无任何输出:摄像头不支持 V4L2_BUF_TYPE_VIDEO_CAPTURE 类型,或驱动未实现枚举接口。
  • 分辨率缺失:部分驱动对 VIDIOC_ENUM_FRAMESIZES 支持不完整,此时输出仅含格式名,属驱动限制。