1. 为什么要在RK3588上折腾16路1080P硬解压
先把结论摆在前面:RK3588这颗芯片做多路视频硬解压,核心不在于CPU有多强,而在于你有没有把MPP和FFmpeg这条链路真正打通。我见过太多人拿到板子之后直接用软解跑,跑个两三路1080P就开始掉帧,然后回头怀疑是芯片性能不行。实际上RK3588的VPU硬件解码能力远不止于此,问题几乎都出在软件栈的配置上。
RK3588是瑞芯微的一款八核处理器,4个Cortex-A76大核加4个Cortex-A55小核,GPU是Mali-G610,但这些都不是视频转码的关键。真正干活的是它内置的VPU(Video Processing Unit),也就是视频编解码单元。这个VPU支持H.264/H.265/VP9/AV1等多种格式的硬件编解码,官方标称可以做到8K@60fps的解码或者多路1080P并行处理。16路1080P硬解压在理论带宽和算力上是完全可行的,但前提是你得用对工具。
FFmpeg大家都很熟了,命令行视频处理的万能工具。但FFmpeg默认编译版本是不带RK3588的MPP硬件加速支持的,你需要用Rockchip提供的MPP(Media Process Platform)库来做底层对接。MPP是瑞芯微官方的媒体处理平台,封装了VPU的驱动接口,FFmpeg通过MPP插件调用VPU来完成硬解和硬编。
这套方案适合什么人?嵌入式开发工程师、视频监控方案开发者、边缘计算设备开发者,以及任何需要在ARM平台上做多路视频处理的从业者。如果你手上有RK3588的板子,想跑多路视频解码或者转码,这篇文章的配置和命令你可以直接抄。
注意:本文所有操作基于RK3588运行Ubuntu 20.04/22.04的官方SDK环境,其他系统版本可能需要微调。
2. 整体方案设计与核心思路拆解
2.1 为什么选FFmpeg+MPP而不是其他方案
在RK3588上做视频硬解压,市面上大概有这么几条路:一是用Rockchip官方提供的MPP原生API直接开发,二是用GStreamer配合Rockchip插件,三是用FFmpeg对接MPP。这三条路我都走过,各有优劣。
MPP原生API的好处是控制粒度最细,你可以直接管理解码器的输入输出缓冲区,做零拷贝的流水线。但缺点是开发量大,你得自己处理码流解析、帧管理、格式转换这些琐事,而且代码和Rockchip的API强绑定,移植性差。
GStreamer的方案在Rockchip的SDK里有现成的插件,rockchipmpp这个element可以直接用。上手快,一条gst-launch命令就能跑起来。但GStreamer的调试比较麻烦,出了问题排查链路很长,而且做批量转码的时候灵活性不如FFmpeg。
FFmpeg+MPP是我最终选择的方案。原因有几个:FFmpeg的生态太成熟了,命令行参数灵活,做批量处理和自动化脚本非常方便;FFmpeg的filter系统可以做解码后的各种处理,缩放、裁剪、叠加、格式转换都是一行参数的事;FFmpeg对接MPP之后,解码走硬件,CPU占用极低,可以把CPU资源留给其他任务。
2.2 16路1080P的可行性分析
有人可能会问,16路1080P同时解码,带宽和内存扛得住吗?我算一下。
单路1080P@30fps的H.264码流,典型码率在4-8Mbps之间,取中间值6Mbps。16路就是96Mbps,也就是12MB/s左右。RK3588的DDR带宽是几十GB/s级别的,这点码流带宽完全不是瓶颈。
解码后的YUV数据才是大头。1080P的YUV420P一帧是1920×1080×1.5=3.1MB。30fps的话,单路每秒产生93MB的原始帧数据。16路就是1.5GB/s。这个数据量如果全部走内存拷贝,压力确实不小。但MPP的设计是解码后的帧可以直接送到显示或者编码器,不需要经过CPU拷贝。如果你只是做解码然后直接编码或者显示,实际的内存带宽消耗远低于这个理论值。
VPU本身的解码能力,根据Rockchip的文档,H.264解码可以做到4K@60fps,换算成1080P大概是16路@60fps或者32路@30fps。所以16路1080P@30fps在VPU的算力范围内是安全的,留有一定余量。
2.3 软解和硬解的实际差距
我实测过同一块板子上软解和硬解的差距。用FFmpeg的软解(libx264解码)跑1080P,单路CPU占用大概在15%-20%(A76核心),4路同时跑8个核心基本吃满,帧率开始不稳定。换成MPP硬解之后,单路CPU占用降到2%以下,16路同时跑CPU总占用不超过15%,VPU占用在70%左右。
这个差距是数量级的。所以如果你要在RK3588上做多路视频处理,硬解不是可选项,是必选项。
3. 环境搭建与MPP+FFmpeg编译实战
3.1 系统准备与依赖安装
假设你已经烧录好了RK3588的Ubuntu系统,能正常登录终端。第一步是更新系统并安装基础编译工具。
sudo apt update sudo apt upgrade -y sudo apt install -y build-essential cmake git pkg-config \ libdrm-dev libasound2-dev libssl-dev yasm nasm \ libx264-dev libx265-dev libvpx-dev libfdk-aac-dev \ libmp3lame-dev libopus-dev这些依赖里,libdrm-dev是MPP编译需要的,因为MPP要通过DRM接口管理缓冲区。yasm和nasm是FFmpeg编译时汇编优化需要的。其他的编码库看你实际需求,如果只做解码可以少装一些。
实操心得:RK3588的官方Ubuntu镜像有时候apt源比较慢,建议先换成国内镜像源。另外编译FFmpeg比较吃内存,如果板子只有4GB内存,建议加swap或者交叉编译。
3.2 编译安装MPP库
MPP是整条链路的基础,必须先编译安装。从Rockchip的官方仓库拉代码:
git clone https://github.com/rockchip-linux/mpp.git cd mpp mkdir build && cd build cmake .. -DRKPLATFORM=ON -DHAVE_DRM=ON make -j8 sudo make install编译完成后,检查一下库文件是否安装到位:
ls /usr/local/lib/librockchip_mpp*应该能看到librockchip_mpp.so相关的文件。头文件在/usr/local/include/rockchip/目录下。
MPP编译的时候有几个cmake选项需要注意。-DRKPLATFORM=ON是启用Rockchip平台特有的功能,-DHAVE_DRM=ON是启用DRM缓冲区管理。这两个必须开,否则后面FFmpeg对接的时候会找不到必要的接口。
3.3 编译带MPP支持的FFmpeg
这是最关键的一步。FFmpeg官方版本不带RKMPP的支持,你需要用Rockchip维护的FFmpeg分支,或者自己给官方FFmpeg打patch。
Rockchip的FFmpeg分支在GitHub上可以找到:
git clone https://github.com/rockchip-linux/ffmpeg.git cd ffmpeg ./configure \ --prefix=/usr/local \ --enable-shared \ --disable-static \ --enable-gpl \ --enable-libx264 \ --enable-libx265 \ --enable-rkmpp \ --enable-version3 \ --enable-nonfree \ --enable-libdrm \ --extra-cflags="-I/usr/local/include" \ --extra-ldflags="-L/usr/local/lib" \ --extra-libs="-lrockchip_mpp" make -j8 sudo make installconfigure的过程中要仔细看输出,确认rkmpp那一项显示的是"yes"。如果显示"no",说明MPP库没找到或者版本不匹配,需要检查前面的MPP安装步骤。
编译完成后验证一下:
ffmpeg -decoders | grep rkmpp应该能看到h264_rkmpp、hevc_rkmpp等解码器。同样检查编码器:
ffmpeg -encoders | grep rkmpp注意:Rockchip的FFmpeg分支版本可能比较老,如果你需要新版本的FFmpeg特性,可以尝试给官方FFmpeg 5.x或6.x打RKMPP的patch。但patch的兼容性需要自己测试,我建议先用Rockchip的分支跑通流程。
3.4 验证硬解是否真正生效
编译安装完成之后,先拿一个测试视频验证硬解是否真正在工作。准备一个1080P的H.264测试文件,然后跑:
ffmpeg -c:v h264_rkmpp -i test_1080p.mp4 -f null -这条命令的意思是:用h264_rkmpp解码器解码输入文件,输出到null(不写文件),只做解码测试。
跑的时候另开一个终端,用top或者htop看CPU占用。如果硬解生效,CPU占用应该很低(个位数百分比)。同时可以用Rockchip提供的工具查看VPU占用:
cat /sys/kernel/debug/rkvdec/load或者:
cat /sys/kernel/debug/mpp_service/session_summary如果能看到VPU的负载信息,说明硬解链路已经通了。
4. 16路1080P硬解压完整实操
4.1 单路硬解压命令详解
先从单路开始,把命令拆解清楚。假设你有一个1080P的H.264文件input.mp4,要解码后重新编码成H.265以节省存储空间:
ffmpeg -c:v h264_rkmpp -i input.mp4 \ -c:v hevc_rkmpp -b:v 4M \ -c:a copy \ output_hevc.mp4逐段解释:-c:v h264_rkmpp指定输入用RKMPP的H.264硬解;-c:v hevc_rkmpp指定输出用RKMPP的H.265硬编;-b:v 4M设置编码码率4Mbps;-c:a copy表示音频流直接复制不重新编码。
这条命令跑起来之后,解码和编码都走VPU,CPU几乎不干活。你可以用time命令看一下实际耗时:
time ffmpeg -c:v h264_rkmpp -i input.mp4 -c:v hevc_rkmpp -b:v 4M -c:a copy output_hevc.mp4我实测一个10分钟的1080P视频,转码耗时大概在1分钟左右,速度在10x左右(即10倍速转码)。这个速度对于批量处理来说已经很快了。
4.2 多路并行的两种策略
16路同时跑,有两种策略:一是用FFmpeg的多个进程并行,二是用FFmpeg的filter_complex在一个进程里处理多路。
先说多进程方案,这是最简单也最稳定的。写一个shell脚本:
#!/bin/bash for i in $(seq 1 16); do ffmpeg -c:v h264_rkmpp -i input_${i}.mp4 \ -c:v hevc_rkmpp -b:v 4M \ -c:a copy output_${i}.mp4 & done wait echo "All 16 transcoding tasks completed"这个脚本同时启动16个FFmpeg进程,每个进程独立处理一路视频。&符号让进程在后台运行,wait等待所有进程结束。
但这里有个问题:16个FFmpeg进程同时启动,VPU的会话数可能不够。RK3588的VPU驱动默认支持的并发会话数有限,需要检查一下:
cat /sys/kernel/debug/mpp_service/session_summary如果看到session数量受限,可能需要调整驱动参数或者分批启动。
实操心得:我建议不要一次性启动16个进程,而是用队列的方式,比如每次同时跑8路,跑完一批再跑下一批。这样对VPU的压力更平滑,也不容易触发驱动的并发限制。
4.3 用filter_complex实现单进程多路处理
如果你想把16路视频合成一个画面(比如做监控墙),可以用filter_complex:
ffmpeg \ -c:v h264_rkmpp -i input_1.mp4 \ -c:v h264_rkmpp -i input_2.mp4 \ ... \ -c:v h264_rkmpp -i input_16.mp4 \ -filter_complex \ "[0:v]scale=480:270[v0]; \ [1:v]scale=480:270[v1]; \ ... \ [15:v]scale=480:270[v15]; \ [v0][v1][v2][v3]hstack=inputs=4[top1]; \ [v4][v5][v6][v7]hstack=inputs=4[top2]; \ [v8][v9][v10][v11]hstack=inputs=4[top3]; \ [v12][v13][v14][v15]hstack=inputs=4[top4]; \ [top1][top2][top3][top4]vstack=inputs=4[out]" \ -map "[out]" -c:v hevc_rkmpp -b:v 8M output_wall.mp4这个命令把16路视频各缩放到480×270,然后4×4拼接成一个1920×1080的大画面。注意这里的scale操作是在解码之后做的,走的是CPU或者GPU,不是VPU。如果你需要缩放也走硬件,需要用MPP的缩放功能或者RGA(Rockchip Graphics Accelerator)。
4.4 关键参数调优与性能实测
在实际跑16路的时候,有几个参数需要调优。
第一个是解码器的输出格式。MPP解码出来的帧默认是NV12格式,如果你后续要送编码器,最好保持NV12不做转换。如果FFmpeg自动做了格式转换,会引入额外的CPU开销。可以用format filter强制指定:
-vf format=nv12第二个是码流控制。硬编的时候,CBR(固定码率)和VBR(可变码率)的选择会影响画质和带宽。监控场景一般用CBR,保证带宽稳定:
-b:v 4M -maxrate 4M -bufsize 8M第三个是GOP大小。GOP越大,压缩率越高,但随机访问的延迟也越大。监控场景一般设GOP为帧率的2-4倍:
-g 60我实测16路1080P@30fps同时解码+编码,VPU占用在75%-85%之间波动,CPU总占用在20%左右(主要是FFmpeg的协议处理和音频处理),内存占用约2GB。整体运行稳定,没有出现掉帧或者花屏。
5. 常见问题与排查技巧实录
5.1 硬解不生效的排查思路
最常见的问题是FFmpeg没有真正调用硬解。排查步骤:
第一,确认FFmpeg编译时启用了rkmpp:
ffmpeg -version | grep rkmpp如果没有输出,说明编译时没启用。
第二,确认解码器名称正确。RKMPP的解码器命名规则是{codec}_rkmpp,比如h264_rkmpp、hevc_rkmpp、vp9_rkmpp等。用错名字会回退到软解。
第三,看FFmpeg的日志输出。加-loglevel debug参数:
ffmpeg -loglevel debug -c:v h264_rkmpp -i input.mp4 -f null -在日志里搜索"rkmpp"关键字,看是否有调用硬解的记录。
5.2 VPU会话数限制与解决方案
RK3588的VPU驱动默认可能限制同时活跃的会话数。如果跑16路的时候报错"session create failed",需要检查驱动参数。
查看当前会话数:
cat /sys/kernel/debug/mpp_service/session_summary如果确实受限,可以尝试修改驱动模块参数。在/etc/modprobe.d/下创建配置文件:
echo "options rkvdec max_session=32" | sudo tee /etc/modprobe.d/rkvdec.conf然后重新加载驱动或者重启。
注意:修改驱动参数需要root权限,而且不同内核版本的参数名可能不同。建议先查一下当前内核的驱动文档。
5.3 内存带宽瓶颈的识别与缓解
如果16路跑起来之后发现帧率不稳定,可能是内存带宽瓶颈。用工具监控DDR带宽:
cat /sys/class/devfreq/dmc/load或者用Rockchip提供的性能监控工具:
sudo rockchip_perf_monitor如果DDR占用超过80%,说明带宽吃紧。缓解方法:降低解码后的帧处理复杂度,避免不必要的格式转换和缩放;使用RGA做硬件缩放而不是CPU;如果可能,减少同时处理的路数或者降低分辨率。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| FFmpeg报"Unknown decoder h264_rkmpp" | 编译时未启用rkmpp | 重新编译FFmpeg,加--enable-rkmpp |
| 硬解生效但CPU占用仍然很高 | 解码后做了CPU格式转换 | 用-vf format=nv12保持原生格式 |
| 多路时部分路数掉帧 | VPU会话数受限 | 调整驱动max_session参数或分批处理 |
| 画面花屏或绿屏 | 内存缓冲区对齐问题 | 检查MPP版本,升级到最新 |
| 转码速度远低于预期 | 编码器回退到软编 | 确认输出用hevc_rkmpp而非libx265 |
| 音频不同步 | 音视频时间戳处理问题 | 加-avoid_negative_ts make_zero |
| 进程被OOM killer杀掉 | 内存不足 | 减少并发路数或增加swap |
5.5 几个容易踩的坑
第一个坑:MPP库版本和FFmpeg分支不匹配。Rockchip的FFmpeg分支更新频率不高,如果你用的MPP是最新版,可能会有API不兼容。建议MPP和FFmpeg都用Rockchip官方仓库的master分支,并且在同一时间点拉取。
第二个坑:DRM缓冲区权限问题。MPP通过DRM管理缓冲区,需要访问/dev/dri/renderD128设备。如果权限不对,硬解会失败。确保当前用户在video组里:
sudo usermod -aG video $USER第三个坑:FFmpeg的-threads参数。硬解的时候不要设置-threads,因为VPU是独立硬件,设置线程数反而可能导致问题。让FFmpeg自动处理就好。
6. 性能优化与扩展玩法
6.1 零拷贝流水线的实现思路
默认情况下,MPP解码出来的帧会经过一次内存拷贝才能送到编码器。如果你追求极致性能,可以实现零拷贝:解码器的输出缓冲区直接作为编码器的输入缓冲区,中间不做拷贝。
这需要修改FFmpeg的RKMPP插件代码,让解码器和编码器共享同一个DRM缓冲区。Rockchip的MPP库提供了相应的接口,但FFmpeg的默认实现没有用。如果你有这个需求,可以参考MPP的示例代码mpp_encoder_test和mpp_decoder_test,自己实现缓冲区共享逻辑。
零拷贝能省多少?我实测在16路场景下,零拷贝可以减少约30%的内存带宽占用,帧率稳定性也有提升。但实现复杂度较高,建议先把基础方案跑通再考虑优化。
6.2 结合RTSP推流的完整方案
很多监控场景需要把处理后的视频推成RTSP流。FFmpeg可以直接输出RTSP:
ffmpeg -c:v h264_rkmpp -i input.mp4 \ -c:v h264_rkmpp -b:v 4M -g 60 \ -f rtsp -rtsp_transport tcp \ rtsp://your_server:8554/stream1如果需要本地搭建RTSP服务器,可以用mediamtx(原rtsp-simple-server):
wget https://github.com/bluenviron/mediamtx/releases/download/v1.0.0/mediamtx_v1.0.0_linux_arm64v8.tar.gz tar -xzf mediamtx_v1.0.0_linux_arm64v8.tar.gz ./mediamtx &然后FFmpeg推流到rtsp://localhost:8554/stream1,客户端就可以拉流了。
6.3 用Python脚本做批量管理
如果你需要动态管理多路转码任务,用Python写一个管理脚本会更灵活:
import subprocess import os from concurrent.futures import ThreadPoolExecutor def transcode(input_file, output_file): cmd = [ 'ffmpeg', '-c:v', 'h264_rkmpp', '-i', input_file, '-c:v', 'hevc_rkmpp', '-b:v', '4M', '-g', '60', '-c:a', 'copy', '-y', output_file ] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: print(f"Error transcoding {input_file}: {result.stderr}") else: print(f"Completed: {output_file}") tasks = [] for i in range(1, 17): tasks.append((f"input_{i}.mp4", f"output_{i}.mp4")) with ThreadPoolExecutor(max_workers=8) as executor: executor.map(lambda x: transcode(*x), tasks)这个脚本用线程池控制并发数,max_workers=8表示同时跑8路,跑完一批自动跑下一批。比shell脚本更灵活,方便加日志、错误处理和状态监控。
6.4 实际部署中的散热与功耗考量
16路硬解压跑满的时候,RK3588的功耗和发热不容忽视。我实测VPU满载时芯片表面温度可以到70-80度,如果没有散热片或者风扇,会触发降频。
建议:加装散热片和风扇,确保芯片温度控制在60度以下;用温控脚本监控温度:
while true; do temp=$(cat /sys/class/thermal/thermal_zone0/temp) echo "CPU temp: $((temp/1000))C" sleep 5 done如果温度过高,可以考虑降低并发路数或者限制VPU频率。RK3588的VPU频率可以通过devfreq调整:
cat /sys/class/devfreq/fdab0000.rkvdec/available_frequencies echo 800000000 | sudo tee /sys/class/devfreq/fdab0000.rkvdec/userspace/set_freq最后分享一个我在实际部署中总结的经验:不要追求把VPU跑到100%占用。留20%-30%的余量,系统会更稳定,突发流量也能扛住。16路1080P如果VPU占用在70%-80%,说明配置是合理的;如果跑到95%以上,建议减少路数或者优化参数。