Android基于Fmod实现变音

Android基于Fmod实现变音,第1张

Android基于Fmod实现变音 背景

先看两个场景:



怎么做到的?我也想装一波

今天带小伙伴们了解一下如何做到上面的效果。

fmod介绍

古人有云:视频界有ffmpeg,音频界有fmod。

下面的游戏大家伙可以看看,有没有眼熟的:




我随便截了几张图,有兴趣的可以到Fmod官网去具体了解。
怎么样有没有兴趣搞点有意思的玩玩,音频引擎已经有了。

我这边在调音编辑器里玩了一会,但是调音水平有限,不过多介绍了,有兴趣的自己down下来玩儿。

废话不多说,准备干活

第一步 先下载Android平台的引擎

第二步 下载后需要的文件:

新建项目并部署C/C++库步骤 第一步 新建Native Project


然后一路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版本和环境变量的请不吝赐教。

最终可运行的项目如下

Cmake导入库流程 第一步 导入头文件并声明

将上面提到的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开发系列

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5672525.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存