最近要爲論文搭建實驗環境,須要將FFmpeg移植到Android平臺。參考不少前輩的技術文章,研究以下。另外,Android下的多媒體技術很是有意思,要學的東西不少,編譯個小庫只是很簡單的一件事,麻煩的是如何在這個基礎上去作應用。JNI如何封裝,上層應用又如何跟底下的庫協同工做,如何架構,這纔是有價值的技術,會嗎? java
交叉編譯主機:UBUNTU14.04LTS 64位 linux
Android NDK:android-ndk-r9d-linux-x86_64.tar.bz2 android
Android SDK:adt-bundle-linux-x86_64-20140702.zip shell
這個過程沒有什麼技術含量 bash
這類開源庫的編譯或者交叉編譯無非就是三步:1 configure;2 make; make install 多線程
若是直接按照未修改的配置進行編譯,結果編譯出來的so文件相似libavcodec.so.55.39.101,版本號位於so以後,Android上彷佛沒法加載。所以須要按以下修改: 架構
將該文件中的以下四行: eclipse
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)' ide
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"' ui
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)'
在此以後,能夠開始配置,編寫一個配置的shell腳本,內容以下:
#!/bin/bash NDK=/home/abc/tools/android_ndk/android-ndk-r9d/ SYSROOT=$NDK/platforms/android-16/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
make的時候能夠多線程make,個人I7-4700是四核八線程的,用-j9。
LD libavutil/libavutil-54.so
LD libswscale/libswscale-3.so
LD libswresample/libswresample-1.so
LD libavcodec/libavcodec-56.so
LD libavformat/libavformat-56.so
LD libavfilter/libavfilter-5.so
LD libavdevice/libavdevice-56.so
在android/arm/目錄下就有交叉編譯好的include和lib目錄,這些是所需的。抽取include目錄下全部和lib目錄裏以下所示的文件:
-rwxr-xr-x 1 abc abc 8.5M 5月 6 15:18 libavcodec-56.so
-rwxr-xr-x 1 abc abc 58K 5月 6 15:18 libavdevice-56.so
-rwxr-xr-x 1 abc abc 790K 5月 6 15:18 libavfilter-5.so
-rwxr-xr-x 1 abc abc 1.6M 5月 6 15:18 libavformat-56.so
-rwxr-xr-x 1 abc abc 326K 5月 6 15:18 libavutil-54.so
-rwxr-xr-x 1 abc abc 82K 5月 6 15:18 libswresample-1.so
-rwxr-xr-x 1 abc abc 334K 5月 6 15:18 libswscale-3.so
至此,FFmpeg的編譯就已經完成了,接下來,主要是將其應用在安桌應用程序裏,一方面研究如何在Android應用中調用FFmpeg庫來工做,另外一方面,確認FFmpeg庫是否能正常工做。
JNI項目的常規流程就是4步,先了解這四步:
A 建立一個類,其中包含要實現的native成員方法
B 用javah -jni A步定義的類生成的class名,生成頭文件,用C/C++實現生成的頭文件中的方法
C 編寫Android.mk和Application.mk文件並處理好要用到的頭文件和連接庫文件
D ndk-build編譯
public class FFmpegNative { static { System.loadLibrary("avutil-54"); // 1 System.loadLibrary("swresample-1"); // 2 System.loadLibrary("avcodec-56"); // 3 System.loadLibrary("avformat-56"); // 4 System.loadLibrary("swscale-3"); // 5 System.loadLibrary("avfilter-5"); // 6 System.loadLibrary("avdevice-56"); // 7 } public native int avcodec_find_decoder(int codecID); }其中定義了native成員方法,在eclipse中保存會自動編譯爲class文件。
在安桌工程的根目錄下建立一個jni目錄,用來放jni全部相關的文件。
將2中的lib文件和include目錄下全部頭文件放到jni下。再到android工程目錄的bin/class目錄下,執行以下命令:
javah -jni -d ../../jni/ com.example.ffmpeg4android.FFmpegNative便可生成頭文件到jni目錄,內容以下:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_ffmpeg4android_FFmpegNative */ #ifndef _Included_com_example_ffmpeg4android_FFmpegNative #define _Included_com_example_ffmpeg4android_FFmpegNative #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_ffmpeg4android_FFmpegNative * Method: avcodec_find_decoder * Signature: (I)I */ JNIEXPORT jint JNICALL Java_com_example_ffmpeg4android_FFmpegNative_avcodec_1find_1decoder (JNIEnv *, jobject, jint); #ifdef __cplusplus } #endif #endif實現其中的native方法,代碼以下,保存在com_example_ffmpeg4android_FFmpegNative.c文件中:
#include <math.h> #include <libavutil/opt.h> #include <libavcodec/avcodec.h> #include <libavutil/channel_layout.h> #include <libavutil/common.h> #include <libavutil/imgutils.h> #include <libavutil/mathematics.h> #include <libavutil/samplefmt.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <jni.h> #include "com_example_ffmpeg4android_FFmpegNative.h" jint JNICALL Java_com_example_ffmpeg4android_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); // avcodec_find_decoder是根據codec的ID來找AVCodec的 if(codec != NULL) { return 0; } else { return -1; } }
在工程的jni目錄下的Android.mk文件內容以下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE :=avcodec-56 LOCAL_SRC_FILES :=libavcodec-56.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE :=avdevice-56 LOCAL_SRC_FILES :=libavdevice-56.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE :=avfilter-5 LOCAL_SRC_FILES :=libavfilter-5.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE :=avformat-56 LOCAL_SRC_FILES :=libavformat-56.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avutil-54 LOCAL_SRC_FILES :=libavutil-54.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avswresample-1 LOCAL_SRC_FILES :=libswresample-1.so include $(PREBUILT_SHARED_LIBRARY) LOCAL_MODULE := swscale-3 LOCAL_SRC_FILES :=libswscale-3.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE :=ffmpeg_codec LOCAL_SRC_FILES :=com_example_ffmpeg4android_FFmpegNative.c LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid LOCAL_SHARED_LIBRARIES:= avcodec-56 avdevice-56 avfilter-5 avformat-56 avutil-54 include $(BUILD_SHARED_LIBRARY)其中的Application.mk文件內容以下:
APP_ABI:=armeabi APP_PLATFORM:=android-16
abc@k610c:~/and_workspace/FFmpeg4Android/jni$ ndk-build [armeabi] Install : libavcodec-56.so => libs/armeabi/libavcodec-56.so [armeabi] Install : libavdevice-56.so => libs/armeabi/libavdevice-56.so [armeabi] Install : libavfilter-5.so => libs/armeabi/libavfilter-5.so [armeabi] Install : libavformat-56.so => libs/armeabi/libavformat-56.so [armeabi] Install : libswresample-1.so => libs/armeabi/libswresample-1.so [armeabi] Install : libavutil-54.so => libs/armeabi/libavutil-54.so [armeabi] Compile thumb : ffmpeg_codec <= com_example_ffmpeg4android_FFmpegNative.c [armeabi] SharedLibrary : libffmpeg_codec.so [armeabi] Install : libffmpeg_codec.so => libs/armeabi/libffmpeg_codec.so [armeabi] Prebuilt : libswscale-3.so <= jni/prebuilt/ [armeabi] Install : libswscale-3.so => libs/armeabi/libswscale-3.so
此時,已經生成了libffmpeg_codec.so這個文件,這個庫是編寫的jni代碼生成的庫,因此也要加載到應用。修改FFmpegNative類定義以下:
public class FFmpegNative { static { System.loadLibrary("avutil-54"); // 1 System.loadLibrary("swresample-1"); // 2 System.loadLibrary("avcodec-56"); // 3 System.loadLibrary("avformat-56"); // 4 System.loadLibrary("swscale-3"); // 5 System.loadLibrary("avfilter-5"); // 6 System.loadLibrary("avdevice-56"); // 7 System.loadLibrary("ffmpeg_codec"); // 8 /*特別注意,在ffmpeg2.5中這8個庫的順序是不能錯的,由於,加庫的時候庫有前後依賴關係*/ } public native int avcodec_find_decoder(int codecID); }
在Activity裏面建立一個TextView控件,而後根據調用native方法的返回值結果來顯示。
TextView tv = (TextView)this.findViewById(R.id.textview_hello); FFmpegNative ffmpeg = new FFmpegNative(); int codecId = 28; //H.264 int res = ffmpeg.avcodec_find_decoder(codecId); if(res == 0) { tv.setText("Success"); } else { tv.setText("Failed"); }
結果顯示Success