当前位置: 首页 > 科技观察

说说Linuxv4l2框架解析

时间:2023-03-21 18:09:55 科技观察

本文转载自微信公众号《LoyenWang》,作者LoyenWang。转载本文请联系LoyenWang公众号。背景阅读他妈的源代码!——鲁迅一图胜千言。--作者:Gorky描述:内核版本:4.14ARM64处理器、Contex-A53、双核工具:SourceInsight3.5、Visio1。概述V4L2(VideoforLinux2):Linux内核中的视频设备驱动框架,为应用层提供统一的接口,支持各种复杂硬件的灵活扩展;V4L2框架主要包括v4l2-core、meida框架、videobuf2等模块,这也是本文要展开的内容,仅做提纲;开始吧。2、从v4l2-core2.1的应用角度,我们先从应用角度看一下v4l2如何使用:如果要采集视频数据,一般步骤如上图左侧所示:打开设备文件/dev/videoX;根据打开的设备,查询设备能力集;设置视频数据格式、参数等;allocatebuffer,这个buffer可以在用户态分配,也可以从内核获取;开始视频流采集工作;enqueuebuffer到v4l2框架,底层填充视频数据后,应用层会dequeuebuffer获取数据,然后enqueuebuffer,依此类推。上图右边是v4l2-core的总体框架,右边是硬件的抽象。为了理解好,我们先来看看比较常见的硬件拓扑结构:图中通常是一个摄像头模块,通常包括Lens、Sensor、CSI接口等,其中CSI接口用于视频数据传输;SoC的Mipi接口连接Camera,通过I2C/SPI控制摄像头模块;camera模块还可以包含一个ISP模块,用于图像处理,有些SoC也集成了ISPIP,接收到camera的原始数据后进行图像处理;2.2数据结构如果以上图中的硬件为例,如何抽象相机的硬件?没错,就是用v4l2_device和v4l2_subdev进行抽象,v4l2_device代表整个输入设备,v4l2_subdev代表子模块,比如CSI,Sensor等;v4l2_device:将视频设备抽象为一个整体,可以看作是连接各个子设备的纽带。通常会嵌入到其他结构中,提供v4l2框架的功能,如strcutisp_device;v4l2_subdev:抽象子设备。该结构体包含的structv4l2_subdev_ops是一套完整的操作函数,用于连接各种子设备,如video、audio、sensor等,还有一个核心函数集structv4l2_subdev_core_ops,提供更通用的功能.子设备驱动可以根据设备的特性实现功能集中的一些功能;video_device:用于向系统注册字符设备节点,以便用户空间进行交互,包括各种设置和数据缓冲区的获取等,在这个结构体中还可以看到structv4l2_ioctl_ops和structvb2_queue结构体字段,它们与上面写的应用层代码;如果子设备不需要与应用层交互,structv4l2_subdev中嵌入的video_device可以不向系统注册字符设备;video_device结构体可以嵌入其他结构体,提供用户级交互功能,如structisp_video;对于图中设置的回调函数,v4l2-core提供了一些实现,所以driver在实现的时候没有特殊情况不需要重复造轮子;2.3流程分析我们来仔细看看内部注册调用流程:在驱动实现中,在驱动结构体中嵌入了structvideo_device,同时实现了structv4l2_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中,该函数会查询系统提供的structv4l2_ioctl_infov4l2_ioctls[]表,找到对应项后调用;驱动的工作就是填空,实现相应的回调,在合适的时候被调用;一个小节,我们来看稍微复杂一点的情况。3.引入mediaframework3.1的问题。为了更好的说明,本节以omap3isp为例。我们来看看它的硬件结构:CSI:摄像头接口,接收图像数据,RGB/YUV/JPEG等;CCDC:videoprocessingfrontend,CCDC为图像传感器和数字视频源提供接口,处理图像数据;Preview/Resizer:视频处理后端,Preview提供预览功能,可以针对不同类型的传感器进行定制,Resizer提供输入图像数据按需显示或视频编码分辨率调整大小的方法;H3A/HIST:静态统计模块,H3A支持AF、AWB、AE循环控制,HIST根据输入数据提供各种3A算法所需的统计数据;以上硬件模块,可以对应驱动结构体structisp_device中的各个字段。omap3isp的硬件模块支持多种数据流通道,并不是唯一的。以RGB为例,如下图所示:RawRGB数据进入ISP模块后,运行时可根据实际需要设置通道;因此,重点是:它需要动态设置路径!那么,软件如何满足这个要求呢?3.2Framework是的,pipeline框架的引入可以解决这个问题。巧合的是,我也实现了类似的框架。看mediaframework的时候有种似曾相识的感觉,核心思想大体相同。模块之间相互独立,由structmedia_entity抽象出来。通常,structmedia_entity被嵌入到其他结构中以支持媒体框架功能;实体模块包含structmedia_pad,可以认为是端口,是与其他模块通信的媒介。对于具体的模块是确定的;pad通过structmedia_link建立连接,通过指定source和sink即可建立路径;每个模块之间最终建立了一个数据流,也就是一个管道,在同一个管道模块中,可以根据上一个模块找到下一个模块,这样也很方便遍历和做进一步的设置操作;因此,你只需要将structmedia_entity嵌入到一个特定的子模块中,最后你就可以将子模块串联起来形成一个数据流。因此在omap3isp的驱动中,数据流向如下图所示:videodevnode表示视频设备,也就是上面提到的导出到用户空间的节点,用于控制和与用户进行数据交互;每个模块都有sourcepad和sinkpad,从连接图可以看出数据通路灵活多变;至于数据路径的选择问题,可以在驱动初始化时创建链接,如isp_create_links;我们看一下数据结构:media_device:和v4l2_device类似,也是负责各个子模块的集中管理。同时在注册的时候会向系统注册设备节点,方便用户层的操作;media_entity、media_pad、media_link等结构体的作用上面已经介绍过了。注意这几个结构体会被添加到media_device的链表中,它们结构体的起始字段必须是structmedia_gobj,这个结构体中的mdev会指向它所属的media_device。这种设计便于结构间的查找;media_entity包含多个media_pads,同时media_pad会指向它所属的media_entity;media_graph和media_pipeline是media_entities的集合。直观上,它是由一些模块组成的数据通路。统一的数据结构来组织和管理;列举一些常用的接口,细节就不列举了:/*初始化实体的pads*/intmedia_entity_pads_init(structmedia_entity*entity,u16num_pads,structmedia_pad*pads);/*在两个实体之间创建链接*/intmedia_create_pad_links(conststructmedia_device*mdev,constu32source_function,structmedia_entity*source,constu16source_pad,constu32sink_function,structmedia_entity*sink,constu16sink_pad,u32flags,constboolallow_both_undefined);/*开始图的遍历,从指定的实体开始*/voidmedia_graph_walk_entity*/voidmedia_graph_walk_entity*structitymediagraphent*Startitymediagraph*structitymedia_startthepipeline*/__must_checkintmedia_pipeline_start(structmedia_entity*entity,structmedia_pipeline*pipe);将媒体框架与v4l2_device和v4l2_subdev结合起来,就可以为每个子设备建立一个pipeline,完美!4.videobuf24。1框架分析框架可以分为两部分:控制流+数据流。上面已经大致描述了控制流,数据流部分就是视频缓冲区。V4L2的缓冲区管理是通过videobuf2完成的,videobuf2充当用户空间和驱动之间的中间层,提供低级、模块化的内存管理功能;上图大致包含了videobuf2的框架;vb2_queue:核心数据结构,用于描述buffer队列,其中structvb2_buffer*bufs[]是一个存放buffer节点的数组,这个数组中的成员代表vb2buffer,会流入queued_list和done_list两个队列;structvb2_buf_ops:buffer操作函数集由驱动实现,框架通过call_bufop宏调用具体函数;structvb2_mem_ops:内存缓冲区分配函数接口,缓冲区类型分为三种:1)虚拟地址和物理地址是分散的,可以通过dma-sg来完成;2)物理地址是分散的,虚拟地址是连续的,可以通过vmalloc分配;3)物理地址是连续的,可以通过dma-contig来实现;这三种类型在vb2框架中也有实现,框架可以通过call_memop进行调用;structvb2_ops:vb2队列操作函数集,对应接口由驱动实现,在框架中通过call_vb_qop宏调用;4.2流程分析本节以omap3isp为例简单分析一下,感觉直接看图就是了:bufferapplicationbufferenqueuebufferdequeuestreamon至此,正文就讲完了。相信看完这篇文章,应该会有一个大概的轮廓。还有一些细节没有进一步描述,就此打住。参考https://lwn.net/Articles/416649/《OMAP35x Technical Reference Manual (Rev. Y).pdf》