转自:http://blog.csdn.net/fwqlzz/article/details/51126653

版权声明:本文为博主原创文章,未经博主允许不得转载。

USB video class(又称为USB video device class or UVC)就是USB device class视频产品在不需要安装任何的驱动程序下即插即用,包括摄像头、数字摄影机、模拟视频转换器、电视卡及静态视频相机。

最新的UVC版本为UVC 1.5,由USB-IF(USB Implementers Forum)定义包括基本协议及负载格式 [] [].

这个链接是Linux中对UVC支持的相关描述。http://www.ideasonboard.org/uvc/

V4L2:

Video4Linux或V4L是一个视频截取及设备输出API,以及Linux的驱动程序框架,支持很多USB摄像头、电视调谐卡以及其他设备。Video4Linux与Linux内核紧密集成,Video4Linux取名的灵感来自Video for Windows(有时候会缩写为“V4W”),但两者在技术上并没有任何关系[][]。

从两个的介绍也能看出来这两者之间的关系。

简单的讲V4L2就是用来管理UVC设备的并且能够提供视频相关的一些API。那么这些API怎么使用或者能被谁使用呢。在Linux系统上有很多的开源软件能够支持V4L2。常见的又FFmpeg、OpenCV、Skype、Mplayer等等。

这样一个UVC能够进行视频显示的话应该满足三个条件:

 UVC的camera硬件支持

 UVC驱动支持,包括USB设备驱动以及v4l2的支持

 上层的应用程序支持

linux UVC驱动是为了全面的支持UVC设备。它包括V4L2内核驱动程序和用户空间工具补丁。这个视频设备或者USB视频类的USB设备类的定义定义了在USB上的视频流的功能。UVC类型的外设只需要一个通用的驱动支持就能够正常工作,就像USB 大容量存储设备一样。

UVC的linux  kernel驱动程序和支持的硬件设备都在这里有相关的描述:http://www.ideasonboard.org/uvc/。

判断一个摄像头是否属于UVC规范可以使用如下方法:

 使用lsusb命令或其他硬件信息查看工具,找出摄像头的设备号(Vendor ID)和产品号(Product ID)。

 查找是否有视频类借口信息

lsusb -d VID:PID -v | grep "14 Video"

如果兼容UVC,则会输出类似信息

bFunctionClass  Video

bInterfaceClass  Video

bInterfaceClass  Video

bInterfaceClass  Video

如果没有以上信息,则是non-UVC设备。

. Kernel配置:
Device Drivers ---> <*> Multimediasupport ---> <M> Video For Linux
Device Drivers ---> <*> Multimediasupport ---> [*] Video capture adapters ---> [*] V4L USB devices ---> <M> USB Video Class (UVC) --- V4L USB devices :这里还有很多特定厂商的driver.可供选择。 分析:
"USB Video Class(UVC)":对应的driver是:uvcvideo.ko
"Video For Linux": 对应driver是:videodev.ko 安装driver顺序如下:
insmod v4l1_compat.ko
insmod videodev.ko
insmod uvcvideo.ko driver会创建一个或多个主设备号为81,次设备号:-255的设备。
除了camera会创建为:/dev/videoX 之外,还有VBI设备-/dev/vbiX.Radio设备--/dev/radioX. . V4L2一些概念:
2.1:Video Input and Output:
video input and output是指device物理连接。
只有video 和VBI capture拥有input.
Radio设备则没有video input 和output. 2.2: Video Standards:
Video Device支持一个或多个Video 标准。 . 使用V4L2编程:
使用V4L2(Video for Linux ) API的过程大致如下: Opening the device Changing device properties, selecting a video and audio input,video standard, picture brightness a. o. Negotiating a data format Negotiating an input/output method The actual input/output loop Closing the device 3.1:打开设备:
fd = open("/dev/video0", O_RDWR, ); //以阻塞模式打开设想头 3.2: 查询设备能力:Querying Capabilities:
因为V4L2可以对多种设备编程,所以并不是所有API可以对所有设备编程,哪怕是同类型的设备,使用ioctl--VIDIOC_QUERYCAP去询问支持什么功能。 struct v4l2_capability cap; rel = ioctl(fdUsbCam, VIDIOC_QUERYCAP,&cap);
if(rel != )
{ perror("ioctl VIDIOC_QUERYCAP"); return -; } 结构体如下: struct v4l2_capability { __u8 driver[]; __u8 card[]; __u8 bus_info[]; __u32 version; __u32 capabilities; __u32 reserved[]; };
这里面最重要的是:capabilities: 头文件linux/videodev2.h和kernel头文件linux/videodev2.h中都有描述: #define V4L2_CAP_VIDEO_CAPTURE 0x00000001 #define V4L2_CAP_VIDEO_OUTPUT 0x00000002 #define V4L2_CAP_VIDEO_OVERLAY 0x00000004 #define V4L2_CAP_VBI_CAPTURE 0x00000010 #define V4L2_CAP_VBI_OUTPUT 0x00000020 #define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 #define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 #define V4L2_CAP_RDS_CAPTURE 0x00000100 #define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 #define V4L2_CAP_HW_FREQ_SEEK 0x00000400 #define V4L2_CAP_RDS_OUTPUT 0x00000800 #define V4L2_CAP_TUNER 0x00010000 #define V4L2_CAP_AUDIO 0x00020000 #define V4L2_CAP_RADIO 0x00040000 #define V4L2_CAP_MODULATOR 0x00080000 #define V4L2_CAP_READWRITE 0x01000000 #define V4L2_CAP_ASYNCIO 0x02000000 #define V4L2_CAP_STREAMING 0x04000000 这里要说到VBI, Vertical Blanking Interval的缩写 。电视信号包括一部分非可视信号,它不传送可视信息,因此被称为ⅦI(垂直消隐期间)。VBI可以用于传送其他信息,通常是一种专用字幕信号
这和Blog 重显率中所说暗合。 在这里, V4L2_CAP_VIDEO_CAPTURE 说明设备是个图像采集设备,V4L2_CAP_STREAMING 说明是个Streaming设备。
通常,摄像头都支持以上两个能力。 3.3:查询当前捕获格式: memset(&fmt, , sizeof(structv4l2_format)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fdUsbCam, VIDIOC_G_FMT, &fmt)< ) { printf("get format failed\n"); return -; }
注意,此处,fmt是个in/out参数。
参见Kernel代码v4l2_ioctl.c中。此ioctl,它会首先判断
fmt.type.
type类型和含义如下: V4L2_BUF_TYPE_VIDEO_CAPTURE :vid-cap V4L2_BUF_TYPE_VIDEO_OVERLAY :vid-overlay V4L2_BUF_TYPE_VIDEO_OUTPUT :vid-out V4L2_BUF_TYPE_VBI_CAPTURE :vbi-cap V4L2_BUF_TYPE_VBI_OUTPUT : vbi-out V4L2_BUF_TYPE_SLICED_VBI_CAPTURE :sliced-vbi-cap V4L2_BUF_TYPE_SLICED_VBI_OUTPUT :sliced-vbi-out V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY : vid-out-overlay
咱们是使用Video Cam的。所以用V4L2_BUF_TYPE_VIDEO_CAPTURE struct v4l2_format { enum v4l2_buf_type type; union { struct v4l2_pix_format pix; struct v4l2_window win; struct v4l2_vbi_format vbi; struct v4l2_sliced_vbi_format sliced; __u8 raw_data[]; } fmt; };
我们得到的信息在v4l2_pix_format中。
你可以看到,宽,高,像素格式。
其中像素格式包括: #define V4L2_PIX_FMT_RGB332 v4l2_fourcc('R','G','B','1') #define V4L2_PIX_FMT_RGB555 v4l2_fourcc('R','G','B','O') #define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R','G','B','P') #define V4L2_PIX_FMT_RGB555X v4l2_fourcc('R','G','B','Q') #define V4L2_PIX_FMT_RGB565X v4l2_fourcc('R','G','B','R') #define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B','G','R','3') #define V4L2_PIX_FMT_RGB24 v4l2_fourcc('R','G','B','3') #define V4L2_PIX_FMT_BGR32 v4l2_fourcc('B','G','R','4') #define V4L2_PIX_FMT_RGB32 v4l2_fourcc('R','G','B','4') #define V4L2_PIX_FMT_GREY v4l2_fourcc('G','R','E','Y') #define V4L2_PIX_FMT_YVU410 v4l2_fourcc('Y','V','U','9') #define V4L2_PIX_FMT_YVU420 v4l2_fourcc('Y','V','1','2') #define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y','U','Y','V') #define V4L2_PIX_FMT_UYVY v4l2_fourcc('U','Y','V','Y') #define V4L2_PIX_FMT_YUV422P v4l2_fourcc('4','2','2','P') #define V4L2_PIX_FMT_YUV411P v4l2_fourcc('4','1','1','P') #define V4L2_PIX_FMT_Y41P v4l2_fourcc('Y','4','1','P') #define V4L2_PIX_FMT_NV12 v4l2_fourcc('N','V','1','2') #define V4L2_PIX_FMT_NV21 v4l2_fourcc('N','V','2','1') #define V4L2_PIX_FMT_YUV410 v4l2_fourcc('Y','U','V','9') #define V4L2_PIX_FMT_YUV420 v4l2_fourcc('Y','U','1','2') #define V4L2_PIX_FMT_YYUV v4l2_fourcc('Y','Y','U','V') #define V4L2_PIX_FMT_HI240 v4l2_fourcc('H','I','2','4') #define V4L2_PIX_FMT_HM12 v4l2_fourcc('H','M','1','2') #define V4L2_PIX_FMT_SBGGR8 v4l2_fourcc('B','A','8','1') #define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M','J','P','G') #define V4L2_PIX_FMT_JPEG v4l2_fourcc('J','P','E','G') #define V4L2_PIX_FMT_DV v4l2_fourcc('d','v','s','d') #define V4L2_PIX_FMT_MPEG v4l2_fourcc('M','P','E','G') #define V4L2_PIX_FMT_WNVA v4l2_fourcc('W','N','V','A') #define V4L2_PIX_FMT_SN9C10X v4l2_fourcc('S','9','1','0') #define V4L2_PIX_FMT_PWC1 v4l2_fourcc('P','W','C','1') #define V4L2_PIX_FMT_PWC2 v4l2_fourcc('P','W','C','2') #define V4L2_PIX_FMT_ET61X251 v4l2_fourcc('E','6','2','5') fxxk,真TNND多。 请注意,此时取到的宽,高,像素格式均正确。但不知为何,bytesperline却为0。 3.4:设置当前捕获格式 fmt.fmt.pix.width = ; fmt.fmt.pix.height = ; fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; rel = ioctl(fdUsbCam, VIDIOC_S_FMT, &fmt); if (rel < ) { printf("\nSet format failed\n"); return -; } 此时,再取当前捕获格式,则一切正常。包括 bytesperline 3.5:读取Stream 设置。 struct v4l2_streamparm *setfps; setfps=(struct v4l2_streamparm *) calloc(, sizeof(structv4l2_streamparm)); memset(setfps, , sizeof(struct v4l2_streamparm)); setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rel = ioctl(fdUsbCam,VIDIOC_G_PARM, setfps); if(rel == ) { printf("\n Frame rate: %u/%u\n", setfps->parm.capture.timeperframe.denominator, setfps->parm.capture.timeperframe.numerator ); } else { perror("Unable to read out current framerate"); return -; } 注意: ioctl(fdUsbCam, VIDIOC_G_PARM,setfps); 参数3也是i/o 参数。必须要首先其type.
struct v4l2_streamparm
{
enum v4l2_buf_type type;
union
{
struct v4l2_captureparm capture;
struct v4l2_outputparm output;
__u8 raw_data[];
} parm;
};
type字段描述的是在涉及的操作的类型。对于视频捕获设备,应该为V4L2_BUF_TYPE_VIDEO_CAPTURE。对于输出设备应该为V4L2_BUF_TYPE_VIDEO_OUTPUT。它的值也可以是V4L2_BUF_TYPE_PRIVATE,在这种情况下,raw_data字段用来传递一些私有的,不可移植的,甚至是不鼓励的数据给内核。 enum v4l2_buf_type {
V4L2_BUF_TYPE_VIDEO_CAPTURE = ,
V4L2_BUF_TYPE_VIDEO_OUTPUT = ,
V4L2_BUF_TYPE_VIDEO_OVERLAY = ,
V4L2_BUF_TYPE_VBI_CAPTURE = ,
V4L2_BUF_TYPE_VBI_OUTPUT = , V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = ,
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = ,
V4L2_BUF_TYPE_PRIVATE = 0x80,
};
咱们当然选用V4L2_BUF_TYPE_VIDEO_CAPTURE 对于捕获设备而言,parm.capture字段是要关注的内容,这个结构体如下:
struct v4l2_captureparm
{
__u32 capability;
__u32 capturemode;
structv4l2_fract timeperframe;
__u32 extendedmode;
__u32 readbuffers;
__u32 reserved[];
}; timeperframe字段用于指定想要使用的帧频率,它又是一个结构体: struct v4l2_fract{
__u32 numerator;
__u32 denominator;
};
numerator和denominator所描述的系数给出的是成功的帧之间的时间间隔。
numerator 分子,denominator 分母。主要表达每次帧之间时间间隔。 numerator/denominator秒一帧。 3.6:设置Stream参数。(主要是采集帧数)
setfps->parm.capture.timeperframe.numerator=;
setfps->parm.capture.timeperframe.denominator=;
rel = ioctl(fdUsbCam,VIDIOC_S_PARM, setfps);
if(rel != )
{
printf("\nUnable to Set FPS");
return -;
}
当然,setfps的其它项目,都是之前使用VIDIOC_G_PARM取得的。 3.7:创建一组缓冲区(buf)
struct v4l2_requestbuffers rb;
memset(&rb, , sizeof(structv4l2_requestbuffers));
rb.count = ;
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
rel = ioctl(fdUsbCam, VIDIOC_REQBUFS,&rb);
if (rel < )
{
printf("Unable to allocate buffers: %d.\n",errno);
return -;
} 其中参数rb为:struct v4l2_requestbuffers:
struct v4l2_requestbuffers
{
__u32 count;
enum v4l2_buf_type type;
enum v4l2_memory memory;
__u32 reserved[];
};
type 字段描述的是完成的I/O操作的类型。通常它的值要么是视频获得设备的V4L2_BUF_TYPE_VIDEO_CAPTURE,要么是输出设备的V4L2_BUF_TYPE_VIDEO_OUTPUT struct v4l2_memory:
enum v4l2_memory {
V4L2_MEMORY_MMAP = ,
V4L2_MEMORY_USERPTR = ,
V4L2_MEMORY_OVERLAY = ,
}; 想要使用内存映谢的缓冲区,它将会把memory字段置为V4L2_MEMORY_MMAP,count置为它想要使用的缓冲区的数目。 顺便看看USB TO Serail:
Device Drivers --->[*] USB support ---> <M> USB Serial Converter support ---> <M> USB Prolific Single Port SerialDriver USB Prolific Single Port Serial Driver是指出支持pl2303芯片的USB 2serial.
pl2303.ko USB Serial Converter support是基础driver. 对应usbserial.ko 注1:ioctl中常用的cmd. 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。
VIDIOC_G_PARM :得到Stream信息。如帧数等。
VIDIOC_S_PARM:设置Stream信息。如帧数等。 注2:
如何判断某ioctl cmd所用参数类型:
例如:
ioctl-cmd: VIDIOC_QUERYCAP.
它的返回参数类型ioctl(fd, cmd, 参数)。
首先想到的是从kernel Sourcev4l2_ioctl.c中看。但这比较麻烦,又个简单办法:可以在video2dev.h中看到:
#define VIDIOC_QUERYCAP _IOR ('V', 0, struct v4l2_capability)
即使用cmd为 VIDIOC_QUERYCAP 时,参数为structv4l2_capability V4L2是V4L的升级版本,linux下视频设备程序提供了一套接口规范。 常用的结构体在内核目录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 //视频制式 V4L2采用流水线的方式,操作更简单直观,基本遵循打开视频设备、设置格式、处理数据、关闭设备,更多的具体操作通过ioctl函数来实现。 .打开视频设备
在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备:
// 用非阻塞模式打开摄像头设备
int cameraFd;
cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK, );
// 如果用阻塞模式打开摄像头设备,上述代码变为:
//cameraFd = open("/dev/video0", O_RDWR, 0);
应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。 . 设定属性及采集方式
打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。在Linux编程中,一般使用ioctl函数来对设备的I/O通道进行管理:
int ioctl (int __fd, unsigned long int __request, .../*args*/) ;
在进行V4L2开发中,常用的命令标志符如下(some are optional):
• 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检查当前视频设备支持的标准
在亚洲,一般使用PAL(720X576)制式的摄像头,而欧洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD来检测:
v4l2_std_id std;
do {
ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == - && errno == EAGAIN);
switch (std) {
case V4L2_STD_NTSC:
//……
case V4L2_STD_PAL:
//……
}
2.2 设置视频捕获格式
当检测完视频设备支持的标准后,还需要设定视频捕获格式,结构如下:
struct v4l2_format fmt;
memset ( &fmt, , sizeof(fmt) );
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = ;
fmt.fmt.pix.height = ;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -) {
return -;
} v4l2_format结构如下:
struct v4l2_format {
enum v4l2_buf_type type; //数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
union
{
struct v4l2_pix_format pix;
struct v4l2_window win;
struct v4l2_vbi_format vbi;
__u8 raw_data[];
} fmt;
};
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;
};
2.3 分配内存
接下来可以为视频捕获分配内存:
struct v4l2_requestbuffers req; req.count = BUFFER_COUNT;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, &req) == -) {
return -;
} v4l2_requestbuffers 结构如下:
struct v4l2_requestbuffers {
u32 count;//缓存数量,也就是说在缓存队列里保持多少张照片
enum v4l2_buf_type type; //数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
enum v4l2_memory memory;//V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
u32 reserved[];
};
2.4 获取并记录缓存的物理空间
使用VIDIOC_REQBUFS,我们获取了req.count个缓存,下一步通过调用VIDIOC_QUERYBUF命令来获取这些缓存的地址,然后使用mmap函数转换成应用程序中的绝对地址,最后把这段缓存放入缓存队列: typedef struct VideoBuffer {
void *start;
size_t length;
} VideoBuffer; v4l2_buffer 结构如下:
struct v4l2_buffer {
__u32 index;
enum v4l2_buf_type type;
__u32 bytesused;
__u32 flags;
enum v4l2_field field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
enum v4l2_memory memory;
union {
__u32 offset;
unsigned long userptr;
} m;
__u32 length;
__u32 input;
__u32 reserved;
};
VideoBuffer* buffers = calloc( req.count, sizeof(*buffers) );
struct v4l2_buffer buf; for (numBufs = ; numBufs < req.count; numBufs++)
{
memset( &buf, , sizeof(buf) );
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = numBufs;
// 读取缓存
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -) {
return -;
} buffers[numBufs].length = buf.length;
// 转换成相对地址
buffers[numBufs].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
MAP_SHARED,fd, buf.m.offset); if (buffers[numBufs].start == MAP_FAILED) {
return -;
} // 放入缓存队列
if (ioctl(fd, VIDIOC_QBUF, &buf) == -) {
return -;
}
}
2.5 视频采集方式
操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是供内核访问的代码和数据,用户不能直接访问。v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地址。
一共有三种视频采集方式:使用read/write方式;内存映射方式和用户指针模式。
read、write方式,在用户空间和内核空间不断拷贝数据,占用了大量用户内存空间,效率不高。
内存映射方式:把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。上面的mmap函数就是使用这种方式。
用户指针模式:内存片段由应用程序自己分配。这点需要在v4l2_requestbuffers里将memory字段设置成V4L2_MEMORY_USERPTR。
2.6 处理采集数据
V4L2有一个数据缓存,存放req.count数量的缓存数据。数据缓存采用FIFO的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的视频数据缓存送出,并重新采集一张视频数据。这个过程需要用到两个ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF:
struct v4l2_buffer buf;
memset(&buf,,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=;
//读取缓存
if (ioctl(cameraFd, VIDIOC_DQBUF, &buf) == -)
{
return -;
}
//…………视频处理算法
//重新放入缓存队列
if (ioctl(cameraFd, VIDIOC_QBUF, &buf) == -) {
return -;
} . 关闭视频设备
使用close函数关闭一个视频设备
close(cameraFd)
如果使用mmap,最后还需要使用munmap方法。 下面是damo程序(经过实际验证,修改了网上的例程的错误)
-----------------------------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <fcntl.h>
#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 <asm/types.h>
#include <linux/videodev2.h> #define CAMERA_DEVICE "/dev/video0"
#define CAPTURE_FILE "frame.jpg" #define VIDEO_WIDTH 640
#define VIDEO_HEIGHT 480
#define VIDEO_FORMAT V4L2_PIX_FMT_YUYV
#define BUFFER_COUNT 4 typedef struct VideoBuffer {
void *start;
size_t length;
} VideoBuffer; int main()
{
int i, ret; // 打开设备
int fd;
fd = open(CAMERA_DEVICE, O_RDWR, );
if (fd < ) {
printf("Open %s failed\n", CAMERA_DEVICE);
return -;
} // 获取驱动信息
struct v4l2_capability cap;
ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (ret < ) {
printf("VIDIOC_QUERYCAP failed (%d)\n", ret);
return ret;
}
// Print capability infomations
printf("Capability Informations:\n");
printf(" driver: %s\n", cap.driver);
printf(" card: %s\n", cap.card);
printf(" bus_info: %s\n", cap.bus_info);
printf(" version: %08X\n", cap.version);
printf(" capabilities: %08X\n", cap.capabilities); // 设置视频格式
struct v4l2_format fmt;
memset(&fmt, , sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = VIDEO_WIDTH;
fmt.fmt.pix.height = VIDEO_HEIGHT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
if (ret < ) {
printf("VIDIOC_S_FMT failed (%d)\n", ret);
return ret;
} // 获取视频格式
ret = ioctl(fd, VIDIOC_G_FMT, &fmt);
if (ret < ) {
printf("VIDIOC_G_FMT failed (%d)\n", ret);
return ret;
}
// Print Stream Format
printf("Stream Format Informations:\n");
printf(" type: %d\n", fmt.type);
printf(" width: %d\n", fmt.fmt.pix.width);
printf(" height: %d\n", fmt.fmt.pix.height);
char fmtstr[];
memset(fmtstr, , );
memcpy(fmtstr, &fmt.fmt.pix.pixelformat, );
printf(" pixelformat: %s\n", fmtstr);
printf(" field: %d\n", fmt.fmt.pix.field);
printf(" bytesperline: %d\n", fmt.fmt.pix.bytesperline);
printf(" sizeimage: %d\n", fmt.fmt.pix.sizeimage);
printf(" colorspace: %d\n", fmt.fmt.pix.colorspace);
printf(" priv: %d\n", fmt.fmt.pix.priv);
printf(" raw_date: %s\n", fmt.fmt.raw_data); // 请求分配内存
struct v4l2_requestbuffers reqbuf; reqbuf.count = BUFFER_COUNT;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd , VIDIOC_REQBUFS, &reqbuf);
if(ret < ) {
printf("VIDIOC_REQBUFS failed (%d)\n", ret);
return ret;
} // 获取空间
VideoBuffer* buffers = calloc( reqbuf.count, sizeof(*buffers) );
struct v4l2_buffer buf; for (i = ; i < reqbuf.count; i++)
{
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd , VIDIOC_QUERYBUF, &buf);
if(ret < ) {
printf("VIDIOC_QUERYBUF (%d) failed (%d)\n", i, ret);
return ret;
} // mmap buffer
framebuf[i].length = buf.length;
framebuf[i].start = (char *) mmap(, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
if (framebuf[i].start == MAP_FAILED) {
printf("mmap (%d) failed: %s\n", i, strerror(errno));
return -;
} // Queen buffer
ret = ioctl(fd , VIDIOC_QBUF, &buf);
if (ret < ) {
printf("VIDIOC_QBUF (%d) failed (%d)\n", i, ret);
return -;
} printf("Frame buffer %d: address=0x%x, length=%d\n", i, (unsigned int)framebuf[i].start, framebuf[i].length);
} // 开始录制
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if (ret < ) {
printf("VIDIOC_STREAMON failed (%d)\n", ret);
return ret;
} // Get frame
ret = ioctl(fd, VIDIOC_DQBUF, &buf);
if (ret < ) {
printf("VIDIOC_DQBUF failed (%d)\n", ret);
return ret;
} // Process the frame
FILE *fp = fopen(CAPTURE_FILE, "wb");
if (fp < ) {
printf("open frame data file failed\n");
return -;
}
fwrite(framebuf[buf.index].start, , buf.length, fp);
fclose(fp);
printf("Capture one frame saved in %s\n", CAPTURE_FILE); // Re-queen buffer
ret = ioctl(fd, VIDIOC_QBUF, &buf);
if (ret < ) {
printf("VIDIOC_QBUF failed (%d)\n", ret);
return ret;
} // Release the resource
for (i=; i< ; i++)
{
munmap(framebuf[i].start, framebuf[i].length);
} close(fd);
printf("Camera test Done.\n");
return ;
}
----------------------------------------------------------------------------------------------------------- 附件:
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *start, size_t length);
参数说明:
——start:映射区的开始地址。
——length:映射区的长度。
——prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
   —PROT_EXEC //页内容可以被执行
   —PROT_READ //页内容可以被读取
   —PROT_WRITE //页可以被写入
—PROT_NONE //页不可访问
——flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
  —MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
  —MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
  —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:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-。
——offset:被映射对象内容的起点。 返回值:
成功执行时,mmap()返回被映射区的指针,munmap()返回0。
失败时,mmap()返回MAP_FAILED[其值为(void *)-],munmap返回-。errno被设为以下的某个值。

嵌入式Linux下Camera编程--V4L2【转】的更多相关文章

  1. 【转】基于Qt, TUIO和TSLIB的嵌入式Linux下的多点触摸设计

    这个教程描述了在嵌入式linux下使用Qt如何设置一个支持多点触摸和单点触摸的输入系统.这里假定你已经有了对应的驱动程序,驱动可以从触摸屏的厂商那里获得或者使用一个linux 内核源码中已经存在的驱动 ...

  2. 嵌入式linux的网络编程(1)--TCP/IP协议概述

    嵌入式linux的网络编程(1)--TCP/IP协议概述 1.OSI参考模型及TCP/IP参考模型 通信协议用于协调不同网络设备之间的信息交换,它们建立了设备之间互相识别的信息机制.大家一定都听说过著 ...

  3. 嵌入式 Linux下永久生效环境变量bashrc

    嵌入式 Linux下永久生效环境变量bashrc 1) .bashrc文件 在linux系统普通用户目录(cd /home/xxx)或root用户目录(cd /root)下,用指令ls -al可以看到 ...

  4. 嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误

    嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误 2015-05-27 14:19 184人阅读 评论(0) 收藏 举报  分类: 嵌入式(928)  一般察看函数运行时堆栈的 ...

  5. linux 下 poll 编程

    poll 与 select 很类似,都是对描述符进行遍历,查看是否有描述符就绪.如果有就返回就绪文件描述符的个数将.poll 函数如下: #include <poll.h> int pol ...

  6. Linux下Socket编程的端口问题( Bind error: Address already in use )

    Linux下Socket编程的端口问题( Bind error: Address already in use ) 在进行linux网络编程时,每次修改了源代码并再次编译运行时,常遇到下面的地使用错误 ...

  7. Linux 下IOport编程訪问

    曾经写的一篇笔记.偶尔翻出来了,放在这里做个纪念 Linux 下IOport编程訪问 这里记录的方法是在用户态訪问IOport,不涉及驱动程序的编写. 首先要包括头文件 /usr/include/as ...

  8. linux下socket编程实例

    linux下socket编程实例一.基本socket函数Linux系统是通过提供套接字(socket)来进行网络编程的.网络的socket数据传输是一种特殊的I/O,socket也是一种文件描述符.s ...

  9. Linux 下shell 编程学习脚手架

    linux body { font-family: Helvetica, arial, sans-serif; font-size: 14px; line-height: 1.6; padding-t ...

随机推荐

  1. sql查询平均下单时间

    SQL查询订单平均审核时长 今天在写一个sql,需求是算一个订单在执行状态中的各个节点的时长 比如在订单中,状态0为开始接单,状态3为已经审核,那么现在需要计算每个客服的平均审核时长 像图中所示:这个 ...

  2. Partitioning by Palindromes UVA - 11584 简单dp

    题目:题目链接 思路:预处理出l到r为回文串的子串,然后如果j到i为回文串,dp[i] = min(dp[i], dp[j] + 1) AC代码: #include <iostream> ...

  3. POJ:2010-Moo University - Financial Aid

    Moo University - Financial Aid Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 10894 Acce ...

  4. 云心出岫——Splay Tree

    (多图预警!!!建议在WI-FI下观看) 之前我们谈论过AVL树,这是一种典型适度平衡的二叉搜索树,成立条件是保持平衡因子在[-1,1]的范围内,这个条件已经是针对理想平衡做出的一个妥协了,但依然显得 ...

  5. Java语言基础---转义符

    转义符 转义符使用“\”表示.常用转义符如下: 1.‘\n’回车 2.‘\t’制表位字符,一个表示向右跳8-10个字符 3.‘\\’表示’\’ 4.‘\’’表示单引号 5.‘\’’’表示双引号 6.‘ ...

  6. MySQL权限管理创建帐户

    权限管理 1.创建账号 # 本地账号 create user 'egon1'@'localhost' identified by '123'; # mysql -uegon1 -p123 # 远程帐号 ...

  7. 恢复误删除表黑科技之relay log大法(续)

      Preface       I've stuck twice in my previous experiments in backing up dropped tables.I am still ...

  8. 孤荷凌寒自学python第五十八天成功使用python来连接上远端MongoDb数据库

    孤荷凌寒自学python第五十八天成功使用python来连接上远端MongoDb数据库 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第四天.今天的感觉是,mongoDB数据 ...

  9. packstack测试环境安装heat

    虚机all in one环境测试安装heat [root@armstrong ~]# tmux at -t mysql MariaDB [(none)]> CREATE DATABASE hea ...

  10. J2EE的十三个技术——EJB之概述

    含义: 企业级的JavaBeans(Enterprise JavaBean),其设计目标是部署分布式应用程序. EJB是J2EE的一部分,称为Java企业Bean,它把使用Java开发的服务器组件的部 ...