Android编译FFmpeg之命令行

上篇文章介绍了 FFmpeg 大体的编译流程,并在 Java 层进行 JNI 接口的调用,借此了解 Android 编译底层 so 库的方式,本文主要介绍如何在 Android 平台使用 FFmoeg 命令行对音视频进行操作。

承接上文,本文从编译好各个 so 文件之后,编写 Java 层调用的 c 文件开始。

源代码 GitHub - FFmpegAndroidSupport(注意在 try_cmd 分支)

Java 层 Native 方法

首先进行 Java 层代码的编写,创建 FFmpegSupport 类,加载 so 文件,声明 native 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class FFmpegSupport {

static {
try {
System.loadLibrary("avdevice-57");
System.loadLibrary("avfilter-6");
System.loadLibrary("avformat-57");
System.loadLibrary("avutil-55");
System.loadLibrary("avcodec-57");
System.loadLibrary("postproc-54");
System.loadLibrary("swresample-2");
System.loadLibrary("swscale-4");

System.loadLibrary("ffmpegjni");
} catch (Exception e) {
e.printStackTrace();
}
}

public static int ffmpegRunCommand(String command) {
if (command.isEmpty()) {
return 1;
}
String[] args = command.split(" ");
return ffmpegRunCommand(args);
}

static int ffmpegRunCommand(String[] commands) {
for (String command : commands) {
Log.d("ffmpeg-jni", command);
}
return ffmpegRun(commands.length, commands);
}

public static native int ffmpegRun(int argc, String[] args);
}

生成 .h 头文件

使用 javah 命令根据你的 java 文件自动生成 .h 文件,javah 命令需要 class 文件,所以首先你需要对工程进行编译,生成 class 文件,它位于 project/app/build/intermediates/classes/debug 目录下。

比如我的类叫做 FFmpegSupport,包名 com.march.fas,运行命令后生成了 com_march_fas_FFmpegSupport.h 文件,这个 .h 文件的名字应该是可以自定义的,现在是默认的包名类名,我还没有查到相关参数。

1
2
3
4
5
6
javah -d <.h文件生成的目录> -classpath <class文件存储的目录> <包名.类名>

// 如下
javah -d /Users/march/AndroidPro/FFmpegAndroidSupport/app/src/main/jni \
-classpath /Users/march/AndroidPro/FFmpegAndroidSupport/app/build/intermediates/classes/debug \
com.march.fas.FFmpegSupport

查看 .h 文件的内容,将 jclass 改为 jobject,原因的话我也不知道为什么,不是很懂这一块儿,也是查资料慢慢总结的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_march_fas_FFmpegSupport */

#ifndef _Included_com_march_fas_FFmpegSupport
#define _Included_com_march_fas_FFmpegSupport
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_march_fas_FFmpegSupport
* Method: ffmpegRun
* Signature: (I[Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_march_fas_FFmpegSupport_ffmpegRun
(JNIEnv *, jobject, jint, jobjectArray);

#ifdef __cplusplus
}
#endif
#endif

编写对应 c 文件

根据生成的 .h 编写 c 文件,其中 logjni.h 是一个打印日志的工具类,详细可以参考 github 上面的源文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "logjni.h"
#include "com_march_fas_FFmpegSupport.h"

#include <stdlib.h>
#include <stdbool.h>

int main(int argc, char **argv);

JNIEXPORT jint JNICALL Java_com_march_fas_FFmpegSupport_ffmpegRun
(JNIEnv *env, jobject obj, jint argc, jobjectArray args) {
int i = 0;
char **argv = NULL;
jstring *strr = NULL;

if (args != NULL) {
argv = (char **) malloc(sizeof(char *) * argc);
strr = (jstring *) malloc(sizeof(jstring) * argc);

for (i = 0; i < argc; ++i) {
strr[i] = (jstring)(*env)->GetObjectArrayElement(env, args, i);
argv[i] = (char *)(*env)->GetStringUTFChars(env, strr[i], 0);
LOGD("args: %s", argv[i]);
}
}

LOGD("Run ffmpeg");
int result = main(argc, argv);
LOGD("ffmpeg result %d", result);

for (i = 0; i < argc; ++i) {
(*env)->ReleaseStringUTFChars(env, strr[i], argv[i]);
}
free(argv);
free(strr);
return result;
}

准备工作

如上代码中,我们使用 ffmpeg.c 中的 main() 函数,因此需要将 ffmpeg.cffmpeg.h 拷贝到 jni 目录中,同时 ffmpeg.c 又引用了多个文件,大致有以下几个文件,这些文件都需要拷贝到 jni 目录中

1
2
3
4
5
6
7
8
config.h
ffmpeg.c
ffmpeg.h
ffmpeg_filter.c
ffmpeg_opt.c
cmdutils_common_opts.h
cmdutils.h
cmdutils.c

跟上一篇文章说的一样,我们还是要将编译好的 so 文件拷贝到 jni 目录,同时编写 Android.mkApplication.mk 文件。为了让结构更加清晰,我对目录做了些许改动。在 jni 目录下新建了 prebuilt/armeabi 文件夹,用来存放 so 文件。同时 Android.mkApplication.mk 文件也需要做相应修改。

Android.mk 文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE:= avcodec-prebuilt-armeabi
LOCAL_SRC_FILES:= prebuilt/armeabi/libavcodec-57.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= avdevice-prebuilt-armeabi
LOCAL_SRC_FILES:= prebuilt/armeabi/libavdevice-57.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= avfilter-prebuilt-armeabi
LOCAL_SRC_FILES:= prebuilt/armeabi/libavfilter-6.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= avformat-prebuilt-armeabi
LOCAL_SRC_FILES:= prebuilt/armeabi/libavformat-57.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avutil-prebuilt-armeabi
LOCAL_SRC_FILES := prebuilt/armeabi/libavutil-55.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample-prebuilt-armeabi
LOCAL_SRC_FILES := prebuilt/armeabi/libswresample-2.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale-prebuilt-armeabi
LOCAL_SRC_FILES := prebuilt/armeabi/libswscale-4.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := postproc-prebuilt-armeabi
LOCAL_SRC_FILES := prebuilt/armeabi/libpostproc-54.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := libffmpegjni

LOCAL_ARM_MODE := arm

LOCAL_SRC_FILES := com_march_fas_FFmpegSupport.c \
ffmpeg.c \
cmdutils.c \
ffmpeg_opt.c \
ffmpeg_filter.c

LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -lz

LOCAL_SHARED_LIBRARIES:= avcodec-prebuilt-armeabi \
avdevice-prebuilt-armeabi \
avfilter-prebuilt-armeabi \
avformat-prebuilt-armeabi \
avutil-prebuilt-armeabi \
swresample-prebuilt-armeabi \
swscale-prebuilt-armeabi \
postproc-prebuilt-armeabi

LOCAL_C_INCLUDES += -L$(SYSROOT)/usr/include

LOCAL_C_INCLUDES += /Users/march/AndroidPro/FFmpegAndroidSupport/ffmpeg-3.3.1

LOCAL_CFLAGS := -DUSE_ARM_CONFIG

include $(BUILD_SHARED_LIBRARY)

Application.mk 文件

1
2
APP_ABI := armeabi-v7a
APP_PLATFORM := android-14

修改 ffmpeg 源代码

------ 本文结束 🎉🎉 谢谢观看  ------