Overview

V4L2 memory-to-memory 设备可以在内存中 compress, decompress、transform 或以其他方式将视频数据从一种格式转换为另一种格式。此类 memory-to-memory 设备会设置 video device device_caps 的 V4L2_CAP_VIDEO_M2M 或 V4L2_CAP_VIDEO_M2M_MPLANE flag。内存到内存设备的示例包括 codecs、scalers、deinterlacers 或 format converters(例如,从 YUV 转换为 RGB)。

memory-to-memory video device node 的行为与普通 video device node 类似,但它同时支持 output(将 frame 从内存发送到硬件)和 capture(将处理后的 frame 从硬件接收到内存)stream I/O。

memory-to-memory 设备是一种共享资源,可以多次 open video device node,每个 application 通过他们的 file handler 设置属于自己的属性,并且每个 application 都可以独立地使用它。

driver 会 arbitrate 硬件的访问,在其他 file handler 获得访问权限时对硬件进行重新编程。这与 normal video device node 的行为不同,在 normal device node 中,属性是全局的。

normal v4l2 driver:

  %%{init: {'theme': 'neutral' }}%%
graph TD
        G1[应用程序1 open] --> H[共享设备实例]
        G2[应用程序2 open] --> H
        G3[应用程序3 open] --> H
        
        H --> I[共享格式配置]
        H --> J[共享缓冲区队列]
        H --> K[全局设备状态]
        
        L[传感器数据流] --> H
        H --> M[所有应用程序接收相同数据]

v4l2 m2m driver:

  %%{init: {'theme': 'neutral' }}%%
graph TD
        A1[应用程序1 open] --> B1[创建 ctx1]
        A2[应用程序2 open] --> B2[创建 ctx2]
        A3[应用程序3 open] --> B3[创建 ctx3]
        
        B1 --> C1[独立的输入队列1]
        B1 --> D1[独立的输出队列1]
        B1 --> E1[独立的格式配置1]
        
        B2 --> C2[独立的输入队列2]
        B2 --> D2[独立的输出队列2]
        B2 --> E2[独立的格式配置2]
        
        B3 --> C3[独立的输入队列3]
        B3 --> D3[独立的输出队列3]
        B3 --> E3[独立的格式配置3]
        
        C1 --> F[硬件处理器]
        C2 --> F
        C3 --> F
        F --> D1
        F --> D2
        F --> D3

数据结构

struct v4l2_m2m_ops {
	void (*device_run)(void*priv);
	int (*job_ready)(void*priv);
	void (*job_abort)(void*priv);
};

device_run: 必须要实现的回调,在这个回调里执行 job(transaction). job 可能在 callback return 之后仍然没有完成,需要在 job 完成后调用 v4l2_m2m_job_finish() 或者 v4l2_m2m_buf_done_and_job_finish()

job_ready: optional, 检查驱动是否已经准备好执行一个完整的 m2m job, 未准备好返回 0,准备好返回 1. 这个函数不可以休眠。

job_abort: optional, 停止当前的 m2m job, 需要调用 v4l2_m2m_job_finish() 或者 v4l2_m2m_buf_done_and_job_finish(). 不用阻塞等待清理工作完成便可返回。


通过 v4l2_m2m_init() 函数初始化注册一个 struct v4l2_m2m_dev 结构体:

struct v4l2_m2m_dev {
	struct v4l2_m2m_ctx	*curr_ctx;
#ifdef CONFIG_MEDIA_CONTROLLER
	struct media_entity	*source;
	struct media_pad	source_pad;
	struct media_entity	sink;
	struct media_pad	sink_pad;
	struct media_entity	proc;
	struct media_pad	proc_pads[2];
	struct media_intf_devnode *intf_devnode;
#endif

	struct list_head	job_queue;
	spinlock_t		job_spinlock;
	struct work_struct	job_work;
	unsigned long		job_queue_flags;

	const struct v4l2_m2m_ops *m2m_ops;
};

curr_ctx: 当前运行的 m2m context.

job_queue: 任务链表,存放 v4l2_m2m_ctx->queue 节点。在 v4l2_m2m_try_queue 中添加,在 v4l2_m2m_try_run 中取出。

job_work:工作队列,在 v4l2_m2m_job_finish->v4l2_m2m_schedule_next_job 后 schedule work,进入 v4l2_m2m_device_run_work->v4l2_m2m_try_run 从上面的 job_queue 中取出下一个 m2m_ctx, 调用.device_run

job_queue_flags: 目前只有 QUEUE_PAUSED, 在 v4l2_m2m_suspend 中置起,v4l2_m2m_resume 中清除,防止 suspend 之后仍然可以 run.


v4l2_m2m_ctx 结构体和一个 file handler 绑定,代表一个应用程序与设备的会话实例。

一般在 video device file operation 的 .open 回调中创建:

struct v4l2_m2m_ctx {
	struct mutex			*q_lock;
	bool				new_frame;
	bool				is_draining;
	struct vb2_v4l2_buffer		*last_src_buf;
	bool				next_buf_last;
	bool				has_stopped;
	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;
};

new_frame: 如果 m2m_ctx->out_q_ctx.q.subsystem_flags 没有置起 VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF flag 表示 slicing support, 那么每次 try_queue 都会置为 true,暂时没看到哪里利用到了这个 flag。

is_draining:

last_src_buf:

next_buf_last:

has_stopped:

cap_q_ctx/out_q_ctx: capture/output queue context.

queue: v4l2_m2m_dev 中的 job_queue 链表节点

job_flags: 当前 context 的状态,有 TRANS_QUEUED/RUNNING/ABORT.

finished: 等待队列,用于同步,在 v4l2_m2m_cancel_job() 和 v4l2_m2m_suspend() 中判断 job_flags 是否不是 RUNNING 状态, 如果仍然是 RUNNING 状态,则会睡眠。在 v4l2_m2m_job_finish() 和 v4l2_m2m_streamoff() 中唤醒睡眠进程。

priv: driver private m2m context structure.


struct v4l2_m2m_queue_ctx {
	struct vb2_queue	q;

	struct list_head	rdy_queue;
	spinlock_t		rdy_spinlock;
	u8			num_rdy;
	bool			buffered;
};

rdy_queue: m2m ready buffer 链表,在 v4l2_m2m_buf_queue() 中把 buffer 加入链表。

num_rdy: ready buffer 的数量。

buffered: v4l2_m2m_set_src/dst_buffered() 函数置起 flag,在 v4l2_m2m_try_queue() 中检查。这个 flag 好像没什么 driver 使用。

  %%{init: {'theme': 'neutral' }}%%
graph TD
    A["应用程序1<br/>/dev/video0"] --> B["v4l2_m2m_ctx #1"]
    C["应用程序2<br/>/dev/video0"] --> D["v4l2_m2m_ctx #2"]
    E["应用程序3<br/>/dev/video0"] --> F["v4l2_m2m_ctx #3"]
    
    B --> G["v4l2_m2m_dev<br/>(设备实例)"]
    D --> G
    F --> G
    
    G --> H["硬件设备<br/>(编码器/解码器/图像处理器)"]
    
    B --> I["输入队列<br/>(out_q_ctx)"]
    B --> J["输出队列<br/>(cap_q_ctx)"]
    
    K["输入缓冲区1<br/>输入缓冲区2<br/>输入缓冲区3"] --> I
    J --> L["输出缓冲区1<br/>输出缓冲区2<br/>输出缓冲区3"]
    
    M["任务调度器"] --> N["job_queue"]
    N --> O["ctx#1→ctx#2→ctx#3"]
    
    P["当前运行:<br/>curr_ctx = ctx#1"] --> H
    
    style B fill:#99ff99,stroke:#333,stroke-width:2px
    style D fill:#ffcc99,stroke:#333,stroke-width:2px  
    style F fill:#99ccff,stroke:#333,stroke-width:2px
    style G fill:#ff9999,stroke:#333,stroke-width:2px

接着通过v4l2_m2m_register_media_controller() 注册 media entity, pad, link.

拓扑图如下:

  %%{init: {'theme': 'neutral' }}%%
graph TD
    A["应用程序"] --> B["/dev/videoX<br/>(V4L2 Interface)"]
    
    B -.->|控制| C["SOURCE Entity<br/>(Input DMA)<br/>MEDIA_ENT_F_IO_V4L"]
    B -.->|控制| E["SINK Entity<br/>(Output DMA)<br/>MEDIA_ENT_F_IO_V4L"]
    
    C -->|"pad 0 → pad 0<br/>数据流"| D["PROC Entity<br/>(Video Processing)<br/>编码器/解码器/图像处理"]
    D -->|"pad 1 → pad 0<br/>数据流"| E
    
    F["输入缓冲区<br/>(Raw Data)"] --> C
    E --> G["输出缓冲区<br/>(Processed Data)"]
    
    H["Media Controller<br/>拓扑管理"] -.-> C
    H -.-> D  
    H -.-> E
    H -.-> B
    
    style C fill:#99ff99,stroke:#333,stroke-width:2px
    style D fill:#ff9999,stroke:#333,stroke-width:2px
    style E fill:#99ccff,stroke:#333,stroke-width:2px
    style B fill:#ffeb99,stroke:#333,stroke-width:2px

APIs

// v4l2-mem2mem.h
void *v4l2_m2m_get_curr_priv(struct v4l2_m2m_dev *m2m_dev);
struct vb2_queue *v4l2_m2m_get_vq(struct v4l2_m2m_ctx *m2m_ctx,
				       enum v4l2_buf_type type);
void v4l2_m2m_try_schedule(struct v4l2_m2m_ctx *m2m_ctx);
void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev,
			 struct v4l2_m2m_ctx *m2m_ctx);
void v4l2_m2m_buf_done_and_job_finish(struct v4l2_m2m_dev *m2m_dev,
				      struct v4l2_m2m_ctx *m2m_ctx,
				      enum vb2_buffer_state state);
static inline void v4l2_m2m_buf_done(struct vb2_v4l2_buffer *buf, enum vb2_buffer_state state);
static inline void v4l2_m2m_clear_state(struct v4l2_m2m_ctx *m2m_ctx);
static inline void v4l2_m2m_mark_stopped(struct v4l2_m2m_ctx *m2m_ctx);

流程分析

device run 流程:

  %%{init: {'theme': 'neutral' }}%%
graph TD
    A["用户空间应用"] --> B1["v4l2_m2m_qbuf()<br/>队列缓冲区"]
    A --> B2["v4l2_m2m_streamon()<br/>开始流传输"]
    A --> B3["v4l2_m2m_request_queue()<br/>请求队列处理"]
    
    B1 --> C["v4l2_m2m_try_schedule()"]
    B2 --> C
    B3 --> C
    
    C --> D["__v4l2_m2m_try_queue()"]
    D --> E["检查条件并添加到job_queue"]
    E --> F["v4l2_m2m_try_run()"]
    
    F --> G["从job_queue取出第一个任务"]
    G --> H["设置curr_ctx和TRANS_RUNNING"]
    H --> I["m2m_dev->m2m_ops->device_run(curr_ctx->priv)"]
    
    I --> J["驱动程序启动硬件操作"]
    J --> K["硬件处理数据..."]
    K --> L["硬件完成,触发中断"]
    L --> M["中断处理程序 (ISR)"]
    
    M --> N["v4l2_m2m_job_finish()<br/>或<br/>v4l2_m2m_buf_done_and_job_finish()"]
    N --> O["清理当前任务状态<br/>(在中断上下文中)"]
    O --> P["v4l2_m2m_schedule_next_job()"]
    
    P --> Q["schedule_work(&job_work)<br/>调度工作队列"]
    Q --> R["中断返回"]
    
    S["工作队列上下文<br/>(非中断)"] --> T["v4l2_m2m_device_run_work()"]
    T --> F
    
    Q -.->|异步调度| S
    
    style I fill:#ff9999,stroke:#333,stroke-width:3px
    style M fill:#ffaa99,stroke:#333,stroke-width:2px
    style N fill:#99ff99,stroke:#333,stroke-width:2px
    style S fill:#99ccff,stroke:#333,stroke-width:2px
    style C fill:#ffeb99,stroke:#333,stroke-width:2px