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
流程分析
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