基於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的應用程序了,比如一個簡單的播放器,我們在接下來的文章中再研究。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章