Android 使用 FFmpeg命令列(多包)
1. 新建專案,匯入.so檔案
將之前編譯生成的多個.so檔案及inclulde資料夾下的標頭檔案拷貝到/app/libs資料夾下,拷貝後目錄結果如下:
圖1.png
2. 配置/app/src/main目錄下新建jni資料夾
圖2.png3. 建立FFmpegCmd.java類
package com.mazaiting.ffmpegcmdtest; /** * FFmpeg命令列操作 * @author mazaiting * @date 2018/1/12 */ public class FFmpegCmd { static { System.loadLibrary("avcodec-57"); System.loadLibrary("avdevice-57"); System.loadLibrary("avfilter-6"); System.loadLibrary("avformat-57"); System.loadLibrary("avutil-55"); System.loadLibrary("swresample-2"); System.loadLibrary("swscale-4"); System.loadLibrary("FfmpegCmd"); } public static int execute(String[] commands) { return run(commands); } public native static int run(String[] commands); }
4. 配置/app/build.gradle檔案
1). 在android節點下的defaultConfig目錄下新增
ndk {
moduleName "ffmpegcmd"
abiFilters "armeabi"
}
2). 在android節點下新增,如果已有則替換這段
sourceSets { main { jniLibs.srcDirs = ['src/main/libs'] jni.srcDirs=[] } }
5. 生成jni的標頭檔案
javah命令配置, 執行命令完成後,在jni資料夾下生成com_mazaiting_ffmpegcmdtest_FFmpegCmd.h檔案
6. 配置jni目錄
將ffmpeg原始碼中的ffmpeg.h, ffmpeg.c, ffmpeg_opt.c, ffmpeg_filter.c,cmdutils.c, cmdutils.h, cmdutils_common_opts.h,config.h 賦值到jni目錄下,並在 jni 目錄新建檔案 Android.mk Application.mk com_mazaiting_ffmpegcmdtest_FFmpegCmd.c。
7. 修改ffmpeg原始碼
1). 編輯ffmpeg.c,把
int main(int argc, char **argv)
改名為
int run(int argc, char **argv)
編輯ffmpeg.h, 在檔案末尾(在#endif前面)新增函式申明:
int run(int argc, char **argv)
2). 編輯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. 其他檔案
1). com_mazaiting_ffmpegcmdtest_FFmpegCmd.c
#include "com_mazaiting_ffmpegcmdtest_FFmpegCmd.h"
#include "ffmpeg.h"
#include <android/log.h>
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"ffmpeg",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"ffmpeg",FORMAT,##__VA_ARGS__);
JNIEXPORT jint JNICALL Java_com_mazaiting_ffmpegcmdtest_FFmpegCmd_run
(JNIEnv *env, jclass cls, jobjectArray commands) {
int argc = (*env)->GetArrayLength(env, commands);
char *argv[argc];
LOGE("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);
LOGE("Kit argv %s\n", argv[i]);
}
return run(argc, argv);
}
2). Android.mk
LOCAL_PATH := $(call my-dir)
# FFmpeg library
include $(CLEAR_VARS)
# 列印當前路徑
$(warning $(LOCAL_PATH))
# .so檔名稱,去除字首lib和-後的內容
LOCAL_MODULE := avcodec
# .so檔案
LOCAL_SRC_FILES := libavcodec-57.so
# 已編譯好的庫使用PREBUILT_SHARED_LIBRARY
include $(PREBUILT_SHARED_LIBRARY)
# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := libavdevice-57.so
include $(PREBUILT_SHARED_LIBRARY)
# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-6.so
include $(PREBUILT_SHARED_LIBRARY)
# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-57.so
include $(PREBUILT_SHARED_LIBRARY)
# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-55.so
include $(PREBUILT_SHARED_LIBRARY)
# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-2.so
include $(PREBUILT_SHARED_LIBRARY)
# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-4.so
include $(PREBUILT_SHARED_LIBRARY)
# Program
# module名稱
include $(CLEAR_VARS)
LOCAL_MODULE := FfmpegCmd
# module中的原始檔
LOCAL_SRC_FILES := com_mazaiting_ffmpegcmdtest_FFmpegCmd.c ffmpeg.c ffmpeg_opt.c cmdutils.c ffmpeg_filter.c
# ffmpeg原始碼路徑
LOCAL_C_INCLUDES += C:/Users/Administrator/Desktop/ffmpeg-3.3.6
LOCAL_LDLIBS := -llog -lz
# 連結的動態庫檔案
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil swresample swscale
# 編譯生成的庫使用BUILD_SHARED_LIBRARY
include $(BUILD_SHARED_LIBRARY)
3). Application.mk
APP_MODULES := FfmpegCmd
APP_ABI := armeabi
APP_PLATFORM := android-14
# 去除原子性編譯
APP_LDFLAGS := -latomic
9. 編譯FfmpegCmd.so檔案
編譯之前先將/app/libs資料夾內的ffmpeg中拷貝的.so檔案拷貝到jni資料夾一份,此時jni資料夾內的所有檔案如圖4,使用ndk-build命令編譯(圖5)。
圖4.png 圖5.png
10. 編譯成功
編譯成功後在main目錄下生成libs和obj資料夾,將/libs/armeabi/下的libFfmpegcmd.so或者/obj/local/armeabi/objs/下的libFfmpegcmd.so檔案複製到/app/libs/armeabi/目錄下,此時在java檔案中即可成功使用ffmpeg。
圖6.png
11. 使用
1). 在AndroidManifest.xml新增許可權
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
2). activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.mazaiting.ffmpegcmdtest.MainActivity">
<Button
android:onClick="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="合成"/>
</RelativeLayout>
3). MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private boolean isRun = false;
/**視訊源路徑*/
private String videoUrl = "/storage/emulated/0/input.mp4";
/**圖片路徑*/
private String imageUrl = "/storage/emulated/0/image.png";
/**視訊輸出路徑*/
private String outputUrl = "/storage/emulated/0/output.mp4";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void start(View view){
if (!isRun) {
new Thread(mRunnable).start();
isRun = true;
} else {
Toast.makeText(this, "正在執行", Toast.LENGTH_SHORT).show();
}
}
/**
* 新增水印任務
*/
Runnable mRunnable = 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;
Log.e(TAG, "run: 開始執行");
FFmpegCmd.execute(commands);
Log.e(TAG, "run: 執行結束");
isRun = false;
}
};
}