V4L2开发要点【转】
转自:https://blog.csdn.net/mr_raptor/article/details/7441141
首先来看 Read/Write ,如果 VIDIOC_QUERYCAP 调用返回的 v4l2_capability 参数中, V4L2_CAP_READWRITE 被设置成真了的话,就说明支持 Read/Write I/O 。这是最简单最原始的方法,它需要进行数据的拷贝 ( 而不是像memory map 那样只需要进行指针的交换 ) ,而且不会交换元数据 ( 比如说帧计数器和时间戳之类的可用于识别帧丢失和进行帧同步 ) ,虽然它是最原始的方法,但因为其简单,所以对于简单的应用程序比如只需要 capture静态图像是很有用的 。
如果使用 Read/Write 方法支持的话,必须同时支持另外两个函数 select() 和 poll() ,这两个函数用来进行 I/0 的多路复用。
对于 streaming 它有两种方式, driver 对两种方式的支持要使用 VIDIOC_REQBUFS 来确定:
int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);
对于 memory mapped 方式, Memory mapped buffers 是通过 VIDIOC_REQBUFS 在 device memory 中申请的,而且必须在 map 进应用程序虚拟地址空间之前就申请好。而对于 User pointers , User buffers 是在应用程序自己开辟的,只是通过 VIDIOC_REQBUFS 将驱动转化到 user pointer 的 I/O 模式下。这两种方式都不会拷贝数据,而只是 buffer 指针的交互。
首先来看一下 v4l2_requestbuffers 这个数据结构:
__u32 count
// 要申请的 buffer 的数量,只有当 memory 被设置成 V4L2_MEMORY_MMAP 的时候才会设置这个参数
enum v4l2_buf_type type
enum v4l2_memory memory
// 要么是 V4L2_MEMORY_MMAP ,要么是 V4L2_MEMORY_USERPTR
对于 memory mapped 模式,要在 device memory 下申请 buffer ,应用程序必须初始化上面的 3 个参数,驱动最后返回的 buffer 的个数可能等于 count ,也可能少于或者多于 count ,少于可能是因为内存不足,多于则可能是驱动为更好地完成相应功能增加的 buffer 。如果 driver 不支持 memory mapped 调用这个 ioctl 就会返回 EINVAL。
因为 memory map 模式下分配的是实实在在的物理内存,不是虚拟内存,所以使用完以后一定要使用 munmap()释放。
应用程序可以重新调用 VIDICO_REQBUFS 来改变 buffer 的个数,但前提是必须先释放已经 mapped 的 buffer ,可以先 munmap ,然后设置参数 count 为 0 来释放所有的 buffer 。
对于 User pointer I/O ,应用程序只需设置上面的 type 和 memory 类型就可以了。
申请好 buffer 后在进行 memory mapped 之前,首先要使用 VIDIOC_QUERYBUF 来获得分配的 buffer 信息,以传给函数 mmap() 来进行 map :
int ioctl(int fd, int request, struct v4l2_buffer *argp);
VIDIOC_QUERYBUF 是 memory mapped 这种模式下使用的方法,在 User pointer 模式下不需要使用这个函数,在调用之前应用程序需要设定 v4l2_buffer 中的两个参数,一个是 buffer 类型,另外一个是 index number( 有效值从0 到申请的 buffer 数目减 1) ,调用这个 ioctl 会将相应 buffer 中的 flag : V4L2_BUF_FLAG_MAPPED, V4L2_BUF_FLAG_QUEUED 和 V4L2_BUF_FLAG_DONE 设置为有效。下面我们来仔细看看 v4l2_buffer 这个数据结构:
__u32 index
// 应用程序来设定,仅仅用来申明是哪个 buffer
enum v4l2_buf_type type
__u32 bytesused
//buffer 中已经使用的 byte 数,如果是 input stream 由 driver 来设定,相反则由应用程序来设定
__u32 flags
// 定义了 buffer 的一些标志位,来表明这个 buffer 处在哪个队列,比如输入队列或者输出队列(V4L2_BUF_FLAG_QUEUED V4L2_BUF_FLAG_DONE) ,是否关键帧等等,具体可以参照 spec
enum v4l2_memory memory
//V4L2_MEOMORY_MMAP / V4L2_MEMORY_USERPTR / V4L2_MEMORY_OVERLAY
union m
__u32 offset
// 当 memory 类型是 V4L2_MEOMORY_MMAP 的时候,主要用来表明 buffer 在 device momory 中相对起始位置的偏移,主要用在 mmap() 参数中,对应用程序没有左右
unsigned long userptr
// 当 memory 类型是 V4L2_MEMORY_USERPTR 的时候,这是一个指向虚拟内存中 buffer 的指针,由应用程序来设定。
__u32 length
//buffer 的 size
在 driver 内部管理着两个 buffer queues ,一个输入队列,一个输出队列。对于 capture device 来说,当输入队列中的 buffer 被塞满数据以后会自动变为输出队列,等待调用 VIDIOC_DQBUF 将数据进行处理以后重新调用VIDIOC_QBUF 将 buffer 重新放进输入队列;对于 output device 来说 buffer 被显示以后自动变为输出队列。
刚初始化的所有 map 过的 buffer 开始都处于 dequeced 的状态,由 driver 来管理对应用程序是不可访问的。对于 capture 应用程序来说,首先是通过 VIDIOC_QBUF 将所有 map 过的 buffer 加入队列,然后通过VIDIOC_STREAMON 开始 capture ,并进入 read loop ,在这里应用程序会等待直到有一个 buffer 被填满可以从队列中 dequeued ,当数据使用完后再 enqueue 进输入队列;对于 output 应用程序来说,首先应用程序会buffer 装满数据然后 enqueued ,当足够的 buffer 进入队列以后就调用 VIDIOC_STREAMON 将数据输出。
有两种方法来阻塞应用程序的执行,直到有 buffer 能被 dequeued ,默认的是当调用 VIDIOC_DQBUF 的时候会被阻塞,直到有数据在 outgoing queue ,但是如果打开设备文件的时候使用了 O_NONBLOCK ,则当调用VIDIOC_DQBUF 而又没有数据可读的时候就会立即返回。另外一种方法是调用 select 和 poll 来对文件描述符进行监听是否有数据可读。
VIDIOC_STREAMON 和 VIDIOC_STREAMOFF 两个 ioctl 用来开始和停止 capturing 或者 output ,而且VIDIOC_STREAMOFF 会删除输入和输出队列中的所有 buffer 。
因此 drvier 如果要实现 memory mapping I/O 必须支持 VIDIOC_REQBUFS, VIDIOC_QUERYBUF, VIDIOC_QBUF, VIDIOC_DQBUF, VIDIOC_STREAMON 和 VIDIOC_STREAMOFF ioctl, the mmap(), munmap(), select() 和 poll() 函数。
User Pointers 是一种综合了 Read/Write 和 memory mappded 优势的 I/O 方法, buffer 是由应用程序自己申请的,可以是在虚拟内存或者共享内存中。在 capture 和 output 方面基本来说和 memory mapped 方式是相同的,在这里只提一下它申请内存的方式。
User pointer 方式下,申请的内存也 memory page size 为单位对齐,而且 buffersize 也有一定限制,例示代码中是这样计算 buffer size 的,暂时还不知道这样分配 buffer size 的依据是什么,先简单地这样用就好了:
page_size = getpagesize ();
buffer_size = (buffer_size + page_size - 1) & ~(page_size – 1);
buffers[n_buffers].start = memalign ( page_size,
buffer_size);
3 、 start_capturing
经过上面的一系列的数据协商已经 buffer 的分配以后就可以调用 VIDIOC_QBUF 将 buffer 全部加入输入队列中,并调用 VIDIOC_STREAM0N 开始捕获数据了:
int ioctl(int fd, int request, struct v4l2_buffer *argp);
//VIDIOC_QBUF VIDIOC_DQBUF
int ioctl(int fd, int request, const int *argp);
//VIDIOC_STREAM0N VIDIOC_STREAMOFF ( int 参数是 buffer 类型)
4 、 mainloop
开始捕获数据以后就会进入一个主循环,可以使用 select 或者 poll 来监听文件描述符的状态,一旦有数据可读,就调用函数来读取数据。
5 、 read_frame
读取数据根据 I/O 方式的不同而不同:
Read/Write 方式直接从文件描述符中读一个帧大小的数据;
Memory mapped 方式下先从输出队列中 dequeued 一个 buffer ,然后对帧数据进行处理,处理完成以后再放入输入队列。
User pointer 方式下也是首先从输出队列中 dequeued 一个 buffer ,然后对这个 buffer 进行判断,看是否是应用程序开始申请的 buffer ,然后再对这个 buffer 进行处理,最后放入输入队列。
6 、 stop_capturing / uninit_device / close device
最后就是捕捉以及资源释放并关闭 device
下面给出一个示例代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <fcntl.h> /* low-level i/o */
- #include <unistd.h>
- #include <errno.h>
- #include <malloc.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <sys/time.h>
- #include <sys/mman.h>
- #include <sys/ioctl.h>
- #include <linux/videodev2.h>
- #define DEVICE "/dev/video"
- static struct v4l2_requestbuffers req;
- struct buffer
- {
- void* start;
- unsigned int length;
- };
- static struct buffer *buffers;
- static struct v4l2_buffer buf;
usb_camera.c
- #include "head.h"
- int main()
- {
- int fd;
- fd=open_device();
- get_device_info(fd);
- get_frame_fmt(fd);
- get_current_frame_info(fd);
- try_format_support(fd);
- set_frame_format(fd);
- apply_memory_buf(fd);
- memory_mapping(fd);
- buffer_enqueue(fd);
- close(fd);
- return 0;
- }
- int open_device()
- {
- int fd;
- if(-1==(fd=open(DEVICE,O_RDWR)))
- printf("info:Can't open video device\n");
- else
- printf("info:Open the device :%d\n",fd);
- return fd;
- }
- int get_device_info(int fd)
- {
- struct v4l2_capability cap;
- if(-1==ioctl(fd,VIDIOC_QUERYCAP,&cap))
- printf("info:VIDIOC_QUERYCAP ERROR\n");
- else
- printf("info:Driver Name:%s....Card Name:%s....Bus info:%s....Driver Version:%u.%u.%u\n",
- cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF,(cap.version>>8)&0XFF,cap.version&0XFF);
- return 1;
- }
- int get_frame_fmt(int fd)
- {
- struct v4l2_fmtdesc fmtdesc;
- fmtdesc.index=0;
- fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- printf("info:Support format:");
- while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
- {
- printf("\t%d.%s",fmtdesc.index+1,fmtdesc.description);
- fmtdesc.index++;
- }
- printf("\n");
- return 1;
- }
- int get_current_frame_info(int fd)
- {
- struct v4l2_format fmt;
- fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ioctl(fd,VIDIOC_G_FMT,&fmt);
- printf("info:Current data format information:\n\twidth:%d\n\theight:%d\n",fmt.fmt.pix.width,fmt.fmt.pix.height);
- struct v4l2_fmtdesc fmtdesc;
- fmtdesc.index=0;
- fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
- {
- if(fmtdesc.pixelformat & fmt.fmt.pix.pixelformat)
- {
- printf("\tformat:%s\n",fmtdesc.description);
- break;
- }
- fmtdesc.index++;
- }
- return 1;
- }
- int try_format_support(int fd)
- {
- struct v4l2_format fmt;
- fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- //fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB32;
- fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;
- if(ioctl(fd,VIDIOC_TRY_FMT,&fmt)==-1)
- if(errno==EINVAL)
- printf("info:not support format RGB32!\n");
- return 1;
- }
- int set_frame_format(int fd)
- {
- struct v4l2_format fmt;
- fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- fmt.fmt.pix.width=640;
- fmt.fmt.pix.height=480;
- fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
- fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
- if(ioctl(fd,VIDIOC_S_FMT,&fmt)==-1)
- if(errno==EINVAL)
- printf("info:set frame format error!\n");
- return 1;
- }
- int apply_memory_buf(int fd)
- {
- //struct v4l2_requestbuffers req;
- req.count=4;
- req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- req.memory=V4L2_MEMORY_MMAP;
- if(-1==ioctl(fd,VIDIOC_REQBUFS,&req))
- printf("info:VIDIOC_REQBUFS FAILED\n");
- else
- printf("info:VIDIOC_REQBUFS SUCCESS\n");
- return 1;
- }
- int memory_mapping(int fd)
- {
- unsigned int n_buffers;
- buffers = (struct buffer*)calloc(req.count,sizeof(struct buffer));
- if (!buffers) {
- fprintf (stderr, "Out of memory\n");
- exit (EXIT_FAILURE);
- }
- // 映射
- for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
- //struct v4l2_buffer buf;
- memset(&buf,0,sizeof(buf));
- buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory = V4L2_MEMORY_MMAP;
- buf.index = n_buffers;
- // 查询序号为n_buffers 的缓冲区,得到其起始物理地址和大小
- if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf))
- exit(-1);
- buffers[n_buffers].length = buf.length;
- // 映射内存
- buffers[n_buffers].start =mmap (NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd, buf.m.offset);
- if (MAP_FAILED == buffers[n_buffers].start)
- exit(-1);
- }
- printf("info:memory mapping success\n");
- return 1;
- }
- int buffer_enqueue(int fd)
- {
- unsigned int i;
- enum v4l2_buf_type type;
- // 将缓冲帧放入队列
- for (i = 0; i < 4; ++i)
- {
- struct v4l2_buffer buf;
- buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory = V4L2_MEMORY_MMAP;
- buf.index = i;
- if(-1==ioctl (fd, VIDIOC_QBUF, &buf))
- printf("buffer enqueue failed\n");
- }
- type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- //open stream
- if(-1==ioctl (fd, VIDIOC_STREAMON, &type))
- printf("info:open stream failed\n");
- else
- printf("info:open stream success\n");
- return 1;
V4L2开发要点【转】的更多相关文章
- 一文看懂汽车电子ECU bootloader工作原理及开发要点
随着半导体技术的不断进步(按照摩尔定律),MCU内部集成的逻辑功能外设越来越多,存储器也越来越大.消费者对于汽车节能(经济和法规对排放的要求)型.舒适性.互联性.安全性(功能安全和信息安全)的要求越来 ...
- USB 3.0 开发要点
最近在公司里安排了我一个新的任务,那就是USB3.0的研发.对于我之前都是做ARM+LINUX和单片机软件研发的来说,虽然之前都是做驱动程序和应用程序,但是没有做与USB 相关的开发,毕竟这是第一次. ...
- Android响应式界面开发要点
现在很多项目需要到达同一个Apk既可以在Phone上跑也尅在tablet上跑,即界面要适应不同尺寸和类型的需要而自动调整.这个即为响应式设计.在web开发商响应式设计已经是个常谈的内容了,而对于and ...
- 原创:微信小程序开发要点总结
废话不多少,下面是对我从开发微信小程序的第一步开始到发布的总结,觉得对您有帮助的话,可以赞赏下,以对我表示鼓励. 一:首先注册登录微信公众平台,这个平台很重要,以后查文档全在上面看.https://m ...
- NK3C开发要点
1.业务逻辑:文档, 2.后端资料 框架:spring + mybatis + maven + Shiro + 数据库(Oracle.SQL Server.MySQL) 分层:nmodel,ndal, ...
- 使用View为Data Source的Form开发要点
(Data Source为View) 要点一:创建View的SQL语法 View的SQL里必须指定Form里唯一一个对其新增.修改.删除的基本表及其主键,其它表为辅助信息表,其字段仅用来在Form里显 ...
- WebApp触屏版网站开发要点
所谓的触屏版网站其实也是WebApp的一种展示形式,主要是依赖HTML+CSS+Javascript这三个关键因素来实现,相比较原生客户端程序来说优点就是开发周期短.升级简单.维护成本低,因为从根本上 ...
- 《修炼之道:.NET开发要点精讲》读书笔记(三)
后几章的习题 1.异步调用开始后,什么时候才能使用异步执行的结果? A:最好在EndInvoke()方法返回之后才能使用异步执行的结果,其它时候不能保证异步调用已完成. 2.委托的异步调用开始后(即调 ...
- outlook vba开发要点
1.学学基础的VB语法 https://www.yiibai.com/vba/vba_programming_charts.html 2.找一个样例看看 VBA编程实现自动回复邮件 https://b ...
随机推荐
- 建立Heapster Influxdb Grafana集群性能监控平台
依赖于kubenets dns服务 图形化展示度量指标的实现需要集成k8s的另外一个Addons组件: Heapster .Heapster原生支持K8s(v1.0.6及以后版本)和 CoreOS , ...
- Kafka Offset相关命令总结
Kafka Offset相关命令总结 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.查询topic的offset的范围 1>.查询某个topic的offset的最小值 [ ...
- Idea使用Maven创建Java Web项目
最近学到了Java Web项目,使用Idea和Maven创建Java Web的时候遇到了诸多问题,最多的还是404问题.现在记录一下解决方案. 一.使用maven创建一个web项目,这一步网上都有,下 ...
- Linux记录-在线扩容8e
1.fdisk -l 2.增加分区 3.3:键入 p,主分区,并键入3(编号): 默认起始扇区和结束扇区即可(键入两次Enter) 键入t,修改分区类型为8e: 键入w,写分区表,然后重启: 卷扩容, ...
- CSS常用选择器的认识
---恢复内容开始--- 前言:在CSS中选择器的种类有很多很多,但是在实际的工作中,我们经常会用到的分为两大类:基础选择器和复合选择器这两个大类,学习选择器的目的就是为了在复杂的页面中能够快速定位到 ...
- ES学习之分片路由
本文主要内容: 1.路由一个文档到一个分片 2.新建.索引和删除请求 3.取回单个文档 4.局部单个文档 5.多文档模式 6.理解一下ES深度分页(from-size)的劣势 路由一个文档到一个分片 ...
- 【1】【leetcode-127】单词接龙word-ladder
(不会,经典广度优先搜索) 给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度.转换需遵循如下规则: 每次转换只能改 ...
- HDU 1045(炮台安置 DFS)
题意是在 n*n 的方格中进行炮台的安置,炮台不能处于同一行或同一列(类似于八皇后问题),但若是炮台间有墙壁阻挡,则可以同时安置这对炮台.问图中可以安放的最大炮台数目. 用深搜的方法,若此处为空地,则 ...
- POJ 3070(求斐波那契数 矩阵快速幂)
题意就是求第 n 个斐波那契数. 由于时间和内存限制,显然不能直接暴力解或者打表,想到用矩阵快速幂的做法. 代码如下: #include <cstdio> using namespace ...
- Mongodb aggregation 基本操作示例
MongoDB二个主要的操作:一个是查询,另一个是统计.对于查询而言,主要是find()方法,再配合Filters组合多个查询条件. 对于统计而言,则主要是aggregate操作,比如 group.s ...