前言
我們這次用到的是fmod這個庫,fmod是音效引擎游戲開發(fā)革命引擎每币,著名的游戲開發(fā)引擎CosCos2D魂仍、 Unity都封裝了這個庫可款。
FMOD的如下優(yōu)點:
- 使用FMOD我們可以使用更少的資源創(chuàng)建更加高級和豐富的音效,減少運行時內(nèi)存資源消耗逼庞;
- 音效管理只需要在FMOD Studio中管理好即可蛇更;
- 編程人員只需要依賴于各種字符串形式的Sound Event和簡單的播放API即可,使用簡單赛糟;
- 平臺支持較為完善派任。
項目演示
原理分析
- 原聲:直接播放音頻文件
- 蘿莉:對音頻提高八度左右
- 大叔:對音頻減低八度左右
- 驚悚:增加音頻的顫音
- 搞笑:增加音頻的播放速度
- 空靈:增加音頻的回音
資源下載與使用
1.fmod下載
2.解壓壓縮包以后,打開api文件夾璧南,會有fsbank吨瞎、lowlevel、studio:
- fsbank穆咐,bank是一個fmod的概念颤诀,包含里一些聲音的事件,也可以通過
FMOD Studio Tool來制作对湃,這里是通過代碼去制作崖叫。 - lowlevel,我們需要的文件都在這里拍柒,包含了基礎(chǔ)的聲音處理功能心傀。
- studio,這里存放的api跟界面相關(guān)的拆讯,我們不需要使用這些界面脂男。
3.使用下載包中api-> lowlevel 下的庫與資源文件
- examples示例程序养叛,里面有一個Activity,以及對應(yīng)的cpp文件宰翅,供我們參考寫弃甥。
- inc目錄,放的是fmod相關(guān)的頭文件汁讼,直接Copy到我們的Android項目當中淆攻。
- lib目錄,放的是需要預(yù)編譯調(diào)用的so庫嘿架,以及一個jar包瓶珊,直接Copy到我們的Android項目當中。
代碼編寫
1耸彪、我們創(chuàng)建一個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 實現(xiàn)(無需命令行)
注意:新版Android Studio ndk-build 要指定Application.mk和Android.mk路徑
生成org_fmod_example_EffectUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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 <stdlib.h>
#include <unistd.h>
#include <android/log.h>
#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);
//創(chuàng)建聲音
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 蜀肘,提升或者降低音調(diào)用的一種音效
// FMOD_DSP_TYPE_PITCHSHIFT 在fmod_dsp_effects.h中
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
//設(shè)置音調(diào)的參數(shù)
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","發(fā)生異常");
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音頻文件的位數(shù)
則在c++中要相應(yīng)寫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(應(yīng)用二進制接口)
APP_STL := gnustl_static ##支持標準STL C++
APP_PLATFORM := android-14
5.編寫Android調(diào)用變聲主程序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<Boolean>() {
@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, "權(quán)限被拒絕,請到設(shè)置中打開",Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
FMOD.close();
}
}
myvoice.wav是用專業(yè)錄音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中
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE "/>