Android-NDK-clang 编译 FFmpeg
Android-NDK-clang 编译 FFmpeg - 知乎 (zhihu.com)
前期准备
NDK-21
和 ffmpeg-4.4
进行编译,如果版本不同可能会有所不同。NDK20 - NDK22
和 ffmpeg 4.0 - ffmpeg 4.4
,均可使用。
本文你可以了解到
clang
交叉编译出 Android 平台可以使用的 libffmpeg.so 库从 NDK20 - NDK22 编译工具链目录结构基本没变,这里以 NDK21 作为演示( NDK 在 windows、linux、mac 中的目录基本一样)
如上图,主要用的就是这几个目录,其中编译 FFmpeg 需要用到的 gcc
库就在 aarch64、arm、x86_64、x86
这几个文件夹中,这里先介绍一下这几个名字在 Android 中的不同平台库的联系。
aarch64:带这个前缀的目录都是与 arm64-v8a 库相关
arm:带这个前缀的目录都是与 armeabi-v7a 库相关
x86_64:带这个前缀的目录都是与 x86_64 库相关
x86:带这个前缀的目录都是与 x86 库相关
1.clang 编译工具
进入目录 llvm->prebuilt->darwin-x86_64->bin
里面都是与交叉编译相关的文件,我们以 clang
进行编译,所以主要关注的是以 clang、clang++
结尾的文件,clang
用于编译 c 文件
、clang++
用于编译 c++
文件。
21 和 i686
上面的 NDK 目录名在各系统上的对应形式:
mac:darwin-x86_64
linux:linux-x86_64
windows:windows-x86_64
注意:下面都以 mac 系统下的 NDK 目录进行介绍
2.编译环境,需要用到的库
库和头文件所在的目录在 darwin-x86_64
下的 sysroot
目录,其中头文件在 include
目录,库在 lib
目录,了解完这些,就可以开始编译了。
二、使用 clang
交叉编译出 Android 平台可以使用的 libffmpeg.so 库
进入 FFmpeg 源码根目录
脚本的主要内容如下:
#!/bin/sh
# NDK 所在的路径
NDK=/Users/mac/Library/Android/sdk/ndk/21.4.7075529
# 需要编译出的平台,这里是 arm64-v8a
ARCH=aarch64
# 支持的最低 Android API
API=21
# 编译后输出目录,在 ffmpeg 源码目录下的 /android/arm64-v8a
OUTPUT=$(pwd)/android/arm64-v8a
# NDK 交叉编译工具链所在路径
TOOLCHAIN=/Users/mac/Library/Android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/darwin-x86_64
build() {
./configure \
--target-os=android \
--prefix=$OUTPUT \
--arch=$ARCH \
--sysroot=$TOOLCHAIN/sysroot \
--disable-static \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-debug \
--disable-doc \
--disable-avdevice \
--enable-shared \
--enable-cross-compile \
--cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \
--cc=$TOOLCHAIN/bin/aarch64-linux-android$API-clang \
--cxx=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++ \
--extra-cflags="-fpic"
make clean all
make -j12
make install
}
build
这个shell脚本,大体上其实还是很容易懂的,比如--disabble-static
禁止输出静态库--enable-shared
输出动态库--arch
用于配置输出的so库是什么架构的--prefix
用于配置输出的so库的存放路径enable-cross-compile
开启多平台编译,也就是可以编译多个平台的库
更多的选项可以查看官网的介绍,这里不再多说。
接下来重点来讲一下几个选项:
--target-os=android
:在旧版本的 FFmpeg
中,对Android平台的支持并不是很完善,并没有 android
这个target,所以在一些比较老的文章中都会提到,编译Android平台的so库,需要对 configure
做修改,否则会按照 linux
标准的方式输出so库,其命名方式和Android的so不一样,Android是无法加载的,所以编译时,FFmpeg
源码版本最好选和笔者的一致。问题一:Linux 下输出的 so 库,Android 下无法加载
--sysroot=$TOOLCHAIN/sysroot
: 用于配置交叉编译环境的 根路径
,编译的时候会默认从这个路径下去寻找 usr/include
usr/lib
这两个路径,进而找到相关的头文件和库文件。NDK20-NDK22
系统的头文件和库文件就是在 $SYSYROOT/usr/include
和 $SYSYROOT/usr/lib
中。extra-cflags 给编译器指定一些编译标志,例如:设置头文件路径
:格式 -I头文件路径
设置编译出的二进制文件为位置无关码文件
:格式 -fpic
至于为什么需要编译出位置无关码文件,就是因为 打包
出的 so 库就是由多个为位置无关码的二进制文件组成的。
extra-ldflags 给链接器指定一些链接标志,例如:设置需要链接的库的路径
:格式 -L库文件路径
输出库并设置名字
:格式 -o 库名
设置需要链接的库
:格式 -l库名
这里需要注意:
假设库名为:a-o 库名
需要带 lib
前缀,与 .so/.a
后缀的部分,如 -o liba.so
-l库名
是不带 lib
前缀,与 .so/.a
后缀的部分,如 -la
关于编译与链接标志的问题,想了解详情可以查看这里。
aarch64-linux-android-
,而编译 armeabi-v7a 平台的就是 arm-linux-androideabi-
,具体是什么,到 交叉编译工具链目录下的 bin
目录查看即可。Android 自带的 clang 工具的具体路径
。通过终端进入到 FFmpeg 源码根目录
,并运行刚刚写好的编译脚本,
sh build_ffmpeg_android.sh
运行结果如下
如上图,红框内的就是我们编译出的所以文件,但是这么多个 so 文件,用起来也麻烦,所以我们要把它们打包
成一个 so 文件。
#!/bin/sh
# ...省略了不变的部分
SYSROOT_L=$TOOLCHAIN/sysroot/usr/lib/aarch64-linux-android
GCC_L=$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/lib/gcc/aarch64-linux-android/4.9.x
build() {
# ...省略了不变的部分
--disable-shared \
--enable-static \
--extra-cflags="-fpic -I$OUTPUT/include" \
--extra-ldflags="-lc -ldl -lm -lz -llog -lgcc -L$OUTPUT/lib"
# ...省略了不变的部分
}
package_library() {
$TOOLCHAIN/bin/aarch64-linux-android-ld -L$OUTPUT/lib -L$GCC_L \
-rpath-link=$SYSROOT_L/$API -L$SYSROOT_L/$API -soname libffmpeg.so \
-shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o $OUTPUT/libffmpeg.so \
-lavcodec -lpostproc -lavfilter -lswresample -lavformat -lavutil -lswscale -lgcc \
-lc -ldl -lm -lz -llog \
--dynamic-linker=/system/bin/linker
# 设置动态链接器,不同平台的不同,android 使用的是/system/bin/linker
}
build
package_library
上面列出的只是片段代码,只列出了做出了改变的部分,大致流程就是:clang 编译出静态库
-> 使用 Android 自带的链接器将编译出的静态库打包成一个动态库
接下来重点来讲一下几个选项:
When using ELF or SunOS, one shared library may require another. This happens when
an `"ld -shared"` link includes a shared library as one of the input files.
When the linker encounters such a dependency when doing a non-shared, non-relocatable
link, it will automatically try to locate the required shared library and include it in
the link, if it is not included explicitly. In such a case, the **-rpath-link** option
specifies the first set of directories to search. The **-rpath-link** option may
specify a sequence of directory names either by specifying a list of names separated by
colons, or by appearing multiple times.
上面是官方给出的介绍,这里我斗胆用一句话概括一下,可能不太准确,但是能理解就行了:
这是传递给链接器的一个标志,当我们使用的库有依赖关系
时,打包就需要按照依赖关系
进行,否则会报错,用了这标志,我们只需要设置库的目录,不需要管依赖关系,链接时链接器帮我们处理。
可以通过别名引用库
,为什么要起别名?因为我们一开始是先打出了多个静态库,静态库已经有它自己的名字了,如果不对它们统一的做别名映射,你会发现你加载库的时候一直报错,说找不到你指定的库文件,不信你可以尝试一下哦^^。问题一
吗,为了解决这个问题,这里设置成 Android 使用的链接器,就是/system/bin/linker
,关于这点,可以看看这里运行结果如下
至此,我们就完成了 FFmpeg 的编译工作。
三、脚本使用介绍
笔者把编译脚本封装了一下,以适应方便的编译出 Android 各个平台的 so 库,脚本链接在文章开头,下面介绍使用步骤:
# 构建的最低支持 API 等级
API=21
# 在什么系统上构建,mac:darwin,linux:linux,windows:windows
OS_TYPE=darwin
# 自己本机 NDK 所在目录
NDK=/Users/mac/Library/Android/sdk/ndk/21.4.7075529
# 目标文件输出目录,默认是当前目录下的 android 目录
OUTPUT=$(pwd)/android/$ABI
执行规则
sh build_ffmpeg_android.sh 后可以附带 1、2、3、4 这四项,下面说明这四项的意义
1:构建出 arm64-v8a 架构的库文件
2:构建出 armeabi-v7a 架构的库文件
3:构建出 x86_64 架构的库文件
4:构建出 x86 架构的库文件
如果想要构建多个平台的,可以附带多项,中间通过空格分隔开即可,如构建全平台:
sh build_ffmpeg_android.sh 1 2 3 4
四、遇到的问题
修改文件权限,再次运行即可:chmod 777 build_ffmpeg_android.sh
解决方法一:
Visual Studio Code 代替记事本,重新编辑
解决方法二:
安装 dos2unix 软件
mac 下:brew install dos2unix
ubuntu 下:sudo apt install dos2unix
使用:dos2unix build_ffmpeg_android.sh
然后再次运行即可
编译x86
库的时候报错,错误如下
这个时候只需要在 ./configure 后加上:--disable-asm 即可,然后重新编译就没问题了,因为 x86
平台移除了寄存器,如果不禁用这一项就会报错,详细原因在这。
gcc
的库在别的目录也有这里容许我吐槽一下,我认为是一个巨坑... 因为我打包的时候一开始用的是别的目录的 gcc ,部分平台的打包 是正常的,但是 armeabi-v7a 平台的一直打包不成功,试了很久才发现现在用的目录也有,并且没问题。如果你 也遇到了同样的问题,那就换成我介绍的目录的 gcc 就没问题了。
五、总结
FFmpeg so库编译
如何跨平台编译能执行在 Android 上的文件
FFmpeg x86 编译问题
链接器的-rpath介绍
将FFmpeg编译成一个libffmpeg.so库