天天看点

【原创】Linux v4l2框架分析

<code>Read the fucking source code!</code> --By 鲁迅

<code>A picture is worth a thousand words.</code> --By 高尔基

说明:

Kernel版本:4.14

ARM64处理器,Contex-A53,双核

使用工具:Source Insight 3.5, Visio

<code>V4L2(Video for Linux 2)</code>:Linux内核中关于视频设备驱动的框架,对上向应用层提供统一的接口,对下支持各类复杂硬件的灵活扩展;

<code>V4L2</code>框架,主要包括<code>v4l2-core</code>、<code>meida framework</code>、<code>videobuf2</code>等模块,这也是本文将要展开的内容,仅提纲挈领;

开始吧。

先从应用的角度来看如何使用<code>v4l2</code>吧:

【原创】Linux v4l2框架分析

假如要进行视频数据采集,大体的步骤如上图左侧所示:

打开设备文件<code>/dev/videoX</code>;

根据打开的设备,查询设备能力集;

设置视频数据的格式、参数等;

分配buffer,这个buffer可以是用户态分配的,也可以是从内核中获取的;

开始视频流采集工作;

将buffer enqueue到v4l2框架,底层负责将视频数据填充后,应用层再将buffer dequeue以便获取数据,然后再将buffer enqueue,如此循环往复;

上图右侧是v4l2-core的大体框架,右侧是对硬件的抽象,要想理解好它,可以先看一下较常见的硬件拓扑结构:

【原创】Linux v4l2框架分析

通常一个camera的模组如图所示,通常包括Lens、Sensor、CSI接口等,其中CSI接口用于视频数据的传输;

SoC的Mipi接口对接Camera,并通过I2C/SPI控制camera模组;

Camera模组中也可以包含ISP模块,用于对图像进行处理,有的SoC中也集成了ISP的IP,接收camera的raw数据后,进行图像处理;

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

【原创】Linux v4l2框架分析

<code>v4l2_device</code>:对视频设备的整体进行抽象,可以看成是一个纽带,将各个子设备联系在一起,通常它会嵌入在其他结构体中以提供<code>v4l2</code>框架的功能,比如<code>strcut isp_device</code>;

<code>v4l2_subdev</code>:对子设备进行抽象,该结构体中包含的<code>struct v4l2_subdev_ops</code>是一个完备的操作函数集,用于对接各种不同的子设备,比如video、audio、sensor等,同时还有一个核心的函数集<code>struct v4l2_subdev_core_ops</code>,提供更通用的功能。子设备驱动根据设备特点实现该函数集中的某些函数即可;

<code>video_device</code>:用于向系统注册字符设备节点,以便用户空间可以进行交互,包括各类设置以及数据buffer的获取等,在该结构体中也能看到<code>struct v4l2_ioctl_ops</code>和<code>struct vb2_queue</code>结构体字段,这些与上文中的应用层代码编写息息相关;

如果子设备不需要与应用层交互,<code>struct v4l2_subdev</code>中内嵌的<code>video_device</code>也可以不向系统注册字符设备;

<code>video_device</code>结构体,可以内嵌在其他结构体中,以便提供用户层交互的功能,比如<code>struct isp_video</code>;

针对图中回调函数集,<code>v4l2-core</code>提供了一些实现,所以driver在实现时,非特殊情况下可以不用重复造轮子;

来进一步看一下内部的注册,及调用流程吧:

【原创】Linux v4l2框架分析

在驱动实现中,驱动结构体中内嵌<code>struct video_device</code>,同时实现<code>struct v4l2_file_operations</code>结构体中的函数,最终通过<code>video_register_device</code>向提供注册;

<code>v4l2_register_device</code>函数通过<code>cdev_add</code>向系统注册字符设备,并指定了<code>file_operations</code>,用户空间调用<code>open/read/write/ioctl</code>等接口,便可回调到驱动实现中;

<code>v4l2_register_device</code>函数中,通过<code>device_register</code>向系统注册设备,会在<code>/sys</code>文件系统下创建节点;

完成注册后,用户空间便可通过文件描述符来进行访问,从应用层看,大部分都是通过<code>ioctl</code>接口来完成,流程如下:

【原创】Linux v4l2框架分析

用户层的<code>ioctl</code>回调到<code>__video_do_ioctl</code>中,该函数会对系统提供的<code>struct v4l2_ioctl_info v4l2_ioctls[]</code>表进行查询,找到对应的项后进行调用;

驱动做的工作就是填空题,实现对应的回调,在合适的时候被调用;

下一个小节,让我们看看更复杂一点的情况。

为了更好的描述,本节以<code>omap3isp</code>为例,先看一下它的硬件构成:

【原创】Linux v4l2框架分析

CSI:camera接口,接收图像数据,RGB/YUV/JPEG等;

CCDC:视频处理前端,CCDC为图像传感器和数字视频源提供接口,并处理图像数据;

Preview/Resizer:视频处理后端,Preview提供预览功能,可针对不同类型的传感器进行定制,Resizer提供将输入图像数据按所需的显示或视频编码分辨率调整大小的方法;

H3A/HIST:静态统计模块,H3A支持AF、AWB、AE的回路控制,HIST根据输入数据,提供各种3A算法所需的统计数据;

上述硬件模块,可以对应到驱动结构体<code>struct isp_device</code>中的各个字段。

omap3isp的硬件模块,支持多种数据流通路,它并不是唯一的,以RGB为例,如下图:

【原创】Linux v4l2框架分析

Raw RGB数据进入ISP模块后,可以在运行过程中,根据实际的需求进行通路设置;

所以,重点是:它需要动态设置路径!

那么,软件该如何满足这种需求呢?

没错,pipeline框架的引入可以解决这个问题。说来很巧,我曾经也实现过一个类似的框架,在阅读media framework时有一种似曾相识的感觉,核心的思想大体一致。

【原创】Linux v4l2框架分析

模块之间相互独立,通过<code>struct media_entity</code>来进行抽象,通常会将<code>struct media_entity</code>嵌入到其他结构中,以支持<code>media framework</code>功能;

模块包含<code>struct media_pad</code>,pad可以认为是端口,与其他模块进行联系的媒介,针对特定模块来说它是确定的;

pad通过<code>struct media_link</code>来建立连接,指定source和sink,即可将通路建立起来;

各个模块之间最终建立一条数据流,便是一条pipeline了,同一条pipeline中的模块,可以根据前一个模块查找到下一个模块,因此也可以很方便进行遍历,并做进一步的设置操作;

因此,只需要将<code>struct media_entity</code>嵌入到特定子模块中,最终便可以将子模块串联起来,构成数据流。所以,<code>omap3isp</code>的驱动中,数据流就如下图所示:

【原创】Linux v4l2框架分析

<code>video devnode</code>代表<code>video device</code>,也就是前文中提到的导出到用户空间的节点,用于与用户进行控制及数据交互;

每个模块分别有source pad和sink pad,从连接图就可以看出,数据通路灵活多变;

至于数据通路选择问题,可以在驱动初始化的时候进行链接创建,比如<code>isp_create_links</code>;

还是看一下数据结构吧:

【原创】Linux v4l2框架分析

<code>media_device</code>:与<code>v4l2_device</code>类似,也是负责将各个子模块集中进行管理,同时在注册的时候,会向系统注册设备节点,方便用户层进行操作;

<code>media_entity</code>、<code>media_pad</code>、<code>media_link</code>等结构体的功能在上文中描述过,注意,这几个结构体会添加到<code>media_device</code>的链表中,同时它们结构体的开始字段都需是<code>struct media_gobj</code>,该结构中的<code>mdev</code>将会指向它所属的<code>media_device</code>。这种设计方便结构之间的查找;

<code>media_entity</code>中包含多个<code>media_pad</code>,同时<code>media_pad</code>又会指向它所属的<code>media_entity</code>;

<code>media_graph</code>和<code>media_pipeline</code>是<code>media_entity</code>的集合,直观来理解,就是由一些模块构成的一条数据通路,由一个统一的数据结构来组织管理;

罗列一下常见的几个接口吧,细节不表了:

将<code>media framework</code>和<code>v4l2_device</code>及<code>v4l2_subdev</code>结合起来,就可以将各个子设备构建pipeline,完美!

框架可以分成两个部分看:控制流+数据流,上文已经大概描述了控制流,数据流的部分就是<code>video buffer</code>了。

<code>V4L2</code>的buffer管理是通过<code>videobuf2</code>来完成的,它充当用户空间和驱动之间的中间层,并提供low-level,模块化的内存管理功能;

【原创】Linux v4l2框架分析

上图大体包含了videobuf2的框架;

<code>vb2_queue</code>:核心的数据结构,用于描述buffer的队列,其中<code>struct vb2_buffer *bufs[]</code>是存放buffer节点的数组,该数组中的成员代表了<code>vb2 buffer</code>,并将在<code>queued_list</code>和<code>done_list</code>两个队列中进行流转;

<code>struct vb2_buf_ops</code>:buffer的操作函数集,由驱动来实现,并由框架通过<code>call_bufop</code>宏来对特定的函数进行调用;

<code>struct vb2_mem_ops</code>:内存buffer分配函数接口,buffer类型分为三种:1)虚拟地址和物理地址都分散,可以通过dma-sg来完成;2)物理地址分散,虚拟地址连续,可以通过vmalloc分配;3)物理地址连续,可以通过dma-contig来完成;三种类型也vb2框架中都有实现,框架可以通过<code>call_memop</code>来进行调用;

<code>struct vb2_ops</code>:vb2队列操作函数集,由驱动来实现对应的接口,并在框架中通过<code>call_vb_qop</code>宏被调用;

本节以<code>omap3isp</code>为例进行简要分析,感觉直接看图就可以了:

buffer申请
【原创】Linux v4l2框架分析
buffer enqueue
【原创】Linux v4l2框架分析
buffer dequeue
【原创】Linux v4l2框架分析
stream on
【原创】Linux v4l2框架分析

行文至此,主体讲完了,相信看完本文应该有个大概的轮廓了,还有一些细节未进一步描述,就此打住。

https://lwn.net/Articles/416649/

《OMAP35x Technical Reference Manual (Rev. Y).pdf》

欢迎关注公众号,不定期分享技术文章

【原创】Linux v4l2框架分析

作者:LoyenWang

出处:https://www.cnblogs.com/LoyenWang/

公众号:<b>LoyenWang</b>

版权:本文版权归作者和博客园共有

转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

继续阅读