Android NDK開發(fā)之旅30--使用fmod模仿QQ變聲特效

前言

我們這次用到的是fmod這個庫,fmod是音效引擎游戲開發(fā)革命引擎每币,著名的游戲開發(fā)引擎CosCos2D魂仍、 Unity都封裝了這個庫可款。

FMOD的如下優(yōu)點:
  • 使用FMOD我們可以使用更少的資源創(chuàng)建更加高級和豐富的音效,減少運行時內(nèi)存資源消耗逼庞;
  • 音效管理只需要在FMOD Studio中管理好即可蛇更;
  • 編程人員只需要依賴于各種字符串形式的Sound Event和簡單的播放API即可,使用簡單赛糟;
  • 平臺支持較為完善派任。

項目演示

image.png

原理分析

  • 原聲:直接播放音頻文件
  • 蘿莉:對音頻提高八度左右
  • 大叔:對音頻減低八度左右
  • 驚悚:增加音頻的顫音
  • 搞笑:增加音頻的播放速度
  • 空靈:增加音頻的回音

資源下載與使用

1.fmod下載
2.解壓壓縮包以后,打開api文件夾璧南,會有fsbank吨瞎、lowlevel、studio:
  • fsbank穆咐,bank是一個fmod的概念颤诀,包含里一些聲音的事件,也可以通過
    FMOD Studio Tool來制作对湃,這里是通過代碼去制作崖叫。
  • lowlevel,我們需要的文件都在這里拍柒,包含了基礎(chǔ)的聲音處理功能心傀。
  • studio,這里存放的api跟界面相關(guān)的拆讯,我們不需要使用這些界面脂男。
3.使用下載包中api-> lowlevel 下的庫與資源文件
  • examples示例程序养叛,里面有一個Activity,以及對應(yīng)的cpp文件宰翅,供我們參考寫弃甥。
  • inc目錄,放的是fmod相關(guān)的頭文件汁讼,直接Copy到我們的Android項目當中淆攻。
  • lib目錄,放的是需要預(yù)編譯調(diào)用的so庫嘿架,以及一個jar包瓶珊,直接Copy到我們的Android項目當中。

代碼編寫

1耸彪、我們創(chuàng)建一個EffectUtils類伞芹,編寫我們的fmod變聲處理

package org.fmod.example;

/**
 * Created by Xionghu on 2017/11/24.
 * Desc:
 */

public class EffectUtils {

    //音效類型
    public static final int MODE_NORMAL = 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;
    /**
     * 音效處理
     * @param path
     * @param type
     */
    public native static void fix(String path,int type);

    static
    {
         System.loadLibrary("fmodL");
         System.loadLibrary("fmod");
         System.loadLibrary("qq_voicer");
    }
}

2、javah我們的聲明文件EffectUtils生成頭文件

生成頭文件請參考超級簡單的Android Studio jni 實現(xiàn)(無需命令行)

注意:新版Android Studio ndk-build 要指定Application.mk和Android.mk路徑


生成org_fmod_example_EffectUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_fmod_example_EffectUtils */

#ifndef _Included_org_fmod_example_EffectUtils
#define _Included_org_fmod_example_EffectUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_fmod_example_EffectUtils
 * Method:    fix
 * Signature: (Ljava/lang/String;I)V
 */
JNIEXPORT void JNICALL Java_org_fmod_example_EffectUtils_fix
  (JNIEnv *, jclass, jstring, jint);

#ifdef __cplusplus
}
#endif
#endif

3蝉娜、編寫變聲核心代碼 effects_qq_voicer.cpp

注意paly_sound.cpp 唱较、effects.cpp是fmod示例代碼,大家可以在編寫代碼 effects_qq_voicer.cpp之前進行測試
effects_qq_voicer.cpp
#include "inc/fmod.hpp"
#include "org_fmod_example_EffectUtils.h"

#include <stdlib.h>
#include <unistd.h>
#include <android/log.h>

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

JNIEXPORT void JNICALL Java_org_fmod_example_EffectUtils_fix
(JNIEnv *env, jclass jcls, jstring path_jstr, jint type) {
    LOGI("%s", "fix normal55555555555");
    System *system;
    Sound *sound;
    Channel *channel;
    DSP *dsp;
    float frequency = 0;
    bool playing = true;

    const char* path_cstr = env->GetStringUTFChars(path_jstr, NULL);
    try {
    //初始化
    System_Create(&system);
    //手機錄音一般是16位  如果是32位的音頻要填32 否則無法播放聲音
    system->init(16, FMOD_INIT_NORMAL, NULL);
    //創(chuàng)建聲音
    system->createSound(path_cstr, FMOD_DEFAULT, NULL, &sound);
    switch (type) {
    case MODE_NORMAL:
        //原生播放
        LOGI("%s", path_cstr);
        system->playSound(sound, 0, false, &channel);
        LOGI("%s", "fix normal");
        break;
    case MODE_LUOLI:
        //蘿莉
        //DSP digital signal process
        //dsp -> 音效
        //FMOD_DSP_TYPE_PITCH  dsp 蜀肘,提升或者降低音調(diào)用的一種音效
        // FMOD_DSP_TYPE_PITCHSHIFT 在fmod_dsp_effects.h中
        system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
        //設(shè)置音調(diào)的參數(shù)
        dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.5);
        system->playSound(sound, 0, false, &channel);
        //添加到channel
        channel->addDSP(0, dsp);
        LOGI("%s", "fix luoli");
        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
        channel->addDSP(0, dsp);
        LOGI("%s", "fix dashu");
        break;
        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.6;
        channel->setFrequency(frequency);
        LOGI("%s", "fix gaoguai");
        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);
        LOGI("%s", "fix kongling");

        break;

    default:
        break;
    }
    }catch(...){
        LOGE("%s","發(fā)生異常");
        goto END;
    }
    system->update();
    //進程休眠 單位微秒 us
    //每秒鐘判斷是否在播放
    while (playing) {
        channel->isPlaying(&playing);
        usleep(1000 * 1000);
    }
    goto END;
    //釋放資源
END:
    env->ReleaseStringUTFChars(path_jstr,path_cstr);
    sound->release();
    system->close();
    system->release();


}
注意myvoice.wav音頻文件的位數(shù)
16位音頻 錄音工具
則在c++中要相應(yīng)寫16绊汹,否則無法播放
    //手機錄音一般是16位  如果是32位的音頻要填32 否則無法播放聲音
    system->init(16, FMOD_INIT_NORMAL, NULL);

4.寫mk文件

Android.mk
LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)
LOCAL_MODULE := fmod
LOCAL_SRC_FILES := $(LOCAL_PATH)/lib/$(TARGET_ARCH_ABI)/libfmod.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := fmodL
LOCAL_SRC_FILES := $(LOCAL_PATH)/lib/$(TARGET_ARCH_ABI)/libfmodL.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)


LOCAL_MODULE    := qq_voicer
#LOCAL_SRC_FILES := play_sound.cpp common.cpp common_platform.cpp  #音效果一 用MainActivity
#LOCAL_SRC_FILES := effects.cpp common.cpp common_platform.cpp   #音效果二 用MainActivity
LOCAL_SRC_FILES :=effects_qq_voicer.cpp    #qq變聲用 QQVoiceActivity
LOCAL_SHARED_LIBRARIES := fmod fmodL
LOCAL_LDLIBS := -llog
LOCAL_CPP_FEATURES := exceptions  #支持異常處理
include $(BUILD_SHARED_LIBRARY)

Applicatoin.mk
APP_MODULES := qq_voicer
APP_ABI :=   arm64-v8a armeabi armeabi-v7a #表示 編譯目標 ABI(應(yīng)用二進制接口)
APP_STL := gnustl_static ##支持標準STL C++
APP_PLATFORM := android-14

5.編寫Android調(diào)用變聲主程序QQVoiceActivity

package org.fmod.example;

import android.Manifest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.tbruyelle.rxpermissions2.RxPermissions;
import org.fmod.FMOD;
import java.io.File;
import io.reactivex.functions.Consumer;

/**
 * Created by Xionghu on 2017/11/24.
 * Desc:
 */

public class QQVoiceActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FMOD.init(this);
        setContentView(R.layout.activity_qq_voice);


    }

    public void mFix(final View btn) {
        RxPermissions rxPermission = new RxPermissions(QQVoiceActivity.this);
        rxPermission.request(
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.RECORD_AUDIO)
                .subscribe(new Consumer<Boolean>() {
                    @Override
                    public void accept(Boolean granted) {
                        if (granted) {
                            String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "myvoice.wav";
                           //String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "drumloop.wav";
                            Log.d("QQVoiceActivity", path);
                            switch (btn.getId()) {
                                case R.id.btn_normal:
                                    EffectUtils.fix(path, EffectUtils.MODE_NORMAL);
                                    break;

                                case R.id.btn_luoli:
                                    EffectUtils.fix(path, EffectUtils.MODE_LUOLI);
                                    break;

                                case R.id.btn_dashu:
                                    EffectUtils.fix(path, EffectUtils.MODE_DASHU);
                                    break;

                                case R.id.btn_jingsong:
                                    EffectUtils.fix(path, EffectUtils.MODE_JINGSONG);
                                    break;

                                case R.id.btn_gaoguai:
                                    EffectUtils.fix(path, EffectUtils.MODE_GAOGUAI);
                                    break;

                                case R.id.btn_kongling:
                                    EffectUtils.fix(path, EffectUtils.MODE_KONGLING);
                                    break;

                                default:
                                    break;
                            }


                        } else {
                              Toast.makeText(QQVoiceActivity.this, "權(quán)限被拒絕,請到設(shè)置中打開",Toast.LENGTH_SHORT).show();
                        }
                    }
                });


    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        FMOD.close();
    }
}
myvoice.wav是用專業(yè)錄音app工具錄制,放在sdcard根目錄下

5其它文件配置

build.gradle中
        ndk{
            moduleName "qq_voicer"
        }
        sourceSets.main{
            jni.srcDirs = []
            jniLibs.srcDir "src/main/libs"
        }
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'io.reactivex.rxjava2:rxjava:2.0.5'
    compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
AndroidManifest.xml中
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE "/>

源碼下載

Github:https://github.com/kpioneer123/QQVoicerChange

特別感謝:

動腦學(xué)院Jason

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扮宠,一起剝皮案震驚了整個濱河市西乖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坛增,老刑警劉巖获雕,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異收捣,居然都是意外死亡届案,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門罢艾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來楣颠,“玉大人,你說我怎么就攤上這事咐蚯⊥觯” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵春锋,是天一觀的道長矫膨。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么侧馅? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任危尿,我火速辦了婚禮,結(jié)果婚禮上馁痴,老公的妹妹穿的比我還像新娘谊娇。我一直安慰自己,他們只是感情好弥搞,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布邮绿。 她就那樣靜靜地躺著渠旁,像睡著了一般攀例。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上顾腊,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天粤铭,我揣著相機與錄音,去河邊找鬼杂靶。 笑死梆惯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的吗垮。 我是一名探鬼主播垛吗,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼烁登!你這毒婦竟也來了怯屉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤饵沧,失蹤者是張志新(化名)和其女友劉穎锨络,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狼牺,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡羡儿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了是钥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掠归。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖悄泥,靈堂內(nèi)的尸體忽然破棺而出虏冻,到底是詐尸還是另有隱情,我是刑警寧澤码泞,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布兄旬,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏领铐。R本人自食惡果不足惜悯森,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绪撵。 院中可真熱鬧瓢姻,春花似錦、人聲如沸音诈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽细溅。三九已至褥傍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喇聊,已是汗流浹背恍风。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留誓篱,地道東北人朋贬。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像窜骄,于是被迫代替她去往敵國和親锦募。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容