1. 程式人生 > >基於NDK編譯Android平臺的FFmpeg動態庫

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