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

资讯详情

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

RK平台MPP从源码编译到H.264编码测试全流程实战

RK平台MPP从源码编译到H.264编码测试全流程实战 做RK平台开发尤其是碰到视频采集、编码推流、本地录像这类需求时MPP几乎是绕不开的一环。我最早在RK3568上调H.264硬编码时因为不熟悉MPP的构建方式和接口习惯光是把源码编译跑通就花了一个多星期。后来在RK3588上做多路解码又踩了一堆坑才把这套框架的脾性摸得差不多。这篇文章就从实测角度把RK平台MPP从源码编译到H.264编码测试全流程梳理一遍给打算用MPP做视频项目的朋友一份能直接参考的步骤和踩坑记录。MPP这个模块对很多刚接触瑞芯微平台的开发者来说概念上容易混淆实际操作层面也有不少暗坑。但它确实是整个视频链路的基石搞明白它后续做RGA图像处理、多路并发、硬件转码都会顺畅很多。这篇内容既适合刚拿到开发板、准备在RK平台上跑视频应用的新手也适合已经调过一段时间但被各种报错困扰的工程师。下面直接进入正题。1. 认识MPPRockchip视频处理的基石1.1 MPP到底是什么为什么绕不开MPP全称叫Rockchip Media Process Platform是瑞芯微官方提供的一套媒体处理软件平台。它做的事情可以简单理解为把芯片内部VPUVideo Process Unit的硬件能力封装成一组相对统一的接口应用层通过MPIMedia Process Interface调用就能完成视频编码、解码、转码等操作而不用直接去操作底层寄存器。为什么要这么设计因为VPU硬件本身非常复杂。不同型号的芯片VPU的寄存器地址不同、中断机制不同、支持的编解码格式和分辨率也不同。如果每个项目都直接写寄存器操作代码基本没法复用而且调试难度极高。MPP的价值就在于把这一层差异封装掉让应用层写成“打开通道、送数据、取数据”的固定模式。底层是H.264还是H.265是RK3568还是RK3588对上层的接口使用方式差别不大。我常跟团队里新来的同事说MPP的定位很像家里那个转接头电视背面的接口五花八门但通过转接头HDMI、DP、Type-C都能统一到同一块屏幕。MPP就是视频应用和VPU硬件之间的那个转接头。没有它你每个视频项目都得重新面对那几百页的芯片寄存器手册。1.2 VPU、RGA和MPP怎么分工协作在瑞芯微平台上一个典型的视频编码流程是这样的摄像头或者其他数据源送来原始图像帧应用层把帧数据交给MPP编码通道MPP内部经过格式检查、内存映射、硬件提交等步骤调用VPU完成编码最后把编码后的码流返回给应用层。这个过程对开发者来说主要是和MPP暴露出来的MPI接口打交道硬件细节都被隔离开了。这里有一个很容易混淆的概念VPU和RGA。VPU是编解码专用硬件负责H.264、H.265这类重负载计算RGA是2D图形加速单元负责图像缩放、颜色空间转换、旋转、裁剪这些操作。两者不是一回事但经常配合使用。比如你想把摄像头采集的NV12图像先缩小再编码就要用RGA做缩放再把结果送给MPP编码通道。在RK3588平台上MPP加RGA组合起来可以比较轻松地处理4K甚至8K的视频流水线这是CPU纯软编完全做不到的。MPP支持的编解码格式也值得提前了解。解码侧常见的有H.264、H.265、VP9、AV1部分平台、MJPEG等编码侧常见的有H.264、H.265、VP8、JPEG等。不同芯片支持的格式和能力有差异比如RK3588支持8K解码和8K编码而RV1126这类轻量级平台的功能就弱一些。做项目前的第一件事就是去确认你用的芯片到底支持哪些格式和分辨率避免方案白做。MPP的源码目录结构也比较典型刚开始看可能有点晕但理清后就很清晰。根目录下有mpp核心库源码、mpi接口层实现、hal硬件抽象层、codec编解码相关、test测试程序、tools工具等几个子目录。编译完以后我们主要用到的是librkmp.a这样名字的库以及test目录下生成的mpp_enc_test、mpp_dec_test这类可执行测试工具。2. 环境准备与工具链选择2.1 目标平台与系统检查先确认两件事我开始做MPP实验用的是RK3568开发板后来换到了RK3588。如果你只是学习MPP的框架和流程RK3568完全够用如果想跑8K或者多路1080p并发就直接上RK3588。板子安装的系统优先选Ubuntu桌面版或服务器版Debian和Buildroot也行但Ubuntu下装工具链、测试时拷贝YUV文件都方便不少。拿到板子后先确认两件事。第一/dev/下面有没有mpp设备节点。在板子上执行ls -l /dev/mpp*正常会看到类似于/dev/mpp_service的节点。如果没有大概率是内核里mpp_service驱动没编进去或者设备树里VPU节点被关闭了。这种情况要去查内核配置打开对应的rockchip mpp服务选项后重新编译内核。开发板上这类问题相对少见但如果是自己做的内核裁剪就很容易踩到。第二件事确认板子的CPU架构uname -mRK3568和RK3588都是aarch64架构所以在x86主机上交叉编译时要选64位ARM工具链。如果某些老平台是32位ARM比如RK3288那就要换arm-linux-gnueabihf工具链。架构选错基本编出来的程序在板子上跑不起来轻则报No such file or directory重则直接Segmentation fault。2.2 交叉编译工具链的安装与版本选择交叉编译的意思是在x86主机上编译出ARM平台可以运行的程序。最简单的方式是直接用发行版仓库里的工具链Ubuntu上执行sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu装完以后可以用aarch64-linux-gnu-gcc -v验证版本。如果想用官方推荐的Linaro工具链也可以去Linaro官网下载版本选7.x或更新一点的都行。MPP对编译器版本不算挑剔但太老的GCC会在新版本源码上出一些C兼容问题我试过用4.9的老工具链编新版MPP报了几个头文件相关的错误换成7.5以后就正常了。一个容易被忽略的细节交叉编译出来的程序在板子上运行还依赖动态链接库。MPP编译时可以选静态库也可以选动态库如果选动态库记得把librkmp.so这类文件也拷到板子上放到/usr/lib或者用LD_LIBRARY_PATH环境变量指定路径。否则测试工具在板子上运行时会提示找不到共享库。2.3 拉取MPP源码分支和版本怎么选MPP源码在GitHub上可以找到仓库名字是rockchip-linux/mpp。克隆命令git clone https://github.com/rockchip-linux/mpp.git仓库里有几个分支我一般用develop分支获取最新特性用release分支跑稳定版本。如果你是做产品建议直接选一个和芯片匹配的release tag比如在RK3588平台上选择对应版本的release编译和运行的兼容性会好很多。学习阶段用develop没问题但要注意develop分支偶尔会有接口调整遇到编译报错时先看看是不是上游代码刚改过。克隆完以后源码根目录下会有一个build.sh脚本这是官方提供的编译入口。打开这个脚本会发现里面主要是配置了一些环境变量比如工具链路径和交叉编译参数然后调用CMake完成实际构建。脚本本身不复杂但里面几个变量需要根据本机情况改一下下一节我会详细说。3. 从源码编译MPP解决构建过程中的关键参数3.1 理解build.sh背后的CMake机制MPP采用的是CMake构建系统build.sh只是对CMake命令做了一层封装。它的基本思路是在源码目录下创建一个build目录通过CMake指定交叉编译工具链文件然后生成Makefile再执行make编译出库文件和测试工具。交叉编译的关键在于CMake的toolchain文件。MPP源码的cmake目录下放了一个arm.linux.cross.cmake里面定义了CMAKE_C_COMPILER、CMAKE_CXX_COMPILER等变量。build.sh会通过-DCMAKE_TOOLCHAIN_FILE把这个文件传给CMake。我手动编译时更习惯用这种直接的方式。在源码目录外建一个build目录mkdir build_manual cd build_manual cmake -DCMAKE_BUILD_TYPERelease -DCMAKE_TOOLCHAIN_FILE../mpp/cmake/arm.linux.cross.cmake ../mpp make -j$(nproc)这种方式的好处是直观能清楚看到CMake的每一个参数。但要注意toolchain文件里写死的编译器路径可能和你本机实际安装的路径不一致。比如文件里写的是/opt/arm/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc而你的工具链在/usr/bin下面这时就要手动改一下文件或者在CMake命令行用-DCMAKE_C_COMPILER覆盖指定。3.2 必须搞清楚的编译参数工具链路径与目标平台build.sh里值得关注的变量主要有两个。一个是RK_TOOL_CHAIN它指定交叉编译工具链的根目录另一个是RK_PLATFORM它决定目标平台。默认情况下脚本会尝试自动判断但强烈建议显式设置避免脚本逻辑在某个版本里突然抽风。比如在RK3588上我通常把build.sh里的工具链路径改成RK_TOOL_CHAIN/usr/bin如果你的工具链装在非系统路径就改成实际目录所在位置。需要留意的是MPP的CMake脚本有时会根据RK_TOOL_CHAIN去拼接gcc、g、strip等工具的名字所以路径层级要写对别写成了bin目录里完整gcc的文件名否则脚本会把路径拼重复。除了build.sh里这些变量CMake层面也有一些选项可以控制编译产物。比如只想编静态库不编动态库可以加-DBUILD_SHARED_LIBSOFF不想编测试工具可以加-DBUILD_TESTOFF。默认情况下测试工具会一起编出来mpp_enc_test和mpp_dec_test这两个工具对后面的实测非常有用建议保留。3.3 实际操作编译、产物收集与板端部署我实际操作的标准流程是这样。第一步进入源码目录修改build.sh里的工具链路径。第二步设置目标平台export RK_PLATFORMRK3588第三步直接执行./build.sh如果一切正常编译输出会出现在build/linux/aarch64目录下。里面有几个子目录最关键的产物是librkmp.a或librkmpd.so这样的库文件以及test目录下的可执行工具。编译过程如果报错绝大多数情况是工具链路径不对或者缺少某个依赖库。我第一次编译时因为只装了aarch64-linux-gnu-gcc但没装g结果CMake在检查C编译器时直接失败。还有一次是环境变量CC和CXX被之前的项目污染了导致CMake用了本机x86的gcc去编ARM代码最后链接阶段一堆“file format not recognized”。排查这类问题首先把make输出完整贴出来看是配置阶段还是编译阶段挂的再看报错信息里有没有“cannot find”“No such file”这类关键词。编译完成之后记得把库文件和测试工具都拷贝到板子上。我习惯用一个临时目录收集这些文件cp build/linux/aarch64/mpp/librockchip_mpp.so* /target_lib/ cp build/linux/aarch64/test/mpp_enc_test /target_lib/然后通过scp或者U盘把整个目录拷贝到板子。到这里MPP编译算是跑通了接下来就是编码测试的环节。4. H.264编码测试从命令行工具到码流验证4.1 mpp_enc_test工具参数逐个说MPP编译完成后会在test目录下生成一批测试工具其中mpp_enc_test是编码测试工具mpp_dec_test是解码测试工具。这两个工具名字很相似使用时最容易搞混的就是把编码工具和解码工具的-t参数理解错。先看mpp_enc_test的基本用法。在板子上执行./mpp_enc_test -h会看到完整的参数说明。核心参数有以下几个-t编码器类型7代表H.2648代表H.2659代表VP9-w图像宽度比如1920-h图像高度比如1080-n要编码的帧数-f输入像素格式0代表NV121代表YUV420P-gGOP也就是关键帧间隔-b目标码率单位是bps我一般先用一条最简命令跑通流程./mpp_enc_test -t 7 -w 1280 -h 720 -n 150 -f 0 -g 30这个命令的意思是用H.264编码器输入1280x720的NV12数据编码150帧关键帧间隔30帧。工具运行时会打印每一帧的编码状态、返回码、码流写入路径等信息结束后在当前目录生成H.264码流文件。4.2 编码参数背后的选择逻辑格式、GOP和码率这里逐个解释为什么这么设置。-t 7对应H.264的编码类型。MPP内部用MPP_VIDEO_CodingAVC这样一组枚举值来表示编码格式AVC就是H.264。如果改成-t 8就是H.265但前提是你的芯片VPU支持H.265编码。RK3588没问题老一点的平台就要查文档确认。-w和-h是输入图像的宽高。这里要注意VPU对分辨率对齐有要求很多平台要求宽高至少16对齐有些还要求64对齐。如果你传入1921x1081这种不规则的尺寸某些驱动会直接报错有些会默默校准但输出图像可能不对。我在试验中发现用1920x1080这种标准分辨率基本不会出问题自定义分辨率时需要确认对齐规则。-f指定输入像素格式默认填0就是NV12。NV12是YUV420的半平面格式Y平面和UV交错平面分开存放在编码场景里非常常见。如果输入数据其实是YUV420P全平面格式但-f填了0编码出来的画面就会颜色错乱通常表现为偏绿或者偏紫。这是新手最容易踩的坑后文我还会再提。-g是GOP间隔也就是每隔多少帧插入一个关键帧IDR帧。网络推流场景里GOP一般设为帧率的两倍比如30fps设置为60这样每两秒一个关键帧便于播放器中途切入解码。如果GOP设得过大关键帧太少码流从中间开始播时可能长时间花屏因为后面的P帧依赖前面的参考帧拿不到参考帧就解不出来。4.3 跑通一条完整的H.264编码测试链路实际测试时还需要一个YUV输入文件。生成方式很多最简单的是在x86主机上用FFmpeg生成测试源ffmpeg -f lavfi -i testsrcduration5:size1280x720:rate30 -pix_fmt nv12 test_720p.yuv这条命令会生成一个时长5秒、分辨率1280x720、帧率30fps、NV12格式的YUV文件。把这个文件拷贝到板子上然后运行./mpp_enc_test -t 7 -w 1280 -h 720 -n 150 -f 0 -g 30 -b 2000000 -i test_720p.yuv这里-b 2000000表示目标码率约2Mbps。工具编码完150帧后会在日志里打印消耗的时间、平均编码帧率等信息这些数据可以用来评估当前分辨率下VPU的编码性能。编码出来的H.264文件怎么验证我习惯把文件拷回x86主机用FFmpeg的ffprobe看一下ffprobe output.h264正常会看到编码格式、分辨率、帧率、码率等信息。如果ffprobe能正确识别出H.264基本说明编码流程是通的。接下来还可以用播放器直接播放码流或者在板子上用mpp_dec_test做一次解码验证编码和解码链路是否完整。mpp_enc_test默认生成的输出文件名在运行日志里会明确打印出来如果默认生成的名字不是你预期的可以在参数里找输出文件相关的选项参考-h帮助里的说明。5. 常见问题排查与调试心得5.1 mpp解码失败最常见的三个原因“mpp解码失败”这类报错在我刚开始调试时出现频率很高。先要明确一点MPP的解码和编码工具虽然用起来简单但底层涉及内存分配、硬件提交、中断处理等多个环节任何一个环节出问题都会表现为“解码失败”或者返回某个错误码。我遇到的第一类问题是设备节点或权限问题。程序启动时如果报错说找不到/dev/mpp_service或者打开文件失败先检查节点是否存在、当前用户是否有权限。开发阶段最简单的方法是sudo chmod 666 /dev/mpp_service产品阶段当然不建议这么干正确做法是配置udev规则让特定用户组有访问权限。另外内核日志里偶尔也会出现mpp相关报错用dmesg查看能帮我们判断是驱动问题还是应用层问题。第二类问题是码流本身的问题。如果输入MPP解码器的H.264码流是截断的、不完整的解码器在某个时刻会返回错误。对比明显的现象是同一个工具用完整码流测试一切正常用网络抓包抓下来的半截码流就报错。这类问题不能怪MPP需要检查码流来源比如推流端是否正常发送了SPS/PPS和关键帧。第三类问题是参数和实际码流不匹配。比如-t填了7H.264但输入文件其实是H.265码流解码器肯定会失败。还有分辨率和宽高参数填错导致解码后的缓冲区大小不对。排查时先把-t、-w、-h这几个参数和码流的实际信息对齐再看其他原因。5.2 运行时报错与异常画面的定位方法mpp_enc_test和mpp_dec_test运行中常见的错误码在MPP头文件里能查到定义比较典型的有MPP_ERR_VPU_API、MPP_ERR_MALLOC等。这些错误码本身只是一个线索关键还是要看打印日志里的上下文。有一次我在RK3588上跑多路编码程序运行几秒钟后报内存分配失败。后来排查发现板子的CMA内存池被其他模块占满了VPU申请不到连续物理内存。解决办法是把CMA预留内存调大或者在设备树里调整VPU相关的内存配置。这类问题在压力测试中尤其常见单路编码基本遇不到一旦多路并发内存规划就是重点。还有一次现象是编码出来的视频播放卡顿但编码日志显示帧率正常。后面发现是输入YUV文件本身帧率不够30fps但编码配置写的是30fps导致输出码流时间戳不均匀。验证方法很简单用ffprobe看输出文件的帧率信息再对比实际编码帧数和耗时。我还遇到过一种情况程序里调用MPI接口反复创建和销毁通道偶尔会出现通道资源没被正确释放的问题。后来查代码发现是没有正确调用deinit释放资源。MPP的接口要求成对使用init对应deinitstart对应stop谁申请谁释放。这些约束在文档里写得很清楚但实际开发中很容易漏掉漏掉以后日志里看不出明显异常跑久了就会出现资源泄漏。5.3 调试技巧与多路并发经验MPP带了调试日志开关可以通过设置环境变量打开。比如export MPP_DEBUG1打开后程序运行时会打印更多内部状态信息包括帧提交流程、硬件状态等。对排查问题非常有用但生产环境别开着日志量太大性能影响明显。还有一个经验是尽量用FFmpeg先做一次对照验证。如果一条H.264码流用FFmpeg解不开那大概率是码流本身有问题而不是MPP的问题。反过来FFmpeg能解开而MPP解不开那再怀疑MPP的用法和参数配置。参数组合上我建议第一次跑通时只用最基础的参数不要一上来就开多线程、加RGA、搞复杂的颜色转换。先把“YUV输入-H.264码流输出”这条链路跑通再逐步叠加功能。这样做的好处是一旦出问题你很清楚是新增的哪个环节导致的。多路并发场景下要合理分配编码通道的优先级和缓冲池大小。RK3588这种平台虽然能力强但也不是无上限的。我一般会用mpp_enc_test连续跑多路测试监控系统负载和内存占用找到当前业务负载下的安全并发数再留一定余量给别的模块。最后分享一个我个人的习惯做MPP相关实验时我会把每一步操作命令和日志输出保存到单独的文件里尤其是错误码和对应环境。因为MPP报错很多时候依赖具体平台的驱动版本和芯片型号同样的错误码在不同平台上原因可能完全不同。把这些现场信息完整保存下来碰到问题再翻看能省下大量重复排查的时间。RK平台的MPP功能很强大但文档相对零散遇到问题多翻源码、多看内核日志比到处问人靠谱得多。
返回列表