基于NDK编译Android平台的FFmpeg动态库

需求

FFmpeg在Linux平台(如Ubuntu)上的支持已经比较完善了,如前述文章介绍 http://blog.csdn.net/ericbar/article/details/73702061,我们很容易就可以基于FFmpeg+SDL实现一个播放器,比如FFmpeg自带的ffplay程序,就可以实现音视频的解码播放。
现在基于Android手机的媒体应用场景也愈发增多起来,比如流行的直播技术,在终端就用到了音视频解码的方法。当然,Android平台本身提供了java层的MediaPlayer播放器API供调用,但是其灵活性和可定制化程度受到制约,且不便于在其他操作系统(如ios)之间移植,所以我们有必要研究一下FFmpeg在Android平台的使用移植方法。
首先要完成的工作,就是完成FFmpeg在Android平台上的编译,并生成相应的库。

思路

FFmpeg是基于c语言实现的,所以在Android上只能基于NDK来编译,网络上关于如何编译FFmpeg库的方法很多,我觉得在参考这些文章的同时,需要坚持几点方向:
1. 在Ubuntu下编译;虽然在Windows平台下也可以实现编译,但是各种小问题纠缠不清,况且前期我们已经搭建了Ubuntu的环境,所以更加方便;
2. 不用eclipse等工具来编译,直接基于NDK编译链来进行;
3. 不修改FFmpeg的主要代码结构,直接依赖代码原生的Makefile进行;
4. 最终的FFmpeg编译成一个库,不再编译成多个库(libavformat,libavfilter,libavutil等),所以会有少量的代码修改;

步骤

NDK编译链下载

首先,下载NDK的Linux版本编译链,地址在https://developer.android.com/ndk/downloads/index.html,可能需要科学上网,当然国内也有很多的下载链接,记住一定要下载Linux的版本,由于我们的Ubuntu是64位的,所以我们要下载64位的,现在32位NDK已经不更新和发布了。
写此文的时候,最新版本的NDK是r14b,所以我们下载android-ndk-r14b-linux-x86_64.zip即可,整个zip包大小在800M左右。
下载完毕后,我们在Ubuntu里解压缩,得到对应的ndk目录,把NDK的路径加到环境变量里即可。

源码下载

从FFmpeg官方网站下载FFmpeg的源代码,这里我们仍然以ffmpeg-3.2.4为基础,从官网下载的包名为ffmpeg-3.2.4.tar.xz,解压缩代码得到ffmpeg-3.2.4文件夹。

添加编译规则

为了基于NDK编译源码,网上有很多的方法,包括重新按照代码目录建立Android.mk编译规则,或者大量的修改源代码,我认为这打乱了FFmpeg本身的代码结构和Makefile机制,不便于后期版本升级和维护,所以我们这里必须坚持前面“思路”中提到的几条原则和方向。
为了方便编译,我们可以通过如下两个脚本方便的完成动态库libffmpeg.so的编译。这两个脚本分别叫做config.sh和make.sh,其中,config.sh是配置脚本,make.sh是编译脚本,下面我们来分析这两个脚本的执行过程,首先分析config.sh,内容如下:

#!/bin/bash

FFMPEG_SRC_PATH=$(cd `dirname $0`; pwd)

SYSROOT=/opt/android-ndk-r14b/platforms/android-19/arch-arm
LIBPATH=/opt/android-ndk-r14b/platforms/android-19/arch-arm/usr/lib/
TOOLCHAIN=/opt/android-ndk-r14b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/


export PATH=$TOOLCHAIN/bin:$PATH
export CROSS_PREFIX=arm-linux-androideabi-
export CC="$CCACHE ${CROSS_PREFIX}gcc "
export CXX=${CROSS_PREFIX}g++
export LD=${CROSS_PREFIX}ld
export AR=${CROSS_PREFIX}ar
export STRIP=${CROSS_PREFIX}strip

LDFLAGS="-lm -lz -Wl,-soname=libffmpeg.so,-z,noexecstack"

CPU=arm
PREFIX=ffout
ADDI_CFLAGS="-marm"

echo " "
echo "please wait..."
echo " "

#cd $FFMPEG_SRC_PATH
rm ./$PREFIX -rf
make clean

echo " "
echo "preparing to configure..."
echo " "

./configure \
    --prefix=$PREFIX \
    --enable-shared \
    --disable-static \
    --disable-doc \
    --disable-ffmpeg \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-ffserver \
    --disable-avdevice \
    --disable-doc \
    --disable-symver \
    --disable-programs \
    --disable-avdevice \
    --disable-w32threads \
    --disable-os2threads \
    --disable-encoders \
    --disable-muxers \
    --disable-devices \
    --disable-sdl \
    --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
    --target-os=linux \
    --arch=arm \
    --enable-cross-compile \
    --sysroot=$SYSROOT \
    --extra-cflags="-Os -fpic $ADDI_CFLAGS" \
    --extra-ldflags="$ADDI_LDFLAGS" \
    $ADDITIONAL_CONFIGURE_FLAG

其中,
FFMPEG_SRC_PATH 获取当前FFmpeg源代码的路径,
SYSROOT 为目标系统头文件和库所在路径,我们这里选择NDK路径下的相关位置即可,不同版本的NDK其路径指向可能有差异,但是我们可以通过SYSROOT下的usr包括lib和include两个目录来区别。
TOOLCHAIN 为实际的编译工具链路径,这里选择4.9版本的64位系统。
PREFIX 指定我们编译后的文件输出路径。
最后的 ./configure 和FFmpeg的标准编译配置类似,可以根据各自需要调整相应的配置选项。我们这里需要编译动态库,所以将如下开关打开–enable-shared。

下面是脚本make.sh的内容:

#!/bin/bash

FFMPEG_SRC_PATH=$(cd `dirname $0`; pwd)

SYSROOT=/opt/android-ndk-r14b/platforms/android-19/arch-arm
LIBPATH=/opt/android-ndk-r14b/platforms/android-19/arch-arm/usr/lib/
TOOLCHAIN=/opt/android-ndk-r14b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/


export PATH=$TOOLCHAIN/bin:$PATH
export CROSS_PREFIX=arm-linux-androideabi-
export CC="$CCACHE ${CROSS_PREFIX}gcc "
export CXX=${CROSS_PREFIX}g++
export LD=${CROSS_PREFIX}ld
export AR=${CROSS_PREFIX}ar
export STRIP=${CROSS_PREFIX}strip

LDFLAGS="-lm -lz -Wl,-soname=libffmpeg.so,-z,noexecstack"

CPU=arm
PREFIX=ffout
ADDI_CFLAGS="-marm"

#make -j${NUMBER_OF_CORES} && make install || exit 1
make -j16 && make install || exit 1

rm  libavcodec/reverse.o libavcodec/log2_tab.o libavformat/log2_tab.o libavformat/golomb_tab.o \
    libswresample/log2_tab.o libavfilter/log2_tab.o libswscale/log2_tab.o

$CC -o $PREFIX/libffmpeg.so -shared $LDFLAGS $EXTRA_LDFLAGS --sysroot=$SYSROOT -L $LIBPATH \
    libavutil/*.o libavutil/arm/*.o libavcodec/*.o libavcodec/arm/*.o  \
    libavformat/*.o libavfilter/*.o libswresample/*.o libswresample/arm/*.o \
    libswscale/*.o libswscale/arm/*.o compat/*.o

cp $PREFIX/libffmpeg.so $PREFIX/libffmpeg-debug.so
${STRIP} --strip-unneeded $PREFIX/libffmpeg.so

前面的配置和config.sh类似,make 是编译,其中j16 可以根据自己机器配置情况进行调整,如果配置并发编译线程数过多的话,可能会导致编译错误,所以可以将线程数调低再次尝试。
因为原始的FFmpeg编译是将多个库分别编译得出,而我们这里是将这些库都打成一个动态库so,所以如果不做处理的将这些库链接到一起,会出现某些函数重复定义问题,像log2_tab是最常见的,因为这里需要将重复的删除rm掉,只留下一个过程文件.o即可。
cc是把所有的.o文件打包成so,这里需要注意的是,如果相关目录下有和cpu相关(比如arm)的子文件夹,则需要将子文件夹的.o也链接进来,否则在编译so库的时候不会报错,但是运行程序的时候会找不到相关的函数报错。
最后的strip是剔除debug信息的ffmpeg库。
最后,需要注意一下LDFLAGS里的-soname=libffmpeg.so,在早期的Android版本智能手机上运行时,此定义无关紧要,但是在Android 6以后的版本手机上运行时,对so的检查会更严格,如果不加上此项,在运行时可能会报DT_NEEDED的错误。
最后,执行make.sh后,会在ffout目录下生成libffmpeg.so以及相关的库和头文件,其中头文件也是我们后期在Android平台需要用到的。
接下来,我们便可以在Android平台编写基于FFmpeg的应用程序了,比如一个简单的播放器,我们在接下来的文章中再研究。

你可能感兴趣的:(FFmpeg)