AndroidStudio 2.2 开始支持 CMake 编译,网上的文章清一色都是 ndk-build,记录一下我用 CMake 搭建环境的过程。

基础环境

  • MacOS 10.12.3
  • AndroidStudio 2.3
  • NDK 13.1.*
  • FFmpeg 3.2.*

下载 FFmpeg 源码

1
git clone https://git.ffmpeg.org/ffmpeg.git

切换到 3.2 分支

1
git checkout -b 3.2 remotes/origin/release/3.2

当然你也可以去去官网下载压缩包。

编译 FFmpeg for Android

修改 FFmpeg 的 configure

打开 configure 文件,找到:

1
2
3
4
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR)$(SLIBNAME)'

替换成:

1
2
3
4
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'

编写 Android 编译脚本

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
#!/bin/bash
NDK=/Users/gavin/Develop/android-sdk/ndk-bundle
SYSROOT=$NDK/platforms/android-15/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64

CPU=arm

PREFIX=$(pwd)/android/$CPU

ADDI_CFLAGS="-marm"

function build_one
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}

build_one

前三行自行更换成自己的 NDK 路径即可,PREFIX 就是最终编译输出地址,根据情况也可以修改。

保存文件到 FFmpeg 根目录为 build_android.sh chmod +x build_android.sh 赋予执行权限。

编译

执行脚本进行编译。

1
./build_android.sh

小憩一会儿,编译完成。

进入 PREFIX 目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
── include
│   ├── libavcodec
│   ├── libavfilter
│   ├── libavformat
│   ├── libavutil
│   ├── libswresample
│   └── libswscale
└── lib
├── libavcodec-57.so
├── libavfilter-6.so
├── libavformat-57.so
├── libavutil-55.so
├── libswresample-2.so
├── libswscale-4.so
└── pkgconfig

我们需要的 so 和头文件都有啦。

导入 Android 工程

对于如何开始一个 C++ 项目,官方已经有中文文档了:

https://developer.android.google.cn/studio/projects/add-native-code.html

官网有的东西不赘述,再次声明学什么东西优先看 官方文档,官方文档,官方文档,并且 Google dev 现在已经有很多中文资源了。

新建一个 C/C++ 工程

勾选 Include C++ Support 即可得到一个基于 CMake 的模板工程。

拷贝 so 和 incude 文件到 libs

因为我们只编译了 arm 版本的 so,所以需要把 so 拷到 armeabi-v7a 目录下,完整路径如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
├── build.gradle
├── libs
│   ├── armeabi-v7a
│   │   ├── libavcodec-57.so
│   │   ├── libavfilter-6.so
│   │   ├── libavformat-57.so
│   │   ├── libavutil-55.so
│   │   ├── libswresample-2.so
│   │   └── libswscale-4.so
│   └── include
│   ├── libavcodec
│   ├── libavfilter
│   ├── libavformat
│   ├── libavutil
│   ├── libswresample
│   └── libswscale
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── cpp
├── java
└── res

指定 jniLibs 路径

1
2
3
4
5
6
7
8
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}

指定 abiFilters

因为 so 只有 armeabi-v7a 的,所以我们还需要指定一下 abi 为 armeabi-v7a

1
2
3
4
5
6
7
8
9
android {
...
defaultConfig {
...
ndk {
abiFilters 'armeabi-v7a'
}
}
}

编写 CMakeLists

脚本怎么写,有什么讲究其实官方文档已经很清楚了:
创建 CMake 构建脚本

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
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
native-lib

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )

add_library( avcodec-57
SHARED
IMPORTED )
set_target_properties( avcodec-57
PROPERTIES IMPORTED_LOCATION
../../../../libs/armeabi-v7a/libavcodec-57.so )

add_library( avfilter-6
SHARED
IMPORTED )
set_target_properties( avfilter-6
PROPERTIES IMPORTED_LOCATION
../../../../libs/armeabi-v7a/libavfilter-6.so )

add_library( avformat-57
SHARED
IMPORTED )
set_target_properties( avformat-57
PROPERTIES IMPORTED_LOCATION
../../../../libs/armeabi-v7a/libavformat-57.so )

add_library( avutil-55
SHARED
IMPORTED )
set_target_properties( avutil-55
PROPERTIES IMPORTED_LOCATION
../../../../libs/armeabi-v7a/libavutil-55.so )

add_library( swresample-2
SHARED
IMPORTED )
set_target_properties( swresample-2
PROPERTIES IMPORTED_LOCATION
../../../../libs/armeabi-v7a/libswresample-2.so )

add_library( swscale-4
SHARED
IMPORTED )
set_target_properties( swscale-4
PROPERTIES IMPORTED_LOCATION
../../../../libs/armeabi-v7a/libswscale-4.so )


include_directories( libs/include )

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
native-lib

avcodec-57

avfilter-6

avformat-57

avutil-55

swresample-2

swscale-4

# Links the target library to the log library
# included in the NDK.
${log-lib} )

Java 引入 so 库

1
2
3
4
5
6
7
8
9
10
static {
System.loadLibrary("native-lib");

System.loadLibrary("avcodec-57");
System.loadLibrary("avfilter-6");
System.loadLibrary("avformat-57");
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("swscale-4");
}

运行测试一下

如果成功运行不报错的话,就证明一切没有问题。


编写 FFmpeg 测试代码

UI

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
<?xml version="1.0" encoding="utf-8"?>
<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"
android:orientation="vertical"
tools:context="cn.gavinliu.android.ffmpeg.MainActivity">

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

<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="protocol"
android:text="Protocol"
android:textAllCaps="false" />

<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="filter"
android:text="Filter"
android:textAllCaps="false" />

<Button
android:id="@+id/button3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="format"
android:text="Format"
android:textAllCaps="false" />

<Button
android:id="@+id/button4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="codec"
android:text="Codec"
android:textAllCaps="false" />
</LinearLayout>

<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/sample_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:text="Hello World!" />
</ScrollView>
</LinearLayout>
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
package cn.gavinliu.android.ffmpeg;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");

System.loadLibrary("avcodec-57");
System.loadLibrary("avfilter-6");
System.loadLibrary("avformat-57");
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("swscale-4");
}

private TextView tv;

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

// Example of a call to a native method
tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}

public void protocol(View view) {
tv.setText(urlprotocolinfo());
}

public void filter(View view) {
tv.setText(avformatinfo());
}

public void format(View view) {
tv.setText(avfilterinfo());
}

public void codec(View view) {
tv.setText(avcodecinfo());
}


/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();

public native String avformatinfo();

public native String urlprotocolinfo();

public native String avcodecinfo();

public native String avfilterinfo();
}

C++

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
113
114
115
#include <jni.h>
#include <string>

extern "C" {

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>

JNIEXPORT jstring JNICALL
Java_cn_gavinliu_android_ffmpeg_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {

std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

JNIEXPORT jstring JNICALL
Java_cn_gavinliu_android_ffmpeg_MainActivity_avformatinfo(
JNIEnv *env,
jobject /* this */) {
char info[40000] = {0};

av_register_all();

AVInputFormat *if_temp = av_iformat_next(NULL);
AVOutputFormat *of_temp = av_oformat_next(NULL);

while (if_temp != NULL) {
sprintf(info, "%sInput: %s\n", info, if_temp->name);
if_temp = if_temp->next;
}

while (of_temp != NULL) {
sprintf(info, "%sOutput: %s\n", info, of_temp->name);
of_temp = of_temp->next;
}

return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL
Java_cn_gavinliu_android_ffmpeg_MainActivity_urlprotocolinfo(
JNIEnv *env,
jobject /* this */) {

char info[40000] = {0};
av_register_all();

struct URLProtocol *pup = NULL;

struct URLProtocol **p_temp = &pup;
avio_enum_protocols((void **) p_temp, 0);

while ((*p_temp) != NULL) {
sprintf(info, "%sInput: %s\n", info, avio_enum_protocols((void **) p_temp, 0));
}
pup = NULL;
avio_enum_protocols((void **) p_temp, 1);
while ((*p_temp) != NULL) {
sprintf(info, "%sInput: %s\n", info, avio_enum_protocols((void **) p_temp, 1));
}
return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL
Java_cn_gavinliu_android_ffmpeg_MainActivity_avcodecinfo(
JNIEnv *env,
jobject /* this */) {

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, "%sdecode:", info);
} else {
sprintf(info, "%sencode:", 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;
}

return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL
Java_cn_gavinliu_android_ffmpeg_MainActivity_avfilterinfo(JNIEnv *env, jobject /* this */) {

char info[40000] = {0};
avfilter_register_all();

AVFilter *f_temp = (AVFilter *) avfilter_next(NULL);
while (f_temp != NULL) {
sprintf(info, "%s%s\n", info, f_temp->name);
f_temp = f_temp->next;
}
return env->NewStringUTF(info);
}
}

运行

更多