基于V4L2驱动的摄像头应用程序开发.docx
《基于V4L2驱动的摄像头应用程序开发.docx》由会员分享,可在线阅读,更多相关《基于V4L2驱动的摄像头应用程序开发.docx(9页珍藏版)》请在冰豆网上搜索。
基于V4L2驱动的摄像头应⽤程序开发
这⼏天在学习写⼀个基于v4l2的摄像头应⽤程序,⽹上找了很多资料和程序,终于成功的采集到了图像。
关于vl42的应⽤⽹上很多资料,我根据⾃⼰的理解总结了⼀下⾃⼰的学习成果。
⼀、相关数据结构:
参见include/linux/videodev2.h1、设备能⼒结构
structv4l2_capability{
u8 driver[16];
驱动名*/ /*
u8 card[32];
设备名*/ /*
u8 bus_info[32];设备在系统中/*的位置*/
u32 version;
驱动版本/*号*/
u32 capabilities;
设备能⼒,即/*设备⽀持的操作*/
u32 reserved[4];
保留字段*//*
};
structv4l2_fmtdesc{
u32 in要dex查;询的帧格式序号/*,由应⽤程序设置 */
enumv4l2_buf_typetype;
帧类型,/由*应⽤程序设置 */
u32 flags;
是否为压缩格式*//*
u8 descr格ipt式ion名[3称2];*/ /*
u32 pixel格for式mat;
*/ /*
u32 reserved[4];
};
2、数据格式结构structv4l2_format{
enumv4l2_buf_typetype;
帧类型,由//应⽤程序设置
union{
structv4l2_pix_format pix; /*V4L2_BUF_TYPE,_V视ID频EO设_C备A使PT⽤URE*/
structv4l2_window win; /*V4L2_BUF_TYPE_VIDEO_OVERLAY*/structv4l2_vbi_format vbi; /*V4L2_BUF_TYPE_VBI_CAPTURE*/
structv4l2_sliced_vbi_format sliced;/*V4L2_BUF_TYPE_SLICED_VBI_CAPTURE*/
u8 raw_data[200]; /*user-defined*/
}fmt;
};
3、像素格式结构structv4l2_pix_format{
u32 w宽idt度h; //
u32 height⾼;度 //
u32 pixelformat;
enumv4l2_field field;
帧格式 //
u32 bytesperline; /*forpadding,zeroifunused*/
u32 sizeimage;
enumv4l2_colorspace colorspace;
u32 priv; /*privatedata,dependsonpixelformat*/
};
4、请求缓冲
structv4l2_requestbuffers{
u32 c缓ou存nt数;量 //
enumv4l2_buf_type type;
数据流类型//
enumv4l2_memory memory;
u32 reserved[2];
};
5、数据流类型enumv4l2_buf_type{
V4L2_BUF_TYPE_VIDEO_CAPTURE =1,
V4L2_BUF_TYPE_VIDEO_OUTPUT =2,
V4L2_BUF_TYPE_VIDEO_OVERLAY =3,
V4L2_BUF_TYPE_VBI_CAPTURE =4,
V4L2_BUF_TYPE_VBI_OUTPUT =5,
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE =6,
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT =7,
#if1
/*Experimental*/V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY=8,
#endif
V4L2_BUF_TYPE_PRIVATE =0x80,
};
enumv4l2_memory{
V4L2_MEMORY_MMAP =1,
V4L2_MEMORY_USERPTR =2,
V4L2_MEMORY_OVERLAY =3,
};
命令标字
功能
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
检查当前视频采集设备⽀持的标准
⼆、ioctl命令字
三、成功运⾏的程序:
show.c
#include#include#include#include
#include
#include
#include#include#include#include#include#include#include#include#include
#include#include
#defineCLEAR(x)memset(&(x),0,sizeof(x))
//定义⼀个缓存结构体structbuffer
{
void*start; //空指针size_tlength; //长度⼤⼩
};
#defineCAPTURE_FILE"test.yuyv"
staticchar*dev_name=NULL; //设备名staticintfd=-1; //⽂件序列
staticstructbuffer*buffers=NULL; //缓存对象
staticunsignedintn_buffers=0; //缓存数
staticvoiderrno_exit(constchar*s) //错误输出
{
fprintf(stderr,"%serror%d,%s\n",s,errno,strerror(errno));exit(EXIT_FAILURE);
}
/*
staticvoidprocess_image(constvoid*p,size_tlength) //处理获取图像
{
//fputc('!
',stdout);
//fflush(stdout);
FILE*fp=fopen(CAPTURE_FILE,"a");
if(fp<0)
{
printf("openframedatafilefailed\n");exit(EXIT_FAILURE);
}
fwrite(p,length,1,fp);perror("fwriteerror");
fclose(fp);
fclose(fp);
printf("Captureoneframesavedin%s\n",CAPTURE_FILE);
}
*/
staticvoidread_frame(FILE*fp) //读取数据帧,⽤的都是mmap
{
structv4l2_bufferbuf;unsignedinti;
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
if(-1==ioctl(fd,VIDIOC_DQBUF,&buf)) //读取errno_exit("VIDIOC_DQBUF");
fwrite(buffers[buf.index].start,buf.length,1,fp);
if(-1==ioctl(fd,VIDIOC_QBUF,&buf)) //放回缓存errno_exit("VIDIOC_QBUF");
}
staticvoidmainloop(void)
{
unsignedintcount;count=100;
FILE*fp=fopen(CAPTURE_FILE,"w");
if(fp<0)
{
printf("openframedatafilefailed\n");exit(EXIT_FAILURE);
}
while(count-->0)
{
read_frame(fp);
}
fclose(fp);
printf("Captureoneframesavedin%s\n",CAPTURE_FILE);
}
staticvoidstop_capturing(void)//停⽌捕获帧
{
enumv4l2_buf_typetype;
type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(-1==ioctl(fd,VIDIOC_STREAMOFF,&type))//关闭视频流errno_exit("VIDIOC_STREAMOFF");
printf("\nStreamOffsuccess!
\n");
}
staticvoidstart_capturing(void) //开始捕获帧
{
unsignedinti;
enumv4l2_buf_typetype;
enumv4l2_buf_typetype;
for(i=0;i{
structv4l2_bufferbuf;
CLEAR(buf);
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;buf.index=i;
if(-1==ioctl(fd,VIDIOC_QBUF,&buf)) //放⼊缓存errno_exit("VIDIOC_QBUF");
}
type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(-1==ioctl(fd,VIDIOC_STREAMON,&type)) //打开视频流errno_exit("VIDIOC_STREAMON");
elseprintf("StreamOnsuccess!
\n");
}
staticvoiduninit_device(void) //释放存储空间
{
unsignedinti;
for(i=0;i{
if(-1==munmap(buffers[i].start,buffers[i].length))errno_exit("munmap");
}
free(buffers);
}
staticvoidinit_mmap(void) //初始化读取⽅式
{
structv4l2_requestbuffersreq;
CLEAR(req);
req.count=2;
req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory=V4L2_MEMORY_MMAP;
if(-1==ioctl(fd,VIDIOC_REQBUFS,&req)) //分配内存
{
if(EINVAL==errno)
{
fprintf(stderr,"%sdoesnotsupportmemorymapping\n",dev_name);exit(EXIT_FAILURE);
}
else
errno_exit("VIDIOC_REQBUFS");
}
buffers=calloc(req.count,sizeof(*buffers)); //分配缓存if(!
buffers)
{
fprintf(stderr,"Outofmemory\n");exit(EXIT_FAILURE);
}
for(n_buffers=0;n_buffers{
structv4l2_bufferbuf;
CLEAR(buf);
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;buf.index=n_buffers;
if(-1==ioctl(fd,VIDIOC_QUERYBUF,&buf))errno_exit("VIDIOC_QUERYBUF");
buffers[n_buffers].length=buf.length; //设置映射⽅式为mmap
buffers[n_buffers].start=mmap(NULL,buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buf.m.offset);
if(MAP_FAILED==buffers[n_buffers].start)errno_exit("mmap");
}
}
staticvoidinit_device(void)//初始化设备
{
structv4l2_capabilitycap;structv4l2_formatfmt;unsignedintmin;
if(-1==ioctl(fd,VIDIOC_QUERYCAP,&cap))
{
if(EINVAL==errno)
{
fprintf(stderr,"%sisnoV4L2device\n",dev_name);
exit(EXIT_FAILURE);
}
else
errno_exit("VIDIOC_QUERYCAP");
}
if(!
(cap.capabilities&V4L2_CAP_VIDEO_CAPTURE))
{
fprintf(stderr,"%sisnovideocapturedevice\n",dev_name);exit(EXIT_FAILURE);
}
if(!
(cap.capabilities&V4L2_CAP_STREAMING))
{
fprintf(stderr,"%sdoesnotsupportstreamingi/o\n",dev_name);exit(EXIT_FAILURE);
exit(EXIT_FAILURE);
}
//
CLEAR(fmt); //设置帧格式
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width=640;
fmt.fmt.pix.height=480;
fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;fmt.fmt.pix.field=V4L2_FIELD_INTERLACED;
if(-1==ioctl(fd,VIDIOC_S_FMT,&fmt))errno_exit("VIDIOC_S_FMT");printf("Setframeformatsuccessful!
\n");init_mmap();
}
staticvoidclose_device(void) //关闭设备
{
if(-1==close(fd))errno_exit("close");fd=-1;
printf("Closethedevicesuccessful!
\n");
}
staticvoidopen_device(void) //打开设备
{
fd=open(dev_name,O_RDWR,0);if(-1==fd)
{
fprintf(stderr,"Cannotopen%s\n",dev_name);exit(EXIT_FAILURE);
}
printf("Openthe%ssuccessful\n",dev_name);
}
intmain(intargc,char*argv[])
{
dev_name="/dev/video0";
open_device();init_device();start_capturing();mainloop();stop_capturing();uninit_device();close_device();exit(EXIT_SUCCESS);
return0;
}
//---
Makefile编写:
KERNELDIR?
=/home/student/linux-2.6.32.2show:
show.c
gcc-I$(KERNELDIR)-o$@$^
clean:
rmshow
//
将摄像头直接连接到PC机的usb⼝,会在虚拟机linux下有检测到摄像头的提⽰,表⽰摄像头可以使⽤,然后make,./show就可以运⾏完毕
四、对上述show.c程序的说明
⽹上很多程序会把上述相关数据结构整合到⼀个⾃定义的数据结构中,我认为没有必要,因为核⼼是通过mmap映射后的指针,所以相关数据结构只需要函数中局部变量就⾏,没必要整成全局变量,⽽buffers则需要全局变量,便于使⽤。
show.c源程序中最重要的是buffers指针,在init_mmap()函数中,⾸先通过buffers=calloc(req.count,sizeof(*buffers));使它指向
count个内存空间,然后通过
buffers[n_buffers].length=buf.length; //设置映射⽅式为mmap
buffers[n_buffers].start=mmap(NULL,buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buf.m.offset);
length的⼤⼩是640*480*2个字节,即⼀副图像⼤⼩。
将buffers和驱动层的缓存通过mmap联系起来,将驱动层的缓存映射到应⽤层。
最后通过read_frame()函数将buffers指向的地址数据写道fp中,fp指向"test.yuyv"
程序运⾏后会得到⼀个test.yuyv⽂件,通过samba服务器从虚拟机拷贝到Windows,⽤YUVViewer.exe软件打开,设置⼀下格式为
YUYV,640X480然后play即可。