先看两个场景:
怎么做到的?我也想装一波
今天带小伙伴们了解一下如何做到上面的效果。
fmod介绍古人有云:视频界有ffmpeg,音频界有fmod。
下面的游戏大家伙可以看看,有没有眼熟的:
我随便截了几张图,有兴趣的可以到Fmod官网去具体了解。
怎么样有没有兴趣搞点有意思的玩玩,音频引擎已经有了。
我这边在调音编辑器里玩了一会,但是调音水平有限,不过多介绍了,有兴趣的自己down下来玩儿。
废话不多说,准备干活
然后一路next 完事儿。
- AS环境为最新版白狐
- AGP为最新版7.0.2
要求JDK为java 11,但是我主项目必须配置为jdk1.8,AS本身自带的就是java11所以我没有另外下载,而是直接在app的build.gradle里面
//app的build.gradle中android闭包下修改为 compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 }
结果无效,不想折腾了所以降低agp版本,解决首次编译问题。
如果有大佬 *** 作过不同项目配置不同jdk版本和环境变量的请不吝赐教。
将上面提到的Fmod引擎Android平台的inc文件夹拷贝到项目的cpp文件下。并声明之:
//CMakeLists.txt # 声明导入的头文件(以cpp文件夹为~) include_directories("inc")第二步 为了以后拓展,修改c文件引入形式(可忽略)
//CMakeLists.txt # 批量导入所有源文件 file(GLOB allCPP *.c *.h *.cpp) add_library( voicechangeapp SHARED ${allCPP} )第三步 导入fmod库文件并配置
将上面提到的Fmod引擎Android平台的lib文件夹下面的arm64-v8a(armeabi、armeabi-v7a、x86等)放到src/main/jniLibs文件夹下。并在CMakeList.txt中新增配置
//CMakeLists.txt set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}") target_link_libraries( # Specifies the target library. voicechangeapp log fmod fmodL )
最终CMakeLists.txt文件为:
# 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.10.2) # 声明导入的头文件(以cpp文件夹为~) include_directories("inc") # 批量导入所有源文件 file(GLOB allCPP *.c *.h *.cpp) # Declares and names the project. project("voicechangeapp") # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library. voicechangeapp # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). ${allCPP} ) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. #find_library( # Sets the name of the path variable. # log-lib # # # Specifies the name of the NDK library that # # you want CMake to locate. # log ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}") target_link_libraries( # Specifies the target library. voicechangeapp log fmod fmodL )第四步 导入jar并配置ndk架构过滤
文件还是上面提到的Android平台提供的
这一步随意点,理论上来说其实不需要配置,直接在jniLibs里面只保留一个主流的即可,或者动态下发更贴近实际开发,这里只是为了演示。
QQ语音变声实现activity里的代码很简单,随便给了几个按钮,提供一个java去调用native的方法,以及一个native播放完成后的方法回调。
public class MainActivity extends AppCompatActivity { private static final int MODE_NORMAL = 0; private static final int MODE_LOLITA = 1; private static final int MODE_UNCLE = 2; private static final int MODE_HORROR = 3; private static final int MODE_FUNNY = 4; private static final int MODE_INTANGIBLE = 5; static { System.loadLibrary("native-lib"); } private String path; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); path = "file:///android_asset/hello.m4a"; FMOD.init(this); } @Override protected void onDestroy() { super.onDestroy(); FMOD.close(); } // 六个 点击事件 public void onFix(View view) { switch (view.getId()) { case R.id.btn_normal: voiceChangeNative(MODE_NORMAL, path); // 真实开发中,必须子线程 JNI线程(很多坑) break; case R.id.btn_lolita: voiceChangeNative(MODE_LOLITA, path); break; case R.id.btn_uncle: voiceChangeNative(MODE_UNCLE, path); break; case R.id.btn_horror: voiceChangeNative(MODE_HORROR, path); break; case R.id.btn_funny: voiceChangeNative(MODE_FUNNY, path); break; case R.id.btn_intangible: voiceChangeNative(MODE_INTANGIBLE, path); break; } } // 给C++调用的函数 private void playerEnd(String msg) { Toast.makeText(this, "" + msg, Toast.LENGTH_SHORT).show(); } private native void voiceChangeNative(int modeNormal, String path); }生成.h的头文件
cd到src/main/java文件夹下,运行命令即可生成,然后拷贝到cpp文件夹下,备用。
javah com.alex.voicechangeapp.MainActivity拷贝c代码去实现方法
extern "C" JNIEXPORT void JNICALL Java_com_alex_voicechangeapp_MainActivity_voiceChangeNative(JNIEnv *env, jobject thiz, jint mode, jstring path) { char *content_ = "默认 播放完毕"; const char *path_ = env->GetStringUTFChars(path, NULL); System *system = 0; Sound *sound = 0; Channel *channel = 0; // DSP:digital signal process == 数字信号处理 指针 DSP *dsp = 0; // TODO 第一步 创建系统 System_Create(&system); // TODO 第二步 系统的初始化 参数1:最大音轨数, 参数2:系统初始化标记, 参数3:额外数据 system->init(32, FMOD_INIT_NORMAL, 0); // TODO 第三步 创建声音 参数1:路径, 参数2:声音初始化标记, 参数3:额外数据, 参数4:声音指针 system->createSound(path_, FMOD_DEFAULT, 0, &sound); // TODO 第四步:播放声音 音轨 声音 // 参数1:声音, 参数2:分组音轨, 参数3:控制, 参数4:通道 system->playSound(sound, 0, false, &channel); // TODO 第五步:增加特效 switch (mode) { case com_alex_voicechangeapp_MainActivity_MODE_NORMAL: // 原生 content_ = "原生 播放完毕"; break; case com_alex_voicechangeapp_MainActivity_MODE_LOLITA: // 萝莉 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_alex_voicechangeapp_MainActivity_MODE_UNCLE: // 大叔 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_alex_voicechangeapp_MainActivity_MODE_FUNNY: // 搞怪 content_ = "搞怪 小黄人 播放完毕"; // 小黄人声音 频率快 // 从音轨拿 当前 频率 float mFrequency; channel->getFrequency(&mFrequency); // 修改频率 channel->setFrequency(mFrequency * 1.5f); // 频率加快 小黄人的声音 break; case com_alex_voicechangeapp_MainActivity_MODE_HORROR: // 惊悚 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_alex_voicechangeapp_MainActivity_MODE_INTANGIBLE: // 空灵 学校广播 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); usleep(500 * 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); }总结
这就是一整个java调C代码,C调java代码的比较初级的例子。通过Fmod提供的引擎可以比较轻松的实现变音效果。总体来说在技术上没有什么难度,唯一需要注意的就是C层的配置和声明都要小心一点,否则就直接崩溃。
参考《享学课堂》Derry老师的NDK开发系列
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)