# rk_isp_study **Repository Path**: simple_ye/rk_isp_study ## Basic Information - **Project Name**: rk_isp_study - **Description**: 关于ISP学习的相关文件 - **Primary Language**: C - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-09-06 - **Last Updated**: 2025-09-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 三周学习RK ISP # 1. 第一周 ## 1.1 V4L2与Media Controller核心理论 ### 1.1.1 相关概念 + V4L2 (Video for Linux 2) :Linux内核中处理视频设备的一套标准的API(应用程序编程接口和驱动框架。简单来说,它连接了摄像头硬件和你的应用程序。 - **对于应用开发者**:不需关心摄像头型号,也不需知道如何操作硬件寄存器。只需要调用V4L2提供的标准open`, `ioctl等函数,就能获取图像、设置参数。 - **对于驱动开发者**:不需要从零创造接口。只需按V4L2框架要求,填充一系列的操作函数(`v4l2_file_operations`, `v4l2_ioctl_ops` 等),将硬件的具体操作与 V4L2 的标准请求对接起来即可。 + 摄像头传感器 (Sensor):将光信号转换为原始的数字图像数据(通常是 RAW Bayer 格式)。它通过 MIPI CSI 或 DVP 接口将数据发送给主控芯片。 + CSI/DVP 控制器:接收来自 Sensor 的数据流。 + ISP (Image Signal Processor):图像信号处理器。这是图像质量的“魔术师”,负责进行自动曝光(AE)、自动白平衡(AWB)、自动对焦(AF)(合称3A算法)、色彩校正、降噪等一系列复杂的处理,将原始的 RAW 数据转换成我们常见的 YUV 或 RGB 格式。 + Media Controller:是Linux内核中的一个更高层次的抽象,它提供了一个统一的方法来枚举和控制与视频设备相关组件如:Sensor、CSI、ISP: + 实体 (Entity):Sensor、ISP、CSI都是一个独立的“实体”。 + 端点 (Pad):每个组件都有自己的“收件箱 (Sink Pad)”和“发件箱 (Source Pad)”,用来接收和传递数据。 + 链接 (Link):**Media Controller**需要制定工作流程,比如规定Sensor的“发件箱”必须连接到CSI的“收件箱”。这个连接就是“链接”。 + 管道 (Pipeline):*当所有必要的链接都建立并**激活**后,就形成了一条从RAW图 (原始数据)到最终图片的完整流水线,这就是“管道”。 + 子设备 (Sub-device):Media Controller除了看整体流程,有时还需要跟组件单独沟通,比如告诉Sensor“你需要把曝光时间调长一点”。`/dev/v4l-subdevX` 就是通往每个专家的“专线电话”,让我们可以对单个硬件模块进行精细控制。 ### 1.1.2数据流拓扑图 RK3576上从Sensor到ISP再到内存的 ```markdown +-------------------------+ | IMX296 Sensor | (实体: 'm00_b_imx219 6-0010') | (输出 RAW 数据) | (设备: /dev/v4l-subdev3) +-------------------------+ | | MIPI CSI-2 物理总线 v +-------------------------+ | MIPI D-PHY (接收器) | (实体: 'rockchip-csi2-dphy1') | (物理信号转数字信号) | (设备: /dev/v4l-subdev2) +-------------------------+ | v +-------------------------+ | MIPI CSI-2 Controller | (实体: 'rockchip-mipi-csi2') | (解析MIPI协议) | (设备: /dev/v4l-subdev1) +-------------------------+ | v +-------------------------+ | RKCIF (图像采集接口) | (实体: 'rkcif-mipi-lvds1') | (连接CSI和ISP的桥梁) | (设备: /dev/v4l-subdev5) +-------------------------+ | v +-------------------------+ | RKISP (图像信号处理器) | (实体: 'rkisp-isp-subdev') | (RAW转YUV, 3A处理等) | (设备: /dev/v4l-subdev4) +-------------------------+ | v +-------------------------+ | ISP Main Path (主通道) | (实体: 'rkisp_mainpath') | (最终图像输出节点) | (设备: /dev/video64) +-------------------------+ | | DMA (直接内存访问) v +-------------------------+ | 系统内存 (RAM) | | (应用程序可访问的图像) | +-------------------------+ ``` ## 1.2 定位摄像头 ### 1.2.1 驱动提示信息 执行`dmesg | grep imx`,确认内核在启动时有尝试加载imx系列sensor驱动的日志,输出如下。 ```bash [ 5.113333] platform csi2-dphy2: Fixed dependency cycle(s) with /i2c@27300000/imx219@10 [ 5.198311] platform csi2-dphy4: Fixed dependency cycle(s) with /i2c@2ac40000/imx219@10 [ 5.354752] platform csi2-dphy1: Fixed dependency cycle(s) with /i2c@2ac90000/imx219@10 [ 5.355523] imx219 0-0010: driver version: 00.01.02 [ 5.355860] imx219 0-0010: Reading register 100 from 10 failed [ 5.356003] imx219 0-0010: Reading register 100 from 10 failed [ 5.362423] imx219 0-0010: Error -5 setting default controls [ 5.362434] imx219: probe of 0-0010 failed with error -5 [ 5.362471] imx219 1-0010: driver version: 00.01.02 [ 5.362632] imx219 1-0010: Reading register 100 from 10 failed [ 5.362773] imx219 1-0010: Reading register 100 from 10 failed [ 5.369188] imx219 1-0010: Error -5 setting default controls [ 5.369195] imx219: probe of 1-0010 failed with error -5 [ 5.369220] imx219 6-0010: driver version: 00.01.02 [ 5.393333] imx219 6-0010: Model ID 0x0219, Lot ID 0x228b82, Chip ID 0x08e5 [ 5.546200] imx219 6-0010: Consider updating driver imx219 to match on endpoints [ 5.546204] rockchip-csi2-dphy csi2-dphy1: dphy1 matches m00_b_imx219 6-0010:bus type 5 ``` 日志分析: + 失败的尝试 (I2C总线 0 和 1): ```bash [ 5.355860] imx219 0-0010: Reading register 100 from 10 failed ... [ 5.362434] imx219: probe of 0-0010 failed with error -5 ``` 系统根据设备树的配置,在I2C总线0和1上尝试与地址为 `0x10` 的设备通信,并试图加载 `imx219` 驱动。驱动程序的第一步通常是读取芯片的ID寄存器来“打招呼”,确认对方真的是IMX219。因为你的传感器是IMX296,所以“打招呼”失败了(`Reading register...failed`),导致驱动加载失败 (`probe...failed`)。 + 成功的现象 (I2C总线 6): ```bash [ 5.369220] imx219 6-0010: driver version: 00.01.02 [ 5.393333] imx219 6-0010: Model ID 0x0219, Lot ID 0x228b82, Chip ID 0x08e5 [ 5.546204] rockchip-csi2-dphy csi2-dphy1: dphy1 matches m00_b_imx219 6-0010... ``` 在I2C总线6上,`imx219` 驱动加载成功,读出一个 `Model ID 0x0219`。`dphy1 matches m00_b_imx219` 这条日志证明了**硬件连接(从Sensor到MIPI D-PHY)是通的** . ### 1.2.2 通过media-ctl工具 执行`for i in {0..14}; do echo "--- 正在检查 /dev/media$i ---"; media-ctl -d /dev/media$i -p 2>/dev/null; done`命令,以下是实际链路输出 #### 1.2.2.1 DHY相关 ```bash --- 正在检查 /dev/media1 --- Media controller API version 6.1.115 Media device information ------------------------ driver          rkcif model           rkcif-mipi-lvds1 serial bus info        platform:rkcif-mipi-lvds1 hw revision     0x0 driver version  6.1.115 Device topology - entity 1: stream_cif_mipi_id0 (1 pad, 11 links)             type Node subtype V4L flags 0             device node name /dev/video11         pad0: Sink                 <- "rockchip-mipi-csi2":1 [ENABLED]                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [] - entity 5: stream_cif_mipi_id1 (1 pad, 11 links)             type Node subtype V4L flags 0             device node name /dev/video12         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 [ENABLED]                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [] - entity 9: stream_cif_mipi_id2 (1 pad, 11 links)             type Node subtype V4L flags 0             device node name /dev/video13         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 [ENABLED]                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [] - entity 13: stream_cif_mipi_id3 (1 pad, 11 links)              type Node subtype V4L flags 0              device node name /dev/video14         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 [ENABLED]                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [] - entity 17: rkcif_scale_ch0 (1 pad, 11 links)              type Node subtype V4L flags 0              device node name /dev/video15         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 [ENABLED]                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [] - entity 21: rkcif_scale_ch1 (1 pad, 11 links)              type Node subtype V4L flags 0              device node name /dev/video16         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 [ENABLED]                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [] - entity 25: rkcif_scale_ch2 (1 pad, 11 links)              type Node subtype V4L flags 0              device node name /dev/video17         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 [ENABLED]                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [] - entity 29: rkcif_scale_ch3 (1 pad, 11 links)              type Node subtype V4L flags 0              device node name /dev/video18         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 [ENABLED]                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [] - entity 33: rkcif_tools_id0 (1 pad, 11 links)              type Node subtype V4L flags 0              device node name /dev/video19         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 [ENABLED]                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [] - entity 37: rkcif_tools_id1 (1 pad, 11 links)              type Node subtype V4L flags 0              device node name /dev/video20         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 [ENABLED]                 <- "rockchip-mipi-csi2":11 [] - entity 41: rkcif_tools_id2 (1 pad, 11 links)              type Node subtype V4L flags 0              device node name /dev/video21         pad0: Sink                 <- "rockchip-mipi-csi2":1 []                 <- "rockchip-mipi-csi2":2 []                 <- "rockchip-mipi-csi2":3 []                 <- "rockchip-mipi-csi2":4 []                 <- "rockchip-mipi-csi2":5 []                 <- "rockchip-mipi-csi2":6 []                 <- "rockchip-mipi-csi2":7 []                 <- "rockchip-mipi-csi2":8 []                 <- "rockchip-mipi-csi2":9 []                 <- "rockchip-mipi-csi2":10 []                 <- "rockchip-mipi-csi2":11 [ENABLED] - entity 45: rockchip-mipi-csi2 (12 pads, 122 links)              type V4L2 subdev subtype Unknown flags 0              device node name /dev/v4l-subdev1         pad0: Sink                 [fmt:SRGGB10_1X10/1920x1080 field:none                  crop.bounds:(0,0)/1920x1080                  crop:(0,0)/1920x1080]                 <- "rockchip-csi2-dphy1":1 [ENABLED]         pad1: Source                 -> "stream_cif_mipi_id0":0 [ENABLED]                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 []         pad2: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 [ENABLED]                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 []         pad3: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 [ENABLED]                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 []         pad4: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 [ENABLED]                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 []         pad5: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 [ENABLED]                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 []         pad6: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 [ENABLED]                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 []         pad7: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 [ENABLED]                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 []         pad8: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 [ENABLED]                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 []         pad9: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 [ENABLED]                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 []         pad10: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 [ENABLED]                 -> "rkcif_tools_id2":0 []         pad11: Source                 -> "stream_cif_mipi_id0":0 []                 -> "stream_cif_mipi_id1":0 []                 -> "stream_cif_mipi_id2":0 []                 -> "stream_cif_mipi_id3":0 []                 -> "rkcif_scale_ch0":0 []                 -> "rkcif_scale_ch1":0 []                 -> "rkcif_scale_ch2":0 []                 -> "rkcif_scale_ch3":0 []                 -> "rkcif_tools_id0":0 []                 -> "rkcif_tools_id1":0 []                 -> "rkcif_tools_id2":0 [ENABLED] - entity 58: rockchip-csi2-dphy1 (2 pads, 2 links)              type V4L2 subdev subtype Unknown flags 0              device node name /dev/v4l-subdev2         pad0: Sink                 [fmt:SRGGB10_1X10/1920x1080@10000/300000 field:none]                 <- "m00_b_imx219 6-0010":0 [ENABLED]         pad1: Source                 -> "rockchip-mipi-csi2":0 [ENABLED] - entity 63: m00_b_imx219 6-0010 (1 pad, 1 link)              type V4L2 subdev subtype Sensor flags 0              device node name /dev/v4l-subdev3         pad0: Source                 [fmt:SRGGB10_1X10/1920x1080@10000/300000 field:none]                 -> "rockchip-csi2-dphy1":0 [ENABLED] ``` 具体拓扑如图所示: ![image-20250907102023599](https://share.note.youdao.com/yws/api/personal/file/WEB1d3543cf989765199dc05e909fe8059c?method=download&shareKey=4507038f55de55b1ae4f234557689839) 1. 核心硬件:`rockchip-mipi-csi2` (实体 45):这是整个系统的**数据分发中心**。你可以把它想象成一个非常智能的**视频路由器** 🎛️。 + 输入 (`pad0: Sink`): + 接收来自 `"rockchip-csi2-dphy1":1` 的数据。这是 MIPI 摄像头的物理接口,是整个流程的数据源头。 + 输入数据格式为 `SRGGB10_1X10/1920x1080`,这表示:10-bit 的 RAW 图像格式(Bayer 格式);图像分辨率为 1080p + 输出 (`pad1` 到 `pad11: Source`):它有 11 个输出端口(pad1 到 pad11)每个输出端口都可以独立地将 1080p 的视频流路由到不同的处理单元 2. 输出处理单元 (实体 1 到 41共11个对应rockchip-mipi-csi2的11个pad):这些是最终的视频处理和输出模块,它们接收来自 `rockchip-mipi-csi2` 的数据,并生成应用程序可以访问的 `/dev/videoX` 设备节点。 + 主视频流 (`stream_cif_mipi_idX`):共 4 个(id0~3)。通常用于输出未经缩放或经过主要 ISP 处理的高质量视频流。 + 缩放通道 (`rkcif_scale_chX`):共 4 个(ch0~3)。图像缩放。应用程序可从节点获取不同分辨率的视频流(例,一个高清录制,一个低清预览)。 + 工具通道 (`rkcif_tools_idX`):共有 3 个(id0 到 id2)。用于输出 ISP 算法需要的**统计数据**,比如自动对焦、自动曝光、自动白平衡(3A)等算法所需的数据。 3. 数据源头 (实体 58 和 63):这部分描述了视频数据是如何产生的 - `m00_b_imx219 6-0010` (实体 63):明确指出传感器型号为 Sony IMX219,挂在 I2C6 地址0x10,用于控制传感器。:它是所有数据的最顶层源头 。它输出 1080p 30fps (`@10000/300000`) 的 RAW 格式视频。 - `rockchip-csi2-dphy1` (实体 58):是 MIPI CSI-2 接口的物理层 (D-PHY)。就像一个“码头”,接收来自 IMX219 的数据,然后将其传递给 `rockchip-mipi-csi2` 。 #### 1.2.2.2 ISP相关 ```bash --- 正在检查 /dev/media6 --- Media controller API version 6.1.115 Media device information ------------------------ driver rkisp-vir1 model rkisp1 serial bus info platform:rkisp-vir1 hw revision 0x0 driver version 6.1.115 Device topology - entity 1: rkisp-isp-subdev (4 pads, 10 links) type V4L2 subdev subtype Unknown flags 0 device node name /dev/v4l-subdev4 pad0: Sink [fmt:SRGGB10_1X10/1920x1080 field:none crop.bounds:(0,0)/1920x1080 crop:(0,0)/1920x1080] <- "rkisp_rawrd0_m":0 [] <- "rkisp_rawrd2_s":0 [] <- "rkcif-mipi-lvds1":0 [ENABLED] pad1: Sink <- "rkisp-input-params":0 [ENABLED] pad2: Source [fmt:YUYV8_2X8/1920x1080 field:none colorspace:smpte170m quantization:full-range crop.bounds:(0,0)/1920x1080 crop:(0,0)/1920x1080] -> "rkisp_mainpath":0 [ENABLED] -> "rkisp_selfpath":0 [ENABLED] -> "rkisp_ldcpath":0 [] -> "rkisp_iqtool":0 [ENABLED] pad3: Source -> "rkisp-statistics":0 [ENABLED] -> "rkisp-pdaf":0 [ENABLED] - entity 6: rkisp_mainpath (1 pad, 1 link) type Node subtype V4L flags 0 device node name /dev/video64 pad0: Sink <- "rkisp-isp-subdev":2 [ENABLED] - entity 12: rkisp_selfpath (1 pad, 1 link) type Node subtype V4L flags 0 device node name /dev/video65 pad0: Sink <- "rkisp-isp-subdev":2 [ENABLED] - entity 18: rkisp_ldcpath (1 pad, 1 link) type Node subtype V4L flags 0 device node name /dev/video66 pad0: Sink <- "rkisp-isp-subdev":2 [] - entity 24: rkisp_iqtool (1 pad, 1 link) type Node subtype V4L flags 0 device node name /dev/video67 pad0: Sink <- "rkisp-isp-subdev":2 [ENABLED] - entity 30: rkisp_rawrd0_m (1 pad, 1 link) type Node subtype V4L flags 0 device node name /dev/video68 pad0: Source -> "rkisp-isp-subdev":0 [] - entity 36: rkisp_rawrd2_s (1 pad, 1 link) type Node subtype V4L flags 0 device node name /dev/video69 pad0: Source -> "rkisp-isp-subdev":0 [] - entity 42: rkisp-statistics (1 pad, 1 link) type Node subtype V4L flags 0 device node name /dev/video70 pad0: Sink <- "rkisp-isp-subdev":3 [ENABLED] - entity 48: rkisp-input-params (1 pad, 1 link) type Node subtype V4L flags 0 device node name /dev/video71 pad0: Source -> "rkisp-isp-subdev":1 [ENABLED] - entity 54: rkisp-pdaf (1 pad, 1 link) type Node subtype V4L flags 0 device node name /dev/video72 pad0: Sink <- "rkisp-isp-subdev":3 [ENABLED] - entity 60: rkcif-mipi-lvds1 (1 pad, 1 link) type V4L2 subdev subtype Unknown flags 0 device node name /dev/v4l-subdev5 pad0: Source [fmt:SRGGB10_1X10/1920x1080@10000/300000 field:none] -> "rkisp-isp-subdev":0 [ENABLED] ``` 具体拓扑如图所示: ![image-20250907103705048](https://share.note.youdao.com/yws/api/personal/file/WEBc9c71a4b45994bf07b6d73731712d8da?method=download&shareKey=d139107953481e328ebebe41ac90b342) 1. 核心硬件:rkisp-isp-subdev (实体 1):是整个系统的**大脑和心脏**,负责接收RAW 图像,按照控制参数进行图像处理,然后输出处理后的视频和统计数据。 + 输入 (`pad0: Sink`):接收原始图像数据的地方 + 状态为 **`[ENABLED]`** ,表明正从一个**实时的摄像头接口**获取 1080p 分辨率的 RAW 格式(`SRGGB10_1X10`)的数据。 + 也列出了 `rkisp_rawrd0_m` 和 `rkisp_rawrd2_s` 这两个“冷冻食材库”(从内存读取 RAW 图像),但它们没有被启用,说明当前用的是新鲜食材。 + 输入 (`pad1: Sink`):接收来自 `rkisp-input-params` 的指令。这就像主厨正在阅读的**菜谱**,告诉他曝光度、白平衡、增益等应该如何设置。 + 输出 (pad2: Source):输出处理后视频的地方,ISP 将 RAW 图像处理成了 `YUYV8_2X8` 格式(一种常见的 YUV 视频格式),分辨率依然是 1080p。这个出口同时向多个地方输出 + `rkisp_mainpath (实体 6)`: **主菜**,通常是最高质量的视频流。 + `rkisp_selfpath`(实体 12): **副菜或小样**,通常用于视频预览。 + `rkisp_iqtool`(实体 24)**: 提供给“**美食评论家**”(图像质量调试工具)的特供菜。 + `rkisp_ldcpath(实体 18)`: **特殊处理**(镜头畸变校正),当前未启用。 + 输出 (pad3: Source):输出统计数据,而不是视频。 + `rkisp-statistics`(实体 42)**: 输出用于 **3A 算法**(自动曝光、自动白平衡、自动对焦)的统计数据。帮助自动调节过程。 + `rkisp-pdaf`(实体 54): 输出用于**相位检测自动对焦**(PDAF)的专用数据,帮助相机更快地对焦。 2. 数据来源 + **`rkcif-mipi-lvds1` (实体 60,连接实体1的pad0)**: **物理摄像头接口**,是当前**启用的**数据来源。它正在以 30fps 的帧率提供 1080p 的 RAW 视频流。 + **`rkisp-input-params` (实体 48,连接实体1的pad1)**: 这是**参数输入通道** (`/dev/video71`),应用程序通过它来控制 ISP 的各种图像效果。 #### 1.2.2.3 后处理 ```bash --- 正在检查 /dev/media10 --- Media controller API version 6.1.115 Media device information ------------------------ driver          rkvpss-vir0 model           rkvpss0 serial bus info        platform:rkvpss-vir0 hw revision     0x0 driver version  6.1.115 Device topology - entity 1: rkvpss-subdev (2 pads, 5 links)             type V4L2 subdev subtype Unknown flags 0         pad0: Sink                 <- "rkisp-vir0-sditf":0 [ENABLED]         pad1: Source                 -> "rkvpss_scale0":0 [ENABLED]                 -> "rkvpss_scale1":0 [ENABLED]                 -> "rkvpss_scale2":0 [ENABLED]                 -> "rkvpss_scale3":0 [ENABLED] - entity 4: rkvpss_scale0 (1 pad, 1 link)             type Node subtype V4L flags 0             device node name /dev/video101         pad0: Sink                 <- "rkvpss-subdev":1 [ENABLED] - entity 8: rkvpss_scale1 (1 pad, 1 link)             type Node subtype V4L flags 0             device node name /dev/video102         pad0: Sink                 <- "rkvpss-subdev":1 [ENABLED] - entity 12: rkvpss_scale2 (1 pad, 1 link)              type Node subtype V4L flags 0              device node name /dev/video103         pad0: Sink                 <- "rkvpss-subdev":1 [ENABLED] - entity 16: rkvpss_scale3 (1 pad, 1 link)              type Node subtype V4L flags 0              device node name /dev/video104         pad0: Sink                 <- "rkvpss-subdev":1 [ENABLED] - entity 20: rkisp-vir0-sditf (1 pad, 1 link)              type V4L2 subdev subtype Unknown flags 0              device node name /dev/v4l-subdev0         pad0: Source                 [fmt:YUYV8_2X8/800x600 field:none colorspace:smpte170m quantization:full-range]                 -> "rkvpss-subdev":0 [ENABLED] ``` 具体拓扑如图所示: ![image-20250907105324893](https://share.note.youdao.com/yws/api/personal/file/WEBc867aa75540adb957bf4d8c281aa6a18?method=download&shareKey=93a4761595a7103625a45a41f3db85ad) 1. 核心硬件:rkvpss-subdev (实体 1):**核心处理单元**。可以看作是一个智能分配器 + 输入 (`pad0: Sink`):它通过这个接口接收视频数据。`<- "rkisp-vir0-sditf":0 [ENABLED]` 这行表示,它正从一个名为 `rkisp-vir0-sditf` 的上游模块**接收**视频流,并且这个连接是**激活**的。 + 输出 (`pad1: source`):它通过这个接口分发处理后的视频数据。可以看到,它同时连接到了 `rkvpss_scale0`、`1`、`2`、`3` 四个下游模块,并且所有连接都是**激活**的。这完美地展示了它**一进四出**的能力。 2. 数据I/O + **`rkisp-vir0-sditf` (实体 20,连接实体1的pad0)**: 本次处理的**视频源**。可以看出,这个视频流来自 **ISP (图像信号处理器)**`[fmt:YUYV8_2X8/800x600]` 这部分信息非常关键,它告诉我们输入的视频格式是 **YUYV** (一种常见的 YUV 格式),分辨率为 **800x600**。 + **`rkvpss_scale0` 到 `rkvpss_scale3` (实体 4, 8, 12, 16) 是**四个独立的缩放输出通道****: 每一个通道都对应一个操作系统可以访问的视频设备节点 (从 `/dev/video101` 到 `/dev/video104`)。应用程序可以同时打开这四个设备中的任意一个或多个,来获取**不同分辨率**的视频流。例如,一个应用可以从 `/dev/video101` 读取 800x600 的原始视频,同时另一个应用可以从 `/dev/video102` 读取一个被缩放成 320x240 的小尺寸视频流用于网络传输。 ### 1.2.3 后续所有操作的目标设备对 **总调度是哪个 media 设备?****`/dev/media6` (模型 `rkisp1`)** 是整个图像处理流程的**总调度和核心**。因为它代表了 **ISP (图像信号处理器)**,负责接收最原始的摄像头数据,执行最复杂的图像算法(如自动曝光、白平衡、降噪等),并决定输出哪些处理后的视频流和统计数据。 系统中没有唯一的“出图口”,而是有多路最终的视频输出,服务于不同目的。它们是: - **ISP 直接输出**: - **`/dev/video64` (`rkisp_mainpath`)**: 这是**主视频流**,通常是质量最高的、经过ISP完整处理的视频,用于录制或高质量编码。 - **`/dev/video65` (`rkisp_selfpath`)**: 这是**次视频流**,通常用于实时预览。 - **经过 VPSS 后处理的输出**: - **`/dev/video101` 至 `/dev/video104`**: 四路是 ISP 视频流经 **VPSS (视频后处理子系统)** 硬件缩放后的输出。应用可以按需从中获取不同分辨率的视频。 pipeline如下图: ![image-20250907154228423](https://share.note.youdao.com/yws/api/personal/file/WEBad1ddde1975b7526d40ce6bf0983944e?method=download&shareKey=8970a982a8684c73a2360bc1a16796c5) ## 1.3 设备树(DTS) 阅读设备树,将硬件原理图与软件配置完全对应起来,kernel6.1/arch/arm64/boot/dts/rockchip/rk3576-evm.dts。 ```c &i2c6 { //这是一个引用(phandle)。它表示我们正在修改一个已经在别处定义好的 i2c6 节点。这个节点代表系统中的第 6 个 I2C 控制器 status = "okay"; // 启用该 I2C 总线控制器。如果设置为 "disabled",内核将忽略这个设备。 /* pinctrl-names 和 pinctrl-0: 这两行是用来配置引脚复用(Pin Control 或 Pin Muxing)的 */ pinctrl-names = "default"; pinctrl-0 = <&i2c6m0_xfer>; // 指定了在 "default" 状态下,需要应用的引脚配置。<&i2c6m0_xfer> 引用了另一个设备树节点,该节点详细定义了需要将哪几个物理引脚配置为 I2C6 的 SDA(数据线)和 SCL(时钟线)功能。简而言之,它确保了正确的物理引脚被连接到 I2C6 控制器上。 imx219_1: imx219@10 { //摄像头传感器节点 通过这个标签,设备树的其他部分可以使用 &imx219_1 来引用这个摄像头节点。imx219@10: 这是节点的标准名称,格式为 @。imx219: 节点名,通常与设备或驱动的名称相关。@10: 这是单元地址。在 I2C 总线中,它代表设备的 7位 I2C 从机地址,这里是 0x10。CPU 就是通过这个地址在 I2C 总线上与摄像头进行通信的。 compatible = "sony,imx219"; //内核会用它来查找匹配的设备驱动程序。当内核启动时,它会扫描所有已加载的驱动,寻找一个声称可以处理 "sony,imx219" 这种设备的驱动。一旦找到,就会加载该驱动来管理这个摄像头。 reg = <0x10>; clocks = <&imx219_clk>; status = "okay"; //启用这个摄像头设备 reset-gpios = <&xl9555 9 GPIO_ACTIVE_HIGH>; /* &xl9555: 表示复位引脚连接在一个 GPIO 扩展芯片上(由 xl9555 节点定义)。9: 是该扩展芯片上的第 9 个 GPIO 引脚。GPIO_ACTIVE_HIGH: 表示高电平有效,即把这个 GPIO 拉高会使摄像头复位。 */ rockchip,camera-module-index = <0>; // 多摄像头系统中为该摄像头分配索引号 0 rockchip,camera-module-facing = "back"; //指明这是后置摄像头 rockchip,camera-module-name = "CMK-OT1980-PX1"; //摄像头的模组名称 rockchip,camera-module-lens-name = "SHG102"; // 镜头的名称。这些信息可能会被用户空间的程序(如相机 App)用来加载特定的校准参数。 // I2C 总线只用于控制,真正的图像数据是通过高速的 MIPI CSI-2 接口传输的。port 节点就是用来描述这种复杂的数据连接的。 port { //代表设备的一个数据端口,这里是摄像头的 MIPI 输出端口。 camera1_out: endpoint { // endpoint 代表这个端口的一个连接端点。camera1_out 是这个端点的标签。 link-frequencies = /bits/ 64 <456000000>; // 指定了 MIPI D-PHY 物理层链路的数据传输速率。这里的 456000000 表示 456 MHz 的时钟频率。这是一个 64 位的数值。 remote-endpoint = <&mipi_dphy1_in_imx219>; // 这是描述连接关系的关键。它指向了数据链路的另一端——SoC 上的 MIPI 接收器端点。内核通过解析这个属性,就能建立起从摄像头传感器到 SoC 内部图像处理单元的完整数据通路图。 data-lanes = <1 2>; //定义了使用哪些 MIPI 数据通道。这里表示使用了 2 条数据通道(lane),分别是 lane 1 和 lane 2 }; }; }; }; i2c6 { /omit-if-no-ref/ i2c6m0_xfer: i2c6m0-xfer { rockchip,pins = /* i2c6_scl_m0 */ <0 RK_PA2 11 &pcfg_pull_none_smt>, /* i2c6_sda_m0 */ <0 RK_PA5 11 &pcfg_pull_none_smt>; }; }; imx219_clk: imx219-clk { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <24000000>; clock-output-names = "imx219_clk"; }; &i2c0 { pinctrl-0 = <&i2c0m0_xfer>; status = "okay"; xl9555: xl9555@20 { status = "okay"; compatible = "nxp,pca9555"; reg = <0x20>; gpio-controller; #gpio-cells = <2>; gpio-group-num = <200>; }; }; ``` ## 1.4 命令行 前言:摄像头输出的能力是根据模组来定,那么如何知道isp可以接收什么样的数据呢,使用 `v4l2-ctl` 查询 Sub-device 的能力 **`media-ctl` (流水线施工图 & 调度器):** - **功能:** 它的作用是查看和搭建硬件层面的数据流管道。你可以用它来查看厨房里有哪些专家(实体)、他们的收发件箱(Pad)是如何连接的(Link)。 - **核心命令:** - `media-ctl -p`: 打印施工图。这是你到任何一个新厨房要做的第一件事,让你对整体结构一目了然。 - `media-ctl -l "..."`: 连接水管。用它来建立或断开专家之间的链接。 - `media-ctl -V "..."`: 规定接口标准。设置每个收发件箱的数据格式(如分辨率、图像格式),确保上下游能正确交接。 **`v4l2-ctl` (成品质量检测器):** - **功能:** 当流水线搭建好后,它负责与最终的出菜口 (`/dev/videoX`) 交互,用来检查和获取最终的成品。 - **核心命令:** - `v4l2-ctl --list-formats-ext`: 查看菜单。问问出菜口能提供哪些格式(分辨率、图像格式)的菜品。 - `v4l2-ctl --list-ctrls`: 查看调料。看看你能对这道菜做哪些微调(如亮度、对比度)。 - `v4l2-ctl --set-ctrl <...>`: 加点盐。调整某个参数的值。 - `v4l2-ctl --stream-mmap ...`: 打包带走。从出菜口取一份样品(抓取一帧图像)并保存下来。 1. 根据上一节,确定了`rkisp-isp-subdev`对应 `/dev/v4l-subdev4`。 2. `v4l2-ctl` 命令,带上 `--pad` 参数来查询这个子设备上特定“接口”的能力。`rkisp-isp-subdev` 是接收数据的一方,所以我们要查询它的 **输入接口(Sink Pad)**根据上节为 `0`,执行命令 ```bash v4l2-ctl -d /dev/v4l-subdev4 --list-formats-ext --pad 0 ``` 配置格式: 1. 观察拓扑: 运行`media-ctl -d /dev/mediaX -p`(X是你的media设备号),在纸上画出从sensor到`rkisp_mainpath`的简化拓扑图 2. 配置格式: 使用`media-ctl -V`命令,/dev/media6必须是isp的节点: ```bash # 设置rkcif的输出格式(RAW 10bit, 1920x1080) media-ctl -d /dev/mediaX -V '"rkcif-mipi-lvds1":0 [fmt:SRGGB10_1X10/1920x1080]' # 设置ISP的输入格式(必须与上一级输出完全一致) media-ctl -d /dev/mediaX -V '"rkisp-isp-subdev":0 [fmt:SRGGB10_1X10/1920x1080]' ``` 3. **抓取图像**: 使用`v4l2-ctl`从最终的video节点抓图。 ```bash v4l2-ctl -d /dev/video64 --set-fmt-video=width=1920,height=1080,pixelformat=NV12 --stream-mmap --stream-count=1 --stream-to=test.yuv ``` # 2. 第二周 - **理论学习:驱动的“秘密” (预计1天)** - 精读第二部分“为什么不匹配的驱动也能工作?”和“为什么必须移植驱动?”章节,深刻理解软件与硬件匹配的重要性。 - **实战任务二:驱动源码初探 (预计2天)** - **目标**: 找到现有驱动,理解其结构,为移植做准备。 - **操作**: 1. 在您的内核源码中,找到`drivers/media/i2c/imx290.c`文件(或一个与你的sensor相近的驱动)。 2. **代码寻宝**: - 找到`imx290_of_match[]`,理解`compatible`字符串是如何将设备树和驱动“绑定”在一起的。 - 找到`imx290_probe()`函数,这是驱动的入口。看看驱动是如何通过I2C读取芯片ID的(搜索`v4l2_i2c_subdev_read`)。 - 找到`imx290_core_ops`或类似`v4l2_subdev_core_ops`的结构体,理解`s_power`(开关电源)、`s_stream`(开关流)等回调函数的作用。 - **产出**: 在`imx290.c`文件中添加注释,标记出您找到的关键部分,并写下您的理解。 #### 第二周达成标准 - 成功运行自己编写的C/C++程序,稳定采集到多帧图像。 - 能够清晰地向别人解释,为什么不匹配的驱动有时也能出图,以及这样做的严重弊端。 - 能够对着驱动代码,说出`probe`函数的大致流程,以及`compatible`字符串的作用。 # 2.1 V4L2应用编程 ### 2.1.1 V4L2应用开发核心流程:10步掌握数据采集 + 以下是几乎所有 V4L2 采集程序的标准流程(以最高效的内存映射`mmap`方式为例): 1. `open()` 打开设备 (`/dev/video64`) 2. `ioctl(VIDIOC_QUERYCAP)` 查询设备能力,确认支持视频采集和流式I/O。 + 确认它是否是"捕获设备",因为有些节点是输出设备 + 确认它是否支持mmap操作,还是仅支持read/write操作 3. `ioctl(VIDIOC_ENUM_FMT)` 枚举支持的格式,了解摄像头能力。 4. `ioctl(VIDIOC_S_FMT)` 设置格式,选择并设置期望的分辨率和像素格式。 5. `ioctl(VIDIOC_REQBUFS)` 请求缓冲区,向驱动申请若干帧内存,APP可以申请很多个buffer,但是驱动程序不一定能申请到 6. `ioctl(VIDIOC_QUERYBUF)` 和 `mmap()`,循环查询每个缓冲区的物理信息,并将其映射到应用的虚拟地址空间。 * 如果申请到了N个buffer,这个ioctl就应该执行N次 * 执行mmap后,APP就可以直接读写这些buffer 7. `ioctl(VIDIOC_QBUF)` 将缓冲区放入队列,在开始前,把所有缓冲区都“还给”驱动,让其填充数据。如果申请到了N个buffer,这个ioctl就应该执行N次 8. `ioctl(VIDIOC_STREAMON)` 启动采集,命令硬件开始工作。 9. 循环采集: 使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表" * poll/select * ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer * 处理:前面使用mmap映射了每个buffer的地址,处理时就可以直接使用地址来访问buffer * ioclt VIDIOC_QBUF:把buffer放入"空闲链表" 10. `ioctl(VIDIOC_STREAMOFF)` 停止采集。 + **核心数据结构** - `struct v4l2_format`: 重点关注`fmt.pix`中的`width`, `height`, `pixelformat`。 - `struct v4l2_requestbuffers`: 重点关注`count`, `memory`。 - `struct v4l2_buffer`: 最核心的结构。`QUERYBUF`时,关心`index`和`m.offset`;`DQBUF`时,关心`index`和`bytesused`。 代码参照v4l2_capture.c。 ### 2.1.1 几个关键点 #### 2.1.1.1 支持的格式 ```bash ---------- 支持的格式 ---------- 格式: UYVY 4:2:2 (UYVY) 格式: Y/UV 4:2:2 (NV16) 格式: Y/VU 4:2:2 (NV61) 格式: Y/VU 4:2:0 (NV21) 格式: Y/UV 4:2:0 (NV12) 格式: Y/VU 4:2:0 (N-C) (NM21) 格式: Y/UV 4:2:0 (N-C) (NM12) ``` 这些格式都属于 **YUV 色彩空间**,它将图像信息分为亮度(Luminance, Y)和色度(Chrominance, U和V)两部分。人眼对亮度的敏感度远高于色度,因此可以通过减少色度的采样率来压缩数据,这就是所谓的“色度二次采样” (Chroma Subsampling)。 我们可以从三个维度来理解它们的区别:**采样方式**、**内存布局**和**UV顺序**。 1. 采样方式 (4:2:2 vs 4:2:0)。这是最重要的区别,它决定了色度信息的分辨率和数据量。 + 4:2:2 (如 UYVY, NV16, NV61): + 含义:水平方向上,每两个Y分量,共享一组UV分量;垂直方向不压缩。 + 特点:保留了完整的垂直色度分辨率,图像质量较高,但数据量也较大。 + 数据量:平均每个像素占用 (8Y + 16UV/2) = 16位 (2字节)。 + 4:2:0 (如 NV12, NV21, NM12, NM21): - 含义:水平和垂直方向上,每 2x2 四个Y分量,共同享用一组UV分量。 - 特点:这是最主流的视频压缩格式,在人眼几乎无法察觉差异的情况下,极大地压缩了数据量。 - 数据量:平均每个像素占用 (8Y + 16UV/4) = 12位 (1.5字节)。 2. 内存布局 (Packed vs Semi-Planar):这决定了Y、U、V数据在内存中是如何存放的。 + 打包格式 (Packed) - `UYVY 4:2:2` - 布局:Y, U, V 数据交错存储在一个连续的内存块(一个平面)中。 - `UYVY` 的存储顺序:`U Y0 V Y1`, `U Y2 V Y3`, ... 每4个字节代表2个像素。处理起来比较直观,但如果要单独提取Y分量会比较麻烦。 + 半平面格式 (Semi-Planar) - `NV` 系列 - 布局:数据存储在两个内存块(两个平面)中。 - 第一个平面:连续存放所有的Y分量。这个平面就是一张完整的灰度图。 - 第二个平面:连续存放所有的UV分量,并且U和V交错排列。 - 优点:非常适合AI推理和图像处理,因为可以直接把Y平面(灰度图)送去处理,而无需进行数据重排。 3. UV顺序 (NV12/NV16 vs NV21/NV61),对于半平面格式,唯一的区别就是第二个平面里U和V的顺序。 + `NV12` 和 `NV16` (`Y/UV`): UV平面中,U在前,V在后。存储顺序是:`U0 V0`, `U1 V1`, ... + `NV21` 和 `NV61` (`Y/VU`): UV平面中,V在前,U在后。存储顺序是:`V0 U0`, `V1 U1`, ... (安卓系统早期常用) 4. 总结与区分 `NM12` 和 `NM21` 中的 "N-C" 指的是 **Non-Contiguous(非连续)**。这是硬件(如DMA或GPU)为了提高存取效率而采用的一种特殊的内存排列方式(如分块存储Tiled)。从应用层视角看,它的颜色信息和NV12/NV21一样,但在进行内存拷贝等操作时,不能简单地按 `width x height x 1.5` 计算大小,而必须严格使用驱动返回的 `bytesused` 字段。 #### 2.1.1.2 MEMORY方式 | | 核心思想 | **工作流程** | 优点 | 缺点 | 使用场景 | | --------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | `V4L2_MEMORY_MMAP` (内存映射 - 主流高效) | 驱动程序在内核空间分配一块物理连续的内存作为缓冲区,然后应用程序通过 `mmap()` 系统调用,将这块内核的内存**映射**到自己的虚拟地址空间。 | 1. 应用通过 `ioctl(VIDIOC_REQBUFS)` 请求驱动分配若干个缓冲区。
2. 驱动在内核里分配好内存。
3. 应用通过 `ioctl(VIDIOC_QUERYBUF)` 获取每个缓冲区的物理偏移等信息。
4. 应用调用 `mmap()`,将这些缓冲区一一映射到自己的地址空间,得到可以直接访问的指针。
5. 硬件(DMA)直接将图像数据写入这些内核缓冲区。
6. 应用通过映射后的指针直接读取数据,全程没有发生从内核到用户的内存拷贝。 | 零拷贝 (Zero-Copy): 性能极高,因为CPU无需参与数据搬运。
实现简单: 是V4L2中最成熟、最标准的用法。 | 内存由驱动管理: 应用程序无法控制这块内存具体分配位置,也无法将一块预先分配好的内存交给驱动使用。 | 绝大多数标准的视频采集应用。当只是想从摄像头获取图像并进行处理时,这是默认的最佳选择。 | | `V4L2_MEMORY_USERPTR` (用户指针 - 灵活但复杂) | 应用程序自己在用户空间分配内存(例,使用 `malloc`),然后把这些内存块指针告诉驱动。 | 1. 应用自己调用 `malloc` 或其他方式分配好若干个缓冲区。
2. 应用通过 `ioctl(VIDIOC_REQBUFS)` 告诉驱动,接下来将使用用户指针模式。
3. 应用将自己分配的内存地址填充到 `v4l2_buffer` 结构体中,然后通过 `ioctl(VIDIOC_QBUF)` 将这些指针“注册”给驱动。
4. 驱动程序需要进行额外的工作,将用户空间的内存“锁定”(Pin),并转换成硬件DMA可以识别的物理地址。
5. 硬件(DMA)将数据写入这些用户空间的内存。
| 内存由应用控制: 应用程序可以完全控制内存的分配,比如使用一些特殊的对齐方式,或者直接使用从其他库(如图形库)获取的现成内存块。 | 性能可能更低: 驱动为了让硬件能访问用户内存,内部操作非常复杂,可能需要创建复杂的映射表(scatter-gather list),这个过程会带来额外的开销。在某些架构上,如果硬件DMA不能直接访问任意用户内存,甚至可能触发隐藏的拷贝。
实现复杂: 对应用开发者要求更高,需要自己管理内存生命周期。 | 当图像数据需要和另一个不兼容MMAP的模块直接交互时。例如,一个特殊的图形处理库已经为你分配好了一块内存,你希望摄像头直接把数据采集到这块内存里,避免之后再做一次拷贝。 | | `V4L2_MEMORY_DMABUF` (DMA缓冲区 - 现代零拷贝) | 这是目前在Linux系统中实现**跨设备、跨驱动零拷贝**的最先进机制。它将一块内存封装成一个由**文件描述符(File Descriptor, fd)**来代表的、可共享的DMA缓冲区。 | 1. **导出方 (Exporter)**: 系统中的一个设备驱动(比如GPU、视频解码器)分配了一块DMA内存,并将其“导出”,返回给应用程序一个`fd`。
2. **导入方 (Importer)**: 我们的V4L2应用程序拿到这个`fd`后,将其填充到 `v4l2_buffer` 结构体中。
3. **入队**: 通过 `ioctl(VIDIOC_QBUF)` 将这个包含`fd`的缓冲区交给相机驱动。
4. **共享**: 相机驱动通过内核机制,“导入”这个`fd`所代表的物理内存,并让自己的DMA可以直接向这块**共享内存**中写入数据。 | **真正的跨设备零拷贝**: 数据可以在**相机 -> GPU**、**相机 -> 硬件编码器**、**视频解码器 -> 视频编码器**之间无缝流转,CPU完全不参与,性能达到极致。 | **编程模型最复杂**: 需要应用层去协调不同设备驱动之间的缓冲区导出和导入。 | 高性能的多媒体处理流水线。例如:
**视频会议**: 相机采集数据(DMABUF) -> 硬编码器压缩(DMABUF) -> 网络发送。
**AI视觉**: 相机采集数据(DMABUF) -> NPU/GPU进行AI推理(DMABUF) -> 编码或显示。 | 一个具体的例子:针对 **“相机采集 -> ISP处理 -> NPU推理 -> DRM显示”** 这一经典场景,最合适、也是唯一正确的选择是 **`V4L2_MEMORY_DMABUF`**。 + 为什么必须是 `DMABUF`? + **`MMAP` 的局限**:`MMAP` 实现了相机驱动和**用户应用程序**之间的零拷贝。但数据到了应用程序后,如果你想把它送给NPU,就需要CPU进行一次`memcpy`,从相机缓冲区拷贝到NPU的输入缓冲区。同理,NPU处理完后,也需要CPU把结果从NPU的输出缓冲区拷贝到DRM的显示缓冲区。在高清视频流下,这两次拷贝会消耗大量的CPU资源和内存带宽,导致性能瓶颈。 + **`DMABUF` 的威力**:`DMABUF` 的设计初衷就是为了解决**跨设备、跨驱动**之间的数据共享问题。它将一块物理内存封装成一个“令牌”(即文件描述符`fd`),任何支持`DMABUF`的驱动(V4L2、NPU、DRM)都可以通过这个“令牌”直接访问同一块物理内存,从而彻底消灭CPU在中间的拷贝操作。 + 详细流程:一个“零拷贝”流水线是如何诞生的 1. 从终点开始——向DRM申请“画板” (Display Buffers)** 1. 应用程序首先与DRM(Direct Rendering Manager,Linux的显示框架)通信。 2. 请求DRM驱动分配若干个用于屏幕显示的缓冲区(“画板”)。DRM驱动最清楚显示硬件需要什么样的内存(比如特定的对齐、tiled格式等)。 3. DRM驱动分配好内存后,通过 **`drmPrimeHandleToFD`** 接口,将每一块“画板”**导出 (Export)** 成一个 `DMABUF fd`。 4. 现在,应用程序手里握着几个`fd`,每个`fd`都代表着一块可以直接显示在屏幕上的物理内存。 2. 将“画板”同时作为NPU的“画布” (NPU Output Buffers) 1. 应用程序调用NPU驱动的API(例如瑞芯微的RKNN API)。 2. 它将从DRM获取的 `DMABUF fd` **导入 (Import)** 到NPU驱动中,并告诉NPU:“这些`fd`是你的**输出缓冲区**”。 3. NPU驱动通过内核机制,理解了这些`fd`背后是哪些物理内存,并配置好自己的硬件,使其可以直接向这些内存中写入数据。 4. 至此,我们已经建立了 **NPU -> DRM** 的零拷贝通道。NPU处理完的任何结果,只要写入这些缓冲区,就可以直接被显示出来。 3. 向V4L2/ISP申请“原材料盘子”并交给NPU (V4L2 Output -> NPU Input) 1. 应用程序现在需要为NPU的输入准备缓冲区。它再次调用NPU驱动的API,请求分配若干个输入缓冲区。 2. NPU驱动分配好这些输入缓冲区后,同样将它们**导出**为 `DMABUF fd`。 3. 应用程序拿到这些代表NPU输入的`fd`后,转向V4L2相机驱动。 4. 在 `ioctl(VIDIOC_REQBUFS)` 之后,应用程序将 `v4l2_buffer` 结构体的 `memory` 字段设置为 `V4L2_MEMORY_DMABUF`。 5. 然后,将从NPU获取的输入`fd`填入 `v4l2_buffer` 的 `m.fd` 字段。 6. 通过 `ioctl(VIDIOC_QBUF)` 将缓冲区交给相机驱动。相机驱动会导入这些`fd`,配置ISP和DMA,将处理好的图像数据直接写入这些缓冲区。 7. 至此,我们也建立了 Camera/ISP -> NPU 的零拷贝通道。 4. 启动流水线 1. 应用程序启动V4L2的视频流 (`VIDIOC_STREAMON`)。 2. 进入循环: - 从相机驱动获取一个已经填充好图像数据的缓冲区。此时获取到的`v4l2_buffer`结构体里,包含了是哪个`DMABUF fd`被填充好了。 - 应用程序以这个`fd`(或其索引)为参数,调用NPU的推理API,同时指定一个从DRM获取的`fd`作为输出。 - NPU硬件开始工作,它直接从第一个`fd`代表的物理内存中读取图像,并将推理结果直接写入第二个`fd`代表的物理内存中。 - NPU推理完成后,应用程序调用DRM的API,通知显示控制器刷新屏幕,内容就来自刚刚被NPU写入的那个`fd`。 - 最后,应用程序将第一个`fd`(即被NPU读取完的那个)再次通过`ioctl(VIDIOC_QBUF)` 交还给相机驱动,以便接收下一帧图像。 通过这一整套复杂的协同工作,视频数据流就像在预设好的物理管道中流动,CPU只负责调度,完全不参与繁重的数据搬运,从而实现了性能的最大化。 #### 2.1.1.3 单平面与多平面 + 概念:正确理解单平面(Single-Plane)和多平面(Multi-Plane)的区别,是编写健壮、高效V4L2应用程序的关键。首先,内核本身支持最多`VIDEO_MAX_PLANES`个平面(通常是8个)。核心区别:数据是如何在内存中“摆放”的想象一下,一张YUV图像包含了Y、U、V三种“颜料”。这两种API的区别,就在于如何存储这些“颜料”。 1. 单平面 API (Legacy API):将Y、U、V所有“颜料”混合在一起,存放在**一个连续的大内存块**(一个平面)中。 + **内存布局**:数据是**交错(Interleaved)**存放的。 - 例如,对于`YUYV`格式,内存中的顺序是:`Y0 U0 Y1 V0, Y2 U1 Y3 V1, ...` - 对于`RGB24`格式,内存中的顺序是:`R0 G0 B0, R1 G1 B1, ...` + **对应的数据结构**: - 设置格式时使用 `struct v4l2_format` 的 `fmt.pix` 成员。 - 整个图像只有一组尺寸信息(`width`, `height`)和一个缓冲区大小(`sizeimage`)。 + **API特征**:使用V4L2最原始的`ioctl`命令,例如 `VIDIOC_QUERYCAP` 返回的能力是 `V4L2_CAP_VIDEO_CAPTURE`。 2. 多平面 API (MPLANE API):将Y、U、V这些“颜料”分开存放,放在**两个或多个独立的内存块**(多个平面)中。 + **内存布局**:数据是平面(Planar)或半平面(Semi-Planar)存放的。 - 例如,对于`NV12`格式(半平面): - **平面0**: 连续存放所有的Y分量 `Y0 Y1 Y2 ...` - **平面1**: 连续存放所有的UV分量,并且U和V交错 `U0 V0 U1 V1 ...` - 对于`I420`格式(平面): - **平面0**: 存放所有Y分量。 - **平面1**: 存放所有U分量。 - **平面2**: 存放所有V分量。 + **对应的数据结构**: - 设置格式时使用 `struct v4l2_format` 的 `fmt.pix_mp` 成员。 - `fmt.pix_mp` 包含一个 `v4l2_plane_pix_format` 数组,**每个平面都有自己独立的尺寸和大小信息**。 + **API特征**:需要使用带有 `MPLANE` 后缀的`ioctl`命令和能力标志,例如 `VIDIOC_QUERYCAP` 返回的能力是 `V4L2_CAP_VIDEO_CAPTURE_MPLANE`。 + 应用程序如何抉择?**答案是:应用程序不能随心所欲地选择,而必须根据你所使用的“像素格式(Pixel Format)”来决定。**驱动程序已经为每种像素格式规定好了应该使用哪套API。你的应用程序需要做的,是去“查询”并“遵守”这个规定。**标准抉择流程如下:** 1. **查询设备能力**: - 你的程序首先应该调用 `ioctl(VIDIOC_QUERYCAP)`。 - 检查返回的 `v4l2_capability` 结构体中的 `capabilities` 字段。 - 如果它包含 `V4L2_CAP_VIDEO_CAPTURE_MPLANE`,说明设备支持多平面API。 - 如果它包含 `V4L2_CAP_VIDEO_CAPTURE`,说明设备支持单平面API。 - (现代驱动通常两者都支持)。 2. **枚举可用格式 (最关键的一步)**: - 你的程序应该循环调用 `ioctl(VIDIOC_ENUM_FMT)` 来找出设备支持的所有像素格式。 - 在返回的 `v4l2_fmtdesc` 结构体中,检查 `flags` 字段。 - 如果 `flags` 包含 `V4L2_FMT_FLAG_MPLANE`,那么这个格式(例如`V4L2_PIX_FMT_NV12`)**必须**使用多平面API来处理。你需要使用 `v4l2_format` 里的 `fmt.pix_mp`,并且在请求和排队缓冲区时,为每个平面提供信息。 - 如果 `flags` **不包含** `V4L2_FMT_FLAG_MPLANE`,那么这个格式(例如`V4L2_PIX_FMT_YUYV`)**必须**使用单平面API来处理。你需要使用 `v4l2_format` 里的 `fmt.pix`。