本篇介绍在Android/Ndk环境下FFmpeg的编译及使用, FFmpeg自带了H264、AAC、MP3的解码器,但却没有(或没有好的)相应的编码器。相应的编码器需要使用第三方库。推荐使用的第三方库为x264(H264编码) 、FDK_AAC(AAC编码),lame(MP3编码)。
在顺序上,应该先编译好第三方库,最后再编译FFmpeg库。
【本书说明:本文作者:邵发,本文选自《FFmpeg视音频编程指南》。有关本书的详细信息请访问官网:http://www.afanihao.cn 】
【权利声明:作者保留本文的全部权利。作者授权任何人都可以自由转载本文,但转载时必须遵守以下限制:①转载时必须全文转载,不得有任何修改,包括“权利声明”和“本书说明”部分 ② 仅限于网络转载,即最终结果公布于网络上。凡是不遵守以上两条的转载行为视为侵权行为。除非本人允许,任何人不得将本文内容用于任何的其他用途。】
1.1 NDK环境的准备
本篇的演示示例使用的是android-ndk-r8e版本,但理论上也适用于更新的NDK版本。为了让你的编译过程极其顺利,应该对NDK作以下改动:
进入目录 platforms/android-14/arch-arm/usr/lib
应该可以看到一些文件,例如 crtbegin_dynamic.o ,crtbegin_static.o crtend_so.o , crtbegin_so.o ,crtend_android.o,等等。
把这个目录下的所有文件拷贝到
\toolchains\arm-linux-androideabi-4.6\prebuilt\linux-x86\lib\gcc\arm-linux-androideabi\4.6
否则在链接的时候ld会提示“找不到crtbegin_so.o和crtend_so.o”的错误。
应该把NDK的相关工具都把加PATH环境变量中,例如,
#!/bin/sh export PATH=$PATH:/opt/ndk/android-ndk-r8e/ export PATH=$PATH: /opt/ndk/android-ndk-r8e/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/ |
1.2 FDK_AAC库的编译
如果你不打算使用AAC编码的功能,则可以略过本节。目前(2015年3月),fdk_aac的官方发布地址是在source_forge上,也许以后会变,作者可以自行搜索。目前地址为:
http://sourceforge.net/projects/opencore-amr/files/fdk-aac/
本文使用的版本是fdk-aac-0.1.3,为减少不必要的麻烦,读者也请下载这个包。
1.2.1 修改configure脚本
修改脚本的目的是去除目标库的版本号,以适应Android/NDK的对库的加载要求。简单地讲,这是因为在Android/NDK项目的java代码中,使用System.loadLibrary()函数来加载so文件时,动态库是不能加版本号的。
在fdk_aac的configure脚本里找到如下的行:
# This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec=‘${libname}${shared_ext} ${libname}${shared_ext} $libname${shared_ext}‘ soname_spec=‘${libname}${shared_ext}‘ finish_cmds=‘PATH="\$PATH:/sbin" ldconfig -n $libdir‘ shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no |
把粗体字部分替换为:
library_names_spec=‘$libname${shared_ext} $libname.a‘ |
1.2.2 新建配置脚本
在源码的根目录下建议一个myconfig.sh脚本。这是一个SHELL脚本,要求读者自己稍微了解一些SHELL脚本的写法。在这里只强调一点:SHELL脚本是以\n结尾的,如果你是在windows下面编辑的,那么应该用dos2unix来把文本的换行符转成unix格式。
为了避免不必要的麻烦,把NDK解压缩在/opt/ndk/android-ndk-r8e目录,并将FDK的输出目录--prefix设置在/opt/ndk/openlib目录下。读者第一遍操作时,不要擅自修改目录,必须在熟练掌握之后再自行修改目录。
myconfig.sh里面内容为:
#!/bin/sh export CC=arm-linux-androideabi-gcc export CXX=arm-linux-androideabi-g++ export AR=arm-linux-androideabi-ar export LD=arm-linux-androideabi-ld export AS=arm-linux-androideabi-gcc export CFLAGS=" --sysroot=/opt/ndk/android-ndk-r8e/platforms/android-14/arch-arm -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 " export CXXFLAGS=" --sysroot=/opt/ndk/android-ndk-r8e/platforms/android-14/arch-arm -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 " export LDFLAGS=" -L/opt/ndk/android-ndk-r8e/platforms/android-14/arch-arm/usr/lib -march=armv7-a -Wl,--fix-cortex-a8 " ./configure --enable-static --prefix=/opt/ndk/openlib --host=arm-linux |
1.2.3 编译
如果你严格按照以上的操作进行,那么编译过程会非常简单。Linux下的开源软件一般都是分为三步编译 configure , make,make install,而fdk_aac也不例外。
1) ./myconfig.sh
执行上述配置脚本,开始配置
2) make
3) make install
一切正常,将在/opt/ndk/openlib下存放输出库及头文件,注意这个库是去掉版本号的libfdk-aac。显然,Android/NDK的程序员会更喜欢不带版本号的库。
1.3 LAME-MP3的编译
如果你不打算在项目中使用mp3编码,则略过本节。
本节介绍lame的编译,但遗憾的是,其动态库没有办法去掉版本号。所以本方法生成的目标动态库是带版本号的。
1.3.1 新建配置脚本
新建配置文件myconfig.sh,内容如下,
#!/bin/sh export CC=arm-linux-androideabi-gcc export CXX=arm-linux-androideabi-g++ export AR=arm-linux-androideabi-ar export LD=arm-linux-androideabi-ld export CFLAGS=" --sysroot=/opt/ndk/android-ndk-r8e/platforms/android-14/arch-arm " export LDFLAGS=" -L/opt/ndk/android-ndk-r8e/platforms/android-14/arch-arm/usr/lib " ./configure --enable-static --prefix=/opt/ndk/openlib --host=arm-linux |
1.3.2 编译
1) ./myconfig.sh
执行上述配置脚本,开始配置
2) make
3) make install
一切正常,将在/opt/ndk/openlib下存放输出库及头文件。
1.4 x264的编译
x264属于VLC的一部分,当前的源码位置为:
ftp://ftp.videolan.org/pub/videolan/x264/snapshots/
本篇使用的源码版本为x264-snapshot-20140723-2245-stable.tar.bz2
1.4.1 修改configure脚本
这一步的目的是使输出的库文件不带版本号。如果你坚持认为版本号不影响你的开发,则忽略本小节。
找到这一位置:
else echo "SOSUFFIX=so" >> config.mak echo "SONAME=libx264.so.$API" >> config.mak echo "SOFLAGS=-shared -Wl,-soname,\$(SONAME) $SOFLAGS" >> config.mak fi |
将粗体字修改为:(即去掉$API后缀)
echo "SONAME=libx264.so" >> config.mak |
1.4.2 新建配置脚本
新建myconfig.sh配置脚本,
#!/bin/sh export CC=arm-linux-androideabi-gcc export CXX=arm-linux-androideabi-g++ export AR=arm-linux-androideabi-ar export LD=arm-linux-androideabi-ld export AS=arm-linux-androideabi-gcc export CFLAGS=" --sysroot=/opt/ndk/android-ndk-r8e/platforms/android-14/arch-arm -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16" export LDFLAGS=" -L/opt/ndk/android-ndk-r8e/platforms/android-14/arch-arm/usr/lib -march=armv7-a -Wl,--fix-cortex-a8 " ./configure --enable-static --enable-shared --prefix=/opt/ndk/openlib --host=arm-linux |
1.4.3 编译
1) ./myconfig.sh
执行上述配置脚本,开始配置
2) make
3) make install
1.5 FFmpeg的编译
本示例使用的是FFmpeg2.1.3版本,可以从其官网ffmpeg.org上下载。如果不需要x264/aac/lame,可以直接进行本节的编译。
1.5.1 修改configure脚本
本节的目的是让输出的库不含有版本号。
先找到configure的如下位置:
SHFLAGS=‘-shared -Wl,-soname,$$(@F)‘ LIBPREF="lib" LIBSUF=".a" FULLNAME=‘$(NAME)$(BUILDSUF)‘ LIBNAME=‘$(LIBPREF)$(FULLNAME)$(LIBSUF)‘ SLIBPREF="lib" SLIBSUF=".so" SLIBNAME=‘$(SLIBPREF)$(FULLNAME)$(SLIBSUF)‘ SLIBNAME_WITH_VERSION=‘$(SLIBNAME).$(LIBVERSION)‘ 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_VERSION=‘$(SLIBNAME)‘ SLIBNAME_WITH_MAJOR=‘$(SLIBNAME)‘ LIB_INSTALL_EXTRA_CMD=‘$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"‘ SLIB_INSTALL_NAME=‘$(SLIBNAME_WITH_VERSION)‘ SLIB_INSTALL_LINKS= |
1.5.2 修改配置脚本
新建myconfig.sh
export NDK_ROOT=/opt/ndk export TMPDIR=/tmp ./configure --prefix=$NDK_ROOT/ffmpeg --sysroot=$NDK_ROOT/android-ndk-r8e/platforms/android-14/arch-arm --cross-prefix=arm-linux-androideabi- --target-os=linux --arch=arm --extra-cflags="-I$NDK_ROOT/openlib/include -fPIC " --extra-ldflags=-L$NDK_ROOT/openlib/lib |
这个脚本中是把x264, fdk-aac, lame全部编译上了,你可以视自己的情况来修改。
1.5.3 编译
1) ./myconfig.sh
执行上述配置脚本,开始配置
2) make
3) make install
1.6 在Android.mk中引用FFmpeg库
1.6.1 使用静态库
推荐使用静态库。需要注意,FFmpeg库的静态库引用顺序是不能随便写的,为了避免不必要的麻烦,请照抄以下配置。
在你的Android.mk中添加以下几行(见黑体字部分)
LOCAL_LDLIBS += -llog -lz # ffmpeg FFMPEG=/opt/ndk LOCAL_CFLAGS += -I$(FFMPEG)/include LOCAL_LDLIBS += -l$(FFMPEG)/lib/libavformat.a LOCAL_LDLIBS += -l$(FFMPEG)/lib/libavcodec.a LOCAL_LDLIBS += -l$(FFMPEG)/lib/libavdevice.a LOCAL_LDLIBS += -l$(FFMPEG)/lib/libswresample.a LOCAL_LDLIBS += -l$(FFMPEG)/lib/libavdevice.a LOCAL_LDLIBS += -l$(FFMPEG)/lib/libswscale.a LOCAL_LDLIBS += -l$(FFMPEG)/lib/libpostproc.a LOCAL_LDLIBS += -l$(FFMPEG)/lib/libavutil.a
# fdk_aac LOCAL_LDLIBS += -l/opt/ndk/lib/libfdk-aac.a LOCAL_LDLIBS += -l/opt/ndk/lib/libmp3lame.a LOCAL_LDLIBS += -l/opt/ndk/lib/ libx264.a include $(BUILD_SHARED_LIBRARY) |
1.6.2 使用动态库
不推荐使用动态库,此过程过于复杂。要注意的有两点:①编译的时候,必须去除库的版本号 ② 在Java代码中加载的时候,要注意加载的顺序。
以下可以加在Android.mk中,但是仅供参考,遇到自己问题需要自己调试
# ffmpeg FFMPEG=f:/Ndk/ffmpeg LOCAL_CFLAGS += -I$(FFMPEG)/include LOCAL_LDLIBS += -L$(FFMPEG)/lib LOCAL_LDLIBS += -lavformat -lavcodec -lswresample -lswscale -lavutil -lavfilter include $(BUILD_SHARED_LIBRARY) |
1.6.3 在C++中使用ffmpeg
ffmpeg的库是按C编译的,里面的符号都是C格式,因此,若在C++中使用,还是要使用extern "C"这种技术。相关语法请参考《C/C++语法指南》(作者:邵发,官网http://www.afanihao.cn)。
为了大家的方便,在/opt/ndk/ffmpeg/include下新建一个ffmpeg.h,内容为:
#ifndef _FFMPEG_H #define _FFMPEG_H #ifdef __cplusplus extern "C" { #define INT64_C(val) val##LL #define UINT64_C(val) val##ULL #endif #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <libavutil/avutil.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> #include <libswresample/swresample.h> #include <libavutil/error.h> #include <libavfilter/avfiltergraph.h> #include <libavfilter/avcodec.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> #ifdef __cplusplus } #endif #endif |
然后在C++代码中直接#include "ffmpeg.h"就可以了。