JNI技术之QQ语言变声实现

assets目录下放正常声音的mp4文件

cpp目录下新建inc文件夹放fmod的头文件

main目录下新建jniLibs文件夹放各个CPU架构的so库

app目录下新建lib文件夹放fmod的jar包

界面效果activity_main.xml

bash 复制代码
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="对应声音的文字"
        android:textColor="#f00"
        android:textSize="20dp"
        android:layout_marginTop="40dp"
        />

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

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

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical">

                <ImageView
                    android:id="@+id/btn_normal"
                    style="@style/AudioImgStyle"
                    android:onClick="onFix"
                    android:src="@drawable/record" />

                <TextView
                    style="@style/AudioTextStyle"
                    android:text="原声" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical">

                <ImageView
                    android:id="@+id/btn_luoli"
                    style="@style/AudioImgStyle"
                    android:onClick="onFix"
                    android:src="@drawable/luoli" />

                <TextView
                    style="@style/AudioTextStyle"
                    android:text="萝莉" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical">

                <ImageView
                    android:id="@+id/btn_dashu"
                    style="@style/AudioImgStyle"
                    android:onClick="onFix"
                    android:src="@drawable/dashu" />

                <TextView
                    style="@style/AudioTextStyle"
                    android:text="大叔" />
            </LinearLayout>
        </LinearLayout>

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

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical">

                <ImageView
                    android:id="@+id/btn_jingsong"
                    style="@style/AudioImgStyle"
                    android:onClick="onFix"
                    android:src="@drawable/jingsong" />

                <TextView
                    style="@style/AudioTextStyle"
                    android:text="惊悚" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical">

                <ImageView
                    android:id="@+id/btn_gaoguai"
                    style="@style/AudioImgStyle"
                    android:onClick="onFix"
                    android:src="@drawable/gaoguai" />

                <TextView
                    style="@style/AudioTextStyle"
                    android:text="搞怪" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical">

                <ImageView
                    android:id="@+id/btn_kongling"
                    style="@style/AudioImgStyle"
                    android:onClick="onFix"
                    android:src="@drawable/kongling" />

                <TextView
                    style="@style/AudioTextStyle"
                    android:text="空灵" />
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>

MainActivity.java

java 复制代码
package com.example.as_jni_project;

import androidx.appcompat.app.AppCompatActivity;

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

import org.fmod.FMOD;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity {

    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 audioFilePath; // 真实文件路径

    private String path;

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

    private void copyAssetToFiles(String assetName) {
        try {
            InputStream in = getAssets().open(assetName);
            File outFile = new File(getFilesDir(), assetName);
            audioFilePath = outFile.getAbsolutePath();

            if (!outFile.exists()) {
                OutputStream out = new FileOutputStream(outFile);
                byte[] buffer = new byte[1024];
                int read;
                while ((read = in.read(buffer)) != -1) {
                    out.write(buffer, 0, read);
                }
                out.close();
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        copyAssetToFiles("xxx.mp3");

        FMOD.init(this);

    }

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

    public void onFix(View view) {
        Log.d("cyr","打印一下audioFilePath----"+audioFilePath);
        int id = view.getId();
        if (id == R.id.btn_normal) {
            voiceChangeNative(MODE_NORMAL, audioFilePath);
        } else if (id == R.id.btn_luoli) {
            voiceChangeNative(MODE_LUOLI, audioFilePath);
        } else if (id == R.id.btn_dashu) {
            voiceChangeNative(MODE_DASHU, audioFilePath);
        } else if (id == R.id.btn_jingsong) {
            voiceChangeNative(MODE_JINGSONG, audioFilePath);
        } else if (id == R.id.btn_gaoguai) {
            voiceChangeNative(MODE_GAOGUAI, audioFilePath);
        } else if (id == R.id.btn_kongling) {
            voiceChangeNative(MODE_KONGLING, audioFilePath);
        }
    }

    public void playerEnd(String mes){
        Toast.makeText(this,mes,Toast.LENGTH_LONG).show();
    }

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


}

javah com.xxx.xxx.MainActivity 根据MainActivity生成的头文件:

c 复制代码
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_derry_derry_voicechange_MainActivity */

// xxx.h ---- xxx.c     早期
// xxx.hpp ---- xxx.cpp
// xxx.h ----  xxx.cpp  兼容的,可以的

// xxx.hpp 是头文件而已

#include <fmod.hpp> // TODO 最后一步 FMOD的头文件,必须导入,才能使用功能

#include <string>
//定义相关的宏
#ifndef _Included_com_example_as_jni_project_MainActivity
#define _Included_com_example_as_jni_project_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
#undef com_example_as_jni_project_MainActivity_MODE_NORMAL
#define com_example_as_jni_project_MainActivity_MODE_NORMAL 0L
#undef com_example_as_jni_project_MainActivity_MODE_LUOLI
#define com_example_as_jni_project_MainActivity_MODE_LUOLI 1L
#undef com_example_as_jni_project_MainActivity_MODE_DASHU
#define com_example_as_jni_project_MainActivity_MODE_DASHU 2L
#undef com_example_as_jni_project_MainActivity_MODE_JINGSONG
#define com_example_as_jni_project_MainActivity_MODE_JINGSONG 3L
#undef com_example_as_jni_project_MainActivity_MODE_GAOGUAI
#define com_example_as_jni_project_MainActivity_MODE_GAOGUAI 4L
#undef com_example_as_jni_project_MainActivity_MODE_KONGLING
#define com_example_as_jni_project_MainActivity_MODE_KONGLING 5L
/*
 * Class:     com_derry_derry_voicechange_MainActivity
 * Method:    voiceChangeNative
 * Signature: (ILjava/lang/String;)V
 */
//方法声明
JNIEXPORT void JNICALL Java_com_example_as_1jni_1project_MainActivity_voiceChangeNative
  (JNIEnv *, jobject, jint, jstring);

#ifdef __cplusplus
}
#endif
#endif

头文件对应的实现native-lib.cpp:

cpp 复制代码
#include <unistd.h>
#include "com.example.as_jni_project.MainActivity.h"

using namespace FMOD;//fmod的命名空间
extern "C"
JNIEXPORT void JNICALL
//方法实现
Java_com_example_as_1jni_1project_MainActivity_voiceChangeNative(JNIEnv *env, jobject thiz,
                                                                 jint mode, jstring path) {
    char * content_ = "默认 播放完毕";
    //C认识的字符串
    const char * path_ = env->GetStringUTFChars(path,NULL);

    //音效引擎系统指针
    System *system = 0;

    //声音 指针
    Sound * sound = 0;

    //通道,音轨,声音在上面跑
    Channel* channel = 0;

    //DSP 数字信号处理 指针
    DSP * dsp = 0;

    //第一步 创建系统
    System_Create(&system);

    //第二步 系统的初始化 参数1:最大音轨数 参数2:系统初始化标记 参数3:额外数据
    system->init(32,FMOD_INIT_NORMAL,0);
    //第三步 创建声音 参数1:路径 参数2:声音初始化标记 参数3:额外数据 参数4:声音指针
    system->createSound(path_,FMOD_DEFAULT,0,&sound);
    //第四步 播放声音 音轨 声音
    //参数1:声音 参数2:分组音频 参数3:控制 参数4:通道
    system->playSound(sound,0, false,&channel);

    switch (mode) {
        case com_example_as_jni_project_MainActivity_MODE_NORMAL: // 原生
            content_ = "原生 播放完毕";
            break;
        case com_example_as_jni_project_MainActivity_MODE_LUOLI: // 萝莉
            content_ = "萝莉 播放完毕";

            // 音调高 -- 萝莉 2.0
            // 1.创建DSP类型的Pitch 音调条件
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 2.设置Pitch音调调节2.0
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.0f);
            // 3.添加音效进去 音轨
            channel->addDSP(0, dsp);
            break;
        case com_example_as_jni_project_MainActivity_MODE_DASHU: // 大叔
            content_ = "大叔 播放完毕";

            // 音调低 -- 大叔 0.7
            // 1.创建DSP类型的Pitch 音调条件
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 2.设置Pitch音调调节2.0
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7f);
            // 3.添加音效进去 音轨
            channel->addDSP(0, dsp);
            break;
        case com_example_as_jni_project_MainActivity_MODE_GAOGUAI: // 搞怪
            content_ = "搞怪 小黄人 播放完毕";

            // 小黄人声音 频率快

            // 从音轨拿 当前 频率
            float mFrequency;
            channel->getFrequency(&mFrequency);

            // 修改频率
            channel->setFrequency(mFrequency * 1.5f); // 频率加快  小黄人的声音
            break;
        case com_example_as_jni_project_MainActivity_MODE_JINGSONG: // 惊悚
            content_ = "惊悚 播放完毕";

            // 惊悚音效:特点: 很多声音的拼接

            // TODO 音调低
            // 音调低 -- 大叔 0.7
            // 1.创建DSP类型的Pitch 音调条件
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 2.设置Pitch音调调节2.0
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7f);
            // 3.添加音效进去 音轨
            channel->addDSP(0, dsp); // 第一个音轨

            // TODO 搞点回声
            // 回音 ECHO
            system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
            dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 200); // 回音 延时    to 5000.  Default = 500.
            dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 10); // 回音 衰减度 Default = 50   0 完全衰减了
            channel->addDSP(1,dsp); // 第二个音轨

            // TODO 颤抖
            // Tremolo 颤抖音 正常5    非常颤抖  20
            system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
            dsp->setParameterFloat(FMOD_DSP_TREMOLO_FREQUENCY, 20); // 非常颤抖
            dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.8f); // ???
            channel->addDSP(2, dsp); // 第三个音轨

            // 调音师:才能跳出来  同学们自己去调
            break;
        case com_example_as_jni_project_MainActivity_MODE_KONGLING: // 空灵  学校广播
            content_ = "空灵 播放完毕";

            // 回音 ECHO
            system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
            dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 200); // 回音 延时    to 5000.  Default = 500.
            dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 10); // 回音 衰减度 Default = 50   0 完全衰减了
            channel->addDSP(0,dsp);
            break;

    }

    //等待播放完毕再回收
    bool isPlayer = true;
    while (isPlayer){
        channel->isPlaying(&isPlayer);// 如果真的播放完成了,音轨是知道的,内部会修改isPlayer=false
        usleep(1000*1000);
    }

    //记得回收
    sound->release();
    system->close();
    system->release();
    env->ReleaseStringUTFChars(path,path_);

    //告知Java播放完毕
    jclass mainCls = env->GetObjectClass(thiz);
    jmethodID endMethod = env->GetMethodID(mainCls,"playerEnd","(Ljava/lang/String;)V");
    jstring value = env->NewStringUTF(content_);
    env->CallVoidMethod(thiz,endMethod,value);








}
相关推荐
我命由我1234521 小时前
Android 开发 - UriMatcher(一个 URI 分类器)
android·java·java-ee·kotlin·android studio·android-studio·android runtime
我命由我123451 天前
Android 多进程开发 - FileDescriptor、Uri、AIDL 接口定义不能抛出异常
android·java·java-ee·kotlin·android studio·android-studio·android runtime
JMchen1236 天前
企业级图表组件库完整实现
android·java·经验分享·笔记·canvas·android-studio
tangshuai967 天前
Android Studio 安装指南(Ubuntu 22.04)
ubuntu·android-studio
我命由我1234521 天前
在 Android Studio 中,新建 AIDL 文件按钮是灰色
android·ide·android studio·安卓·android jetpack·android-studio·android runtime
我命由我1234521 天前
Android 多进程开发 - AIDL 回调、RemoteCallbackList、AIDL 安全校验
android·java·安全·android studio·安卓·android-studio·android runtime
我命由我123451 个月前
Android多进程开发 - AIDL 参数方向、AIDL 传递自定义对象、AIDL 传递自定义对象(参数方向)
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
我命由我123451 个月前
Android多进程开发 - AIDL 最简单的实现、传递数据大小限制
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
我命由我123451 个月前
Android Jetpack Compose - Switch(切换)、Slider(滑块)、RangeSlider(范围滑块)
android·java·java-ee·kotlin·android jetpack·android-studio·android runtime