Android之FFmpeg 5.0起航

语言: CN / TW / HK

theme: github highlight: androidstudio


本文介绍FFmpeg交叉编译中的一些概念以及编译的详细过程,帮助你跳过一些交叉编译的坑。

  • 系统:Ubuntu 22.04
  • FFmpeg:5.0.1、5.1.2
  • NDK:r21,r22,r23,r24,r25

NDK直到r18才正式移除GCC(原因查看此issue),虽然此前很长一段时间内筹划将Clang/LLVM作为GCC的替代者。接下来补充GCC和Clang/LLVM一些知识。

GCC 和 Clang/LLVM

GCCClang/LLVM编译器联系甚密,它们实现编译器的理念基本遵循最经典的三段式架构(编译器具体实的理论基础可以查看编译原理或者龙书)。

  • 编译器: Compiler,一种将高级语言编写的源代码翻译成低级语言的软件,注意: 这里的低级语言有很多类型,可以是汇编语言也可以是机器语言,一些编译器只负责将高级语言翻译成汇编语言而不是直接翻译成机器语言,至于翻译成机器语言的工作交给汇编器处理
  • 三段式设计: 将源码编译生成目标语言的过程划分成前端中端(也称优化器)以及后端三个阶段:

1)前端(front end):负责词法、语法和语义分析,随后将源代码转换为中间代码。

2)中端(middle end或optimizer ):对中间代码进行优化。

3)后端(back end):负责将中端产物转化为针对各自平台的机器码(可以是汇编也可以是目标代码)。

GCC

1987年3月22日理查德·马修·斯托曼发布GCC第一个beta测试版(当时被称为GNU C Compiler,也就是GNU C编译器)。1999年4月,经过长时间的谈判EGCS最终成为GCC(此时改名为GNU Compiler Collection,GNU 编译器集合)的官方版本,并于当年7月31号发布统一后的第一个GCC版本2.95,之后由GCC指导委员会的指导下不断迭代更新。GCC中编译器也遵循三段式设计(如下图中所示,详见GCC内部课程),有前端中端以及后端

Clang/LLVM

介绍Clang之前不得不说一下LLVM(原来被称为Low Level Virtual Machine,其实它与传统虚拟机一点关系也没有,准确说它是LLVM Project的全名),它源于2000年美国UIUC大学的一个研究项目,目的是提供一种现代的、基于 SSA 的编译策略,并能支持任意编程语言的静态和动态编译(参考内容),最初由该大学的 Vikram AdveChris Lattner开发(有趣的是Chris Lattner主页中称自己是LLVM的原作者,理论还是实践这里就不清楚了)。现在它已经发展成为一个由多个子项目组成的巨型项目,原来的定义已经不适合,更为确切的描述是LLVM Project是模块化和可重用的编译器和工具链技术的集合。下图描述的是LLVM三段式设计实现,源于Chris Lattner在The Architecture of Open Source Applications一书中的介绍。

Clang(a C language family frontend for LLVM,LLVM中C家族语言的前端)诞生背景也是很有意思,2005年Chris Lattner进入苹果公司,并致力于LLVM在苹果开发体系中应用,自此苹果公司开始在多个商业产品中广泛使用LLVM,包括使用修改版的GCC(llvm-gcc) 作为Objective-C、C、C++等语言的编译器前端,然并卵,受限于GCC社区的支持以及许可协议的限制(GCC 4.2.1是最后一个GPL v2许可证下的版本),苹果公司选择开发Clang,替代llvm-gcc作为LLVM的一个前端,并于2007年7月11开源。

小结

GCC是编译器的集合;LLVM是编译器+工具链的集合;Clang仅是用于替代当初llvm-gcc补足LLVM中C家族语言编译器缺失的前端。如果仅从代码库出发考虑,GCC就是烤全羊,LLVM就是全羊宴,Clang就是全羊宴中的一道菜,你要说谁更好吃,那就见仁见智了。

交叉编译

交叉编译实质上是在本机上为其它平台生成库或者可执行代程序,在这个过程中涉及使用工具链(toolchain,一组专用工具,一般包括编译器、汇编器以及链接器,可以查看此处),它们一般一个接一个的调用,上一个的输出即是下一个的输入。

NDK去GNU化

NDK存在两套工具链如下:

  1. gcc/g++和GNU Binutils
  2. clang/clang++和LLVM tools(LLVM Binutils)

早期工具链1能够投入实际生产环境,工具链2还处于预研阶段,中期逐步将工具链2中的工具替换工具链1中的工具,后期工具链2完全投入生产环境,抛弃工具链1。

  • NDK r19之前工具链1位于$NDK/toolchains/文件夹下后缀为-4.9的子文件夹下,工具链2位于$NDK/toolchains/llvm/prebuilt/<host-tag>/bin/文件夹下。
  • NDK r18时,移除GCC,虽然工具链中还有名为gcc/g++的脚本,但是已被强制指向clang/clang++。
  • NDK r19进一步将工具链中的GNU Binutils移动到$NDK/toolchains/llvm/prebuilt/<host-tag>下,自此去GNU化拉开帷幕。
  • NDK r22时,使用LLD替换ld。
  • NDK r23时GNU Binutils中除了GNU汇编器(一般被称为gas)之外都被移除。
  • NDK r24时移除gas(替代者不是llvm-as),自此完成去GNU化,这也意味着自NDK r24开始NDK中只有一套工具链,即工具链2。

关于NDK中Binutils大致变化可以查看BulildSystemMaintainers#binutils,更多细节请查看NDK各个版本对应的Changelog

工具链变化:

由于NDK去GUN化,需要工具链2中的工具兼容工具链1中的功能,所以一些工具并不直接对应于LLVM中的工具,如下表中所示:

| GNU | LLVM | 介绍 | | --------- | -------------------------------- | ------------------------ | | gcc | clang | C编译器 | | g++ | clang++ | c++编译器 | | as | clang(llvm-as处理的是LLVM IR,不是汇编源码) | 汇编器,将汇编代码生成目标文件 | | ld | lld、clang(也能用于链接) | 链接器,将所有的目标文件以及依赖的库文件进行链接 | | addr2line | llvm-addr2line | 查找对应位置的源码信息,一般用于调试 | | ar | llvm-ar | 用于创建、修改和从档案中提取的实用程序 | | nm | llvm-nm | 列出object文件中的符号。 | | ranlib | llvm-ranlib | 生成存档内容的索引 | | strip | llvm-strip | 裁剪符号表 |

需要注意表中是clang不是Clang,clang是 一个基于Clang和LLVM的C家族语言编译器,它涉及预处理、解析、优化、代码生成、汇编和链接,你可以通过传递参数控制它在执行完整链接之前的任何阶段停止;你也可以将clang理解成驱动器 Clang Driver,由它控制诸如编译器、汇编器和链接器的执行,完成编译过程。关于clang的命令参数可以查看对应版本的文档或者直接在终端使用--help查看,clang同时也兼容gcc中的一些命令参数,编辑脚本时可以仔细阅读。

链接器GNU ld的替代者是lld或者clang,为什么不是llvm-ld呢?其实llvm-ld也是存在的,只不过在LLVM 3.2中被移除,它的部分功能被其它程序取代。

llvm-ld and llvm-stub have been removed, llvm-ld functionality can be partially replaced by llvm-link | opt | {llc | as, llc -filetype=obj} | ld, or fully replaced by Clang.

lld是LLVM Linker的缩写,llvm-link是LLVM bitcode linker的缩写,这样就很容易区分lld和llvm-link,二者都是链接器,前者GNU链接器的直接替代,接受与GNU相同的命令行参数和链接脚本;后者用于处理多个LLVM bitcode文件,并将它们链接在一起形成一个LLVM bitcode文件。上述内容可以LLVM Download Page页面中查各个版本的release notes 或者 docs,如下图所示。关于llvm tools的一些命令描述文档可以从docs中查看,例如Clang的命令行参数描述就可以从docs的index界面中的如下内容中找到:

  • Clang Compiler User’s Manual(Clang编译器的用户手册)
  • Clang command line argument reference(Clang命令行参数引用)
  • Clang “man” pages

FFmpeg交叉编译

一般步骤如下:

  1. 创建并启动Ubuntu虚拟机
  2. 搭建NDK环境
  3. 安装必要软件
  4. 下载并解压FFmpeg源码包
  5. 将脚本放置于FFmpeg解压包的根路径
  6. 执行脚本,等待编译结束

步骤1可以参考《VMware虚拟机安装Ubuntu20.04详细图文教程》,不再赘述;接下来对2-6步骤中一些关键部分进行一一介绍。

NDK环境搭建

下载NDK并将它解压至usr/local/路径下,在终端中进行如下操作,进行环境变量配置(可以参考超详干货!Linux 环境变量配置全攻略,当然你还可以在/etc/profile.d/目录下添加脚本文件)。

  • 执行 sudo vim ~/.bashrc,如果未安装vim软件,使用sudo apt install vim命令进行安装。
  • 键盘输入i进入编辑模式,并在文件末尾依次添加export NDK=/usr/local/android-ndk-r23bexport PATH=$PATH:$NDK,如下图所示:

  • 键盘敲击Esc键,退出编辑模式后,键盘输入:wq保存并退出vim软件。
  • 继续执行source ~/.bashrc更新配置或者打开新的终端,保证NDK的环境变量配置生效
  • 输入ndk-build -v验证是NDK的环境变量配置是否成功,成功结果如下图所示:

安装必要软件

虽然一般情况下仅需安装NDK+pkg-config+make就可以进行交叉编译,但是FFmpeg对外提供的编译配置中有一些特殊优化,比如enable-asm时,可能需要安装额外的软件包。

  • pkg-config:提供库的一些信息,终端输入sudo apt install pkg-config进行安装
  • make:用于解释Makefile文件中的规则,终端输入sudo apt install make进行安装

安装yasm/nasm条件

FFmpeg中x86/x86_64架构对应的汇编代码采用了Intel语法风格的x86汇编语言(x86汇编语言有两种语法风格(Intel和AT&T ) ),开启汇编优化时需要支持Intel语法风格的汇编器。由于gas或者llvm-as只支持AT&T语法风格,因此安装yasm/nasm。

yasm:虽然在Github上的仓库时有更新,但最新版本停留在了2014年8月10号。终端中输入sudo apt install yasm

nasm:一直在维护更新,可以查看changelog。终端输入sudo apt install nasm

下载并解压FFmpeg源码包

源码包可以https://github.com/FFmpeg/FFmpeg 仓库中选择对应版本下载,当然也可使用📎FFmpeg-n5.0.1.zip。~~原先即使使用最新版本的 open-vm-tools-desktop (2:11.3.5-1ubuntu4,终端中输入dpkg -l | grep open-vm可查看版本)也无法从windows直接拷贝文件到虚拟机。(已被修复,仅做记录)~~

编写脚本

Tip:这部分内容涉及Shell脚本的语法规则非常简单,花费一点时间就能掌握,阅读Shell教程

FFmpeg遵循与大多数Unix软件一样的构建方式,用到如下三条命令:

  1. ./configure+参数,向configure脚本传递参数并执行,并生成相应的Makefile文件或与编译相关文件(配置可能与其它项目中的参数不一致,使用./configure --help查看更多参数,当然也可以使用./configure --help > configure_help.txt将帮助文档的内容输入到txt文件中查看)。
  2. make,使用命令make编译,make命令会自动查找上一步生成的Makefile文件并根据Makefile中的内容执行编译。
  3. make install,将编译生成的库文件、配置文件、头文件等等拷贝到指定的安装目录。

除了上述指令外还有可能用到make cleanmake distcleanmake uninstall等命令配合使用,具体介绍可以查看make、make clean、make install、make uninstall、make dist、make distcheck和make distclean

配置./configure+参数

shell脚本中的字符串可以用单引号,也可以用双引号,也可以不用引号

下面是一个基本的架子:

接下来对上面脚本中使用到的FFmpeg Toolchanain option和自定义变量逐行介绍:

Tip:为了方便阅读,将变量的定义和赋值的内容放到下面介绍

补充上述变量的定义与值

CONFIGURATION:封装一些向configure脚本传递的参数。

CFLAGSLDFLAGS:向编译器或链接器传递参数,辅助编译。

其它变量:用于确定文件位置,如下:

```bash NDK="/usr/local/android-ndk-r25" HOST="linux-x86_64" SYSROOT="$NDK/toolchains/llvm/prebuilt/$HOST/sysroot" LLVM_TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/$HOST/bin" PKG_CONFIG="/usr/bin/pkg-config"

省略了一些代码,具体查看脚本

PREFIX="$(pwd)/android/$ARCH" CC=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang CXX=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang++ AS=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang LD=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang AR=$LLVM_TOOLCHAIN/llvm-ar NM=$LLVM_TOOLCHAIN/llvm-nm STRIP=$LLVM_TOOLCHAIN/llvm-strip RUNLIB=$LLVM_TOOLCHAIN/llvm-ranlib ```

接下来重点介绍CONFIGURATIONCFLAGS以及LDFLAGS的值:

$CONFIGURATION

FFmpeg configure可用以配置的选项有很多,如图📎configure_help.svg,网上有很多这方面的解释,可以自行搜索,这里不做一一介绍。

注意:在前缀为--disable---enable-的选项中,disable表示关闭 ,enable 代表开启。对于变量COMPONENT_LIST和变量CMDLINE_SELECT中的选项来说,可以自由选择设置--disable---enable-作为前缀(详见下方Configure脚本分析)。

```bash

位于4105行,不同版本可能有变化。

关于for关键字,可以在终端中输入help for查看文档,这里有一些特殊,没有指定[in WORDS],

因此按照文档中说法,就是默认使用了in "$@",

相当于遍历一个由命令行的所有参数分别填充的字符串数组,["opt0","opt1","opt2"...].

for opt do # ${opt#=}表示将opt字符串中=右边的字符串取出,详见参考内容《shell变量操作${}详细用法》 optval="${opt#=}" case "$opt" in # 省略部分分支 #匹配以--enable-或者--disable-开头的选项,当然有一些已经提前处理 --enable-?|--disable-?) #eval命令会将参数组合成一个字符串,并将结果作为shell的输入,然后执行。 #1. echo "--enable-xxx" | sed 's/--/action=/;s/-/ option=/;s/-//g' #2.执行上述命令,打印action=enable option=xxx eval $(echo "$opt" | sed 's/--/action=/;s/-/ option=/;s/-//g') # is_in函数相当于ArrayList#contains方法,在后面解析 if is_in $option $COMPONENT_LIST; then # 对于COMPONENT_LIST变量中的参数来说,如果设置disable,那么重新将action设置为unset test $action = disable && action=unset # 假设$option=bsfs, # bsfs去掉s,并转换成大写;最终执行unset $BSF_LIST或者enable $BSF_LIST eval $action $$(toupper ${option%s})_LIST elif is_in $option $CMDLINE_SELECT; then # 对于CMDLINE_SELECT变量中的参数可以自由设置disable还是enable $action $option else die_unknown $opt fi ;; # 省略部分判断 esac done ```

```bash

假设 is_in $option $COMPONENT_LIST

is_in(){ # $1的值就是$option value=$1 # 保证后面$获取的参数中移除$1(即$option) shift # 此处遍历$,如果$option在$COMPONENT_LIST中,那么返回0,shell中0表示true. for var in $*; do [ $var = $value ] && return 0 done return 1 } ```

这里介绍一些常用的。

Configuration options

| --disable-static | 不构建静态库 | | ----------------- | ------- | | --enable-shared | 编译生成动态库 | | --enable-small | 降低库体积 |

Program options

| --disable-programs | 不构建命令行程序(指ffmpeg、ffplay以及ffprobe) | | ------------------- | ----------------------------------------------------------- | | --disable-ffmpeg | 不构建ffmpeg程序(转换器) | | --disable-ffplay | 不构建ffplay程序(播放器) | | --disable-ffprobe | 不构建ffprobe程序(查看多媒体文件的信息) |

Documentation options

| --disable-doc | 不产生文档 | | -------------------- | ---------------- | | --disable-htmlpages | 不产生 HTML类型的文档 | | --disable-manpages | 不产生 manpage类型的文档 | | --disable-podpages | 不产生 POD 类型的文档 | | --disable-txtpages | 不产生 text 类型的文档 |

Component options

  • --disable-avdevice:不构建libavdevice库,libavdevice is a library containing input and output devices for grabbing from and rendering to many common multimedia input/output software frameworks, including Video4Linux, Video4Linux2, VfW, and ALSA.
  • --disable-avcodec:不构建libavcodec库,libavcodec is a library containing decoders and encoders for audio/video codecs.
  • --disable-avformat:不构建libavformat库,libavformat is a library containing demuxers and muxers for multimedia container formats.
  • --disable-swresample:不构建libswresample库,libswresample is a library performing highly optimized audio resampling, rematrixing and sample format conversion operations.
  • --disable-swscale:不构建libswcale库,libswscale is a library performing highly optimized image scaling and color space/pixel format conversion operations.

Toolchain options

| --enable-cross-compile | 开启交叉编译 | | ----------------------- | -------- | | --enable-pic | 生成位置无关代码 |

Optimization options

  • --disable-asm:禁止所有汇编优化

注:脚本中在7281行中有enabled asm || { arch=c; disable $ARCH_LIST $ARCH_EXT_LIST; }这样的代码,意为着当检测到asm关闭时,会关闭ARCH_LISTARCH_EXT_LIST中的选项。顺便提一下,当关闭asm时,neon也被关闭,asm默认被开启的,具体可以查看脚本。

```

这里已被调整,只做参考,具体详见对于版本中的configure脚本

ARCH_LIST=" aarch64 alpha arm avr32 avr32_ap avr32_uc bfin ia64 loongarch loongarch32 loongarch64 m68k mips mips64 parisc ppc ppc64 riscv s390 sh4 sparc sparc64 tilegx tilepro tomi x86 x86_32 x86_64" ARCH_EXT_LIST=" $ARCH_EXT_LIST_ARM $ARCH_EXT_LIST_PPC $ARCH_EXT_LIST_X86 $ARCH_EXT_LIST_MIPS $ARCH_EXT_LIST_LOONGSON"

ARCH_EXT_LIST_ARM=" armv5te armv6 armv6t2 armv8 neon vfp vfpv3 setend"

ARCH_EXT_LIST_X86=" $ARCH_EXT_LIST_X86_SIMD cpunop i686 " ARCH_EXT_LIST_X86_SIMD="aesni amd3dnow amd3dnowext avx avx2 avx512 fma3 fma4 mmx mmxext sse sse2 sse3 sse4 sse42 ssse3 xop" ```

  • --disable-neon:禁止NEON优化

Demystifying ARM Floating Point Compiler Options一文中对NEON、float-abi、fpu有详细解释,可以解决很多疑惑。

一般情况下对于ARM架构推荐开启NEON优化,原因:1.绝大多数ARM 安卓设备支持NEON,特别是基于ARMv8的设备都支持NEON。2.NDK r23是最后一个支持non-Neon的版本,原因是极少数非常旧的设备不支持Neon。3.性能优化很可观。本文中对于x86架构关闭NEON优化的原因:x86架构不兼容NEON指令。如果你需要在基于x86架构的安卓设备(比如Window11上的android子系统)支持NEON指令,可以参考From ARM NEON to Intel SSE-the automatic porting solution, tips and tricks。一些拓展链接error:'neon_vector_type'attribute is not suppoted for this target #420 Neon for x86 not enabled in Clang #260

Developer options

  • --disable-debug:禁止调试符号,脚本中默认开启。
$CFLAGS

这里介绍clang/clang++常见参数。

控制代码优化程度,-O0, -O1, -O2, -O3, -Ofast, -Os, -Oz, -Og, -O, -O4

-O0, -O1, -O2, -O3, -Ofast, -Os, -Oz, -Og, -O, -O4

Specify which optimization level to use:

-O0 Means “no optimization”: this level compiles the fastest and generates the most debuggable code.

-O1 Somewhere between -O0 and -O2.

-O2 Moderate level of optimization which enables most optimizations.

-O3 Like -O2, except that it enables optimizations that take longer to perform or that may generate larger code (in an attempt to make the program run faster).

-Ofast Enables all the optimizations from -O3 along with other aggressive optimizations that may violate strict compliance with language standards.

-Os Like -O2 with extra optimizations to reduce code size.

-Oz Like -Os (and thus -O2), but reduces code size further.

-Og Like -O1. In future versions, this option might disable different optimizations in order to improve debuggability.

-O Equivalent to -O1.

-O4 and higher

Currently equivalent to -O3

-D \=\ :Define \ to \ (or 1 if \ omitted)

相当于C语言中的#define macro=value

-Wl,\ :Pass the comma separated arguments in to the linker

向linker传递参数

-I \

:Add directory to the end of the list of include search paths

寻找头文件时会在dir路径中寻找

-L \

: Add directory to library search path

$LDFLAGS

-L \

:Add a directory to the library search path

-l \ :Root name of library to use

--nostdlib: Only search directories specified on the command line

修改config.h中的内容

利用sed指令修改config.h中的值,config.h文件会在configure脚本执行完成后自动生成,里面记录一些配置信息,比如是否开启了asm、是否开启neon等等。因此也可以通过config.h文件确定配置是否与预期相符。

编译&安装

编译

提供脚本:📎build_shared_asm.sh📎build_static_asm.sh

你可以使用上述提供的脚本进行编译(你需要根据需求调整参数的编译出最合适的库),注:已经测试NDKr21及以上版本,你只需调整NDK版本或者路径。

将脚本放入FFmpeg源码解压包的根目录下,在终端中执行脚本,等待结束,如图:

编译结果如下:

问题记录

编译过程中出现

GNU assembler not found, install/update gas-preprocessor

原来脚本中汇编器使用的GAS,由于未开启汇编优化,所以一直没有发现脚本中的错误。当使用NDK中的GAS编译Arm架构对应的汇编代码时,出现此问题。

解决方法:

根据提示安装gas-preprocessor(没什么用,毕竟gas-preprocessor是Perl脚本,用于实现一个 Apple's as没有的gas preprocessor的子集):

  • 下载gas-preprocessor.pl文件。
  • 使用 sudo cp gas-preprocessor.pl /usr/local/bin/将其拷贝至usr/local/bin路径下,并通过sudo chmod 777 /usr/local/bin/gas-preprocessor.pl 赋予权限。

~~方法1~~:clang内置汇编器,用于减少生成.s文件以及调用汇编器的时间。向clang传递-fno-integrated-as参数,禁用内置汇编器,默认使用/usr/bin/as替代。经实践,方法1不适合当前版本,会报如下错误(NDK issues),NDK r23中直到r23c中被修复,虽然之前版本依旧能使用,但是NDK r24中此参数已被移除。

test_ld cc test_cc BEGIN /tmp/ffconf.MNVahc6Q/test.c 1 int main(void){ return 0; } END /tmp/ffconf.MNVahc6Q/test.c /usr/local/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang --sysroot=/usr/local/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/sysroot -I/usr/local/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/arm-linux-androideabi -Os -fPIC -fno-integrated-as -ferror-limit=0 -DVK_ENABLE_BETA_EXTENSIONS=0 -Wno-implicit-int-float-conversion -Wno-sometimes-uninitialized -Wno-unused-function -Wno-deprecated-declarations -Wno-tautological-constant-out-of-range-compare -Wno-inline-asm -march=armv7-a -c -o /tmp/ffconf.MNVahc6Q/test.o /tmp/ffconf.MNVahc6Q/test.c /usr/bin/as: unrecognized option '-EL' clang-12: error: assembler command failed with exit code 1 (use -v to see invocation) C compiler test failed.

方法2:直接由clang指定汇编器,即--as=$cc

x86架构开启asm,编译时报错,inline assembly requires more registers than available

libavcodec/x86/cabac.h:200:9: error: inline assembly requires more registers than available BRANCHLESS_GET_CABAC("%0", "%q0", "(%4)", "%1", "%w1", ^ libavcodec/x86/cabac.h:145:9: note: expanded from macro 'BRANCHLESS_GET_CABAC' "movzbl "statep" , "ret" \n\t"\ ^ CC libavcodec/hcom.o CC libavcodec/hevc_cabac.o libavcodec/x86/cabac.h:200:9: error: inline assembly requires more registers than available libavcodec/x86/cabac.h:145:9: note: expanded from macro 'BRANCHLESS_GET_CABAC' "movzbl "statep" , "ret" \n\t"\ ^ CC libavcodec/hevc_data.o CC libavcodec/hevc_filter.o In file included from libavcodec/h264_cabac.c:43: libavcodec/x86/h264_cabac.c:66:9: error: inline assembly requires more registers than available "3: \n\t" ^ CC libavcodec/hevc_mp4toannexb_bsf.o CC libavcodec/hevc_mvs.o CC libavcodec/hevc_parse.o libavcodec/hevc_cabac.c:37:21: warning: variable 'num_bins_in_se' is not needed and will not be emitted [-Wunneeded-internal-declaration] static const int8_t num_bins_in_se[] = { ^ CC libavcodec/hevc_parser.o CC libavcodec/hevc_ps.o CC libavcodec/hevc_refs.o CC libavcodec/hevc_sei.o In file included from libavcodec/hevc_cabac.c:27: In file included from libavcodec/cabac_functions.h:49: libavcodec/x86/cabac.h:200:9: error: inline assembly requires more registers than available BRANCHLESS_GET_CABAC("%0", "%q0", "(%4)", "%1", "%w1", ^ libavcodec/x86/cabac.h:145:9: note: expanded from macro 'BRANCHLESS_GET_CABAC' "movzbl "statep" , "ret" \n\t"\ ^

经过搜索和实践ffmepg trac 779x86 inline assembly code which compiled with r16b does not compile with r17android 4.2.2 x86,解决Android NDK编译FFmpeg 4.2.2的x86 cpu版时的问题的内容,向CFLAG中传递-DHAVE_EBP_AVAILABLE=0方法仍会出错,报如下错误:

./config.h:356:9: warning: 'HAVE_EBP_AVAILABLE' macro redefined [-Wmacro-redefined] warning: 'HAVE_EBP_AVAILABLE' macro redefined [-Wmacro-redefined]

将libavutils/x86/asm.h中#define HAVE_7REGS (ARCH_X86_64 || (HAVE_EBX_AVAILABLE && HAVE_EBP_AVAILABLE))修改为#define HAVE_7REGS (ARCH_X86_64)的方式可以编译通过。

运行过程中出现

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "xxx" referenced by

一般无法定位符号的原因是没有链接对应的库文件。下面是三个架构不同报错:

W/System.err: java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__extenddftf2" referenced by "/data/app/~~5iB4UFPumZmTrJq8DUYFmg==/com.floatWind.snap-2vw43nTh6DG8YL6K3Td2aA==/lib/x86_64/libavutil.so"... W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:1077) W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:998) W/System.err: at java.lang.System.loadLibrary(System.java:1656)

W/System.err: java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__ashldi3" referenced by "/data/app/com.floatWind.snap-s8RJlycCvRQZYgyulGFC3Q==/lib/x86/libavformat.so"... W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:1016) W/System.err: at java.lang.System.loadLibrary(System.java:1657) W/System.err: at com.ffmpeg.VideoSnap.<clinit>(VideoSnap.java:16) W/System.err: at com.floatWind.snap.MainActivity$2.run(MainActivity.java:70)

W/System.err: java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__floatunsitf" referenced by "/data/app/com.floatWind.snap-K2FtbiNL0c7eCXN_xanOMg==/lib/arm64/libavutil.so"... W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:1071) W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:1007) W/System.err: at java.lang.System.loadLibrary(System.java:1668) W/System.err: at com.ffmpeg.VideoSnap.<clinit>(VideoSnap.java:16) W/System.err: at com.floatWind.snap.MainActivity$2.run(MainActivity.java:70)

extenddftf2、ashldi3、floatunsitf这几个符号皆是用于long double数据类型的浮点运算,它们位于compiler-rt project中builtins中(原本libgcc也有对应实现,后被移除),它位于$NDK/toolchains/llvm/prebuilt/$HOST/lib64/clang/*/lib/linux之下。你也可在Building with 128-bit floating-point (long double) support for AArch64 state, with Arm Compiler 6文章了解更多。

原因:使用-nostdlib导致无法链接到compiler-rt

解决方法: ~~1.将对应库(clang_rt.builtins-XXX-android)的头文件位置和库文件所在位置分别告知编译器和链接器(NDK r21中此方案不生效)。~~

2.移除-nostdlib标志

持续跟踪该问题

undefined reference to 'inflateInit_'

编译ffmpeg静态产物时链接了libz,本地编写CMakeList.txt未链接libz,导致出现未定义的引用

fatal error: 'vulkan_beta.h' file not found

方法1:配置configure脚本时禁用vulkan:--disable-vulkan

方法2:配置extra-cflags时添加 -DVK_ENABLE_BETA_EXTENSIONS=0,防止引用vulkan_beta.h头文件

ld: error: relocation XXX cannot be used against symbol XXX; recompile with -fPIC

arm64-v8a

虽然可以通过CFlAG中添加-mcmodel=large避免(阅读12),但是还有其它错误发生,仅作记录

C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_32'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_32'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_64'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_64'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_128'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_128'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_256'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_256'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_512'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_512'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_1024'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_1024'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_2048'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_2048'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_4096'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_4096'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_8192'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_8192'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_16384'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_16384'; recompile with -fPIC C/C++: ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)

x86_64

C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_ac3_bap_bits'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_ac3_bap_bits'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pd_1'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pd_1'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)

x86

C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_ac3_bap_bits'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_ac3_bap_bits'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pd_1'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pd_1'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)

经过长时间尝试各种方案,最终在Unable to compile ffmpeg extension with NDK 23 + CMake-3.22看到解决方案(不是很完美,无法解决x86中的全部报错,但已经够用),如下图所示,当然你也可以在CMakeLists.txt文件中向CFLAGS中添加-Wl,-Bsymbolic,即向链接器传递-Bsymbolic,强制采用本地的全局变量定义。

虽然问题解决了,突然想起FFmpeg官网中Platform Specific InformationAdvanced linking configuration中提及(/(ㄒoㄒ)/~~)

If you compiled FFmpeg libraries statically and you want to use them to build your own shared library, you may need to force PIC support (with --enable-pic during FFmpeg configure) and add the following option to your project LDFLAGS:-Wl,-Bsymbolic.

If your target platform requires position independent binaries, you should pass the correct linking flag (e.g. -pie) to --extra-ldexeflags.

dlopen failed: "xxx.so" has text relocations

System.err: java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/com.floatWind.snap-lMxvRHX5I84nYETkUOhOow==/lib/x86/libavformat.so" has text relocations

通过阅读Android linker changes for NDK developers中Text Relocations一些情况,可以通过Gentoo Textrels guide中的介绍处理文本重定位的问题,下面是一些针对上述具体问题的处理步骤:

  1. 文章中用到scanelf命令来自pax-utils 软件包,因此在终端输入sudo apt install pax-utils

  2. 终端中输入scanelf -qT libavformat.so寻找出错代码,-q表示只输出一些可能有问题的提示,-T表示文本重定位的位置,scanelf命令其它具体参数的含义可以查看这里

  3. 上面的还没研究透彻,待续。
  4. 经过查询,向链接器传递了"-z notext" 导致连接时允许text relocations,忽略了ld: error: can't create dynamic relocation R_386_32 against local symbol in readonly segment; recompile object files with -fPIC or pass '-Wl,-z,notext' to allow text relocations in the output错误。
  5. libavutil/x86/float_dsp.asm、libavutil/x86/tx_float.asm文件出了问题,需要重新编写。

dlopen failed: cannot locate symbol "ff_tx_codelet_list_float_x86"(x86 & x86_64 )

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "ff_tx_codelet_list_float_x86" referenced by "/data/app/~~K5KAUCDK4rV3L6O1Qu8t7w==/com.floatWind.snap-_Cs7dWX9r8E_ELZ7aiTP-g==/lib/x86_64/libavutil.so"...

dlopen failed: cannot locate symbol "__aarch64_ldadd8_acq_rel"

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__aarch64_ldadd8_acq_rel" referenced by "/data/app/com.floatWind.snap--aifSTof5wmQ0hbczZDovw==/lib/arm64/libavformat.so"...

在CFLAGS中添加-mno-outline-atomics(Don't generate local calls to out-of-line atomic operations,关于Out-of-line Atomics可以查看What is new in LLVM12 for Arm),参考Building for Android with OpenSSL

参考内容