1141 字
6 分钟
毕设日志(一) RV1106的RK_MPI视频流与AI推理链路分析

主要分析 luckfox_pico_rtsp_retinaface_osd 例程,探讨其如何实现摄像头采集、NPU 推理、硬件编码及 RTSP 推流的完整链路。

LuckfoxTECH
/
luckfox_pico_rkmpi_example
Waiting for api.github.com...
00K
0K
0K
Waiting...

1. 视频流链路概览#

该程序在硬件层面上构建了两条并行的核心路径,通过 RK_MPI(Rockchip Media Process Interface)协调不同硬件模块(VI、VENC、NPU、RGN)的交互。

路径名称数据流向核心功能
推流路径VI Chn0 → VENC → RTSP负责视频采集、硬件 H.264 编码及网络传输
推理路径VI Chn1 → CPU/NPU → RGN负责获取原始帧、图像预处理、AI 推理及结果绘制

2. VI 模块初始化与格式选择#

程序初始化了两个 VI 通道,分别进行推流与AI推理。

// vi init
vi_dev_init();
vi_chn_init(0, width, height);
vi_chn_init(1, width, height);

2.1 通道分配#

  • 通道 0:负责推流,直接绑定至编码器。
  • 通道 1:负责 AI 推理,供用户态程序主动获取帧数据。

2.2 像素格式与配置#

两个通道均设置为 RK_FMT_YUV420SP(即 NV12 格式)。采用 VI_V4L2_MEMORY_TYPE_DMABUF 以减少内存拷贝开销。

int vi_chn_init(int channelId, int width, int height) {
int ret;
int buf_cnt = 4;
// VI init
VI_CHN_ATTR_S vi_chn_attr;
memset(&vi_chn_attr, 0, sizeof(vi_chn_attr));
vi_chn_attr.stIspOpt.u32BufCount = buf_cnt;
vi_chn_attr.stIspOpt.enMemoryType = VI_V4L2_MEMORY_TYPE_DMABUF;
vi_chn_attr.stSize.u32Width = width;
vi_chn_attr.stSize.u32Height = height;
vi_chn_attr.enPixelFormat = RK_FMT_YUV420SP;
vi_chn_attr.enCompressMode = COMPRESS_MODE_NONE;
vi_chn_attr.u32Depth = 2;
ret = RK_MPI_VI_SetChnAttr(0, channelId, &vi_chn_attr);
ret |= RK_MPI_VI_EnableChn(0, channelId);
if (ret) {
printf("ERROR: create VI error! ret=%d\n", ret);
return ret;
}
return ret;
}

3. 推流通路 (VI Chn0 → VENC → RTSP)#

推流路径实现了纯硬件链路,数据在底层驱动中通过 SYS_Bind 自动流转。

3.1 硬件绑定逻辑#

通过 RK_MPI_SYS_Bind 将 VI 模块与 VENC 模块在内核层关联,避免了将原始 YUV 数据频繁拷贝到用户空间。

// bind vi to venc
stSrcChn.enModId = RK_ID_VI;
stSrcChn.s32DevId = 0;
stSrcChn.s32ChnId = 0;
stvencChn.enModId = RK_ID_VENC;
stvencChn.s32DevId = 0;
stvencChn.s32ChnId = 0;
s32Ret = RK_MPI_SYS_Bind(&stSrcChn, &stvencChn);

3.2 编码与传输#

  1. 硬件编码:VENC 接收到 NV12 数据,根据 RK_VIDEO_ID_AVC 配置进行 H.264 硬件压缩。
  2. 流获取GetMediaBuffer 线程通过 RK_MPI_VENC_GetStream(0, &stFrame, -1) 阻塞式获取码流。
  3. 推流:调用 rtsp_tx_video 将 H.264 数据包发送至客户端。

4. AI 推理路径 (VI Chn1 → NPU)#

推理路径涉及用户态的图像处理与 NPU 调度。

4.1 图像获取与预处理#

  • 获取数据RetinaProcessBuffer 线程通过 RK_MPI_VI_GetChnFrame 从通道 1 获取 NV12 帧。
  • 格式转换:利用 opencv-mobileNV12 转换为 BGR
  • 尺寸缩放:调用 cv::resize 将图像适配模型输入的 640×640640 \times 640 尺寸。

4.2 NPU 任务调度#

inference_retinaface_model 函数执行同步推理:CPU 调用后进入等待状态,直到 NPU 硬件完成计算并返回 od_results 结果。


5. OSD 叠加机制#

OSD(On-Screen Display)并未修改原始图像内存,而是通过 RGN(Region)模块 实现。

  • 原理:AI 推理得到的坐标结果被转化为 RGN 画布操作。
  • 硬件合成:RGN 模块将线框作为叠加层,在进入 VENC 编码前由硬件自动合入视频帧。这保证了 RTSP 流中自带检测框。

6. 模块同步机制#

监控对象实现机制关键函数/参数
VENC 编码完成阻塞等待 (Blocking)RK_MPI_VENC_GetStreams32MilliSec 参数设为 -1
NPU 推理完成同步调用 (Synchronous)inference_retinaface_model 直至计算结束才返回
VI 数据就绪循环轮询/阻塞在推理线程中主动请求帧数据 RK_MPI_VI_GetChnFrame

这些阻塞其实可以接受,毕竟本来就是在对应单独线程中的操作,而不是同一个线程中的同步操作。这个线程阻塞了就是阻塞了,也没有优化的必要。

7. 关于毕业设计软件部分#

在此基础上实现:WebRTC推流,显示在一个经过设计的网页里,通过该网页,可以动态调整模型类型(例如从yolov5切换为retainface)。

7.1 WebRTC#

复用 venc 的码流即可,引入第三方WebRTC库,准备选择libdatachannel

paullouisageneau
/
libdatachannel
Waiting for api.github.com...
00K
0K
0K
Waiting...

7.2 AI模型动态切换#

注意到luckfox提供的例程中,对于不同的AI模型,虽然推理、处理和绘制的逻辑有很大不同,但是都有几个实现这些功能的线程,也就是说,可以直接用线程隔离,而不是去做复杂的接口设计和动态切换。

这里牵扯到一个问题,就是RKNN模型的加载问题,如果只是简单的线程切换的话,所有RKNN模型在启动阶段就要加载进内存中,对于RV1106来说,这些内存占用不能忽视。

为此需要实现一个热加载逻辑,不仅仅要维护不同AI模型线程的切换,还有做到资源的管理,实现“先卸载、再加载”。

毕设日志(一) RV1106的RK_MPI视频流与AI推理链路分析
https://hyrsoft.github.io/posts/linux/毕业设计智能网络摄像头/rv1106的rk_mpi视频流与ai推理链路分析/
作者
好软好温暖
发布于
2026-01-30
许可协议
CC BY-NC-SA 4.0