从 Rockchip CIF 驱动源码看懂 Camera 数据流:V4L2、DMA 搬运与零拷贝原理

在嵌入式音视频开发中,我们经常会遇到这些问题:

  • 摄像头数据到底是怎么从 Sensor 进入 DDR 的?

  • V4L2 采集为什么不用 CPU 一帧一帧 memcpy?

  • DMA 在 Camera 驱动里到底搬运了什么?

  • 零拷贝为什么可以降低延迟、提升性能?

  • NV12、NV16、YUYV、RAW10、RAW12 这些格式在驱动里是怎么体现的?

本文结合一段 Rockchip CIF 驱动源码,从 代码结构、驱动框架、格式表分析、DMA 搬运机制、零拷贝原理 几个角度,把 Camera 驱动中最核心的一条数据链路讲清楚。

说明:当前源码片段主要包含头文件依赖、宏定义、fcc_xysubs() 函数以及 out_fmts[] 输出格式表,没有完整包含 probestream_onirqvb2 queue 等后续实现。因此本文会明确区分:哪些是当前源码中能直接看到的内容,哪些是基于 Rockchip CIF + V4L2 框架的典型驱动流程分析。


1. 这段代码属于什么驱动?

源码开头已经说明:

/*
 * Rockchip CIF Driver
 *
 * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
 */

这是一段 Rockchip CIF Driver 代码。

CIF 可以理解为 Camera Interface,也就是摄像头接口控制器。它位于 Sensor 和内存之间,主要负责接收 Camera 数据,并通过 DMA 写入 DDR。

一个典型 Rockchip Camera 采集链路可以简化成这样:

Camera Sensor
     |
     | MIPI CSI-2 / DVP
     v
MIPI CSI2 Receiver / DVP Interface
     |
     v
Rockchip CIF
     |
     | DMA Write
     v
DDR Buffer
     |
     v
V4L2 / Encoder / ISP / RGA / NPU / Display

从这个链路可以看出,CIF 驱动并不是简单地“读摄像头数据”,而是连接了:

Sensor 数据输入
MIPI CSI2 / DVP 接口
CIF 硬件控制器
V4L2 视频采集框架
videobuf2 buffer 队列
DMA 写 DDR
IOMMU 地址映射
后级编码器或图像处理模块

所以 CIF 驱动本质上是一条高速视频数据通路的入口。


2. 从 include 看驱动整体架构

源码一开始 include 了很多头文件,这些头文件其实已经暴露了驱动的大体设计方向。

部分关键 include 如下:

#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/iommu.h>

#include <media/v4l2-common.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-subdev.h>

#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-dma-sg.h>

#include <soc/rockchip/rockchip_iommu.h>

#include <linux/dma-fence.h>
#include <linux/sync_file.h>
#include <linux/kfifo.h>
#include <linux/gpio/consumer.h>

这些 include 可以分成几类。


2.1 电源、复位、硬件资源管理

#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/gpio/consumer.h>

这说明驱动会涉及:

runtime PM 电源管理
reset 控制器复位
GPIO 控制

Camera 驱动里经常需要控制 sensor reset、powerdown、mclk、电源域等资源。

虽然当前片段没有展示 probe() 和 power on/off 代码,但从 include 可以看出,这个驱动是按照 Linux 设备驱动模型来管理硬件资源的。


2.2 V4L2 框架相关

#include <media/v4l2-common.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-subdev.h>

这说明 CIF 驱动接入的是 Linux V4L2 媒体框架

用户态访问摄像头时,通常不是直接操作 CIF 寄存器,而是访问:

/dev/video0
/dev/video1
...

用户程序通过 ioctl 与驱动交互,例如:

VIDIOC_QUERYCAP      查询设备能力
VIDIOC_ENUM_FMT      枚举支持格式
VIDIOC_S_FMT         设置图像格式
VIDIOC_REQBUFS       申请 buffer
VIDIOC_QBUF          buffer 入队
VIDIOC_STREAMON      启动采集
VIDIOC_DQBUF         取出采集完成的 buffer
VIDIOC_STREAMOFF     停止采集

其中 v4l2-subdev 非常关键。

它说明 CIF 并不是孤立工作的,而是会和 Sensor、MIPI CSI2 等 subdev 组成一个 media pipeline。

典型 media graph 类似这样:

Sensor Subdev
     |
     v
MIPI CSI2 Subdev
     |
     v
CIF Video Node
     |
     v
/dev/videoX

2.3 videobuf2 buffer 管理

#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-dma-sg.h>

这是 V4L2 采集驱动里非常核心的部分。

videobuf2 简称 vb2,是 V4L2 的 buffer 队列管理框架。

它主要负责:

用户申请 buffer
驱动分配或导入 buffer
用户 QBUF 入队
驱动把 buffer 交给硬件 DMA
DMA 写入一帧图像
中断通知完成
驱动 vb2_buffer_done
用户 DQBUF 取出 buffer

这里包含两个 DMA 后端:

videobuf2-dma-contig
videobuf2-dma-sg

含义如下:

后端

含义

典型场景

dma-contig

分配物理连续内存

硬件不支持 IOMMU,要求连续物理地址

dma-sg

scatter-gather 分散页

硬件支持 IOMMU,可映射成连续 IOVA

这说明 CIF 采集不是通过 CPU memcpy,而是基于 DMA buffer 体系完成的。


2.4 IOMMU 相关

#include <linux/iommu.h>
#include <soc/rockchip/rockchip_iommu.h>

IOMMU 的作用是为外设提供一个设备视角下的虚拟地址空间。

CPU 访问内存一般使用 CPU 虚拟地址;
而 CIF、VPU、RGA、Display 这类硬件模块访问内存,需要 DMA 地址或者 IOVA 地址。

IOMMU 可以把一组物理页映射成设备可见的连续地址。

这对零拷贝非常重要。

因为在零拷贝场景下,同一块 buffer 可能会被多个硬件模块共享:

CIF 写入 buffer
Encoder 读取同一块 buffer
Display 显示同一块 buffer
RGA 处理同一块 buffer

如果没有统一的 DMA/IOMMU buffer 管理机制,这种跨硬件共享会非常困难。


2.5 dma-fence 与 sync_file

#include <linux/dma-fence.h>
#include <linux/sync_file.h>

这两个头文件通常和跨设备同步有关。

为什么需要同步?

因为多个硬件共享同一块 buffer 时,必须保证读写顺序正确。

例如:

CIF 正在写第 N 帧
Encoder 不能提前读取第 N 帧

Encoder 正在读取第 N 帧
CIF 不能提前复用这块 buffer

否则就会出现:

半帧数据
画面撕裂
颜色异常
偶发花屏
buffer 被提前覆盖

dma-fence 可以表示一次 DMA 操作完成的同步点。
sync_file 可以把 fence 以 fd 的形式传递到用户态或者其他模块。

在现代 Linux 图形和视频系统中,dma-buf + fence 是零拷贝和跨硬件同步的重要基础。


3. 基础宏定义分析

源码中定义了一些基础参数:

#define CIF_REQ_BUFS_MIN    1
#define CIF_MIN_WIDTH       64
#define CIF_MIN_HEIGHT      64
#define CIF_MAX_WIDTH       8192
#define CIF_MAX_HEIGHT      8192

#define OUTPUT_STEP_WISE    8

这几个宏说明 CIF 输出尺寸有基本边界:

最小分辨率:64 x 64
最大分辨率:8192 x 8192

OUTPUT_STEP_WISE 为 8,通常用于宽高或者 crop 参数的步进对齐。

很多图像硬件对宽、高、stride 都有对齐要求,例如:

8 字节对齐
16 字节对齐
64 字节对齐
256 字节对齐

如果没有满足硬件对齐要求,可能会出现:

DMA 写错行
图像倾斜
画面花屏
颜色错乱
编码器无法直接读取

4. Plane 定义分析

源码中有如下定义:

#define RKCIF_PLANE_Y       0
#define RKCIF_PLANE_CBCR    1
#define RKCIF_MAX_PLANE     3

这说明驱动内部会按 plane 管理图像数据。

以 NV12 为例:

Y plane   :亮度数据
UV plane  :色度数据,UV 交织

NV12 的内存布局可以理解为:

buffer_base
    |
    +-- Y plane
    |
    +-- UV plane

虽然很多 V4L2 格式在 memory plane 上可能是一个平面,也就是 mplanes = 1,但从图像内容上看,它仍然可以拆成 Y 和 UV 两个 component plane。

因此代码里会同时看到:

cplanes:component planes,图像内容平面数量
mplanes:memory planes,V4L2 内存平面数量

这两个概念要区分清楚。


5. Media Pad 定义分析

源码中还有:

#define STREAM_PAD_SINK     0
#define STREAM_PAD_SOURCE   1

这对应 media controller 中的数据流方向。

sink pad    :输入端
source pad  :输出端

对于 CIF 来说,它通常是从 MIPI CSI2 或 DVP 接收图像数据,所以:

CIF sink pad    接收来自上游的数据
CIF source pad  输出到 video node

完整 media pipeline 可以理解为:

Sensor source pad
        |
        v
MIPI CSI2 sink pad
MIPI CSI2 source pad
        |
        v
CIF sink pad
CIF source pad
        |
        v
Video Node

这个结构是 V4L2 Media Controller 框架管理复杂 Camera pipeline 的基础。


6. 高度 16 对齐:这是零拷贝优化的关键

源码中有一段非常重要的注释:

/*
 * Round up height when allocate memory so that Rockchip encoder can
 * use DMA buffer directly, though this may waste a bit of memory.
 */
#define MEMORY_ALIGN_ROUND_UP_HEIGHT        16

这段注释的意思是:

分配内存时,把高度向上按 16 对齐,这样 Rockchip 编码器可以直接使用 DMA buffer,虽然会浪费一点内存。

这句话其实非常关键,它直接说明了驱动设计是在为 camera 到 encoder 的零拷贝链路 做准备。

为什么?

因为硬件编码器通常要求输入图像满足一定对齐条件,例如:

宽度对齐
高度对齐
stride 对齐
buffer size 对齐
plane offset 对齐

如果 CIF 采集出来的 buffer 不满足编码器要求,后面就只能重新分配一块符合编码器要求的 buffer,然后做一次拷贝或者格式转换:

CIF DMA buffer
     |
     | memcpy / RGA 转换
     v
Encoder input buffer

这样就不是零拷贝了。

而这里提前把高度按 16 对齐,就是为了让 CIF 采集出来的 buffer 能被编码器直接使用:

CIF DMA 写入 buffer
     |
     | 同一块 DMA buffer
     v
Encoder 直接读取

这就是典型的工程优化:
采集端多分配一点内存,换取后级编码链路不再拷贝。


7. fcc_xysubs() 函数代码分析

源码中有一个很重要的小函数:

static int fcc_xysubs(u32 fcc, u32 *xsubs, u32 *ysubs)
{
    switch (fcc) {
    case V4L2_PIX_FMT_NV16:
    case V4L2_PIX_FMT_NV61:
    case V4L2_PIX_FMT_UYVY:
    case V4L2_PIX_FMT_VYUY:
    case V4L2_PIX_FMT_YUYV:
    case V4L2_PIX_FMT_YVYU:
        *xsubs = 2;
        *ysubs = 1;
        break;
    case V4L2_PIX_FMT_NV21:
    case V4L2_PIX_FMT_NV12:
        *xsubs = 2;
        *ysubs = 2;
        break;
    default:
        return -EINVAL;
    }

    return0;
}

这个函数的作用是:
根据 V4L2 fourcc 格式,计算 YUV 色度子采样比例。


7.1 什么是 xsubs 和 ysubs?

注释中写得很清楚:

/* Get xsubs and ysubs for fourcc formats
 *
 * @xsubs: horizontal color samples in a 4*4 matrix, for yuv
 * @ysubs: vertical color samples in a 4*4 matrix, for yuv
 */

可以简单理解为:

xsubs:水平方向色度采样比例
ysubs:垂直方向色度采样比例

对于 YUV 格式,Y 是亮度,UV 是色度。

人眼对亮度更敏感,对色度不那么敏感,所以视频格式里常常会减少 UV 数据量,这就是色度子采样。


7.2 YUV422 格式分析

代码中这些格式被归为:

*xsubs = 2;
*ysubs = 1;

对应格式包括:

V4L2_PIX_FMT_NV16
V4L2_PIX_FMT_NV61
V4L2_PIX_FMT_UYVY
V4L2_PIX_FMT_VYUY
V4L2_PIX_FMT_YUYV
V4L2_PIX_FMT_YVYU

这类格式属于 YUV422。

含义是:

水平方向:2 个 Y 共用一组 UV
垂直方向:每一行都有 UV

所以 YUV422 的数据量通常是:

width * height * 2 bytes

例如 1920x1080 的 YUV422:

1920 * 1080 * 2 ≈ 4MB/frame

如果是 60fps:

4MB * 60 ≈ 240MB/s

这只是单路写 DDR 的数据量,还没计算后续处理和额外拷贝。


7.3 YUV420 格式分析

代码中 NV12 和 NV21 被归为:

*xsubs = 2;
*ysubs = 2;

对应:

V4L2_PIX_FMT_NV21
V4L2_PIX_FMT_NV12

这类格式属于 YUV420。

含义是:

水平方向:2 个 Y 共用一组 UV
垂直方向:2 行 Y 共用一行 UV

所以 YUV420 的数据量通常是:

width * height * 1.5 bytes

例如 1920x1080 的 NV12:

Y  plane = 1920 * 1080
UV plane = 1920 * 1080 / 2

总大小 = 1920 * 1080 * 1.5 ≈ 3MB/frame

所以 NV12 相比 YUV422 更省带宽,也更适合编码。

在实际工程中,Camera -> Encoder 链路经常优先选择 NV12。


7.4 default 返回 -EINVAL 的意义

函数末尾有:

default:
    return -EINVAL;

这表示如果传入的 fourcc 不属于函数支持的 YUV 格式,就返回非法参数。

这类函数通常会被用于:

计算 bytesperline
计算 sizeimage
计算 UV plane 偏移
判断格式是否合法

如果格式不支持,驱动必须及时返回错误,否则后面 DMA 地址、stride、buffer size 都可能计算错误,最终导致花屏或者内存越界。


8. out_fmts[] 输出格式表分析

源码中最核心的数据结构是:

static const struct cif_output_fmt out_fmts[] = {
    ...
};

这个表非常重要。

它的作用是把用户层看到的 V4L2 像素格式,转换成 CIF 硬件能够理解的格式配置。

也就是说:

V4L2_PIX_FMT_NV12
        |
        v
CIF 硬件寄存器格式值
        |
        v
CSI 写 DDR 类型
        |
        v
DMA 输出内存布局

一个表项大致长这样:

{
    .fourcc = V4L2_PIX_FMT_NV12,
    .fmt_val = YUV_OUTPUT_420 | UV_STORAGE_ORDER_UVUV,
    .cplanes = 2,
    .mplanes = 1,
    .bpp = { 8, 16 },
    .csi_fmt_val = CSI_WRDDR_TYPE_YUV420SP,
    .fmt_type = CIF_FMT_TYPE_YUV,
}

下面逐个字段分析。


8.1 fourcc:用户层看到的格式

.fourcc = V4L2_PIX_FMT_NV12

fourcc 是 V4L2 中用于描述像素格式的四字符编码。

用户态通过 v4l2-ctl 可以看到类似格式:

v4l2-ctl --list-formats-ext -d /dev/video0

可能输出:

[0]: 'NV12' (Y/CbCr 4:2:0)
[1]: 'NV16' (Y/CbCr 4:2:2)
[2]: 'YUYV' (YUYV 4:2:2)
[3]: 'RG10' (10-bit Bayer RGRG/GBGB)

驱动内部就是通过 fourcc 判断用户选择了哪种输出格式。


8.2 cplanes 和 mplanes:图像平面与内存平面

例如 NV12 表项:

.cplanes = 2,
.mplanes = 1,

这说明:

cplanes = 2:图像内容上有 Y 和 UV 两个平面
mplanes = 1:V4L2 内存上用一个连续 buffer 表示

NV12 内存布局可以画成:

+-----------------------------+
|           Y plane           |
|     width * height          |
+-----------------------------+
|           UV plane          |
|     width * height / 2      |
+-----------------------------+

虽然图像内容上分为 Y 和 UV,但是内存上通常是一整块连续 buffer。

这也是很多初学者容易混淆的地方:

component plane 不等于 memory plane

8.3 fmt_val:CIF 硬件输出格式配置

以 NV12 为例:

.fmt_val = YUV_OUTPUT_420 | UV_STORAGE_ORDER_UVUV

这说明硬件输出格式是:

YUV420
UV 顺序为 UVUV

对应 NV12。

而 NV21 是:

.fmt_val = YUV_OUTPUT_420 | UV_STORAGE_ORDER_VUVU

这说明:

YUV420
UV 顺序为 VUVU

所以 NV12 和 NV21 的核心区别不是 Y plane,而是 UV 顺序:

NV12:UVUVUV...
NV21:VUVUVU...

如果 UV 顺序配置错,最典型的现象就是:

亮度正常
颜色异常
画面偏绿
画面偏紫

8.4 bpp:每个 plane 的位宽

NV12 表项中:

.bpp = { 8, 16 },

这个字段要结合格式理解。

对于 NV12:

Y plane:每个 Y 是 8bit
UV plane:UV 交织,每组 UV 可以理解为 16bit

所以这里用 {8, 16} 描述 Y 和 UV plane 的 bit per pixel 关系。

驱动后面通常会基于 bpp 计算:

bytesperline
sizeimage
plane offset
DMA 写入地址

例如:

Y stride  = width * 8 / 8
UV stride = width * 16 / 8 / xsubs

具体计算方式要看驱动后续实现,但 bpp 字段一定会参与 buffer size 和地址偏移计算。


8.5 csi_fmt_val:CSI 写 DDR 类型

例如 NV12:

.csi_fmt_val = CSI_WRDDR_TYPE_YUV420SP

NV16:

.csi_fmt_val = CSI_WRDDR_TYPE_YUV422

RAW10:

.csi_fmt_val = CSI_WRDDR_TYPE_RAW10

这个字段通常用于配置 CIF/CSI 写 DDR 时的数据类型。

可以理解为告诉硬件:

当前写 DDR 的数据类型是什么?
是 YUV420 semi-planar?
是 YUV422?
是 RAW8?
是 RAW10?
是 RGB888?

硬件根据这个值决定如何组织 DMA 写入数据。


8.6 fmt_type:格式大类

表中可以看到:

.fmt_type = CIF_FMT_TYPE_YUV

或者:

.fmt_type = CIF_FMT_TYPE_RAW

这表示格式大类。

YUV 格式一般用于视频采集、编码、显示预览。

RAW 格式一般来自 Bayer Sensor,后续通常要经过 ISP 做:

黑电平校正
坏点校正
去马赛克
白平衡
降噪
颜色校正
Gamma

如果直接采集 RAW,则用户层拿到的是原始 Bayer 数据,而不是正常 RGB/YUV 图像。


9. 典型格式表项代码走读

下面挑几个典型格式表项进行分析。


9.1 NV16:YUV422 semi-planar

{
    .fourcc = V4L2_PIX_FMT_NV16,
    .cplanes = 2,
    .mplanes = 1,
    .fmt_val = YUV_OUTPUT_422 | UV_STORAGE_ORDER_UVUV,
    .bpp = { 8, 16 },
    .csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
    .fmt_type = CIF_FMT_TYPE_YUV,
}

NV16 是 YUV422 semi-planar 格式。

内存布局:

+-----------------------------+
|           Y plane           |
+-----------------------------+
|        UVUVUVUV plane       |
+-----------------------------+

特点:

色度垂直方向不降采样
画质比 NV12 更好
数据量比 NV12 更大

数据量:

width * height * 2 bytes

适合对色彩要求较高的采集场景。


9.2 NV12:YUV420 semi-planar

{
    .fourcc = V4L2_PIX_FMT_NV12,
    .fmt_val = YUV_OUTPUT_420 | UV_STORAGE_ORDER_UVUV,
    .cplanes = 2,
    .mplanes = 1,
    .bpp = { 8, 16 },
    .csi_fmt_val = CSI_WRDDR_TYPE_YUV420SP,
    .fmt_type = CIF_FMT_TYPE_YUV,
}

NV12 是嵌入式视频里最常见的格式之一。

内存布局:

+-----------------------------+
|           Y plane           |
+-----------------------------+
|        UVUVUVUV plane       |
+-----------------------------+

数据量:

width * height * 1.5 bytes

它常用于:

Camera 采集
硬件编码 H.264/H.265
视频预览
NPU 前处理
RGA 图像处理

如果目标是 Camera -> Encoder,NV12 通常是优先选择。


9.3 NV21:和 NV12 只差 UV 顺序

{
    .fourcc = V4L2_PIX_FMT_NV21,
    .fmt_val = YUV_OUTPUT_420 | UV_STORAGE_ORDER_VUVU,
    .cplanes = 2,
    .mplanes = 1,
    .bpp = { 8, 16 },
    .csi_fmt_val = CSI_WRDDR_TYPE_YUV420SP,
    .fmt_type = CIF_FMT_TYPE_YUV,
}

NV21 和 NV12 非常像。

区别是:

NV12:UVUVUV
NV21:VUVUVU

如果后级模块期望 NV12,但驱动输出 NV21,就会出现颜色异常。

调试时可以重点观察:

画面亮度是否正常
颜色是否偏紫
颜色是否偏绿
肤色是否异常

这类问题多数和 UV 顺序有关。


9.4 YUYV / UYVY:Packed YUV422

源码中也支持:

V4L2_PIX_FMT_YUYV
V4L2_PIX_FMT_YVYU
V4L2_PIX_FMT_UYVY
V4L2_PIX_FMT_VYUY

这些属于 packed YUV422。

例如 YUYV 的内存排列可以理解为:

Y0 U0 Y1 V0  Y2 U1 Y3 V1 ...

UYVY 则是:

U0 Y0 V0 Y1  U1 Y2 V1 Y3 ...

这类格式常见于:

USB Camera
DVP Sensor
调试采集
部分显示输入

但是如果后面要接编码器,而编码器只支持 NV12,那么中间可能需要格式转换。

一旦发生格式转换,就可能引入额外拷贝或 RGA 处理,零拷贝链路就会变复杂。


9.5 RGB24 / BGR24 / RGB565

源码中支持:

V4L2_PIX_FMT_RGB24
V4L2_PIX_FMT_BGR24
V4L2_PIX_FMT_RGB565
V4L2_PIX_FMT_BGR666

RGB 格式更直观,但数据量通常更大。

例如 RGB24:

每个像素 3 bytes
1920 * 1080 * 3 ≈ 6MB/frame

60fps 下:

6MB * 60 ≈ 360MB/s

相比 NV12:

1920 * 1080 * 1.5 ≈ 3MB/frame

RGB24 的带宽几乎是 NV12 的 2 倍。

所以如果后面要编码 H.264/H.265,一般不建议优先使用 RGB 格式。更好的选择通常是让 CIF 直接输出 NV12 或 NV16。


9.6 RAW8 / RAW10 / RAW12:Bayer 原始数据

源码中支持多种 Bayer RAW 格式:

V4L2_PIX_FMT_SRGGB8
V4L2_PIX_FMT_SGRBG8
V4L2_PIX_FMT_SGBRG8
V4L2_PIX_FMT_SBGGR8

V4L2_PIX_FMT_SRGGB10
V4L2_PIX_FMT_SGRBG10
V4L2_PIX_FMT_SGBRG10
V4L2_PIX_FMT_SBGGR10

V4L2_PIX_FMT_SRGGB12
...

例如 RAW10 表项:

{
    .fourcc = V4L2_PIX_FMT_SRGGB10,
    .cplanes = 1,
    .mplanes = 1,
    .bpp = { 16 },
    .raw_bpp = 10,
    .csi_fmt_val = CSI_WRDDR_TYPE_RAW10,
    .fmt_type = CIF_FMT_TYPE_RAW,
}

这里有一个很重要的细节:

raw_bpp = 10
bpp = 16

这说明原始 sensor 数据是 10bit,但写入 DDR 时可能按照 16bit 容器来存储。

也就是说,RAW10 不一定在内存中紧凑地按 10bit 排列,它可能被 unpack 成 16bit。

这样做的好处是:

地址计算简单
DMA 写入对齐更友好
CPU 或 ISP 读取更方便

代价是:

占用内存更多
DDR 带宽更大

这也是调试 RAW10/RAW12 时非常容易踩坑的地方。

如果你以为 RAW10 一定是:

width * height * 10 / 8

但实际驱动按照 16bit 存储,那么你计算出来的 sizeimage 就会偏小,最终导致取帧异常或者图像错乱。


10. 根据代码推导完整采集流程

当前源码片段没有展示完整的 stream_onirq handler 和 vb2_ops,但结合 V4L2 + Rockchip CIF 的典型结构,完整采集流程一般如下:

1. 用户打开 /dev/videoX
        |
2. VIDIOC_QUERYCAP 查询能力
        |
3. VIDIOC_ENUM_FMT 枚举 out_fmts[] 支持的格式
        |
4. VIDIOC_S_FMT 设置分辨率和像素格式
        |
5. 驱动根据 fourcc 查找 out_fmts[] 表项
        |
6. 根据 bpp、xsubs、ysubs 计算 stride 和 sizeimage
        |
7. VIDIOC_REQBUFS 申请 DMA buffer
        |
8. VIDIOC_QBUF 把 buffer 放入 vb2 队列
        |
9. VIDIOC_STREAMON 启动 Sensor / MIPI / CIF
        |
10. CIF 配置 DMA 地址和格式寄存器
        |
11. Sensor 输出图像数据
        |
12. CIF 接收数据并 DMA 写入 DDR
        |
13. 一帧完成后触发中断
        |
14. 驱动调用 vb2_buffer_done
        |
15. 用户 VIDIOC_DQBUF 取出一帧

从软件角度看:

V4L2 ioctl
     |
     v
vb2 buffer queue
     |
     v
CIF register config
     |
     v
DMA write
     |
     v
IRQ frame done
     |
     v
vb2_buffer_done

从硬件角度看:

Sensor
  |
  v
MIPI CSI2 / DVP
  |
  v
CIF
  |
  v
DMA
  |
  v
DDR

11. DMA 搬运到底做了什么?

DMA 全称是 Direct Memory Access,直接内存访问。

在 Camera 场景中,DMA 的本质是:

CIF 硬件作为 bus master,
直接把接收到的图像数据写入 DDR buffer,
CPU 不参与每个像素的搬运。

CPU 主要负责:

配置 CIF 寄存器
设置 DMA 目标地址
设置输出格式
设置 stride
设置 crop/width/height
启动 stream
处理中断
维护 buffer 队列

真正的视频像素搬运由硬件完成。


12. DMA 写 NV12 的过程

以 NV12 为例,一帧图像有两个逻辑平面:

Y plane
UV plane

如果分辨率是 1920x1080:

Y 大小  = 1920 * 1080
UV 大小 = 1920 * 1080 / 2
总大小  = 1920 * 1080 * 1.5

内存布局:

buffer_base
    |
    +-- Y plane
    |       size = y_stride * aligned_height
    |
    +-- UV plane
            size = uv_stride * aligned_height / 2

驱动通常需要给硬件配置:

Y plane DMA 地址
UV plane DMA 地址
Y stride
UV stride
图像宽度
图像高度
输出格式

对于 single-planar NV12,虽然 V4L2 层面可能只有一个 memory plane,但驱动内部仍然会计算:

Y address  = buffer_base
UV address = buffer_base + y_stride * aligned_height

这就是为什么前面 cplanes = 2mplanes = 1 要分开理解。


13. DMA 为什么比 CPU memcpy 高效?

假设采集 4K60 NV12:

3840 * 2160 * 1.5 * 60 ≈ 746MB/s

这只是 CIF DMA 写 DDR 的原始数据量。

如果中间再做一次 CPU memcpy,相当于额外发生:

读 746MB/s
写 746MB/s

也就是接近:

1.5GB/s

的额外 DDR 带宽开销。

如果链路是:

Camera -> RGA -> Encoder -> Display

中间每多一次拷贝,带宽都会被进一步放大。

所以嵌入式视频系统中,真正的优化往往不是“写一个更快的 memcpy”,而是:

让 memcpy 根本不要发生

14. DMA 搬运最容易出问题的地方

14.1 DMA 地址错误

硬件 DMA 不能直接使用普通用户态虚拟地址。

它需要的是:

物理地址
或 IOMMU 映射后的 IOVA 地址

如果地址配置错,轻则图像异常,重则总线错误。


14.2 stride 错误

图像宽度不一定等于内存 stride。

例如:

width  = 1920
stride = 2048

这是很常见的对齐情况。

如果 stride 配错,常见现象是:

画面倾斜
错行
花屏
图像撕裂

14.3 UV offset 错误

NV12、NV21、NV16、NV61 都涉及 UV plane。

如果 UV 起始地址计算错,会出现:

亮度正常
颜色异常
画面偏绿
画面偏紫
色块错位

14.4 RAW10/RAW12 sizeimage 错误

RAW10/RAW12 要特别注意:

raw_bpp 不一定等于内存 bpp

如果驱动把 RAW10 unpack 到 16bit,那么 sizeimage 应该按 16bit 计算,而不是按 10bit 计算。

否则用户态 mmap 后读取数据时,很容易出现:

一帧大小不对
图像错位
行数据错乱
后半帧异常

14.5 Cache 同步问题

如果 CPU 和 DMA 同时访问同一块 buffer,就要考虑 cache coherency。

典型问题:

CPU 看到旧数据
硬件读到旧数据
偶发花屏
偶发撕裂

在 vb2 / dma-buf 体系里,很多 cache 同步动作会由框架处理,但驱动必须正确遵守 buffer 生命周期。


15. 零拷贝是什么?

零拷贝不是“不发生 DMA”。

在 Camera 场景里,零拷贝通常指:

摄像头采集出来的 DMA buffer,
不再被 CPU memcpy 到另一块 buffer,
而是直接交给后级硬件模块使用。

也就是说:

有 DMA
但没有 CPU memcpy

普通链路可能是:

CIF DMA -> Kernel Buffer
Kernel Buffer -> User Buffer
User Buffer -> Encoder Buffer
Encoder 读取 Encoder Buffer

零拷贝链路则是:

CIF DMA -> DMA-BUF
Encoder 直接读取同一个 DMA-BUF

关键区别是:

同一块物理内存或同一组物理页,
被多个硬件模块共享。

16. Camera 到 Encoder 的零拷贝链路

典型零拷贝编码流程:

1. CIF 驱动分配 DMA buffer
        |
2. CIF DMA 把图像写入 buffer
        |
3. 用户态 DQBUF 拿到 buffer
        |
4. 用户态获取 dmabuf fd
        |
5. 把 dmabuf fd 传给编码器
        |
6. 编码器 import 这块 dmabuf
        |
7. 编码器通过 IOMMU 映射同一块 buffer
        |
8. 编码器直接读取该 buffer 编码

数据没有被 CPU 重新复制。

这就是零拷贝的核心价值。


17. 零拷贝成立的几个条件

17.1 格式兼容

CIF 输出格式必须是后级模块能接受的格式。

例如编码器通常更喜欢:

NV12
NV16

如果 CIF 输出 RGB24,而编码器只接受 NV12,中间就需要格式转换。

格式转换可能由 CPU 做,也可能由 RGA 做。
但只要产生新的输出 buffer,就不再是严格意义上的 Camera -> Encoder 零拷贝。


17.2 对齐兼容

源码里的这句非常关键:

#define MEMORY_ALIGN_ROUND_UP_HEIGHT        16

它说明采集端分配 buffer 时,提前考虑了 Rockchip encoder 的输入对齐要求。

如果不提前对齐,就可能变成:

CIF buffer 不满足 encoder 要求
        |
重新分配 encoder buffer
        |
copy / RGA 转换
        |
encoder 使用新 buffer

这样就破坏了零拷贝。


17.3 地址空间兼容

CIF 和 Encoder 是不同硬件模块。

它们要共享同一块 buffer,需要依赖:

dma-buf
sg_table
IOMMU mapping
DMA address / IOVA

这也是代码中同时出现:

#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-dma-sg.h>
#include <linux/iommu.h>
#include <soc/rockchip/rockchip_iommu.h>

的原因。


17.4 同步正确

零拷贝最大的问题之一是同步。

必须保证:

CIF 写完后,Encoder 才能读
Encoder 读完后,CIF 才能复用

这类同步可以通过:

中断
vb2 buffer 状态
dma-fence
sync_file
显式 fence fd

来实现。

源码中出现:

#include <linux/dma-fence.h>
#include <linux/sync_file.h>

说明该驱动或相关链路具备和 fence 同步机制结合的可能。


18. 从这段代码看驱动设计思路

虽然源码片段不完整,但已经能看出几个明确设计方向。

第一,驱动以 V4L2 为上层接口

通过 V4L2 ioctl 向用户态暴露能力,用户不用直接操作硬件寄存器。

第二,使用 vb2 管理 DMA buffer

这说明数据通路是面向高性能视频采集设计的,不是普通 CPU 拷贝模型。

第三,格式表是驱动和硬件之间的桥

out_fmts[] 把 V4L2 fourcc 转换成 CIF 硬件寄存器需要的格式值。

第四,IOMMU 和 dma-buf 是跨模块共享基础

Camera、RGA、Encoder、Display 这类硬件要共享 buffer,离不开 DMA 地址映射和 buffer 生命周期管理。

第五,高度 16 对齐直接服务于零拷贝

这不是随便写的宏,而是为了让 encoder 可以直接使用 camera 采集出来的 DMA buffer。


19. 调试 Rockchip CIF/V4L2 时重点看什么?

19.1 看格式是否一致

需要确认:

Sensor 输出格式
MIPI CSI2 接收格式
CIF 输出格式
V4L2 fourcc
用户态设置格式
后级 encoder 输入格式

重点关注:

NV12 / NV21
NV16 / NV61
YUYV / UYVY
RAW8 / RAW10 / RAW12

19.2 看 stride 和 sizeimage

很多花屏不是 sensor 的问题,而是:

bytesperline 算错
sizeimage 算错
UV plane offset 算错
height alignment 没处理

尤其是 NV12 和 RAW10/RAW12。


19.3 看 DMA 地址

需要确认:

DMA 地址是否正确
IOMMU 映射是否成功
buffer 是否物理连续
sg table 是否正确

19.4 看中断是否正常

一帧完成后,CIF 通常会产生 frame done interrupt。

如果中断不正常,用户态可能一直卡在:

VIDIOC_DQBUF

19.5 看是否真的零拷贝

判断零拷贝不要只看有没有 DMA,而要看有没有额外 memcpy。

重点排查:

用户态是否 memcpy 了一份图像
encoder 是否 import 同一个 dmabuf fd
中间是否经过 RGA 转换
格式是否被迫转换
buffer 对齐是否满足后级要求

20. 总结

这段 Rockchip CIF 驱动代码虽然只是片段,但已经能看出 Camera 驱动的核心主线:

通过 V4L2 暴露采集接口
通过 out_fmts[] 管理输出格式
通过 vb2 管理 DMA buffer
通过 CIF 硬件 DMA 写入 DDR
通过 IOMMU / dma-buf / fence 支撑跨模块零拷贝

从工程角度看,Camera 驱动的关键不是“能不能出图”,而是:

格式是否正确
stride 是否正确
buffer 是否对齐
DMA 地址是否正确
同步是否可靠
后级能否零拷贝使用

真正高性能的视频链路应该是:

Sensor -> CIF -> DMA-BUF -> Encoder / RGA / Display

而不是:

Sensor -> CIF -> buffer -> memcpy -> buffer -> encoder

对于嵌入式音视频系统来说,优化的核心往往不是写一个更快的拷贝函数,而是从架构上减少拷贝,让数据在硬件模块之间通过 DMA buffer 直接流动。

这就是 DMA 和零拷贝的真正价值。

内容概要:本文围绕可变桨叶四旋翼无人机的规范控制点对点运动模拟展开,重点研究优化推力分配策略在翻转动作中的应用性能比较。通过Matlab代码实现,构建了四旋翼动力学模型,并设计了多种控制算法以实现精确的姿态调整轨迹跟踪。研究对比了不同推力分配方案在执行高机动性翻转动作时的稳定性、能耗效率响应速度,旨在提升无人机在复杂飞行任务中的动态性能控制精度。该仿真研究为无人机飞控系统的设计优化提供了理论依据和技术支持。; 适合人群:具备一定自动控制理论基础和Matlab编程能力,从事无人机控制、飞行器动力学或机器人系统研究的科研人员及研究生。; 使用场景及目标:① 实现四旋翼无人机在三维空间中的精确点对点运动控制;② 对比分析不同推力分配策略在执行翻转等高难度动作时的控制效果能耗表现,优化飞行性能;③ 为无人机自主飞行、特技飞行及复杂环境下的机动控制提供算法验证平台。; 阅读建议:此资源以Matlab仿真为核心,建议读者结合相关控制理论知识,深入理解代码实现细节,重点关注动力学建模、控制律设计推力分配模块。在学习过程中,应动手调试参数,复现文中翻转动作的仿真结果,并尝试拓展至其他复杂飞行任务,以加深对无人机控制机理的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值