02.使用fmod实现QQ变声效果

介绍

fmod是一款非常优秀的c++开源框架,这次就通过这个框架来实现仿QQ的变声效果,变声效果有6种,正常,萝莉,大叔,惊悚,搞怪,空灵。关于更多的效果自己感兴趣再去探索,套路大致相同。

项目效果图及其原理

这里写图片描述

项目采用Fmod开源库,一个非常简单通用的音频引擎,对原始声音进行音效的处理即可做出变声的效果,下面是变声音频的处理

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

运行

如果能将上次的fmod的示例程序在Android studio中跑起来了,这次运行也没有多大的问题。
本次使用的工具是 android studio 2.3.3,

步骤

1.下载fmod

到fmod的官网注册下载相应的fmod的Android版本。解压。得到如下
这里写图片描述
这次要用到的是api/lowlevel包下的inc下的文件和lib下的库

2.新建Android项目

通过as新建Android项目,比如这次我的项目名是QQVoice_change
然后将api/lowlevel下包的lib包下的 armeabi,x86文件及其文件下的.so库,fmod.jar 拷贝到项目的libs目录下。

将api/lowlevel包下的inc文件及其文件拷贝到项目的cpp目录下拷贝完成后如图所示
这里写图片描述

不用忘了拷过去之后要将fmod.jar编译到项目中。

3.修改build.gradle和CMakeLists.txt

很多人在android studio编译c/c++文件动态库,都死在了这一步,这一步需要有gradle和Cmake编程的基础才能真正的搞明白。这一步如果弄错会发现一编译会发生很多错误。

修改Builde.gradle

1、假如build.gradle中没有配置ndk{ }。那么当编译器在编译的时候将会编译所有平台的so库。而这里只导入了armeabix86的so文件,那么在编译其他的平台的so时,将找不到其他平台so文件而报错。因此需要在build.gradle中配置ndk{ }
如果不在build.gradle中配置ndk{ },那么就需要导入所有平台的so库。

在这个例子中只导入了armeabix86的so文件,如果直接编译将会报错。所以要在build.gradle添加如下代码:

ndk { 
    abiFilters "armeabi","x86" 
}

2.这里将so文件配置在了libs文件夹下,因此要在build.gradle中配置:

    sourceSets.main {
        jniLibs.srcDirs = ['libs']
        jni.srcDirs = []
    }

修改后的build.gradle内容如下

android {
    compileSdkVersion 26
    buildToolsVersion "27.0.3"
    defaultConfig {
        applicationId "com.gxl.change"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
        ndk {
            abiFilters "armeabi","x86"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    sourceSets.main {
        jniLibs.srcDirs = ['libs']
        jni.srcDirs = []
    }
}

修改CMakeLists.txt

由于这里编译需要fmod.so和fmodL.so,我们实现变声的cpp代码就放在native-lib.cpp中,生成的变声.so名称为changeVoice,所以CMakeLists.txt内容如下

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#-----------------------------------------
find_library( log-lib
              log )

set(my_lib_path ${CMAKE_SOURCE_DIR}/libs)

# 添加三方的so库
add_library(libfmod
            SHARED
            IMPORTED )

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

 add_library(libfmodL
             SHARED
             IMPORTED )

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

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")

#--------------------------------
add_library( # Sets the name of the library.
             changeVoice

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.



# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

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

# 链接好三个路径
target_link_libraries( changeVoice
                       libfmod
                       libfmodL

                       ${log-lib} )

可以多看看这个文件,这个文件看多了后就会发现很多c/c++的库在as下运行套路大致相同.

4.实现变声的功能

新建EffectUtils.java文件,通过这个文件来处理变声,代码如下

package com.gxl.change;

/**
 * Created by Administrator on 2018/7/10/010.
 */

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 static native void fix(String path,int type);

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

在android studio 2.2版本以上都可以自动生成头文件到native-lib.cpp中

5.通过c++实现功能

在native-lib中实现逻辑

#include <jni.h>
#include "inc/fmod.hpp"
#include <string>

#include <unistd.h>

using namespace FMOD;

#define MODE_NORMAL 0
#define MODE_LUOLI 1
#define MODE_DASHU 2
#define MODE_JINGSONG 3
#define MODE_GAOGUAI 4
#define MODE_KONGLING 5

#include <android/log.h>

#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"jason",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"jason",FORMAT,##__VA_ARGS__);


extern "C"
JNIEXPORT void JNICALL
Java_com_gxl_change_EffectUtils_fix(JNIEnv *env, jclass cls, jstring path_str, jint type) {
    //这里很多都是二级指针,但是这里可以通过定义一级指针,下面通过引用来达到同样的效果
    System * system;
    Sound * sound;
    Channel  *channel;
    DSP *dsp;
    bool playing= true;
    float frequency=1;

    //初始化
    System_Create(&system);
    system->init(32, FMOD_INIT_NORMAL, NULL);

    //将string转成char*
    const char* path=env->GetStringUTFChars(path_str,NULL);

    //创建声音
    system->createSound(path,FMOD_DEFAULT, 0, &sound);
    try {
        //根据类型改变声音
        switch (type) {
            case MODE_NORMAL:
                //正常声音
                system->playSound(sound, 0, false, &channel);
                break;
            case MODE_LUOLI:
                //萝莉
                //DSP digital signal process
                //dsp -> 音效 创建fmod中预定义好的音效
                //FMOD_DSP_TYPE_PITCHSHIFT dsp,提升或者降低音调用的一种音效
                system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
                //提高音效
                dsp->setParameterFloat(FMOD_DSP_TYPE_PITCHSHIFT, 2.5);
                //播放声音
                system->playSound(sound, 0, false, &channel);
                //将channel添加到dsp
                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添加到dsp
                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);
                //frequency  原来的声音速度
                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","发生异常");
    }
    system->update();
    //释放资源
    //单位是微秒
    //每秒钟判断下是否在播放
    while(playing){
        channel->isPlaying(&playing);
        usleep(1000 * 1000);
    }

    //释放
    sound->release();
    system->close();
    system->release();
    env->ReleaseStringUTFChars(path_str,path);
}

上面就是关于声音的处理,可以看到套路大致相同,最难的就是改变的音效的调节,如果需要更多音效,自行百度。我也只是将老师讲的例子敲一遍,其中的如何改变成什么样的音效还是不太清楚。可能可以参考源码中的例子吧,可能会有些麻烦.

6.编写界面处理

在MainActivity.java中实现界面的点击事件

public class QQActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FMOD.init(this);
        setContentView(R.layout.activity_main);
    }

    public void mFix1(View btn) {
        Toast.makeText(this, "变声", Toast.LENGTH_SHORT).show();
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "gxl.wav";
        EffectUtils.fix(path, EffectUtils.MODE_NORMAL);
        Log.d("jason", "mFix");
    }
    public void mFix2(View btn) {
        Toast.makeText(this, "变声", Toast.LENGTH_SHORT).show();
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "gxl.wav";
        EffectUtils.fix(path, EffectUtils.MODE_LUOLI);
        Log.d("jason", "mFix");
    }
    public void mFix3(View btn) {
        Toast.makeText(this, "变声", Toast.LENGTH_SHORT).show();
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "gxl.wav";
        EffectUtils.fix(path, EffectUtils.MODE_DASHU);
        Log.d("jason", "mFix");
    }
    public void mFix4(View btn) {
        Toast.makeText(this, "变声", Toast.LENGTH_SHORT).show();
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "gxl.wav";
        EffectUtils.fix(path, EffectUtils.MODE_JINGSONG);
        Log.d("jason", "mFix");
    }
    public void mFix5(View btn) {
        Toast.makeText(this, "变声", Toast.LENGTH_SHORT).show();
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "gxl.wav";
        EffectUtils.fix(path, EffectUtils.MODE_GAOGUAI);
        Log.d("jason", "mFix");
    }
    public void mFix6(View btn) {
        Toast.makeText(this, "变声", Toast.LENGTH_SHORT).show();
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "gxl.wav";
        EffectUtils.fix(path, EffectUtils.MODE_KONGLING);
        Log.d("jason", "mFix");
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        FMOD.close();
    }

}

界面如下
activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="vertical"
        android:background="#FFF"
        android:paddingBottom="20dp" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="20dp">

            <LinearLayout 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <ImageView
                    android:id="@+id/btn_record"
                    style="@style/AudioImgStyle"
                    android:src="@drawable/record"
                    android:onClick="mFix1"/>
                <TextView 
                    style="@style/AudioTextStyle"
                    android:text="原声"/>
            </LinearLayout>

            <LinearLayout 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <ImageView
                    android:id="@+id/btn_luoli"
                    style="@style/AudioImgStyle"
                    android:src="@drawable/luoli"
                    android:onClick="mFix2"/>
                <TextView 
                    style="@style/AudioTextStyle"
                    android:text="萝莉"/>
            </LinearLayout>


            <LinearLayout 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <ImageView
                    android:id="@+id/btn_dashu"
                    style="@style/AudioImgStyle"
                    android:src="@drawable/dashu"
                    android:onClick="mFix3"/>
                <TextView 
                    style="@style/AudioTextStyle"
                    android:text="大叔"/>
            </LinearLayout>
        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="20dp">

            <LinearLayout 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <ImageView
                    android:id="@+id/btn_jingsong"
                    style="@style/AudioImgStyle"
                    android:src="@drawable/jingsong"
                    android:onClick="mFix4"/>
                <TextView 
                    style="@style/AudioTextStyle"
                    android:text="惊悚"/>
            </LinearLayout>

            <LinearLayout 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <ImageView
                    android:id="@+id/btn_gaoguai"
                    style="@style/AudioImgStyle"
                    android:src="@drawable/gaoguai"
                    android:onClick="mFix5"/>
                <TextView 
                    style="@style/AudioTextStyle"
                    android:text="搞怪"/>
            </LinearLayout>

            <LinearLayout 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <ImageView
                    android:id="@+id/btn_kongling"
                    style="@style/AudioImgStyle"
                    android:src="@drawable/kongling" 
                    android:onClick="mFix6"/>
                <TextView 
                    style="@style/AudioTextStyle"
                    android:text="空灵"/>
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>


</RelativeLayout>

猜你喜欢

转载自blog.csdn.net/a_thousand_miles/article/details/81150954