(1)
设定属性及采集方式
打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。在Linux编程中,一般使用ioctl函数来对设备的I/O通道进行管理:
int ioctl (int __fd, unsigned long int __request, .../*args*/) ;
在进行V4L2开发中,常用的命令标志符如下(some are optional):
VIDIOC_REQBUFS:分配内存,开启内存映射或用户指针I/O
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。
VIDIOC_G_INPUT 和 VIDIOC_S_INPUT 用来查询和选择当前的input,一个 video 设备节点可能对应多个视频源,比如saf7113可以最多支持四路 cvbs 输入,如果上层想在四个cvbs视频输入间切换,那么就要调用 ioctl(fd, VIDIOC_S_INPUT, &input) 来切换。VIDIOC_G_INPUT and VIDIOC_G_OUTPUT 返回当前的 video input和output的index.
调用内核中的接口:
drivers/media/video/soc_camera.c
static const struct v4l2_ioctl_ops soc_camera_ioctl_ops = {
.vidioc_querycap = soc_camera_querycap,
.vidioc_g_fmt_vid_cap = soc_camera_g_fmt_vid_cap,
.vidioc_enum_fmt_vid_cap = soc_camera_enum_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = soc_camera_s_fmt_vid_cap,
.vidioc_enum_input = soc_camera_enum_input,
.vidioc_g_input = soc_camera_g_input,
.vidioc_s_input = soc_camera_s_input,
.vidioc_s_std = soc_camera_s_std,
.vidioc_enum_framesizes = soc_camera_enum_fsizes,
.vidioc_enum_frameintervals = soc_camera_enum_frameintervals,
.vidioc_reqbufs = soc_camera_reqbufs,
.vidioc_try_fmt_vid_cap = soc_camera_try_fmt_vid_cap,
.vidioc_querybuf = soc_camera_querybuf,
.vidioc_qbuf = soc_camera_qbuf,
.vidioc_dqbuf = soc_camera_dqbuf,
.vidioc_streamon = soc_camera_streamon,
.vidioc_streamoff = soc_camera_streamoff,
.vidioc_queryctrl = soc_camera_queryctrl,
.vidioc_querymenu = soc_camera_querymenu,
.vidioc_g_ctrl = soc_camera_g_ctrl,
.vidioc_s_ctrl = soc_camera_s_ctrl,
.vidioc_cropcap = soc_camera_cropcap,
.vidioc_g_crop = soc_camera_g_crop,
.vidioc_s_crop = soc_camera_s_crop,
.vidioc_g_parm = soc_camera_g_parm,
.vidioc_s_parm = soc_camera_s_parm,
.vidioc_g_chip_ident = soc_camera_g_chip_ident,
.vidioc_s_tlb_base = soc_camera_s_tlb_base,
#ifdef CONFIG_VIDEO_ADV_DEBUG
.vidioc_g_register = soc_camera_g_register,
.vidioc_s_register = soc_camera_s_register,
#endif
};
(2)
常用的结构体在内核目录include/linux/videodev2.h中定义
struct v4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFS
struct v4l2_capability //视频设备的功能,对应命令VIDIOC_QUERYCAP
struct v4l2_input //视频输入信息,对应命令VIDIOC_ENUMINPUT
struct v4l2_standard //视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
struct v4l2_format //帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
struct v4l2_buffer //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
struct v4l2_crop //视频信号矩形边框
v4l2_std_id //视频制式
struct v4l2_capability cap;
ioctl(camera_v4l2->fd, VIDIOC_QUERYCAP, &cap)
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取
if (!(cap.capabilities & V4L2_CAP_STREAMING)) 视频捕捉设备并且具有数据流控制模式
在V4L2驱动中,有三种使用方式,其中的一种就是V4L2_MEMORY_USERPTR。V4L2_MEMORY_USERPTR表示使用用户空间内存。编程者在用户空间申请了内存后,可以通过这种方式,将该空间送到kernel空间使用。问题是:在用户空间申请的内存是逻辑空间(虚拟地址),其物理上未必是连续的;而在kernel空间使用的内存应该是物理上连续,尤其是在kernel空间将该内存空间送给底层的硬件,比如视频编解码器使用(它们需要的是物理上连续的空间)。在这种情况下,用户空间的不连续空间如何与kernel里的要求的连续空间兼容的?用户空间的逻辑地址是如何转换成kernel空间的物理地址的?
逻辑地址(logical address):包含在机器语言指令中用来指定一个操作数或者一条指令的地址。即程序编译后形成的地址。
线性地址(linear address)或也叫虚拟地址(virtual address): 逻辑地址,经过MMU的分段单元的硬件电路转换后,形成线性地址,接着被子第二个称为分页单元的硬件电路把线性地址转换成物理地址。
有两种方式的I/O,Memory Mapping 和User Pointer。
Memory Mapping的Buffer由Driver申请为物理连续的内存空间(Kernel空间)。在此ioctl调用时被分配,需要早于mmap()动作将他们映射到用户空间。
区别:mmap: VIDIOC_REQBUFS-->VIDIOC_QUERYBUF-->mmap
userptr: VIDIOC_REQBUFS-->valloc 这个接口申请的物理地址是连续的
(1)
申请数据Buffer。
int ioctl(int fd, int requestbuf, struct v4l2_requestbuffers * argp);
参数一:open()所产生的句柄。
参数二:VIDIOC_REQBUFS
参数三:in/out结构体
支持Memory Mapping I/O方式的前提是:v4l2_capability 中支持V4L2_CAP_STREAMING。
在这个模式下,数据本身不会被Copy,只是在Kernel和用户态之间交换。在应用程序想要访问到这些数据之前,它必须调用mmap()影射到用户态。同时也要注意,通过ioctl申请的内存,是物理内存,无法被交换入Disk,所以一定要释放:munmap()。
控制命令VIDIOC_REQBUFS
功能:请求V4L2驱动分配视频缓冲区(申请V4L2视频驱动分配内存),V4L2是视频设备的驱动层,位于内核空间,所以通过VIDIOC_REQBUFS控制命令字申请的内存位于内核空间,应用程序不能直接访问,需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。
参数说明:参数类型为V4L2的申请缓冲区数据结构体类型struct v4l2_requestbuffers;返回值说明: 执行成功时,函数返回值为 0;V4L2驱动层分配好了视频缓冲区;
(2)
询问Buffer状态:
int ioctl(int fd, int request, struct v4l2_buffer* argp);
参数一:open()所产生的句柄。
参数二:VIDIOC_QUERYBUF
参数三:v4l2_buffer 结构体。(IN/OUT参数)
注意,此ioctl是Memory Mapping的I/O方法之一。User Pointer模式不需要。在Buffer在ioctl-VIDIOC_REQBUFS执行时创建后,随时都可以调用此Ioctl得到buffer信息。视频缓冲区的使用状态、在内核空间的偏移地址、缓冲区长度等。在应用程序设计中通过调VIDIOC_QUERYBUF来获取内核空间的视频缓冲区信息,然后调用函数mmap把内核空间地址映射到用户空间,这样应用程序才能够访问位于内核空间的视频缓冲区.
控制命令VIDIOC_QUERYBUF
功能: 查询已经分配的V4L2的视频缓冲区的相关信息,包括视频缓冲区的使用状态、在内核空间的偏移地址、缓冲区长度等。在应用程序设计中通过调VIDIOC_QUERYBUF来获取内核空间的视频缓冲区信息,然后调用函数mmap把内核空间地址映射到用户空间,这样应用程序才能够访问位于内核空间的视频缓冲区。
参数说明:参数类型为V4L2缓冲区数据结构类型 struct v4l2_buffer
返回值说明: 执行成功时,函数返回值为 0;struct v4l2_buffer结构体变量中保存了指令的缓冲区的相关信息;
一般情况下,应用程序中调用VIDIOC_QUERYBUF取得了内核缓冲区信息后,紧接着调用mmap函数把内核空间地址映射到用户空间,方便用户空间应用程序的访问。
(3)v4l2_buf_type类型
V4L2_BUF_TYPE_VIDEO_CAPTURE 指定buf的类型为capture,用于视频捕获设备 即视频捕捉模式
V4L2_BUF_TYPE_VIDEO_OUTPUT 指定buf的类型output,用于视频输出设备
V4L2_BUF_TYPE_VIDEO_OVERLAY 指定buf的类型为overlay,用于overlay设备
V4L2_BUF_TYPE_VBI_CAPTURE 用于vbi捕获设备
V4L2_BUF_TYPE_VBI_OUTPUT 用于vbi输出设备
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE 用于切片vbi捕获设备
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT 用于切片vbi输出设备
V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY 用于视频输出overlay设备
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 用于多平面存储格式的视频捕获设备
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE用于多平面存储格式的视频输出设备
(4)mmap
mmap用于把文件映射到内存空间中,简单说mmap就是把一个文件的内容在内存里面做一个映像。映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset)
start:要映射到的内存区域的起始地址,通常都是用NULL(NULL即为0)。NULL表示由内核来指定该内存地址
length:要映射的内存区域的大小
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
MAP_FIXED :使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
MAP_SHARED :对映射区域的写入数据会复制回文件内, 而且允许其他映射该文件的进程共享。
MAP_PRIVATE :建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
MAP_DENYWRITE :这个标志被忽略。
MAP_EXECUTABLE :同上
MAP_NORESERVE :不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
MAP_LOCKED :锁定映射区的页面,从而防止页面被交换出内存。
MAP_GROWSDOWN :用于堆栈,告诉内核VM系统,映射区可以向下扩展。
MAP_ANONYMOUS :匿名映射,映射区不与任何文件关联。
MAP_ANON :MAP_ANONYMOUS的别称,不再被使用。
MAP_FILE :兼容标志,被忽略。
MAP_32BIT :将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
MAP_POPULATE :为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
MAP_NONBLOCK :仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
fd:文件描述符(由open函数返回)
offset:表示被映射对象(即文件)从那里开始对映,通常都是用0。 该值应该为大小为PAGE_SIZE的整数倍
返回说明
成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值
EACCES:访问出错
EAGAIN:文件已被锁定,或者太多的内存已被锁定
EBADF:fd不是有效的文件描述词
EINVAL:一个或者多个参数无效
ENFILE:已达到系统对打开文件的限制
ENODEV:指定文件所在的文件系统不支持内存映射
ENOMEM:内存不足,或者进程已超出最大内存映射数量
EPERM:权能不足,操作不允许
ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程的内存区
(4)VIDIOC_QBUF和VIDIOC_DQBUF 命令
VIDIOC_QBUF 和 VIDIOC_DQBUF 命令都采用结构 v4l2_buffer与驱动通信:VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频的队列,传递的主要参数为 index;VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会根据 index 确定可用数据的起始地址和范围。
控制命令VIDIOC_QBUF
功能: 投放一个空的视频缓冲区到视频缓冲区输入队列中 ;
参数说明:参数类型为V4L2缓冲区数据结构类型 struct v4l2_buffer ;
返回值说明: 执行成功时,函数返回值为 0;函数执行成功后,指令(指定)的视频缓冲区进入视频输入队列,在启动视频设备拍摄图像时,相应的视频数据被保存到视频输入队列相应的视频缓冲区中。
(5)
控制命令VIDIOC_STREAMON
功能: 启动视频采集命令,应用程序调用VIDIOC_STREAMON启动视频采集命令后,视频设备驱动程序开始采集视频数据,并把采集到的视频数据保存到视频驱动的视频缓冲区中。
参数说明:参数类型为V4L2的视频缓冲区类型 enum v4l2_buf_type;
返回值说明: 执行成功时,函数返回值为 0;函数执行成功后,视频设备驱动程序开始采集视频数据,此时应用程序一般通过调用select函数来判断一帧视频数据是否采集完成,当视频设备驱动完成一帧视频数据采集并保存到视频缓冲区中时,select函数返回,应用程序接着可以读取视频数据;否则select函数阻塞直到视频数据采集完成.
(6)
select机制中提供了一个数据结构 struct fd_set ,可以理解为一个集合,实际上是一个位图,每一个特定为来标志相应大小文件描述符,这个集合中存放的是文件描述符(file descriptor),即文件句柄(也就是位图上的每一位都能与一个打开的文件句柄(文件描述符)建立联系,这个工作由程序员来完成),这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,程序员通过操作4类宏,来完成最fd_set的操作:
(1)、FD_ZERO(fd_set *) 清空一个文件描述符集合;
(2)、FD_SET(int ,fd_set *)将一个文件描述符添加到一个指定的文件描述符集合中;
(3)、FD_CLR(int ,fd_set*) 将一个给定的文件描述符从集合中删除;
(4)、FD_ISSET(int ,fd_set* )检查集合中指定的文件描述符是否可以读写。
struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
struct timeval *timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态
第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止
第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值
第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回
返回值
负值:select错误
正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件 如果没有输入数据而引发超时,返回0
如果参数timeout设为NULL则表示select()没有timeout。
调用select监测文件描述符,缓冲区的数据是否填充好
(7)
控制命令VIDIOC_DQBUF //第二个D是删除的意思
功能: 从视频缓冲区的输出队列中取得一个已经保存有一帧视频数据的视频缓冲区;
参数说明:参数类型为V4L2缓冲区数据结构类型 struct v4l2_buffer
返回值说明: 执行成功时,函数返回值为 0;函数执行成功后,相应的内核视频缓冲区中保存有当前拍摄到的视频数据,应用程序可以通过访问用户空间来读取该视频数据。(前面已经通过调用函数mmap做了用户空间和内核空间的内存映射)
(8)
控制命令VIDIOC_STREAMOFF
功能: 停止视频采集命令,应用程序调用VIDIOC_ STREAMOFF停止视频采集命令后,视频设备驱动程序不在采集视频数据。
参数说明:参数类型为V4L2的视频缓冲区类型 enum v4l2_buf_type
返回值说明: 执行成功时,函数返回值为 0;函数执行成功后,视频设备停止采集视频数据。
视频采集的过程:
(1)
open 节点/dev/video0
(2)
ioctl(camera_v4l2->fd, VIDIOC_QUERYCAP, &cap) 获取设备支持的操作
(3)
ioctl(camera_v4l2->fd, VIDIOC_S_INPUT, &tmp) 查询和选择当前的input,一个video设备节点可能对应多个视频源
(4)
ioctl(camera_v4l2->fd, VIDIOC_S_FMT, &fmt) 设置当前驱动的频捕获格式 ioctl(camera_v4l2->fd, VIDIOC_G_FMT, &fmt)读取当前驱动的频捕获格式
drivers/media/video/v4l2-ioctl.c
case VIDIOC_S_FMT:
ret = ops->vidioc_s_fmt_vid_cap(file, fh, f);
drivers/media/video/soc_camera.c
static const struct v4l2_ioctl_ops soc_camera_ioctl_ops = {
.vidioc_s_fmt_vid_cap = soc_camera_s_fmt_vid_cap,
}
static int soc_camera_s_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{
soc_camera_set_fmt(icd, f)
}
static int soc_camera_set_fmt(struct soc_camera_device *icd,struct v4l2_format *f)
{
return ici->ops->set_bus_param(icd, pix->pixelformat);
}
调用我们的摄像头驱动:
drivers/media/video/ov5640.c
static struct soc_camera_ops ov5640_ops = {
.set_bus_param = ov5640_set_bus_param,
.query_bus_param = ov5640_query_bus_param,
.controls = ov5640_controls,
.num_controls = ARRAY_SIZE(ov5640_controls),
};
(5)
ioctl(camera_v4l2->fd, VIDIOC_REQBUFS, &req)
//申请数据Buffer,VIDIOC_REQBUFS控制命令字申请的内存位于内核空间,应用程序不能直接访问,需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。
ioctl(camera_v4l2->fd, VIDIOC_QUERYBUF, &buf)
//询问Buffer状态,获取内核空间的视频缓冲区信息
mmap(NULL, buf.length, PROT_READ | PROT_WRITE,MAP_SHARED, camera_v4l2->fd, buf.m.offset)
(6)
ioctl(camera_v4l2->fd, VIDIOC_QBUF, &buf) 投放一个空的视频缓冲区到视频缓冲区输入队列中
(7)
ioctl(camera_v4l2->fd, VIDIOC_STREAMON, &type)
//启动视频采集命令,应用程序调用VIDIOC_STREAMON启动视频采集命令后,视频设备驱动程序开始采集视频数据,并把采集到的视频数据保存到视频驱动的视频缓冲区中
(8)
select(camera_v4l2->fd + 1, &fds, NULL, NULL, &tv)
//调用select函数来判断一帧视频数据是否采集完成,当视频设备驱动完成一帧视频数据采集并保存到视频缓冲区中时,select函数返回,应用程序接着可以读取视频数据;否则select函数阻塞直到视频数据采集完成.
static int v4l2_read_frame(struct camera_v4l2 *camera_v4l2)
{
ioctl(camera_v4l2->fd, VIDIOC_DQBUF, &buf) //从视频缓冲区的输出队列中取得一个已经保存有一帧视频数据的视频缓冲区
ioctl(camera_v4l2->fd, VIDIOC_QBUF, &buf)
}
(9)
ioctl(camera_v4l2->fd, VIDIOC_STREAMOFF, &type) //停止视频采集命令,应用程序调用VIDIOC_ STREAMOFF停止视频采集命令后,视频设备驱动程序不在采集视频数据。