使用自定义NDK交叉编译链编译ffmpeg

上一节我们讲到了通过自定义的NDK交叉编译链来编译第三方库,还不了解自定义交叉编译工具链的同学,可以看下上一节的内容,本节我们讲一下如何使用NDK交叉编译链来编译我们经常用到的ffmpeg这个库。

编译环境

操作系统:mac os 10.13.5
ndk版本:android-ndk-r17b
编译器:clang
ffmpeg版本:4.0.2

armeabi工具链

首先,这里我们需要使用到的是armeabi的交叉编译工具链,关于工具链的自定义,如果不知道如何自定义,我们可以参考我前面的文章自定义NDK交叉编译链,本节中我要采用armeabi这个平台下的交叉编译链来编译ffmpeg库。

关于ffmpeg

ffmpeg,我想只要做音视频相关工作的人,大概可能有至少50%的时间需要与这个开源库打交道,关于他的强大之处,请自行google,这里不做更多的叙述。需要说明的一点是,由于这个库的强大之处,在编译的时候提供了很多的选项来定制这个库的功能,比如音视频编解码格式的支持,openssl的支持等等非常多的功能,这里的定制功能,我们不做过多的讨论,在后续的文章中我会特别讲一些关于定制方面的内容。

编译环境脚本

我们这里整理了一个编译环境的脚本ffmpeg-toolchain-env.sh

#!/bin/sh
# Android cross-compile environment setup script
# Author  : eggsy
# Date    : 2018-08-28
# Version : 1.0

# Android NDK sources and standalone toolchain is put here
export DEV=${HOME}/Library/Android/sdk

export CHAIN_ENV=${HOME}/Chain/android-toolchain

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

# 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

# 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 -mandroid"
export LIBS="-lgcc"
export LDFLAGS="-Wl,-rpath-link=-I${SYSROOT}/usr/lib -L${SYSROOT}/usr/lib -L${PREFIX}/lib -L${NDK_TOOLCHAIN}/lib"

编译ffmpeg

设置编译环境

source ffmpeg-toolchain-env.sh

配置与编译

./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

编译结果

使用自定义NDK交叉编译链编译ffmpeg_第1张图片
编译结果

我们看到这里有 includelibshare目录,其中 include是相关的头文件, lib中是边界结果的静态库
使用自定义NDK交叉编译链编译ffmpeg_第2张图片
静态库

我们将lib和include拷贝到我们的工程中,就可以开始进行ffmpeg的相关开发了。这里看我们生成的是静态库,因为我们上面configure中我们使用了 --enable-static --disable-shared参数,如果要生成动态库,这里可以反过来配置 --disable-static --enable-shared,后面执行

make clean

./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

这时候得到的编译结果与上面类似,不过lib中获得的是动态库

使用自定义NDK交叉编译链编译ffmpeg_第3张图片
动态库

其中 libavcodec.solibavcodec.so.58都是软连接指向 libavcodec.so.58.18.100,其他动态库以此类推,这时候我们可以删除软连接并且将 libavcodec.so.58.18.100重命名为 libavcodec.so,与 include中的头文件一同拷贝到android工程中使用,其他动态库以此类推。
上面的这种操作其实也有不方便的地方,就是每次都要重命名文件,然后拷贝,能不能直接生成我们想要的动态库呢?替换原有配置

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)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR)'

然后重新执行configuremakemake install

使用自定义NDK交叉编译链编译ffmpeg_第4张图片
优化输出结果

同样,复制我们的libXXX.so文件和 include目录下的头文件到android工程,就可以在android上开始我们ffmpeg开发之旅了。

编译遇到错误

在这个ffmpeg版本编译的时候可能会遇到几处错误

错误1

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

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

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程序启动的时候只要加载一个动态库即可,理论上效率更高。
同样的,先加载环境变量、打包、压缩优化

# 先重置环境变量
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


使用自定义NDK交叉编译链编译ffmpeg_第5张图片
生成ffmpeg.so

最后对包的大小进行优化压缩

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

这样,我们的ffmpeg.so动态库就生成了,拷贝到android工程中使用吧,当然也别忘记了拷贝头文件哦~~~

遇到问题

使用android-ndk-r17b这个版本的ndk,可能会遇到以下错误

libavfilter/vf_psnr.c:387: error: undefined reference to 'stdout'
libavfilter/vf_psnr.c:258: error: undefined reference to 'stdout'
libavfilter/vf_ssim.c:478: error: undefined reference to 'stdout'
libavfilter/vf_ssim.c:356: error: undefined reference to 'stdout'
libavutil/log.c:138: error: undefined reference to 'stderr'
libavutil/log.c:217: error: undefined reference to 'stderr'
libavutil/log.c:217: error: undefined reference to 'stderr'

这时候切换到android-ndk-r14b这个版本,重新制作工具链,重新configuremakemake install后重新打包,压缩优化,即可生成

总结

上面我们讲解了通过android的arm平台架构的自定义交叉编译链来编译ffmpeg的动态或者静态库,不知道对你是否有用处,后面开始会陆续基于ffmpeg进行android上播放器的开发,欢迎留言交流~~

你可能感兴趣的:(使用自定义NDK交叉编译链编译ffmpeg)