尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RK3588多路视频硬解码与RGA零拷贝流水线实践

RK3588多路视频硬解码与RGA零拷贝流水线实践 做边缘计算项目这两年RK3588 几乎是我经手最多的一颗芯片MPP 硬解码、RGA 2D 加速、零拷贝这些词也成了日常。但说实话很多刚上手的朋友第一反应是拿 CPU 去堆解码等 8 路视频一上来就发现问题大了CPU 占用飙到 80%画面还狂掉帧最后又把锅甩给芯片性能不够。其实 RK3588 的 VPU 和 RGA 能力非常强大多数时候是没走上正确的硬件加速通路。这篇内容我围绕一个落地项目整理用 MPP 做多路视频硬解码用 RGA 做缩放和格式转换再把两者通过 dma-buf fd 串成零拷贝流水线。主要解决两块问题一是多路 1080P 视频的解码性能二是解码之后像素数据在 CPU 里反复横跳导致的带宽浪费。内容会覆盖 MPP 完整调用流程、RGA 实操、零拷贝链路怎么搭、多路并发模型怎么设计以及我踩过的各种坑。适合已经能跑通单路解码、但准备做多路视频墙、多路 RTSP 接入、或者解码后接 yolov8 推理的开发者参考。1. 方案设计与资源盘点RK3588 上为什么是 MPP RGA1.1 硬件资源怎么分工VPU、RGA、NPU 各自擅长什么RK3588 的媒体处理能力不是靠 CPU 硬顶而是把任务拆给了几个专门的硬件单元这点和 x86 平台的思维很不一样。我简单盘点一下要参与视频链路的几个核心模块VPU视频编解码主力支持 H.265、H.264、VP9、AV1 等主流编码格式硬解能力官方标称能到 8K30fps。多路 1080P 解码是它的强项实际能跑到十几路甚至更多取决于码流分辨率和帧率。RGA2D 图形加速单元RK3588 里同时有 RGA3 大核和 RGA2 小核专门做缩放、旋转、镜像、格式转换、裁剪、alpha 混合这类操作。很多开发者只盯着 VPU觉得解完码就完事了实际上真正卡性能的往往是后续的图像处理。NPU6TOPS 算力配合 RKNN 工具链跑 yolov8 这类模型可以接在解码链路后面做 AI 推理。周边ISP、MIPI CSI、HDMI、VOP 显示控制器视频要上屏也得经过它们。我见过不少项目把 RGA 当空气解码完用 CPU 去转 YUV 到 RGB、做缩放结果每路视频吃掉一个 CPU 核多路一上就废了。RK3588 的 CPU 是 4 个 A76 加 4 个 A55虽然不算弱但像素级操作用 CPU 做太亏了。正确的分工应该是CPU 只做控制面把码流拆包、调度、状态管理这些事干好像素面的活交给 VPU、RGA、NPU 这些硬件单元。1.2 软解与硬解的取舍多路场景下的性能账先算一笔账。用 FFmpeg 的 libx264 或者纯 CPU 解码RK3588 上软解 1 路 1080P H.264 大概要占一个 A76 核的大部分软解 4 路时 CPU 基本就饱和了还要给系统和其他业务留余量所以 4 路以上软解基本不可行。换上 MPP 硬解之后VPU 解码一帧 1080P H.264 的 CPU 开销可以忽略主控 CPU 只负责喂码流和收帧。我实际做 16 路 1080P 解码时CPU 占用能控制在 20% 到 30% 左右剩下的算力还能跑业务逻辑。这就是硬解和软解最直观的差距。那为什么不直接拿 FFmpeg 的 h264_rkmpp 解码器跑这个解码器本身也是走 MPP 硬解的单路播放场景完全够用。但问题在于你要在 FFmpeg 框架里把解码输出的 dma-buf fd 安全地拿出来传给 RGA再做零拷贝链路绕且版本兼容性问题多。MPP 是底层平台直接操作它控制力最强也最接近零拷贝的本质。如果只是做单路播放用 FFmpeg 就行一旦涉及多路和性能优化我建议直接在 MPP 层写。1.3 零拷贝到底省了什么数据通路对比我用大白话解释一下零拷贝到底省了什么。传统的视频处理路径是码流 → VPU 解码到内核内存 → memcpy 到用户空间缓冲区 → CPU 做格式转换/缩放 → 再把结果交给显示或编码。每一步像素数据都要被 CPU 搬运一遍1080P 一帧 NV12 数据大概是 3MB听起来不多但 16 路 25fps 就是每秒 400 帧算下来每秒要搬 1.2GB 以上的内存。零拷贝路径则是码流 → VPU 解码到 dma-buf → 拿着 fd 直接传给 RGA → RGA 输出到另一个 dma-buf → fd 再直接给显示、编码器或 NPU。整条链路上 CPU 不碰像素数据只传递句柄和坐标参数。嵌入式平台的 DDR 带宽本来就不富裕省下这一大块带宽系统流畅度完全是两个档次。提示零拷贝不是玄学本质就是谁分配的内存谁消费数据不落地用户空间。RK3588 上 VPU、RGA、VENC、VOP、NPU 都支持 dma-buf fd 输入输出这是整个方案成立的硬件基础。2. MPP 视频硬解码全流程从 API 到多路码流接入2.1 MPP 核心对象与基本调用流程MPPMedia Process Platform把编解码能力封装成了一套统一的 C API核心就几个对象MppCtx 是编解码上下文句柄可以理解成一个通道MppApi 是操作接口集合通过 mpp_create 获得MppPacket 承载输入码流MppFrame 承载解码输出帧MppBuffer 和 MppBufferGroup 负责统一管理内存。基本流程很固定先创建上下文再初始化设置输入编码类型然后进入循环喂码流、取帧。第一次写 MPP 的代码时对照官方 demo 里的 mpi_dec_test 看基本能照葫芦画瓢。核心代码骨架是这样#include mpp_api.h MppCtx ctx NULL; MppApi *mpi NULL; // 1. 创建解码上下文 mpp_create(ctx, mpi); // 2. 设置输入码流类型H.264 或 H.265 MppCodingType type MPP_VIDEO_CodingAVC; // 或 MPP_VIDEO_CodingHEVC mpi-control(ctx, MPP_CMD_SET_INPUT_TYPE, type); // 3. 初始化为解码器 mpi-init(ctx, MPP_CTX_DEC); // 4. 可选开启 split mode让 MPP 自己切分帧 RK_U32 split_mode 1; mpi-control(ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, split_mode);这里有个容易被忽略的细节MPP_CTX_DEC 是纯解码MPP_CTX_ENC 是编码还有 MPP_CTX_DEC_ENC 这类组合模式一般用不到。类型别设错否则 control 会返回错误。2.2 码流接入RTSP 拉流与 H.264/H.265 帧处理实际项目里码流来源最多的是 RTSP 网络流。RK3588 上我有两种接法一种是用 FFmpeg 拉 RTSP拿到 AVPacket 之后把数据塞给 MPP另一种是 live555 直接拉流再喂给 MPP适合对依赖体积和启动速度敏感的场景。我项目里用的是前者因为 FFmpeg 对各种摄像头和视频源的兼容性最好。有一个坑必须提醒MPP 解码需要的是 Annex-B 格式的 ES 流也就是带起始码 00 00 00 01 的裸流而 FFmpeg 解封装后通常给你的是 AVCC 格式也就是带 NALU 长度前缀的格式。我一开始没注意这个直接塞给 MPP结果解码输出花屏。解决办法是在解封装后加一个 bitstream filter把 AVCC 转成 Annex-Bav_bsf_init(bsf_ctx, h264_mp4toannexb) # H.265 对应 hevc_mp4toannexb或者在初始化 FFmpeg 解封装器时对每个视频流手动插入对应的 bsf。转换完之后把 AVPacket 的 data 和 size 拷出来喂给 MPP 即可。喂码流时还有一个选择如果不开 split mode你必须自己按帧切包一帧一个 MppPacket如果开了 split mode可以一次性塞一大块数据MPP 内部自己找帧边界。我实测下来开了 split mode 更稳码流稍微不干净也不容易崩而且 CPU 占用更低。喂包的循环大致是MppPacket packet NULL; mpp_packet_init(packet, av_pkt-data, av_pkt-size); mpp_packet_set_pts(packet, pts); // 时间戳一定要传编码和同步要用 mpi-decode_put_packet(ctx, packet); mpp_packet_deinit(packet); // 只释放 packet 外壳数据仍由 FFmpeg 管理2.3 解码输出帧的获取与 buffer 归还时机解码完成后从上下文里取帧MppFrame frame NULL; mpi-decode_get_frame(ctx, frame); if (frame) { // 关键信息 int width mpp_frame_get_width(frame); int height mpp_frame_get_height(frame); MppFrameFormat fmt mpp_frame_get_fmt(frame); // 一般是 NV12 / NV21 RK_S64 pts mpp_frame_get_pts(frame); // 拿到背后的 dma-buf buffer MppBuffer buffer mpp_frame_get_buffer(frame); int fd mpp_buffer_get_fd(buffer); // 这是零拷贝的入口 // 使用完毕后归还 mpi-decode_put_frame(ctx, frame); }网上很多 demo 到这里就拿完数据结束了但归还这个动作非常关键。MPP 解码器的内部 buffer 是复用的decode_get_frame 出来一帧后如果你一直不归还解码器缓冲池里的可用 buffer 会越来越少最后直接卡死。常见表现是解码跑一会儿之后不再输出帧或者卡在某一路。还有一点MPP 解码输出帧的宽高不一定是你输入码流的显示宽高特别是 H.264 码流里可能出现宽高 16 对齐的情况。用 mpp_frame_get_width 拿到的可能是解码器实际 buffer 的行数而 mpp_frame_get_hor_stride 和 ver_stride 才是对齐后的实际分配尺寸后面给 RGA 传参数时要用 stride不要直接用 width/height尤其是做缩放时否则画面会歪或者有绿边。2.4 多路解码的并发模型设计多路解码的并发模型我试过每路一个上下文 每路一个解码线程也试过固定线程池 上下文池。结论是每路必须有一个独立的 MppCtx绝不能共享线程方面每路一个 pthread 最省心控制逻辑也简单。网络收流和解码要拆开我一般用两个环节网络线程负责从 RTSP 拉流、做 bsf 转换、把 AVPacket 塞进一个环形缓冲区解码线程从环形缓冲区取数据调 decode_put_packet然后 decode_get_frame拿到帧之后丢给下游 RGA 处理线程。这样网络卡顿不会直接阻塞解码解码慢了也不会把网络线程拖死。MPP 内部的 API 没有为跨线程并发同一个 ctx 做保护多线程同时操作同一个 ctx 会导致死锁或野指针崩溃。我踩过一次排查了很久才发现是两路解码线程误用了同一个 ctx。所以最稳妥的方式是MppCtx 和线程一一绑定一个 ctx 同时只允许一个线程操作。注意多路解码时 MPP 每个解码器都会分配自己的内存池一路 1080P 解码器大概占几十 MB 内存16 路就是几百 MB。内存不够时解码会报 buffer group 分配失败要提前评估好。3. RGA 2D 加速实操格式转换、缩放与 fd 直通3.1 RGA 的边界能做什么不擅长什么RGA 的全称是 Raster Graphic Acceleration定位是轻量 2D 引擎。它最拿手的几件事任意尺寸缩放、NV12/NV21 和 RGB 系列格式互转、旋转镜像、矩形裁剪、alpha 混合。我项目里最常用的组合是解码出来是 NV12要转成 RGB888 给显示用同时缩放到目标分辨率这是 RGA 的标准戏码。RGA 做不了的也有它不做 3D 变换不做复杂的透视校正色彩管理能力有限高精度颜色处理别指望它它对格式的支持虽然广但不同 chip 版本的能力有差异RK3588 的 RGA3 能力强一些RGA2 弱一些代码里要判断能力集。刚上手时容易高估它实际规划方案前先查一下当前内核和 librga 支持的能力矩阵。3.2 librga 两种调用风格rga_info_t 与 im2dRGA 的用户空间库是 librga我用到的主要有两种 API 风格。老一点的 rga_info_t 加 rga_blit 接口代码直观资料多新一点的是 im2d 系列接口封装了更友好的 buffer 描述和操作组合官方新项目推荐 im2d。我项目里用的是 im2d因为操作组合写起来更简洁。老式接口大概是这个感觉#include rga.h rga_info_t src, dst; memset(src, 0, sizeof(src)); memset(dst, 0, sizeof(dst)); src.fd src_fd; // 源 dma-buf fd src.mmuFlag 1; src.rect.x 0; src.rect.y 0; src.rect.width src_w; src.rect.height src_h; src.format src_fmt; // 例如 RK_FORMAT_YCbCr_420_SP dst.fd dst_fd; dst.mmuFlag 1; dst.rect.x 0; dst.rect.y 0; dst.rect.width dst_w; dst.rect.height dst_h; dst.format dst_fmt; int ret rga_blit(src, dst, NULL);im2d 风格的区别是把 buffer 描述和操作分开可以链式调用#include im2d.h #include rga.h rga_buffer_t rga_src wrapbuffer_fd(src_fd, src_w, src_h, src_wstride, src_hstride, src_fmt); rga_buffer_t rga_dst wrapbuffer_fd(dst_fd, dst_w, dst_h, dst_wstride, dst_hstride, dst_fmt); im_rect src_rect {0, 0, src_w, src_h}; im_rect dst_rect {0, 0, dst_w, dst_h}; // 同时做格式转换 缩放 int ret improcess(rga_src, rga_dst, src_rect, dst_rect, 0, 0, 0, IM_CVT_COLOR | IM_SCALE);两种风格底层都是走 /dev/rga 的 ioctl性能没有差别选一个用熟了就行。im2d 文档更全出问题也好查我用的是 im2d。3.3 从 MPP 帧到 RGA 输入的零拷贝衔接零拷贝衔接是这个方案里最重要的一环。MPP 解码出来的 MppFrame背后是一块 dma-buf我可以拿到它的 fdRGA 的输入也支持用 fd 来描述。这样一接像素数据完全不用落地用户空间。具体的代码路径是从 MppFrame 拿 MppBuffer再从 MppBuffer 拿 fd最后把这个 fd 直接填进 RGA 的源描述里。目标端如果是继续走编码或显示也同样用 fd。我放一段实际用的核心逻辑// 1. 从 MPP 解码输出帧提取 dma-buf fd MppBuffer src_buf mpp_frame_get_buffer(frame); int src_fd mpp_buffer_get_fd(src_buf); // 2. 目标缓冲区从哪里来自己管理一个 RGA 输出 buffer group MppBufferGroup dst_group NULL; mpp_buffer_group_get_external(dst_group, MPP_BUFFER_TYPE_DMA_HEAP); MppBuffer dst_buf NULL; mpp_buffer_get(dst_group, dst_buf, dst_size); int dst_fd mpp_buffer_get_fd(dst_buf); // 3. 用 fd 构造 RGA 操作 rga_buffer_t rga_src wrapbuffer_fd(src_fd, src_w, src_h, src_wstride, src_hstride, fmt); rga_buffer_t rga_dst wrapbuffer_fd(dst_fd, dst_w, dst_h, dst_wstride, dst_hstride, dst_fmt); // 4. 执行操作 int ret improcess(rga_src, rga_dst, src_rect, dst_rect, 0, 0, 0, IM_CVT_COLOR | IM_SCALE); // 5. 用完归还解码帧 mpi-decode_put_frame(ctx, frame);这里有个关键点wrapbuffer_fd 传入的宽高和 stride 必须是解码器实际对齐后的值不能拿显示宽高直接填。我曾经因为 width 和 wstride 混用缩出来的图是歪的排查了很久。如果你不确定用 mpp_frame_get_hor_stride 和 mpp_frame_get_ver_stride 拿对齐后的值。3.4 典型落地链路视频墙拼接与推理前处理我实际搭过两条链路一条是视频墙一条是推理前处理都跑通了。视频墙链路4 路 1080P RTSP 流 → MPP 硬解出 4 路 NV12 帧 → RGA 分别缩放到 960×540 → 再通过 RGA 把 4 块拼成 2×2 的一整帧 → 送给 VENC 编码成一路 1080P 推出去。整个过程像素面零拷贝CPU 只处理控制逻辑。这个方案做 9 宫格、16 宫格都行RGA 负责拼图VENC 负责编码输出。推理前处理链路解码出 NV12 帧 → RGA 转成 RGB888 并缩放到模型输入尺寸比如 640×640→ 输出到一块 RGA 分配的 dma-buf → 把这块的 fd 传给 RKNN 做推理。yolov8 部署到 RK3588 时前处理是最容易被人忽略的性能瓶颈用 CPU 做归一化和 letterbox 很吃算力换成 RGA 之后几乎感觉不到损耗。要注意的是 RKNN 输入 tensor 可能需要连续内存直接在 fd 上构建 rknn_input 时先把 tensor 类型设成带 fd 的 variant具体可以参考 RKNN 手册里 zero copy 输入部分。4. 性能调优与工程化细节内存、线程与实测数据4.1 内存分配策略dma-heap、ION 与 MppBufferGroup零拷贝链路里内存分配方式决定可靠性。MPP 解码器内部有自己的 buffer group输出帧内存默认由它管理一般不用自己分配。但 RGA 的输出 buffer 得自己管我推荐直接用 MPP 的 buffer group 统一管理而不是在 librga 里单独分配。RK3588 的内核里有两套分配接口老的 ION 和新的 dma-heap。新内核基本都走 dma-heap/dev/dma_heap/system 这类设备节点可以分配连续或系统内存。MPP 的 MPP_BUFFER_TYPE_DMA_HEAP 类型封装了这块用 mpp_buffer_group_get_external 创建外部 group 就行省得自己反复调 ioctl。buffer 的复用也很重要。解码输出帧属于解码器内部池用完及时归还RGA 的目标 buffer 可以做成一个双缓冲或三缓冲队列轮换使用避免每帧都重新分配销毁分配销毁本身有开销。16 路场景下如果每帧都重新分配 dma-buf系统会有明显卡顿。内存相关的排查有个技巧连续跑半小时看 /proc/meminfo 和 /sys/kernel/debug/dma_buf/bufinfo如果 fd 数和内存一直在涨基本是某处没有归还 frame 或者没有释放 buffer group。4.2 并发模型与关键参数调优多路场景的线程模型我前面说过基本原则了这里补充几个调参细节。第一解码线程数不一定要等于路数。16 路视频如果码流不太大可以用 8 个线程每线程管理 2 路上下文省线程切换开销。但每路上下文还是独立只是串行处理自己那一路。第二RGA 是硬件共享单元多个线程同时调用 rga_blit/improcess 时 librga 内部有锁并发不会线性加速。我实践下来一个专用的 RGA 处理线程处理 16 路缩放转换也够用多了反而增加锁竞争。MPP 的解码参数里最值得关注的是 MPP_DEC_SET_PARSER_SPLIT_MODE 和 MPP_DEC_SET_OUTPUT_BLOCK。split mode 开起来能减少包切分次数output block 方式选阻塞还是非阻塞我建议用非阻塞加轮询配合条件变量避免解码慢时无限等下去。另外码流不好时解码器可能卡在错误状态我加了一个 watchdog连续 N 帧拿不到输出就重建这路的解码器。线程优先级方面解码线程和 RGA 线程建议设置实时优先级至少比普通线程高。我用的 pthread_setschedparam 设到 SCHED_RR配合对应 rt 权限多路场景下帧率更稳不容易因为调度抖动造成卡顿。4.3 一组实测数据多路 1080P 解码 RGA 处理的资源消耗我在自己的板子上做过一组负载测试给大家一个直观参考。测试条件是16 路 RTSP 拉流视频流为 1080P 25fps H.264平均码流 4Mbps解码端用 MPP 硬解RGA 端把每路缩放成 720P 并转成 NV12。最终整机 CPU 占用在 20% 到 30% 之间内存占用约 400MB 左右其中大部分是解码器内存池系统不卡顿画面基本满帧。单路 4K 60fps H.265 解码加上 RGA 缩放成 1080P 的场景CPU 占用低于 5%。作为对比软解 4 路 1080P 时 CPU 已经飙到 90% 以上而且掉帧明显。RGA 单次操作耗时方面1080P NV12 缩放到 720P NV12 大概 0.8ms 到 1.5ms转 RGB888 会更久一些但也远低于 CPU 方案。场景解码方式CPU 占用是否满帧4 路 1080P H.264软解90%掉帧明显4 路 1080P H.264MPP 硬解5%~8%满帧16 路 1080P H.264MPP 硬解 RGA 720P20%~30%满帧单路 4K 60fps H.265MPP 硬解 RGA 1080P5%基本满帧这些数据受驱动版本、码流复杂度影响不同板子会有浮动但量级可以参考。如果 CPU 占用和我这里差距很大先回头看是不是某段代码做了 memcpy 或 mmap 读像素。4.4 好用的调试工具与环境变量MPP 和 librga 都带了日志开关调试效率差很多。MPP 的日志通过环境变量控制mpp_log_level 控制全局日志mpi_log_level 控制控制面日志设成 5 能看到比较详细的解码流程信息。librga 方面可以用 rga_get_version() 打印版本出现错误时配合 im2d 的 IM_ERR 返回值定位。PP 自带的测试程序也值得仔细研究rk_mpi_dec_test 可以单路解码测试rk_mpi_multi_test 是多路压力测试我多路并发模型就是参考它的结构改的。RK 官方仓库里还有 gst 插件gst-launch-1.0 配合 mpph264dec 和 rga 插件可以快速验证一条流水线是否通不用写代码就能排查问题平时做最小复现非常方便。RK3588 跑 16 路解码时功耗和发热都不小我遇到过长时间满载导致降频、花屏的问题。板子上加了 PWM 散热风扇之后稳定多了。软件层面可以用 thermal 节点读温度温度超过 80 度时主动降帧或者做任务调度别等到降频才反应过来。5. 常见问题与排查技巧实录5.1 画面花屏、绿边、颜色偏色花屏和绿边基本是 stride 和格式不匹配问题。我遇到最多的情况是给 RGA 传了显示宽高而不是实际对齐后的 stride导致 RGA 读取数据时每行错位。解决办法解码侧用 mpp_frame_get_hor_stride 和 mpp_frame_get_ver_stride 拿实际 strideRGA 的 wstride 和 hstride 填对齐值显示区域用原始宽高。颜色偏色通常是格式枚举搞错NV12、NV21 的通道顺序不一样BGR 和 RGB 也要仔细区分。我有一个小技巧先在单帧上把格式固定为 NV12 转 RGB888 跑通再扩展其他格式不要一开始就做多格式自适应否则出了问题不好定位是格式问题还是别的问题。RGA 对宽高有对齐要求某些版本要求宽高是 16 的倍数或者 8 的倍数不满足时直接报错或产绿边这种情况要么补齐 stride要么裁剪到合法尺寸。5.2 帧率上不去、CPU 占用异常帧率上不去先看 CPU。如果 CPU 高先确认解码走的是不是硬解。一个常见的乌龙是FFmpeg 里选的 decoder 名字拼错了实际fallback到了软解CPU 当然高。MPP 日志里能看到 decoder 有没有初始化成功mpp_log_level 设到 5 就能看到。如果 CPU 不高但帧率还是低检查是否偷偷做了 memcpy。有人为了把 MPP 帧接进其他图像库调了 mpp_buffer_get_ptr 然后 memcpy一帧 3MB16 路就是 1.2GB/s 的搬运量带宽跑满帧率自然掉。另一处是 RGA 调用频率RGA 虽然单帧只要 1ms但如果每帧都做多次小操作ioctl 开销会累积。能合并的操作尽量用 improcess 一次完成比如缩放加格式转换一次调用搞定别拆成两个。5.3 死锁、卡死与 fd 泄漏死锁最常见的原因是跨线程共享 ctx 或者 decode_get_frame 后没归还。共享 ctx 属于使用方式错误必须每路独立 ctx没归还 buffer 会导致解码器缓冲池耗尽表现是程序不崩但不出帧了。我加了一个缓冲池水位检查如果连续 2 秒没有新帧输出主动 dump 一下当前缓冲池状态。fd 泄漏一般是 MppBufferGroup 没释放或者 MppFrame 归还逻辑没走到。排查手段我前面提过跑一段时间看 /sys/kernel/debug/dma_buf/bufinfo如果 fd 数量一直在涨基本就是泄漏。还有一个隐蔽问题某些库会隐式复制 fd用 int fd 传参没问题但如果你把 fd 存到结构体里反复使用记得 dup 一份谁持有谁 close。5.4 问题定位速查表现象最可能原因排查顺序花屏/绿边stride 不对或 RGA 宽高未对齐先核对 mpp_frame stride再核对 RGA 参数颜色偏色格式枚举错误、通道顺序颠倒检查 NV12/NV21 与 RGB 格式枚举帧率低且 CPU 高实际走了软解 / 存在 memcpy看 MPP 日志确认硬解grep memcpy/mmap帧率低但 CPU 不高RGA 调用过于频繁 / 链路阻塞减少 RGA 调用次数检查缓冲队列运行一段时间不出帧decode_put_frame 未归还 / 缓冲池耗尽检查归还逻辑观察 buffer 数量fd 持续增长MppBufferGroup 未释放看 dma_buf/bufinfo定位泄漏处提示做多路视频处理时尽量保留一套最小可复现工程。我把 4 路解码 RGA 转格式的最小 demo 单独放在一个目录所有新改动先在这个 demo 上验证再合入大工程。出了问题有干净的对照样本排查效率高很多。6. 写在最后MPPRGA 项目落地中我的几点体会这套方案做完之后我个人的体会是RK3588 这类 SoC 的性能挖掘七分靠硬件选型三分靠数据通路设计。硬件明明提供了 VPU、RGA、NPU如果代码还在用 CPU 硬扛像素等于白花钱。做多路视频处理第一步就是确定好数据通路让 CPU 彻底退出像素搬运这条链路。还有一个小建议是版本管理。MPP 和 librga 的库版本、内核版本、板级 dts 配置都会影响行为我之前因为 librga 版本太旧导致 RK3588 上 RGA3 的能力没有完全暴露性能只有预期的一半。升级到较新的 librga 并重新移植之后才正常。如果你也遇到 RGA 性能异常先检查驱动和库版本别急着改业务代码。最后再分享一个我在多路解码项目里体会最深的事情RK3588 的 VPU 解码能力确实强但真正让它能打的是 RGA 把这套像素处理补齐了。解码只是把码流变成图像数据后面还有缩放、格式转换、拼接、编码、推理前处理一整套链路。把 MPP 和 RGA 串成一条零拷贝流水线之后你会发现同样的硬件能干的事多了一倍不止。希望这篇内容能帮你少走一些弯路有问题欢迎在评论区交流。
返回列表