mtk android 音频架构 android音频处理库_变声

FMOD

FMOD是一个强大的声音引擎框架,QQ、魔兽世界及其他很多游戏都是使用的这套框架,框架内包含几十种声音类型,还可以修改声音的频率、速度等等。

1.FMOD download

进入FMOD download,下载Android端引擎库。

mtk android 音频架构 android音频处理库_Android_02

2.复制代码

将下载下来的FOMD引擎库中的Jar包,so库和jni c++文件,复制到项目中。

mtk android 音频架构 android音频处理库_android_03

3.编辑配置

编辑配置CMakeLists.txt

-----------------------------------------
find_library( log-lib
        log )

set(lib_path ${CMAKE_SOURCE_DIR}/libs)
# 添加三方的so库
add_library(libfmod
        SHARED
        IMPORTED )

# 指名第三方库的绝对路径
set_target_properties( libfmod
        PROPERTIES IMPORTED_LOCATION
        ${lib_path}/${ANDROID_ABI}/libfmod.so )

add_library(libfmodL
        SHARED
        IMPORTED )

set_target_properties( libfmodL
        PROPERTIES IMPORTED_LOCATION
        ${lib_path}/${ANDROID_ABI}/libfmodL.so )

#--------------------------------
add_library( # Sets the name of the library.
        FmodSound
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        src/main/cpp/native-lib.cpp)

#---------------------
# 导入路径,为了让编译时能够寻找到这个文件夹
include_directories(src/main/cpp/inc)

# 链接好三个路径
target_link_libraries( FmodSound
        libfmod
        libfmodL
        ${log-lib} )
4.修改代码

修改native-lib.cpp,已完成项目需求,如保存变声文件的代码:

extern "C"
JNIEXPORT jint JNICALL
Java_com_demon_fmodsound_FmodSound_saveSound(JNIEnv *env, jobject cls, jstring path_jstr, jint type, jstring save_jstr) {
    Sound *sound;
    DSP *dsp;
    bool playing = true;
    float frequency = 0;
    System *mSystem;
    JNIEnv *mEnv = env;
    int code = 0;
    System_Create(&mSystem);
    const char *path_cstr = mEnv->GetStringUTFChars(path_jstr, NULL);
    LOGI("saveAiSound-%s", path_cstr)
    const char *save_cstr;
    if (save_jstr != NULL) {
        save_cstr = mEnv->GetStringUTFChars(save_jstr, NULL);
        LOGI("saveAiSound-save_path=%s", save_cstr)
    }
    try {
        if (save_jstr != NULL) {
            char cDest[200];
            strcpy(cDest, save_cstr);
            mSystem->setSoftwareFormat(8000, FMOD_SPEAKERMODE_MONO, 0); //设置采样率为8000,channel为1
            mSystem->setOutput(FMOD_OUTPUTTYPE_WAVWRITER); //保存文件格式为WAV
            mSystem->init(32, FMOD_INIT_NORMAL, cDest);
            mSystem->recordStart(0, sound, true);
        }
        //创建声音
        mSystem->createSound(path_cstr, FMOD_DEFAULT, NULL, &sound);
        mSystem->playSound(sound, 0, false, &channel);
        LOGI("saveAiSound-%s", "save_start")
        switch (type) {
            case MODE_NORMAL:
                LOGI("saveAiSound-%s", "save MODE_NORMAL")
                break;
            case MODE_FUNNY:
                LOGI("saveAiSound-%s", "save MODE_FUNNY")
                mSystem->createDSPByType(FMOD_DSP_TYPE_NORMALIZE, &dsp);
                channel->getFrequency(&frequency);
                frequency = frequency * 1.6;
                channel->setFrequency(frequency);
                break;
            case MODE_UNCLE:
                LOGI("saveAiSound-%s", "save MODE_UNCLE")
                mSystem->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
                dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.8);
                channel->addDSP(0, dsp);
                break;
            case MODE_LOLITA:
                LOGI("saveAiSound-%s", "save MODE_LOLITA")
                mSystem->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
                dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 1.8);
                channel->addDSP(0, dsp);
                break;
            case MODE_ROBOT:
                LOGI("saveAiSound-%s", "save MODE_ROBOT")
                mSystem->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
                dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 50);
                dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 60);
                channel->addDSP(0, dsp);
                break;
            case MODE_ETHEREAL:
                LOGI("saveAiSound-%s", "save MODE_ETHEREAL")
                mSystem->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
                dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 300);
                dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 20);
                channel->addDSP(0, dsp);
                break;
            case MODE_CHORUS:
                LOGI("saveAiSound-%s", "save MODE_CHORUS")
                mSystem->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
                dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 100);
                dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 50);
                channel->addDSP(0, dsp);
                break;
            case MODE_HORROR:
                LOGI("saveAiSound-%s", "save MODE_HORROR")
                mSystem->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
                dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.8);
                channel->addDSP(0, dsp);
                break;
            default:
                break;
        }
        mSystem->update();
    } catch (...) {
        LOGE("saveAiSound-%s", "save error!")
        code = 1;
        goto end;
    }
    while (playing) {
        usleep(1000);
        channel->isPlaying(&playing);
    }
    LOGI("saveAiSound-%s", "save over!")
    goto end;
    end:
    if (path_jstr != NULL) {
        mEnv->ReleaseStringUTFChars(path_jstr, path_cstr);
    }
    if (save_jstr != NULL) {
        mEnv->ReleaseStringUTFChars(save_jstr, save_cstr);
    }
    sound->release();
    mSystem->close();
    mSystem->release();
    return code;
}
5.原生加载SO

加载FMOD的方法,提供给Android调用:

object FmodSound {
    //音效的类型
    const val MODE_NORMAL = 0 //正常

    const val MODE_FUNNY = 1 //搞笑

    const val MODE_UNCLE = 2 //大叔

    const val MODE_LOLITA = 3 //萝莉

    const val MODE_ROBOT = 4 //机器人

    const val MODE_ETHEREAL = 5 //空灵

    const val MODE_CHORUS = 6 //混合

    const val MODE_HORROR = 7 //恐怖

    init {
        System.loadLibrary("fmodL")
        System.loadLibrary("fmod")
        System.loadLibrary("FmodSound")
    }


    external fun saveSound(path: String, type: Int, savePath: String): Int
    external fun playSound(path: String, type: Int = MODE_NORMAL): Int

    external fun stopPlay()
    external fun resumePlay()
    external fun pausePlay()
    external fun isPlaying(): Boolean

    fun saveSoundAsync(path: String, type: Int, savePath: String, listener: ISaveSoundListener? = null) {
        try {
            if (isPlaying()) {
                stopPlay()
            }
            val result = saveSound(path, type, savePath)
            if (result == 0) {
                listener?.onFinish(path, savePath, type)
            } else {
                listener?.onError("error")
            }
        } catch (e: Exception) {
            listener?.onError(e.message)
        }
    }

    interface ISaveSoundListener {
        //成功
        fun onFinish(path: String, savePath: String, type: Int)

        //出错
        fun onError(msg: String?)
    }

}
6.原生调用

在项目中调用FMOD方法,先保存变声文件,保存成功后播放。

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //初始化
        FMOD.init(this)
        binding = ActivitySoundFmodBinding.inflate(layoutInflater)
        setContentView(binding.root)
        var path = intent.getStringExtra("path") ?: ""
        val file = File(path)
        if (!file.exists()) {
            showToast("录音文件不存在,请重新录制!")
            finish()
        } else {
            if (path.endsWith(".amr")) {
                path = AmrToWav.makeAmrToWav(path, false)
            }
            binding.tvPath.text = "音频文件:$path"
        }

        binding.fnRG.setOnCheckedChangeListener { group, checkedId ->
            val pos = group.indexOfChild(group.findViewById(checkedId))
            Log.i(TAG, "onCreate: $pos")
            type = pos
        }
        binding.btnPlay.setOnClickListener {
            GlobalScope.launchIO {
                FmodSound.playSound(path, type)
            }
        }
        binding.btnSave.setOnClickListener {
            binding.tvSave.text = "开始变声..."
            //耗时任务,需要在子线程中执行
            GlobalScope.launchIO {
                FmodSound.saveSoundAsync(path, type, getRecordFilePath(1), object : FmodSound.ISaveSoundListener {
                    override fun onFinish(path: String, savePath: String, type: Int) {
                        runOnUiThread {
                            binding.tvSave.text = "变声输出文件路径:$savePath"
                        }
                        FmodSound.playSound(savePath)
                    }

                    override fun onError(msg: String?) {
                        Log.e(TAG, "onError: $msg")
                        runOnUiThread {
                            binding.tvSave.text = "变声失败:$msg"
                        }
                    }
                })
            }
        }
    }


    override fun onDestroy() {
        super.onDestroy()
        //释放
        FMOD.close()
    }
7.FMOD优缺分析
  • 优点:变声类型多,自定义功能强大,百度文档比较多。
  • 缺点:变声引擎库体积大,调用方法多,使用麻烦,保存变声文件的速度较慢。

SoundTouch

SoundTouch是一个开源音频处理库,用于更改音频流或音频文件的速度,音调和播放速率。该库还支持估算音轨的稳定每分钟节拍速率。

1.下载源码

进入SoundTouch的Gitlab仓库下载最新的源码。

2.解压编译

解压下载的源码,进入到目录soundtouch/source/Android-lib/jni,打开CMD执行ndk-build,将jni编译成so库。

注意安装NDK环境。

mtk android 音频架构 android音频处理库_Android_04


如果编译中遇到下图的错误:

mtk android 音频架构 android音频处理库_ide_05


可以在Android.mk中添加APP_ALLOW_MISSING_DEPS=true即可。

3.复制SO库

soundtouch/source/Android-lib/libs生成的so库,选择需要的平台复制到你的项目中。

由于SoundTouch已经不再支持armeabi,需要armeabi平台的,可以直接使用armeabi-v7a平台的,两个平台的so库是完全兼容的。

如果so不是放在默认的'src/main/jniLibs'目录下,需要在在build.gradle中配置。
例如放在libs目录下:

sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
4.原生加载SO

复制source/Android-lib/src/net/surina/soundtouch/SoundTouch.java到你项目中,注意该文件需要放在包名net.surina.soundtouch下(如果你修改了soundtouch-jni.cpp中的包名,则放到自己指定的包名下即可)。

5.原生调用

在项目中调用,设置变声音调/速率,生成变声文件,并播放。

/**
     * 执行变声,需要在子线程中执行
     *
     * @param path 音频文件陆宇
     * @param savePath 变声后文件保存路径
     */
    private fun process(path: String, savePath: String) {
        try {
            val st = SoundTouch()
            st.setTempo(tempo) //速度
            st.setSpeed(speed) //速度&音调
            st.setPitchSemiTones(pitch) //音调
            val res = st.processFile(path, savePath)
           //res==0 变声成功
           if (res == 0) {
               //播放savePath   
             } else {
               showToast(SoundTouch.getErrorString())
            }
        } catch (e: Exception) {
            e.printStackTrace()

        }
    }
6.SoundTouch优缺分析
  • 优点:so库体积小,使用方便,方法简单,生成变声文件速度快。
  • 缺点:变声选择少,只能控制音调和速率;没有现成的so库需要自己配环境编译。

参考文档

FMOD

http://blackchy.com/2018/12/10/2018-12-10-Fmod-Voice-Change/

https://www.jianshu.com/p/2e1fd3035ae1

SoundTouch

http://www.surina.net/soundtouch/README-SoundTouch-Android.html

https://gitlab.com/soundtouch/soundtouch