基于V4L2的视频驱动开发.docx

上传人:b****2 文档编号:24092707 上传时间:2023-05-24 格式:DOCX 页数:28 大小:265.45KB
下载 相关 举报
基于V4L2的视频驱动开发.docx_第1页
第1页 / 共28页
基于V4L2的视频驱动开发.docx_第2页
第2页 / 共28页
基于V4L2的视频驱动开发.docx_第3页
第3页 / 共28页
基于V4L2的视频驱动开发.docx_第4页
第4页 / 共28页
基于V4L2的视频驱动开发.docx_第5页
第5页 / 共28页
点击查看更多>>
下载资源
资源描述

基于V4L2的视频驱动开发.docx

《基于V4L2的视频驱动开发.docx》由会员分享,可在线阅读,更多相关《基于V4L2的视频驱动开发.docx(28页珍藏版)》请在冰豆网上搜索。

基于V4L2的视频驱动开发.docx

基于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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工作范文 > 行政公文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1