RK3588 MPP 模块学习记录

目录


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 事件。此时必须:

  1. 通过 mpp_frame_get_buf_size() 获取新缓冲区大小
  2. 更新 MppBufferGroup(mpp_buffer_group_limit_config
  3. 返回当前帧后发送 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 硬件编码器的配置与调用。

编码核心流程

  1. 创建上下文 & 初始化编码器mpp_create()mpp_init(ctx, MPP_CTX_ENC, MPP_VIDEO_CodingAVC)
  2. 配置编码参数:码率控制(CBR/VBR/FIXQP)、QP范围、帧率、Profile/Level、GOP大小等
  3. 创建 DRM 内存组mpp_buffer_group_get_internal(&buf_grp, MPP_BUFFER_TYPE_DRM)
  4. 逐帧编码循环:获取缓冲区 → 拷贝YUV数据 → 设置帧属性 → encode_put_frame()encode_get_packet()
  5. 释放资源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);
}

注意事项

  1. 16字节对齐: 输入宽高必须严格 16 对齐,这是 MPP 硬件编码器的强制要求。
  2. 仅支持 YUV 输入: 推荐 YUV420P / NV12,RGB 格式存在兼容性问题。
  3. PTS 计算: 使用 帧序号 × (90000 / fps) 作为 90kHz 时基时间戳。
  4. 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 生命周期

  1. rknn_init — 初始化上下文
    加载 .rknn 模型文件到内存,创建 NPU 推理上下文。支持 RKNN_FLAG_PRIOR_HIGH(高优先级)和 RKNN_FLAG_ASYNC_MASK(异步模式)标志。

  2. rknn_set_io_mem — 设置零拷贝内存
    通过 rknn_create_mem 预分配 NPU 内存,再通过 rknn_set_io_mem 绑定到输入/输出张量。避免每帧 memcpy。

  3. rknn_run — 执行推理
    在 NPU 上执行一次前向推理。异步模式下可与下一帧预处理并行。

  4. 获取输出 → 后处理
    从预分配的 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 节省 CPU 95.4% → 33%
RGA vs OpenCV 预处理加速 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 (推理) │
                 └──────────────────────────────────────┘

多线程注意事项

  1. 每个线程独立的 MppCtx: MPP 上下文不支持多线程共享,每路视频需独立的解码上下文。
  2. NPU 共享策略: RK3588 NPU 有3个核心,可通过多 rknn_context 实现并行推理,但需注意 NPU 利用率。
  3. DMA Buffer 池化: 预分配 DMA 缓冲区池,避免频繁分配释放。
  4. 异步模式: 使用 RKNN_FLAG_ASYNC_MASK,rknn_run 与下一帧预处理并行执行,最大化流水线吞吐量。

10 开发指南与踩坑经验

环境搭建、编译配置、常见问题排查,以及开发过程中的关键注意事项。

环境搭建

  1. 编译 MPP 库

    git clone https://github.com/rockchip-linux/mpp
    cd mpp/build/linux/aarch64/
    ./make-Makefiles.bash && make
    
  2. 编译 librga (RGA)

    git clone https://github.com/airockchip/librga
    # CMake 编译,获取 librga.so 和头文件
    
  3. 安装 RKNN Runtime

    sudo apt install librknnrt.so
    # 或从 SDK 复制。需与 RKNN-Toolkit2 版本匹配。
    
  4. 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 源码、社区实践整理

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐