V4L2(Video For Linux Two) 是内核提供给应用程序访问音、视频驱动的统一接口。
在应用程序获取视频数据的流程中,都是通过 ioctl 命令与驱动程序进行交互,常见的 ioctl 命令有:
VIDIOC_QUERYCAP /* 查询设备属性 */
VIDIOC_G_FMT /* 查询的视频格式 */
VIDIOC_S_FMT /* 设置捕获视频的格式 */
VIDIOC_REQBUFS /* 向驱动申请内存的请求 */
VIDIOC_QUERYBUF /* 向驱动查询申请到的内存 */
VIDIOC_QBUF /* 将空闲的内存加入可捕获视频的队列 */
VIDIOC_DQBUF /* 将已经捕获好视频的内存拉出已捕获视频的队列 */
VIDIOC_STREAMON /* 打开视频流 */
VIDIOC_STREAMOFF /* 关闭视频流 */
VIDIOC_QUERYCTRL /* 查询驱动是否支持该命令 */
VIDIOC_CROPCAP /* 图像裁剪 */
VIDIOC_G_INPUT /* 查询当前input */
VIDIOC_S_INPUT /* 设置当前input */
VIDIOC_QBUF /* 把帧放入队列 */
VIDIOC_DQBUF /* 从队列中取出帧 */
1.查询设备属性: VIDIOC_QUERYCAP
相关函数:
int ioctl(int fd, int request, struct v4l2_capability *argp);
相关结构体:
struct v4l2_capability
{
u8 driver[16]; // 驱动名字
u8 card[32]; // 设备名字
u8 bus_info[32]; // 设备在系统中的位置
u32 version; // 驱动版本号
u32 capabilities; // 设备支持的操作
u32 reserved[4]; // 保留字段
};
其中域 capabilities 代表设备支持的操作模式,常见的值有 V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING 表示是一个视频捕捉设备并且具有数据流控制模式.
2.查询并显示所有支持的格式:VIDIOC_ENUM_FMT
相关函数:
int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);
相关结构体:
struct v4l2_fmtdesc
{
u32 index; // 要查询的格式序号,应用程序设置
enum v4l2_buf_type type; // 帧类型,应用程序设置
u32 flags; // 是否为压缩格式
u8 description[32]; // 格式名称
u32 pixelformat; // 格式
u32 reserved[4]; // 保留
};
3.查看或设置当前格式: VIDIOC_G_FMT, VIDIOC_S_FMT
相关函数:
int ioctl(int fd, int request, struct v4l2_format *argp);
相关结构体:
struct v4l2_format
{
enum v4l2_buf_type type; // 帧类型,应用程序设置
union fmt
{
struct v4l2_pix_format pix; // 视频设备使用
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
u8 raw_data[200];
};
};
struct v4l2_pix_format
{
u32 width; // 帧宽,单位像素
u32 height; // 帧高,单位像素
u32 pixelformat; // 帧格式
enum v4l2_field field;
u32 bytesperline;
u32 sizeimage;
enum v4l2_colorspace colorspace;
u32 priv;
};
通过VIDIOC_S_FMT命令和结构体 v4l2_format 初始化捕获视频的格式,如果要改变格式则用 VIDIOC_TRY_FMT 命令,常见的捕获模式为 V4L2_BUF_TYPE_VIDEO_CAPTURE 即视频捕捉模式,在此模式下 fmt 联合体采用域 v4l2_pix_format:其中 width 为视频的宽、height 为视频的高、pixelformat 为视频数据格式(常见的值有 V4L2_PIX_FMT_YUV422P | V4L2_PIX_FMT_RGB565)、bytesperline 为一行图像占用的字节数、sizeimage 则为图像占用的总字节数、colorspace 指定设备的颜色空间。
4.查看或设置当前Inputs and Outputs
相关函数:
int ioctl(int fd, int request, struct v4l2_input *argp);
相关结构体:
struct v4l2_input {
__u32 index; /* Which input */
__u8 name[32]; /* Label */
__u32 type; /* Type of input */
__u32 audioset; /* Associated audios (bitfield) */
__u32 tuner; /* Associated tuner */
v4l2_std_id std;
__u32 status;
__u32 reserved[4];
};
VIDIOC_G_INPUT 和 VIDIOC_S_INPUT 用来查询和设置当前的 input,一个 video 设备 节点可能对应多个视频源,比如 saf7113 可以最多支持四路 cvbs 输入,如果上层想在四 个cvbs视频输入间切换,那么就要调用 ioctl(fd, VIDIOC_S_INPUT, &input) 来切换。
5.向设备申请缓冲区 VIDIOC_REQBUFS
相关函数:
int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);
相关结构体:
struct v4l2_requestbuffers
{
u32 count; // 缓冲区内缓冲帧的数目
enum v4l2_buf_type type; // 缓冲帧数据格式
enum v4l2_memory memory; // 区别是内存映射还是用户指针方式
u32 reserved[2];
};
Camera设备有三个队列。缓存刚分配时放在EMPTY队列,等待设备填充时放在IN队列,设备填充好时放在OUT队列。
下面的代码中,使用V4L2_MEMORY_MMAP要求设备分配6个缓存。这时缓存放在EMPTY队列。
struct v4l2_requestbuffers rb;
memset (&rb, 0, sizeof(rb));
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
rb.count = 6;
ret = ioctl (fd, VIDIOC_REQBUFS, &rb);
V4L2_MEMORY_MMAP之外还可能有其他选择,如V4L2_MEMORY_DMABUF。这时缓存不是设备分配,而是应用程序传进去。它可能来自其他Video设备,或任何能分配DMABUF的设备。
6.查询缓冲帧信息:VIDIOC_QUERYBUF
相关函数:
int ioctl(int fd, int request, struct v4l2_buffer *argp);
相关结构体:
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;
/* memory location */
enum v4l2_memory memory;
union {
__u32 offset;
unsigned long userptr;
} m;
__u32 length;
__u32 input;
__u32 reserved;
};
index 为缓存编号,type 为视频捕获模式,bytesused 为缓存已使用空间大小,flags 为缓存当前状态(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据),timestamp 为时间戳,sequence为缓存序号,memory 为缓存使用方式,offset 为当前缓存与内存区起始地址的偏移,length 为缓存大小,reserved 一般用于传递物理地址值。
另外VIDIOC_QBUF 和 VIDIOC_DQBUF 命令都采用结构 v4l2_buffer 与驱动通信:VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频的队列,传递的主要参数为 index;VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会根据 index 确定可用数据的起始地址和范围。缓冲区处理好之后,就可以开始获取数据了, 通过命令VIDIOC_STREAMON, VIDIOC_STREAMOFF启动或停止数据流。
下面的代码中,使用索引值依次得到6个缓存。对每个缓存,使用VIDIOC_QUERYBUF从EMPTY 队列取出,再使用VIDIOC_QBUF放到 IN 队列。如果应用层代码需要处理帧,则可以使用mmap()将它映射到虚拟地址空间。
void* mmap[6];
int mlen;
struct v4l2_buffer buf;
for (int i = 0; i < 6; i++)
{
memset (&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
// get it from EMPTY queue.
ret = ioctl (fd, VIDIOC_QUERYBUF, &buf);
// map it to file descriptor
mmap[i] = mmap (0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
mlen = buf.length;
// put it into IN queue.
ret = ioctl (fd, VIDIOC_QBUF, &buf);
}
如果设备支持,也可以使用VIDIOC_EXPBUF导出buffer,得到dmafd。
struct v4l2_exportbuffer expbuf;
memset (&expbuf, 0, sizeof(expbuf));
expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
expbuf.index = i;
ret = ioctl (fd, VIDIOC_EXPBUF, &expbuf);
int dmfd = expbuf.fd;