说明
文章首发于HURUWO的博客小站,本平台做同步备份发布。
如有浏览或访问异常图片加载失败或者相关疑问可前往原博客下评论浏览。
原文链接 jni编程笔记androidstudio3-0下的jni编程直接点击即可前往访问。
更多技术文章访问本人博客HuRuWo的技术小站,包括Android 逆向 app,闲鱼爬虫 微信爬虫 抖音爬虫 群控 等相关知识
前言
最近项目上做到了直播和音视频的播放,这里不可避免的涉及到了Android JNI开发。于是去市面上找到一本资料《音视频开发进阶指南》,我看出版时间很近是2017年12月。结果遇到了大坑,里面的所有demo都是基于eclipse。包括作者提供的源码也是eclipse的工程。没办法只有自己填坑了,下面就是整个移植过程。由于初次JNI开发,整个过程也是跌跌撞撞的。
所有资料
《音视频开发进阶指南》 第二章,关于LAME编码MP3文件。
作者源码地址(eclipse):https://github.com/zhanxiaokai/Android-Mp3Encoder
移植修正后的AS3.0版本:AndroidStudio3.0上使用LAME编码mp3
首先认识一下Cmake
正当我不知道怎么入手的时候,我发现了一个可以作为参考的项目。
https://github.com/ShawnJings/Android-AudioPlayer
这个也是移植了这本书的项目
https://github.com/zhanxiaokai/Android-AudioPlayer
全部下载之后,我得出一些结论:
1.AS和Eclipse的c++代码存放位置不同,一个是jni文件夹,一个是cpp文件夹
2.AS使用的是CMakeList.txt文件 而eclipse里用的是.mk文件
所以核心的不同就是这个CMakeList.txt 和.mk(makefile)文件
这两个文件的功能一样都是用于配置在不同平台下的编译
在androidstudio2.2之后,内置了Cmake编译器。
关于Cmake的资料除了官网,其他地方都是零散的https://cmake.org/
里面内容很多,但是没必要全掌握。用到什么再了解吧。
其实最好的资料是这个https://developer.android.com/studio/projects/configure-cmake.html
当然 如果你习惯了.mk文件配置 AS也是支持的
这里
新建AS工程导入文件
新建工程,记得勾选C++支持
然后就是一路next
完成新建项目之后,删掉里面的
jni方法 和C++ 文件 以及CmakeList.txt文件里面的东西
现在变成了一个干净的JNI工程,这点很重要
把eclipse下的jni文件拉入到AS中的cpp文件夹下面
删掉.mk文件 把java文件拉入java目录下面
编译java native 方法的Cpp文件
Mp3Encoder.java 里面有三个native方法
public class Mp3Encoder {
public native int init(String pcmPath, int audioChannels, int bitRate, int sampleRate, String mp3Path);
public native void encode();
public native void destroy();
}
需要编写对应的Cpp文件,这里我们可以用java -jni 命令生成也可以直接写。
我的建议是直接写,如果清楚对应的命名规则
跑到java 文件目录下面
javah -jni com.huruwo.androidmp3encoder.Mp3Encoder
注意记得加上包名,否则找不到类
生成了.h文件,移到cpp目录下面
然后打开AudioEncoder.cpp 把对应的函数名替换成com_huruwo_androidmp3encoder_Mp3Encoder.h
里的函数名
编写CMakeLists.txt
接下来重点来了我将一行行讲解下面的代码
cmake_minimum_required(VERSION 3.4.1)
首先设置Cmake的版本,这是每个CMakeLists.txt固定的字段
include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/3rdparty/lame/include/lame
${CMAKE_SOURCE_DIR}/src/main/cpp/common)
添加依赖的头文件目录,这个要写在前面
#添加静态库
add_library(
#库名称
lib-mp3
#类型 STATIC或者SHARE
STATIC
#文件 静态库 查看 set_target_properties
IMPORTED
)
set_target_properties(
#库名 与前面的对应
lib-mp3
PROPERTIES IMPORTED_LOCATION
#文件目录
${CMAKE_SOURCE_DIR}/src/main/cpp/3rdparty/lame/lib/libmp3lame.a
)
添加静态库.a,注意两个库名要对应一样
find_library(log-lib log)
用于寻找存在的库(内置的log库,输出日志信息)
add_library(native-lib SHARED src/main/cpp/mp3_encoder.cpp src/main/cpp/AudioEncoder.cpp)
编译本地的CPP 文件 设置为native-lib 类型为SHARE类型 并且添加两个文件
最后使用target_link_libraries
把所有的库连起来,注意顺序
如果A依赖于B 那么A写在前面,所有我们整个顺序倒过来加入
target_link_libraries( # Specifies the target library.
native-lib
lib-mp3
# Links the target library to the log library
# included in the NDK.
${log-lib} )
好了整个CmakeLists,txt写完了
Sync一下,然后尝试编译一下。
成功运行,并且生成了.so文件
这里由于我设置了平台是armeabi/armeabi-v7a 所以只有文件下有东西
ndk {
abiFilters 'armeabi', 'armeabi-v7a'
}
完美
能用,跑两步试试
我们把pcm文件放入手机根目录,在代码中指向这个文件的目录
同时配置一个生成的mp3目录
跑起来,点击转换按钮
完美闪退
输出日志
04-01 23:57:56.218 6244-6273/? I/OpenGLRenderer: Initialized EGL, version 1.4
04-01 23:57:56.307 6244-6273/? W/MALI: glDrawArrays:714: [MALI] glDrawArrays takes more than 5ms here. Total elapse time(us): 25174
04-01 23:57:57.263 6244-6244/com.huruwo.androidmp3encoder I/Mp3Encoder: mp3Path is /storage/emulated/0/auto.mp3...
04-01 23:57:57.264 6244-6244/com.huruwo.androidmp3encoder A/libc: Fatal signal 4 (SIGILL), code 1, fault addr 0xef0d63c4 in tid 6244 (droidmp3encoder)
我观察到输出了日志mp3Path is /storage/emulated/0/auto.mp3...
这行日志是在AudioEncoder.cpp中的
Java_com_huruwo_androidmp3encoder_Mp3Encoder_init
函数
说明加载so文件成功了,并且进入了函数 只是CPP代码出了问题
同时我搜索了Fatal signal 4 (SIGILL)
这几个关键字
得到的答案也是关于NDK中cpp代码的异常导致的
但是这歌日志完全看不出哪里有问题,于是我开始断点调试cpp代码
发现程序总在env->ReleaseStringUTFChars(pcmPathParam, pcmPath);
这一行之后就会崩溃
妈的 我仔细的回想大学期间学过的C++代码,竟然发现这个函数没有返回值
按道理这个不是void 声明的函数,应该带有返回值给java层
所以稍微修改一下
int result=encoder->Init(pcmPath, mp3Path, sampleRate, channels, bitRate);
return result;
好的,再跑一下试试
ok 这次没有崩溃
但是没有得到想要的
输出日志为
Encoder Initialized Failed..
继续调试,发现ret
参数得到的值为-1
这个值是从cpp代码返回来的,具体函数如下:
int ret = -1;
pcmFile = fopen(pcmFilePath, "rb");
if(pcmFile) {
mp3File = fopen(mp3FilePath, "wb");
if(mp3File) {
lameClient = lame_init();
lame_set_in_samplerate(lameClient, sampleRate);
lame_set_out_samplerate(lameClient, sampleRate);
lame_set_num_channels(lameClient, channels);
lame_set_brate(lameClient, bitRate / 1000);
lame_init_params(lameClient);
ret = 0;
}
}
return ret;
为-1 说明文件没有读取成功
路径反复尝试没有错误,突然想到权限问题
因为这里不仅需要读取文件,还需要写入文件
但是原来的配置文件只有 写入文件的权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
所以应该加上 读取文件的权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
最后完美运行,生成相应的mp3文件
可以看到文件确实压缩了很多1.6MB–>148kb
点击播放 效果不错。
#总结
买书一定要谨慎,多看评论 包括豆瓣读书 商品评价
否则遇上大坑只能自己填