簡介:
本文介紹如何使用 Android NDK(r7) 設置 Android 本地代碼編譯工具鏈,如何根據 Makefile 編寫 Android.mk,並以 ffmpeg(0.8.5) 為例子介紹如何使用此工具鏈移植。使用編譯出來的庫文件,可以通過本地 C/C++ 程序調用 ffmpeg 解碼庫;也可以另外編寫 JNI 接口,使用 Java 程序調用 ffmepg。
我們都知道編譯軟件的一般步驟為:
./configure make make install |
當然還可以增加參數做些自定義,但大概的流程是這樣。要移植一個已有的庫到 Android 當中卻有很大的不同,首先需要搭建一個交叉編譯環境去運行 configure 腳本以便生成配置文件,然后還需要編寫 Android.mk 才能編譯。
拿 ffmpeg 作例子,運行 configure 會生成 config.mak、config.h 和 libavutil/avconfig.h 這幾個文件,里面決定了 ffmpeg 編譯哪些模塊、是否開啟某些特性等。當然如果足夠熟悉的話也可以手動修改這幾個文件,但是其中的依賴關系復雜,較容易出錯。接着根據原來的 Makefile 手動編寫 Android.mk 文件,就能編譯了。以下是詳細流程。
注意:不能直接在宿主系統上運行 configure 腳本,因為環境和目標系統(Android)是不同的,這需要建立交叉編譯環境。
1. 設定編譯工具鏈
這一步沒有實質作用,只是為了說明下一步。有兩種等價的方法,手動指定工具鏈或者使用 NDK 自動生成工具鏈,詳細文檔在 NDK 目錄下的 docs/STANDALONE-TOOLCHAIN.html。
手動指定工具鏈
就是手動指定交叉編譯工具鏈的位置,其中 $NDK 為 NDK 所在目錄。此方法是比較麻煩的一種方法,以下是手動指定 gcc,並編譯 foo.c:
SYSROOT=$NDK/platforms/android-8/arch-arm #可更改 API 版本,8 對應 Android 2.2 export CC=“$NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86/bin/arm-linux-androideabi-gcc --sysroot=$SYSROOT“ $CC -o foo.o -c foo.c |
2. 設定編譯參數
運行 configure 腳本的時候有很多選項,根據自己的需要以及目標系統進行自定義,運行 ./configure --help 了解所有選項。重點需要了解的有:
- 需要的模塊與功能:ffmpeg 有很多組件,根據需要裁減。比如說 codec 有很多,如果只需要其中幾個的話可以把不需要的屏蔽,減小代碼體積。-> 查看 ffmpeg/doc 目錄下的幫助,了解每個模塊的作用。
- 根據目標 CPU 開啟某些指令集:需要了解目標 CPU 架構,一般是 ARM11 或 Cortex-A8;以及是否支持 VFP、NEON 這些擴展指令。-> 查閱 CPU 供應商提供的芯片資料。
- 指定交叉編譯工具鏈:設置交叉編譯工具位置,設置一些必要的 cflags、ldflags。-> 查看 GNU Make 幫助,了解必要選項。了解 Android NDK,了解其支持什么指令集、提供哪些庫。
我現在的目標 CPU 是高通的 8255,架構是 Cortex-A8 支持 VFPv3 以及 NEON 指令集;目標系統是 Android 2.3。希望編譯一個支持文件解析與解碼的庫,不需要其他組件。以下是符合我的需求的配置(手動指定工具鏈):
NDK=你的NDK所在目錄 SYSROOT=$NDK/platforms/android-9/arch-arm PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86 ./configure --disable-ffmpeg --disable-ffplay --disable-ffserver \ # 屏蔽與解碼無關組件,運行時請刪除行末注釋 --disable-ffprobe --disable-swscale --disable-postproc \ --disable-bsfs --disable-filters \ --disable-avdevice --disable-network --disable-devices \ --disable-encoders --disable-muxers \ # 屏蔽編碼相關組件 --disable-protocols --enable-protocol=file \ # 只保留本地文件協議 --enable-cross-compile --target-os=linux \ --arch=arm --cpu=armv7-a \ --enable-shared \ # 直接用 make 編譯時加上 --sysroot=$SYSROOT \ --cc=$PREBUILT/bin/arm-linux-androideabi-gcc \ --enable-memalign-hack \ --extra-cflags=“-march=armv7-a -mfloat-abi=softfp -mfpu=neon“ # CPU 特性 |
運行成功的話會顯示詳細的報告,說明開啟了那些功能與選項,檢查這些選項看看與自己設定的是否一致。檢查 config.h 或 config.mak 也可以確認所有選項的取值情況,檢查 config.log 進一步了解有些選項為什么檢查不通過。其中對性能有重大影響的是 CPU 特性,因為有很多算法都用到了匯編語言優化,檢查以下 config.mak 變量:
HAVE_ARMV5TE=yes HAVE_ARMV6=yes HAVE_ARMV6T2=yes HAVE_ARMVFP=yes HAVE_NEON=yes HAVE_VFPV3=yes |
注意:configure 腳本中檢測某項目標平台特性是通過調用編譯器編譯某些源碼實現的,例如檢測編譯器是否支持 NEON 指令集就是檢測是否能夠成功編譯匯編指令 "vadd.i16 q0, q0, q0"。
注意:如果 CPU 是 ARM11 系列(例如高通 MSM7227),則上面配置相應改為 “--arch=arm --cpu=armv6 \”,--extra-cflags 部分可以去掉。
NEON 指令集加速效果還是很明顯的,因為 ffmpeg 里面不少算法都使用對此進行了優化,可以充分發揮 NEON 單指令多數據(SIMD)的特性。例如在 1.0GHz 的高通 MSM8255 上播放級別為 insane 的 APE 音樂,CPU 占有率從 80% 以上下降到 30% 左右;AVC 解碼時 CPU 占有率也稍微下降了一些。
3. 編譯庫文件
注意:其實也可以根本不寫 Android.mk 而直接使用原有的 Makefile,只要設置時有 ”--enable-shared“ 就可以運行完 configure 后直接 make 編譯。如果想使用 Android.mk 的話請繼續看下去,否則本文已完結。
編寫 Android.mk
以 libavcodec 模塊為例子,我們打開 libavcodec/Makefile 看看,重點需要參考的是 OBJS 開頭的語句,摘抄如下:
# parts needed for many different codecs OBJS-$(CONFIG_AANDCT) += aandcttab.o # 意思是如果 config.mak 中 CONFIG_AANDCT = yes,則添加到變量 OBJS OBJS-$(CONFIG_AC3DSP) += ac3dsp.o OBJS-$(CONFIG_CRYSTALHD) += crystalhd.o OBJS-$(CONFIG_ENCODERS) += faandct.o jfdctfst.o jfdctint.o OBJS-$(CONFIG_DCT) += dct.o dct32_fixed.o dct32_float.o OBJS-$(CONFIG_DWT) += dwt.o OBJS-$(CONFIG_DXVA2) += dxva2.o |
這是根據上一步生成的 config.mak 決定哪些文件將會被編譯,非常重要,我們需要將其添加到 Android.mk 當中。新建一個 libavcodec/Android.mk,復制 libavcodec/Makefile 有關 OBJS 的語句過來,把其中所有的 *.o 替換為 *.c,也就是指定需要編譯的源代碼文件。
#libavcodec/Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) include $(LOCAL_PATH)/../config.mak OBJS = allcodecs.c \ audioconvert.c \ avpacket.c \ bitstream.c \ bitstream_filter.c \ dsputil.c \ faanidct.c \ fmtconvert.c \ imgconvert.c \ jrevdct.c \ options.c \ parser.c \ raw.c \ rawdec.c \ resample.c \ resample2.c \ simple_idct.c \ apedec.c \ utils.c \ # parts needed for many different codecs OBJS-$(CONFIG_AANDCT) += aandcttab.c OBJS-$(CONFIG_AC3DSP) += ac3dsp.c OBJS-$(CONFIG_CRYSTALHD) += crystalhd.c OBJS-$(CONFIG_ENCODERS) += faandct.c jfdctfst.c jfdctint.c OBJS-$(CONFIG_DCT) += dct.c dct32_fixed.c dct32_float.c OBJS-$(CONFIG_DWT) += dwt.c OBJS-$(CONFIG_DXVA2) += dxva2.c ### 中間省略類似的 include $(LOCAL_PATH)/arm/Android.mk # 添加 ARM 相關源文件,下面會講到 LOCAL_CFLAGS := $(CFLAGS) # 添加 config.mak 中的編譯選項 LOCAL_CPPFLAGS := $(CPPFLAGS) LOCAL_SRC_FILES = $(sort $(OBJS) $(OBJS-yes)) # 添加所需源文件,使用 sort 防止多次添加 LOCAL_MODULE := libavcodec LOCAL_MODULE_TAGS := optional LOCAL_PRELINK_MODULE := false LOCAL_SHARED_LIBRARIES += libavutil libz # 依賴 libavutil.so LOCAL_C_INCLUDES := $(LOCAL_PATH) \ # 頭文件位置 $(LOCAL_PATH)/arm \ $(LOCAL_PATH)/../ \ external/zlib \ include $(BUILD_SHARED_LIBRARY) # 編譯成動態庫 |
另外 libavcodec 當中有很多匯編優化的文件,例如我們目標平台是 arm,需要加入 libavcodec/arm 目錄下的源文件。同樣是根據 libavcodec/arm/Makefile 寫成的,摘錄如下:
#libavcodec/arm/Android.mk OBJS-$(CONFIG_AC3DSP) += arm/ac3dsp_init_arm.c \ arm/ac3dsp_arm.c OBJS-$(CONFIG_DCA_DECODER) += arm/dcadsp_init_arm.c \ # 中間省略,根據 ffmpeg 版本不同,文件可能有不同。。。 OBJS-$(HAVE_NEON) += arm/dsputil_init_neon.c \ arm/dsputil_neon.c \ arm/fmtconvert_neon.c \ arm/int_neon.c \ arm/mpegvideo_neon.c \ arm/simple_idct_neon.c \ $(NEON-OBJS-yes) |
在每個需要編譯的目錄下都這樣寫 Android.mk。最后還要在編譯的頂層目錄寫一個調用子目錄的 Android.mk 文件(下一節介紹)。這里我們只編譯其中的三個解碼必須模塊,libavutil libavcodec libavformat。其中 libavutil 依賴 libz,libavcodec 依賴 libavutil,libavformat 依賴前兩者。
編譯
使用 ndk-build 命令可編譯,但要注意目錄布局,假設當前目錄是 $PROJECT,必須把 ffmpeg 源碼目錄以及頂層 Android.mk 放在 $PROJECT/jni 目錄下,然后在 $PROJECT 目錄運行 ndk-build。
#$PROJECT/jni/Android.mk,與 ffmpeg 目錄同級 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) include $(LOCAL_PATH)/ffmpeg/config.mak LOCAL_SHARED_LIBRARIES := libz include $(LOCAL_PATH)/ffmpeg/libavutil/Android.mk \ $(LOCAL_PATH)/ffmpeg/libavcodec/Android.mk \ $(LOCAL_PATH)/ffmpeg/libavformat/Android.mk |
另外可以使用 Android 源碼提供的編譯命令 mmm 編譯,需要把頂層 Android.mk 放在 ffmpeg 目錄下並修改里面的文件路徑。我使用的是此方法,編譯出來的庫使用自制播放器可以正常解碼,應該與 ndk-build 等價。
擴展閱讀
- Android.mk 文件編寫:NDK 自帶文檔,在 NDK/docs 目錄
- Makefile 編寫:Tutorial on writing makefiles
- 網上也有人寫過類似文章:用Android NDK編譯FFmpeg,我參考了其中不少地方。