c++ - 使用 CMAKE 和 RPATH 捆绑 FFMPEG

标签 c++ ffmpeg cmake rpath runpath

正如标题所解释的,我正在尝试将 FFMPEG 与我的可执行应用程序捆绑在一起。
但是,我似乎无法弄清楚 FFMPEG 共享库的运行时加载。我不确定什么是不正确的:

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

  • 这是一个 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.
    
    调查:
  • 跟随this的方向帖子我设置了RPATH/RUNPATH可执行文件的 $ORIGIN/../deps FFMPEG 库上没有 RPATH。
  • 当我这样做时,cmake 安装步骤失败并提示 libavutil.so无法在 install(RUNTIME_DEPENDENCY_SET...) 中解决功能。这确实有意义,因为生成的 cmake_install.cmake build 中的脚本目录正在将演示可执行文件移动到安装目录并执行file(RPATH_CHANGE...)在执行运行时依赖集分析之前对可执行文件进行分析。根据 CMake 文档 here并使用 readelf -d demo RUNPATH可执行文件是$ORIGIN/../deps并且只有文档中的案例#1 适用于此,因此根本找不到该库。
  • 来自 1 中引用的 CMake 文档,有一个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 )时,RUNPATHdemo不搜索,而是加载程序回退到系统路径并且找不到库。
  • 通过在 FFMPEG 库上设置 RPATH 并且不使用 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 中看到那LDSOFLAGSdefine 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/

    相关文章:

    CMake - 如何创建可执行文件,但不添加到 "all"目标?

    cmake - 包与库

    c++ - 在 Visual Studio 中测试时如何覆盖标准 C++ 函数?

    c++ - 关于C++中bool的问题

    image - FFmpeg:计算将图像和 .mp3 转换为视频的时间

    rust 中的 FFMPEG 输入选项

    c - 如何修复: Sql logic error sqlite3 in Clion?

    c++ - 捕获函数参数中的常量表达式

    c++ - OS X 上的 std::locale 段错误,无法在任何其他平台上重现

    objective-c - 可以使用 AVCaptureMovieFileOutput 将流视频发送到网络吗?