v4l2设备驱动开发

2016-08-28 09:52:19 LG1259156776 阅读数 8589

Author:CJOK

Contact:cjok.liao#gmail.com

SinaWeibo:@廖野cjok

 

1、概述

Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。就像公司的老板一般都不会直接找底层的员工谈话,而是找部门经理了解情况,一个是因为底层屌丝人数多,意见各有不同,措辞也不准,部门经理会把情况汇总后再向上汇报;二个是老板时间宝贵。

         V4L2支持三类设备:视频输入输出设备、VBI设备和radio设备(其实还支持更多类型的设备,暂不讨论),分别会在/dev目录下产生videoX、radioX和vbiX设备节点。我们常见的视频输入设备主要是摄像头,也是本文主要分析对象。下图V4L2在Linux系统中的结构图:



Linux系统中视频输入设备主要包括以下四个部分:

字符设备驱动程序核心:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;

V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;

平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev。

具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。

 

V4L2的核心源码位于drivers/media/v4l2-core,源码以实现的功能可以划分为四类:

核心模块实现:由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数;

V4L2框架:由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c等文件实现,构建V4L2框架;

Videobuf管理:由videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。

Ioctl框架:由v4l2-ioctl.c文件实现,构建V4L2ioctl的框架。

 

2、V4L2框架

         结构体v4l2_device、video_device、v4l2_subdev和v4l2_fh是搭建框架的主要元素。下图是V4L2框架的结构图:


从上图V4L2框架是一个标准的树形结构,v4l2_device充当了父设备,通过链表把所有注册到其下的子设备管理起来,这些设备可以是GRABBER、VBI或RADIO。V4l2_subdev是子设备,v4l2_subdev结构体包含了对设备操作的ops和ctrls,这部分代码和硬件相关,需要驱动工程师根据硬件实现,像摄像头设备需要实现控制上下电、读取ID、饱和度、对比度和视频数据流打开关闭的接口函数。Video_device用于创建子设备节点,把操作设备的接口暴露给用户空间。V4l2_fh是每个子设备的文件句柄,在打开设备节点文件时设置,方便上层索引到v4l2_ctrl_handler,v4l2_ctrl_handler管理设备的ctrls,这些ctrls(摄像头设备)包括调节饱和度、对比度和白平衡等。

 

v4l2_device

v4l2_device在v4l2框架中充当所有v4l2_subdev的父设备,管理着注册在其下的子设备。以下是v4l2_device结构体原型(去掉了无关的成员):

struct v4l2_device {

         structlist_head subdevs;    //用链表管理注册的subdev

         charname[V4L2_DEVICE_NAME_SIZE];    //device 名字

         structkref ref;      //引用计数

         ……

};

可以看出v4l2_device的主要作用是管理注册在其下的子设备,方便系统查找引用到。

V4l2_device的注册和注销:

int v4l2_device_register(struct device*dev, struct v4l2_device *v4l2_dev)

static void v4l2_device_release(struct kref *ref)

 

V4l2_subdev

V4l2_subdev代表子设备,包含了子设备的相关属性和操作。先来看下结构体原型:

struct v4l2_subdev {

         structv4l2_device *v4l2_dev;  //指向父设备

         //提供一些控制v4l2设备的接口

         conststruct v4l2_subdev_ops *ops;

         //V4L2框架提供的接口函数

         conststruct v4l2_subdev_internal_ops *internal_ops;

         //subdev控制接口

         structv4l2_ctrl_handler *ctrl_handler;

         /* namemust be unique */

         charname[V4L2_SUBDEV_NAME_SIZE];

         /*subdev device node */

         structvideo_device *devnode;  

};

每个子设备驱动都需要实现一个v4l2_subdev结构体,v4l2_subdev可以内嵌到其它结构体中,也可以独立使用。结构体中包含了对子设备操作的成员v4l2_subdev_opsv4l2_subdev_internal_ops

v4l2_subdev_ops结构体原型如下:

struct v4l2_subdev_ops {

//视频设备通用的操作:初始化、加载FW、上电和RESET

         conststruct v4l2_subdev_core_ops        *core;

//tuner特有的操作

         conststruct v4l2_subdev_tuner_ops      *tuner;

//audio特有的操作

         conststruct v4l2_subdev_audio_ops      *audio;

//视频设备的特有操作:设置帧率、裁剪图像、开关视频流等

         conststruct v4l2_subdev_video_ops      *video;

……

};

视频设备通常需要实现core和video成员,这两个OPS中的操作都是可选的,但是对于视频流设备video->s_stream(开启或关闭流IO)必须要实现。

 

v4l2_subdev_internal_ops结构体原型如下:

struct v4l2_subdev_internal_ops {

    //subdev注册时被调用,读取ICID来进行识别

         int(*registered)(struct v4l2_subdev *sd);

         void(*unregistered)(struct v4l2_subdev *sd);

//当设备节点被打开时调用,通常会给设备上电和设置视频捕捉FMT

         int(*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);

         int(*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);

};

v4l2_subdev_internal_ops是向V4L2框架提供的接口,只能被V4L2框架层调用。在注册或打开子设备时,进行一些辅助性操作。

 

Subdev的注册和注销

当我们把v4l2_subdev需要实现的成员都已经实现,就可以调用以下函数把子设备注册到V4L2核心层:

int v4l2_device_register_subdev(struct v4l2_device*v4l2_dev, struct v4l2_subdev *sd)

当卸载子设备时,可以调用以下函数进行注销:

void v4l2_device_unregister_subdev(struct v4l2_subdev*sd)

 

video_device

         video_device结构体用于在/dev目录下生成设备节点文件,把操作设备的接口暴露给用户空间

struct video_device

{

         conststruct v4l2_file_operations *fops;  //V4L2设备操作集合

 

         /*sysfs */

         structdevice dev;             /* v4l device */

         structcdev *cdev;            //字符设备

 

         /* Seteither parent or v4l2_dev if your driver uses v4l2_device */

         structdevice *parent;              /* deviceparent */

         structv4l2_device *v4l2_dev;          /*v4l2_device parent */

 

         /*Control handler associated with this device node. May be NULL. */

         structv4l2_ctrl_handler *ctrl_handler;

 

         /* 指向video buffer队列*/

         structvb2_queue *queue;

 

         intvfl_type;      /* device type */

         intminor;  //次设备号

 

         /* V4L2file handles */

         spinlock_t                  fh_lock; /* Lock for allv4l2_fhs */

         structlist_head        fh_list; /* List ofstruct v4l2_fh */

 

         /*ioctl回调函数集,提供file_operations中的ioctl调用 */

         conststruct v4l2_ioctl_ops *ioctl_ops;

         ……

};

Video_device分配和释放,用于分配和释放video_device结构体:

struct video_device *video_device_alloc(void)

void video_device_release(struct video_device *vdev)

 

video_device注册和注销,实现video_device结构体的相关成员后,就可以调用下面的接口进行注册:

static inline int __must_checkvideo_register_device(struct video_device *vdev,

                   inttype, int nr)

void video_unregister_device(struct video_device*vdev);

vdev:需要注册和注销的video_device;

type:设备类型,包括VFL_TYPE_GRABBER、VFL_TYPE_VBI、VFL_TYPE_RADIO和VFL_TYPE_SUBDEV。

nr:设备节点名编号,如/dev/video[nr]。

 

v4l2_fh

         v4l2_fh是用来保存子设备的特有操作方法,也就是下面要分析到的v4l2_ctrl_handler,内核提供一组v4l2_fh的操作方法,通常在打开设备节点时进行v4l2_fh注册。

初始化v4l2_fh,添加v4l2_ctrl_handler到v4l2_fh:

void v4l2_fh_init(struct v4l2_fh *fh, structvideo_device *vdev)

添加v4l2_fh到video_device,方便核心层调用到:

void v4l2_fh_add(struct v4l2_fh *fh)

 

v4l2_ctrl_handler

v4l2_ctrl_handler是用于保存子设备控制方法集的结构体,对于视频设备这些ctrls包括设置亮度、饱和度、对比度和清晰度等,用链表的方式来保存ctrls,可以通过v4l2_ctrl_new_std函数向链表添加ctrls

struct v4l2_ctrl *v4l2_ctrl_new_std(structv4l2_ctrl_handler *hdl,

                            conststruct v4l2_ctrl_ops *ops,

                            u32id, s32 min, s32 max, u32 step, s32 def)

hdl是初始化好的v4l2_ctrl_handler结构体;

ops是v4l2_ctrl_ops结构体,包含ctrls的具体实现;

id是通过IOCTL的arg参数传过来的指令,定义在v4l2-controls.h文件;

min、max用来定义某操作对象的范围。如:

v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS,-208, 127, 1, 0);

用户空间可以通过ioctl的VIDIOC_S_CTRL指令调用到v4l2_ctrl_handler,id透过arg参数传递。

 

 

3、ioctl框架

         你可能观察到用户空间对V4L2设备的操作基本都是ioctl来实现的,V4L2设备都有大量可操作的功能(配置寄存器),所以V4L2的ioctl也是十分庞大的。它是一个怎样的框架,是怎么实现的呢?

         Ioctl框架是由v4l2_ioctl.c文件实现,文件中定义结构体数组v4l2_ioctls,可以看做是ioctl指令和回调函数的关系表。用户空间调用系统调用ioctl,传递下来ioctl指令,然后通过查找此关系表找到对应回调函数。

以下是截取数组的两项:

IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf,v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),

IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf,v4l_print_framebuffer, 0),

         内核提供两个宏(IOCTL_INFO_FNCIOCTL_INFO_STD)来初始化结构体,参数依次是ioctl指令、回调函数或者v4l2_ioctl_ops结构体成员、debug函数、flag。如果回调函数是v4l2_ioctl_ops结构体成员,则使用IOCTL_INFO_STD;如果回调函数是v4l2_ioctl.c自己实现的,则使用IOCTL_INFO_FNC

 

IOCTL调用的流程图如下:


  用户空间通过打开/dev/目录下的设备节点,获取到文件的file结构体,通过系统调用ioctl把cmd和arg传入到内核。通过一系列的调用后最终会调用到__video_do_ioctl函数,然后通过cmd检索v4l2_ioctls[],判断是INFO_FL_STD还是INFO_FL_FUNC。如果是INFO_FL_STD会直接调用到视频设备驱动中video_device->v4l2_ioctl_ops函数集。如果是INFO_FL_FUNC会先调用到v4l2自己实现的标准回调函数,然后根据arg再调用到video_device->v4l2_ioctl_ops或v4l2_fh->v4l2_ctrl_handler函数集。

 

 

4、IO访问

V4L2支持三种不同IO访问方式(内核中还支持了其它的访问方式,暂不讨论):

read和write,是基本帧IO访问方式,通过read读取每一帧数据,数据需要在内核和用户之间拷贝,这种方式访问速度可能会非常慢;

内存映射缓冲区(V4L2_MEMORY_MMAP),是在内核空间开辟缓冲区,应用通过mmap()系统调用映射到用户地址空间。这些缓冲区可以是大而连续DMA缓冲区、通过vmalloc()创建的虚拟缓冲区,或者直接在设备的IO内存中开辟的缓冲区(如果硬件支持);

用户空间缓冲区(V4L2_MEMORY_USERPTR),是用户空间的应用中开辟缓冲区,用户与内核空间之间交换缓冲区指针。很明显,在这种情况下是不需要mmap()调用的,但驱动为有效的支持用户空间缓冲区,其工作将也会更困难。

Read和write方式属于帧IO访问方式,每一帧都要通过IO操作,需要用户和内核之间数据拷贝,而后两种是流IO访问方式,不需要内存拷贝,访问速度比较快。内存映射缓冲区访问方式是比较常用的方式。

内存映射缓存区方式

         硬件层的数据流传输

         Camerasensor捕捉到图像数据通过并口或MIPI传输到CAMIF(camera interface),CAMIF可以对图像数据进行调整(翻转、裁剪和格式转换等)。然后DMA控制器设置DMA通道请求AHB将图像数据传到分配好的DMA缓冲区。


         待图像数据传输到DMA缓冲区之后,mmap操作把缓冲区映射到用户空间,应用就可以直接访问缓冲区的数据。

 

vb2_queue

为了使设备支持流IO这种方式,驱动需要实现struct vb2_queue,来看下这个结构体:

struct vb2_queue {

         enumv4l2_buf_type                  type;  //buffer类型

         unsignedint                        io_modes;  //访问IO的方式:mmapuserptr etc

 

         conststruct vb2_ops                 *ops;   //buffer队列操作函数集合

         conststruct vb2_mem_ops     *mem_ops;  //buffer memory操作集合

 

         structvb2_buffer              *bufs[VIDEO_MAX_FRAME];  //代表每个buffer

         unsignedint                        num_buffers;    //分配的buffer个数

……

};

Vb2_queue代表一个videobuffer队列,vb2_buffer是这个队列中的成员,vb2_mem_ops是缓冲内存的操作函数集,vb2_ops用来管理队列。

 

vb2_mem_ops

         vb2_mem_ops包含了内存映射缓冲区、用户空间缓冲区的内存操作方法:

struct vb2_mem_ops {

         void           *(*alloc)(void *alloc_ctx, unsignedlong size);  //分配视频缓存

         void           (*put)(void *buf_priv);            //释放视频缓存

//获取用户空间视频缓冲区指针

         void           *(*get_userptr)(void *alloc_ctx,unsigned long vaddr, 

                                               unsignedlong size, int write);

         void           (*put_userptr)(void *buf_priv);       //释放用户空间视频缓冲区指针

 

//用于缓存同步

         void           (*prepare)(void *buf_priv);

         void           (*finish)(void *buf_priv);

 

         void           *(*vaddr)(void *buf_priv);

         void           *(*cookie)(void *buf_priv);

         unsignedint     (*num_users)(void *buf_priv);         //返回当期在用户空间的buffer

 

         int              (*mmap)(void *buf_priv, structvm_area_struct *vma);  //把缓冲区映射到用户空间

};

         这是一个相当庞大的结构体,这么多的结构体需要实现还不得累死,幸运的是内核都已经帮我们实现了。提供了三种类型的视频缓存区操作方法:连续的DMA缓冲区、集散的DMA缓冲区以及vmalloc创建的缓冲区,分别由videobuf2-dma-contig.c、videobuf2-dma-sg.c和videobuf-vmalloc.c文件实现,可以根据实际情况来使用。

 

vb2_ops

         vb2_ops是用来管理buffer队列的函数集合,包括队列和缓冲区初始化

struct vb2_ops {

         //队列初始化

         int(*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,

                               unsigned int *num_buffers, unsigned int*num_planes,

                               unsigned int sizes[], void *alloc_ctxs[]);

         //释放和获取设备操作锁

         void(*wait_prepare)(struct vb2_queue *q);

         void(*wait_finish)(struct vb2_queue *q);

         //buffer的操作

         int(*buf_init)(struct vb2_buffer *vb);

         int(*buf_prepare)(struct vb2_buffer *vb);

         int(*buf_finish)(struct vb2_buffer *vb);

         void(*buf_cleanup)(struct vb2_buffer *vb);

//开始视频流

         int(*start_streaming)(struct vb2_queue *q, unsigned int count);

//停止视频流

         int(*stop_streaming)(struct vb2_queue *q);

//VB传递给驱动

         void(*buf_queue)(struct vb2_buffer *vb);

};

 

vb2_buffer是缓存队列的基本单位,内嵌在其中v4l2_buffer是核心成员。当开始流IO时,帧以v4l2_buffer的格式在应用和驱动之间传输。一个缓冲区可以有三种状态:

在驱动的传入队列中,驱动程序将会对此队列中的缓冲区进行处理,用户空间通过IOCTL:VIDIOC_QBUF把缓冲区放入到队列。对于一个视频捕获设备,传入队列中的缓冲区是空的,驱动会往其中填充数据;

在驱动的传出队列中,这些缓冲区已由驱动处理过,对于一个视频捕获设备,缓存区已经填充了视频数据,正等用户空间来认领;

用户空间状态的队列,已经通过IOCTL:VIDIOC_DQBUF传出到用户空间的缓冲区,此时缓冲区由用户空间拥有,驱动无法访问。

这三种状态的切换如下图所示:

v4l2_buffer结构如下:

struct v4l2_buffer {

         __u32                          index;  //buffer 序号

         __u32                          type;   //buffer类型

         __u32                          bytesused;  缓冲区已使用byte

         __u32                          flags;

         __u32                          field;

         structtimeval           timestamp;  //时间戳,代表帧捕获的时间

         structv4l2_timecode       timecode;

         __u32                          sequence;

 

         /*memory location */

         __u32                          memory;  //表示缓冲区是内存映射缓冲区还是用户空间缓冲区

         union {

                   __u32           offset;  //内核缓冲区的位置

                   unsignedlong   userptr;   //缓冲区的用户空间地址

                   structv4l2_plane *planes;

                   __s32                 fd;

         } m;

         __u32                          length;   //缓冲区大小,单位byte

};

当用户空间拿到v4l2_buffer,可以获取到缓冲区的相关信息。Byteused是图像数据所占的字节数,如果是V4L2_MEMORY_MMAP方式,m.offset是内核空间图像数据存放的开始地址,会传递给mmap函数作为一个偏移,通过mmap映射返回一个缓冲区指针p,p+byteused是图像数据在进程的虚拟地址空间所占区域;如果是用户指针缓冲区的方式,可以获取的图像数据开始地址的指针m.userptr,userptr是一个用户空间的指针,userptr+byteused便是所占的虚拟地址空间,应用可以直接访问。

 

5、用户空间访问设备

下面通过内核映射缓冲区方式访问视频设备(capturedevice)的流程。

1>    打开设备文件

fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);

dev_name[/dev/videoX]

2>    查询设备支持的能力

Struct v4l2_capability  cap;

ioctl(fd, VIDIOC_QUERYCAP, &cap)

3>    设置视频捕获格式

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;

ioctl(fd,VIDIOC_S_FMT, & fmt)

4>    向驱动申请缓冲区

Struct  v4l2_requestbuffers req;

req.count= 4;  //缓冲个数

req.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;

req.memory= V4L2_MEMORY_MMAP;

if(-1 == xioctl(fd, VIDIOC_REQBUFS, &req))

5>    获取每个缓冲区的信息,映射到用户空间

structbuffer {

        void  *start;

        size_t length;

} *buffers;

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

 

for (n_buffers= 0; n_buffers < req.count; ++n_buffers) {

struct  v4l2_buffer buf;

 

buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory      = V4L2_MEMORY_MMAP;

buf.index       = n_buffers;

if (-1 ==xioctl(fd, VIDIOC_QUERYBUF, & buf))

                       errno_exit("VIDIOC_QUERYBUF");

buffers[n_buffers].length= buf.length;

buffers[n_buffers].start=

        mmap(NULL /* start anywhere */,

        buf.length,

        PROT_READ | PROT_WRITE /* required */,

        MAP_SHARED /* recommended */,

        fd, buf.m.offset);

 }

6>    把缓冲区放入到传入队列上,打开流IO,开始视频采集

for (i =0; i < n_buffers; ++i) {

    struct v4l2_buffer buf;

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    buf.index = i;

 

    if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))

          errno_exit("VIDIOC_QBUF");

 }

 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

 if (-1 == xioctl(fd, VIDIOC_STREAMON, & type))

7>  调用select监测文件描述符,缓冲区的数据是否填充好,然后对视频数据

        for (;;) {

                        fd_set fds;

                        struct timeval tv;

                        int r;

 

                        FD_ZERO(&amp;fds);

                        FD_SET(fd,&amp;fds);

 

                        /* Timeout. */

                        tv.tv_sec = 2;

                        tv.tv_usec = 0;

                                                        //监测文件描述是否变化

                        r = select(fd + 1,& fds, NULL, NULL, & tv);

 

                        if (-1 == r) {

                                if (EINTR ==errno)

                                       continue;

                               errno_exit("select");

                        }

 

                        if (0 == r) {

                                fprintf(stderr,"select timeout\n");

                               exit(EXIT_FAILURE);

                        }

                                                        //对视频数据进行处理

                        if (read_frame())

                                break;

                        /* EAGAIN - continueselect loop. */

               }

 

8>    取出已经填充好的缓冲,获取到视频数据的大小,然后对数据进行处理。这里取出的缓冲只包含缓冲区的信息,并没有进行视频数据拷贝。

buf.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory= V4L2_MEMORY_MMAP;

if (-1 ==ioctl(fd, VIDIOC_DQBUF, & buf))    //取出缓冲

           errno_exit("VIDIOC_QBUF");

process_image(buffers[buf.index].start,buf.bytesused);   //视频数据处理

if (-1 ==xioctl(fd, VIDIOC_QBUF, & buf))  //然后又放入到传入队列

     errno_exit("VIDIOC_QBUF");

9>    停止视频采集

type =V4L2_BUF_TYPE_VIDEO_CAPTURE;

ioctl(fd,VIDIOC_STREAMOff, & type);

10> 关闭设备

Close(fd);

 

暂时分析到这里,后续在更新!

Reference:

http://lxr.linux.no/linux+v3.8.8/Documentation/video4linux/v4l2-framework.txt

http://lxr.linux.no/linux+v3.9/Documentation/DocBook/media/v4l/capture.c.xml

http://linuxtv.org/downloads/v4l-dvb-apis/vidioc-reqbufs.html

http://lwn.net/Articles/203924/

http://lxr.linux.no/linux+v3.9.1/drivers/media/platform/vivi.c

2016-11-02 15:29:03 michaelcao1980 阅读数 2210

大部分所需的信息都在这里。作为一个驱动作者,当挖掘头文件的时候,你可能也得看看include/media/v4l2-dev.h,它定义了许多你将来要打交道的结构体。
一个视频驱动很可能要有处理PCI总线,或USB总线的部分。这里我们不会花什么时间还接触这些东西。通常会有一个内部一I2C接口,我们在这一系列的后续文章中会接触到它。然后还有一个V4L2的子系统接口。这个子系统是围绕video_device这个结构体建立的,它代表的是一个V4L2设备。讲解进入这个结构体的一切,将会是这个系列中几篇文章的主题。这里我们先有一个概览。
video_device结构体的name字段是这一类设备的名字,它会出现在内核日志和sysfs中出现。这个名字通常与驱动的名字相同。
所表示的设备有两个字段来描述。第一个字段(type)似乎是从V4L1的API中遗留下来的,它可以下列四个值之一:


  笔者最近有机会写了一个摄像头的驱动,是“One laptop per child”项目的中摄像头专用的。这个驱动使用了为此目的而设计的内核API:the Video4Linux2 API。在写这个驱动的过程中,笔者发现了一个惊人的问题:这个API的文档工作做得并不是很好,而用户层的文档则写的,实际上,相当不错。为了补救现在的状况,LWN将在未来的内个月里写一系列文章,告诉大家如何写V4L2接口的驱动。
V4L2有一段历史了。大约在1998的秋天,它的光芒第一次出现在Bill Dirks的眼中。经过长足的发展,它于2002年11月,发布2.5.46 时,融入了内核主干之中。然而直到今天,仍有一部分内核驱不支持新的API,这种新旧API的转换工作仍在进行。同时,V4L2 API也在发展,并在2.6.18版本中进行了一些重大的改变。支持V4L2的应用依旧相对较少。
V4L2在设计时,是要支持很多广泛的设备的,它们之中只有一部分在本质上是真正的视频设备:

•video capture interface (影像捕获接口)从调谐器或是摄像头上获取视频数据。对很多人来讲,影像捕获(video capture) 是V4L2的基本应用。由于笔者在这方面的经验是强项,这一系列文章也趋于强调捕获API,但V4L2不止这些。
•video output interface (视频输出接口)允许应用使用PC的外设,让其提供视频图像。有可能是通过电视信号的形式。
•捕获接口还有一个变体,存在于video overlay interface(视频覆盖接口) 之中。它的工作是方便视频显示设备直接从捕获设备上获取数据。视频数据直接从捕获设备传到显示设备,无需经过CPU。
•VBI interfaces (Vertical blanking interval interface,垂直消隐接口)提供垂直消隐期的数据接入。这个接口包括raw和sliced两种接口,其分别在于硬件中处理的VBI数据量。(为什么要在消隐期间接入数据呢?看这里 ) 
•radio interface (广播接口) 用于从AM或FM调谐器中获得音频数据。 
也可能出现其它种类的设备。V4L2 API中还有一些关于编译码和效果设备的stub,他们都用来转换视频数据流。然而这块的东西尚未完成确定,更不说应用了。还有“teletext” 和”radio data system”的接口,他们目前在V4L1 API中实现。他们没有移动到V4L2的API中来,而且目前也没有这方面的计划。
视频驱动与其他驱动不同之处,在于它的配置方式多种多样。因此大部分V4L2驱动都有一些特定的代码,好让应用可以知道给定的设备有什么功能,并配置设备,使其按期望的方式工作。V4L2的API定义了几十个回调函数,用来配置如调谐频率、窗口和裁剪、帧速率、视频压缩、图像参数(亮度、对比度…)、视频标准、视频格式等参数。这一系列文章的很大部分都要用来考察这些配置的过程。
然后,还有一个小任务,就是有效地在视频频率下进行I/O操作。V4L2定义了三种方法来在用户空间和外设之间移动视频数据,其中有些会比较复杂。视频I/O和视频缓冲层,将会分成两篇文章来写,它们是用来处量一些共性的任务的。
随后的文章每几周发一篇,共会加入到下面的列表中。

 

v4l2驱动编写篇二--注册和打开 [复制链接] 
原文网址:
http://lwn.net/Articles/204545/ 
  这篇文章是LWN写V4L2接口的设备驱动系列文章的第二篇。没看过介绍篇的,也许可以从那篇开始看。这一期文章将关注Video for Linux驱动的总体结构和设备注册过程。
开始之前,有必要提一点,那就是对于搞视频驱动的人来说,有两份资料是非常有价值的。

•TheV4L2 API Specification . (V4L2 API说明)这份文档涵盖了用户空间视角下的API,但在很大程度上,V4L2驱动直接实现的就是那些API。所以大部分结构体是相同的,而且V4L2调用的语义也表述很很明了。打印一份出来(可以考虑去掉自由文本协议的文本内容,以保护树木[前面是作者原文,节省纸张就是保护树木嘛 ]),放在容易够到的地方。
•内核代码中的vivi驱动,即drivers/media/video/vivi.c.这是一个虚拟驱动。它可以用来测试,却不使用任何实际硬件。这样,它就成一个教人如何写V4L2驱动的非常好的实例。 
首先,每个V4L2驱动都要包含一个必须的头文件:

 

•VFL_TYPE_GRABBER 表明是一个图像采集设备–包括摄像头、调谐器,诸如此类。
•VFL_TYPE_VBI 代表的设备是从视频消隐的时间段取得信息的设备。
•VFL_TYPE_RADIO 代表无线电设备。
•VFL_TYPE_VTX 代表视传设备。 
如果你的设备支持上面提到的不只一种功能,那就要为每个功能注册一个V4L2设备。然而在V4L2中,注册的每个设备都可以用作它实际支持的各种模式(就是说,你要为一个物理设备创建不多个设备节点,但你却可以调用任意一个设备节点,来实现这个物理设备支持的任意功能)。实质上的问题是,在V4L2中,你实际上只需一个设备,注册多个V4l2设备只是为了与V4l1兼容。 
第二个字段是type2,它以掩码的形式对设备的功能提供了更详尽的描述。它可以包含以下值:

/* These defines are V4L1 specific and should not be used with the V4L2 API!
   They will be removed from this header in the future. */

•VID_TYPE_CAPTURE 它可以捕获视频数据 
•VID_TYPE_TUNER 它可以接收不同的频率 
•VID_TYPE_TELETEXT 它可以抓取字幕 
•VID_TYPE_OVERLAY 它可以将视频数据直接覆盖到显示设备的帧缓冲区 
•VID_TYPE_CHROMAKEY 一种特殊的覆盖能力,覆盖的仅是帧缓冲区中像素值为某特定值的区域 
•VID_TYPE_CLIPPING 它可以剪辑覆盖数据 
•VID_TYPE_FRAMERAM 它使用帧缓冲区中的存储器 
•VID_TYPE_SCALES 它可以缩放视频数据 
•VID_TYPE_MONOCHROME 这个是一个纯灰度设备 
•VID_TYPE_SUBCAPTURE 它可以捕获图像的子区域 
•VID_TYPE_MPEG_DECODER 它支持mpeg码流解码 
•VID_TYPE_MPEG_ENCODER 它支持编码mpeg码流 
•VID_TYPE_MJPEG_DECODER 它支持mjpeg解码 
•VID_TYPE_MJPEG_ENCODER 它支持mjpeg编码 
V4L2驱动还要初始化的一个字段是minor,它是你想要的子设备号。通常这个值都设为-1,这样会让video4linux子系统在注册时自动分配一个子设备号。 
在video_device结构体中,还有三组不同的函数指针集。第一组只包含一个函数,那就是 release(),如果驱动没有release()函数,内核就会抱怨(笔者发现一个件有趣的事,就是这个抱怨涉及到冒犯一篇LWN文章的作者)。 release()函数很重要:由于多种原因,对video_device的引用可以在最后一个应用关闭文件描述符后很长一段时间依然保持。它们甚至可以在设备己经注销后依然保持。因此,在release()函数调用前,释放这个结构体是不安全的。所以这个函数通常要包含一个简单的kfree()调用。 
video_device的 file_operations结构体包含都是常规的函数指针。视频设备通常都包括open()和release()函数。注意:这里所说的 release函数并非上面所讲到的同名的release()函数,这个release() 函数只要设备关闭就要调用。通常都还要有read()和write()函数,这取决于设备的功能是输入还是输出。然而我们要注意的是,对于视频流设备而言,传输数据还有别的方法。多数处理视频流数据的设备还需要实现poll()和mmap();而且每个V4L2设备都要有ioctl()函数,但是也可以使用V4L2子系统的video_ioctl2(); 
第三组函数存在于video_device结构体本身里面,它们是V4L2 API的核心。这组函数有几十个,处理不同的设备配置操作、流输入输出和其他操作。 
最后,从一开始就要知道的一个字段就是debug.可以把它设成是V4L2_DEBUG_IOCTL或V4L2_DEBUG_IOCTL_ARG(或是两个都设,这是个掩码),可以生成很多的调试信息,它们可以帮助一个迷糊的程序员找到毛病,知道为什么驱动和应用谁也不知道对方在说什么。
视频设备注册 
一旦video_device己经配置好,就可以下面的函数注册了:

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


这里vfd是设备的结构体(video_device),type的值与它的type字段值相同,nr也是一样,想要的子设备号(为-1则注册时自动分配)。返回值当为0,若返加的是负的
出错码,则表明出错了,和通常一样,我们要知道,设备一旦注册,它的函数可能就会立即调用,所以不到一切准备就绪,不要调用video_register_device();
设备的注销方法为:

1
 void video_unregister_device(struct video_device *vfd);


请继续关注本系列的下篇文章,我们将会看看这些函数的具体实现。
open() 和 release()每个V4L2设备都需要open()函数,其原型也与常规的相同。
int (*open)(struct inode *inode, struct file *filp);

open()函数要做的第一件事是通过给定的inode找到内部设备,这是通过找到inode中存存储的子设备号来完成的。这里还可以实现一定数量的初始化,如果有关闭电源选项的话,这个时间恰好可以用来开启硬件电源。
V4L2规范还定义了一些相关的惯例。其一是:根据其设计,文件描述符可以在给定的任何时间重复打开。这样设定的目的是当一个应用在显示(或是产生)视频信号时,另一个应用可以改变控制值。所以,虽然某些操作是独占性质的(特别是数据读、写等),但是设备总体本身是要支持描述符复用的。
另一个值得一提的惯例是:open()函数,总体上讲,不可以改变硬件中现行的操作参数。有些时候可能会有这样的情况:通过命令行程序,根据一组参数(分辨率,视频格式等)来改变摄像头配置,然后运行一个完全不同的程序来,比如说,从摄像头获取上帧图像。如果摄像头在设置在中途改变了,这种模式就不好用。所以除非应用明确表示要改变设置(这种情况当然不包括在open函数中)V4L2驱动要尽量保持设定不变。
release()函数做一些必要清理工作。因为文件描述符可以重复打开,所以release函数中减小引用计数,并在彻底退出之前做检查。如果关闭的文件描述符是用来传输数据的,release函数很可能要关掉DMA,并做一些其他的清理工作。
本系列的下一篇文章我们将进入查询设备功能和设定系统模式的冗长过程之中,请续断关注。

 

v4l2驱动编写篇三--基本I/O处理 
如果有人在video for linux API规范上花了我时间的话,他肯定已经注意到了一个问题,那就是V4L2大量使用了ioctl接口。视频硬件有大量的可操作旋钮,可能比其它任何处设都要多。视频流要与许多参数相联系,而且有很大一部分处理要通过硬件进行。不使用硬件有良好支持模式可能导致表现不好,甚至根本没有表现。所以我们不得不揭露硬件的许多特性,而对最终应用表现得怪异一点。
传统上来讲,视频驱动中包含的ioctl()函数一般会长得像一部小说,而函数所得到的结论也往往比小说更令人满意来,他们往往在中间拖了很多(这句话完全不明白什么意思)。所以V4L2的API在2.6.18版本的内核开始做出了改变。冗长的ioctl函数被替换成了一个大的回调函数的集合,每个回调函数实现自己的ioctl函数。实际上,在2.6.19-rc3中,有79个这样的回调函数。而幸运的是,多数驱动并不需实现所有的回调函数,甚至都不是大部分回调函数。
在ioctl()函数中发生的事情都放到了drivers/media/video/videodev.c里面。这部分代码处理数据在内核和用户空间之间的传输并把ioctl调用发送给驱动。要使用它的话,只要把video_device中的video_ioctl2()做为ioctl()来调用就行了。实际上,多数驱动也要把它当成unlocked_ioctl()来用。Video4Linux2层的锁可以对其进行处理,而且驱动也应该在合适的地方加锁。(这一段没看明白,乱写的)

 

你的驱动第一个可能要实现的回调函数是:

int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);

这个函数处理VIDIOC_QUERYCAP ioctl(), 只是简单问问“你是谁?你能干什么?”实现它是V4L2驱动的责任。在这个函数中,和所有其他V4L2回调函数一样, 参数priv是file->private_data域的内容;通常的实现是在open()的时候把它指向驱动中表示设备的内部结构。

 

驱动应该负责填充cap结构并且返回“0或者负的错误码”值。如果成功返回,则V4L2层会负责把回复拷贝到用户空间。

 

v4l2_capability结构(定义在<linux/videodev2.h>中)是这样的:

struct v4l2_capability {
 __u8 driver[16]; /* i.e. "bttv" */
 __u8 card[32]; /* i.e. "Hauppauge WinTV" */
 __u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
 __u32   version;        /* should use KERNEL_VERSION() */
 __u32 capabilities; /* Device capabilities */
 __u32 reserved[4];
};

 

其中driver域应该被填充设备驱动的名字,card域应该被填充这个设备的硬件描述信息。并不是所有的驱动都消耗精力去处理bus_info域,这些驱动通常使用下面这个方法:

   springf(cap->bus_info, "PCI:%s", pci_name(&my_dev));

 

version域用来保存驱动的版本号。capabilities域是一个位掩码用来描述驱动能做的不同的事情:

/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE  0x00000001  /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT  0x00000002  /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY  0x00000004  /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE  0x00000010  /* Is a raw VBI capture device */
#define V4L2_CAP_VBI_OUTPUT  0x00000020  /* Is a raw VBI output device */
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040  /* Is a sliced VBI capture device */
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080  /* Is a sliced VBI output device */
#define V4L2_CAP_RDS_CAPTURE  0x00000100  /* RDS data capture */
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200  /* Can do video output overlay */
#define V4L2_CAP_HW_FREQ_SEEK  0x00000400  /* Can do hardware frequency seek  */

#define V4L2_CAP_TUNER   0x00010000  /* has a tuner */
#define V4L2_CAP_AUDIO   0x00020000  /* has audio support */
#define V4L2_CAP_RADIO   0x00040000  /* is a radio device */

#define V4L2_CAP_READWRITE              0x01000000  /* read/write systemcalls */
#define V4L2_CAP_ASYNCIO                0x02000000  /* async I/O */
#define V4L2_CAP_STREAMING              0x04000000  /* streaming I/O ioctls */

 

最后一个域(reserved)是保留的。V4L2规则要求要求reserved被置为0, 但是, 因为video_ioctl2()设置了整个的结构体为0,所以这个就不用我们操心了。

 

可以在“vivi”这个驱动中找到一个典型的应用:

static int vidioc_querycap(struct file *file, void  *priv,
     struct v4l2_capability *cap)
{
 struct vivi_fh  *fh  = priv;
 struct vivi_dev *dev = fh->dev;

 strcpy(cap->driver, "vivi");
 strcpy(cap->card, "vivi");
 strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info));
 cap->version = VIVI_VERSION;
 cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
    V4L2_CAP_STREAMING     |
    V4L2_CAP_READWRITE;
 return 0;
}

考虑到这个回调函数的出现,我们希望应用程序使用它,避免要求设备完成它们不可能完成的功能。然而,在你编程的有限经验中,应用程序不会花太多的精力来关注VIDIOC_QUERYCAP调用。

 

另一个可选而又不常被是实现的回调函数是:

     int (*vidioc_log_status)       (struct file *file, void *fh);

这个函数用来实现VIDIOC_LOG_STATUS调用,作为视频应用程序编写者的调试助手。当调用时,它应该打印藐视驱动及其硬件的当前状态信息。这个信息应该足够充分以便帮助迷糊的应用程序开发者弄明白为什么视频显示一片空白。

 

 

下一部分开始剩下的77个回调函数。特别的,我们要开始看看与硬件协商一组操作模式的过程。

 

v4l2驱动编写篇第四--输入输出


输入和输出
这是不定期发布的关于写视频驱动程序的LWN系统文章的第四篇.没有看过介绍篇的,也许想从这里开始.本周的文章介绍的是应用程序如何确定在特定适配器上哪些输入和输出可用,并且它们之间做出选择。

在很多情况下,视频适配器并不能提供很多的输入输出选项.比如说摄像头控制器,可能只是提供摄像头,而没什么别的功能.然而,在一些其他的情况下,事情将变得很复杂.一个电视卡可能对应板上不用的接头有不同的输入.他甚至可能有可以独立发挥其功能的多路调谐器.有时,那些输入会有不同的特性;有些调谐器可以支持比其他的更广泛的视频标准.对于输出来说,也有同样的问题.

很明显,若想一个应用可以有效地利用视频适配器,它必须有能力找到可用的输入和输出,而且他必须能找到他想操作的那一个.为此,Video4Linux2 API提供三种不同的ioctl()调用来处理输入,相应地有三个来处理输出.

这三个(对于硬件支持的每一个功能)驱动都要支持.虽然如此,对于简单的硬件而言,代码还是很简单的.驱动也要提供一此启动时的默认值.然而,驱动不应该做的是,在应用退出时重置输入输出信息.对于其他视频参数,在多次打开之间,参数应维持不变.

 

视频标准

  在我们进入输入输出的细节之前,我们应该先了解一下视频标准 .这些标准描述的是视频为进行传输而做出的格式转换–分辨率,帧频率等.这些标准通常是由每一个国家的监管部门制定的。现在世界上使标准主要的有三个:NTSC(主要是北美使用),PAL(主要是欧洲,非洲和中国),和SECAM(法,俄和非洲部分地区).然而这在标准在国家之间都有变化,而且有些设备比其他设备能更加灵活,能与更多的标准变种协同工作.

V4L2使用v4l2_std_id来代表视频标准,它是一个64位的掩码。每个标准变种在掩码中就是一位。所以标准NTSC就是V4L2_STD_NTSC_M, 值为0x1000,而日本的变种就是V4L2_STD_NTSC_M_JP
(0x2000)。如果一个设备可以处理所以有NTSC变种,它就可以设为V4L2_STD_NTSC,它可以所有相关位置位。对PAL和SECAM标准,也存在一组类似的位集。Seethis page for a complete list.

对于用户空间而言,V4L2提供一个ioctl()命令(VIDIOC_ENUMSTD),它允许应用查询设备实现了哪些标准。驱动却无需直接回答查询,而是将video_device结构体的tvnorm字段设置为它所支持的所有标准。然后V4L2层会向应用输出所支持的标准。VIDIOC_G_STD命令,可以用来查询现在哪种标准是激活的,它也是在V4L2层通过返回video_device结构的current_norm字段来处理的,驱动程序应在启动时,初始化current_norm来反应现实情况。有些应用即使他并没有设置过标准,发现标准没有设置也会感到困惑。

当某个应用想要申请某个标准的时候,会发出一个VIDIOC_S_STD调用,该调用通过下面的函数传到驱动:

  int (*vidioc_s_std) (struct file *file, void *private_data,                         v4l2_std_id std);


驱动要对硬件编程,以使用给定的标准,并返回0(或是负的出错编码).V4L2层需要把current_norm设为新的值。

应用可能想要知道硬件所看到的是何种信号,答案可以通过VIDIOC_QUERYSTD找到,它到了驱动里面就是:

    int (*vidioc_querystd) (struct file *file, void *private_data,                            v4l2_std_id *std);


驱动要尽可能地在这个字段填写详细信息。如果硬件没有提供足够的信息,std字段就会暗示任何可能出现的标准。

这里还有一点值得一提:所以的视频设备必须支持(或是声明支持)至少一种视频标准。视频标准对于摄像头来说没什么意义,它不与任何监管制度绑定。但是也不存一个标准说“我是个摄像头,我什么都能做”,所以V4L2层有很多摄像头声明可以返回PAL或NTSC数据(实际只是如些声明而己)。

 

输入

  视频捕获的应用首先要通过VIDIOC_ENUMINPUT
命令来枚举所有可用的输入。在V4L2层,这个调用会转换成调用一个驱动中对应的回调函数:

  int (*vidioc_enum_input)(struct file *file, void *private_data,                             struct v4l2_input *input);


在这个调用中,file 对就的是打开的视频设备。private_data是驱动的私有字段,input字段是真正的传递的信息,它有如下几个值得关注的字段:

•__u32 index:应用关注的输入的索引号; 这是惟一一个用户空间设定的字段. 驱动要分配索引号给输入,从0开始,依次往上增加.想要知道所以可用的输入的应用会调用VIDIOC_ENUMINPUT
调用过索引号从0开始,并开始递增。 一旦返回EINVAL,应用就知道,输入己经用光了.只要有输入,输入索引号0就一定要存在的.
•__u8 name[32]: 输入的名字,由驱动设定.简单起见,可以设为”Camera”,诸如此类;如果卡上有多个输入,名称就要与接口的打印相符合.
•__u32 type:输入的类型,现在只有两个值可选:V4L2_INPUT_TYPE_TUNER
和V4L2_INPUT_TYPE_CAMERA.
•__u32 audioset:描述哪个音频输入可以与些视频输入相关联. 音频输入与视频输入一样通过索引号枚举 (我们会在另一篇文章中关注音频),但并非所以的音频与视频的组合都是可用的.这个字段是一个掩码,代表对于当前枚举出的视频而言,哪些音频输入是可以与之关联的.如果没有音频输入可以与之关联,或是只有一个可选,那么就可以简单地把这个字段置0.
•__u32 tuner: 如果输入是一个调谐器 (type
字段置为V4L2_INPUT_TYPE_TUNER), 这个字段就是会包含一个相应的调谐设备的索引号.枚举和调谐器的控制也将在未来的文章中讲述.
•v4l2_std_id std: 描述设备支持哪个或哪些视频标准.
•__u32 status: 给出输入的状态. 全整的标识符集合可以在V4L2的文档中找到(即这里 
);简而言之,status
中设置的每一位都代表一个问题. 这些问题包括没有电源,没有信号,没有同频锁,或 the presence of Macrovision(这个是什么意思?没查到)或是其他一些不幸的问题.
•__u32 reserved[4]:保留字段,驱动应该将其置0. 
通常驱动会设置上面所以的字段,并返回0。如果索引值超出支持的输入范围,应该返回-EINVAL.这个调用里可能出现的错误不多。

当应用想改变现行的输入时,驱动会收到一个对回调函数vidioc_s_input()的调用。

int (*vidioc_s_input) (struct file *file, void *private_data,                           unsigned int index);


index的值与上面讲到的意义相同 – 它相来确定哪个输入是相要的.驱动要对硬件编辑,选择那个输入并返回0.也有可能要返回-EINVAL
(索引号不正确时) 或-EIO
(硬件有问题). 即使只有一路输入,驱动也要实现这个回调函数.

还有另一个回调函数,指示哪一个输入是激活状态的:

  int (*vidioc_g_input) (struct file *file, void *private_data,                           unsigned int *index);

 


这里驱动把index值设为对应的输入的索引号.

 

 

输出

  枚举和选择输出的过程与输入的是十分相似的。所以这里的描述就从简。输入枚举的回调函数是这样的:

  int (*vidioc_enumoutput) (struct file *file, void *private_data                                  struct v4l2_output *output);


v4l2_output
结构的字段是:

•__u32 index:相关输入的索引号.其工作方式与输入的索引号相同:它从0开始递增。
•__u8 name[32]: 输出的名字.
•__u32 type: 输入的类型.支持的输出类型为:V4L2_OUTPUT_TYPE_MODULATOR
用于模拟电视调制器,V4L2_OUTPUT_TYPE_ANALOG
用于基本模拟视频输出,和V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY
用于模拟VGA覆盖设备.
•__u32 audioset: 能与这个视频协同工作的音频集.
•__u32 modulator: 与此设备相关的调制器 (对于类型为V4L2_OUTPUT_TYPE_MODULATOR 的设备而言).
•v4l2_std_id std:输出所支持的视频标准.
•__u32 reserved[4]: 保留字段,要设置为0. 
也有用于获得和设定现行输入设置的回调函数;他们与输入的具体对称性:

    int (*vidioc_g_output) (struct file *file, void *private_data,    unsigned int *index);   

    int (*vidioc_s_output) (struct file *file, void *private_data,     unsigned int index);


即便只有一个输出,设备驱动也要定义所有上述的三个回调函数.

有了这些函数之后,V4L2应用就可以知道有哪些输入和输入,并在它们间进行选择.然而选译这些输入输出中所传输的是什么视频数据流则是一件更加复杂的事情.本系列的下一期文章,我们将关注视频数据流的格式,以及如何与用户空间协定数据格式.

 

v4l2文档第五A--颜色与格式 
  颜色与格式这是不定期发布的关于写视频驱动程序的LWN系统文章的第五篇.没有看过介绍篇的,也许想从这里 开始.

 


  应用在可以使视频设备工作之前,它必须与驱动达成了解,知道视频数据是何种格式的。这种协商将是一个非常复杂的过程,其原因有二:1、视频硬件所支持的视频格互不相同。2、在内核的格式转换是令人难以接受的。所以应用在找出一种硬件支持的格式,并做出一种大家都可以接受的配置。这篇文章将会讲述格式的基本描述方式;下期文章则会讲述V4L2驱动与应用协商格式时所实现的API。


色域 
  色域在广义上来讲,就是系统在描述色彩时所使用的坐标。V4L2规范中定义了好几个,但只有两个使用最为广泛。它们是:

   •V4L2_COLORSPACE_SRGB.多数开发者所熟悉的[red,green,blue]数组包含在这个色域之中。它为每一种颜色提供了一个简单的强度值,把它们混合在一起,从而产生了一种广泛的颜色的效果。表示RGB值的方法有很多,我们在下面将会有所介绍。 
这个色域也包含YUV和YCbCr的表示方法,这个表示方法最早是为了早期的彩色电视信号可以在黑白电视中的播放,所以Y(或说亮度)值只是一个简单的亮度值,单独播放时可以产生灰度图像。U和V(或Cb和Cr)色度值描述的是色彩中蓝色和红色的分量。绿色可以通过从亮度中减去这些分量而得到。YUV和RGB之间的转换并不简单,但是我们有一些成形的公式 可选。
注意:YUV和YCbCr并非完成一样,虽然有时他们的名字会替代使用。

   •V4L2_COLORSPACE_SMPTE170M 这个是NTSC或PAL等电视信号的模拟色彩表示方法,电视调谐器通常产生的色域都属于这个色域。 
还存在很多其他的色域,他们多数都是电视相关标准的变种。点击查看V4L2规范 中的详细列表。

 

密集存储和平面存储

  如汝所见,像素值是以数组的方式表示的,通常由RGB或YUV值组成。要把这数组组织成图像,通常有两种常用的方法。

  •Packed 格式把一个像素的所有值存领教在一起.
  •Planar 格式把每一个分量单独存储成一个阵列,这样在YUV格式中,所有Y值都连续地一起存储在一个阵列中,U值存储在另一个中,V值存在第三个中.这些平面常常都存储在一个缓冲区中,但并不一定非要这样. 
紧密型存储方式可能使用更为广泛,特别是RGB格式,但这两种存储方式都可以由硬件产生并由应用程序请求。如果设备可以产生紧密型和平面型两种,那么驱动就要让两种都在用户空间可见。


四字符码(four Charactor Code:FourCC)

V4L2 API中表示色彩格式采用的是广受好评的四字符码(fourcc)机制。这些编码都是32位的值,由四个ASCII码产生。如此一来,它就有一个优点就是,易于传递,对人可读。当一个色彩格式读作,例如,”RGB4″就没有必要去查表了。
  注意:四字符码在很多不同的设定中都会使用,有些还是早于linux的.Mplayer中内部使用它们,然而,fourcc只是说明一种编码机制,并不说明使用何种编码。Mplayer有一个转换函数,用于在它自己的fourcc码和v4l2用的fourcc码之间做出转换。

RGB格式

在下面的格式描述中,字节是按存储顺序列出的。在小端模式下,LSByte在前面。每字节的LSbit在右侧。每种色域中,轻阴影位是最高有效的。

 

Name

fourcc

Byte 0

Byte 1

Byte 2

Byte 3

V4L2_PIX_FORMAT_RGB332

RGB1

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_RGB444

R444

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_RGB555

RGB0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_RGB565

RGBP

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_RGB555X

RGBQ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_RGB565X

RGBR

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_BGR24

BGR3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_RGB24

RGB3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_BGR32

BGR4

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_RGB32

RGB4

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V4L2_PIX_FORMAT_SBGGR8

BA81

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

当使用有空位(上图中灰色部分)的格式 , 应用可以使用空位作alpha(透明度)值.
上面的最后一个格式是”Bayer(人名好像是)”格式,此格式与多数摄像机感光器所得到的真实数据非常接近。每个像素都有绿色分量,但蓝和红只是隔一个像素才有分量。本质上讲,绿色带有更重的强度信息,而蓝红色则在丢失时以相隔像素的内插值替换。这种模式我们交在YUV格式中再次见到。

YUV格式

YUV的紧密型模式在下面首先展示,看表的关键处如下:

  •  

 

 

 

 

 

 

 

 

  • = Y (intensity) 
  •  

 

 

 

 

 

 

 

 

  • = U (Cb) 
  •  

 

 

 

 

 

 

 

 

  • = V (Cr)

 

 

 

也有几中平面型的YUV格式在用,但把它们全画出来并没有什么大的帮助,所以我们只是在下面举一下例子,常用”YUV 4:2:2″(V4L2_PIX_FMT_YUV422, fourcc422P)格式使用三组阵列,一幅4X4的图片将如下表示:

Y plane:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

U plane:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V plane:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

对于Bayer格式, YUV 4:2:2 每隔一个Y值有一个U和一个V值,展示图像需要以内插值替换的丢失的值。其他的平面YUV格式有:


•V4L2_PIX_FMT_YUV420: YUV 4:2:0格式,每四个Y值才有一个U值一个V值.U和V都要在水平和垂直两个方向上都以内插值替换.平面是以Y-U-V的顺序存储的,与上面的例子一致. 
•V4L2_PIX_FMT_YVU420: 与YUV 4:2:0格式类似,只是U,V值调换了位置. 
•V4L2_PIX_FMT_YUV410: 每16个Y值才有一个U值和V值.阵列的顺序是 Y-U-V. 
•V4L2_PIX_FMT_YVU410: 每16个Y 值才有一个U值和V值.阵列的顺序是Y-V-U.

 

其他格式

还有一些可能对驱动有用的格式如下:

•V4L2_PIX_FMT_JPEG: 一种定义模糊的JPEG流;更多信息请看这里 . 
•V4L2_PIX_FMT_MPEG: MPEG流.还有一些MPEG流格式的变种;未来的文章中将讨论流的控制.

[p=21, null, left]还有一些其他的混杂的格式,其中一些还是有传利保护的;这里 有一张列表.
格式的描述[p=21, null, left]现在我们己经了解了颜色的格式,下面将要看看下V4L2 API中是如何描述图像格式的了。这里的主要的结构体是struct v4l2_pix_format (定义于<linux/videodev2.h>),它包含如下字段:


•__u32 width: 图片宽度,以像素为单位. 
•__u32 height:图片高度,以像素为单位. 
•__u32 pixelformat: 描述图片格式的四字符码. 
•enum v4l2_field field:很多图片的源会使数据交错 -先传输奇数行,然后是偶到行.真正的摄像头设备是不会做数据的交错的。 V4L2 API 允许应用使用很多种方式交错字段.常用的值为V4L2_FIELD_NONE (字段不交错),V4l2_FIELD_TOP (只交错顶部字面),或V4L2_FIELD_ANY (无所谓). 详情见这里 . 
•__u32 bytesperline: 相临扫描行之间的字节数.这包括各种设备可能会加入的填充字节.对于平面格式,这个值描述的是最大的 (Y) 平面. 
•__u32 sizeimage: 存储图片所需的缓冲区的大小. 
•enum v4l2_colorspace colorspace: 使用的色域. 
加到一起,这些参数以合理而完整的方式描述了视频数据缓冲区。应用可以填充 v4l2_pix_format 请求用户空间开发者所能想到的几乎任何格式. 然而,在驱动层面上,驱动开发者则要限制在硬件所能支持的格式上. 所以每一个 V4L2 应用都必须经历一个与驱动协定的过程,以便于使用一个硬件支持并且能滿足应用需要的图像格式。下一期文章,我们将从驱动角度,描述这种协定是什么进行的。

 

v4l2驱动编写篇第五B--格式的协定

 
 这是不定期发布的关于写视频驱动程序的LWN系统文章的一篇续篇.介绍篇 包含了对整个系统的描述,并且包含对本篇的上一篇的链接,在上一集,我们关注了V4L2 API是如何描述视频格式的:图片的大小,和像素在其内部的表示方式。这篇文章将完成对这个问题的讨论,它将描述如就硬件所支持的实际视频格与应用达到协议。
如我们在上一篇中所见,在存储器中表示图像有很多种方法。市场几乎找不到可以处理所有V4L2所理解的视频格式的设备。驱动不应支持底层硬件不懂的视频格式。实际上在内核中进行格式的转换是令人难以接受的。所以驱动必须可以应用选择一个硬件可以支持的格式。
第一步就是简单的允许应用查询所支持的格式。VIDIOC_ENUM_FMT ioctl()就是为此目的而提供的。在驱动内部这个调用会转化为这样一个回调函数(如果查询的是视频捕获设备)。
  int (*vidioc_enum_fmt_cap)(struct file *file, void *private_data,
                               struct v4l2_fmtdesc *f);

这个回返调函数要求视频捕获设备描述其支持的格式。应用会传递一个v4l2_fmtdesc 结构体:
   struct v4l2_fmtdesc
    {
        __u32                    index;
        enum v4l2_buf_type  type;
        __u32               flags;
        __u8                    description[32];
        __u32                    pixelformat;
        __u32                    reserved[4];
    };

应用会设置index 和type 字段.index 是用来确定格式的一个简单的整型 数;与其他V4L2所使用的索引(indexes)一样,这个也是从0开始递增,至最大允许的值为止.应用可以通过一直递增索引值(index)直到返回 EINVAL的方式枚举所有支持的格式.type 字段描述的是数据流类型 ;对于视频捕获设备来说(摄像头或调谐器就是V4L2_BUF_TYPE_VIDEO_CAPTURE.
如果index 对就某个支持的格式,驱动应该填写结构体的其他字段.pixelformat字段应该是描述视频表现方式的fourcc编码,而 description 是对这个格式的一种简短的字符串描述.flags 字段只定义了一个值即V4L2_FMT_FLAG_COMPRESSED,它表示是一个压缩了的视频格式.
上述的函数是视频捕获函数;只有当type 字段的是值是V4L2_BUF_TYPE_VIDEO_CAPTURE时才会调用. VIDIOC_ENUM_FMT 调用将根据type字段的值的不同解释成不同的回调函数。
    /* V4L2_BUF_TYPE_VIDEO_OUTPUT */ 
    int (*vidioc_enum_fmt_video_output)(file, private_date, f);

    /* V4L2_BUF_TYPE_VIDEO_OVERLAY */ 
    int (*vidioc_enum_fmt_overlay)(file, private_date, f);

    /* V4L2_BUF_TYPE_VBI_CAPTURE */ 
    int (*vidioc_enum_fmt_vbi)(file, private_date, f);

    /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */ */
    int (*vidioc_enum_fmt_vbi_capture)(file, private_date, f);

    /* V4L2_BUF_TYPE_VBI_OUTPUT */ 
    /* V4L2_BUF_TYPE_SLICED_VBI_OUTPUT */ 
    int (*vidioc_enum_fmt_vbi_output)(file, private_date, f);

    /* V4L2_BUF_TYPE_VIDEO_PRIVATE */ 
    int (*vidioc_enum_fmt_type_private)(file, private_date, f);

参数类似对于所以的调用都是一样. 对于以V4L2_BUF_TYPE_PRIVATE开头的解码器,驱动可以支持特殊的缓冲类型是没有意义的。, 但在应用端却需要一个清楚的认识. 对这篇文章的目的而言,我们更加关心的是视频捕获和输出设备; 其他的视频设备我们会在将来某期的文章中讲述.
应用可以通过调用VIDIOC_G_FMT知道硬件现在的配置如何。这种情况下传递的参数是一个v4l2_format 结构体:
    struct v4l2_format
    {
        enum v4l2_buf_type type;
        union
        {
                struct v4l2_pix_format                pix;
                struct v4l2_window                win;
                struct v4l2_vbi_format                vbi;
                struct v4l2_sliced_vbi_format        sliced;
                __u8        raw_data[200];
        } fmt;
    };

同样,type 描述的是缓冲区类型;V4L2层会根据type的不同,将调用解释成不同的驱动的回调函数. 对于视频捕获设备而言,这个回调函数就是:
  int (*vidioc_g_fmt_cap)(struct file *file, void *private_data,
                                struct v4l2_format *f);

对于视频捕获(和输出)设备, 联合体中pix 字段是我们关注的重点. 这是我们在上一期中见过的v4l2_pix_format 结构体;驱动应该用现在的硬件设置填充那个结构体并且返回。这个调用通常不会失败,除非是硬件出现了非常严重的问题。
其他的回调函数还有:
    int (*vidioc_s_fmt_overlay)(file, private_data, f);
    int (*vidioc_s_fmt_video_output)(file, private_data, f);
    int (*vidioc_s_fmt_vbi)(file, private_data, f);
    int (*vidioc_s_fmt_vbi_output)(file, private_data, f);
    int (*vidioc_s_fmt_vbi_capture)(file, private_data, f);
    int (*vidioc_s_fmt_type_private)(file, private_data, f);

vidioc_s_fmt_video_output()与捕获接口一样使用相同的方式使用同一个pix字段。
多数应用都想最终对硬件进行配置以使其为应用提供一种符合其目的的格式。改变视频格有两个接口。第一个是VIDIOC_TRY_FMT 调用,它在V4L2驱动中转化为下面的回调函数:
    int (*vidioc_try_fmt_cap)(struct file *file, void *private_data,
                              struct v4l2_format *f);
    int (*vidioc_try_fmt_video_output)(struct file *file, void *private_data,
                                             struct v4l2_format *f);
    /* And so on for the other buffer types */

要处理这个调用,驱动会查看请求的视频格式,然后断定硬件是否支持这个格式。如果应用请求的格式是不能支持的,就会返回-EINVAL.所以,例如,一个描述了一个不支持格的fourcc编码或者请求了一个隔行扫描的视频,而设备只支持逐行扫描的就会失败。在另一方面,驱动可以调整size字段,以与硬件支持的图像大小相适应。普便的做法是可能的话就将大小调小。所以一个只能处理VGA分辨率的设备驱动会根据情况相应地调整width和height参数而成功返回。v4l2_format 结构体会在调用后复制给用户空间;驱动应该更新这个结构体以反映改变的参数,这样应用才可以知道它真正得到就是什么。
VIDIOC_TRY_FMT 这个处理对于驱动来说是可选的,但是不推荐忽略这个功能.如果提供了的话,这个函数可以在任何时候调用,甚至时设备正在工作的时候。它不可以对实质上的硬件参数做任何改变,只是让应用知道都可以做什么的一种方式。
如果应用要真正的改变硬件的格式,它使用VIDIOC_S_FMT 调用,它以下面的方式到达驱动:
    int (*vidioc_s_fmt_cap)(struct file *file, void *private_data,
                                struct v4l2_format *f);
    int (*vidioc_s_fmt_video_output)(struct file *file, void *private_data,
                                         struct v4l2_format *f);

与VIDIOC_TRY_FMT不同,这个调用是不能随时调用的.如果硬件正在工作,或者有流缓冲器己经开辟了(未来另一篇文章的),改变格式会带来无尽的麻烦。想想会发生什么,比如说,一个新的格式比现在使的缓冲区大的时候。所以驱动要一直保证硬件是空闲的,如果不空闲就对请求返回失败 (-EBUSY).
格式的改变应该是原子的 – 它或者改变所以的参数以实现请求否则就必须一个也不改变.同样,驱动在必要时是可以改变图像的大小的,通常的回调函数格式与下面的差不多:
    int my_s_fmt_cap(struct file *file, void *private,
                     struct v4l2_format *f)
    {
        struct mydev *dev = (struct mydev *) private;
        int ret;

        if (hardware_busy(mydev))
            return -EBUSY;
        ret = my_try_fmt_cap(file, private, f);
        if (ret != 0)
            return ret;
        return tweak_hardware(mydev, &f->fmt.pix);
    }

使用VIDIOC_TRY_FMT 句柄可以避免代码重写而且可以避免任何没有先实现那个函数的借口. 如果”try”函数成功返回,结果格式就己知并且可以直接编程进硬件。
还有很多其他调用也用影响视频I/O的完成方式。将来的文章将会讨论他们中的一部分。支持设置格式就足以让应用开始传输图像了,而且那也这个结构体的最终目的.所以下一篇文章,(希望会在这次之后的时间不会太久)我们会来关注对视频数据的读和写的支持。

 

v4l2驱动编写篇第六A--基本的帧输入输出 
基本的帧输入输出
关于视频驱动的这一系列文章己经更新了好几期,但是我们还没有传输过一帧的视频数据。虽然在这一点上,我们己经了解了足够多的关于格式协定方面的细节,我们可以看一下视频帧是如何在应用和设备之间传输的了。

V4L2 API定义了三种不同的传输视频帧的方法,现在有两种是可以实现的:

read() 和write() 系统调用这种普通的方法. 根据硬件和驱动的不同,这种方法可能会非常慢 -但也不是一定会那样.
将帧直接以视频流的方法送到应用可以访问的缓冲区. 视频流这际上是传输视频数据的最有效的方法;这种接口还允许在图像帧中附带一些其他信息. 视频流的方法有两种变种,其分别在于缓冲的开辟是在用户空间还是内核空间。
Video4Linux2 API规范提供一种异频的输入输出机制用于帧的传输。然而这种模式还没有实现,因此不能使用。
这一篇将关注的是简单的read()和write()接口,视频流的方式将在下一期来讲解。

read() 和 write()
Video4Linux2 规范并没有规定要实现read()和write(),然而很多简单的应用希望这种系统调用可用,所以可能的话,驱动的作者应该使之工作。如果驱动没有实现这些系统调用,它应该在保证V4L2_CAP_READWRITE置位,来回应VIDIOC_QUERYCAP调用。然而以笔者的经验,多数的应用在使用调用之前,根本就不会是费心查看调用是否可用。

驱动的read()和/或write()方法必须存在相关的video_device结构中的fops字段里。注意:V4L2规范要求实现这些方法,从而也提供poll()操作。

在一下视频捕获设备上实现read()操作是非常直接的:驱动告诉硬件开始捕获帧,发送一帧到用户空间缓冲,然后关停硬件并返回。如果可能的话,驱动应该安排DMA操作直接将数据传送到目的缓冲区,但这种方式只有在控制器可以处理分散/聚集I/O的时候可能。否则,驱动应该在内核里启用帧缓冲区。同样,写操作也是尽可能直接传到设备,否则启用帧缓冲区。

不那么简单的操作也是可以的。例如笔者的”cafe”驱动会在read()操作后让摄像头控制器工作在一种投机的状态,在一秒钟的下一部分,摄像头中的后续帧将会存储在内核的缓冲区中,如果应用发出了另一个读的调用,它将会更快的反应,无续再次启动硬件。经过一定数目的帧都没有读的话,控制器就会被放回空闲的状态。同理,写操作时,也会廷时几十毫秒,意在帮助应用与硬件帧同步。

流参数
VIDIOC_G_PARM 和VIDIOC_S_PARM ioctl()系统调用会调整一些read(),write()专用的参数,其中一些更加普便。它看起来像是一个设置没有明显归属的杂项的调用。我们在这里就了解一下,虽然有些参数同时会影响流输入输出的参数。

支持这些调用的Video4Linux2驱动提供如下两个方法:

int (*vidioc_g_parm) (struct file *file, void *private_data,
                              struct v4l2_streamparm *parms);
    int (*vidioc_s_parm) (struct file *file, void *private_data,
                          struct v4l2_streamparm *parms);
v4l2_streamparm结构包含下面的联合,这一系列文章的读者到现在应该对它们己经很熟悉了。

struct v4l2_streamparm
    {
        enum v4l2_buf_type type;
        union
        {
                struct v4l2_captureparm        capture;
                struct v4l2_outputparm        output;
                __u8 raw_data[200];
        } parm;
    };
type字段描述的是在涉及的操作的类型。对于视频捕获设备,应该为V4L2_BUF_TYPE_VIDEO_CAPTURE。对于输出设备应该为 V4L2_BUF_TYPE_VIDEO_OUTPUT。它的值也可以是V4L2_BUF_TYPE_PRIVATE,在这种情况下,raw_data字段用来传递一些私有的,不可移植的,甚至是不鼓励的数据给内核 。

对于捕获设备而言,parm.capture字段是要关注的内容,这个结构体如下:

struct v4l2_captureparm
    {
        __u32                   capability;
        __u32                   capturemode;
        struct v4l2_fract  timeperframe;
        __u32                   extendedmode;
        __u32              readbuffers;
        __u32                   reserved[4];
    };
capability字段是一组功能标签。目前为止已经定义的只有一个V4L2_CAP_TIMEPERFRAME,它代表可以改变帧频率。 capturemode也是一个只定义了一个标签的字段:V4L2_MODE_HIGHQUALITY,这个标签意在使硬件在高清模式下工作,实现单帧的捕获。这个模式可以做出任何的牺牲(包括支持的格式,曝光时间等),以达到设备可以处理的最佳图片质量。

timeperframe字段用于指定想要使用的帧频率,它又是一个结构体:

    struct v4l2_fract {
        __u32   numerator;
        __u32   denominator;
    };
numerator 和denominator所描述的系数给出的是成功的帧之间的时间间隔。另一个驱动相关的字段是:extendedmode,它在API中没有明确的意义。readbuffers字段是read()操作被调用时内核应为输入的帧准备的缓冲区数量。

对于输出设备,其结构体如下:

struct v4l2_outputparm
    {
        __u32                   capability;
        __u32                   outputmode;
        struct v4l2_fract  timeperframe;
        __u32                   extendedmode;
        __u32              writebuffers;
        __u32                   reserved[4];
    };
capability, timeperframe, 和 extendedmode字段与捕获设备中的意义相同。outputmode 和writebuffers与capturemode 和readbuffers对应相同。

当应用想要查询现在的参数时,它会发出一个VIDIOC_G_PARM调用,因而调用驱动的vidioc_g_parm()方法。驱动应该提供现在的设置,不用的话确保将extendedmode设为0,并且把reserved字段永远设为0.

设置参数将调用vidioc_s_parm()。在这种情况下,驱动将参数设为与应用所提供的参数尽可能近的值,并调整v4l2_streamparm结构体以反应现行使用的值 。例如,应用可以会请求一个比硬件所能提供的更高的帧频率,在这种情况下,帧频率会设为最高,并把imeperframe设为这个最高的帧频率。

如果应用提供timeperframe为0,the driver should program the nominal frame rate associated with the current video norm.(这句不懂)。如果readbuffers 或writebuffers是0,驱动应返回现行设置而不是删除缓冲区。

到现在为止,我们已经可以写一个支持read()和write()方式帧传输的简单的驱动了。然而多数正式的应用要使用流输入输出方式:流的方式使高性能变得更简单;帧可以打包带上附加信息如帧序号,请继续关注本系列的下篇文章,我们将讨论如何在视频驱动中实现流API.
v4l2驱动编写篇第六B--流输入输出 
在本系列文章的上一期中,我们讨论了如何通过read()和write()的方式实现视频帧的传输,这样的实现可以完成基本的工作,却并不是普便上用来实现视频输入输出大家偏爱的方法。为了实现最高的性能和最好的信息传输,视频驱动应该支持V4L2 流输入输出。

使用read()和write()方法,每一帧都要通过I/O操作在用户和内核空间之间拷贝数据。然而,当使用流输入输出的方式时,这种情况就不会发生。替代的方案是用户与内核空间之间交换缓冲区的指针,这些缓冲区将被映射到应用的地址空间,这也就使零帧复制数成为可能。有两种流输入输出缓冲区:

    * 内存映谢缓冲区(memory-mapped buffers) (typeV4L2_MEMORY_MMAP) 是在内核空间开辟的;应用通过themmap()系统调用将其映谢到地址空间。这些缓冲区可以是大而相临DMA缓冲区,通过vmalloc()创建的虚拟缓冲区,或者(如果硬件支持的话)直接在设备的输入输出存储器中开辟的。
    * 用户空间缓冲区 (V4L2_MEMORY_USERPTR) 是在用户空间的应用中开辟的.很明显,在这种情况下,是不需要mmap()调用的,但驱动在有效地支持用户空间缓冲区上的工作将会更难一些。

注意:驱动支持流输入输出的方式并非必需,即便做了实现,驱动也不必两种缓冲区类型都做处理。一个灵活的驱动可以支持更多的应用。在实际应用中,似乎多数应用都是使用内存映射缓冲区的。同时使用两种缓冲区是不可能的。

现在,我们将要探索一下支持流输入输出的众多而邋遢的细节。任何Video4Linux2驱动的作者都要了解这部分API。然而值得指出的是,有一个更高层次的API,它能够帮助驱动作者写流驱动。当底层设备可以支持分散/聚集I/O的时候,这一层(称为video-buf)可以使事情变得容易。关于 video-buf API我们将在将来的某期讨论。

支持流输入输出的驱动应该通知应用这一事实,方法是在vidioc_querycap()方法中设置V4L2_CAP_STREAMING标签。注意:并没有办法来描述支持的是哪一种缓冲区,那是后话。
v4l2_buffer结构体

当流输入输出是有效的,帧是以v4l2_buffer的形式在应用和驱动之间传输的。这个结构体是一个复杂的--畜生?--,要用很长的时间才能描述完。一个很好的开头要大家知道一个缓冲区可以有三种基本的状态:

    * 在驱动的传入队列中.如果驱动不用它做什么有用事的话,应用就可以把缓冲区放在这个队列里。对于一个视频捕获设备还讲,传入队列中的缓冲区是空的,等待驱动向其内填入视频数据。对于输入设备来讲,这些缓冲区内是要送入设备的帧数据。
    * 在驱动的传出队列中.这些缓冲区已经经过驱动的处理,正等待应用来认领。对于捕获设备而言,传出缓冲区内是新的帧数据;对出输出设备而言,这个缓冲区是空的。
    * 不在上述两个队列里.在这种状态时,缓冲区是由用户空间拥有的,驱动无法访问。这是应用可以对缓冲区进行操作的唯一的时间。我们称其为用户空间状态。

这些状态和造成他们之间传输的操作都放在一起,在下图中示出:

实际上的v4l2_buffer结构体如下:

    struct v4l2_buffer
    {
        __u32                        index;
        enum v4l2_buf_type      type;
        __u32                        bytesused;
        __u32                        flags;
        enum v4l2_field                field;
        struct timeval                timestamp;
        struct v4l2_timecode        timecode;
        __u32                        sequence;

        /* memory location */
        enum v4l2_memory        memory;
        union {
                __u32           offset;
                unsigned long   userptr;
        } m;
        __u32                        length;
        __u32                        input;
        __u32                        reserved;
    };

index 字段是鉴别缓冲区的序号;它只在内存映射缓冲区中使用。与其它可以在V4L2接口中枚举的对相一样,内存映射缓冲区的index从0开始,依次递增。 type字段描述的是缓冲区的类型 ,通常是V4L2_BUF_TYPE_VIDEO_CAPTURE 或V4L2_BUF_TYPE_VIDEO_OUTPUT.

缓冲区的大小是论长度给定的,单位为byte.缓冲区中的图像数据大小可以在bytesused字段中找到。很明显,bytesused<=length.对于捕获设备而言,驱动会设置bytesused; 对输出设备而言,应用必须设置这个字段。

field字段描述的是图像存在缓冲区的那一个区域。这些区域在这系统文章中的part5a 中可以找到。

timestamp(时间戳)字段,对于输入设备来说,代表帧捕获的时间.对输出设备来说,在没有到达时间戳所代表的时间前,驱动不可以把帧发送出去;时间戳值为0代表越快越好。驱动会把时间戳设为帧的第一个字节传送到设备的时间,或者说是驱动所能达到的最接近的时间。timecode 字段可以用来存放时间编码,对于视频编辑类的应用是非常有用的。关于时间编码详情见表 .

驱动对传过设备的帧维护了一个递增的计数; 每一帧传送时,它都会在sequence字段中存入现行序号。对于输入设备来讲,应用可以观察这一字段来检测帧。

memory 字段表示的是缓冲是内存映射的还是用户空间的。对于内存映谢的缓冲区,m.offset 描述的是缓冲区的位置. 规范将它描述为“从设备存储器开发的缓冲区偏移”,但其实质却是一个 magic cookie,应用可以将其传给mmap(),以确定那一个缓冲区被映射了。然而对于用户空间缓冲区而言,m.userptr是缓冲区的用户空间地址。

input 字段可以用来快速切换捕获设备的输入 – 当然,这要得设备支持帧与帧音的快速切换才来。reserved字段应置0.

最后,还有几个标签定义:

    * V4L2_BUF_FLAG_MAPPED 暗示缓冲区己映射到用户空间。它只应用于内存映射缓冲区。

    * V4L2_BUF_FLAG_QUEUED: the buffer is in the driver’s incoming queue.
    * V4L2_BUF_FLAG_DONE: 缓冲区在驱动的传出队列.
    * V4L2_BUF_FLAG_KEYFRAME: 缓冲区包含一个关键帧,它在压缩流中是非常有用的.
    * V4L2_BUF_FLAG_PFRAME 和V4L2_BUF_FLAG_BFRAME 也是应用于压缩流中;他们代表的是预测的或者说是差分的帧.
    * V4L2_BUF_FLAG_TIMECODE: timecode 字段有效.
    * V4L2_BUF_FLAG_INPUT: input字段有效.

缓冲区设定

一旦流应用已经完成了基本的设置,它将转去执行组织I/O缓冲区的任务。第一步就是使用VIDIOC_REQBUFS ioctl()来建立一组缓冲区,它将由V4L2转换成对驱动的vidioc_reqbufs()方法的调用。

    int (*vidioc_reqbufs) (struct file *file, void *private_data,
                           struct v4l2_requestbuffers *req);

我们要关注的所以内容都在v4l2_requestbuffers结构体中,如下所示:

    struct v4l2_requestbuffers
    {
        __u32                        count;
        enum v4l2_buf_type      type;
        enum v4l2_memory        memory;
        __u32                        reserved[2];
    };

type 字段描述的是完成的I/O操作的类型。通常它的值要么是视频获得设备的V4L2_BUF_TYPE_VIDEO_CAPTURE ,要么是输出设备的V4L2_BUF_TYPE_VIDEO_OUTPUT.也有其它的类型,但在这里我们不予讨论。

如果应用想要使用内存映谢的缓冲区,它将会把memory字段置为V4L2_MEMORY_MMAP,count置为它想要使用的缓冲区的数目。如果驱动不支持内存映射,它就该返回-EINVAL.否则它将在内部开辟请求的缓冲区并返回0.返回之后,应用就会认为缓冲区是存在的,所以任何可以失败的任务都在在这个阶段进行处理 (比如说内存开辟)。

注意:驱动并不一定要开辟与请求的一样数目的缓冲区.在很多情况下,只有一个缓冲区数的最小值有意义。如果应用请求的比最小值小,它可以能实际须要的要多一些。以笔者的经验,mplayer要用两个缓冲区,如果用户空间速度慢下来的话,这将很容易超支(因而丢失帧)。通过强制一个大一点的最小缓冲区数(通过模块能数可以调整),cafe_ccic驱动可以使流输入输出通道更加强壮。count 字段应设为方法返回前实际开辟的缓冲区数。

应用可以通设置count字段为0的方式来释放掉所有已存在的缓冲区。在这种情况下,驱动必须在释放缓冲前停止所有的DMA操作,否则会发生非常严重的事情。如果缓冲区已映射到用户空间,则释放缓冲区是不可能的。

相反,如果用的是用户空间缓冲区,则有意义的字段只有缓冲区的type和只有V4L2_MEMORY_USERPTR这个值可用的memory 字段。应用不需要指定它想用的缓冲区的数目。因为内存是在用户空间开辟的,驱动无须操心。如果驱动支持用户空间缓冲区,它只须注意应用会使用这一特性,返回0就可以了,否则通常的-EINVAL 返回值会被调用到.

VIDIOC_REQBUFS 命令是应用得知驱动支持的流输入输出缓冲区类型的唯一方法。
将缓冲区映射到用户空间

如果使用了用户空间,在应用向传入队列放置缓冲区之前,驱动看不到任何缓冲区相关的调用。内存映射缓冲区需要更多的设置。应用通常会查看每一个开辟了的缓冲区,并将其映射到地址空间。第一站是VIDIOC_QUERYBUF命令,它将转换成驱动中的vidioc_querybuf() 方法:

    int (*vidioc_querybuf)(struct file *file, void *private_data,
                           struct v4l2_buffer *buf);

进入这个方法时,buf 字段中要设置的字段有type (在缓冲区开辟时,它将被检查是否与给定的类型相同)和index,它们可以确定一个特定的缓冲区。驱动要保证index有意义,并添充buf中的其余字段。通常来说,驱动内部存储着一个v4l2_buffer结构体的数组, 所以vidioc_querybuf()方法的核心只是一个结构体的赋值。

应用访问内存映射缓冲区的唯一方法就是将其映射到它们的地址空间,所以vidioc_querybuf() 调用后面通常会跟着一个驱动的mmap()方法 -这个方法,大家要记住,是存储在相关设备的video_device结构体中的fops字段中的。 设备如何处理mmap() 将依赖于内核中缓冲区是如何设置的。如果缓冲区可以在remap_pfn_range() 或remap_vmalloc_range()之前映射,那就应该在这个时间来做.对于内核空间的缓冲区,页也可以在页错误时通过常规的使用 nopage()方法的方式单独被映射,对于需要的人来说,在Linux Device Drivers 可以找到一个关于handlingmmap()的一个很好的讨论。

mmap() 被调用时,传递的VMA结构应该含有vm_pgoff字段中的某个缓冲区的地址-当然是经过PAGE_SHIFT右移过的。特别是,它应该是你的驱动对于 VIDIOC_QUERYBUF调用返回值的楄移。请您遍历缓冲区列表,并确保传入地址匹配其中之一。视频驱动程序不应该是一个可以让恶意程序映射内存的任意区域手段。

你所提供的偏移值可几乎所有的东西.有些驱动只是返回(index<<PAGE_SHIFT),意思是说传入的vm_pgoff字段应该正好是缓冲区索引。有一件事你不可以做的就是把缓冲区的内核实际地址存储到offset字段,把内核地址泄露给用户空间永远也不是一个好注意。

当用户空间映射缓冲区时,驱动应该在相关的v4l2_buffer结构体中调置V4L2_BUF_FLAG_MAPPED标签.它也必须设定open() 和close() VMA 操作,这样它才可以跟踪映谢了缓冲区的进程数。只要缓冲区在哪里映射了,它就不可以在内核里释放掉。如果一个或多个缓冲区的映谢计算降为0,驱动就应该停止正在进行的输入输出,因为没有进程要用它。
流输入输出

到现为止,我们己经看了很多设置,却没有传输过一帧的数据,我们离这步己经很近了,但在些之前还有一个步骤要做。当应用通过 VIDIOC_REQBUFS获得了缓冲区后,那个缓冲区都处于用户空间状态。如果他们是用户空间缓冲区,他们甚至还并不真的存在。在应用可以开始流的输入输出之前,它必须至少将一个缓冲区放到驱动传入队列,对出输出设备,那些缓冲区当然还要先添完有效的帧数据。

要把一个缓冲区加入队列,应用首先要发出一个VIDIOC_QBUF ioctl()调用,这个调用V4L2会映射为对驱动的vidioc_qbuf()方法的调用。

    int (*vidioc_qbuf) (struct file *file, void *private_data,
                        struct v4l2_buffer *buf);

对于内存映射缓冲而言,还是那样,只有buf的type和 index字段有效. 驱动只能进行一些明显的检查(type 和index 有意义,缓冲区还没有在驱动的队列里,缓冲区己映射等。),把缓冲区放在传入队列里 (设置V4L2_BUF_FLAG_QUEUED 标签),并返回.

在这点上,用户空间缓冲区可能会更加复杂,因为驱动可能从来没看过缓冲区的样子。使用这个方法时,允许应用在每次向队列传入缓冲区时,传递不同的地址,所以驱动不能提前做任何的设置。如果你的驱动通过内核空间缓冲区将帧送回,它只须记录一下应用提供的用户空间地址就可以了。然而如果你正尝试将通过 DMA直接将数据传送到用户空间,那将会非常的具有挑战性。

要想把数据直接传送到用户空间,驱必须先fault in缓冲区的所以的页,并将其锁定。get_user_pages() 可以做这件事.注意这个函数会在开辟很大的内存空间和硬盘I/O-它可能会卡住很长时间。你得注意要保证重要的驱动功能不能在 get_user_pages()时停止,因为它可能停止很长时间等待许多视频帧通过。

下面就是要告诉设备把图像数据传到用户空间缓冲区(或是相反的方向)了。缓冲区在物理上不是相临的,相反,它会被分散成很多很多单独的4096字节的页(在大部分架构上如此)。很明显,设备得可以实现分散/聚集DMA操作才行。如果设备立即传输一个完整的视频帧,它就需要接受一个包含很多页的分散列表(scatterlist)。一个 16位格式的VGA解决方案的图像需要150个页,随着图像大小的增加,分散列表的大小也会增加.V4L2规范说:

    如果硬件需要,驱动要与物理内存交换内存页,以产生相临的内存区。这对内核子系统的虚拟内存中的应用来说是透明的。

然而,笔者却不推荐驱动作者尝试这种深层的虚拟内存技巧。有一个更有前途的方法就是要求用户空间缓冲区分配成大的tlb页,但是现在的驱动不那么做。

如果你的设备转输的是小图像(如USB摄像头),直接从DMA到用户空间的设定就简单些。在任何情况下,不得不支持到用户空间缓冲区的直接I/O 时,驱动作者都应该(1)确定的确值得这么大的麻烦,因为应用更趋向于使用内存映射缓冲区。(2)使用video_buf屋,它可以帮你解决一些痛苦的难题 。

一旦流输入输出开始,驱动就要从它的传入队列里抓取缓冲区,让设备更快地实现转送请求,然后把缓冲区移动到传出队列。转输开始时,缓冲区标签也要相应调整。像序号和时间戳这样的字段必需在这个时候添充。最后,应用会在传出队列中认领缓冲区,让它变回为用户空间状态。这是VIDIOC_DQBUF的工作,它最终变为如下调用:

    int (*vidioc_dqbuf) (struct file *file, void *private_data,
                         struct v4l2_buffer *buf);

这里,驱动会从传出队列中移除第一个缓冲区,把相关的信息存入buf.通常,传出队列是空的,这个调用会处于阻塞状态直到有缓冲区可用。然而V4L2是用来处理非阻塞I/O的,所以如果视频设备是以O_NONBLOCK方式打开的,在队列为空的情况下驱动就该返回-EAGAIN .当然,这个要求也暗示驱动必须为流I/O支持poll();

剩下最后的一个步骤实际上就是告诉设备开始流输入输出操作。这个任务的 Video4Linux2驱动方法是:

    int (*vidioc_streamon) (struct file *file, void *private_data,
                            enum v4l2_buf_type type);
    int (*vidioc_streamoff)(struct file *file, void *private_data,
                                enum v4l2_buf_type type);

对vidioc_streamon()的调用应该在检查类型有意义之后让设备开始工作。如查需要的话,驱动可以请求等传入队列中有一定数目的缓冲区后再开始流的转输.

当应用关闭时,它应发出一个对vidioc_streamoff()的调用,这个调用要停止设备。驱动还应从传入还传出队列是移除所有的缓冲区,使它们都处于用户空间状态。当然,驱动必须准备好,应用可能在没有停流转输的情况下关闭设备。

v4l2驱动编写篇第七--控制方法 
刚刚完成了这一系列文章的第六部分,我们现在知道如何设置视频设备,并来回传输帧了。然而,有一个众所周知的事实,那就是用户永远也不会满意,不会满足于能从摄像头上看到视频,他们马上就会问我可不可以调参数啊?像亮度、对比度等等。这些参数可以视频应用中调整,有时也的确会这样做,但是当硬件支持时,在硬件中进行调整有其优势。比如说亮度调整,如果不这样做的话,可能会丢失动态范围,但是基于硬件的调整可以完整保持传感器可以传递的动态范围。很明显,基于硬件的调整也可以让主机的处理器压力轻些。

现代的硬件中通常都可以在运行的时候调整很多参数。然而现在,在不同设备之间这些参数差别很大。简单的亮度调整可以直观地设置一个寄存器,也可以需要处理一个没有头绪的矩阵变换。最好是能尽可能多的把诸多细节对应用隐藏,但能隐藏到什么程序却受到很多限制。一个过于抽象的接口会使硬件的控制无法发挥其极限。

V4L2的控制接口试图使事情尽可能地简单,同时还能完全发挥硬件的功能。它开始于定义一个标准控制名字的集合,包括 V4L2_CID_BRIGHTNESS,V4L2_CID_CONTRAST,V4L2_CID_SATURATION,还有许多其他的。对于白平衡、水平,垂直镜像等特性,还提供了一些布尔型的控制。定义的控制ID值的完整列表请见the V4L2 API spec 。还有一个驱动程序特定的控制,但这些,显然,一般只能由专用的应用程序使用。私有的控制从V4L2_CID_PRIVATE_BASE开始往后都是。

一种典型的做法,V4L2 API提供一种机制可以让应用可以枚举可用的控制操作。为此,他们要发出最终要实现为驱动中的vidioc_queryctrl()方法的ioctl()调用。

   int (*vidioc_queryctrl)(struct file *file, void *private_data,
                            struct v4l2_queryctrl *qc);

驱动通常会用所关心的控制信息来添充qc结构体,或是当控制操作不支持时返回EINVAL,这个结构体有很多个字段:

    struct v4l2_queryctrl
    {
        __u32                     id;
        enum v4l2_ctrl_type  type;
        __u8                     name[32];
        __s32                     minimum;
        __s32                     maximum;
        __s32                     step;
        __s32                     default_value;
        __u32                flags;
        __u32                     reserved[2];
    };

被查询的控制操作将会通过id传送。作为一个特殊的情况,应用可以通过设定V4L2_CTRL_FLAG_NEXT_CTRL位的方式传递控制id.当这种情况发生时,驱动会返回关于下一个所支持的控制id的信息,这比应用给出的ID要高。无论在何种情况下,id都应设为实际上被描述的控制操作的id.

其他所有字段都由驱动设定,用来描述所选的控制操作。控制的数据类型在type字段中给定。这可以是V4L2_CTRL_TYPE_INTEGER、 V4L2_CTRL_TYPE_BOOLEAN、V4L2_CTRL_TYPE_MENU (针对一组固定的择项) 或V4L2_CTRL_TYPE_BUTTON (针对一些设定时会忽略任何给出的值的控制操作).name字段用来描述控制操作;它可以在展现给用户的应用接口中使用。 对于整型的控制来说(仅针对这种控制),minimum和maximum 描述的是控制所实现的值的范围,step 给出的是此范围下的粒度大小。default_value顾名思义就是默认值 – 仅管他只对整型,布尔型和菜单控制适用。驱动只应在初始化时将控制参数设为默认。至于其它设备参数,他们应该从open()到close()保持不变。结果,default_value 很可能不是现在的控制参数值。

不可避免地,还有一组值进一步描述控制操作。V4L2_CTRL_FLAG_DISABLED 意为控制操作不可用,就用应该忽略它。V4L2_CTRL_FLAG_GRABBED 意为控制暂进不可变,可能会是因为另一个应用正在使用它。V4L2_CTRL_FLAG_READ_ONLY 意为可以查看,但不可以改变的控制操作。V4L2_CTRL_FLAG_UPDATE意为调整这个参数可以会对其他控制操作造成影响。 V4L2_CTRL_FLAG_INACTIVE 意为与当前设备配置无关的控制操作。V4L2_CTRL_FLAG_SLIDER 意在暗示应用在表现这个操作的时候可以使用类似于滚动条的接口。

应用可以只是查询几个特别编程过的控制操作,或者他们也想枚举整个集合。对后而来讲,他们会从开始V4L2_CID_BASE 至V4L2_CID_LASTP1结束,过程中可能会用到V4L2_CTRL_FLAG_NEXT_CTRL标签.对于菜单型的诸多控制操作 (type=V4L2_CTRL_TYPE_MENU)而言, 应用很可能想要枚举可能的值,相关的回调函数是:

int (*vidioc_querymenu)(struct file *file, void *private_data,
                            struct v4l2_querymenu *qm);

v4l2_querymenu 结构体如下:

    struct v4l2_querymenu
    {
        __u32                id;
        __u32                index;
        __u8                name[32];
        __u32                reserved;
    };

在输入中,id 是相关的菜单控制操作的ID值,index 某特定菜单ID值的索引值.索引值从0开始,依次递增到vidioc_queryctrl()返回的最大值.驱动会填充菜单项的name字段。reserved字段恒设为0.

一旦应用知道了可用的控制操作,它就很可以开始查询并改变其值。这种情况下相关的结构体是:

    struct v4l2_control
    {
        __u32 id;
        __s32 value;
    };

要查询某一给定控制操作,应用应将id字段设为对应的控制的ID,并发出一个调用,这个调最终实现为:

    int (*vidioc_g_ctrl)(struct file *file, void *private_data,
                             struct v4l2_control *ctrl);

驱动应将值设为当前控制的设定,还要保证它知道这个特定的控制操作并在应用试图查询不存在的控制操作时返回EINVAL,试图访问按键控制时也应返回EINVAL.

一个试图改变控制操作的请求实现为:

    int (*vidioc_s_ctrl)(struct file *file, void *private_data,
                         struct v4l2_control *ctrl);

驱动应该验证id,保证其值在允许的区间。如果一切都没有问题的话,就将新值写入硬件。

最后, 值得注意的是,还有一个单独的扩展控制接口 也为V4L2所支持.这个API是一组相当复杂的控制操作。实际上,它的主要应用就是 MPEG编解码的参数。扩展控制可以分门归类, 而且支持64位整型值。其接口与常规的控制接口类似。详见API规范。

2013-01-09 09:17:51 bingqingsuimeng 阅读数 0

Video for Linux Two
           
           V4L2的是V4L的第二个版本。原来的V4L被引入到Linux内核2.1.x的开发周期后期。Video4Linux2修正了一些设计缺陷,并开始出现在2.5.X内核。Video4Linux2驱动程序包括Video4Linux1应用的兼容模式,但实际上,支持是不完整的,并建议V4L2的设备使用V4L2的模式。现在,该项目的DVB-Wiki托管在LinuxTV的网站上。

        要想了解 V4l2 有几个重要的文档是必须要读的,Documentation/video4linux目录下的V4L2-framework.txt和videobuf、V4L2的官方API文档V4L2 API Specification 、drivers/media/video目录下的vivi.c(虚拟视频驱动程序 -此代码模拟一个真正的视频设备V4L2 API)。


        V4l2可以支持多种设备,它可以有以下几种接口:

         1. 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的.

         2. 视频输出接口(video output interface):可以驱动计算机的外围视频图像设备--像可以输出电视信号格式的设备.

         3. 直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU.

         4. 视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号.

         5. 收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流.

V4L2 驱动核心

         V4L2 的驱动源码在 drivers/media/video目录下,主要核心代码有:
              v4l2-dev.c                  //linux版本2视频捕捉接口,主要结构体 video_device 的注册
 
              v4l2-common.c        //在Linux操作系统体系采用低级别的操作一套设备structures/vectors的通用视频设备接口。
                                  //此文件将替换videodev.c的文件配备常规的内核分配。

              v4l2-device.c            //V4L2的设备支持。注册v4l2_device
 
              v4l22-ioctl.c             //处理V4L2的ioctl命令的一个通用的框架。
                    
              v4l2-subdev.c          //v4l2子设备
 
              v4l2-mem2mem.c  //内存到内存为Linux和videobuf视频设备的框架。设备的辅助函数,使用其源和目的地videobuf缓冲区。

   头文件linux/videodev2.h、media/v4l2-common.h、media/v4l2-device.h、media/v4l2-ioctl.h、media/v4l2-dev.h、media/v4l2-ioctl.h等。


V4l2相关结构体

 1.V4l2_device

 struct V4l2_device{

      /* DEV-> driver_data指向这个结构。 注:DEV可能是空的,如果没有父设备是如同ISA设备。 */
      struct device *dev;
     /* 用于跟踪注册的subdevs */
    struct list_head subdevs;
    /*锁定此结构体;可以使用的驱动程序以及如果这个结构嵌入到一个更大的结构。 */
    spinlock_t lock;
    /* 独特的设备名称,默认情况下,驱动程序姓名+总线ID */
    char name[V4L2_DEVICE_NAME_SIZE];
   /*报告由一些子设备调用的回调函数。 */
     void (*notify)(struct v4l2_subdev *sd,
     unsigned int notification, void *arg);
};

v4l2_device注册和注销      
    
       v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);   

     第一个参数‘dev’通常是一个pci_dev的struct device的指针,但它是ISA设备或一个设备创建多个PCI设备时这是罕见的DEV为NULL,因此makingit不可能联想到一个特定的父母v4l2_dev。 您也可以提供一个notify()回调子设备,可以通过调用通知你的事件。取决于你是否需要设置子设备。一个子设备支持的任何通知必须在头文件中定义 .
   
    注册时将初始化 v4l2_device 结构体. 如果 dev->driver_data字段是空, 它将连接到 v4l2_dev.

       v4l2_device_unregister(struct v4l2_device *v4l2_dev);
         
    注销也将自动注销设备所有子设备。

2.video_device 

      在/dev目录下的设备节点使用的 struct video_device(v4l2_dev.h)创建。
 struct video_device
     {
         /*设备操作函数 */
         const struct v4l2_file_operations *fops;

         /* 虚拟文件系统 */
         struct device dev;        /* v4l 设备 */
         struct cdev *cdev;        /* 字符设备 */

        struct device *parent;        /*父设备 */
        struct v4l2_device *v4l2_dev;    /* v4l2_device parent */

        /* 设备信息 */
         char name[32];
         int vfl_type;
         /* 'minor' is set to -1 if the registration failed */
        int minor;
        u16 num;
         /* use bitops to set/clear/test flags */
         unsigned long flags;
         /*属性来区分一个物理设备上的多个索引 */
        int index;

         /* V4L2 文件句柄 */
         spinlock_t        fh_lock; /*锁定所有的 v4l2_fhs */
         struct list_head    fh_list; /* List of struct v4l2_fh */

         int debug;            /* Activates debug level*/

         /* Video standard vars */
         v4l2_std_id tvnorms;        /* Supported tv norms */
         v4l2_std_id current_norm;    /* Current tvnorm */

         /* 释放的回调函数 */
        void (*release)(struct video_device *vdev);

         /* 控制的回调函数 */
         const struct v4l2_ioctl_ops *ioctl_ops;
     }

  动态分配:
          struct video_device *vdev = video_device_alloc();

   结构体配置:

         fops:设置这个v4l2_file_operations结构,file_operations的一个子集。v4l2_dev: 设置这个v4l2_device父设备
         name:
         ioctl_ops:使用v4l2_ioctl_ops简化的IOCTL,然后设置v4l2_ioctl_ops结构。
         lock:如果你想要做的全部驱动程序锁定就保留为NULL。否则你给它一个指针指向一个mutex_lock结构体和任何v4l2_file_operations被调用之前核心应该释放释放锁。
         parent:一个硬件设备有多个PCI设备,都共享相同v4l2_device核心时,设置注册使用NULL v4l2_device作为父设备结构。
         flags:可选的。设置到V4L2_FL_USE_FH_PRIO如你想让框架处理VIDIOC_G/ S_PRIORITY的ioctl。这就需要您使用结构v4l2_fh。这个标志最终会消失,一旦所有的驱动程序使用的核心优先处理。但现在它必须明确设定。

   如果使用v4l2_ioctl_ops,那么你应该设置。unlocked_ioctlvideo_ioctl2在v4l2_file_operations结构。

 注册/注销 video_device:

    video_register_device(struct video_device *vdev, int type, int nr);
    __video_register_device(struct video_device *vdev, int type, int nr,int warn_if_nr_in_use)
        参数:
           vdev:我们要注册的视频设备结构。
           type:设备类型注册
           nr:设备号(0==/dev/video0,1​​== /dev/video1,...-1==释放第一个)
           warn_if_nr_in_use:如果所需的设备节点号码已经在使用另一个号码代替选择。
    
       注册程式分配次设备号和设备节点的数字根据请求的类型和注册到内核新设备节点。如果无法找到空闲次设备号或设备节点编号,或者如果设备节点注册失败,就返回一个错误。
 
           video_unregister_device(struct video_device *vdev); 

  3.v4l2_subdev
 
          每个子设备驱动程序必须有一个v4l2_subdev结构。这个结构可以独立简单的设备或者如果需要存储更多的状态信息它可能被嵌入在一个更大的结构。由于子设备可以做很多不同的东西,你不想结束一个巨大的OPS结构其中只有少数的OPS通常执行,函数指针进行排序按类别,每个类别都有其自己的OPS结构。顶层OPS结构包含的类别OPS结构,这可能是NULL如果在subdev驱动程序不支持任何从该类别指针。
         struct v4l2_subdev {
     #if defined(CONFIG_MEDIA_CONTROLLER)
         struct media_entity entity;
     #endif
         struct list_head list;
         struct module *owner;
         u32 flags;
         struct v4l2_device *v4l2_dev;
         const struct v4l2_subdev_ops *ops;
         /* 从驱动程序中不要调用这些内部操作函数! */
         const struct v4l2_subdev_internal_ops *internal_ops;
         /*这个subdev控制处理程序。可能是NULL。 */
         struct v4l2_ctrl_handler *ctrl_handler;
         /* 名字必须是唯一 */
         char name[V4L2_SUBDEV_NAME_SIZE];
         /* 可用于到类似subdevs组,值是驱动程序特定的 */
         u32 grp_id;
         /* 私有数据的指针 */
         void *dev_priv;
         void *host_priv;
         /* subdev 设备节点*/
         struct video_device devnode;
         /* 事件的数量在打开的时候被分配 */
         unsigned int nevents;
      };
 

 4.v4l2_buffer

struct v4l2_buffer {
    __u32            index;
    enum v4l2_buf_type      type;
    __u32            bytesused;
    __u32            flags;
     enum v4l2_field        field;
    struct timeval        timestamp;
     struct v4l2_timecode    timecode;
    __u32            sequence;

    /* memory location */
    enum v4l2_memory        memory;
    union {
        __u32           offset;
        unsigned long   userptr;
    } m;
    __u32            length;
    __u32            input;
    __u32            reserved;
};

   V4L2核心API提供了一套标准方法的用于处理视频缓冲器(称为“videobuf”)。这些方法允许驱动程序以一致的方式来实现read(),mmap()和overlay()。目前使用的设备上的视频缓冲器,支持scatter/gather方法(videobuf-dma-SG),线性存取的DMA的(videobuf-DMA-contig),vmalloc分配的缓冲区,主要用于在USB驱动程序(DMA缓冲区的方法videobuf-vmalloc)。

   videobuf层的功能为一种V4L2驱动和用户空间之间的粘合层。它可以处理存储视频帧缓冲区的分配和管理。有一组可用于执行许多标准的POSIX I / O系统调用的功能,包括read(),poll()的,happily,mmap()。另一套功能可以用来实现大部分的V4L2的ioctl()调用相关的流式I/ O的,包括缓冲区分配,排队和dequeueing,流控制。驱动作者使用videobuf规定了一些设计决定,但回收期在驱动器和一个V4L2的用户空间API的贯彻实施在减少代码的形式。

   关于videobuf的层的更多信息,请参阅Documentation/video4linux/videobuf

V4l2驱动架构 

      驱动架构图

        

      所有的驱动程序有以下结构:


         1) 每个设备包含设备状态的实例结构。

         2) 子设备的初始化和命令方式(如果有).

         3) 创建V4L2的设备节点 (/dev/videoX, /dev/vbiX and /dev/radioX)和跟踪设备节点的具体数据。

         4)文件句柄特定的结构,包含每个文件句柄数据;

         5) 视频缓冲处理。


驱动源码分析

        vivi.c 虚拟视频驱动程序
                      ----- 此代码模拟一个真正的视频设备V4L2 API (位于drivers/media/video目录下)

  入口:+int __init vivi_init(void)

                 + vivi_create_instance(i) /*创建设备*//**/。

                         + 分配一个vivi_dev的结构体 /*它嵌套这结构体v4l2_device 和video_device*/
                         + v4l2_device_register(NULL, &dev->v4l2_dev);/*注册vivi_dev中的V4l2_device*/
                         + 初始化视频的DMA队列
                         + 初始化锁
                         + video_device_alloc(); 动态分配video_device结构体
                         + 构建一个video_device结构体 vivi_template 并赋给上面分配的video_device

                                static struct video_device vivi_template = {
                                          . name        = "vivi",
                                          .fops           = &vivi_fops,
                                          .ioctl_ops     = &vivi_ioctl_ops,
                                          .minor        = -1,
                                          .release    = video_device_release,

                                          .tvnorms              = V4L2_STD_525_60,
                                          .current_norm         = V4L2_STD_NTSC_M,
                                 };

                       + video_set_drvdata(vfd, dev);设置驱动程序专有数据
                       + 所有控件设置为其默认值
                       + list_add_tail(&dev->vivi_devlist, &vivi_devlist);添加到设备列表
          + 构建 v4l2_file_operations 结构体vivi_fops 并实现.open .release .read .poll .mmap函数
                            ----- .ioctl 用标准的v4l2控制处理程序
          + 构建 v4l2_ioctl_ops结构体 vivi_ioctl_ops 

                             static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
                                        .vidioc_querycap      = vidioc_querycap,
                                        .vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
                                        .vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
                                        .vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
                                        .vidioc_reqbufs       = vidioc_reqbufs,
                                        .vidioc_querybuf      = vidioc_querybuf,
                                        .vidioc_qbuf          = vidioc_qbuf,
                                        .vidioc_dqbuf         = vidioc_dqbuf,
                                        .vidioc_s_std         = vidioc_s_std,
                                        .vidioc_enum_input    = vidioc_enum_input,
                                        .vidioc_g_input       = vidioc_g_input,
                                        .vidioc_s_input       = vidioc_s_input,
                                        .vidioc_queryctrl     = vidioc_queryctrl,
                                        .vidioc_g_ctrl        = vidioc_g_ctrl,
                                        .vidioc_s_ctrl        = vidioc_s_ctrl,
                                        .vidioc_streamon      = vidioc_streamon,
                                        .vidioc_streamoff     = vidioc_streamoff,
                             #ifdef CONFIG_VIDEO_V4L1_COMPAT
                                       .vidiocgmbuf          = vidiocgmbuf,
                           #endif
                       };

           + int vivi_open(struct file *file)
                     + vivi_dev *dev = video_drvdata(file);  访问驱动程序专用数据
                     + 分配+初始化句柄(vivi_fh)数据
                     + 重置帧计数器
                     + videobuf_queue_vmalloc_init(); 初始化视频缓冲队列
                     + 开启一个新线程用于开始和暂停
           + 实现自定义的v4l2_ioctl_ops 函数 

附:Sub-devices

                   Sub-devices 支持的头文件 V4l2-subdev.h

  •            子设备某种程度上主桥设备连接的设备。这些设备通常是音频/视频流 合并器/编码器/解码器或传感器和摄像头控制器。

                         一般控制这些器件通过I2C总线,但也可用于其他总线。

  •           v4l2_subdev结构提供了一个通用的方式访问这些设备, sub-devices支持的大部分操作分为以下几个类别:

                    核心操作、音频操作、视频操作、调谐器操作每个类别都可以在实现subdev的驱动程序时设置自己的操作。

                                 struct v4l2_subdev_ops {
                                           const struct v4l2_subdev_core_ops  *core;
                                           const struct v4l2_subdev_tuner_ops *tuner;
                                           const struct v4l2_subdev_audio_ops *audio;
                                           const struct v4l2_subdev_video_ops *video;
                                 };

  •           一个subdev驱动程序可以设置执行的操作函数指针为空(如要产生音频subdev通常没有实现视频类操作)。唯一的例外是核心类:必须始终存在。
  •           这些OPS都在内部使用,所以它是没有必要去更改,添加或删除它们或从一个移动到另一个类别。目前这些OPS基于原有的控制命令,但OPS不仅限于一个参数,一旦所有的I2C驱动subdev转换为使用这些OPS,就有改进的余地。

   1. 核心操作 v4l2_subdev_core_ops 

struct v4l2_subdev_core_ops {
    int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);
    int (*log_status)(struct v4l2_subdev *sd);
    int (*s_config)(struct v4l2_subdev *sd, int irq, void *platform_data);
    int (*init)(struct v4l2_subdev *sd, u32 val);
    int (*load_fw)(struct v4l2_subdev *sd);
    int (*reset)(struct v4l2_subdev *sd, u32 val);
    int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
    int (*queryctrl)(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc);
    int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
    int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
    int (*g_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
    int (*s_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
    int (*try_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
    int (*querymenu)(struct v4l2_subdev *sd, struct v4l2_querymenu *qm);
    int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);
    long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
#ifdef CONFIG_VIDEO_ADV_DEBUG
    int (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);
    int (*s_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);
#endif
};

           我们强烈建议至少实现这些操作:

                        g_chip_ident
                        log_status
                        g_register
                        s_register

              这些提供了基本的调试支持。
               ioctl:指为仿制的ioctl类的命令。根据使用情况,它可能是更好地使用特定子设备操作目前尚未实施),因为OPS提供适当的类型检查。
 

               s_config:如果设置,那么它总是在v4l2_subdev被注册后由v4l2_i2c_new_subdev函数调用后。它用于将数据传递到平台,可用于subdev在初始化过程中。

               init:一些适当的默认值初始化传感器寄存器。不使用新的驱动程序时现有的驱动程序应该删除它。

               reset复位通用的命令。参数选择复位哪一个子系统。传递0复位整个芯片。使用新的驱动程序前要先挂载在linux-media 列表上再复位。

               s_gpio:设置GPIO引脚。如果需要的话可能还需要扩展方向参数。


     2.分类操作 

                   struct v4l2_subdev_video_ops、

                   struct v4l2_subdev_audio_ops、

                   struct v4l2_subdev_tuner_ops

             以上结构体都在V4l2-subdev.h 中定义。由于操作函数太多就没有必要一一列举。

    3.v4l2_subde初始化

                   (1)子设备驱动初始化的v4l2_subdev结构使用:

                                        v4l2_subdev_init(sd, &ops);

                   (2)V4l2 i2c子设备的初始化

                                  void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client, const struct v4l2_subdev_ops *ops)

                   (3)装载一个新的i2c 子设备

                                       v4l2_i2c_new_subdev_board()

                                注:如果模块加载先加载这驱动程序发现后设置client->driver,如果模块不是先加载的,那么I2C核心试图延迟加载的模块,然后驱动程序的client->drive 默认为 NULL,直到加载模块。如果其他驱动程序要使用I2C设备,使明确的加载模块是最好的选择,这延迟加载机制不起作用。

            

                  (4)获取i2c subdev地址

                                 short v4l2_i2c_subdev_addr(struct v4l2_subdev *sd)  

                          返回I2C v4l2_subdev客户端地址。  

2018-07-26 16:45:13 huhaoxuan2010 阅读数 1866

环境:

OS:Ubuntu 16.04 (Win10 hypev)

Kernel Version:3.13.0-24-generic

这里终极目标是注册一个/dev/video0的设备,再通过一个应用程序去读取它:

#include <linux/module.h>
#include <linux/videodev2.h>
#include <media/videobuf2-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <media/v4l2-common.h>
// 声明结构体sv供v4l2_device_register使用
struct sv{
    struct v4l2_device v4l2_dev;
    struct video_device vdev;
}; 

static struct sv sdev;
// 查询设备支持的功能
static int sv_querycap(struct file *file, void *priv, struct v4l2_capability *vcap)
{
    struct sv *sv = video_drvdata(file);
    strlcpy(vcap->driver, sv->vdev.name,sizeof(vcap->driver));
    strlcpy(vcap->card, "Stanway test card",sizeof(vcap->card));
    strlcpy(vcap->bus_info, "Stanway test bus",sizeof(vcap->bus_info));
    vcap->capabilities = V4L2_CAP_VIDEO_CAPTURE|V4L2_CAP_READWRITE; // report capabilities
    printk(KERN_INFO "[stanway]%s, %d, \n", __func__,__LINE__);
    return 0;
}

/***********************************************
 *
 *V4L2模块函数
 *
 **********************************************/

static const struct v4l2_file_operations sv_fops = {
    .owner = THIS_MODULE,
    .open = v4l2_fh_open, // open /dev/video0
    .release = v4l2_fh_release, // close /dev/video
    .unlocked_ioctl = video_ioctl2,
};

static const struct v4l2_ioctl_ops sv_ioctl_ops = {
    .vidioc_querycap = sv_querycap, // ioctl VIDIOC_QUERYCAP 时会调用sv_querycap函数
};

static int __init vivi_init(void)
{
    struct sv *sv;
    struct v4l2_device *v4l2_dev;
    int ret;

    sv = &sdev;
    v4l2_dev = &sv->v4l2_dev;
    //init v4l2 name, version
    strlcpy(v4l2_dev->name, "sv", sizeof(v4l2_dev->name));
    v4l2_info(v4l2_dev, "Color SV VGA driver %s\n", "0.0.1"); //output V4l2 info
    ret = v4l2_device_register(NULL, v4l2_dev);
    if (ret < 0)
    {
      printk(KERN_INFO "Could not register v4l2_device\n");
      return ret;
    }

    //setup video
    strlcpy(sv->vdev.name, "My vivi driver", sizeof(sv->vdev.name));
    sv->vdev.v4l2_dev = v4l2_dev; // set v4;2_device address to video_device
    sv->vdev.fops = &sv_fops; // v4l2_file_operations
    sv->vdev.ioctl_ops = &sv_ioctl_ops; // v4l2_ioctl_ops
    sv->vdev.release = video_device_release_empty;
    set_bit(V4L2_FL_USES_V4L2_FH, &sv->vdev.flags);
    video_set_drvdata(&sv->vdev, sv); //将sv设置为驱动私有数据

    if (video_register_device(&sv->vdev, VFL_TYPE_GRABBER, -1) != 0){
        printk(KERN_INFO "[stanway%s, %d, video_register_device FAIL\n", __func__,__LINE__);
        ret = -ENODEV;
        goto out_dev;
    }
    printk(KERN_INFO "[stanway]%s, %d, module inserted\n", __func__, __LINE__);
    return 0;

out_dev:
    v4l2_device_unregister(&sv->v4l2_dev);
    video_unregister_device(&sv->vdev);
    return ret;
}

static void __exit vivi_exit(void)
{
    struct sv *sv;
    sv = &sdev;

    printk(KERN_INFO "[stanway] %s, %d, module remove\n", __func__, __LINE__);
    video_unregister_device(&sv->vdev);
    v4l2_device_unregister(&sv->v4l2_dev);
}

module_init(vivi_init);
module_exit(vivi_exit);
MODULE_DESCRIPTION("Stanway test module");
MODULE_AUTHOR("Stanway Hu");
MODULE_LICENSE("GPL");

Makefile:

#
#Makefile for kernel test
#
CONFIG_MODULE_SIG=n
PWD := $(shell pwd)
KVERSION := $(shell uname -r)
KERNEL_DIR = /usr/src/linux-headers-$(KVERSION)/

MODULE_NAME = myv4l2
obj-m := $(MODULE_NAME).o

all:
        make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
        make -C $(KERNEL_DIR) M=$(PWD) clean

编译:

make

加载:

sudo insmod myv412.ko

dmesg报错:

[11095.173012] myv4l2: Unknown symbol video_ioctl2 (err 0)
[11095.173017] myv4l2: Unknown symbol v4l2_fh_open (err 0)
[11095.173020] myv4l2: Unknown symbol video_devdata (err 0)
[11095.173022] myv4l2: Unknown symbol v4l2_fh_release (err 0)
[11095.173026] myv4l2: Unknown symbol video_unregister_device (err 0)
[11095.173029] myv4l2: Unknown symbol v4l2_device_register (err 0)
[11095.173032] myv4l2: Unknown symbol __video_register_device (err 0)
[11095.173034] myv4l2: Unknown symbol v4l2_device_unregister (err 0)
[11095.173037] myv4l2: Unknown symbol video_device_release_empty (err 0)

解决:

1.安装v4l2支持:

sudo apt-get install v4l2loopback-dkms

2.进入目录:/lib/modules/3.13.0-24-generic/kernel/drivers/media/v4l2-core,运行:

sudo insmod videodev.ko

3.再insmod myv4l2.ko即可:lsmod

ls /dev/video

dmesg:

再sudo rmmod myv4l2

现在再来编一个应用程序vtest.c读取/dev/video0:

#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>


static int xioctl(int fd,int request, void *arg)
{
int r;
do r=ioctl(fd,request,arg);
while(-1 ==r && EINTR == errno);


return r;
}




int print_caps(int fd)
{
struct v4l2_capability caps={};


perror("print_caps enter");


if(-1 == xioctl(fd,VIDIOC_QUERYCAP,&caps))
{
perror("Querying cap fail");
return 1;        
}


printf( "Driver Caps:\n"
	  "  Driver: \"%s\"\n"
	  "  Card: \"%s\"\n"
	  "  Bus: \"%s\"\n"
	  "  Version: %d.%d\n"
	  "  Capabilities: %08x\n",
	  caps.driver,
	  caps.card,
	  caps.bus_info,
	  (caps.version>>16)&&0xff,
	  (caps.version>>24)&&0xff,
	  caps.capabilities);
	  
	  
perror("print_caps enter");


return 0;    
}


int main()
{
int fd;
fd=open("/dev/video0",O_RDWR);
if(fd ==-1)
{
    perror("opening video device fail");
    return 1;
      
}
perror("opening video device success");


if(print_caps(fd))
{
  return 1;
}else{
  
}
perror("close fd");
close(fd);


return 0;


}  
	  "  Driver: \"%s\"\n"
	  "  Card: \"%s\"\n"
	  "  Bus: \"%s\"\n"
	  "  Version: %d.%d\n"
	  "  Capabilities: %08x\n",
	  caps.driver,
	  caps.card,
	  caps.bus_info,
	  (caps.version>>16)&&0xff,
	  (caps.version>>24)&&0xff,
	  caps.capabilities);
	  
	  
perror("print_caps enter");


return 0;    
}


int main()
{
int fd;
fd=open("/dev/video0",O_RDWR);
if(fd ==-1)
{
    perror("opening video device fail");
    return 1;
      
}
perror("opening video device success");


if(print_caps(fd))
{
  return 1;
}else{
  
}
perror("close fd");
close(fd);


return 0;


}  

再gcc vtest.c -o test,再运行./vtest:

V4L2架构概览:

由框架可知,有两种方式编写sensor的驱动程序:

一是直接将sensor作为video device,若采用这种方式需要自己处理内存管理问题,比较复杂。

二是将sensor作为子设备供上一层V4L2核心调用,采用这种方式只需要调用V4L2提供的API即可。

上面例子使用的就是第二种方式。

下面介绍一下V4L2整体情况:

V4L2提供一套数据结构和底层V4L2驱动接口规范供Linux下的视频设备程序使用,主要是一系列回调函数,如设置摄像头频率,帧率,视频压缩格式和图像参数等,还可用于其他多媒体开发,如音频等。

一般采用V4L2驱动的摄像头设备文件是/dev/video0,V4L2支持两种方式采集图像:内存映射方式mmap和直接读取方式read。

V4L2的重要数据结构都存放在/include/linux/videodev2.h文件中,在采集图像过程中,就是通过操作这些数据结构来获得最终图像数据,Linux系统对V4L2的支持是从Linux 2.5.x版本开始的,它可在内核编译阶段配置,或后期安装,默认情况下都有此开发接口。

videodev2.hl类似位置,其内部实际指向另外的.h:

V4L2定义了通用API元素,图像的格式,输入输出方法,以及Linux内核驱动处理视频信息的一系列接口,有主要以下五大接口:

1.视频采集接口(video capture interface)

2.视频输出接口(video output interface)

3.视频覆盖预览接口 (video overlay interface) --- 此功能会将采集到的画面保存在内存中,同时不需要经过其他处理直接将画面显示在屏幕上

4.视频输出覆盖接口 (video output overlay interface)--- 此功能打开,第3个功能需要关闭

5.编解码接口 (codec interface)

V4L2结构体:

1.常用结构体在文件./include/uapi/linux/videodev2.h下定义:

struct v4l2_requestbuffers:申请缓冲区,对应命令为VIDIOC_REQBUFS

VIDIOC_REQBUFS命令通过该结构体向驱动发出申请,请求一片连续的内存空间用于缓存视频信息。

cout:申请的缓冲区个数,是根据图像占用空间大小而设定的

type:视频捕获模式,枚举v4l2_buf_type

memory:内存区的使用方式

struct v4l2_capability:视频设备的功能,对应命令为VIDIOC_QUERYCAP

这里就是上例中用到的结构体:

1>.driver[16]:表示驱动名,这个名需要与struct video_device中的name字段匹配

2>.card[32]:表示设备名

3>.bus_info[32]:表示设备在系统中的位置

4>.version:表示驱动版本号

5>.capabilities:表示设备支持的操作,常见值有V4L2_CAP_VIDEO_CAPTURE|V4L2_CAP_STREAMING表示的是一个视频捕捉设备且具有数据流控制模式,支持的模式可见如下枚举,它是后面v4l2_format结构体type字段的值:

常见捕获模式为视频捕获模式V4L2_BUF_TYPE_VIDEO_CAPTURE,在此模式下采用fmt枚举域:v4l2_pix_format:

其中:

width:视频宽

height:视频高

pixelformat:视频数据格式(常见值有V4L2_PIX_FMT_YUV422P|V4L2_PIX_FMT_RGB565)

field:v4l2_field的枚举量

bytesperline:一行图像占用的字节数

sizeimage:图像占用的总字节数

colorspace:设备的颜色空间

6>.reserved[4]:保留字段

struct v4l2_input:视频输入信息,对应命令VIDIOC_ENUMINPUT

index:表示哪一个输入

name:输入标签

type:输入类型

audioset:关联的音频(位域)

tunner:关联的调谐器(FM/AM?)

std:

status:

reserved:

struct v4l2_standard:视频制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD

struct v4l2_format:帧格式,对应命令VIDIOC_G_FMT,VIDIOC_S_FMT等

struct v4l2_buffer:驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF

index:缓存编号

type:视频捕获模式

bytesused:缓存已使用空间大小

flags:缓存当前状态,常见值有V4L2_BUF_FLAG_MAPPED|V4L2_BUF_FLAG_QUEUED|V4L2_BUF_FLAG_DONE,分别表示当前缓存已经映射,缓存可以采集数据,缓存可以提取数据。

timestamp:时间戳

sequence:缓存序号

memory:缓存使用方式

offset:当前缓存与内存区起始地址的偏移

length:缓存大小

reserved2:

reserved:一般用于传递物理地址

VIDIOC_QBUF和VIDIOC_DQBUF命令都采用该结构与驱动通信:

VIDIOC_QBUF命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频队列,传递的主要参数为index

VIDIOC_DQBUF命令向驱动获取已经存放有视频数据的缓存,该结构体的各个字段几乎都会被更新,但主要的参数也是index,应用程序会根据index确定可用数据的起始地址和范围。

struct v4l2_crop:视频信号矩形边框

 

2.常用的IOCTL接口命令也在文件./include/uapi/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:检查当前视频设备支持的标准,如PATL或NTSC

调用V4L2的流程:

打开设备 -> 检查和设置设备属性 -> 设置帧格式 -> 设置一种输入输出方法,缓冲区管理 - > 循环获取数据 ->关闭设备

1.打开设备:在V4L2中,视频设备被看作一个文件,使用open函数即可打开该设备,有两种模式:

一是非阻塞模式打开设备,这种模式下即使尚未捕获到数据,驱动依旧会把缓冲DQBUFF内的数据返回给应用层:

int fd = open("/dev/video0", O_RDWR|O_NONBLOCK);

二是阻塞模式打开设备,这种模式下必须捕获到数据才返回,否则一直等待:

int fd = open("/dev/video0", O_RDWR);

2.获取设备特性:查看设备都支持什么功能,比如是否具有视频输入功能

struct v4l2_capability caps={};

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

//获取成功,检查是否有视频捕获功能

if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)){

...

}

//是否具有流控制功能

 

if (!(caps.capabilities & V4L2_CAP_STREAMING)){

...

}

3.选择视频输入:

struct v4l2_input input;

.......这里初始化input?

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

一个视频设备可以有多个视频输入,若只有一路输入,这个功能可省去。

VIDIOC_G_INPUT和VIDIOC_S_INPUT用于查询和选择当前的input,一个video设备节点可能对应多个视频源,比如saf7113可最多支持四路cvbs输入,若上层想要在四个cvbs视频输入间切换,则就要调用ioctl(fd, VIDIOC_S_INPUT, &input)来切换。

VIDIOC_G_INPUT和VIDIOC_G_OUTPUT返回当前的video input和output的index值。

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.设置视频捕获格式:v4l2_format结构体用以设置摄像头的视频制式,帧格式等,在设置这个参数时应先填好v4l2_format的各个域,如type传输流类型,fmt.pix.width宽,fmt.pix.height高,fmt.pix.pixelformat采样类型,如YUV4:2:2,后通过VIDIOC_S_FMT命令设置视频捕捉格式。

struct v4l2_format fmt;

memset(&fmt, 0, sizeof(fmt));

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

fmt.fmt.pix.width = g_display_width;

fmt.fmt.pix.heigth = g_display_height;

fmt.fmt.pix.pixelformat = g_fmt;

fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

//设置设备捕获视频的格式

if (ioctl(dev->fd, VIDIOC_S_FMT, &fmt) < 0)

{

...

}

若该视频设备驱动不支持所设定的图像格式,视频驱动会重新修改struct v4l2_format结构体变量的值为该设备所支持的图像格式,因此在程序设计时,设定完所有的视频格式后,要获取实际的视频格式,需要重新读取struct v4l2_format结构体变量。

6.向驱动申请帧缓存:一般不超过5个,CAP_BUF_NUM = 4

struct v4l2_requestbuffers req;/* 申请设备的缓存区 */
memset(&req, 0, sizeof(req));
req.count = CAP_BUF_NUM;  //申请一个拥有四个缓冲帧的缓冲区
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;

if (ioctl(dev->fd, VIDIOC_REQBUFS, &req) < 0)
{
    if (EINVAL == errno)
    {
        printf(stderr, "%s does not support "
                 "memory mapping\n", dev->dev);
        return TFAIL;
    }
    else
    {
        printf(stderr, "%s does not support "
                 "memory mapping, unknow error\n", dev->dev);
        return TFAIL;
    }
}
if (req.count < 2)
{
    printf(stderr, "Insufficient buffer memory on %s\n",
             dev->dev);
    return TFAIL;
}

v4l2_requestbuffers结构体定义了缓存的数量,驱动会根据此申请对应数量的视频缓存,多个缓存可用于建立FIFO,来提高视频采集的效率,控制命令为VIDIOC_REQBUFS

主要功能:请求V4L2驱动分配视频缓冲区,也就是申请V4L2视频驱动分配内存,V4L2是视频设备的驱动层,它位于内核空间,因此通过VIDIOC_REQBUFS控制命令申请的内存空间位于内核空间中,应用程序不能直接访问,需要调用mmap内存映射函数把内核空间的内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。若成功,则会在V4L2驱动层分配好视频缓冲区。

7.获取每个缓存的信息,并mmap到用户空间

应用层和设备有三种交换数据的方法,直接read/write,内存映射mmap和用户指针,这里只说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;
}

mmap语法:

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

参数:

addr:映射的起始地址,一般为NULL,让内核自动选择

length:被映射内存块的长度

prot:标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE

flags:确定此内存映射能否被其他进程共享:MAP_SHARED,MAP_PRIVATE

fd:返回成功映射后的地址,不成功返回MAP_FAILED((void)-1)

offset:被映射对象内容的起点

munmap语法:

int munmap( void * addr, size_t len ) ;

执行成功返回0,失败返回-1。该调用在进程地址空间中解除一个映射关系

addr:为调用mmap()时返回的地址

len:为映射区的大小

详细参考网址

8.开始采集视频,也就是在缓冲区处理好之后就可获得视频了:在开始之前,还需要把缓冲帧放入缓冲队列中。

//把四个缓冲帧放入队列
for (i = 0; i < CAPBUFNUM; i++)
{
memset(&buf, 0, sizeof(buf));
buf.type = V4L2BUFTYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
buf.m.offset = dev->buffer[i].offset;
/ 将空闲的内存加入可捕获视频的队列 /
if(ioctl(dev->fd, VIDIOC_QBUF, &buf) < 0)
{
printf(“ERROR: VIDIOC_QBUF[%s], FUNC[%s], LINE[%d]\n”, dev->dev, __FUNCTION, __LINE);
return TFAIL;
}
}

type = V4L2BUFTYPEVIDEOCAPTURE;
/ 打开设备视频流 /
if(ioctl(dev->fd, VIDIOC_STREAMON, &type) < 0)
{
printf(“ERROR: VIDIOC_STREAMON[%s], FUNC[%s], LINE[%d]\n”, dev->dev, __FUNCTION, __LINE);
return TFAIL;
}

在前期初始化完成之后,只是解决了一帧视频数据的格式和大小问题,而连续视频帧数据的采集需要用帧缓冲区队列的方式来解决,也就是要通过驱动程序在内存中申请多个缓冲区来存放视频数据。以下三点比较重要:

第1点.应用程序通过API提供的方法VIDIOC_REQBUFS申请若干个视频数据的帧缓冲区,申请帧缓冲区数量不低于3个,每个帧缓冲区存放一帧视频数据,这些帧缓存区在内核空间。

第2点.应用程序通过API提供的查询方法VIDIOC_QUERYBUF查询到帧缓冲区在内核空间的长度和偏移量地址

第3点.应用程序再通过mmap,将申请到的内核空间帧缓冲区的地址映射到用户空间地址,这样就可以直接处理帧缓冲区的数据

执行步骤,以下三步:

第一步:将帧缓冲区排队在视频输入队列中,并启动视频采集

在驱动程序处理视频的过程中,会定义两个队列:视频采集输入队列incoming queues和视频采集输出队列outgoing queues,前者等待驱动放入视频数据的队列,后者是驱动程序已经放入视频数据的队列,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集。

第二步:循环往复,采集连续的视频数据

一是启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成后,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移到视频采集输出队列,等待应用程序从输出队列取出,驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样在第二个帧缓冲区存满下一帧数据后,也会被放入视频采集输出队列中。

二是应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区后,会处理帧缓冲区中的视频数据,比如存储或压缩这些数据。

9.取出FIFO缓冲中已经采样的帧缓存:

struct v4l2_buffer capture_buf;
memset(&capture_buf, 0, sizeof(capture_buf));
capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
capture_buf.memory = V4L2_MEMORY_MMAP;
/* 将已经捕获好视频的内存拉出已捕获视频队列 */
if (ioctl(dev.fd, VIDIOC_DQBUF, &capture_buf) < 0)
 {
   printf("ERROR: VIDIOC_DQBUF[%s], FUNC[%s], LINE[%d]\n", dev, __FUNCTION__, __LINE__);
   return TFAIL;
   }
 }
 image_data_handle(buffer[capture_buf.index].start, capture_buf.bytesused);

10.将刚刚处理完的缓重新入队列尾,实现循环采集

if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
      return -1;
}

 

以上第8,9,10三步可用以下图示描述:

 

11.停止视频采集,解除映射

int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
munmap(buffer[j].start, buffer[j].length);

12.关闭视频设备

close(fd);

简洁版流程描述:

(1)打开视频设备文件。int fd=open(“/dev/video0”,O_RDWR);
(2)查询视频设备的能力,比如是否具有视频输入,或者音频输入输出等。ioctl(fd_v4l, VIDIOC_QUERYCAP, &cap)
(3)设置视频采集的参数
设置视频的制式,制式包括PAL/NTSC,使用ioctl(fd_v4l, VIDIOC_S_STD, &std_id)
设置视频图像的采集窗口的大小,使用ioctl(fd_v4l, VIDIOC_S_CROP, &crop)
设置视频帧格式,包括帧的点阵格式,宽度和高度等,使用ioctl(fd_v4l, VIDIOC_S_FMT, &fmt)
设置视频的帧率,使用ioctl(fd_v4l, VIDIOC_S_PARM, &parm)
设置视频的旋转方式,使用ioctl(fd_v4l, VIDIOC_S_CTRL, &ctrl)
(4)向驱动申请视频流数据的帧缓冲区
请求/申请若干个帧缓冲区,一般为不少于3个,使用ioctl(fd_v4l, VIDIOC_REQBUFS, &req)
查询帧缓冲区在内核空间中的长度和偏移量 ioctl(fd_v4l, VIDIOC_QUERYBUF, &buf)
(5)应用程序通过内存映射,将帧缓冲区的地址映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。
buffers[i].start = mmap (NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l, buffers[i].offset);
(6)将申请到的帧缓冲全部放入视频采集输出队列,以便存放采集的数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)
(7)开始视频流数据的采集。 ioctl (fd_v4l, VIDIOC_STREAMON, &type)
(8) 驱动将采集到的一帧视频数据存入输入队列第一个帧缓冲区,存完后将该帧缓冲区移至视频采集输出队列。
(9)应用程序从视频采集输出队列中取出已含有采集数据的帧缓冲区。ioctl (fd_v4l, VIDIOC_DQBUF, &buf) ,应用程序处理该帧缓冲区的原始视频数据。
(10)处理完后,应用程序的将该帧缓冲区重新排入输入队列,这样便可以循环采集数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)
重复上述步骤8到10,直到停止采集数据。
(11)停止视频的采集。ioctl (fd_v4l, VIDIOC_STREAMOFF, &type)
(12)释放申请的视频帧缓冲区unmap,关闭视频设备文件close(fd_v4l)。
以上的程序流程,包含了视频设备采集连续的视频数据的逻辑关系。而在实际运用中,往往还要加入对视频数据进行处理(如压缩编码)的工作,否则,视频流数据量相当大,需要很大的存储空间和传输带宽。

详细参考网址1

详细参考网址2

详细参考网址3

详细参考网址4

2013-10-09 16:13:09 yangsong512 阅读数 631


一、            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;

           if (register_chrdev(VIDEO_MAJOR, VIDEO_NAME, &video_fops)) {

                  return -EIO;

           }

           ret = class_register(&video_class);

……

}

上面的代码注册了256个视频设备,并注册了video_class类。video_fops为这256个设备共同的操作方法。

(2)V4L2驱动注册函数的实现

 

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

{

int i=0;

int base;

int end;

int ret;

       char *name_base;

 

       switch(type) //根据不同的type确定设备名称、次设备号

       {

              case VFL_TYPE_GRABBER:

                     base=MINOR_VFL_TYPE_GRABBER_MIN;

                     end=MINOR_VFL_TYPE_GRABBER_MAX+1;

                     name_base = "video";

                     break;

              case VFL_TYPE_VTX:

                     base=MINOR_VFL_TYPE_VTX_MIN;

                     end=MINOR_VFL_TYPE_VTX_MAX+1;

                     name_base = "vtx";

                     break;

              case VFL_TYPE_VBI:

                     base=MINOR_VFL_TYPE_VBI_MIN;

                     end=MINOR_VFL_TYPE_VBI_MAX+1;

                     name_base = "vbi";

                     break;

              case VFL_TYPE_RADIO:

                     base=MINOR_VFL_TYPE_RADIO_MIN;

                     end=MINOR_VFL_TYPE_RADIO_MAX+1;

                     name_base = "radio";

                     break;

              default:

                     printk(KERN_ERR "%s called with unknown type: %d/n",

                            __func__, type);

                     return -1;

       }

 

       /* 计算出次设备号 */

       mutex_lock(&videodev_lock);

       if (nr >= 0  &&  nr < end-base) {

              /* use the one the driver asked for */

              i = base+nr;

              if (NULL != video_device[i]) {

                     mutex_unlock(&videodev_lock);

                     return -ENFILE;

              }

       } else {

              /* use first free */

              for(i=base;i<end;i++)

                     if (NULL == video_device[i])

                            break;

              if (i == end) {

                     mutex_unlock(&videodev_lock);

                     return -ENFILE;

              }

       }

       video_device[i]=vfd; //保存video_device结构指针到系统的结构数组中,最终的次设备号和i相关。

       vfd->minor=i;

       mutex_unlock(&videodev_lock);

       mutex_init(&vfd->lock);

 

       /* sysfs class */

       memset(&vfd->class_dev, 0x00, sizeof(vfd->class_dev));

       if (vfd->dev)

              vfd->class_dev.parent = vfd->dev;

       vfd->class_dev.class       = &video_class;

       vfd->class_dev.devt       = MKDEV(VIDEO_MAJOR, vfd->minor);

       sprintf(vfd->class_dev.bus_id, "%s%d", name_base, i - base);//最后在/dev目录下的名称

       ret = device_register(&vfd->class_dev);//结合udevmdev可以实现自动在/dev下创建设备节点

       ……

}

从上面的注册函数中可以看出V4L2驱动的注册事实上只是完成了设备节点的创建,如:/dev/video0。和video_device结构指针的保存。

(3)视频驱动的打开过程

当用户空间调用open打开对应的视频文件时,如:

int fd = open(/dev/video0, O_RDWR);

对应/dev/video0的文件操作结构是/drivers/media/videodev.c中定义的video_fops。

static const struct file_operations video_fops=

{

       .owner           = THIS_MODULE,

       .llseek            = no_llseek,

       .open             = video_open,

};

奇怪吧,这里只实现了open操作。那么后面的其它操作呢?还是先看看video_open吧。

static int video_open(struct inode *inode, struct file *file)

{

       unsigned int minor = iminor(inode);

       int err = 0;

       struct video_device *vfl;

       const struct file_operations *old_fops;

 

       if(minor>=VIDEO_NUM_DEVICES)

              return -ENODEV;

       mutex_lock(&videodev_lock);

       vfl=video_device[minor];

       if(vfl==NULL) {

              mutex_unlock(&videodev_lock);

              request_module("char-major-%d-%d", VIDEO_MAJOR, minor);

              mutex_lock(&videodev_lock);

              vfl=video_device[minor]; //根据次设备号取出video_device结构

              if (vfl==NULL) {

                     mutex_unlock(&videodev_lock);

                     return -ENODEV;

              }

       }

       old_fops = file->f_op;

       file->f_op = fops_get(vfl->fops);//替换此打开文件的file_operation结构。后面的其它针对此文件的操作都由新的结构来负责了。也就是由每个具体的video_devicefops负责。

       if(file->f_op->open)

              err = file->f_op->open(inode,file);

       if (err) {

              fops_put(file->f_op);

              file->f_op = fops_get(old_fops);

       }

……

}



转载:http://blog.csdn.net/hongtao_liu/article/details/5894089