Android 導入 So 庫的流程

本章內容用來記錄導入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);
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章