在Android与IOS上面如果做录音功能,一般手机录制出来的音频格式都不是MP3,为了两个平台的APP的录音文件一致,需要选择一种两个平台都支持播放且占用存储空间不会太大的音频文件格式,这里MP3就符合这一需求。我们这里选择libmp3lame把AudioRecord音频流直接转换成MP3格式。
本文使用eclipse进行开发
那下面就开始我们的前期工作

1.给eclipse增加NDK开发支持

我这里使用的不是Cygwin,而是谷歌提供的android-ndk-r10e,大家自行下载并配置环境变量,这里就不多说。

2.下载Lame源码

我这里为了方便大家下载,上传了一个版本为lame-3.99.5的源码包,地址为

大家也可以在这里下载最新的lame,https://sourceforge.net/projects/lame/files/lame/

不过有可能被墙

android 录音成mp3格式 安卓录音转mp3_android 录音成mp3格式

3.创建工程(这里我们创建一个Java工程)

工程结构如下

android 录音成mp3格式 安卓录音转mp3_jni_02


我们在这里创建一个名称为jni的文件夹,目录树如下图

android 录音成mp3格式 安卓录音转mp3_jni_03


1. 我们将上文下载好的lame源码解压,复制lame-3.99.5/libmp3lame 到jni目录里,改名为lame-3.99.5_libmp3lame(这个名字大家随意取,后面要用到)

2. 将源码中lame.h (include目录下),拷贝到jni/lame-3.99.5_libmp3lame/lame.h

3. 对lame-3.99.5_libmp3lame下的源码进行处理,处理如下

a. 删除非.c/.h文件: Makefile.am、Makefile.in、depcomp、logoe.ico、

b. 删除文件夹i386及其目录下的文件

c. 编辑 util.h文件。

android 录音成mp3格式 安卓录音转mp3_jni_04

把extern ieee754_float32_t fast_log2(ieee754_float32_t x);替换为extern float fast_log2(float x);。如果忘了替换,编译时会报出以下错误:

[armeabi] Compile thumb : mp3lame <= bitstream.c
In file included from jni/bitstream.c:36:0:
jni/util.h:574:5: error: unknown type name ‘ieee754_float32_t’
jni/util.h:574:40: error: unknown type name ‘ieee754_float32_t’
make.exe: * [obj/local/armeabi/objs/mp3lame/bitstream.o] Error 1

d. 编辑set_get.h文件

android 录音成mp3格式 安卓录音转mp3_android 录音成mp3格式_05

e. 编辑vector\xmm_quantize_sub.c文件,头文件引用#include “lame.h”修改为#include “../lame.h”

d跟e不处理的话编译的时候会出现找不到lame.h的错误

4. 编写Android.mk文件和Application.mk文件
下面我把代码贴上来
Android.mk :

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LAME_LIBMP3_DIR := lame-3.99.5_libmp3lame

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 com_csf_lame4android_utils_FLameUtils.c

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

注意com_csf_lame4android_utils_FLameUtils.c
注意,这个记得要带上,这个是我们通过java 通过jni调用c的入口类,这个文件我们等会再说,先补下Android.mk的知识

Android.mk介绍:
Android.mk:用于配置编译生成的so库名、引用的头文件(.h文件)、需要编译的.c文件和.a静态库文件等。要掌握jni,必须了解Android.mk里面变量的作用和规范。

(1)LOCAL_PATH: 这个变量用于给出当前文件的路径。
必须在 Android.mk 的开头定义,可以这样使用:LOCAL_PATH := (callmy−dir)如当前目录下有个文件夹名称src,则可以这样写 (call src),那么就会得到 src 目录的完整路径
这个变量不会被$(CLEAR_VARS)清除,因此每个 Android.mk 只需要定义一次(即使在一个文件中定义了几个模块的情况下)。

(2)LOCAL_MODULE: 这是模块的名字,它必须是唯一的,而且不能包含空格。
必须在包含任一的$(BUILD_XXXX)脚本之前定义它。模块的名字决定了生成文件的名字。

(3)LOCAL_SRC_FILES: 这是要编译的源代码文件列表。
只要列出要传递给编译器的文件,因为编译系统自动计算依赖。注意源代码文件名称都是相对于 LOCAL_PATH的,你可以使用路径部分,例如:
LOCAL_SRC_FILES := foo.c toto/bar.c\ Hello.c
文件之间可以用空格或Tab键进行分割,换行请用”\”
如果是追加源代码文件的话,请用LOCAL_SRC_FILES +=
注意:可以LOCAL_SRC_FILES := $(call all-subdir-Java-files)这种形式来包含local_path目录下的所有java文件。

(4)LOCAL_C_INCLUDES: 可选变量,表示头文件的搜索路径。
默认的头文件的搜索路径是LOCAL_PATH目录。

(5)LOCAL_STATIC_LIBRARIES: 表示该模块需要使用哪些静态库,以便在编译时进行链接。

(6)LOCAL_SHARED_LIBRARIES: 表示模块在运行时要依赖的共享库(动态库),在链接时就需要,以便在生成文件时嵌入其相应的信息。
注意:它不会附加列出的模块到编译图,也就是仍然需要在Application.mk 中把它们添加到程序要求的模块中。

(7)LOCAL_LDLIBS: 编译模块时要使用的附加的链接器选项。这对于使用‘-l’前缀传递指定库的名字是有用的。
例如,LOCAL_LDLIBS := -lz表示告诉链接器生成的模块要在加载时刻链接到/system/lib/libz.so
可查看 docs/STABLE-APIS.TXT 获取使用 NDK发行版能链接到的开放的系统库列表。

( 8 )LOCAL_MODULE_PATH 和 LOCAL_UNSTRIPPED_PATH
在 Android.mk 文件中, 还可以用LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH指定最后的目标安装路径.
不同的文件系统路径用以下的宏进行选择:
TARGET_ROOT_OUT:表示根文件系统。
TARGET_OUT:表示 system文件系统。
TARGET_OUT_DATA:表示 data文件系统。
用法如:LOCAL_MODULE_PATH :=$(TARGET_ROOT_OUT)
至于LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH的区别,暂时还不清楚。

(9)LOCAL_JNI_SHARED_LIBRARIES:定义了要包含的so库文件的名字,如果程序没有采用jni,不需要
LOCAL_JNI_SHARED_LIBRARIES := libxxx 这样在编译的时候,NDK自动会把这个libxxx打包进apk; 放在youapk/lib/目录下

Application.mk

APP_ABI := all
APP_MODULES := mp3lame
APP_CFLAGS += -DSTDC_HEADERS
#APP_ABI:=x86_64
#APP_PLATFORM := android-21

编译所有的架构的so文件

Application.mk介绍

Application.mk:用于配置JNI生成该CPU使用的so ,文件格式如下:

APP_ABI := armeabi armeabi-v7a x86 arm64-v8a x86_64 mips mips64

TARGET_CPU_API :=armeabi armeabi-v7a x86 arm64-v8a x86_64 mips mips64

或者 配置所有

APP_ABI := all

APP_CFLAGS += -DSTDC_HEADERS
Application.mk里要加APP_CFLAGS += -DSTDC_HEADERS,不然会报错undefined reference to `bcopy’等错误

5. 通过jni编写java调用的本地方法

android 录音成mp3格式 安卓录音转mp3_jni_06

通过上图了解,看我圈中的三个文件,他们之间有什么关系呢,如果使用jni的同学,应该不需要我多说,不清楚的同学可以看下我之前写的一篇文章了解

我们来看代码:

FLameUtils.java

挑几个主要的方法讲下

android 录音成mp3格式 安卓录音转mp3_lame_07


在FLameUtils中我们需要调用到initEncoder、destroyEncoder、encodeFile三个方法,意味着我们的中间文件com_csf_lame4android_utils_FLameUtils.c 要通过jni定义并实现这三个方法。那怎么定义呢?

a. 编译FLameUtils.java 得到FLameUtils.class

我们可以在工程bin下找到对应的class文件

android 录音成mp3格式 安卓录音转mp3_mp3_08


b.通过FLameUtils.class得到对应的.c的头文件com_csf_lame4android_utils_FLameUtils.h

javah -classpath . -jni com.csf.lame4android.utils.FLameUtils

我们在dos下键入到bin下,通过上面命令生成.h文件

android 录音成mp3格式 安卓录音转mp3_android 录音成mp3格式_09


android 录音成mp3格式 安卓录音转mp3_android_10


c.将com_csf_lame4android_utils_FLameUtils.h复制到jni目录下

d.创建.c文件编写代码定义并实现initEncoder、destroyEncoder、encodeFile这三个方法

void Java_com_csf_lame4android_utils_FLameUtils_initEncoder(JNIEnv *env,
        jobject jobj, jint in_num_channels, jint in_samplerate, jint in_brate,
        jint in_mode, jint in_quality)

void Java_com_csf_lame4android_utils_FLameUtils_destroyEncoder(
        JNIEnv *env, jobject jobj)

void Java_com_csf_lame4android_utils_FLameUtils_encodeFile(JNIEnv *env,
        jobject jobj, jstring in_source_path, jstring in_target_path)

我们以initEncoder为例,

android 录音成mp3格式 安卓录音转mp3_jni_11


再看看之前的com_csf_lame4android_utils_FLameUtils.h文件

android 录音成mp3格式 安卓录音转mp3_mp3_12


再看看我们FLameUtils.java的包名类名

com.csf.lame4android.utils.FLameUtils

是不是有一种豁然开朗的感觉,.h是将FLameUtils的initEncoder 这个native方法按一定固定格式声明

以Java_开头,后面拼上包名类名方法名,将连接符“.”转成“_”。

.c文件则是以.h文件中声明的方法名来实现。所以直接从.h文件中将三个方法名复制到.c文件中并实现即可。这里话说的有点多,主要是怕有的同学没有接触过jni,补充下基础知识。

注意:这里方法名称跟后面java通过FLameUtils实现转码的功能会有挂钩,所以FLameUtils中的包名类名方法名(上文定义那三个)都是不可以改变的。所以我们一般打成jar包(AndroidDemo中将以jar形式提供使用,本文不讲怎么打jar,感兴趣的朋友私聊我)

那我们就接着编写com_csf_lame4android_utils_FLameUtils.c中的逻辑,调用lame库中的方法进行方法实现,因为代码长度问题,这里就不贴上代码,后面我会附上Demo,大家直接下载查看。

前期准备已经完成

6.编译so库

我们应该怎么来编译so库呢,这就得用到前文配置好的NDK 环境。

我们需要创建一个Builder,工程右键点击,选择Build path -Configure build path - Builders ,点击new

android 录音成mp3格式 安卓录音转mp3_android 录音成mp3格式_13


下面我就不文字说明了,直接上图

android 录音成mp3格式 安卓录音转mp3_lame_14


切换到Refresh tab

android 录音成mp3格式 安卓录音转mp3_lame_15


切换到Build options tab

android 录音成mp3格式 安卓录音转mp3_lame_16


android 录音成mp3格式 安卓录音转mp3_jni_17


这样编译器就创建完毕,将编译器Up置顶

android 录音成mp3格式 安卓录音转mp3_jni_18


点击OK,就开始编译so文件,不出错误的话,会在工程下libs跟obj文件夹

android 录音成mp3格式 安卓录音转mp3_jni_19


哈哈,终于是到了尾声了。

4.集成使用,创建Android工程测试

我们将FLameUtil打成jar包,命名为flame.jar。

创建一个Android工程,将flame.jar和libs的所有资源拷贝到该工程的libs文件夹中。

使用FLameUtil中的raw2mp3方法进行格式转换

android 录音成mp3格式 安卓录音转mp3_lame_20

demo效果如下,

android 录音成mp3格式 安卓录音转mp3_jni_21

录音并转换后在存储盘根目录下

android 录音成mp3格式 安卓录音转mp3_android 录音成mp3格式_22

好了,文章就到此结束了,可能篇幅有点长
下面贴下Project和Demo的源码地址
Lame4Android 和Lame4AndroidDemo
如果有什么疑问,或者发现存在上面问题,请告知我下,我们一起探讨,谢谢!
上文忘了说,最后提醒下,记得添加录音权限

<uses-permission android:name="android.permission.RECORD_AUDIO" />

昨天忘记说了,对于跑在6.0系统的机器上,要在应用中赋予录音权限,不然录音会失败!后面有时间我再增加对于权限这块的处理。