1.首先我們需要一個已經編譯好的libffmpeg.so文件。(怎么編譯是個大坑,可以參考windows環境下編譯android中使用的FFmpeg,也可以用網上下載的現成的,本文相關的github項目里也有。),當然也要下載好ffmpeg的源碼,一會要用到。
2.打開你的Android工程,在 src/main/ 目錄下新建 jni 目錄。並將libffmpeg.so文件丟進去。
3.創建FFmpegKit.java。寫入如下代碼
package codepig.ffmpegcldemo; import android.os.AsyncTask; public class FFmpegKit { public interface KitInterface{ void onStart(); void onProgress(int progress); void onEnd(int result); } static{ System.loadLibrary("ffmpeg"); System.loadLibrary("ffmpeginvoke"); } public static int execute(String[] commands){ return run(commands); } public static void execute(String[] commands, final KitInterface kitIntenrface){ new AsyncTask<String[],Integer,Integer>(){ @Override protected void onPreExecute() { if(kitIntenrface != null){ kitIntenrface.onStart(); } } @Override protected Integer doInBackground(String[]... params) { return run(params[0]); } @Override protected void onProgressUpdate(Integer... values) { if(kitIntenrface != null){ kitIntenrface.onProgress(values[0]); } } @Override protected void onPostExecute(Integer integer) { if(kitIntenrface != null){ kitIntenrface.onEnd(integer); } } }.execute(commands); } public native static int run(String[] commands); }
這個是用來調用ffmpeg可執行文件的。
4.在終端中切到src/main/java文件夾下,輸入:
javah codepig.ffmpegcldemo.FFmpegKit
(這里注意你自己的文件的實際位置)
然后就會在該目錄生成 codepig_ffmpegecldemo_FFmpegKit.h 文件,將這個文件移動到 jni 目錄。
5.復制FFmpeg源碼文件 ffmpeg.h, ffmpeg.c, ffmpeg_opt.c, ffmpeg_filter.c,cmdutils.c, cmdutils.h, cmdutils_common_opts.h 到jni目錄下。
在 jni 目錄新建文件 Android.mk Application.mk codepig_ffmpegcldemo_FFmpegKit.c。
6.編輯ffmpeg.c,把
int main(int argc, char **argv)
改名為
int run(int argc, char **argv)
編輯ffmpeg.h, 在文件末尾添加函數申明:
int run(int argc, char **argv)
7.編輯cmdutils.c中的exit_program函數,刪掉函數中原來的內容, 添加 return ret;並修改函數的返回類型為int。
長這樣:
int exit_program(int ret) { return ret; }
編輯cmdutils.h中exit_program的申明,把返回類型修改為int。
長這樣:
int exit_program(int ret);
8.在 codepig_ffmpegcldemo_FFmpegKit.c 中實現 codepig_ffmpegcldemo_FFmpegKit.h 中的方法。
#include <stdio.h> #include "codepig_ffmpegcldemo_FFmpegKit.h" #include "ffmpeg.h" #include "logjam.h" JNIEXPORT jint JNICALL Java_codepig_ffmpegcldemo_FFmpegKit_run (JNIEnv *env, jclass obj, jobjectArray commands){ //FFmpeg av_log() callback int argc = (*env)->GetArrayLength(env, commands); char *argv[argc]; LOGD("Kit argc %d\n", 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); LOGD("Kit argv %s\n", argv[i]); } return run(argc, argv); }
9.編輯Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := ffmpeg LOCAL_SRC_FILES := libffmpeg.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := ffmpeginvoke LOCAL_SRC_FILES := codepig_ffmpegcldemo_FFmpegKit.c ffmpeg.c ffmpeg_opt.c cmdutils.c ffmpeg_filter.c LOCAL_C_INCLUDES := F:/demo/ffmpeg-3.0 LOCAL_LDLIBS := -llog -lz -ldl LOCAL_SHARED_LIBRARIES := ffmpeg include $(BUILD_SHARED_LIBRARY)
其中LOCAL_C_INCLUDES的值為ffmpeg源碼文件夾地址
10.編輯Application.mk 文件
APP_ABI := armeabi-v7a APP_PLATFORM := android-17
其中APP_ABI的值是支持的cpu類型。要支持多種cpu的話,可以把類型寫一起用空格隔開,比如
APP_ABI := armeabi-v7a x86
11.在終端中定位到jni目錄,執行ndk -build
成功后就會在libs文件夾生成相應的libffmpeg.so和libffmpeginvoke.so文件。這些so文件就是最終我們用來調用的FFmpeg可執行文件。
如果出現如下錯誤提示
Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
則在Application.mk文件中添加
APP_BUILD_SCRIPT := Android.mk
如果出現如下提示
Android NDK: Your Android application project path contains spaces: 'F:/qzd android/Android/workspace/' Android NDK: The Android NDK build cannot work here. Please move your project to a different location.
那大約是因為項目所在文件夾名稱有空格。改名就好了(感覺好弱雞)
12.在build.gradle文件中修改下庫文件地址的指向
android { sourceSets { main { jniLibs.srcDirs = ['src/main/libs'] jni.srcDirs=[] } } }
現在終於可以在android中使用ffmpeg庫了。
13.舉個栗子
(以下例子里的videoUrl是原始視頻文件地址,imageUrl是水印圖片地址,musicUrl是音頻mp3地址,outputUrl是最終輸出視頻地址。)
(1) 給視頻添加圖片水印:
Runnable compoundRun=new Runnable() { @Override public void run() { String[] commands = new String[10]; commands[0] = "ffmpeg"; commands[1] = "-i"; commands[2] = videoUrl; commands[3] = "-i"; commands[4] = imageUrl; commands[5] = "-filter_complex"; commands[6] = "overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2"; commands[7] = "-codec:a"; commands[8] = "copy"; commands[9] = outputUrl; FFmpegKit.execute(commands, new FFmpegKit.KitInterface() { @Override public void onStart() { Log.d("FFmpegLog LOGCAT","FFmpeg 命令行開始執行了..."); } @Override public void onProgress(int progress) { Log.d("FFmpegLog LOGCAT","done com"+"FFmpeg 命令行執行進度..."+progress); } @Override public void onEnd(int result) { Log.d("FFmpegLog LOGCAT","FFmpeg 命令行執行完成..."); } }); } }; ThreadPoolUtils.execute(compoundRun);
(2) 合成音頻視頻
Runnable compoundRun=new Runnable() { @Override public void run() { String[] commands = new String[6]; commands[0] = "ffmpeg"; commands[1] = "-i"; commands[2] = videoUrl; commands[3] = "-i"; commands[4] = musicUrl; commands[5] = outputUrl; FFmpegKit.execute(commands, new FFmpegKit.KitInterface() { @Override public void onStart() { Log.d("FFmpegLog LOGCAT","FFmpeg 命令行開始執行了..."); } @Override public void onProgress(int progress) { Log.d("FFmpegLog LOGCAT","done com"+"FFmpeg 命令行執行進度..."+progress); } @Override public void onEnd(int result) { Log.d("FFmpegLog LOGCAT","FFmpeg 命令行執行完成..."); // getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // Message msg = new Message(); // msg.what = 1; // mHandler.sendMessage(msg); } }); } }; ThreadPoolUtils.execute(compoundRun);
(3)把這兩個命令寫一起
Runnable compoundRun=new Runnable() { @Override public void run() { String[] commands = new String[11]; commands[0] = "ffmpeg"; commands[1] = "-i"; commands[2] = videoUrl; commands[3] = "-i"; commands[4] = imageUrl; commands[5] = "-filter_complex"; commands[6] = "overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2"; commands[7] = "-i"; commands[8] = musicUrl; commands[9] = "-y"; commands[10] = outputUrl; FFmpegKit.execute(commands, new FFmpegKit.KitInterface() { @Override public void onStart() { Log.d("FFmpegLog LOGCAT","FFmpeg 命令行開始執行了..."); } @Override public void onProgress(int progress) { Log.d("FFmpegLog LOGCAT","done com"+"FFmpeg 命令行執行進度..."+progress); } @Override public void onEnd(int result) { Log.d("FFmpegLog LOGCAT","FFmpeg 命令行執行完成..."); // getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } }); } }; ThreadPoolUtils.execute(compoundRun);
附:ffmpeg命令格式簡要說明
基本形式:
ffmpeg -i inputFile -參數名 參數值 …… outputFile;
頭尾不變,中間的參數順序無所謂。但是一個操作的參數必須寫一起。
參數是以 -參數名+參數值 這樣的成對形式賦值。
參考:
ffmpeg參數解釋
FFmpeg常用命令大全,並簡單封裝
Android最簡單的基於FFmpeg的例子(四)---以命令行的形式來使用ffmpeg
本文github項目:
ffmpegCLDemo