28 Linux V4L2框架
V4L2(Video for Linux 2)是 Linux 系统中用于处理视频设备的标准接口框架。它提供了统一的 API,使得应用程序能够与各种视频设备(如摄像头、视频采集卡等)进行交互。V4L2 的设计目的是为了简化视频设备的管理和操作,提高设备驱动开发的效率。
28.1 Linux V4L2框架
Linux 视频设备涉及到很多方面, 驱动程序往往非常复杂,因为硬件本身的复杂性:大多数设备具有多个 IC,会在 /dev 中导出多个设备节点,并且还会创建非 V4L2 设备,如 DVB、ALSA、FB、I2C 和输入(红外)设备。
特别是 V4L2 驱动程序必须设置支持的 IC 来进行音视频的复用、编码和解码,这使得它比大多数驱动程序更为复杂。通常这些 IC 通过一个或多个 I2C 总线连接到主桥驱动程序,但也可以使用其他总线。这类设备被称为“子设备”。
因此,V4L2框架建立了所有驱动程序都需要的基本构建模块,这同样的框架应使得重构通用代码变得更为容易,能够将其转化为所有驱动程序共享的实用函数。
下面是Rockchip平台在Linux上面V4L2的框架:
|------------------------------------------------------------------------------|
| userspace app (gstreamer) |
|------------------------------------------------------------------------------|
^
|
v
|------------------------------------------------------------------------------|
| libv4l v4l2 utilities... |
|------------------------------------------------------------------------------|
^
| /dev/videox
| export videox devices
v ^
|---------------------------------------------------------------------|---------|
| V4L2 Core (kernel space) | |
| | |
| |--------------------------------------------------------------|----| |
| | v4l2 core module | |
| |--------------------------------------------------------------|----| |
| ^ | |
| | | |
| |--------------------------------------------------------------|----| |
| | Media Controller & Sub-device Framework | |
| |--------------------------------------------------------------|----| |
| ^ ^ ^ ^ ^ |
| | | | | | |
| | v v v v |
| | |-------------| |------------| |--------| |------| |
| | | csi2-dphy |-> | mipi2-csi2 |-> | rkcif |-> | isp | |
| | |-------------| |------------| |--------| |------| |
| v | |
| |----------------| | |
| | ov13855 driver | | |
| |----------------| | |
|-------------------------------------------------------------------------------|
^ ^
signal| i2c data| mipi-dcphy
v v
|---------------------------------|
| camera sensor |
|---------------------------------|
Rockchip RK3588 mipi摄像头视频设备拓扑图如下:
28.2 V4l2 Core
Linux V4l2 核心代码在: kernel/drivers/media/v4l2-core/*
, 其中:
v4l2-dev.c
- 负责 V4L2 设备的注册、打开、关闭、IOCTL 处理、缓冲区管理等。
v4l2-ioctl.c
v4l2-subdev.c
- 处理子设备(sub-device)的注册和管理,通常用于摄像头传感器、图像处理器等。
v4l2-common.c
v4l2-mem2mem.c
处理内存到内存的视频流转换。
实现内存到内存的流处理(如编码、解码)。
处理缓冲区的请求和管理。
videodev2.h
v4l2-file.c
处理与 V4L2 设备文件的交互。
设备文件的打开、关闭和读写操作的实现。
处理用户空间与内核空间之间的数据传输。
v4l2-fwnode.c
v4l2-v4l2-ctrls.c
v4l2-async.c
支持异步注册和连接子设备。
实现子设备的异步管理。
支持在设备连接和断开时的通知。
28.2.1 V4l2 Core作用
V4L2 Core模块的作用主要集中在为Linux内核提供视频设备的支持和管理。主要作用包括如下:
统一接口
V4L2 Core提供了一套统一的接口,使得不同类型的视频设备(如摄像头、视频捕获卡、视频输出设备等)可以通过相同的方式进行操作。这种统一性简化了设备驱动程序的编写和用户空间应用的开发。
设备管理
V4L2 Core负责管理所有注册的视频设备,包括设备的注册、注销、初始化和配置。这包括管理视频设备的生命周期,以及跟踪设备的状态和属性。
数据格式支持
V4L2 Core支持多种视频数据格式,允许用户和应用程序指定所需的视频分辨率、帧速率和像素格式。通过v4l2_format结构体,用户可以查询和设置设备的格式。
缓冲区管理
V4L2 Core负责处理视频数据的缓冲和传输。它提供了缓冲区的请求、分配、队列和解队列的管理功能。这些功能对于视频流的实时处理非常重要。
事件通知
V4L2 Core能够通过事件机制向用户空间应用程序通知各种事件(例如,缓冲区准备就绪、设备状态变化等),使得应用程序能够及时响应这些事件。
IOCTL接口
V4L2 Core实现了IOCTL(Input/Output Control)接口,允许用户空间应用程序通过特定命令与内核进行交互,控制视频设备的操作。这种机制使得设备控制灵活而强大。
支持扩展
V4L2 Core支持扩展和新功能的加入。它提供了良好的基础结构,使得开发者可以为新的设备或功能编写驱动,而无需重新实现整个框架。
28.2.2 启动步骤
内核初始化
当Linux内核启动时,V4L2 Core模块会被初始化。相关的初始化代码通常在内核的drivers/media/目录下。
设备驱动注册
每个V4L2设备驱动在其初始化函数中,会调用video_register_device()来注册其对应的视频设备。这是将设备与V4L2 Core框架连接的关键步骤。各种视频设备, 通常是在DTS中配置相关参数, Kernel启动时会加载DTS配置。
设备初始化
驱动程序会在注册设备后进行设备的初始化。这通常包括设置设备的默认参数、初始化硬件以及分配必要的资源(如内存、缓冲区等)。
创建文件节点
V4L2 Core会在/dev目录下创建设备文件节点(例如/dev/video0),允许用户空间程序通过标准文件操作接口访问视频设备。
IOCTL操作的实现
驱动程序需要实现IOCTL操作,以处理来自用户空间的控制命令。这些操作被定义在struct v4l2_ioctl_ops中。
- 事件注册
如果设备支持事件,驱动程序需要注册事件处理函数,以便在设备状态发生变化时通知用户空间。
28.2.3 V4L2 主设备
V4L2 主设备(或称为视频设备)是 Video4Linux2 (V4L2) 框架中用于处理视频流的核心组件。主设备通常代表一个完整的硬件视频捕获或播放设备,如摄像头、视频采集卡或视频输出设备。其主要作用是作为用户空间与内核空间之间的接口,通过该接口,应用程序可以与设备进行交互。
视频设备通过v4l2_device_register
向V4L2框架注册主设备, V4L2 主设备通常使用 struct video_device
数据结构来表示。这个结构体包含了与视频设备相关的所有信息和操作函数:
struct video_device {
struct device dev; // 设备结构体
const char *name; // 设备名称
int vfl_dir; // 设备类型,输入/输出
int device_caps; // 设备能力
int *fops; // 文件操作结构
struct video_device *next;
struct v4l2_capability cap;
// 其他成员
};
(1) struct device dev
- 这是设备的基本结构体,包含了设备的所有通用信息(如设备ID、父设备等)
(2) const char *name
(3) int vfl_dir
- 设备的方向,指示是输入设备还是输出设备。可以是
V4L2_BUF_TYPE_VIDEO_CAPTURE
或 V4L2_BUF_TYPE_VIDEO_OUTPUT
。
(4) int device_caps
- 设备的能力标志,用于指示设备支持的特性和功能,例如支持的格式、分辨率、帧率等。
(5) int *fops
- 指向与该设备相关的文件操作函数指针的指针,定义了设备支持的操作,例如打开、关闭、读、写、IOCTL 等。
(6) struct video_device *next
- 指向下一个设备的指针,通常用于在设备列表中遍历设备。
(7) struct v4l2_capability cap
- 存储设备能力的结构体,包含设备类型、驱动名称、版本等信息。
28.2.4 V4L2 子设备
在 Linux 的媒体子系统中,特别是在复杂的视频设备(如摄像头模块或多媒体系统)中,设备通常包含多个独立的硬件组件(例如传感器、解码器、编码器等)。这些硬件被称为子设备(subdevices)。每个子设备可以通过 v4l2_subdev 结构来表示,它们可以作为更大视频设备系统的一部分协同工作。
视频子设备使用 v4l2_subdev_init()
和 v4l2_device_register_subdev()
注册子设备, 为了管理这些子设备的操作,V4L2 提供了多个操作接口,v4l2_subdev_video_ops
是其中一个用于视频数据流控制的操作集。
struct v4l2_subdev_video_ops {
int (*s_stream)(struct v4l2_subdev *sd, int enable);
int (*g_frame_interval)(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *interval);
int (*s_frame_interval)(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *interval);
int (*s_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);
int (*g_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);
int (*cropcap)(struct v4l2_subdev *sd, struct v4l2_cropcap *cc);
int (*s_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);
int (*g_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);
int (*g_mbus_config)(struct v4l2_subdev *sd, struct v4l2_mbus_config *cfg);
};
(1) s_stream
:启动或停止视频流
原型:int (*s_stream)(struct v4l2_subdev *sd, int enable);
作用:这个函数用于控制子设备的视频流的开启或关闭。enable 参数为1表示启动流,为0表示停止流。
典型用途:当应用程序需要开始捕获视频时,驱动会调用子设备的 s_stream 函数来启动数据流。
(2) g_frame_interval
:获取当前帧间隔
原型:int (*g_frame_interval)(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *interval);
作用:获取子设备的当前帧间隔(即帧率)。
典型用途:在一些摄像头传感器中,帧间隔会影响视频流的帧率,可以通过该操作函数来获取当前的帧间隔值
(3) s_frame_interval
:设置帧间隔
原型:int (*s_frame_interval)(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *interval);
作用:设置子设备的帧间隔,控制视频流的帧率。
典型用途:应用程序需要调整视频的帧率时,驱动会通过该操作函数与子设备交互,调整帧率
(4) s_parm
和 g_parm
:设置和获取流参数
s_parm
原型:int (*s_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);
g_parm
原型:int (*g_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);
作用:设置和获取流的参数,如视频的比特率、分辨率、帧率等。
典型用途:用于设置流传输的各类属性,在视频编码器或解码器子设备中比较常见
(5) cropcap
:查询设备支持的裁剪能力
原型:int (*cropcap)(struct v4l2_subdev *sd, struct v4l2_cropcap *cc);
作用:返回设备支持的裁剪能力,如最大裁剪窗口、最小裁剪窗口等。
典型用途:在视频裁剪功能实现时,会首先调用该函数获取子设备的裁剪能力,然后再进行具体的裁剪操作。
(6) s_crop
和 g_crop
:设置和获取裁剪窗口
s_crop
原型:int (*s_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);
g_crop
原型:int (*g_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);
作用:设置和获取当前视频流的裁剪窗口。
典型用途:用户需要在视频流中裁剪某一部分画面时,这两个函数用来调整和获取裁剪窗口的位置和大小。
(7) g_mbus_config
:获取媒体总线配置
原型:int (*g_mbus_config)(struct v4l2_subdev *sd, struct v4l2_mbus_config *cfg);
作用:获取设备当前的媒体总线配置,比如数据的传输格式、时钟极性等。
典型用途:在硬件层面控制摄像头传感器与处理器之间的通信时,通过该函数来获取总线配置的详细信息。
28.3 APP读取摄像头数据
当应用层APP打开视频设备抓图时, 整个V4L2是一个什么样的步骤呢? 如下:
+---------------------------+
| 用户空间 |
| |
| +--------------------+ |
| | V4L2 应用程序 | |
| +--------------------+ |
| | |
| | ioctl() |
| | |
+-----------v---------------+
|
|
v
+---------------------------+
| 内核空间 |
| |
| +--------------------+ |
| | V4L2 驱动层 | |
| | | |
| | 1. open() | |
| | 2. querycap() | |
| | 3. set_format() | |
| | 4. reqbufs() | |
| | 5. mmap() | |
| | 6. streamon() | |
| | 7. dqbuf() | |
| | 8. streamoff() | |
| | 9. reqbufs() | |
| | 10. close() | |
| +--------------------+ |
| | |
| v |
| +--------------------+ |
| | 硬件设备 | |
| +--------------------+ |
+---------------------------+
1.) 设备打开
2.) 查询设备能力
3.) 设置视频格式
4.) 请求缓冲区
5.) 映射缓冲区
6.) 开始视频流
7.) 捕获图像
8.) 处理图像数据
9.) 停止视频流
10.) 释放缓冲区
- 用户通过 ioctl() 调用 VIDIOC_REQBUFS 请求释放之前分配的缓冲区。
11.) 关闭设备
- 用户调用 close() 关闭设备节点,内核会调用设备的关闭操作,清理相关资源。
28.4 总结
V4L2 框架是 Linux 系统中用于视频捕捉、输出和处理的核心框架。它为用户空间应用程序提供标准化的 API,并通过内核驱动与底层硬件进行交互。V4L2 框架支持多种设备类型,包括视频捕获设备、视频输出设备、以及内存到内存设备,具有丰富的功能和灵活的控制接口。