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

任务目标

编写一个C程序,枚举Linux系统中所有可用的V4L2视频设备(/dev/video*),对每个设备执行VIDIOC_QUERYCAP查询,输出驱动名称、总线信息、设备能力标志及对应的能力描述。

环境依赖

  • Linux系统(内核已启用V4L2框架)
  • V4L2开发头文件:libv4l-dev(Debian/Ubuntu)或 v4l-utils(部分发行版提供头文件)
  • 编译器:gcc

关键完整代码

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

static void print_caps(__u32 caps) {
    printf("    设备能力标志: 0x%08x\n", caps);
    printf("    支持: ");
    if (caps & V4L2_CAP_VIDEO_CAPTURE)       printf("视频采集 ");
    if (caps & V4L2_CAP_VIDEO_OUTPUT)        printf("视频输出 ");
    if (caps & V4L2_CAP_VIDEO_OVERLAY)       printf("视频叠加 ");
    if (caps & V4L2_CAP_VIDEO_M2M)           printf("内存到内存 ");
    if (caps & V4L2_CAP_STREAMING)           printf("流式I/O ");
    if (caps & V4L2_CAP_READWRITE)           printf("读写I/O ");
    if (caps & V4L2_CAP_DEVICE_CAPS)         printf("独立设备能力 ");
    if (caps & V4L2_CAP_META_CAPTURE)        printf("元数据采集 ");
    if (caps & V4L2_CAP_TOUCH)               printf("触摸设备 ");
    if (caps & V4L2_CAP_IO_MC)               printf("媒体控制器I/O ");
    printf("\n");
}

static void query_device(const char *path) {
    int fd = open(path, O_RDWR | O_NONBLOCK);
    if (fd < 0) {
        printf("%s: 打开失败 (%s)\n", path, strerror(errno));
        return;
    }

    struct v4l2_capability cap;
    memset(&cap, 0, sizeof(cap));
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
        printf("%s: QUERYCAP失败 (%s)\n", path, strerror(errno));
        close(fd);
        return;
    }

    printf("设备节点: %s\n", path);
    printf("  驱动名称: %s\n", cap.driver);
    printf("  设备名称: %s\n", cap.card);
    printf("  总线信息: %s\n", cap.bus_info);
    printf("  驱动版本: %u.%u.%u\n",
           (cap.version >> 16) & 0xff,
           (cap.version >> 8) & 0xff,
           cap.version & 0xff);

    if (cap.capabilities & V4L2_CAP_DEVICE_CAPS) {
        printf("  设备特定能力:\n");
        print_caps(cap.device_caps);
    } else {
        printf("  全局能力:\n");
        print_caps(cap.capabilities);
    }
    printf("\n");
    close(fd);
}

int main(void) {
    DIR *dir = opendir("/dev");
    if (!dir) {
        perror("opendir /dev");
        return 1;
    }

    struct dirent *entry;
    int found = 0;
    while ((entry = readdir(dir)) != NULL) {
        if (strncmp(entry->d_name, "video", 5) == 0) {
            char path[64];
            snprintf(path, sizeof(path), "/dev/%s", entry->d_name);
            query_device(path);
            found = 1;
        }
    }
    closedir(dir);

    if (!found) {
        printf("未找到任何 /dev/video* 设备\n");
        return 1;
    }
    return 0;
}

编译与运行

gcc -o v4l2_enum v4l2_enum.c
sudo ./v4l2_enum

需要root权限或加入video组才能打开设备节点。

验证方法

  • 正常输出:每个设备节点显示驱动名、设备名、总线信息、版本号及能力标志,能力描述中至少包含一项(如“视频采集”或“流式I/O”)。
  • 对比系统工具:运行 v4l2-ctl --list-devices(若已安装v4l-utils),对比设备名称与总线信息是否一致。
  • 无设备情况:若系统无摄像头或视频设备,程序输出“未找到任何 /dev/video* 设备”并返回退出码1。

常见问题

  • 打开设备失败:确认设备节点存在且当前用户有权限(ls -l /dev/video*查看属组)。
  • ioctl失败:某些设备(如部分USB摄像头)在打开时可能需要特定访问模式,可尝试将O_RDWR改为O_RDONLY重试。
  • 能力标志为空:部分驱动未正确填充capabilities字段,可检查内核日志(dmesg)确认驱动加载状态。