ESC
输入关键词搜索文章标题和内容

V4L2到DRM全链路:从摄像头到屏幕的零拷贝之旅

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

V4L2到DRM全链路:从摄像头到屏幕的零拷贝之旅

导读:摄像头数据如何在Linux内核中走完"采集→处理→显示"全程?本文用一张全景图+一次采集追踪,讲清楚V4L2、DRM/DMABUF三者的关系。每个模块标注输入输出,5分钟搞懂Linux视频管线。


一、三层架构全景图

视频数据在Linux内核中流经三个子系统:V4L2负责采集,DRM负责显示,DMA-BUF负责零拷贝桥接。

flowchart TB subgraph 采集层["① V4L2 采集层"] direction TB VDEV["video_device<br/>/dev/video0"] VB2["vb2_queue<br/>缓冲区管理"] end subgraph 共享层["② DMA-BUF 零拷贝桥"] DMA["文件描述符传递<br/>/dev/dma_buf fd"] end subgraph 显示层["③ DRM 显示层"] direction TB FB["Framebuffer<br/>像素缓冲区"] PLANE["Plane 图层"] CRTC["CRTC 扫描时序"] CONN["Connector<br/>HDMI/eDP 输出"] end VDEV --> VB2 VB2 -->|"EXPBUF<br/>导出fd"| DMA DMA -->|"PRIME导入<br/>复用同一帧"| FB FB --> PLANE --> CRTC --> CONN CONN --> 屏幕(["屏幕"]) style VDEV fill:#E3F2FD,stroke:#1976D2 style VB2 fill:#E3F2FD,stroke:#1976D2 style DMA fill:#FFF8E1,stroke:#F57C00 style FB fill:#E8F5E9,stroke:#388E3C style PLANE fill:#E8F5E9,stroke:#388E3C style CRTC fill:#E8F5E9,stroke:#388E3C style CONN fill:#E8F5E9,stroke:#388E3C

三层职责速记:

层 核心组件 一句话
① V4L2 /dev/video0 + vb2_queue 把传感器数据送入内核缓冲区
② DMA-BUF EXPBUF + PRIME 把内核缓冲区"借"给显示驱动,不拷贝
③ DRM FB → Plane → CRTC → Connector 把像素数据扫描到屏幕

二、一次采集请求的完整旅程

摄像头按下录制键,数据走了一条怎样的路?追踪一帧图像从镜头到屏幕的完整路径。

来自 linuxros.cn · linuxROS
flowchart LR SENSOR(["CMOS 图像传感器"]) --> CSI["MIPI CSI-2<br/>高速串行"] CSI --> ISP["ISP 图像处理<br/>Bayer/去噪/WB"] ISP --> VDEV["video_device<br/>/dev/video0"] VDEV --> VB2["vb2_queue<br/>缓冲区入队"] VB2 --> DQ["DQBUF<br/>出队取帧"] DQ --> EXP["EXPBUF<br/>导出 DMA-BUF fd"] EXP --> PRIME["DRM PRIME<br/>导入 fd"] PRIME --> FB["Framebuffer<br/>像素写入"] FB --> SCAN["CRTC 扫描<br/>HDMI 输出"] SCAN --> 屏幕(["屏幕显示"]) style SENSOR fill:#FFEBEE,stroke:#D32F2F style CSI fill:#FFEBEE,stroke:#D32F2F style ISP fill:#FFEBEE,stroke:#D32F2F style VDEV fill:#E3F2FD,stroke:#1976D2 style VB2 fill:#E3F2FD,stroke:#1976D2 style DQ fill:#E3F2FD,stroke:#1976D2 style EXP fill:#FFF8E1,stroke:#F57C00 style PRIME fill:#FFF8E1,stroke:#F57C00 style FB fill:#E8F5E9,stroke:#388E3C style SCAN fill:#E8F5E9,stroke:#388E3C

关键数据流(按顺序):

  1. 镜头光信号 → CMOS 传感器 → MIPI CSI-2 串行传输
  2. ISP 完成 Bayer 解码/去噪/白平衡 → 原始图像
  3. open("/dev/video0") → VIDIOC_S_FMT 设置格式(1920×1080 NV12)
  4. VIDIOC_REQBUFS + VIDIOC_QBUF 申请并入队缓冲区
  5. VIDIOC_STREAMON 启动流 → 驱动开始填充帧
  6. VIDIOC_DQBUF 出队 → 拿到已填充帧的句柄
  7. VIDIOC_EXPBUF 导出 DMA-BUF fd → 零拷贝关键
  8. drmPrimeFDToHandle 导入 fd → 创建 Framebuffer
  9. CRTC 持续扫描 → HDMI 输出到屏幕

三、V4L2 三种缓冲区模式对比

模式 原理 适用场景
MMAP 内核分配缓冲区,mmap 直接访问 简单采集,最常用
USERPTR 用户空间分配内存,传地址给驱动 已有内存池复用
DMABUF ⭐ 内核缓冲区通过 fd 跨设备共享 V4L2↔DRM 零拷贝

四、DMA-BUF 零拷贝 vs 传统拷贝路径

flowchart TB subgraph 零拷贝["🟢 DMA-BUF 零拷贝(推荐)"] Z1["V4L2 采集帧"] --> Z2["内核 DMA 缓冲区"] Z2 --> Z3["导出 dma_buf_fd"] Z3 --> Z4["DRM PRIME 导入"] Z4 --> Z5["直接 scanout 到屏幕"] end subgraph 传统["🔴 传统拷贝(低效)"] T1["V4L2 采集帧"] --> T2["mmap 到用户空间"] T2 --> T3["CPU 拷贝/转换"] T3 --> T4["写入 DRM dumb buffer"] T4 --> T5["DRM 显示输出"] end Z1 -.->|"零次CPU拷贝"| Z5 T1 -.->|"≥2次CPU拷贝"| T5 style Z1 fill:#E8F5E9,stroke:#388E3C style Z2 fill:#E8F5E9,stroke:#388E3C style Z3 fill:#E8F5E9,stroke:#388E3C style Z4 fill:#E8F5E9,stroke:#388E3C style Z5 fill:#E8F5E9,stroke:#388E3C style T1 fill:#FFEBEE,stroke:#D32F2F style T2 fill:#FFEBEE,stroke:#D32F2F style T3 fill:#FFEBEE,stroke:#D32F2F style T4 fill:#FFEBEE,stroke:#D32F2F style T5 fill:#FFEBEE,stroke:#D32F2F

五、总结

记一个口诀:采集入队→DQBUF取帧→EXPBUF导出→PRIME导入→CRTC扫描。

整个Linux视频管线的本质就是零拷贝传递:数据从摄像头到屏幕全程在DMA层面流转,不经过用户空间,不触发一次CPU拷贝。理解这个传递链,就理解了V4L2和DRM的协作方式。

下篇预告:libcamera 如何用一套API统一V4L2与Android Camera HAL,让摄像头应用一次编写、跨平台运行。


参考来源

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页