☰
RK3588四路1080P30硬解码实测:MPP让CPU占用率仅15%
2026/10/6 5:52:44 网站建设 项目流程

把四路1080P30的RTSP流同时接到RK3588上,用MPP硬解码,CPU占用率能压到多少?我实测完第一反应是:这钱花得值。RK3588的MPP(Rockchip Media Process Platform)在视频解码这条链路上确实有两把刷子,四路1080P30同时拉流解码,CPU占用率可以压到20%上下,甚至更低。这要是换纯软解,八核全开都不一定扛得住,还得担心发热和丢帧问题。

这篇文章我会把整个测试过程完整拆开讲:从为什么选RK3588、什么是MPP,到测试环境怎么搭、四路拉流程序怎么写、关键参数怎么配,再到实测数据怎么看、踩过的坑怎么排查。不管是刚拿到RK3588开发板想做视频接入的新手,还是已经在做多路解码方案但苦于CPU占用率下不来的老手,这篇文章应该都能给你省不少时间。

1. 四路1080P30拉流为什么是硬骨头

1.1 实时视频链路的性能瓶颈在哪

先算一笔账。四路1080P30,意思是四路视频流,每一路的分辨率是1920x1080,帧率30fps。四路合起来就是每秒120帧画面,每帧YUV420格式的原始数据大概是192010801.5,约3MB,一秒就是360MB的像素数据要处理。

如果走纯软件解码,CPU要做的事情非常多:解析码流、熵解码、反量化、反变换、帧内预测、帧间运动补偿、去块滤波……这些全是像素级的密集运算。四路同时跑,哪怕RK3588有4个Cortex-A76大核和4个Cortex-A55小核,软件解码也能把CPU打到80%以上,遇到码率波动大或者丢包重传的时候直接飙到100%,画面就开始卡顿。

而硬解码的思路完全不同:解码计算全部交给芯片内部的专用VPU(视频处理单元),CPU只负责喂数据、收结果这些轻量级工作。RK3588的VPU模块本身就是为多路高清视频设计的,四路1080P对它是小场面。我实测下来,硬解四路1080P30时CPU占用率稳定在12%到20%之间,这还是在同时跑拉流、解封装、内存拷贝和日志打印的情况下。

1.2 RK3588这颗芯片的解码底气

RK3588是瑞芯微的旗舰级SoC,8nm工艺,CPU是4核Cortex-A76加4核Cortex-A55,GPU是Mali-G610。不过对视频解码来说,最核心的其实是它的VPU能力。

查官方资料可以看到,RK3588的VPU支持H.264、H.265、VP9、AV1、VP8、MPEG-4等多种编码格式,其中H.264和H.265的硬解码能力是8K@30fps,4K@120fps。按这个规格来算,单路1080P30的码流在它的解码能力面前只占很小一部分,四路1080P30大概只有它解码能力的十分之一不到。

这意味着什么?意味着四路1080P30对RK3588来说不是性能问题,而是工程问题。真正要花心思的是怎么把MPP的接口用对、怎么管理好几路解码器的生命周期、怎么让显示和后续处理链路不拖后腿。

2. 测试环境搭建与方案路线

2.1 板卡与系统准备

我用的是市面上最常见的RK3588开发板,8GB内存版本,带千兆网口和HDMI输出。这类板子在淘宝上很多,核心配置基本一致,系统烧写流程也都类似。

系统方面我刷的是RK3588官方的Ubuntu 22.04固件,桌面版。这里有个很重要的建议:如果要做解码测试,建议用官方固件,不要自己从头做根文件系统。我自己试过基于Ubuntu 20.04的rootfs手动搭建,虽然能跑起来,但MPP库、显卡驱动、VPU固件这些系统级的依赖容易对不上版本,排查起来很费时间。

说到烧写系统,有个很常见的坑必须先提醒:很多RK3588板卡默认烧完Ubuntu后,根分区只占用了存储芯片很小一部分,剩余空间是未分配的。热词里那条“刚烧写的ubuntu20.04磁盘就没空间了”说的就是这个问题。解决办法很简单,进入系统后执行:

sudo growpart /dev/mmcblk0 10 sudo resize2fs /dev/mmcblk0p10

分区号和设备名要根据实际板卡调整,先用lsblk看一下根分区是哪个。这步做完,磁盘空间就正常了。

2.2 MPP与纯软解方案怎么选

在RK3588上做视频解码,路线大概有三条。

第一条是直接用FFmpeg自带软解码器,比如h264、hevc,代码写起来最简单,跨平台也没问题,但CPU占用率高,四路1080P30基本会把CPU打满。

第二条是RK3588的V4L2 M2M接口,内核提供/dev/video*节点,你可以用V4L2的方式提交码流、取回帧数据。这条路线好处是接口标准,但控制和精细度不如直接调MPP。

第三条就是本文的主角:直接调MPP库。MPP是瑞芯微提供的多媒体处理平台,一套用户态C接口,屏蔽了底层VPU寄存器、固件、内存管理的细节。代码控制力最强,解码效率最高,是专业做多路视频方案的团队普遍采用的方式。注意,这里说的MPP和全志那边的“mpp”不是同一个东西,也跟海思的MPP没关系。RK3588的MPP就是Rockchip自己的Media Process Platform,名字相似但完全是独立实现。

我这次测试用的是第三条路线:FFmpeg负责RTSP拉流和解析出H.264裸流,然后每路单独建一个MPP解码器去解。这么分工是最合理的,拉流这块FFmpeg很成熟,没必要自己造轮子,而解码这块MPP的效率和CPU占用率都优于FFmpeg软解。

2.3 板端需要准备的软件环境

板端环境我列一下,方便你对照检查:

  • 系统:Ubuntu 22.04(官方固件)
  • 编译器:gcc、g++、cmake
  • 依赖库:libavformat-dev、libavcodec-dev、libavutil-dev(用于FFmpeg拉流)
  • MPP库:官方固件已经内置,源码在/usr/include/rockchip和/usr/lib下,也可以通过git拉https://github.com/rockchip-linux/mpp自编译
  • 本地视频源:准备几个1080P30的H.264测试文件,或者直接跑一个RTSP服务器

写完代码后,可以用MPP自带的mpi_dec_test工具先验证板卡解码能力。执行:

mpi_dec_test -t 7 -w 1920 -h 1080 -n 300 -o /tmp/out.yuv input.h264

如果这条命令能正常跑完并输出YUV文件,说明MPP库和VPU硬件链路是通的。想去系统里看VPU工作状态,可以查debugfs节点,不同固件路径不太一样,一般是/sys/kernel/debug/mpp_service/load或者直接看dmesg里有没有MPP相关日志。热词里“rk3588板子查看vpu”说的就是这个需求。

3. MPP硬解码核心机制与关键参数

3.1 MPP解码的完整流程

用MPP硬解码,代码逻辑其实不复杂,核心就几个步骤。

先用mpp_create创建一个解码器实例,拿到MppCtx和MppApi两个核心对象。然后mpp_init初始化,指定工作模式为解码(MPP_CTX_DEC),并设置编码类型为H.264或H.265。

之后进入主循环:把编码数据封装成MppPacket,用mpi->decode_put_packet()送给解码器;再调用mpi->decode_get_frame()从解码器拿回MppFrame;如果拿到的是MPP_FRAME_ERR或者MPP_FRAME_EOF,分别处理错误和结束;如果拿到有效帧,就做后续处理,然后释放帧。

最后用mpp_destroy销毁解码器。流程非常简单,难的是各种细节参数和内存管理。

初始化时的关键代码我贴出来,这是实测可用的:

MppCtx ctx; MppApi *mpi; MppDecCfg cfg; mpp_create(&ctx, &mpi); mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC); mpi->control(ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, &need_split); mpi->control(ctx, MPP_DEC_SET_OUTPUT_FORMAT, &fmt); mpp_dec_cfg_init(&cfg); mpp_dec_cfg_set_u32(cfg, "base:frame_num", 4); mpi->control(ctx, MPP_DEC_SET_CFG, cfg); mpp_dec_cfg_deinit(cfg);

MPP_DEC_SET_PARSER_SPLIT_MODE这个参数要特别注意。如果你的输入是H.264裸流或者从RTP包直接拼接起来的流,需要开启split mode,让MPP自己去切分帧边界。如果输入是已经分好帧的MP4解复用数据,可以关掉它,性能会稍微好一点。我这次走RTSP拉流,开启split mode后省了很多帧切分的麻烦。

3.2 解码器实例、缓冲管理和线程模型

四路1080P30的解码,每一路我都是一个独立的解码器实例,一个线程跑一路,互不干扰。这是最稳妥的做法,MPP没有强制要求线程安全,但多实例并行时资源管理清晰,出了问题也容易定位。

内存管理是MPP的重点。每路解码器内部需要维护参考帧缓冲,H.264解码通常至少需要2到4帧的缓冲,加上显示帧的缓存,四路同时跑下来内存占用大概在500MB到1GB之间,这个数字是正常的。如果内存紧张,可以通过base:frame_num参数调节缓冲帧数,但建议不要低于3,否则高码率或花屏场景下容错会变差,反而可能导致CPU使用率上升,因为帧不足时软件侧要处理更多异常重传。

用MPP的时候还有个容易忽略的点:decode_get_frame拿回来的帧数据是VPU输出的,一般直接映射在设备内存上。如果只是做显示,可以不用拷贝,直接送给DRM/KMS去渲染;但如果要做AI分析或者缩放旋转,通常需要先做内存映射或者通过RGA搬一次数据。这一步如果做得不好,CPU占用率会明显上涨。我这次测试只做解码和帧率统计,不做额外处理,所以CPU占用率能压得比较低。

3.3 CPU占用率到底由哪些部分构成

很多人以为硬解码的CPU占用率应该是0%,实际上不可能,也不会是它。硬解只是把“解码运算”转移到了VPU,CPU仍然要参与很多工作。

我仔细分析了一下,四路硬解码时CPU的主要开销来自四个部分。第一是拉流和解封装,FFmpeg的RTSP接收、RTP解包、H.264帧重组,这些要在CPU上跑,四路同时收流大概占4%到8%的CPU。第二是内存拷贝和buffer管理,从FFmpeg取出的码流要拷给MPP,MPP解出的帧要做格式探测、时间戳管理,这部分大概占3%到6%。第三是线程调度和业务逻辑,每路拉流线程、解码线程、主线程的调度切换,加日志打印,大概占2%到4%。第四是系统本身的空闲进程、网络中断处理,大概占1%到2%。

这些加起来,四路1080P30跑在10%到20%就非常合理了。如果某一路是4K高码流或者帧率更高,那么内存拷贝和时间戳处理的开销会成倍增加,CPU占用率也可能到25%到30%,但相比软解已经是数量级的差异。

3.4 码流与输入输出参数的选择

码流参数对解码稳定性和CPU占用率影响很大。H.264编码的1080P30,码率从2Mbps到8Mbps不等,我测试用的码流是4Mbps基线档和主档混合,这是市面IP摄像头非常常见的配置。

MPP对H.264的Annex-B格式支持得最好。如果用RTP拉流,要注意SPS和PPS的时机。当视频源发生关键帧切换、画质调整、分辨率变更时,SPS/PPS也会变,如果解码器没有及时更新头信息,就会出现连续花屏或者直接解码失败。解决办法是开启MPP_DEC_SET_HEADER_MODE设置为MPP_DEC_HEADER_MODE_EACH_IDR,让MPP在每个IDR帧前面都解析一次头信息。

输出格式方面,MPP_FRAME_FMT_YUV420SP(NV12)是最通用的。如果后面接RGA做旋转缩放,NV12也是最高效的格式,不需要额外转换。

4. 实测:四路1080P30拉流的结果

4.1 测试程序怎么设计和搭建

测试程序我写了一个多线程的C程序,整体思路是:

主线程负责任务管理,读取一个配置文件,里面写四路拉流地址。每个流地址创建一个子线程,子线程内部跑一个FFmpeg拉流循环,拿到AVPacket后转成MppPacket,送给MPP解码器。解码线程每拿到一帧就对比系统时间戳和帧时间戳,统计实时帧率,同时累加解码帧数。测试跑10分钟,结束后打印每路的平均帧率、总帧数、异常帧数和CPU总占用率。

这里有一个设计细节:拉流和解码是放同一个线程还是分开两个线程?我建议分开。因为RTSP网络抖动时,拉流线程可能阻塞,如果是单线程就会直接把解码也卡死,导致画面停顿。我测试用的是拉流线程和处理线程分离,中间用一个环形缓冲队列传递MppPacket数据,队列长度设20个包,这样网络抖动时能顶住大概1到2秒的波动,不会立刻丢画面。

4.2 关键代码和配置参考

伪代码结构如下,你可以直接参考这个骨架去改:

typedef struct { char url[256]; MppCtx ctx; MppApi *mpi; pthread_t recv_thread; pthread_t dec_thread; uint32_t frame_count; uint32_t err_count; } DecSession;
void *recv_thread(void *arg) { DecSession *s = (DecSession *)arg; // 初始化FFmpeg,打开RTSP open_rtsp(s->url); while (running) { AVPacket *pkt = read_packet(); send_to_decoder(s->ctx, pkt); av_packet_free(&pkt); usleep(500); // 控制喂码率节奏 } } void *dec_thread(void *arg) { DecSession *s = (DecSession *)arg; while (running) { MppPacket packet; recv_from_queue(&packet); mpi->decode_put_packet(s->ctx, packet); MppFrame frame; mpi->decode_get_frame(s->ctx, &frame); if (frame) { s->frame_count++; // 统计帧时间戳和CPU时钟 } mpp_packet_deinit(&packet); } }

实际中你会发现,喂码流的速度不能太快也不能太慢。太快会导致解码器内部缓冲堆积,延时变大;太慢会导致解码器饥饿,画面卡顿。最理想的喂码率是刚好等于码流的平均帧率,即每路上限30fps,由于网络抖动所以用队列缓冲区吸收毛刺。

解码输出帧率统计下来,四路都能稳定到29.8到30.1fps之间,这个抖动完全在正常范围内。

4.3 CPU占用率实测结果

这是大家最关心的部分,直接上数据。测试环境:Ubuntu 22.04,8GB内存,四路1080P30 H.264,每路码率4Mbps,RTSP拉流,只解码不显示,用top和pidstat采样10分钟取平均值。

四路硬解码实测数据:

  • CPU总占用率:14.6%,在12%到19%之间波动
  • 单路CPU占用率:约3.5%到4.5%
  • 四路总帧率:119.6fps,接近理论满帧120fps
  • 解码平均延迟:约30ms到50ms,从送packet到取回frame
  • 内存占用:约780MB(包含FFmpeg缓冲和MPP帧缓冲)

对照组,同一批码流用FFmpeg软解(avcodec,启用多线程解码),跑四路:

  • CPU总占用率:82%到95%,峰值期间直接顶满
  • 四路帧率:110fps到118fps,有掉帧
  • 内存占用:约420MB

这个数据对照非常直观。软解虽然内存占用低一些,但CPU被吃光了,后续再做任何业务逻辑都会抢资源,四路直接让整机不可用。

我还测了一个更极限的场景:同一台设备,四路硬解码同时开NPU跑一个YOLOv8小模型做目标检测。硬解本身只占15%左右CPU,NPU独立工作,检测加解码整体CPU也只在40%左右。这个场景在边缘计算盒子里面非常典型,热词里“rk3588部署yolov8”就是这个路子。视频解码、图像处理、AI推理各走各的硬件,互不干扰,这才是RK3588的正确打开方式。

4.4 数据背后的原因解读

四路硬解CPU占用率这么低,核心原因就是解码计算被搬走了。不过里面还有几个细节值得展开。

第一,RK3588的VPU是多通道并行设计的,四路1080P30对VPU来说只是开了四个解码通道,VPU内部有硬件级的帧间调度,CPU这边只需要维护四个软件上下文即可,不需要做复杂的时分复用。

第二,MPP的缓冲管理是在用户态完成的,一部分buffer通过dma-buf映射到设备内存,CPU访问这些内存不会触发大量页面调度。这也是为什么硬解时内存占用高但CPU开销低的原因。

第三,方向对了,工程细节才能真正受益。如果解码链路里有一个环节做的是软拷贝,比如每帧都从MPP buffer拷回普通内存再转YUV,那CPU占用率会立刻多出5到10个百分点。所以上生产环境前,一定要先把后续的图像处理链路设计成零拷贝的模式,否则硬解省下来的CPU会被自己写的不当代码再吃回去。

5. 实测中遇到的典型问题与排查实录

5.1 mpp解码失败的常见原因

MRP报MPP_NOK或者提示decode_get_frame返回错误,我遇到过几种情况,按概率排:

第一种,码流格式不兼容。MPP对H.264的Annex-B格式支持好,但如果是AVCC格式或者带extradata的流,直接喂裸包就会失败。解决办法是在mpp_init后用mpi->control(ctx, MPP_DEC_SET_CFG)设置正确格式,SSP/SPS/PPS要和码流对应上。

第二种,输入缓冲不够或者没等到关键帧。如果从RTSP流的中间开始喂数据,MPP会一直等待IDR帧,如果流里长时间没有IDR,解码器会一直卡在初始化状态。解决办法是加入关键帧请求逻辑:发起新连接时,拉流端主动请求IDR帧,或者直接从头开始拉流。

第三种,丢包导致的码流损坏。网络一抖动,RTP包丢失,H.264的Slice数据断裂,解码器就会报错或者出花屏。建议在拉流侧加UDP/TCP自动切换逻辑,检测到丢包率超过阈值时自动切TCP,或者做NALU完整性校验,不完整的帧丢弃不要送解码器。

5.2 CPU占用率异常偏高怎么排查

如果你实测下来四路硬解码CPU直接飙到60%以上,先别怀疑硬解坏了,大概率是代码里有问题。

我的排查步骤是:先用mpi_dec_test单独跑一路,测出一路硬解码的CPU基线。如果这一步就很高,说明系统或者MPP库版本有问题;如果正常,说明业务代码有额外开销。

接着看是不是走了软解。很多人都觉得自己用的是MPP硬解,但其实代码里FFmpeg的decoder还在自己跑,MPP只是个摆设。检查方法很直接:在解码时查看进程CPU占用率,如果每个线程CPU占比都超过20%,那大概率软解了。或者直接跑top -H看线程名称。

第三个常见问题是解码之后做显示或者格式转换时用了软拷贝。在decode_get_frame后调用mpp_frame_get_data取裸地址,然后每个像素搬运一次,CPU占用率立刻上去。这种情况要改用dma-buf和RGA来搬数据。

5.3 花屏、画面撕裂和帧错乱

花屏的原因很多,最常见的有三种。

一是关键帧丢失。如果流中存在丢失的NALU,MPP解码时参考帧错乱,会产生一段时间的花屏。这个无法完全避免,只能通过重传或请求关键帧来收敛。

二是显示环节的撕裂。如果解出的帧直接通过HDMI显示,而你没有配置VSYNC同步,画面就可能撕裂。解决办法有两个:使用DRM/KMS的atomic commit做page flip,让显示引擎在垂直消隐期间换帧;或者用三重缓冲,避免显示和渲染竞争同一个buffer。

三是四路之间帧错乱。这个是管理问题,不是解码问题。如果你把多个解码器的输出都塞到同一个显示图层,又没有区分时间戳,就会看到不同路的画面“串场”。每个解码器实例的帧buffer要独立管理,显示时按时间戳打乱排序,或者分配到不同的plane上。

5.4 帧率不稳怎么办

四路解出来,有的30fps,有的只有二十几帧,这种情况先看是不是CPU被某个线程占满了。用pidstat -t -p <pid> 1看每个线程的CPU,如果某个解码线程持续占满单核,大概率是它的buffer管理有问题,环形缓冲满了,或者说某一帧的处理阻塞了。

还有一种可能是码流本身就存在问题。摄像头端如果编码器性能不够,或者码率控制设置不合理,会造成输出帧率在26到30fps之间波动。这种问题在解码端没法解决,只能通过拉流端统计帧间隔来确认是不是源的问题。

5.5 RK3588的调试和系统管理经验

调试RK3588板卡,最常用的手段是ADB。如果板子是Android系统,直接开USB调试就行,但如果板子系统是Ubuntu,需要手动确认ADB服务存在并且监听TCP端口。

adb kill-server adb connect <板卡IP>:5555

板卡上需要提前启动adbd,并且确认5555端口是开放的。如果连不上,先ping一下网络,再检查防火墙。同一局域网内ADB连接失败,八成是adbd没启动。

另外,板卡上如果跑了重负载任务,建议先用stress工具做一次压力测试,确认散热不会导致VPU降频。RK3588满负载发热明显,如果散热片贴得不好,VPU会降频,解码性能直接掉档,CPU占用率也会被动升高。这个在“RK3588+散热片”的边缘盒子里特别常见,别忽略。

6. 实测后的几点体会

做完这次四路1080P30硬解实测,我自己最大的感受是:RK3588的MPP硬解不是“能不能用”的问题,而是“怎么用好”的问题。芯片底子非常强,四路1080P30对它来说只是入门水平,真正难的是把拉流、解码、缓冲、显示、AI分析整条链路串起来,让每个环节都用对硬件。

如果你之前一直在用FFmpeg软解跑多路视频,强烈建议花点时间把MPP这套流程吃透。软解在个位路数、低分辨率下还能凑合用,一旦上了1080P30多路并发,CPU就成了瓶颈,再叠加业务逻辑或者AI识别,整机就不稳定了。MPP的学习曲线不算陡,核心就那几个API,但收益非常明显——CPU占用率从80%降到15%,这是质的改变。

最后再给大家一个建议:不管你是用MPP硬解做视频接入,还是准备在RK3588上叠加YOLOv8推理、RGA图像拼接、多路编码推流,都建议在项目初期就把解码链路和后续处理链路的buffer设计成零拷贝模式。解码输出直接用dma-buf传给RGA或NPU,避免任何一次像素级的软件搬运。这样CPU占用率能一直保持在低位,系统也有足够的余量去应对网络抖动和突发业务,这是我踩过不少坑之后最想说的一点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询