前几天我们的视频处理服务在部署到Linux服务器后,启动阶段反复挂在ffmpeg初始化上,堆栈里明晃晃的一行:
java.lang.UnsatisfiedLinkError: org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_GBRAP14()I at org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_GBRAP14(Native Method) at org.bytedeco.ffmpeg.global.avutil$AVPixelFormat.<clinit>(avutil.java:1680)第一反应是"native库没打进去",但真去排查才发现,这个报错牵扯出了JavaCPP加载机制、bytedeco版本矩阵、平台jar架构匹配、Spring Boot打包方式等一系列细节,远不是补一个依赖就能解决的。这篇文章把我这次的完整排查过程、根因定位和三种修复方案都记录下来,给所有准备在Java项目里集成ffmpeg的朋友做个参考。
1. 这个报错长什么样:一次典型的ffmpeg初始化翻车现场
1.1 报错出现的具体场景
我们是一个基于Spring Boot的媒体文件处理模块,用bytedeco的JavaCV封装做视频缩略图提取和转码。开发期在Windows机器上一切正常,代码一打包发到某台Linux服务器上,服务启动没几秒就抛异常,直接导致ApplicationContext加载失败,进程退出。
当时的启动命令和所有常规配置看起来都没问题:
java -jar media-processor.jar --spring.profiles.active=prod崩溃点定位在org.bytedeco.ffmpeg.global.avutil类的静态初始化块里。堆栈里的关键信息是:
java.lang.UnsatisfiedLinkError: org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_GBRAP14()I这里有个容易看走眼的地方:AV_PIX_FMT_GBRAP14不是一个普通Java方法,而是一个native方法。方法签名()I表示无参数、返回int。JavaCPP把C语言的AVPixelFormat枚举生成成了Java类,每个枚举常量对应一个native方法,用来从底层库读取该常量的数值。AV_PIX_FMT_GBRAP14是ffmpeg里的一种像素格式,对应拜耳阵列的14位GBR排列。
所以这条报错的本质是:JVM试图调用一个声明在avutil类中的native方法,结果在底层动态库里的找不到对应符号,直接抛了UnsatisfiedLinkError。
1.2 报错堆栈的关键信息解读
很多同事第一次看到这种报错会怀疑是代码写错了,其实UnsatisfiedLinkError属于LinkageError,跟业务代码逻辑无关,它指向的是Java层与native层之间的链接断裂。
拆开看堆栈里的三个关键点:
Native Method标识:说明这个方法的实现不在Java代码里,要跑到某个.so或.dll里去找。- 类名
org.bytedeco.ffmpeg.global.avutil:说明这是JavaCPP根据ffmpeg的C头文件自动生成的绑定类,不是我们手动写的JNI。 - 方法名
AV_PIX_FMT_GBRAP14():这是生成器从libavutil/pixfmt.h里的AVPixelFormat枚举解析出来的常量读取方法。
如果把这个报错类比成日常场景,就像你去配电箱找墙上的插座,Java端举着一个三脚插头,结果底下的供电系统只提供了两脚插座。插头脚位对不上,自然插不进去,于是整个系统停电。
在我们这次情况里,表面上是一个像素枚举常量找不到,实际暴露的问题是:**Java层的绑定类和一个版本的native库链接,但运行时真正加载的却是另一个版本的native库。**两者对不上,第一个碰到的符号就成了引爆点。
2. UnsatisfiedLinkError不是代码问题,是native库加载链路问题
2.1 JNI链接失败的底层原因
要彻底理解这个报错,得先回顾一下JNI的运作方式。一个Java类里写了public static native int xxx();,编译后只是一个声明。JVM第一次执行到这个方法时,会通过System.loadLibrary或System.load加载对应的动态库,然后用符号解析机制去库里找这个方法的实现函数。
JNIEXPORT的导出符号有一套约定名称,例如JavaCPP生成的函数名类似:
Java_org_bytedeco_ffmpeg_global_avutil_AV_1PIX_1FMT_1GBRAP14如果动态库文件存在,但这个符号没有导出,或者库文件本身是另一个版本没有这个常量,JVM就会抛UnsatisfiedLinkError。
常见触发原因有四类:
- 动态库根本不存在:路径写错,或缺失平台对应的classifier jar。
- 动态库存在但版本不对:Java绑定类来自新版本,底层库是旧版本,新枚举符号不存在。
- 动态库架构不匹配:x86_64的机器加载了arm64的库,或反过来,导致库无法链接。
- 动态库本身能加载但依赖残缺:比如
libavutil.so.58依赖了libva.so,而系统里没有这个基础库。
2.2 bytedeco ffmpeg加载native库的机制
bytedeco的加载机制和普通JNI略有不同,它多了一层自己的Loader调度器。JavaCV在使用时,实际上依赖三个层面的工作:
org.bytedeco:javac:提供JNI加载器的核心逻辑。org.bytedeco:ffmpeg:包含Java绑定类的主jar包。org.bytedeco:ffmpeg-platform或各自的classifier jar:包含真正编译好的.so/.dll/.dylib文件。
运行时,Loader.load(avutil.class)会做这样几件事:
- 根据
os.name和os.arch判断当前平台,比如Linux amd64对应linux-x86_64。 - 在classpath中寻找artifact名为
ffmpeg-<version>-linux-x86_64的jar包。 - 把jar包里的
libavutil.so等文件解压到临时目录。 - 调用
System.load加载这些动态库。 - 完成Java绑定类中所有native方法的符号绑定。
这中间任何一个环节出问题,最终表现都可能是UnsatisfiedLinkError,但具体根因可能完全不同。这也是为什么这类报错排查起来容易迷路——你看到的结果一样,背后的链路危机不一样。
在我们的开发机上,Maven根据Windows系统自动下载了windows-x86_64的classifier jar,一切正常;打包时如果Maven没有把linux-x86_64的jar带进构建产物,或者服务器上Maven仓库没有这个jar,就会走到"库不存在"的坑里。
所以遇到这个报错,第一步不是改代码,而是搞清楚JavaCPP的加载链路在哪一环断了。
3. 排查过程复盘:从版本对齐到平台依赖的逐个击破
3.1 先查依赖版本矩阵
我这次的第一步是用Maven的依赖树把bytedeco相关的jar全部拉出来看:
mvn dependency:tree -Dincludes=org.bytedeco结果出来之后,问题立刻有了一些眉目。依赖树里出现了两个版本的ffmpeg:
org.bytedeco:ffmpeg:4.4.2-1.5.7 org.bytedeco:ffmpeg:5.1.3-1.5.8原因是项目里某个模块手动引入了org.bytedeco:ffmpeg的高版本,而父级BOM又在统一管理javacv-platform的1.5.7版本。Maven仲裁最终保留了ffmpeg 5.1.3的Java绑定类,但实际加载的native库来自1.5.7版本族,两者不是同一个适配版本。
这里需要强调一个关键概念:bytedeco的ffmpeg artifact版本号是双段式的,例如4.4.2-1.5.7:
- 前段
4.4.2是ffmpeg源码版本。 - 后段
1.5.7是JavaCPP适配器版本,或者叫javacv版本族。
即便两段中的前段相同,只要后段的适配器版本不一致,生成出的Java绑定类也可能存在差异。因为适配器会随JavaCPP版本调整枚举生成方式、方法签名,甚至常量值。版本不一致时,Java绑定类里声明的native方法符号和底层库导出的符号对不上,第一个报错往往就是某个枚举常量或方法。
我这次遇到的就是Java绑定类来自ffmpeg 5.1.3-1.5.8,但运行时实际解压加载的libavutil.so来自4.4.2-1.5.7版本族。5.x的AV_PIX_FMT_GBRAP14即便在4.4.2的C库管理员视角里是一个新加的值,底层库里没有对应符号,立刻爆炸。
版本对齐表在这方面非常有用。以几个常见组合为例:
| JavaCV版本 | 配套ffmpeg artifact版本 |
|---|---|
| 1.5.6 | 4.4.1-1.5.6 |
| 1.5.7 | 4.4.2-1.5.7 |
| 1.5.8 | 4.4.2-1.5.8 |
注意这里的"配套版本"以后缀为准,只要javacv、javacpp、ffmpeg三者后缀一致,兼容性基本有保障。具体每个JavaCV版本对应的ffmpeg大版本,以官方GitHub Release说明为准。
3.2 再查jar包内部的平台映射
版本对齐是第一步,第二步必须确认平台相关的native jar有没有真正进入classpath。
Maven里常见的错误做法是只引入了主jar,没有引入platform jar,或没引入任何带classifier的jar:
mvn dependency:tree | grep ffmpeg如果看到只有ffmpeg-4.4.2-1.5.7.jar,而没有ffmpeg-4.4.2-1.5.7-linux-x86_64.jar,那native库缺失就是必然结果。
可以进一步用jar命令检查jar包内容:
jar tf ~/.m2/repository/org/bytedeco/ffmpeg/ffmpeg-4.4.2-1.5.7/ffmpeg-4.4.2-1.5.7.jar | grep -i "linux\|windows"正常情况下,主jar包内的路径只会包含META-INF和Java class文件。所有的.so文件都在带classifier的jar里。比如ffmpeg-4.4.2-1.5.7-linux-x86_64.jar里才能看到:
org/bytedeco/ffmpeg/linux-x86_64/libavutil.so org/bytedeco/ffmpeg/linux-x86_64/libavcodec.so org/bytedeco/ffmpeg/linux-x86_64/libavformat.so如果系统里装的是ARM服务器,classifier就应该是linux-arm64,而不是linux-x86_64。
另外,Java的os.arch在64位Linux下返回的是amd64或x86_64,取决于JVM实现和所在平台,Maven的classifier规范里对应的是x86_64。如果自己在代码里用System.getProperty("os.arch")做判断,不要直接拿输出值去拼classifier名称,容易踩外壳壳不匹配。
3.3 最后确认JVM加载路径
版本和平台都排查完,如果还没头绪,就要看JVM实际尝试加载的是哪个路径下的哪个文件。
JavaCPP提供了调试开关,启动时加上:
-Dorg.bytedeco.javacpp.logger.debug=true可以打开详细日志,看到Loader在尝试加载什么:
[INFO] Loader: Could not load classes from /home/xxx/.javacpp/cache/ffmpeg-4.4.2-1.5.7-linux-x86_64.jar [INFO] Loader: Loaded /tmp/javacpp.../libavutil.so from /home/xxx/.m2/repository/org/bytedeco/ffmpeg/...如果这一步能看到明确的Could not load,那就说明native jar已经选到了,但库文件加载失败。此时再用Linux下的工具检查文件属性:
file /tmp/javacpp*/libavutil.so ldd /tmp/javacpp*/libavutil.sofile可以确认ELF 64-bit还是32-bit、架构是不是x86-64。ldd可以看到它依赖的其他系统库是否齐全。比如某些精简版服务器镜像里缺少libxcb.so.1、libasound.so.2这类音视频依赖库,即使ffmpeg的jar没问题,底层库加载时也会失败,最终体现为UnsatisfiedLinkError或NoClassDefFoundError。
如果这些都没问题,那最后再确认classpath里是否存在多个版本的ffmpeg jar同时存在。我见过有些项目把ffmpeg-4.4.2-1.5.7-linux-x86_64.jar和ffmpeg-5.1.3-1.5.8-linux-x86_64.jar都打进了fat jar,临时目录解压时后解压的库覆盖了先解压的库,惨案直接发生。
4. 问题根因:本地native库与Java层符号表断裂
4.1 javacpp-parser生成的符号索引机制
搞清楚这个报错的根因,必须理解JavaCPP的代码生成机制。
JavaCPP在构建时会解析ffmpeg的C/C++头文件,比如libavutil/pixfmt.h。它会读取enum AVPixelFormat里的所有枚举项,然后为每一项生成一个native方法,用于在运行时向C库查询该常量的真实数值。AV_PIX_FMT_GBRAP14就是这样被生成的。
这种设计有个隐藏约束:**native方法集合必须和C库的枚举值集合一一对应。**一旦Java绑定类和C库来自不同版本,就会出三种情况:
- Java绑定类里声明了C库里不存在的枚举方法,调用时直接
UnsatisfiedLinkError。 - C库有但Java绑定类没有,不影响加载,但也用不到。
- 枚举顺序或数值发生变化,静默拿到错误常量值,表现成视频花屏或格式判断异常。
AV_PIX_FMT_GBRAP14正好属于第一种。我们Java端的绑定类是新的,而运行时挂载的libavutil.so是旧的,旧库里压根没有导出这个符号,JVM在解析时自然找不到。
有人会问,JavaCPP为什么不提前校验版本?其实它有版本校验逻辑,但当不同jar混在classpath里时,校验规则会被绕过去,或者校验失败的异常被吞掉,最后还是撞到符号解析这堵墙上。
4.2 常见触发场景:Spring Boot打包、多模块依赖、架构差异
在实际项目中,这种"符号表断裂"常见于四类场景。
第一类是Spring Boot fat jar打包。Spring Boot会把所有依赖jar嵌套到BOOT-INF/lib里,JavaCPP的Loader能识别这种布局,并自动解压native库到临时目录。但如果项目里用了自定义ClassLoader,比如某些热部署框架或者插件系统,Loader在扫描classpath时找不到BOOT-INF/lib里的classifier jar,native库就不会被解压,最终报错。
第二类是多模块Maven工程的传递依赖冲突。模块A引入javacv-platform,模块B引入ffmpeg-platform,两个模块的版本由不同BOM管理,传递到最终的打包模块时,Maven仲裁只保留一个版本,但Java绑定类和native jar可能来自两个不同版本族,符号对不上。
第三类是开发平台与部署平台不一致。开发机是Windows,构建机是Linux,服务器又是ARM架构。Maven在解析ffmpeg-platform时按当前构建机的系统下载classifier依赖,如果打包方式不对,产出的jar里可能只有构建机平台的native库,或者完全没有带classifier的依赖。
第四类是手动指定了错误的classifier。有同事图省事,在pom里直接写了:
<dependency> <groupId>org.bytedeco</groupId> <artifactId>ffmpeg</artifactId> <version>4.4.2-1.5.7</version> <classifier>linux-x86_64</classifier> </dependency>结果这份依赖被带到另一个ARM环境的服务里,启动时加载linux-x86_64的库,遇到libavutil.so: cannot open shared object file或者架构不匹配的错误。如果架构错误被某个旧版本库掩盖,表面症状仍然是UnsatisfiedLinkError。
4.3 我遇到的实际根因
回到我们这次的问题。最终定位到的根因是:父级BOM统一管理了javacv-platform 1.5.7,但某个子模块单独引入了org.bytedeco:ffmpeg:5.1.3-1.5.8,Maven仲裁后Java绑定类来自5.1.3,而native库依赖来自1.5.7版本族。
Spring Boot打包时,classpath里存在:
ffmpeg-5.1.3-1.5.8.jar ffmpeg-4.4.2-1.5.7-linux-x86_64.jarJavaCPP加载流程中先根据系统匹配到了4.4.2-1.5.7-linux-x86_64作为native库来源,解压出libavutil.so。然而org.bytedeco.ffmpeg.global.avutil这个Java类是从5.1.3-1.5.8.jar里加载进来的。新类声明了AV_PIX_FMT_GBRAP14这个native方法,老库4.4.2里没有对应符号,于是初始化这个枚举类时,第一次调用就抛了UnsatisfiedLinkError。
按说Maven仲裁本来应该让两个版本冲突直接编译失败,但因为子模块是在后期合并进工程的,父BOM和子模块的依赖管理范围不同,最终产物里同时混入了两个版本族的jar,这种隐秘冲突在依赖树里不仔细看根本发现不了。
搞清楚这个根因之后,修复思路就非常清晰了。
5. 三个可行的修复方案与验证过程
5.1 方案一:精确对齐bytedeco版本族
最推荐的方式是放弃手动管理ffmpeg依赖,直接依赖ffmpeg-platform,并让版本后缀和javacv版本保持一致。
在Maven里可以这样组织:
<properties> <javacv.version>1.5.7</javacv.version> <ffmpeg.platform.version>4.4.2-1.5.7</ffmpeg.platform.version> </properties> <dependencies> <dependency> <groupId>org.bytedeco</groupId> <artifactId>ffmpeg-platform</artifactId> <version>${ffmpeg.platform.version}</version> </dependency> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv</artifactId> <version>${javacv.version}</version> </dependency> </dependencies>ffmpeg-platform这个artifact的特殊之处在于它不直接包含native库,而是通过pom文件按当前平台自动引入对应的classifier依赖。在Windows上构建时它引入ffmpeg-4.4.2-1.5.7-windows-x86_64.jar,在Linux x86_64上构建时引入ffmpeg-4.4.2-1.5.7-linux-x86_64.jar,省去了手动维护classifier的麻烦。
为什么建议用ffmpeg-platform而不是javacv-platform?因为javacv-platform会把OpenCV、OpenBLAS、Leptonica等一整套视觉库全带进来,体积动辄几百MB。如果项目只需要ffmpeg做音视频解析,ffmpeg-platform更轻量,也更不容易触发其他库的版本冲突。
5.2 方案二:在pom中显式声明平台相关依赖
如果项目对产物体积极其敏感,或者需要精确控制最终打入的native库,可以走手动classifier路线。但这里强烈建议用Maven profile按平台自动激活,而不是硬编码一个classifier:
<profiles> <profile> <id>linux-x86_64</id> <activation> <os> <family>unix</family> <arch>amd64</arch> </os> </activation> <dependencies> <dependency> <groupId>org.bytedeco</groupId> <artifactId>ffmpeg</artifactId> <version>4.4.2-1.5.7</version> <classifier>linux-x86_64</classifier> </dependency> </dependencies> </profile> <profile> <id>linux-arm64</id> <activation> <os> <family>unix</family> <arch>aarch64</arch> </os> </activation> <dependencies> <dependency> <groupId>org.bytedeco</groupId> <artifactId>ffmpeg</artifactId> <version>4.4.2-1.5.7</version> <classifier>linux-arm64</classifier> </dependency> </dependencies> </profile> </profiles>主jar包和平台jar分开的好处是,Linux环境部署时可以少带Windows的native库;坏处是每个平台都要维护一个profile。如果团队不大、服务器架构固定,手动classifier也够用。但只要有混合架构部署需求,还是建议方案一。
5.3 方案三:通过JVM参数加载指定路径的native库
如果不想依赖Maven仓库里的classifier jar,而想使用我们自己编译或系统包管理器安装的ffmpeg动态库,可以复用JavaCPP提供的自定义加载路径。
启动时通过设置系统属性指定native库搜索路径:
-Dorg.bytedeco.javacpp.loadPath=/opt/ffmpeg/nativeJavaCPP的Loader在解析时会优先往loadPath指定的目录找libavutil.so、libavcodec.so等文件。这样能绕过classpath内jar解压的流程,直接加载外部库。
前提是外部库版本必须和Java绑定类严格对应。比如Java绑定类是5.1.3-1.5.8版本族的,就要求/opt/ffmpeg/native下的库也是这个版本,否则一样撞符号。
我们有一次是在内网环境,没法从Maven仓库拉取classifier jar,就把当时线上服务器配套的so文件全部拷贝到指定目录,配合这个启动参数完成了修复。这种方法适合离线部署、自定义编译ffmpeg的场景,灵活度最高,但对版本管理要求也最严格。
5.4 修复后的验证方法
修复完不能只看服务能起来就完事,我的习惯是加一段"自检代码"来确认native库真的被正确加载到了:
public class FfmpegInitCheck { public static void main(String[] args) { String nativeLibPath = org.bytedeco.javacpp.Loader.load( org.bytedeco.ffmpeg.global.avutil.class ); System.out.println("avutil native lib path: " + nativeLibPath); System.out.println("GBRAP14 = " + org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_GBRAP14()); } }如果这一小段代码能正常打印出像素常量值,说明:
- Java绑定类加载正确。
- native库路径正确。
- 符号表匹配,
AV_PIX_FMT_GBRAP14能正常链接。
再进一步,用真正的ffmpeg转码链路做一次冒烟测试。比如用FFmpegFrameGrabber打开一个样片视频,抓取第一帧转为BufferedImage,确认avformat、avcodec、avutil、swscale这几条native链路都通。
如果服务是Spring Boot,还可以在启动位加一个ApplicationRunner做初始化检查,启动失败直接Fail Fast,避免服务以为起来了结果一调用就崩。
6. 从这次报错提炼的ffmpeg集成避坑清单
6.1 版本不匹配是最常见的元凶
凡是bytedeco相关的UnsatisfiedLinkError,第一优先级永远是查版本矩阵。
javacv、javacpp、ffmpeg、opencv这些库虽然由同一个组织维护,但它们是独立发布的。javacv的版本决定整体API,javacpp的版本决定JNI生成器,ffmpeg的版本决定native库内容。三者只要后缀版本对不上,就有符号表断裂风险。
我个人的检查习惯是:
- 看
javacv.version是什么。 - 看
ffmpeg的artifactId后缀,比如4.4.2-1.5.7,后一段必须和javacv.version一致。 - 看
javacpp的版本,如果被其他依赖引入,也要确认它和上述版本配套。
如果用的是BOM统一管理,尽量让子模块不要单独覆盖ffmpeg版本。经验是,一个项目里JavaCV全家桶只保留一个版本来源,其他模块通过parent继承,避免Maven仲裁把不同版本族的jar同时带进最终产物。
6.2 无网络环境的离线部署要特别注意
离线部署遇到UnsatisfiedLinkError的概率比在线高很多。原因很简单:Maven在打包机上可能只缓存了当前打包系统的classifier jar,比如Windows的windows-x86_64;到了Linux服务器的内网环境,Maven仓库里根本没有linux-x86_64这个jar,JavaCPP在服务器上初始化时找不到本地库,立刻报错。
应对办法有三个:
- 在构建机上执行
mvn dependency:go-offline,把依赖完整拉取到本地仓库,确保所有平台的classifier jar都已缓存。 - 直接用
ffmpeg-platform依赖,它会在pom层面引入所有平台的jar,但这样会把多个平台的native jar都带进制品,体积增大。 - 在运维侧搭好内网Maven私服,并提前同步好bytedeco的所有平台包。
我见过一个项目在离线环境反复遇到同一个报错,查了三天才发现内网仓库里只有Windows的classifier jar。换到ffmpeg-platform全量依赖后,配合内网仓库同步,问题立即消失。
6.3 架构混用的坑:linux-x86_64 vs linux-aarch64
容器化和国产化服务器普及之后,ARM架构的部署环境越来越常见。开发环境、测试环境、生产环境的CPU架构往往不一致,这就特别容易踩到classifier架构混用的坑。
举个例子:在x86_64的构建机上,Maven自动下载了ffmpeg-4.4.2-1.5.7-linux-x86_64.jar,打包进容器镜像后部署到了ARM机器。容器里的JVM判断出os.arch=aarch64,尝试加载arm64的native库,但classpath里只有x86_64的jar。此时会有两种表现:
- 容器直接报
libavutil.so: cannot open shared object file,属于库找不到。 - 镜像里碰巧打了多架构jar,加载到了错误架构的库,报
wrong ELF class或UnsatisfiedLinkError。
处理办法是在CI流程里按目标架构构建镜像,或者使用ffmpeg-platform并明确最终运行平台的profile。最忌讳的是手动把某个架构的classifier硬编码进pom,然后把jar复制到另一个架构的机器上。
6.4 遇到UnsatisfiedLinkError的通用排查顺序
这次排查之后,我给自己总结了一套固定的排查顺序,遇到同类报错直接照做:
- 确认报错的native方法归属于哪个库。看类名里的包路径,比如
org.bytedeco.ffmpeg.global.avutil对应libavutil.so,avcodec对应libavcodec.so,先锁定目标。 - 查依赖树。用
mvn dependency:tree -Dincludes=org.bytedeco看是否存在多个版本族,重点看ffmpeg的artifact后缀和javacv版本是否配对。 - 检查classpath中的classifier jar。用
jar tf看平台jar是否包含目标.so文件,文件名里的平台标识是否和运行机器一致。 - 开启JavaCPP debug日志。加
-Dorg.bytedeco.javacpp.logger.debug=true,看Loader实际扫描到了哪些jar、解压到了哪个路径、加载了哪个文件。 - 检查so文件的架构和依赖。用
file和ldd确认ELF位数、CPU架构、依赖系统库是否齐全。 - 最小化复现。写一个只加载
avutil.class并调用目标native方法的独立类,排除Spring Boot、业务代码、动态代理等干扰因素。
这六步走完,90%的UnsatisfiedLinkError都能在半小时内定位。剩下的10%,基本是加载顺序和多个native库之间的符号依赖问题,需要进一步看ldd的依赖树和LD_LIBRARY_PATH环境变量。
处理完这次ffmpeg初始化报错之后,我在所有涉及到JavaCV的模块里都加了一个启动自检,专门打印Loader.load返回的native库路径。后来再遇到类似的UnsatisfiedLinkError,基本几分钟就能判断出是哪一层出了问题。
调试JavaCV的native层问题,核心还是把"Java绑定类"和"底层动态库"当成两个独立产物来对待。它们就像一对插头和插座,任何一个变了,另一个不换,早晚要冒火花。版本、平台、路径这三件事先对齐,再去看代码,才能真正少走弯路。