最近想做一个视频推流拉流的小项目,需要使用V4L2驱动框架,先来学习学习
Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。
在Linux下,所有外设都被看成一种特殊的文件,成为“设备文件”,可以象访问普通文件一样对其进行读写。一般来说,采用V4L2驱动的摄像头设备文件是/dev/video0。V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。V4L2在include/linux/videodev2.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。
一、工作流程
1、打开设备
int fd = open(“/dev/video0”, O_RDWR);
2、取得设备的属性信息,查看设备具有什么功能,比如是否具有视频输入,或者音频输入输出等
struct v4l2_capability VIDIOC_QUERYCAP
3、选择视频输入,一个视频设备可以有多个视频输入
struct v4l2_input VIDIOC_S_INPUT
4、设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式包括宽度和高度等
VIDIOC_S_STD, VIDIOC_S_FMT
struct v4l2_std_id struct v4l2_format
5、申请帧缓冲区
VIDIOC_REQBUFS
struct v4l2_requestbuffers
6、将申请到的内核空间帧缓冲区映射到用户空间
VIDIOC_QUERYBUF
struct v4l2_buffer
7、将申请到的缓冲区进入视频采集输入队列
VIDIOC_QBUF
struct v4l2_buffer
8、开始视频采集
VIDIOC_STREAMON
enum v4l2_buf_type
9、取出视频输出队列的帧缓冲数据,对数据处理
VIDIOC_DQBUF
struct v4l2_buffer
10、将帧缓冲区重新进入视频采集输入队列,这样便可循环采集帧数据
VIDIOC_QBUF
11、停止视频采集,解除映射
VIDIOC_STREAMOFF
12、关闭视频设备
close(fd)
二、V4L2API与数据结构
1、相关结构体
struct v4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFS
struct v4l2_capability //视频设备的功能,对应命令VIDIOC_QUERYCAP
struct v4l2_input //视频输入信息,对应命令VIDIOC_ENUMINPUT
struct v4l2_standard //视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
struct v4l2_format //帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
struct v4l2_buffer //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
struct v4l2_crop //视频信号矩形边框
v4l2_std_id //视频制式
2、相关IOCTL接口命令
VIDIOC_REQBUFS //分配内存
VIDIOC_QUERYBUF //把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
VIDIOC_QUERYCAP //查询驱动功能
VIDIOC_ENUM_FMT //获取当前驱动支持的视频格式
VIDIOC_S_FMT //设置当前驱动的频捕获格式
VIDIOC_G_FMT //读取当前驱动的频捕获格式
VIDIOC_TRY_FMT //验证当前驱动的显示格式
VIDIOC_CROPCAP //查询驱动的修剪能力
VIDIOC_S_CROP //设置视频信号的矩形边框
VIDIOC_G_CROP //读取视频信号的矩形边框
VIDIOC_QBUF //把数据从缓存中读取出来
VIDIOC_DQBUF //把数据放回缓存队列
VIDIOC_STREAMON //开始视频显示函数
VIDIOC_STREAMOFF //结束视频显示函数
VIDIOC_QUERYSTD //检查当前视频设备支持的标准,例如PAL或NTSC。
三、V4L2编程
一、对摄像头的相关配置
1. 设备的打开和关闭:
int fd = open(“/dev/video0”, O_RDWR);
close(fd);
2、查询设备属性:VIDIOC_QUERYCAP
相关函数
int ioctl(int fd, int request, struct v4l2_capability *argp);
相关结构体
structv4l2_capability
{
__u8 driver[16]; // 驱动名字
__u8 card[32]; // 设备名字
__u8bus_info[32]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32capabilities; // 设备支持的操作
__u32reserved[4]; // 保留字段
};
capabilities 常用值:
V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取
capabilities域是一个位掩码用来描述驱动能做的不同的事情:
/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE 0x00000010 /* Is a raw VBI capture device */
#define V4L2_CAP_VBI_OUTPUT 0x00000020 /* Is a raw VBI output device */
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 /* Is a sliced VBI capture device */
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 /* Is a sliced VBI output device */
#define V4L2_CAP_RDS_CAPTURE 0x00000100 /* RDS data capture */
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 /* Can do video output overlay */
#define V4L2_CAP_HW_FREQ_SEEK 0x00000400 /* Can do hardware frequency seek */
#define V4L2_CAP_TUNER 0x00010000 /* has a tuner */
#define V4L2_CAP_AUDIO 0x00020000 /* has audio support */
#define V4L2_CAP_RADIO 0x00040000 /* is a radio device */
#define V4L2_CAP_READWRITE 0x01000000 /* read/write systemcalls */
#define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */
#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */
查看设备信息
ret = ioctl(fd, VIDIOC_QUERYCAP, &argp);
if(ret == -1){
perror("ioctl");
exit(EXIT_FAILURE);
}
printf("driver_name:%s\ncard :%s\nbus_info:%s\nversion:%u\n",
argp.driver, argp.card, argp.bus_info, argp.version);
3、选择视频输入
相关结构体
struct v4l2_input {
__u32 index; /* Which input */
__u8 name[32]; /* Label */
__u32 type; /* Type of input */
__u32 audioset; /* Associated audios (bitfield) */
__u32 tuner; /* enum v4l2_tuner_type */
v4l2_std_id std;
__u32 status;
__u32 capabilities;
__u32 reserved[3];
};
index: 应用关注的输入的索引号; 这是惟一一个用户空间设定的字段。驱动要分配索引号给输入,从0开始,依次往上增加。应用想要知道所有可用的输入时,要调用VIDIOC_ENUMINPUT 控制,调用索引号从0开始,并开始递增。 一旦返回EINVAL,应用就知道,输入己经遍历结束了,只要有输入,输入索引号0就一定要存在的。
name[32]: /* 输入的名字,由驱动设定 */
type: 输入的类型 目前有两个值可选:V4L2_INPUT_TYPE_TUNER 和 V4L2_INPUT_TYPE_CAMERA
audioset: 描述那个音频输入可以与此视频输入相关联,音频输入与视频输入一样通过索引号枚举,但并非所有的音频和视频的组合都是可用的,这个字段是一个掩码,代表对于当前枚举出的视频而言,那些音频输入是可以与之关联的,如果没有音频输入可以与之关联,或者只有一个可选,那么就可以简单地把这个字段置为0
tuner: 如果输入是一个调谐器(type字段置为V4L2_INPUT_TYPE_TUNER),这个字段就是会包含一个相应的调谐设备的索引号
std: 描述设备支持哪个或哪些视频标准
status: 给出输入的状态,完整的标识符集可以再V4L2的文档中找到;简而言之,status中设置的每一位都代表一个问题,这些问题包括没有电源,没有信号,没有同频锁 等等
capabilities 设备支持的操作
选择视频输入 相关配置
struct v4l2_input input;
memset(&input, 0, sizeof(struct v4l2_input));
input.index = 0;
ret = ioctl(fd, VIDIOC_S_INPUT, &input);
if(ret == -1){
printf("VIDIOC_S_INPUT is error! LINE:%d\n",__LINE__);
exit(EXIT_FAILURE);
}
4、设置与查看帧格式
1、查看硬件所支持的格式
相关结构体
struct v4l2_fmtdesc
{
__u32 index; // 要查询的格式序号,应用程序设置
enum v4l2_buf_type type; // 帧类型,应用程序设置
__u32 flags; // 是否为压缩格式
__u8 description[32]; // 格式名称
__u32 pixelformat; // 格式
__u32 reserved[4]; // 保留
};
enum v4l2_buf_type {
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
V4L2_BUF_TYPE_VBI_CAPTURE = 4,
V4L2_BUF_TYPE_VBI_OUTPUT = 5,
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,
V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10,
V4L2_BUF_TYPE_SDR_CAPTURE = 11,
V4L2_BUF_TYPE_SDR_OUTPUT = 12,
V4L2_BUF_TYPE_META_CAPTURE = 13,
V4L2_BUF_TYPE_META_OUTPUT = 14,
/* Deprecated, do not use */
V4L2_BUF_TYPE_PRIVATE = 0x80,
};
VIDIOC_ENUM_FMT 命令枚举出所有支持的格式
在存储器中表示图像有很多种方法,市场几乎找不到可以处理所有V4l2所理解的视频格式的设备。驱动不应支持底层硬件不懂的视频格式。实际上在内核中进行格式转换是令人难以接受的。所以驱动必须可以应用选择一个硬件可以支持的格式。第一步就是简单的允许应用查询硬件所支持的格式。VIDIC_EMUM_FMT 就是为此目的而提供的。在驱动内部这个调用会转换为这样一个回调函数
int (vidioc_enum_fmt_cap)(struct file file, void private_data, struct v4l2_fmtdesc f);
这个回调函数要求视频捕捉设备描述其支持的格。应用会传递一个v4l2_fmdesc结构体
应用会设置index和type字段。index是用来区别格式的一个整形数;与V4l2所使用的其他索引一样,这个也是从0开始递增至最大允许值为止,应用可以通过一直递增索引值index直到返回EINVAL的方式枚举所有支持的格式。
type字段描述的是数据流类型;对于视频捕捉设备来说(摄像头)
就是V4L2_BUF_TYPE_VIDEO_CAPTURE
上述的回掉函数只作用于视频捕获设备;只有当type 字段的是值是 V4L2_BUF_TYPE_VIDEO_CAPTURE时才会调用。
查看当前摄像头支持的格式
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("fm:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1){
printf("%d.%s %c%c%c%c\n", fmtdesc.index + 1, fmtdesc.description,
fmtdesc.pixelformat & 0xFF,
(fmtdesc.pixelformat >> 8) & 0xFF,
(fmtdesc.pixelformat >> 16) & 0xFF,
(fmtdesc.pixelformat >> 24) & 0xFF);
fmtdesc.index++;
}
关于pixelformat的值 >>8 >> 16 >> 24 的原因
在头文件#include<linux/videodev2.h>中有定义
#define v4l2_fourcc(a, b, c, d)
((__u32)(a) | ((__u32)(b) << 8) | ((__u32)© << 16) | ((__u32)(d) << 24))
例如YUYV
#define V4L2_PIX_FMT_YUYV v4l2_fourcc(‘Y’, ‘U’, ‘Y’, ‘V’) /* 16 YUV 4:2:2
例如OV5640所支持的格式
2、查看或设置当前硬件的图像配置
应用可以通过调用VIDIOC_G_FMT知道硬件现在的配置如何。这种情况下传递的参数是一个v4l2_format 结构体: