在项目中要实装lame的开源库实现录音转码mp3的功能。
期间遇到诸多问题,度娘了N多博客,最终总结了接入了lame
首先在gradle.properties里面添加 android.useDeprecatedNDK = true
1、Android Studio中配置NDK路径,步骤为:
1)先下载NDK并安装(这句基本是废话)
2)点菜单栏的File->ProjectStructure…->在打开的窗口中左侧选中SDKLocation->在右侧Android NDK Location中填入NDK目录所在路径,如下图所示:
2、项目中配置JNI
1)项目切换到Project开发模式
2)在app工程下面的main/src/main/目录下面新建jni目录
默认在src/main/jni目录下面找c/c++文件编译,也可以在android{}下添加sourceSet{}
sourceSet{
main{
jni.srcDirs=['src/main/cpp']
}
}
就能将指定目录下的c/c++文件编译成指定的模块名.so。
注意:上面这种方式通过gradle无法编译成多个模块,编译多个模块还是要自己写Android.mk文件实现,通过手动执行ndk-build编译成的*.so文件默认在src/main/libs下面(下面会详细讲到配置Android.mk文件)。
打包APK时,默认是寻找so的目录是/build/intermediates/ndk和/build/intermediates/jniLibs,前者是使用源码由Gradle编译成的so所处的位置,后者一般是一些第三方的so或者自己手动使用ndk编译生成的so文件所生成的,这个位置在src/main/jniLibs。
因此手动编译好so文件后,将其复制到jniLibs下面,或者在此放置第三方so,或者在build.gradle文件里面的android下面添加jniLib.srcDir定义了Gradle在哪里寻找生成的so库文件
3)在app工程下的build.gradle文件android下添加配置sourceSets
默认情况下,你需要把C/C++源代码放在 [module]/src/main/jni/ 路径下。
当然,也可以自定义源代码路径:
sourceSets {
main {
// jni.srcDirs禁用通过Gradle来编译本地c/c++代码
jni.srcDirs = []
// jniLib.srcDirs定义了Gradle在哪里寻找生成的so库文件
jniLibs.srcDir 'src/main/libs'
}
}
NDK和JNI配置完成后,接下来就是配置lame相关
3、首先下载lame的源码,可能需要自备梯子。
http://lame.sourceforge.net/download.php
http://sourceforge.net/projects/lame/files/lame/3.99/lame-3.99.5.tar.gz
1)下载好之后解压
2)在jni下面创建文件目录(命名自己随意):lame-3.99.5_libmp3lame;
3)将解压文件夹下lame-x.xx.x/libmp3lame/ 目录下面的.c和.h文件拷贝到项目下的jni/lame-3.99.5_libmp3lame目录下,同时将将 lame-x.xx.x/include/ 目录下的 lame.h 也复制到 jni/lame-3.99.5_libmp3lame/中
4、修改部分.c和.h文件的代码
1)删除fft.c文件的47行的"include "vector/lame_intrin.h""
2)删除set_get.h文件的24行的"#include <lame.h>"
3)将util.h文件的574行的"extern ieee754_float32_t fast_log2(ieee754_float32_t x);" 替换为 "extern float fast_log2(float x);"
5、在jni文件夹下创建Application.mk和Android.mk文件
Application.mk里面的代码为:
APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64
APP_MODULES := mp3lame
APP_CFLAGS += -DSTDC_HEADERS
#APP_ABI:=x86_64
#APP_PLATFORM := android-21
注意:
如果是x86_64的话需要在Application.mk中加上
APP_CFLAGS += -DSTDC_HEADERS
/*--以下为Native部分--*/
static {
System.loadLibrary("mp3lame");
}
Android.mk里面的代码为:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LAME_LIBMP3_DIR := lame-3.99.5_libmp3lame // 此处对于jni下面的lame目录
// 此处表示的是java代码引入.so的名称与Application文件的APP_MODULES := mp3lame 保持一致
LOCAL_MODULE := mp3lame
LOCAL_SRC_FILES :=\
$(LAME_LIBMP3_DIR)/bitstream.c \
$(LAME_LIBMP3_DIR)/fft.c \
$(LAME_LIBMP3_DIR)/id3tag.c \
$(LAME_LIBMP3_DIR)/mpglib_interface.c \
$(LAME_LIBMP3_DIR)/presets.c \
$(LAME_LIBMP3_DIR)/quantize.c \
$(LAME_LIBMP3_DIR)/reservoir.c \
$(LAME_LIBMP3_DIR)/tables.c \
$(LAME_LIBMP3_DIR)/util.c \
$(LAME_LIBMP3_DIR)/VbrTag.c \
$(LAME_LIBMP3_DIR)/encoder.c \
$(LAME_LIBMP3_DIR)/gain_analysis.c \
$(LAME_LIBMP3_DIR)/lame.c \
$(LAME_LIBMP3_DIR)/newmdct.c \
$(LAME_LIBMP3_DIR)/psymodel.c \
$(LAME_LIBMP3_DIR)/quantize_pvt.c \
$(LAME_LIBMP3_DIR)/set_get.c \
$(LAME_LIBMP3_DIR)/takehiro.c \
$(LAME_LIBMP3_DIR)/vbrquantize.c \
$(LAME_LIBMP3_DIR)/version.c \
MP3Recorder.c
include $(BUILD_SHARED_LIBRARY)
6、书写jni下面的MP3Recorder.c和MP3Recorder.h文件的代码
MP3Recorder.c文件代码:
#include "lame-3.99.5_libmp3lame/lame.h"
#include "MP3Recorder.h"
static lame_global_flags *glf = NULL;
JNIEXPORT void JNICALL Java_com_babi_story_audio_MP3Recorder_init(
JNIEnv *env, jclass cls, jint inSamplerate, jint outChannel,
jint outSamplerate, jint outBitrate, jint quality) {
if (glf != NULL) {
lame_close(glf);
glf = NULL;
}
glf = lame_init();
lame_set_in_samplerate(glf, inSamplerate);
lame_set_num_channels(glf, outChannel);
lame_set_out_samplerate(glf, outSamplerate);
lame_set_brate(glf, outBitrate);
lame_set_quality(glf, quality);
lame_init_params(glf);
}
JNIEXPORT jint JNICALL Java_com_babi_story_audio_MP3Recorder_encode(
JNIEnv *env, jclass cls, jshortArray buffer_l, jshortArray buffer_r,
jint samples, jbyteArray mp3buf) {
jshort* j_buffer_l = (*env)->GetShortArrayElements(env, buffer_l, NULL);
jshort* j_buffer_r = (*env)->GetShortArrayElements(env, buffer_r, NULL);
const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
int result = lame_encode_buffer(glf, j_buffer_l, j_buffer_r,
samples, j_mp3buf, mp3buf_size);
(*env)->ReleaseShortArrayElements(env, buffer_l, j_buffer_l, 0);
(*env)->ReleaseShortArrayElements(env, buffer_r, j_buffer_r, 0);
(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
return result;
}
JNIEXPORT jint JNICALL Java_com_babi_story_audio_MP3Recorder_flush(
JNIEnv *env, jclass cls, jbyteArray mp3buf) {
const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
int result = lame_encode_flush(glf, j_mp3buf, mp3buf_size);
(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
return result;
}
JNIEXPORT void JNICALL Java_com_babi_story_audio_MP3Recorder_close(
JNIEnv *env, jclass cls) {
lame_close(glf);
glf = NULL;
}
MP3Recorder.h文件代码:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_kubility_demo_MP3Recorder */
#ifndef _Included_MP3Recorder
#define _Included_MP3Recorder
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_kubility_demo_MP3Recorder
* Method: init
* Signature: (IIIII)V
*/
JNIEXPORT void JNICALL Java_com_babi_story_audio_MP3Recorder_init
(JNIEnv *, jclass, jint, jint, jint, jint, jint);
/*
* Class: com_kubility_demo_MP3Recorder
* Method: encode
* Signature: ([S[SI[B)I
*/
JNIEXPORT jint JNICALL Java_com_babi_story_audio_MP3Recorder_encode
(JNIEnv *, jclass, jshortArray, jshortArray, jint, jbyteArray);
/*
* Class: com_kubility_demo_MP3Recorder
* Method: flush
* Signature: ([B)I
*/
JNIEXPORT jint JNICALL Java_com_babi_story_audio_MP3Recorder_flush
(JNIEnv *, jclass, jbyteArray);
/*
* Class: com_kubility_demo_MP3Recorder
* Method: close
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_babi_story_audio_MP3Recorder_close
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
7、在终端进入jni目录,然后执行ndk-build命令
执行命令后将会在libs文件夹目录生成项目配置相关的.so文件