视频监控—V4L2框架的简单分析
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:Video for Linux Two - Driver Writer’s Guide、《Android驱动开发权威指南》
- 开发环境:Linux 3.4.2内核、arm-linux-gcc 4.3.2工具链
- 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3
目录
一、概述
1、V4L2
V4L,其全称是Ⅴideo4 Linux(即 Video for Linux),是 Linux内核中关于视频设备的API接口,涉及开关视频设备,以及从该类设备采集并处理相关的音、视频信息。
现在 Linux内核中用的是V4L2,即 Video4 Linux2(即 Video for Linux Two),其是修改V4L相关Bug后的一个升级版,始于 Linux2.5内核。
V4L2的主设备号是81,次设备号为0~255。这些次设备号里又分为多类设备:视频设备、 Radio(收音机)设备、 Teletext on VBI等。因此V4L2设备对应的文件节点有:/dev/videoX、/dev/vbix、/dev/ radioX。
2、UVC
UVC全称为USB Video Class,即:USB视频类,是一种为USB视频捕获设备定义的协议标准,在不需要安装任何的驱动程序下即插即用,包括摄像头、数字摄影机、模拟视频转换器、电视卡及静态视频相机。
二、从UVC设备文件反推V4L2框架
备注
- 涉及文件:uvc_driver.c、v4l2-dev.c、v4l2-ctrls.c、v4l2-iocl.c、vivi.c
- 下面的分析为个人看法,肯定有所不足,望各位提出宝贵修改意见
1、uvc_driver
- uvc_driver结构体:
从图中可以看出uvc_driver采用platform平台机制进行管理,其中比较关键的函数就是.probe
- uvc_probe():
在这个函数中主要进行如下设置,其中我们会看到
一个比较显眼的函数v4l2_device_register()
,这个函数并不是实际意义上的v4l2_device注册函数(可以理解为v4l2_device_init()
,进行一些相关初始化工作),但是是后面介绍到的V4L2框架的一员。
后面的uvc_register_chains()
比较关键。
uvc_register_chains()
函数:
1、可以看到,在uvc_register_chains()
函数中,遍历链表,调用video_register_device()
注册uvc设备;
2、在video_register_device()
函数中遍历对uvc设备进行绑定、初始化与注册。
uvc_register_video()
函数:
在这个函数中uvc_device真正进行分配video_device_alloc()
和注册结构体video_register_device()
的相关操作
2、v4l2_device结构体
video_device_alloc()
函数:
调用kzalloc
分配video_device结构体
大小的内存空间
video_register_device()
函数:
通过源码追踪可以发现最终调用的是__video_register_device()
函数
在__video_register_device()
函数中进行六个步骤的设置,主要实现注册的是如下三个操作:
cdev_alloc()
、vdev->cdev->ops = &v4l2_fops;
、cdev_add()
三个操作。
3、初步总结uvc_device设置
通过上述分析,可以得到uvc_device
驱动建立步骤如下图:
注意:
v4l2_device_register()
、video_device_alloc()
、video_register_device()
其函数实现是在v4l2-dev.c
文件中cdev_alloc()
、cdev_add()
是在char_dev.c
中实现的。vdev->cdev->ops = &v4l2_fops
把v4l2-dev.c
文件与具体的uvc-driver.c
联系起来。
通过追踪v4l2_fops结构体
可以知道,file_operation结构体的定义是在v4l2-dev.c
文件中实现
4、V4L2的初步框架
- 对于uvc设备类,其属于字符驱动的一种,所以最终会调用
cdev_alloc()
、cdev_add()
对该设备类进行注册; - 对于内核空间中的V4L2框架,大致分为:具体的设备类、V4L2核心层
v4l2-dev.c
、字符设备驱动程序 - 当具体的设备与内核中的设备类匹配上时,会调用其
.probe
函数建立联系; - 在
.probe
函数中,实现与V4L2接口的注册与连接; - 在V4L2核心层
v4l2-dev.c
中,对该设备进行字符设备的注册。 - 当上述操作完成后,用户空间就可以通过
open、read、write、ioctl
的函数,操作具体的设备。
三、用户空间通过V4L2核心如何操作具体的uvc设备
从用户空间往V4L2核心层看去,需要先分析v4l2-dev.c
核心层
1、v4l2_fops
结构体
在v4l2-dev.c
文件中,发现了驱动的关键结构体file_operations
,在这个文件中,实现了具体的操作函数,供上层应用调用,下面以v4l2_ioctl
为例子进行分析。
2、v4l2_ioctl()
函数
对于完全的函数就不作展示了,留意其比较关键的函数:
if (video_is_registered(vdev))
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
中的vdev->fops->unlocked_ioctl(filp, cmd, arg)
中vdev->fops
与 uvc设备类注册时最终调用的__video_register_device()
中vdev->cdev->ops = &v4l2_fops
对应起来了。
即vdev->fops->unlocked_ioctl(filp, cmd, arg)
最终对调用具体设备的.ioctl
函数,对于内核中,有一个虚拟视频驱动程序vivi.c
,对它来进一步分析。
3、vivi_fops
结构体
在vivi.c
文件中,发现了驱动的关键结构体file_operations
,其中的.unlocked_ioctl
函数就是实际调用的函数。
vdev->fops->unlocked_ioctl(filp, cmd, arg)
---> vivi_fops->video_ioctl2()
4、video_ioctl2()
函数
通过源码追踪,可以找到实际的操作函数为video_usercopy()
,最后进入到__video_do_ioctl()
,进行获得、设置某些属性。
5、vivi.c的部分属性在哪里设置
对于一个设备,在编写驱动时,会在驱动程序中设定一些必须的属性,来对此设备进行必要的初始化工作。
通过追踪vivi_init()
函数,找到了部分属性设置在vivi_creat_instance()
函数中:
在vivi.c中使用的是v4l2_ctrl_new_std()
与 v4l2_ctrl_new_custom()
来设置属性与控制项,当然还有其他的设置函数,这里不一一赘述了。
6、以vivi.c的video_ioctl2
为例,初步总结
- 总源码追踪过程:
画图理解与总结:
当用户空间执行相关open、read、ioctl.....
函数时
- 由于为字符设备,会先调用字符设备驱动程序,在字符设备驱动找到对应v4l2接口;
- 在V4L2核心层中,调用对应的
open、read、ioctl.....
函数,通过函数指针找到具体的设备的函数 - 在具体设备的对应的
open、read、ioctl.....
函数,进行硬件的相关操作。
四、总结
V4L2整体的框架如下:
下图分析的是带有platform平台机制下的V4L2框架分析,若设备本身不采用platform平台机制管理,则不需要xxx_driver.c部分。