編譯FFmpeg在Android上使用
編譯Android上可用的FFmpeg並測試。
編譯環境:
Ubuntu 16.04.1 64位 (虛擬機器)
android-ndk-r9d
開發環境:
Window 10 64位
android-ndk-r9d
AndroidStudio 2.2.3
詳細步驟
以下步驟在Ubuntu環境中執行
配置NDK環境變數
- 下載並解壓ndk包(本次測試ndk版本是android-ndk-r9d)
- 在終端執行 gedit ~/.bashrc
- 在檔案末尾寫入如下內容並儲存(注意替換自己的路徑)
ANDROID_NDK = “/home/ygl/ndk/android-ndk-r9d”
export ANDROID_NDK - 可以通過在終端輸入 ndk-build 驗證是否配置正確
- 如需配置sdk環境,步驟與以上類似
編譯最新FFmpeg原始碼
- 將下載的檔案拷貝至合適的資料夾
- 在該目錄下執行tar -jxvf ffmpeg-3.3.2.tar.bz2(請注意自己的版本和壓縮格式)
修改 ./configure 檔案,該檔案影響編譯後so檔案的命名,預設在Android上出現問題
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)' LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"' SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)
新建可執行檔案,在根目錄下新建 build_android.sh,並寫入一下內容
export TMPDIR=/Users/hubin/Desktop/ffmpeg-3.3.2/ffmpegtemp #這句很重要,不然會報錯 unable to create temporary file in # NDK的路徑,根據自己的安裝位置進行設定 NDK=~/Applications/android-sdk/ndk-bundle # 編譯針對的平臺,可以根據自己的需求進行設定 # 這裡選擇最低支援android-14, arm架構,生成的so庫是放在 # libs/armeabi資料夾下的,若針對x86架構,要選擇arch-x86 PLATFORM=$NDK/platforms/android-14/arch-arm # 工具鏈的路徑,根據編譯的平臺不同而不同 # arm-linux-androideabi-4.9與上面設定的PLATFORM對應,4.9為工具的版本號, # 根據自己安裝的NDK版本來確定,一般使用最新的版本 TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 function build_one { ./configure \ --prefix=$PREFIX \ --target-os=linux \ --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \ --arch=arm \ --sysroot=$PLATFORM \ --extra-cflags="-I$PLATFORM/usr/include" \ --cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \ --nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \ --enable-shared \ --enable-runtime-cpudetect \ --enable-gpl \ --enable-small \ --enable-cross-compile \ --disable-debug \ --disable-static \ --disable-doc \ --disable-asm \ --disable-ffmpeg \ --disable-ffplay \ --disable-ffprobe \ --disable-ffserver \ --disable-postproc \ --disable-avdevice \ --disable-symver \ --disable-stripping \ $ADDITIONAL_CONFIGURE_FLAG sed -i '' 's/HAVE_LRINT 0/HAVE_LRINT 1/g' config.h sed -i '' 's/HAVE_LRINTF 0/HAVE_LRINTF 1/g' config.h sed -i '' 's/HAVE_ROUND 0/HAVE_ROUND 1/g' config.h sed -i '' 's/HAVE_ROUNDF 0/HAVE_ROUNDF 1/g' config.h sed -i '' 's/HAVE_TRUNC 0/HAVE_TRUNC 1/g' config.h sed -i '' 's/HAVE_TRUNCF 0/HAVE_TRUNCF 1/g' config.h sed -i '' 's/HAVE_CBRT 0/HAVE_CBRT 1/g' config.h sed -i '' 's/HAVE_RINT 0/HAVE_RINT 1/g' config.h make clean make -j4 make install } # arm v7vfp CPU=armv7-a OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU " PREFIX=./android/$CPU-vfp ADDITIONAL_CONFIGURE_FLAG= build_one
- 執行 chmod 777 build_android.sh, 新增執行許可權
- 執行* ./build_android.sh * 完成編譯
- 編譯成功後再 android 資料夾下會有 include資料夾和編譯好的so檔案
以下步驟在開發環境中進行
JNI實現FFmpeg方法的呼叫
- 在MainActivity中新建本地方法(位置隨意)
//輸出FFmpeg資訊作為測試
public native String avformatinfo();
//執行FFmpeg 命令
public native int ffmpegcore(String[] argv);
- 在Terminal中執行 javah com.ygl.ffmpegtest.MainActivity (javah 完整包名)
- 將生成的 xxx.h檔案移至 jni目錄下
新建ffmpegdemo.c檔案,並將ffmpeg部分原始碼檔案和編譯好的so檔案也拷貝至該目錄,專案最終目錄如下:
需要拷貝的原始檔有:cmdutils.c cmdutils.h cmdutils_common_opts.h ffmpeg.c ffmpeg.h ffmpeg_filter.c ffmpeg_opt.c修改 ffmpeg.c 和 ffmpeg.h
在ffmpeg.c中,把 void main(int argc,char argv) 改成 int main(int argc,char argv)
在ffmpeg.h 末尾新增函式申明 int run(int argc,char **argv)int run(int argc,char **argv);
- 修改 cmdutils.c 和 cmdutils.h
cmdutils.c中刪除 exit_program 的函式體,直接返回引數,並修改函式的返回型別
cmdutils.h 中修改 exit_program 的申明將返回值型別修改為 int
int exit_program(int ret){
return ret;
}
jni目錄下新建Android.mk檔案
LOCAL_PATH := $(call my-dir) # FFmpeg library include $(CLEAR_VARS) LOCAL_MODULE := avformat LOCAL_SRC_FILES := libavformat-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avcodec LOCAL_SRC_FILES := libavcodec-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avfilter LOCAL_SRC_FILES := libavfilter-6.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avdevice LOCAL_SRC_FILES := libavdevice-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avutil LOCAL_SRC_FILES := libavutil-55.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swresample LOCAL_SRC_FILES := libswresample-2.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swscale LOCAL_SRC_FILES := libswscale-4.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := postproc LOCAL_SRC_FILES := libpostproc-54.so include $(PREBUILT_SHARED_LIBRARY) # Program include $(CLEAR_VARS) LOCAL_MODULE := ffmpegdemo LOCAL_SRC_FILES := ffmpegdemo.c ffmpeg.c ffmpeg_opt.c cmdutils.c ffmpeg_filter.c #原始檔地址,替換成自己的地址 LOCAL_C_INCLUDES := F:/ffmpeg-3.3.2 LOCAL_LDLIBS := -llog -lz LOCAL_SHARED_LIBRARIES := avfilter avformat avdevice avcodec avutil swresample swscale postproc include $(BUILD_SHARED_LIBRARY)
- jni下新建Application.mk(可選)
APP_ABI := armeabi-v7a
APP_MODULES := libffmpegdemo
編輯 ffmpegdemo.c 檔案
// // Created by Ygl on 2017/6/7. // #include <stdio.h> #include "com_ygl_ffmpegtest_MainActivity.h" #include "include/libavformat/avformat.h" #include "include/libavcodec/avcodec.h" #include "include/libavutil/avutil.h" #include "include/libavfilter/avfilter.h" #include <ffmpeg.h> //Log #ifdef ANDROID #include <jni.h> #include <android/log.h> #define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__) #else #define LOGE(format, ...) printf("(>_<) " format "\n", ##__VA_ARGS__) #endif /** * com.ihubin.ffmpegstudy.MainActivity.avformatinfo() * AVFormat Support Information */ int ffmpegmian(int argc, char **argv); JNIEXPORT jstring JNICALL Java_com_ygl_ffmpegtest_MainActivity_avformatinfo (JNIEnv *env, jobject obj){ char info[40000] = { 0 }; av_register_all(); AVInputFormat *if_temp = av_iformat_next(NULL); AVOutputFormat *of_temp = av_oformat_next(NULL); //Input while(if_temp!=NULL){ sprintf(info, "%s[In ][%10s]\n", info, if_temp->name); if_temp=if_temp->next; } //Output while (of_temp != NULL){ sprintf(info, "%s[Out][%10s]\n", info, of_temp->name); of_temp = of_temp->next; } //LOGE("%s", info); return (*env)->NewStringUTF(env, info); } JNIEXPORT jint JNICALL Java_com_ygl_ffmpegtest_MainActivity_ffmpegcore (JNIEnv *env, jobject obj, jobjectArray commands){ int argc = (*env)->GetArrayLength(env, commands); char *argv[argc]; int i; for(i =0;i<argc;i++){ jstring js = (jstring) (*env)->GetObjectArrayElement(env, commands, i); argv[i] = (char*) (*env)->GetStringUTFChars(env, js, 0); } return run(argc, argv); }
- 在 jni 目錄下,執行 ndk-build 命令,便可以生成需要so檔案
編輯我們的 MainActivity
public class MainActivity extends AppCompatActivity { static { System.loadLibrary("avcodec-57"); System.loadLibrary("avdevice-57"); System.loadLibrary("avfilter-6"); System.loadLibrary("avformat-57"); System.loadLibrary("avutil-55"); System.loadLibrary("postproc-54"); System.loadLibrary("swresample-2"); System.loadLibrary("swscale-4"); System.loadLibrary("ffmpegdemo"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onClick(View view){ String base = Environment.getExternalStorageDirectory().getPath(); StringBuilder cmdLine = new StringBuilder(); cmdLine.append("ffmpeg -i ").append(base).append("/input.mp4 ").append(base).append("/out.avi"); Log.v("ygl", cmdLine.toString()); final String[] commands = cmdLine.toString().split(" "); new Thread(){ @Override public void run() { super.run(); int result = ffmpegcore(commands); Log.v("ygl","result = "+result); } }.start(); } //輸出FFmpeg資訊作為測試 public native String avformatinfo(); //執行FFmpeg 命令 public native int ffmpegcore(String[] argv); }
- 可以呼叫 ffmpegcore 該本地方法來執行我們的FFmpeg命令了
實踐中可能出現的問題
- 儘量保證編譯和開發環境的ndk版本一致
在編譯FFmpeg的時候提示錯誤”config.mak no such file or directory”
先執行 ./configure 再執行 build_android.sh
如果不熟悉JNI,一定要先照著網上寫一些例子,熟悉一下過程。
- 執行的時候如果忘記加適當的許可權或者命令有錯都可能導致崩潰哦