正如标题所解释的,我正在尝试将 FFMPEG 与我的可执行应用程序捆绑在一起。
但是,我似乎无法弄清楚 FFMPEG 共享库的运行时加载。我不确定什么是不正确的:
RPATH/RUNPATH
主要项目和 FFMPEG 库。 这是一个 CMake 项目,希望最终找到“标准解决方案”,我将包含重现此问题所需的所有内容。
CMakeLists.txt:
cmake_minimum_required(VERSION 3.21) # Required for the install(RUNTIME_DEPENDENCY_SET...) subcommand
project(cmake-demo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Setup CMAKE_PREFIX_PATH for `find_package`
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/vendor/install)
find_package(PkgConfig REQUIRED)
pkg_check_modules(ffmpeg REQUIRED IMPORTED_TARGET libavdevice libavfilter libavformat libavcodec
libswresample libswscale libavutil)
# Setup variable representing the vendor install directory
set(VENDOR_PATH ${CMAKE_CURRENT_LIST_DIR}/vendor/install)
# Set RPATH to the dependency install tree
set(CMAKE_INSTALL_RPATH $ORIGIN/../deps)
# Add our executable target
add_executable(demo main.cpp)
target_include_directories(demo PRIVATE ${VENDOR_PATH}/include)
target_link_libraries(demo PRIVATE PkgConfig::ffmpeg)
include(GNUInstallDirs)
install(TARGETS demo RUNTIME_DEPENDENCY_SET runtime_deps)
install(RUNTIME_DEPENDENCY_SET runtime_deps
DESTINATION ${CMAKE_INSTALL_PREFIX}/deps
# DIRECTORIES ${VENDOR_PATH}/lib
POST_EXCLUDE_REGEXES "^/lib" "^/usr" "^/bin")
主.cpp:extern "C"{
#include <libavcodec/avcodec.h>
}
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "FFMPEG AVCODEC VERSION: " << avcodec_version() << std::endl;
return 0;
}
指示:设置 FFMPEG(这可能需要一段时间......):
git clone https://github.com/FFmpeg/FFmpeg.git --recurse-submodules --shallow-submodules vendor/src/ffmpeg
git checkout release/5.0
export INSTALL_PATH=$PWD/vendor/install
cd vendor/src/ffmpeg
./configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie
# WITH RPATH (HARDCODED TO VENDOR install directory)
# ./configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldflags=-Wl,-rpath,$INSTALL_PATH/lib --extra-ldexeflags=-pie
make -j16 V=1
make install
cd ../../..
CMake 命令:cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install
cmake --build build -j16 #-v
cmake --install build
步骤之间的清理:# Run from top-level dir of project
rm -rf build install
rm -rf vendor/install # ONLY if you need to modify the FFMPEG step.
调查:RPATH/RUNPATH
可执行文件的 $ORIGIN/../deps
FFMPEG 库上没有 RPATH。 libavutil.so
无法在 install(RUNTIME_DEPENDENCY_SET...)
中解决功能。这确实有意义,因为生成的 cmake_install.cmake
build
中的脚本目录正在将演示可执行文件移动到安装目录并执行file(RPATH_CHANGE...)
在执行运行时依赖集分析之前对可执行文件进行分析。根据 CMake 文档 here并使用 readelf -d demo
RUNPATH
可执行文件是$ORIGIN/../deps
并且只有文档中的案例#1 适用于此,因此根本找不到该库。 DIRECTORIES
install(RUNTIME_DEPENDENCY_SET...)
的参数可用作搜索路径的函数。组合 1 中的设置时,在 CMakeLists.txt
中取消注释并重新运行所有 CMake 命令,安装成功(尽管如文档所述,CMake 确实会为使用 DIRECTORIES
找到的所有依赖项发出警告)。但是,运行可执行文件(即 ./install/bin/demo
)不成功,因为运行时加载程序无法找到 libswresample.so
.进一步调试,我运行了 export LD_DEBUG=libs
并尝试重新启动可执行文件。这表明运行时加载程序正在查找 libavcodec.so
使用 $ORIGIN/../deps
运行路径来自 demo
.然而,当搜索传递依赖(即 libavcodec.so
的依赖于 libswresample.so
)时,RUNPATH
的 demo
不搜索,而是加载程序回退到系统路径并且找不到库。 DIRECTORIES
,我能够满足 CMake 和加载器的要求。与 CMake install(RUNTIME_DEPENDENCY_SET...)
的参数子命令。 CMake 运行时没有任何警告,并且可执行文件按预期加载/运行。但是,这不是一个可行的解决方案,因为在检查 install/deps
中的库时带有 ldd
的文件夹, libswresample.so
和其他图书馆指向 vendor/install
部署时不会出现在包中的目录。 有谁知道标准方法是什么或我上面缺少什么?我对任何和所有建议/工具持开放态度,但我这样做是为了了解什么是“标准做法”,并希望将其扩展到跨平台解决方案(即 Windows/macOS)。因此,我想避免弄乱系统级加载程序配置(
ldconfig
)。谢谢!
最佳答案
重现问题
为了尝试重现您的问题,我实际上丢弃了所有 CMake 内容并编译了 main.cpp
从命令行。我想你会同意问题的根源在于 ffmpeg
build 。
g++ -I vendor/install/include -c -o main.o main.cpp
g++ -L vendor/install/lib -o main main.o -lavcodec
输出如下所示:usr/bin/ld: warning: libswresample.so.4, needed by vendor/install/lib/libavcodec.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: warning: libavutil.so.57, needed by vendor/install/lib/libavcodec.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: vendor/install/lib/libavcodec.so: undefined reference to `av_opt_set_int@LIBAVUTIL_57'
/usr/bin/ld: vendor/install/lib/libavcodec.so: undefined reference to `av_get_picture_type_char@LIBAVUTIL_57'
# many more lines of undefined references
您可以通过添加 -rpath-link
来消除此问题...g++ -Wl,-rpath-link,vendor/install/lib -L vendor/install/lib -o main main.o -lavcodec
但可执行文件失败并出现此错误:./main: error while loading shared libraries: libavcodec.so.59: cannot open shared object file: No such file or directory
如果您使用 -rpath
而不是 -rpath-link
, 你会得到这个错误:./main: error while loading shared libraries: libswresample.so.4: cannot open shared object file: No such file or directory
最后一个错误是因为 libavcodec.so
引用资料 libwresample.so
,并且它的 RPATH 中没有它。 main
找到 libavcodec.so
成功,但随后动态链接器卡住了。解决问题
据我所知,ffmpeg
configure
脚本清理通过 --extra-ldflags
传入的参数它的变种。不管你怎么努力,我认为它不会让你通过 $
以这种方式登录。要传入未经过滤的参数,您应该设置 LDFLAGS
(及其变体)调用 configure
时的环境中.最终目标是得到字符串 -Wl,-rpath,'$ORIGIN'
进入创建每个库的命令,这意味着我们希望将该值分配给 LDSOFLAGS
.让我们从直接传递它开始:LDSOFLAGS=-Wl,-rpath,'$ORIGIN' ../configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie
查看 src/build/ffbuild/config.mak
我们看到以下行:LDSOFLAGS=-Wl,-rpath,$ORIGIN
在这种情况下,单引号由运行 configure
的 shell 解释。命令,因此它们不会出现在生成的 makefile 中。但是,如果我们转义单引号,那么 shell 将扩展
$ORIGIN
环境变量(在这种情况下为空),并生成以下行:LDSOFLAGS=-Wl,-rpath,''
但是,如果您将“真实”单引号放在转义的单引号内...LDSOFLAGS=-Wl,-rpath,\''$ORIGIN'\' ../configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie
它产生正确的行:LDSOFLAGS=-Wl,-rpath,'$ORIGIN'
然而,现在出现了make
的问题。也使用 $
迹象。通常我们可以逃脱 $
将其替换为 $$
进行签名.但是,您可以在 src/ffbuild/library.mak
中看到那LDSOFLAGS
在 define RULES
内部使用,然后与行 $(eval $(RULES))
一起应用.这将扩大 LDSOFLAGS
以及其中的任何变量,即 $$
将替换为单个 $
在配方的最终版本中,因此,我们需要使用四个 $
在eval
扩张中幸存下来的迹象以及执行配方时的扩展。LDSOFLAGS=-Wl,-rpath,\''$$$$ORIGIN'\' ../configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie
为了完整起见,我们还要定义 LDEXEFLAGS
,因此 ffmpeg
可执行文件正常工作。请注意 LDEXEFLAGS
只需要两个 $
标志(您必须查看生成文件才能知道这一点)。LDEXEFLAGS=-Wl,-rpath,\''$$ORIGIN/../lib'\' LDSOFLAGS=-Wl,-rpath,\''$$$$ORIGIN'\' ../configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie
构建和安装后,最后要做的是确保 main
也有正确的RPATH
:g++ -Wl,-rpath,'$ORIGIN/vendor/install/lib' -L vendor/install/lib -o main main.o -lavcodec
结果如下:$ ./main
FFMPEG AVCODEC VERSION: 3871332
关于c++ - 使用 CMAKE 和 RPATH 捆绑 FFMPEG,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71423110/