给APP做语音功能,必须考虑到IOS和Android平台的通用性。wav录音质量高,文件太大,AAC和AMR格式在IOS平台却不支持,所以采用libmp3lame把AudioRecord音频流直接转换成MP3格式。
声明一下,代码参考了http://blog.****.net/cboy017/article/details/8455629,这里只是借花献佛,把整个流程写得更详细。
这里采用的是最新的lame-3.99.5.tar。
可以去Lame官网下载,博文最后也有****下载地址。官网地址:http://lame.sourceforge.net/
如果你对JNI和NDK完全不熟悉的话,请看前一篇博文 Android NDK开发之入门教程
先看一下项目文件目录:
开始Coding吧!
1 新建项目AndroidLameMP3。
2 创建JNI目录。
3 下载lame-3.99.5.tar。
解压,把子文件夹libmp3lame中的非.h和.c格式的文件删除后的剩余的所有文件和include下的lame.h放进一个新建的lame-3.99.5_libmp3lame文件夹中,最后把整个lame-3.99.5_libmp3lame文件夹拷贝到JNI目录下。
4 在com.example.lamemp3下创建MP3Recorder.class:
MP3Recorder.class
- public class MP3Recorder {
- private String mFilePath = null;
- private int sampleRate = 0;
- private boolean isRecording = false;
- private boolean isPause = false;
- private Handler handler = null;
- /**
- * 开始录音
- */
- public static final int MSG_REC_STARTED = 1;
- /**
- * 结束录音
- */
- public static final int MSG_REC_STOPPED = 2;
- /**
- * 暂停录音
- */
- public static final int MSG_REC_PAUSE = 3;
- /**
- * 继续录音
- */
- public static final int MSG_REC_RESTORE = 4;
- /**
- * 缓冲区挂了,采样率手机不支持
- */
- public static final int MSG_ERROR_GET_MIN_BUFFERSIZE = -1;
- /**
- * 创建文件时扑街了
- */
- public static final int MSG_ERROR_CREATE_FILE = -2;
- /**
- * 初始化录音器时扑街了
- */
- public static final int MSG_ERROR_REC_START = -3;
- /**
- * 录音的时候出错
- */
- public static final int MSG_ERROR_AUDIO_RECORD = -4;
- /**
- * 编码时挂了
- */
- public static final int MSG_ERROR_AUDIO_ENCODE = -5;
- /**
- * 写文件时挂了
- */
- public static final int MSG_ERROR_WRITE_FILE = -6;
- /**
- * 没法关闭文件流
- */
- public static final int MSG_ERROR_CLOSE_FILE = -7;
- public MP3Recorder() {
- this.sampleRate = 8000;
- }
- /**
- * 开片
- */
- public void start() {
- if (isRecording) {
- return;
- }
- new Thread() {
- @Override
- public void run() {
- String fileDir = StorageUtil.getSDPath() + "LameMP3/Voice/";
- File dir = new File(fileDir);
- if (!dir.exists()) {
- dir.mkdirs();
- }
- mFilePath = StorageUtil.getSDPath() + "LameMP3/Voice/"
- + System.currentTimeMillis() + ".mp3";
- System.out.println(mFilePath);
- android.os.Process
- .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
- // 根据定义好的几个配置,来获取合适的缓冲大小
- final int minBufferSize = AudioRecord.getMinBufferSize(
- sampleRate, AudioFormat.CHANNEL_IN_MONO,
- AudioFormat.ENCODING_PCM_16BIT);
- if (minBufferSize < 0) {
- if (handler != null) {
- handler.sendEmptyMessage(MSG_ERROR_GET_MIN_BUFFERSIZE);
- }
- return;
- }
- AudioRecord audioRecord = new AudioRecord(
- MediaRecorder.AudioSource.MIC, sampleRate,
- AudioFormat.CHANNEL_IN_MONO,
- AudioFormat.ENCODING_PCM_16BIT, minBufferSize * 2);
- // 5秒的缓冲
- short[] buffer = new short[sampleRate * (16 / 8) * 1 * 5];
- byte[] mp3buffer = new byte[(int) (7200 + buffer.length * 2 * 1.25)];
- FileOutputStream output = null;
- try {
- output = new FileOutputStream(new File(mFilePath));
- } catch (FileNotFoundException e) {
- if (handler != null) {
- handler.sendEmptyMessage(MSG_ERROR_CREATE_FILE);
- }
- return;
- }
- MP3Recorder.init(sampleRate, 1, sampleRate, 32);
- isRecording = true; // 录音状态
- isPause = false; // 录音状态
- try {
- try {
- audioRecord.startRecording(); // 开启录音获取音频数据
- } catch (IllegalStateException e) {
- // 不给录音...
- if (handler != null) {
- handler.sendEmptyMessage(MSG_ERROR_REC_START);
- }
- return;
- }
- try {
- // 开始录音
- if (handler != null) {
- handler.sendEmptyMessage(MSG_REC_STARTED);
- }
- int readSize = 0;
- boolean pause = false;
- while (isRecording) {
- /*--暂停--*/
- if (isPause) {
- if (!pause) {
- handler.sendEmptyMessage(MSG_REC_PAUSE);
- pause = true;
- }
- continue;
- }
- if (pause) {
- handler.sendEmptyMessage(MSG_REC_RESTORE);
- pause = false;
- }
- /*--End--*/
- /*--实时录音写数据--*/
- readSize = audioRecord.read(buffer, 0,
- minBufferSize);
- if (readSize < 0) {
- if (handler != null) {
- handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD);
- }
- break;
- } else if (readSize == 0) {
- ;
- } else {
- int encResult = MP3Recorder.encode(buffer,
- buffer, readSize, mp3buffer);
- if (encResult < 0) {
- if (handler != null) {
- handler.sendEmptyMessage(MSG_ERROR_AUDIO_ENCODE);
- }
- break;
- }
- if (encResult != 0) {
- try {
- output.write(mp3buffer, 0, encResult);
- } catch (IOException e) {
- if (handler != null) {
- handler.sendEmptyMessage(MSG_ERROR_WRITE_FILE);
- }
- break;
- }
- }
- }
- /*--End--*/
- }
- /*--录音完--*/
- int flushResult = MP3Recorder.flush(mp3buffer);
- if (flushResult < 0) {
- if (handler != null) {
- handler.sendEmptyMessage(MSG_ERROR_AUDIO_ENCODE);
- }
- }
- if (flushResult != 0) {
- try {
- output.write(mp3buffer, 0, flushResult);
- } catch (IOException e) {
- if (handler != null) {
- handler.sendEmptyMessage(MSG_ERROR_WRITE_FILE);
- }
- }
- }
- try {
- output.close();
- } catch (IOException e) {
- if (handler != null) {
- handler.sendEmptyMessage(MSG_ERROR_CLOSE_FILE);
- }
- }
- /*--End--*/
- } finally {
- audioRecord.stop();
- audioRecord.release();
- }
- } finally {
- MP3Recorder.close();
- isRecording = false;
- }
- if (handler != null) {
- handler.sendEmptyMessage(MSG_REC_STOPPED);
- }
- }
- }.start();
- }
- public void stop() {
- isRecording = false;
- }
- public void pause() {
- isPause = true;
- }
- public void restore() {
- isPause = false;
- }
- public boolean isRecording() {
- return isRecording;
- }
- public boolean isPaus() {
- if (!isRecording) {
- return false;
- }
- return isPause;
- }
- public String getFilePath() {
- return mFilePath;
- }
- /**
- * 录音状态管理
- *
- * @see RecMicToMp3#MSG_REC_STARTED
- * @see RecMicToMp3#MSG_REC_STOPPED
- * @see RecMicToMp3#MSG_REC_PAUSE
- * @see RecMicToMp3#MSG_REC_RESTORE
- * @see RecMicToMp3#MSG_ERROR_GET_MIN_BUFFERSIZE
- * @see RecMicToMp3#MSG_ERROR_CREATE_FILE
- * @see RecMicToMp3#MSG_ERROR_REC_START
- * @see RecMicToMp3#MSG_ERROR_AUDIO_RECORD
- * @see RecMicToMp3#MSG_ERROR_AUDIO_ENCODE
- * @see RecMicToMp3#MSG_ERROR_WRITE_FILE
- * @see RecMicToMp3#MSG_ERROR_CLOSE_FILE
- */
- public void setHandle(Handler handler) {
- this.handler = handler;
- }
- /*--以下为Native部分--*/
- static {
- System.loadLibrary("mp3lame");
- }
- /**
- * 初始化录制参数
- */
- public static void init(int inSamplerate, int outChannel,
- int outSamplerate, int outBitrate) {
- init(inSamplerate, outChannel, outSamplerate, outBitrate, 7);
- }
- /**
- * 初始化录制参数 quality:0=很好很慢 9=很差很快
- */
- public native static void init(int inSamplerate, int outChannel,
- int outSamplerate, int outBitrate, int quality);
- /**
- * 音频数据编码(PCM左进,PCM右进,MP3输出)
- */
- public native static int encode(short[] buffer_l, short[] buffer_r,
- int samples, byte[] mp3buf);
- /**
- * 刷干净缓冲区
- */
- public native static int flush(byte[] mp3buf);
- /**
- * 结束编码
- */
- public native static void close();
- }
5 在JNI文件夹下创建com_example_lamemp3_MP3Recorder.h头文件,在里面定义几个方法,然后在
com_example_lamemp3_MP3Recorder.c中实现。
com_example_lamemp3_MP3Recorder.h:
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class com_kubility_demo_MP3Recorder */
- #ifndef _Included_com_example_lamemp3_MP3Recorder
- #define _Included_com_example_lamemp3_MP3Recorder
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: com_kubility_demo_MP3Recorder
- * Method: init
- * Signature: (IIIII)V
- */
- JNIEXPORT void JNICALL Java_com_example_lamemp3_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_example_lamemp3_MP3Recorder_encode
- (JNIEnv *, jclass, jshortArray, jshortArray, jint, jbyteArray);
- /*
- * Class: com_kubility_demo_MP3Recorder
- * Method: flush
- * Signature: ([B)I
- */
- JNIEXPORT jint JNICALL Java_com_example_lamemp3_MP3Recorder_flush
- (JNIEnv *, jclass, jbyteArray);
- /*
- * Class: com_kubility_demo_MP3Recorder
- * Method: close
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_com_example_lamemp3_MP3Recorder_close
- (JNIEnv *, jclass);
- #ifdef __cplusplus
- }
- #endif
- #endif
com_example_lamemp3_MP3Recorder.c:
- #include "lame-3.99.5_libmp3lame/lame.h"
- #include "com_example_lamemp3_MP3Recorder.h"
- static lame_global_flags *glf = NULL;
- JNIEXPORT void JNICALL Java_com_example_lamemp3_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_example_lamemp3_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_example_lamemp3_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_example_lamemp3_MP3Recorder_close(
- JNIEnv *env, jclass cls) {
- lame_close(glf);
- glf = NULL;
- }
6 创建Android.mk,注意把com_example_lamemp3_MP3Recorder.c添加进去。
- 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_example_lamemp3_MP3Recorder.c
- include $(BUILD_SHARED_LIBRARY)
7 AndroidManifest.xml添加权限。
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.RECORD_AUDIO" />
8 创建Builder后(上篇中有详细介绍,这里不赘述),Clean,报错:
- In file included from jni/lame-3.99.5_libmp3lame/bitstream.c:36:0:
- jni/lame-3.99.5_libmp3lame/util.h:574:5: error: unknown type name 'ieee754_float32_t'
- jni/lame-3.99.5_libmp3lame/util.h:574:40: error: unknown type name 'ieee754_float32_t'
- make.exe: *** [obj/local/armeabi/objs/mp3lame/lame-3.99.5_libmp3lame/bitstream.o] Error 1
打开util.h
把 574行的 extern ieee754_float32_t fast_log2(ieee754_float32_t x);替换成extern float fast_log2(float x);
再次Clean,又报错:
- jni/lame-3.99.5_libmp3lame/fft.c:47:32: fatal error: vector/lame_intrin.h: No such file or directory
- compilation terminated.
- make.exe: *** [obj/local/armeabi/objs/mp3lame/lame-3.99.5_libmp3lame/fft.o] Error 1
打开fft.c删除47行#include "vector/lame_intrin.h"
Clean还报错:
- In file included from jni/lame-3.99.5_libmp3lame/presets.c:29:0:
- jni/lame-3.99.5_libmp3lame/set_get.h:24:18: fatal error: lame.h: No such file or directory
- compilation terminated.
- make.exe: *** [obj/local/armeabi/objs/mp3lame/lame-3.99.5_libmp3lame/presets.o] Error 1
打开set_get.h,删除24行,#include <lame.h>
再次Clean,成功:
- [armeabi] Compile thumb : mp3lame <= version.c
- [armeabi] Compile thumb : mp3lame <= com_example_lamemp3_MP3Recorder.c
- [armeabi] SharedLibrary : libmp3lame.so
- [armeabi] Install : libmp3lame.so => libs/armeabi/libmp3lame.so