基于V4L2驱动程序的USB摄像头Android(JNI)的编写(一)
http://blog.csdn.net/jansonzhe/article/details/47319727
video4 linux2(V4L2)是Linux内核中关于视频设备的内核驱动,它为Linux中视频设备访问提供了通用接口,在Linux系统中,本文主要介绍如何使用V4L2驱动程序打开我们的USB摄像头。同时着重介绍如何编写相应JNI(NDK)使Android应用程序能够打开我们外接的USB摄像头,并获取视频流信息。
一、 确定USB摄像头的设备文件
V4L2驱动的Video设备节点路径通常/dev/video/中的videoX,V4L2驱动对用户空间提供字符设备,主设备号为81,对于视频设备,其次设备号为0-63。除此之外,次设备号为64-127的Radio设备,次设备号为192-223的是Teletext设备,次设备号为224-255的是VBI设备。V4L2驱动的Video设备在用户空间通过各种ioctl调用进行控制,并且可以使用mmap进行内存映射。
这里首先第一步是要确定免驱的USB摄像头的设备文件,否则便无法进入下一步工作,要确定USB摄像头的设备文件也比较简单。
-
将USB摄像头通过USB线连接进开发板,使用adb shell进入dev目录,这时可以查看一下当下的包含video的目录,具体查看命令如下:ls –l video*,这样就可以看到目前开发板上所包含有的video设备,如下图所示:
由上图我们可以看到video设备的主设备号都为81,同时次设备号介于0到63之间。但是这上面有这么多的video设备,我们并不清楚我们的USB摄像头的设备文件是哪一个。所以下一步就是要确定我们的设备文件。 -
将USB摄像头拔出,同样的按照上一步的方式,查看dev目录下的video设备文件。得到的结果如下图所示
由上面我们可以看到,video4已经随着我们的USB摄像头拔出,已经消失不见了,说明我们USB摄像头的设备文件就是video4
二、 编写JNI文件
我们确定好USB的设备文件之后,就可以开始编写JNI程序啦,不过在编写之前,必须要弄清楚基于V4L2驱动的上层调用程序的一般流程。在后面会介绍许多V4L2驱动的API。
1. 结构流程
1) 打开设备文件。 int fd=open(”/dev/video4″,O_RDWR);
2) 取得设备的capability,看看设备具有什么功能,比如是否具有视频输入,或者音频输入输出等。VIDIOC_QUERYCAP,struct v4l2_capability
3) 选择视频输入,一个视频设备可以有多个视频输入。VIDIOC_S_INPUT,struct v4l2_input
4) 设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等。VIDIOC_S_STD,VIDIOC_S_FMT,struct v4l2_std_id,struct v4l2_format
5) 向驱动申请帧缓冲,一般不超过5个。struct v4l2_requestbuffers
6) 将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。Mmap
7) 将申请到的帧缓冲全部入队列,以便存放采集到的数据.VIDIOC_QBUF,struct v4l2_buffer
8) 开始视频的采集。VIDIOC_STREAMON
9) 出队列以取得已采集数据的帧缓冲,取得原始采集数据。VIDIOC_DQBUF
10) 将缓冲重新入队列尾,这样可以循环采集。VIDIOC_QBUF
11) 停止视频的采集。VIDIOC_STREAMOFF
12) 关闭视频设备。close(fd);
下面将具体介绍如何具体编写JNI的方式
2. 编写流程
1、打开设备文件
首先第一步当然是要打开设备文件,通过上一步,我们已经知道了设备文件的名称了,所以这一步我们就可以知道应该打开哪一个设备文件了。打开设备文件一共有两种方式。
阻塞式
使用阻塞式的方式打开设备文件,如果后面没有捕获到视频信息,驱动程序便会停止不动,直至有视频信息到来。方式如下:
- 1
非阻塞式
使用非阻塞式打开设备文件,即使尚未捕获到信息,驱动依旧会把缓存里面的东西返回给应用程序。方式如下:
- 1
- 2
- 3
在这里我们目前使用的是非阻塞式。
2、设定采集方式和属性
打开完设备文件之后,我们下一步需要做的工作就是去给驱动程序设置我们需要采集的视频信息的格式、大小、缓冲数等等。设置方式是通过我们非常熟悉的ioctl函数进行的,一般的格式为:
extern int ioctl(fd,request,…..)
这里的fd便是我们之前打开设备文件时所返回的文件句柄了,而request是V4L2驱动程序所特有的一些命令,用unsigned long表示,通过这些request给底层驱动,驱动程序便会映射相应的处理函数,所以这也是V4L2驱动程序的强大之处。常见的request有:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
当然我们也可以去官网查阅更加详细的API介绍,现在我们还是按照上面操作V4L2的结构流程图进行逐一分析。
(1) 检查当前USB摄像头设备的功能集
查询其功能集,使用的request为:VIDIOC_QUERYCAP
- 1
- 2
- 3
使用ioctl这个函数一共有两个目的,第一点事可以检查一下当前的这个设备文件是否符合4VL2的规范,因为如果该设备文件不符合4VL2规范的话,其返回值为-1,也就是说里面要么没有定义iotcl这个函数,要么就是没有request这个命令;第二点就是通过将v4l2_capability这个结构体的指针传递给驱动设备,以此来获取这个设备的功能集,其中这里的结构体v4l2_capability是Linux特有的一个用来保存摄像头功能集的一个结构体。定义在linux\videodev2.h文件中,该结构体的结构定义如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
从上面的结构体可以看到,其里面定义了一个u32(32位)的capabilities(能力集)
其能力集包括:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
因此我们要判断其是否有某种能力集,只要将获取的capabilities与某一个功能相“与”看其是否为1就可以了。通常如果是USB摄像头设备的话,其获得的v4l2_capability结构体一般是这样的:
- 1
- 2
- 3
- 4
- 5
- 6
(2) 查询并修改USB摄像头设备的剪裁能力
首先要解释一下,为什么要做这一步工作,在解释之前,我想打一个比方:比如我们的USB摄像头可以捕获到640*480格式大小的图片信息,但是我们给它显示的屏幕只有4.3’’(482*280),那么这个时候问题就来了,如果我们不修改USB摄像头捕获到的图片大小,直接将640*480给只支持482*280的显示屏显示,肯定会有问题的,所以这个时候,我们便要命令底层驱动去剪裁USB捕获到的图片大小,使之成为482*280大小的。这也是V4L2一个强大的地方。
首先第一步便是查询驱动设备采集区域的一些信息,比如缺省值(defrect)、最大值(bounds)等等这些信息。
这里需要的命令(request)是VIDIOC_CROPCAP,其iotcl命令是:
- 1
- 2
这样结构体cropcap便可以获得采集区域的一些信息了。v4l2_cropcap结构体是这样定义的:
- 1
- 2
- 3
- 4
- 5
- 6
在查询到了设备的采集区域的信息之后呢,这个时候我们便可以开始重新修改我们的采集区域了。修改命令我们用VIDIOC_S_CROP。其使用的ioctl命令是:
struct v4l2_crop crop;
ioctl(Handle, VIDIOC_CROPCAP, &crop);
其中crop结构体包含了我们要对采集区域进行修改的一些参数,v4l2_crop结构体定义如下:
- 1
- 2
- 3
- 4
- 5
可以看到在v4l2_crop中还包含一个结构体v4l2_rect其用c表示,这里的v4l2_rect(rectangle便是矩形的意思)定义如下:
- 1
- 2
- 3
- 4
- 5
- 6
由于我们现在用的USB摄像头的缺省值和我们需要显示的值的大小是一样的,因此这里我们就直接设置缺省值为我们的采集区域,全部代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
(3) 设置采集视频帧的格式、宽、高、大小等等
现在我们已经设置好了我们的摄像头采集区域的大小,那么这个时候问题就来了,我们要采用怎么的方式在这个采集区域里面进行图片信息的采集呢?采集的大小?采集来的数据的存放格式?等等这些问题都需要我们在这一步进行设定。这里同样是通过一个命令进行设置的,这个request是VIDIOC_S_FMT,使用方式是:
- 1
- 2
这里我们使用到的结构体是v4l2_format,其定义如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
我们可以看到v4l2_format这个结构体还比较复杂,里面还包含一个名为fmt的union,在union里面仍然包含有许多结构体,像我们接下来要用到的v4l2_pix_format,v4l2_pix_format这个结构体的定义是这样的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
所以整个程序就应该是:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
这里需要指出的是bytesperline这个参数,其表示表明缓冲区中有多少字节用于表示图像中一行像素的所有像素值。由于一个像素可能有多个字节表示,所以 bytesPerLine 可能是字段 width 值的若干倍,在这里设置为2倍,即一个像素点用两个2节表示,同理我们也可以推出sizeimage这个参数所表示的意思,应该是整个图像需要用多少字节来存储。