前言
我们这次用到的是fmod这个库,fmod是音效引擎游戏开发革命引擎,著名的游戏开发引擎CosCos2D、 Unity都封装了这个库。
FMOD的如下优点:
- 使用FMOD我们可以使用更少的资源创建更加高级和丰富的音效,减少运行时内存资源消耗;
- 音效管理只需要在FMOD Studio中管理好即可;
- 编程人员只需要依赖于各种字符串形式的Sound Event和简单的播放API即可,使用简单;
- 平台支持较为完善。
项目演示
原理分析
- 原声:直接播放音频文件
- 萝莉:对音频提高八度左右
- 大叔:对音频减低八度左右
- 惊悚:增加音频的颤音
- 搞笑:增加音频的播放速度
- 空灵:增加音频的回音
资源下载与使用
1.fmod下载
2.解压压缩包以后,打开api文件夹,会有fsbank、lowlevel、studio:
- fsbank,bank是一个fmod的概念,包含里一些声音的事件,也可以通过
FMOD Studio Tool来制作,这里是通过代码去制作。 - lowlevel,我们需要的文件都在这里,包含了基础的声音处理功能。
- studio,这里存放的api跟界面相关的,我们不需要使用这些界面。
3.使用下载包中api-> lowlevel 下的库与资源文件
- examples示例程序,里面有一个Activity,以及对应的cpp文件,供我们参考写。
- inc目录,放的是fmod相关的头文件,直接Copy到我们的Android项目当中。
- lib目录,放的是需要预编译调用的so库,以及一个jar包,直接Copy到我们的Android项目当中。
代码编写
1、我们创建一个EffectUtils类,编写我们的fmod变声处理
package org.fmod.example;
/**
* Created by Xionghu on 2017/11/24.
* Desc:
*/
public class EffectUtils {
//音效类型
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;
/**
* 音效处理
* @param path
* @param type
*/
public native static void fix(String path,int type);
static
{
System.loadLibrary("fmodL");
System.loadLibrary("fmod");
System.loadLibrary("qq_voicer");
}
}
2、javah我们的声明文件EffectUtils生成头文件
生成头文件请参考超级简单的Android Studio jni 实现(无需命令行)
注意:新版Android Studio ndk-build 要指定Application.mk和Android.mk路径
生成org_fmod_example_EffectUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class org_fmod_example_EffectUtils */
#ifndef _Included_org_fmod_example_EffectUtils
#define _Included_org_fmod_example_EffectUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_fmod_example_EffectUtils
* Method: fix
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_org_fmod_example_EffectUtils_fix
(JNIEnv *, jclass, jstring, jint);
#ifdef __cplusplus
}
#endif
#endif
3、编写变声核心代码 effects_qq_voicer.cpp
注意paly_sound.cpp 、effects.cpp是fmod示例代码,大家可以在编写代码 effects_qq_voicer.cpp之前进行测试
effects_qq_voicer.cpp
#include "inc/fmod.hpp"
#include "org_fmod_example_EffectUtils.h"
#include
#include
#include
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"kpioneer",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"kpioneer",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_EffectUtils_fix
(JNIEnv *env, jclass jcls, jstring path_jstr, jint type) {
LOGI("%s", "fix normal55555555555");
System *system;
Sound *sound;
Channel *channel;
DSP *dsp;
float frequency = 0;
bool playing = true;
const char* path_cstr = env->GetStringUTFChars(path_jstr, NULL);
try {
//初始化
System_Create(&system);
//手机录音一般是16位 如果是32位的音频要填32 否则无法播放声音
system->init(16, FMOD_INIT_NORMAL, NULL);
//创建声音
system->createSound(path_cstr, FMOD_DEFAULT, NULL, &sound);
switch (type) {
case MODE_NORMAL:
//原生播放
LOGI("%s", path_cstr);
system->playSound(sound, 0, false, &channel);
LOGI("%s", "fix normal");
break;
case MODE_LUOLI:
//萝莉
//DSP digital signal process
//dsp -> 音效
//FMOD_DSP_TYPE_PITCH dsp ,提升或者降低音调用的一种音效
// FMOD_DSP_TYPE_PITCHSHIFT 在fmod_dsp_effects.h中
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);
LOGI("%s", "fix luoli");
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
channel->addDSP(0, dsp);
LOGI("%s", "fix dashu");
break;
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.6;
channel->setFrequency(frequency);
LOGI("%s", "fix gaoguai");
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);
LOGI("%s", "fix kongling");
break;
default:
break;
}
}catch(...){
LOGE("%s","发生异常");
goto END;
}
system->update();
//进程休眠 单位微秒 us
//每秒钟判断是否在播放
while (playing) {
channel->isPlaying(&playing);
usleep(1000 * 1000);
}
goto END;
//释放资源
END:
env->ReleaseStringUTFChars(path_jstr,path_cstr);
sound->release();
system->close();
system->release();
}
注意myvoice.wav音频文件的位数
则在c++中要相应写16,否则无法播放
//手机录音一般是16位 如果是32位的音频要填32 否则无法播放声音
system->init(16, FMOD_INIT_NORMAL, NULL);
4.写mk文件
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := fmod
LOCAL_SRC_FILES := $(LOCAL_PATH)/lib/$(TARGET_ARCH_ABI)/libfmod.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := fmodL
LOCAL_SRC_FILES := $(LOCAL_PATH)/lib/$(TARGET_ARCH_ABI)/libfmodL.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := qq_voicer
#LOCAL_SRC_FILES := play_sound.cpp common.cpp common_platform.cpp #音效果一 用MainActivity
#LOCAL_SRC_FILES := effects.cpp common.cpp common_platform.cpp #音效果二 用MainActivity
LOCAL_SRC_FILES :=effects_qq_voicer.cpp #qq变声用 QQVoiceActivity
LOCAL_SHARED_LIBRARIES := fmod fmodL
LOCAL_LDLIBS := -llog
LOCAL_CPP_FEATURES := exceptions #支持异常处理
include $(BUILD_SHARED_LIBRARY)
Applicatoin.mk
APP_MODULES := qq_voicer
APP_ABI := arm64-v8a armeabi armeabi-v7a #表示 编译目标 ABI(应用二进制接口)
APP_STL := gnustl_static ##支持标准STL C++
APP_PLATFORM := android-14
5.编写Android调用变声主程序QQVoiceActivity
package org.fmod.example;
import android.Manifest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.tbruyelle.rxpermissions2.RxPermissions;
import org.fmod.FMOD;
import java.io.File;
import io.reactivex.functions.Consumer;
/**
* Created by Xionghu on 2017/11/24.
* Desc:
*/
public class QQVoiceActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FMOD.init(this);
setContentView(R.layout.activity_qq_voice);
}
public void mFix(final View btn) {
RxPermissions rxPermission = new RxPermissions(QQVoiceActivity.this);
rxPermission.request(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO)
.subscribe(new Consumer() {
@Override
public void accept(Boolean granted) {
if (granted) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "myvoice.wav";
//String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "drumloop.wav";
Log.d("QQVoiceActivity", path);
switch (btn.getId()) {
case R.id.btn_normal:
EffectUtils.fix(path, EffectUtils.MODE_NORMAL);
break;
case R.id.btn_luoli:
EffectUtils.fix(path, EffectUtils.MODE_LUOLI);
break;
case R.id.btn_dashu:
EffectUtils.fix(path, EffectUtils.MODE_DASHU);
break;
case R.id.btn_jingsong:
EffectUtils.fix(path, EffectUtils.MODE_JINGSONG);
break;
case R.id.btn_gaoguai:
EffectUtils.fix(path, EffectUtils.MODE_GAOGUAI);
break;
case R.id.btn_kongling:
EffectUtils.fix(path, EffectUtils.MODE_KONGLING);
break;
default:
break;
}
} else {
Toast.makeText(QQVoiceActivity.this, "权限被拒绝,请到设置中打开",Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
FMOD.close();
}
}
myvoice.wav是用专业录音app工具录制,放在sdcard根目录下
5其它文件配置
build.gradle中
ndk{
moduleName "qq_voicer"
}
sourceSets.main{
jni.srcDirs = []
jniLibs.srcDir "src/main/libs"
}
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.0.5'
compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
AndroidManifest.xml中
源码下载
Github:https://github.com/kpioneer123/QQVoicerChange
特别感谢:
动脑学院Jason