0. Official documents
It is recommended that students with strong self-study ability read the official documents directly
- FMOD SDK Android platform access document description
- If you are not familiar with NDK, check out the NDK: Google official NDK Documentation (to climb the wall, bring your own ladder 🪜)
- CMAKELIST syntax: links
1. Download
1.1 download
Log in to the official FMOD website, open the download address, select the corresponding platform and version, process: FMOD Studio Suit -> FMOD Engine select the version, platform select Android
1.2 Directory Level Brief
Unzip and go to fmodStudioAPI {version number}/ API /core/ directory
- Example: provides two kinds of NDK compilation methods cmake, NDKBuild various functions Demo
- Inc: Secondary CPP source files provided by FMOD
- Lib: fmod.jar and the dynamic SO library for different ABI architectures, import as needed
If you want to see what each Demo does, you can open the app/ SRC /main/ CPP /{project name}. CPP file and check the DOC description at the top of the file
2. Import voice changing Demoeffects
Dem path: / fmodstudioapi20104android/API/core/examples/androidstudio/cmake/effects
Personally, starting from the official Demo is the best way to get started.
Be careful not to pull out the Demo directory separately, as it contains some library files that are used in external directories.
This Demo is very simple, showing roughly four kinds of flange effects, lowpass, Highpass, Echo, etc.
At the same time, you can learn how to compile NDK in Cmake mode in Demo. If problems occur in configuring NDK in the future, you can refer to Demo to modify the NDK.
3. Create a project
3.1 Creating a Project
- Select the template when creating a new project
Native C++
3.2 Import relevant SO library
Open the unzipped directory: fmodstudioapi20104android/API/core /
- will
inc
andlib
Import the corresponding directory as shown below:
- The configuration selects the supported ABI schema
Open the build.gradle file in the app directory and configure the NDK architecture
defaultConfig { ... NDK {abiFilters "x86", "armeabi-v7A "," arm64-v8A" In general, these three ABI architectures are sufficient to represent all Android models currently on the market}}Copy the code
- configuration
CMakeLists.txt
file
To facilitate configuration, I moved the file to the following directory hierarchy:
Of course, after moving, you need to modify the path of CMakeLists in the build.gradle at the project level:
android{ ... // Specify the cmakelists. TXT path and version number. The default path is the same as build.gradle. ExternalNativeBuild {cmake {path "cmakelists. TXT" version "3.10.2"}}}Copy the code
Detailed configuration of cmakelists.txt
The configuration of CMakeLists is complicated, so it is suggested to learn the basic grammar first
Pay particular attention to the project path where the file is located, which affects the path in the configuration
After the configuration is complete, run it first to see if there is any problem before proceeding to other steps
Cmake_minimum_required (VERSION 3.4.1) # ${CMAKE_SOURCE_DIR} ## ${ANDROID_ABI} This refers to the abi set(FMOD_LIB_DIR) supported in build.gragle My_source file(GLOB my_source SRC /main/ CPP /*.cpp SRC /main/ CPP /*.c CPP /main/ CPP /*/*.c) # add the name of the path variable that Defines the bmDEFINES the name of the site the # location of the NDK library. log-lib # Specifies the name of the NDK library that # CMake needs to locate. log) # add libfmod. So add_library(libfmod SHARED IMPORTED) # add libfmod IMPORTED_LOCATION ${FMOD_LIB_DIR}/ IMPORTED_LOCATION ${FMOD_LIB_DIR}/libfmod.so) Set_target_properties (libfmodL PROPERTIES IMPORTED_LOCATION ${FMOD_LIB_DIR}/ libfmodl.so Add_library (myfmod # set the name of this custom library to myfmod SHARED # set the property to "SHARED" ${my_source} # Specify one or more files, Provide relative paths to source files (C/C++ files) # Specify the libraries that need to be dynamically linked, and specify the resulting so library. Target_link_libraries (myfmod libfmod libfmodL ${log-lib})Copy the code
By the way: libfmod. So and libfomdL. So differ only in that the latter provides the logging function
4. FMOD basis
4.1 the initialization
// Load the library file, where the name depends on you
System.loadLibrary("libfmod")
System.loadLibrary("libfmodL")
System.loadLibrary("myfmod")
// It is recommended to call the FMOD initialization method in onCreate of the main Activity
org.fmod.FMOD.init(this);
Copy the code
4.2 DSP Effector
The FMOD SDK provides a series of DSP effects that allow developers to quickly adjust audio parameters. See the file fmod_dsp_effects.h in demo for details
typedef enum
{
FMOD_DSP_TYPE_UNKNOWN,
FMOD_DSP_TYPE_MIXER,
FMOD_DSP_TYPE_OSCILLATOR,
FMOD_DSP_TYPE_LOWPASS,
FMOD_DSP_TYPE_ITLOWPASS,
FMOD_DSP_TYPE_HIGHPASS,
FMOD_DSP_TYPE_ECHO,
FMOD_DSP_TYPE_FADER,
FMOD_DSP_TYPE_FLANGE,
FMOD_DSP_TYPE_DISTORTION,
FMOD_DSP_TYPE_NORMALIZE,
FMOD_DSP_TYPE_LIMITER,
FMOD_DSP_TYPE_PARAMEQ,
FMOD_DSP_TYPE_PITCHSHIFT,
FMOD_DSP_TYPE_CHORUS,
FMOD_DSP_TYPE_VSTPLUGIN,
FMOD_DSP_TYPE_WINAMPPLUGIN,
FMOD_DSP_TYPE_ITECHO,
FMOD_DSP_TYPE_COMPRESSOR,
FMOD_DSP_TYPE_SFXREVERB,
FMOD_DSP_TYPE_LOWPASS_SIMPLE,
FMOD_DSP_TYPE_DELAY,
FMOD_DSP_TYPE_TREMOLO,
FMOD_DSP_TYPE_LADSPAPLUGIN,
FMOD_DSP_TYPE_SEND,
FMOD_DSP_TYPE_RETURN,
FMOD_DSP_TYPE_HIGHPASS_SIMPLE,
FMOD_DSP_TYPE_PAN,
FMOD_DSP_TYPE_THREE_EQ,
FMOD_DSP_TYPE_FFT,
FMOD_DSP_TYPE_LOUDNESS_METER,
FMOD_DSP_TYPE_ENVELOPEFOLLOWER,
FMOD_DSP_TYPE_CONVOLUTIONREVERB,
FMOD_DSP_TYPE_CHANNELMIX,
FMOD_DSP_TYPE_TRANSCEIVER,
FMOD_DSP_TYPE_OBJECTPAN,
FMOD_DSP_TYPE_MULTIBAND_EQ,
FMOD_DSP_TYPE_MAX,
FMOD_DSP_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */
} FMOD_DSP_TYPE;
Copy the code
The above are the supported DSP effectors. For example, when you want to perform echo and pitch processing on audio, the core code is as follows. The principle is very simple, and the DSP effector can be directly superimposed on the corresponding audio playback channel
System *system; Channel *channel; Sound *sound; / /... // Echo echo echo; system->createDSPByType(FMOD_DSP_TYPE_ECHO, &echoDsp); Echo ->setParameterFloat(FMOD_DSP_ECHO_DELAY, echoDelay); Echo ->setParameterFloat(fmod_dsp_echo feedback, echoFeedback); Echo ->setParameterFloat(FMOD_DSP_ECHO_DRYLEVEL, echoDryLevel); Echo ->setParameterFloat(FMOD_DSP_ECHO_WETLEVEL, echoWetLevel); echo ->setParameterFloat(fmod_dSP_echo wetlevel, echoWetLevel); Channel ->addDSP(0, echoDsp); channel->addDSP(0, echoDsp); // DSP *pitchDsp; system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &pitchDsp); PitchDsp ->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, Pitch); PitchDsp ->setParameterFloat(FMOD_DSP_PITCHSHIFT_FFTSIZE, fftSize); channel->addDSP(0, pitchDsp); / /... Omit the playback processCopy the code
The same can be done for other DSP effectors.
4.3 Multi-audio fusion
Multi-audio fusion is very simple, play corresponding audio files on multiple sound track channels at the same time
system->playSound(sound1, 0, false, &channel1); system->playSound(sound2, 0, false, &channel2); system->playSound(sound3, 0, false, &channel3); .Copy the code
4.4 Save audio Files after audio processing
The core code
system->setOutput(FMOD_OUTPUTTYPE_WAVWRITER); / / save the file format is WAV system - > init (32, FMOD_INIT_NORMAL | FMOD_INIT_PROFILE_ENABLE, output path {}); // Save the core code of the compositing effectCopy the code
After the audio is processed, the audio file in WAV format will be output in the specified path
4.5 Core C code written during the test for reference only
Note: do not ridicule the author to write C code is chaotic, the author is also to drive a duck to write temporarily, everyone gather together to live see….
#include <jni.h>
#include <string>
#include "aisound.h"
#include <fmod.hpp>
#include <android/log.h>
#include <unistd.h>
#include <cstring>
#include <fmod_errors.h>
#define TAG "FMOD"
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,TAG,FORMAT,##__VA_ARGS__);
#define LOGD(FORMAT, ...) __android_log_print(ANDROID_LOG_DEBUG,TAG,FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,TAG,FORMAT,##__VA_ARGS__);
using namespace FMOD;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * hybrid broadcast voice effect * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** * auxiliary methods */
void printFmodResult(FMOD_RESULT result) {
LOGE("%s".FMOD_ErrorString(result))
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * fmod parameter adjustment Start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** * applyXXXEffect * @param env JNI tool-assisted environment variable * @param sound_effect_bean Jobject passed by the Java layer to the JNI layer. SoundEffectBean * @Param adjustChannel Fmod::System instance * @Param adjustChannel
void applyChorusSoundEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *adjustChannel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
/ / the harmony
jmethodID getChorusEffect = env->GetMethodID(cls, "getChorusEffect"."()Lio/microshow/aisound/soundeffect/type/ChorusEffect;");
jobject chorusEffectObject = env->CallObjectMethod(sound_effect_bean, getChorusEffect);
if(chorusEffectObject ! =NULL) {
jclass chorusEffectClass = env->GetObjectClass(chorusEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(chorusEffectObject, env->GetMethodID(chorusEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat chorusDepth = env->CallFloatMethod(chorusEffectObject, env->GetMethodID(chorusEffectClass, "getChorusDepth"."()F"));
jfloat chorusMix = env->CallFloatMethod(chorusEffectObject, env->GetMethodID(chorusEffectClass, "getChorusMix"."()F"));
jfloat chorusRate = env->CallFloatMethod(chorusEffectObject, env->GetMethodID(chorusEffectClass, "getChorusRate"."()F"));
DSP *chorusDsp;
system->createDSPByType(FMOD_DSP_TYPE_CHORUS, &chorusDsp); /* This unit produces a chorus effect on the sound. */
chorusDsp->setParameterFloat(FMOD_DSP_CHORUS_DEPTH, chorusDepth); /* (Type:float) - Chorus Modulation depth.0.0 to 100.0. Default = 3.0. */
chorusDsp->setParameterFloat(FMOD_DSP_CHORUS_MIX, chorusMix); /* (Type:float) -volume of original signal to pass to output. 0.0 to 100.0. Default = 50.0
chorusDsp->setParameterFloat(FMOD_DSP_CHORUS_RATE, chorusRate); /* (Type:float) - Chorus modulation rate in HZ.0.0 to 20.0.default = 0.8 Hz. */
adjustChannel->addDSP(0, chorusDsp);
LOGI("%s%.2f"."chorusDepth:", chorusDepth)
LOGI("%s%.2f"."chorusMix:", chorusMix)
LOGI("%s%.2f"."chorusRate:", chorusRate)
}
}
void applyEchoEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getEchoEffect = env->GetMethodID(cls, "getEchoEffect"."()Lio/microshow/aisound/soundeffect/type/EchoEffect;");
jobject echoEffectObject = env->CallObjectMethod(sound_effect_bean, getEchoEffect);
if(echoEffectObject ! =NULL) {
jclass echoEffectClass = env->GetObjectClass(echoEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(echoEffectObject, env->GetMethodID(echoEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat echoDelay = env->CallFloatMethod(echoEffectObject, env->GetMethodID(echoEffectClass, "getEchoDelay"."()F"));
jfloat echoFeedback = env->CallFloatMethod(echoEffectObject, env->GetMethodID(echoEffectClass, "getEchoFeedback"."()F"));
jfloat echoDryLevel = env->CallFloatMethod(echoEffectObject, env->GetMethodID(echoEffectClass, "getEchoDryLevel"."()F"));
jfloat echoWetLevel = env->CallFloatMethod(echoEffectObject, env->GetMethodID(echoEffectClass, "getEchoWetLevel"."()F"));
DSP *echoDsp;
system->createDSPByType(FMOD_DSP_TYPE_ECHO, &echoDsp); // Control the echo
echoDsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, echoDelay); // Delay, in ms, 10~5000, default 500
echoDsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, echoFeedback); // echo attenuation delay, 0~ 100,100 no attenuation, 0 all attenuation, default 50
echoDsp->setParameterFloat(FMOD_DSP_ECHO_DRYLEVEL, echoDryLevel); // Original sound volume, in db, range from -80 to 10, default 0
echoDsp->setParameterFloat(FMOD_DSP_ECHO_WETLEVEL, echoWetLevel); Echo signal volume, in db, range from -80 to 10, default 0
channel->addDSP(0, echoDsp);
LOGI("%s%.2f"."echoDelay:", echoDelay)
LOGI("%s%.2f"."echoFeedback:", echoFeedback)
LOGI("%s%.2f"."echoDryLevel:", echoDryLevel)
LOGI("%s%.2f"."echoWetLevel:", echoWetLevel)
}
}
void applyEqEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getEqEffect = env->GetMethodID(cls, "getEqEffect"."()Lio/microshow/aisound/soundeffect/type/EqEffect;");
jobject eqEffectObject = env->CallObjectMethod(sound_effect_bean, getEqEffect);
if(eqEffectObject ! =NULL) {
jclass eqEffectClass = env->GetObjectClass(eqEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(eqEffectObject, env->GetMethodID(eqEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat lowGain = env->CallFloatMethod(eqEffectObject, env->GetMethodID(eqEffectClass, "getLowGain"."()F"));
jfloat midGain = env->CallFloatMethod(eqEffectObject, env->GetMethodID(eqEffectClass, "getMidGain"."()F"));
jfloat highGain = env->CallFloatMethod(eqEffectObject, env->GetMethodID(eqEffectClass, "getHighGain"."()F"));
jfloat lowToMidCrossover = env->CallFloatMethod(eqEffectObject, env->GetMethodID(eqEffectClass, "getLowToMidCrossover"."()F"));
jfloat midToHighCrossover = env->CallFloatMethod(eqEffectObject, env->GetMethodID(eqEffectClass, "getMidToHighCrossover"."()F"));
jint crossOverSlope = env->CallIntMethod(eqEffectObject, env->GetMethodID(eqEffectClass, "getCrossOverSlope"."()I"));
DSP *eqAdjustDsp;
system->createDSPByType(FMOD_DSP_TYPE_THREE_EQ, &eqAdjustDsp);
// Set the following two dividers to divide the audio into three segments: 0~400 400~4000 4000~
eqAdjustDsp->setParameterFloat(FMOD_DSP_THREE_EQ_LOWCROSSOVER, lowToMidCrossover);// Low crossover frequency, low/medium crossover point meaning, range of 10~22000, default 400. The more low frequencies are retained, the deeper the sound.
eqAdjustDsp->setParameterFloat(FMOD_DSP_THREE_EQ_HIGHCROSSOVER, midToHighCrossover);// Medium/high crossover frequency, medium/high frequency point meaning, range of 10~22000, default 4000. The more high frequencies are retained, the more penetrating the sound is. Set to 10KHZ reverb to send sound soaring into the air, but lower is more natural.
eqAdjustDsp->setParameterInt(FMOD_DSP_THREE_EQ_CROSSOVERSLOPE, crossOverSlope);// There are three options: 1= 12dB /Octave,2=24db/Octave,3=48db/Octave. The default value is 1.
// Set the db values of the three audio segments
eqAdjustDsp->setParameterFloat(FMOD_DSP_THREE_EQ_LOWGAIN, lowGain);// Low frequency DB gain, range -80 to 10, default value 0
eqAdjustDsp->setParameterFloat(FMOD_DSP_THREE_EQ_MIDGAIN, midGain);// Medium frequency DB gain, range -80 to 10, default value 0
eqAdjustDsp->setParameterFloat(FMOD_DSP_THREE_EQ_HIGHGAIN, highGain);// High frequency DB gain, range -80 to 10, default value 0
channel->addDSP(0, eqAdjustDsp);
LOGI("%s%.2f"."lowGain:", lowGain)
LOGI("%s%.2f"."midGain:", midGain)
LOGI("%s%.2f"."highGain:", highGain)
LOGI("%s%.2f"."lowToMidCrossover:", lowToMidCrossover)
LOGI("%s%.2f"."midToHighCrossover:", midToHighCrossover)
LOGI("%s%d"."crossOverSlope:", crossOverSlope)
}
}
void applyFlangeEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getFlangeEffect = env->GetMethodID(cls, "getFlangeEffect"."()Lio/microshow/aisound/soundeffect/type/FlangeEffect;");
jobject flangeEffectObject = env->CallObjectMethod(sound_effect_bean, getFlangeEffect);
if(flangeEffectObject ! =NULL) {
jclass flangeEffectClass = env->GetObjectClass(flangeEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(flangeEffectObject, env->GetMethodID(flangeEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat flangeMix = env->CallFloatMethod(flangeEffectObject, env->GetMethodID(flangeEffectClass, "getFlangeMix"."()F"));
jfloat flangeDepth = env->CallFloatMethod(flangeEffectObject, env->GetMethodID(flangeEffectClass, "getFlangeDepth"."()F"));
jfloat flangeRate = env->CallFloatMethod(flangeEffectObject, env->GetMethodID(flangeEffectClass, "getFlangeRate"."()F"));
DSP *flangeDsp;
system->createDSPByType(FMOD_DSP_TYPE_FLANGE, &flangeDsp);
flangeDsp->setParameterFloat(FMOD_DSP_FLANGE_MIX, flangeMix); // The value ranges from 0 to 100. The default value is 50
flangeDsp->setParameterFloat(FMOD_DSP_FLANGE_DEPTH, flangeDepth); // The value ranges from 0.01 to 1.0. The default value is 1.0
flangeDsp->setParameterFloat(FMOD_DSP_FLANGE_RATE, flangeRate); // The range is 0.0 to 20, in Hz. The default value is 0.1
channel->addDSP(0, flangeDsp);
LOGI("%s%.2f"."flangeMix:", flangeMix)
LOGI("%s%.2f"."flangeDepth:", flangeDepth)
LOGI("%s%.2f"."flangeRate:", flangeRate)
}
}
void applyFrequencyEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channle) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getFrequencyEffect = env->GetMethodID(cls, "getFrequencyEffect"."()Lio/microshow/aisound/soundeffect/type/FrequencyEffect;");
jobject frequencyEffectObject = env->CallObjectMethod(sound_effect_bean, getFrequencyEffect);
if(frequencyEffectObject ! =NULL) {
jclass frequencyEffectClass = env->GetObjectClass(frequencyEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(frequencyEffectObject, env->GetMethodID(frequencyEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat frequency = env->CallFloatMethod(frequencyEffectObject, env->GetMethodID(frequencyEffectClass, "getFrequency"."()F"));
// Speak as fast as possible
float originalFrequency;
channle->getFrequency(&originalFrequency);
channle->setFrequency(originalFrequency * frequency);
LOGI("%s%.2f"."frequencyTimes:", frequency)
}
}
void applyPitchEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getPitchEffect = env->GetMethodID(cls, "getPitchEffect"."()Lio/microshow/aisound/soundeffect/type/PitchEffect;");
jobject pitchEffectObject = env->CallObjectMethod(sound_effect_bean, getPitchEffect);
if(pitchEffectObject ! =NULL) {
jclass pitchEffectClass = env->GetObjectClass(pitchEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(pitchEffectObject, env->GetMethodID(pitchEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat pitch = env->CallFloatMethod(pitchEffectObject, env->GetMethodID(pitchEffectClass, "getPitch"."()F"));
jfloat fftSize = env->CallFloatMethod(pitchEffectObject, env->GetMethodID(pitchEffectClass, "getFftSize"."()F"));
DSP *pitchDsp;
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &pitchDsp);// Change Pitch
pitchDsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, pitch);//1.0 is the standard value. Each 0.5 change in the upper and lower values represents an 8 degree, ranging from 0.5 to 2.0
pitchDsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_FFTSIZE, fftSize);
channel->addDSP(0, pitchDsp);
LOGI("%s%.2f"."pitch:", pitch)
LOGI("%s%.2f"."fftSize:", fftSize)
}
}
void applyTremoloEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getTremoloEffect = env->GetMethodID(cls, "getTremoloEffect"."()Lio/microshow/aisound/soundeffect/type/TremoloEffect;");
jobject tremoloEffectObject = env->CallObjectMethod(sound_effect_bean, getTremoloEffect);
if(tremoloEffectObject ! =NULL) {
jclass tremoloEffectClass = env->GetObjectClass(tremoloEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(tremoloEffectObject, env->GetMethodID(tremoloEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat tremoloDepth = env->CallFloatMethod(tremoloEffectObject, env->GetMethodID(tremoloEffectClass, "getTremoloDepth"."()F"));
jfloat tremoloDuty = env->CallFloatMethod(tremoloEffectObject, env->GetMethodID(tremoloEffectClass, "getTremoloDuty"."()F"));
jfloat tremoloFrequency = env->CallFloatMethod(tremoloEffectObject, env->GetMethodID(tremoloEffectClass, "getTremoloFrequency"."()F"));
jfloat tremoloPhase = env->CallFloatMethod(tremoloEffectObject, env->GetMethodID(tremoloEffectClass, "getTremoloPhase"."()F"));
jfloat tremoloShape = env->CallFloatMethod(tremoloEffectObject, env->GetMethodID(tremoloEffectClass, "getTremoloShape"."()F"));
jfloat tremoloSkew = env->CallFloatMethod(tremoloEffectObject, env->GetMethodID(tremoloEffectClass, "getTremoloSkew"."()F"));
jfloat tremoloSpread = env->CallFloatMethod(tremoloEffectObject, env->GetMethodID(tremoloEffectClass, "getTremoloSpread"."()F"));
jfloat tremoloSquare = env->CallFloatMethod(tremoloEffectObject, env->GetMethodID(tremoloEffectClass, "getTremoloSquare"."()F"));
DSP *tremoloDsp;
system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &tremoloDsp);
//TREMOLO DEPTH(TREMOLO DEPTH) is used to adjust the pitch of the TREMOLO
tremoloDsp->setParameterFloat(FMOD_DSP_TREMOLO_DEPTH, tremoloDepth); /* (Type:float) - Tremolo depth. 0 to 1. Default = 1. */
tremoloDsp->setParameterFloat(FMOD_DSP_TREMOLO_DUTY, tremoloDuty); /* (Type:float) -lfo on-time. 0 to 1.default = 0.5. */
tremoloDsp->setParameterFloat(FMOD_DSP_TREMOLO_FREQUENCY, tremoloFrequency); /* (Type:float) -lFO frequency in HZ.0.1 to 20.default = 5. */
tremoloDsp->setParameterFloat(FMOD_DSP_TREMOLO_PHASE, tremoloPhase); /* (Type:float) - Instantaneous LFO phase. 0 to 1. Default = 0. */
tremoloDsp->setParameterFloat(FMOD_DSP_TREMOLO_SHAPE, tremoloShape); /* (Type:float) - LFO shape morph between triangle and sine. 0 to 1. Default = 0. */
tremoloDsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, tremoloSkew); /* (Type:float) - Time-skewing of LFO cycle. -1 to 1. Default = 0. */
tremoloDsp->setParameterFloat(FMOD_DSP_TREMOLO_SPREAD, tremoloSpread); /* (Type:float) - Rotation / auto-pan effect. -1 to 1. Default = 0. */
tremoloDsp->setParameterFloat(FMOD_DSP_TREMOLO_SQUARE, tremoloSquare); /* (Type:float) - Flatness of the LFO shape. 0 to 1. Default = 0. */
channel->addDSP(0, tremoloDsp);
LOGI("%s%.2f"."tremoloDepth:", tremoloDepth);
LOGI("%s%.2f"."tremoloDuty:", tremoloDuty);
LOGI("%s%.2f"."tremoloFrequency:", tremoloFrequency);
LOGI("%s%.2f"."tremoloPhase:", tremoloPhase);
LOGI("%s%.2f"."tremoloShape:", tremoloShape);
LOGI("%s%.2f"."tremoloSkew:", tremoloSkew);
LOGI("%s%.2f"."tremoloSpread:", tremoloSpread);
LOGI("%s%.2f"."tremoloSquare:", tremoloSquare); }}void applyNormalizeEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getNormalizeEffect = env->GetMethodID(cls, "getNormalizeEffect"."()Lio/microshow/aisound/soundeffect/type/NormalizeEffect;");
jobject normalizeEffectObject = env->CallObjectMethod(sound_effect_bean, getNormalizeEffect);
if(normalizeEffectObject ! =NULL) {
jclass normalizeEffectClass = env->GetObjectClass(normalizeEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(normalizeEffectObject, env->GetMethodID(normalizeEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat fadeTime = env->CallFloatMethod(normalizeEffectObject, env->GetMethodID(normalizeEffectClass, "getFadeTime"."()F"));
jfloat threshold = env->CallFloatMethod(normalizeEffectObject, env->GetMethodID(normalizeEffectClass, "getThreshold"."()F"));
jfloat maxAmp = env->CallFloatMethod(normalizeEffectObject, env->GetMethodID(normalizeEffectClass, "getMaxAmp"."()F"));
DSP *normalizeDsp;
system->createDSPByType(FMOD_DSP_TYPE_NORMALIZE, &normalizeDsp); // Amplify the sound
normalizeDsp->setParameterFloat(FMOD_DSP_NORMALIZE_FADETIME, 5000.0 f); /* (Type:float) - Time to ramp the silence to full in Ms. 0.0 to 20000.0. Default = 5000.0. */
normalizeDsp->setParameterFloat(FMOD_DSP_NORMALIZE_THRESHHOLD,
0.1 f); /* (Type:float) -lower volume range threshold to ignore.0.0 to 1.0.default = 0.1. Raise higher to stop amplification of very quiet signals. */
normalizeDsp->setParameterFloat(FMOD_DSP_NORMALIZE_MAXAMP,
20.0 f); /* (Type:float) - Maximum amplification allowed. 1.0 to 100000.0. Default = 20.0.1.0 = no amplifaction, higher values allow more boost. */
channel->addDSP(0, normalizeDsp);
LOGI("%s%.2f"."fadeTime:", fadeTime);
LOGI("%s%.2f"."threshold:", threshold);
LOGI("%s%.2f"."maxAmp:", maxAmp); }}void applySfxReverbEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getSfxReverbEffect = env->GetMethodID(cls, "getSfxReverbEffect"."()Lio/microshow/aisound/soundeffect/type/SfxReverbEffect;");
jobject sfxReverbEffectObject = env->CallObjectMethod(sound_effect_bean, getSfxReverbEffect);
if(sfxReverbEffectObject ! =NULL) {
jclass sfxReverbEffectClass = env->GetObjectClass(sfxReverbEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat decayTime = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getDecayTime"."()F"));
jfloat earlyDelay = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getEarlyDelay"."()F"));
jfloat lateDelay = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getLateDelay"."()F"));
jfloat hfReference = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getHfReference"."()F"));
jfloat hfDecayRatio = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getHfDecayRatio"."()F"));
jfloat diffusion = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getDiffusion"."()F"));
jfloat density = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getDensity"."()F"));
jfloat lowShelfFrequency = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getLowShelfFrequency"."()F"));
jfloat lowShelfGain = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getLowShelfGain"."()F"));
jfloat highCut = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getHighCut"."()F"));
jfloat earlyLateMix = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getEarlyLateMix"."()F"));
jfloat wetLevel = env->CallFloatMethod(sfxReverbEffectObject, env->GetMethodID(sfxReverbEffectClass, "getWetLevel"."()F"));
DSP *sfxReverbDsp;
system->createDSPByType(FMOD_DSP_TYPE_SFXREVERB, &sfxReverbDsp);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_DECAYTIME, decayTime);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_EARLYDELAY, earlyDelay);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_LATEDELAY, lateDelay);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_HFREFERENCE, hfReference);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_HFDECAYRATIO, hfDecayRatio);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_DIFFUSION, diffusion);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_DENSITY, density);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_LOWSHELFFREQUENCY, lowShelfFrequency);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_LOWSHELFGAIN, lowShelfGain);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_HIGHCUT, highCut);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_EARLYLATEMIX, earlyLateMix);
sfxReverbDsp->setParameterFloat(FMOD_DSP_SFXREVERB_WETLEVEL, wetLevel);
channel->addDSP(0, sfxReverbDsp);
LOGI("%s%.2f"."decayTime:", decayTime);
LOGI("%s%.2f"."earlyDelay:", earlyDelay);
LOGI("%s%.2f"."lateDelay:", lateDelay);
LOGI("%s%.2f"."hfReference:", hfReference);
LOGI("%s%.2f"."hfDecayRatio:", hfDecayRatio);
LOGI("%s%.2f"."Coursing together.", diffusion);
LOGI("%s%.2f"."density:", density);
LOGI("%s%.2f"."lowShelfFrequency:", lowShelfFrequency);
LOGI("%s%.2f"."lowShelfGain:", lowShelfGain);
LOGI("%s%.2f"."highCut:", highCut);
LOGI("%s%.2f"."earlyLateMix:", earlyLateMix);
LOGI("%s%.2f"."wetLevel:", wetLevel); }}void applyDistortionEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getDistortionEffect = env->GetMethodID(cls, "getDistortionEffect"."()Lio/microshow/aisound/soundeffect/type/DistortionEffect;");
jobject distortionEffectObject = env->CallObjectMethod(sound_effect_bean, getDistortionEffect);
if(distortionEffectObject ! =NULL) {
jclass distortionEffectClass = env->GetObjectClass(distortionEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(distortionEffectObject, env->GetMethodID(distortionEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jfloat distortionLevel = env->CallFloatMethod(distortionEffectObject, env->GetMethodID(distortionEffectClass, "getDistortionLevel"."()F"));
DSP *distortionDsp;
system->createDSPByType(FMOD_DSP_TYPE_DISTORTION, &distortionDsp);
distortionDsp->setParameterFloat(FMOD_DSP_DISTORTION_LEVEL, distortionLevel);
channel->addDSP(0, distortionDsp);
LOGI("%s%.2f"."distortionLevel:", distortionLevel); }}void applyOscillatorEffectEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
jclass cls = env->GetObjectClass(sound_effect_bean);
jmethodID getOscillatorEffect = env->GetMethodID(cls, "getOscillatorEffect"."()Lio/microshow/aisound/soundeffect/type/OscillatorEffect;");
jobject oscillatorEffectObject = env->CallObjectMethod(sound_effect_bean, getOscillatorEffect);
if(oscillatorEffectObject ! =NULL) {
jclass oscillatorEffectClass = env->GetObjectClass(oscillatorEffectObject);
// Whether to enable the sound change effect
jboolean isEffectActive = env->CallBooleanMethod(oscillatorEffectObject, env->GetMethodID(oscillatorEffectClass, "isActive"."()Z"));
if(! isEffectActive)return;
// Effect parameters
jint oscillatorType = env->CallIntMethod(oscillatorEffectObject, env->GetMethodID(oscillatorEffectClass, "getOscillatorType"."()I"));
jfloat oscillatorRate = env->CallFloatMethod(oscillatorEffectObject, env->GetMethodID(oscillatorEffectClass, "getOscillatorRate"."()F"));
DSP *oscillatorDsp;
system->createDSPByType(FMOD_DSP_TYPE_OSCILLATOR, &oscillatorDsp);
oscillatorDsp->setParameterInt(FMOD_DSP_OSCILLATOR_TYPE, oscillatorType);
oscillatorDsp->setParameterFloat(FMOD_DSP_OSCILLATOR_RATE, oscillatorRate);
channel->addDSP(0, oscillatorDsp);
LOGI("%s%d"."oscillatorType:", oscillatorType);
LOGI("%s%.2f"."oscillatorRate:", oscillatorRate); }}void applyAllSoundEffect(JNIEnv *env, jobject sound_effect_bean, System *system, Channel *channel) {
applyChorusSoundEffect(env, sound_effect_bean, system, channel);
applyEchoEffect(env, sound_effect_bean, system, channel);
applyEqEffect(env, sound_effect_bean, system, channel);
applyFlangeEffect(env, sound_effect_bean, system, channel);
applyFrequencyEffect(env, sound_effect_bean, system, channel);
applyPitchEffect(env, sound_effect_bean, system, channel);
applyTremoloEffect(env, sound_effect_bean, system, channel);
applyNormalizeEffect(env, sound_effect_bean, system, channel);
applySfxReverbEffect(env, sound_effect_bean, system, channel);
applyDistortionEffect(env, sound_effect_bean, system, channel);
applyOscillatorEffectEffect(env, sound_effect_bean, system, channel);
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * fmod parameters to adjust the End * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
Sound *mainSound, *subSound;
Channel *mainChannel, *subChannel;
bool loopMode;
jobject soundEffect;
extern "C"
JNIEXPORT void JNICALL
Java_io_microshow_aisound_AiSound_playMixSound
(JNIEnv *env, jclass clazz, jstring main_audio, jstring sub_audio, jfloat main_audio_volume, jfloat sub_audio_volume, jobject sound_effect, jboolean isLoop, jobject callBack) {
/ / declare
System *system;
bool isPlaying = true;
bool isPause = false;
// The system object is initialized
System_Create(&system);
system->init(32, FMOD_INIT_NORMAL, NULL);
//C/C++ convert the String from Java to use it
const char *cstr_main_audio = env->GetStringUTFChars(main_audio, NULL);
const char *cstr_sub_audio = env->GetStringUTFChars(sub_audio, NULL);
// The current playback position, in ms
unsigned int playPosition;
// Actual playback duration, in ms
jlong actualPlayTime = 0l;
// Whether to loop
loopMode = isLoop;
// the C++ layer invokes Java layer methods, i.e., upper-level callbacks
jclass cls = env->GetObjectClass(callBack);
jmethodID onStartMethod = env->GetMethodID(cls, "onStart"."()V");
jmethodID onTimeMethod = env->GetMethodID(cls, "onTime"."(JJ)V");
jmethodID onCompleteMethod = env->GetMethodID(cls, "onComplete"."()V");
jmethodID onErrorMethod = env->GetMethodID(cls, "onError"."(Ljava/lang/String;) V");
try {
// Main audio processing
system->createSound(cstr_main_audio, loopMode ? FMOD_LOOP_NORMAL : FMOD_DEFAULT, NULL, &mainSound);
system->playSound(mainSound, 0.false, &mainChannel);
mainChannel->setVolume(main_audio_volume);
// Add a sound change effect
soundEffect = sound_effect;// Record the first sound change parameters
applyAllSoundEffect(env, sound_effect, system, mainChannel);
// Subaudio processing. If an empty string is passed in, no subaudio is added
if (env->GetStringLength(sub_audio) ! =0) {
//LOGE("%s", "background sound ")
system->createSound(cstr_sub_audio, FMOD_LOOP_NORMAL, NULL, &subSound);//subSound is background music, let it play until MainSound is finished playing
system->playSound(subSound, 0.false, &subChannel);
subChannel->setVolume(sub_audio_volume);
}
// Notify the upper layer before playing
env->CallVoidMethod(callBack, onStartMethod);
} catch(...). { jstring data = env->NewStringUTF("Abnormal mixing playback");
env->CallVoidMethod(callBack, onErrorMethod, data);
goto end;
}
// check the current status every 100ms
while (isPlaying) {
usleep(1000 * 100);// Note that the parameter is in microseconds, 1s=10^6 microseconds
mainChannel->isPlaying(&isPlaying);
// Notify the upper layer of the current playback progress, unit ms,
mainChannel->getPaused(&isPause);
if(! isPause) {// Get the relative playback time
mainChannel->getPosition(&playPosition, FMOD_TIMEUNIT_MS);
jlong relativePlayTime = playPosition;// Note that the playback progress refers to the current progress of the original track, not the actual playback time progress
// Manually calculate the actual playing time, in ms
actualPlayTime = actualPlayTime + 100;
// The notification interval is 1s, that is, the number of whole seconds to send the notification
if (actualPlayTime % 1000= =0) {
env->CallVoidMethod(callBack, onTimeMethod, relativePlayTime, actualPlayTime); }}// Determine whether the listening layer changes the loop playback Settings during playback
if(loopMode ! = isLoop) { isLoop =static_cast<jboolean>(loopMode);
mainChannel->setMode(loopMode ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF); }}// Call the callback function after the playback
env->CallVoidMethod(callBack, onCompleteMethod);
goto end;
end:
// Release resource processing
env->ReleaseStringUTFChars(main_audio, cstr_main_audio);
env->ReleaseStringUTFChars(sub_audio, cstr_sub_audio);
mainSound->release(a); subSound->release(a); system->close(a); system->release(a); }extern "C"
JNIEXPORT jint JNICALL
Java_io_microshow_aisound_AiSound_saveMixSound
(JNIEnv *env, jclass clazz, jstring main_audio, jstring sub_audio, jstring output_audio, jfloat main_audio_volume, jfloat sub_audio_volume, jobject sound_effect) {
//GetStringUTFChars converts a JString pointer (pointing to a sequence of Unicode characters within the JVM) to a UTF-8 C string
const char *cstr_main_audio = env->GetStringUTFChars(main_audio, NULL); // Main audio path
const char *cstr_sub_audio = env->GetStringUTFChars(sub_audio, NULL); // Subaudio path
const char *cstr_output_audio = env->GetStringUTFChars(output_audio, NULL); // The output audio path specified by the upper layer
/ / declare
System *system;
Sound *mainSound, *subSound;
Channel *mainChannel, *subChannel;
bool isMainSoundPlaying = true;
jint result = 1;
/ / initialization
System_Create(&system);
// Set the sampling rate to 48000, mono. Note that the SampleRate sampling rate parameter greatly affects audio quality and is recommended to be the same as that used for recording
system->setSoftwareFormat(48000, FMOD_SPEAKERMODE_MONO, 0);
char cDest[200];
strcpy(cDest, cstr_output_audio);
system->setOutput(FMOD_OUTPUTTYPE_WAVWRITER); // Save the file in WAV format
system->init(32, FMOD_INIT_NORMAL | FMOD_INIT_PROFILE_ENABLE, cDest);// Save the core code of the compositing effect
try {
// Main audio processing
system->createSound(cstr_main_audio, FMOD_DEFAULT, NULL, &mainSound);
system->playSound(mainSound, 0.false, &mainChannel);
mainChannel->setVolume(main_audio_volume);
// Add a sound change effect
applyAllSoundEffect(env, sound_effect, system, mainChannel);
// Subaudio processing
if (env->GetStringLength(sub_audio) ! =0) {
LOGI("%s"."Processing secondary audio, background sound.")
system->createSound(cstr_sub_audio, FMOD_LOOP_NORMAL, NULL, &subSound);//subSound is background music, let it play until MainSound is finished playing
system->playSound(subSound, 0.false, &subChannel);
subChannel->setVolume(sub_audio_volume); }}catch(...). {LOGE("%s"."Abnormal sound change effect saved")
result = 0;
}
try {
system->update(a);while (isMainSoundPlaying) {
mainChannel->isPlaying(&isMainSoundPlaying);
usleep(1000);
}
// Release resource processing
env->ReleaseStringUTFChars(main_audio, cstr_main_audio);
env->ReleaseStringUTFChars(sub_audio, cstr_sub_audio);
env->ReleaseStringUTFChars(output_audio, cstr_output_audio);
mainSound->release(a); subSound->release(a); system->close(a); system->release(a); }catch(...). {LOGE("%s"."Abnormal sound change effect saved")
result = 0;
}
return result;
}
extern "C"
JNIEXPORT void JNICALL
Java_io_microshow_aisound_AiSound_setMixSoundMainVolume(JNIEnv *env, jclass clazz, jfloat volume) {
LOGI("%s"."--> AiSound_setMixSoundMainVolume");
mainChannel->setVolume(volume);
}
extern "C"
JNIEXPORT void JNICALL
Java_io_microshow_aisound_AiSound_setMixSoundSubVolume(JNIEnv *env, jclass clazz, jfloat volume) {
LOGI("%s"."--> AiSound_setMixSoundSubVolume");
subChannel->setVolume(volume);
}
extern "C"
JNIEXPORT void JNICALL
Java_io_microshow_aisound_AiSound_pauseMixSound(JNIEnv *env, jclass clazz) {
LOGI("%s"."--> AiSound_pauseMixSound");
mainChannel->setPaused(true);
subChannel->setPaused(true);
}
extern "C"
JNIEXPORT void JNICALL
Java_io_microshow_aisound_AiSound_resumeMixSound(JNIEnv *env, jclass clazz) {
LOGI("%s"."--> AiSound_resumeMixSound");
mainChannel->setPaused(false);
subChannel->setPaused(false);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_io_microshow_aisound_AiSound_isMixPlay(JNIEnv *env, jclass clazz) {
bool isPlaying = true;
return! mainChannel->isPlaying(&isPlaying);
}
extern "C"
JNIEXPORT void JNICALL
Java_io_microshow_aisound_AiSound_stopMixSound(JNIEnv *env, jclass clazz) {
LOGI("%s"."--> AiSound_stopMixSound");
mainChannel->stop(a); }extern "C"
JNIEXPORT void JNICALL
Java_io_microshow_aisound_AiSound_changeLoopMode(JNIEnv *env, jclass clazz, jboolean is_loop) {
loopMode = is_loop;
}
extern "C"
JNIEXPORT void JNICALL
Java_io_microshow_aisound_AiSound_changeSoundEffectWhilePlaying(JNIEnv *env, jclass clazz, jobject sound_effect) {
soundEffect = sound_effect;
}
Copy the code