Linux 编译
本项目使用 FFmpeg(avcodec、avformat、swresample、avutil 等)和 mp3lame 进行音视频处理,同时使用 JNI 与 Java 交互。由于不同操作系统(Linux 与 Windows)的库查找、加载机制不同,本文档提供了两套配置方式,并解释相关原因和注意事项。
1. 安装依赖库
在 Linux 下构建项目前,需要先安装编译工具和相关依赖库。可以使用系统的包管理工具 apt 进行安装。
1.1 基础编译工具
apt install build-essential -y
apt-get install nasm -y
apt-get install pkg-config -y
1.2 安装 FFmpeg 库
项目依赖 FFmpeg 的动态库版本。在 vcpkg 下安装动态库的方式为:
export VCPKG_LIBRARY_LINKAGE=dynamic
vcpkg install ffmpeg[mp3lame]:x64-linux-dynamic --recurse
注意: 由于 Linux 下 vcpkg 有时不支持安装 FFmpeg 动态库,为避免 -fPIC 问题,建议直接使用系统包管理工具安装:
sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libmp3lame-dev -y
1.3 安装 Java 并设置 JAVA_HOME
安装 Java 开发环境后,确保设置好环境变量 JAVA_HOME
:
export JAVA_HOME=/usr/java/jdk1.8.0_411
2. 修改 CMake 配置
为了在 Windows 和 Linux 下均能正确查找 FFmpeg 与 mp3lame 库,可以在 CMakeLists.txt 中增加对操作系统类型的判断。以下是修改后的 CMake 配置示例:
cmake_minimum_required(VERSION 3.15)
project(native_media)
set(CMAKE_CXX_STANDARD 17)
# Find JNI
find_package(JNI REQUIRED)
include_directories(${JNI_INCLUDE_DIRS})
# Find FFmpeg components explicitly
find_path(AVCODEC_INCLUDE_DIR libavcodec/avcodec.h)
find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h)
find_path(SWRESAMPLE_INCLUDE_DIR libswresample/swresample.h)
find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h)
find_library(AVCODEC_LIBRARY avcodec)
find_library(AVFORMAT_LIBRARY avformat)
find_library(SWRESAMPLE_LIBRARY swresample)
find_library(AVUTIL_LIBRARY avutil)
# Print paths for debugging
message(STATUS "AVCODEC_INCLUDE_DIR: ${AVCODEC_INCLUDE_DIR}")
message(STATUS "AVCODEC_LIBRARY: ${AVCODEC_LIBRARY}")
message(STATUS "AVFORMAT_INCLUDE_DIR: ${AVFORMAT_INCLUDE_DIR}")
message(STATUS "AVFORMAT_LIBRARY: ${AVFORMAT_LIBRARY}")
message(STATUS "SWRESAMPLE_INCLUDE_DIR: ${SWRESAMPLE_INCLUDE_DIR}")
message(STATUS "SWRESAMPLE_LIBRARY: ${SWRESAMPLE_LIBRARY}")
message(STATUS "AVUTIL_INCLUDE_DIR: ${AVUTIL_INCLUDE_DIR}")
message(STATUS "AVUTIL_LIBRARY: ${AVUTIL_LIBRARY}")
# 检测操作系统类型
if(WIN32)
message(STATUS "Configuring for Windows")
# Windows 下通过 vcpkg 查找 mp3lame
find_package(mp3lame CONFIG REQUIRED)
else()
message(STATUS "Configuring for Linux/UNIX")
endif()
# Include directories
include_directories(
${AVCODEC_INCLUDE_DIR}
${AVFORMAT_INCLUDE_DIR}
${SWRESAMPLE_INCLUDE_DIR}
${AVUTIL_INCLUDE_DIR}
jni
)
# Add sources
add_library(native_media SHARED src/native_mp3.c src/native_mp4.c)
# Link libraries
target_link_libraries(native_media
${JNI_LIBRARIES}
${AVCODEC_LIBRARY}
${AVFORMAT_LIBRARY}
${SWRESAMPLE_LIBRARY}
${AVUTIL_LIBRARY})
if(WIN32)
target_link_libraries(native_media mp3lame::mp3lame)
else() # UNIX / Linux
target_link_libraries(native_media mp3lame)
endif()
# Add test executable
add_executable(mp4_test src/mp4_test.c src/native_mp4.c)
target_link_libraries(mp4_test
${AVCODEC_LIBRARY}
${AVFORMAT_LIBRARY}
${SWRESAMPLE_LIBRARY}
${AVUTIL_LIBRARY}
)
2.1 说明
JNI 查找与包含: 使用
find_package(JNI REQUIRED)
自动查找系统中已安装的 JDK,并包含必要的头文件目录。FFmpeg 组件查找: 使用
find_path
与find_library
分别查找头文件和库文件路径。Linux 下系统包管理工具安装的库会被自动检测到。平台判断:
- Windows 下,通过 vcpkg 查找并链接 mp3lame 的配置包。
- Linux 下,直接链接系统中通过 apt 安装的动态库。
调试信息: 利用
message(STATUS ...)
输出各库路径,方便调试和确认是否正确找到依赖。
3. 构建项目
在 Linux 上使用 CMake 分为配置生成和构建两个阶段。
3.1 配置生成阶段
在项目根目录下执行以下命令生成构建系统:
默认构建(Debug 模式):
cmake -S . -B cmake-build-debug
Release 模式:
cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release
配置过程中会检测 C 编译器、JNI、FFmpeg 组件等信息,并在输出中显示各依赖库的路径,如下所示:
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
...
-- Found JNI: /usr/java/jdk1.8.0_411/include found components: AWT JVM
-- AVCODEC_INCLUDE_DIR: /usr/include/x86_64-linux-gnu
-- AVCODEC_LIBRARY: /usr/lib/x86_64-linux-gnu/libavcodec.so
-- AVFORMAT_INCLUDE_DIR: /usr/include/x86_64-linux-gnu
-- AVFORMAT_LIBRARY: /usr/lib/x86_64-linux-gnu/libavformat.so
...
-- Configuring for Linux/UNIX
-- Configuring done
-- Generating done
-- Build files have been written to: /root/code/native-media/cmake-build-release
3.2 构建阶段
进入生成的构建目录后,执行以下命令进行编译:
cmake --build cmake-build-debug --target all
或
cmake --build cmake-build-release --target all
这将编译项目并生成共享库(.so 文件)和测试可执行文件。
4. 常见错误与解决方法
4.1 -fPIC 问题
在链接共享库时,可能会出现如下错误:
can not be used when making a shared object; recompile with -fPIC
该错误原因是:你正在将未使用 -fPIC
编译的静态 FFmpeg 库链接进共享库,而在 Linux 下,嵌入共享库的所有代码都必须使用 -fPIC
编译。
解决方法
使用共享(动态)版 FFmpeg 库
确保使用动态库版本进行链接,如使用系统包管理工具安装或通过 vcpkg 指定动态链接(参见前文)。重新编译静态库时启用 -fPIC
如果必须使用静态库,则需要重新编译时加入-fPIC
(例如:在 CMake 配置时添加-DCMAKE_POSITION_INDEPENDENT_CODE=ON
)。使用系统库
直接使用 apt 安装系统动态库,避免手动编译的静态库问题。
5. 部署注意事项
5.1 系统依赖
如果项目通过动态链接依赖系统共享库(例如 apt 安装的 FFmpeg 库),在目标系统上运行时也必须安装这些依赖库,否则程序会因找不到共享库而无法启动。常见解决方案包括:
- 要求目标系统安装依赖: 在部署文档中说明需要安装相应版本的库。
- 静态链接: 将依赖库编译进最终可执行文件,但文件体积会较大且部分库可能不支持。
- 打包共享库: 将所需共享库一同打包,并通过设置
LD_LIBRARY_PATH
或在链接时嵌入 rpath 指定查找路径。
5.2 共享库查找机制
在 Windows 上,部署 DLL 文件通常只需将其复制到 PATH 中。而在 Linux 上,动态链接器的查找机制与 Windows 不同,主要参考:
- 系统默认目录(如
/lib
、/usr/lib
、/usr/local/lib
)。 - 环境变量
LD_LIBRARY_PATH
中指定的路径。 - 可执行文件中嵌入的 rpath 信息。
因此,将 .so 文件复制到系统目录或通过环境变量指定路径是确保应用正常加载依赖库的关键步骤。
6. Java 中动态库加载
在 Windows 下,很多情况下需要在 Java 中手动加载每个依赖的 DLL 文件,如:
System.load(new File("lib", "avutil-59.dll").getAbsolutePath());
System.load(new File("lib", "swresample-5.dll").getAbsolutePath());
System.load(new File("lib", "avcodec-61.dll").getAbsolutePath());
System.load(new File("lib", "avformat-61.dll").getAbsolutePath());
6.1 在 Linux 下的区别
文件命名: Linux 下动态库通常以
.so
结尾,并带有lib
前缀。例如:libavutil.so.59
libswresample.so.5
libavcodec.so.61
libavformat.so.61
加载方式: 如果动态库已经安装在系统默认的库目录中,并且在编译时已正确链接,Linux 动态链接器会自动加载所有依赖项。你只需在 Java 中加载主 JNI 库即可:
System.loadLibrary("native_media");
而无需手动调用
System.load
加载每个依赖库。
6.2 为什么不需要手动加载所有库
Linux 的动态链接器在加载共享库时,会自动解析并加载该库所依赖的其他共享库(前提是这些库能在默认路径中找到或通过 LD_LIBRARY_PATH
指定)。因此,只要你将主要的 JNI 库(例如 native_media.so)加载,系统就会自动查找并加载所依赖的 FFmpeg 库,而不必在 Java 中额外调用 System.loadLibrary
。
7. 总结
- 依赖安装: 在 Linux 下推荐使用系统包管理工具安装 FFmpeg 动态库和其他依赖,避免 -fPIC 问题。
- CMake 配置: 通过平台判断分别配置 Windows 与 Linux,确保在不同平台下都能正确查找和链接库文件。
- 构建过程: 分为配置生成和构建两个阶段,注意在不同构建模式下的设置。
- 部署与加载: 在 Linux 上部署时需确保动态库能被动态链接器找到(通过系统目录、LD_LIBRARY_PATH 或 rpath)。同时,Java 只需加载主 JNI 库,依赖库由系统自动加载。
通过以上步骤和解释,可以帮助开发者在跨平台开发时正确配置与部署项目,确保应用在各系统环境下都能稳定运行。