1. 开局先聊为什么我最终选 RK3588 作为多路视频处理平台先说说背景。我手上有一个边缘视频接入类项目需求本身不算复杂接入 8 路 1080p30fps 的 RTSP 摄像头流做实时预览、关键帧抓图再把画面缩略拼接后送进后续的 AI 推理流程。最初评估过两种路线一是直接用 x86 平台用 Intel QSV 或 NVIDIA NVENC/NVDEC 硬解走 GPU 做缩放拼接二是走嵌入式 ARM 平台用 SoC 自带的 VPU 处理。选型时对比过几款主流开发板最终敲定 RK3588。原因很简单这颗 SoC 内置了 8K VPU 解码单元、独立的 RGARaster Graphic Acceleration2D 加速引擎加上 6 TOPS 的 NPU一颗片子就能覆盖解码 - 缩放拼接 - 推理全链路不需要外挂 GPU 或独立视频处理芯片BOM 成本和功耗都低很多非常适合做边缘视频处理盒子。但真正让我决定写这篇实战记录的原因是 RK3588 这套 MPP RGA 的软件栈有个非常关键的优化点零拷贝。如果只是按最直接的方式去调用硬解码然后从内存里把 YUV 数据拷出来再做格式转换和缩放CPU 占用会高到离谱多路场景直接崩。把 MPP 解码输出和 RGA 加速之间打通零拷贝通道之后8 路 1080p 解码 缩放的 CPU 占用能从 40% 以上压到不足 10%帧率稳定在满帧。这篇内容面向的是正在基于 RK3588 做视频接入、视频预览、智能分析盒子的嵌入式 Linux 开发工程师。我会把 MPP 硬解码、RGA 2D 加速、dma-buf 零拷贝这条链路的完整用法、背后的原理、以及实际调优过程中踩过的坑都讲清楚。不需要你之前接触过 Rockchip 平台但最好有基本的 Linux 多媒体开发经验熟悉 V4L2 或 GStreamer 的基本概念。2. 硬解码前的认知补齐RK3588 的 VPU 能力边界与 MPP 库到底是个什么层次很多人第一次接触 RK3588 的硬解码最迷茫的地方不是 API 怎么调而是这套东西在整个系统里的位置。所以我先用我理解的方式把硬件架构和软件层次讲明白。2.1 VPU、RGA、NPU 三者之间怎么分工RK3588 在音视频处理上其实是三个独立硬件单元协同工作VPUVideo Processing Unit专门做视频编解码支持 H.264/H.265/VP9/AV1 等多种格式解码能力最高到 8K30fps 或者多路 4K/1080p 同时解码。它只负责把压缩码流变成 YUV 原始图像不负责缩放和叠加。RGARaster Graphic Acceleration专门做 2D 图像操作包括缩放、格式转换比如 NV12 转 BGR、旋转、镜像、裁剪、alpha 混合等。它相当于一个轻量级 GPU 的 2D 部分。NPUNeural Processing Unit专做神经网络推理对视频来说就是 YOLO 目标检测、姿态估计这类任务。流水线很清晰RTSP 拉流 - VPU 硬解码 - RGA 做预处理 - NPU 推理。这三者之间用 dma-buf 共享内存数据不需要经过 CPU 拷贝这就是零拷贝的核心硬件基础。2.2 MPP 不是播放器它是一层底层编解码封装库Rockchip MPPMedia Process Platform是瑞芯微官方提供的编解码库直接跑在 VPU 之上向上提供统一的解码/编码接口。它不是一个播放器没有 RTSP 客户端、没有文件解析、没有音频处理它的职责边界非常明确喂给它一个视频码流分片它给你吐出一帧 YUV 图像。这里有个重要概念需要区分RKMPP 库用户态 API 层和 MPP 服务进程古代 mpp_service的区别。现在的 RK3588 平台上Mpp 解码主要是通过内核提供的 V4L2 接口或者用户态直接操作 dma-buf 来实现。MppDec 是它的解码 API 封装我们平常调用mpi_dec系列函数就是走这条路径。2.3 什么时候用 MPP什么时候用 GStreamer/FFmpeg这是新手最容易纠结的问题。我的建议如下如果你只是在开发板上快速验证解一个 RTSP 流是否正常直接用 GStreamer 插件两行命令搞定。如果是做正式产品特别是要对每一帧做 AI 预处理、要精细控制解码缓冲、要优化内存复用JDK 用 MPP 或者 FFmpeg 的rkmpp后端。GStreamer 的插件封装层级太高你对内存流向的掌控能力会弱很多。我最终是直接用 MPP 的 C API 做的解码线程RGA 的 C API 做的图像处理再配合 dma-buf 做共享内存把整条链路的每一环都控制在自己手里。3. 环境准备与第一个坑librga 和 librknnrt 的版本千万别混装在开始写任何代码之前先把环境折腾好。这一步我浪费了两天时间值得单独拿出来讲。3.1 官方 SDK 里的库并不是全的RK3588 的系统开发环境大部分人首选 Buildroot 或 Debian。我实际用的是 Rockchip 官方提供的 Debian11 rootfs带桌面的版本。这个基础系统里已经有 librga 了就是 RGA 的用户态库版本一般在 1.9 左右。但注意这个版本可能和你的内核驱动版本不匹配或者缺少某些新接口。我的建议是从 Rockchip 官方的 GitHub 仓库单独拉最新版本的 librga 源码手动编译安装同时确保内核里的 RGA 驱动版本对应。判断库和驱动是否匹配看/dev/rga节点的存在只是第一步真正跑起来要看rga_info结构体里的版本号。# 检查 RGA 设备节点是否存在 ls -l /dev/rga # 用 rga_im2d 的版本查询函数确认驱动版本3.2 FFmpeg 和 MPP 的版本绑定关系如果你打算让 FFmpeg 也走 RK3588 硬解它在很多场景下确实方便必须用 Rockchip 的 FFmpeg fork不要用上游版本。上游 FFmpeg 的 rkmpp 支持虽然已经合入主分支但很多时候缺少针对新固件的适配。我踩过的一个具体坑官方 SDK 的 FFmpeg 版本和直接 apt 装的 FFmpeg 版本冲突导致能够编出来但一运行就报 h264 解码参数解析错误。后来我把PKG_CONFIG_PATH单独指到 SDK 编译出来的库目录才把这个坑填平。3.3 推荐的目标环境配置我的最终环境是这样搭配的目前非常稳定组件版本/来源说明官方 SDKrk3588_linux_release_20231019Buildroot Debian 混合内核 RGA 驱动内核自带 rga 驱动需要确认 im2d 版本 1.9librga官方 GitHub 最新 release 编译安装对应 RGA2/RGA3MPP官方 SDK 里的 mpp 组件buildroot 编译产物不要自行 clone 编译除非你有把握交叉工具链完全一致FFmpeg官方 SDK 的 rockchip 版本不要混合使用系统版本重要不要分别从 GitHub 上各个仓库拉最新代码来混搭。官方 SDK 是一个整体验证过的软件栈库之间的兼容性已经测过你自己混一套可能编译全过但运行期各种莫名其妙的问题会消耗大量精力。4. MPP 硬解码从打开设备到拿到 YUV 帧的核心流程拆解零拷贝的关键链路是 MPP - RGA。要理解这条链路得先把 MPP 解码的经典流程走一遍。4.1 MPP 解码的经典调用逻辑MPP 解码的流程大致如下创建解码器实例mpp_create绑定解码类型H.264/H.265 等和码流格式如 Annex B初始化解码器mpp_init循环做从码流读取一个分片packet - 送入解码器 - 取出一个帧frame - 处理帧结束解码释放资源源码里最核心的发送接收逻辑片段如下// 解码主循环 while (!(quit)) { // 从 demuxer 读取一个 packet size_t read_size fread(pkt_buf, 1, pkt_size, f); mpp_packet_init(pkt, pkt_buf, read_size); mpp_meta_set_packet(pkt, META_KEY_PACKET_POSITION, pos); // 发送到解码器 ret mpi-decode_put_packet(ctx, pkt); if (ret MPP_OK) { // 从解码器取回解码后的帧 ret mpi-decode_get_frame(ctx, frame); if (frame) { // 这里是零拷贝的关键把 frame 的 fd 取出来 MppBuffer buffer mpp_frame_get_buffer(frame); int fd mpp_buffer_get_fd(buffer); // 传给 RGA 或其他硬件模块 process_frame_with_rga(fd, frame_info); mpp_frame_deinit(frame); } } mpp_packet_deinit(pkt); }这段代码最关键的一行是mpp_buffer_get_fd。它拿到的 fd 指向的是 MPP 内部管理的一块 dma-buf 内存。这块内存可以直接通过 RGA 的 import 机制传给 RGA 硬件去读不需要把里面的 YUV 数据 copy 到用户态再用软件缩放。4.2 关键解码帧的 buffer 类型决定了你能不能零拷贝MPP 输出帧的 buffer 所在的内存类型由输入码流决定更准确地说由解码器的内存模式决定。MPP 支持两种模式MPP_BUFFER_MODE_NORMAL内部管理内存从系统内存池分配MPP_BUFFER_MODE_DMA_HEAP从 dma-heap 分配这种内存可以被多个设备共享在 RK3588 上做零拷贝必须用 DMA_HEAP 模式。初始化方式是在mpp_init之后再调用一次recommend use MPP_DEC_SET_MMU_MODE? 不对 // 正确做法设置解码器的内存模式 MppDecSetCfg cmd; cmd.buf_mode MPP_BUFFER_MODE_DMA_HEAP; mpi-control(ctx, MPP_DEC_SET_CFG, cmd);实际上 MPP 的 API 里这个控制命令是通过MPP_DEC_SET_CFG传入一个MppDecCfg结构体来实现的。多路解码时这个配置尤其重要直接影响内存分配策略。4.3 多路解码时如何避免线程模型踩坑8 路 RTSP 解码最自然的设计是每个流一个线程每个线程一个 MPP 解码器实例。我刚做的时候就是这么干的结果发现 CPU 占用还是偏高仔细分析发现线程之间因为 MPP 内部全局锁和 dma-buf 分配竞争导致不少等待。后面换成了解码线程池 事件分发模型。简单说固定 4 个解码线程每个线程可以处理多个解码上下文用 epoll 在网络事件和数据就绪事件之间做多路复用。每个解码上下文只负责一个流。这个改造让 CPU 占用下降了 5-8%多路并发时更稳定。不过这个优化有点超前先初始化基础功能后期再考虑。第一部分先把能解出画面这件事做对。4.4 解码异常排查延迟线、花屏、丢帧怎么应对接入了 8 路摄像头之后我陆陆续续遇到几个怪问题偶尔出现花屏多发生在摄像头画面变动剧烈时某一路出现cant find suitable delayline这类内核日志这个随机性很强不是必然出现长时间运行之后解码延迟越来越大最后掉帧严重花屏的原因我排查了比较久最终定位是部分低端摄像头的 H.264 码流不是干净的 Annex B 格式里面存在长度码AVCC 格式混入的情况。MPP 对纯 Annex B 支持最好所以我写了一个简单的码流规整层把所有送入 MPP 的分片都格式化为 Annex B。处理好之后花屏基本消灭。延迟变大的问题那是因为解码器输出队列里堆积的 frame 太多用户态消费速度跟不上。解决办法是检测到队列超过阈值时主动丢帧具体做法是判断mpp_frame_get_eos和帧时间戳如果当前帧与已处理帧的间隔太大直接不传给 RGA 处理只释放 buffer。5. RGA 2D 加速从 NV12 到 RGB、缩放、拼接的硬件飞驰5.1 一个反直觉的事实RGA 做格式转换比 CPU 快两个数量级如果不用 RGA用 CPU 做 NV12 到 RGB 的转换1080p 的单帧在 RK3588 的 Cortex-A76 核上大概需要 12-18ms。12 路就是每路每秒 30 帧你自己算 CPU 要用多少核才能转得动。但同样的转换交给 RGA单帧时间在 1ms 甚至更低看数据宽度CPU 几乎不用参与。很多来自纯 x86 背景的朋友习惯用 SIMD 优化或者 OpenCV 的cvtColor去解决格式转换问题。嵌入式平台上的最佳实践不是优化 CPU 代码而是直接把活交给硬件去做。这是我一开始做这个项目时最难扭转的思维定势。RGA 能做的常见操作格式转换NV12、NV21、YUYV、RGB888、BGR888、RGBA8888、BGRA8888 等几何变换任意角度旋转0/90/180/270 和任意角度、水平/垂直镜像缩放双线性、最近邻等多种滤波模式最大支持 8192x8192 输入裁剪和拼接把一块 buffer 的多个区域分别贴到目标 buffer 的不同位置Alpha 混合两层叠图5.2 RGA 的三种 API 编写方式ioctl、im2d、librga 的 C APIRK3588 平台操作 RGA 有三条路径直接 ioctl 操作/dev/rga原始但繁琐使用 librga 提供的 im2d API函数名一看就明白imscale、imtransfer、imcvtcolor使用更高层的 GStreamer 插件真正做产品我建议用 librga 的 im2d API。它封装好了所有参数构造细节上手快并且有较好的错误提示。一个典型的 NV12 转 RGB888 缩放的调用#include im2d.h #include rga.h // 输入decoder 解码出来的 NV12 帧 rga_buffer_t src wrapbuffer_fd_t(fd_src, src_w, src_h, RK_FORMAT_YCbCr_420_SP); // 输出预分配好的 RGB888 buffer rga_buffer_t dst wrapbuffer_fd_t(fd_dst, dst_w, dst_h, RK_FORMAT_RGB_888); im_rect src_rect {0, 0, src_w, src_h}; im_rect dst_rect {0, 0, dst_w, dst_h}; int ret imresize(src, dst, src_rect, dst_rect, IM_INTER_LINEAR, IM_SYNC); if (ret ! IM_STATUS_SUCCESS) { printf(RGA resize failed: %s\n, imStrError(ret)); }这里的wrapbuffer_fd_t是零拷贝的关键。它不拷贝数据只是告诉 RGA 驱动从 fd 对应的 dma-buf 里去读数据。输入 fd 就是上一节从 MPP frame 里拿到的那个 fd。5.3 零拷贝链路中的 buffer 生命周期管理这是整篇文章最核心的实操经验。零拷贝不等于不管理内存而是让你可以在不同硬件单元之间共享同一块内存但你必须非常小心地管理这块内存的生命周期谁分配谁释放建议所有 buffer 都由自己统一分配不要依赖 MPP 内部 buffer 向外借用除非你非常清楚引用计数的规则RGA 读 MPP 输出的 buffer是借用关系不是持有关系必须在 RGA 操作完成同步模式之后才能把 buffer 还给 MPP 继续使用具体做法是我自己维护一个 buffer 池按需从 dma-heap 分配 fb 大小的 dma-buf然后把这些 fd 提前注册给 MPP 作为它的输出 bufferMPP 支持外部 buffer 池模式。这样解码器直接解到我的 buffer 池里RGA 从池里取用处理完放回池子循环往复。这个方案彻底避开了每次解码都向内核申请一块新内存的开销。// 初始化 buffer 池每个 pool_count 够用即可 int pool_count 8; // 解码深度 处理中帧数量的和 for (int i 0; i pool_count; i) { int fd dma_heap_alloc(heap_fd, img_size); mp_pool_add_fd(pool, fd); } // 把这个 pool 挂到 MPP 解码器的 external buffer group 上 mpp_buffer_group_import(ext_buf_grp, pool-fds, pool_count); mpi-control(ctx, MPP_DEC_SET_EXT_BUF_GRP, ext_buf_grp);注意设置外部 buffer 组必须在解码器开始接收 packet 之前完成而且要保证 buffer 池的数量足以容纳解码线程当前的工作深度。太少了会阻塞解码流程太多了会浪费内存。5.4 RGA 拼接8 路画面的矩阵贴图体验多路视频接入之后最常见的需求是做一个 N 宫格预览墙。RGA 天然适合干这个事。思路不复杂准备一个大 buffer比如 1920x1080格式 NV12然后对每一路解码帧通过imblend或者improcess把缩略图贴到对应的小格子里。这里有个关键点如果使用improcess可以在一次调用里传入多个源和目标描述符那效率最高如果拆成 8 次单路叠加性能会打折扣。实测下来8 路 1080p 缩放到 480x270 后拼接成一个 1920x1080 的画面整次操作耗时约 3-4msCPU 占用接近于 0。这是 CPU 纯软件拼接完全做不到的数字。6. 零拷贝的底层原理解析dma-buf、ion/dma-heap 与硬件一致性聊到这儿很多人会问为什么你用 fd 传一传数据就不需要拷了这不是魔法这是 Linux 内核 dma-buf 框架的作用。6.1 物理内存、dma-buf、fd 三者是什么关系简化版解释可以这么说dma-buf 是内核里一块可以被多个设备访问的物理内存的描述。它通过一个 fd 暴露给用户态。这个 fd 可以被传递、被复用。MPP 解码器执行完一帧后VPU 已经把 YUV 数据写进了这块内存你把 fd 传给 RGA 驱动RGA 驱动拿到 fd在内核里把 fd 换算到对应的 sg_table物理页列表然后配置 RGA2/RGA3 硬件的 DMA 引擎去读这些页整个过程中用户态和内核态之间没有数据拷贝只有元数据传递6.2 RK3588 上的 DMA 内存分配方式dma-heap vs IONRK3588 内核同时支持 ION 和新的 dma-heap。新代码建议直接用 dma-heap它是 ION 的后继者更简洁兼容性更好。设备树里通常会定义rk_dma_heap节点。用户态分配 dma-buf 的方法// 打开 dma heap 设备 int heap_fd open(/dev/dma_heap/linux,cma, O_RDWR); // 分配一块 1080p NV12 大小的内存 struct dma_heap_allocation_data alloc { .len img_size, .fd_flags O_RDWR | O_CLOEXEC, }; ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, alloc); int dma_buf_fd alloc.fd;RK3588 上有linux,cma和linux,system两个 heap。CMA 用于连续物理内存适合 VPU/RGA 这类需要 DMA 的设备system 是分散页适合某些不需要连续性的场景。视频帧建议用 CMA路径是/dev/dma_heap/linux,cma。6.3 多设备访问时需要显式同步吗这是个容易踩的细节。在 RK3588 上MPP 和 RGA 挂在不同的 IOMMU 域但访问的都是同一块物理内存。大多场景不需要手动做 cache 一致性维护因为 VPU 和 RGA 都是通过 DMA 访问内存它们走的是硬件 cache 一致性。但在某些边界条件下比如你又要用 CPU 去读这块 buffer 做调试就必须做显式 sync。librga 提供了imsync接口内核里有DMA_BUF_IOCTL_SYNC。如果画面一直是马赛克或者花屏而你确认格式和缩放参数没问题检查一下是不是缺了 sync。7. 实战性能优化从 40% CPU 到 8% CPU 的调优过程7.1 第一版实现最容易想到但性能很差的方案第一版代码完全可以用但性能惨不忍睹。我做了一个简单的性能基线测试结果如下方案8 路 1080p 解码 缩放CPU 占用延迟MPP 解码 CPU 软件缩放满帧运行35-45%80-120msMPP 解码 RGA 硬件缩放零拷贝满帧运行7-9%40-60ms第一版我用mpp_buffer_get_fd拿到 fd 之后用 mmap 把 YUV 数据映射到用户态再用swscale做缩放和格式转换。这种方式能跑满 8 路但 CPU 长期高负载边缘设备稳定性很差散热压不住系统偶发卡顿。第一版性能不佳的根源在于数据在内核物理内存 - 用户态虚拟内存之间来回搬运了好几次每次 mmap 和 memcpy 都是开销。7.2 定位瓶颈perf top 看看 CPU 都去哪儿了我一开始也不确定瓶颈在哪就用了perf top和简单的top -H -p pid观察。结论非常明确CPU 大量的时间花在memcpy和swscale的色彩转换函数里也就是说这不是解码器不够快而是我在解码器和 RGB 数据使用方之间做了太多中介。然后用trace-cmd或bpftrace观察内核中 dma buf 相关操作看到频繁地dma_buf_attach和dma_buf_detach那是每次我 mmap 和 ummap 引起的重复建立映射。这些操作虽然单个很快但每帧、每路累积起来开销就很明显。7.3 优化策略reserve buffer pool 零拷贝调度优化方案分三步走把 MPP 解码输出设置为 external buffer pool 模式让解码器直接解到我们自己管理的内存池里所有 RGA 操作的源和目的都使用 fd 传递杜绝 mmap 用户态映射加一个帧池调度模块解码线程拿到 frame 后投递到一个无锁队列RGA 线程从队列取出 fd 并处理处理完把 fd 归还池子这套方案跑通之后的收益是CPU 占用从 40% 以上降到 8% 左右7x24 小时稳定运行内存占用反而比原来少了 30%因为没有了反复的内存映射和 torture 拷贝。7.4 最终性能数据一个比较有代表性的性能测试结果RK3588 Development BoardLinux 5.10 kernel板载 8GB RAM测试项结果单路 4K30 H.265 解码流畅CPU 5%8 路 1080p30 H.264 解码 NV12-RGB 缩放流畅CPU ~8%16 路 720p25 解码 拼接流畅CPU ~15%解码到输出第一帧延迟不含网络缓冲~35ms这个数据对边缘盒子的硬件资源预算非常有参考价值。8 路视频处理的片子CPU 还能腾出大部分资源去做别的事比如跑一些轻量级的 AI 检测任务。8. 疑难杂症排查记录这段代码跑不过去大概率是这些原因8.1 解码直接报错或程序崩溃报错MppDecIsEndOfStream相关异常确认码流格式是 Annex B 开头不要直接把 AVCC 数据喂给 MPPSegmentation fault检查mpp_frame_get_buffer返回的 MppBuffer 是否为 NULL多路解码时尤其容易因为 buffer 竞争导致这种问题初始化失败检查/dev/dma_heap/linux,cma是否存在有些旧内核路径是/dev/ion8.2 RGA 操作异常输出的图片是黑色的目标 buffer 没有正确 mapped或者 fd 传错了常见于 buffer pool 管理混乱导致 fd 复用问题图片颜色错乱格式宏写错了注意 NV12 是RK_FORMAT_YCbCr_420_SPBGR888 是RK_FORMAT_BGR_888很容易搞混图片有条纹变形缩放尺寸设置问题检查源和目的的 stride行字节数是不是按照 16 字节对齐计算的我遇到过一个特别隐蔽的问题librga 中的RGA_BUF_VERSION和内核驱动版本不匹配导致某些函数调用返回IM_STATUS_FAILED但日志不打印具体原因。后来在 librga 的 im2d 头文件里看到版本检查逻辑后续升级了内核驱动才彻底解决。8.3 RTSP 拉流环节容易忽略的点RTSP 拉流本身不在 MPP/RGA 的范围内但多路视频处理和它强相关。我遇到最经典的问题是某些摄像头的 RTP 时间戳不准导致解码帧时间间隔忽大忽小网络抖动引起 RTSP 重传解码端收到乱序的 SPS/PPS我的处理方式是拉流用 FFmpeg 的 RTSP 客户端做 1-2 秒的 jitter buffer并且定期刷新 SPS/PPS。这个做法基本可以覆盖市面主流摄像头。8.4 风扇转速和散热对长时间稳定性的影响RK3588 满负荷解码时发热非常可观尤其是多路 4K 场景。如果你的板子有风扇接口建议用 PWM 风扇根据温度自动调速。这虽然不属于视频链路的技术话题但我在实际测试中发现高温降频对解码性能的影响远比想象中大特别是多路并发场景一个不小心系统降到低频率掉帧就开始了。系统读取风扇转速和 CPU 温度的方法是通用的# 查看 CPU 温度 cat /sys/class/thermal/thermal_zone0/temp # 查看风扇转速根风扇接口不同路径有差异 cat /sys/devices/platform/pwm-fan/hwmon/hwmon*/fan1_input9. 进一步扩展把 RGA 预处理接到 NPU 推理链路上写到这里零拷贝的视频解码链路已经全部打通。如果只做预览墙和录像到这就够了。但我的场景还要做 AI 检测这里顺便讲讲 RGA 输出如何直接喂给 NPU。RK3588 的 NPU 推理工具链中rknn-toolkit2 支持传入 dma-buf fd 作为输入。这意味着 RGA 处理完的 RGB 图像也不需要拷贝到 CPU直接rknn_inputs里设置buf (void*)buf_fd对应版本 API 不同然后调用推理接口。链路就成了VPU 解码 - dma-buf fd - RGA 缩放/格式转换 - dma-buf fd - NPU 推理整条流水线没有任何 memcpy。我在 YOLOv8s 模型实测中输入 640x6408 路并发做检测NPU 单次推理大约 25-35ms看模型优化程度CPU 占用可以忽略不计。这个数字对智能视频分析盒子来说相当可用了。需要注意RKNN 的输入 buffer 格式有严格对齐要求通常 16 字节对齐RGA 输出的 stride 要手动设置对齐否则推理结果会错位。10. 最后分享一点踩坑后的心得一开始做这个项目时以为把 MPP 和 RGA 的 API 调通就完事了真正做完才发现性能调优的难点从来不在 API而在数据的流向设计。你为每帧数据多安排一次不经意的拷贝整个 8 路吞吐量就会下降一个量级。RK3588 这套 MPP RGA RKNN 的组合硬件能力确实足够强但软件栈的门槛也在调用的层次和版本的匹配上。强烈建议在做产品之前先把官方 SDK 版本的库关系和设备树配置捋清楚能省下至少一周的零碎时间。如果你正在做多路视频类的边缘计算设备我的建议是先画清楚数据流图明确每一帧内存从哪来、到哪去、谁读谁写然后再去写代码。硬件资源不会骗人把这份功夫花在前面后端的稳定性和性能会很从容。