AndroidStudio3.0使用Cmake进行JNI开发

前言

最近项目上做到了直播和音视频的播放,这里不可避免的涉及到了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文件

TIM截图20180402174238.png

TIM图片20180402174610.jpg

可以看到文件确实压缩了很多1.6MB–>148kb

点击播放 效果不错。

#总结
买书一定要谨慎,多看评论 包括豆瓣读书 商品评价
否则遇上大坑只能自己填

发布了85 篇原创文章 · 获赞 40 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/lw_zhaoritian/article/details/79794462