本章內容用來記錄導入so庫的流程, 會以 Fmod爲例. 點擊直達FMOD官網
1. 新建好工程後將 FMOD 的頭文件複製到工程中
2. src 目錄下新建 jniLibs 文件夾, 將各 CPU 架構對應的 so 庫複製進去.
3. 在 app 目錄下新建 libs 文件夾, 放入 fmod.jar
4, 在 app gradle 中引入 jar 包
dependencies {
...
//引入 jar 包
implementation files("libs\\fmod.jar")
}
5.修改 CMakeLists.txt
5.1 導入頭文件
#導入頭文件, 和 cmakelists 同級的情況下直接寫文件夾名字, 再外層可以使用相對路徑
include_directories("inc")
5.2 導入庫文件
#上面之導入了頭文件, 還需要導入庫文件, 必須放到jniLibs中. (jniLibs必須這樣命名), 或者再 build 中使用 sourceSet 修改.
#類似於配置環境變量, 再後面追加. -L${CMakeLists 文件的路徑}/../jniLibs/${對應CPU架構的文件夾}
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
5.3 批量導入源文件
#批量導入所有源文件
file(GLOB myAllCppFile *.c *.h *.cpp)
add_library(
study002-lib
SHARED
${myAllCppFile}) #這裏就不再指定單一的源文件,而使用批量的
5.4 將具體的 so 庫鏈接到 study002-lib
target_link_libraries(
study002-lib
log
fmod #將具體的庫鏈接到總庫 study002-lib.so 中 ,省略 lib. 當前路徑爲 jniLibs/arm64-v8a/libfmod.so, cmake會自動尋找
fmodL #將具體的庫鏈接到總庫 study002-lib.so 中 ,省略 lib. 當前路徑爲 jniLibs/arm64-v8a/libfmodL.so,cmake會自動尋找
)
6. 生成頭文件
cd 到 app/src/main/java/
目錄下使用 javah com.example.study002.MainActivity
生成頭文件(自帶宏定義),將生成的頭文件放到 cpp 目錄下
CMake 完整代碼
cmake_minimum_required(VERSION 3.22)
project("study002")
#導入頭文件, 和 cmakelists 同級的情況下直接寫文件夾名字, 再外層可以使用相對路徑
include_directories("inc")
#上面之導入了頭文件, 還需要導入庫文件, 必須放到jniLibs中. (jniLibs必須這樣命名), 或者再 build 中使用 sourceSet 修改.
#類似於配置環境變量, 再後面追加. -L${CMakeLists 文件的路徑}/../jniLibs/${對應CPU架構的文件夾}
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
#批量導入所有源文件
file(GLOB myAllCppFile *.c *.h *.cpp)
add_library(
study002-lib
SHARED
${myAllCppFile}) #這裏就不再指定單一的源文件,而使用批量的
#find_library(
# log-lib
# log)
target_link_libraries(
study002-lib
log
fmod #將具體的庫鏈接到總庫 study002-lib.so 中 ,省略 lib. 當前路徑爲 jniLibs/arm64-v8a/libfmod.so, cmake會自動尋找
fmodL #將具體的庫鏈接到總庫 study002-lib.so 中 ,省略 lib. 當前路徑爲 jniLibs/arm64-v8a/libfmodL.so,cmake會自動尋找
)
Java 代碼
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final int MODE_NORMAL = 0;
public static final int MODE_DASHU = 1;
public static final int MODE_LUOLI = 2;
public static final int MODE_GAOGUAI = 3;
public static final int MODE_JINGSONG = 4;
public static final int MODE_KONGLING = 5;
// Used to load the 'study002' library on application startup.
static {
System.loadLibrary("study002-lib");
}
private ActivityMainBinding binding;
private String path;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
path = "file:///android_asset/26.mp3";
FMOD.init(this);
binding.btYuansheng.setOnClickListener(this);
binding.btDashu.setOnClickListener(this);
binding.btGaoguai.setOnClickListener(this);
binding.btJingsong.setOnClickListener(this);
binding.btKongling.setOnClickListener(this);
binding.btLuoli.setOnClickListener(this);
}
@Override
protected void onDestroy() {
FMOD.close();
super.onDestroy();
}
@SuppressLint("NonConstantResourceId")
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bt_yuansheng:
fix(path, MODE_NORMAL); //正式開發時,這裏需要用子線程,.不能使用主線程
break;
case R.id.bt_luoli:
fix(path, MODE_LUOLI);
break;
case R.id.bt_dashu:
fix(path, MODE_DASHU);
break;
case R.id.bt_gaoguai:
fix(path, MODE_GAOGUAI);
break;
case R.id.bt_jingsong:
fix(path, MODE_JINGSONG);
break;
case R.id.bt_kongling:
fix(path, MODE_KONGLING);
break;
default:
break;
}
}
public native void fix(String path, int type);
//讓C++調用
public void playerEnd(String msg){
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
生成的頭文件代碼
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_study002_MainActivity */
#include "fmod.hpp"
#ifndef _Included_com_example_study002_MainActivity
#define _Included_com_example_study002_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
#undef com_example_study002_MainActivity_MODE_NORMAL
#define com_example_study002_MainActivity_MODE_NORMAL 0L
#undef com_example_study002_MainActivity_MODE_DASHU
#define com_example_study002_MainActivity_MODE_DASHU 1L
#undef com_example_study002_MainActivity_MODE_LUOLI
#define com_example_study002_MainActivity_MODE_LUOLI 2L
#undef com_example_study002_MainActivity_MODE_GAOGUAI
#define com_example_study002_MainActivity_MODE_GAOGUAI 3L
#undef com_example_study002_MainActivity_MODE_JINGSONG
#define com_example_study002_MainActivity_MODE_JINGSONG 4L
#undef com_example_study002_MainActivity_MODE_KONGLING
#define com_example_study002_MainActivity_MODE_KONGLING 5L
/*
* Class: com_example_study002_MainActivity
* Method: fix
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_example_study002_MainActivity_fix(JNIEnv *, jobject, jstring, jint);
#ifdef __cplusplus
}
#endif
#endif
總庫的代碼 native-lib.cpp
#include <jni.h>
#include <string>
#include <unistd.h>
#include <android/log.h>
#include "com_example_study002_MainActivity.h"
using namespace FMOD;
#define _MY_LOGE(...) __android_log_print(ANDROID_LOG_DEBUG, "JNI", __VA_ARGS__);
extern "C"
JNIEXPORT void JNICALL Java_com_example_study002_MainActivity_fix(JNIEnv * env, jobject thisz, jstring path, jint type){
char * content_ = "播放完畢";
const char *path_ = env->GetStringUTFChars(path, nullptr);
System * system = nullptr;
Sound * sound = nullptr;
Channel * channel = nullptr;
DSP * dsp = nullptr;
//基礎回顧, 二級指針存放的是一級指針的地址. 可以修改一級指針的值
//開始初始化
System_Create(&system);
//參數1: 最大音軌
//參數2: 初始化標記
system->init(32, FMOD_INIT_NORMAL, nullptr);
//創建聲音
system->createSound(path_, FMOD_DEFAULT, nullptr, &sound);
//播放聲音
system->playSound(sound, nullptr, false, &channel);
switch (type) {
case com_example_study002_MainActivity_MODE_NORMAL:
break;
case com_example_study002_MainActivity_MODE_LUOLI:
//創建DSP類型的pitch 音調調節
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
//設置 pitch 音調調節
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.0f);
channel->addDSP(0, dsp);
break;
case com_example_study002_MainActivity_MODE_DASHU:
//創建DSP類型的pitch 音調調節
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
//設置 pitch 音調調節
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.6f);
channel->addDSP(0, dsp);
break;
case com_example_study002_MainActivity_MODE_GAOGUAI:
//需要改變音軌頻率
//先獲得當前頻率
float mFrequency;
channel->getFrequency(&mFrequency);
//修改頻率
channel->setFrequency(mFrequency * 2.0f);
break;
case com_example_study002_MainActivity_MODE_JINGSONG:
//會有很多通道, 進行拼接
//先調低音調
system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
//設置 pitch 音調調節
dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.8f);
channel->addDSP(0, dsp);
//再調整回聲
system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 100); //迴音延遲,10-5000, 默認500
dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 20);//迴音的衰減度,默認50
channel->addDSP(1,dsp);
//再添加顫抖
system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
dsp->setParameterFloat(FMOD_DSP_TREMOLO_FREQUENCY, 16.0f);
dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 16.0f);
dsp->setParameterFloat(FMOD_DSP_TREMOLO_SPREAD, 3.0f);
channel->addDSP(2,dsp);
break;
case com_example_study002_MainActivity_MODE_KONGLING:
//設置迴音
system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 100); //迴音延遲,10-5000, 默認500
dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 10);//迴音的衰減度,默認50
channel->addDSP(0,dsp);
break;
}
bool isPlayer = true;
while (isPlayer) {
channel->isPlaying(&isPlayer);
usleep(1000 * 1000);
}
//開始回收
sound->release();
system->close();
system->release();
env->ReleaseStringUTFChars(path, path_);
jclass objectClass = env->GetObjectClass(thisz);
jmethodID methodId = env->GetMethodID(objectClass, "playerEnd", "(Ljava/lang/String;)V");
jstring msg = env->NewStringUTF(content_);
env->CallVoidMethod(thisz, methodId, msg);
}