NDK学习笔记:一起来变萝莉音!FMOD学习总结(下)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a360940265a/article/details/83417030

NDK学习笔记:一起来变萝莉音!FMOD学习总结(下)

 

一、创建自己的变音demo

上一节我已经能够在AndroidStudio上跑起了fmod的基础教程。还有疑问的同学可以重新阅读跟着来跑一次。这章节计划参照官方的play_sound.cpp + effect.cpp,实现类似变音器的效果。并且带大家熟悉AS的NDK代码的编写流程,毕竟往下的计划文章都慢慢的向NDK靠近,所以希望同学们都能熟练掌握这一块。

事不宜迟,开始撸码。

首先我们从java层入手,创建EffectActivity,跟随页面的生命周期,记得进行java.FMOD的初始化/关闭工作。代码如下:

public class EffectActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fmod_effect);
        org.fmod.FMOD.init(this); // 初始化java.FMOD,全局引用Context = this;

    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        org.fmod.FMOD.close(); // 回收java.FMOD,全局引用Context = null;
    }
}

UI 效果图就不截取了,就是六个按钮,分别对应6种声效:原声、萝莉、大叔、惊悚、搞怪、幽灵。

入口Activity的准备工作做好之后,我们就可以开始音效NDK方法的入口编写了。创建工具类VoiceEffectUtils,如下图所示:

可以看到工具类定义6种类型,三个native方法,但是方法出现错误,编译器提示我们找不到jni方法的native声明与实现。我们在项目目录的/src/main/cpp/fmod/ 创建 effect_sound.cpp,这样就能让编译器找到native方法的实现。

#include <jni.h>

JNIEnv *gJNIEnv;

extern "C"
{

void Java_org_zzrblog_fmod_VoiceEffectUtils_init(JNIEnv *env, jclass clazz )
{
    gJNIEnv = env;
}

void Java_org_zzrblog_fmod_VoiceEffectUtils_play(JNIEnv *env, jclass clazz, jint type )
{

}

void Java_org_zzrblog_fmod_VoiceEffectUtils_release(JNIEnv *env, jclass clazz )
{
    gJNIEnv = NULL;
}

} /* extern "C" */

全局初始化释放方法暂时不知道要做什么,来一习惯性操作,暂存JNIEnv指针的地址,释放方法置空。

至此,NDK基本构建完成,可以开始往里面进行方法的实现了。

二、实现play方法

首先还是从java层入手,根据页面生命周期进行VoiceEffectUtils的初始化和释放的操作,然后把UI的六个按钮分别都响应play的六种声效类型。EffectActivity的代码修改如下:

public class EffectActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fmod_effect);
        org.fmod.FMOD.init(this);
        VoiceEffectUtils.init();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        VoiceEffectUtils.release();
        org.fmod.FMOD.close();
    }

    public void onClickedEffect(View btn){
        //String path = Environment.getExternalStorageDirectory().getAbsolutePath()+File.pathSeparator+"singing.wav";
        //String path = "file:///android_asset/"+"singing.wav";
        switch (btn.getId()) {
            case R.id.btn_normal:
                VoiceEffectUtils.play(VoiceEffectUtils.MODE_NORMAL);
                break;
            case R.id.btn_luoli:
                VoiceEffectUtils.play(VoiceEffectUtils.MODE_LUOLI);
                break;
            case R.id.btn_dashu:
                VoiceEffectUtils.play(VoiceEffectUtils.MODE_DASHU);
                break;
            case R.id.btn_jingsong:
                VoiceEffectUtils.play(VoiceEffectUtils.MODE_JINGSONG);
                break;
            case R.id.btn_gaoguai:
                VoiceEffectUtils.play(VoiceEffectUtils.MODE_GAOGUAI);
                break;
            case R.id.btn_kongling:
                VoiceEffectUtils.play(VoiceEffectUtils.MODE_KONGLING);
                break;
            default:
                break;
        }
    }
}

接下来,到effect_sound.cpp里面根据不同类型实现不同音效的转变。


#define MODE_NORMAL 0
#define MODE_LUOLI 1
#define MODE_DASHU 2
#define MODE_JINGSONG 3
#define MODE_GAOGUAI 4
#define MODE_KONGLING 5
//cpp内全局引用,可以跨越多个线程,在手动释放之前,一直有效
JNIEnv *gJNIEnv;
jstring gMediaPath;


extern "C"
{

void Java_org_zzrblog_fmod_VoiceEffectUtils_init(JNIEnv *env, jclass clazz )
{
    gJNIEnv = env;
    jstring obj = gJNIEnv->NewStringUTF("file:///android_asset/dfb.mp3");
    gMediaPath = (jstring) gJNIEnv->NewGlobalRef(obj); //创建cpp内全局引用。
}

void Java_org_zzrblog_fmod_VoiceEffectUtils_release(JNIEnv *env, jclass clazz )
{
    if(gJNIEnv != NULL)
    {
        gJNIEnv->DeleteGlobalRef(gMediaPath);
    }
    gJNIEnv = NULL;
}

}

首先我们在init方法做些准备工作。这里硬编码assets里面的一个音频文件,代码不太优雅。有强迫症的朋友可以从java读取正确的路径,然后传参进init方法。然后我们根据之前在VoiceEffectUtils.java中的音效类型,在cpp也进行类型的宏定义。准备工作就绪,我们开始play的实现。

void Java_org_zzrblog_fmod_VoiceEffectUtils_play(JNIEnv *env, jclass clazz, jint type )
{
    FMOD::System    *system;
    void            *extradriverdata = 0;
    FMOD::Sound     *sound;
    FMOD::Channel   *channel;
    bool            playing = true; //判断音频是否还在播放.

    //初始化 FMOD.System
    System_Create(&system);
    system->init(32, FMOD_INIT_NORMAL, extradriverdata);

    //创建 FMOD.Sound
    const char* path_c_str = env->GetStringUTFChars(gMediaPath, NULL);
    LOGD("%s", path_c_str);
    system->createSound(path_c_str, FMOD_DEFAULT, NULL, &sound);

    //根据type 处理不同特效
    try {
        switch (type)
        {
            case MODE_NORMAL: //触发音频播放
                system->playSound(sound, 0, false, &channel);
                break;
            // ...
            default:
                break;
        }
    } catch (...) {
        LOGW("%s","VoiceEffectUtils play 发生异常...");
        goto end;
    }
    //update一下FMOD的各种状态位。
    system->update();
    //每秒钟判断下是否在播放
    while(playing) {
        channel->isPlaying(&playing);
        usleep(1000 * 1000); //单位是微秒
    }


end:
    LOGI("%s","VoiceEffectUtils play end ...");
    env->ReleaseStringUTFChars(gMediaPath,path_c_str);
    sound->release();
    system->close();
    system->release();
}

我们来简单解读以上代码:首先第一步初始化FMOD.System,最大支持32个channel。第二步创建FMOD.Sound,可以指定音频文件路径path_name,也可以直接传入音频数据data,我们现在这里就是载入初始化的 "file:///android_asset/dfb.mp3" 。成功创建Sound对象之后,第三步就是通过system->playSound(sound, 0, false, &channel); 触发播放音频,并指向到其中一个channel如果需要添加特效(DSP)就是通过channel对象操作的。我们现在是原声播放不做特效,直接跳到system->update();并轮询当前的播放状态,如果还没播放结束就睡眠等待,注意这里的usleep是nuistd.h中,POSIX标准定义的unix的方法,单位是微秒。最后一步当播放结束,我们进行各种对象的回收操作

接下来一一介绍和实现其他音效功能。废话不说,先上代码干货:

    //根据type 处理不同特效
    try {
        switch (type)
        {
            case MODE_NORMAL:{
                //触发音频播放
                system->playSound(sound, 0, false, &channel);
            }break;
            case MODE_LUOLI:{
                //DSP digital signal process
                //FMOD_DSP_TYPE_PITCHSHIFT fmod中预定义好的dsp,用于音调的提升或者降低
                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_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_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_GAOGUAI:{
                //搞怪 提高说话的速度
                float frequency = 0;
                system->playSound(sound, 0, false, &channel);
                channel->getFrequency(&frequency);
                frequency = frequency * 1.6f;
                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:
                break;
        }
    } catch (...) {
        LOGW("%s","VoiceEffectUtils play 发生异常...");
        goto end;
    }
    ... ...

代码如上,我们以萝莉特效来着重分析。首先我们来了解下什么是DSP:数字信号处理,英文:Digital Signal Processing,缩写为DSP。广义来说,数字信号处理是研究用数字方法对信号进行分析、变换、滤波、检测、调制、解调以及快速算法的一门技术学科。随着数字电路与系统技术以及计算机技术的发展,数字信号处理技术也相应地得到发展,其应用领域十分广泛。语音信号处理是信号处理中的重要分支之一。它包括的主要方面有:语音的识别,语言的理解,语音的合成,语音的增强,语音的数据压缩等。详细介绍可以参考这里

我们这里从FMOD内置的dsp功能开始,创建一个FMOD_DSP_TYPE_PITCHSHIFT的dsp,设置音调的参数 dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.5); 此类设置参数的接口方法和OpenGL状态机原理一样,请注意并不是以方法命名为引导的。 创建设置dsp之后,我们就可以在system->playSound(sound, 0, false, &channel); 触发音频播放并获取音频内部的一个通道channel,把dsp添加到此通道上channel->addDSP(index,dsp),并指定索引号。

其他效果同理,代码大致一样,就是创建dsp类型不一样,还有就是dsp的配置参数不一样。惊悚特效我们需要产生一样颤音的效果;大叔特效和萝莉的相反,我们只需要降低音频音调就可以了;搞怪特效我们这里尝试把音频的速度(频率)加大;最后空灵的效果,我们让他产生声音回旋的,达到空无一人效果就搞定。

代码算是搞定了,但是还不能运行demo,我们还需要到CMakeLists增加特效库的编译脚本,在原来的基础上增加如下语句:

... ...

add_library( # 生成动态库的名称
             fmod-effect-lib
             # 指定是动态库SO
             SHARED
             # 编译库的源代码文件
             src/main/cpp/fmod/effect_sound.cpp)

target_link_libraries( # 指定目标链接库
                       fmod-effect-lib
                       # 添加预编译库到目标链接库中
                       ${log-lib}
                       fmod
                       fmodL)

然后build -> make Module "app" 查看src/main/jniLibs/下是否生成特效库 libfmod-effect-lib.so 

最后在VoiceEffectUtils.java静态代码块中System.loadLibrary("fmod-effect-lib");

开心的run起来吧。

猜你喜欢

转载自blog.csdn.net/a360940265a/article/details/83417030