今天工作需要用V4L2获取摄像头的数据,所以稍微了解了一些相关内容。在这里记录一下。
参考了很多优秀的博客,大家如果感兴趣也可以直接参考,链接放在文末。
我也是初学者,有什么问题大家多多交流。
VideoForLinuxTwo(Video4Linux2)简称为V4L2,是linux内核中自带的采集图片,视频,音频的一套api接口。
因为它是linux内核自带的,所以可以在linux上直接使用。主流的视频设备几乎都适配了这套框架,因此我们可以很方便的使用这一套api操作不同的视频设备,免去了学习视频设备驱动的成本(程序员福音!)。
下面我们介绍一下用V4L2采集视频数据的流程。
将usb摄像头连接到主机上,一般V4L2的设备结点为/dev/videoN,N取值0,1,2等等。如果该设备是当前连接的第一个视频设备会从0开始。
我的摄像头有两根usb线,因此有两个设备,/dev/video0,/dev/video1。
Linux中万物即文件,因此我们需要打开对应的设备文件才能进行接下来的操作。
//打开设备
int fd = open("/dev/video0", O_RDWR);
方法一:
//存储设备信息的结构体 v4l2_capability
struct v4l2_capability cap = {0};
//用ioctl查询设备支持的功能
//int ioctl(int fd, int request, struct v4l2_capability *argp);
//fd:打开设备返回的文件句柄
//request:选择VIDIOC_QUERYCAP:是来查询视频设备是否支持V4L2规范的宏
ioctl(fd,VIDIOC_QUERYCAP, &cap);
//输出设备信息
printf("cap.driver = %s \n",cap.driver);
printf("cap.card = %s \n",cap.card);
printf("cap.bus_info = %s \n",cap.bus_info);
printf("cap.version = %d \n",cap.version);
printf("cap.capabilities = %x \n",cap.capabilities);
printf("cap.device_caps = %x \n",cap.device_caps);
printf("cap.reserved = %x \n",cap.reserved);
输出如下:
cap.driver = uvcvideo
cap.card = Yitu USB Camera RGB: Yitu USB C
cap.bus_info = usb-0000:00:14.0-2
cap.version = 328896
cap.capabilities = 84a00001
cap.device_caps = 4200001
cap.reserved = 6016507c
这里介绍一些相关知识
所有支持V4L2的设备都支持用VIDIOC_QUERYCAP来查询。当驱动程序与此规范不兼容时,ioctl 返回EINVAL错误代码。
查询到的数据存储在结构体v4l2_capability中。
struct v4l2_capability
{
__u8 driver[16]; //驱动名,通常是uvcvideo
__u8 card[32]; // Device名,厂商会写
__u8 bus_info[32]; //在Bus系统中存放位置
__u32 version; //driver 版本
__u32 capabilities; //能力集
__u32 device_caps;
__u32 reserved[4];
};
其中最有价值的是capabilities字段,这个字段的值是若干个宏或的结果(应该是,我找到的博客都一笔带过没有详细解释这些 ? )。
这个集合里有一系列标识设备功能的宏,这些宏都在linux/videodev2.h
像我这个摄像头的capabilities就是
0x84a00001 = 0x80000000 | 0x04000000 | 0x00800000 | 0x00200000 | 0x00000001
对比上面的宏列表可知摄像头支持的功能为
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* 支持视频捕获 */
#define V4L2_CAP_EXT_PIX_FORMAT 0x00200000 /* 支持扩展像素格式 */
#define V4L2_CAP_META_CAPTURE 0x00800000 /* 支持元数据捕获 */
#define V4L2_CAP_STREAMING 0x04000000 /* 支持流式I/O ioctl功能 */
#define V4L2_CAP_DEVICE_CAPS 0x80000000 /* 设备支持capabilities字段 只有有这个flag,device_caps字段才会被设置*/
当然按照我这种一个一个算太麻烦了,我们可以用更简单的方法来检查设备是否有我们需要的功能
/* 检查设备是否支持视频捕获 */
if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
{
printf("the video device support capture\n");
}
让capabilities与我们想要查询的对应功能的宏进行与运算,如果结果不为0说明支持此功能。
方法二:
v4l2-ctl -D
输出
Driver Info:
Driver name : uvcvideo
Card type : USB 2.0 Camera: ZL Camera
Bus info : usb-0000:00:14.0-10.1.1
Driver version : 5.4.44
Capabilities : 0x84a00001
Video Capture
Metadata Capture
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04200001
Video Capture
Streaming
Extended Pix Format
这些信息就是我们通过ioctl(fd,VIDIOC_QUERYCAP, &cap)查询到的信息,Capabilities会自动解析出支持的功能,是不是比第一种方法方便很多!
方法一:
//存储输出格式的结构体 v4l2_fmtdesc
struct v4l2_fmtdesc fmt;
//从第一个输出格式开始查询
fmt.index = 0;
//查询照片的输出格式,所以type选择V4L2_BUF_TYPE_VIDEO_CAPTURE
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//输出当前设备的输出格式
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) != -1)
{
printf("pixelformat = %c%c%c%c, description = %s \n",
fmt.pixelformat & 0xFF, (fmt.pixelformat >> 8) & 0xFF, (fmt.pixelformat >> 16) & 0xFF,
(fmt.pixelformat >> 24) & 0xFF, fmt.description);
fmt.index ++;
}
我的输出是
pixelformat = MJPG, description = Motion-JPEG
pixelformat = YUYV, description = YUYV 4:2:2
int ioctl(int fd, VIDIOC_ENUM_FMT, struct v4l2_fmtdesc *argp)
查询摄像头相关信息用到的都是ioctl。
函数的第二个参数选择VIDIOC_ENUM_FMT来查询设备支持的输出格式。将查询到的存储信息存储到v4l2_fmtdesc结构体中。
成功时返回0,失败时返回-1。
struct v4l2_fmtdesc {
__u32 index; /* 格式编号 */
__u32 type; /* 数据流的类型,v4l2_buf_type类型 */
__u32 flags;
__u8 description[32]; /* 描述*/
__u32 pixelformat; /* 图像格式标识符,这是一个由 v4l2_fourcc() 宏计算的四字符代码*/
__u32 reserved[4];
};
真正的输出格式在pixelformat里,一般也会把description打出来方便阅读。
pixelformat是一个由 v4l2_fourcc() 宏计算的四字符代码,像我的设备支持的输出格式在头文件里是这样被定义的。
#define v4l2_fourcc(a, b, c, d)\
((__u32)(a) | ((__u32)(b) << 8) | ((__u32)(c) << 16) | ((__u32)(d) << 24))
//测试设备的输出格式
#define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M', 'J', 'P', 'G') /* Motion-JPEG */
可以看到v4l2_fourcc的处理流程,因此我们逆向处理一下,就可以还原原本的字符。
printf("pixelformat = %c%c%c%c, description = %s \n",
fmt.pixelformat & 0xFF, (fmt.pixelformat >> 8) & 0xFF, (fmt.pixelformat >> 16) & 0xFF,
(fmt.pixelformat >> 24) & 0xFF, fmt.description);
type是v4l2_buf_type型的变量,只能取相应的值。
一种设备往往支持不止一种输出格式,每种输出格式都对应v4l2_fmtdesc的一个index,因此在index自增的情况下循环查询对应的pixelformat和description来获取全部的输出格式。
在ioctl查询没有失败(返回 -1)的情况下就继续查询,直到查询到所有的输出格式。
方法二:
v4l2-ctl --list-formats -d /dev/video0
输出
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture
[0]: 'MJPG' (Motion-JPEG, compressed)
[1]: 'YUYV' (YUYV 4:2:2)
查询出来视频设备支持两种输出格式,这种方式也比第一种简洁很多。
没想到不知不觉竟然有差不多4000字了,其实这么一点才只是开头而已。不想让一篇文章太多字,打算分成几篇文章来写。
我在网上看了很多相关的博客,V4L2的大体流程大家都写的很清楚,可是参数为什么这么设置,为什么在这么多的参数中选择这一个等等细节的问题却几乎没什么人讲,所以我打算自己写一篇,是学习也是分享给大家。
V4L2 API详解 <二> Camera详细设置【转】
Linux内核 API手册