assets目录下放正常声音的mp4文件
cpp目录下新建inc文件夹放fmod的头文件
main目录下新建jniLibs文件夹放各个CPU架构的so库
app目录下新建lib文件夹放fmod的jar包
界面效果activity_main.xml
bash
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="对应声音的文字"
android:textColor="#f00"
android:textSize="20dp"
android:layout_marginTop="40dp"
/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#FFF"
android:orientation="vertical"
android:layout_alignParentBottom="true"
android:paddingBottom="20dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_normal"
style="@style/AudioImgStyle"
android:onClick="onFix"
android:src="@drawable/record" />
<TextView
style="@style/AudioTextStyle"
android:text="原声" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_luoli"
style="@style/AudioImgStyle"
android:onClick="onFix"
android:src="@drawable/luoli" />
<TextView
style="@style/AudioTextStyle"
android:text="萝莉" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_dashu"
style="@style/AudioImgStyle"
android:onClick="onFix"
android:src="@drawable/dashu" />
<TextView
style="@style/AudioTextStyle"
android:text="大叔" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_jingsong"
style="@style/AudioImgStyle"
android:onClick="onFix"
android:src="@drawable/jingsong" />
<TextView
style="@style/AudioTextStyle"
android:text="惊悚" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_gaoguai"
style="@style/AudioImgStyle"
android:onClick="onFix"
android:src="@drawable/gaoguai" />
<TextView
style="@style/AudioTextStyle"
android:text="搞怪" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_kongling"
style="@style/AudioImgStyle"
android:onClick="onFix"
android:src="@drawable/kongling" />
<TextView
style="@style/AudioTextStyle"
android:text="空灵" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
MainActivity.java
java
package com.example.as_jni_project;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import org.fmod.FMOD;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MainActivity extends AppCompatActivity {
private static final int MODE_NORMAL = 0; // 正常
private static final int MODE_LUOLI = 1; //
private static final int MODE_DASHU = 2; //
private static final int MODE_JINGSONG = 3; //
private static final int MODE_GAOGUAI = 4; //
private static final int MODE_KONGLING = 5; //
private String audioFilePath; // 真实文件路径
private String path;
static {
System.loadLibrary("native-lib");
}
private void copyAssetToFiles(String assetName) {
try {
InputStream in = getAssets().open(assetName);
File outFile = new File(getFilesDir(), assetName);
audioFilePath = outFile.getAbsolutePath();
if (!outFile.exists()) {
OutputStream out = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.close();
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
copyAssetToFiles("xxx.mp3");
FMOD.init(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
FMOD.close();
}
public void onFix(View view) {
Log.d("cyr","打印一下audioFilePath----"+audioFilePath);
int id = view.getId();
if (id == R.id.btn_normal) {
voiceChangeNative(MODE_NORMAL, audioFilePath);
} else if (id == R.id.btn_luoli) {
voiceChangeNative(MODE_LUOLI, audioFilePath);
} else if (id == R.id.btn_dashu) {
voiceChangeNative(MODE_DASHU, audioFilePath);
} else if (id == R.id.btn_jingsong) {
voiceChangeNative(MODE_JINGSONG, audioFilePath);
} else if (id == R.id.btn_gaoguai) {
voiceChangeNative(MODE_GAOGUAI, audioFilePath);
} else if (id == R.id.btn_kongling) {
voiceChangeNative(MODE_KONGLING, audioFilePath);
}
}
public void playerEnd(String mes){
Toast.makeText(this,mes,Toast.LENGTH_LONG).show();
}
public native void voiceChangeNative(int modelNormal,String path);
}
javah com.xxx.xxx.MainActivity 根据MainActivity生成的头文件:
c
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_derry_derry_voicechange_MainActivity */
// xxx.h ---- xxx.c 早期
// xxx.hpp ---- xxx.cpp
// xxx.h ---- xxx.cpp 兼容的,可以的
// xxx.hpp 是头文件而已
#include <fmod.hpp> // TODO 最后一步 FMOD的头文件,必须导入,才能使用功能
#include <string>
//定义相关的宏
#ifndef _Included_com_example_as_jni_project_MainActivity
#define _Included_com_example_as_jni_project_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
#undef com_example_as_jni_project_MainActivity_MODE_NORMAL
#define com_example_as_jni_project_MainActivity_MODE_NORMAL 0L
#undef com_example_as_jni_project_MainActivity_MODE_LUOLI
#define com_example_as_jni_project_MainActivity_MODE_LUOLI 1L
#undef com_example_as_jni_project_MainActivity_MODE_DASHU
#define com_example_as_jni_project_MainActivity_MODE_DASHU 2L
#undef com_example_as_jni_project_MainActivity_MODE_JINGSONG
#define com_example_as_jni_project_MainActivity_MODE_JINGSONG 3L
#undef com_example_as_jni_project_MainActivity_MODE_GAOGUAI
#define com_example_as_jni_project_MainActivity_MODE_GAOGUAI 4L
#undef com_example_as_jni_project_MainActivity_MODE_KONGLING
#define com_example_as_jni_project_MainActivity_MODE_KONGLING 5L
/*
* Class: com_derry_derry_voicechange_MainActivity
* Method: voiceChangeNative
* Signature: (ILjava/lang/String;)V
*/
//方法声明
JNIEXPORT void JNICALL Java_com_example_as_1jni_1project_MainActivity_voiceChangeNative
(JNIEnv *, jobject, jint, jstring);
#ifdef __cplusplus
}
#endif
#endif
头文件对应的实现native-lib.cpp:
cpp
#include <unistd.h>
#include "com.example.as_jni_project.MainActivity.h"
using namespace FMOD;//fmod的命名空间
extern "C"
JNIEXPORT void JNICALL
//方法实现
Java_com_example_as_1jni_1project_MainActivity_voiceChangeNative(JNIEnv *env, jobject thiz,
jint mode, jstring path) {
char * content_ = "默认 播放完毕";
//C认识的字符串
const char * path_ = env->GetStringUTFChars(path,NULL);
//音效引擎系统指针
System *system = 0;
//声音 指针
Sound * sound = 0;
//通道,音轨,声音在上面跑
Channel* channel = 0;
//DSP 数字信号处理 指针
DSP * dsp = 0;
//第一步 创建系统
System_Create(&system);
//第二步 系统的初始化 参数1:最大音轨数 参数2:系统初始化标记 参数3:额外数据
system->init(32,FMOD_INIT_NORMAL,0);
//第三步 创建声音 参数1:路径 参数2:声音初始化标记 参数3:额外数据 参数4:声音指针
system->createSound(path_,FMOD_DEFAULT,0,&sound);
//第四步 播放声音 音轨 声音
//参数1:声音 参数2:分组音频 参数3:控制 参数4:通道
system->playSound(sound,0, false,&channel);
switch (mode) {
case com_example_as_jni_project_MainActivity_MODE_NORMAL: // 原生
content_ = "原生 播放完毕";
break;
case com_example_as_jni_project_MainActivity_MODE_LUOLI: // 萝莉
content_ = "萝莉 播放完毕";
// 音调高 -- 萝莉 2.0
// 1.创建DSP类型的Pitch 音调条件
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
// 2.设置Pitch音调调节2.0
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.0f);
// 3.添加音效进去 音轨
channel->addDSP(0, dsp);
break;
case com_example_as_jni_project_MainActivity_MODE_DASHU: // 大叔
content_ = "大叔 播放完毕";
// 音调低 -- 大叔 0.7
// 1.创建DSP类型的Pitch 音调条件
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
// 2.设置Pitch音调调节2.0
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7f);
// 3.添加音效进去 音轨
channel->addDSP(0, dsp);
break;
case com_example_as_jni_project_MainActivity_MODE_GAOGUAI: // 搞怪
content_ = "搞怪 小黄人 播放完毕";
// 小黄人声音 频率快
// 从音轨拿 当前 频率
float mFrequency;
channel->getFrequency(&mFrequency);
// 修改频率
channel->setFrequency(mFrequency * 1.5f); // 频率加快 小黄人的声音
break;
case com_example_as_jni_project_MainActivity_MODE_JINGSONG: // 惊悚
content_ = "惊悚 播放完毕";
// 惊悚音效:特点: 很多声音的拼接
// TODO 音调低
// 音调低 -- 大叔 0.7
// 1.创建DSP类型的Pitch 音调条件
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
// 2.设置Pitch音调调节2.0
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7f);
// 3.添加音效进去 音轨
channel->addDSP(0, dsp); // 第一个音轨
// TODO 搞点回声
// 回音 ECHO
system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 200); // 回音 延时 to 5000. Default = 500.
dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 10); // 回音 衰减度 Default = 50 0 完全衰减了
channel->addDSP(1,dsp); // 第二个音轨
// TODO 颤抖
// Tremolo 颤抖音 正常5 非常颤抖 20
system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
dsp->setParameterFloat(FMOD_DSP_TREMOLO_FREQUENCY, 20); // 非常颤抖
dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.8f); // ???
channel->addDSP(2, dsp); // 第三个音轨
// 调音师:才能跳出来 同学们自己去调
break;
case com_example_as_jni_project_MainActivity_MODE_KONGLING: // 空灵 学校广播
content_ = "空灵 播放完毕";
// 回音 ECHO
system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 200); // 回音 延时 to 5000. Default = 500.
dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 10); // 回音 衰减度 Default = 50 0 完全衰减了
channel->addDSP(0,dsp);
break;
}
//等待播放完毕再回收
bool isPlayer = true;
while (isPlayer){
channel->isPlaying(&isPlayer);// 如果真的播放完成了,音轨是知道的,内部会修改isPlayer=false
usleep(1000*1000);
}
//记得回收
sound->release();
system->close();
system->release();
env->ReleaseStringUTFChars(path,path_);
//告知Java播放完毕
jclass mainCls = env->GetObjectClass(thiz);
jmethodID endMethod = env->GetMethodID(mainCls,"playerEnd","(Ljava/lang/String;)V");
jstring value = env->NewStringUTF(content_);
env->CallVoidMethod(thiz,endMethod,value);
}