使用NDK编译FFmpeg4.1

编译环境

操作系统: Mac OS 10.13.6
FFMpeg版本: 4.1
NDK版本: android-ndk-r18b
编译器: clang

构建ndk编译链

执行ndk自带的脚本构建编译链,会生成编译环境

1
$NDK/build/tools/make_standalone_toolchain.py --arch arm --api 21 --install-dir /tmp/my-android-toolchain

构建ffmpeg编译环境

修改ffmpeg项目根目录下configure文件将这几个环境变量替换成如下

1
2
3
4
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR)'

准备ffmpeg-toolchain-env.sh脚本,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/bin/sh
# Android cross-compile environment setup script
# Author : binea
# Date : 2019-01-14
# Version : 1.0
export HOME=根目录
# Android NDK sources and standalone toolchain is put here
export DEV=${HOME}/dev/android/sdk/adt-bundle-mac-x86_64-20140702/sdk
export NDK=${DEV}/android-ndk-r18b
export CHAIN_ENV=${NDK}/android-21

# All the built binaries, libs and their header will be installed here
# export PREFIX=${HOME}/Chain
export PREFIX=$(pwd)/android/$CPU

# static or share libs dir
export OUT_PUT=${PREFIX}/android-output

# Don't mix up .pc files from your host and build target
export PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig

# The building system we are using (Linux x86_64)
export BUILD_SYS=arm-linux-gnu

# Set Android target API level
export ANDROID_API=21

# Set Android target arch
export ANDROID_ARCH=arm

# Set Android target name, according to Table 2 in
# https://developer.android.com/ndk/guides/standalone_toolchain.html
export ANDROID_TARGET=armv5te-none-linux-androideabi

# The cross-compile toolchain we use
export TOOLCHAIN=arm-linux-androideabi

# The path of standalone NDK toolchain
# Refer to https://developer.android.com/ndk/guides/standalone_toolchain.html
export NDK_TOOLCHAIN=${CHAIN_ENV}/${ANDROID_ARCH}

# export toolchain path
export PATH=${NDK_TOOLCHAIN}/bin:$PATH

# Set Android Sysroot according to API and arch
export SYSROOT=${NDK_TOOLCHAIN}/sysroot
# this one is the absolute, prebuilt path

# Binutils path
export CROSS_PREFIX=${NDK_TOOLCHAIN}/bin/${TOOLCHAIN}
# this one is the absolute, prebuilt path

# Non-exhaustive lists of compiler + binutils
export AR=${CROSS_PREFIX}-ar
export AS=${CROSS_PREFIX}-as
export LD=${CROSS_PREFIX}-ld
export NM=${CROSS_PREFIX}-nm
export CC=${CROSS_PREFIX}-gcc
export CXX=${CROSS_PREFIX}-g++
export CPP=${CROSS_PREFIX}-cpp
export CXXCPP=${CROSS_PREFIX}-cpp
export STRIP=${CROSS_PREFIX}-strip
export RANLIB=${CROSS_PREFIX}-ranlib
export STRINGS=${CROSS_PREFIX}-strings
export CLANGXX=${CROSS_PREFIX}-clang++

# Set build flags
# Refer to https://developer.android.com/ndk/guides/standalone_toolchain.html
export PATH=$PATH:${PREFIX}/bin:${PREFIX}/lib
export CFLAGS="--sysroot=${SYSROOT} -I${SYSROOT}/usr/include -I${PREFIX}/include -fPIE -DANDROID -Wno-multichar"
export CXXFLAGS=${CFLAGS}
export CPPFLAGS="--sysroot=${SYSROOT} -I${SYSROOT}/usr/include -I${NDK_TOOLCHAIN}/include/c++/ -DANDROID -DNO_XMALLOC -android"
export LIBS="-lgcc"
export LDFLAGS="-Wl,-rpath-link=-I${SYSROOT}/usr/lib -L${SYSROOT}/usr/lib -L${PREFIX}/lib -L${NDK_TOOLCHAIN}/lib"

这个脚本应该是通用脚本,网上很多,但是有些脚本提供是错的。-android写成了-mandroid这里需要注意

执行以下命令

1
2
3
4
5
./configure --enable-static --disable-shared  --disable-doc --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-avdevice --disable-doc --disable-symver --prefix=${OUT_PUT}/ffmpeg/${ANDROID_ARCH} --target-os=android --arch=arm --enable-cross-compile --sysroot=${SYSROOT} --cross-prefix=${TOOLCHAIN}- --extra-cflags="${CFLAGS}" --extra-ldflags="${LDFLAGS}" --extra-libs="${LIBS}" --extra-cxxflags="${CXXFLAGS}" 

make

make install

然后ffmpeg就开始编译了。编译成功后会生成如下文件件

ffmpeg_output

编译过程中可能遇到的错误

错误1

1
2
3
4
5
6
7
8
9
10
11
12
libavcodec/aaccoder.c: In function 'search_for_ms':
libavcodec/aaccoder.c:803:25: error: expected identifier or '(' before numeric constant
int B0 = 0, B1 = 0;
^
libavcodec/aaccoder.c:865:28: error: lvalue required as left operand of assignment
B0 += b1+b2;
^
libavcodec/aaccoder.c:866:25: error: 'B1' undeclared (first use in this function)
B1 += b3+b4;
^
libavcodec/aaccoder.c:866:25: note: each undeclared identifier is reported only once for each function it appears in
make: *** [libavcodec/aaccoder.o] Error 1

解决: 解决:将libavcodec/aaccoder.c文件B0变量替换成b0

错误2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
libavcodec/hevc_mvs.c: In function 'derive_spatial_merge_candidates':
libavcodec/hevc_mvs.c:208:15: error: 'y0000000' undeclared (first use in this function)
((y ## v) >> s->ps.sps->log2_min_pu_size))
^
libavcodec/hevc_mvs.c:204:14: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:368:23: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
libavcodec/hevc_mvs.c:208:15: note: each undeclared identifier is reported only once for each function it appears in
((y ## v) >> s->ps.sps->log2_min_pu_size))
^
libavcodec/hevc_mvs.c:204:14: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:368:23: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
libavcodec/hevc_mvs.c:207:15: error: 'x0000000' undeclared (first use in this function)
TAB_MVF(((x ## v) >> s->ps.sps->log2_min_pu_size), \
^
libavcodec/hevc_mvs.c:204:34: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:368:23: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
libavcodec/hevc_mvs.c: In function 'ff_hevc_luma_mv_mvp_mode':
libavcodec/hevc_mvs.c:208:15: error: 'y0000000' undeclared (first use in this function)
((y ## v) >> s->ps.sps->log2_min_pu_size))
^
libavcodec/hevc_mvs.c:204:14: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:683:24: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
libavcodec/hevc_mvs.c:207:15: error: 'x0000000' undeclared (first use in this function)
TAB_MVF(((x ## v) >> s->ps.sps->log2_min_pu_size), \
^
libavcodec/hevc_mvs.c:204:34: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:683:24: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
make: *** [libavcodec/hevc_mvs.o] Error 1

解决: 将libavcodec/hevc_mvs.c文件的变量B0改成b0,xB0改成xb0,yB0改成yb0

错误3

1
2
3
4
5
6
7
8
libavcodec/opus_pvq.c: In function 'quant_band_template':
libavcodec/opus_pvq.c:498:9: error: expected identifier or '(' before numeric constant
int B0 = blocks;
^
libavcodec/opus_pvq.c:559:12: error: lvalue required as left operand of assignment
B0 = blocks;
^
make: *** [libavcodec/opus_pvq.o] Error 1

解决:将libavcodec/opus_pvq.c文件的变量B0改成b0

打包成ffmpeg.so

上面我们编译出了libXXX.a或者libXXX.so库,当然直接拷贝到android工程引用是一种方式,还有一种就是将静态库打成一个动态库,这样android程序启动的时候只要加载一个动态库即可,理论上效率更高。同样的,先加载环境变量、打包、压缩优化。

1
2
3
4
# 先重置环境变量
source ffmpeg-toolchain-env.sh
# 执行打包命令
arm-linux-androideabi-ld -rpath-link=$SYSROOT/usr/lib -L$SYSROOT/usr/lib -L${OUT_PUT}/ffmpeg/${ANDROID_ARCH}/lib -soname libffmpeg.so -shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o ${OUT_PUT}/ffmpeg/${ANDROID_ARCH}/libffmpeg.so libavcodec.a libavfilter.a libavformat.a libavutil.a libswresample.a libswscale.a -lc -lm -lz -ldl -llog --dynamic-linker=/system/bin/linker $CHAIN_ENV/${ANDROID_ARCH}/lib/gcc/arm-linux-androideabi/4.9.x/libgcc.a

会生成一个ffmpeg.so

对so文件进行裁剪压缩以下

1
arm-linux-androideabi-strip  ${OUT_PUT}/ffmpeg/${ANDROID_ARCH}/libffmpeg.so

这样打包出来的so库大小

至此ffmpg的编译已经完成了。

Android Studio使用ffmpeg.so

main下面建立cpp或者jni然后将libffmpeg.so拷到如下目录

cpp或者jni根目录下创建CMakeLists.txt文件,在build.gradle文件中加入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
defaultConfig {
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}

ndk {
abiFilters "armeabi-v7a"
}
}

sourceSets {
main {
jniLibs.srcDirs = ['src/main/cpp/ffmpeg/lib']
}
}

CMakeFileLists脚本中加入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required(VERSION 3.4.1)

set(CURRENT_DIR ${CMAKE_SOURCE_DIR})

message("CURRENT_DIR:" ${CMAKE_SOURCE_DIR})

add_library(player SHARED ${CMAKE_SOURCE_DIR}/ffmpeg/player.c)

add_library(myffmpeg SHARED IMPORTED)
set_target_properties(myffmpeg PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/ffmpeg/lib/armeabi-v7a/libffmpeg.so)

include_directories(${CMAKE_SOURCE_DIR}/ffmpeg/include)

# Include libraries needed for player lib
target_link_libraries(player myffmpeg android log)

在kotlin中载入动态库

1
2
System.loadLibrary("ffmpeg")
System.loadLibrary("player")

然后build,这个时候去看apk

至此ffmpeg.so成功打包到了apk当中。

打包的过程中可能会报找不到so文件,这个时候需要检查一下so文件与eabi是否与gradle的配置一致。so文件的目录不能单纯的使用armeabi,这样会导致so无法打包到apk