NDK开发之使用现有so动态库

前面将的都是如何使用C/C++文件生成so动态库,那么在使用别人的so动态库的时候应该怎么做呢?这篇文章就是使用一个变声功能的动态库,完成对于以有so动态库的说明。

动态库来源

  • 在互联网中,有着许许多多动态库,很多厂商也对外提供动态库供开发者调用,例如高德地图的动态库,做地图开发的时候还是很方便的

  • 本文主要讲一个可以使声音改变的动态库,这个动态库主要用于游戏中,游戏引擎中有使用到

  • 这就是fmod动态库,首先我们要去下载其动态库文件
    官网地址
    先要注册才能下载其文件,按照步骤来就好

  • 在其下载界面,有FMOD Studio API,这里可以选择版本下载,我写这篇博客的时候,最新版本是1.10.07,在这里就不下载最新的了,我选择的是1.08.28,也就是1.08的最后一个版本。

添加到项目中

  • 解压下载的文件,发现在api文件夹下有三个目录:fsbanklowlevelstudio

  • 这里选择lowlevel,这是基于普遍使用选择的,也可以选择studio,其功能更为强大,不过相对地也更需要运算性能

  • 在Android项目中新建libs目录,将fmod.jar拷贝至libs目录

  • 新建jni目录,将armeabiarmeabi-v7a下的so文件拷贝至jni目录,将lowlevel目录下的inc头文件拷贝至jni文件夹,在这里先实现原声播放的功能,故在lowlevel下的examples下找到play_sound.cpp源文件,将其放在jni目录下,打开文件得知,其依赖的common.h头文件并不在inc中,找到common.h并拷贝至jni中,逐步寻找缺失的依赖文件,导入到jni中,整理完成后的jni文件目录如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    │  Android.mk
    │ common.cpp
    │ common.h
    │ common_platform.cpp
    │ common_platform.h
    │ libfmod.so
    │ libfmodL.so
    │ play_sound.cpp
    └─inc
    fmod.h
    fmod.hpp
    fmod_codec.h
    fmod_common.h
    fmod_dsp.h
    fmod_dsp_effects.h
    fmod_errors.h
    fmod_output.h

修改文件使其能够调用

  • lowlevel目录下,有Java的调用示例,在这里直接使用这个MainActivity.java进行修改调用

  • 阅读MainActivity.java源代码,发现其使用的是动态获取权限,为方便使用,直接在清单文件中生命其权限,将其动态申请注释掉,在动态库加载时候,发现加载了一些没有的动态库,将没有的动态库去掉,加上自己的动态库,注意到jni中的调用方法和现有包名不统一,修改之,并将清单文件中的启动活动包名也修改

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package org.fmod.example;

import android.os.Build;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Typeface;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import android.widget.Button;
import android.content.pm.PackageManager;

public class MainActivity extends Activity implements OnTouchListener, Runnable
{
private TextView mTxtScreen;
private Thread mThread;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

// Create the text area
mTxtScreen = new TextView(this);
mTxtScreen.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10.0f);
mTxtScreen.setTypeface(Typeface.MONOSPACE);

// Create the buttons
Button[] buttons = new Button[9];
for (int i = 0; i < buttons.length; i++)
{
buttons[i] = new Button(this);
buttons[i].setText(getButtonLabel(i));
buttons[i].setOnTouchListener(this);
buttons[i].setId(i);
}

// Create the button row layouts
LinearLayout llTopRowButtons = new LinearLayout(this);
llTopRowButtons.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout llMiddleRowButtons = new LinearLayout(this);
llMiddleRowButtons.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout llBottomRowButtons = new LinearLayout(this);
llBottomRowButtons.setOrientation(LinearLayout.HORIZONTAL);

// Create the main view layout
LinearLayout llView = new LinearLayout(this);
llView.setOrientation(LinearLayout.VERTICAL);

// Create layout parameters
LinearLayout.LayoutParams lpLayout = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f);

// Set up the view hierarchy
llTopRowButtons.addView(buttons[0], lpLayout);
llTopRowButtons.addView(buttons[6], lpLayout);
llTopRowButtons.addView(buttons[1], lpLayout);
llMiddleRowButtons.addView(buttons[4], lpLayout);
llMiddleRowButtons.addView(buttons[8], lpLayout);
llMiddleRowButtons.addView(buttons[5], lpLayout);
llBottomRowButtons.addView(buttons[2], lpLayout);
llBottomRowButtons.addView(buttons[7], lpLayout);
llBottomRowButtons.addView(buttons[3], lpLayout);
llView.addView(mTxtScreen, lpLayout);
llView.addView(llTopRowButtons);
llView.addView(llMiddleRowButtons);
llView.addView(llBottomRowButtons);

setContentView(llView);

// Request necessary permissions
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
// {
// String[] perms = { "android.permission.RECORD_AUDIO", "android.permission.WRITE_EXTERNAL_STORAGE" };
// if (checkSelfPermission(perms[0]) == PackageManager.PERMISSION_DENIED ||
// checkSelfPermission(perms[1]) == PackageManager.PERMISSION_DENIED)
// {
// requestPermissions(perms, 200);
// }
// }

org.fmod.FMOD.init(this);

mThread = new Thread(this, "Example Main");
mThread.start();

setStateCreate();
}

@Override
protected void onStart()
{
super.onStart();
setStateStart();
}

@Override
protected void onStop()
{
setStateStop();
super.onStop();
}

@Override
protected void onDestroy()
{
setStateDestroy();

try
{
mThread.join();
}
catch (InterruptedException e) { }

org.fmod.FMOD.close();

super.onDestroy();
}

@Override
public boolean onTouch(View view, MotionEvent motionEvent)
{
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN)
{
buttonDown(view.getId());
}
else if (motionEvent.getAction() == MotionEvent.ACTION_UP)
{
buttonUp(view.getId());
}

return true;
}

@Override
public void run()
{
main();
}

public void updateScreen(final String text)
{
runOnUiThread(new Runnable()
{
@Override
public void run()
{
mTxtScreen.setText(text);
}
});
}

private native String getButtonLabel(int index);
private native void buttonDown(int index);
private native void buttonUp(int index);
private native void setStateCreate();
private native void setStateStart();
private native void setStateStop();
private native void setStateDestroy();
private native void main();

static
{
/*
* To simplify our examples we try to load all possible FMOD
* libraries, the Android.mk will copy in the correct ones
* for each example. For real products you would just load
* 'fmod' and if you use the FMOD Studio tool you would also
* load 'fmodstudio'.
*/

// // Try debug libraries...
// try { System.loadLibrary("fmodD");
// System.loadLibrary("fmodstudioD"); }
// catch (UnsatisfiedLinkError e) { }
// // Try logging libraries...
// try { System.loadLibrary("fmodL");
// System.loadLibrary("fmodstudioL"); }
// catch (UnsatisfiedLinkError e) { }
// // Try release libraries...
// try { System.loadLibrary("fmod");
// System.loadLibrary("fmodstudio"); }
// catch (UnsatisfiedLinkError e) { }
//
// System.loadLibrary("stlport_shared");
// System.loadLibrary("example");

try {
System.loadLibrary("fmod");
System.loadLibrary("fmodL");
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
}
System.loadLibrary("VoiceChangeTest");
}
}
  • 使用Android Tools添加本地支持

  • 修改Android.mk文件,并记录动态库的文件名,将其加载至MainActivity.java

1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := VoiceChangeTest
LOCAL_SRC_FILES := play_sound.cpp

include $(BUILD_SHARED_LIBRARY)

编译项目

  • 此时便可以编译项目了,编译时候会提示有些文件找不到,那是因为包含文件路径不对造成的,此时修改包含文件路径即可

  • 文件包含错误解决以后,再次编译,发现很多函数找不到,此时是因为编译时候那些函数的实现没有编译到项目,修改Android.mk文件,加入依赖实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := fmod
LOCAL_SRC_FILES := libfmod.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := fmodL
LOCAL_SRC_FILES := libfmodL.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := VoiceChangeTest
LOCAL_SRC_FILES := play_sound.cpp \
common_platform.cpp \
common.cpp
LOCAL_SHARED_LIBRARIES := fmod fmodL
include $(BUILD_SHARED_LIBRARY)
  • 另外,由于用到了STL库,需要在配置里说明,在jni下新建Application.mk文件,写入以下配置

    1
    APP_STL := gnustl_static
  • 至此,项目修改完毕,便可以生成apk了。运行界面如下:

仿QQ变声效果实现

在大致了解fmod以后,就可以做一些基于fmod的项目了,正好QQ有一个变声的功能,这里就使用fmod去实现

采集素材

直接将QQ安装包解压,就可以得到图片素材,将其加入到素材中

编辑界面

编写界面,这里直接采用他人布局好的文件

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
<RelativeLayout 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" >

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:background="#FFF"
android:paddingBottom="20dp" >

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="20dp">

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_normal"
style="@style/AudioImgStyle"
android:src="@drawable/record"
android:onClick="mFix"/>
<TextView
style="@style/AudioTextStyle"
android:text="原声"/>
</LinearLayout>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_luoli"
style="@style/AudioImgStyle"
android:src="@drawable/luoli"
android:onClick="mFix"/>
<TextView
style="@style/AudioTextStyle"
android:text="萝莉"/>
</LinearLayout>


<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_dashu"
style="@style/AudioImgStyle"
android:src="@drawable/dashu"
android:onClick="mFix"/>
<TextView
style="@style/AudioTextStyle"
android:text="大叔"/>
</LinearLayout>
</LinearLayout>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="20dp">

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_jingsong"
style="@style/AudioImgStyle"
android:src="@drawable/jingsong"
android:onClick="mFix"/>
<TextView
style="@style/AudioTextStyle"
android:text="惊悚"/>
</LinearLayout>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_gaoguai"
style="@style/AudioImgStyle"
android:src="@drawable/gaoguai"
android:onClick="mFix"/>
<TextView
style="@style/AudioTextStyle"
android:text="搞怪"/>
</LinearLayout>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_kongling"
style="@style/AudioImgStyle"
android:src="@drawable/kongling"
android:onClick="mFix"/>
<TextView
style="@style/AudioTextStyle"
android:text="空灵"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

</RelativeLayout>

style文件

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
<resources>
<style name="AppBaseTheme" parent="android:Theme.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>

<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>

<style name="AudioImgStyle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginLeft">25dp</item>
<item name="android:layout_marginRight">25dp</item>
</style>

<style name="AudioTextStyle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">5dp</item>
<item name="android:layout_gravity">center_horizontal</item>
</style>
</resources>

编辑java代码

主活动

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
package org.fmod.example;

import java.io.File;

import org.fmod.FMOD;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;

public class QQActivity extends Activity {

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

public void mFix(View v) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "test.wav";
Log.e("java cj5785", path);
switch (v.getId()) {
case R.id.btn_normal:
Log.d("cj5785", "normal");
QQVoice.fix(path, QQVoice.MODE_NORMAL);
break;
case R.id.btn_luoli:
Log.d("cj5785", "luoli");
QQVoice.fix(path, QQVoice.MODE_LUOLI);
break;
case R.id.btn_dashu:
Log.d("cj5785", "dashu");
QQVoice.fix(path, QQVoice.MODE_DASHU);
break;
case R.id.btn_jingsong:
Log.d("cj5785", "jingsong");
QQVoice.fix(path, QQVoice.MODE_JINGSONG);
break;
case R.id.btn_gaoguai:
Log.d("cj5785", "gaoguai");
QQVoice.fix(path, QQVoice.MODE_GAOGUAI);
break;
case R.id.btn_kongling:
Log.d("cj5785", "kongling");
QQVoice.fix(path, QQVoice.MODE_KONGLING);
break;
}
}

@Override
protected void onDestroy() {
super.onDestroy();
FMOD.close();
}
}

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.fmod.example;

public class QQVoice {

//音效的类型
public static final int MODE_NORMAL = 0;
public static final int MODE_LUOLI = 1;
public static final int MODE_DASHU = 2;
public static final int MODE_JINGSONG = 3;
public static final int MODE_GAOGUAI = 4;
public static final int MODE_KONGLING = 5;

public native static void fix(String path, int type);
static {
System.loadLibrary("fmod");
System.loadLibrary("fmodL");
System.loadLibrary("QQVoice");
}
}

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
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
#include <stdlib.h>
#include <unistd.h>

#include "inc/fmod.hpp"
#include "org_fmod_example_QQVoice.h"

#include <android/log.h>
#define LOGD(FORMAT,...) __android_log_print(ANDROID_LOG_DEBUG,"cj5785",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"cj5785",FORMAT,##__VA_ARGS__);

#define MODE_NORMAL 0
#define MODE_LUOLI 1
#define MODE_DASHU 2
#define MODE_JINGSONG 3
#define MODE_GAOGUAI 4
#define MODE_KONGLING 5

using namespace FMOD;

JNIEXPORT void JNICALL Java_org_fmod_example_QQVoice_fix
(JNIEnv *env, jclass jcls, jstring jstr_path, jint type)
{
System *system;
Sound *sound;
Channel *channel;
DSP *dsp;
bool playing = true;
float frequency = 0.0F;

const char *path_cstr = env->GetStringUTFChars(jstr_path, NULL);
try{
//初始化
System_Create(&system);
system->init(32, FMOD_INIT_NORMAL, NULL);

//创建声音
system->createSound(path_cstr, FMOD_DEFAULT, NULL, &sound);
switch (type)
{
//原声
case MODE_NORMAL:
LOGD("%s", "NORMAL");
system->playSound(sound, 0, false, &channel);
break;
//萝莉
case MODE_LUOLI:
LOGD("%s", "LUOLI");
system->playSound(sound, 0, false, &channel);
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
//设置音调
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.5);
//添加至channel
channel->addDSP(0, dsp);
break;
//大叔
case MODE_DASHU:
LOGD("%s", "DASHU");
system->playSound(sound, 0, false, &channel);
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
//设置音调
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7);
//添加至channel
channel->addDSP(0, dsp);
break;
//惊悚
case MODE_JINGSONG:
LOGD("%s", "JINGSONG");
system->playSound(sound, 0, false, &channel);
system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.5);
channel->addDSP(0, dsp);
break;
//搞怪
case MODE_GAOGUAI:
LOGD("%s", "GAOGUAI");
system->playSound(sound, 0, false, &channel);
//提高说话速度
channel->getFrequency(&frequency);
frequency *= 2.3;
channel->setFrequency(frequency);
break;
//空灵
case MODE_KONGLING:
LOGD("%s", "KONGLING");
system->playSound(sound, 0, false, &channel);
system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 300);
dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 50);
channel->addDSP(0, dsp);
break;
default:
break;
}
}catch(...){
LOGE("s", "异常状况发生");
goto End;
}

system->update();

while(playing)
{
channel->isPlaying(&playing);
usleep(1000 * 1000);
}
goto End;
End:
//释放资源
env->ReleaseStringUTFChars(jstr_path, path_cstr);
sound->release();
system->close();
system->release();
}

mk文件修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := fmod
LOCAL_SRC_FILES := libfmod.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := fmodL
LOCAL_SRC_FILES := libfmodL.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := QQVoice
LOCAL_SRC_FILES := QQVoice.cpp
LOCAL_SHARED_LIBRARIES := fmod fmodL
LOCAL_LDLIBS := -llog
LOCAL_CPP_FEATURES := exceptions

include $(BUILD_SHARED_LIBRARY)

jni目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
│  Android.mk
│ Application.mk
│ common.cpp
│ common.h
│ common_platform.cpp
│ common_platform.h
│ effects.cpp
│ libfmod.so
│ libfmodL.so
│ org_fmod_example_QQVoice.h
│ QQVoice.cpp
└─inc
fmod.h
fmod.hpp
fmod_codec.h
fmod_common.h
fmod_dsp.h
fmod_dsp_effects.h
fmod_errors.h
fmod_output.h
Donate comment here