Linux之摄像头简述

Linux下与摄像头相关的部分主要分有以下几类:

  • V4L2/Media框架,包括UVC
  • MPP框架
  • ISP

V4L2/Meida框架

  • V4L2

This driver emulates video4linux hardware of various types: video capture, video output, vbi capture and output, radio receivers and transmitters and a software defined radio receiver.

V4L2(Video4Linux)是Linux下关于视频相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范
v4l2

拓扑链路

多媒体设备涉及到多个设备之间的数据链接和数据流控制,按照v4l2的标准,它会将一个数据流设备抽象成一个videoX节点,从属主设备都对应着各自的v4l2_subdev实现,并且按照media controller进行统一管理。

media将这些媒体设备形成的数据通路上的各个设备建立拓扑关系,便于实现各个设备之间的链接控制和数据传输管理。

v4l2_media.png

RK356X链路举例

举个例,结合rk的vicap和isp的链路,来看看:
下面RK356X的一种摄像头链路,注:RK356X的VICAP和ISP是独立的两个图像处理IP

链接关系:

sensor -> csi2 dphy -> mipi csi host -> vicap

img

DTS配置:
与上图是一一对应的

gc5025: gc5025@37 {
	status = "okay";
	compatible = "galaxycore,gc5025";
	...
	port {
		gc5025_out: endpoint {
			remote-endpoint = <&dphy2_in>;
			data-lanes = <1 2>;
		};
	};
};

&csi2_dphy_hw {
	status = "okay";
};

&csi2_dphy2 {

	status = "okay";
	ports {
		#address-cells = <1>;
		#size-cells = <0>;
		port@0 {
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;
			dphy2_in: endpoint@1 {
				reg = <1>;
				remote-endpoint = <&gc5025_out>;
				data-lanes = <1 2>;
			};
		};
		port@1 {
			reg = <1>;
			#address-cells = <1>;
			#size-cells = <0>;
			dphy2_out: endpoint@1 {
				reg = <1>;
				remote-endpoint = <&mipi_csi2_input>;
			};
		};
	};
};

&mipi_csi2 {
	status = "okay";
	ports {
		#address-cells = <1>;
		#size-cells = <0>;
		port@0 {
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;
			mipi_csi2_input: endpoint@1 {
				reg = <1>;
				remote-endpoint = <&dphy2_out>;
				data-lanes = <1 2>;
			};
		};
		port@1 {
			reg = <1>;
			#address-cells = <1>;
			#size-cells = <0>;
			mipi_csi2_output: endpoint@0 {
				reg = <0>;
				remote-endpoint = <&cif_mipi_in>;
				data-lanes = <1 2>;
			};
		};
	};
};

&rkcif_mipi_lvds {
	status = "okay";
	port {
		cif_mipi_in: endpoint {
			remote-endpoint = <&mipi_csi2_output>;
			data-lanes = <1 2>;
		};
	};
};

&rkcif_mipi_lvds_sditf {
	status = "okay";
	port {
		/* MIPI CSI-2 endpoint */
		mipi_lvds_sditf: endpoint {
			remote-endpoint = <&isp_in>;
			data-lanes = <1 2>;
		};
	};
};

&rkisp_vir0 {
	status = "okay";
	ports {
		port@0 {
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;
			isp_in: endpoint@0 {
				reg = <0>;
				remote-endpoint = <&mipi_lvds_sditf>;
			};
		};
	};
};

这里的sensor、csi_dphy、csi_host、isp等都是一个v4l2_subdev,都会链接到video和media节点下面,这样就可以通过这两个节点去获取和配置相应子节点的参数,链接通路,和控制上下电等

代码分析

一些概念

从上面可知,与摄像头视频相关的各个子v4l2_subdev都是通过media子系统串联起来的,包括cis,mipi-dphy,vcm,flashlight,ircut等
而这个主要靠的是media子系统中最重要的3个组件:entity、pad、link

  1. media_entity:硬件设备模块抽象(类比电路板上面的各个元器件、芯片)
  2. media_pad :硬件设备端口抽象(类比元器件、芯片上面的管脚),pad分source pad和sink pad
  3. media_link :硬件设备的连接抽象,link 的两端是 pad(类比元器件管脚之间的连线),数据由source pad 流向 sink pad
#------------#                #------------#
|          __|__            __|__          |
|         |  |  |   link   |  |  |         |
|         | pad |<-------->| pad |         |
|         |__|__|          |__|__|         |
|            |                |            |
|   entity   |                |   entity   |
#------------#                #------------# 

主要代码

主要代码目录: drivers/media

V4L2部分:
在驱动probe阶段,通过 v4l2_subdev_init或类似的函数进行v4l2子设备的初始化,重要的是那些操作集回调函数
例如下面是一个cis的v4l2_subdev初始化:

static const struct v4l2_subdev_ops gc8034_subdev_ops = {
	.core	= &gc8034_core_ops,  // Define core ops callbacks for subdevs, 上下电控制,ioctl等
	.video	= &gc8034_video_ops, //Callbacks used when v4l device was opened in video mode
	.pad	= &gc8034_pad_ops, // v4l2-subdev pad level operations
};
v4l2_i2c_subdev_init(sd, client, &gc8034_subdev_ops);

后面是 v4l2_async_register_subdev或类似的函数进行v4l2子设备的注册,例如sensor的注册:

v4l2_async_register_subdev_sensor(sd);

有些中间的节点会多些下面的处理:

//初始化异步notifier
v4l2_async_notifier_init()

//该函数会根据dts里面配置的port(可参看上面列举的例子),将各个节点(sensor, mipi-phy等)绑定在同一notifier,实现联动
v4l2_async_notifier_parse_fwnode_endpoints_by_port()

//注册notifier
v4l2_async_subdev_notifier_register()

Media部分:

在驱动probe阶段,通过 media_entity_pads_init初始化该模块的media entity,
后面 media_create_pad_link进行pad之间的link,这样不同的 entity 之间就建立的联系

下面就是一个获取远端sensor的 entity 的例子:

static struct v4l2_subdev *get_remote_sensor(struct v4l2_subdev *sd)
{
	struct media_pad *local, *remote;
	struct media_entity *sensor_me;

    local = &sd->entity.pads[CSI2_DPHY_RX_PAD_SINK];
	remote = media_entity_remote_pad(local);
	if (!remote) {
		v4l2_warn(sd, "No link between dphy and sensor\n");
		return NULL;
	}

    sensor_me = media_entity_remote_pad(local)->entity;
	return media_entity_to_v4l2_subdev(sensor_me);
}

使用

使用流程

主要是通过open、ioctl、close等接口去操作video的设备节点。

v4l2

具体的使用代码实例可以参考:V4L2采集视频

v4l-utils

v4l-utils工具集,它主要包含两个常用工具:

  • media-ctl:用于配置拓扑结构中各节点的format、大小、链接等
  • v4l2-ctl:用于获取和设置v4l2设备的参数等

UVC

UVC:The Linux USB Video Class,具体协议文档可从USB官网下载,《USB Video Class 》,现在最新的是1.5版本

整体流程如下:

摄像头sensor+处理芯片(带固件)--> USB 接口(UVC协议)--> CPU/MPU

UVC驱动也是基于V4L2框架实现,所以应用层的操作基本上和片上摄像头类似,有些特殊自定义的接口需要通过扩展单元(XUs)来实现:

  • through mappings of XU controls to V4L2 controls
  • through a driver-specific ioctl interface

内核驱动文档:https://www.kernel.org/doc/html/v5.15/userspace-api/media/drivers/uvcvideo.html

在桌面PC,用的最多的应该就是USB摄像头了,CPU一般没有专门的摄像头接口,成本不是太敏感,简单易用易更换,应用场景摄像头需求并不是那么强烈(基本只有笔记本带摄像头),

嵌入式(移动端)用的最多的主要是MIPI、DVP、LVDS等接口的摄像头, Soc一般都带有这些摄像头接口,成本敏感,专用性强,特定的应用场景

MPP框架

MPP:Media Process Platform,MPP屏蔽了不同操作系统和不同芯片平台的差异,为上层使用者提供统一的 MPI 接口。

当前一些芯片的sdk中都提供了这一层,但各自的实现可能各不相同,主要针对IPC等特定领域,能使客户能够快速的开发自己的多媒体应用

这里主要讲讲Hisi和Rockchip。

Hisi:

海思的MPP包括以下功能:输入视频捕获、视频编解码、视频输出显示、视频图像前处理(包括去噪、增强、锐化、Deinterlace)、编码码流叠加 OSD、视频侦测分析、智能分析、音频捕获及输出、音频编解码等功能

HiMpp

  • VI 模块
    视频捕获,从视频采集接口如mipi,lvds,HISPI6等采集图像,可对其剪切、缩放等,并输出多路不同分辨率的图像数据

  • VPSS视频处理模块
    接收 VI 和解码模块发送过来的图像,可对图像进行去噪、图像增强、锐化等处理,并实现同源输出多路不同分辨率的图像数据用于编码、预览或抓拍

  • VENC编码模块

    经VPSS 处理后输出的图像数据,可叠加用户通过 Region模块设置的OSD 图像,然后按不同协议进行编码并输出相应码流

  • VDEC解码模块

    视频码流进行解码,并将解析后的图像数据送 VPSS 进行图像处理或直接送 VO 显示

  • VDA模块

    接收 VI 的输出图像,并进行移动侦测和遮挡侦测,最后输出侦测分析结果

  • VO模块
    接收 VPSS 处理后的输出图像,可进行播放控制等处理,最后按用户配置的输出协议输出给外围视频设备

  • AI模块

    捕获音频数据,然后 AENC 模块支持按多种音频协议对其进行编码,最后输出音频码流

  • AO模块

    从网络或外围存储设备获取的音频码流可直接送给 ADEC 模块,ADEC 支持解码多种不同的音频格式码流,解码后数据送给 AO 模块播放声音

Rockchip:

rockchip的MPP只做了视频编解码,视频处理(缩放,色彩空间转换等),视频采集使用了标准的 v4l2/media 框架。

源码地址为:https://github.com/rockchip-linux/mpp/

+---------------------------------------+
             |                                       |
             | ffmpeg / OpenMax / gstreamer / libva  |
             |                                       |
             +---------------------------------------+

         +-------------------- MPP ----------------------+
         |                                               |
         |   +-------------------------+    +--------+   |
         |   |                         |    |        |   |
         |   |        MPI / MPP        |    |        |   |
         |   |   buffer queue manage   |    |        |   |
         |   |                         |    |        |   |
         |   +-------------------------+    |        |   |
         |                                  |        |   |
         |   +-------------------------+    |        |   |
         |   |                         |    |        |   |
         |   |          codec          |    |  OSAL  |   |
         |   |    decoder / encoder    |    |        |   |
         |   |                         |    |        |   |
         |   +-------------------------+    |        |   |
         |                                  |        |   |
         |   +-----------+ +-----------+    |        |   |
         |   |           | |           |    |        |   |
         |   |  parser   | |    HAL    |    |        |   |
         |   |  recoder  | |  reg_gen  |    |        |   |
         |   |           | |           |    |        |   |
         |   +-----------+ +-----------+    +--------|   |
         |                                               |
         +-------------------- MPP ----------------------+

             +---------------------------------------+
             |                                       |
             |                kernel                 |
             |       RK vcodec_service / v4l2        |
             |                                       |
             +---------------------------------------+
  • 内核驱动层 :视频编解码器驱动,以及相关内存,时钟,电源管理模块等
  • MPP 层:包括 MPI 模块, OSAL 模块, HAL 模块、视频codec模块和解析器等
  • 应用层:通过 MPI 对接各种中间件软件,如 OpenMax, ffmpeg 和 gstreamer,或者直接对接客户的上层应用

ISP

最后再说下ISP,ISP与摄像头息息相关

ISP: Image Signal Processing
ISP一般可集成到sensor里面(比较弱),也可集成到AP/MPU中,或者是独立ISP芯片(像现在的一些手机厂商都出了自己的ISP芯片)。
这里我们主要讨论集成在AP/MPU的ISP,比如在这列举的Rockchip的RK356x的ISP21。
厂家ISP都有一套自己的闭源实现,通过v4l2/media或ioctl的接口与驱动对接,或者类似于海思一样,直接集成到MPP之中

处理流程

ISP大致流程:

ISP

从 sensor 端过来的图像是 Bayer 图像,经过黑电平补偿 (black level compensation)、镜头矫正 (lens shading correction)、坏像素矫正(bad pixel correction)、颜色插值 (demosaic)、Bayer 噪声去除、 白平衡(awb) 矫正,色彩矫正(color correction) 、 gamma 矫正、 色彩空间转换(RGB 转换为 YUV)、 在 YUV 色彩空间上彩噪去除与边缘加强、 色彩与对比度加强,中间还要进行自动曝光控制(AEC)等, 然后输出 YUV(或者 RGB) 格式的数据, 再通过 I/O 接口传输到 CPU 中处理

Rockchip的ISP

摄像头采集视频流流程图

rockchip-ISP2

这里的ISP模块都是作为一个v4l2_subdev,驱动都是基于v4l2/media框架。

Rockchip的ISP21系统框图

rk_isp21.png

Rockchip的ISP21软件架构图

rk_isp_soft_fwk.png

总结

视频采集与图像处理的一些现状:

  1. 随着带摄像头接口的嵌入式移动端平台的增多,以前基于V4L2或是openMAX接口不能很好地满足实际需要,特别是针对芯片平台特有的一些功能及应用,以及各种越来越多的复杂运用,像超分,超广角畸变校正,光学变焦,人像模式,防抖,机器视觉,多摄等等
  2. 桌面端,相对于移动端或嵌入式端,视频输入处理这块相对偏弱,主要是侧重点不一样。通用桌面端主要依赖USB摄像头,成本较高,但选择灵活,但针对于嵌入式平台(带视频处理能力)的桌面端,使用USB摄像头并没有充分利用该平台的图像处理能力

本文只是走马观花,简单地介绍了Linux下摄像头相关的几大部分,及里面涉及的一些概念和简单流程,并没有逐个去深入,不涉及CIS、MIPI、ISP等驱动细节,有兴趣的同学可以自己去撸下代码,深入研究

参考