前言
这个V4L2的驱动框架,是主要用来配置摄像头的,市面上买到的大多数的免驱摄像头,其实本质都是用的一个UVC协议,而这个V4L2就是Linux下用来驱动UVC设备的一个驱动框架。
本节是基于正点原子IMX6ULL的出厂驱动实现UVC摄像头插入的自动挂载成一个/dev下videox设备的一个配置方法,及后续应用层调用。由于此部分涉及的细节比较多,也是我综合了网上很多的教程,搭配自己的一些理解进行的总结。
查看摄像头支不支持UVC
本环节主要是在Ubuntu下进行,首先执行以下代码安装一个工具库。
sudo apt-get install v4l-utils libv4l-dev -y
在安装完成后,执行以下代码,在插上摄像头以后你会发现你的摄像头被列了出来。如果有,则说明Ubuntu识别到了了你的摄像头。
v4l2-ctl --list-device列出所有摄像头
此时再输入,会列出所有的usb设备,找到你摄像头那一个(可以用插拔前后对比着去找)
lsusb
我的摄像头由于是杂牌,所以没有名字信息,只有一串id:
这里的349c是对应后面可能会用到的idVendor,而0411则对应后面可能会用到的ipProduct。接下来大家可以在http://www.ideasonboard.org/uvc/这个链接去搜一下id,能搜到的就是100%支持UVC且极有可能内核已经帮你写好驱动了。
如果搜不到执行下面步骤:
lsusb -t
这时候有些摄像头已经可能看到参数这里写着支不支持uvc了。一目了然。如果还是没有,再往下一步执行:
lsusb -d 349c:0411 -v|grep "14 Video"
如果显示如上,也大概率支持。
环境搭建
本人用的摄像头是某宝十几块钱买的便宜货,理论上所有UVC设备都能通用。
首先,烧录正点原子的出场驱动后,直接将USB摄像头插入板子上,板子是不会自动匹配为/dev下的videox设备的,而板子默认的video0设备,是imx6ull板载的,并不对应我们插入的摄像头。在插入后他会有这样的一个提示信息:
root@ATK-IMX6U:~# [ 34.084069] usb 1-1.1: new high-speed USB device number 3 using ci_hdrc
很显然,他识别到了有USB设备插入,但并未识别为一个UVC 设备。此时/dev/下是没有video1这个节点的,这个设备被挂载在了/sys/bus/usb下。
我们想要让他实现自动匹配需要做如下修改。
内核配置文件
在内核目录下用make menuconfig打开配置界面,按照如下路径配置:
Device Drivers
[*]Multimedia support
[*]Camer/video grabbers support
[*]Media Controller API
[*]V4L2 sub-device userspace API
[*]Media USB Adapters
[*]USB Video Class(UVC)
[*] UVC input events device support
[*]V4L platform devices
[*]platform camera support
[*]soC camera support
[*]USB support
[*]Support for Host-side USB
[*] USB announce new devices
配置完成以后保存退出,之后他会随你的menuconfig 生成一个.config配置文件,在编译内核时会用。这时候正常来说应该可以运行build.sh脚本进行编译。但如果直接编译会导致我们前面配置的信息全部被覆写。
原因在箭头所指两处,第一处,是清零工程,这个清零工程会把我们目录下所有编译的产物给清空了,包括但不限于.config(注:此时清空的只是menuconfig生成的.config,menuconfig的设置是不会被清空的)。第二句话编译一个配置文件,编译这个配置文件,会生成一个全新的.config不止,而且还会把我们menuconfig的修改项全部擦除回默认值,所以这两句话要先屏蔽。此时编译出来的zImage便是一个可以支持UVC自动加载的内核。
此时一般情况下就可烧录下载了,但还有一种不一般的情况,那就是你和我一样买的是杂牌摄像头。官方并没有预设默认值供你匹配,所以还是有可能匹配不上的。这时候就要进行如下操作。
添加你的摄像头模块到内核(非必须)
我们打开内核下的这个路径:
找到uvc_driver.c这个文件,搜索以下关键词:
static struct usb_device_id uvc_ids[]
这个素组里存放着的就是内核支持的摄像头列表,比较重要的以下三个参数。前两个我在如何看摄像头支不支持UVC的部分已经说明了如何找。第三个参数好像是和摄像头硬件兼容性有关,本人也不是很懂,但可以附上链接,欢迎有懂的大佬也帮我解答一下。
http://www.ideasonboard.org/uvc/faq/
在这里添加完成以后,还需要重新编译烧录一遍内核才可生效。自此,关于UVC摄像头的驱动的环境搭建就已经完成了。
驱动程序的编写
用到的头文件如下
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
查看摄像头支持的采集格式
int main(void)
{
int ret;
//打开设备
int fd = open("/dev/video1",O_RDWR);
if(fd < 0)
{
perror("打开设备失败!");
return -1;
}
//获取摄像头支持的格式
struct v4l2_fmtdesc v4fmt;
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int i = 0;
while(1) //遍历索引,直到获取失败(获取失败前把支持的格式都打印出来了)
{
v4fmt.index = i++;
ret = ioctl(fd,VIDIOC_ENUM_FMT,&v4fmt);
if(ret < 0)
{
perror("获取失败");
break;
}
printf("index=%d\n",v4fmt.index);
printf("flags=%d\n",v4fmt.flags);
printf("description=%s\n",v4fmt.description);
unsigned char *p =(unsigned char *) &v4fmt.pixelformat;
printf("pixelformat=%4s\n",p);
printf("reserved=%d\n",v4fmt.reserved[0]);
}
执行这行代码,主要是看看摄像头都支持什么样的格式输出。用以下指令编译
arm-linux-gnueabihf-gcc video_test.c -o video_test //用交叉编译器
编译完成输出到嵌入式设备上运行 输出结果如下
root@ATK-IMX6U:~/app_user# ./video_test
index=0
flags=0
description=YUV 4:2:2 (YUYV)
pixelformat=YUYV
reserved=0
获取失败: Invalid argument
可以看到,我这里只输出了一种格式,即我这个摄像头只支持YUYV(422)的格式,这关乎我们后面的配置。
设置采样格式
//设置采样格式
struct v4l2_format vfmt; //配置结构体
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//这里很重要,只要是有关摄像头的结构体基本都有这个tyep成员,不配置为视频输入设备后面的设置不会生效。
vfmt.fmt.pix.width = 320; //分辨率
vfmt.fmt.pix.height = 240;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//这里设置采集格式,即通过我们之前获取到的格式设置
ret = ioctl(fd,VIDIOC_S_FMT,&vfmt);//结构体的配置项装载生效
if(ret<0)
{
perror("设置失败");
}
以上便是 设置采样格式的代码,值得注意的是在这里的分辨率,本人的摄像头是支持480*640的。在Ubuntu上也是可以跑这个分辨率,但在IMX6ULL上如果这里设置成480*640,后面的缓冲区就会很大,导致获取图像会卡死,这里暂时未排查出成因。初步推测是内存不够,或者默认缓冲区分配太小,后续会继续优化。
申请内核缓冲区
struct v4l2_requestbuffers reqbuffer; //配置结构体
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 4; //四个缓冲区
reqbuffer.memory = V4L2_MEMORY_MMAP; //采用内存映射方式
ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffer);
if(ret<0)
{
perror("缓冲区设置失败");
}
这里4个缓冲区的作用就有点类似乒乓操作,避免对一个缓冲区同时读写,同时使得画面更流畅。
映射内核缓冲区队列到用户空间
要知道linux下的内核空间和用户空间是相互隔离的,前文申请到的的缓冲区是内核缓冲区,是储存在内核里的,用户态是无法直接访问的,如果想要访问,就需要将其映射到用户空间。
//映射内核缓冲区队列到用户空间
unsigned char *mptr[4];
struct v4l2_buffer mapbuffer;
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for(i=0;i<4;i++)//因为申请了4个缓冲区,所以要映射4个缓冲区
{
printf("i:%d\r\n",i);
mapbuffer.index = i;
ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer);//从内核空间查询一个空间做映射,粗俗点理解就是我后面如果想做映射,要知道一些有关映射的信息(例如映射长读等),这个操作就是调用ioctl区查询这些信息,能查到才能说明能进行映射。
if(ret<0)
{
perror("查询内核空间队列失败");
}
mptr[0]=(unsigned char*)mmap(NULL,mapbuffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
//这一部分是实现映射操作,第一个参数是映射的起始地址传入NULL,就是让内核自己选,第二个是映射长度,上面的查询操作成功后自动填充进这个结构体,第三个是内存保护方式,声明映射区有读写权限,修改数据是否会同步到磁盘空间之类的。第四个是要映射的文件对应的id,这里就是摄像头这个设备,第五个是映射文件偏移量。
之类的
//通知使用完毕,
ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer);//这里是把缓冲区放回给内核
if(ret<0)
{
perror("放回失败");
}
}
这一套的逻辑就有点像,内核有4个缓冲区用来缓冲摄像头数据,我依次向内核申请,每要来一片缓冲区,就在这个缓冲区上做映射,然后得到一个地址值保存起来(映射以后就可以在用户态读写操作),映射完了就把这片缓冲区还给内核。
至于为什么要有映射这个多此一举的操作,个人认为,其一是为了实现内核和用户的隔离(避免用户随意操作内核导致系统崩溃),其二做过FPGA的都知道,很多时候数据的读写都需要一个fifo来进行缓冲,这个地方的映射的作用就有点像用fifo缓冲再去写入,避免了读写冲突,保证数据的完整性。
开始采集
//开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//这个摄像头的类型也要作为传参,但是下面的开始采集没有传入结构体,只需要单独传入一个参数。
ret = ioctl(fd,VIDIOC_STREAMON,&type);//这里可以理解为打开一条数据流,数据流打开以后,内核就会循环在4个缓冲区里填充数据。
if(ret<0)
{
perror("开启采集失败");
}
printf("开启采集\r\n");//这个位置,如果前面分辨率设大了有可能会卡死在这里,原因未知还在排查中。
//从队列提取一帧数据
struct v4l2_buffer readbuffer;//这个地方定义了一个结构体,这个结构体就可以具象为一个缓冲区类型的结构体,因为我要取缓冲区出来,所以对应取出的缓冲区就填入这个容器
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer);//这里是取流,即取出一片缓冲区,至于取出哪一片,由系统决定(系统不会给你一片他正在读写的)
if(ret<0)
{
perror("提取一帧数据失败");
}
FILE *file = fopen("my.jpg","w+");
//mptr[readbuffer.index]//由于我们之前已经对这4片地址做了内存映射了,这时候我只需要知道系统给我的是哪一片我就可以通过映射的地址获取到数据了。
fwrite(mptr[readbuffer.index],readbuffer.length,1,file);//把数据写进一个文件
fclose(file);//关闭文件
//通知内核已经使用完了,这时候会把取的流还回去,意味着内核又可以对这篇缓冲区读写了。
ret = ioctl(fd,VIDIOC_QBUF,&readbuffer);
if(ret<0)
{
perror("放回失败");
}
//停止采集
ret = ioctl(fd,VIDIOC_STREAMOFF,&type);//停止数据流
这里只采集了一次,因为学习是个循序渐进的过程前期更注重框架思维以及环境搭建,所以我这里就暂时实现取一包数据即一张图片的数据。后续再做数据流的采样,即视频。
可以看到我这里已经取出来了一张图片了,但是由于我们是yuv422强行储存为jpg格式的,所以这个图片是打不开的,下一步便是移植一个jpeglib实现把yuv转为rgb打开显示。
jpeglib的移植
分为两部分,第一部分是Ubuntu下的移植,第二部分是嵌入式Linux设备的移植,这部分雷点很多,由于本人也是第一次做这种移植,所以整了七八个小时才整好。
Ubuntu下的移植
Independent JPEG Group去这个网站下载压缩包,当然如果不涉及嵌入式平台的移植你大可用sudo apt-get install libjpeg-dev这个指令去安装。但是如果你要放到嵌入式平台九按照我的方法来。解压完成后打开目录
在这个目录下依次执行
make clean
make
make install
然后就会安装好了,检验是否安装好,很简单
引用一下这些头文件看看会不会报错就行。
嵌入式平台移植(交叉编译器)
这个地方很重要,建议先把之前解压的文件整个删掉重新解压一个新的,避免残留影响你的安装结果。
然后第一步make clean
第二步设置
./configure CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld --host=arm-linux-gnueabihf --prefix=/home/xxx/test_tool/jpeg --exec-prefix=/home/xxx/test_tool/jpeg --enable-shared --enable-static
配置编译器链接文件,以及安装的位置。
由于我们的这个jpeglib要在嵌入式平台跑,所以要全部指定为我们的交叉编译器。
prefix是我们安装的位置,建议选在一个你自己找得到的位置。
没有报错,接下来就是make
也成功了。
最后就是make install了
安装成功以后,会有以下文件
这些文件很重要。
交叉编译
接下来我们就要实现用这个jpeglib库将yuv422转成jpg格式了,转换方法我在网上找到了现成能用的,就不解释原理了直接贴代码
.c:
#include "yuv_to_jpeg.h"
#include <jpeglib.h>
#include <jerror.h>
#define OUTPUT_BUF_SIZE 4096
typedef struct
{
struct jpeg_destination_mgr pub; /* public fields */
JOCTET * buffer; /* start of buffer */
unsigned char *outbuffer;
int outbuffer_size;
unsigned char *outbuffer_cursor;
int *written;
} mjpg_destination_mgr;
typedef mjpg_destination_mgr *mjpg_dest_ptr;
/******************************************************************************
函数功能: 初始化输出的目的地
******************************************************************************/
METHODDEF(void) init_destination(j_compress_ptr cinfo)
{
mjpg_dest_ptr dest = (mjpg_dest_ptr) cinfo->dest;
/* Allocate the output buffer --- it will be released when done with image */
dest->buffer = (JOCTET *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, OUTPUT_BUF_SIZE * sizeof(JOCTET));
*(dest->written) = 0;
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
}
/******************************************************************************
函数功能: 当jpeg缓冲区填满时调用
******************************************************************************/
METHODDEF(boolean) empty_output_buffer(j_compress_ptr cinfo)
{
mjpg_dest_ptr dest = (mjpg_dest_ptr) cinfo->dest;
memcpy(dest->outbuffer_cursor, dest->buffer, OUTPUT_BUF_SIZE);
dest->outbuffer_cursor += OUTPUT_BUF_SIZE;
*(dest->written) += OUTPUT_BUF_SIZE;
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
return TRUE;
}
/******************************************************************************
函数功能:在写入所有数据之后,由jpeg_finish_compress调用。通常需要刷新缓冲区。
******************************************************************************/
METHODDEF(void) term_destination(j_compress_ptr cinfo)
{
mjpg_dest_ptr dest = (mjpg_dest_ptr) cinfo->dest;
size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;
/* Write any data remaining in the buffer */
memcpy(dest->outbuffer_cursor, dest->buffer, datacount);
dest->outbuffer_cursor += datacount;
*(dest->written) += datacount;
}
/******************************************************************************
功能描述:初始化输出流
函数参数:
j_compress_ptr cinfo :保存JPG图像压缩信息的结构体地址
unsigned char *buffer :存放压缩之后的JPG图片的缓冲区首地址
int size :源图像字节总大小
int *written :存放压缩之后的JPG图像字节大小
******************************************************************************/
GLOBAL(void) dest_buffer(j_compress_ptr cinfo, unsigned char *buffer, int size, int *written)
{
mjpg_dest_ptr dest;
if (cinfo->dest == NULL) {
cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(mjpg_destination_mgr));
}
dest = (mjpg_dest_ptr) cinfo->dest;
dest->pub.init_destination = init_destination;
dest->pub.empty_output_buffer = empty_output_buffer;
dest->pub.term_destination = term_destination;
dest->outbuffer = buffer;
dest->outbuffer_size = size;
dest->outbuffer_cursor = buffer;
dest->written = written;
}
/************************************************
功能描述:将YUV格式的数据转为JPG格式。
函数参数:
int Width 源图像宽度
int Height 源图像高度
int size 源图像字节总大小
unsigned char *yuv_buffer :存放YUV源图像数据缓冲区的首地址
unsigned char *jpg_buffer :存放转换之后的JPG格式数据缓冲区首地址
int quality :jpg图像的压缩质量(值越大质量越好,图片就越清晰,占用的内存也就越大)
一般取值范围是: 10 ~ 100 。 填10图片就有些模糊了,一般的JPG图片都是质量都是80。
返回值:压缩之后的JPG图像大小
**************************************************************/
int yuv_to_jpeg(int Width,int Height,int size,unsigned char *yuv_buffer, unsigned char *jpg_buffer, int quality)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1];
unsigned char *line_buffer, *yuyv;
int z;
static int written;
/*1. 解压之前的初始化*/
line_buffer =(unsigned char *)calloc(Width*3,1);
yuyv=yuv_buffer; //得到图像源数据
cinfo.err = jpeg_std_error (&jerr);
jpeg_create_compress (&cinfo);
/* 原版jpeglib库的标准输出初始化函数,只能填文件指针: jpeg_stdio_dest (&cinfo, file); */
/* 修改之后的标准输出初始化函数,将输出指向内存空间*/
dest_buffer(&cinfo, jpg_buffer, size, &written);
cinfo.image_width = Width;
cinfo.image_height =Height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults (&cinfo);
jpeg_set_quality (&cinfo, quality, TRUE);
jpeg_start_compress (&cinfo, TRUE);
/*2. YUV转RGB格式*/
z = 0;
while (cinfo.next_scanline < Height)
{
int x;
unsigned char *ptr = line_buffer;
for (x = 0; x < Width; x++) {
int r, g, b;
int y, u, v;
if (!z)
y = yuyv[0] << 8;
else
y = yuyv[2] << 8;
u = yuyv[1] - 128;
v = yuyv[3] - 128;
r = (y + (359 * v)) >> 8;
g = (y - (88 * u) - (183 * v)) >> 8;
b = (y + (454 * u)) >> 8;
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
if (z++) {
z = 0;
yuyv += 4;
}
}
/*3.进行JPG图像压缩(一行一行压缩)*/
row_pointer[0] = line_buffer;
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
/*4. 释放压缩时占用的内存空间*/
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
free (line_buffer);
/*5. 返回压缩之后JPG图片大小*/
return (written);
}
.h:
#ifndef YUC_TO_JPEG_H
#define YUC_TO_JPEG_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int yuv_to_jpeg(int Width,int Height,int size,unsigned char *yuv_buffer, unsigned char *jpg_buffer, int quality);
#endif
接下来在完成代码的修改以后,我们需要编译出一个app供嵌入式平台运行,这时候编译就非常有技巧。直接传统编译法是会失败的,这里要看如下代码 :
arm-linux-gnueabihf-gcc video_test.c yuv_to_jpeg.c -o app -L/home/xxx/test_tool/jpeg/lib/ -I/home/xxx/test_tool/jpeg/include/ -ljpeg
这里的-L指定的是库文件
-I指定的是头文件
由于这个jepglib是外部库,不在交叉编译器内,我们引用到了就需要指定一个地址给他
-ljpeg是库的名字
这样才能编译成功,少一个都失败
编译成功了(如果报一堆库文件里面的函数找不到定义啥的,就重启一下电脑,我之前也是这样,重启了就好了) 。
把app搞到开发板上,此时还没完,你直接运行是运行不了的,因为你开发板上并没有jpeglib的环境。
开发板上移植jpeglib
在交叉编译出来的库文件里,找到lib文件夹下的文件,全部复制粘贴到开发板上的/lib文件下。其他可以不移过去,但是lib文件里的必须移动。这时候再去运行./app就可以运行了。运行完把照片弄回Ubuntu下打开查看一下。
可以看到,采集成功并显示出来了,移植非常成功。 下一步便是要实现视频流的采集显示了。
代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <jpeglib.h>
#include <jerror.h>
#include "yuv_to_jpeg.h"
int main(void)
{
struct jpeg_error_mgr jerr;
struct jpeg_decompress_struct cinfo;
cinfo.err=0;
int ret;
//打开设备
int fd = open("/dev/video1",O_RDWR);
if(fd < 0)
{
perror("打开设备失败!");
return -1;
}
//获取摄像头支持的格式
struct v4l2_fmtdesc v4fmt;
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int i = 0;
while(1)
{
v4fmt.index = i++;
ret = ioctl(fd,VIDIOC_ENUM_FMT,&v4fmt);
if(ret < 0)
{
perror("获取失败");
break;
}
printf("index=%d\n",v4fmt.index);
printf("flags=%d\n",v4fmt.flags);
printf("description=%s\n",v4fmt.description);
unsigned char *p =(unsigned char *) &v4fmt.pixelformat;
printf("pixelformat=%4s\n",p);
printf("reserved=%d\n",v4fmt.reserved[0]);
}
//设置采样格式
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vfmt.fmt.pix.width = 320;
vfmt.fmt.pix.height = 240;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
ret = ioctl(fd,VIDIOC_S_FMT,&vfmt);
if(ret<0)
{
perror("设置失败");
}
//申请内核缓冲区队列
struct v4l2_requestbuffers reqbuffer;
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 4; //四个缓冲区
reqbuffer.memory = V4L2_MEMORY_MMAP; //采用内存映射方式
ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffer);
if(ret<0)
{
perror("缓冲区设置失败");
}
//映射内核缓冲区队列到用户空间
unsigned char *mptr[4];
unsigned int size[4];
struct v4l2_buffer mapbuffer;
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for(i=0;i<4;i++)
{
printf("i:%d\r\n",i);
mapbuffer.index = i;
ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer);//从内核空间查询一个空间做映射
if(ret<0)
{
perror("查询内核空间队列失败");
}
mptr[i]=(unsigned char*)mmap(NULL,mapbuffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
size[i]=mapbuffer.length;
//通知使用完毕
ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer);
if(ret<0)
{
perror("放回失败");
}
}
//开始采集
int jpg_size;
unsigned char *jpg_p=malloc(240*320*3);
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_STREAMON,&type);
if(ret<0)
{
perror("开启采集失败");
}
printf("开启采集\r\n");
//从队列提取一帧数据,转JPG
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer);
if(ret<0)
{
perror("提取一帧数据失败");
}
jpg_size = yuv_to_jpeg(320,240,240*320*3,mptr[readbuffer.index],jpg_p,80);
FILE *file = fopen("my.jpg","w+");
//mptr[readbuffer.index]
fwrite(jpg_p,1,jpg_size,file);
fclose(file);
//通知内核已经使用完了
ret = ioctl(fd,VIDIOC_QBUF,&readbuffer);
if(ret<0)
{
perror("放回失败");
}
//停止采集
ret = ioctl(fd,VIDIOC_STREAMOFF,&type);
//关闭设备
close(fd);
//释放映射
for(i=0;i<4;i++)
{
munmap(mptr[i],size[i]);
}
return 0;
}
结尾
QT摄像头采集数据已经更新了,大家可以去看看