Android NDK开发之旅27 使用fmod模仿QQ变声特效

###前言

我们这次用到的是fmod这个库,fmod是音效引擎游戏开发革命引擎,著名的游戏开发引擎CosCos2D、 Unity都封装了这个库。 #####FMOD的如下优点:

  • 使用FMOD我们可以使用更少的资源创建更加高级和丰富的音效,减少运行时内存资源消耗;
  • 音效管理只需要在FMOD Studio中管理好即可;
  • 编程人员只需要依赖于各种字符串形式的Sound Event和简单的播放API即可,使用简单;
  • 平台支持较为完善。

###项目演示

###原理分析

  • 原声:直接播放音频文件
  • 萝莉:对音频提高八度左右
  • 大叔:对音频减低八度左右
  • 惊悚:增加音频的颤音
  • 搞笑:增加音频的播放速度
  • 空灵:增加音频的回音

###资源下载与使用 #####1.fmod下载 #####2.解压压缩包以后,打开api文件夹,会有fsbank、lowlevel、studio:

  • fsbank,bank是一个fmod的概念,包含里一些声音的事件,也可以通过 FMOD Studio Tool来制作,这里是通过代码去制作。
  • lowlevel,我们需要的文件都在这里,包含了基础的声音处理功能。
  • studio,这里存放的api跟界面相关的,我们不需要使用这些界面。

#####3.使用下载包中api-> lowlevel 下的库与资源文件

  • examples示例程序,里面有一个Activity,以及对应的cpp文件,供我们参考写。
  • inc目录,放的是fmod相关的头文件,直接Copy到我们的Android项目当中。
  • lib目录,放的是需要预编译调用的so库,以及一个jar包,直接Copy到我们的Android项目当中。

###代码编写 ####1、我们创建一个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 实现(无需命令行)

注意:新版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);
	//创建声音
	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 ,提升或者降低音调用的一种音效
		// FMOD_DSP_TYPE_PITCHSHIFT 在fmod_dsp_effects.h中
		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);
		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","发生异常");
		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音频文件的位数

#####则在c++中要相应写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(应用二进制接口)
APP_STL := gnustl_static ##支持标准STL C++
APP_PLATFORM := android-14
复制代码

####5.编写Android调用变声主程序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, "权限被拒绝,请到设置中打开",Toast.LENGTH_SHORT).show();
                        }
                    }
                });


    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        FMOD.close();
    }
}
复制代码

#####myvoice.wav是用专业录音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:github.com/kpioneer123…

###特别感谢: 动脑学院Jason

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值