细说JNI与NDK(三)ndk 配置说明

细说JNI与NDK专题目录:

细说JNI与NDK(一) 初体验
细说JNI与NDK(二)基本操作)
细说JNI与NDK(三)ndk 配置说明
细说JNI与NDK(四)动态和静态注册
细说JNI与NDK(五)JNI 线程
细说JNI与NDK(六)静态缓存,异常捕获,内置函数
细说JNI与NDK(七)Parcel底层JNI思想与OpenCV简单对比

Cmake 配置使用说明(简化版)

  • cmake_minimum_required(VERSION 3.4.1)

最低支持的cmake版本

  • include_directories(src/main/cpp/include)

include_directories 导入头文件

  • aux_source_directory 定义文件夹路径别名

aux_source_directory(${CMAKE_SOURCE_DIR}/src/main/cpp my_source_path)
aux_source_directory(${CMAKE_SOURCE_DIR}/src/main/cpp/bzip2 SOURCES)

${CMAKE_SOURCE_DIR} 这个就是你的CmakeList.txt 所在的目录位置,通常我们会根据他,进行相对位置,进行配置信息。
aux_source_directory 他的意思就是给你这个目录起一个变量名替代,因为路径太长,所以相当与起一个别名,简化配置。
除了这种配置我们还有其他方式 file ( Glob ...)

  • file ( Glob ...) 不推荐

file(GLOB allCpp *.h *.cpp *.c)
全局引入文件,并起别名

  • add_library

用第一种:分别对应不同库路径配置方法

add_library(
        native-lib 
        SHARED 
        ${my_source_path} ${SOURCES} )

native-lib 对应的实际上是libnative-lib.so lib自定义名字.so 这种规则
SHARED 是动态库.so---->对应的静态库STATIC 静态库.a
然后候命可以跟着很多不同库文件夹路径。

#方法1
aux_source_directory(${CMAKE_SOURCE_DIR}/src/main/cpp my_source_path)
aux_source_directory(${CMAKE_SOURCE_DIR}/src/main/cpp/bzip2 SOURCES)
add_library(native-lib SHARED ${my_source_path} ${SOURCES} )

不推荐第二种:file(GLOB) 的方式

# 方法2
#file(GLOB allCpp *.h *.cpp *.c)
#add_library(native-lib SHARED ${allCpp} )

简单的项目还好,引入的库比较少,如果是多个项目,不好替换,随时增删的情况,不好分类管理,建议用第一种

  • find_library 这个多数情况可以简化操作,在target_link_libraries里面

find_library( 
        log-lib # Sets the name of the path variable.
        log# Specifies the name of the NDK library that you want CMake to locate.     
        )

看注释也知道,给log-lib 是个变量别名
log----> 在本地ndk配置的中动态查找liblog.so

  • set_target_properties

set_target_properties(lib_opencv
        PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jni/${ANDROID_ABI}/libopencv_java3.so)

我们在开发自己的库或者C层的时候,会用到第三方成形的so库,所以set_target_properties 的使用就比较重要
规则简单
lib_opencv(用到的so库名)
IMPORTED_LOCATION(固定引入方式)
具体的路径${ANDROID_ABI} 比较有意思,是配合gradle 打包配置,可以循环判断,对应平台的so引入
由于版本有区别,和${CMAKE_ANDROID_ARCH_ABI} 是一样的

同理,和set_target_properties,对应的还有 set 方式

  • set 作用和目的类似set_target_properties

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CXX_FLAGS}/src/main/${CMAKE_ANDROID_ARCH_ABI}")

CMAKE_CXX_FLAGS 这个就相当于,我们在windows或者mac配置path一样,这个是系统变量基本的,我们自定义的进行追加,追加标记就是-L 后面跟路径就好了,批量操作set更合适

回忆下mac path:xxxxx
回一下windows %path%;%xxxxx%
是不是瞬间就有感觉理解了

  • target_link_libraries 编译的步骤中,链接

target_link_libraries( # Specifies the target library.
        native-lib
        log #${log-lib}
        jnigraphics
        lib_opencv)

log #${log-lib}是不是吧find_library 去掉就行了,就是不要别名,是不是简洁很多

  • set(CMAKE_VERBOSE_MAKEFILE on) 调试打印cmake

set(CMAKE_VERBOSE_MAKEFILE on)
message("zcw before")

message(STATUS ${SOURCES})

message(STATUS "zcw after")

有时候对于复杂的脚本,出问题,我们需要定位,这个是后类似log信息就很重了。

好了,基本上上面都掌握的话,加上前面的基础ndk配置和引入第三方native库我们就不棘手了。

gradle 配置说明

我们需要了解的cpu架构模型:
arm64-v8a
armeabi-v7a
armeabi
x86 x86_64

我们一般兼容大多数手机,x86在windows上的模拟器比较多,所以对于正常运行的apk的话,我们兼容arm所有的类型就好。

但是开发的期间,我们手里有的是某一个,为了开发效率,我们选择一个对应测试机的cpu进行调试,用命令查询当前手机的cpu类型

 adb shell getprop  会查到所有的属性,所以我们过滤cpu型prop

 adb shell getprop ro.product.cpu.abi

可以开心的配置了

externalNativeBuild/cmake,ndk配置

defaultconfig/externalNativeBuild/cmake.

externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
                abiFilters "armeabi-v7a"
// 过时了
//                arguments "-DANDROID_STL=gnustl_static"
//                替换
                arguments "-DANDROID_STL=c++_static"

            }
        }

注意,我写的这个参数arguments ,google 官方相关的库可能会用到,编译失败,不要怕,仔细看信息,让你更新语法,因为可能我的ndk更新了,所以需要部分调整,所以遇到编译失败先仔细看输出信息。
cppFlags 默认的四大平台

defaultconfig/ndk , 指定cpu架构,打进apk的lib架构

ndk {
            abiFilters 'armeabi-v7a'

        }

实战练习-Fmod

这个引入的脚本CmakeList.txt 和过程理论结束了,我们来实践一下。
.QQ语言变声,这个功能是不是挺有意思,所谓的女装大佬,大佬女装。
还有QQmusic里面有那么多的音效DSP,DST等。底层用了音效库。
我们就引入fmod库进行demo一下。

native 核心

#include "fmod_demo.h"
#include 

#define LOG_TAG "native_zcw"

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
using namespace FMOD;
extern "C"
JNIEXPORT void JNICALL
Java_top_zcwfeng_jni_FmodVoiceActivity_voiceChangeNative(JNIEnv *env, jobject thiz, jint mode,
                                                         jstring path) {
    char *_content = "默认:播放结束";
    const char *_path = env->GetStringUTFChars(path, NULL);

    //Fmod 音效引擎
    System *system = 0;
    // fmod 声音
    Sound *sound = 0;
    // 通道 音轨
    Channel *channel = 0;
    // digital signal process 数字信号处理
    DSP *dsp = 0;
    // ① 创建系统
    System_Create(&system);
    // ② 初始化
    system->init(32, FMOD_INIT_NORMAL, 0);
    // ③ 创建声音
    system->createSound(_path, FMOD_DEFAULT, 0, &sound);
    // ④ 播放声音
    system->playSound(sound, 0, false, &channel);

    switch (mode) {
        case top_zcwfeng_jni_FmodVoiceActivity_MODE_NORMAL:
            _content = "原生:播放完毕";
            break;
        case top_zcwfeng_jni_FmodVoiceActivity_MODE_LUOLI:
            _content = "萝莉:播放完毕";
            //1.创建DSP类型 类型是 Pitch 音调调节 默认正常:1.0    0.5 ~ 2.0
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 2.设置Pitch音调调节为:2.0,音调很高就是萝莉了
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.0f);
            // 3.添加音效进去 参数一:0 是因为只有一个dsp
            channel->addDSP(0, dsp);
            break;
        case top_zcwfeng_jni_FmodVoiceActivity_MODE_DASHU:
            _content = "大叔:播放完毕";
            // 1.创建DSP类型是Pitch 音调调节 默认正常:1.0  0.5 ~ 2.0
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 2.设置Pitch音调调节为:2.0,音调很高就是萝莉了
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.5f);
            // 3.添加音效进去 参数一:0 是因为只有一个dsp
            channel->addDSP(0, dsp);
            break;
        case top_zcwfeng_jni_FmodVoiceActivity_MODE_GAOGUAI:
            _content = "搞怪 小黄人:播放完毕";
            // 1.从通道里面拿频率, 原始频率
            float frequency;
            channel->getFrequency(&frequency);
            // 2.在原来的频率上更改
            channel->setFrequency(frequency * 1.3f);
            break;
        case top_zcwfeng_jni_FmodVoiceActivity_MODE_JINGSONG:
            _content = "惊悚音 播放完毕";
            //大叔
            system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7f);
            channel->addDSP(0, dsp);
            // 回声 搞点回声
            system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
            dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 400); // 延时的回音
            dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 40); // 默认:50  0完全衰减了
            channel->addDSP(1, dsp);
            // 颤抖 Tremolo
            system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
            dsp->setParameterFloat(FMOD_DSP_TREMOLO_FREQUENCY, 0.8f);
            dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.8f);
            channel->addDSP(2, dsp);
            break;
        case top_zcwfeng_jni_FmodVoiceActivity_MODE_KONGLING:
            _content = "空灵:播放完毕";
            //回音 ECHO
            system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
            // 延迟声音
            dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 150);
            // 默认:50  0完全衰减了
            dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 20);
            channel->addDSP(0, dsp);
            break;
    }
    bool isPlay = true;
    while (isPlay) {
        channel->isPlaying(&isPlay);
        usleep(1000 * 1000);
    }

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

    // 调用java弹出播放提示 char* ---> jstring  ---> String(Java)
    jclass clazz = env->GetObjectClass(thiz);
    jmethodID jmethodId = env->GetMethodID(clazz,"playEnd", "(Ljava/lang/String;)V");
    jstring str = env->NewStringUTF(_content);
    env->CallVoidMethod(thiz,jmethodId,str);
}
  • 创建过程
    ① 创建系统
    ② 初始化
    ③ 创建声音
    ④ 播放声音
  • 音效
    根据音轨调用api即可
    思路,创建dsp,然后更改dsp参数,加入到channel中

kotlin 代码

class FmodVoiceActivity : AppCompatActivity() {
    private val MODE_NORMAL = 0 // 正常
    private val MODE_LUOLI = 1 // 萝莉
    private val MODE_DASHU = 2 // 大叔
    private val MODE_JINGSONG = 3 // 惊悚
    private val MODE_GAOGUAI = 4 // 搞怪
    private val MODE_KONGLING = 5 // 空灵
    // 播放的路径
    private val PATH = "file:///android_asset/test.m4a"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_fmod_voice)
        FMOD.init(this);
    }
    // 六个 点击事件
    fun onFix(view: View) {
        when (view.id) {
            R.id.btn_normal -> voiceChangeNative(MODE_NORMAL, PATH)
            R.id.btn_luoli -> voiceChangeNative(MODE_LUOLI, PATH)
            R.id.btn_dashu -> voiceChangeNative(MODE_DASHU, PATH)
            R.id.btn_jingsong -> voiceChangeNative(MODE_JINGSONG, PATH)
            R.id.btn_gaoguai -> voiceChangeNative(MODE_GAOGUAI, PATH)
            R.id.btn_kongling -> voiceChangeNative(MODE_KONGLING, PATH)
        }
    }

    private external fun voiceChangeNative(mode:Int,path:String)

    private fun playEnd(nativeMessageContent:String){
        Toast.makeText(this,""+nativeMessageContent,Toast.LENGTH_SHORT).show()
    }

    override fun onDestroy() {
        super.onDestroy()
        FMOD.close();
    }

}

效果类似qq的这种变声

fennu4.jpeg

你可能感兴趣的:(细说JNI与NDK(三)ndk 配置说明)