1. 程式人生 > >FFMPEG研究: FFmpeg的Android平臺移植編譯

FFMPEG研究: FFmpeg的Android平臺移植編譯

摘要:本文主要介紹將FFmpeg音視訊編解碼庫移植到Android平臺上的編譯和基本測試過程。

環境準備:

Ubuntu12.04 TLS

android-ndk-r9d-linux-x86_64.tar.bz2

adt-bundle-windows-x86_64-20131030.zip

第一步:原始碼下載

到FFmpeg官方網站http://www.ffmpeg.org/上去下載原始碼,這裡下載的原始碼是最權威的。進入官網之後,選擇”Download”進入下載頁面,截止2014年3月28日止,最新的釋出的穩定版本為FFmpeg2.2,代號”Muybridge”。選擇該下方的”Downloadgzip tarball”進行下載,下載後的檔名為ffmpeg-2.2.tar.gz,大約8.3M。

第二步:在Linux環境下編譯FFmpeg

在Windows平臺可以採用VMplayer虛擬機器上安裝ubuntu的方式,本人也是採用這種方式。

本文以/home/dennis為根目錄進行操作和說明:

將ffmpeg-2.2.tar.gz拷貝至根目錄,然後執行如下解壓命令將其解壓:

$tar zxf ffmpeg-2.2.tar.gz

解壓後將得到/home/dennis/ffmpeg-2.2目錄。

修改ffmpeg-2.2/configure檔案

如果直接按照未修改的配置進行編譯,結果編譯出來的so檔案類似libavcodec.so.55.39.101,版本號位於so之後,Android上似乎無法載入。因此需要按如下修改:

將該檔案中的如下四行:

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_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'

LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'

SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'

SLIB_INSTALL_LINKS='$(SLIBNAME)'

編寫build_android.sh指令碼檔案

FFmpeg可以說是一個包絡音視訊編解碼及格式的超級霸。因此在編譯前通常都需要進行配置,設定相應的環境變數等。

所有的配置選項都在ffmpeg-2.2/configure這個指令碼檔案中,可以通過執行如下命令來檢視所有的配置選項:

$ ./configure –help

配置選項很多,也較為複雜,這裡先把我需要的搞出來,然後有時間再慢慢看。

我們將需要的配置項和環境變數設定寫成一個sh指令碼檔案來執行以便編譯出Android平臺需要的so檔案出來。

build_android.sh的內容如下:

  1. #!/bin/bash  
  2. NDK=/home/dennis/android-ndk-r9d  
  3. SYSROOT=$NDK/platforms/android-9/arch-arm/  
  4. TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64  
  5. function build_one  
  6. {  
  7. ./configure \  
  8.     --prefix=$PREFIX \  
  9.     --enable-shared \  
  10.     --disable-static \  
  11.     --disable-doc \  
  12.     --disable-ffserver \  
  13.     --enable-cross-compile \  
  14.     --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \  
  15.     --target-os=linux \  
  16.     --arch=arm \  
  17.     --sysroot=$SYSROOT \  
  18.     --extra-cflags="-Os -fpic $ADDI_CFLAGS" \  
  19.     --extra-ldflags="$ADDI_LDFLAGS" \  
  20.     $ADDITIONAL_CONFIGURE_FLAG  
  21. }  
  22. CPU=arm  
  23. PREFIX=$(pwd)/android/$CPU  
  24. ADDI_CFLAGS="-marm"  
  25. build_one  

這個指令碼檔案有幾個地方需要注意:

(1)    NDK,SYSROOT和TOOLCHAIN這三個環境變數一定要換成你自己機器裡的。

(2)    確保cross-prefix變數所指向的路徑是存在的。

給build_android.sh增加可執行許可權:

  1. $chmod+x build_android.sh  

執行build_android.sh

  1. $./build_android.sh  

配置該指令碼完成對ffmpeg的配置,會生成config.h等配置檔案,後面的編譯會用到。如果未經過配置直接進行編譯會提示無法找到config.h檔案等錯誤。

  1. $make  
  2. $make install  

至此,會在/home/dennis/ffmpeg-2.2目錄下生成一個android目錄,其中/home/dennis/ffmpeg-2.2/android/arm/lib目錄下的so庫檔案如下:

  1. -rwxr-xr-x 1 dennisdennis   55208 Mar 29 16:26libavdevice-55.so  
  2. -rwxr-xr-x 1 dennisdennis  632476 Mar 29 16:26 libavfilter-4.so  
  3. -rwxr-xr-x 1 dennisdennis 1442948 Mar 29 16:26 libavformat-55.so  
  4. -rwxr-xr-x 1 dennisdennis 7985396 Mar 29 16:26 libavcodec-55.so  
  5. -rwxr-xr-x 1 dennisdennis   83356 Mar 29 16:26libswresample-0.so  
  6. -rwxr-xr-x 1 dennisdennis  308636 Mar 29 16:26 libswscale-2.so  
  7. -rwxr-xr-x 1 dennisdennis  300580 Mar 29 16:26libavutil-52.so  

注:以上列表去掉了符號連結檔案和pkgconfig目錄。

第三步:建立一個普通的Android工程

  1. 建立一個新的Android工程FFmpeg4Android
  2. 在工程根目錄下建立jni資料夾
  3. 在jni下建立prebuilt目錄,然後:

(1)     將上面編譯成功的7個so檔案放入到該目錄下;

(2)     將/home/dennis/ffmpeg-2.2/android/arm/include下的所有標頭檔案夾拷貝到該目錄下.

  1. 建立包含native方法的類,先在src下建立cn.dennishucd包,然後建立FFmpegNative.java類檔案。主要包括載入so庫檔案和一個native測試方法兩部分,其內容如下:
  1. package cn.dennishucd;  
  2. publicclass FFmpegNative {  
  3.                 static{  
  4.                                 System.loadLibrary("avutil-52");  
  5.                                 System.loadLibrary("avcodec-55");  
  6.                                 System.loadLibrary("swresample-0");  
  7.                                 System.loadLibrary("avformat-55");  
  8.                                 System.loadLibrary("swscale-2");  
  9.                                 System.loadLibrary("avfilter-3");  
  10.                                 System.loadLibrary("ffmpeg_codec");  
  11.                 }  
  12.                 publicnative int avcodec_find_decoder(int codecID);  
  13. }  
  1. 用javah建立.標頭檔案:

進入bin/classes目錄,執行:javah-jni cn.dennishucd.FFmpegNative                 

 會在當前目錄產生cn_dennishucd_FFmpegNative.h的C標頭檔案;

  1. 根據標頭檔案名,建立相同名字才C原始檔cn_dennishucd_FFmpegNative.c

在這個原始檔中實現標頭檔案中定義的方法,核心部分程式碼如下:

  1. JNIEXPORT jint JNICALLJava_cn_dennishucd_FFmpegNative_avcodec_1find_1decoder  
  2.  (JNIEnv *env, jobject obj, jint codecID)  
  3. {  
  4.                 AVCodec*codec = NULL;  
  5.                 /*register all formats and codecs */  
  6.                 av_register_all();  
  7.                 codec= avcodec_find_decoder(codecID);  
  8.                 if(codec != NULL)  
  9.                 {  
  10.                                 return0;  
  11.                 }  
  12.                 else  
  13.                 {  
  14.                                 return-1;  
  15.                 }  
  16. }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>  
  1. 編寫Android.mk,內容如下:
  1. LOCAL_PATH := $(callmy-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_MODULE :=avcodec-55-prebuilt  
  4. LOCAL_SRC_FILES :=prebuilt/libavcodec-55.so  
  5. include$(PREBUILT_SHARED_LIBRARY)  
  6. include $(CLEAR_VARS)  
  7. LOCAL_MODULE :=avdevice-55-prebuilt  
  8. LOCAL_SRC_FILES :=prebuilt/libavdevice-55.so  
  9. include$(PREBUILT_SHARED_LIBRARY)  
  10. include $(CLEAR_VARS)  
  11. LOCAL_MODULE :=avfilter-4-prebuilt  
  12. LOCAL_SRC_FILES :=prebuilt/libavfilter-4.so  
  13. include$(PREBUILT_SHARED_LIBRARY)  
  14. include $(CLEAR_VARS)  
  15. LOCAL_MODULE :=avformat-55-prebuilt  
  16. LOCAL_SRC_FILES :=prebuilt/libavformat-55.so  
  17. include$(PREBUILT_SHARED_LIBRARY)  
  18. include $(CLEAR_VARS)  
  19. LOCAL_MODULE :=  avutil-52-prebuilt  
  20. LOCAL_SRC_FILES :=prebuilt/libavutil-52.so  
  21. include$(PREBUILT_SHARED_LIBRARY)  
  22. include $(CLEAR_VARS)  
  23. LOCAL_MODULE :=  avswresample-0-prebuilt  
  24. LOCAL_SRC_FILES :=prebuilt/libswresample-0.so  
  25. include $(PREBUILT_SHARED_LIBRARY)  
  26. include $(CLEAR_VARS)  
  27. LOCAL_MODULE :=  swscale-2-prebuilt  
  28. LOCAL_SRC_FILES :=prebuilt/libswscale-2.so  
  29. include$(PREBUILT_SHARED_LIBRARY)  
  30. include $(CLEAR_VARS)  
  31. LOCAL_MODULE :=ffmpeg_codec  
  32. LOCAL_SRC_FILES :=cn_dennishucd_FFmpegNative.c  
  33. LOCAL_LDLIBS := -llog-ljnigraphics -lz -landroid  
  34. LOCAL_SHARED_LIBRARIES:= avcodec-55-prebuilt avdevice-55-prebuilt avfilter-4-prebuiltavformat-55-prebuilt avutil-52-prebuilt  
  35. include$(BUILD_SHARED_LIBRARY)  
  1. 編寫Application.mk[可省略]
  2. 編譯so檔案

開啟cmd命令列,進入FFmpeg4Android\jni目錄下,執行如下命令:

  1. $ndk-build  

截止本步驟完成,將在FFmpeg4Android根目錄下生成libs\armeabi目錄,該目錄除了包含上面的7個so之外,另外還生成了libffmpeg_codec.so檔案。

  1. 新增庫的載入方法

在FFmpegNative類中增加如下載入so庫的程式碼:

//注意以下所有lib,從make install 出來後,必須保持名字始終是一致的,否則apk載入不到庫  

  1. static {  
  2.                                 System.loadLibrary("avutil-52");  
  3.                                 System.loadLibrary("avcodec-55");  
  4.                                 System.loadLibrary("swresample-0");  
  5.                                 System.loadLibrary("avformat-55");  
  6.                                 System.loadLibrary("swscale-2");  
  7.                                 System.loadLibrary("avfilter-3");  
  8.                                 System.loadLibrary("avdevice-55");  
  9.                                 System.loadLibrary("ffmpeg_codec");  
  10. }  
  1. 修改layout/main.xml,給TextView增加id,以便在程式碼中操作它。
    1. <?xmlversion="1.0"encoding="utf-8"?>
    2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    3.     android:orientation="horizontal"
    4.     android:layout_width="fill_parent"
    5.     android:layout_height="fill_parent"
    6.     >
    7.   <TextView
    8.     android:id="@+id/textview_hello"
    9.     android:text="@string/hello"
    10.     android:layout_width="wrap_content"
    11.     android:layout_height="wrap_content"
    12.     android:layout_gravity="center"
    13.     />
    14. </LinearLayout>

  2. 增加一個Activity實現類FFmpeg4AndroidActivity,在OnCreate方法中呼叫native函式將值傳給TextView控制元件,打包執行即可。FFmpeg4AndroidActivity程式碼如下:
  1. package cn.dennishucd;  
  2. import android.app.Activity;  
  3. import android.os.Bundle;  
  4. import android.widget.TextView;  
  5. public class FFmpeg4AndroidActivity extends Activity {  
  6.                 @Override  
  7.                 protectedvoid onCreate(Bundle savedInstanceState) {  
  8.                                 super.onCreate(savedInstanceState);  
  9.                                 setContentView(R.layout.main);  
  10.                                 TextViewtv = (TextView)this.findViewById(R.id.textview_hello);  
  11.                                 FFmpegNativeffmpeg = new FFmpegNative();  
  12.                                 intcodecID = 28; //28 is the H264 Codec ID  
  13.                                 intres = ffmpeg.avcodec_find_decoder(codecID);  
  14.                                 if(res ==0) {  
  15.                                                 tv.setText("Success!");  
  16.                                 }  
  17.                                 else{  
  18.                                                 tv.setText("Failed!");  
  19.                                 }  
  20.                 }  
  21. }  

        程式碼中的28是H264的編解碼ID,可以在ffmpeg的原始碼中找到,它是列舉型別定義的。在C語言中,可以換算為整型值。這裡測試能否找到H264編解碼,如果能找到,說明呼叫ffmpeg的庫函式是成功的,這也表明我們編譯的so檔案是基本可用。

作者注:

[1] 本文編譯的方法主要參考了參考資料 [1] 中的思路,這裡要感謝作者的貢獻;

[2] 後面的測試過程是參考了ffmpeg-2.1.4中的decoding_encoding.c例子;

[3] 關於如何使用pre-built參考了參考資料 [2] 中的思路;

[4] 這只是移植過程第一步,後面還會進一步分析ffmpeg的介面來呼叫其編解碼庫.

[5]Android.mk檔案應該