概述
本篇记录的内容在上一篇 Android NDK的使用实例——fmod example 的基础上继续开发。这次要使用fmod模仿QQ变声的效果。
页面布局
几个按钮,就不贴布局代码了
编写native方法
NdkUtil.java
package org.fmod.example;
/**
* NDK 工具类
* Created by lex on 2017/12/30.
*/
public class NdkUtil {
static {
System.loadLibrary("native-lib");
}
public static final int MODE_PLAY = 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 final void play(int mode);
}
官方Demo解读
首先看下官方的Demo,这里把 effects.cpp 复制到工程里面,添加编译到 CMakeLists.txt 里面。
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/effects.cpp
src/main/cpp/common.cpp
src/main/cpp/common_platform.cpp
src/main/cpp/native-lib.cpp )
跑起来是这样的效果。
其中A、B、C、D按钮分别响应低通滤波lowpass、高通滤波highpass、回音echo、法兰flange的变声效果。点击后可以听到音频的发生了变声的效果。
以下是 effects.cpp 源代码,做了注释和删减部分。通过不断判断与测试,抽出必要的代码,以达到自己想要的效果。
#include "inc/fmod.hpp"
#include "common.h"
int FMOD_Main()
{
// 初始化变声需要的相关变量
FMOD::System *system = 0;
FMOD::Sound *sound = 0;
FMOD::Channel *channel = 0;
FMOD::ChannelGroup *mastergroup = 0;
FMOD::DSP *dsplowpass = 0;
FMOD::DSP *dsphighpass = 0;
FMOD::DSP *dspecho = 0;
FMOD::DSP *dspflange = 0;
FMOD_RESULT result;
unsigned int version;
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize
创建一个系统对象
*/
result = FMOD::System_Create(&system);
// 初始化相关数据
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
// 创建一个声音
result = system->createSound(Common_MediaPath("dream.m4a"), FMOD_DEFAULT, 0, &sound);
// 播放声音
result = system->playSound(sound, 0, false, &channel);
/*
Create some effects to play with
创建不同的音效
*/
result = system->createDSPByType(FMOD_DSP_TYPE_LOWPASS, &dsplowpass);
result = system->createDSPByType(FMOD_DSP_TYPE_HIGHPASS, &dsphighpass);
result = system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dspecho);
result = system->createDSPByType(FMOD_DSP_TYPE_FLANGE, &dspflange);
/*
Add them to the master channel group. Each time an effect is added (to position 0) it pushes the others down the list.
*/
result = mastergroup->addDSP(0, dsplowpass);
result = mastergroup->addDSP(0, dsphighpass);
result = mastergroup->addDSP(0, dspecho);
result = mastergroup->addDSP(0, dspflange);
/*
By default, bypass all effects. This means let the original signal go through without processing.
It will sound 'dry' until effects are enabled by the user.
*/
result = dsplowpass->setBypass(true);
result = dsphighpass->setBypass(true);
result = dspecho->setBypass(true);
result = dspflange->setBypass(true);
/*
Main loop
*/
do
{
Common_Update();
// 判断不同按钮的点击状态,以进行不同的变声效果处理
if (Common_BtnPress(BTN_MORE))
{
bool paused;
result = channel->getPaused(&paused);
paused = !paused;
result = channel->setPaused(paused);
}
if (Common_BtnPress(BTN_ACTION1))
{
bool bypass;
result = dsplowpass->getBypass(&bypass);
bypass = !bypass;
result = dsplowpass->setBypass(bypass);
}
if (Common_BtnPress(BTN_ACTION2))
{
bool bypass;
result = dsphighpass->getBypass(&bypass);
bypass = !bypass;
result = dsphighpass->setBypass(bypass);
}
if (Common_BtnPress(BTN_ACTION3))
{
bool bypass;
result = dspecho->getBypass(&bypass);
bypass = !bypass;
result = dspecho->setBypass(bypass);
}
if (Common_BtnPress(BTN_ACTION4))
{
bool bypass;
result = dspflange->getBypass(&bypass);
bypass = !bypass;
result = dspflange->setBypass(bypass);
}
result = system->update();
// 线程休眠时间
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
关闭移除数字信号处理器
*/
result = mastergroup->removeDSP(dsplowpass);
result = mastergroup->removeDSP(dsphighpass);
result = mastergroup->removeDSP(dspecho);
result = mastergroup->removeDSP(dspflange);
// 释放资源
result = dsplowpass->release();
result = dsphighpass->release();
result = dspecho->release();
result = dspflange->release();
result = sound->release();
result = system->close();
result = system->release();
Common_Close();
return 0;
}
实现变声效果
描述声音特性的三个要素,包括响度、音色、音调。响度由振幅决定,音色由声波的波形决定,音调由频率决定。这里的变声效果有几种,原理也是对音频的不同属性做处理,当然,fmod对音效处理的类型做的更丰富。
1、萝莉:女高音,提高声音的频率
2、大叔:男低音,降低声音的频率
3、惊悚:声音添加了颤抖的效果
4、搞怪:加快了声音的播放速度 frequency
5、空灵:做了回声 echo 的音效
这里先用手机录制了一段声音文件 dream.m4a,复制到 assets 目录下,那么这段音频的路径就是
file:///android_asset/dream.m4a
这段音频数据是由二进制十六进制组成,那么我们要对这些数据进行处理?那得多复杂。我们可以借助于 fmod 来实现。以下是处理变声的代码
#include <jni.h>
#include <string>
#include <unistd.h>
#include <android/log.h>
#include "inc/fmod.hpp"
#define LOG_TAG "lex"
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,FORMAT,__VA_ARGS__)
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,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;
extern "C"
JNIEXPORT void JNICALL
Java_org_fmod_example_NdkUtil_play(JNIEnv *env, jclass type, jint mode) {
// 播放声音、处理变声所需要的一些变量
System *system;
Sound *sound;
Channel *channel;
DSP *dsp;
bool isPlaying = true;
float frequency = 0F;
// 播放文件的路径
const char *path = "file:///android_asset/dream.m4a";
// 初始化
System_Create(&system);
system->init(32, FMOD_INIT_NORMAL, NULL);
// 创建声音
system->createSound(path, FMOD_DEFAULT, NULL, &sound);
switch (mode) {
case MODE_NORMAL:
// 原声播放
system->playSound(sound, 0, false, &channel);
break;
case MODE_LUOLI:
// 萝莉音效
// FMOD_DSP_TYPE_PITCHSHIFT,提高或者降低音调的类型
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
// 设置音调的参数
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.5);
system->playSound(sound, 0, false, &channel);
// 添加到channel
channel->addDSP(0, dsp);
break;
case MODE_DASHU:
// 大叔音效
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.8);
system->playSound(sound, 0, false, &channel);
channel->addDSP(0, dsp);
break;
case MODE_JINGSONG:
// 惊悚音效
system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.5);
system->playSound(sound, 0, false, &channel);
channel->addDSP(0, dsp);
break;
case MODE_GAOGUAI:
// 搞怪音效
// 提高音频的播放速度
system->playSound(sound, 0, false, &channel);
channel->getFrequency(&frequency);
frequency = frequency * 1.5F;
channel->setFrequency(frequency);
break;
case MODE_KONGLING:
// 空灵音效
system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 300);
dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 20);
system->playSound(sound, 0, false, &channel);
channel->addDSP(0, dsp);
break;
default:
channel = NULL;
break;
}
system->update();
// 单位是微秒
while (isPlaying && channel != NULL) {
channel->isPlaying(&isPlaying);
usleep(100 * 1000);
}
goto end;
// 释放资源
end:
sound->release();
system->close();
system->release();
}
源码已经上传至 GitHub
体会
也是参考了很多资料来实现这个效果。官网文档也并不会很清晰的告诉你如何使用,甚至很多时候需要去推断。
C/C++开源的世界真是非常的庞大,拥有着非常多优秀算法的开源项目,fmod 就是其中之一。通常我们做Android使用到网络上的开源库比较多的是UI、网络、数据库、架构之类的。对于算法类的确实并不多。利用NDK进入C/C++犹如打开了另一扇大门,继续探索前进。