
1. 为什么RKMPP值得单独拿出来讲搞瑞芯微平台音视频开发的兄弟大概率都绕不开一个东西——MPP。全称Rockchip Media Process Platform是瑞芯微官方提供的一套硬件编解码抽象层。你在RK3588、RK3568、RV1106这些芯片上做视频编解码不管上层用的是FFmpeg、GStreamer还是自己写的RTSP服务底层十有八九跑的都是它。但MPP这个东西有个特点官方文档不算少但散。交叉编译的说明在SDK的buildroot里API的说明在doc目录下性能调优的经验基本靠社区帖子和自己踩坑。我第一次在RK3568上跑MPP的时候光是把库编出来、让demo跑通就折腾了整整两天。后来在RK3588上做多路解码又遇到了码流分配、buffer复用、RGA配合这一堆问题。这篇东西是我自己从交叉编译到性能优化的一整套实战记录。适合两类人看一类是刚拿到瑞芯微开发板、想把MPP跑起来但不知道从哪下手的另一类是把MPP跑通了、但发现帧率上不去、CPU占用高、多路跑不稳想进一步压榨硬件性能的。我会把每一步为什么这么做讲清楚包括参数怎么算、坑在哪、怎么绕。2. 先把RKMPP的定位搞清楚2.1 MPP在瑞芯微软件栈里的位置很多人一开始会搞混MPP和FFmpeg的关系。简单说FFmpeg是通用的多媒体框架MPP是瑞芯微自己的硬件编解码接口。FFmpeg在瑞芯微平台上可以通过h264_rkmpp、hevc_rkmpp这类解码器调用MPP也可以完全不用FFmpeg直接调MPP的C API。MPP往下对接的是VPU视频处理单元和RGA2D图形加速器。VPU负责H.264/H.265/VP8/VP9这些格式的硬编硬解RGA负责缩放、旋转、格式转换、合成。MPP把这两者的调用封装成统一的MppCtx、MppApi接口你创建解码器的时候指定编码类型送码流进去它吐YUV或RGB出来。这里有个关键点MPP本身不做封装格式解析。你给它的是裸码流annexb或length-prefixed不是MP4文件。所以实际项目里通常是FFmpeg负责demuxMPP负责decodeRGA负责后处理最后送显示或编码。这个分工要在一开始就想清楚不然后面架构会乱。2.2 什么场景下必须用MPP如果你只是做个简单的视频播放用系统自带的播放器或者FFmpeg软解也能跑。但以下几种情况MPP基本是唯一选择多路高清解码RK3588号称能跑32路1080p解码这个只有走VPU硬解才可能软解CPU直接爆。低延迟编码做RTSP推流、视频会议、车载DVR编码延迟要求几十毫秒级别软编根本达不到。CPU资源紧张RV1106这种单核A7的小芯片软解720p都费劲必须硬解。需要和RGA配合做图像处理比如多路视频拼接、AI推理前的预处理RGA的零拷贝通路只有和MPP配合才能发挥。反过来说如果你只是偶尔解个视频、对功耗不敏感、平台又是x86那没必要折腾MPP。选型阶段想清楚能省很多事。2.3 版本选择与SDK获取瑞芯微的MPP是开源的仓库在GitHub上rockchip-linux/mpp。但要注意不同芯片、不同SDK版本对应的MPP分支可能不一样。我的建议是优先用你手上SDK里自带的MPP源码而不是直接clone最新master。原因很简单SDK里的版本是经过瑞芯微验证的和内核驱动、VPU固件匹配。你随便换个版本可能出现ioctl不兼容、固件加载失败这类问题。如果你用的是Buildroot或YoctoMPP通常已经作为package存在了直接make mpp就行。但很多时候我们需要自己交叉编译比如要把MPP集成到一个已有的Qt工程里或者SDK里没有预编译。下面重点讲手动交叉编译。3. 交叉编译从工具链到产物验证3.1 工具链的选择与验证交叉编译第一步永远是确认工具链。瑞芯微平台基本都是ARM架构RK3588是Cortex-A76A55aarch64RK3568是A55aarch64RV1106是Cortex-A7armv7。所以你要先确认目标架构再选对应的工具链。我常用的是Linaro的GCC或者SDK自带的arm-rockchip830-linux-uclibcgnueabihf这类。验证工具链是否可用跑一条命令aarch64-linux-gnu-gcc -v看输出的target是不是aarch64-linux-gnu。如果是说明工具链本身没问题。接下来要确认sysroot也就是目标平台的根文件系统头文件和库。交叉编译MPP需要依赖一些系统库比如libdrm、libion旧版本、pthread等。这些库的路径必须指向目标平台的sysroot不能指向你主机的/usr/include否则编出来的东西链接的是x86的库跑不起来。提示很多人交叉编译失败90%是sysroot没设对。用--sysroot/path/to/target/rootfs显式指定比依赖环境变量靠谱。3.2 CMake交叉编译配置实战MPP用的是CMake构建系统。交叉编译的核心是写一个toolchain file。我一般命名为rk3588.toolchain.cmake内容大概这样set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(TOOLCHAIN_PATH /opt/toolchain/gcc-aarch64-linux-gnu) set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-linux-gnu-g) set(CMAKE_SYSROOT /opt/target/rootfs) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)这里几个点解释一下。CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER的意思是找可执行程序比如cmake自己调用的工具时不要在sysroot里找因为sysroot里没有主机能跑的程序。LIBRARY ONLY和INCLUDE ONLY则是强制只在sysroot里找库和头文件避免误用主机的。然后配置编译选项cmake -B build -DCMAKE_TOOLCHAIN_FILErk3588.toolchain.cmake \ -DCMAKE_BUILD_TYPERelease \ -DHAVE_DRMON \ -DRKPLATFORMON \ -DHAVE_AFBCONHAVE_DRM打开DRM支持用于直接显示RKPLATFORM是瑞芯微平台特有的一些优化HAVE_AFBC是压缩帧缓冲支持RK3588上建议开能省带宽。3.3 编译产物与部署验证编译完成后产物在build/目录下主要是librockchip_mpp.so和一堆测试程序mpi_dec_test、mpi_enc_test等。把这些拷到板子上注意库的搜索路径。可以放到/usr/lib或者设置LD_LIBRARY_PATH。验证第一步跑mpi_dec_test解一个H.264文件./mpi_dec_test -i test.h264 -t 7 -n 100 -o out.yuv-t 7表示H.264-n 100表示解100帧。如果能看到帧率输出、out.yuv大小合理说明MPP基本跑通了。这一步跑不通后面性能优化都是空谈。注意如果报mpp_dev_init失败大概率是内核里的VPU驱动没加载或者设备节点/dev/mpp_service权限不对。先ls /dev/mpp_service确认存在再检查权限。4. 核心API与数据流把MPP用对4.1 解码流程的五个关键步骤MPP的API看起来多但解码流程其实就五步初始化、配置、送码流、取帧、释放。我用伪代码串一遍// 1. 创建上下文 MppCtx ctx; MppApi *mpi; mpp_create(ctx, mpi); // 2. 初始化解码器指定编码类型 mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC); // 3. 配置解码参数 MppDecCfg cfg; mpp_dec_cfg_init(cfg); mpp_dec_cfg_set_u32(cfg, base:split_parse, 1); mpp_dec_cfg_set_u32(cfg, base:need_split, 1); mpp_dec_cfg_set_u32(cfg, base:disable_error, 0); mpp_dec_cfg_set_u32(cfg, base:timeout, 0); mpi-control(ctx, MPP_DEC_SET_CFG, cfg); // 4. 循环送码流、取帧 while (has_data) { MppPacket packet; mpp_packet_init(packet, data, size); mpi-decode_put_packet(ctx, packet); MppFrame frame; while (mpi-decode_get_frame(ctx, frame) MPP_OK) { // 处理frame比如送RGA或显示 mpp_frame_deinit(frame); } mpp_packet_deinit(packet); } // 5. 释放 mpp_destroy(ctx);这里面有几个参数值得展开。split_parse和need_split是控制码流解析方式的。如果你的输入是完整的一帧一帧比如从FFmpeg demux出来的AVPacket设成1让MPP自己拆如果输入已经是单帧可以设0减少开销。disable_error设0表示遇到错误不直接退出而是尝试恢复做流媒体的时候这个很重要网络丢包导致的码流错误不能让整个解码器挂掉。4.2 Buffer管理与零拷贝MPP性能优化的核心之一就是减少内存拷贝。默认情况下MPP解码出来的帧在内部buffer里你decode_get_frame拿到的是引用。如果你要送RGA处理可以直接把MppFrame的fd传给RGA实现零拷贝。关键API是mpp_frame_get_fd和mpp_frame_get_buffer。RGA的im2d接口支持直接传fdMppBuffer buffer mpp_frame_get_buffer(frame); int fd mpp_buffer_get_fd(buffer); // 把fd传给RGA rga_buffer_t src wrapbuffer_fd(fd, width, height, RK_FORMAT_YCbCr_420_SP);这样数据全程在DMA buffer里流转CPU只负责控制不碰数据。实测在RK3588上4路1080p解码RGA缩放走零拷贝比走memcpyCPU占用能从60%降到25%左右。提示零拷贝的前提是MPP的buffer group配置正确。创建解码器时要设置MPP_DEC_SET_EXT_BUF_GROUP让MPP从你指定的group里分配buffer这样fd才是稳定的、可共享的。4.3 编码端的配置要点编码流程和解码类似但配置项更多。以H.264编码为例关键参数有参数说明推荐值rc_mode码率控制模式VBR或CBRbps_target目标码率根据分辨率定fps_in_num/fps_in_denom输入帧率30/1gop关键帧间隔30-60quality编码质量根据场景调profile编码档次high码率计算有个经验公式bps width * height * fps * factor。factor对于H.264一般在0.07到0.15之间。比如1080p301920108030*0.1 ≈ 6.2Mbps。这个只是起点实际要根据画面复杂度调。CBR适合直播推流码率稳定VBR适合本地录制画质优先。RK3588的编码器支持到4K60但多路的时候要注意VPU的总带宽限制。5. 性能优化从能跑到跑得好5.1 多路解码的架构设计单路解码跑通不难难的是多路。RK3588标称32路1080p解码但实际能跑多少路取决于你的架构。我做过一个16路1080p30解码AI推理的项目总结下来几个关键点第一解码线程和取帧线程要分开。MPP的decode_put_packet和decode_get_frame可以在不同线程调用送码流是异步的取帧是同步的。如果在一个线程里既送又取容易因为取帧阻塞导致送码流不及时VPU饿死。第二每路解码器独立一个MppCtx但共享一个buffer group。这样内存分配统一管理避免每路各自malloc导致碎片。第三用MPP_DEC_SET_PARSER_SPLIT_MODE让MPP内部多线程解析。RK3588有8个核解析可以并行。实测下来16路1080p30CPU占用大概40%VPU占用70%左右。如果架构没设计好比如所有路共用一个线程可能8路就卡了。5.2 RGA配合的带宽优化RGA虽然快但也不是免费的。每次RGA操作都要读写内存带宽是瓶颈。RK3588的DDR带宽大概在10GB/s级别4路1080p60的YUV420数据每帧约3MB每秒240帧就是720MB/s的读写来回就是1.4GB/s。如果再加上缩放、格式转换带宽很快就吃紧。优化手段有几个。一是尽量用RGA的原地操作比如只做格式转换不做缩放减少一次读写。二是用AFBC压缩RK3588支持能把带宽降一半。三是合并操作比如缩放格式转换旋转RGA可以一次完成不要分多次调用。注意RGA的并发数有限RK3588上有多个RGA核心但每个核心同时只能处理一个任务。多路的时候要用RGA的任务队列不要每个线程直接调否则会互相阻塞。5.3 内存与缓存调优MPP的buffer分配策略对性能影响很大。默认用的是ion或dma-buf具体看内核版本。RK3588新内核用dma-buf heap。分配的时候要注意对齐VPU对buffer的地址对齐有要求一般是16或32字节对齐。另外YUV数据的stride行跨距也很关键。如果stride和width不一致RGA处理的时候要额外处理影响效率。建议创建buffer的时候显式指定stride让它和width对齐到64字节。缓存方面如果CPU要访问解码后的YUV数据比如做AI推理前的预处理要注意cache一致性。MPP出来的buffer默认是non-cacheable的CPU读会慢。可以用mpp_buffer_sync做cache同步或者让RGA直接处理避免CPU碰。6. 常见问题与排查实录6.1 解码失败问题速查现象可能原因排查方法mpp_dev_init失败驱动未加载/权限不足ls /dev/mpp_service检查权限解码花屏码流不完整/stride不对检查输入码流确认stride解码卡住送码流不足/取帧阻塞检查线程模型加超时帧率低单线程/拷贝过多多线程零拷贝内存泄漏frame未deinit检查每帧是否释放6.2 几个我踩过的坑第一个坑decode_get_frame返回的frame如果不mpp_frame_deinitbuffer不会释放跑一会儿就OOM。这个在demo里可能看不出来因为demo跑完就退出了。实际长跑必须每帧释放。第二个坑多路的时候如果所有路用同一个MppCtx会串流。必须每路独立ctx。但buffer group可以共享。第三个坑RK3588的VPU固件版本要和MPP库版本匹配。有次我升级了内核但没升级MPP结果解码直接失败报固件加载错误。后来把MPP也升到对应版本才好。第四个坑RGA处理的时候如果源和目标的格式不一致比如源是NV12目标是RGBRGA会自动转换但性能会下降。如果可能尽量让格式一致转换放到最后一步。6.3 性能瓶颈定位方法性能上不去的时候不要瞎猜用工具定位。top看CPUcat /sys/kernel/debug/mpp_service/status看VPU占用perf看热点函数。如果VPU占用低但帧率上不去说明是送码流或取帧的瓶颈不是VPU的问题。如果VPU占用高但帧率还是低说明码流复杂度太高或者VPU频率不够可以试试调频。RK3588的VPU频率可以通过/sys/class/devfreq调整但一般不建议手动调默认的DVFS策略已经够用。如果确实需要可以看看是不是散热问题导致降频。7. 一些实战心得MPP这个东西入门门槛不算高但要用好需要理解整个数据流。我的建议是先把单路跑通把API的每个参数都试一遍看看效果。然后再上多路这时候架构设计比API调用更重要。另外瑞芯微的社区虽然不如一些大厂活跃但GitHub上的issue和wiki还是有不少干货。遇到问题先搜issue大概率有人踩过。实在不行看源码MPP的源码不算复杂核心逻辑在mpp_dec.cpp和mpp_enc.cpp里读一遍对理解行为很有帮助。最后说个实际项目里的经验不要过度优化。我见过有人为了省一点CPU把架构搞得特别复杂结果维护成本极高。先保证功能正确、稳定再谈优化。MPP本身的性能已经很强了大部分场景下把零拷贝和多线程做好就够用了。