Linux中开发摄像头,用到V4L2架构,提供了开发摄像头需要用到的参数变量
摄像头常见的采集格式是YUV格式或JPG格式,本文使用的摄像头格式是YUV格式
用V4L2架构中提供的宏定义和结构体实现摄像头循环显示视频流
用V4L2架构中提供的宏定义和结构体实现摄像头循环显示视频流
使用ioctl()函数给摄像头的驱动发送"指令
#include <stropts.h> //变参函数
int ioctl(int fildes, int request, ... /* arg */); /*所有的硬件设备都可以用这个函数实现应用程
序和底层驱动的通信
底层驱动发送数据给应用程序
应用程序发送数据给底层驱动都可以用该函数*/
返回值:成功 0 失败 -1
参数:fildes --》摄像头的文件描述符
request --》你要发送的指令,指令在V4L2的头文件中有清楚地定义
ioctl(camerafd,helloworld)
详细步骤
1.打开摄像头驱动
//摄像头的驱动是谁?--》打开摄像头的驱动
//查看开发板中摄像头驱动是哪个可以通过插拔摄像头看/dev中多了哪个驱动文件判断
camerafd=open("/dev/video7",O_RDWR);
if(camerafd==-1)
{
perror("打开摄像头的驱动失败!\n");
return -1;
}
2.设置摄像头采集格式
ioctl(camerafd,VIDIOC_S_FMT,你要设置的具体格式)
指令:VIDIOC_S_FMT
用到的变量类型:
struct v4l2_format {
__u32 type; //开关,用来选择联合体里面的结构体变量
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
} fmt;
};
struct v4l2_pix_format {
__u32 width; //画面宽
__u32 height; //画面高
__u32 pixelformat; //画面的采集格式
例子:
//设置好摄像头的采集格式--》画面宽,画面高,画面是什么格式。。。。
struct v4l2_format myfmt;
bzero(&myfmt,sizeof(myfmt));
myfmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
myfmt.fmt.pix.width=W;
myfmt.fmt.pix.height=H;
myfmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; //yuv格式
ret=ioctl(camerafd,VIDIOC_S_FMT,&myfmt);
if(ret==-1)
{
perror("设置采集格式失败了!\n");
return -1;
}
3.申请缓冲块
指令:VIDIOC_REQBUFS
用到的变量类型:
struct v4l2_requestbuffers {
count; //你打算申请多少个缓冲块,每个缓冲块存放一帧画面数据,一般申请4-8个左右即可
type; //V4L2_BUF_TYPE_VIDEO_CAPTURE
memory; //V4L2_MEMORY_MMAP
};
例子:
//跟摄像头的驱动申请缓冲区
struct v4l2_requestbuffers mybuf;
bzero(&mybuf,sizeof(mybuf));
mybuf.count=4;
mybuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; //与上一步的类型一致
mybuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_REQBUFS,&mybuf);
if(ret==-1)
{
perror("申请缓冲区失败!\n");
return -1;
}
4.分配缓冲块,映射缓冲块首地址
指令:VIDIOC_QUERYBUF
用到的变量类型:
struct v4l2_buffer
{
index; //你刚才申请的缓冲块的索引,从0开始
type; //V4L2_BUF_TYPE_VIDEO_CAPTURE
memory; //V4L2_MEMORY_MMAP
union
{
offset; //很重要,表示分配给你的缓冲块的地址偏移
}m;
length; //分配给你的缓冲块大小
}
封装结构体
struct xxxx
{
void *start; //保存映射得到的缓冲块首地址
int somelength; //保存缓冲块大小
};
struct v4l2_buffer mybuf;
for(int i=0; i<4; i++)
{
mybuf.index=i;
ioctl()
//顺便映射得到每个缓冲块的首地址
当前缓冲块首地址 =mmap(NULL,mybuf.length,PROT_READ|PROT_WRITE,MAP_SHARED,camerafd,mybuf.m.offset);
}
先申请入队,然后再启动摄像头采集
入队指令:VIDIOC_QBUF
例子:
//定义结构体数组,存放4个缓冲区的信息
struct bufmsg array[4];
//分配刚才你申请的4个缓冲区
struct v4l2_buffer otherbuf;
for(i=0; i<4; i++)
{
bzero(&otherbuf,sizeof(otherbuf));
otherbuf.index=i;
otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
otherbuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_QUERYBUF,&otherbuf);
if(ret==-1)
{
perror("分配缓冲区失败!\n");
return -1;
}
//顺便映射得到4个缓冲块的首地址(方便后面取出画面数据)
array[i].somelen=otherbuf.length;
array[i].start=mmap(NULL,otherbuf.length,PROT_READ|PROT_WRITE,MAP_SHARED,
camerafd,otherbuf.m.offset);
if(array[i].start==NULL)
{
perror("映射缓冲区首地址失败!\n");
return -1;
}
//立马申请入队--》下一步摄像头立马就要启动采集了
ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
if(ret==-1)
{
perror("入队失败!\n");
return -1;
}
}
5.启动摄像头采集画面数据
指令:VIDIOC_STREAMON
用到的变量类型:
enum v4l2_buf_type
{
V4L2_BUF_TYPE_VIDEO_CAPTURE
}
例子:
//启动摄像头采集
enum v4l2_buf_type mytype;
mytype=V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret=ioctl(camerafd,VIDIOC_STREAMON,&mytype);
if(ret==-1)
{
perror("启动摄像头失败!\n");
return -1;
}
6.循环出队入队,显示视频流(可以此处截取摄像画面)
概念:出队指的就是把画面数据从映射得到的缓冲区里面取出来
入队指的就是把画面数据自动存放到刚才映射的缓冲区里面
入队指令:VIDIOC_QBUF
出队指令:VIDIOC_DQBUF
用到的变量类型:
struct v4l2_buffer
建议:把显示画面数据的代码放在出队和入队之间
例子:
char rgbbuf[W*H*3];
fd_set myset;
//循环出队,入队显示视频流
while(1)
{
FD_ZERO(&myset);
FD_SET(camerafd,&myset); //监测摄像头
//用多路复用监测摄像头是否有数据可读,有数据可读我才来出队
ret=select(camerafd+1,&myset,NULL,NULL,NULL);
if(ret>0)
{
for(i=0; i<4; i++)
{
bzero(&otherbuf,sizeof(otherbuf));
otherbuf.index=i;
otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
otherbuf.memory=V4L2_MEMORY_MMAP;
//出队
ret=ioctl(camerafd,VIDIOC_DQBUF,&otherbuf);
if(ret==-1)
{
perror("出队失败!\n");
return -1;
}
//把出队的画面数据保存成jpeg图片
//array[i].start里面保存的就是一张张画面数据
//如果想在屏幕上显示实时的画面,需要将画面数据转换成ARGB格式,然后使用memcpy拷贝画
// 面数据到液晶屏的映射内存中,并减少sleep的时间
/*
for(j=0; j<H; j++)
memcpy(lcdmem+800*j,&argbbuf[W*j],W*4);
*/
//画面数据是YUV格式,jpeg图片要求是RGB数据压缩得到的
//把YUV格式转换成RGB格式
allyuvtorgb(array[i].start,rgbbuf);
//把刚才转换得到的RGB数据压缩成jpeg图片
sprintf(jpgname,"%d.jpg",n++);
rgbtojpg(rgbbuf,jpgname);
sleep(1);
//入队
ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
if(ret==-1)
{
perror("入队失败!\n");
return -1;
}
}
}
}
7.关闭摄像头
指令:VIDIOC_STREAMOFF
用到的变量类型:
enum v4l2_buf_type
{
V4L2_BUF_TYPE_VIDEO_CAPTURE
}
例子:
ret=ioctl(camerafd,VIDIOC_STREAMOFF,&mytype);
if(ret==-1)
{
perror("关闭摄像头失败了!\n");
return -1;
}
//收尾工作
//解除映射
for(i=0; i<4; i++)
munmap(array[i].start,array[i].len);
munmap(lcdmem,800*480*4);
close(camerafd);
close(lcdfd);
其他功能函数
//封装函数把YUV转成RGB
int yuvtorgb(int y,int u,int v)
{
int r,g,b;
int pix;
r=y+1.4075*(v-128);
g=y-0.3455*(u-128)-0.7169*(v-128);
b=y+1.779*(u-128);
//修正计算结果
if(r>255)
r=255;
if(g>255)
g=255;
if(b>255)
b=255;
if(r<0)
r=0;
if(g<0)
g=0;
if(b<0)
b=0;
//因为lcd要求是ARGB,所以我们顺便在这里把RGB转成ARGB,方便后续使用
pix=0x00<<24|r<<16|g<<8|b;
return pix;
}
//封装函数把一张完整画面的YUYV数据全部转换成RGB数据
int allyuvtorgb(char *yuvdata,char *rgb)
{
/*
yuvdata[0] -->Y
yuvdata[1] -->U
yuvdata[2] -->Y
yuvdata[3] -->V
*/
int i,j;
int pix;
char *p;
//有W*H个像素点
for(i=0,j=0; i<W*H*3; i+=6,j+=4) // W*H*3/6 次数
{
pix=yuvtorgb(yuvdata[j],yuvdata[j+1],yuvdata[j+3]);
p=(char *)&pix;
//根据学习的C语言指针运算规则
//p+0-->B p+1-->G p+2-->R p+3 -->A
rgb[i]=*(p+2);
rgb[i+1]=*(p+1);
rgb[i+2]=*(p+0);
pix=yuvtorgb(yuvdata[j+2],yuvdata[j+1],yuvdata[j+3]);
p=(char *)&pix;
rgb[i+3]=*(p+2);
rgb[i+4]=*(p+1);
rgb[i+5]=*(p+0);
}
return 0;
}
//YUV图片数据转换成开发板使用的ARGB格式
//
nt yuyvtoargb(char *yuyvdata,int *argbdata)
{
int i,j;
/*
循环究竟要循环多少次呢?--》你设置的画面大小(W*H个像素点)决定了循环的次数
每一轮循环可以得到2个像素点
总共的像素点是W*H个
得出结论:循环的次数应该是(W*H)/2
*/
for(i=0,j=0; i<(W*H); i+=2,j+=4)
{
argbdata[i]=yuvtorgb(yuyvdata[j],yuyvdata[j+1],yuyvdata[j+3]);
argbdata[i+1]=yuvtorgb(yuyvdata[j+2],yuyvdata[j+1],yuyvdata[j+3]);
}
return 0;
}
//把RGB压缩成jpeg图片
int rgbtojpg(char *rgbdata,char *jpgfile)
{
int i;
//定义压缩结构体和处理错误的结构体并初始化
struct jpeg_compress_struct mycom;
jpeg_create_compress(&mycom);
struct jpeg_error_mgr myerr;
mycom.err=jpeg_std_error(&myerr);
//设置压缩参数(宽,高)
mycom.image_width=W;
mycom.image_height=H;
mycom.in_color_space=JCS_RGB;
mycom.input_components=3;
jpeg_set_defaults(&mycom);
//设置压缩比例(压缩质量)
jpeg_set_quality(&mycom,90,true);
//绑定输出
FILE *myfile=fopen(jpgfile,"w+");
if(myfile==NULL)
{
perror("新建jpg图片失败了!\n");
return -1;
}
jpeg_stdio_dest(&mycom,myfile);
//开始压缩
jpeg_start_compress(&mycom,true);
//JSAMPARRAY类型本质上就是JSAMPROW类型的指针
JSAMPROW row_pointer[1];
//把压缩后的数据写入到空白的jpeg文件
for(i=0; i<H; i++)
{
//把每一行的RGB数据保存到数组中
row_pointer[0]=(JSAMPROW)(rgbdata+i*W*3);
//一次写入一行数据
jpeg_write_scanlines(&mycom,row_pointer,1);
}
//收尾
jpeg_finish_compress(&mycom);
jpeg_destroy_compress(&mycom);
fclose(myfile);
return 0;
}