[Linux 基础] -- Linux v4l2 框架分析

本文转自: Linux v4l2框架分析

背景

        说明:

  1. Kernel 版本:4.14;
  2. ARM64 处理器,Contex-A53,双核;
  3. 使用工具:Source Insight 3.5,Visio。

一、概述

  • V4L2(Video for Linux 2):Linux内核中关于视频设备驱动的框架,对上向应用层提供统一的接口,对下支持各类复杂硬件的灵活扩展;
  • V4L2 框架,主要包括 v4l2-coremedia frameworkvideobuf2 等模块,这也是本文将要展开的内容,仅提纲挈领。

        开始吧!

二、v4l2-core

2.1 应用视角

        先从应用的角度来看如何使用 v4l2 吧:

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

  1. 打开设备文件 /dev/videoX
  2. 根据打开的设备,查询设备能力集;
  3. 设置视频数据的格式、参数等;
  4. 分配 buffer,这个 buffer 可以是用户态分配的,也可以是从内核中获取的;
  5. 开始视频流采集工作;
  6. 将 buffer enqueue 到 V4L2 框架,底层负责将视频数据填充后,应用层再将 buffer dequeue 以便获取数据,然后再将 buffer enqueue,如此循环往复;

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

  •  通常一个 camera 的模组如图所示,通常包括 Lens、Sensors、CSI 接口等,其中 CSI 接口用于视频数据的传输;
  • SoC 的 MIPI 接口对接 Camera,并通过 I2C/SPI 控制 Camera 模组;
  • Camera 模组中也可以包含 ISP 模块,用于对图像进行处理,有点 SoC 中也集成了 ISP 的 IP,接收 Camera 的 raw 数据后,进行图像处理。

2.2 数据结构

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

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

2.3 流程分析

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

  • 在驱动实现中,驱动结构体中内嵌 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[ ] 表进行查询,找到对应的项后进行调用;
  • 驱动做的工作就是填空题,实现对应的回调,在合适的时候被调用;

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

三、media framework

3.1 问题引入

        为了更好的描述,本节以 omap3isp 为例,先看一下它的硬件结构:

  • CSI:camera 接口,接收图像数据,RGB/YUV/JPEG 等;
  • CCDC:视频处理前端,CCDC 为图像传感器和数字视频源提供接口,并处理图像数据;
  • Preview/Resizer:视频处理后端,Preview 提供预览功能,可针对不同类型的传感器进行定制,Resizer 提供将输入图像数据按所需的显示或视频编码分辨率调整大小的方法;
  • H3A/HIST:静态统计模块,H3A 支持 AF、AWB、AE 的回路控制,HIST 根据输入数据,提供各种 3A 算法所需的统计数据;

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

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

  •  Raw RGB 数据进入 ISP 模块后,可以在运行过程中,根据实际的需求进行通路设置;
  • 所以,重点是:它需要动态设置路径!

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

3.2 框架

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

  • 模块之间相互独立,通过 struct media_entity 来进行抽象,通常会将 struct media_entity 嵌入到其他结构中,以支持 media framework 功能;
  • entity 模块包含 struct media_pad,pad 可以认为是端口,与其他模块进行联系的媒介,针对特定模块来说它是确定的;
  • pad 通过 struct media_link 来建立连接,指定 source 和 sink,即可将通路建立起来;
  • 各个模块之间最终建立一条数据流,便是一条 pipeline 了,同一条 pipeline 中的模块,可以根据前一个模块查找到下一个模块,因此也可以很方便进行遍历,并做进一步的设置操作;

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

  • video devnode 代表 video device,也就是前文中提到的导出到用户空间的节点,用于与用户进行控制及数据交互;
  • 每个模块分别有 source pad 和 sink pad,从连接图就可以看出,数据通路灵活多变;
  • 至于数据通路选择问题,可以在驱动初始化的时候进行链接创建,比如 isp_create_links

        还是看一下数据结构吧:

  • media_device:与 v4l2_device 类似,也是负责将各个子模块集中进行管理,同时在注册的时候,会向系统注册设备节点,方便用户层进行操作;
  • media_entitymedia_padmedia_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 frameworkv4l2_device v4l2_subdev 结合起立,就可以将各个子设备构建 pipeline,完美!

四、videobuf2

4.1 框架分析

  • 框架可以分成两个部分看:控制流 + 数据流,上文已经大概描述了控制流,数据流的部分就是 video buffer 了。
  • V4L2 的 buffer 管理是通过 videobuf2 来完成的,它充当用户空间和驱动之间的中间层,并提供 low-level,模块化的内存管理功能;

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

  • 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 宏被调用;

4.2 流程分析

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

        1、buffer 申请

         2、buffer enqueue

         3、buffer dequeue

        4、stream on

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

参考:

  • https://lwn.net/Articles/416649/ 
  • 《OMAP35x Technical Reference Manual (Rev. Y).pdf》

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值