您的位置:首页 > 移动开发 > Android开发

Android MP3录音实现

2015-04-17 14:30 369 查看
给APP做语音功能,必须考虑到IOS和Android平台的通用性。wav录音质量高,文件太大,AAC和AMR格式在IOS平台却不支持,所以采用libmp3lame把AudioRecord音频流直接转换成MP3格式。

声明一下,代码参考了http://blog.csdn.net/cboy017/article/details/8455629,这里只是借花献佛,把整个流程写得更详细。

这里采用的是最新的lame-3.99.5.tar。


可以去Lame官网下载,博文最后也有CSDN下载地址。官网地址: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


录音后文件发送至iPhone,播放OK……

lame-3.99.5下载地址:http://download.csdn.net/detail/yalinfendou/8604729

Android
MP3 DEMO下载地址:http://download.csdn.net/detail/yalinfendou/8604759
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: