V4L2 架构文档 (Linux Kernel 5.0)
1. 概述
V4L2 (Video for Linux 2) 是 Linux 内核的视频设备驱动框架,提供统一的 API 用于视频采集、视频输出、收音机、软件定义无线电 (SDR) 和触摸传感器等设备。它是整个 Linux 媒体子系统的基础组件,位于 drivers/media/v4l2-core/ 目录下。
V4L2 与 Media Controller Framework 和 Videobuf2 紧密集成,共同构成了 Linux 内核的完整媒体处理流水线。
┌─────────────────────────────────────────────────────────────┐
│ Userspace (应用程序) │
├─────────────────────────────────────────────────────────────┤
│ /dev/videoX /dev/vbiX /dev/radioX /dev/mediaX │
│ │ │ │ │ │
├─────────┼──────────────┼────────────┼─────────────┼──────────┤
│ │ V4L2 Core (v4l2-core/) │ │
│ │ ┌────────┬────────┬─────────┬────────┐ │ │
│ │ │v4l2-dev│v4l2- │v4l2- │v4l2- │ │ │
│ │ │ │subdev │ctrls │ioctl │ │ │
│ └───┤ │ │ │ │ │ │
│ │ video_device │v4l2_device (subdevs) │ │
│ └────────┬────────┴─────────┴────────┘ │ │
│ │ │ │
│ ┌──────────────────┤ │ │
│ │ Media Controller│ │ │
│ │ (media-device) │ │ │
│ │ ┌───────────────┤ │ │
│ │ │ entities/pads │ │ │
│ │ │ /links/pipelines │ │
│ │ └───────────────┘ │ │
│ └──────────────────┘ │ │
│ │ │ │
│ ┌──────────────────┴──────────────────┐ │ │
│ │ Videobuf2 (videobuf2-core) │ │ │
│ │ ┌─────────┬──────────┬──────────┐ │ │ │
│ │ │ MMAP │ USERPTR │ DMABUF │ │ │ │
│ │ └─────────┴──────────┴──────────┘ │ │ │
│ └────────────────────────────────────┘ │ │
├─────────────────────────────────────────────────────┴─────────┤
│ Hardware Drivers (硬件驱动) │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ │
│ │ USB │ │ PCI/PCIe │ │ Platform │ │ I2C/SPI │ │
│ │ Cameras │ │ Capture │ │ (SoC ISP) │ │ Sensors │ │
│ └─────────┘ └──────────┘ └───────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
2. 核心数据结构层次
V4L2 采用分层架构,从顶层到底层依次为:
2.1 顶层调度层: v4l2_device
v4l2_device 是整个 V4L2 设备的顶层容器,充当所有子设备和视频设备节点的聚合点。
c
struct v4l2_device {
struct device *dev; // 指向物理设备 (PCI/USB/Platform)
struct media_device *mdev; // 关联的 Media Controller 设备
struct list_head subdevs; // 已注册的子设备链表
spinlock_t lock; // 设备级自旋锁
char name[V4L2_DEVICE_NAME_SIZE]; // 唯一设备名称
void (*notify)(...); // 子设备通知回调
struct v4l2_ctrl_handler *ctrl_handler; // 设备级控制处理器
struct v4l2_prio_state prio; // 设备优先级状态
struct kref ref; // 引用计数
void (*release)(...); // 释放回调
};
作用:
- 管理所有子设备 (subdevs) 的注册/注销
- 提供遍历子设备并调用其操作的辅助宏 (
v4l2_device_call_all,v4l2_device_for_each_subdev) - 作为 media_device 和子设备之间的桥梁
2.2 设备节点层: video_device
video_device 代表一个用户空间可见的设备节点 (/dev/videoX),负责处理文件操作和 ioctl 调用。
c
struct video_device {
struct media_entity entity; // Media Controller 实体
struct media_intf_devnode *intf_devnode; // Media 设备节点接口
struct media_pipeline pipe; // Media 流水线
const struct v4l2_file_operations *fops; // 文件操作集 (open/release/read/write/mmap/poll/ioctl)
u32 device_caps; // 设备能力标志
/* sysfs */
struct device dev; // 内嵌的 struct device
struct cdev *cdev; // 字符设备
struct v4l2_device *v4l2_dev; // 指向父 v4l2_device
struct device *dev_parent; // 父设备指针
struct v4l2_ctrl_handler *ctrl_handler; // 设备节点级控制处理器
struct vb2_queue *queue; // 关联的 videobuf2 队列
struct v4l2_prio_state *prio; // 优先级状态
char name[32]; // 设备名称
enum vfl_devnode_type vfl_type; // 设备类型
enum vfl_devnode_direction vfl_dir; // 数据方向 (RX/TX/M2M)
int minor; // 次设备号
u16 num; // 设备节点编号
unsigned long flags; // 标志位 (V4L2_FL_REGISTERED 等)
int index; // 同物理设备上多个索引的区分
/* V4L2 文件句柄 */
spinlock_t fh_lock;
struct list_head fh_list; // 打开的文件句柄链表
v4l2_std_id tvnorms; // 支持的电视制式
/* 回调函数 */
void (*release)(struct video_device *vdev);
const struct v4l2_ioctl_ops *ioctl_ops; // IOCTL 操作集
DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);
struct mutex *lock; // 序列化锁
};
设备节点类型 (vfl_devnode_type):
| 类型 | 设备节点 | 说明 |
|---|---|---|
VFL_TYPE_GRABBER |
/dev/videoX |
视频采集/输出设备 |
VFL_TYPE_VBI |
/dev/vbiX |
垂直消隐期数据 (字幕/图文) |
VFL_TYPE_RADIO |
/dev/radioX |
收音机调谐器 |
VFL_TYPE_SUBDEV |
/dev/v4l-subdevX |
V4L2 子设备 |
VFL_TYPE_SDR |
/dev/swradioX |
软件定义无线电 |
VFL_TYPE_TOUCH |
/dev/v4l-touchX |
触摸传感器 |
数据方向 (vfl_devnode_direction):
| 方向 | 说明 |
|---|---|
VFL_DIR_RX |
接收端 (采集设备) |
VFL_DIR_TX |
发送端 (输出设备) |
VFL_DIR_M2M |
内存到内存设备 (编解码器/转换器) |
2.3 子设备层: v4l2_subdev
v4l2_subdev 代表连接到主桥接芯片的子设备 (如传感器、编码器、解码器、调谐器等)。通常通过 I2C 或 SPI 总线控制。
c
struct v4l2_subdev {
struct media_entity entity; // Media Controller 实体
struct list_head list; // 设备链表
struct module *owner;
bool owner_v4l2_dev;
u32 flags; // V4L2_SUBDEV_FL_IS_I2C/SPI/HAS_DEVNODE/HAS_EVENTS
struct v4l2_device *v4l2_dev; // 指向父 v4l2_device
const struct v4l2_subdev_ops *ops; // 子设备操作集
const struct v4l2_subdev_internal_ops *internal_ops; // 内部操作 (open/close/registered/unregistered)
struct v4l2_ctrl_handler *ctrl_handler; // 子设备控制处理器
char name[V4L2_SUBDEV_NAME_SIZE]; // 子设备名称
u32 grp_id; // 组 ID (驱动自定义)
void *dev_priv; // 驱动私有数据
void *host_priv; // 主机驱动私有数据
struct video_device *devnode; // 子设备自身的设备节点
struct device *dev; // 物理设备指针
struct fwnode_handle *fwnode; // 固件节点
struct v4l2_async_notifier *notifier; // 异步通知器
struct v4l2_async_notifier *subdev_notifier;
struct v4l2_subdev_platform_data *pdata;
};
子设备操作集 (v4l2_subdev_ops):
c
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core; // 核心操作 (初始化/复位/电源/日志/中断/事件)
const struct v4l2_subdev_tuner_ops *tuner; // 调谐器操作 (频率/制式)
const struct v4l2_subdev_audio_ops *audio; // 音频操作 (路由/时钟/流)
const struct v4l2_subdev_video_ops *video; // 视频操作 (制式/流/帧间隔/时序)
const struct v4l2_subdev_vbi_ops *vbi; // VBI 操作 (图文/字幕)
const struct v4l2_subdev_ir_ops *ir; // 红外操作
const struct v4l2_subdev_sensor_ops *sensor; // 传感器操作 (跳行/跳帧)
const struct v4l2_subdev_pad_ops *pad; // Pad 级操作 (格式/选择/EDID/链路验证)
};
2.4 文件句柄层: v4l2_fh
v4l2_fh 代表一个用户空间打开的文件句柄,维护每个打开实例的状态。
c
struct v4l2_fh {
struct list_head list; // 文件句柄链表节点
struct video_device *vdev; // 关联的 video_device
struct v4l2_ctrl_handler *ctrl_handler; // 文件句柄级控制处理器
enum v4l2_priority prio; // 优先级
wait_queue_head_t wait; // 等待队列
struct list_head subscribed; // 已订阅的事件列表
struct list_head available; // 可用事件列表
unsigned int navailable; // 可用事件计数
u32 sequence; // 事件序列号
};
3. IOCTL 处理框架
V4L2 的 ioctl 处理是其核心机制,通过 v4l2_ioctl_ops 定义所有标准 ioctl 的回调函数。
3.1 ioctl 操作集
c
struct v4l2_ioctl_ops {
/* VIDIOC_QUERYCAP */
int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);
/* 缓冲区操作 */
int (*vidioc_reqbufs)(struct file *file, void *fh, struct v4l2_requestbuffers *rb);
int (*vidioc_querybuf)(struct file *file, void *fh, struct v4l2_buffer *buf);
int (*vidioc_qbuf)(struct file *file, void *fh, struct v4l2_buffer *buf);
int (*vidioc_dqbuf)(struct file *file, void *fh, struct v4l2_buffer *buf);
int (*vidioc_prepare_buf)(struct file *file, void *fh, struct v4l2_buffer *buf);
int (*vidioc_create_bufs)(struct file *file, void *fh, struct v4l2_create_buffers *create);
int (*vidioc_expbuf)(struct file *file, void *fh, struct v4l2_exportbuffer *eb);
/* 流控制 */
int (*vidioc_streamon)(struct file *file, void *fh, enum v4l2_buf_type i);
int (*vidioc_streamoff)(struct file *file, void *fh, enum v4l2_buf_type i);
/* 格式操作 */
int (*vidioc_g_fmt_*)(...); // 获取格式 (video/vbi/sliced-vbi 等)
int (*vidioc_s_fmt_*)(...); // 设置格式
int (*vidioc_try_fmt_*)(...); // 尝试格式
int (*vidioc_enum_fmt_*)(...); // 枚举格式
/* 输入/输出 */
int (*vidioc_g_input)(...);
int (*vidioc_s_input)(...);
int (*vidioc_enum_input)(...);
int (*vidioc_g_output)(...);
int (*vidioc_s_output)(...);
int (*vidioc_enum_output)(...);
/* 标准/制式 */
int (*vidioc_g_std)(...);
int (*vidioc_s_std)(...);
int (*vidioc_querystd)(...);
int (*vidioc_enumstd)(...);
/* 控件 */
int (*vidioc_g_ctrl)(...);
int (*vidioc_s_ctrl)(...);
int (*vidioc_queryctrl)(...);
int (*vidioc_g_ext_ctrls)(...);
int (*vidioc_s_ext_ctrls)(...);
int (*vidioc_try_ext_ctrls)(...);
int (*vidioc_querymenu)(...);
/* 事件 */
int (*vidioc_subscribe_event)(...);
int (*vidioc_unsubscribe_event)(...);
int (*vidioc_dqevent)(...);
/* 裁剪/选择 */
int (*vidioc_g_crop)(...);
int (*vidioc_s_crop)(...);
int (*vidioc_g_selection)(...);
int (*vidioc_s_selection)(...);
/* 其它 */
int (*vidioc_g_parm)(...); // 流参数
int (*vidioc_s_parm)(...);
int (*vidioc_g_priority)(...);
int (*vidioc_s_priority)(...);
int (*vidioc_log_status)(...);
...
};
3.2 ioctl 分发流程
用户空间 ioctl()
│
▼
v4l2_fops.unlocked_ioctl (或 compat_ioctl32)
│
▼
__video_do_ioctl()
│
├── 查找 ioctl 命令对应的回调函数
│
├── 检查 valid_ioctls 位图 (是否允许该 ioctl)
│
├── 获取文件句柄 v4l2_fh (打开设备时创建)
│
├── 获取设备锁 vdev->lock (序列化访问)
│
├── 调用 vdev->ioctl_ops 中对应的回调
│
├── 如果驱动未实现, 调用 v4l2_device_call_all()
│ 遍历所有子设备, 尝试子设备的 ioctl 操作
│
└── 返回结果给用户空间
4. 视频缓冲管理: Videobuf2 (VB2)
Videobuf2 是 V4L2 的视频缓冲区管理框架,位于 drivers/media/common/videobuf2/,头文件在 include/media/videobuf2-core.h。
4.1 内存模型
VB2 支持四种内存模型:
c
enum vb2_memory {
VB2_MEMORY_UNKNOWN = 0, // 未知
VB2_MEMORY_MMAP = 1, // 内核分配, 用户空间 mmap
VB2_MEMORY_USERPTR = 2, // 用户空间分配, 传入指针
VB2_MEMORY_DMABUF = 4, // DMA-buf 共享
};
4.2 核心数据结构
vb2_queue --- 缓冲区队列:
c
struct vb2_queue {
unsigned int type; // 缓冲区类型 (V4L2_BUF_TYPE_*)
unsigned int io_modes; // 支持的 IO 模式 (VB2_MMAP/USERPTR/READ/WRITE/DMABUF)
struct device *dev; // 分配设备
unsigned long dma_attrs; // DMA 属性
unsigned bidirectional:1; // 双向 DMA
unsigned supports_requests:1; // 支持 Request API
unsigned uses_qbuf:1;
unsigned uses_requests:1;
struct mutex *lock; // 队列锁
void *owner; // 拥有者文件句柄
const struct vb2_ops *ops; // 驱动回调
const struct vb2_mem_ops *mem_ops; // 内存操作
const struct vb2_buf_ops *buf_ops; // 缓冲区操作
void *drv_priv; // 驱动私有数据
unsigned int buf_struct_size; // 驱动自定义缓冲区结构大小
u32 timestamp_flags; // 时间戳标志
gfp_t gfp_flags; // 内存分配标志
u32 min_buffers_needed; // 启动流所需最小缓冲区数
/* 内部状态 (私有) */
struct mutex mmap_lock;
unsigned int memory;
enum dma_data_direction dma_dir;
struct vb2_buffer *bufs[VB2_MAX_FRAME]; // 缓冲区数组 (最多 32)
unsigned int num_buffers;
struct list_head queued_list; // 用户空间已入队的缓冲区
unsigned int queued_count;
atomic_t owned_by_drv_count; // 驱动持有的缓冲区计数
struct list_head done_list; // 已完成待出队的缓冲区
spinlock_t done_lock;
wait_queue_head_t done_wq; // 等待缓冲区完成的等待队列
unsigned int streaming:1; // 流状态
unsigned int start_streaming_called:1;
unsigned int error:1; // 致命错误标志
};
vb2_buffer --- 单个视频缓冲区:
c
struct vb2_buffer {
struct vb2_queue *vb2_queue;
unsigned int index; // 缓冲区索引
unsigned int type;
unsigned int memory;
unsigned int num_planes; // 平面数 (单平面或多平面格式)
u64 timestamp; // 帧时间戳 (ns)
enum vb2_buffer_state state; // 缓冲区状态
/*
* VB2_BUF_STATE_DEQUEUED --- 在用户空间控制下
* VB2_BUF_STATE_IN_REQUEST --- 在 media request 中排队
* VB2_BUF_STATE_PREPARING --- 正在准备
* VB2_BUF_STATE_QUEUED --- 在 VB2 中排队, 未给驱动
* VB2_BUF_STATE_REQUEUEING --- 重新排队到驱动
* VB2_BUF_STATE_ACTIVE --- 驱动正在处理
* VB2_BUF_STATE_DONE --- 驱动处理完成, 等待用户空间取出
* VB2_BUF_STATE_ERROR --- 处理失败
*/
struct vb2_plane planes[VB2_MAX_PLANES]; // 平面信息 (最多 8 平面)
struct list_head queued_entry;
struct list_head done_entry;
};
4.3 驱动回调 (vb2_ops)
c
struct vb2_ops {
// 队列设置: 确定缓冲区数量和平面大小
int (*queue_setup)(struct vb2_queue *q,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], struct device *alloc_devs[]);
// 锁管理 (避免死锁)
void (*wait_prepare)(struct vb2_queue *q);
void (*wait_finish)(struct vb2_queue *q);
// 缓冲区生命周期
int (*buf_init)(struct vb2_buffer *vb); // 分配后初始化
int (*buf_prepare)(struct vb2_buffer *vb); // 每次入队前准备
void (*buf_finish)(struct vb2_buffer *vb); // 每次出队后处理
void (*buf_cleanup)(struct vb2_buffer *vb); // 释放前清理
// 流控制
int (*start_streaming)(struct vb2_queue *q, unsigned int count);
void (*stop_streaming)(struct vb2_queue *q);
// 缓冲区排队到驱动
void (*buf_queue)(struct vb2_buffer *vb);
// Request API 相关
void (*buf_request_complete)(struct vb2_buffer *vb);
};
4.4 缓冲区生命周期
用户空间 VB2 Core 驱动
───────── ───────── ────
REQBUFS ──────► queue_setup() ────────► 确定缓冲区数量和大小
分配内存 (MMAP 模式)
QBUF ─────────► buf_prepare() ─────────► 准备缓冲区 (缓存同步等)
状态 → QUEUED
STREAMON ──────► start_streaming() ─────► 启动硬件/DMA
所有预排队缓冲区: buf_queue() ──► 给驱动
驱动处理完成:
vb2_buffer_done(vb, DONE) ◄─── 驱动调用
状态 → DONE
DQBUF ◄──────── 返回完成的缓冲区
状态 → DEQUEUED
用户空间处理数据...
QBUF ─────────► buf_prepare()
buf_queue() ─────────────► 给驱动继续处理
...
STREAMOFF ─────► stop_streaming() ──────► 停止硬件
buf_finish() --- 所有未出队缓冲区
4.5 内存分配器 (mem_ops)
VB2 提供三种内置内存分配器:
| 分配器 | 文件 | 说明 |
|---|---|---|
vb2-dma-contig |
videobuf2-dma-contig.c |
连续 DMA 内存 (最常用) |
vb2-dma-sg |
videobuf2-dma-sg.c |
分散/聚集 DMA 内存 |
vb2-vmalloc |
videobuf2-vmalloc.c |
虚拟内存 (无 DMA 需求时) |
5. 控制框架 (Control Framework)
V4L2 控制框架 (v4l2-ctrls.c) 提供了一套完整的管理摄像头/视频设备参数 (亮度、对比度、曝光等) 的机制。
5.1 核心结构
c
struct v4l2_ctrl_handler {
struct mutex _lock;
struct mutex *lock; // 控制处理器锁
struct list_head ctrls; // 拥有的控制列表
struct list_head ctrl_refs; // 控制引用列表 (包括继承的)
struct v4l2_ctrl_ref *cached; // 最近查找的缓存
struct v4l2_ctrl_ref **buckets; // 哈希桶 (快速查找)
v4l2_ctrl_notify_fnc notify; // 值变化通知回调
void *notify_priv;
u16 nr_of_buckets;
int error; // 第一个失败的控制添加错误码
};
struct v4l2_ctrl {
struct list_head node;
struct v4l2_ctrl_handler *handler;
struct v4l2_ctrl **cluster; // 控制集群
unsigned int ncontrols; // 集群中控制数量
unsigned int is_new:1; // 用户设置了新值
unsigned int is_private:1; // 私有控制 (不被继承)
unsigned int is_auto:1; // 自动/手动模式控制
const struct v4l2_ctrl_ops *ops; // 控制操作
u32 id; // 控制 ID (V4L2_CID_*)
const char *name; // 控制名称
enum v4l2_ctrl_type type; // 类型 (INTEGER/BOOLEAN/MENU/BUTTON/STRING/...)
s64 minimum, maximum, default_value; // 范围
u64 step; // 步长
u64 menu_skip_mask; // 菜单跳过掩码
const char * const *qmenu; // 菜单项字符串数组
unsigned long flags; // 控制标志
union v4l2_ctrl_ptr p_new; // 新值 (待应用)
union v4l2_ctrl_ptr p_cur; // 当前值
};
5.2 控制集群
支持将多个控制组合成一个集群, 用于自动/手动模式场景:
c
// 示例: 自动增益 + 增益 集群
v4l2_ctrl_auto_cluster(2, &controls, 0, true);
// controls[0] = 自动增益 (0=自动, 1=手动, manual_mode_value=0)
// controls[1] = 增益 (自动模式下变 volatile, 只读)
5.3 控制继承
控制处理器可以形成继承链:
v4l2_device.ctrl_handler (顶层)
│
├── video_device.ctrl_handler (设备节点层)
│ │
│ ├── v4l2_fh.ctrl_handler (文件句柄层, 可选)
│
└── v4l2_subdev.ctrl_handler (子设备层)
通过 v4l2_ctrl_add_handler() 可以将一个 handler 的控制合并到另一个 handler。
6. Media Controller Framework
Media Controller 是 Linux 媒体子系统的拓扑管理框架。在启用 CONFIG_MEDIA_CONTROLLER 时, V4L2 的设备被表示为媒体图中的实体 (entity), 通过 pad 和 link 连接成流水线 (pipeline)。
6.1 核心概念
┌──────────────────────────────────────────────────────────┐
│ media_device │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Entity A │ │ Entity B │ │ Entity C │ │
│ │ (Sensor) │ │ (ISP) │ │ (DMA Cap) │ │
│ │ │ │ │ │ │ │
│ │ [Pad 0]───┼────┼─[Pad 1] │ │ │ │
│ │ │ │ [Pad 2]───┼────┼─[Pad 3] │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ┌────┴──────────────────┴──────┐ │
│ │ Device Node Interfaces │ │
│ │ /dev/video0 /dev/v4l- │ │
│ │ subdev0 │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
6.2 实体类型
c
enum media_entity_type {
MEDIA_ENTITY_TYPE_BASE, // 基础实体
MEDIA_ENTITY_TYPE_VIDEO_DEVICE, // video_device 的实体
MEDIA_ENTITY_TYPE_V4L2_SUBDEV, // v4l2_subdev 的实体
};
6.3 V4L2 与 Media Controller 的集成
当 CONFIG_MEDIA_CONTROLLER 启用时:
video_device内嵌media_entity和media_pipelinev4l2_subdev内嵌media_entityv4l2_device包含指向media_device的指针
Pad 操作 是子设备与 Media Controller 交互的核心:
c
struct v4l2_subdev_pad_ops {
int (*init_cfg)(...); // 初始化 pad 配置
int (*enum_mbus_code)(...); // 枚举媒体总线像素编码
int (*enum_frame_size)(...); // 枚举帧尺寸
int (*enum_frame_interval)(...); // 枚举帧间隔
int (*get_fmt)(...); // 获取 pad 格式
int (*set_fmt)(...); // 设置 pad 格式
int (*get_selection)(...); // 获取选择区域 (crop/compose)
int (*set_selection)(...); // 设置选择区域
int (*link_validate)(...); // 验证链路是否可用
};
6.4 流水线管理
c
// 启动流水线
media_pipeline_start(&vdev->entity, &vdev->pipe);
// 遍历整个图, 标记所有连通实体的 stream_count++
// 停止流水线
media_pipeline_stop(&vdev->entity);
7. 内存到内存框架 (M2M)
v4l2-mem2mem 框架专门用于内存到内存设备 (硬件编解码器、缩放器、颜色空间转换器等)。这些设备不从硬件采集也不输出到硬件, 而是在内存缓冲区之间传输和转换数据。
7.1 核心结构
c
struct v4l2_m2m_dev {
struct v4l2_m2m_ctx *curr_ctx; // 当前运行的上下文
struct list_head job_queue; // 待处理作业队列
spinlock_t job_spinlock;
struct workqueue_struct *job_workqueue;
};
struct v4l2_m2m_ctx {
struct mutex *q_lock; // 队列锁
struct v4l2_m2m_dev *m2m_dev;
struct v4l2_m2m_queue_ctx cap_q_ctx; // 捕获队列 (输出到内存)
struct v4l2_m2m_queue_ctx out_q_ctx; // 输出队列 (从内存输入)
struct list_head queue;
unsigned long job_flags;
wait_queue_head_t finished; // 等待作业完成
void *priv; // 驱动私有数据
};
struct v4l2_m2m_queue_ctx {
struct vb2_queue q; // VB2 队列
struct list_head rdy_queue; // 就绪缓冲区列表
spinlock_t rdy_spinlock;
u8 num_rdy; // 就绪缓冲区数
bool buffered; // 是否缓冲模式
};
7.2 M2M 驱动回调
c
struct v4l2_m2m_ops {
void (*device_run)(void *priv); // 开始处理作业
int (*job_ready)(void *priv); // 检查作业是否准备就绪
void (*job_abort)(void *priv); // 中止当前作业
};
7.3 M2M 调度流程
QBUF (输出队列) ──► rdy_queue
QBUF (捕获队列) ──► rdy_queue
│
▼
v4l2_m2m_try_schedule()
│
├── 检查: 输出队列有就绪缓冲区? ✓
├── 检查: 捕获队列有就绪缓冲区? ✓
├── 检查: job_ready() 返回 1? ✓
│
▼
device_run() ──► 驱动开始 DMA/硬件处理
│
▼
处理完成 ──► v4l2_m2m_job_finish()
│
├── 将已处理的输出缓冲区: vb2_buffer_done(DONE)
├── 将填充的捕获缓冲区: vb2_buffer_done(DONE)
│
▼
v4l2_m2m_try_schedule() --- 检查是否有下一个作业
8. 事件系统
V4L2 支持异步事件通知, 使应用程序可以订阅并等待特定事件。
c
// 事件类型
#define V4L2_EVENT_ALL 0
#define V4L2_EVENT_VSYNC 1 // 垂直同步
#define V4L2_EVENT_EOS 2 // 流结束
#define V4L2_EVENT_CTRL 3 // 控制值变化
#define V4L2_EVENT_FRAME_SYNC 4 // 帧同步
#define V4L2_EVENT_SOURCE_CHANGE 5 // 源变化 (分辨率等)
#define V4L2_EVENT_MOTION_DET 6 // 运动检测
#define V4L2_EVENT_PRIVATE_START 0x08000000
事件订阅流程
用户空间: VIDIOC_SUBSCRIBE_EVENT
│
▼
v4l2_ctrl_subscribe_event() / v4l2_event_subscribe()
│
├── 分配 v4l2_subscribed_event 结构
├── 添加到 v4l2_fh.subscribed 列表
│
▼
事件发生时:
v4l2_event_queue(vdev, &event)
│
├── 遍历 v4l2_fh.subscribed 列表
├── 对每个匹配的订阅: v4l2_event_queue_fh(fh, &event)
│
用户空间: VIDIOC_DQEVENT (或 poll/select 等待)
9. V4L2 用户空间 API 概览
9.1 典型采集流程
1. 打开设备
fd = open("/dev/video0", O_RDWR)
2. 查询设备能力
ioctl(fd, VIDIOC_QUERYCAP, &cap)
3. 设置格式
ioctl(fd, VIDIOC_S_FMT, &fmt)
4. 请求缓冲区
ioctl(fd, VIDIOC_REQBUFS, &reqbufs)
5. 查询每个缓冲区并 mmap
for each buffer:
ioctl(fd, VIDIOC_QUERYBUF, &buf)
mmap(..., buf.m.offset)
6. 将所有缓冲区入队
for each buffer:
ioctl(fd, VIDIOC_QBUF, &buf)
7. 启动流
ioctl(fd, VIDIOC_STREAMON, &type)
8. 采集循环
while (capturing):
ioctl(fd, VIDIOC_DQBUF, &buf) // 等待一帧完成
处理 buf 中的数据
ioctl(fd, VIDIOC_QBUF, &buf) // 重新入队
9. 停止流
ioctl(fd, VIDIOC_STREAMOFF, &type)
10. 关闭
close(fd)
9.2 与 Media Controller 结合使用
1. 打开 media 设备
media_fd = open("/dev/media0", O_RDWR)
2. 枚举拓扑
ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology)
3. 建立链路
ioctl(media_fd, MEDIA_IOC_SETUP_LINK, &link)
4. 然后按正常 V4L2 流程操作 video 设备节点
10. 驱动开发要点
10.1 驱动注册流程
c
// 1. 分配并初始化 v4l2_device
struct v4l2_device v4l2_dev;
v4l2_device_register(dev, &v4l2_dev);
// 2. 初始化 Media Device (如果启用)
media_device_init(&mdev);
media_device_register(&mdev);
v4l2_dev.mdev = &mdev;
// 3. 注册子设备 (sensor, decoder 等)
v4l2_i2c_subdev_init(&sd, client, &subdev_ops);
v4l2_device_register_subdev(&v4l2_dev, &sd);
// 4. 分配并初始化 video_device
struct video_device *vdev = video_device_alloc();
vdev->fops = &v4l2_fops;
vdev->ioctl_ops = &ioctl_ops;
vdev->v4l2_dev = &v4l2_dev;
vdev->queue = q; // vb2_queue
// 5. 初始化 videobuf2 队列
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
q->ops = &vb2_ops;
q->mem_ops = &vb2_dma_contig_memops;
q->buf_struct_size = sizeof(struct my_buffer);
q->drv_priv = my_dev;
vb2_queue_init(q);
// 6. 注册 video_device
video_register_device(vdev, VFL_TYPE_GRABBER, -1);
10.2 关键源文件索引
| 文件 | 说明 |
|---|---|
drivers/media/v4l2-core/v4l2-dev.c |
video_device 注册/注销/文件操作 |
drivers/media/v4l2-core/v4l2-device.c |
v4l2_device 管理 |
drivers/media/v4l2-core/v4l2-subdev.c |
子设备框架 |
drivers/media/v4l2-core/v4l2-ioctl.c |
IOCTL 分发和标准实现 |
drivers/media/v4l2-core/v4l2-ctrls.c |
控制框架 |
drivers/media/v4l2-core/v4l2-fh.c |
文件句柄管理 |
drivers/media/v4l2-core/v4l2-event.c |
事件系统 |
drivers/media/v4l2-core/v4l2-mem2mem.c |
M2M 框架 |
drivers/media/v4l2-core/v4l2-mc.c |
V4L2-Media Controller 集成 |
drivers/media/v4l2-core/v4l2-async.c |
异步子设备注册 |
drivers/media/v4l2-core/v4l2-common.c |
通用辅助函数 |
drivers/media/v4l2-core/videobuf2-core.c |
VB2 核心 |
include/media/v4l2-device.h |
v4l2_device 头文件 |
include/media/v4l2-dev.h |
video_device 头文件 |
include/media/v4l2-subdev.h |
子设备头文件 |
include/media/videobuf2-core.h |
VB2 核心头文件 |
include/media/v4l2-ctrls.h |
控制框架头文件 |
include/media/v4l2-mem2mem.h |
M2M 框架头文件 |
include/media/media-device.h |
Media 设备头文件 |
include/media/media-entity.h |
Media 实体头文件 |
include/uapi/linux/videodev2.h |
用户空间 API 定义 |
11. 总结
V4L2 架构的核心设计原则是分层抽象:
v4l2_device--- 顶层设备容器, 管理子设备和 media 设备video_device--- 用户空间可见的设备节点, 处理文件操作和 ioctlv4l2_subdev--- 子设备抽象, 封装传感器/编码器等 I2C/SPI 设备v4l2_fh--- 每文件句柄的状态和事件订阅vb2_queue/vb2_buffer--- 视频缓冲区管理和流控制v4l2_ctrl_handler/v4l2_ctrl--- 设备参数控制 (亮度、曝光等)media_device/media_entity/media_pad/media_link--- 硬件拓扑描述
这种分层结构使得驱动开发者可以选择性地使用框架的各层功能, 从简单的单设备驱动到复杂的多实体流水线都能优雅地支持。