v4l2视频驱动中关于vivi.c的个人分析(菜鸟入门,请轻拍!)

时间:2021-10-19 23:34:27

今天是2013-3-22,在前一段时间看了很多I2C以后(虽然没有经过什么实际检验,但是感觉还是对I2C有了一点点的了解),今天开始来学习学习有关视频方面的东西。

首先我看的参考入门文档:

http://zjbintsystem.blog.51cto.com/964211/464729

还有几个datasheet,如:VPFE、VPBE以及TMS320DigitSubsystem

 

应用层是怎么做的,先参看这个文档,可以参看这个文档:

http://www.rosoo.net/a/201001/8382.html

 

我觉得说清楚一个东西,把他的来龙去脉讲清楚是比较困难的,但是按照以下几个方面来讲,效果一定不错:是什么,为什么,怎么样。记得这是初中政治老师对我说的,但是我觉得在任何地方都是通的。

 

V4L2是什么:video for linux 2的简称吧。所以就变成V4L2了。有2,当然之前有1了,至于未来怎么样,有没有3,这个我就不管了。

 

为什么要用V4L2:或者说有啥用呢。看这个:

参考文档:http://dongyulong.blog.51cto.com/1451604/344987

V4L2主要用来搞视频的。视频并不是说我加个摄像头,然后往我画的板子上一接,就可以在我的板子的LCD上显示数据了。那我们要做什么呢?要让板子认识外设芯片(比如tvp5146),要对我采集到的视频数据进行处理(这个是不是属于应用层?)等等工作。V4L2就提供了许多这样的接口。因为我们摄像头不同,板子芯片更不同,所以要有一套可移植的东东。我感觉大概是这样。(才看两天,以后有了更深刻的理解,再来补充更改吧。)

 

V4L2有一个重要的头文件,大部分文章都会说到:videodev2.h

注释中都写了:/**Video for Linux Two header file**/

 

这个头文件看的真蛋疼!

文档:http://blog.csdn.net/hongtao_liu/article/details/5894089

这篇文档写的不错,初步入门值得细看。

 

好几天没看了,都不知道该怎么看了,真蛋疼。

给个链接:http://blog.csdn.net/shui1025701856/article/details/7459868

这个文章里面说:V4L2驱动对用户空间提供字符设备,主设备号为81,对于视频设备,其次设备号为0-63.初次之外,次设备号为64-127的radio设备,次设备号为192-223的teletext设备,次设备号为224-255的VBI设备。

这个跟我刚才在v4l2-dev.c中看到的是相关的。

minor_offset是偏移,minor_cnt表示的是设备号的count。

即这段:switch (type) {

       case VFL_TYPE_GRABBER:

              minor_offset = 0;

              minor_cnt = 64;

              break;

       case VFL_TYPE_RADIO:

              minor_offset = 64;

              minor_cnt = 64;

              break;

       case VFL_TYPE_VBI:

              minor_offset = 224;

              minor_cnt = 32;

              break;

       default:

              minor_offset = 128;

              minor_cnt = 64;

              break;

       }

这里并没有给出192-223这一部分。

 

正如之前看的那个参考文档中写的那些:大部分是通过ioctl来进行控制的,而那些命令就定义在include/linux/videodev2.h中。

在命令中用到了一些像_IOR、_IOWR等相关的宏,按照CSDN上一个帖子:

http://bbs.csdn.net/topics/240007617描述:是对ioctl命令号的宏转换定义,用于对命令进行分类。

另外还有这个链接写的较为详细:http://www.groad.net/bbs/read.php?tid-1212.html

 

关于V4L2几个比较重要的文件为:

Include/linux/videodev2.h

Include/media/v4l2-dev.h

V4L2驱动核心实现文件:driver/media/video/v4l2-dev.c

//video核心层:drivers/media/video/videodev.c

 

许多文章里面提到了上面这个文件videodev.c,在老版的2.x内核中似乎存在这个文件,但是我是基于3.x内核看的,已经不存在这个文件了,现在原来存在于其中的函数定义现在都已经在v4l2-dev.h中了,比如video_register_device和video_unregister_device函数。

而V4L2提供的统一的应用层的接口都已经实现在v4l2-dev.c文件中。

static const structfile_operations v4l2_fops = {

       .owner = THIS_MODULE,

       .read = v4l2_read,

       .write = v4l2_write,

       .open = v4l2_open,

       .get_unmapped_area = v4l2_get_unmapped_area,

       .mmap = v4l2_mmap,

       .unlocked_ioctl = v4l2_ioctl,

#ifdef CONFIG_COMPAT

       .compat_ioctl = v4l2_compat_ioctl32,

#endif

       .release = v4l2_release,

       .poll = v4l2_poll,

       .llseek = no_llseek,

};

 

现在以vivi.c和v4l2来分析一下整个过程是如何实现的吧。

假设我们现在有应用程序,其中使用了open、close、ioctl等函数。

应用层的调用,在内核中会调用到相对应的驱动函数。之前看到说V4L2其实就是又封装了一层。感觉对这里说的还不是很理解。

在v4l2-dev.c文件中,有相应的驱动函数。当应用层执行open函数时,内核会执行v4l2_open函数。v4l2_open函数也是定义在v4l2-dev.c文件中。

 

static int v4l2_open(struct inode *inode, struct file*filp)

传进的参数为inode节点和文件指针。

在v4l2_open函数中,定义了struct video_device结构体vdev,并调用video_devdata函数:vdev =video_devdata(filp);获得视频设备结构体。

if(video_is_registered(vdev))

       ret = vdev->fops->open(filp);

如果设备已经注册过了,那么就会去执行vdev->fops->open函数。

 

我们再来看vdev,即struct video_device结构体。

这个结构体的定义在v4l2-dev.h文件中。

其中,包括:

/* device ops */

       const struct v4l2_file_operations*fops;

关于v4l2的设备文件操作集

 

/*sysfs */

       struct device dev;          /* v4ldevice */

       struct cdev *cdev;         /*character device */

在sysfs下建立设备和字符设备

 

/* Set either parent or v4l2_dev if your driver uses v4l2_device */

       structdevice *parent;           /* device parent*/

       struct v4l2_device *v4l2_dev;       /* v4l2_device parent */

 

/*ioctl callbacks */

       const struct v4l2_ioctl_ops*ioctl_ops;

关于ioctl的文件操作函数。

 

在vivi.c中,我加载设备驱动的时候,会执行module_init(vivi_init);在vivi_init中调用了vivi_create_instance函数。我们来看这个函数:

其中定义了structvideo_device *vfd;

struct vivi_dev *dev(这个应该就是自定义的设备类型了);

 

在函数中调用函数v4l2_device_register注册v4l2设备。

后面:vfd = &dev->vdev;

让vfd指针指向注册的v4l2设备。

 

接下来调用了两个函数:video_set_drvdata(vfd,dev);

和video_register_device函数。注册video device。

 

这两个函数,第一个查到说是设置驱动程序专有数据;第二个函数之前分析过,是注册video设备的函数,比如是摄像头,或者是VBI设备等等。

 

那么回到一开始,现在我们应该就是传递了vfd这个参数了。

也就是实际上会执行v4l2_file_operations这个结构体中的相关驱动函数了,在本例中,在vivi.c文件中有如下定义:

static const struct v4l2_file_operations vivi_fops = {

       .owner           =THIS_MODULE,

       .open           = v4l2_fh_open,

       .release        =vb2_fop_release,

       .read           =vb2_fop_read,

       .poll        =vb2_fop_poll,

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

       .mmap           =vb2_fop_mmap,

};

也就是实际上最终会执行v4l2_fh_open这个函数。

 

其余的release、read、write等也是类似。但是好像ioctl不是一样的。这里继续来分析一下:

我看的是3.6.x内核,跟2.6等内核还是有不少变化的,这个变化有哪些,我也搞不全。

 

当我在应用层调用ioctl的时候,在驱动层会调用相应的ioctl函数,但是这里有点区别,因为在file_operations中我没看到ioctl的定义,而有:

long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);

long (*compat_ioctl)(struct file *, unsigned int, unsigned long);

百度之,这两个跟ioctl的声明:int(*ioctl)(struct inode *node, struct file *filp,unsigned int cmd, unsigned longarg);相比,主要是缺少了inode参数。

查看到两个文档:http://blog.sina.com.cn/s/blog_693301190100vyhh.html

http://blog.csdn.net/cbl709/article/details/7295772

其实没什么大的差别,就是参数少了一个而已。内核驱动中,会优先执行unlocked_ioctl函数,如果没有unlocked_ioctl,那么就会去执行ioctl函数。应用层是没有差别的。

 

在v4l2-dev.c的v4l2_fops定义中,有:

.unlocked_ioctl = v4l2_ioctl,

#ifdef CONFIG_COMPAT

       .compat_ioctl= v4l2_compat_ioctl32,

#endif

在应用层执行ioctl时候,会执行v4l2_ioctl函数。

 

在v4l2_ioctl的函数定义中(仍然在v4l2-dev.c中):

if (video_is_registered(vdev))

       ret= vdev->fops->unlocked_ioctl(filp, cmd, arg);

注册了video设备后,会去执行video_device中fops的unlocked_ioctl函数。

 

再看我们的vivi.c文件:

static const struct v4l2_file_operations vivi_fops = {

       .owner           =THIS_MODULE,

       .open           = v4l2_fh_open,

       .release        = vb2_fop_release,

       .read           =vb2_fop_read,

       .poll        =vb2_fop_poll,

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

       .mmap           =vb2_fop_mmap,

};   //之前好像贴过了。

对我们的vivi.c驱动程序来说:也就是会去执行video_ioctl2这个函数。

 

这个video_ioctl2函数是定义在v4l2-ioctl.c文件中:

long video_ioctl2(structfile *file, unsigned int cmd, unsigned long arg)

{

       return video_usercopy(file, cmd, arg, __video_do_ioctl);

}

百度video_usercopy函数的作用说是将信息传送到用户进程,执行__video_do_ioctl函数。

参考文档:http://bbs.chinaunix.net/thread-2156911-1-1.html

 

跟踪__video_do_ioctl函数:

static long__video_do_ioctl(struct file *file, unsigned int cmd, void *arg)

其中:struct video_device *vfd = video_devdata(file);

       const struct v4l2_ioctl_ops*ops = vfd->ioctl_ops;

也就是说;我在应用层执行ioctl时,一开始执行video_ioctl2函数,并传递file文件指针,实际上调用了__video_do_ioctl函数,并实际上是执行了video_device中ioctl_ops的函数操作,也就是相应的v4l2-ioctl-ops函数操作。

 

在vivi.c中,有:

static const struct v4l2_ioctl_ops vivi_ioctl_ops = {

       .vidioc_querycap      =vidioc_querycap,

       .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,

       .vidioc_g_fmt_vid_cap    = vidioc_g_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       =vb2_ioctl_reqbufs,

       .vidioc_create_bufs   =vb2_ioctl_create_bufs,

       .vidioc_prepare_buf   =vb2_ioctl_prepare_buf,

       .vidioc_querybuf      =vb2_ioctl_querybuf,

       .vidioc_qbuf          =vb2_ioctl_qbuf,

       .vidioc_dqbuf         =vb2_ioctl_dqbuf,

       .vidioc_enum_input    = vidioc_enum_input,

       .vidioc_g_input       = vidioc_g_input,

       .vidioc_s_input       =vidioc_s_input,

       .vidioc_streamon      =vb2_ioctl_streamon,

       .vidioc_streamoff     =vb2_ioctl_streamoff,

       .vidioc_log_status    = v4l2_ctrl_log_status,

       .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,

       .vidioc_unsubscribe_event = v4l2_event_unsubscribe,

};

这个就对应了应用程序中的那些获取视频capability、开启streamon、streamoff等命令吧。