NDK开发—仿QQ变声器
1、实现功能前的准备
这是我学习NDK的一个练手项目。虽然最后实现的结果并不是非常专业,但是确实能改变声音的音色,这是非常适合NDK新手的项目。
1.1、主要原理
通过修改声音的属性来实现声音音色的变化。
1.2、依赖的三方库
fmod,专业的音频工具。
下载地址:https://www.fmod.com/download
不过需要登陆之后才能下载而且下载速度可能会有点慢。这里准备了一份百度云的地址:
https://pan.baidu.com/s/1eTJuWUm
2、实现功能
如果需要实现这个功能,需要掌握NDK的基本知识,比如CMakeLists的配置、native方法的实现、以及C语言的基础。
2.1、导入三方的工具
解压下载的压缩包。然后找到如下路径:api/lowlevel
里面有三个文件夹:
- examples:示例代码
- inc:源文件
- lib:打包好的so动态库和jar
用AndroidStudio新建NDK项目之后,将inc和lib导入。
导入
libs中添加so库和jar。
这里涉及到两个容易出现错误的知识点:
1、假如build.gradle
中没有配置ndk{ }
。那么当编译器在编译的时候将会编译所有平台的so库。而这里只导入了armeabi
和x86
的so文件,那么在编译其他的平台的so时,将找不到其他平台so文件而报错。因此需要在build.gradle
中配置ndk{ }
。
如果不在build.gradle
中配置ndk{ }
,那么就需要导入所有平台的so库。
在这个例子中只导入了armeabi
和x86
的so文件,如果直接编译将会报错。所以要在build.gradle
添加如下代码:
ndk {
abiFilters "armeabi","x86"
}
2、这里将so文件配置在了libs文件夹下,因此要在build.gradle
中配置:
sourceSets.main {
jniLibs.srcDirs = ['libs']
jni.srcDirs = []
}
CMakeLists.txt的配置
配置CMakeLists的时候要仔细配置,不要将路径写错。
# 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)
find_library( log-lib
log )
set(my_lib_path ${CMAKE_SOURCE_DIR}/libs)
# 添加三方的so库
add_library(libfmod
SHARED
IMPORTED )
# 指名第三方库的绝对路径
set_target_properties( libfmod
PROPERTIES IMPORTED_LOCATION
${my_lib_path}/${ANDROID_ABI}/libfmod.so )
# 添加三方的so库
add_library(libfmodL
SHARED
IMPORTED )
# 指名第三方库的绝对路径
set_target_properties( libfmodL
PROPERTIES IMPORTED_LOCATION
${my_lib_path}/${ANDROID_ABI}/libfmodL.so )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
# 添加我们需要写代码的路径
add_library(changeVoice
SHARED
src/main/cpp/changeVoice.cpp )
# 导入路径,为了让编译时能够寻找到这个文件夹
include_directories(src/main/cpp/inc)
# 链接好三个路径
target_link_libraries( changeVoice
libfmod
libfmodL
${log-lib} )
如果三方库导入完毕,不要先急于写代码,先build看看是否能通过吧。
2.2、编写native方法,实现主要逻辑
编写native方法,加载动态库。这里需要加载在CMakeLists.txt配置的三个动态库:
target_link_libraries( changeVoice
libfmod
libfmodL
${log-lib} )
public class VoiceTools {
public static native void changeVoice(String path,int mode);
static {
System.loadLibrary("changeVoice");
System.loadLibrary("fmod");
System.loadLibrary("fmodL");
}
}
将native方法生成.h文件,includefmod.hpp
实现逻辑。
这里会通过改变声音的通道来修改音色:
1、降低音调,实现“大叔”效果;
2、升高音调,实现“萝莉”效果;
3、加快声音的速度,实现“搞怪”效果;
4、设置声音颤抖和降低声音速度,实现“惊悚”的效果;
5、设置声音的重复,实现“空灵”的效果;
JNICALL
JNIEXPORT void JNICALL Java_com_mg_axechen_changevoice_VoiceTools_changeVoice
(JNIEnv *jniEnv, jclass jclass, jstring jstring, jint mode) {
// 初始化fmod
FMOD::System *system;
FMOD::System_Create(&system);
Sound *sound;
// 通道(声音是由多种音效组成)
Channel *channel;
// 音频
DSP *pDSP;
// 速度
float frequency;
system->init(32, FMOD_INIT_NORMAL, NULL);
// 将 jstring转为 char
const char *path = jniEnv->GetStringUTFChars(jstring, NULL);
system->createSound(path, FMOD_DEFAULT, NULL, &sound);
try {
switch (mode) {
case MODE_NORMAL:
LOGI("%s", "正常");
system->playSound(sound, NULL, false, &channel);
break;
case MODE_DASHU:
LOGI("%s", "大叔");
// 设置音调,调低音调
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &pDSP);
pDSP->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7);
system->playSound(sound, NULL, false, &channel);
channel->addDSP(0, pDSP);
break;
case MODE_LUOLI:
LOGI("%s", "萝莉");
// 设置音调,调高音调
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &pDSP);
pDSP->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 3);
system->playSound(sound, NULL, false, &channel);
channel->addDSP(0, pDSP);
break;
case MODE_GAOGUAI:
LOGI("%s", "搞怪");
system->createDSPByType(FMOD_DSP_TYPE_NORMALIZE, &pDSP);
system->playSound(sound, NULL, false, &channel);
channel->addDSP(0, pDSP);
// 获取速度并加速
channel->getFrequency(&frequency);
frequency = frequency * 1.6;
channel->setFrequency(frequency);
break;
case MODE_JINGSONG:
// FMOD_DSP_TYPE_TREMOLO 颤抖
LOGI("%s", "惊悚");
// 设置颤抖
system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &pDSP);
// 设置颤抖的频率
pDSP->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.8);
system->playSound(sound, NULL, false, &channel);
channel->addDSP(0, pDSP);
channel->getFrequency(&frequency);
frequency = frequency * 0.5;
channel->setFrequency(frequency);
break;
case MODE_KONGLING:
LOGI("%s", "空灵");
// 设置重复
system->createDSPByType(FMOD_DSP_TYPE_ECHO, &pDSP);
// 设置重复的重复延迟
pDSP->setParameterFloat(FMOD_DSP_ECHO_DELAY, 300);
pDSP->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 20);
system->playSound(sound, NULL, false, &channel);
channel->addDSP(0, pDSP);
break;
}
system->update();
bool isPlaying = true;
// 需要等待,等声音全部播放完成
// 检查是否播放完成
while (isPlaying) {
channel->isPlaying(&isPlaying);
usleep(1000);
}
} catch (...) {
LOGE("%s", "error");
goto end;
}
// 回收内存
end:
system->close();
system->release();
jniEnv->ReleaseStringUTFChars(jstring, path);
}
以上参考来自:
https://www.jianshu.com/p/9d1a3429badc
最后附上界面和源码:
界面