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的內容如下:
- #!/bin/bash
- NDK=/home/dennis/android-ndk-r9d
- SYSROOT=$NDK/platforms/android-9/arch-arm/
- TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64
- function build_one
- {
- ./configure \
- --prefix=$PREFIX \
- --enable-shared \
- --disable-static \
- --disable-doc \
- --disable-ffserver \
- --enable-cross-compile \
- --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
- --target-os=linux \
- --arch=arm \
- --sysroot=$SYSROOT \
- --extra-cflags="-Os -fpic $ADDI_CFLAGS" \
- --extra-ldflags="$ADDI_LDFLAGS" \
- $ADDITIONAL_CONFIGURE_FLAG
- }
- CPU=arm
- PREFIX=$(pwd)/android/$CPU
- ADDI_CFLAGS="-marm"
- build_one
這個指令碼檔案有幾個地方需要注意:
(1) NDK,SYSROOT和TOOLCHAIN這三個環境變數一定要換成你自己機器裡的。
(2) 確保cross-prefix變數所指向的路徑是存在的。
給build_android.sh增加可執行許可權:
- $chmod+x build_android.sh
執行build_android.sh
- $./build_android.sh
配置該指令碼完成對ffmpeg的配置,會生成config.h等配置檔案,後面的編譯會用到。如果未經過配置直接進行編譯會提示無法找到config.h檔案等錯誤。
- $make
- $make install
至此,會在/home/dennis/ffmpeg-2.2目錄下生成一個android目錄,其中/home/dennis/ffmpeg-2.2/android/arm/lib目錄下的so庫檔案如下:
- -rwxr-xr-x 1 dennisdennis 55208 Mar 29 16:26libavdevice-55.so
- -rwxr-xr-x 1 dennisdennis 632476 Mar 29 16:26 libavfilter-4.so
- -rwxr-xr-x 1 dennisdennis 1442948 Mar 29 16:26 libavformat-55.so
- -rwxr-xr-x 1 dennisdennis 7985396 Mar 29 16:26 libavcodec-55.so
- -rwxr-xr-x 1 dennisdennis 83356 Mar 29 16:26libswresample-0.so
- -rwxr-xr-x 1 dennisdennis 308636 Mar 29 16:26 libswscale-2.so
- -rwxr-xr-x 1 dennisdennis 300580 Mar 29 16:26libavutil-52.so
注:以上列表去掉了符號連結檔案和pkgconfig目錄。
第三步:建立一個普通的Android工程
- 建立一個新的Android工程FFmpeg4Android
- 在工程根目錄下建立jni資料夾
- 在jni下建立prebuilt目錄,然後:
(1) 將上面編譯成功的7個so檔案放入到該目錄下;
(2) 將/home/dennis/ffmpeg-2.2/android/arm/include下的所有標頭檔案夾拷貝到該目錄下.
- 建立包含native方法的類,先在src下建立cn.dennishucd包,然後建立FFmpegNative.java類檔案。主要包括載入so庫檔案和一個native測試方法兩部分,其內容如下:
- package cn.dennishucd;
- publicclass FFmpegNative {
- static{
- System.loadLibrary("avutil-52");
- System.loadLibrary("avcodec-55");
- System.loadLibrary("swresample-0");
- System.loadLibrary("avformat-55");
- System.loadLibrary("swscale-2");
- System.loadLibrary("avfilter-3");
- System.loadLibrary("ffmpeg_codec");
- }
- publicnative int avcodec_find_decoder(int codecID);
- }
- 用javah建立.標頭檔案:
進入bin/classes目錄,執行:javah-jni cn.dennishucd.FFmpegNative
會在當前目錄產生cn_dennishucd_FFmpegNative.h的C標頭檔案;
- 根據標頭檔案名,建立相同名字才C原始檔cn_dennishucd_FFmpegNative.c
在這個原始檔中實現標頭檔案中定義的方法,核心部分程式碼如下:
- JNIEXPORT jint JNICALLJava_cn_dennishucd_FFmpegNative_avcodec_1find_1decoder
- (JNIEnv *env, jobject obj, jint codecID)
- {
- AVCodec*codec = NULL;
- /*register all formats and codecs */
- av_register_all();
- codec= avcodec_find_decoder(codecID);
- if(codec != NULL)
- {
- return0;
- }
- else
- {
- return-1;
- }
- }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
- 編寫Android.mk,內容如下:
- LOCAL_PATH := $(callmy-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE :=avcodec-55-prebuilt
- LOCAL_SRC_FILES :=prebuilt/libavcodec-55.so
- include$(PREBUILT_SHARED_LIBRARY)
- include $(CLEAR_VARS)
- LOCAL_MODULE :=avdevice-55-prebuilt
- LOCAL_SRC_FILES :=prebuilt/libavdevice-55.so
- include$(PREBUILT_SHARED_LIBRARY)
- include $(CLEAR_VARS)
- LOCAL_MODULE :=avfilter-4-prebuilt
- LOCAL_SRC_FILES :=prebuilt/libavfilter-4.so
- include$(PREBUILT_SHARED_LIBRARY)
- include $(CLEAR_VARS)
- LOCAL_MODULE :=avformat-55-prebuilt
- LOCAL_SRC_FILES :=prebuilt/libavformat-55.so
- include$(PREBUILT_SHARED_LIBRARY)
- include $(CLEAR_VARS)
- LOCAL_MODULE := avutil-52-prebuilt
- LOCAL_SRC_FILES :=prebuilt/libavutil-52.so
- include$(PREBUILT_SHARED_LIBRARY)
- include $(CLEAR_VARS)
- LOCAL_MODULE := avswresample-0-prebuilt
- LOCAL_SRC_FILES :=prebuilt/libswresample-0.so
- include $(PREBUILT_SHARED_LIBRARY)
- include $(CLEAR_VARS)
- LOCAL_MODULE := swscale-2-prebuilt
- LOCAL_SRC_FILES :=prebuilt/libswscale-2.so
- include$(PREBUILT_SHARED_LIBRARY)
- include $(CLEAR_VARS)
- LOCAL_MODULE :=ffmpeg_codec
- LOCAL_SRC_FILES :=cn_dennishucd_FFmpegNative.c
- LOCAL_LDLIBS := -llog-ljnigraphics -lz -landroid
- LOCAL_SHARED_LIBRARIES:= avcodec-55-prebuilt avdevice-55-prebuilt avfilter-4-prebuiltavformat-55-prebuilt avutil-52-prebuilt
- include$(BUILD_SHARED_LIBRARY)
- 編寫Application.mk[可省略]
- 編譯so檔案
開啟cmd命令列,進入FFmpeg4Android\jni目錄下,執行如下命令:
- $ndk-build
截止本步驟完成,將在FFmpeg4Android根目錄下生成libs\armeabi目錄,該目錄除了包含上面的7個so之外,另外還生成了libffmpeg_codec.so檔案。
- 新增庫的載入方法
在FFmpegNative類中增加如下載入so庫的程式碼:
//注意以下所有lib,從make install 出來後,必須保持名字始終是一致的,否則apk載入不到庫
- static {
- System.loadLibrary("avutil-52");
- System.loadLibrary("avcodec-55");
- System.loadLibrary("swresample-0");
- System.loadLibrary("avformat-55");
- System.loadLibrary("swscale-2");
- System.loadLibrary("avfilter-3");
- System.loadLibrary("avdevice-55");
- System.loadLibrary("ffmpeg_codec");
- }
- 修改layout/main.xml,給TextView增加id,以便在程式碼中操作它。
- <?xmlversion="1.0"encoding="utf-8"?>
- <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <TextView
- android:id="@+id/textview_hello"
- android:text="@string/hello"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- />
- </LinearLayout>
- 增加一個Activity實現類FFmpeg4AndroidActivity,在OnCreate方法中呼叫native函式將值傳給TextView控制元件,打包執行即可。FFmpeg4AndroidActivity程式碼如下:
- package cn.dennishucd;
- import android.app.Activity;
- import android.os.Bundle;
- import android.widget.TextView;
- public class FFmpeg4AndroidActivity extends Activity {
- @Override
- protectedvoid onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- TextViewtv = (TextView)this.findViewById(R.id.textview_hello);
- FFmpegNativeffmpeg = new FFmpegNative();
- intcodecID = 28; //28 is the H264 Codec ID
- intres = ffmpeg.avcodec_find_decoder(codecID);
- if(res ==0) {
- tv.setText("Success!");
- }
- else{
- tv.setText("Failed!");
- }
- }
- }
程式碼中的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檔案應該