ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RK3588硬件编解码实战:MPP架构解析与性能优化指南

RK3588硬件编解码实战:MPP架构解析与性能优化指南 1. 从一块RK3588板子说起MPP到底解决了什么问题第一次在RK3588上跑视频解码的时候我天真地以为跟PC上一样装个FFmpeg就完事了。结果一跑才发现CPU占用率直接飙到400%以上四路1080p同时解码的时候画面开始丢帧风扇呼呼转得像要起飞。后来翻RK3588的数据手册才知道这颗芯片里藏着一个独立的VPUVideo Processing Unit专门负责视频编解码而要让应用程序用上这个硬件单元中间必须经过一层软件框架——这就是MPP。MPP的全称是Media Process Platform翻译过来叫媒体处理平台。它不是某个具体的编解码器也不是一个播放器而是一套介于应用程序和底层硬件编解码单元之间的中间层软件框架。你可以把它理解成一个“翻译官”上层应用说“我要把这段H.264视频解成YUV数据”MPP负责把这句话翻译成VPU能听懂的寄存器操作和DMA指令然后把VPU处理完的数据再搬回内存给应用使用。没有这层翻译应用就只能用CPU软解性能差距大概是十倍起步。为什么我要专门写MPP这个系列因为在实际项目里我发现太多人在RK3588、RK3568这类瑞芯微芯片上做视频相关开发时要么直接调FFmpeg的rkmpp插件但不知道底层发生了什么要么想自己写硬编解码却卡在MPP的API文档上。MPP的官方文档虽然全但偏底层很多概念需要你有一定的视频处理和嵌入式基础才能看懂。我打算用几篇的篇幅把MPP从“是什么”到“怎么用”再到“怎么调优”完整地梳理一遍第一篇先解决最基础的问题MPP的架构长什么样它在整个系统里处于什么位置以及不同平台上MPP的支持情况有什么区别。这篇文章适合谁看如果你正在RK3588、RK3568、RK3399这类瑞芯微平台上做视频编解码相关的开发或者你打算用FFmpeg的rkmpp硬件加速但想搞清楚背后的原理又或者你只是好奇一块嵌入式芯片是怎么做到同时解码十几路视频的那这篇内容应该能给你一个清晰的图景。我会尽量用生活化的类比来解释架构层面的东西同时给出足够的技术细节让你看完之后能自己判断某个场景下该不该用MPP、该怎么用。2. MPP的软件架构三层结构拆解2.1 从应用到硬件的数据流转路径要理解MPP的架构最好的方式是从数据流的走向来看。假设你现在有一个H.264码流文件想把它解码成YUV帧显示出来数据大概会经历这么几个阶段应用程序调用MPP提供的API把码流数据写入MPP的输入缓冲区。MPP的解析模块先对码流做格式分析提取出SPS、PPS这些参数集信息确定分辨率、帧率、编码档次等关键参数。然后MPP根据这些参数配置VPU的寄存器把码流数据通过DMA搬到VPU的输入FIFO里。VPU硬件开始解码解出来的YUV帧通过DMA写回内存中的帧缓冲区。MPP再通知应用程序“有一帧解好了”应用就可以去取这帧数据做后续处理。整个过程里MPP承担了参数解析、硬件配置、内存管理、同步控制这几件事。它不直接操作像素也不做具体的编解码算法运算那些都是VPU硬件干的。MPP的价值在于把VPU那套复杂的寄存器操作和DMA描述符封装成了相对友好的C语言API。2.2 三层架构的具体划分MPP的软件架构大致可以分成三层我用一个表格来对比各层的职责和典型模块层级职责典型模块开发者接触频率应用接口层提供对外的API封装编解码器的创建、配置、送帧、取帧等操作mpp_create、mpp_init、mpp_encode_put_frame、mpp_decode_get_frame高日常开发主要用这层核心框架层管理编解码器实例、内存池、任务调度、硬件资源分配MppCtx、MppApi、MppBufferGroup、MppTask中调优和排错时需要了解硬件抽象层直接操作VPU寄存器、DMA控制器、时钟和电源管理HAL模块、寄存器定义、中断处理低一般由芯片原厂维护应用接口层是大多数开发者唯一需要直接打交道的部分。MPP提供了一套C风格的API核心对象是MppCtx编解码器上下文和MppApi操作函数集。你创建一个MppCtx指定它是解码器还是编码器然后通过MppApi里的函数指针来调用具体操作。这种设计的好处是编解码器类型对上层透明你换一个codec类型只需要改初始化参数调用的API函数是一样的。核心框架层做的事情就比较复杂了。它要管理多个编解码器实例对VPU硬件的竞争访问因为一块芯片通常只有一个VPU但同时可能有多个应用或线程在请求编解码服务。MPP内部有一个任务队列和调度机制把各个实例的请求排队送给VPU处理。同时它还管理内存池因为视频编解码对内存带宽和容量要求很高频繁的malloc/free会导致碎片化和性能下降MPP会预分配一组缓冲区循环使用。硬件抽象层是跟芯片型号绑定最紧密的部分。不同代际的VPU寄存器定义和操作方式可能完全不同比如RK3588的VPU和RK3399的VPU在寄存器层面就有很大差异。HAL层把这些差异屏蔽掉对上提供统一的硬件操作接口。这一层通常不需要应用开发者关心但如果你要移植MPP到新的芯片平台或者要调试VPU层面的问题就需要深入看这部分代码。2.3 内存管理的设计逻辑MPP的内存管理是一个容易被忽视但非常关键的设计。视频编解码对内存的要求跟普通应用不一样首先数据量大一帧4K的YUV420数据大概12MB如果同时有十几帧在流水线上内存占用很快就上百MB其次DMA操作要求内存物理地址连续不能是散页再次VPU对内存的访问有对齐要求比如某些VPU要求缓冲区起始地址按64字节或4KB对齐。MPP用MppBufferGroup来管理内存。你可以创建一个buffer group指定它管理的内存类型比如DMA heap、ION、或者普通malloc、缓冲区大小和对齐要求。然后从这个group里分配MppBuffer每个MppBuffer代表一块可用的内存区域。解码器输出的每一帧都对应一个MppBuffer应用取到帧之后处理完需要把buffer还回去否则buffer group里的buffer耗尽之后解码就会卡住。这里有一个实际开发中很容易踩的坑如果你取帧之后忘记还buffer程序跑一会儿就会卡死在取帧的调用上。因为MPP的buffer group默认是有限数量的用完了就不会再分配新的。我刚开始用的时候就被这个问题坑过调试了半天才发现是有一帧处理完之后没有调用mpp_buffer_put。3. MPP与VPU的协作机制硬件编解码到底快在哪3.1 VPU在RK3588上的具体形态RK3588的VPU是一个独立的硬件模块跟CPU核心挂在同一个总线上但独立运行。根据RK3588的数据手册它支持H.264、H.265、VP9、AV1等多种格式的硬件编解码解码能力最高到8K60fps编码最高到8K30fps。这些数字听起来很吓人但实际能达到多少取决于你的内存带宽和MPP的配置。VPU内部其实可以分成几个子模块码流解析器负责从码流中提取语法元素熵解码器负责把压缩数据还原成系数反量化和反变换模块把系数还原成残差像素运动补偿模块根据运动矢量从参考帧里取像素最后去块滤波和SAO滤波做后处理。这些步骤全部是硬件流水线并行执行的所以吞吐量远高于CPU软解。MPP跟VPU的协作方式是通过寄存器读写和中断。MPP把一帧的码流数据和参数配置写到VPU的寄存器和输入缓冲区然后启动VPU。VPU解完一帧之后产生一个中断MPP的中断处理程序收到中断后标记这一帧完成然后通知等待的应用程序。整个过程里CPU只负责配置和搬运不参与实际的解码运算所以CPU占用率可以降到很低。3.2 硬解和软解的实际性能对比我在RK3588上做过一组对比测试用同一段4K H.265视频分别用FFmpeg软解和MPP硬解结果如下指标CPU软解MPP硬解CPU占用率单路4K约320%约15%解码帧率18-22fps55-60fps功耗整板约8W约4.5W同时解码路数1路勉强8路以上这个差距在嵌入式场景下是决定性的。RK3588的CPU虽然不弱但用软解跑4K视频基本上就把所有核心吃满了其他任务没法跑。用MPP硬解之后CPU几乎空闲可以同时处理网络接收、AI推理、界面渲染等其他任务。不过硬解也不是没有代价。VPU对码流的兼容性不如软解一些非标准的码流或者有错误的码流软解可能能容错解出来硬解就直接报错了。另外VPU的编码质量在低码率下通常不如x264/x265的软编如果你对编码质量要求很高可能需要调高码率或者用软编。3.3 MPP的零拷贝设计MPP在内存搬运上做了一个很重要的优化叫零拷贝。传统的视频处理流程里解码器输出一帧到内存然后应用把这一帧拷贝到另一个缓冲区做后续处理处理完再拷贝到显示缓冲区。每次拷贝都要消耗内存带宽4K视频一帧12MB60fps就是720MB/s的带宽消耗对嵌入式芯片来说是不小的负担。MPP的设计允许解码输出的MppBuffer直接传递给后续模块使用比如直接送给DRM显示或者送给RGA做缩放中间不需要拷贝。这要求各个模块对内存的格式和对齐方式有统一的约定。MPP的buffer group可以配置成支持这种跨模块共享的模式具体怎么配要看你的使用场景。我在一个视频监控项目里就用了这个特性MPP解码出来的NV12帧直接通过RGA缩放到目标分辨率然后送给编码器重新编码整个链路里没有一次CPU拷贝。相比之前用FFmpeg的流程内存带宽占用降低了大概40%系统整体延迟也小了很多。4. 平台支持矩阵从RK3399到RK3588的差异4.1 不同芯片平台的VPU能力对比瑞芯微的芯片产品线很长从早期的RK3288到最新的RK3588每一代的VPU能力都不一样。MPP作为上层框架虽然API层面尽量保持一致但底层支持的格式和性能特性是有差异的。下面这个表格整理了常见平台的关键差异芯片型号解码格式支持编码格式支持最大解码能力MPP支持状态RK3288H.264, H.265, VP8H.2644K30fps完整支持RK3399H.264, H.265, VP9H.264, H.2654K60fps完整支持RK3568H.264, H.265, VP9H.264, H.2654K60fps完整支持RK3588H.264, H.265, VP9, AV1H.264, H.2658K60fps完整支持从表格可以看出RK3588在格式支持和性能上都有明显提升特别是加入了对AV1解码的支持。AV1作为新一代开源编码格式压缩效率比H.265还要高20%左右在带宽受限的场景下很有优势。不过AV1的编码目前在RK3588上还不支持硬件编码只能软编。4.2 MPP在不同Linux内核版本上的适配MPP运行在Linux用户空间但它依赖内核空间的驱动来访问VPU硬件。这个驱动叫rkvdec解码和rkvenc编码是内核的一部分。不同版本的Linux内核里这些驱动的接口可能有变化所以MPP的版本需要跟内核版本匹配。我在RK3588上用的是Linux 5.10内核搭配的MPP版本是1.0.6。如果你用的是更新的6.1内核可能需要用MPP的develop分支或者等官方发布适配版本。判断方法很简单编译MPP的时候如果报错说找不到某些ioctl或者结构体定义大概率就是内核头文件跟MPP代码不匹配。另外要注意的是有些发行版的内核可能没有默认开启rkvdec和rkvenc驱动。你需要检查内核配置里CONFIG_ROCKCHIP_VDEC和CONFIG_ROCKCHIP_VENC是否被设为y或m。如果没有需要重新编译内核或者加载对应的模块。4.3 在Ubuntu和Debian上的安装方式差异在瑞芯微的官方Buildroot和Debian镜像里MPP通常已经预装了。但如果你用的是自己装的Ubuntu或者Debian就需要手动安装。两种系统的包管理方式不同安装的包名也不一样。在Debian/Ubuntu上可以尝试用apt安装sudo apt update sudo apt install rockchip-mpp rockchip-mpp-dev但很多第三方镜像的源里没有这两个包那就需要从源码编译。编译过程不复杂但有几个依赖需要注意sudo apt install cmake git build-essential git clone https://github.com/rockchip-linux/mpp.git cd mpp mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease -DHAVE_DRMON make -j$(nproc) sudo make install编译选项里-DHAVE_DRMON是为了支持DRM显示的直接输出如果你不需要这个功能可以不加。编译出来的库包括librockchip_mpp.so和对应的头文件安装到/usr/local/lib和/usr/local/include。注意从源码编译MPP时要确保你的内核头文件版本跟运行内核一致。如果编译时用的头文件跟实际运行的内核不匹配运行时可能会出现ioctl调用失败的问题。5. 从FFmpeg看MPP的集成方式5.1 FFmpeg的rkmpp插件是怎么工作的大多数开发者第一次接触MPP其实是通过FFmpeg。FFmpeg从4.0版本开始加入了对rkmpp的支持编译时加上--enable-rkmpp就可以启用。启用之后FFmpeg的h264_rkmpp、hevc_rkmpp这些解码器就会用MPP来做硬件解码。FFmpeg的rkmpp插件本质上是一个适配层它把FFmpeg的AVCodec接口翻译成MPP的API调用。当你用ffmpeg命令解码视频时如果指定了rkmpp解码器FFmpeg会把码流数据通过插件送给MPPMPP调用VPU解码解出来的帧再通过插件返回给FFmpeg的帧队列。这个流程里有一个关键点FFmpeg的帧数据默认是在系统内存里的而MPP解码输出的帧可能在DMA缓冲区里。插件需要处理这两种内存之间的映射和同步。如果配置不当可能会出现帧数据不完整或者颜色不对的问题。5.2 用FFmpeg调用MPP的实际命令如果你只是想快速验证MPP硬解是否工作可以用下面这个命令ffmpeg -c:v h264_rkmpp -i input.mp4 -f rawvideo -pix_fmt nv12 output.yuv这个命令会把input.mp4用MPP硬解成NV12格式的原始YUV数据。你可以通过观察解码速度来判断是否真的走了硬件如果解码速度远快于实时比如4K视频能跑到2倍速以上那基本就是硬解在工作。如果要看更详细的日志可以加上-loglevel debugffmpeg -loglevel debug -c:v h264_rkmpp -i input.mp4 -f null -日志里会显示MPP的初始化信息、buffer分配情况、每帧的解码耗时等。如果看到“mpp: decoder init success”之类的字样说明MPP已经正常加载了。5.3 直接用MPP API和通过FFmpeg的取舍用FFmpeg调用MPP的好处是简单快捷FFmpeg帮你处理了容器解析、码流封装、帧格式转换等一堆琐事。但缺点是灵活性受限FFmpeg的rkmpp插件只暴露了MPP的一部分能力一些高级特性比如自定义buffer group、零拷贝输出、多实例调度策略通过FFmpeg是用不了的。如果你做的是简单的视频转码或者播放用FFmpeg就够了。但如果你要做低延迟的实时视频处理、多路并发解码、或者需要精确控制内存和帧的生命周期那就需要直接调MPP的API。直接调MPP的代码量会大一些但控制力强很多。我在一个需要同时解码16路1080p视频的项目里一开始用FFmpeg发现每路解码器都独立管理自己的buffer内存占用很高而且16个解码器实例对VPU的竞争导致调度不均匀。后来改成直接用MPP API自己管理一个共享的buffer group和统一的任务队列内存占用降低了30%解码延迟也更稳定。6. 搭建MPP开发环境的实操记录6.1 确认硬件和内核支持在开始写代码之前先要确认你的板子和内核确实支持MPP。第一步是检查VPU设备节点是否存在ls /dev/mpp_service ls /dev/rkvdec ls /dev/rkvenc如果这几个设备节点都存在说明内核驱动已经加载了。如果不存在可能是内核配置里没有开启对应的驱动需要重新配置内核。第二步是检查MPP库是否已经安装ldconfig -p | grep rockchip_mpp如果有输出说明库已经装好了。如果没有就需要按前面说的方法从源码编译安装。6.2 编译一个最小的MPP测试程序光看文档不够直观我习惯写一个最小的测试程序来验证环境。下面这个程序创建一个MPP解码器打印它的版本信息然后销毁#include stdio.h #include rockchip/rk_mpi.h int main() { MppCtx ctx NULL; MppApi *mpi NULL; MPP_RET ret; ret mpp_create(ctx, mpi); if (ret ! MPP_OK) { printf(mpp_create failed: %d\n, ret); return -1; } ret mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC); if (ret ! MPP_OK) { printf(mpp_init failed: %d\n, ret); mpp_destroy(ctx); return -1; } printf(MPP decoder initialized successfully\n); mpp_destroy(ctx); return 0; }编译命令gcc -o mpp_test mpp_test.c -lrockchip_mpp如果编译通过并且运行输出“MPP decoder initialized successfully”说明环境基本可用了。如果报错说找不到头文件检查/usr/local/include/rockchip/目录下是否有rk_mpi.h。如果链接时报错检查/usr/local/lib下是否有librockchip_mpp.so。6.3 常见环境问题排查在实际搭建环境的过程中我遇到过几个典型问题这里列出来供参考第一个是权限问题。/dev/mpp_service默认只有root可访问普通用户运行程序会报Permission denied。解决方法要么是用sudo运行要么改设备节点的权限sudo chmod 666 /dev/mpp_service第二个是库版本冲突。如果系统里同时存在多个版本的librockchip_mpp.so链接时可能链接到旧版本。可以用ldd命令检查程序实际链接的是哪个库ldd ./mpp_test | grep mpp第三个是内核驱动和用户态库不匹配。这种情况通常表现为mpp_init返回错误或者初始化成功但送帧时崩溃。解决方法是确保MPP库的版本跟内核驱动版本对应一般来说用官方SDK里配套的版本最稳妥。7. 几个容易混淆的概念澄清7.1 MPP和MPI的区别搜索热词里出现了MPI这里需要澄清一下。MPI在MPP的语境下通常指MppApi接口也就是MPP对外提供的那组函数指针。但在其他领域MPI可能指Message Passing Interface并行计算的消息传递接口或者Micro Processor Interface。如果你在搜MPP相关资料时看到MPI要根据上下文判断具体指什么。在瑞芯微的文档里MPI基本就是MppApi的简称。7.2 MPP和FFmpeg的关系FFmpeg是一个完整的多媒体框架包含容器解析、编解码、滤镜、封装等全套功能。MPP只是FFmpeg在瑞芯微平台上可以调用的一个硬件解码后端。FFmpeg可以用MPP也可以不用MPP而用其他后端。MPP也可以不通过FFmpeg而直接被应用程序调用。两者是不同层次的东西不是替代关系。7.3 MPP和OpenMAX的关系OpenMAX是一个标准化的多媒体组件接口规范理论上不同厂商的硬件只要实现了OpenMAX接口上层应用就可以用统一的API调用。但在嵌入式领域OpenMAX的实际采用率并不高因为它的抽象层次太高很难充分发挥特定硬件的性能。瑞芯微选择自己定义MPP API而不是实现OpenMAX就是为了更直接地控制硬件。所以你在RK3588上基本看不到OpenMAX的身影都是用MPP。8. 关于MPP后续学习路径的个人建议如果你已经跟着这篇内容把MPP的环境搭起来了也跑通了最小的测试程序接下来我建议按这个顺序深入先搞清楚MppBufferGroup的内存分配机制因为这是后面所有操作的基础然后研究解码器的送帧和取帧流程理解MPP的任务调度模型最后再看编码器和高级特性比如ROI编码、多实例并发。我在实际项目里最大的体会是MPP的文档虽然看起来很多但关键的信息往往散落在示例代码和头文件注释里。遇到不确定的地方直接去看MPP源码里的test目录那里有各种场景的完整示例比文档管用。另外瑞芯微的开发者社区里有一些关于MPP的讨论帖虽然质量参差不齐但偶尔能翻到一些官方文档里没写的实操细节。还有一点值得注意MPP的版本更新比较频繁不同版本之间的API可能有细微变化。如果你在网上找到的示例代码编译不过先检查一下你的MPP版本跟代码对应的版本是否一致。版本信息可以在mpp_version.h里找到或者调用mpp_query_version函数在运行时获取。
返回列表