这篇我们来学习实践ffmpeg的交叉编译,其中会涉及到ffmpeg的版本、NDK的版本、编译脚本的编写、Gradler ABI处理 以及 CMakeLists.txt的针对不同ndk版本脚步的编写
在交叉编译的时候由于平台差异性大,需要工具来解决这一问题,就出现了各种工具链,即Toolchains。而NDK提供了交叉编译的一整套工具的集合,不同版本和配置会有不同差异,很容易就会出现各种问题,是一个不断折腾的过程,),本文记录学习实践的过程,ffmpeg3.3.9+ndk16,以及ffmpeg4.3.1或者ffmpeg4.2.4+ndk21 欢迎交流讨论。
下载NDK
NDK有很多版本 14,16,21等等,我们该选择什么版本比较好呐?
现在一般都选用16以上的,16时编译工具链做了不少变化。而不同的ffmpeg版本支持的NDK版本也不同,比如,ffmpeg3.x的版本使用android-ndk-r16b,但是ffmpeg4.x的版本使用16就编译不过,这里选用的是android-ndk-r21e.
配置NDK环境
android-ndk-r21e
配置环境变量
vim ~/.bash_profile
export NDK_PATH=/Users/xxx/tools/android-ndk-r21e
export PATH=${PATH}:$NDK_PATH
编辑保存好之后执行下下面语句使之生效
. ~/.bash_profile
用到make_standalone_toolchain.py
先创建交叉编译工具链输出的路径
/Users/xxx/tools/android-ndk-r21e/android-toolchains/android-19
mkdir android-toolchains
cd android-toolchains
mkdir android-19
cd android-19
with open(src, 'rb') as fsrc:
到android-ndk-r16b/build/tools
下执行
sudo ./make_standalone_toolchain.py --arch arm --api 19 --install-dir ../../android-toolchains/android-19/arm-arch
这个步骤相当于把配置的arch和api等相关信息在指定文件夹下面做个copy(个人理解),方便后续编写ffmpeg编译脚本时用指定toolchain。
之所以说这个步骤可以省略,是因为,也可以直接在后续编写ffmpeg编译脚本中指定不同ABI和API对应的toolchain路径。
下载ffmpeg
不同的ffmpeg版本使用编译时需要的ndk版本也会有不同,如果在上一步你下载配置的NDK是16,则下载3.x的ffmpeg。如果NDK是21,则下载4.x的ffmpeg。
这个编译的过程折腾了自己两三天的时间,为了搞清楚原因,避免后续踩坑,针对ffmpeg3.x和ffmpeg4.x都进行记录。
我们先来看下编译第三方库的一般通用步骤
不要去copy,一定要手写,原因有三:
下面我们分别编写下ffmpeg3.x和ffmpeg4.x对应的配置编译脚本。
ffmpeg3.x_build.sh
#用于编译android平台的脚本
#!/bin/bash
#定义几个变量
ARCH=arm
CPU=armv7-a
#so生成的对应路径
PREFIX = $(pwd)/android
#注意,这里就是上一小节1.3中说的 安装android交叉编译工具链对应的路径
ANDROID_TOOLCHAINS_PATH=$NDK_PATH/android-toolchains/android-19/arch-arm
CROSS_PREFIX=$ANDROID_TOOLCHAINS_PATH/bin/arm-linux-androideabi-
SYSROOT=$NDROID_TOOLCHAINS_PATH/sysroot
#执行.configure文件 (./configure)
#运行./configure --help查看配置 裁剪
#换行后不要用空格,可以使用tab
echo "start build ffmpeg for $ARCH"
./configure --prefix =${PREFIX} \
#编译动态库
--disable-static \
--enable-shared \
#根据需要进行裁剪
--enable-samall \
--disable-programs \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-doc \
--disable-ffserver \
#指定cpu架构
--arch=$ARCH \
指定cpu类型
--cpu=$CPU \
#指定交叉编译工具目录
--cross-prefix=${CROSS_PREFIX} \
#开启交叉编译
--enable-crosss-compile \
#指定目标平台为linux(android内核也是基于linux的ffmpeg早期版本目标平台还不可以指定为android)
--target-os=linux \
#配置编译环境c语言的头文件环境
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic" \ #生成与位置无关的代码
make clean
make
make install
echo "end build ffmpeg for $ARCH"
编译生成的so的名称版本号在后面,这不符合在android中使用so的命名规范,为此修改configure脚本,替换以下四行内容,重新编译生成对应的so。
# 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)'
#!/bin/bash
#这里每个使用上一小节1.3中安装android交叉编译工具链对应的路径,我们采用手动指定的toolchain,注意要根据不同的API和不同的ABI进行不同的配置。
#另外注意 darwin-x86_64是mac上对应的文件夹名称,linux上对应的是linux-x86_64,需要根据自己编译的环境具体确认。
API=21
TOOLCHAIN=$NDK_PATH/toolchains/llvm/prebuilt/darwin-x86_64/
#armv8-a
ARCH=arm64
CPU=armv8-a
#r21版本的ndk中所有的编译器都在/android-ndk-r21e/toolchains/llvm/prebuilt/darwin-x86_64/目录下(clang)
#配置.c文件的编译工具
CC=$TOOLCHAIN/bin/aarch64-linux-android$API-clang
#.cpp文件的编译工具
CXX=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++
#头文件环境用的不是/android-ndk-r21e/sysroot,而是编译器//android-ndk-r21e/toolchains/llvm/prebuilt/darwin-x86_64/sysroot
SYSROOT=$NDK_PATH/toolchains/llvm/prebuilt/darwin-x86_64/sysroot
#交叉编译工具目录,对应关系如下(我们这里使用的是armv8a,根据自己的需要进行配置)
# armv8a -> arm64 -> aarch64-linux-android-
# armv7a -> arm -> arm-linux-androideabi-
# x86 -> x86 -> i686-linux-android-
# x86_64 -> x86_64 -> x86_64-linux-android-
CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android-
#输出目录
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-march=$CPU"
# 把configure,make写到一个function中(方便编译不同ABI时复用)
# 运行./configure --help查看配置 裁剪
# 换行后不要用空格,可以使用tab
function build_android
{
echo "CC is $CC"
echo "CROSS_PREFIX is $CROSS_PREFIX"
./configure \
#指定输出路径
--prefix=$PREFIX \
#编译动态库
--enable-shared \
--disable-static \
#根据需要进行裁剪
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-avdevice \
--disable-doc \
--disable-symver \
#指定交叉编译工具目录
--cross-prefix=$CROSS_PREFIX \
#指定目标平台为android
--target-os=android \
#指定cpu架构
--arch=$ARCH \
#指定cpu类型
--cpu=$CPU \
#指定c、cpp语言编译器
--cc=$CC \
--cxx=$CXX \
#开启交叉编译
--enable-cross-compile \
#配置编译环境c语言的头文件环境
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
make clean
make
make install
}
build_android
Ffmpeg4.x的版本编译出来命名直接时正确的(但不带版本号),可以不用修改configure直接使用生成的so。
首先在local.properties中指定对应的ndk.dir,之所以没有用AS自动配置的ndk版本,是因为通过自己手动配置更加灵活,指定不同的版本。
不同的camkelist版本以及gradle版本有不同的API
针对ffmpeg3.x和android-ndk-r16b
cmake_minimum_required(VERSION 3.4.1)
project("myffmpegdemo")
MESSAGE(STATUS "PROJECT_SOURCE_DIR =" ${PROJECT_SOURCE_DIR})
MESSAGE(STATUS "CMAKE_SOURCE_DIR =" ${CMAKE_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/ffmpeg/include)
link_directories(${CMAKE_SOURCE_DIR}/../jniLibs/armeabi-v7a)
add_library(
native-lib
SHARED
native-lib.cpp )
find_library(
log-lib
log)
target_link_libraries(
native-lib
avcodec-57
avfilter-6
avformat-57
avutil-55
swresample-2
swscale-4
avdevice-57
postproc-54
${log-lib})
针对ffmpeg4.x和 android-ndk-r21e
cmake_minimum_required(VERSION 3.4.2)
project("myffmpeg42demo")
#指定ffmpeg对应的头文件,根据自己的具体情况进行设置
include_directories(${PROJECT_SOURCE_DIR}/ffmpeg/include)
message("CMAKE_CXX_FLAGS:" + ${CMAKE_CXX_FLAGS})
message("CMAKE_SOURCE_DIR:" + ${CMAKE_SOURCE_DIR})
message("CMAKE_ANDROID_ARCH_ABI版本是:" + ${CMAKE_ANDROID_ARCH_ABI})
#4.x采用这种方式引入ffmpeg库不行,改下下面的方式
#link_directories(${CMAKE_SOURCE_DIR}/../jniLibs/armeabi)
# 引入FFmpeg的库文件,设置内部的方式引入,指定库的目录是 -L 指定具体的库-l
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
add_library( # Sets the name of the library.
native-lib
SHARED
native-lib.cpp)
find_library( # Sets the name of the path variable.
log-lib
log)
target_link_libraries( # Specifies the target library.
native-lib
avformat avcodec avfilter avutil swresample swscale
${log-lib})
如何适配so架构
每一种架构对应一种ABI,是可以向下兼容的
比如:项目里面没有armeabi-v7a的文件夹,如果你有两个文件夹armeabi和armeabi-v7a两个文件夹,armeabi里面有a.so 和 b.so, armeabi-v7a里面只有a.so,那么arm64-v8a的手机在用到b的时候发现有armeabi-v7a的文件夹,发现里面没有b.so,就报错了,如果没有armeabi-v7a文件夹,运行时发现没有适配armeabi-v7a,就会直接去找armeabi的so库,所以要么你别加armeabi-v7a,要么armeabi里面有的so库,armeabi-v7a里面也必须有。
1. ffbuild/config.mak: No such file or directory
./ffmpeg_build3.sh
Unknown option "--disable-ffserver".
See ./configure --help for available options.
Makefile:2: ffbuild/config.mak: No such file or directory
Makefile:40: /tools/Makefile: No such file or directory
Makefile:41: /ffbuild/common.mak: No such file or directory
Makefile:94: /libavutil/Makefile: No such file or directory
Makefile:94: /ffbuild/library.mak: No such file or directory
Makefile:96: /fftools/Makefile: No such file or directory
Makefile:97: /doc/Makefile: No such file or directory
Makefile:98: /doc/examples/Makefile: No such file or directory
Makefile:163: /tests/Makefile: No such file or directory
make: *** No rule to make target `/tests/Makefile'. Stop.
Makefile:2: ffbuild/config.mak: No such file or directory
Makefile:40: /tools/Makefile: No such file or directory
Makefile:41: /ffbuild/common.mak: No such file or directory
Makefile:94: /libavutil/Makefile: No such file or directory
Makefile:94: /ffbuild/library.mak: No such file or directory
Makefile:96: /fftools/Makefile: No such file or directory
Makefile:97: /doc/Makefile: No such file or directory
Makefile:98: /doc/examples/Makefile: No such file or directory
Makefile:163: /tests/Makefile: No such file or directory
make: *** No rule to make target `/tests/Makefile'. Stop.
Makefile:2: ffbuild/config.mak: No such file or directory
Makefile:40: /tools/Makefile: No such file or directory
Makefile:41: /ffbuild/common.mak: No such file or directory
Makefile:94: /libavutil/Makefile: No such file or directory
Makefile:94: /ffbuild/library.mak: No such file or directory
Makefile:96: /fftools/Makefile: No such file or directory
Makefile:97: /doc/Makefile: No such file or directory
Makefile:98: /doc/examples/Makefile: No such file or directory
Makefile:163: /tests/Makefile: No such file or directory
make: *** No rule to make target `/tests/Makefile'. Stop.
解决方案:一般是configure配置出错了,或者换行带有空格或者copy的tab被转为了空格导致,解决方案搞清楚ffmpeg编译脚本的意义,按照规范手写。
2. 4.x的版本编译的so,在编译时报错 incompatible target
/Users/yabin/tools/android-ndk-r21e/toolchains/llvm/prebuilt/darwin-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin/ld: error: /Users/yabin/work/tmp/myffmpegDemo/app/src/main/cpp/../jniLibs/armeabi-v7a/libswscale.so: incompatible target
把ndk的版本从21换成16也一样的错误
ndk.dir=/Users/yabin/tools/android-ndk-r16b
/Users/yabin/tools/android-ndk-r16b/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin/ld: error: /Users/yabin/work/tmp/myffmpegDemo/app/src/main/cpp/../jniLibs/armeabi-v7a/libswscale.so: incompatible target
解决方案:
换成了3.3.9版本的ffmpeg的编译产物,可以了
根本原因在于不同NDK版本对应的Cmakelist中引入so的方式不同。
3. AS引入so后在armeabi中加入so,正确配置后,编译报错“armeabi is no longer supported. Use armeabi-v7a”
CMake Error at /Users/yabin/tools/android-ndk-r21e/build/cmake/android.toolchain.cmake:174 (message):
armeabi is no longer supported. Use armeabi-v7a.
Error while executing process cmake/3.10.2.4988404/bin/cmake with arguments
Unknown argument -
abiFilters Collection contains no element matching the predicate.
No valid Native abi found to request!
解决方案:使用ndk21不支持armeabi,改为armeabi-v7a
4. 【ffmpeg】编译时报错:error: undefined reference to 'av_codec_next(AVCodec const)’**
解决方案:ffmpeg库的接口都是c函数,其头文件也没有extern "C"的声明,所以在cpp文件里调用ffmpeg函数要加extern “C” 。
感谢你的阅读
下一篇我们开始进入ffmpeg的使用篇,以结果为导向,通过ffmpeg实现一个视频解码器,欢迎关注公众号“音视频开发之旅”,一起学习成长。
欢迎交流