3. V4L2 ioctl 接口:VIDIOC_QUERYCAP、VIDIOC_ENUM_FMT、VIDIOC_S_FMT 等关键 ioctl
V4L2 驱动层与应用层之间的通信核心是 ioctl 系统调用。应用程序通过 open() 打开视频设备节点(如 /dev/video0)后,使用 ioctl(fd, request, arg) 向驱动发送控制命令。本章聚焦帧采集前必须调用的三个关键 ioctl:查询设备能力、枚举支持的格式、设置采集格式。这些调用是 mmap 映射与 poll 机制的前提条件。
3.1 VIDIOC_QUERYCAP:查询设备能力
VIDIOC_QUERYCAP 是应用层与 V4L2 设备交互的第一个 ioctl。它用于获取设备的基本信息,包括驱动名称、设备类型(capture / output / overlay 等)、设备能力标志(是否支持 streaming I/O、是否支持读写 I/O 等)。
调用时,应用层传入一个 struct v4l2_capability 结构体指针,驱动填充该结构体后返回。结构体定义如下:
struct v4l2_capability {
__u8 driver[16]; // 驱动名称,如 "uvcvideo"
__u8 card[32]; // 设备名称,如 "UVC Camera"
__u8 bus_info[32]; // 总线信息,如 "usb-0000:00:14.0-1"
__u32 version; // 驱动版本号(内核版本)
__u32 capabilities; // 设备能力标志位
__u32 device_caps; // 设备特定能力(内核 3.3+)
__u32 reserved[3];
};
capabilities 字段的关键标志位:
| 标志位 | 值 | 含义 |
|---|---|---|
V4L2_CAP_VIDEO_CAPTURE |
0x00000001 | 支持视频捕获(从摄像头采集帧) |
V4L2_CAP_STREAMING |
0x04000000 | 支持 streaming I/O(mmap / userptr / dmabuf) |
V4L2_CAP_READWRITE |
0x01000000 | 支持 read / write 方式读取帧 |
V4L2_CAP_EXT_PIX_FORMAT |
0x00200000 | 支持扩展像素格式描述 |
VIDIOC_QUERYCAP 后,必须检查 capabilities 是否包含 V4L2_CAP_VIDEO_CAPTURE 和 V4L2_CAP_STREAMING。如果缺少 V4L2_CAP_STREAMING,则无法使用 mmap 映射方式采集帧,只能退化为 read 方式。
3.2 VIDIOC_ENUM_FMT:枚举支持的格式
确认设备具备视频捕获能力后,下一步是查询设备支持的像素格式。V4L2 设备可能支持多种格式,如 YUYV、MJPEG、NV12、RGB565 等。应用层通过 VIDIOC_ENUM_FMT 逐个枚举,直到驱动返回 EINVAL 表示枚举结束。
调用时使用 struct v4l2_fmtdesc 结构体:
struct v4l2_fmtdesc {
__u32 index; // 枚举索引,从 0 开始递增
__u32 type; // 缓冲区类型:V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 flags; // 标志位:V4L2_FMT_FLAG_COMPRESSED 等
__u8 description[32]; // 格式描述,如 "Motion-JPEG"
__u32 pixelformat; // 四字符码,如 V4L2_PIX_FMT_MJPEG
__u32 reserved[4];
};
常见像素格式四字符码:
| 四字符码 | 数值 | 描述 | 是否压缩 |
|---|---|---|---|
V4L2_PIX_FMT_YUYV |
0x56595559 | YUYV 4:2:2 打包格式 | 否 |
V4L2_PIX_FMT_MJPEG |
0x47504A4D | Motion-JPEG 压缩流 | 是 |
V4L2_PIX_FMT_NV12 |
0x3231564E | NV12 YUV 4:2:0 平面格式 | 否 |
V4L2_PIX_FMT_RGB565 |
0x20424752 | RGB565 16 位 | 否 |
V4L2_PIX_FMT_H264 |
0x34363248 | H.264 编码流 | 是 |
type 字段必须与后续 VIDIOC_S_FMT 使用的类型一致。对于视频捕获设备,type 固定为 V4L2_BUF_TYPE_VIDEO_CAPTURE。如果设备同时支持视频输出,则需分别枚举 V4L2_BUF_TYPE_VIDEO_OUTPUT。
3.3 VIDIOC_S_FMT:设置采集格式
枚举完支持的格式后,应用层通过 VIDIOC_S_FMT 设置最终的采集参数,包括图像宽度、高度、像素格式、帧间隔等。这是帧采集前最关键的一步,因为它决定了后续 mmap 缓冲区的大小和布局。
调用时使用 struct v4l2_format 结构体:
struct v4l2_format {
__u32 type; // V4L2_BUF_TYPE_VIDEO_CAPTURE
union {
struct v4l2_pix_format pix; // 单平面格式
struct v4l2_pix_format_mplane pix_mp; // 多平面格式
// ... 其他类型
} fmt;
};
struct v4l2_pix_format {
__u32 width; // 图像宽度(像素)
__u32 height; // 图像高度(像素)
__u32 pixelformat; // 像素格式四字符码
__u32 field; // 场序:V4L2_FIELD_NONE / V4L2_FIELD_INTERLACED
__u32 bytesperline; // 每行字节数(对齐后)
__u32 sizeimage; // 一帧图像的总字节数
__u32 colorspace; // 色彩空间:V4L2_COLORSPACE_SRGB 等
__u32 priv; // 私有标志
};
设置格式的标准步骤:
- 先调用 VIDIOC_TRY_FMT(可选):测试驱动是否接受该格式,但不实际生效。
- 调用 VIDIOC_S_FMT:设置格式,驱动可能调整 width/height/bytesperline 等参数。
- 读取返回的 v4l2_format:驱动会回写实际采用的参数,应用层必须使用回写后的值。
S_FMT 返回后的 width、height、bytesperline、sizeimage,这些值将直接决定 mmap 映射的缓冲区大小。
3.4 其他关键 ioctl 简介
除了上述三个核心 ioctl,帧采集流程中还会用到以下 ioctl:
| ioctl | 用途 | 调用时机 |
|---|---|---|
VIDIOC_TRY_FMT |
测试格式是否被支持,不实际设置 | S_FMT 之前(可选) |
VIDIOC_G_FMT |
获取当前格式参数 | 任意时刻 |
VIDIOC_REQBUFS |
申请视频缓冲区(mmap 的前提) | S_FMT 之后 |
VIDIOC_QUERYBUF |
查询缓冲区信息(用于 mmap) | REQBUFS 之后 |
VIDIOC_QBUF |
将缓冲区放入驱动队列 | 流启动前后 |
VIDIOC_DQBUF |
从驱动队列取出已填满的缓冲区 | 流启动后 |
VIDIOC_STREAMON |
启动视频流 | QBUF 之后 |
VIDIOC_STREAMOFF |
停止视频流 | 采集结束 |
3.5 完整调用序列与代码示例
以下代码片段展示了从打开设备到设置格式的完整流程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
int main() {
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
// 1. VIDIOC_QUERYCAP:查询设备能力
struct v4l2_capability cap;
memset(&cap, 0, sizeof(cap));
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
perror("VIDIOC_QUERYCAP");
close(fd);
return -1;
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
fprintf(stderr, "设备不支持视频捕获\n");
close(fd);
return -1;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf(stderr, "设备不支持 streaming I/O\n");
close(fd);
return -1;
}
// 2. VIDIOC_ENUM_FMT:枚举格式(示例只枚举前两个)
struct v4l2_fmtdesc fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.index = 0;
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) {
printf("格式 %d: %s (0x%08x)\n",
fmt.index, fmt.description, fmt.pixelformat);
fmt.index++;
}
// 3. VIDIOC_S_FMT:设置格式为 640x480 YUYV
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = 640;
format.fmt.pix.height = 480;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
format.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &format) < 0) {
perror("VIDIOC_S_FMT");
close(fd);
return -1;
}
// 读取驱动回写的实际参数
printf("实际分辨率: %dx%d\n",
format.fmt.pix.width, format.fmt.pix.height);
printf("每帧大小: %d bytes\n", format.fmt.pix.sizeimage);
printf("每行字节数: %d\n", format.fmt.pix.bytesperline);
// 后续:VIDIOC_REQBUFS → VIDIOC_QUERYBUF → mmap → ...
close(fd);
return 0;
}
v4l2-ctl --list-formats 和 v4l2-ctl --get-fmt-video 命令行工具可以快速查看设备支持的格式和当前格式,方便对比应用层代码的行为是否正确。
3.6 常见错误与排查
| 错误码 | 常见原因 | 排查方向 |
|---|---|---|
EINVAL |
type 字段错误、pixelformat 不支持、设备节点错误 | 检查 type 是否为 V4L2_BUF_TYPE_VIDEO_CAPTURE;确认枚举结果 |
EBUSY |
设备已被其他进程占用 | 使用 fuser /dev/video0 查看占用进程 |
ENODEV |
设备不存在或驱动未加载 | 检查 ls /dev/video* 和 dmesg |
ENOMEM |
内核内存不足 | 减少缓冲区数量或分辨率 |