基于V4L2的视频驱动开发.docx
- 文档编号:24092707
- 上传时间:2023-05-24
- 格式:DOCX
- 页数:28
- 大小:265.45KB
基于V4L2的视频驱动开发.docx
《基于V4L2的视频驱动开发.docx》由会员分享,可在线阅读,更多相关《基于V4L2的视频驱动开发.docx(28页珍藏版)》请在冰豆网上搜索。
基于V4L2的视频驱动开发
基于V4L2的视频驱动开发
(1)
华清远见刘洪涛
编写基于V4L2视频驱动主要涉及到以下几个知识点:
● 摄像头方面的知识
要了解选用的摄像头的特性,包括访问控制方法、各种参数的配置方法、信号输出类型等。
● Camera解码器、控制器
如果摄像头是模拟量输出的,要熟悉解码器的配置。
最后数字视频信号进入camera控制器后,还要熟悉camera控制器的操作。
● V4L2的API和数据结构
编写驱动前要熟悉应用程序访问V4L2的方法及设计到的数据结构。
● V4L2的驱动架构
最后编写出符合V4L2规范的视频驱动。
本文介绍基于S3C2440硬件平台的V4L2视频驱动开发。
摄像头采用OmniVision公司的OV9650和OV9655。
主要包含以下几个方面的内容:
● 视频驱动的整体驱动框架
● S3C2440camera控制器+ov9650(ov9655)
● V4L2API及数据结构
● V4L2驱动框架
● ov9650(ov9655)+s3c2440+V4L2实例
一、 视频驱动的整体框架
视频驱动的整体框架见下图:
二、S3C2440camera控制器+ov9650(ov9655)
(1)S3C2440camera控制器介绍
S3C2440支持ITU-RBT601/656格式的数字图像输入,支持的2个通道的DMA,Preview通道和Codec通道,参见下图。
Preview通道可以将YCbCr4:
2:
2格式的图像转换为RGB(16bit或24bit)格式的数据,并存放于为PreviewDMA分配的内存中,最大分辨率为640*480。
主要用于本地液晶屏显示。
如果将PreviewDMA的内存和Framebuffer内存重叠的话,就可以实现采集直接输出到液晶屏上了。
Codec通道可以输出YCbCr4:
2:
0或YCbCr4:
2:
2格式到为CodecDMA分配的内存中。
最大分辨率为4096*4096。
主要用于图像的编解码处理。
上图中的windowcut功能是指在图像可以先做一个裁剪。
通过设置CIWDOFST完成此功能,见下图。
图像进入P、C通道后,各自的scaler单元还可以对其进行缩放、旋转等处理。
S3C2440camera控制器支持乒乓存储。
为了防止采集和输出之间的冲突,采用了乒乓存储方式。
每次采集一帧后,自动转到下一个存储区。
如果你因为内存空间不足,不想使用此功能的话,可以将四个区域设置到同一块空间。
在做图像处理时,需要关注到最后存储区中的图像格式,如codec通道硬件自动把Y、Cb、Cr分离存储。
S3C2440camera控制器LastIRQ功能的使用,也是需要掌握的。
如果处理不好,输出的图像效果会受影响。
控制器会在每个VSYNC下降沿判断ImgCptEn信号等命令。
如果在下降沿发现ImgCptEn信号有效,则产生IRQ中断。
然后才开始一帧图像的真正采集。
而如果在VSYNC下降沿判断到ImgCptEn为低电平且之前LastIRQEn没有使能,则不会产生任何中断,且不会再进行下一帧的采集。
如果你想在ImgCptEn关闭后,一帧采集完后产生一个中断通知你,那么就需要在最后一次中断产生前(stopcapturing后的vysnc下将沿)使能lastirq就可以了。
我在移植linux驱动时就遇到了一个LastIRQ的问题。
现象是输出图像上面总是有一条比其它部分反应慢。
采集运动图像,就能看出现象。
查看代码是因为没有设立lastirq,因为每次如果不在lastirq产生的情况下读取,图像缓冲中的数据是不稳定的,可能照成图像不完整。
修改代码支持lastirq后,问题解决。
Camera控制器时钟设置也是需要注意的,ov9650需要Camera控制器为其提供时钟。
提供给外部摄像头的时钟是由UPLL输出时钟分频得到的。
而CAMIF的时钟是由HCLK提供的。
本例中,提供给ov9650的时钟为24M。
(2)ov9650(ov9655)设置方法
OV9650是OmniVision公司的COMS摄像头,130万像素,支持SXVGA、VGA、QVGA、CIF等图像输出格式。
最大速率在SXVGA时为15fps,在VGA时为30fps。
OV9650摄像头时序如下图:
上图中D[9:
2]用于8-bitYUV或者RGB565/RGB555(D[9]MSB、D[2]LSB)。
D[9:
0]用于10-bitRGB。
本例中使用8-bitYUV模式。
我手边开发板的Camera和S3C2440的接线原理图如下(对应camera中具体的信号名称参见前文的驱动整体架构图)。
注:
GPG12用于PWEN信号
(3)编写ARM测试代码测试camera功能
在Keil环境下编写一个测试代码完成从摄像头采集图像输出到液晶屏。
下面列出程序的流程。
(4)编写测试代码过程中常见的问题
● 摄像头寄存器的配置
因为摄像头有很多寄存器,可能一下无法理解里面所有的配置含义,所以开始时希望得到一份可用的配置。
但往往从别人的测试代码中拿到配置后,仍然无法使用。
我这里列出几个可能的原因:
(1)摄像头中的图像输出格式和你在camera控制器中设置的不一致,同一个摄像头可以设置多种输入格式,如:
YCbYCr或CbYCrY。
(2)图像输出的一些时序和你的camera控制器设置不一致,摄像头可以设置一些时序,如:
图像数据在CAMPCLK的上升沿有效还是下降沿有效。
(3)注意输出图像的格式和Framebuffer控制器的匹配,如字节顺序等问题。
● Ov9650和ov9655的使用区别
这里主要列出两者之间在复位信号上有差别,ov9650是高电平复位,而ov9655是低电平复位。
基于V4L2的视频驱动开发
(2)
华清远见刘洪涛
三、 V4L2API及数据结构
V4L2是V4L的升级版本,为linux下视频设备程序提供了一套接口规范。
包括一套数据结构和底层V4L2驱动接口。
1、常用的结构体在内核目录include/linux/videodev2.h中定义
structv4l2_requestbuffers//申请帧缓冲,对应命令VIDIOC_REQBUFS
structv4l2_capability//视频设备的功能,对应命令VIDIOC_QUERYCAP
structv4l2_input//视频输入信息,对应命令VIDIOC_ENUMINPUT
structv4l2_standard//视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
structv4l2_format//帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
structv4l2_buffer//驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
structv4l2_crop//视频信号矩形边框
v4l2_std_id//视频制式
2、常用的IOCTL接口命令也在include/linux/videodev2.h中定义
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。
3、操作流程
V4L2提供了很多访问接口,你可以根据具体需要选择操作方法。
需要注意的是,很少有驱动完全实现了所有的接口功能。
所以在使用时需要参考驱动源码,或仔细阅读驱动提供者的使用说明。
下面列举出一种操作的流程,供参考。
(1)打开设备文件
intfd=open(Devicename,mode);
Devicename:
/dev/video0、/dev/video1……
Mode:
O_RDWR[|O_NONBLOCK]
如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。
(2)取得设备的capability
structv4l2_capabilitycapability;
intret=ioctl(fd,VIDIOC_QUERYCAP,&capability);
看看设备具有什么功能,比如是否具有视频输入特性。
(3)选择视频输入
structv4l2_inputinput;
……初始化input
intret=ioctl(fd,VIDIOC_QUERYCAP,&input);
一个视频设备可以有多个视频输入。
如果只有一路输入,这个功能可以没有。
(4)检测视频支持的制式
v4l2_std_idstd;
do{
ret=ioctl(fd,VIDIOC_QUERYSTD,&std);
}while(ret==-1&&errno==EAGAIN);
switch(std){
caseV4L2_STD_NTSC:
//……
caseV4L2_STD_PAL:
//……
}
(5)设置视频捕获格式
structv4l2_formatfmt;
fmt.type=V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_UYVY;
fmt.fmt.pix.height=height;
fmt.fmt.pix.width=width;
fmt.fmt.pix.field=V4L2_FIELD_INTERLACED;
ret=ioctl(fd,VIDIOC_S_FMT,&fmt);
if(ret){
perror("VIDIOC_S_FMT/n");
close(fd);
return-1;
}
(6)向驱动申请帧缓存
structv4l2_requestbuffersreq;
if(ioctl(fd,VIDIOC_REQBUFS,&req)==-1){
return-1;
}
v4l2_requestbuffers结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。
多个缓存可以用于建立FIFO,来提高视频采集的效率。
(7)获取每个缓存的信息,并mmap到用户空间
typedefstructVideoBuffer{
void*start;
size_tlength;
}VideoBuffer;
VideoBuffer*buffers=calloc(req.count,sizeof(*buffers));
structv4l2_bufferbuf;
for(numBufs=0;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){//获取到对应index的缓存信息,此处主要利用length信息及offset信息来完成后面的mmap操作。 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; } (8)开始采集视频 intbuf_type=V4L2_BUF_TYPE_VIDEO_CAPTURE; intret=ioctl(fd,VIDIOC_STREAMON,&buf_type); (9)取出FIFO缓存中已经采样的帧缓存 structv4l2_bufferbuf; memset(&buf,0,sizeof(buf)); buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory=V4L2_MEMORY_MMAP; buf.index=0;//此值由下面的ioctl返回 if(ioctl(fd,VIDIOC_DQBUF,&buf)==-1) { return-1; } 根据返回的buf.index找到对应的mmap映射好的缓存,取出视频数据。 (10)将刚刚处理完的缓冲重新入队列尾,这样可以循环采集 if(ioctl(fd,VIDIOC_QBUF,&buf)==-1){ return-1; } (11)停止视频的采集 intret=ioctl(fd,VIDIOC_STREAMOFF,&buf_type); (12)关闭视频设备 close(fd); 四、 V4L2驱动框架 上述流程的各个操作都需要有底层V4L2驱动的支持。 内核中有一些非常完善的例子。 比如: linux-2.6.26内核目录/drivers/media/video//zc301/zc301_core.c中的ZC301视频驱动代码。 上面的V4L2操作流程涉及的功能在其中都有实现。 1、V4L2驱动注册、注销函数 Video核心层(drivers/media/video/videodev.c)提供了注册函数 intvideo_register_device(structvideo_device*vfd,inttype,intnr) video_device: 要构建的核心数据结构 Type: 表示设备类型,此设备号的基地址受此变量的影响 Nr: 如果end-base>nr>0: 次设备号=base(基准值,受type影响)+nr; 否则: 系统自动分配合适的次设备号 具体驱动只需要构建video_device结构,然后调用注册函数既可。 如: zc301_core.c中的 err=video_register_device(cam->v4ldev,VFL_TYPE_GRABBER, video_nr[dev_nr]); Video核心层(drivers/media/video/videodev.c)提供了注销函数 voidvideo_unregister_device(structvideo_device*vfd) 2、structvideo_device的构建 video_device结构包含了视频设备的属性和操作方法。 参见zc301_core.c strcpy(cam->v4ldev->name,"ZC0301[P]PCCamera"); cam->v4ldev->owner=THIS_MODULE; cam->v4ldev->type=VID_TYPE_CAPTURE|VID_TYPE_SCALES; cam->v4ldev->fops=&zc0301_fops; cam->v4ldev->minor=video_nr[dev_nr]; cam->v4ldev->release=video_device_release; video_set_drvdata(cam->v4ldev,cam); 大家发现在这个zc301的驱动中并没有实现structvideo_device中的很多操作函数,如: vidioc_querycap、vidioc_g_fmt_cap等。 主要原因是structfile_operationszc0301_fops中的zc0301_ioctl实现了前面的所有ioctl操作。 所以就不需要在structvideo_device再实现structvideo_device中的那些操作了。 另一种实现方法如下: staticstructvideo_devicecamif_dev= { .name="s3c2440camif", .type=VID_TYPE_CAPTURE|VID_TYPE_SCALES|VID_TYPE_SUBCAPTURE, .fops=&camif_fops, .minor=-1, .release=camif_dev_release, .vidioc_querycap=vidioc_querycap, .vidioc_enum_fmt_cap=vidioc_enum_fmt_cap, .vidioc_g_fmt_cap=vidioc_g_fmt_cap, .vidioc_s_fmt_cap=vidioc_s_fmt_cap, .vidioc_queryctrl=vidioc_queryctrl, .vidioc_g_ctrl=vidioc_g_ctrl, .vidioc_s_ctrl=vidioc_s_ctrl, }; staticstructfile_operationscamif_fops= { .owner=THIS_MODULE, .open=camif_open, .release=camif_release, .read=camif_read, .poll=camif_poll, .ioctl=video_ioctl2,/*V4L2ioctlhandler*/ .mmap=camif_mmap, .llseek=no_llseek, }; 注意: video_ioctl2是videodev.c中是实现的。 video_ioctl2中会根据ioctl不同的cmd来 调用video_device中的操作方法。 3、Video核心层的实现 参见内核/drivers/media/videodev.c (1)注册256个视频设备 staticint__initvideodev_init(void) { intret; if(register_chrdev(VIDEO_MAJOR,VIDEO_NAME,&video_fops)){ return-EIO; } ret=class_register(&video_class); …… } 上面的代码注册了256个视频设备,并注册了video_class类。 video_fops为这256个设备共同的操作方法。 (2)V4L2驱动注册函数的实现 intvideo_register_device(structvideo_device*vfd,inttype,intnr) { inti=0; intbase; intend; intret; char*name_base; switch(type)//根据不同的type确定设备名称、次设备号 { caseVFL_TYPE_GRABBER: base=MINOR_VFL_TYPE_GRABBER_MIN; end=MINOR_VFL_TYPE_GRABBER_MAX+1; name_base="video"; break; caseVFL_TYPE_VTX: base=MINOR_VFL_TYPE_VTX_MIN; end=MINOR_VFL_TYPE_VTX_MAX+1; name_base="vtx"; break; caseVFL_TYPE_VBI: base=MINOR_VFL_TYPE_VBI_MIN; end=MINOR_VFL_TYPE_VBI_MAX+1; name_base="vbi"; break; caseVFL_TYPE_RADIO: base=MINOR_VFL_TYPE_RADIO_MIN; end=MINOR_VFL_TYPE_RADIO_MAX+1; name_base="radio"; break; default: printk(KERN_ERR"%scalledwithunknowntype: %d/n", __func__,type); return-1; } /*计算出次设备号*/ mutex_lock(&videodev_lock); if(nr>=0&&nr /*usetheonethedriveraskedfor*/ i=base+nr; if(NULL! =video_device[i]){ mutex_unlock(&videodev_lock); return-ENFILE; } }else{ /*usefirstfree*/ for(i=base;i
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 基于 V4L2 视频 驱动 开发