Linux下V4l2框架编程_USB摄像头数据采集

举报
DS小龙哥 发表于 2022/10/22 22:08:45 2022/10/22
【摘要】 v4L2是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等。 这篇文章介绍V4L2框架读取摄像头数据的流程,介绍ioctl常用的命令参数,以及各种摄像头相关的结构体成员含义,最终完成数据采集。

Linux内核版本:3.5.0

1.1 V4L2简介

v4L2是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等。 这篇文章介绍V4L2框架读取摄像头数据的流程,介绍ioctl常用的命令参数,以及各种摄像头相关的结构体成员含义,最终完成数据采集。

编程模式如下:

V4l2支持多种设备,它可以有以下几种接口:

1. 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的。

2. 视频输出接口(video output interface):可以驱动计算机的外围视频图像设备--像可以输出电视信号格式的设备。

3. 直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU。

4. 视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号。

5. 收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流


1.2 V4L2驱动分析

1.2.1 v4l2驱动接口

video4linux2(V4L2)是Linux内核中关于视频设备的内核驱动,它为Linux中视频设备访问提供了通用接口,在Linux系统中,V4L2驱动的Video设备节点路径通常/dev/中的videoX。

V4L2驱动对用户空间提供字符设备,主设备号为81,对于视频设备,其次设备号为0-63。除此之外,次设备号为64-127的Radio收音机设备,次设备号为192-223的是Teletext广播设备,次设备号为224-255的是VBI视频消影设备。

V4L2驱动的Video设备在用户空间通过各种ioctl调用进行控制,并且可以使用mmap进行内存映射。

Linux系统中视频输入设备主要包括以下四个部分:

1)​ 字符设备驱动程序核心:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;

2)​ V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;

3)​ 平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev。

4)​ 具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。

1.2.2 v4l2核心源码分布路径

V4l2源码位于/drivers/media/video路径下。

源码以实现的功能可以划分为四类:

1)​ 核心模块实现:由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数;

2)​ V4L2框架:由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c等文件实现,构建V4L2框架;

3)​ Videobuf管理:由videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。

4)​ Ioctl框架:由v4l2-ioctl.c文件实现,构建V4L2ioctl的框架。


1.2.3 v4l2入口与出口函数

static int __init videodev_init(void)
{
/*1.1 合成设备号,主设备号81*/
dev_t dev = MKDEV(VIDEO_MAJOR, 0);
int ret;
printk(KERN_INFO "Linux video capture interface: v2.00\n");

/*1.2 注册一个范围的设备数量,连续注册256个设备*/
ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
if (ret < 0) {
printk(KERN_WARNING "videodev: unable to get major %d\n",
VIDEO_MAJOR);
return ret;
}

/*1.3 注册类*/
ret = class_register(&video_class);
if (ret < 0) {
unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
printk(KERN_WARNING "video_dev: class_register failed\n");
return -EIO;
}

return 0;
}







static void __exit videodev_exit(void)

{

dev_t dev = MKDEV(VIDEO_MAJOR, 0);

/*1.1 注销类*/

class_unregister(&video_class);

/*1.2 注销指定范围内的设备*/

unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);

}


1.3 V4L2核心数据结构与ioctl命令分析

1.3.1 struct v4l2_capability描述V4L2设备信息

struct v4l2_capability {

__u8 driver[16]; //存放驱动程序模块的名称

__u8 card[32]; //生产商名称

__u8 bus_info[32]; //总线的名称,设备在系统中的位置

__u32 version; //版本

__u32 capabilities; //保存摄像头支持的功能--

__u32 device_caps; //设备支持的设备节点操作

__u32 reserved[3];//保留未来扩展的领域

};

Capabilities成员支持的值:

#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* 是一个视频捕捉设备*/

#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* 是一个视频输出设备 */

#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* 可以做视频叠加 */

#define V4L2_CAP_VBI_CAPTURE 0x00000010 /* 是原始VBI捕获设备 */




#define V4L2_CAP_READWRITE 0x01000000 /* read/write 支持读写操作 */

#define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */

#define V4L2_CAP_STREAMING 0x04000000 /* 流媒体方式,mmap内存映射方式*/


1.3.2 struct v4l2_fmtdesc保存摄像头属性信息

struct v4l2_fmtdesc {

__u32 index; /*索引值*/

__u32 type; /*填写枚举类型 enum v4l2_buf_type */

__u32 flags;

__u8 description[32]; /*产品描述*/

__u32 pixelformat; /*摄像头捕获的图像编码格式 */

__u32 reserved[4]; /*保留字段*/

};

枚举类型:

enum v4l2_buf_type {

//BUF类型是视频捕获

V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, //视频捕获设备

V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, //视频输出设备


1.3.3 struct v4l2_format数据流格式

struct v4l2_format {

__u32 type; /*数据流的格式类型: enum v4l2_buf_type*/

union {

struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE 视频捕获设备使用*/

struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */

struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */

struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */

struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */

__u8 raw_data[200]; /* user-defined */

} fmt;

};

1.3.4 struct v4l2_pix_format设置视频捕获参数

struct v4l2_pix_format {

__u32 width; //宽度

__u32 height; //高度

__u32 pixelformat; //像素格式

__u32 field; /* enum v4l2_field */

__u32 bytesperline; /* for padding, zero if unused */

__u32 sizeimage; //大小

__u32 colorspace; /* enum v4l2_colorspace */

__u32 priv; /* private data, depends on pixelformat */

};

1.3.5 struct v4l2_requestbuffers 缓冲区请求参数

struct v4l2_requestbuffers {

__u32 count; /*记录个数--BUFF空间*/

__u32 type; /* enum v4l2_buf_type */

__u32 memory; /* enum v4l2_memory */

__u32 reserved[2]; /*保留*/

};

1.3.6 视频缓冲区结构

struct v4l2_buffer {

__u32 index; /*缓冲区的索引编号*/

__u32 type;

__u32 bytesused; /*缓存的字节大小*/

__u32 flags;

__u32 field;

struct timeval timestamp;

struct v4l2_timecode timecode;

__u32 sequence;




/* memory location */

__u32 memory;

union {

__u32 offset;

unsigned long userptr;

struct v4l2_plane *planes;

int fd;

} m;

__u32 length; /*缓冲区的长度*/

__u32 input;

__u32 reserved;

};

1.3.7 常见的ioctl命令

 VIDIOC_QUERYCAP /* 获取设备支持的操作 */

VIDIOC_G_FMT /* 获取设置支持的视频格式 */

VIDIOC_S_FMT /* 设置捕获视频的格式 */

VIDIOC_REQBUFS /* 向驱动提出申请内存的请求 */

VIDIOC_QUERYBUF /* 向驱动查询申请到的内存 */

VIDIOC_QBUF /* 将空闲的内存加入可捕获视频的队列 */

VIDIOC_DQBUF /* 将已经捕获好视频的内存拉出已捕获视频的队列 */

VIDIOC_STREAMON /* 打开视频流 */

VIDIOC_STREAMOFF /* 关闭视频流 */

VIDIOC_QUERYCTRL /* 查询驱动是否支持该命令 */

VIDIOC_G_CTRL /* 获取当前命令值 */

VIDIOC_S_CTRL /* 设置新的命令值 */

VIDIOC_G_TUNER /* 获取调谐器信息 */

VIDIOC_S_TUNER /* 设置调谐器信息 */

VIDIOC_G_FREQUENCY /* 获取调谐器频率 */

VIDIOC_S_FREQUENCY /* 设置调谐器频率 */ 

1.4 V4L2编程步骤

1.4.1 打开视频设备节点

iFd = open("/dev/video15",O_RDWR);


1.4.2 查询设备的支持类型

error = ioctl(iFd,VIDIOC_QUERYCAP,&tV4L2Cap);
if(error)
{
printf("没有视频设备\n");
return -1;
}

/* 2.1、检测是否是视频CAPTURE设备 */
if (!(tV4L2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
printf("非视频设备\n");
return -1;
}

/* 2.2、支持哪种接口:mmap read/write */
if (tV4L2Cap.capabilities & V4L2_CAP_STREAMING)
{
printf("支持流媒体i/o\n");
}


if (tV4L2Cap.capabilities & V4L2_CAP_READWRITE)
{
printf("支持读写i/o\n");
}


1.4.3 查询设备支持的编码格式

 /* 2.3、VIDIOC_ENUM_FMT 查询支持哪种格式 */
memset(&tV4L2FmtDesc, 0, sizeof(tV4L2FmtDesc));
tV4L2FmtDesc.index = 0; //索引值置0
tV4L2FmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //视频捕获模式

while ((error = ioctl(iFd, VIDIOC_ENUM_FMT, &tV4L2FmtDesc)) == 0)
{
printf("检查次数: %d\n",++cnt);
if (!isSpFmt(tV4L2FmtDesc.pixelformat))
{
printf("Support :%d\n",tV4L2FmtDesc.pixelformat);
break;
}
tV4L2FmtDesc.index++; //移动到下一个格式
} 


1.4.4 配置设备的图像格式

 /* 4、 VIDIOC_S_FMT 设置摄像头使用哪种格式 */
memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //设置数据流类型为视频捕获
tV4l2Fmt.fmt.pix.pixelformat = tV4L2FmtDesc.pixelformat; //设置视频图像的格式
printf("期望支持的格式 :%d\n",tV4l2Fmt.fmt.pix.pixelformat);
tV4l2Fmt.fmt.pix.width = 800; //期望输出的宽度
tV4l2Fmt.fmt.pix.height = 480; //期望输出的高度
tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY; //由驱动程序进行选择


/* 如果驱动程序发现无法设置某些参数(比如分辨率),
* 它会自动调整这些参数, 并且返回给应用程序
*/

error = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);
if (error)
{
printf("Unable to set format\n");
return -1;
}

/*打印出摄像头实际支持的信息*/
printf("Support Format 支持的图像编码格式:%d\n",tV4l2Fmt.fmt.pix.pixelformat);
printf("Support width 输出的图像宽度:%d\n",tV4l2Fmt.fmt.pix.width);
printf("Support height 输出的图像高度:%d\n",tV4l2Fmt.fmt.pix.height); 

1.4.5 设置摄像头采集的帧率

/*设置摄像头采集的帧率*/
struct v4l2_streamparm parm;
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm.parm.capture.timeperframe.numerator = 1;
parm.parm.capture.timeperframe.denominator = 30;
parm.parm.capture.capturemode = 0;
if(ioctl(uvc_video_fd, VIDIOC_S_PARM, &parm) == -1)
{
perror("VIDIOC_S_PARM failed\n");
}

printf("摄像头支持的帧率:%d\n",parm.parm.capture.timeperframe.denominator);

1.4.6 申请buff空间

 /* 5、VIDIOC_REQBUFS 申请buffer */
memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));

/* 人为的想要分配4个buffer:实际上由VIDIOC_REQBUFS获取到的信息来决定 */
tV4l2ReqBuffs.count = 4; //期望值分配4个缓冲空间
tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /*期望输出的类型为视频捕获类型*/
/* 表示申请的缓冲是支持MMAP */
tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP; /*期望设备支持mmap内存映射*/

/* 为分配buffer做准备,如果设置的参数不合理,驱动程序会自动调整合理的参数并且返回。 */
error = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
if (error)
{
printf("Unable to allocate buffers.\n");
return -1;
} 

1.4.7 缓冲区分配映射地址

 /* 判断是否支持mmap 内存映射*/
if (tV4L2Cap.capabilities & V4L2_CAP_STREAMING)
{
/* map the buffers ,根据缓冲区的数量,申请空间*/
for (i = 0; i < tV4l2ReqBuffs.count; i++)
{
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i; //缓冲区的索引编号
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//捕获的类型
tV4l2Buf.memory = V4L2_MEMORY_MMAP; //mmap方式


/* 6、VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap */
error = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
if (error)
{
printf("Unable to query buffer.\n");
return -1;
}
printf("length = %d\n",tV4l2Buf.length); /*缓冲区的长度*/
pucVideBuf[i] = mmap(0 /* start anywhere 由系统自动决定地址*/ ,
tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,
tV4l2Buf.m.offset); /*映射的起始位置*/
if (pucVideBuf[i] == MAP_FAILED) /*判断是否申请失败*/
{
printf("Unable to map buffer\n");
return -1;
}
printf("mmap %d addr:%p\n",i,pucVideBuf[i]);
}
}


 

1.4.8 将缓冲区放入队列

 /* 7、VIDIOC_QBUF 放入队列 */
for (i = 0; i <tV4l2ReqBuffs.count; i++)
{
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i;
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
error = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);
if (error)
{
printf("Unable to queue buffer.\n");
return -1;
}
} 

1.4.9 开始捕获数据

 /* 8、启动摄像头开始读数据 */
error = ioctl(iFd, VIDIOC_STREAMON, &iType);
if (error)
{
printf("Unable to start capture.\n");
return -1;
} 

1.4.10 开启线程循环读取数据

void *camera_pthread(void * arg)
{
int error;
int cnt=0;
int i=0;
int ListNum;
struct pollfd fds[1];
/* 8.1、使用poll来等待是否有数据 */
fds[0].fd = iFd;
fds[0].events = POLLIN;

/* YUV格式的数据<------>在LCD上显示:rgb888 */
initLut(); //初始化格式
ptVideoBufOut.aucPixelDatas =NULL; /*存放摄像头的地址*/


while(1)
{
error = poll(fds, 1, -1); /*等待数据*/

memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));

tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

tV4l2Buf.memory = V4L2_MEMORY_MMAP;

/* 9、VIDIOC_DQBUF 从队列中取出 */

error = ioctl(iFd, VIDIOC_DQBUF, &tV4l2Buf);

ListNum = tV4l2Buf.index; /*得到数据缓冲区的索引编号*/

ptVideoBufIn.aucPixelDatas = pucVideBuf[ListNum]; /*根据索引值得到对应的地址*/


/*转换数据格式,显示在开发板的LCD上*/

/* 显示转化的数据:已经转化为24bpp了 */

Yuv2RgbConvert(5,90,&ptVideoBufIn,&ptVideoBufOut);

memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));

tV4l2Buf.index = ListNum; /*报告这个缓冲区已经读取过了*/

tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

tV4l2Buf.memory = V4L2_MEMORY_MMAP;

error = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf); /*将缓冲区放入队列,准备采集*/

}
}

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。