Android - FFmpeg & Mac & AndroidStudio & CMake 环境搭建
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
|
#!/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);
}
}
|