电子说
Read the ***ing source code! --By 鲁迅A picture is worth a thousand words. --By 高尔基说明:
V4L2(Video for Linux 2):Linux内核中关于视频设备驱动的框架,对上向应用层提供统一的接口,对下支持各类复杂硬件的灵活扩展;V4L2框架,主要包括v4l2-core、meida framework、videobuf2等模块,这也是本文将要展开的内容,仅提纲挈领;开始吧。
先从应用的角度来看如何使用v4l2吧:

假如要进行视频数据采集,大体的步骤如上图左侧所示:
/dev/videoX;上图右侧是v4l2-core的大体框架,右侧是对硬件的抽象,要想理解好它,可以先看一下较常见的硬件拓扑结构:

如果以上图的硬件为例,对摄像头的硬件该怎么来抽象呢?没错,就是以v4l2_device和v4l2_subdev来进行抽象,以v4l2_device来代表整个输入设备,以v4l2_subdev来代表子模块,比如CSI、Sensor等;

v4l2_device:对视频设备的整体进行抽象,可以看成是一个纽带,将各个子设备联系在一起,通常它会嵌入在其他结构体中以提供v4l2框架的功能,比如strcut isp_device;v4l2_subdev:对子设备进行抽象,该结构体中包含的struct v4l2_subdev_ops是一个完备的操作函数集,用于对接各种不同的子设备,比如video、audio、sensor等,同时还有一个核心的函数集struct v4l2_subdev_core_ops,提供更通用的功能。子设备驱动根据设备特点实现该函数集中的某些函数即可;video_device:用于向系统注册字符设备节点,以便用户空间可以进行交互,包括各类设置以及数据buffer的获取等,在该结构体中也能看到struct v4l2_ioctl_ops和struct vb2_queue结构体字段,这些与上文中的应用层代码编写息息相关;struct v4l2_subdev中内嵌的video_device也可以不向系统注册字符设备;video_device结构体,可以内嵌在其他结构体中,以便提供用户层交互的功能,比如struct isp_video;v4l2-core提供了一些实现,所以driver在实现时,非特殊情况下可以不用重复造轮子;来进一步看一下内部的注册,及调用流程吧:

struct video_device,同时实现struct v4l2_file_operations结构体中的函数,最终通过video_register_device向提供注册;v4l2_register_device函数通过cdev_add向系统注册字符设备,并指定了file_operations,用户空间调用open/read/write/ioctl等接口,便可回调到驱动实现中;v4l2_register_device函数中,通过device_register向系统注册设备,会在/sys文件系统下创建节点;
完成注册后,用户空间便可通过文件描述符来进行访问,从应用层看,大部分都是通过ioctl接口来完成,流程如下:

ioctl回调到__video_do_ioctl中,该函数会对系统提供的struct v4l2_ioctl_info v4l2_ioctls[]表进行查询,找到对应的项后进行调用;下一个小节,让我们看看更复杂一点的情况。
为了更好的描述,本节以omap3isp为例,先看一下它的硬件构成:

上述硬件模块,可以对应到驱动结构体struct isp_device中的各个字段。
omap3isp的硬件模块,支持多种数据流通路,它并不是唯一的,以RGB为例,如下图:

那么,软件该如何满足这种需求呢?
没错,pipeline框架的引入可以解决这个问题。说来很巧,我曾经也实现过一个类似的框架,在阅读media framework时有一种似曾相识的感觉,核心的思想大体一致。

struct media_entity来进行抽象,通常会将struct media_entity嵌入到其他结构中,以支持media framework功能;struct media_pad,pad可以认为是端口,与其他模块进行联系的媒介,针对特定模块来说它是确定的;struct media_link来建立连接,指定source和sink,即可将通路建立起来;
因此,只需要将struct media_entity嵌入到特定子模块中,最终便可以将子模块串联起来,构成数据流。所以,omap3isp的驱动中,数据流就如下图所示:

video devnode代表video device,也就是前文中提到的导出到用户空间的节点,用于与用户进行控制及数据交互;isp_create_links;还是看一下数据结构吧:

media_device:与v4l2_device类似,也是负责将各个子模块集中进行管理,同时在注册的时候,会向系统注册设备节点,方便用户层进行操作;media_entity、media_pad、media_link等结构体的功能在上文中描述过,注意,这几个结构体会添加到media_device的链表中,同时它们结构体的开始字段都需是struct media_gobj,该结构中的mdev将会指向它所属的media_device。这种设计方便结构之间的查找;media_entity中包含多个media_pad,同时media_pad又会指向它所属的media_entity;media_graph和media_pipeline是media_entity的集合,直观来理解,就是由一些模块构成的一条数据通路,由一个统一的数据结构来组织管理;罗列一下常见的几个接口吧,细节不表了:
/* 初始化entity的pads */
int media_entity_pads_init(struct media_entity *entity, u16 num_pads,
struct media_pad *pads);
/* 在两个entity之间创建link */
int media_create_pad_links(const struct media_device *mdev,
const u32 source_function,
struct media_entity *source,
const u16 source_pad,
const u32 sink_function,
struct media_entity *sink,
const u16 sink_pad,
u32 flags,
const bool allow_both_undefined);
/* 开始graph的遍历,从指定的entity开始 */
void media_graph_walk_start(struct media_graph *graph,
struct media_entity *entity);
/* 启动pipeline */
__must_check int media_pipeline_start(struct media_entity *entity,
struct media_pipeline *pipe);
将media framework和v4l2_device及v4l2_subdev结合起来,就可以将各个子设备构建pipeline,完美!
video buffer了。V4L2的buffer管理是通过videobuf2来完成的,它充当用户空间和驱动之间的中间层,并提供low-level,模块化的内存管理功能;

vb2_queue:核心的数据结构,用于描述buffer的队列,其中struct vb2_buffer *bufs[]是存放buffer节点的数组,该数组中的成员代表了vb2 buffer,并将在queued_list和done_list两个队列中进行流转;struct vb2_buf_ops:buffer的操作函数集,由驱动来实现,并由框架通过call_bufop宏来对特定的函数进行调用;struct vb2_mem_ops:内存buffer分配函数接口,buffer类型分为三种:1)虚拟地址和物理地址都分散,可以通过dma-sg来完成;2)物理地址分散,虚拟地址连续,可以通过vmalloc分配;3)物理地址连续,可以通过dma-contig来完成;三种类型也vb2框架中都有实现,框架可以通过call_memop来进行调用;struct vb2_ops:vb2队列操作函数集,由驱动来实现对应的接口,并在框架中通过call_vb_qop宏被调用;
本节以omap3isp为例进行简要分析,感觉直接看图就可以了:
- buffer申请

- buffer enqueue

- buffer dequeue

- stream on

行文至此,主体讲完了,相信看完本文应该有个大概的轮廓了,还有一些细节未进一步描述,就此打住。
原文标题:参考
文章出处:【微信公众号:一口Linux】欢迎添加关注!文章转载请注明出处。
全部0条评论
快来发表一下你的评论吧 !