linux版本
项目源码: https://github.com/litongjava/java-native-media
一、问题现象
应用启动时报错:
os name: linux user.home: /root
Detected FFmpeg ABI: 58
copy from /lib/linux_amd64/libnative_media_av58.so to /root/lib/linux_amd64/libnative_media_av58.so
Resource does not exist: /lib/linux_amd64/libnative_media_av58.so
Loading native: /root/lib/linux_amd64/libnative_media_av58.so
Exception in thread "main" java.lang.reflect.InvocationTargetException
Caused by: java.lang.UnsatisfiedLinkError: Can't load library: /root/lib/linux_amd64/libnative_media_av58.so
二、问题根因
1)应用内置的 native 库依赖 FFmpeg 59
检查 .so 依赖:
readelf -d /root/lib/linux_amd64/libnative_media_av59.so | grep NEEDED
输出:
Shared library: [libavformat.so.59]
Shared library: [libavcodec.so.59]
Shared library: [libswresample.so.4]
Shared library: [libswscale.so.6]
Shared library: [libavutil.so.57]
Shared library: [libavfilter.so.8]
说明该库是 按 FFmpeg 5.x (ABI 59) 编译的
2)服务器实际安装的是 FFmpeg 4.x(ABI 58)
ldconfig -p | grep libavformat
输出:
libavformat.so.58 => /lib/x86_64-linux-gnu/libavformat.so.58
3)ldd 验证动态链接失败
ldd /root/lib/linux_amd64/libnative_media_av59.so
输出:
libavformat.so.59 => not found
libavcodec.so.59 => not found
libswresample.so.4 => not found
libswscale.so.6 => not found
libavutil.so.57 => not found
libavfilter.so.8 => not found
结论
| 项目 | 版本 |
|---|---|
| native 库 | FFmpeg 59 |
| 系统 FFmpeg | FFmpeg 58 |
| 结果 | 动态链接失败 |
因此 JVM 无法加载 .so 文件,触发:
java.lang.UnsatisfiedLinkError
三、java-native-media 的加载机制
库启动时会:
- 检测系统 FFmpeg ABI
- 自动选择对应 native 库
日志:
Detected FFmpeg ABI: 58
Loading native: libnative_media_av58.so
但 jar 包内只带 av59,没有 av58:
Resource does not exist: /lib/linux_amd64/libnative_media_av58.so
因此加载失败。
四、解决方案
有三种方案:
| 方案 | 推荐度 | 说明 |
|---|---|---|
| 升级系统 FFmpeg 到 59 | ⭐⭐ | 影响系统组件 |
| 静态链接 FFmpeg | ⭐⭐ | 编译复杂 |
| 按服务器版本编译 native | ⭐⭐⭐⭐ | 最安全 |
推荐:为服务器编译 av58 版本 native 库
五、编译适配服务器的 native 库
1)准备环境
Ubuntu / Debian:
sudo apt install build-essential -y
sudo apt-get install nasm -y
sudo apt-get install pkg-config -y
sudo apt install cmake -y
sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libswresample-dev libmp3lame-dev -y
确认版本:
ffmpeg -version
应包含:
libavformat 58.x
安装java
mkdir /opt/package/java -p &&cd /opt/package/java
wget https://github.com/litongjava/oracle-jdk/releases/download/8u411/jdk-8u411-linux-x64.tar.gz
wget https://gitcode.com/ppnt/oracle-jdk/releases/download/8u411/jdk-8u411-linux-x64.tar.gz
mkdir /usr/java/ -p
tar -xf jdk-8u411-linux-x64.tar.gz -C /usr/java
export JAVA_HOME=/usr/java/jdk1.8.0_411
export JAVA_HOME=/usr/java/jdk1.8.0_411
2)下载源码
git clone https://github.com/litongjava/native-media.git
cd native-media
3)编译
编译
cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release
cmake --build cmake-build-release --target all
编译成功后生成:
libnative_media.so
六、部署 native 库
创建运行目录:
mkdir -p /root/lib/linux_amd64/
复制编译产物:
cp cmake-build-release/libnative_media.so /root/lib/linux_amd64/libnative_media_av58.so
权限:
chmod 755 /root/lib/linux_amd64/libnative_media_av58.so
七、验证
重新启动服务:
Detected FFmpeg ABI: 58
Loading native: /root/lib/linux_amd64/libnative_media_av58.so
若无 UnsatisfiedLinkError,说明成功。
八、原理总结
为什么不能跨 ABI 使用?
FFmpeg 每个大版本都会改变:
- 结构体大小
- 函数签名
- 枚举值
- 内存布局
例如:
| ABI | FFmpeg |
|---|---|
| 57 | FFmpeg 3 |
| 58 | FFmpeg 4 |
| 59 | FFmpeg 5 |
| 60 | FFmpeg 6 |
native 库是 强绑定 ABI 的
因此:
libnative_media_av59.so ≠ 可在 ffmpeg58 上运行
九、最佳实践建议
生产环境建议:
1)构建阶段与运行环境一致
Docker 构建示例:
构建机 FFmpeg版本 == 服务器 FFmpeg版本
否则必然出现 JNI 加载失败。
2)将 native 编译纳入 CI
建议:
CI 为每种 Linux 发行版编译对应 so
例如:
libnative_media_av58_ubuntu20.so
libnative_media_av59_ubuntu22.so
3)不要依赖 jar 内置 native
原因:
- Linux 发行版 FFmpeg 版本差异极大
- 云服务器经常是旧版 LTS
应改为:
启动时优先加载外部 native
