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

image

概述

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

4.jpg

页面布局

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


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 )

跑起来是这样的效果。


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 <jni.h>
#include <string>
#include <unistd.h>
#include <android/log.h>
#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语音变声功能实现

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • 概述 这次记录的是NDK的使用实例,使用 fmod 来处理变声。有着上一次 Android NDK的使用实例——增...
    自然like阅读 1,197评论 0 2
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,609评论 4 59
  • 不学习的时候总是让人很空虚 人呐,要想往前走,还是要随时给自己充电
    你有你的阳光阅读 189评论 0 0
  • 记忆中,我家曾有一只猫,棕白相间的条纹,琥珀色的双眼,全身柔软密实的毛,爱在我家门前的三棵桑葚树上跳跃,爱晃着尾巴...
    南方成小鹿阅读 317评论 2 1