v4l2协议,v4l2编程
先看读/写。如果VIDIOC_QUERYCAP调用返回的v4l2_capability参数中V4L2_CAP_READWRITE设置为true,则表示支持读/写I/O。这是最简单最原始的方法。它需要复制数据(而不是像内存映射那样只交换指针),不交换元数据(比如帧计数器和时间戳,可以用来识别丢帧和同步帧)。虽然这是最原始的方法,但由于其简单性,对于简单的应用程序(如捕捉静止图像)非常有用。
如果使用读/写方法,则必须同时支持另外两个函数,select()和poll()。这两个功能用于多路复用I/O。
流式传输有两种方式,驱动程序对这两种方式的支持通过使用VIDIOC_REQBUFS来确定:
int ioctl(int fd,int request,struct v4 L2 _ request buffers * argp);
关于存储器映射模式,存储器映射缓冲器通过VIDIOC_REQBUFS应用于设备存储器,并且必须在映射进入应用虚拟地址空间之前应用。至于用户指针,用户缓冲区是应用自己创建的,驱动只是通过VIDIOC_REQBUFS转换成User pointers的I/O模式。这两种方法都不会复制数据,只是缓冲区指针的交互。
我们先来看看数据结构v4l2_requestbuffers:
__u32计数
//要申请的缓冲区数量。仅当内存设置为V4L2 _内存_MMAP时,才会设置该参数
枚举v4l2_buf_type类型
枚举v4l2_memory内存
//v4l 2 _ MEMORY _ MMAP或V4L2_MEMORY_USERPTR
对于内存映射模式,要申请设备内存下的缓冲区,应用程序必须初始化上述三个参数。驱动程序返回的缓冲区数量可能等于count,或者小于或大于count。少于那个可能是内存不足,多于那个可能是驱动为了更好的完成相应的功能而增加的缓冲。如果驱动程序不支持内存映射,调用这个ioctl将返回EINVAL。
因为内存映射模式分配的是真实的物理内存,而不是虚拟内存,所以使用后必须使用munmap()来释放。
应用程序可以再次调用VIDICO_REQBUFS来改变缓冲区的数量,但前提是必须先释放映射的缓冲区。您可以先执行munmap,然后将参数count设置为0,以释放所有缓冲区。
对于用户指针I/O,应用只需要设置上述类型和内存类型。
申请buffer之后,内存映射之前,首先要用VIDIOC_QUERYBUF获取分配的缓冲区信息,然后传递给函数mmap()进行映射:
int ioctl(int fd,int request,struct v4 L2 _ buffer * argp);
VIDIOC_QUERYBUF是一种用于这种内存映射模式的方法。没有必要在用户指针模式下使用该功能。在调用之前,应用需要在v4l2_buffer中设置两个参数,一个是缓冲区类型,一个是索引号(有效值从0到应用的缓冲区数减1)。调用此ioctl会将相应缓冲区中的标志:V4L2 _ BUF _ flag _ mapped、V4L2 _ BUF _ flag _ queued和V4L2_BUF_FLAG_DONE设置为有效。让我们仔细看看数据结构v4l2_buffer:
__u32索引
//应用程序来设置,只是为了声明哪个缓冲区
枚举v4l2_buf_type类型
__u32字节已使用
//如果是输入流,//缓冲区中已经使用的字节数由驱动程序设置,否则由应用程序设置。
__u32标志
//定义了buffer的一些标志位来表示这个buffer在哪个队列,比如输入队列还是输出队列(v4l 2 _ BUF _ flag _ queued v4l 2 _ BUF _ flag _ done),是否是关键帧等。详情请参考规格。
枚举v4l2_memory内存
//v4 L2 _内存_ MMAP/v4 L2 _内存_用户ptr/v4 L2 _内存_覆盖
联盟m
__u32偏移量
//当内存类型为v4l 2 _ me memory _ MMAP时,主要用于表示设备内存中缓冲区相对起始位置的偏移量,主要用于MMAP()参数中,对应用没有影响。
无符号long userptr
//当内存类型为V4L2_MEMORY_USERPTR时,这是指向虚拟内存中buffer的指针,由应用程序设置。
__u32长度
//缓冲区的大小
驱动程序中管理两个缓冲队列,一个输入队列和一个输出队列。对于捕获设备,当输入队列中的缓冲区被数据填满时,它将自动成为输出队列。在等待调用VIDIOC_DQBUF处理数据后,再次调用VIDIOC_QBUF将缓冲区放回输入队列。对于输出设备,缓冲区在显示后自动成为输出队列。
所有刚初始化的映射缓冲区最初都处于出队状态,由驱动程序管理,应用程序无法访问。对于capture应用程序,首先通过VIDIOC_QBUF将所有映射的缓冲区添加到队列中,然后通过VIDIOC_STREAMON启动capture,应用程序进入read循环,在这里应用程序将等待直到一个缓冲区被填满,可以从队列中出列,然后在数据用完时入队到输入队列中;对于输出应用程序,首先应用程序用数据填充缓冲区,然后将它排入队列。当足够多的缓冲区进入队列时,它调用VIDIOC_STREAMON输出数据。
有两种方法可以阻止应用程序的执行,直到缓冲区可以出队。默认情况下,当调用VIDIOC_DQBUF时,它将被阻塞,直到传出队列中有数据。但是,如果在打开设备文件时使用O_NONBLOCK,那么在调用VIDIOC_DQBUF且没有数据读取时,会立即返回。另一种方法是调用select和poll来监视文件描述符是否可读。
两个ioctl,VIDIOC_STREAMON和VIDIOC_STREAMOFF用于开始和停止捕获或输出,VIDIOC_STREAMOFF删除输入和输出队列中的所有缓冲区。
所以如果drvier要实现内存映射I/O,就必须支持vidioc _ reqbufs,vidioc _ querybuf,vidioc _ qbuf,vidioc _ streamon和vidioc _ streamoff ioctl,mmap(),munmap(),select()和poll()函数。
用户指针是一种I/O方法,结合了读/写和内存映射的优点。缓冲区由应用程序本身应用,可以在虚拟内存或共享内存中。在捕获和输出方面,它与内存映射方法基本相同。这里只提到它申请内存的方式。
在用户指针模式下,申请的内存也是以内存页大小为单位对齐的,对buffersize也有一定的限制。这就是示例代码中计算缓冲区大小的方式。暂时不知道这样分配缓冲区大小的依据是什么。就像这样简单地使用它:
page _ size=get pagesize();
buffer _ size=(buffer _ size page _ size-1)~(page _ size1);
缓冲区[n_buffers]。start=memalign ( page_size,
buffer _ size);
3、开始_捕获
经过以上一系列的数据协商和缓冲区分配,可以调用VIDIOC_QBUF将所有缓冲区添加到输入队列中,调用VIDIOC_STREAM0N开始捕获数据:
int ioctl(int fd,int request,struct v4 L2 _ buffer * argp);
//VIDIOC_QBUF VIDIOC_DQBUF
int ioctl(int fd,int request,const int * argp);
//vidi oc _ stream 0 vidi oc _ stream off(int参数为缓冲区类型)
4、主循环
开始捕获数据后,您将进入一个主循环。您可以使用select或poll来监控文件描述符的状态。一旦数据可读,您将调用函数来读取数据。
5、读_帧
读取数据因I/O模式而异:
读/写模式直接从文件描述符中读取一帧大小的数据;
在内存映射模式下,首先将一个缓冲区从输出队列中出队,然后处理帧数据,并在处理完成后将其放入输入队列。
在用户指针模式下,首先从输出队列中取出一个缓冲区,然后判断该缓冲区是否是应用程序开始申请的缓冲区,然后处理该缓冲区,最后放入输入队列。
6、停止捕获/取消激活设备/关闭设备
最后,捕获、释放资源并关闭设备。
下面是一个示例代码:
#包含stdio.h
#包含stdlib.h
#包含字符串. h
# include fcntl . h/*低级
输入/输出*/
#包括unistd.h
#包含错误号h
#包含malloc.h
#包含系统/统计信息
#包含sys/types.h
#包含系统/时间. h
#include sys/mman.h
#包含sys/ioctl.h
#包含linux/videodev2.h
#定义设备/开发/视频
静态结构v4l2 _ requestbuffers请求
结构缓冲区
{
无效*开始
unsignedintlength
};
静态结构缓冲区*缓冲区;
静态结构v4l2 _ buffer缓冲区
usb_camera.c
#包含" head.h "
intmain()
{
intfd
FD=open _ device();
获取设备信息(FD);
get _ frame _ fmt(FD);
获取当前帧信息(FD);
try _ format _ support(FD);
设置_帧_格式(FD);
apply _ memory _ buf(FD);
内存映射(FD);
缓冲区入队(FD);
关闭(FD);
返回0;
}
intopen _ device()
{
intfd
if(-1==(fd=open(DEVICE,O_RDWR)))
printf(信息:无法打开视频设备\ n’);
其他
printf(信息:打开设备:%d\n ,FD);
返回FD;
}
获取设备信息(intfd)
{
结构v4l2 _能力上限
if(-1==ioctl(fd,VIDIOC_QUERYCAP,CAP))
printf( info:vidi oc _ query cap ERROR \ n );
其他
printf(信息:驱动程序名称:%s.卡名:%s.总线信息:%s.驱动程序版本:%u.%u.%u\n ,
cap.driver,cap.card,cap.bus_info,(第16版)0XFF,(第8版)0XFF,cap .版本0x ff);
返回1;
}
intget_frame_fmt(intfd)
{
结构v4l2 _ fmtdesc fmtdesc
fmtdesc。索引=0;
fmtdesc。TYPE=v4 L2 _缓冲_类型_视频_捕获;
printf( info:Support format:);
while(ioctl(fd,VIDIOC_ENUM_FMT,fmtdesc)!=-1)
{
printf(\t%d.%s ,fmtdesc.index 1,fmtdesc。描述);
fmtdesc.index
}
printf( \ n );
返回1;
}
int获取当前帧信息(intfd)
{
结构v4l2 _格式fmt
fmt。TYPE=v4 L2 _缓冲_类型_视频_捕获;
ioctl(fd,VIDIOC_G_FMT,fmt);
printf(信息:当前数据格式信息:\ n \ t %d\n \高度:% d \ n ,fmt.fmt.pix.width,fmt。fmt。pix。身高);
结构v4l2 _ fmtdesc fmtdesc
fmtdesc。索引=0;
fmtdesc。TYPE=v4 L2 _缓冲_类型_视频_捕获;
while(ioctl(fd,VIDIOC_ENUM_FMT,fmtdesc)!=-1)
{
如果(fmt desc。像素格式fmt。fmt。pix。像素格式)
{
printf( \ t格式:%s\n ,fmt desc。描述);
打破;
}
fmtdesc.index
}
返回1;
}
inttry_format_support(intfd)
{
结构v4l2 _格式fmt
fmt。TYPE=v4 L2 _缓冲_类型_视频_捕获;
//fmt。fmt。PIX。像素格式=v4 L2 _ PIX _ FMT _ RGB 32;
fmt。fmt。PIX。像素格式=v4 L2 _ PIX _ FMT _ YUYV;
if(ioctl(fd,VIDIOC_TRY_FMT,fmt)==-1)
if(errno==EINVAL)
printf(信息:不支持格式RGB32!\ n’);
返回1;
}
整数帧格式
{
结构v4l2 _格式fmt
fmt。TYPE=v4 L2 _缓冲_类型_视频_捕获;
fmt。fmt。pix。宽度=640;
fmt。fmt。pix。身高=480;
fmt。fmt。PIX。像素格式=v4 L2 _ PIX _ FMT _ YUYV;
fmt。fmt。pix。FIELD=v4 L2 _场_交错;
if(ioctl(fd,VIDIOC_S_FMT,fmt)==-1)
if(errno==EINVAL)
printf(信息:设置帧格式错误!\ n’);
返回1;
}
intapply_memory_buf(intfd)
{
//结构v4 L2请求缓冲区请求;
请求。计数=4;
请求。TYPE=v4 L2 _缓冲_类型_视频_捕获;
请求。内存=v4l 2 _内存_ MMAP;
if(-1==ioctl(fd,VIDIOC_REQBUFS,req))
printf(info:VIDIOC_REQBUFS失败\ n’);
其他
printf(info:VIDIOC_REQBUFS成功\ n’);
返回1;
}
intmemory_mapping(intfd)
{
unsignedintn _ buffers
缓冲区=(结构缓冲区*)calloc(请求。计数,sizeof(结构
缓冲));
如果(!缓冲区){
fprintf(stderr,内存不足\ n’);
退出(退出_失败);
}
//映射
for(n _ buffers=0;n _缓冲器请求计数;n_buffers){
//结构v4 L2 _缓冲区buf
memset( buf,0,sizeof(buf));
BUF。TYPE=v4 L2 _缓冲_类型_视频_捕获;
buf。内存=v4l 2 _内存_ MMAP;
buf.index=n _ buffers
//查询序号为n _缓冲器的缓冲区,得到其起始物理地址和大小
if(-1==ioctl(fd,VIDIOC_QUERYBUF,BUF))
退出(-1);
缓冲区[n_buffers].长度=缓冲长度
//映射内存
缓冲区[n_buffers].start=mmap(空,缓冲长度,PROT _读PROT写,MAP_SHARED,fd,buf。m .偏移);
if(MAP _ FAILED==buffers[n _ buffers]).开始)
退出(-1);
}
printf(信息:内存映射成功\n ).
返回1;
}
intbuffer_enqueue(intfd)
{
未签名的
枚举v4l2_buf_type类型;
//将缓冲帧放入队列
for(I=0;I 4;我)
{
结构v4l 2 _ buf buf
BUF。TYPE=v4 L2 _缓冲_类型_视频_捕获;
buf。内存=v4l 2 _内存_ MMAP;
buf。index=I;
if(-1==ioctl(fd,VIDIOC_QBUF,BUF))
printf(缓冲区排队失败\ n’);
}
TYPE=v4l 2 _ BUF _ TYPE _ VIDEO _ CAPTURE;
//打开流
if(-1==ioctl(fd,VIDIOC_STREAMON,type))
printf(信息:打开流失败\ n’);
其他
printf(信息:打开流成功\ n’);
返回1;
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。