
最近手上的项目要从RK3568平台上跑一路视频采集、处理和输出的链路方案里要用到瑞芯微的rockit组件。网上关于rockit的资料零零散散官方SDK里也是把它当成一个子模块扔在那里文档不细。我自己从拉源码、交叉编译、烧板到跑通第一个demo前前后后折腾了两天多踩了不少坑。这篇文章就是把我这次编译测试的整个过程整理一遍包括环境准备、依赖库处理、交叉编译参数、板端部署和常见问题希望能帮到同样在RK3568上搞rockit的朋友少走弯路。rockit这个东西往简单说就是瑞芯微提供的一套多媒体处理框架主要用来管理摄像头采集、ISP处理、编解码、显示输出这类任务。它和内核里的v4l2驱动配合把底层复杂的媒体通路封装成用户态的流对象。对做安防、AI盒子、工业视觉的人来说编译测试rockit基本是绕不开的一步因为很多预编译库版本和内核驱动对不上或者你需要在里面增加自定义后处理逻辑。这篇文章适合两类读者一类是刚接触RK3568、想把rockit跑起来的新手另一类是被交叉编译折磨过、想找一套通用编译测试流程的嵌入式工程师。1. 项目背景与整体方案拆解先说清楚我为什么要碰rockit。项目的硬件平台是RK3568跑的是Buildroot出来的系统内核是5.10需要接入两路MIPI摄像头做连续采集然后在屏幕上叠加显示。readback/显示这块一开始想直接用MPP硬解但发现上层逻辑越做越复杂采集、缩放、旋转、格式转换、送显这些环节都要自己串。后来发现rockit正好把这些模块拧在了一起提供了一套相对统一的管理接口于是决定迁到rockit上。不过rockit的定位和你理解的可能不太一样。它不是单独的ISP驱动也不是编解码库更像是一个中间的“胶水层”。它依赖mpp做编解码、依赖rga做图像处理、依赖drm做显示底层还会通过v4l2访问media controller。换句话说编译rockit之前你的电脑里或SDK里得先有这些依赖库的交叉编译版本。很多人在最早一步挂掉就是以为只要把rockit源码拉到本地就可以直接编译结果CMake配置阶段就报找不到mpp头文件。所以在开始动手前我先把整体方案拆成了四步准备好RK3568的交叉编译链确认系统里已经编译出了mpp、rga这些基础库。获取rockit源码通常是RK SDK里自带或者从git服务器单独拉取。配置CMake让它找到依赖库的头文件和so文件然后交叉编译生成librckit和测试sample。把编译产物拷贝到板子上配置好动态库路径和bin目录跑通一个最简单的采集demo验证链路。这四步看起来很简单但每一步都有隐藏的坑。依赖库的版本号、编译工具链的路径、板端rootfs里缺少符号、设备树里视频节点没有使能任何一处对不上都会让你怀疑人生。我下面按顺序慢慢讲。1.1 为什么选择手动交叉编译而不是直接用SDK预编译库很多拿到RK3568开发板的人第一反应是直接用板厂提供的完整SDK buildroot然后在menuconfig里勾选rockit让它自动编译、自动打包进镜像。这样确实最省事适合只做业务层开发的团队。但有一个非常现实的问题板厂SDK里出厂带的rootfs往往是定制的rockit版本、依赖库版本、内核side配置可能都已经锁死。你要改上游源码或者在编译期就做一些裁剪就得回到源码手动编译。另外手动交叉编译rockit能让依赖关系更清晰。自动编译虽然一把梭但出了问题很难定位比如你改了dts之后发现采集不通到底是内核驱动问题还是rockit上层库的问题手动编译的话你可以精确控制每一步至少能把问题范围缩小。我这次没有用SDK一键编译的另一个原因是项目的代码仓库比较分散rockit源码需要单独维护不能老跟着整个SDK一起build。所以我选择“提取rockit源码 手动交叉编译”作为标准的编译测试方式这样可以独立出出一份改进日志也方便同事之间共用同一套编译脚本。1.2 编译测试前需要确认的硬件和系统前提在编译阶段其实不需要板子但你得预先确认目标平台的几个信息SoC型号RK3568确认是标准版还是J版、工业版这会影响部分外设路径。工具链位宽RK3568的Cortex-A55是64位一般用aarch64-linux-gnu工具链。别用arm-linux-gnueabihf那是给32位用的。系统libc版本板端是glibc还是uclibcrockit如果直接用so头文件依赖和动态链接库会不一样。我这次用的是glibc也就是标准Buildroot配置。内核中video节点是否使能比如测试OV5695或BT1120输入必须确认DTS里对应的sensor节点、isp节点都打开否则后边跑sample的时候会报“open video node failed”。这些信息不确定的话建议打开板子串口跑一下uname -a cat /etc/os-release ldd --version先把运行环境看清楚再回来搞编译能省很多无用功。2. 环境准备与源码获取如果需要完整的重现过程我建议你在Ubuntu 18.04或20.04的服务器上做交叉编译。我用的编译环境是Ubuntu 20.04装了必要的构建工具和aarch64交叉编译器。Rockit的CMake逻辑并不复杂但对工具链版本有一定要求我用的是gcc-arm-10.3-aarch64-none-linux-gnu这套工具链比较稳在瑞芯微官方文档里也经常出现。2.1 下载并安装交叉编译工具链工具链有很多获取方式。如果你的机器上已经有瑞芯微SDK其实SDK自带的prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu可以直接用。没有的话就从ARM官网或第三方镜像下载解压到/opt/toolchain目录下。具体安装步骤大致是sudo mkdir -p /opt/toolchain sudo tar xf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz -C /opt/toolchain export PATH/opt/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH export CROSS_COMPILEaarch64-none-linux-gnu-需要注意的是有些教程里喜欢用aarch64-linux-gnu-gcc这是一套发行版自带的交叉工具链。如果你之前用Ubuntu的gcc-aarch64-linux-gnu包装了编译普通C程序没问题但编译依赖到特定sysroot的rockit时可能会因为找不到库头文件而失败。我建议尽量用编译器自带的sysroot不要混用不同工具链。这个坑我在编译依赖库时遇到过一次编译出来的libmpp.a用的是老版本GCC语法和新的rockit代码不兼容最后全部重编才解决。2.2 获取rockit源码与分支匹配rockit源码我是在板厂SDK的external目录里找到的目录名就是rockit。如果你手上的是单独拿来的仓库要注意分支和SDK里的base version是否一致。瑞芯微经常在更新不同的kernel版本、不同的mpp版本对应的rockit接口会有差异。最稳妥的做法是检查SDK发布说明找一个和当前kernel版本匹配的rockit分支。源码拿下来后建议先看一遍目录结构。一般会有inc对外头文件src核心实现samples测试demoCMakeLists.txt编译入口build可能存有老的编译脚本我这次在samples目录里找到的主要demo是sample_video功能就是从指定的video节点采集视频流经过简单处理后保存文件或者送显示。这个demo正好可以用来做编译后的链路验证。2.3 提前准备依赖库mpp、rga、drmrockit不是一个完全独立的东西它要链接mpp、rga、drm这些库。我强烈建议在编译rockit前先在工具链的sysroot里或者一个自定义的依赖目录里放好这些库的头文件和库文件。我自己整理了一个目录结构/home/yourname/rk3568_deps/ ├── include/ │ ├── rockchip/ │ │ ├── mpp_log.h │ │ ├── mpp_rc.h │ ├── rga/ │ │ ├── RockchipRga.h │ ├── libdrm/ │ ├── drm.h ├── lib/ ├── librockchip_mpp.so ├── librga.so ├── libdrm.so ├── libmpp_enc.so这些库文件可以从Buildroot编译产物里拿也可以直接从开发板的rootfs里拷出来。省事的做法是把板子上/oem/usr/lib和/usr/lib下的so都拉下来放到deps/lib目录。但要注意把符号链接也一起拷过来很多人在板端部署时遇到“cannot open shared object file”就是因为so的软链丢了系统只识别带版本号的真实文件。如果你对依赖版本有洁癖那就老老实实把mpp、rga单独交叉编译一遍。编译的时候注意启用和rk3568匹配的平台宏比如mpp的-DPLATFORMlinuxrga的-DCMAKE_BUILD_TYPERelease。这些库编译完后再回来编译rockit后面基本不会报缺依赖的错误。3. 交叉编译rockit的完整流程这一步是整个博文的核心。我把过程分成三次实践第一次是最小化编译第二次是集成deps编译第三次是处理编译报错。这里给出我已经跑通的通用流程你替换成自己的路径就能直接用。3.1 建立构建目录与CMake配置在rockit源码根目录下我习惯新建build目录并把交叉编译工具链的路径和依赖库路径都传到CMake变量里。cd rockit mkdir build cd build cmake .. \ -DCMAKE_C_COMPILER/opt/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc \ -DCMAKE_CXX_COMPILER/opt/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-g \ -DCMAKE_SYSROOT/opt/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/aarch64-none-linux-gnu/libc \ -DCMAKE_BUILD_TYPERelease \ -DROCKIT_DEPS_PATH/home/yourname/rk3568_deps \ -DCMAKE_INSTALL_PREFIX/home/yourname/rockit_out这里的ROCKIT_DEPS_PATH是我自己整理的依赖库路径如果你的源码CMake不支持这个变量直接把-DCMAKE_INCLUDE_PATH和-DCMAKE_LIBRARY_PATH指到deps目录也可以。核心目的就是让CMake能在交叉环境下找到头文件和库文件。如果你不想污染源码建议把build目录单独建在源码外面。瑞芯微这套代码的CMakeLists有时候写得不严谨在源码树内多次编译会产生缓存污染切分支后编译经常报旧文件残留。外部构建要干净得多。3.2 编译过程与产出说明配置完CMake后直接make即可make -j$(nproc) make install第一次编译可能比较久如果机器核数多1到2分钟就能完成。编译完成后rockit_out目录下的东西大概是rockit_out/ ├── bin/ │ ├── sample_video │ └── likely_more_samples ├── lib/ │ ├── librckit.so │ └── maybe_sub_libs └── include/ └── rockit headers重点看librckit.so是否生成。这个库文件就是rockit的核心动态库后面跑板端demo全靠它。示例bin可能会依赖这个so要保证它们在一个目录或者在LD_LIBRARY_PATH里能找到。我编译时用的-DCMAKE_BUILD_TYPERelease作用不仅是去掉调试符号更重要的是启用一些编译优化宏。rockit源码里有不少针对性能的关键路径优化如果以Debug模式编译采集帧率会很差。除非你需要gdb单步调试否则建议Release。3.3 与SDK内部编译的差异处理如果你的SDK里之前用buildroot编译过rockit那么SDK的output目录下其实已经有了打过包的文件。我在第一次测试时偷懒想直接拿SDK里编译出来的so放到板子上用结果发现不同buildroot版本之间的so依赖old symbol导致板端启动rockit时直接抛undefined symbol错误。后来改成手动交叉编译指定了和板端rootfs一致的libc就正常了。这里给一个建议如果你对工具链版本没有把握可以先用SDK自带的toolchain不要自己另搞一套。手动编译时留意CMake缓存里记录的工具链路径如果换编译器前不删掉build目录会出现“build with a different compiler”的警告轻则浪费半小时重编重则产生难以定位的运行崩溃。4. 板端部署与功能验证编译只是第一步真正验证rockit是否可用还得把库和demo部署到RK3568板端跑起来看采集和输出是否通畅。这一章我记录的是整个测试过程中最琐碎也最容易踩坑的部分。4.1 文件部署与环境变量把编译产物拷到板子上我一般放在/oem/usr目录下因为RK3568镜像里这个分区是可读写并且专门给应用层用的。如果直接放/usr/lib后面系统升级可能会被覆盖。scp -r rockit_out/bin useryour-board:/oem/usr/bin/ scp -r rockit_out/lib useryour-board:/oem/usr/lib/登到板端后设置动态库路径export LD_LIBRARY_PATH/oem/usr/lib:$LD_LIBRARY_PATH这里有个细节rockit自身的so可能还依赖板端已有的librga.so、librockchip_mpp.so你要确保这些so也在LD_LIBRARY_PATH里。通常板子出厂固件在/usr/lib或/oem/usr/lib下已经有这些库。可以先用ldd检查rockit so的依赖是否都能找到ldd /oem/usr/lib/librckit.so如果输出里面出现not found就去rootfs里找对应的库软链到统一目录里。千万不要只拷贝一个librckit.so上去运行demo时会死得很难看。4.2 跑通采集demo板端的demo我用的就是samples里最常用的sample_video。它启动时会读取配置文件或参数指定video节点、采集格式、分辨率等。粗看一下用法cd /oem/usr/bin ./sample_video --help不同版本参数不一样有些版本支持-w 1920 -h 1080 -f nv12这种简化参数有些版本则必须改配置文件。我这次使用的版本比较老需要在配置文件里填sensor节点。如果怕麻烦可以直接看源码里sample的main函数把参数写死再编译一次也行。跑起来后如果一切正常会看到类似“stream on ... success”的日志并且风扇转动负荷增加。如果代码链路里面写了保存帧会在指定目录生成yuv文件。你可以把文件拉回PC用ffplay或7yuv看一下图像是否正常。这一步建议先在1080p、NV12的简单格式下验证不要一上来就搞BT1120输出或超高清。先把基础链路打通再去调复杂通路。很多时候问题不是rockit本身而是你选的格式、分辨率、通路划分不对。4.3 与设备树和视频输入节点协同验证rockit运行在用户态但它操作的是内核里的media设备。如果你板子上接的是MIPI sensor比如OV5695就必须确认DTS里sensor的i2c地址、reset GPIO、电源时序都配置正确。Rockit本身无法“帮”你把这些底层东西拉起来。我测试中发现如果DTS里sensor probe失败rockit启动时不会报复杂错误只是在打开video节点时会超时或直接返回“no device”。所以建议在跑rockit之前先在板端用v4l2工具确认底层设备是否枚举成功media-ctl -p v4l2-ctl --list-devices ls /dev/video*如果能看到/dev/video0等节点再用v4l2-ctl抓一帧原始图像。这一步能过说明内核侧采集通路是通的然后再跑rockit。否则你先去查设备树和驱动不要浪费在rockit上。BT1120输出也是类似思路。BT1120属于并口视频输出需要在DTS里配置对应的display controller或video port节点。Rockit可能给你提供数据通路但物理接口的复用、iopad配置都在设备树里。我当时调试时发现BT1120无信号最后查到是pinctrl里对应引脚被默认复用成了gpio改完dts重新编译内核才正常。5. 常见问题与排查技巧实录这段我来整理编译测试过程中最常遇到的几类问题基本都是我踩过或看别人踩过的坑按优先级排一下。5.1 编译阶段报错速查报错信息简写可能原因解决办法rockit/mpp头文件找不到依赖库头文件路径没传给CMake配置CMAKE_INCLUDE_PATH指向deps/includeundefined reference to mpp_xxx链接时没找到librockchip_mpp.so或版本不对检查CMAKE_LIBRARY_PATH重新拷贝依赖库libstdc.so.6: cannot open shared object file工具链版本和板端libstdc不匹配把工具链自带的libstdc.so.6同步到板端CMake Error: generator ... platform ...build目录内有旧缓存删除build目录重新cmakeerror: ‘XXX’ has not been declared代码分支和依赖头文件版本不符更新到同一个SDK发布目录下的rockit/mpp版本cannot find -lstdc交叉工具链没安装C运行时完整安装工具链或手动指定GCC版本遇到链接错误时不要直接改代码。先用gcc -v和echo | aarch64-none-linux-gnu-gcc -E -dM -确认当前使用的交叉编译器再检查CMakeCache.txt里的编译器路径。很多时候你以为用的是新工具链实际CMake缓存里还是旧的。5.2 板端运行的“玄学”问题排查板端运行报错比编译报错更难查因为很多是运行时环境和动态库依赖问题。我总结了一个排查顺序从低到高ldd查看所有so是否完整。缺失so是最常见问题。查看/dev/video*和/dev/media*是否正常枚举。如果设备节点都没有rockit肯定失败。查看内核日志dmesg | grep rkisp、dmesg | grep sensor。这里一般能看到sensor probe是否成功、ISP是否进入stream状态。逐步关闭rockit的功能选项。比如先不加显示、不叠加region只跑采集如果采集还失败就与rockit上层无关。板端时区和日志级别。rockit的日志输出有时默认是error级别你以为没日志其实它可以打印更详细的信息。可以通过环境变量或配置文件打开详细日志多拿点线索。我遇到过最诡异的一个问题是rockit在板子上采集偶发画面卡住。开始怀疑是DMA内存没对齐后来排查发现是内核里面CMA分配不足rockit申请buffer时失败但错误被吞掉了。如果你遇到“看起来没报错但画面不动”优先查一下/proc/meminfo里的CmaFree以及内核日志里有没有CMA alloc fail。这个非常隐蔽。5.3 一个关于BT1120/OV5695等外设的额外提醒得益于搜索热词里一堆“rk3568调试ov5695”“rk3568配置bt1120输出”我能理解很多朋友其实是卡在外部接口调试上而rockit只是其中一个环节。这类问题的共性是不要先怀疑rockit而要先验证底层通路。调试OV5695用i2cdetect确认sensor在I2C总线上是否可见再看中断、复位引脚是否拉对。能读到chip id再谈采集。配置BT1120输出先确认显示控制器那边是否已经把信号切到并口输出再确认输出引脚有没有被其他外设占用。调试其他sensor同理sensor上下电时序是最大坑很多时候dts里reset-gpio初期状态不对sensor没有正常唤醒。这时候rockit怎么配置都是白搭。6. 最后说点我自己的体会弄完这一轮rk3568的rockit编译测试我最大的感受就是这类嵌入式多媒体框架本身并不神秘但它的依赖链太长了。编译一个库容易搞定整条链路的版本匹配不容易。以后再接新项目我会先把deps目录固定下来作为统一依赖基线每次同步代码时也同步一份依赖清单避免同事之间出现“同一个仓库两套编译环境跑出不同结果”的情况。还有一个实用的小技巧不要在板子上直接gcc编译rockitRK3568的CPU性能虽然不弱但编译C大工程还是难受而且板端缺头文件、缺工具链组件折腾成本比交叉编译高得多。如果你实在不想在PC上搭环境可以用Docker拉一个交叉编译容器把工具链和依赖都固化进去体验会好很多。最后如果你的项目里也用到了Rockit并且编译测试时遇到了我这里没提到的新错误建议先从“依赖库版本”和“工具链一致性”入手排查。绝大多数所谓玄学问题最后都会归到这两个根因上。