本篇文章主要介绍Linux内核中的V4L2框架,本篇文章所用内核版本:linux-4.19
v4L2 (Video for Linux 2),是linux的一套视频框架,共主体位于内核,可以理解为是整个linux系统上面的视频源捕获驱动框架。其广泛应用在嵌入式设备、移动端以及个人电脑设备上面,市而上使用视频图像采集的设备如:手机、IPC、行车记录仪都会用到这个框架来进行视频采集。
v4L2允许 应用程序 控制图像传感器以及传输格式,应用程序 借此完成拍照、预览、视频记录等图像传感器数据应用。
之前,Linux还存在第一版的V4L2,该版本在内核2.6.38版中放弃支持。
linux采用多层次的驱动架构来对接口进行统一与抽象,最低层次的驱动总是直接面向硬件的,而最高层次的驱动被划分为字符、块、网络设备三大类,前两类驱动在文件系统中形成类似文件的“虚拟文件”,又称为“节点node",这些节点拥有不同的名称代表不同的设备,在目录/dev下进行统一管理,系统调用函数如open、close、read等也与普通文件的操作有相似之处,这种接口的一致性是由VFS(虚拟文件系统层)抽象完成的。
V4L2是关于视频设备的中间驱动层,向上 为 应用程序 访问 视频设备提供了通用接口,向下为设备驱动程序开发提供了统一的V4L2框架。其视频设备节点路径通常为/dev中的videoX。V4L2驱动对用户空间提供“字符设备”的形式,主设备号为81,在用户空间通过各种ioctl调用进行控制,并且可以使用mmap进行内存映射。
V4L2支持多种设备,有以下接口:
√视频采集接口(video capture interface)
√视频输出接口(video output interface)
√直接传输视频接口(video overlay interface)
√视频间隔消隐信号接口(VBI interface)
√收音机接口(radio interface).
应用通过open、ioctl等系统调用操作video设备。在内核空间,Video设备的具体操作
方法由驱动中的struct video_device提供。
驱动使用video_register_device函数将struct video_device注册到V4L2的核心
层,然后V4L2的核心层再向上注册一个字符设备。这样应用就可以使用系统调用访问虚拟
文件系统中Video设备提供的方法,然后进一步访问V4L2核心层提供的v4l2_fops方法集
合,最后通过struct video_device结构休中的fops和ioctl_ops方法集合访问Video
主设备。
Video主设备通过V4L2_subdev_call方法访问Video从设备,同时Video从设备可以
通过notify回调方法通知主设备发生了事件。
v4L2的驱动源码在kernel/drivers/media/v4l2-core目录下,主要代码文件有:
(1)v4l2-dev.c //视频设备硬件的操作,包含video_device的注册、释放等,主要包括
//以下函数:
videodev_init
register_chrdev_region
class_register
videodev_exit
class_unregister
unregister_chrdev_region
__video_register_device
video_unregister_device
(2)v4l2-common.c //一些通用操作,V4l2的子设备一般是摄像头和摄像头控制器,
//它们和主机的控制操作是通过i2c总线完成的。V4l2驱动框架中跟i2c相关的代码在
//v4l2_common.c中
v4l2_ctrl_query_fill
v4l2_i2c_subdev_init
v4l2_i2c_new_subdev_board
v4l2_i2c_new_subdev
v4l2_i2c_subdev_addr
v4l2_i2c_tuner_addrs
v4l2_spi_subdev_init
v4l2_spi_new_subdev
clamp_align
v4l_bound_align_image
__v4l2_find_nearest_size
v4l2_get_timestamp
v4l2_g_parm_cap
v4l2_s_parm_cap
(3)v4l2-device.c //V4L2的设备支持,主要是注册v4I2_device,包括以下函数:
v4l2_device_register
v4l2_device_unregister
v4l2_device_put
v4l2_device_release
v4l2_device_register_subdev
v4l2_device_unregister_subdev
v4l2_device_register_subdev_nodes
v4l2_device_release_subdev_node
(4)v4l2-ioctl.c //处理V4L2的ioctl命令的一个通用的框架。
(5)v412-subdev.c //v4l2子设备
(6)v4l2-mem2mem.c //使用videobuf缓冲区的设备辅助函数。
V4L2缓冲区管理
V4L2维护着两个缓冲队列:一个用于驱动(INPUT队列),另一个用于用户程序
(OUTPUT队列)。
缓冲区(由VIDIOC_REQBUFS命令申请)被用户空间的应用程序放入驱动的队
列中(通过VIDIOC_QBUF ioctl命令)以便填充数据。驱动按顺序填充缓冲区后,
缓冲区由INPUT队列放入OUTPUT队列。
当用户程序调用VIDIOC_DQBUF命令后,驱动会在OUTPUT队列中寻找可用的
缓冲,如果可用则推送到用户程序,不可用则等待直到有可用缓冲后再推送给用
户程序。缓冲区使用完后,必须调用VIDIOC_QBUF将缓冲区重新加入INPUT队列
以便再次填充数据。
注意驱动程序会独立自主的填充INPUT队列中的缓冲区,如果用户程序对缓
冲数据使用不及时,INPUT队列被填满,驱动暂停等待,会产生丢帧。
以字符设备驱动为例,请阅读我之前所写的一篇文章:字符设备驱动的三种实现方法
struct v4l2_device:一个硬件设备可能包含多个子设备,比如一个电视除了有capture设备,可能还有VBI设备或者FM tunner。而v4l2_device就是所有这些设备的根节点,负责管理所有的子设备。
/ * *
*struct v4l2_device -用于V4L2设备驱动程序的主结构
*
* @dev:指向设备结构体的指针。
* @mdev:指向结构体media_device的指针,可以为NULL。
* @subdevs:用于跟踪已注册的子设备
* @lock:锁定这个结构体;如果该结构嵌入到一个更大的结构中,驱动程序也可以使用
* 该结构。
* @name:唯一的设备名称,默认为驱动器名称+总线ID
* @notify:通知进行了某个操作(某些子设备被调用)
* @ctrl_handler:控制处理程序。可能是NULL。
* @prio:设备的优先级状态
* @ref:跟踪对这个结构体的引用。
* @release:当ref计数变为0时调用的释放函数。
*
* V4L2设备的每个实例都应该创建v4l2_device结构体,无论是独立的还是嵌入到
* 更大的结构体中。
*
*它允许轻松访问子设备(参见V4L2 -subdev.h),并提供基本的V4L2设备级支持。
*
* . .注意::
*
* #) @dev->driver_data指向该结构体。
* #)如果没有父设备,@dev可能是%NULL
* /
struct v4l2_device {
struct device *dev;
struct media_device *mdev;
struct list_head subdevs;
spinlock_t lock;
char name[V4L2_DEVICE_NAME_SIZE];
void (*notify)(struct v4l2_subdev *sd,
unsigned int notification, void *arg);
struct v4l2_ctrl_handler *ctrl_handler;
struct v4l2_prio_state prio;
struct kref ref;
void (*release)(struct v4l2_device *v4l2_dev);
};
struct video_device:这个结构体的主要作用时提供/dev/videoX或/dev/v4l-subdevx设备节点,同时对捕获接口进行抽象,用来描述一个出帧的设备。另外,Video子设备也是继承自该结构体。该结构体包含指向v4l2_file_operations、v4l2_ioctl_ops等的操作对象指针。
/ * *
* struct video_device——用于创建和管理V4L2设备节点的结构。
*
* @entity: &struct media_entity
* @intf_devnode:指向&struct media_intf_devnode的指针
* @pipe: &struct media_pipeline
* @fops:指向视频设备的&struct v4l2_file_operations的指针
* @device_caps: v4l2_capabilities中使用的设备能力
* @dev: &struct设备用于视频设备
* @cdev:字符设备
* @v4l2_dev:指向struct v4l2_device父设备的指针
* @dev_parent:指向&结构设备父设备的指针
* @ctrl_handler:与该设备节点关联的控制处理程序。可能为NULL。
* @queue: &struct vb2_queue与该设备节点相关联。可能为NULL。
* @prio:指向带有设备优先级状态的struct v4l2_prio_state的指针。
* 如果为NULL,则使用v4l2_dev->prio。
* @name:视频设备名称
* @vfl_type: V4L设备类型,由&enum vfl_devnode_type定义
* @vfl_dir: V4L接收器、发射器或m2m
* @minor:设备节点“minor”。如果注册失败,则设置为-1
* @num:视频设备节点编号
* @flags:视频设备标志。使用bitops来设置/清除/测试标志。
* 包含一组&enum v4l2_video_device_flags。
* @index:属性用于区分一个物理设备上的多个索引
* @fh_lock:对所有v4l2_fhs进行锁
* @fh_list: struct v4l2_fh的列表
* @dev_debug:内部设备调试标志,驱动程序不使用
* @tvnorm:支持的电视规范
*
* @release:视频设备release()回调函数
* @ioctl_ops:带有ioctl回调函数的指向&struct v4l2_ioctl_ops的指针
*
* @valid_ioctls:该设备有效的ioctl的位图
* @lock:指向& &struct mutex 序列化锁的指针
*
* . .注意::
*只有在无法从@v4l2_dev推导出@dev_parent时才设置它。
* /
*
struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
struct media_intf_devnode *intf_devnode;
struct media_pipeline pipe;
#endif
const struct v4l2_file_operations *fops;
u32 device_caps;
/* sysfs */
struct device dev;
struct cdev *cdev;
struct v4l2_device *v4l2_dev;
struct device *dev_parent;
struct v4l2_ctrl_handler *ctrl_handler;
struct vb2_queue *queue;
struct v4l2_prio_state *prio;
/* device info */
char name[32];
enum vfl_devnode_type vfl_type;
enum vfl_devnode_direction vfl_dir;
int minor;
u16 num;
unsigned long flags;
int index;
/* V4L2 file handles */
spinlock_t fh_lock;
struct list_head fh_list;
int dev_debug;
v4l2_std_id tvnorms;
/* callbacks */
void (*release)(struct video_device *vdev);
const struct v4l2_ioctl_ops *ioctl_ops;
DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);
struct mutex *lock;
};
(1)分配/设置/注册v4l2_device(调用函数注册v4l2_device_register),有辅助作用,提供自旋锁以及引用计数
(2)分配video_device:video_device_alloc()或kzalloc();
(3)设置video_device:.fops、.ioctl_ops、dev;
(4)注册video_device: video_register_device()
参考阅读:https://blog.csdn.net/seiyaaa/article/details/120199720
Video设备注册时的执行流程可总结如下:
1.设置设备注销时资源释放回调和v4l2_device结构体。
2.检查设备类型并确定设备节点基本名称。
3.设置设备类型、次设备号及设备节点数量。
4.将video_device结构体指针保存到全局video_device数组中。
5.根据设备类型验证哪那些ioctl函数可以使用。
6.分配字符设备结构体。
7.设置字符设备的操作函数集合为v4l2_fops。
8.将video设备注册为字符设备,并注册设备;
9.设置设备引用计数为0时的回调函数,该函数主要的工作是删除注册的字符设备,
回调v4l2_device中的release函数(通常是video_device_release函数)释放
video_device结构体内存,最后减少v4l2_device的引用计数;
10.增加video_device所属v4l2_device的引用计数。
11.设置已注册标志V4L2_FL_REGISTERED。
Video设备访问流程
(1)首先通过系统调用访问/dev/videox用户空间设备节点。
(2)进入到内核空间,访问字符设备struct file_operations中的方法。对于Video
设备,该操作集合被v4L2子系统初始化为v4l2_fops集合。
(3)通过v4L2子系统提供的v4l2_fops集合,可直接调用底层驱动实现的Video主设备
struct v4l2_file_operations方法,对于ioctl方法,则需要借助中间函数
__video_do_ioctl调用底层驱动实现的struct v4l2_ioctl_ops中的ioctl功能。
struct v4l2_file_operations方法和struct v4l2_ioctl_ops方法属于主设备
方法,需要主设备的驱动实现。
( 4) struct v4l2_file_operations和struct v4l2_ioctl_ops中的函数都可以
通过v4l2_subdev_call调用video从设备struct v4l2_subdev_core_ops、struct
v4l2_subdev_video_ops、struct v4l2_subdev_pad_ops等方法,这些方法都要在
从设备驱动中实现。
为便于开发,常见的Linux发行版会附带一个v4l2-ctl的命令行工具,可以用来测试摄像头子系统。该工具可以列出系统内的设备列表,查询设备能力,调整设备属性以及设置像素格式、分辨率、帧率等,同时也可以执行捕捉图像等动作。
参考阅读:v4l2-ctl基本使用方法
V4L2调试
由丁video系统的配置较复杂,为便于调试,V4L2提供了简单但庞大的用户空间调试手段,用于跟踪框架或用户空间API的信息。
椎架的调试信息可通过下述命令开启(通过dmesg查看):
# echo 0x3 > /sys/module/videobuf2_v4l2/parameters/debugt
# echo 0x3 > /sys/module/videobuf2_common/parameters/debug
V4L2的用户空问API跟踪通过下述命令开启:
# echo 0x3 >/sys/class/video4linux/video0/dev_debug
Video设备要工作正常,驱动兼容性是一个重要的方面,v4l2-compliance 工具可以通过测试V4L2设备的各个方面来判断其驱动兼容性。
使用方法请阅读:v4l-utils之v4l2-compliance
初步了解V4L2的驱动框架后,下一篇将以《虚拟摄像头驱动:drivers\media\platform\vivid》进行详细分析解读,进一步深入理解,敬请期待.
本文内容主要来自百度百科以及韦东山老师的课程笔记,如有侵权,联系删除!欢迎各位在评论区指导交流!!!