1. V4L2框架初探

V4L2框架初探:枚举摄像头设备 学习卡片

任务目标

编写一个C程序,通过V4L2框架枚举系统中所有视频设备节点(/dev/video*),对每个节点打开设备并查询其能力信息,打印设备名称、驱动名称、总线信息、设备能力以及支持的格式,从而理解V4L2设备节点与内核驱动的对应关系。

环境与依赖

  • Linux系统(内核已启用V4L2框架)
  • 开发包:libv4l-dev(提供头文件与链接库)
  • 编译器:gcc

核心实现

程序遍历/dev/video0至/dev/video63共64个候选节点,对每个节点尝试open()并调用VIDIOC_QUERYCAP查询能力。若成功则打印设备信息,并进一步枚举该设备支持的像素格式。若打开失败(设备不存在或无权限),则跳过继续下一个节点。

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

#define MAX_VIDEO_DEVICES 64

static const char *buf_type_to_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";
        case V4L2_BUF_TYPE_VBI_CAPTURE:   return "VBI Capture";
        case V4L2_BUF_TYPE_VBI_OUTPUT:    return "VBI Output";
        case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: return "Sliced VBI Capture";
        case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:  return "Sliced VBI Output";
        case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: return "Video Output Overlay";
        case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: return "Video Capture Multiplanar";
        case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:  return "Video Output Multiplanar";
        case V4L2_BUF_TYPE_SDR_CAPTURE:   return "SDR Capture";
        case V4L2_BUF_TYPE_SDR_OUTPUT:    return "SDR Output";
        case V4L2_BUF_TYPE_META_CAPTURE:  return "Metadata Capture";
        default: return "Unknown";
    }
}

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("      [%d] %-10s %s\n", fmtdesc.index,
               (char *)fmtdesc.description, (char *)fmtdesc.pixelformat);
        fmtdesc.index++;
    }
}

int main(void) {
    char dev_path[32];
    int found = 0;

    printf("=== V4L2 Device Enumeration ===\n\n");

    for (int i = 0; i < MAX_VIDEO_DEVICES; i++) {
        snprintf(dev_path, sizeof(dev_path), "/dev/video%d", i);

        int fd = open(dev_path, O_RDWR);
        if (fd < 0) {
            if (errno == ENOENT) {
                // 设备节点不存在,继续尝试下一个
                continue;
            }
            // 存在但打开失败(如权限不足),打印提示
            printf("%s: open failed: %s\n", dev_path, strerror(errno));
            continue;
        }

        struct v4l2_capability cap;
        memset(&cap, 0, sizeof(cap));
        if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
            printf("%s: VIDIOC_QUERYCAP failed: %s\n", dev_path, strerror(errno));
            close(fd);
            continue;
        }

        found++;
        printf("Device: %s\n", dev_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("  Capabilities: 0x%08x\n", cap.capabilities);
        if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
            printf("    - Video Capture\n");
        if (cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)
            printf("    - Video Output\n");
        if (cap.capabilities & V4L2_CAP_VIDEO_OVERLAY)
            printf("    - Video Overlay\n");
        if (cap.capabilities & V4L2_CAP_STREAMING)
            printf("    - Streaming\n");
        if (cap.capabilities & V4L2_CAP_DEVICE_CAPS) {
            printf("    - Device Caps: 0x%08x\n", cap.device_caps);
        }

        // 枚举该设备支持的格式(仅对支持CAPTURE的设备)
        if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
            print_formats(fd);
        }

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

    if (found == 0) {
        printf("No V4L2 video devices found.\n");
        return 1;
    }

    printf("Total %d device(s) found.\n", found);
    return 0;
}

编译与运行

# 编译
gcc -o v4l2_enum v4l2_enum.c

# 运行(可能需要root权限才能打开某些设备)
sudo ./v4l2_enum

验证方法

运行程序后,观察输出是否包含以下内容:

  • 每个/dev/video*节点对应的驱动名称(driver字段)、设备名称(card字段)和总线信息(bus_info字段)。
  • 设备能力标志(如Video Capture、Streaming等)。
  • 若设备支持视频采集,会列出其支持的像素格式(如YUYV、MJPG等)。

可通过v4l2-ctl --list-devices命令(需安装v4l-utils)交叉验证设备节点与驱动信息的对应关系。若程序输出与v4l2-ctl结果一致,且能正确识别所有存在的视频设备,则判定通过。

常见问题

  • 打开设备提示Permission denied:当前用户不在video组中,使用sudo运行或将自己加入video组。
  • 某些节点打开失败但设备存在:可能是设备已被占用(如被相机应用独占),关闭占用程序后重试。
  • 枚举不到设备:确认内核已加载对应驱动(如uvcvideo),且/dev/video*节点已创建。