正如标题所解释的,我正在尝试将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")
main.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文档这里和使用readelf -d demo
,可执行文件的RUNPATH
是$ORIGIN/../deps
,只有文档中的第1种情况才适用于这里,因此根本找不到库。install(RUNTIME_DEPENDENCY_SET...)
函数的DIRECTORIES
参数可以用作搜索路径。当组合1中的设置、在CMakeLists.txt
中取消注释并重新运行所有CMake命令时,安装是成功的(尽管如文档所述,CMake确实对使用DIRECTORIES
找到的所有依赖项发出警告)。但是,运行可执行文件(即./install/bin/demo
)是不成功的,因为运行时加载程序无法定位libswresample.so
。通过进一步调试,我运行了export LD_DEBUG=libs
并尝试重新启动可执行文件。这表明运行时加载程序正在使用来自libavcodec.so
的$ORIGIN/../deps
RUNPATH查找demo
。但是,在搜索传递依赖项(即libavcodec.so
在libswresample.so
上的依赖项)时,不搜索RUNPATH
of demo
,而是将加载器返回到系统路径,而找不到库。DIRECTORIES
参数和CMake install(RUNTIME_DEPENDENCY_SET...)
子命令。CMake在没有任何警告的情况下运行,可执行的加载/运行也按预期运行。但是,这不是一个可行的解决方案,因为当使用ldd
检查ldd
文件夹中的库时,libswresample.so
和其他库将指向部署时不会出现在包中的vendor/install
目录。有没有人知道标准方法是什么,或者我在上面遗漏了什么?我愿意接受任何和所有的建议/工具,但我这样做是为了了解什么是“标准实践”,并希望将其扩展到跨平台解决方案(即Windows/macOS)。因此,我希望避免干扰系统级加载器配置(ldconfig
)。
谢谢!
发布于 2022-03-10 19:51:35
复制这个问题
为了重现您的问题,我实际上放弃了所有CMake内容,只从命令行编译了main.cpp
。我认为您会同意,问题的根源在于ffmpeg
构建。
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
只需要两个$
符号(您必须查看makefile才能知道这一点)。
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
https://stackoverflow.com/questions/71423110
复制相似问题