1. 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*节点已创建。