前言
最近在学习 Android 的音视频开发,接触到了 FFmpeg,那如何交叉编译 FFmpeg 才能在Android上使用呢?
经过一番考察,我决定使用这样一套环境来进行编译:Linux + NDK + FFmepg source code
编译步骤
1.获取 FFmpeg 源码
- 解压
- 进入源代码所在文件夹,打开configure文件,并修改
修改前
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
修改后
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
这一步的主要目的是生成Android能够使用的 名称-版本.so文件的格式,不然的话生成的是Linux上使用库,Android不能用
2.获取 NDK
这里建议选择 android-ndk-r17c 的版本。这个版本支持gcc编译,后续最新的版本似乎都是clang编译的模式,个人不是很擅长。本文下面所有的描述,都是基于gcc编译的!
[android-ndk-r17c]3.编写 build for Android 的脚本
参考的一个大佬写的脚本,里面有很多细节的东西,自己会在文章最后再梳理一下。
#!/bin/bash
# Don't forget to install yasm
# Set your own NDK here
NDK=/home/joshua/AndroidNDK/android-ndk-r17c
ARM_PLATFORM=$NDK/platforms/android-14/arch-arm/
ARM_PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
ARM64_PLATFORM=$NDK/platforms/android-14/arch-arm/
ARM64_PREBUILT=$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64
X86_PLATFORM=$NDK/platforms/android-14/arch-arm/
X86_PREBUILT=$NDK/toolchains/x86-4.9/prebuilt/linux-x86_64
X86_64_PLATFORM=$NDK/platforms/android-14/arch-arm/
X86_64_PREBUILT=$NDK/toolchains/x86_64-4.9/prebuilt/linux-x86_64
#NDK r17 sysroot inlcude及lib相关移到了根目录,cflag中添加链接
ISYSROOT=$NDK/sysroot
ASM=$ISYSROOT/usr/include/arm-linux-androideabi
#生成的so及.h文件路径
BUILD_DIR=$PWD/ffmpeg-android
#ffmpeg版本,后面pushd用,所以要建立第2步正确的目录结构
FFMPEG_VERSION="4.3.2"
function build_one
{
if [ $ARCH == "arm" ]
then
PLATFORM=$ARM_PLATFORM
PREBUILT=$ARM_PREBUILT
HOST=arm-linux-androideabi
elif [ $ARCH == "arm64" ]
then
PLATFORM=$ARM64_PLATFORM
PREBUILT=$ARM64_PREBUILT
HOST=aarch64-linux-android
elif [ $ARCH == "x86_64" ]
then
PLATFORM=$X86_64_PLATFORM
PREBUILT=$X86_64_PREBUILT
HOST=x86_64-linux-android
else
PLATFORM=$X86_PLATFORM
PREBUILT=$X86_PREBUILT
HOST=i686-linux-android
fi
#extra-cflags配置-I$ASM -isysroot $ISYSROOT,否则会报出.h找不到
#pushd ffmpeg-$FFMPEG_VERSION
./configure --target-os=linux \
--incdir=$BUILD_DIR/include \
--libdir=$BUILD_DIR/lib/$CPU \
--enable-cross-compile \
--arch=$ARCH \
--cc=$PREBUILT/bin/$HOST-gcc \
--cross-prefix=$PREBUILT/bin/$HOST- \
--nm=$PREBUILT/bin/$HOST-nm \
--sysroot=$PLATFORM \
--extra-cflags="-I$ASM -isysroot $ISYSROOT -I$BUILD_DIR/include -fPIC -DANDROID -Wfatal-errors -Wno-deprecated $OPTIMIZE_CFLAGS" \
--enable-gpl \
--enable-small \
--extra-ldflags="-L$BUILD_DIR/lib/$CPU" \
--enable-shared \
--disable-doc \
--disable-programs \
--disable-stripping \
--disable-symver \
--disable-static \
--pkg-config=/usr/bin/pkg-config \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make -j8
make install
#popd
}
#arm v7vfpv3
#CPU=armv7-a
#ARCH=arm
#OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfpv3-d16 -marm -march=$CPU -D__thumb__ -mthumb"
#PREFIX=$BUILD_DIR/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
#arm v7vfp
#CPU=armv7-a
#ARCH=arm
#OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
#PREFIX=`pwd`/ffmpeg-android/$CPU-vfp
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
#arm v7n
CPU=armv7-a
ARCH=arm
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=neon -marm -march=$CPU -mtune=cortex-a8"
PREFIX=$BUILD_DIR/$CPU
ADDITIONAL_CONFIGURE_FLAG=
build_one
#arm64-v8a
#CPU=arm64-v8a
#ARCH=arm64
#OPTIMIZE_CFLAGS=
#PREFIX=$BUILD_DIR/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
#x86_64
#CPU=x86_64
#ARCH=x86_64
#OPTIMIZE_CFLAGS="-fomit-frame-pointer"
#PREFIX=$BUILD_DIR/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
#x86
#CPU=i686
#ARCH=i686
#OPTIMIZE_CFLAGS="-fomit-frame-pointer"
#PREFIX=$BUILD_DIR/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
4.执行脚本 ./build_android.sh
5.在ffmpeg-android里可以查看生成的头文件和so库
错误处理
在交叉编译时还遇到了一些这样的问题
报错原因:在ffmpeg里面 有 #define B0 和 int B0 冲突了;
解决方法:libavcodec/aaccoder, hevc_mvs, opus_pvq 中的 B0,xB0, yB0 全都改成 b0, xb0,yb0.
日积月累
1.认识 -isysroot
在build_android.sh里,有这样一个编译参数,抄的大佬的,这里记录一下。
--sysroot=$PLATFORM \
--extra-cflags="-I$ASM -isysroot $ISYSROOT -I$BUILD_DIR/include -fPIC -DANDROID -Wfatal-errors -Wno-deprecated $OPTIMIZE_CFLAGS" \
我在使用NDK交叉编译的时候,其实已经发现了NDK和以前使用的SDK不太一样的地方。
一般来说,交叉编译的一个关键位置就是 SYSROOT,在SYSROOT中有交叉编译的头文件和lib库。NDK有着自己独特的目录结构,lib库位于 platforms/android-xx里,而头文件却位于sysroot/usr/include/arm-linux-androideabi 下,如下表所示。
分类 | 头文件位置 | lib库位置 |
---|---|---|
普通SDK | sysroot | sysroot |
NDK | platforms/android-xx | sysroot/usr/include/arm-linux-androideabi |
-isysroot -- i(include),定义 头文件 的位置
-sysroot -- 定义 lib库 的位置
2.认识 armv7-a vfp neon等等
#arm v7vfpv3
#CPU=armv7-a
#ARCH=arm
#OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfpv3-d16 -marm -march=$CPU -D__thumb__ -mthumb"
#PREFIX=$BUILD_DIR/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
#arm v7vfp
#CPU=armv7-a
#ARCH=arm
#OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
#PREFIX=`pwd`/ffmpeg-android/$CPU-vfp
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
#arm v7n
CPU=armv7-a
ARCH=arm
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=neon -marm -march=$CPU -mtune=cortex-a8"
PREFIX=$BUILD_DIR/$CPU
ADDITIONAL_CONFIGURE_FLAG=
build_one
#arm64-v8a
#CPU=arm64-v8a
#ARCH=arm64
#OPTIMIZE_CFLAGS=
#PREFIX=$BUILD_DIR/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
#x86_64
#CPU=x86_64
#ARCH=x86_64
#OPTIMIZE_CFLAGS="-fomit-frame-pointer"
#PREFIX=$BUILD_DIR/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
#x86
#CPU=i686
#ARCH=i686
#OPTIMIZE_CFLAGS="-fomit-frame-pointer"
#PREFIX=$BUILD_DIR/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one
在大佬的build_android.sh,他把CPU=armv7-a的这一行分的特别细,但AS中编译生成的 只有x86,x86_64,arm64-v8a,armv7-a。那么硬浮点or软浮点,vfp or neon,那种编译方式才是 AS 种的 armv7-a呢?
我个人在想,AS 编译出来的 armv7-a 库应该是通用性,编译选项中应该没有那么多弯弯绕绕的选择才对,不然很容易造成不支持的情况。
我又去仔细查了查AS中 compile_commands.json 中,发现了这一段
[
{
"directory": "D:/AndroidProject/DemoNDK/app/.cxx/cmake/debug/armeabi-v7a",
"command": "C:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk\\ndk\\21.1.6352462\\toolchains\\llvm\\prebuilt\\windows-x86_64\\bin\\clang++.exe --target=armv7-none-linux-androideabi23 --gcc-toolchain=C:/Users/Administrator/AppData/Local/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/windows-x86_64 --sysroot=C:/Users/Administrator/AppData/Local/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/windows-x86_64/sysroot -Dnative_lib_EXPORTS -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a -mthumb -Wformat -Werror=format-security -O0 -fno-limit-debug-info -fPIC -o CMakeFiles\\native-lib.dir\\native-lib.cpp.o -c D:\\AndroidProject\\DemoNDK\\app\\src\\main\\cpp\\native-lib.cpp",
"file": "D:\\AndroidProject\\DemoNDK\\app\\src\\main\\cpp\\native-lib.cpp"
}
]
也就是说,在编译 armv7-a 下lib库的时候,在编译选项中只有 "-march=armv7-a"
这个问题,在这里也没得到一个定论,要在以后的学习中多多留意,来探索如何设置armv7-a 库的编译参数,才能让其通用性最好;或是在适当的时候进行适配,来加强性能。