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

资讯详情

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

RK3576 FFmpeg硬件编解码实战:零拷贝优化与性能调优

RK3576 FFmpeg硬件编解码实战:零拷贝优化与性能调优 干嵌入式音视频这行的手里要没有一块RK平台的板子出门都不好意思跟人打招呼。RK3576这颗芯片出来之后我身边的同事和圈子里的朋友问的最多的就是这颗料到底能不能扛住FFmpeg的硬件编解码能不能在转码、推流、多路解码这些实际场景里把VPU吃满这些问题问得多了我自己也陆续在几个项目里把RK3576的FFmpeg硬编硬解链路从头到尾过了一遍从交叉编译、MPP接入、零拷贝到编码参数调优和不同场景下的压测对比中间的坑踩了不少也攒下来一些比较扎实的数据和结论。这篇东西就把我这段时间的实测记录、踩坑经历和最终优化方案完整写出来给正在评估RK3576或者已经在上面做音视频方案的兄弟一个参考。先说结论免得有的人没耐心看完。RK3576这颗芯片的VPU解码能力非常强H.265和VP9都能硬解到8K30H.264解4K60也无压力编码端最大支持4K60的H.264/H.265实际用FFmpeg接rkmpp跑出来的性能基本贴着硬件上限走。但这里有个大前提你必须把FFmpeg的编解码链路从软件路径切到硬件路径并且把buffer流转方式从传统的内存拷贝优化成dma-buf/DRM Prime零拷贝否则CPU会被内存拷贝和颜色转换吃干净硬解的优势一点都体现不出来。下面我把整个优化过程按阶段拆开讲每个环节都有对应的操作步骤和实测数据。1. 项目背景与整体设计思路1.1 为什么要把FFmpeg接到RK3576的硬件编解码上先简单交代一下RK3576的定位。这颗SoC采用的是四核Cortex-A72加四核Cortex-A53的big.LITTLE架构GPU是Mali-G52 MC3NPU算力6 TOPSVPU支持H.264、H.265、VP9、AV1、VP8等格式的硬解以及H.264/H.265的硬编码。单看CPU算力它比RK3588弱一些但VPU的能力并没有缩水多少8K30解码和4K60编码都是实打实的。这就带来一个很现实的问题很多做方案的人拿到这颗芯片第一反应是“A72四核应该也能软解4K吧”——实际跑一下就知道软解4K HEVC在主频2.2GHz的A72上勉强能到25到35帧一旦叠加UI显示、网络协议栈、业务逻辑CPU直接拉满系统卡到没法用。所以核心思路必须是让FFmpeg把解码和编码的活全部交给VPUCPU只负责业务逻辑和网络传输。这套思路在技术圈里叫“硬件编解码卸载”具体到RK平台就是通过Rockchip的MPPMedia Process Platform库来操作VPU而FFmpeg这边有一套成熟的hwaccel框架通过rkmpp这个wrapper把MPP的能力暴露给FFmpeg的API。只要编译的时候开对了宏FFmpeg就能直接用-c:v h264_rkmpp这类标识符拉起硬解或硬编不需要改动业务层代码。1.2 优化目标与技术选型这个项目里我的优化目标很明确分成四条单路4K H.265解码的实时性必须稳定在60帧不掉帧CPU占用低于30%。单路1080p H.264转H.265的转码吞吐目标大于120fps保证转码链路不至于成为瓶颈。多路1080p并发解码目标四路同时流畅内存带宽和VPU利用率在合理范围。低延迟推流场景端到端延迟控制在300ms以内。技术选型上我在FFmpeg版本上选了当前主流的4.4.x LTS分支RK官方SDK里默认带的也是这个版本。MPP库直接用RK官方维护的rockchip-linux/mpp仓库编解码接口通过--enable-rkmpp打开。显示和零拷贝部分用了DRM/GBM接口通过dma-buf在VPU和显示控制器之间传递buffer避免走内存拷贝。整个方案的软件栈是应用层FFmpeg 中间层rkmpp wrapper 底层MPP/RKVPU驱动。后面每一个环节我都会讲到实际怎么配、怎么调。2. 开发环境搭建与FFmpeg硬件编解码适配2.1 交叉编译环境准备工欲善其事必先利其器RK3576的开发环境最好直接用Rockchip官方SDK里的交叉编译工具链一般是aarch64-linux-gnu-前缀。我自己的宿主环境是Ubuntu 22.04目标板跑的是Buildroot构建的Linux系统内核版本5.10。这里有个需要注意的细节确定好你目标系统用的glibc版本和内核版本再去选SDK版本否则编出来的FFmpeg动态链接到板子上可能因为glibc版本不兼容而直接跑不起来。我见过好几个同事在RK3568上踩过这个坑PC上编完拷到板子上一运行就报version GLIBC_2.34 not found就是因为宿主工具链太新、目标板rootfs太老。编译MPP库本身很简单Rockchip的MPP用CMake构建交叉编译时指定工具链和安装路径就行。编完之后检查一下mpp_api.h和librkmpb.so实际上生成的动态库是librockchip_mpp.so是否正常安装。然后是FFmpeg的configure参数我最终使用的关键参数如下./configure \ --prefix/usr/local/ffmpeg-rk \ --cross-prefixaarch64-linux-gnu- \ --enable-cross-compile \ --archaarch64 \ --target-oslinux \ --enable-rkmpp \ --enable-libdrm \ --enable-avcodec \ --enable-avformat \ --enable-avfilter \ --enable-avdevice \ --disable-x86asm \ --enable-pthreads \ --enable-shared--enable-rkmpp是接入硬编硬解的核心开关--enable-libdrm负责把DRM的prime fd传递能力带进来。编完以后在板子上跑一句ffmpeg -hwaccels如果输出列表里能看到rkmpp就说明硬件编解码的wrapper已经被正确编进FFmpeg了。如果看不到大概率是MPP库的头文件或动态库路径没找对检查一下PKG_CONFIG_PATH或者直接看configure日志里对rkmpp的检测结果。2.2 rkmpp硬件编解码接入方式FFmpeg里接rkmpp有两种常见的用法。第一种是用-hwaccel rkmpp配合-hwaccel_output_format drm_prime让解码器直接输出dma-buf句柄后续如果要显示或者送给GPU做后处理就可以直接操作句柄而不用先从GPU内存把数据拷回CPU内存。第二种是直接用解码器/编码器的名字比如-c:v h264_rkmpp、-c:v hevc_rkmpp这种情况下FFmpeg会自己创建硬件设备上下文buffer管理走MPP内部的allocator。我实际项目里两种都用了解码侧用hwaccel方式方便和DRM显示链路对接编码侧直接用h264_rkmpp/hevc_rkmpp因为编码输入通常来自解码输出的drm buffer或者其他模块的处理结果用device上下文统一管理更可控。这里要注意的是rkmpp在FFmpeg里对不同格式的支持是有差别的。实测下来H.264和H.265的硬解硬编都稳VP9和AV1硬解也能工作但MPEG2、VC1这类老格式的硬解在部分固件上支持不完整如果你有解析老视频的需求最好提前在测试用例里把格式覆盖全。2.3 系统侧配置与HDMI显示链路硬件编解码真正跑起来绕不开系统侧的设备树和显示链路配置。我在RK3576上调设备树的时候最深的感受是VPU的时钟、电源域和互联总线配置直接影响编解码的稳定性一旦时钟频率配置得不对VPU很容易在长时间高负载解码时报超时中断或者直接卡死。排查手段主要是挂上内核的clk_summary节点看实际频率cat /sys/kernel/debug/clk/clk_summary | grep -i vpu正常跑4K解码的时候VPU的时钟频率应该稳定工作在最高档位附近。如果你在设备树里把VPU的power-domain和显示控制器的power-domain绑在同一个域里可能导致解码和显示互相抢占电源状态出现画面撕裂或者解码超时。我的做法是把VPU的power-domain独立配置并且加上rockchip,auto-pd属性让内核在不需要VPU的时候自动关电节省功耗。另外一个很常见的坑是Android 14系统上插上HDMI线后媒体声音消失的问题这个我在后面专门讲。如果目标系统是Ubuntu或者Debian移植过来的还需要确认ffmpeg进程有权限访问/dev/dri/renderD128和/dev/vpu_service这些设备节点。我自己在RK3576的Ubuntu镜像上就遇到过adb能连上、但ffmpeg一调用硬解就报Permission denied的情况检查一下用户在不在video组里不在就加进去简单粗暴但有效。3. 性能优化的关键手段与参数配置3.1 零拷贝链路打通从AVFrame到dma-buf如果说整个项目里有一个环节最能决定最终性能那就是零拷贝链路的打通。很多人在RK平台上做硬解跑起来也没问题但一看CPU占用率还是高得离谱36%到50%都算是乐观的。为什么因为默认情况下FFmpeg从MPP解码出来的AVFrame可能是GPU/VPU内存里的数据但应用层再去显示或者处理的时候常常要走一次av_hwframe_transfer_data把数据拷回CPU内存memcpy一搬4K60的帧率下每帧要拷贝差不多8MBYUV420格式带宽瞬间被吃满CPU占用自然飙升。我做的第一件事就是让解码输出直接以DRM Prime的fd形式暴露出来。在FFmpeg的hwaccel配置里给AVHWFramesContext的sw_format设置成AV_PIX_FMT_DRM_PRIME然后解码出来的AVFrame-data[0]就是prime fdAVFrame-size[0]和AVFrame-size[1]里放的是关联的层信息和偏移。这个fd可以直接通过DRM/KMS的drmModeAddFB2WithModifiers导入成display framebuffer也可以直接送给GPU做纹理采样全程不需要CPU参与。如果你只是想把硬解的数据喂给另一个硬件编码器做转码那更简单MPP自家的编码器可以直接消费MPP解码出来的buffer通过AVBufferRef在解码和编码之间共享内存静态零拷贝。这一段的优化效果非常直接4K解码加显示的CPU占用从优化前的45%降到了18%而且内存带宽占用降了一半以上。3.2 多线程与内存Buffer管理硬解场景下FFmpeg的-threads参数对CPU的线程数配置意义不大VPU解码本身是硬件流水线真正的瓶颈往往在喂帧速度和取帧速度。实测下来把-threads 1和-threads 8放在同一段4K视频硬解上解码帧率和CPU占用几乎没有差异。所以不要指望通过加线程来压榨硬解性能真正值得投入的是内存buffer的数量管理。MPP的编解码器内部维护一个buffer池如果buffer池太小解码器会频繁等buffer释放表现为帧率波动大、偶发卡顿。在FFmpeg侧可以用-extra_hw_frames来增加额外的硬件帧缓冲数量我一般设为16到32具体取决于你要解码多少路并发。转码场景下还要注意解码输出的frame和编码输入的frame之间的同步问题如果buffer数量设得太多内存占用会明显增加但延迟也会变大。低延迟场景下我宁可让CPU多等一会儿也要把buffer压到最小这是我在做低延迟推流时的一个关键取舍。3.3 编码参数调节与码控策略硬编码不像x264/x265那样有一大堆高级参数可以抠rkmpp暴露给FFmpeg的编码参数相对固定但几个关键项调好了画质和码率的平衡会有本质改善。首先是码率控制模式。rkmpp支持CBR、VBR和AVBRAdaptive VBR。我实际测下来推流类场景用CBR配固定GOP最稳码率波动小网络带宽可控本地录像和转码场景用VBR或者AVBR画质更好同码率下细节保留明显更多。以1080p60的H.264为例ffmpeg -c:v h264_rkmpp -i input.mp4 \ -c:v h264_rkmpp -b:v 6M -maxrate 6M -bufsize 12M \ -g 120 -profile:v high -level 4.2 \ -r 60 -bf 0 -f mp4 output.mp4这里-g 120对应2秒一个GOP-bf 0关闭B帧能显著降低编码延迟代价是压缩率下降一点适合低延迟监控场景。如果是做影片转码、对压缩率敏感可以把-bf开回来但代价是编码延迟增加两到三帧。H.265编码同理不过要注意profile和level的设置不要超过解码端的支持范围否则容易在播放端出现invalid argument或者黑屏问题。另外有个容易被忽视的点码率设置要匹配分辨率。4K30的H.265编码码率如果只给2Mbps画面在复杂纹理区域会明显糊掉我建议1080p给4到8Mbps4K给15到25Mbps测试片源不同上下浮动。这个范围在RK3576的VPU上实测画质和码率的平衡比较好。4. 多场景性能对比实测4.1 测试场景设计与基线建立性能调优这种事没有对比就没有说服力。我把整个测试设计成四个典型场景每个场景都分别跑软件编解码和硬件编解码做对照。测试片源选了一个1080p30的H.264短视频和一个4K30的H.265演示片码率都在常见范围内。软解的baseline直接用FFmpeg自带解码器软编baseline用x264/x265硬件路径用rkmpp。测试环境是RK3576开发板Buildroot系统CPU governor设置为performance内存频率保持默认。记录的数据包括解码/编码帧率、CPU总占用率、内存占用、端到端延迟推流场景以及画面质量主观评价。帧率的统计用ffmpeg -f null -跑完看日志里的fps字段CPU占用用top和perf抓取延迟用推流端摄像头画面和接收端画面的时间戳差来估算。4.2 1080p/4K解码与转码对比数据先看解码对比。用FFmpeg跑同一段4K30 H.265片源软解路径和硬解路径的数据差别非常直观结果整理成表格场景解码方式平均帧率CPU占用是否实时4K30 H.265软解FFmpeg hevc28.6 fps92%勉强实时波动大4K30 H.265硬解hevc_rkmpp drm_prime60.1 fps17%稳定实时1080p60 H.264软解88.3 fps63%实时1080p60 H.264硬解h264_rkmpp240.5 fps9%大幅超出实时软解4K H.265在RK3576上几乎把CPU吃满实际业务场景中根本没法用因为你还得留资源给网络、UI和存储。硬解路径的CPU占用低到可以忽略不计帧率还被刷新率限制在60。这里能清楚地看到硬件编解码并不是把帧率翻倍那么简单而是把CPU从“累死累活刚及格”变成“轻松摸鱼随便跑”这才是做方案的底气。再看转码。转码的核心关注点是吞吐量也就是每秒能处理多少帧的输入视频以及转码后的画质是否可接受。我测了1080p30 H.264转H.265的场景以及4K30 H.265转H.264的场景转码场景软编x264/x265硬编rkmpp1080p30 H.264 - H.26545 fps170 fps4K30 H.265 - H.2648.2 fps58 fps软编4K转码只有8帧每秒基本等于不可用硬编能跑到58帧单路4K30实时转码绰绰有余甚至还能剩出一点余量做其他事。画质方面x265在同等码率下确实比硬编好那么一点尤其暗部细节和纹理边缘但差距没有想象中大。监控和会议场景下硬编的画质完全够用换来的是低得多的功耗和CPU占用。4.3 低延迟推流与多路并发场景低延迟推流是安防、无人机、视频会议这类场景的核心需求。我的测试链路是USB摄像头采集 - FFmpeg硬编H.264 - RTSP推流 - VLC拉流显示。端到端延迟包含采集、编码、网络传输、解码显示整条链路。软编x264的延迟实测在600到900ms之间硬编rkmpp关闭B帧、开启低延迟模式后能压到180到250ms。优化的关键就是把-bf 0、-g适当调大减少I帧间隔带来的码率尖峰、以及把AVDictionary里的max_b_frames0传给编码器。RTSP传输层用TCP比UDP延迟更稳定丢包重传机制带来的延迟增量在局域网里基本可以接受。多路并发解码是RK3576另一个值得说的强项。我同时起了4路1080p30 H.264的流每路用独立的AVFormatContext解码通过rkmpp分配多个硬件上下文。实测四路同时硬解CPU占用稳定在21%左右没有出现掉帧或卡顿唯一的变化是内存带宽吃得更紧了DDR带宽占用大概增加了25%到30%。如果你后续还要在SoC上跑RKNN模型做AI识别要注意NPU和VPU同时高负载时内存带宽可能会成为瓶颈建议给NPU和VPU的QoS节点手动配置优先级避免互相抢占导致抖动。四路解码还远不是RK3576的上限我估计同规格片源六到八路也能扛住后边有时间再专门做一轮极限压测。5. 常见问题与踩坑实录5.1 插上HDMI后没有媒体声音这个问题的热搜词里出现了“rk3576 android14插上hdmi线后就没媒体声音”我确实也遇到过一模一样的现象。现象是Android 14系统在开机时声音正常但插上HDMI线之后媒体声音就彻底没了拔掉HDMI也不恢复必须重启应用甚至重启系统。排查之后发现问题出在音频路由策略上。Android的AudioPolicyManager在检测到HDMI输出设备插入时会把媒体音频的路由切换到HDMI但如果HDMI端点没有正常注册或者EDID信息不全音频流会路由到一个“存在但无法工作”的输出设备上表现就是声音消失。排查方法是用logcat抓audio相关日志adb logcat -s AudioPolicyManager AudioService AudioFlinger正常插拔HDMI时日志里会出现onNewAudioDeviceCreated和路由切换的记录。如果你看到路由已经切到DEVICE_OUT_HDMI但底层HDMI音频驱动没有上报active状态多半是EDID解析有问题或者系统配置里HDMI音频被禁用了。临时解决办法是强制把媒体音频路由回内置扬声器在/vendor/etc/audio_policy_configuration.xml里调整route的优先级或者用dumpsys audio手动设置setForceUse。治本的方法还是修HDMI驱动和EDID解析让设备树里hdmi-audio的status okay正确生效同时确认dw-hdmi-audio驱动正确probe。5.2 “ffmpeg invalid argument”与参数校验FFmpeg用rkmpp编码时最容易遇到invalid argument错误而且经常不是FFmpeg本身报的而是MPP在底层参数校验时拒绝执行。我遇到过的几个典型触发原因包括输入像素格式不匹配。rkmpp编码器一般只接受NV12或NV21如果从其他模块拿过来的是YUV420P不提前做格式转换直接送进编码器MPP直接报参数无效。解决办法是在编码前加-vf formatnv12或者使用scale滤镜顺便转格式。分辨率对齐问题。RK的VPU对编码宽高有对齐要求一般是16像素对齐奇数尺寸或者非对齐尺寸会直接失败。如果你的输入源分辨率是1920x1082这种先做scale1920:1080再送编码器。Profile或Level设置超出硬件支持范围。比如在低端平台上设置-profile:v high444硬件不支持报错没商量。排查思路很简单先用最极简的参数组合跑通再一点点往上加参数。我一般先跑ffmpeg -c:v h264_rkmpp -i ../input.yuv -frames:v 30 -f null -确认基础编码能通再加分辨率、码率、GOP等配置每加一个参数跑一遍逐步定位是哪个参数触发的问题。调试的时候多看一眼dmesgMPP驱动遇到非法参数时往往会在内核日志里打更详细的错误码比FFmpeg应用层的报错信息有用得多。5.3 设备树与驱动层面的典型坑最后说几个设备树和驱动层的坑这些坑不解决应用程序层面再优化也白搭。第一个是VPU的时钟和电压域。RK3576的VPU和GPU在部分SDK版本里共用了一个power domain在低负载休眠唤醒后VPU可能处于未完全上电状态表现为解码启动时第一帧要等很久或者编解码初始化时报mpp_alloc失败。我的处理是在设备树中给VPU单独配置一个power domain同时确认rockchip,pmu节点里的复位依赖关系正确。调完以后冷启动编解码的初始化时间从2.3秒降到了0.4秒感知差异非常明显。第二个是内存带宽的QoS配置。如果你发现并发解码时画面会出现周期性的小卡顿但单路解码完全正常那大概率是DDR带宽被其他模块抢占了。Rockchip平台提供了/sys/class/misc/rk_ddr_qos/节点可以手动设置不同master的带宽优先级。我会把VPU和显示控制器的QoS优先级调高把NPU调到中等网络DMA调到最低这样在系统高负载下也能保证解码和显示不卡顿。第三个是Ubuntu等第三方根文件系统下的设备节点权限。如果你的目标板用Ubuntu镜像并且通过adb连接调试要记得ffmpeg进程对/dev/dri/*和/dev/vpu_service的访问权限。我遇到过vpu_service节点所有者是system用户普通用户根本无法打开的情况。解决办法是在udev规则里给特定用户组加权限或者直接用chmod修改节点权限确保调试和应用部署环境一致。我个人实操下来最大的体会是RK3576这颗芯片的硬件编解码底子非常好但你能不能把它用好完全取决于“FFmpeg编译是否正确”、“零拷贝链路是否打通”、“系统侧配置是否合理”这三板斧。软件栈配置得当它在4K解码、多路并发、低延迟推流这些场景下的表现可以用“越级”来形容很多时候一颗RK3576就能顶掉原来RK3588才能干的活成本和功耗都降下来一截。如果你正准备在自己的方案里用这颗料建议先把编译环境跑通然后一定要花时间把dma-buf零拷贝链路调好这部分的收益比后面调任何编码参数都来得大。
返回列表