1. V4L2设备枚举与能力查询

V4L2设备枚举与能力查询 学习卡片

任务目标

编写一个Linux用户空间程序,枚举系统中所有可用的V4L2视频设备(/dev/video*),对每个设备打开文件句柄并查询其基本能力,包括驱动名称、设备名称、总线信息、设备类型(capture/output等)以及支持的像素格式列表,最终将结果格式化打印到终端。

环境与依赖

  • 操作系统:Linux(内核已启用V4L2框架)
  • 开发库:需安装 libv4l-dev(提供头文件与链接库)
  • 编译器:gcc
  • 设备节点:/dev/video*(无物理摄像头时可用 v4l2loopback 虚拟设备验证)

关键完整代码

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

static const char *buf_type_str(__u32 type) {
    switch (type) {
        case V4L2_BUF_TYPE_VIDEO_CAPTURE: return "Video Capture";
        case V4L2_BUF_TYPE_VIDEO_OUTPUT:  return "Video Output";
        case V4L2_BUF_TYPE_VIDEO_OVERLAY: return "Video Overlay";
        default: return "Other";
    }
}

static void print_formats(int fd) {
    struct v4l2_fmtdesc fmtdesc;
    memset(&fmtdesc, 0, sizeof(fmtdesc));
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("  Supported formats (capture):\n");
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
        printf("    [%u] %-10s %s\n", fmtdesc.index,
               (char *)fmtdesc.pixelformat, fmtdesc.description);
        fmtdesc.index++;
    }
}

static void query_device(const char *path) {
    int fd = open(path, O_RDWR);
    if (fd < 0) {
        perror(path);
        return;
    }

    struct v4l2_capability cap;
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
        perror("VIDIOC_QUERYCAP");
        close(fd);
        return;
    }

    printf("=== %s ===\n", path);
    printf("  Driver      : %s\n", cap.driver);
    printf("  Card        : %s\n", cap.card);
    printf("  Bus info    : %s\n", cap.bus_info);
    printf("  Version     : %u.%u.%u\n",
           (cap.version >> 16) & 0xFF,
           (cap.version >> 8) & 0xFF,
           cap.version & 0xFF);
    printf("  Device caps : 0x%08x\n", cap.device_caps);

    if (cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)
        printf("  Type        : %s\n", buf_type_str(V4L2_BUF_TYPE_VIDEO_CAPTURE));
    if (cap.device_caps & V4L2_CAP_VIDEO_OUTPUT)
        printf("  Type        : %s\n", buf_type_str(V4L2_BUF_TYPE_VIDEO_OUTPUT));

    print_formats(fd);
    printf("\n");
    close(fd);
}

int main() {
    struct dirent **namelist;
    int n = scandir("/dev", &namelist, NULL, alphasort);
    if (n < 0) {
        perror("scandir");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        if (strncmp(namelist[i]->d_name, "video", 5) == 0) {
            char path[64];
            snprintf(path, sizeof(path), "/dev/%s", namelist[i]->d_name);
            query_device(path);
        }
        free(namelist[i]);
    }
    free(namelist);
    return 0;
}

编译与运行

gcc -o v4l2_enum v4l2_enum.c
sudo ./v4l2_enum   # 部分设备节点需要root权限才能打开

验证方法

程序执行后,终端会按设备节点顺序输出每个V4L2设备的信息块。判定通过的观察点:

  • 每个 /dev/video* 节点均有一块以 === /dev/videoX === 开头的输出;
  • Driver/Card/Bus info 字段非空且与 v4l2-ctl --all -d /dev/videoX 命令输出一致;
  • 若设备支持视频采集,格式列表至少包含一项(如YUYV或MJPG);
  • 无设备时程序正常退出,不产生段错误。

常见问题

  • 无设备节点:加载 v4l2loopback 内核模块创建虚拟设备:sudo modprobe v4l2loopback。
  • 权限不足:将用户加入 video 组或使用sudo运行。
  • 格式枚举为空:部分输出型设备不支持CAPTURE格式枚举,属正常现象,可查看 device_caps 确认设备类型。