NDK JNI 变声器实现

Android NDK 导入 C库的开发流程学习;通过使用fmod的C库,实现变声器功能。

  1. 导入库文件

1)复制fmod的C库到cpp目录下

2)复制fmod的so库到jniLibs目录下

3)复制fmod的jar库到libs目录下

4)将声音文件复制到assets目录下

fmod库文件资源:https://wwgl.lanzout.com/ilXHP0pn7f4f

  1. 打开CMakeLists.txt导入fmod库文件

cmake_minimum_required(VERSION 3.10.2) # 最低支持的CMake版本

project("whine")

# TODO 第一步 导入头文件(fmod库)
include_directories("inc")

# 批量导入所有文件
file(GLOB allCPP *.c *.h *.cpp)

add_library(
        native-lib # 打包出来库的名称 libnative-lib.so
        SHARED # 动态库
        ${allCPP} # 添加所有库文件
)

# TODO 第二步 导入库文件(fmod库)
# ${CMAKE_CXX_FLAGS} 原先的CMake环境 +
# -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI} 加上新的fmod库的路径
# CMAKE_SOURCE_DIR == CMakeLists.txt所在的路径
# /../ 回退到cpp目录
# jniLibs/ 进入jniLibs目录
# ${CMAKE_ANDROID_ARCH_ABI} 使用当前手机CPU系统结构对应的库 如:arm64-v8a
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")

find_library(
        log-lib
        log # 自动寻找log库
)

# TODO 第三步 链接具体的库,到 libnative-lib.so总库
target_link_libraries(
        native-lib
        ${log-lib}
        fmod # 具体的库 链接到 libnative-lib.so里面去
        fmodL # 具体的库 链接到 libnative-lib.so里面去
)
  1. 打开build.gradle,指定CPU架构和依赖fmod的jar包

defaultConfig {
    // TODO 第四步
    externalNativeBuild {
        cmake {
            // cppFlags "" // 默认五大平台
            // 指定CPU架构,Cmake的本地库, 例如:native-lib ---> arm64-v8a
            abiFilters "arm64-v8a"
        }
    }
    // TODO 第五步
    // 指定CPU架构,打入APK lib/CPU平台
    ndk {
        abiFilters "arm64-v8a"
    }
}

dependencies {
    // TODO 第六步:依赖fmod的jar包
    implementation files("libs\\fmod.jar");
}
  1. 准备工作完成,开始编写Activity代码

1)定义变声模式

/**
 * 定义变声模式
 * 0 正常; 1 萝莉; 2 大叔; 3 惊悚; 4 搞怪; 5 回声;
 */
private static final int MODE_NORMAL = 0;
private static final int MODE_LUOLI = 1;
private static final int MODE_DASHU = 2;
private static final int MODE_JINGSONG = 3;
private static final int MODE_GAOGUAI = 4;
private static final int MODE_KONGLING = 5;

2)加载编译后的lib库

static {
    System.loadLibrary("native-lib");
}

3)初始化fmod.jar库,并在页面关闭的时候释放

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

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

4)编写变声native层方法和变声结束后提供native调用的方法

public native void voiceChangeNative(int modeNormal, String path);

private void playerEnd(String msg) {
    isPlaying = false;
    runOnUiThread(() -> Toast.makeText(this, msg, Toast.LENGTH_SHORT).show());
}

5)Activity完整代码

package com.whine;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import org.fmod.FMOD;

/**
 * TODO Android NDK 导入 C库,开发流程 之 变声器
 */
public class MainActivity extends AppCompatActivity {

    /**
     * 定义变声模式
     * 0 正常; 1 萝莉; 2 大叔; 3 惊悚; 4 搞怪; 5 回声;
     */
    private static final int MODE_NORMAL = 0;
    private static final int MODE_LUOLI = 1;
    private static final int MODE_DASHU = 2;
    private static final int MODE_JINGSONG = 3;
    private static final int MODE_GAOGUAI = 4;
    private static final int MODE_KONGLING = 5;

    /**
     * 声音路径
     */
    private String soundPath;

    private boolean isPlaying = false;

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // TODO 第七步 初始化FMOD库
        FMOD.init(this);
        initData();
    }

    private void initData() {
        soundPath = "file:///android_asset/sound.mp3";
    }

    // 点击事件
    public void onFix(View view) {
        switch (view.getId()) {
            case R.id.btn_normal:
                voiceChange(MODE_NORMAL, soundPath); // 真实开发中,必须子线程  JNI线程(很多坑)
                break;
            case R.id.btn_luoli:
                voiceChange(MODE_LUOLI, soundPath);
                break;
            case R.id.btn_dashu:
                voiceChange(MODE_DASHU, soundPath);
                break;
            case R.id.btn_jingsong:
                voiceChange(MODE_JINGSONG, soundPath);
                break;
            case R.id.btn_gaoguai:
                voiceChange(MODE_GAOGUAI, soundPath);
                break;
            case R.id.btn_kongling:
                voiceChange(MODE_KONGLING, soundPath);
                break;
        }
    }

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

    private void voiceChange(int modeNormal, String path) {
        if (isPlaying) {
            Toast.makeText(this, "正在播放,请稍后!", Toast.LENGTH_SHORT).show();
            return;
        }
        isPlaying = true;
        new Thread(() -> voiceChangeNative(modeNormal, path)).start();
    }

    /**
     * 给C++(native)层调用的函数
     * JNI 调用 Java函数的时候,忽略掉 私有、公开 等
     *
     * @param msg
     */
    private void playerEnd(String msg) {
        isPlaying = false;
        runOnUiThread(() -> Toast.makeText(this, msg, Toast.LENGTH_SHORT).show());
    }

    /**
     * 变声native层方法
     *
     * @param modeNormal
     * @param path
     */
    public native void voiceChangeNative(int modeNormal, String path);
}
  1. 编写native层头文件(声明文件)

1)导入需要使用到的头文件

#include <jni.h>
#include <fmod.hpp>
#include <string>
#include <android/log.h>

2)定义log宏

#define TAG "NDK"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)

3)定义变声模式宏

#undef com_whine_MainActivity_MODE_NORMAL
#define com_whine_MainActivity_MODE_NORMAL 0L
#undef com_whine_MainActivity_MODE_LUOLI
#define com_whine_MainActivity_MODE_LUOLI 1L
#undef com_whine_MainActivity_MODE_DASHU
#define com_whine_MainActivity_MODE_DASHU 2L
#undef com_whine_MainActivity_MODE_JINGSONG
#define com_whine_MainActivity_MODE_JINGSONG 3L
#undef com_whine_MainActivity_MODE_GAOGUAI
#define com_whine_MainActivity_MODE_GAOGUAI 4L
#undef com_whine_MainActivity_MODE_KONGLING
#define com_whine_MainActivity_MODE_KONGLING 5L

4)头文件完整代码

// TODO 第八步 编写头文件(声明文件)
#include <jni.h>
// 导入FMOD的头文件
#include <fmod.hpp>
#include <string>
// 导入log库
#include <android/log.h>

extern "C" {

// log宏
#define TAG "NDK"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)

// 创建变声模式宏
#undef com_whine_MainActivity_MODE_NORMAL
#define com_whine_MainActivity_MODE_NORMAL 0L
#undef com_whine_MainActivity_MODE_LUOLI
#define com_whine_MainActivity_MODE_LUOLI 1L
#undef com_whine_MainActivity_MODE_DASHU
#define com_whine_MainActivity_MODE_DASHU 2L
#undef com_whine_MainActivity_MODE_JINGSONG
#define com_whine_MainActivity_MODE_JINGSONG 3L
#undef com_whine_MainActivity_MODE_GAOGUAI
#define com_whine_MainActivity_MODE_GAOGUAI 4L
#undef com_whine_MainActivity_MODE_KONGLING
#define com_whine_MainActivity_MODE_KONGLING 5L
JNIEXPORT void JNICALL Java_com_whine_MainActivity_voiceChangeNative
        (JNIEnv *, jobject, jint, jstring);
}
  1. 编写native层实现文件

1)转换Java层传递的声音路径jstring -> char *

const char *pathChar = env->GetStringUTFChars(path, NULL);

2)定义和初始化音效引擎系统

// 定义音效引擎系统 指针
System *system = 0;
// 声音
Sound *sound = 0;
// 音轨
Channel *channel = 0;
// 数字信号处理  指针
DSP *dsp = 0;
// 创建系统
System_Create(&system);
// 初始化系统 参数1:最大音轨数,参数2:系统初始化标记,参数3:额外数据
system->init(32, FMOD_INIT_NORMAL, 0);
// 创建声音  参数1:路径,  参数2:声音初始化标记, 参数3:额外数据, 参数4:声音指针
system->createSound(pathChar, FMOD_DEFAULT, 0, &sound);
// 播放声音,并赋值通道 参数1:声音,参数2:分组音轨,参数3:控制,参数4:通道
system->playSound(sound, 0, false, &channel);

3)判断变声模式,调节音效

switch (mode_normal) {
    case com_whine_MainActivity_MODE_NORMAL:
        resultChar = "原生,播放完毕!";
        break;
    case com_whine_MainActivity_MODE_LUOLI:
        resultChar = "萝莉,播放完毕!";
        // 音调高 -- 萝莉 2.0
        // 1.创建DSP类型的Pitch 音调条件
        system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
        // 2.设置Pitch音调调节2.0
        dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 3.0f);
        // 3.添加音效进去 第一个音轨
        channel->addDSP(0, dsp);
        break;
    case com_whine_MainActivity_MODE_DASHU:
        resultChar = "大叔,播放完毕!";
        // 音调低 -- 大叔 0.7
        // 1.创建DSP类型的Pitch 音调条件
        system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
        // 2.设置Pitch音调调节0.7
        dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7f);
        // 3.添加音效进去 第一个音轨
        channel->addDSP(0, dsp);
        break;
    case com_whine_MainActivity_MODE_GAOGUAI:
        resultChar = "搞怪小黄人,播放完毕!";
        // 小黄人声音 频率快
        // 1.从音轨拿 当前 频率
        float frequency;
        channel->getFrequency(&frequency);
        // 2.加快频率加快  小黄人的声音
        channel->setFrequency(frequency * 1.5f);
        break;
    case com_whine_MainActivity_MODE_JINGSONG:
        resultChar = "惊悚,播放完毕!";
        // 惊悚音效:特点:很多声音的拼接
        // TODO 1.音调低
        // 音调低 -- 大叔 0.7
        // 1.创建DSP类型的Pitch
        system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
        // 2.设置Pitch音调调节0.7
        dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7f);
        // 3.添加音效进去 第一个音轨
        channel->addDSP(0, dsp);

        // TODO 2.回声
        // 回音 ECHO
        // 1.创建DSP类型的ECHO
        system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
        // 2.设置回音 延时 to 5000.  Default = 500.
        dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 200);
        // 3.设置回音 衰减度 Default = 50   0 完全衰减了
        dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 10);
        // 4.添加音效进去 第二个音轨
        channel->addDSP(1, dsp);

        // TODO 3.颤抖
        // Tremolo 颤抖音
        // 1.创建DSP类型的Tremolo
        system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
        // 2.设置颤抖 正常 5,非常颤抖 20
        dsp->setParameterFloat(FMOD_DSP_TREMOLO_FREQUENCY, 20);
        // 3.
        dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.8f);
        // 4.第三个音轨
        channel->addDSP(2, dsp);
        break;
    case com_whine_MainActivity_MODE_KONGLING:
        resultChar = "空灵,播放完毕!";
        // 回音 ECHO
        // 1.创建DSP类型的ECHO 音调条件
        system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
        // 2.设置回音 延时 to 5000.  Default = 500.
        dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 200);
        // 3.设置回音 衰减度 Default = 50   0 完全衰减了
        dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 10);
        // 4.添加音效进去 第一个音轨
        channel->addDSP(0, dsp);
        break;
}

4)检测声音是否播放完毕

bool isPlay = true;
while (isPlay) {
    // 如果真的播放完成了,音轨是知道的,内部会修改isPlayer=false
    channel->isPlaying(&isPlay);
    // 休眠100mS
    usleep(100 * 1000);
}

5)回收C层资源

sound->release();
system->close();
system->release();
env->ReleaseStringUTFChars(path, pathChar);

6)播放完毕,通知java层(调用Java层的播放完成方法)

jclass jClazz = env->GetObjectClass(thiz);
jmethodID methodId = env->GetMethodID(jClazz, "playerEnd", "(Ljava/lang/String;)V");
jstring value = env->NewStringUTF(resultChar);
env->CallVoidMethod(thiz, methodId, value);

7)实现文件完整代码

#include <unistd.h>
#include "whine.h"

using namespace FMOD; // fmod的命名空间

extern "C"
JNIEXPORT void JNICALL
Java_com_whine_MainActivity_voiceChangeNative(JNIEnv *env, jobject thiz, jint mode_normal,
                                              jstring path) {
    // 定义一个结果字符串返回
    char *resultChar = "播放完毕!";
    // 转换路径jstring -> char *
    const char *pathChar = env->GetStringUTFChars(path, NULL);
    // 定义音效引擎系统 指针
    System *system = 0;
    // 声音
    Sound *sound = 0;
    // 音轨
    Channel *channel = 0;
    // 数字信号处理  指针
    DSP *dsp = 0;
    // 创建系统
    System_Create(&system);
    // 初始化系统 参数1:最大音轨数,参数2:系统初始化标记,参数3:额外数据
    system->init(32, FMOD_INIT_NORMAL, 0);
    // 创建声音  参数1:路径,  参数2:声音初始化标记, 参数3:额外数据, 参数4:声音指针
    system->createSound(pathChar, FMOD_DEFAULT, 0, &sound);
    // 播放声音,并赋值通道 参数1:声音,参数2:分组音轨,参数3:控制,参数4:通道
    system->playSound(sound, 0, false, &channel);
    LOGI("init done");
    // 判断模式,增加特效
    switch (mode_normal) {
        case com_whine_MainActivity_MODE_NORMAL:
            resultChar = "原生,播放完毕!";
            break;
        case com_whine_MainActivity_MODE_LUOLI:
            resultChar = "萝莉,播放完毕!";
            // 音调高 -- 萝莉 2.0
            // 1.创建DSP类型的Pitch 音调条件
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 2.设置Pitch音调调节2.0
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 3.0f);
            // 3.添加音效进去 第一个音轨
            channel->addDSP(0, dsp);
            break;
        case com_whine_MainActivity_MODE_DASHU:
            resultChar = "大叔,播放完毕!";
            // 音调低 -- 大叔 0.7
            // 1.创建DSP类型的Pitch 音调条件
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 2.设置Pitch音调调节0.7
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7f);
            // 3.添加音效进去 第一个音轨
            channel->addDSP(0, dsp);
            break;
        case com_whine_MainActivity_MODE_GAOGUAI:
            resultChar = "搞怪小黄人,播放完毕!";
            // 小黄人声音 频率快
            // 1.从音轨拿 当前 频率
            float frequency;
            channel->getFrequency(&frequency);
            // 2.加快频率加快  小黄人的声音
            channel->setFrequency(frequency * 1.5f);
            break;
        case com_whine_MainActivity_MODE_JINGSONG:
            resultChar = "惊悚,播放完毕!";
            // 惊悚音效:特点:很多声音的拼接
            // TODO 1.音调低
            // 音调低 -- 大叔 0.7
            // 1.创建DSP类型的Pitch
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 2.设置Pitch音调调节0.7
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7f);
            // 3.添加音效进去 第一个音轨
            channel->addDSP(0, dsp);

            // TODO 2.回声
            // 回音 ECHO
            // 1.创建DSP类型的ECHO
            system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
            // 2.设置回音 延时 to 5000.  Default = 500.
            dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 200);
            // 3.设置回音 衰减度 Default = 50   0 完全衰减了
            dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 10);
            // 4.添加音效进去 第二个音轨
            channel->addDSP(1, dsp);

            // TODO 3.颤抖
            // Tremolo 颤抖音
            // 1.创建DSP类型的Tremolo
            system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
            // 2.设置颤抖 正常 5,非常颤抖 20
            dsp->setParameterFloat(FMOD_DSP_TREMOLO_FREQUENCY, 20);
            // 3.
            dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.8f);
            // 4.第三个音轨
            channel->addDSP(2, dsp);
            break;
        case com_whine_MainActivity_MODE_KONGLING:
            resultChar = "空灵,播放完毕!";
            // 回音 ECHO
            // 1.创建DSP类型的ECHO 音调条件
            system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
            // 2.设置回音 延时 to 5000.  Default = 500.
            dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 200);
            // 3.设置回音 衰减度 Default = 50   0 完全衰减了
            dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 10);
            // 4.添加音效进去 第一个音轨
            channel->addDSP(0, dsp);
            break;
    }
    // 等待播放完毕 再回收
    bool isPlay = true;
    while (isPlay) {
        // 如果真的播放完成了,音轨是知道的,内部会修改isPlayer=false
        channel->isPlaying(&isPlay);
        // 休眠100mS
        usleep(100 * 1000);
    }
    LOGI("play done");
    // 回收资源
    sound->release();
    system->close();
    system->release();
    env->ReleaseStringUTFChars(path, pathChar);
    // 通知java层
    jclass jClazz = env->GetObjectClass(thiz);
    jmethodID methodId = env->GetMethodID(jClazz, "playerEnd", "(Ljava/lang/String;)V");
    jstring value = env->NewStringUTF(resultChar);
    env->CallVoidMethod(thiz, methodId, value);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sziitjin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值