介绍
V4L2,全称为 Video for Linux 2,是 Linux 操作系统上的一个内核框架,旨在支持视频设备。V4L2 是 V4L 的第二版,V4L2 修复了一些设计缺陷,并开始出现在 2.5.x 内核中。它提供了一组 API 和驱动接口,使得用户空间应用程序能够与摄像头和电视卡等各种视频设备进行交互。
框架
V4L2 的视频设备以 /dev/videoX
作为其设备节点。视频设备使用高频摄像头或普通摄像头作为输入源。Linux 内核驱动这些设备,接收并处理相应的视频信息。
V4L2 的架构如下:
用户空间 (User Space)
应用程序通过系统接口访问 /dev/videoX
节点,进行视频数据的采集和处理。
- yavta(Yet Another V4L2 Test Application)是一个简单但功能强大的V4L2测试工具,主要用于抓取视频帧并将其保存为文件。
- guvcview 是一个基于GTK+的图形界面工具,用于与USB摄像头和V4L2设备交互。它提供了一个友好的界面来调节摄像头的设置,并实时查看视频流,非常适合需要图形界面的用户,尤其是摄影、视频会议和视频监控等应用。
- libv4l: 提供视频设备抽象层接口,帮助应用程序与V4L2驱动交互。
- v4l2 utilities 包含了一组常用的工具,这些工具可以用来配置、测试和操作V4L2设备。有如下:
- v4l2-ctl:用于与V4L2设备交互。它可以用来列出设备信息、获取和设置设备参数、控制设备的流媒体等。
- v4l2-compliance:用于检查V4L2设备符合标准的工具,它通过执行一组标准测试来验证视频设备是否完全支持V4L2规范。这个工具适合用来验证设备的兼容性和行为。
- v4l2-ioctl:用于发送原始的ioctl命令给V4L2设备。
- v4l2-dbg:用于查看和调试V4L2设备的内部状态。它允许开发者访问V4L2设备的内部寄存器,查看实时的调试信息,帮助开发人员定位问题。
内核空间 (Kernel Space)
- V4L2 Core: V4L2框架的核心模块,负责统一管理视频设备的驱动程序。
- ISP(Image Signal Processor): 图像信号处理模块,用于处理图像数据(如去噪、白平衡等)。
- VICAP: 视频采集模块,用于控制视频捕获和数据传输。
- Sensor: 视频传感器驱动程序,通过V4L2框架与底层硬件接口进行通信。
- CCI Bus Driver: 用于与传感器等外设进行数据通信,常通过 I2C 控制器连接。
硬件层 (Hardware Layer)
- GPIO Controller:用于控制外部设备。
- I2C Controller:用于与传感器等外设通信(例如通过CCI)。
- EHCI/OHCI(USB Controller):支持USB接口的设备通信(如UVC设备)。
- CSIC Controller:用于与 CSI 视频数据接口进行交互,实现视频数据传输。
外部设备 (External Devices)
- 传感器模块包括:
- DVP(Digital Video Port):数字视频端口。
- UVC(USB Video Class):USB视频设备类。
- CSI(Camera Serial Interface):摄像头串行接口,用于高带宽视频数据传输。
字符设备文件名称含义
设备名称 | 次设备号 | 描述 |
---|---|---|
/dev/videox |
0-63 | 视频捕获设备 |
/dev/radiox |
64-127 | 收音机设备 |
/dev/vtxx |
192-223 | 图文电视设备 |
/dev/vbix |
224-239 | VBI 设备 |
视频采集流程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#define DEVICE "/dev/video0" // 摄像头设备节点
#define BUFFER_COUNT 4 // 缓冲区数量
// 缓冲区结构体
struct buffer {
void *start;
size_t length;
};
int main() {
int fd;
struct v4l2_capability cap;
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
struct v4l2_buffer buf;
struct buffer buffers[BUFFER_COUNT];
int i;
// 1. 打开视频设备
fd = open(DEVICE, O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return -1;
}
// 2. 查询设备能力
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
perror("Failed to query device capabilities");
close(fd);
return -1;
}
printf("Device Name: %s\n", cap.card);
// 3. 设置视频格式
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 设置为YUYV格式
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
perror("Failed to set format");
close(fd);
return -1;
}
printf("Set Format: 640x480 YUYV\n");
// 4. 请求缓冲区
memset(&req, 0, sizeof(req));
req.count = BUFFER_COUNT;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
perror("Failed to request buffers");
close(fd);
return -1;
}
// 5. 映射缓冲区到用户空间
for (i = 0; i < BUFFER_COUNT; i++) {
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
perror("Failed to query buffer");
close(fd);
return -1;
}
buffers[i].length = buf.length;
buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
if (buffers[i].start == MAP_FAILED) {
perror("Failed to mmap buffer");
close(fd);
return -1;
}
// 将缓冲区加入队列
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("Failed to queue buffer");
close(fd);
return -1;
}
}
// 6. 启动视频流
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
perror("Failed to start stream");
close(fd);
return -1;
}
printf("Stream started...\n");
// 7. 采集数据并保存到文件
FILE *output = fopen("frame.yuv", "wb");
if (!output) {
perror("Failed to open output file");
close(fd);
return -1;
}
for (i = 0; i < 100; i++) {
// 采集100帧数据
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
// 取出已填充数据的缓冲区
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
perror("Failed to dequeue buffer");
break;
}
printf("Captured frame %d\n", i);
fwrite(buffers[buf.index].start, buf.bytesused, 1, output);
// 重新将缓冲区放回队列
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("Failed to requeue buffer");
break;
}
}
fclose(output);
// 8. 停止视频流
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
perror("Failed to stop stream");
}
printf("Stream stopped.\n");
// 9. 释放资源
for (i = 0; i < BUFFER_COUNT; i++) {
munmap(buffers[i].start, buffers[i].length);
}
close(fd);
return 0;
}
支持的 ioctl 控制命令
- IOR: 只读操作,即该操作码会返回一个值。
- IOW: 只写操作,即该操作码会接收一个值。
- IOWR: 读写操作,既会发送数据,也会返回数据。
操作码 | I/O | 与命令相关的结构体 | 说明 |
---|---|---|---|
VIDIOC_QUERYCAP | IOR | struct v4l2_capability |
查询设备能力,返回支持的视频设备特性(如设备类型、驱动名称)。 |
VIDIOC_RESERVED | IO | 1 | 保留操作,通常未使用。 |
VIDIOC_ENUM_FMT | IOWR | struct v4l2_fmtdesc |
枚举设备支持的视频格式(如YUV、RGB等)。 |
VIDIOC_G_FMT | IOWR | struct v4l2_format |
获取当前的视频格式设置(包括图像大小、像素格式等)。 |
VIDIOC_S_FMT | IOWR | struct v4l2_format |
设置视频格式。 |
VIDIOC_G_COMP | IOR | struct v4l2_compression |
获取设备的视频压缩设置。 |
VIDIOC_S_COMP | IOW | struct v4l2_compression |
设置设备的视频压缩设置。 |
VIDIOC_REQBUFS | IOWR | struct v4l2_requestbuffers |
请求缓冲区,用于视频数据的交换(内存分配)。 |
VIDIOC_QUERYBUF | IOWR | struct v4l2_buffer |
查询缓冲区的详细信息(大小、地址等)。 |
VIDIOC_G_FBUF | IOR | struct v4l2_framebuffer |
获取视频帧缓冲区的设置(用于显示)。 |
VIDIOC_S_FBUF | IOW | struct v4l2_framebuffer |
设置视频帧缓冲区。 |
VIDIOC_OVERLAY | IOWR | int |
启用或禁用视频叠加(用于图像合成)。 |
VIDIOC_QBUF | IOWR | struct v4l2_buffer |
队列一个缓冲区,准备进行数据捕捉。 |
VIDIOC_DQBUF | IOWR | struct v4l2_buffer |
出队一个缓冲区,获取捕获的数据。 |
VIDIOC_STREAMON | IOW | int |
启动视频流捕获。 |
VIDIOC_STREAMOFF | IOW | int |
停止视频流捕获。 |
VIDIOC_G_PARM | IOWR | struct v4l2_streamparm |
获取视频流参数(如帧率、分辨率等)。 |
VIDIOC_S_PARM | IOW | struct v4l2_streamparm |
设置视频流参数(如帧率、分辨率等)。 |
VIDIOC_G_STD | IOR | v4l2_std_id |
获取当前的标准(例如PAL、NTSC)。 |
VIDIOC_S_STD | IOW | v4l2_std_id |
设置视频标准(例如PAL、NTSC)。 |
VIDIOC_ENUMSTD | IOWR | struct v4l2_standard |
枚举支持的视频标准。 |
VIDIOC_ENUMINPUT | IOWR | struct v4l2_input |
枚举支持的输入源(例如,Composite、S-Video等)。 |
VIDIOC_G_CTRL | IOWR | struct v4l2_control |
获取控件的当前值(例如,亮度、对比度等)。 |
VIDIOC_S_CTRL | IOW | struct v4l2_control |
设置控件的值(例如,亮度、对比度等)。 |
VIDIOC_G_TUNER | IOWR | struct v4l2_tuner |
获取调谐器的当前设置(例如频率、信号类型等)。 |
VIDIOC_S_TUNER | IOW | struct v4l2_tuner |
设置调谐器的参数。 |
VIDIOC_G_AUDIO | IOWR | struct v4l2_audio |
获取音频输入设备的设置。 |
VIDIOC_S_AUDIO | IOW | struct v4l2_audio |
设置音频输入设备的参数。 |
VIDIOC_QUERYCTRL | IOWR | struct v4l2_queryctrl |
查询控件的详细信息。 |
VIDIOC_QUERYMENU | IOWR | struct v4l2_querymenu |
查询菜单控件的选项。 |
VIDIOC_G_INPUT | IOR | int |
获取当前选中的输入源索引。 |
VIDIOC_S_INPUT | IOWR | int |
设置视频输入源。 |
VIDIOC_G_OUTPUT | IOR | int |
获取当前输出设备的索引。 |
VIDIOC_S_OUTPUT | IOWR | int |
设置视频输出设备。 |
VIDIOC_ENUMOUTPUT | IOWR | struct v4l2_output |
枚举支持的视频输出设备。 |
VIDIOC_G_AUDOUT | IOWR | struct v4l2_audioout |
获取音频输出设备的设置。 |
VIDIOC_S_AUDOUT | IOW | struct v4l2_audioout |
设置音频输出设备的参数。 |
VIDIOC_G_MODULATOR | IOWR | struct v4l2_modulator |
获取调制器的设置(例如频率、模式)。 |
VIDIOC_S_MODULATOR | IOW | struct v4l2_modulator |
设置调制器的参数。 |
VIDIOC_G_FREQUENCY | IOWR | struct v4l2_frequency |
获取频率的当前设置。 |
VIDIOC_S_FREQUENCY | IOW | struct v4l2_frequency |
设置频率。 |
VIDIOC_CROPCAP | IOR | struct v4l2_cropcap |
查询设备的裁剪能力。 |
VIDIOC_G_CROP | IOWR | struct v4l2_crop |
获取当前的裁剪设置。 |
VIDIOC_S_CROP | IOW | struct v4l2_crop |
设置裁剪参数。 |
VIDIOC_G_JPEGCOMP | IOR | struct v4l2_jpegcompression |
获取JPEG压缩设置。 |
VIDIOC_S_JPEGCOMP | IOW | struct v4l2_jpegcompression |
设置JPEG压缩参数。 |
VIDIOC_QUERYSTD | IOR | v4l2_std_id |
查询当前支持的视频标准。 |
VIDIOC_TRY_FMT | IOWR | struct v4l2_format |
尝试设置并获取一个视频格式,但不实际应用。 |
下面是一些示例代码,并列出对应结构体的成员列表以及含义:
查询视频设备的能力和特性
struct v4l2_capability capability;
memset(&capability, 0, sizeof(struct v4l2_capability));
ioctl(fd, VIDIOC_QUERYCAP, &capability);
struct v4l2_capability {
__u8 driver[16]; // 驱动程序名称
__u8 card[32]; // 设备名称
__u8 bus_info[32]; // 设备所在总线的名称
__u32 version; // 驱动程序版本号
__u32 capabilities; // 设备支持的功能
__u32 device_caps; // 设备特定的功能
__u32 reserved[3]; // 保留字段
};
枚举视频设备支持的像素格式
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0; // 比如从0开始
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type为"捕获"
ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc);
struct v4l2_fmtdesc {
__u32 index; // 格式索引
enum v4l2_buf_type type; // 缓冲类型
__u32 flags; // 格式标志
__u8 description[32]; // 格式描述字符串
__u32 pixelformat; // 像素格式
__u32 reserved[4]; // 保留字段,必须设置为0
};
枚举视频设备在特定像素格式下支持的帧尺寸
struct v4l2_frmsizeenum fsenum;
memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
fsenum.pixel_format = fmtdesc.pixelformat;
fsenum.index = frame_index;
ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum);
struct v4l2_frmsizeenum {
__u32 index; // 尺寸索引,从0开始递增
__u32 pixel_format; // 像素格式,FourCC 代码
__u32 type; // 尺寸类型,V4L2_FRMSIZE_TYPE_DISCRETE 或 V4L2_FRMSIZE_TYPE_CONTINUOUS 或 V4L2_FRMSIZE_TYPE_STEPWISE
union {
struct v4l2_frmsize_discrete discrete; // 离散帧尺寸
struct v4l2_frmsize_stepwise stepwise; // 连续帧尺寸或步进帧尺寸
};
__u32 reserved[2]; // 保留字段,必须设置为0
};
struct v4l2_frmsize_discrete {
__u32 width; // 帧宽
__u32 height; // 帧高
};
struct v4l2_frmsize_stepwise {
__u32 min_width; // 最小帧宽
__u32 max_width; // 最大帧宽
__u32 step_width; // 帧宽步长
__u32 min_height; // 最小帧高
__u32 max_height; // 最大帧高
__u32 step_height; // 帧高步长
};
获取当前格式
struct v4l2_format current_format;
memset(¤t_format, 0, sizeof(struct v4l2_format));
current_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_G_FMT, ¤t_format);
struct v4l2_format {
enum v4l2_buf_type type; // 缓冲区类型
union {
struct v4l2_pix_format pix; // 视频捕获/输出格式
struct v4l2_window win; // 视频覆盖格式
struct v4l2_vbi_format vbi; // 垂直消隐间隔(VBI)格式
struct v4l2_sliced_vbi_format sliced; // 切片VBI格式
struct v4l2_sdr_format sdr; // 软件定义无线电(SDR)格式
struct v4l2_meta_format meta; // 元数据格式
__u8 raw_data[200]; // 保留用于未来扩展
} fmt; // 格式描述的联合体
};
enum v4l2_buf_type
:
V4L2_BUF_TYPE_VIDEO_CAPTURE
:视频捕获缓冲区V4L2_BUF_TYPE_VIDEO_OUTPUT
:视频输出缓冲区V4L2_BUF_TYPE_VIDEO_OVERLAY
:视频覆盖缓冲区V4L2_BUF_TYPE_VBI_CAPTURE
:VBI捕获缓冲区V4L2_BUF_TYPE_SDR_CAPTURE
:SDR捕获缓冲区
struct v4l2_pix_format
:
__u32 width
:图像宽度__u32 height
:图像高度__u32 pixelformat
:像素格式(FourCC 代码)__u32 field
:场类型(如逐行扫描或隔行扫描)__u32 bytesperline
:每行的字节数__u32 sizeimage
:图像的总大小(字节)__u32 colorspace
:颜色空间__u32 priv
:特定于驱动程序的私有数据__u32 flags
:格式标志
设置当前格式
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(struct v4l2_format));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1024;
fmt.fmt.pix.height = 768;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
ioctl(fd, VIDIOC_S_FMT, &fmt);
向设备申请缓冲区
struct v4l2_requestbuffers rb;
memset(&rb, 0, sizeof(rb));
rb.count = 32;
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &rb);
struct v4l2_requestbuffers {
__u32 count; // 请求的缓冲区数量
enum v4l2_buf_type type; // 缓冲区类型
enum v4l2_memory memory; // 缓冲区内存类型
__u32 reserved[2]; // 保留字段
};
查询设备缓冲区的信息
用来获取已分配缓冲区的详细信息,以便进行内存映射操作。
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_QUERYBUF, &buf)
mmap(...);
struct v4l2_buffer {
__u32 index; // 缓冲区索引
enum v4l2_buf_type type; // 缓冲区类型
__u32 bytesused; // 实际使用的字节数
__u32 flags; // 缓冲区标志
enum v4l2_field field; // 场类型
struct timeval timestamp; // 时间戳
struct v4l2_timecode timecode; // 时间码
__u32 sequence; // 帧序列号
enum v4l2_memory memory; // 内存类型
union {
__u32 offset; // 缓冲区偏移(对于MMAP)
unsigned long userptr; // 用户指针(对于USERPTR)
} m;
__u32 length; // 缓冲区的总长度
__u32 reserved2;
__u32 reserved;
};
将一个空的或已处理完的缓冲区放回驱动程序的队列
使其可以被再次填充数据(用于视频捕获)或再次输出数据(用于视频输出)。
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_QBUF, &buf);
struct v4l2_buffer {
__u32 index; // 缓冲区索引
__u32 type; // 缓冲区类型,如 V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 bytesused; // 实际使用的字节数
__u32 flags; // 缓冲区标志
__u32 field; // 场类型
struct timeval timestamp; // 时间戳
struct v4l2_timecode timecode; // 时间码
__u32 sequence; // 序列号
__u32 memory; // 内存类型,如 V4L2_MEMORY_MMAP
union {
__u32 offset; // 对于 V4L2_MEMORY_MMAP
unsigned long userptr; // 对于 V4L2_MEMORY_USERPTR
__s32 fd; // 对于 V4L2_MEMORY_DMABUF
} m;
__u32 length; // 缓冲区长度
__u32 reserved2;
__u32 reserved;
};
从驱动程序的缓冲区队列中取出一个已经填充数据的缓冲区
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_DQBUF, &buf);
选择输入源
// 获取当前输入源
int value;
ioctl(fd, VIDIOC_G_INPUT, &value);
// 设置当前输入源
int value = 0;
ioctl(fd, VIDIOC_S_INPUT, &value);
查询控制参数信息
struct v4l2_queryctrl qctrl;
memset(&qctrl, 0, sizeof(v4l2_queryctrl));
// 查询亮度控制参数的信息
qctrl.id = V4L2_CID_BRIGHTNESS;
ioctl(fd, VIDIOC_QUERYCTRL, &qctrl);
struct v4l2_queryctrl {
__u32 id; // 控制的ID
__u32 type; // 控制的类型
__u8 name[32]; // 控制的名称
__s32 minimum; // 控制的最小值
__s32 maximum; // 控制的最大值
__s32 step; // 控制值的步长
__s32 default_value; // 控制的默认值
__u32 flags; // 控制标志
__u32 reserved[2]; // 保留字段,必须设置为0
};
获取和设置视频设备的控制参数
// 获取
struct v4l2_control control;
c.id = V4L2_CID_BRIGHTNESS;
ioctl(fd, VIDIOC_G_CTRL, &control)
// 设置
struct v4l2_control control;
c.id = V4L2_CID_BRIGHTNESS;
c.value = 99;
ioctl(fd, VIDIOC_S_CTRL, &control);
struct v4l2_control {
__u32 id; // 控制的ID
__s32 value; // 控制的值
};
参考资料
- https://en.wikipedia.org/wiki/Video4Linux
- https://web.archive.org/web/20110707012738/http://alumnos.elo.utfsm.cl/~yanez/video-for-linux-2-sample-programs/
- https://www.kernel.org/doc/html/v4.9/media/index.html
- https://wiki.luckfox.com/Luckfox-Pico/Luckfox-Pico-V4L2/