V4L2 架构文档

V4L2 架构文档 (Linux Kernel 5.0)

1. 概述

V4L2 (Video for Linux 2) 是 Linux 内核的视频设备驱动框架,提供统一的 API 用于视频采集、视频输出、收音机、软件定义无线电 (SDR) 和触摸传感器等设备。它是整个 Linux 媒体子系统的基础组件,位于 drivers/media/v4l2-core/ 目录下。

V4L2 与 Media Controller FrameworkVideobuf2 紧密集成,共同构成了 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_entitymedia_pipeline
  • v4l2_subdev 内嵌 media_entity
  • v4l2_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 架构的核心设计原则是分层抽象:

  1. v4l2_device --- 顶层设备容器, 管理子设备和 media 设备
  2. video_device --- 用户空间可见的设备节点, 处理文件操作和 ioctl
  3. v4l2_subdev --- 子设备抽象, 封装传感器/编码器等 I2C/SPI 设备
  4. v4l2_fh --- 每文件句柄的状态和事件订阅
  5. vb2_queue/vb2_buffer --- 视频缓冲区管理和流控制
  6. v4l2_ctrl_handler/v4l2_ctrl --- 设备参数控制 (亮度、曝光等)
  7. media_device/media_entity/media_pad/media_link --- 硬件拓扑描述

这种分层结构使得驱动开发者可以选择性地使用框架的各层功能, 从简单的单设备驱动到复杂的多实体流水线都能优雅地支持。

相关推荐
沉迷学习 日益消瘦2 小时前
20-综合实战:微服务部署
微服务·云原生·架构·kubernetes
奈斯先生Vector11 小时前
告别工具碎片化:基于 Nano Banana 全模态 AI 聚合架构搭建“文本-图像-视频”自动化协同生产线
运维·数据库·人工智能·架构·自动化·aigc·音视频
小码哥哥17 小时前
如何评价“构建企业级 AI 知识库“这一趋势?从技术架构到落地实践的完整分析
人工智能·架构
Vince的修炼之路20 小时前
RAG 文档处理技术深度分析:PDF 解析、表格提取、OCR 识别全链路
人工智能·架构
童谣11 天前
越华环保:美丽蓝天项目申报方案数字化研判架构设计与实践
架构
Dr.kangder1 天前
嵌入式软件程序分析技术:原理、方法与实战
servlet·架构·嵌入式·dsp开发
Vince的修炼之路1 天前
大模型 Skills 技术深度分析
人工智能·架构
Dr.kangder1 天前
嵌入式软件单元测试:从理论到实践
架构·单元测试·嵌入式·测试覆盖率
小马9261 天前
GPT-6 Swarm 架构深度解析:从单体模型到 Agent 集群协同的范式跃迁
gpt·架构·大模型·openai·agent·分布式ai·swarm架构