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函数来完成。
1.翻开视频设备
在V4L2中,视频设备被看做一个文件。运用open函数翻开这个设备:
// 用非堵塞形式翻开摄像头设备
int cameraFd;
cameraFd = open(“/dev/video0”, O_RDWR | O_NONBLOCK, 0);
// 假如用堵塞形式翻开摄像头设备,上述代码变为:
//cameraFd = open(“/dev/video0”, O_RDWR, 0);
运用程序能够运用堵塞形式或非堵塞形式翻开视频设备,假如运用非堵塞形式调用视频设备,即便没有捕获到信息,驱动仍旧会把缓存(DQBUFF)里的东西回来给运用程序。
2. 设定特点及收集办法
翻开视频设备后,能够设置该视频设备的特点,例如裁剪、缩放等。这一步是可选的。在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。
2.1查看当时视频设备支撑的规范
在亚洲,一般运用PAL(720X576)制式的摄像头,而欧洲一般运用NTSC(720X480),运用VIDIOC_QUERYSTD来检测:
v4l2_std_id std;
do {
ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno == EAGAIN);
switch (std) {
case V4L2_STD_NTSC:
//……
case V4L2_STD_PAL:
//……
}
2.2 设置视频捕获格局
当检测完视频设备支撑的规范后,还需求设定视频捕获格局,结构如下:
struct v4l2_format fmt;
memset ( &fmt, 0, sizeof(fmt) );
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 720;
fmt.fmt.pix.height = 576;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
return -1;
}
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[200];
} 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) == -1) {
return -1;
}
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];
};
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 = 0; numBufs 《 req.count; numBufs++)
{
memset( &buf, 0, sizeof(buf) );
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = numBufs;
// 读取缓存
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
return -1;
}
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 -1;
}
// 放入缓存行列
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return -1;
}
}
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,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=0;
//读取缓存
if (ioctl(cameraFd, VIDIOC_DQBUF, &buf) == -1)
{
return -1;
}
//…………视频处理算法
//从头放入缓存行列
if (ioctl(cameraFd, VIDIOC_QBUF, &buf) == -1) {
return -1;
}
3. 封闭视频设备
运用close函数封闭一个视频设备
close(cameraFd)
假如运用mmap,最终还需求运用munmap办法。
下面是damo程序(经过实践验证,修正了网上的例程的过错)
———————————————————————————————————–
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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, 0);
if (fd 《 0) {
printf(“Open %s failed\n”, CAMERA_DEVICE);
return -1;
}
// 获取驱动信息
struct v4l2_capability cap;
ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (ret 《 0) {
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, 0, 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 《 0) {
printf(“VIDIOC_S_FMT failed (%d)\n”, ret);
return ret;
}
// 获取视频格局
ret = ioctl(fd, VIDIOC_G_FMT, &fmt);
if (ret 《 0) {
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[8];
memset(fmtstr, 0, 8);
memcpy(fmtstr, &fmt.fmt.pix.pixelformat, 4);
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 《 0) {
printf(“VIDIOC_REQBUFS failed (%d)\n”, ret);
return ret;
}
// 获取空间
VideoBuffer* buffers = calloc( reqbuf.count, sizeof(*buffers) );
struct v4l2_buffer buf;
for (i = 0; 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 《 0) {
printf(“VIDIOC_QUERYBUF (%d) failed (%d)\n”, i, ret);
return ret;
}
// mmap buffer
framebuf[i].length = buf.length;
framebuf[i].start = (char *) mmap(0, 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 -1;
}
// Queen buffer
ret = ioctl(fd , VIDIOC_QBUF, &buf);
if (ret 《 0) {
printf(“VIDIOC_QBUF (%d) failed (%d)\n”, i, ret);
return -1;
}
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 《 0) {
printf(“VIDIOC_STREAMON failed (%d)\n”, ret);
return ret;
}
// Get frame
ret = ioctl(fd, VIDIOC_DQBUF, &buf);
if (ret 《 0) {
printf(“VIDIOC_DQBUF failed (%d)\n”, ret);
return ret;
}
// Process the frame
FILE *fp = fopen(CAPTURE_FILE, “wb”);
if (fp 《 0) {
printf(“open frame data file failed\n”);
return -1;
}
fwrite(framebuf[buf.index].start, 1, 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 《 0) {
printf(“VIDIOC_QBUF failed (%d)\n”, ret);
return ret;
}
// Release the resource
for (i=0; i《 4; i++)
{
munmap(framebuf[i].start, framebuf[i].length);
}
close(fd);
printf(“Camera test Done.\n”);
return 0;
}
———————————————————————————————————–
附件:
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被设定,为了兼容问题,其值应为-1。
——offset:被映射目标内容的起点。
回来值:
成功履行时,mmap()回来被映射区的指针,munmap()回来0。
失利时,mmap()回来MAP_FAILED[其值为(void *)-1],munmap回来-1。errno被设为以下的某个值。