2.5 Camera Control Reference
控制属性
class="table-box">
2.6 Image Format
图像格式
图像由多种格式YUV和RGB还有压缩格式等等,其中每种格式又分有多种格式,比如RGB:RGB565、RGB888…
所以在使用设备时,需要对格式进行设置
class="table-box">
2.7 Cropping, composing and scaling
图像裁剪、插入与缩放
class="table-box">
2.8 Input/Output
数据的输入和输出
内核中使用缓存队列对图像数据进行管理,用户空间获取图像数据有两种方式,一种是通过read、write方式读取内核空间的缓存,一种是将内核空间的缓存映射到用户空间。在操作v4l2设备时,通过VIDIOC_QUERYCAP 获取设备支持哪种方式
class="table-box">
ioctl API就先介绍到这里,还有非常多的接口这里就不一一介绍了,具体可以查看V4L2 Function Reference
下面来讲一讲如何使用这些接口
三、v4l2设备操作流程
V4L2支持多种接口:capture(捕获)
、output(输出)
、overlay(预览)
等等
这里讲解如何使用capture
功能,下面讲解操作流程
step1:打开设备
在Linux中,视频设备节点为/dev/videox
,使用open函数将其打开
int fd = open ( name, flag) ;
if ( fd < 0 )
{
printf ( "ERR(%s):failed to open %s\n" , __func__ , name) ;
return - 1 ;
}
return fd;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 2:查询设备功能
if ( ioctl ( fd, VIDIOC_QUERYCAP, cap) < 0 )
{
printf ( "ERR(%s):VIDIOC_QUERYCAP failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
看一看v4l2_capability
struct v4l2_capability {
__u8 driver[ 16 ] ;
__u8 card[ 32 ] ;
__u8 bus_info[ 32 ] ;
__u32 version;
__u32 capabilities;
__u32 reserved[ 4 ] ;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
其中最重要的是capabilities
字段,这个字段标记着v4l2设备的功能,capabilities
有以下部分标记位
class="table-box">ID 描述符 V4L2_CAP_VIDEO_CAPTURE 设备支持捕获功能 V4L2_CAP_VIDEO_OUTPUT 设备支持输出功能 V4L2_CAP_VIDEO_OVERLAY 设备支持预览功能 V4L2_CAP_STREAMING 设备支持流读写 V4L2_CAP_READWRITE 设备支持read、write方式读写
我们可以通过这样子去判断设备的功能
if ( cap. capabilities & V4L2_CAP_VIDEO_CAPTURE)
printf ( "v4l2 dev support capture\n" ) ;
if ( cap. capabilities & V4L2_CAP_VIDEO_OUTPUT)
printf ( "v4l2 dev support output\n" ) ;
if ( cap. capabilities & V4L2_CAP_VIDEO_OVERLAY)
printf ( "v4l2 dev support overlay\n" ) ;
if ( cap. capabilities & V4L2_CAP_STREAMING)
printf ( "v4l2 dev support streaming\n" ) ;
if ( cap. capabilities & V4L2_CAP_READWRITE)
printf ( "v4l2 dev support read write\n" ) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 3:设置输入设备
一个设备可能有多个输入,比如:在芯片上,摄像头控制器和摄像头接口是分离的,需要选择哪一个摄像头接口作为摄像头控制器的输入源
当然,并不是所有的设备都需要设置输入,比如:uvc摄像头,一般只有一个输入,默认就会选择,不需要设置
下面介绍如何设置输入设备
1.枚举输入设备
下面这段程序枚举了该设备所有的输入源,并打印输入源的名称
struct v4l2_input input;
input. index = 0 ;
while ( ! ioctl ( fd, VIDIOC_ENUMINPUT, & input) )
{
printf ( "input:%s\n" , input. name) ;
++ input. index;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.设置输入设备
struct v4l2_input input;
input. index = index;
if ( ioctl ( fd, VIDIOC_S_INPUT, & input) < 0 )
{
printf ( "ERR(%s):VIDIOC_S_INPUT failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 4:设置图像格式
有的摄像头支持多种像素格式,有的摄像头只支持一种像素格式,在设置格式之前,要先枚举出所有的格式,看一看是否支持要设置的格式,然后再进一步设置
1.枚举支持的像素格式
struct v4l2_fmtdesc fmtdesc;
fmtdesc. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc. index = 0 ;
while ( ! ioctl ( fd, VIDIOC_ENUM_FMT, & fmtdesc) )
{
printf ( "fmt:%s\n" , fmtdesc. description) ;
fmtdesc. index++ ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.设置像素格式
struct v4l2_format v4l2_fmt;
memset ( & v4l2_fmt, 0 , sizeof ( struct v4l2_format) ) ;
v4l2_fmt. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_fmt. fmt. pix. width = width;
v4l2_fmt. fmt. pix. height = height;
v4l2_fmt. fmt. pix. pixelformat = V4L2_PIX_FMT_YUYV;
v4l2_fmt. fmt. pix. field = V4L2_FIELD_ANY;
if ( ioctl ( fd, VIDIOC_S_FMT, & v4l2_fmt) < 0 )
{
printf ( "ERR(%s):VIDIOC_S_FMT failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 5:设置缓存
v4l2设备读取数据的方式有两种,一种是read方式,一种是streaming方式,具体需要看step 2
的返回结果是支持V4L2_CAP_READWRITE
还是V4L2_CAP_STREAMING
read方式很容易理解,就是通过read函数读取,那么streaming是什么意思呢?
streaming就是在内核空间中维护一个缓存队列
,然后将内存映射
到用户空间,应用读取图像数据就是一个不断地出队列
和入队列
的过程,如下图所示
下面讲解如何去申请和映射缓存
1.申请缓存
struct v4l2_requestbuffers req;
req. count = nr_bufs;
req. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req. memory = V4L2_MEMORY_MMAP;
if ( ioctl ( fd, VIDIOC_REQBUFS, & req) < 0 )
{
printf ( "ERR(%s):VIDIOC_REQBUFS failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.映射缓存
为什么要映射缓存?
因为如果使用read方式读取的话,图像数据是从内核空间拷贝会应用空间,而一副图像的数据一般来讲是比较大的,所以效率会比较低。而如果使用映射的方式,讲内核空间的内存应用到用户空间,那么用户空间读取数据就想在操作内存一样,不需要经过内核空间到用户空间的拷贝,大大提高效率
映射缓存需要先查询缓存信息,然后再使用缓存信息进行映射,下面是一个例子
struct v4l2_buffer v4l2_buffer;
void * addr;
memset ( & v4l2_buffer, 0 , sizeof ( struct v4l2_buffer) ) ;
v4l2_buffer. index = i;
v4l2_buffer. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer. memory = V4L2_MEMORY_MMAP;
ret = ioctl ( fd, VIDIOC_QUERYBUF, & v4l2_buffer) ;
if ( ret < 0 )
{
printf ( "Unable to query buffer.\n" ) ;
return - 1 ;
}
addr = mmap ( NULL ,
v4l2_buffer. length, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, v4l2_buffer. m. offset) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 注 :需要将所有申请的缓存使用上述方法进行映射
3.将所有的缓存放入队列
struct v4l2_buffer v4l2_buffer;
for ( i = 0 ; i < nr_bufs; i++ )
{
memset ( & v4l2_buffer, 0 , sizeof ( struct v4l2_buffer) ) ;
v4l2_buffer. index = i;
v4l2_buffer. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer. memory = V4L2_MEMORY_MMAP;
ret = ioctl ( fd, VIDIOC_QBUF, & v4l2_buffer) ;
if ( ret < 0 )
{
printf ( "Unable to queue buffer.\n" ) ;
return - 1 ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
step 6:打开设备
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if ( ioctl ( fd, VIDIOC_STREAMON, & type) < 0 )
{
printf ( "ERR(%s):VIDIOC_STREAMON failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 7:读取数据
获取图像数据其实就是一个不断地入队列和出队列地过程,在出队列前要调用poll
等待数据准备完成
1.poll
struct pollfd poll_fds[ 1 ] ;
poll_fds[ 0 ] . fd = fd;
poll_fds[ 0 ] . events = POLLIN;
poll ( poll_fds, 1 , 10000 ) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.出队列
struct v4l2_buffer buffer;
buffer. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer. memory = V4L2_MEMORY_MMAP;
if ( ioctl ( fd, VIDIOC_DQBUF, & buffer) < 0 )
{
printf ( "ERR(%s):VIDIOC_DQBUF failed, dropped frame\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
出队列后得到了缓存的下标buffer.index
,然后找到对饮的缓存,通过映射过后的地址进行数据的读取
3.入队列
再数据读取完成后,要将buf重新放入队列中
struct v4l2_buffer v4l2_buf;
v4l2_buf. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf. memory = V4L2_MEMORY_MMAP;
v4l2_buf. index = i;
if ( ioctl ( fd, VIDIOC_QBUF, & v4l2_buf) < 0 )
{
printf ( "ERR(%s):VIDIOC_QBUF failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
读取数据就是在上面这三步一直不断地循环
step 8:关闭设备
1.关闭设备
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if ( ioctl ( fd, VIDIOC_STREAMOFF, & type) < 0 )
{
printf ( "ERR(%s):VIDIOC_STREAMOFF failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.取消映射
for ( i = 0 ; i < nr_bufs; ++ i)
munmap ( buf[ i] . addr, buf[ i] -> length) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
关闭文件描述符
close ( fd) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
libv4l2
v4l2设备操作起来还是比较繁琐的,为此我对其进行了封装,写了一套库,使用起来更加方便,可以从这里libv4l2 获取
其中附带一个实例example_cature ,通过capture /dev/video0
运行程序采集一张YUYV格式的图片,采集后得到了pic.yuv
,可以通过ffplay查看ffplay -pixel_format yuyv422 -f rawvideo -video_size 640x480 pic.yuv
,效果图如下
四、v4l2采集图像在frame buffer显示
如何将采集图像在frame buff上显示?
1.转换图像格式,将yuv格式转换成frame buff可以接收的rgb格式 2.操作frame buff,通过映射frame buff的显存到用户空间,直接写显存就可以显示图像
具体的实现过程这里就不详细说了,下面给出一个例子,点击这里example_video2lcd 获取
执行make编译后可以得到video2lcd,执行video2lcd /dev/video0
运行效果如下
五、v4l2采集图像使用Qt显示
如何使用qt显示,道理跟在frame buff上显示是一样的,都是采集,转化格式,显示,只是在显示部分不同而已,这里给出一个例子,点击这里example_qt 获取
运行效果如下
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146284.html","extend1":"pc","ab":"new"}">>
id="article_content" class="article_content clearfix">
id="content_views" class="markdown_views prism-atom-one-light">
深入学习Linux摄像头系列
深入学习Linux摄像头(一)v4l2应用编程
深入学习Linux摄像头(二)v4l2驱动框架
深入学习Linux摄像头(三)虚拟摄像头驱动分析
深入学习Linux摄像头(四)三星平台fimc驱动详解
深入学习Linux摄像头(一)v4l2应用编程
class="toc">
文章目录
一、什么是v4l2
vl42是video for Linux 2的缩写,是一套Linux内核视频设备的驱动框架,该驱动框架为应用层提供一套统一的操作接口(一系列的ioctl)
V4L2在设计时,是要支持很多广泛的设备的,它们之中只有一部分在本质上是真正的视频设备,可以支持多种设备,它可以有以下几种接口
video capture interface :视频采集接口,这种接口应用于摄像头,v4l2在最初设计的时候就是应用于这种功能
video output interface :视频输出接口,将静止图像或图像序列编码为模拟视频信号,通过此接口,应用程序可以控制编码过程并将图像从用户空间移动到驱动程序
video overlay interface :视频直接传输接口,可以将采集到的视频数据直接传输到显示设备,不需要cpu参与,这种方式的显示图像的效率比其他方式高得多
其他接口这里就不介绍了,下面来看一下v4l2的API
二、v4l2 API介绍
对V4L2设备进行编程包括以下步骤
打开设备 更改设备属性,选择视频和音频输入,视频标准,图片亮度等 设置数据格式 设置输入/输出方法 输入/输出缓存队列循环 关闭设备
其中大多数操作都是通过应用层调用ioctl
实现的,可以将这些ioctl分为下面几类
2.1 Querying Capabilities
查询设备的功能
由于V4L2涵盖了各种各样的设备,因此并非API的所有方面都适用于所有类型的设备,在使用v4l2设备时,必须调用此API,获得设备支持的功能(capture、output、overlay…)
class="table-box">
注 :可以点击名称查看API讲解
2.2 Application Priority
应用优先级
当多个应用程序共享设备时,可能需要为它们分配不同的优先级。视频录制应用程序可以例如阻止其他应用程序改变视频控制或切换当前的电视频道。另一个目标是允许在后台工作的低优先级应用程序,这些应用程序可以被用户控制的应用程序抢占,并在以后自动重新获得对设备的控制
class="table-box">
2.3 Device Inputs and Outputs
输入和输出设备
class="table-box">
2.4 Video Standards
视频标准
class="table-box">
2.5 Camera Control Reference
控制属性
class="table-box">
2.6 Image Format
图像格式
图像由多种格式YUV和RGB还有压缩格式等等,其中每种格式又分有多种格式,比如RGB:RGB565、RGB888…
所以在使用设备时,需要对格式进行设置
class="table-box">
2.7 Cropping, composing and scaling
图像裁剪、插入与缩放
class="table-box">
2.8 Input/Output
数据的输入和输出
内核中使用缓存队列对图像数据进行管理,用户空间获取图像数据有两种方式,一种是通过read、write方式读取内核空间的缓存,一种是将内核空间的缓存映射到用户空间。在操作v4l2设备时,通过VIDIOC_QUERYCAP 获取设备支持哪种方式
class="table-box">
ioctl API就先介绍到这里,还有非常多的接口这里就不一一介绍了,具体可以查看V4L2 Function Reference
下面来讲一讲如何使用这些接口
三、v4l2设备操作流程
V4L2支持多种接口:capture(捕获)
、output(输出)
、overlay(预览)
等等
这里讲解如何使用capture
功能,下面讲解操作流程
step1:打开设备
在Linux中,视频设备节点为/dev/videox
,使用open函数将其打开
int fd = open ( name, flag) ;
if ( fd < 0 )
{
printf ( "ERR(%s):failed to open %s\n" , __func__ , name) ;
return - 1 ;
}
return fd;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 2:查询设备功能
if ( ioctl ( fd, VIDIOC_QUERYCAP, cap) < 0 )
{
printf ( "ERR(%s):VIDIOC_QUERYCAP failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
看一看v4l2_capability
struct v4l2_capability {
__u8 driver[ 16 ] ;
__u8 card[ 32 ] ;
__u8 bus_info[ 32 ] ;
__u32 version;
__u32 capabilities;
__u32 reserved[ 4 ] ;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
其中最重要的是capabilities
字段,这个字段标记着v4l2设备的功能,capabilities
有以下部分标记位
class="table-box">ID 描述符 V4L2_CAP_VIDEO_CAPTURE 设备支持捕获功能 V4L2_CAP_VIDEO_OUTPUT 设备支持输出功能 V4L2_CAP_VIDEO_OVERLAY 设备支持预览功能 V4L2_CAP_STREAMING 设备支持流读写 V4L2_CAP_READWRITE 设备支持read、write方式读写
我们可以通过这样子去判断设备的功能
if ( cap. capabilities & V4L2_CAP_VIDEO_CAPTURE)
printf ( "v4l2 dev support capture\n" ) ;
if ( cap. capabilities & V4L2_CAP_VIDEO_OUTPUT)
printf ( "v4l2 dev support output\n" ) ;
if ( cap. capabilities & V4L2_CAP_VIDEO_OVERLAY)
printf ( "v4l2 dev support overlay\n" ) ;
if ( cap. capabilities & V4L2_CAP_STREAMING)
printf ( "v4l2 dev support streaming\n" ) ;
if ( cap. capabilities & V4L2_CAP_READWRITE)
printf ( "v4l2 dev support read write\n" ) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 3:设置输入设备
一个设备可能有多个输入,比如:在芯片上,摄像头控制器和摄像头接口是分离的,需要选择哪一个摄像头接口作为摄像头控制器的输入源
当然,并不是所有的设备都需要设置输入,比如:uvc摄像头,一般只有一个输入,默认就会选择,不需要设置
下面介绍如何设置输入设备
1.枚举输入设备
下面这段程序枚举了该设备所有的输入源,并打印输入源的名称
struct v4l2_input input;
input. index = 0 ;
while ( ! ioctl ( fd, VIDIOC_ENUMINPUT, & input) )
{
printf ( "input:%s\n" , input. name) ;
++ input. index;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.设置输入设备
struct v4l2_input input;
input. index = index;
if ( ioctl ( fd, VIDIOC_S_INPUT, & input) < 0 )
{
printf ( "ERR(%s):VIDIOC_S_INPUT failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 4:设置图像格式
有的摄像头支持多种像素格式,有的摄像头只支持一种像素格式,在设置格式之前,要先枚举出所有的格式,看一看是否支持要设置的格式,然后再进一步设置
1.枚举支持的像素格式
struct v4l2_fmtdesc fmtdesc;
fmtdesc. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc. index = 0 ;
while ( ! ioctl ( fd, VIDIOC_ENUM_FMT, & fmtdesc) )
{
printf ( "fmt:%s\n" , fmtdesc. description) ;
fmtdesc. index++ ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.设置像素格式
struct v4l2_format v4l2_fmt;
memset ( & v4l2_fmt, 0 , sizeof ( struct v4l2_format) ) ;
v4l2_fmt. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_fmt. fmt. pix. width = width;
v4l2_fmt. fmt. pix. height = height;
v4l2_fmt. fmt. pix. pixelformat = V4L2_PIX_FMT_YUYV;
v4l2_fmt. fmt. pix. field = V4L2_FIELD_ANY;
if ( ioctl ( fd, VIDIOC_S_FMT, & v4l2_fmt) < 0 )
{
printf ( "ERR(%s):VIDIOC_S_FMT failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 5:设置缓存
v4l2设备读取数据的方式有两种,一种是read方式,一种是streaming方式,具体需要看step 2
的返回结果是支持V4L2_CAP_READWRITE
还是V4L2_CAP_STREAMING
read方式很容易理解,就是通过read函数读取,那么streaming是什么意思呢?
streaming就是在内核空间中维护一个缓存队列
,然后将内存映射
到用户空间,应用读取图像数据就是一个不断地出队列
和入队列
的过程,如下图所示
下面讲解如何去申请和映射缓存
1.申请缓存
struct v4l2_requestbuffers req;
req. count = nr_bufs;
req. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req. memory = V4L2_MEMORY_MMAP;
if ( ioctl ( fd, VIDIOC_REQBUFS, & req) < 0 )
{
printf ( "ERR(%s):VIDIOC_REQBUFS failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.映射缓存
为什么要映射缓存?
因为如果使用read方式读取的话,图像数据是从内核空间拷贝会应用空间,而一副图像的数据一般来讲是比较大的,所以效率会比较低。而如果使用映射的方式,讲内核空间的内存应用到用户空间,那么用户空间读取数据就想在操作内存一样,不需要经过内核空间到用户空间的拷贝,大大提高效率
映射缓存需要先查询缓存信息,然后再使用缓存信息进行映射,下面是一个例子
struct v4l2_buffer v4l2_buffer;
void * addr;
memset ( & v4l2_buffer, 0 , sizeof ( struct v4l2_buffer) ) ;
v4l2_buffer. index = i;
v4l2_buffer. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer. memory = V4L2_MEMORY_MMAP;
ret = ioctl ( fd, VIDIOC_QUERYBUF, & v4l2_buffer) ;
if ( ret < 0 )
{
printf ( "Unable to query buffer.\n" ) ;
return - 1 ;
}
addr = mmap ( NULL ,
v4l2_buffer. length, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, v4l2_buffer. m. offset) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 注 :需要将所有申请的缓存使用上述方法进行映射
3.将所有的缓存放入队列
struct v4l2_buffer v4l2_buffer;
for ( i = 0 ; i < nr_bufs; i++ )
{
memset ( & v4l2_buffer, 0 , sizeof ( struct v4l2_buffer) ) ;
v4l2_buffer. index = i;
v4l2_buffer. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer. memory = V4L2_MEMORY_MMAP;
ret = ioctl ( fd, VIDIOC_QBUF, & v4l2_buffer) ;
if ( ret < 0 )
{
printf ( "Unable to queue buffer.\n" ) ;
return - 1 ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
step 6:打开设备
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if ( ioctl ( fd, VIDIOC_STREAMON, & type) < 0 )
{
printf ( "ERR(%s):VIDIOC_STREAMON failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 7:读取数据
获取图像数据其实就是一个不断地入队列和出队列地过程,在出队列前要调用poll
等待数据准备完成
1.poll
struct pollfd poll_fds[ 1 ] ;
poll_fds[ 0 ] . fd = fd;
poll_fds[ 0 ] . events = POLLIN;
poll ( poll_fds, 1 , 10000 ) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.出队列
struct v4l2_buffer buffer;
buffer. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer. memory = V4L2_MEMORY_MMAP;
if ( ioctl ( fd, VIDIOC_DQBUF, & buffer) < 0 )
{
printf ( "ERR(%s):VIDIOC_DQBUF failed, dropped frame\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
出队列后得到了缓存的下标buffer.index
,然后找到对饮的缓存,通过映射过后的地址进行数据的读取
3.入队列
再数据读取完成后,要将buf重新放入队列中
struct v4l2_buffer v4l2_buf;
v4l2_buf. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf. memory = V4L2_MEMORY_MMAP;
v4l2_buf. index = i;
if ( ioctl ( fd, VIDIOC_QBUF, & v4l2_buf) < 0 )
{
printf ( "ERR(%s):VIDIOC_QBUF failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
读取数据就是在上面这三步一直不断地循环
step 8:关闭设备
1.关闭设备
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if ( ioctl ( fd, VIDIOC_STREAMOFF, & type) < 0 )
{
printf ( "ERR(%s):VIDIOC_STREAMOFF failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.取消映射
for ( i = 0 ; i < nr_bufs; ++ i)
munmap ( buf[ i] . addr, buf[ i] -> length) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
关闭文件描述符
close ( fd) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
libv4l2
v4l2设备操作起来还是比较繁琐的,为此我对其进行了封装,写了一套库,使用起来更加方便,可以从这里libv4l2 获取
其中附带一个实例example_cature ,通过capture /dev/video0
运行程序采集一张YUYV格式的图片,采集后得到了pic.yuv
,可以通过ffplay查看ffplay -pixel_format yuyv422 -f rawvideo -video_size 640x480 pic.yuv
,效果图如下
四、v4l2采集图像在frame buffer显示
如何将采集图像在frame buff上显示?
1.转换图像格式,将yuv格式转换成frame buff可以接收的rgb格式 2.操作frame buff,通过映射frame buff的显存到用户空间,直接写显存就可以显示图像
具体的实现过程这里就不详细说了,下面给出一个例子,点击这里example_video2lcd 获取
执行make编译后可以得到video2lcd,执行video2lcd /dev/video0
运行效果如下
五、v4l2采集图像使用Qt显示
如何使用qt显示,道理跟在frame buff上显示是一样的,都是采集,转化格式,显示,只是在显示部分不同而已,这里给出一个例子,点击这里example_qt 获取
运行效果如下
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146284.html","extend1":"pc","ab":"new"}">>
id="article_content" class="article_content clearfix">
id="content_views" class="markdown_views prism-atom-one-light">
深入学习Linux摄像头系列
深入学习Linux摄像头(一)v4l2应用编程
深入学习Linux摄像头(二)v4l2驱动框架
深入学习Linux摄像头(三)虚拟摄像头驱动分析
深入学习Linux摄像头(四)三星平台fimc驱动详解
深入学习Linux摄像头(一)v4l2应用编程
class="toc">
文章目录
一、什么是v4l2
vl42是video for Linux 2的缩写,是一套Linux内核视频设备的驱动框架,该驱动框架为应用层提供一套统一的操作接口(一系列的ioctl)
V4L2在设计时,是要支持很多广泛的设备的,它们之中只有一部分在本质上是真正的视频设备,可以支持多种设备,它可以有以下几种接口
video capture interface :视频采集接口,这种接口应用于摄像头,v4l2在最初设计的时候就是应用于这种功能
video output interface :视频输出接口,将静止图像或图像序列编码为模拟视频信号,通过此接口,应用程序可以控制编码过程并将图像从用户空间移动到驱动程序
video overlay interface :视频直接传输接口,可以将采集到的视频数据直接传输到显示设备,不需要cpu参与,这种方式的显示图像的效率比其他方式高得多
其他接口这里就不介绍了,下面来看一下v4l2的API
二、v4l2 API介绍
对V4L2设备进行编程包括以下步骤
打开设备 更改设备属性,选择视频和音频输入,视频标准,图片亮度等 设置数据格式 设置输入/输出方法 输入/输出缓存队列循环 关闭设备
其中大多数操作都是通过应用层调用ioctl
实现的,可以将这些ioctl分为下面几类
2.1 Querying Capabilities
查询设备的功能
由于V4L2涵盖了各种各样的设备,因此并非API的所有方面都适用于所有类型的设备,在使用v4l2设备时,必须调用此API,获得设备支持的功能(capture、output、overlay…)
class="table-box">
注 :可以点击名称查看API讲解
2.2 Application Priority
应用优先级
当多个应用程序共享设备时,可能需要为它们分配不同的优先级。视频录制应用程序可以例如阻止其他应用程序改变视频控制或切换当前的电视频道。另一个目标是允许在后台工作的低优先级应用程序,这些应用程序可以被用户控制的应用程序抢占,并在以后自动重新获得对设备的控制
class="table-box">
2.3 Device Inputs and Outputs
输入和输出设备
class="table-box">
2.4 Video Standards
视频标准
class="table-box">
2.5 Camera Control Reference
控制属性
class="table-box">
2.6 Image Format
图像格式
图像由多种格式YUV和RGB还有压缩格式等等,其中每种格式又分有多种格式,比如RGB:RGB565、RGB888…
所以在使用设备时,需要对格式进行设置
class="table-box">
2.7 Cropping, composing and scaling
图像裁剪、插入与缩放
class="table-box">
2.8 Input/Output
数据的输入和输出
内核中使用缓存队列对图像数据进行管理,用户空间获取图像数据有两种方式,一种是通过read、write方式读取内核空间的缓存,一种是将内核空间的缓存映射到用户空间。在操作v4l2设备时,通过VIDIOC_QUERYCAP 获取设备支持哪种方式
class="table-box">
ioctl API就先介绍到这里,还有非常多的接口这里就不一一介绍了,具体可以查看V4L2 Function Reference
下面来讲一讲如何使用这些接口
三、v4l2设备操作流程
V4L2支持多种接口:capture(捕获)
、output(输出)
、overlay(预览)
等等
这里讲解如何使用capture
功能,下面讲解操作流程
step1:打开设备
在Linux中,视频设备节点为/dev/videox
,使用open函数将其打开
int fd = open ( name, flag) ;
if ( fd < 0 )
{
printf ( "ERR(%s):failed to open %s\n" , __func__ , name) ;
return - 1 ;
}
return fd;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 2:查询设备功能
if ( ioctl ( fd, VIDIOC_QUERYCAP, cap) < 0 )
{
printf ( "ERR(%s):VIDIOC_QUERYCAP failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
看一看v4l2_capability
struct v4l2_capability {
__u8 driver[ 16 ] ;
__u8 card[ 32 ] ;
__u8 bus_info[ 32 ] ;
__u32 version;
__u32 capabilities;
__u32 reserved[ 4 ] ;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
其中最重要的是capabilities
字段,这个字段标记着v4l2设备的功能,capabilities
有以下部分标记位
class="table-box">ID 描述符 V4L2_CAP_VIDEO_CAPTURE 设备支持捕获功能 V4L2_CAP_VIDEO_OUTPUT 设备支持输出功能 V4L2_CAP_VIDEO_OVERLAY 设备支持预览功能 V4L2_CAP_STREAMING 设备支持流读写 V4L2_CAP_READWRITE 设备支持read、write方式读写
我们可以通过这样子去判断设备的功能
if ( cap. capabilities & V4L2_CAP_VIDEO_CAPTURE)
printf ( "v4l2 dev support capture\n" ) ;
if ( cap. capabilities & V4L2_CAP_VIDEO_OUTPUT)
printf ( "v4l2 dev support output\n" ) ;
if ( cap. capabilities & V4L2_CAP_VIDEO_OVERLAY)
printf ( "v4l2 dev support overlay\n" ) ;
if ( cap. capabilities & V4L2_CAP_STREAMING)
printf ( "v4l2 dev support streaming\n" ) ;
if ( cap. capabilities & V4L2_CAP_READWRITE)
printf ( "v4l2 dev support read write\n" ) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 3:设置输入设备
一个设备可能有多个输入,比如:在芯片上,摄像头控制器和摄像头接口是分离的,需要选择哪一个摄像头接口作为摄像头控制器的输入源
当然,并不是所有的设备都需要设置输入,比如:uvc摄像头,一般只有一个输入,默认就会选择,不需要设置
下面介绍如何设置输入设备
1.枚举输入设备
下面这段程序枚举了该设备所有的输入源,并打印输入源的名称
struct v4l2_input input;
input. index = 0 ;
while ( ! ioctl ( fd, VIDIOC_ENUMINPUT, & input) )
{
printf ( "input:%s\n" , input. name) ;
++ input. index;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.设置输入设备
struct v4l2_input input;
input. index = index;
if ( ioctl ( fd, VIDIOC_S_INPUT, & input) < 0 )
{
printf ( "ERR(%s):VIDIOC_S_INPUT failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 4:设置图像格式
有的摄像头支持多种像素格式,有的摄像头只支持一种像素格式,在设置格式之前,要先枚举出所有的格式,看一看是否支持要设置的格式,然后再进一步设置
1.枚举支持的像素格式
struct v4l2_fmtdesc fmtdesc;
fmtdesc. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc. index = 0 ;
while ( ! ioctl ( fd, VIDIOC_ENUM_FMT, & fmtdesc) )
{
printf ( "fmt:%s\n" , fmtdesc. description) ;
fmtdesc. index++ ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.设置像素格式
struct v4l2_format v4l2_fmt;
memset ( & v4l2_fmt, 0 , sizeof ( struct v4l2_format) ) ;
v4l2_fmt. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_fmt. fmt. pix. width = width;
v4l2_fmt. fmt. pix. height = height;
v4l2_fmt. fmt. pix. pixelformat = V4L2_PIX_FMT_YUYV;
v4l2_fmt. fmt. pix. field = V4L2_FIELD_ANY;
if ( ioctl ( fd, VIDIOC_S_FMT, & v4l2_fmt) < 0 )
{
printf ( "ERR(%s):VIDIOC_S_FMT failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 5:设置缓存
v4l2设备读取数据的方式有两种,一种是read方式,一种是streaming方式,具体需要看step 2
的返回结果是支持V4L2_CAP_READWRITE
还是V4L2_CAP_STREAMING
read方式很容易理解,就是通过read函数读取,那么streaming是什么意思呢?
streaming就是在内核空间中维护一个缓存队列
,然后将内存映射
到用户空间,应用读取图像数据就是一个不断地出队列
和入队列
的过程,如下图所示
下面讲解如何去申请和映射缓存
1.申请缓存
struct v4l2_requestbuffers req;
req. count = nr_bufs;
req. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req. memory = V4L2_MEMORY_MMAP;
if ( ioctl ( fd, VIDIOC_REQBUFS, & req) < 0 )
{
printf ( "ERR(%s):VIDIOC_REQBUFS failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.映射缓存
为什么要映射缓存?
因为如果使用read方式读取的话,图像数据是从内核空间拷贝会应用空间,而一副图像的数据一般来讲是比较大的,所以效率会比较低。而如果使用映射的方式,讲内核空间的内存应用到用户空间,那么用户空间读取数据就想在操作内存一样,不需要经过内核空间到用户空间的拷贝,大大提高效率
映射缓存需要先查询缓存信息,然后再使用缓存信息进行映射,下面是一个例子
struct v4l2_buffer v4l2_buffer;
void * addr;
memset ( & v4l2_buffer, 0 , sizeof ( struct v4l2_buffer) ) ;
v4l2_buffer. index = i;
v4l2_buffer. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer. memory = V4L2_MEMORY_MMAP;
ret = ioctl ( fd, VIDIOC_QUERYBUF, & v4l2_buffer) ;
if ( ret < 0 )
{
printf ( "Unable to query buffer.\n" ) ;
return - 1 ;
}
addr = mmap ( NULL ,
v4l2_buffer. length, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, v4l2_buffer. m. offset) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 注 :需要将所有申请的缓存使用上述方法进行映射
3.将所有的缓存放入队列
struct v4l2_buffer v4l2_buffer;
for ( i = 0 ; i < nr_bufs; i++ )
{
memset ( & v4l2_buffer, 0 , sizeof ( struct v4l2_buffer) ) ;
v4l2_buffer. index = i;
v4l2_buffer. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer. memory = V4L2_MEMORY_MMAP;
ret = ioctl ( fd, VIDIOC_QBUF, & v4l2_buffer) ;
if ( ret < 0 )
{
printf ( "Unable to queue buffer.\n" ) ;
return - 1 ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
step 6:打开设备
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if ( ioctl ( fd, VIDIOC_STREAMON, & type) < 0 )
{
printf ( "ERR(%s):VIDIOC_STREAMON failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
step 7:读取数据
获取图像数据其实就是一个不断地入队列和出队列地过程,在出队列前要调用poll
等待数据准备完成
1.poll
struct pollfd poll_fds[ 1 ] ;
poll_fds[ 0 ] . fd = fd;
poll_fds[ 0 ] . events = POLLIN;
poll ( poll_fds, 1 , 10000 ) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.出队列
struct v4l2_buffer buffer;
buffer. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer. memory = V4L2_MEMORY_MMAP;
if ( ioctl ( fd, VIDIOC_DQBUF, & buffer) < 0 )
{
printf ( "ERR(%s):VIDIOC_DQBUF failed, dropped frame\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
出队列后得到了缓存的下标buffer.index
,然后找到对饮的缓存,通过映射过后的地址进行数据的读取
3.入队列
再数据读取完成后,要将buf重新放入队列中
struct v4l2_buffer v4l2_buf;
v4l2_buf. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf. memory = V4L2_MEMORY_MMAP;
v4l2_buf. index = i;
if ( ioctl ( fd, VIDIOC_QBUF, & v4l2_buf) < 0 )
{
printf ( "ERR(%s):VIDIOC_QBUF failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
读取数据就是在上面这三步一直不断地循环
step 8:关闭设备
1.关闭设备
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if ( ioctl ( fd, VIDIOC_STREAMOFF, & type) < 0 )
{
printf ( "ERR(%s):VIDIOC_STREAMOFF failed\n" , __func__ ) ;
return - 1 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.取消映射
for ( i = 0 ; i < nr_bufs; ++ i)
munmap ( buf[ i] . addr, buf[ i] -> length) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
关闭文件描述符
close ( fd) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
libv4l2
v4l2设备操作起来还是比较繁琐的,为此我对其进行了封装,写了一套库,使用起来更加方便,可以从这里libv4l2 获取
其中附带一个实例example_cature ,通过capture /dev/video0
运行程序采集一张YUYV格式的图片,采集后得到了pic.yuv
,可以通过ffplay查看ffplay -pixel_format yuyv422 -f rawvideo -video_size 640x480 pic.yuv
,效果图如下
四、v4l2采集图像在frame buffer显示
如何将采集图像在frame buff上显示?
1.转换图像格式,将yuv格式转换成frame buff可以接收的rgb格式 2.操作frame buff,通过映射frame buff的显存到用户空间,直接写显存就可以显示图像
具体的实现过程这里就不详细说了,下面给出一个例子,点击这里example_video2lcd 获取
执行make编译后可以得到video2lcd,执行video2lcd /dev/video0
运行效果如下
五、v4l2采集图像使用Qt显示
如何使用qt显示,道理跟在frame buff上显示是一样的,都是采集,转化格式,显示,只是在显示部分不同而已,这里给出一个例子,点击这里example_qt 获取
运行效果如下
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146284.html","extend1":"pc","ab":"new"}">>
评论记录:
回复评论: