Android NDK的使用实例——使用fmod模仿QQ变声

Android NDK的使用实例——使用fmod模仿QQ变声_第1张图片
image

概述

本篇记录的内容在上一篇 Android NDK的使用实例——fmod example 的基础上继续开发。这次要使用fmod模仿QQ变声的效果。

Android NDK的使用实例——使用fmod模仿QQ变声_第2张图片
4.jpg

页面布局

几个按钮,就不贴布局代码了


Android NDK的使用实例——使用fmod模仿QQ变声_第3张图片
2.jpeg

编写native方法

NdkUtil.java

package org.fmod.example;

/**
 * NDK 工具类
 * Created by lex on 2017/12/30.
 */
public class NdkUtil {
    static {
        System.loadLibrary("native-lib");
    }

    public static final int MODE_PLAY = 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;

    /**
     * 播放
     */
    public native static final void play(int mode);
}

官方Demo解读

首先看下官方的Demo,这里把 effects.cpp 复制到工程里面,添加编译到 CMakeLists.txt 里面。

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/effects.cpp
             src/main/cpp/common.cpp
             src/main/cpp/common_platform.cpp
             src/main/cpp/native-lib.cpp )

跑起来是这样的效果。


Android NDK的使用实例——使用fmod模仿QQ变声_第4张图片
724493-2c52f28161bbfe31.jpeg

其中A、B、C、D按钮分别响应低通滤波lowpass、高通滤波highpass、回音echo、法兰flange的变声效果。点击后可以听到音频的发生了变声的效果。

以下是 effects.cpp 源代码,做了注释和删减部分。通过不断判断与测试,抽出必要的代码,以达到自己想要的效果。

#include "inc/fmod.hpp"
#include "common.h"

int FMOD_Main()
{
    // 初始化变声需要的相关变量
    FMOD::System       *system        = 0;
    FMOD::Sound        *sound         = 0;
    FMOD::Channel      *channel       = 0;
    FMOD::ChannelGroup *mastergroup   = 0; 
    FMOD::DSP          *dsplowpass    = 0;
    FMOD::DSP          *dsphighpass   = 0;
    FMOD::DSP          *dspecho       = 0;
    FMOD::DSP          *dspflange     = 0;
    FMOD_RESULT         result;
    unsigned int        version;
    void               *extradriverdata = 0;

    Common_Init(&extradriverdata);

    /*
        Create a System object and initialize
        创建一个系统对象
    */
    result = FMOD::System_Create(&system);
    // 初始化相关数据
    result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
    // 创建一个声音
    result = system->createSound(Common_MediaPath("dream.m4a"), FMOD_DEFAULT, 0, &sound);
    // 播放声音
    result = system->playSound(sound, 0, false, &channel);

    /*
        Create some effects to play with
        创建不同的音效
    */
    result = system->createDSPByType(FMOD_DSP_TYPE_LOWPASS, &dsplowpass);
    result = system->createDSPByType(FMOD_DSP_TYPE_HIGHPASS, &dsphighpass);
    result = system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dspecho);
    result = system->createDSPByType(FMOD_DSP_TYPE_FLANGE, &dspflange);

    /*
        Add them to the master channel group.  Each time an effect is added (to position 0) it pushes the others down the list.
    */
    result = mastergroup->addDSP(0, dsplowpass);
    result = mastergroup->addDSP(0, dsphighpass);
    result = mastergroup->addDSP(0, dspecho);
    result = mastergroup->addDSP(0, dspflange);

    /*
        By default, bypass all effects.  This means let the original signal go through without processing.
        It will sound 'dry' until effects are enabled by the user.
    */
    result = dsplowpass->setBypass(true);
    result = dsphighpass->setBypass(true);
    result = dspecho->setBypass(true);
    result = dspflange->setBypass(true);

    /*
        Main loop
    */
    do
    {
        Common_Update();

        // 判断不同按钮的点击状态,以进行不同的变声效果处理
        if (Common_BtnPress(BTN_MORE))
        {
            bool paused;

            result = channel->getPaused(&paused);

            paused = !paused;

            result = channel->setPaused(paused);
        }

        if (Common_BtnPress(BTN_ACTION1))
        {
            bool bypass;

            result = dsplowpass->getBypass(&bypass);

            bypass = !bypass;

            result = dsplowpass->setBypass(bypass);
        }

        if (Common_BtnPress(BTN_ACTION2))
        {
            bool bypass;

            result = dsphighpass->getBypass(&bypass);

            bypass = !bypass;

            result = dsphighpass->setBypass(bypass);
        }

        if (Common_BtnPress(BTN_ACTION3))
        {
            bool bypass;

            result = dspecho->getBypass(&bypass);

            bypass = !bypass;

            result = dspecho->setBypass(bypass);
        }

        if (Common_BtnPress(BTN_ACTION4))
        {
            bool bypass;

            result = dspflange->getBypass(&bypass);

            bypass = !bypass;

            result = dspflange->setBypass(bypass);
        }

        result = system->update();

        // 线程休眠时间
        Common_Sleep(50);
    } while (!Common_BtnPress(BTN_QUIT));

    /*
        Shut down
        关闭移除数字信号处理器
    */
    result = mastergroup->removeDSP(dsplowpass);
    result = mastergroup->removeDSP(dsphighpass);
    result = mastergroup->removeDSP(dspecho);
    result = mastergroup->removeDSP(dspflange);
    
    // 释放资源
    result = dsplowpass->release();
    result = dsphighpass->release();
    result = dspecho->release();
    result = dspflange->release();

    result = sound->release();
    result = system->close();
    result = system->release();

    Common_Close();

    return 0;
}

实现变声效果

描述声音特性的三个要素,包括响度、音色、音调。响度由振幅决定,音色由声波的波形决定,音调由频率决定。这里的变声效果有几种,原理也是对音频的不同属性做处理,当然,fmod对音效处理的类型做的更丰富。
1、萝莉:女高音,提高声音的频率
2、大叔:男低音,降低声音的频率
3、惊悚:声音添加了颤抖的效果
4、搞怪:加快了声音的播放速度 frequency
5、空灵:做了回声 echo 的音效

这里先用手机录制了一段声音文件 dream.m4a,复制到 assets 目录下,那么这段音频的路径就是

file:///android_asset/dream.m4a

这段音频数据是由二进制十六进制组成,那么我们要对这些数据进行处理?那得多复杂。我们可以借助于 fmod 来实现。以下是处理变声的代码

#include 
#include 
#include 
#include 
#include "inc/fmod.hpp"

#define  LOG_TAG    "lex"
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,FORMAT,__VA_ARGS__)
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,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;

extern "C"
JNIEXPORT void JNICALL
Java_org_fmod_example_NdkUtil_play(JNIEnv *env, jclass type, jint mode) {

    // 播放声音、处理变声所需要的一些变量
    System *system;
    Sound *sound;
    Channel *channel;
    DSP *dsp;
    bool isPlaying = true;
    float frequency = 0F;
    // 播放文件的路径
    const char *path = "file:///android_asset/dream.m4a";

    // 初始化
    System_Create(&system);
    system->init(32, FMOD_INIT_NORMAL, NULL);

    // 创建声音
    system->createSound(path, FMOD_DEFAULT, NULL, &sound);

    switch (mode) {
        case MODE_NORMAL:
            // 原声播放
            system->playSound(sound, 0, false, &channel);
            break;
        case MODE_LUOLI:
            // 萝莉音效
            // FMOD_DSP_TYPE_PITCHSHIFT,提高或者降低音调的类型
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 设置音调的参数
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.5);

            system->playSound(sound, 0, false, &channel);
            // 添加到channel
            channel->addDSP(0, dsp);
            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->addDSP(0, dsp);
            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.5F;
            channel->setFrequency(frequency);
            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);
            break;
        default:
            channel = NULL;
            break;
    }

    system->update();

    // 单位是微秒
    while (isPlaying && channel != NULL) {
        channel->isPlaying(&isPlaying);
        usleep(100 * 1000);
    }
    goto end;

    // 释放资源
    end:
    sound->release();
    system->close();
    system->release();
}

源码已经上传至 GitHub

体会

也是参考了很多资料来实现这个效果。官网文档也并不会很清晰的告诉你如何使用,甚至很多时候需要去推断。

C/C++开源的世界真是非常的庞大,拥有着非常多优秀算法的开源项目,fmod 就是其中之一。通常我们做Android使用到网络上的开源库比较多的是UI、网络、数据库、架构之类的。对于算法类的确实并不多。利用NDK进入C/C++犹如打开了另一扇大门,继续探索前进。


感谢

Android NDK开发之旅27 使用fmod模仿QQ变声特效
仿QQ语音变声功能实现

你可能感兴趣的:(Android NDK的使用实例——使用fmod模仿QQ变声)