今天是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等命令吧。