现在的位置: 首页 > 综合 > 正文

基于V4L2的视频驱动开发(2)

2012年06月12日 ⁄ 综合 ⁄ 共 5933字 ⁄ 字号 评论关闭

基于V4L2
的视频驱动开发(2

      
                                                                                                 


华清远见

刘洪涛

三、           


V4L2 API

及数据结构

V4L2


V4L

的升级版本,为
linux

下视频设备程序提供了一套接口规范。包括一套数据结构和底层
V4L2

驱动接口。

1

、常用的结构体在内核目录
include/linux/videodev2.h

中定义

   struct v4l2_requestbuffers //

申请帧缓冲,对应命令
VIDIOC_REQBUFS
   struct v4l2_capability //

视频设备的功能,对应命令

VIDIOC_QUERYCAP
   struct v4l2_input  

//

视频输入信息,对应命令

VIDIOC_ENUMINPUT
   struct v4l2_standard //

视频的制式,比如PAL
NTSC
,对应命令

VIDIOC_ENUMSTD
   struct v4l2_format    
//

帧的格式,对应命令VIDIOC_G_FMT
VIDIOC_S_FMT


   struct v4l2_buffer  
//

驱动




中的一帧图像缓存,对应命令VIDIOC_QUERYBUF

   struct v4l2_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

)打开设备文件

int fd = open(Devicename,mode);

   


Devicename



/dev/video0

/dev/video1
……

    
Mode



O_RDWR [| O_NONBLOCK]

      

如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。


2

)取得设备的
capability

struct v4l2_capability capability


      
      
int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);     


看看设备具有什么功能,比如是否具有视频输入特性。


3

)选择视频输入

struct v4l2_input input


……初始化
input

int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);     


一个视频设备可以有多个视频输入。如果只有一路输入,这个功能可以没有。


4

)检测视频支持的制式

    
      

v4l2_std_id std;

    
      
do {


 
         
 
    

ret = ioctl(fd, VIDIOC_QUERYSTD, &std);

    
      
} while (ret == -1 && errno == EAGAIN);


    
      
switch (std) {


  
      
 
    
case V4L2_STD_NTSC: 


      
             
 
//……


   
    
case V4L2_STD_PAL:


       
    
//……


}


5

)设置视频捕获格式

struct v4l2_format fmt;

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

)向驱动申请帧缓存

    
struct v4l2_requestbuffers 
req;


if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {

 
        
return -1;


}

      
v4l2_requestbuffers


结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立
FIFO

,来提高视频采集的效率。


7

)获取每个缓存的信息,并
mmap

到用户空间

typedef struct VideoBuffer {

   
void  
*start;


   
size_t 
length;


} VideoBuffer;

      
      
      
      
      
      
      
      
      


VideoBuffer*      
buffers = calloc( req.count, sizeof(*buffers) );

struct v4l2_buffer   
buf;

 

for (numBufs = 0; numBufs < req.count; 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

)开始采集视频

int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE


int ret = ioctl(fd, VIDIOC_STREAMON, &buf_type);

 


9

)取出
FIFO

缓存中已经采样的帧缓存

struct v4l2_buffer buf;

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

)停止视频的采集

int ret = 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

)提供了注册函数

int video_register_device(struct video_device *vfd, int type, int nr)

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

)提供了注销函数

void video_unregister_device(struct video_device *vfd)

 

2


struct video_device

的构建

      
      
video_device


结构包含了视频设备的属性和操作方法。参见
zc301_core.c

strcpy(cam->v4ldev->name, "ZC0301[P] PC Camera");

      
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

的驱动中并没有实现
struct video_device

中的很多操作函数



vidioc_querycap


vidioc_g_fmt_cap

等。主要原因是
struct file_operations zc0301_fops


中的
zc0301_ioctl

实现了前面的所有
ioctl

操作。所以就不需要在
struct video_device

再实现
struct video_device

中的那些操作了。

      


另一种实现方法如下:

static struct video_device camif_dev =

{

      
.name            
= "s3c2440 camif",


      
.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,

};

static struct file_operations camif_fops =

{

      
.owner          
= THIS_MODULE,


      
.open            
= camif_open,


      
.release   
= camif_release,


      
.read             
= camif_read,


      
.poll       
= camif_poll,


      
.ioctl             
= video_ioctl2, /* V4L2 ioctl handler */




      
.mmap          
= camif_mmap,


      
.llseek           
= no_llseek,


};

注意

video_ioctl2


videodev.c

中是实现的。
video_ioctl2

中会根据
ioctl

不同的
cmd


调用
video_device

中的操作方法。

3


Video

核心层的实现

      


参见内核
/drivers/media/videodev.c


1

)注册
256

个视频设备

      


static int __init videodev_init(void)

{

int ret;

【上篇】
【下篇】

抱歉!评论已关闭.