测试为 Android 编译的 FFmpeg.so 文件

之前编译过一个 ffmpeg.so 的文件,但运行起来有问题,所以参照网上的例子进行了测试,将过程记录下来。目前正在重新编译 ffmpeg.so,之后整理过后会一起放上来。

环境: Android studio 4.1.1

首先,新建 C++ 项目

命名为 FFmpegTest,我这里用的是 Demo,这个按照规则来就行。

选择 C++ 版本,这里使用 C++ 11

将编译好的 so 文件和对应的 include 文件夹拷贝到 libs 文件夹下

打开 activity_mail.xml 文件,替换为以下内容。

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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<Button
android:id="@+id/formatBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Format" />

<Button
android:id="@+id/codecBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Codec" />

<Button
android:id="@+id/filterBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Filter" />

<Button
android:id="@+id/configBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Config" />
</LinearLayout>

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">

<TextView
android:id="@+id/textview"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</ScrollView>

</LinearLayout>

打开 build.gradle, 做如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
android {
...
defaultConfig {
...
externalNativeBuild {
...
ndk {
abiFilters "arm64-v8a"
}
}
sourceSets {
main {
//库地址
jniLibs.srcDirs = ['libs']
}
}
}
...
//如果不添加这句,运行时会报有多个so文件的错误
packagingOptions {
pickFirst 'lib/arm64-v8a/libffmpeg.so'
}
}

CMakeLists.txt 做如下修改:

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
# 增加以下内容
add_library(ffmpeg-single
SHARED
IMPORTED)

get_filename_component(PARENT_DIR ${CMAKE_SOURCE_DIR} PATH)
set(PARENT_DIRa ${CMAKE_SOURCE_DIR}/../../../)
set(LIBRARY_DIR ${PARENT_DIR}/ffmpeg-single)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../../../libs/${CMAKE_ANDROID_ARCH_ABI}")

set(CMAKE_VERBOSE_MAKEFILE on)

set(distribution_DIR ${PARENT_DIRa}/libs)
set(libsrc_DIR ${distribution_DIR}/${ANDROID_ABI})

set_target_properties(ffmpeg-single
PROPERTIES IMPORTED_LOCATION
# ${distribution_DIR}/${ANDROID_ABI}/libffmpeg.so
${libsrc_DIR}/libffmpeg.so
)
include_directories(${libsrc_DIR}/include)

# 修改
target_link_libraries( # Specifies the target library.
native-lib
ffmpeg-single
# Links the target library to the log library
# included in the NDK.
${log-lib} )

MainActivity 类写入如下内容:

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
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Button formatBtn,codecBtn,filterBtn,configBtn;
JniUtils jniUtils;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

jniUtils = new JniUtils();
textView = findViewById(R.id.textview);
textView.setText(jniUtils.stringFormJNI());

formatBtn=findViewById(R.id.formatBtn);
formatBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText(jniUtils.avformatinfo());
}
});

codecBtn=findViewById(R.id.codecBtn);
codecBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText(jniUtils.avcodecinfo());
}
});

filterBtn=findViewById(R.id.filterBtn);
filterBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText(jniUtils.avfilterinfo());
}
});

configBtn=findViewById(R.id.configBtn);
configBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText(jniUtils.configurationinfo());
}
});
}
}

新建类 JniUtils,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class JniUtils {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
System.loadLibrary("ffmpeg");
}

public native String stringFormJNI();
public native String avformatinfo();
public native String avcodecinfo();
public native String avfilterinfo();
public native String configurationinfo();
}

这时候可以看到 native 函数呈现红色报错状态,鼠标点击函数名,按 alt+enter 键,弹出提示,选择 “创建 JNI 函数”,IDE 会自动在 native-lib.cpp 中建立对应的 JNI 函数。

修改后的 native-lib.cpp 如下:

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <jni.h>
#include <string>

extern "C"{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libavutil/avutil.h"
#include "libavfilter/avfilter.h"
}

extern "C" JNIEXPORT jstring JNICALL
Java_top_luov_demo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
char str[200];
std::string hello = "Hello from C++";

av_register_all();
avcodec_version();
// sprintf(str, "%d", av_register_all() );
// return env->NewStringUTF(hello.c_str());
return env->NewStringUTF(str);
}extern "C"
JNIEXPORT jstring JNICALL
Java_top_luov_demo_JniUtils_stringFormJNI(JNIEnv *env, jobject thiz) {
return (*env).NewStringUTF("Hello from JNI ! Compiled with ABI ");
}
extern "C"
JNIEXPORT jstring JNICALL
Java_top_luov_demo_JniUtils_avformatinfo(JNIEnv *env, jobject thiz) {
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(info);
return (*env).NewStringUTF(info);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_top_luov_demo_JniUtils_avcodecinfo(JNIEnv *env, jobject thiz) {
char info[40000] = { 0 };

av_register_all();

AVCodec *c_temp = av_codec_next(NULL);

while (c_temp != NULL) {
if (c_temp->decode != NULL) {
sprintf(info, "%s[Dec]", info);
} else {
sprintf(info, "%s[Enc]", info);
}
switch (c_temp->type) {
case AVMEDIA_TYPE_VIDEO:
sprintf(info, "%s[Video]", info);
break;
case AVMEDIA_TYPE_AUDIO:
sprintf(info, "%s[Audio]", info);
break;
default:
sprintf(info, "%s[Other]", info);
break;
}
sprintf(info, "%s[%10s]\n", info, c_temp->name);

c_temp = c_temp->next;
}
//LOGE("%s", info);

return (*env).NewStringUTF(info);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_top_luov_demo_JniUtils_avfilterinfo(JNIEnv *env, jobject thiz) {
char info[40000] = { 0 };
avfilter_register_all();
AVFilter *f_temp = (AVFilter *) avfilter_next(NULL);
int i = 0;
while (f_temp != NULL) {
sprintf(info, "%s[%10s]\n", info, f_temp->name);
f_temp = f_temp->next;
}
return (*env).NewStringUTF(info);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_top_luov_demo_JniUtils_configurationinfo(JNIEnv *env, jobject thiz) {
char info[10000] = { 0 };
av_register_all();

sprintf(info, "%s\n", avcodec_configuration());

//LOGE("%s", info);
//return env->NewStringUTF(info);
return (*env).NewStringUTF(info);
}

至此,所有内容编写完毕,文件结构如图:

使用数据线连接手机,点击运行按钮,运行程序。

运行效果如图,点击按钮会输出相应的信息,