您的位置:首页 > 移动开发 > Android开发

基于V4L2驱动程序的USB摄像头Android(JNI)的编写(一)

2017-11-26 14:06 351 查看
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、打开设备文件 

首先第一步当然是要打开设备文件,通过上一步,我们已经知道了设备文件的名称了,所以这一步我们就可以知道应该打开哪一个设备文件了。打开设备文件一共有两种方式。

阻塞式 

使用阻塞式的方式打开设备文件,如果后面没有捕获到视频信息,驱动程序便会停止不动,直至有视频信息到来。方式如下:
int fd = open(”/dev/video4″, O_RDWR | O_NONBLOCK, 0);
1

非阻塞式 

使用非阻塞式打开设备文件,即使尚未捕获到信息,驱动依旧会把缓存里面的东西返回给应用程序。方式如下:
int fd = open(”/dev/video4″, O_RDWR, 0);
//或者下面这样
int fd = open(”/dev/video4″, O_RDWR);
1
2
3

在这里我们目前使用的是非阻塞式。

2、设定采集方式和属性

打开完设备文件之后,我们下一步需要做的工作就是去给驱动程序设置我们需要采集的视频信息的格式、大小、缓冲数等等。设置方式是通过我们非常熟悉的ioctl函数进行的,一般的格式为: 

extern int ioctl(fd,request,…..) 

这里的fd便是我们之前打开设备文件时所返回的文件句柄了,而request是V4L2驱动程序所特有的一些命令,用unsigned long表示,通过这些request给底层驱动,驱动程序便会映射相应的处理函数,所以这也是V4L2驱动程序的强大之处。常见的request有:
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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

当然我们也可以去官网查阅更加详细的API介绍,现在我们还是按照上面操作V4L2的结构流程图进行逐一分析。

(1) 检查当前USB摄像头设备的功能集 

查询其功能集,使用的request为:VIDIOC_QUERYCAP
struct v4l2_capability cap;
int rel = 0;
ioctl(Handle, VIDIOC_QUERYCAP, &cap);
1
2
3

使用ioctl这个函数一共有两个目的,第一点事可以检查一下当前的这个设备文件是否符合4VL2的规范,因为如果该设备文件不符合4VL2规范的话,其返回值为-1,也就是说里面要么没有定义iotcl这个函数,要么就是没有request这个命令;第二点就是通过将v4l2_capability这个结构体的指针传递给驱动设备,以此来获取这个设备的功能集,其中这里的结构体v4l2_capability是Linux特有的一个用来保存摄像头功能集的一个结构体。定义在linux\videodev2.h文件中,该结构体的结构定义如下:
struct v4l2_capability
{
__u8 driver[16];   //驱动名。
__u8 card[32];     // Device名
__u8 bus_info[32];  //在Bus系统中存放位置
__u32 version;      //driver 版本
__u32 capabilities;  //能力集
__u32 reserved[4];
};
1
2
3
4
5
6
7
8
9

从上面的结构体可以看到,其里面定义了一个u32(32位)的capabilities(能力集) 

其能力集包括:
V4L2_CAP_VIDEO_CAPTURE 0x00000001     The device supports the Video    Capture interface.
V4L2_CAP_VIDEO_OUTPUT   0x00000002     The device supports the Video    Output interface.
V4L2_CAP_VIDEO_OVERLAY 0x00000004     The device supports the Video    Overlay interface.
A video overlay device typically stores captured images directly in the video memory   of a graphics card,with hardware clipping and scaling.
V4L2_CAP_VBI_CAPTURE     0x00000010 The device supports the Raw  VBI Capture interface, providing Teletext and Closed Caption   data.
V4L2_CAP_VBI_OUTPUT     0x00000020      The device supports the Raw  VBI Output interface.
V4L2_CAP_SLICED_VBI_CAPTURE  0x00000040 The device supports the Sliced VBI Capture interface.
V4L2_CAP_SLICED_VBI_OUTPUT   0x00000080 The device supports the Sliced VBI Output interface.
V4L2_CAP_RDS_CAPTURE    0x00000100          [to be defined]
#define V4L2_CAP_TUNER 0x00010000
#define V4L2_CAP_AUDIO 0x00020000
#define V4L2_CAP_RADIO 0x00040000
#define V4L2_CAP_READWRITE 0x01000000
#define V4L2_CAP_ASYNCIO 0x02000000
#define V4L2_CAP_STREAMING 0x04000000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

因此我们要判断其是否有某种能力集,只要将获取的capabilities与某一个功能相“与”看其是否为1就可以了。通常如果是USB摄像头设备的话,其获得的v4l2_capability结构体一般是这样的:
__u8 driver[16]; //driver名,通常为:uvcvideo
__u8 card[32];  //设备名:厂商会填写。
__u8 bus_info[32];  //bus,通常为:usb-hiusb-ehci-2.4
__u32 version;
__u32 capabilities;  //通常为:V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING
__u32 reserved[4];
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命令是:
struct v4l2_cropcap cropcap;
ioctl(Handle, VIDIOC_CROPCAP, &cropcap);
1
2

这样结构体cropcap便可以获得采集区域的一些信息了。v4l2_cropcap结构体是这样定义的:
struct v4l2_cropcap {
enum v4l2_buf_type type; //类型
struct v4l2_rect bounds;  //最大值
struct v4l2_rect defrect;  //缺省值
struct v4l2_fract pixelaspect;//有关缩放比的
};
1
2
3
4
5
6

在查询到了设备的采集区域的信息之后呢,这个时候我们便可以开始重新修改我们的采集区域了。修改命令我们用VIDIOC_S_CROP。其使用的ioctl命令是: 

struct v4l2_crop crop; 

ioctl(Handle, VIDIOC_CROPCAP, &crop); 

其中crop结构体包含了我们要对采集区域进行修改的一些参数,v4l2_crop结构体定义如下:
struct v4l2_crop
{
enum v4l2_buf_type type;// 应用程序设置
struct v4l2_rect c;
}
1
2
3
4
5

可以看到在v4l2_crop中还包含一个结构体v4l2_rect其用c表示,这里的v4l2_rect(rectangle便是矩形的意思)定义如下:
struct v4l2_rect {
__s32 left;
__s32 top;
__s32 width;
__s32 height;
};
1
2
3
4
5
6

由于我们现在用的USB摄像头的缺省值和我们需要显示的值的大小是一样的,因此这里我们就直接设置缺省值为我们的采集区域,全部代码如下:
struct v4l2_cropcap cropcap;
struct v4l2_crop crop;
if (0 == xioctl (fd, VIDIOC_CROPCAP, &cropcap)) {
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect;

if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) {
switch (errno) {
case EINVAL:
break;
default:
break;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

(3) 设置采集视频帧的格式、宽、高、大小等等

现在我们已经设置好了我们的摄像头采集区域的大小,那么这个时候问题就来了,我们要采用怎么的方式在这个采集区域里面进行图片信息的采集呢?采集的大小?采集来的数据的存放格式?等等这些问题都需要我们在这一步进行设定。这里同样是通过一个命令进行设置的,这个request是VIDIOC_S_FMT,使用方式是:
struct v4l2_format fmt;
ioctl(Handle, VIDIOC_S_FMT, &fmt);
1
2

这里我们使用到的结构体是v4l2_format,其定义如下:
struct v4l2_format
{
enum v4l2_buf_type type;   // Camera,则用户必须填写:V4L2_BUF_TYPE_VIDEO_CAPTURE
union
{
struct v4l2_pix_format pix;    // used by video capture and output devices
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
__u8 raw_data[200];
} fmt;
};
1
2
3
4
5
6
7
8
9
10
11
12

我们可以看到v4l2_format这个结构体还比较复杂,里面还包含一个名为fmt的union,在union里面仍然包含有许多结构体,像我们接下来要用到的v4l2_pix_format,v4l2_pix_format这个结构体的定义是这样的:
struct v4l2_pix_format
{
__u32                   width;         // 宽,必须是16的倍数
__u32                   height;        // 高,必须是16的倍数
__u32                   pixelformat;   // 视频数据存储类型,例如是//YUV4:2:2还是RGB
enum v4l2_field         field;
__u32                   bytesperline;
__u32                   sizeimage;
enum v4l2_colorspace    colorspace;
__u32                   priv;
};
1
2
3
4
5
6
7
8
9
10
11

所以整个程序就应该是:
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width       = IMG_WIDTH;
fmt.fmt.pix.height      = IMG_HEIGHT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //数据储存类型
fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))
return errnoexit ("VIDIOC_S_FMT");
min = fmt.fmt.pix.width * 2;
if (fmt.fmt.pix.bytesperline < min)
fmt.fmt.pix.bytesperline = min;
min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
if (fmt.fmt.pix.sizeimage < min)
fmt.fmt.pix.sizeimage = min;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这里需要指出的是bytesperline这个参数,其表示表明缓冲区中有多少字节用于表示图像中一行像素的所有像素值。由于一个像素可能有多个字节表示,所以 bytesPerLine 可能是字段 width 值的若干倍,在这里设置为2倍,即一个像素点用两个2节表示,同理我们也可以推出sizeimage这个参数所表示的意思,应该是整个图像需要用多少字节来存储。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: