RK3588 MPP 模块学习记录
RK3588 MPP 模块学习记录
目录
- 01 RK3588 芯片概览
- 02 MPP 架构深度剖析
- 03 视频解码实战
- 04 视频编码实战
- 05 FFmpeg-Rockchip 集成
- 06 MPP + RGA + RKNN AI 推理管线
- 07 零拷贝优化技术
- 08 性能基准测试
- 09 典型应用场景与实战
- 10 开发指南与踩坑经验
01 RK3588 芯片概览
RK3588 是瑞芯微新一代旗舰级 AIoT SoC,8nm 制程,集成 6TOPS NPU 与 8K 编解码能力,是边缘AI+视频处理的核心平台。
| 核心指标 | 参数 |
|---|---|
| 制程工艺 | 8nm |
| NPU 算力 (INT8) | 6 TOPS |
| HEVC 硬件解码 | 8K@60fps |
| H.264/H.265 硬件编码 | 8K@30fps |
核心处理单元
- CPU:4× Cortex-A76 (2.4GHz) + 4× Cortex-A55 (1.8GHz),大小核异构架构,高性能与低功耗兼顾。
- GPU:Mali-G610 MP4,支持 OpenGL ES 3.2、OpenCL 2.2、Vulkan 1.2,适合图形渲染与并行计算。
- NPU:3核心神经网络处理器,6 TOPS 算力,支持 INT4/INT8/INT16/FP16 混合运算,通过 RKNN Runtime 调度。
- VPU:集成硬件视频编解码器,8K@60fps HEVC 解码、8K@30fps H.264 解码与 8K@30fps 编码,是 MPP 的硬件基础。
VPU 编解码能力详表
| 功能 | 编码格式 | 最大分辨率 | 硬件单元 |
|---|---|---|---|
| 硬件解码 | H.265 / HEVC | 8K @ 60fps | VDPU34x |
| 硬件解码 | H.264 / AVC | 8K @ 30fps | VDPU34x |
| 硬件解码 | VP9 | 8K @ 60fps | VDPU34x |
| 硬件解码 | AV1 | 4K @ 60fps | VDPU34x |
| 硬件解码 | VP8 / MPEG-2/4 / H.263 / VC1 | 1080P @ 60fps | VDPU34x |
| 硬件编码 | H.265 / HEVC | 8K @ 30fps | VEPU34x |
| 硬件编码 | H.264 / AVC | 8K @ 30fps | VEPU34x |
| 硬件编码 | VP8 / MJPEG | 1080P @ 60fps | VEPU34x |
关键外设接口
- 显示输出:HDMI 2.1 (8K@60) / DP 1.4 (8K@30) / eDP 1.3 (4K@60) / MIPI DSI (4K@60),支持多屏异显。
- 图像输入:内置 ISP 7.1,最高 48MP,支持多摄像头输入。MIPI CSI 接口,适合视觉采集场景。
- 高速互联:PCIe 3.0 (4-lane) / USB 3.0 / 千兆以太网 / SATA 3.0,满足边缘计算数据传输需求。
02 MPP 架构深度剖析
MPP (Media Process Platform) 是瑞芯微统一媒体处理软件平台,屏蔽芯片底层差异,提供标准化的 MPI 接口。
分层架构设计
┌─────────────────────────────────────────────────┐
│ 应用层:FFmpeg / GStreamer / OpenMAX │
└───────────────────────┬─────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ MPI (Media Process Interface) — 统一API接口层 │
│ MppCtx · MppPacket · MppFrame · MppBuffer │
│ MppTask · MppMeta │
└───────────────────────┬─────────────────────────┘
▼
┌────────────────────────┬────────────────────────┐
│ Codec 层 │ HAL 层 │
│ Decoder / Encoder │ 硬件抽象层 │
│ H.264/H.265/VP9/AV1 │ Parser/Recoder+Reg Gen │
└────────────────────────┴────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ OSAL 层 — 操作系统抽象 (Android ion / Linux DRM)│
└───────────────────────┬─────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ Kernel 层 — RK vcodec_service / V4L2 驱动 │
└─────────────────────────────────────────────────┘
核心数据结构关系
┌─────────────────┐
│ MppCtx │ ← 会话上下文,管理编解码器生命周期
│ (MppApi *mpi) │ ← 函数指针表,提供所有 MPI 操作
└────────┬────────┘
│
┌───────────────┼───────────────┐
│ │
┌───────┴───────┐ ┌───────┴───────┐
│ MppPacket │ │ MppFrame │
│ (压缩码流) │ │ (原始帧数据) │
│ · data │ │ · width │
│ · length │ │ · height │
│ · pts │ │ · fmt (NV12) │
│ · eos │ │ · pts / eos │
└───────┬───────┘ └───────┬───────┘
│ │
└───────────────┬───────────────┘
│
┌───────┴───────┐
│ MppBuffer │ ← 底层缓冲区封装
│ · fd (dma-buf)│ Linux: DRM dma-buf
│ · ptr (虚拟地址)│ Android: ion buffer
│ · size │
└───────────────┘
源码目录结构
# MPP 仓库: https://github.com/rockchip-linux/mpp
mpp/
├── inc/ # 对外头文件 (平台头文件 + MPI 头文件)
├── mpp/
│ ├── base/ # 基础组件: MppBuffer/Frame/Packet/Task/Meta
│ ├── codec/
│ │ ├── dec/ # 解码器: h264/h265/vp9/vp8/jpeg/av1...
│ │ └── enc/ # 编码器: h264/h265/jpeg
│ ├── hal/ # 硬件抽象层
│ │ ├── rkdec/ # RK 硬件解码寄存器生成
│ │ ├── vpu/ # VPU 寄存器生成库
│ │ └── rga/ # RGA 用户库
│ └── test/ # 内部单元测试
├── osal/ # OS 抽象层 (Android ion / Linux DRM)
├── test/ # 示例程序 (mpi_dec_test / mpi_enc_test)
└── doc/ # 设计文档
MPI 核心接口函数
| 接口 | 功能 | 解码场景 | 编码场景 |
|---|---|---|---|
mpp_create() |
创建 MPP 上下文 | ✅ | ✅ |
mpp_init() |
初始化编码/解码模式 | MPP_CTX_DEC | MPP_CTX_ENC |
mpi->decode_put_packet() |
提交压缩码流包 | ✅ | — |
mpi->decode_get_frame() |
获取解码帧 | ✅ | — |
mpi->encode_put_frame() |
提交原始帧 | — | ✅ |
mpi->encode_get_packet() |
获取编码后码流 | — | ✅ |
mpi->control() |
运行时控制命令 | ✅ | ✅ |
mpp_destroy() |
销毁上下文 | ✅ | ✅ |
MPP 采用"上下文+函数指针表"的经典 C 语言面向对象设计。MppCtx 管理会话状态,MppApi 提供函数指针表(类似虚函数表),通过
mpi->method()调用,实现编码/解码/转码统一接口。
03 视频解码实战
从 API 调用流程到代码实现,全面覆盖 MPP 硬件解码开发。
解码交互时序
MPI MPP Decoder Parser HAL
──────────────────────────────────────────────────────────────────────
mpp_create ──▶
mpp_init ──▶ init ──▶ init ──▶ open
send_stream ──▶ parse_stream ──▶ reg generation
──────────────────────────────────▶ send_regs
──────────────────────────────────▶ wait_regs
notify_hw_end
get_picture ──▶ get_picture
control(FLUSH)──▶ flush ──▶ reset
mpp_destroy ──▶ close ──▶ close ──▶ close
完整解码代码实现
// ====== RK3588 MPP H.264 硬件解码完整示例 ======
#include <rockchip/rk_mpi.h>
#include <rockchip/mpp_buffer.h>
#include <rockchip/mpp_frame.h>
#include <rockchip/mpp_packet.h>
MppCtx ctx = NULL;
MppApi* mpi = NULL;
MppPacket pkt = NULL;
MppFrame frame = NULL;
// 1. 创建上下文
mpp_create(&ctx, &mpi);
// 2. 配置分帧模式(必须在 init 之前)
RK_U32 need_split = 1;
mpi->control(ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, &need_split);
// 3. 初始化为 H.264 解码模式
mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC);
// 4. 解码主循环
while (has_data) {
// 4a. 读取裸码流数据到 buffer
mpp_packet_init(&pkt, stream_data, stream_len);
// 4b. 送入解码器
MPP_RET ret = mpi->decode_put_packet(ctx, pkt);
if (ret == MPP_OK) {
// 4c. 获取解码帧
ret = mpi->decode_get_frame(ctx, &frame);
if (ret == MPP_OK && frame) {
// 4d. 读取帧信息
RK_U32 width = mpp_frame_get_width(frame);
RK_U32 height = mpp_frame_get_height(frame);
MppBuffer buf = mpp_frame_get_buffer(frame);
void* data = mpp_buffer_get_ptr(buf);
// data 即为 NV12 格式解码数据,可用于显示或AI推理
}
}
mpp_packet_deinit(&pkt);
}
内存管理模式
模式1:纯内部分配
最简单,MPP 内部分配缓冲区。用户仅调用 MPP_DEC_SET_INFO_CHANGE_READY。缺点:无法控制内存用量,零拷贝困难。
模式2:半内部分配(推荐)
用户创建 MppBufferGroup,通过 mpp_buffer_group_limit_config 限制内存。可根据 Info Change 动态调整。平衡灵活性与复杂度。
模式3:纯外部分配(零拷贝)
创建空的 external 模式 MppBufferGroup,通过文件句柄从外部分配器(如 Android SurfaceFlinger)导入内存。适用于零拷贝显示和跨组件共享,是 Android MediaServer 的标准方案。实现复杂度最高。
Info Change 处理
码流分辨率/格式变更
当视频流分辨率、格式或像素位深发生变化时,解码器会返回 Info Change 事件。此时必须:
- 通过
mpp_frame_get_buf_size()获取新缓冲区大小- 更新 MppBufferGroup(
mpp_buffer_group_limit_config)- 返回当前帧后发送
MPP_DEC_SET_INFO_CHANGE_READY通知解码器就绪
命令行测试工具
| 工具 | 功能 | 关键参数 |
|---|---|---|
mpi_dec_test |
单线程解码 | -i input.h264 -t 7 -n 60 -o out.yuv |
mpi_dec_mt_test |
多线程解码 | 同上,线程间输入输出分离 |
mpi_dec_multi_test |
多实例解码 | -s 4 指定4个MPP实例 |
其中 -t 参数对应 OMX 标准编码类型ID:H.264=7,H.265 需特殊值。MPP 不直接支持 MP4 容器,需先用 FFmpeg 提取裸码流。
04 视频编码实战
从 YUV 输入到 H.264/H.265 编码输出,详解 MPP 硬件编码器的配置与调用。
编码核心流程
- 创建上下文 & 初始化编码器:
mpp_create()→mpp_init(ctx, MPP_CTX_ENC, MPP_VIDEO_CodingAVC) - 配置编码参数:码率控制(CBR/VBR/FIXQP)、QP范围、帧率、Profile/Level、GOP大小等
- 创建 DRM 内存组:
mpp_buffer_group_get_internal(&buf_grp, MPP_BUFFER_TYPE_DRM) - 逐帧编码循环:获取缓冲区 → 拷贝YUV数据 → 设置帧属性 →
encode_put_frame()→encode_get_packet() - 释放资源:
mpp_frame_deinit()→mpi->reset()→mpp_destroy()→mpp_buffer_group_put()
编码参数配置详解
码率控制 (RC):
| 模式 | 说明 | 适用场景 |
|---|---|---|
| CBR | 恒定码率 | 流媒体传输 |
| VBR | 可变码率 | 存储 |
| FIXQP | 固定QP | 质量一致 |
| AVBR | 自适应VBR | 平衡场景 |
H.264 高级参数:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| Profile | 100 (High) | 熵编码优化 |
| Level | 40 | 1080p@30fps |
| CABAC | 开启 | 熵编码优化 |
| 8×8 Transform | 开启 | 变换优化 |
| QP Init/Min/Max | 26 / 20 / 36 | 质量范围 |
| GOP Size | = fps | 1秒1关键帧 |
编码代码核心实现
// ====== MPP H.264 编码器初始化 ======
MppCtx mpp_ctx_;
MppApi* mpp_mpi_;
// 16字节对齐(MPP强制要求)
inline int ALIGN_MPP_SIZE(int size) { return (size + 15) & ~15; }
void init(int width, int height, int fps) {
// 创建 + 初始化编码器
mpp_create(&mpp_ctx_, &mpp_mpi_);
mpp_init(mpp_ctx_, MPP_CTX_ENC, MPP_VIDEO_CodingAVC);
// 获取并设置编码配置
MppEncCfg cfg;
mpp_enc_cfg_init(&cfg);
mpp_mpi_->control(mpp_ctx_, MPP_ENC_GET_CFG, cfg);
// 码率控制:CBR,目标码率 = w*h*fps*0.1
mpp_enc_cfg_set_s32(cfg, "rc:mode", MPP_ENC_RC_MODE_CBR);
mpp_enc_cfg_set_s32(cfg, "rc:bps_target", width*height*fps/10);
mpp_enc_cfg_set_s32(cfg, "rc:fps_in_num", fps);
mpp_enc_cfg_set_s32(cfg, "h264:profile", 100); // High
mpp_enc_cfg_set_s32(cfg, "rc:qp_init", 26);
mpp_mpi_->control(mpp_ctx_, MPP_ENC_SET_CFG, cfg);
mpp_enc_cfg_deinit(cfg);
// DRM 内存组
mpp_buffer_group_get_internal(&mpp_buf_grp_, MPP_BUFFER_TYPE_DRM);
}
// ====== 逐帧编码 ======
void encodeFrame(unsigned char* src_data, int frame_count) {
MppBuffer buffer;
mpp_buffer_get(mpp_buf_grp_, &buffer, align_w*align_h*3/2);
// 拷贝 YUV 数据到 DRM 缓冲区
unsigned char* dst = (unsigned char*)mpp_buffer_get_ptr(buffer);
memcpy(dst, src_data, ...); // Y/U/V 分量拷贝
// 构建帧对象
mpp_frame_set_buffer(frame_, buffer);
mpp_frame_set_width(frame_, width_);
mpp_frame_set_fmt(frame_, MPP_FMT_YUV420P);
mpp_frame_set_pts(frame_, (int64_t)frame_count * (90000/fps_));
// 提交编码 & 获取输出
mpp_mpi_->encode_put_frame(mpp_ctx_, frame_);
MppPacket pkt;
mpp_packet_init(&pkt, NULL, 0);
if (mpp_mpi_->encode_get_packet(mpp_ctx_, &pkt) == MPP_OK) {
auto data = mpp_packet_get_data(pkt);
auto size = mpp_packet_get_length(pkt);
// 检查关键帧
MppMeta meta = mpp_packet_get_meta(pkt);
RK_S32 is_intra = 0;
mpp_meta_get_s32(meta, KEY_OUTPUT_INTRA, &is_intra);
}
mpp_packet_deinit(&pkt);
mpp_buffer_put(buffer);
}
注意事项
- 16字节对齐: 输入宽高必须严格 16 对齐,这是 MPP 硬件编码器的强制要求。
- 仅支持 YUV 输入: 推荐 YUV420P / NV12,RGB 格式存在兼容性问题。
- PTS 计算: 使用
帧序号 × (90000 / fps)作为 90kHz 时基时间戳。- H.265 类型ID: 编码时
-t参数值为16777220,与 H.264 的 7 完全不同。
05 FFmpeg-Rockchip 集成
ffmpeg-rockchip 在标准 FFmpeg 基础上封装了 MPP 和 RGA 的 API,提供 h264_rkmpp / hevc_rkmpp 等硬件编解码器。
硬件编解码器列表
| 类型 | 编解码器名称 | 底层驱动 | 说明 |
|---|---|---|---|
| 解码器 | h264_rkmpp |
MPP | H.264 硬件解码 |
| 解码器 | hevc_rkmpp |
MPP | H.265/HEVC 硬件解码 |
| 解码器 | vp9_rkmpp |
MPP | VP9 硬件解码 |
| 编码器 | h264_rkmpp |
MPP | H.264 硬件编码 |
| 编码器 | hevc_rkmpp |
MPP | H.265/HEVC 硬件编码 |
| 过滤器 | scale_rkrga |
RGA | 硬件缩放/格式转换 |
代码示例:MP4 → H.264 硬解 → HEVC 硬编
// 使用 ffmpeg-rockchip API 实现转码
// 1. 查找 MPP 硬件解码器
AVCodec* dec = avcodec_find_decoder_by_name("h264_rkmpp");
AVCodecContext* dec_ctx = avcodec_alloc_context3(dec);
avcodec_parameters_to_context(dec_ctx, stream->codecpar);
// 关键:启用 DRM buffer 模式
AVDictionary* opts = NULL;
av_dict_set_int(&opts, "buf_mode", 1, 0);
avcodec_open2(dec_ctx, dec, &opts);
// 2. 查找 MPP 硬件编码器
AVCodec* enc = avcodec_find_encoder_by_name("hevc_rkmpp");
AVCodecContext* enc_ctx = avcodec_alloc_context3(enc);
enc_ctx->width = 1920;
enc_ctx->height = 1080;
enc_ctx->pix_fmt = AV_PIX_FMT_NV12; // MPP 常用像素格式
enc_ctx->bit_rate = 10 * 1024 * 1024; // 10 Mbps
av_opt_set(enc_ctx->priv_data, "rc_mode", "0", 0);
avcodec_open2(enc_ctx, enc, NULL);
// 3. 解码 → 编码循环
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
avcodec_send_packet(dec_ctx, &pkt);
while (avcodec_receive_frame(dec_ctx, frame) == 0) {
avcodec_send_frame(enc_ctx, frame);
while (avcodec_receive_packet(enc_ctx, &out_pkt) == 0) {
fwrite(out_pkt.data, 1, out_pkt.size, outfile);
}
}
}
性能优化:硬件设备上下文
使用
hw_device_ctx硬件设备上下文,可让解码后的帧直接留在 VPU 内存中进入编码器,避免 CPU ↔ VPU 间的冗余数据拷贝,显著提升编解码效率。这是零拷贝转码的关键。
命令行方式
# H.264 硬件解码 → HEVC 硬件编码
ffmpeg -i input.mp4 -c:v h264_rkmpp -c:a copy output.mp4
ffmpeg -i input.h264 -c:v hevc_rkmpp -b:v 10M output.hevc
# 使用 RGA 硬件缩放
ffmpeg -i input.mp4 -vf scale_rkrga=1280:720 -c:v hevc_rkmpp output.mp4
06 MPP + RGA + RKNN AI 推理管线
RK3588 最核心的应用场景:MPP 硬件解码 → RGA 硬件预处理 → NPU AI 推理,全程硬件加速,零 CPU 拷贝。
端到端数据流架构
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ MPP │ fd传递 │ DMA-BUF │ fd传递 │ RGA │ fd传递 │ RKNN │ │ 后处理 │
│ 解码 │ ───────▶ │ 零拷贝 │ ───────▶ │ 预处理 │ ───────▶ │ 推理 │ ──────▶ │ NMS+跟踪│
│ H264→NV12│ 共享fd │ 无CPU │ 共享fd │ YUV→RGB │ 共享fd │ 6TOPS │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
各模块职责
- MPP 解码:将 H.264/H.265 码流硬件解码为 NV12 原始帧。输出 DMA-BUF fd,可零拷贝传递给 RGA。避免 CPU 软解码瓶颈。
- RGA 预处理:2D 硬件加速器,执行 YUV→RGB 色彩转换、LetterBox 等比缩放+填充、图像 resize。替代 OpenCV,CPU 零开销。
- RKNN 推理:NPU 运行 .rknn 模型,支持 YOLO/SSD/分类/分割等。零拷贝 API 通过
rknn_set_io_mem直接操作 NPU 内存。
完整代码:MPP解码 → RGA预处理 → RKNN推理
// ====== Step 1: MPP 硬件解码输出 NV12 帧 ======
// (解码流程见第3章,此处获取 MppBuffer 的 fd)
MppBuffer frame_buf = mpp_frame_get_buffer(frame);
int fd = mpp_buffer_get_fd(frame_buf); // DMA-BUF fd,零拷贝关键
// ====== Step 2: RGA YUV→RGB + LetterBox ======
#include <im2d.hpp>
// 封装 MPP 输出的 NV12 帧为 RGA buffer
rga_buffer_t src = wrapbuffer_fd(
fd, img_width, img_height,
RK_FORMAT_YCbCr_420_SP, width_stride, height_stride);
// 封装 DMA 预分配的目标 RGB buffer
rga_buffer_t dst = wrapbuffer_fd(
buffer->fd, target_width, target_height,
RK_FORMAT_RGB_888);
// YUV→RGB 色彩空间转换
IM_STATUS ret = imcvtcolor(
src, dst,
RK_FORMAT_YCbCr_420_SP, RK_FORMAT_RGB_888,
IM_YUV_TO_RGB_BT601_FULL);
// LetterBox 等比缩放(保持宽高比+填充)
im_rect srect = {0, 0, src_width, src_height};
im_rect drect = {pad_x, pad_y, scale_w, scale_h};
improcess(src, dst, {}, srect, drect, {}, IM_SYNC);
// ====== Step 3: RKNN NPU 推理(零拷贝API) ======
#include <rknn_api.h>
rknn_context ctx;
rknn_init(&ctx, model_data, model_size, 0, NULL);
// 创建 NPU 输入内存(零拷贝)
rknn_tensor_mem* input_mem = rknn_create_mem(ctx, input_attr.size_with_stride);
rknn_set_io_mem(ctx, input_mem, &input_attr);
// RGA 输出直接写入 NPU 输入缓冲区
// (通过共享 DMA fd 实现,无需 CPU 拷贝)
// 执行推理
rknn_run(ctx, NULL);
// 获取输出
rknn_tensor_mem* output_mem = ...;
void* result = output_mem->virt_addr;
RKNN Runtime 核心 API 生命周期
-
rknn_init — 初始化上下文
加载 .rknn 模型文件到内存,创建 NPU 推理上下文。支持 RKNN_FLAG_PRIOR_HIGH(高优先级)和 RKNN_FLAG_ASYNC_MASK(异步模式)标志。 -
rknn_set_io_mem — 设置零拷贝内存
通过rknn_create_mem预分配 NPU 内存,再通过rknn_set_io_mem绑定到输入/输出张量。避免每帧 memcpy。 -
rknn_run — 执行推理
在 NPU 上执行一次前向推理。异步模式下可与下一帧预处理并行。 -
获取输出 → 后处理
从预分配的 output_mem->virt_addr 直接读取结果,执行 NMS、跟踪等后处理。
RKNN-Toolkit2 模型转换流程
🔄 PC端转换 → 板端部署
模型转换在 PC(Ubuntu)上完成,推理部署在 RK3588 板端。两套工具链分离。
PC端 (RKNN-Toolkit2):
rknn.config()→rknn.load_onnx()→rknn.build(do_quantization=True)→rknn.export_rknn('model.rknn')板端 (RKNN Runtime):
rknn_init()加载 .rknn →rknn_run()推理支持模型格式:Caffe / TensorFlow / TFLite / ONNX / DarkNet / PyTorch
量化方式:非对称量化(INT8),支持混合量化
07 零拷贝优化技术
RK3588 上 MPP→RGA→RKNN 全程零拷贝是性能优化的核心。通过 DMA-BUF fd 共享,避免 CPU 参与数据搬运。
DMA-BUF 零拷贝原理
传统方式 (需要 CPU 拷贝)
┌──────────┐ memcpy ┌──────────┐ memcpy ┌──────────┐
│ MPP │ ──────────▶ │ CPU │ ──────────▶ │ RKNN │
│ 解码输出 │ CPU拷贝1 │ 中转缓冲 │ CPU拷贝2 │ NPU输入 │
│ (NV12) │ │ (RGB) │ │ (RGB) │
└──────────┘ └──────────┘ └──────────┘
CPU 占用 ~95% 延迟高 带宽浪费
零拷贝方式 (全程硬件通路)
┌──────────┐ fd 传递 ┌──────────┐ fd 传递 ┌──────────┐
│ MPP │ ──────────▶ │ RGA │ ──────────▶ │ RKNN │
│ 解码输出 │ 共享dma-buf │ 硬件转换 │ 共享dma-buf │ NPU推理 │
│ (NV12) │ 无CPU参与 │ (RGB) │ 无CPU参与 │ (RGB) │
└──────────┘ └──────────┘ └──────────┘
CPU 占用 ~33% 低延迟 带宽高效
DMA 缓冲区创建
// DMA 堆路径:使用 system-uncached-dma32
const char* dma_heap_path = "/dev/dma_heap/system-uncached-dma32";
for (int i = 0; i < BUFFER_COUNT; ++i) {
dma_buf_alloc(dma_heap_path, size,
&buffers[i].fd, // 文件描述符(零拷贝传递)
&buffers[i].va); // 虚拟地址(CPU访问用)
buffers[i].width_stride = align_width;
buffers[i].height_stride = align_height;
}
// fd 可在 MPP/RGA/RKNN 之间传递,无需拷贝数据
RGA 零拷贝预处理
// 将 MPP 解码输出的 DMA fd 封装为 RGA 源 buffer
rga_buffer_t src = wrapbuffer_fd(
mpp_fd, // 来自 MPP 解码的 DMA-BUF fd
width, height,
RK_FORMAT_YCbCr_420_SP, width_stride, height_stride);
// 将预分配的 DMA 目标 buffer 封装为 RGA 目标
rga_buffer_t dst = wrapbuffer_fd(
target_fd, // 将写入 RKNN 输入缓冲区的 fd
dst_width, dst_height,
RK_FORMAT_RGB_888);
// RGA 硬件执行:YUV→RGB + LetterBox + Resize
IM_STATUS ret = imcvtcolor(src, dst,
RK_FORMAT_YCbCr_420_SP, RK_FORMAT_RGB_888,
IM_YUV_TO_RGB_BT601_FULL);
// LetterBox 等比缩放
im_rect srect = {0, 0, src_w, src_h};
im_rect drect = {pad_x, pad_y, scale_w, scale_h};
improcess(src, dst, {}, srect, drect, {}, IM_SYNC);
RKNN 零拷贝推理
// 创建 NPU 输入内存(零拷贝API)
rknn_tensor_mem* input_mem = rknn_create_mem(
rknn_ctx, input_attrs[0].size_with_stride);
// 创建 NPU 输出内存
for (int i = 0; i < io_num.n_output; i++) {
output_mems_[i] = rknn_create_mem(
rknn_ctx, output_attrs[i].n_elems * sizeof(float));
}
// 设置输入/输出内存绑定
rknn_set_io_mem(rknn_ctx, input_mems_[0], &input_attrs[0]);
for (int i = 0; i < io_num.n_output; i++)
rknn_set_io_mem(rknn_ctx, output_mems_[i], &output_attrs[i]);
// RGA 输出通过共享 fd 直接写入 NPU 输入缓冲区
// 然后直接调用 rknn_run,无需任何数据拷贝
rknn_run(rknn_ctx, NULL);
// 从预分配的输出缓冲区直接读取结果
void* output_data = output_mems_[0]->virt_addr;
💡 零拷贝 API 限制
零拷贝 API 仅支持输入数据有物理地址或 fd 的情况。普通 API 调用简单但每帧需 memcpy,零拷贝 API 预分配内存后复用,减少拷贝开销。实际部署中推荐零拷贝方案。
08 性能基准测试
各优化维度的实测性能对比,量化 MPP/RGA/RKNN 硬件加速收益。
核心指标对比
| 优化维度 | 倍率 | 对比说明 |
|---|---|---|
| DMA vs memcpy CPU 节省 | 3× | CPU 95.4% → 33% |
| RGA vs OpenCV 预处理加速 | 3× | 3-10ms → ~1ms |
| 零拷贝 vs 通用 RKNN API | 1.5× | CPU 77.2% → 40% |
| HEVC 硬件解码能力 | 8K@60 | VPU 硬件峰值性能 |
详细性能对比表
| 阶段 | 传统方案 | 硬件加速方案 | CPU节省 | 延迟改善 |
|---|---|---|---|---|
| 视频解码 | FFmpeg 软解 (CPU 90%+) | MPP 硬解 (CPU ~10%) | ~9× | 实时 vs 卡顿 |
| 图像预处理 | OpenCV resize+cvtColor (3-10ms, CPU 50%) | RGA 硬件预处理 (~1ms, CPU 15%) | ~3× | 2-9ms ↓ |
| 数据传输 | memcpy (CPU 95%) | DMA-BUF 零拷贝 (CPU 33%) | ~3× | 消除拷贝延迟 |
| NPU 推理 | RKNN 通用 API (CPU 77%) | RKNN 零拷贝 API (CPU 40%) | ~1.5× | 减少1次 memcpy |
| 多路并发 | 单线程逐帧 (1-2 fps) | 多线程 Pipeline (16路@25fps) | — | 吞吐量 ↑8-16× |
YOLO 推理性能参考
YOLOv5s (640×640):
| 指标 | 数值 |
|---|---|
| NPU 推理耗时 | ~20-25ms |
| 含预处理总延迟 | ~30ms |
| 等效帧率 | ~30 fps |
| 后处理(NMS) | ~2-5ms (CPU) |
16路 1080p 实时检测:
| 指标 | 数值 |
|---|---|
| 每路解码 | MPP 硬解 |
| 预处理 | RGA LetterBox |
| 推理方式 | NPU 多实例/多线程 |
| 总帧率 | 16×25fps = 400fps |
| NPU 利用率 | ~90% |
09 典型应用场景与实战
基于 MPP+AI 的端到端应用方案,覆盖智能监控、边缘计算、8K视频处理等核心场景。
场景一:智能视频监控 (RTSP → AI 检测)
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│IP Camera│ ──▶ │MPP 解码 │ ──▶ │RGA 预处理│ ──▶ │YOLOv5推理│ ──▶ │ByteTrack │
│ RTSP拉流│ │H.264→NV12│ │LetterBox │ │ 目标检测 │ │ 多目标跟踪│
└─────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
开源参考项目: RK3588_RKNN_MPP_RGA_demo — 完整的目标识别+跟踪 demo,包含 MppDecoder、YOLOv5s推理、ByteTrack跟踪、Qt5显示、DMA零拷贝内存管理。代码结构清晰,适合作为开发参考。
场景二:8K 超高清视频处理
- 8K 解码显示:HDMI 2.1 输出 8K@60fps。使用 GStreamer + MPP 硬件解码 H.265,直接显示到 8K 显示器。典型命令:
./gst_dec_display -l video.mp4 -x h265 -r 60 - 医疗影像:医疗内窥镜、超声映像系统,8K 超高清采集+实时 AI 辅助诊断。MPP 解码 + RKNN 推理的典型组合。
场景三:边缘计算网关
- 工业视觉:多摄像头实时缺陷检测、产品分类。MPP 多路解码 + NPU 多模型并发推理。
- 智能交通:车牌识别、交通流分析。多路 RTSP 接入,MPP 硬解+RGA预处理+NPU推理。
- 智能家居:人脸识别门禁、手势控制。低功耗AI推理,RK3588小核+A55+NPU协同。
多线程并发架构
┌─── Thread 1 ──────────────────────────────────────┐
│ [RTSP Stream 1] → MPP解码 → RGA预处理 → RKNN推理 → 后处理 │
└────────────────────────────────────────────────────┘
┌─── Thread 2 ──────────────────────────────────────┐
│ [RTSP Stream 2] → MPP解码 → RGA预处理 → RKNN推理 → 后处理 │
└────────────────────────────────────────────────────┘
┌─── Thread N ──────────────────────────────────────┐
│ [RTSP Stream N] → MPP解码 → RGA预处理 → RKNN推理 → 后处理 │
└────────────────────────────────────────────────────┘
│ │
每线程独立MppCtx NPU 多实例/时间片共享
│ │
DMA Buffer Pool rknn_context × N
│ │
┌────────┴────────────────────┴────────┐
│ RK3588 硬件资源 │
│ VPU (解码) │ RGA (预处理) │ NPU (推理) │
└──────────────────────────────────────┘
多线程注意事项
- 每个线程独立的 MppCtx: MPP 上下文不支持多线程共享,每路视频需独立的解码上下文。
- NPU 共享策略: RK3588 NPU 有3个核心,可通过多 rknn_context 实现并行推理,但需注意 NPU 利用率。
- DMA Buffer 池化: 预分配 DMA 缓冲区池,避免频繁分配释放。
- 异步模式: 使用 RKNN_FLAG_ASYNC_MASK,rknn_run 与下一帧预处理并行执行,最大化流水线吞吐量。
10 开发指南与踩坑经验
环境搭建、编译配置、常见问题排查,以及开发过程中的关键注意事项。
环境搭建
-
编译 MPP 库
git clone https://github.com/rockchip-linux/mpp cd mpp/build/linux/aarch64/ ./make-Makefiles.bash && make -
编译 librga (RGA)
git clone https://github.com/airockchip/librga # CMake 编译,获取 librga.so 和头文件 -
安装 RKNN Runtime
sudo apt install librknnrt.so # 或从 SDK 复制。需与 RKNN-Toolkit2 版本匹配。 -
PC 端模型转换
在 Ubuntu PC 上安装 RKNN-Toolkit2,将 ONNX/PyTorch 模型转换为 .rknn 格式并部署到板端。
关键踩坑经验
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 16字节对齐 | MPP 编码器强制要求宽高按 16 字节对齐 | (size + 15) & ~15,RGA 同样要求 stride 和 width 按 16 对齐 |
| NV12 vs YUV420P | MPP 解码默认输出 NV12 (YUV420SP),编码器输入推荐 YUV420P | 注意 RGA 转换或手动处理 UV 分量 |
| MPP 不支持 MP4 | MPP 只处理裸码流,不支持 MP4/AVI 等容器格式 | 先用 FFmpeg 提取码流,或直接使用 ffmpeg-rockchip |
| 分帧模式 | MPP_DEC_SET_PARSER_SPLIT_MODE 必须在 mpp_init 之前设置 |
两种模式不能混用,分帧效率高但需预解析 |
| DMA32 限制 | RGA2 只能使用 DMA32(4GB 限制) | 使用 system-uncached-dma32 堆,避免 buffer 分配到 swap |
| RKNN 版本匹配 | 板端 Runtime 版本必须与 PC 端 Toolkit2 版本一致 | 版本不匹配会导致 RKNN_ERR_TARGET_PLATFORM_UNMATCH 错误 |
MPP 工具程序速查
| 工具 | 功能 | 用途 |
|---|---|---|
mpp_info_test |
MPP 库版本信息 | 反馈问题时附上版本号 |
mpp_buffer_test |
内核内存分配器测试 | 验证 DRM 驱动是否正常 |
mpp_mem_test |
C 库内存分配器测试 | 验证系统内存管理 |
mpp_runtime_test |
软硬件运行时测试 | 验证编解码环境 |
mpp_platform_test |
芯片平台信息测试 | 确认 SoC 型号 |
推荐开发资源
官方仓库:
- MPP: https://github.com/rockchip-linux/mpp
- librga: https://github.com/airockchip/librga
- RKNN-Toolkit2: https://github.com/airockchip/rknn-toolkit2
参考示例:
- MPP Linux C++: https://github.com/WainDing/mpp_linux_cpp
- FFmpeg RTSP+MPP: https://github.com/MUZLATAN/ffmpeg_rtsp_mpp
- MPP+RGA+RKNN Demo: https://github.com/yingliuzhizhuzed/RK3588_RKNN_MPP_RGA_demo
技术文档:
- MPP 开发参考 (PDF): rockchip_developer_guide_mpp_cn.pdf
- 官方 Wiki: https://opensource.rock-chips.com/wiki_Mpp
- 鲁班猫文档: https://doc.embedfire.com
*基于 Rockchip 官方文档、MPP 源码、社区实践整理
更多推荐


所有评论(0)