现在编译 FFmpeg 已经全面采用 Clang 了,gcc 被高版本 NDK 废弃,所以从网上搜索出来的编译脚本既有使用低版本 NDK 编译 FFmpeg 的,也有采用高版本 NDK 编译的。采用低版本 NDK 编译 FFmpeg 虽然也是一种手段,但是采用高版本 NDK 编译 FFmpeg 一定是“大势所趋”。
另外就算采用低版本 NDK 去编译 FFmpeg,你会发现高版本的 FFmpeg 编译会报各种错误,有的人甚至修改了 FFmpeg 源码,虽然修改源码最终可以编译完成,但是终究不是解决问题的良方,毕竟注释掉的代码也是屏蔽了库本身的一些功能点,除非确保不使用这些功能,而且也明确知道这些屏蔽或者修改点,不会对 FFmpeg 内部模块调用造成影响。
在开始编写整个编译脚本之前,先要了解一下 GCC、LLVM、Clang 的“来龙去脉”,这有助于我们了解 NDK 编译链到底做了些什么工作,指导我们如何编写正确的编译脚本。
在 Android 实际使用 FFmpeg 的过程中,偶尔需要使用到 fdk-aac 作为 aac 音频编码器,另外也会用到 x264 来编码 h264,所以编译过程中也集成了 fdk-aac 和 x264 来扩展 FFmpeg 的编码器。当然具体如何灵活定制自己的 FFmpeg ,这需要自行去开启一些编解码器开关等。
一、GCC LLVM Clang
传统的编译器通常分为三个部分,前端(frontEnd)、优化器(Optimizer)和后端(backEnd)。在编译过程中,前端主要负责词法和语法分析,将源代码转化为抽象语法树;优化器则是在前端的基础上,对得到的中间代码进行优化,使代码更加高效;后端则是将已经优化的中间代码转化为针对各自平台的机器代码。
GCC
GCC(GNU Compiler Collection,GNU 编译器套装),是一套由 GNU 开发的编程语言编译器。GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C 语言。GCC 快速演进,变得可处理 C++、Fortran、Pascal、Objective-C 和 Java,以及 Ada 等他语言。
LLVM
LLVM (Low Level Virtual Machine,底层虚拟机) 提供了与编译器相关的支持,能够进行程序语言的编译期优化、链接优化、在线编译优化和代码生成。简而言之,可以作为多种编译器的后端来使用。
苹果公司一直使用 GCC 作为官方的编译器。GCC 作为一款开源的编译器,一直做得不错,但 Apple 对编译工具会提出更高的要求。原因主要有以下两点:
其一,是 Apple 对 Objective-C 语言(包括后来对 C 语言)新增很多特性,但 GCC 开发者并不买 Apple 的账——不给实现,因此索性后来两者分成两条分支分别开发,这也造成 Apple 的编译器版本远落后于 GCC 的官方版本。
其二,GCC 的代码耦合度太高,很难独立,而且越是后期的版本,代码质量越差,但 Apple 想做的很多功能(比如更好的 IDE 支持),需要模块化的方式来调用 GCC,但 GCC 一直不给做。
Clang
Clang 是 LLVM 的前端,可以用来编译 C、C++ 和 Objective-C 等语言。Clang 则是以 LLVM 为后端的一款高效易用,并且与 IDE 结合很好的编译前端。
Clang 只支持 C、C++ 和 Objective-C 三种语言。2007 年开始开发,C 编译器最早完成,而由于 Objective-C 只是 C 语言的一个简单扩展,相对简单,很多情况下甚至可以等价地改写为 C 语言对 Objective-C 运行库的函数调用,因此在 2009 年时,已经完全可以用于生产环境。C++ 在后来也得到了支持。
GCC 和 Clang 对比
Clang 优点
速度快:通过编译 OS X 上几乎包含了所有 C 头文件的 carbon.h 的测试,包括预处理 (Preprocess),语法(lex),解析(parse),语义分析(Semantic Analysis),抽象语法树生成(Abstract Syntax Tree)的时间,Clang 比 GCC 快 2 倍多。
内存占用小:Clang 内存占用是源码的 130%,Apple GCC 则超过 10 倍。
诊断信息可读性强:其中错误的语法不但有源码提示,还会在错误的调用和相关上下文的下方有~~~~~和^的提示,相比之下 GCC 的提示很天书。
兼容性好:Clang 从一开始就被设计为一个 API,允许它被源代码分析工具和 IDE 集成。GCC 被构建成一个单一的静态编译器,这使得它非常难以被作为 API 并集成到其他工具中。
Clang 有静态分析,GCC 没有。
Clang 使用 BSD 许可证,商业友好,GCC 使用 GPL 许可证。
GCC 优点
支持 JAVA、ADA 和 FORTRAN 等。
GCC 支持更多平台。
GCC 更流行,广泛使用,支持完备。
GCC 基于 C,不需要 C++ 编译器即可编译。
二、NDK 版本
Android NDK r17c(2018 年 6 月)
- GCC 不再受支持,且将在 NDK r18 中移除。
- libc++ 现在是 CMake 和独立工具链的默认 STL。如果您已手动选择其他 STL,我们强烈建议您迁移到 libc++。请注意,ndk-build 仍默认为无 STL。
- gnustl 和 stlport 已被弃用,并且将在 NDK r18 中被移除。
- 取消了对 ARMv5 (armeabi)、MIPS 和 MIPS64 的支持。尝试编译任何此类 ABI 都会导致错误。
- r18 中将取消对 ICS(android-14 和 android-15)的支持。
从 2019 年 8 月开始,在您上传 APK 时,Play 商店会要求支持 64 位系统。请立即开始移植,以免到时出现意外。
从 NDK 版本更新说明中不难知道 GCC 在 r17c 之后彻底被废弃了,所以使用 GCC 编译 FFmpeg 就得使用 r17c 之前的 NDK 版本。
这里采用了 Android NDK r21e LTS(2021 年 1 月)作为 NDK 编译版本。合并 FFmpeg 静态库到到一个 so 则使用了 arm-linux-androideabi-ld 和 aarch64-linux-android-ld 工具。
三、GNU binutils
NDK 交叉编译 FFmpeg 期间使用了不少 GNU binutils,理解了它们的作用,才能明白 FFmpeg 编译脚本中的一些关键编译链配置项的含义。
工具 | 描述 |
---|---|
addr2line | 给出一个可执行文件的内部地址,addr2line 使用文件中的调试信息将地址翻译成源代码文件名和行号。 |
ar | 这是一个程序,可通过从文档中增加、删除和析取文件来维护库文件。通常使用该工具是为了创建和管理链接程序使用的目标库文档。 |
as | GNU 汇编器。实际上它是一族汇编器,因为它可以被编译或能够在各种不同平台上工作。 |
c++filt | 程序接受被 C++ 编译程序转换过的名字(不是被重载的) ,而且将该名字翻译成初始形式。 |
elfedit | 更新 ELF 文件的 ELF 头。 |
gprof | 该程序会监督编译程序的执行过程,并报告程序中各个函数的运行时间,可以根据所提供的配置文件来优化程序。 |
ld | GNU 链接程序。该程序将目标文件的集合组合成可执行程序。 |
ld.bfd | 到 ld 的硬链接。 |
libbfd | 二进制文件描述器库。该程序是 binutils 包的一部分 |
libiberty | 包含多个 GNU 程序会使用的途径,包括 getopt、obstack、strerror、strtol 和 strtoul。 |
libopcodes | 一个库,用于处理 opcodes——处理器指令的 “可读文本” 版本;用于编制 objdump 这样的工具。 |
nlmconv | 将可重定位的目标文件转换成 NetWare 可加载模块(NetWare Loadable Module,NLM) 。 |
nm | 列出目标文件中定义的符号。 |
objcopy | 将目标文件从一种二进制格式复制和翻译到另外一种。 |
objdump | 显示一个或多个目标文件中保存的多种不同信息。 |
ranlib | 创建和添加到 ar 文档的索引。该索引被 ld 使用来定位库中的模块。 |
readelf | 从 ELF 格式的目标文件显示信息。 |
size | 列出目标文件中每个部分的名字和尺寸。 |
strings | 浏览所有类型的文件,析取出用于显示的字符串。 |
strip | 从目标文件或文档库中去掉符号表,以及其他调试所需的信息。 |
windres | Window 资源文件编译程序。 |
下面来看一下 android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin 都有那些工具。不难看出上面列表里的工具基本上都涵盖了,我们编译 FFmpeg 只使用了很少一部分工具,当然编译不同的平台工具类路径要改变。比如用到了 gcc、ld、nm 等。
snake@ubuntu:~/Android/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin$ ls
arm-linux-androideabi-addr2line arm-linux-androideabi-gcov
arm-linux-androideabi-ar arm-linux-androideabi-gcov-tool
arm-linux-androideabi-as arm-linux-androideabi-gprof
arm-linux-androideabi-c++ arm-linux-androideabi-ld
arm-linux-androideabi-c++filt arm-linux-androideabi-ld.bfd
arm-linux-androideabi-cpp arm-linux-androideabi-ld.gold
arm-linux-androideabi-dwp arm-linux-androideabi-nm
arm-linux-androideabi-elfedit arm-linux-androideabi-objcopy
arm-linux-androideabi-g++ arm-linux-androideabi-objdump
arm-linux-androideabi-gcc arm-linux-androideabi-ranlib
arm-linux-androideabi-gcc-4.9 arm-linux-androideabi-readelf
arm-linux-androideabi-gcc-4.9.x arm-linux-androideabi-size
arm-linux-androideabi-gcc-ar arm-linux-androideabi-strings
arm-linux-androideabi-gcc-nm arm-linux-androideabi-strip
arm-linux-androideabi-gcc-ranlib
四、详细编译脚本
最终的组织目录结构如下:
FFmpeg4Android 文件夹下的 fdk-aac、ffmpeg 和 x264 分别是对应的源码文件存放目录。tools 目录下存放对应的编译脚本,而最外层的 build_arm64-v8a.sh 和 build_armeabi-v7a.sh 分别用来编译 arm64-v8a 和 armeabi-v7a 版本的 so 库。
编译环境
OS 要求:Ubuntu 16.04
编译 FFmpeg 源码包要求:
FFmpeg 4.4.1
fdk-acc 2.0.2
x264 x264-snapshot-20191217
NDK:android-ndk-r21e
一定要注意设置正确的编译链路径,错误的编译链直接导致编译各种报错,让你丈二的和尚摸不着头脑!如果采用相同的脚本编译,请替换下面的 NDK 路径为自己的正确 NDK 路径。
build_arm64-v8a.sh 和 build_armeabi-v7a.sh 是最外层调用的编译入口脚本,直接执行即可分别生成对应的 ABI so 文件。
build_arm64-v8a.sh
#!/bin/sh
# 根目录
export ROOT_SOURCE=$(cd `dirname $0`; pwd)
export NDK=/home/snake/Android/android-ndk-r21e
export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64
export SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
# 编译的API
export ANDROID_API=21
export TARGET=aarch64-linux-android
export AR=$TOOLCHAIN/bin/aarch64-linux-android-ar
export CC=$TOOLCHAIN/bin/$TARGET$ANDROID_API-clang
export CXX=$TOOLCHAIN/bin/$TARGET$ANDROID_API-clang++
export LD=$TOOLCHAIN/bin/aarch64-linux-android-ld
export RANLIB=$TOOLCHAIN/bin/aarch64-linux-android-ranlib
export STRIP=$TOOLCHAIN/bin/aarch64-linux-android-strip
export NM=$TOOLCHAIN/bin/aarch64-linux-android-nm
# 编译目录是否存在
if [ ! -d "build" ]; then
mkdir ${ROOT_SOURCE}/build/
fi
# 判断编译目录是否存在源码, 不存在则复制到编译目录
if [ ! -d "build/ffmpeg" ]; then
cp -R ffmpeg/ ${ROOT_SOURCE}/build/ffmpeg/
fi
if [ ! -d "build/x264" ]; then
cp -R x264/ ${ROOT_SOURCE}/build/x264/
fi
if [ ! -d "build/fdk-aac" ]; then
cp -R fdk-aac/ ${ROOT_SOURCE}/build/fdk-aac/
fi
# 到工具目录执行编译
cd tools
# 编译x264
if [ ! -x "libx264/build_x264_arm64-v8a.sh" ]; then
echo "can not find x264 build script"
else
chmod a+x libx264/build_x264_arm64-v8a.sh
./libx264/build_x264_arm64-v8a.sh
fi
# 编译fdk-aac
if [ ! -x "fdk-aac/build_fdk_aac_arm64_v8a.sh" ]; then
echo "can not find x264 build script"
else
chmod a+x ./fdk-aac/build_fdk_aac_arm64_v8a.sh
./fdk-aac/build_fdk_aac_arm64_v8a.sh
fi
# 编译ffmpeg
if [ ! -x "ffmpeg/build_ffmpeg_arm64-v8a.sh" ]; then
echo "can not find ffmpeg build script"
else
chmod a+x ./ffmpeg/build_ffmpeg_arm64-v8a.sh
./ffmpeg/build_ffmpeg_arm64-v8a.sh
fi
build_armeabi-v7a.sh
#!/bin/sh
# 根目录
export ROOT_SOURCE=$(cd `dirname $0`; pwd)
export NDK=/home/snake/Android/android-ndk-r21e
export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64
export SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
# 编译的API
export ANDROID_API=19
export AR=$TOOLCHAIN/bin/arm-linux-androideabi-ar
export CC=$TOOLCHAIN/bin/armv7a-linux-androideabi$ANDROID_API-clang
export CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi$ANDROID_API-clang++
export LD=$TOOLCHAIN/bin/arm-linux-androideabi-ld
export RANLIB=$TOOLCHAIN/bin/arm-linux-androideabi-ranlib
export STRIP=$TOOLCHAIN/bin/arm-linux-androideabi-strip
export NM=$TOOLCHAIN/bin/arm-linux-androideabi-nm
# 编译目录是否存在
if [ ! -d "build" ]; then
mkdir ${ROOT_SOURCE}/build/
fi
# 判断编译目录是否存在源码, 不存在则复制到编译目录
if [ ! -d "build/ffmpeg" ]; then
cp -R ffmpeg/ ${ROOT_SOURCE}/build/ffmpeg/
fi
if [ ! -d "build/x264" ]; then
cp -R x264/ ${ROOT_SOURCE}/build/x264/
fi
if [ ! -d "build/fdk-aac" ]; then
cp -R fdk-aac/ ${ROOT_SOURCE}/build/fdk-aac/
fi
# 到工具目录执行编译
cd tools
# 编译x264
if [ ! -x "libx264/build_x264_armeabi-v7a.sh" ]; then
echo "can not find x264 build script"
else
chmod a+x ./libx264/build_x264_armeabi-v7a.sh
./libx264/build_x264_armeabi-v7a.sh
fi
# 编译fdk-aac
if [ ! -x "fdk-aac/build_fdk-aac_armeabi_v7a.sh" ]; then
echo "can not find x264 build script"
else
chmod a+x ./fdk-aac/build_fdk-aac_armeabi_v7a.sh
./fdk-aac/build_fdk-aac_armeabi_v7a.sh
fi
# 编译ffmpeg
if [ ! -x "ffmpeg/build_ffmpeg_armeabi-v7a.sh" ]; then
echo "can not find ffmpeg build script"
else
chmod a+x ./ffmpeg/build_ffmpeg_armeabi-v7a.sh
./ffmpeg/build_ffmpeg_armeabi-v7a.sh
fi
fdk-aac 2.0.2 编译需要修改源代码,如果不修改会报错 log/log.h 无法找到,需要做对应的代码行注释。
修改 fdk-aac/libSBRdec/src/lpp_tran.cpp,分别注释 122、342、940 行,源码里明显是判断 __ANDROID__ 是否目标平台为 android,但是 android 系统的 log 路径并非 log/log.h,而是 android/log.h,因此直接注释 fdk-aac 源码即可,因为这部分功能在 fdk-aac 中主要为打印出错 log 而已。
build_fdk_aac_arm64_v8a.sh
#!/bin/bash
# fdk-aac 源码目录
FDK_AAC_SOURCE=${ROOT_SOURCE}/build/fdk-aac
# 输出路径
PREFIX=${FDK_AAC_SOURCE}/android/arm64-v8a
export AS=$TOOLCHAIN/bin/aarch64-linux-android-as
CFLAGS=" "
FLAGS="--enable-static --host=aarch64-linux-android --target=android --disable-asm"
export CXXFLAGS=$CFLAGS
export CFLAGS=$CFLAGS
cd ${FDK_AAC_SOURCE}
./configure $FLAGS \
--enable-pic \
--enable-strip \
--prefix=${PREFIX}
make clean
make -j4
make install
build_fdk-aac_armeabi_v7a.sh
#!/bin/bash
# fdk-aac 源码目录
FDK_AAC_SOURCE=${ROOT_SOURCE}/build/fdk-aac
# 输出路径
PREFIX=${FDK_AAC_SOURCE}/android/armeabi-v7a
export AS=$TOOLCHAIN/bin/arm-linux-androideabi-as
CFLAGS=""
FLAGS="--enable-static --host=arm-linux-android --target=android --disable-asm "
export CXXFLAGS=$CFLAGS
export CFLAGS=$CFLAGS
cd ${FDK_AAC_SOURCE}
./configure $FLAGS \
--enable-pic \
--enable-strip \
--prefix=${PREFIX}
$ADDITIONAL_CONFIGURE_FLAG
make clean
make -j4
make install
FFmpeg 编译脚本中使用到了 cc、cxx(这个应该是不需要的,FFmpeg 是纯 C 编写的,只不过这部分配置使用了别人成功编译的脚本,没有剔除进行测试)、ar、nm、ranlib、strip、ld 等工具。
build_ffmpeg_arm64-v8a.sh
#!/bin/sh
#编译目录
BUILD_SOURCE=${ROOT_SOURCE}/build
#FFmpeg的源码目录
FFMPEG_SOURCE=${BUILD_SOURCE}/ffmpeg
# x264的目录
X264_INCLUDE=${BUILD_SOURCE}/x264/android/arm64-v8a/include
X264_LIB=${BUILD_SOURCE}/x264/android/arm64-v8a/lib
# fdk-aac的目录
FDK_AAC_INCLUDE=${BUILD_SOURCE}/fdk-aac/android/arm64-v8a/include
FDK_AAC_LIB=${BUILD_SOURCE}/fdk-aac/android/arm64-v8a/lib
# arm v8a
CPU=arm64-v8a
OPTIMIZE_CFLAGS="-march=armv8-a "
ADDI_CFLAGS=""
PREFIX=${FFMPEG_SOURCE}/android/${CPU}
export AS=$TOOLCHAIN/bin/aarch64-linux-android-as
cd $FFMPEG_SOURCE
# 移除旧的文件夹
rm -rf ${PREFIX}
# 重新创建新的文件夹
mkdir -p ${PREFIX}
# 配置
./configure \
--prefix=${PREFIX} \
--arch=aarch64 \
--cpu=armv8-a \
--target-os=android \
--enable-cross-compile \
--cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android- \
--sysroot=${SYSROOT} \
--extra-cflags="-I${X264_INCLUDE} -I${FDK_AAC_INCLUDE} -I${SYSROOT}/usr/include" \
--extra-ldflags="-L${X264_LIB} -L${FDK_AAC_LIB}" \
--cc=${CC} \
--ar=${AR} \
--cxx=${CXX} \
--ranlib=${RANLIB} \
--strip=${STRIP} \
--nm=${NM} \
--disable-shared \
--enable-nonfree \
--enable-static \
--enable-gpl \
--enable-version3 \
--enable-pthreads \
--enable-runtime-cpudetect \
--enable-small \
--enable-network \
--disable-iconv \
--enable-asm \
--enable-neon \
--enable-yasm \
--disable-encoders \
--enable-libx264 \
--enable-libfdk_aac \
--enable-encoder=h263 \
--enable-encoder=libx264 \
--enable-encoder=libfdk-aac \
--enable-encoder=aac \
--enable-encoder=mpeg4 \
--enable-encoder=mjpeg \
--enable-encoder=png \
--enable-encoder=gif \
--enable-encoder=bmp \
--disable-muxers \
--enable-muxer=h264 \
--enable-muxer=flv \
--enable-muxer=gif \
--enable-muxer=image2 \
--enable-muxer=mp3 \
--enable-muxer=dts \
--enable-muxer=mp4 \
--enable-muxer=mov \
--enable-muxer=mpegts \
--enable-muxer=rtsp \
--disable-decoders \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--enable-hwaccel=h264_mediacodec \
--enable-decoder=aac \
--enable-decoder=aac_latm \
--enable-decoder=mp3 \
--enable-decoder=h263 \
--enable-decoder=h264 \
--enable-decoder=mpeg4 \
--enable-decoder=mjpeg \
--enable-decoder=gif \
--enable-decoder=png \
--enable-decoder=bmp \
--enable-decoder=yuv4 \
--disable-demuxers \
--enable-demuxer=image2 \
--enable-demuxer=h263 \
--enable-demuxer=h264 \
--enable-demuxer=flv \
--enable-demuxer=gif \
--enable-demuxer=aac \
--enable-demuxer=ogg \
--enable-demuxer=dts \
--enable-demuxer=mp3 \
--enable-demuxer=mov \
--enable-demuxer=m4v \
--enable-demuxer=concat \
--enable-demuxer=mpegts \
--enable-demuxer=mjpeg \
--enable-demuxer=mpegvideo \
--enable-demuxer=rawvideo \
--enable-demuxer=yuv4mpegpipe \
--enable-demuxer=rtp \
--enable-demuxer=rtsp \
--enable-demuxer=sdp \
--disable-parsers \
--enable-parser=aac \
--enable-parser=ac3 \
--enable-parser=h264 \
--enable-parser=mjpeg \
--enable-parser=png \
--enable-parser=bmp \
--enable-parser=mpegvideo \
--enable-parser=mpegaudio \
--disable-protocols \
--enable-protocol=file \
--enable-protocol=hls \
--enable-protocol=concat \
--enable-protocol=rtmp \
--enable-protocol=rtmpe \
--enable-protocol=rtmps \
--enable-protocol=rtmpt \
--enable-protocol=rtmpte \
--enable-protocol=rtmpts \
--enable-protocol=rtp \
--enable-protocol=udp \
--enable-protocol=tcp \
--disable-filters \
--disable-filters \
--enable-filter=aresample \
--enable-filter=asetpts \
--enable-filter=setpts \
--enable-filter=ass \
--enable-filter=scale \
--enable-filter=concat \
--enable-filter=atempo \
--enable-filter=movie \
--enable-filter=overlay \
--enable-filter=rotate \
--enable-filter=transpose \
--enable-filter=hflip \
--enable-zlib \
--disable-outdevs \
--disable-doc \
--disable-ffplay \
--disable-ffmpeg \
--disable-debug \
--disable-ffprobe \
--disable-postproc \
--disable-avdevice \
--disable-symver \
--disable-stripping \
--extra-cflags="-Os -fpic ${OPTIMIZE_CFLAGS}" \
--extra-ldflags="${ADDI_LDFLAGS}" \
${ADDITIONAL_CONFIGURE_FLAG}
make clean
make -j8
make install
# 合并到libffmpeg.so
${LD} \
-rpath-link=$NDK/platforms/android-$ANDROID_API/arch-arm64/usr/lib \
-L$NDK/platforms/android-$ANDROID_API/arch-arm64/usr/lib \
-L${PREFIX}/lib \
-L${X264_LIB} \
-L${FDK_AAC_LIB} \
-soname libffmpeg.so -shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o \
${PREFIX}/libffmpeg.so \
libavcodec/libavcodec.a \
libavfilter/libavfilter.a \
libswresample/libswresample.a \
libavformat/libavformat.a \
libavutil/libavutil.a \
libswscale/libswscale.a \
${X264_LIB}/libx264.a \
${FDK_AAC_LIB}/libfdk-aac.a \
-lc -lm -lz -ldl -llog --dynamic-linker=/system/bin/linker \
${TOOLCHAIN}/lib/gcc/aarch64-linux-android/4.9.x/libgcc_real.a
# strip 精简文件
$STRIP $PREFIX/libffmpeg.so
#回到根目录
cd ${ROOT_SOURCE}
build_ffmpeg_armeabi-v7a.sh
#!/bin/sh
#编译目录
BUILD_SOURCE=${ROOT_SOURCE}/build
#FFmpeg的源码目录
FFMPEG_SOURCE=${BUILD_SOURCE}/ffmpeg
# x264的目录
X264_INCLUDE=${BUILD_SOURCE}/x264/android/armeabi-v7a/include
X264_LIB=${BUILD_SOURCE}/x264/android/armeabi-v7a/lib
# fdk-aac的目录
FDK_AAC_INCLUDE=${BUILD_SOURCE}/fdk-aac/android/armeabi-v7a/include
FDK_AAC_LIB=${BUILD_SOURCE}/fdk-aac/android/armeabi-v7a/lib
# arm v7vfp
CPU=armeabi-v7a
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=neon -marm -march=armv7-a "
ADDI_CFLAGS="-marm"
PREFIX=${FFMPEG_SOURCE}/android/${CPU}
export AS=$TOOLCHAIN/bin/arm-linux-androideabi-as
cd $FFMPEG_SOURCE
# 移除旧的文件夹
rm -rf ${PREFIX}
# 重新创建新的文件夹
mkdir -p ${PREFIX}
# 配置
./configure \
--prefix=${PREFIX} \
--arch=arm \
--cpu=armv7-a \
--target-os=android \
--enable-cross-compile \
--cross-prefix=${TOOLCHAIN}/bin/arm-linux-androideabi- \
--sysroot=${SYSROOT} \
--extra-cflags="-I${X264_INCLUDE} -I${FDK_AAC_INCLUDE} -I${SYSROOT}/usr/include" \
--extra-ldflags="-L${X264_LIB} -L${FDK_AAC_LIB}" \
--cc=${CC} \
--ar=${AR} \
--cxx=${CXX} \
--ranlib=${RANLIB} \
--strip=${STRIP} \
--nm=${NM} \
--disable-shared \
--enable-nonfree \
--enable-static \
--enable-gpl \
--enable-version3 \
--enable-pthreads \
--enable-runtime-cpudetect \
--enable-small \
--enable-network \
--disable-iconv \
--enable-asm \
--enable-neon \
--enable-yasm \
--disable-encoders \
--enable-libfdk-aac \
--enable-libx264 \
--enable-encoder=h263 \
--enable-encoder=libx264 \
--enable-encoder=libfdk_aac \
--enable-encoder=aac \
--enable-encoder=mpeg4 \
--enable-encoder=mjpeg \
--enable-encoder=png \
--enable-encoder=gif \
--enable-encoder=bmp \
--disable-muxers \
--enable-muxer=h264 \
--enable-muxer=flv \
--enable-muxer=gif \
--enable-muxer=image2 \
--enable-muxer=mp3 \
--enable-muxer=dts \
--enable-muxer=mp4 \
--enable-muxer=mov \
--enable-muxer=mpegts \
--enable-muxer=rtsp \
--disable-decoders \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--enable-hwaccel=h264_mediacodec \
--enable-decoder=aac \
--enable-decoder=aac_latm \
--enable-decoder=mp3 \
--enable-decoder=h263 \
--enable-decoder=h264 \
--enable-decoder=mpeg4 \
--enable-decoder=mjpeg \
--enable-decoder=gif \
--enable-decoder=png \
--enable-decoder=bmp \
--enable-decoder=yuv4 \
--disable-demuxers \
--enable-demuxer=image2 \
--enable-demuxer=h263 \
--enable-demuxer=h264 \
--enable-demuxer=flv \
--enable-demuxer=gif \
--enable-demuxer=aac \
--enable-demuxer=ogg \
--enable-demuxer=dts \
--enable-demuxer=mp3 \
--enable-demuxer=mov \
--enable-demuxer=m4v \
--enable-demuxer=concat \
--enable-demuxer=mpegts \
--enable-demuxer=mjpeg \
--enable-demuxer=mpegvideo \
--enable-demuxer=rawvideo \
--enable-demuxer=yuv4mpegpipe \
--enable-demuxer=rtp \
--enable-demuxer=rtsp \
--enable-demuxer=sdp \
--disable-parsers \
--enable-parser=aac \
--enable-parser=ac3 \
--enable-parser=h264 \
--enable-parser=mjpeg \
--enable-parser=png \
--enable-parser=bmp\
--enable-parser=mpegvideo \
--enable-parser=mpegaudio \
--disable-protocols \
--enable-protocol=file \
--enable-protocol=hls \
--enable-protocol=concat \
--enable-protocol=rtmp \
--enable-protocol=rtmpe \
--enable-protocol=rtmps \
--enable-protocol=rtmpt \
--enable-protocol=rtmpte \
--enable-protocol=rtmpts \
--enable-protocol=rtp \
--enable-protocol=udp \
--enable-protocol=tcp \
--disable-filters \
--enable-filter=aresample \
--enable-filter=asetpts \
--enable-filter=setpts \
--enable-filter=ass \
--enable-filter=scale \
--enable-filter=concat \
--enable-filter=atempo \
--enable-filter=movie \
--enable-filter=overlay \
--enable-filter=rotate \
--enable-filter=transpose \
--enable-filter=hflip \
--enable-zlib \
--disable-outdevs \
--disable-doc \
--disable-ffplay \
--disable-ffmpeg \
--disable-debug \
--disable-ffprobe \
--disable-postproc \
--disable-avdevice \
--disable-symver \
--disable-stripping \
--extra-cflags="-Os -fpic ${OPTIMIZE_CFLAGS}" \
--extra-ldflags="${ADDI_LDFLAGS}" \
${ADDITIONAL_CONFIGURE_FLAG}
make clean
make -j8
make install
# 合并到libffmpeg.so
${LD} \
-rpath-link=$NDK/platforms/android-$ANDROID_API/arch-arm/usr/lib \
-L$NDK/platforms/android-$ANDROID_API/arch-arm/usr/lib \
-L${PREFIX}/lib \
-L${X264_LIB} \
-L${FDK_AAC_LIB} \
-soname libffmpeg.so -shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o \
${PREFIX}/libffmpeg.so \
libavcodec/libavcodec.a \
libavfilter/libavfilter.a \
libswresample/libswresample.a \
libavformat/libavformat.a \
libavutil/libavutil.a \
libswscale/libswscale.a \
${X264_LIB}/libx264.a \
${FDK_AAC_LIB}/libfdk-aac.a \
-lc -lm -lz -ldl -llog --dynamic-linker=/system/bin/linker \
${TOOLCHAIN}/lib/gcc/arm-linux-androideabi/4.9.x/libgcc_real.a
# strip 精简文件
$STRIP $PREFIX/libffmpeg.so
#回到根目录
cd ${ROOT_SOURCE}
build_x264_arm64-v8a.sh
#!/bin/sh
# 编译选项
EXTRA_CFLAGS="-march=armv8-a -D__ANDROID__ -D__ARM_ARCH_8__ -D__ARM_ARCH_8A__"
# x264 源码目录
X264_SOURCE=${ROOT_SOURCE}/build/x264
# 输出路径
PREFIX=${X264_SOURCE}/android/arm64-v8a
# 配置和编译
cd ${X264_SOURCE}
./configure \
--host=aarch64-linux-android \
--sysroot=${SYSROOT} \
--cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android- \
--prefix=${PREFIX} \
--enable-static \
--enable-pic \
--enable-strip \
--disable-cli \
--disable-win32thread \
--disable-avs \
--disable-swscale \
--disable-lavf \
--disable-ffms \
--disable-gpac \
--disable-lsmash \
--extra-cflags="-Os -fpic ${EXTRA_CFLAGS}" \
--extra-ldflags="" \
${ADDITIONAL_CONFIGURE_FLAG}
make clean
make -j4
make install
build_x264_armeabi-v7a.sh
#!/bin/sh
# 编译选项
EXTRA_CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=neon -D__ARM_ARCH_7__ -D__ARM_ARCH_7A__"
# x264 源码目录
X264_SOURCE=${ROOT_SOURCE}/build/x264
# 输出路径
PREFIX=${X264_SOURCE}/android/armeabi-v7a
# 配置和编译
cd ${X264_SOURCE}
./configure \
--host=arm-linux-androideabi \
--cross-prefix=${TOOLCHAIN}/bin/arm-linux-androideabi- \
--sysroot=${SYSROOT} \
--prefix=${PREFIX} \
--enable-static \
--enable-pic \
--enable-strip \
--disable-cli \
--disable-win32thread \
--disable-avs \
--disable-swscale \
--disable-lavf \
--disable-ffms \
--disable-gpac \
--disable-lsmash \
--extra-cflags="-Os -fpic ${EXTRA_CFLAGS}" \
--extra-ldflags="" \
${ADDITIONAL_CONFIGURE_FLAG}
make clean
make -j4
make install
最终生成的 libffmpeg.so 文件和头文件在 FFmpeg4Android/build/ffmpeg/android/armeabi-v7a 和
FFmpeg4Android/build/ffmpeg/android/arm64-v8a 下。fdk-aac 和 x264 的头文件位于自己的 build 目录下。
思考:以上的很多编译链工具看样子还是使用了 GNU binutils,实际上也有对应的 llvm 工具,比如 android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin 目录下可以看到:
llvm-addr2line
llvm-ar
llvm-as
llvm-cfi-verify
llvm-config
llvm-cov
llvm-dis
llvm-lib
llvm-link
llvm-modextract
llvm-nm
llvm-objcopy
llvm-objdump
llvm-profdata
llvm-ranlib
llvm-readelf
llvm-readobj
llvm-size
llvm-strings
llvm-strip
llvm-symbolizer
可能完全可以使用 llvm 里的工具替换 GNU binutils 做为 FFmpeg 编译链。
在 build_armeabi-v7a.sh 中做了个测试,的确可以正常编译出 libffmpeg.so,只不过中间编译 x264 会报一些 error,但全部被自动 ignored 掉了。
/home/snake/Android/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip: error: 'common/arm/cpu-a.o': not stripping symbol 'cpu_enable_armv7_counter' because it is named in a relocation
Makefile:293: recipe for target 'common/arm/cpu-a.o' failed
make: [common/arm/cpu-a.o] Error 1 (ignored)
build_armeabi-v7a.sh 编译链修改点
export AR=$TOOLCHAIN/bin/llvm-ar
......
export LD=$TOOLCHAIN/bin/ld.lld
export RANLIB=$TOOLCHAIN/bin/llvm-ranlib
export STRIP=$TOOLCHAIN/bin/llvm-strip
export NM=$TOOLCHAIN/bin/llvm-nm
参考资料:
- https://developer.android.google.cn/ndk/downloads/revision_history
- https://www.kernel.org/doc/html/latest/kbuild/llvm.html