首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >用CMAKE和RPATH捆绑FFMPEG

用CMAKE和RPATH捆绑FFMPEG
EN

Stack Overflow用户
提问于 2022-03-10 11:09:37
回答 1查看 536关注 0票数 1

正如标题所解释的,我正在尝试将FFMPEG与我的可执行应用程序捆绑在一起。

但是,我似乎无法理解FFMPEG共享库的运行时加载。我不知道什么是不正确的:

  • 主要项目的RPATH/RUNPATH和FFMPEG库。
  • CMake安装步骤。
  • 或者只是我的整个方法(很可能)。

这是一个CMake项目,希望最终找到一个“标准解决方案”,我将包括重现这个问题所需的一切。

CMakeLists.txt

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
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 (这可能需要一段时间.):

代码语言:javascript
运行
复制
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命令:

代码语言:javascript
运行
复制
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install
cmake --build build -j16 #-v
cmake --install build

步骤之间的清理:

代码语言:javascript
运行
复制
# Run from top-level dir of project
rm -rf build install
rm -rf vendor/install # ONLY if you need to modify the FFMPEG step.

调查:

  1. 按照 post的方向,我将可执行文件的RPATH/RUNPATH设置为$ORIGIN/../deps,而在FFMPEG库中没有RPATH。
  • 当我这样做时,cmake步骤失败,抱怨libavutil.so不能在install(RUNTIME_DEPENDENCY_SET...)函数中解析。这是有意义的,因为在cmake_install.cmake目录中生成的build脚本正在将演示可执行文件移动到安装目录,并在执行运行时依赖集分析之前对可执行文件执行file(RPATH_CHANGE...)。根据CMake文档这里和使用readelf -d demo,可执行文件的RUNPATH$ORIGIN/../deps,只有文档中的第1种情况才适用于这里,因此根本找不到库。
  1. 1中引用的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.solibswresample.so上的依赖项)时,不搜索RUNPATH of demo,而是将加载器返回到系统路径,而找不到库。
  2. 我能够满足CMake和加载程序,方法是在FFMPEG库上设置RPATH,而不使用DIRECTORIES参数和CMake install(RUNTIME_DEPENDENCY_SET...)子命令。CMake在没有任何警告的情况下运行,可执行的加载/运行也按预期运行。但是,这不是一个可行的解决方案,因为当使用ldd检查ldd文件夹中的库时,libswresample.so和其他库将指向部署时不会出现在包中的vendor/install目录。

有没有人知道标准方法是什么,或者我在上面遗漏了什么?我愿意接受任何和所有的建议/工具,但我这样做是为了了解什么是“标准实践”,并希望将其扩展到跨平台解决方案(即Windows/macOS)。因此,我希望避免干扰系统级加载器配置(ldconfig)。

谢谢!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-03-10 19:51:35

复制这个问题

为了重现您的问题,我实际上放弃了所有CMake内容,只从命令行编译了main.cpp。我认为您会同意,问题的根源在于ffmpeg构建。

代码语言:javascript
运行
复制
g++ -I vendor/install/include -c -o main.o main.cpp
g++ -L vendor/install/lib -o main main.o -lavcodec

输出如下:

代码语言:javascript
运行
复制
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.

代码语言:javascript
运行
复制
g++ -Wl,-rpath-link,vendor/install/lib -L vendor/install/lib -o main main.o -lavcodec

但是,该可执行文件在出现此错误时失败:

代码语言:javascript
运行
复制
./main: error while loading shared libraries: libavcodec.so.59: cannot open shared object file: No such file or directory

如果使用-rpath而不是-rpath-link,则会得到以下错误:

代码语言:javascript
运行
复制
./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。让我们从直接传递它开始:

代码语言:javascript
运行
复制
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中,我们看到了以下一行:

代码语言:javascript
运行
复制
LDSOFLAGS=-Wl,-rpath,$ORIGIN

在本例中,单引号由运行configure命令的shell解释,因此它们不会出现在生成的makefile中。

但是,如果我们转义单引号,则shell将展开$ORIGIN环境变量(在本例中为空),并生成以下行:

代码语言:javascript
运行
复制
LDSOFLAGS=-Wl,-rpath,''

不过,如果你在转义的单引号里加上“真实”单引号.

代码语言:javascript
运行
复制
LDSOFLAGS=-Wl,-rpath,\''$ORIGIN'\' ../configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie

它生成正确的行:

代码语言:javascript
运行
复制
LDSOFLAGS=-Wl,-rpath,'$ORIGIN'

然而,现在存在的问题是make也使用了$符号。通常,我们可以通过用$替换它来转义它。但是,您可以在src/ffbuild/library.mak中看到LDSOFLAGSdefine RULES内部使用,然后与行$(eval $(RULES))一起应用。这将立即展开LDSOFLAGS和其中的任何变量,这意味着在食谱的最终版本中,$$将被替换为单个$,因此,我们需要使用四个$符号来保存eval中的扩展以及在执行菜谱时的扩展。

代码语言:javascript
运行
复制
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才能知道这一点)。

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
g++ -Wl,-rpath,'$ORIGIN/vendor/install/lib' -L vendor/install/lib -o main main.o -lavcodec

结果是:

代码语言:javascript
运行
复制
$ ./main
FFMPEG AVCODEC VERSION: 3871332
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71423110

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档