linux视频驱动移植Word下载.docx
《linux视频驱动移植Word下载.docx》由会员分享,可在线阅读,更多相关《linux视频驱动移植Word下载.docx(26页珍藏版)》请在冰豆网上搜索。
.vidioc_querycap=vidioc_querycap,
.vidioc_enum_fmt_vid_cap=vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap=vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap
=vidioc_try_fmt_v
.vidioc_s_fmt_vid_cap
=vidioc_s_fmt_vi
.vidioc_reqbufs
=vidioc_reqbufs,
.vidioc_querybuf
=vidioc_querybuf,
.vidioc_qbuf
=vidioc_qbuf,
.vidioc_dqbuf
=vidioc_dqbuf,
.vidioc_s_std
=vidioc_s_std,
.vidioc_enum_input
=vidioc_enum_input,
.vidioc_g_input
=vidioc_g_input,
.vidioc_s_input
=vidioc_s_input,
.vidioc_queryctrl=
vidioc_queryctrl,
.vidioc_g_ctrl
=vidioc_g_ctrl,
.vidioc_s_ctrl=
vidioc_s_ctrl,
.vidioc_streamon
=vidioc_streamon,
.vidioc_streamoff
=vidioc_streamoff,
V4L1_COMPAT
_cap,
#ifdefCONFIG_VIDEO
.vidiocgmbuf=vidiocgmbuf,#endif
3)vivi_template
staticstructvideo_devicevivi_template={
.name="
vivi"
.fops=&
vivi_fops,.ioctl_ops=&
vivi_ioctl_ops,
.minor=-1,
.release=video_device_release,
.tvnorms=V4L2_STD_525_60,.current_norm=V4L2_STD_NTSC_M,
其中函数vivi_xxx和vidioc_xxx都是在vivi.c中实现的。
如果要基于某个硬件来实现V4L2的接口,那这些函数就需要调用硬件的驱动去实现。
4)vivi_dev
structvivi_dev{
structlist_headvivi_devlist;
//内核双向链表,在内核数据结构里有描述
structsemaphorelock;
//信号量,防止竞态访问
intusers;
//用户数量计数
/*variousdeviceinfo*/
unsignedintstructvideo_device
resources;
video_dev;
//这个成员是这个结构的核心,用面向对象的话来说就是
基类
structvivi_dmaqueue
vidq;
//DMA队列
/*Severalcounters*/
inth,m,s,us,jiffies;
//定时器定义
像这样变义的结构在LinuxC中很普遍,这也是利用C来实现面向对象编程的强大方法。
建立这个结构对象之后,所有的操作都是基于这个结构,或者这个结构派生出的来的其它结构。
5)vivi_fh
structvivi_fh{
structvivi_dev*dev;
/*videocapture*/
structvivi_fmt*fmt;
unsignedintwidth,height;
structvideobuf_queuevb_vidq;
enumv4l2_buf_typetype;
这个结构即是vivi_dev结构的更深层次封装,基于那个结构加入了更多的描述信息,如视频制式、视频画面大小、视频缓冲队列等等。
在open的时候,会把这个结构赋给file结构中的private_data域。
在释放设备时注销.其它的像ioctl,mmap,read,write等等都会用到这个结构,其实整个模块的编写的cdev差不多。
只是视频设备的基类是video_device,而字符设备的基类是cdev而已。
2、数据传输方式:
在设备与应用程序之间有三种数据传输方式:
1)read与write这种方式,它像其它设备驱动一样,但是这种方式很慢,对于数据视频流不能满足其要求;
2)直接的内存访问,可以通过其映射方式来传输(IO数据流,交换指向缓冲区指针的
方法);
这是视频设备通常用的方法,采用mmap()的方法,即有内核空间里开辟内存,再在
程序里把这部分的内存映射到程序空间。
如果有设备内存,即直接映射到设备的内核,这种性能更高。
3)异步IO口访问,但是这种方法在V4L2模块中还没有实现。
(重要:
需要确认)
vivi中的mmap是利用第二种方法来实现的,这也是视频设备常用的方法
staticint
vivi_mmap(structfile*file,structvm_area_struct*vma)
{
structvivi_fh*fh=file->
private_data;
intret;
dprintk(1,"
mmapcalled,vma=0x%08lx\n"
(unsignedlong)vma);
ret=videobuf_mmap_mapper(&
fh->
vb_vidq,vma);
dprintk(1,"
vmastart=0x%08lx,size=%ld,ret=%d\n"
(unsignedIong)vma->
vm_start,
vm_end-(unsignedIong)vma->
ret);
returnret;
}
videobuf_mmap_mapper(&
这个核心函数把设备的I/O内存或者设备
内存映射到系统为它开辟的虚拟内存。
3、操控设备的实现:
ioctl
staticintvivi_ioctl(structinode*inode,structfile*file,unsignedintcmd,unsignedlongarg)
returnvideo_usercopy(inode,file,cmd,arg,vivi_do_ioctl);
vivi_do_ioctl这个函数里调用一些命令来设备V4L2模块中的一些结构参数来改变或者
获取设备的参数。
、V4L2的应用
下面简单介绍一下V4L2驱动的应用流程。
1、视频采集的基本流程
一般的,视频采集都有如下流程:
2、打开视频设备
在V4L2中,视频设备被看做一个文件。
使用open函数打开这个设备:
//用非阻塞模式打开摄像头设备
intcameraFd;
cameraFd=open("
/dev/videoO"
O_RDWR|O_NONBLOCK,0);
//如果用阻塞模式打开摄像头设备,上述代码变为:
//cameraFd=open("
O_RDWR,0);
关于阻塞模式和非阻塞模式:
应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)
里的东西返回给应用程序。
3、设定属性及采集方式
打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。
这一步是可选的。
在Linux编程中,一般使用ioctl函数来对设备的I/O通道进行管理:
externintioctl(int__fd,unsignedlongint__request,...)__THROW;
__fd:
设备的ID,例如刚才用open函数打开视频通道后返回的cameraFd;
__request:
具体的命令标志符。
在进行V4L2开发中,一般会用到以下的命令标志符:
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。
这些IO调用,有些是必须的,有些是可选择的。
4、检查当前视频设备支持的标准
在亚洲,一般使用PAL(720X576)制式的摄像头,而欧洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD来检测:
v4l2_std」dstd;
do{
ret=ioctl(fd,VIDIOC_QUERYSTD,&
std);
}while(ret==-1&
&
errno==EAGAIN);
switch(std){
caseV4L2_STD_NTSC:
//……
caseV4L2_STD_PAL:
//
5、设置视频捕获格式
当检测完视频设备支持的标准后,还需要设定视频捕获格式:
structv4l2_formatfmt;
memset(&
fmt,0,sizeof(fmt));
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width=720;
fmt.fmt.pix.height=576;
fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field=V4L2_FIELD_INTERLACED;
if(ioctl(fd,VIDIOC_S_FMT,&
fmt)==-1){return-1;
v4l2_format结构体定义如下:
structv4l2_format
//数据流类型,必须永远是
V4L2_BUF_TYPE_VIDEO_CAPTUREunion
structv4l2_pix_formatpix;
structv4l2_windowwin;
structv4l2_vbi_formatvbi;
__u8raw_data[200];
}fmt;
structv4l2_pix_format
structv4l2_requestbuffersreq;
if(ioctl(fd,VIDIOC_REQBUFS,&
req)==-1){
return-1;
v4l2_requestbuffers定义如下:
structv4l2_requestbuffers
__u32count;
//缓存数量,也就是说在缓存队列里保持多少张照片
〃数据流类型,必须永远是
V4L2_BUF_TYPE_VIDEO_CAPTURE
enumv4l2_memorymemory;
//V4L2_MEMORY_MMAP或
V4L2_MEMORY_USERPTR
__u32reserved[2];
7、获取并记录缓存的物理空间
使用VIDIOC_REQBUFS,我们获取了req.count个缓存,下一步通过调用
VIDIOC_QUERYBUF命令来获取这些缓存的地址,然后使用mmap函数转换成应用程序
中的绝对地址,最后把这段缓存放入缓存队列:
VIOIOC_QUERYBUF
血呂0000000
■
0k10000000
0x80000001
0x10000001
oxa(xxxxx)2
OxlO(XXXX}2
Ox呂(XXXXXJ3
mmap
0x10000003
0x80000004
0x10000004
Ox8(XXXMX)5
0X10000005
内核地址用户地址
typedefstructVideoBuffer{void*start;
size_tlength;
}VideoBuffer;
VideoBuffer*
buffers=calloc(req.count,sizeof(*buffers));
structv4l2_bufferbuf;
for(numBufs=0;
numBufs<
req.count;
numBufs++){memset(&
buf,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=numBufs;
//读取缓存
if(ioctl(fd,VIDIOC_QUERYBUF,&
buf)==-1){return-1;
buffers[numBufs].length=buf.length;
//转换成相对地址buffers[numBufs].start=mmap(NULL,buf.length,
PROT_READ|PROT_WRITE,MAP_SHARED,
fd,buf.m.offset);
if(buffers[numBufs].start==MAP_FAILED){return-1;
//放入缓存队列
if(ioctl(fd,VIDIOC_QBUF,&
}}
8、关于视频采集方式操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。
应用程序可以直接访问内存的地址,而内核空间存放的是供内核访问的代码和数据,用户不能直接访问。
v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地址。
一共有三种视频采集方式:
1)使用read、write方式:
直接使用read和write函数进行读写。
这种方式最简单,但是这种方式会在用户空间和内核空间不断拷贝数据,同时在用户空间和内核空间占用了大量内存,效率不高。
2)内存映射方式(mmap):
把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。
上面的mmap函数就是使用这种方式。
3)用户指针模式:
内存由用户空间的应用程序分配,并把地址传递到内核中的驱动程
序,然后由v4l2驱动程序直接将数据填充到用户空间的内存中。
这点需要在v4l2_requestbuffers里将memory字段设置成V4L2_MEMORY_USERPTR。
第一种方式效率是最低的,后面两种方法都能提高执行的效率,但是对于mmap方式,
文档中有这样一句描述--Rememberthebuffersareallocatedinphysicalmemory,asopposedtovirtualmemorywhichcanbeswappedouttodisk.Applicationsshouldfreethebuffersassoonas
possiblewiththemunmap()function.(使用mmap方法的时候,buffers相当于是在内核空间中分配的,这种情况下,这些buffer是不能被交换到虚拟内存中,虽然这种方法不怎么影响读
写效率,但是它一直占用着内核空间中的内存,当系统的内存有限的时候,如果同时运行有
大量的进程,则对系统的整体性能会有一定的影响。
)
所以,对于三种视频采集方式的选择,推荐的顺序是userptr、mmap、read-write。
当使用mmap或userptr方式的时候,有一个环形缓冲队列的概念,这个队列中,有n个
buffer,驱动程序采集到的视频帧数据,就是存储在每个buffer中。
在每次用VIDIOC_DQBUF取出一个buffer,并且处理完数据后,一定要用VIDIOC_QBUF将这个buffer再次放回到环形缓冲队列中。
环形缓冲队列,也使得这两种视频采集方式的效率高于
直接read/write。
9、处理采集数据
V4L2有一个数据缓存,存放req.count数量的缓存数据。
数据缓存采用FIFO的方式,
当应用程序调用缓存数据时,缓存队列将最先采集到的视频数据缓存送出,并重新采集一
张视频数据。
这个过程需要用到两个ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF:
memset(&
buf,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=0;
〃读取缓存
if(ioctl(cameraFd,VIDIOC_DQBUF,&
buf)==-1)
//视频处理算法
//重新放入缓存队列
if(ioctl(cameraFd,VIDIOC_QBUF,&
buf)==-1){
10、关闭视频设备
使用close函数关闭一个视频设备
close(cameraFd)
三、V4L2的demo
capture.c是官方示例程序。
capture.c程序中的process_image函数:
capture.c程序主要是用来演示怎样使用V4I2接口,并没有对采集到的视频帧数据做任
何实际的处理,仅仅用process_image函数表示了处理图像的代码位置。
process_image函数只有一个参数,就是存储视频帧的内存的地址指针,但是在真正的应用中,通常还需要知道该指针指向的数据的大小。
因此可以修改函数,改成voidprocess_image(constvoid*p,intlen),但是每次调用
process_image的时候,第2个参数该传递什么值?
考虑到程序中对buffer的定义
structbuffer{
void*start;
size_tlength};
如果将buffer.length作为第2个参数传递到修改后的process_image函数中,这样做是不正确的。
process_image需要的第二个参数应该是每帧图像的大小,仔细阅读代码后会发现,buffer.length并不一定就等于图像帧的大小。
(buffer的大小,还需要考虑其他的一些因素,比如内存对齐等)。
capture.c