Android基于Fmod实现变音

背景

先看两个场景:

在这里插入图片描述
在这里插入图片描述
怎么做到的?我也想装一波
在这里插入图片描述

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

fmod介绍

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

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

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

我这边在调音编辑器里玩了一会,但是调音水平有限,不过多介绍了,有兴趣的自己down下来玩儿。
在这里插入图片描述
废话不多说,准备干活

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

在这里插入图片描述

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

在这里插入图片描述

.hpp是什么
.hpp,本质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要include该.hpp文件即可,无需再将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,采用hpp将大幅度减少调用project中的cpp文件数与编译次数,也不用再发布lib与dll文件,因此非常适合用来编写公用的开源库。
hpp的优点不少,但是编写中有以下几点要注意:
1、是Header Plus Plus的简写。(.h和.hpp就如同.c和.cpp似的)
2、与.h类似,.hpp是C++程序头文件格式。
3、是VCL专用的头文件,已预编译。
4、是一般模板类的头文件。
5、一般来说,.h里面只有声明,没有实现,而.hpp里声明实现都有,后者可以减少.cpp的数量。
6、.h里面可以有using namespace std,而.hpp里则无。
7、不可包含全局对象和全局函数。

新建项目并部署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导入库流程

前置知识

CMake是什么?
一个跨平台构建系统

add_library 指令
语法:add_library(libname [SHARED | STATIC | MODULE] [EXCLUDE_FROM_ALL] [source])
将一组源文件 source 编译出一个库文件,并保存为 libname.so (lib 前缀是生成文件时 CMake自动添加上去的)。其中有三种库文件类型,不写的话,默认为 STATIC:

  • SHARED: 表示动态库,可以在(Java)代码中使用 System.loadLibrary(name) 动态调用;
  • STATIC: 表示静态库,集成到代码中会在编译时调用;
  • MODULE: 只有在使用 dyId 的系统有效,如果不支持 dyId,则被当作 SHARED 对待;
  • EXCLUDE_FROM_ALL: 表示这个库不被默认构建,除非其他组件依赖或手工构建

target_link_libraries 指令
语法:target_link_libraries(target library <debug | optimized> library2…)
这个指令可以用来为 target 添加需要的链接的共享库,同样也可以用于为自己编写的共享库添加共享库链接。

#指定 compress 工程需要用到 libjpeg 库和 log 库
target_link_libraries(compress libjpeg ${log-lib})
第一步 导入头文件并声明

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

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
fmod是一个用于音频处理的库,它提供了许多函数和接口来获取和处理音频数据。要使用fmod获取音频数据并绘制图形,可以按照以下步骤进行: 1. 初始化fmod:首先,我们需要初始化fmod库。这可以通过调用fmod提供的初始化函数来完成。在初始化过程,我们需要指定音频设备的一些参数,例如采样率、声道数等。 2. 创建fmod系统对象:一旦fmod初始化完成,我们可以创建一个fmod系统对象。系统对象可以用来管理和控制音频资源,包括加载音频文件、播放音频、获取音频数据等。 3. 打开音频文件:使用系统对象提供的函数,我们可以打开一个音频文件。打开音频文件后,我们可以通过fmod提供的函数来获取音频数据。例如,使用fmod系统对象的"getWaveData"函数可以获取当前播放位置的音频数据。 4. 绘制图形:获取到音频数据后,我们可以使用各种图形库或绘图工具来绘制图形。例如,我们可以使用Python的matplotlib库或者其他绘图库来创建音频波形图。通过将获取到的音频数据作为输入,我们可以将其与时间轴进行绘制,以呈现音频信号的波形形状。 5. 更新音频数据:如果需要实时更新绘图,我们可以使用一个循环来不断获取音频数据并更新图形。在每次循环,我们可以调用fmod的函数来获取新的音频数据,并使用绘图库来更新图形。 总结:使用fmod获取音频数据并绘制图形的过程包括初始化fmod、创建系统对象、打开音频文件、获取音频数据和绘制图形。这些步骤可以帮助我们实现音频数据的可视化,以便更好地理解和分析音频信号。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex_ChuTT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值