细说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的这种变声