V4L2 零基础入门(一)——打开摄像头和获取摄像头基本信息

今天工作需要用V4L2获取摄像头的数据,所以稍微了解了一些相关内容。在这里记录一下。

参考了很多优秀的博客,大家如果感兴趣也可以直接参考,链接放在文末。

我也是初学者,有什么问题大家多多交流。

文章目录

      • 测试环境
      • V4L2介绍
    • 采集流程
      • 打开设备
      • 查询摄像头功能
        • VIDIOC_QUERYCAP
        • v4l2_capability
      • 查询设备支持的输出格式
        • ioctl
        • v4l2_fmtdesc
          • pixelformat
          • type
      • 小结
      • 参考

测试环境

  • Ubuntu 2004
  • 两根usb线的摄像头(公司里随便找的,不知品牌)

V4L2介绍

VideoForLinuxTwo(Video4Linux2)简称为V4L2,是linux内核中自带的采集图片,视频,音频的一套api接口。

因为它是linux内核自带的,所以可以在linux上直接使用。主流的视频设备几乎都适配了这套框架,因此我们可以很方便的使用这一套api操作不同的视频设备,免去了学习视频设备驱动的成本(程序员福音!)。

下面我们介绍一下用V4L2采集视频数据的流程。

采集流程

打开设备

将usb摄像头连接到主机上,一般V4L2的设备结点为/dev/videoN,N取值0,1,2等等。如果该设备是当前连接的第一个视频设备会从0开始。

我的摄像头有两根usb线,因此有两个设备,/dev/video0,/dev/video1。

V4L2 零基础入门(一)——打开摄像头和获取摄像头基本信息_第1张图片
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 

这里介绍一些相关知识

VIDIOC_QUERYCAP

所有支持V4L2的设备都支持用VIDIOC_QUERYCAP来查询。当驱动程序与此规范不兼容时,ioctl 返回EINVAL错误代码。

查询到的数据存储在结构体v4l2_capability中。

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

V4L2 零基础入门(一)——打开摄像头和获取摄像头基本信息_第2张图片

像我这个摄像头的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
ioctl
int ioctl(int fd, VIDIOC_ENUM_FMT, struct v4l2_fmtdesc *argp)

查询摄像头相关信息用到的都是ioctl。

函数的第二个参数选择VIDIOC_ENUM_FMT来查询设备支持的输出格式。将查询到的存储信息存储到v4l2_fmtdesc结构体中。

成功时返回0,失败时返回-1。

v4l2_fmtdesc
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

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

type是v4l2_buf_type型的变量,只能取相应的值。

V4L2 零基础入门(一)——打开摄像头和获取摄像头基本信息_第3张图片

一种设备往往支持不止一种输出格式,每种输出格式都对应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手册

你可能感兴趣的:(linux,图像处理,c语言)