给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

  1. public class MP3Recorder {
  2. private String mFilePath = null;
  3. private int sampleRate = 0;
  4. private boolean isRecording = false;
  5. private boolean isPause = false;
  6. private Handler handler = null;
  7. /**
  8. * 开始录音
  9. */
  10. public static final int MSG_REC_STARTED = 1;
  11. /**
  12. * 结束录音
  13. */
  14. public static final int MSG_REC_STOPPED = 2;
  15. /**
  16. * 暂停录音
  17. */
  18. public static final int MSG_REC_PAUSE = 3;
  19. /**
  20. * 继续录音
  21. */
  22. public static final int MSG_REC_RESTORE = 4;
  23. /**
  24. * 缓冲区挂了,采样率手机不支持
  25. */
  26. public static final int MSG_ERROR_GET_MIN_BUFFERSIZE = -1;
  27. /**
  28. * 创建文件时扑街了
  29. */
  30. public static final int MSG_ERROR_CREATE_FILE = -2;
  31. /**
  32. * 初始化录音器时扑街了
  33. */
  34. public static final int MSG_ERROR_REC_START = -3;
  35. /**
  36. * 录音的时候出错
  37. */
  38. public static final int MSG_ERROR_AUDIO_RECORD = -4;
  39. /**
  40. * 编码时挂了
  41. */
  42. public static final int MSG_ERROR_AUDIO_ENCODE = -5;
  43. /**
  44. * 写文件时挂了
  45. */
  46. public static final int MSG_ERROR_WRITE_FILE = -6;
  47. /**
  48. * 没法关闭文件流
  49. */
  50. public static final int MSG_ERROR_CLOSE_FILE = -7;
  51. public MP3Recorder() {
  52. this.sampleRate = 8000;
  53. }
  54. /**
  55. * 开片
  56. */
  57. public void start() {
  58. if (isRecording) {
  59. return;
  60. }
  61. new Thread() {
  62. @Override
  63. public void run() {
  64. String fileDir = StorageUtil.getSDPath() + "LameMP3/Voice/";
  65. File dir = new File(fileDir);
  66. if (!dir.exists()) {
  67. dir.mkdirs();
  68. }
  69. mFilePath = StorageUtil.getSDPath() + "LameMP3/Voice/"
  70. + System.currentTimeMillis() + ".mp3";
  71. System.out.println(mFilePath);
  72. android.os.Process
  73. .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
  74. // 根据定义好的几个配置,来获取合适的缓冲大小
  75. final int minBufferSize = AudioRecord.getMinBufferSize(
  76. sampleRate, AudioFormat.CHANNEL_IN_MONO,
  77. AudioFormat.ENCODING_PCM_16BIT);
  78. if (minBufferSize < 0) {
  79. if (handler != null) {
  80. handler.sendEmptyMessage(MSG_ERROR_GET_MIN_BUFFERSIZE);
  81. }
  82. return;
  83. }
  84. AudioRecord audioRecord = new AudioRecord(
  85. MediaRecorder.AudioSource.MIC, sampleRate,
  86. AudioFormat.CHANNEL_IN_MONO,
  87. AudioFormat.ENCODING_PCM_16BIT, minBufferSize * 2);
  88. // 5秒的缓冲
  89. short[] buffer = new short[sampleRate * (16 / 8) * 1 * 5];
  90. byte[] mp3buffer = new byte[(int) (7200 + buffer.length * 2 * 1.25)];
  91. FileOutputStream output = null;
  92. try {
  93. output = new FileOutputStream(new File(mFilePath));
  94. } catch (FileNotFoundException e) {
  95. if (handler != null) {
  96. handler.sendEmptyMessage(MSG_ERROR_CREATE_FILE);
  97. }
  98. return;
  99. }
  100. MP3Recorder.init(sampleRate, 1, sampleRate, 32);
  101. isRecording = true; // 录音状态
  102. isPause = false; // 录音状态
  103. try {
  104. try {
  105. audioRecord.startRecording(); // 开启录音获取音频数据
  106. } catch (IllegalStateException e) {
  107. // 不给录音...
  108. if (handler != null) {
  109. handler.sendEmptyMessage(MSG_ERROR_REC_START);
  110. }
  111. return;
  112. }
  113. try {
  114. // 开始录音
  115. if (handler != null) {
  116. handler.sendEmptyMessage(MSG_REC_STARTED);
  117. }
  118. int readSize = 0;
  119. boolean pause = false;
  120. while (isRecording) {
  121. /*--暂停--*/
  122. if (isPause) {
  123. if (!pause) {
  124. handler.sendEmptyMessage(MSG_REC_PAUSE);
  125. pause = true;
  126. }
  127. continue;
  128. }
  129. if (pause) {
  130. handler.sendEmptyMessage(MSG_REC_RESTORE);
  131. pause = false;
  132. }
  133. /*--End--*/
  134. /*--实时录音写数据--*/
  135. readSize = audioRecord.read(buffer, 0,
  136. minBufferSize);
  137. if (readSize < 0) {
  138. if (handler != null) {
  139. handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD);
  140. }
  141. break;
  142. } else if (readSize == 0) {
  143. ;
  144. } else {
  145. int encResult = MP3Recorder.encode(buffer,
  146. buffer, readSize, mp3buffer);
  147. if (encResult < 0) {
  148. if (handler != null) {
  149. handler.sendEmptyMessage(MSG_ERROR_AUDIO_ENCODE);
  150. }
  151. break;
  152. }
  153. if (encResult != 0) {
  154. try {
  155. output.write(mp3buffer, 0, encResult);
  156. } catch (IOException e) {
  157. if (handler != null) {
  158. handler.sendEmptyMessage(MSG_ERROR_WRITE_FILE);
  159. }
  160. break;
  161. }
  162. }
  163. }
  164. /*--End--*/
  165. }
  166. /*--录音完--*/
  167. int flushResult = MP3Recorder.flush(mp3buffer);
  168. if (flushResult < 0) {
  169. if (handler != null) {
  170. handler.sendEmptyMessage(MSG_ERROR_AUDIO_ENCODE);
  171. }
  172. }
  173. if (flushResult != 0) {
  174. try {
  175. output.write(mp3buffer, 0, flushResult);
  176. } catch (IOException e) {
  177. if (handler != null) {
  178. handler.sendEmptyMessage(MSG_ERROR_WRITE_FILE);
  179. }
  180. }
  181. }
  182. try {
  183. output.close();
  184. } catch (IOException e) {
  185. if (handler != null) {
  186. handler.sendEmptyMessage(MSG_ERROR_CLOSE_FILE);
  187. }
  188. }
  189. /*--End--*/
  190. } finally {
  191. audioRecord.stop();
  192. audioRecord.release();
  193. }
  194. } finally {
  195. MP3Recorder.close();
  196. isRecording = false;
  197. }
  198. if (handler != null) {
  199. handler.sendEmptyMessage(MSG_REC_STOPPED);
  200. }
  201. }
  202. }.start();
  203. }
  204. public void stop() {
  205. isRecording = false;
  206. }
  207. public void pause() {
  208. isPause = true;
  209. }
  210. public void restore() {
  211. isPause = false;
  212. }
  213. public boolean isRecording() {
  214. return isRecording;
  215. }
  216. public boolean isPaus() {
  217. if (!isRecording) {
  218. return false;
  219. }
  220. return isPause;
  221. }
  222. public String getFilePath() {
  223. return mFilePath;
  224. }
  225. /**
  226. * 录音状态管理
  227. *
  228. * @see RecMicToMp3#MSG_REC_STARTED
  229. * @see RecMicToMp3#MSG_REC_STOPPED
  230. * @see RecMicToMp3#MSG_REC_PAUSE
  231. * @see RecMicToMp3#MSG_REC_RESTORE
  232. * @see RecMicToMp3#MSG_ERROR_GET_MIN_BUFFERSIZE
  233. * @see RecMicToMp3#MSG_ERROR_CREATE_FILE
  234. * @see RecMicToMp3#MSG_ERROR_REC_START
  235. * @see RecMicToMp3#MSG_ERROR_AUDIO_RECORD
  236. * @see RecMicToMp3#MSG_ERROR_AUDIO_ENCODE
  237. * @see RecMicToMp3#MSG_ERROR_WRITE_FILE
  238. * @see RecMicToMp3#MSG_ERROR_CLOSE_FILE
  239. */
  240. public void setHandle(Handler handler) {
  241. this.handler = handler;
  242. }
  243. /*--以下为Native部分--*/
  244. static {
  245. System.loadLibrary("mp3lame");
  246. }
  247. /**
  248. * 初始化录制参数
  249. */
  250. public static void init(int inSamplerate, int outChannel,
  251. int outSamplerate, int outBitrate) {
  252. init(inSamplerate, outChannel, outSamplerate, outBitrate, 7);
  253. }
  254. /**
  255. * 初始化录制参数 quality:0=很好很慢 9=很差很快
  256. */
  257. public native static void init(int inSamplerate, int outChannel,
  258. int outSamplerate, int outBitrate, int quality);
  259. /**
  260. * 音频数据编码(PCM左进,PCM右进,MP3输出)
  261. */
  262. public native static int encode(short[] buffer_l, short[] buffer_r,
  263. int samples, byte[] mp3buf);
  264. /**
  265. * 刷干净缓冲区
  266. */
  267. public native static int flush(byte[] mp3buf);
  268. /**
  269. * 结束编码
  270. */
  271. public native static void close();
  272. }

5  在JNI文件夹下创建com_example_lamemp3_MP3Recorder.h头文件,在里面定义几个方法,然后在
com_example_lamemp3_MP3Recorder.c中实现。

com_example_lamemp3_MP3Recorder.h:

  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class com_kubility_demo_MP3Recorder */
  4. #ifndef _Included_com_example_lamemp3_MP3Recorder
  5. #define _Included_com_example_lamemp3_MP3Recorder
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     com_kubility_demo_MP3Recorder
  11. * Method:    init
  12. * Signature: (IIIII)V
  13. */
  14. JNIEXPORT void JNICALL Java_com_example_lamemp3_MP3Recorder_init
  15. (JNIEnv *, jclass, jint, jint, jint, jint, jint);
  16. /*
  17. * Class:     com_kubility_demo_MP3Recorder
  18. * Method:    encode
  19. * Signature: ([S[SI[B)I
  20. */
  21. JNIEXPORT jint JNICALL Java_com_example_lamemp3_MP3Recorder_encode
  22. (JNIEnv *, jclass, jshortArray, jshortArray, jint, jbyteArray);
  23. /*
  24. * Class:     com_kubility_demo_MP3Recorder
  25. * Method:    flush
  26. * Signature: ([B)I
  27. */
  28. JNIEXPORT jint JNICALL Java_com_example_lamemp3_MP3Recorder_flush
  29. (JNIEnv *, jclass, jbyteArray);
  30. /*
  31. * Class:     com_kubility_demo_MP3Recorder
  32. * Method:    close
  33. * Signature: ()V
  34. */
  35. JNIEXPORT void JNICALL Java_com_example_lamemp3_MP3Recorder_close
  36. (JNIEnv *, jclass);
  37. #ifdef __cplusplus
  38. }
  39. #endif
  40. #endif

com_example_lamemp3_MP3Recorder.c:

  1. #include "lame-3.99.5_libmp3lame/lame.h"
  2. #include "com_example_lamemp3_MP3Recorder.h"
  3. static lame_global_flags *glf = NULL;
  4. JNIEXPORT void JNICALL Java_com_example_lamemp3_MP3Recorder_init(
  5. JNIEnv *env, jclass cls, jint inSamplerate, jint outChannel,
  6. jint outSamplerate, jint outBitrate, jint quality) {
  7. if (glf != NULL) {
  8. lame_close(glf);
  9. glf = NULL;
  10. }
  11. glf = lame_init();
  12. lame_set_in_samplerate(glf, inSamplerate);
  13. lame_set_num_channels(glf, outChannel);
  14. lame_set_out_samplerate(glf, outSamplerate);
  15. lame_set_brate(glf, outBitrate);
  16. lame_set_quality(glf, quality);
  17. lame_init_params(glf);
  18. }
  19. JNIEXPORT jint JNICALL Java_com_example_lamemp3_MP3Recorder_encode(
  20. JNIEnv *env, jclass cls, jshortArray buffer_l, jshortArray buffer_r,
  21. jint samples, jbyteArray mp3buf) {
  22. jshort* j_buffer_l = (*env)->GetShortArrayElements(env, buffer_l, NULL);
  23. jshort* j_buffer_r = (*env)->GetShortArrayElements(env, buffer_r, NULL);
  24. const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
  25. jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
  26. int result = lame_encode_buffer(glf, j_buffer_l, j_buffer_r,
  27. samples, j_mp3buf, mp3buf_size);
  28. (*env)->ReleaseShortArrayElements(env, buffer_l, j_buffer_l, 0);
  29. (*env)->ReleaseShortArrayElements(env, buffer_r, j_buffer_r, 0);
  30. (*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
  31. return result;
  32. }
  33. JNIEXPORT jint JNICALL Java_com_example_lamemp3_MP3Recorder_flush(
  34. JNIEnv *env, jclass cls, jbyteArray mp3buf) {
  35. const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
  36. jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
  37. int result = lame_encode_flush(glf, j_mp3buf, mp3buf_size);
  38. (*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
  39. return result;
  40. }
  41. JNIEXPORT void JNICALL Java_com_example_lamemp3_MP3Recorder_close(
  42. JNIEnv *env, jclass cls) {
  43. lame_close(glf);
  44. glf = NULL;
  45. }

6  创建Android.mk,注意把com_example_lamemp3_MP3Recorder.c添加进去。

  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LAME_LIBMP3_DIR := lame-3.99.5_libmp3lame
  4. LOCAL_MODULE    := mp3lame
  5. 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
  6. include $(BUILD_SHARED_LIBRARY)

7  AndroidManifest.xml添加权限。

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

8  创建Builder后(上篇中有详细介绍,这里不赘述),Clean,报错:

  1. In file included from jni/lame-3.99.5_libmp3lame/bitstream.c:36:0:
  2. jni/lame-3.99.5_libmp3lame/util.h:574:5: error: unknown type name 'ieee754_float32_t'
  3. jni/lame-3.99.5_libmp3lame/util.h:574:40: error: unknown type name 'ieee754_float32_t'
  4. 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,又报错:

  1. jni/lame-3.99.5_libmp3lame/fft.c:47:32: fatal error: vector/lame_intrin.h: No such file or directory
  2. compilation terminated.
  3. 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还报错:

  1. In file included from jni/lame-3.99.5_libmp3lame/presets.c:29:0:
  2. jni/lame-3.99.5_libmp3lame/set_get.h:24:18: fatal error: lame.h: No such file or directory
  3. compilation terminated.
  4. make.exe: *** [obj/local/armeabi/objs/mp3lame/lame-3.99.5_libmp3lame/presets.o] Error 1

打开set_get.h,删除24行,#include <lame.h>

再次Clean,成功:

    1. [armeabi] Compile thumb  : mp3lame <= version.c
    2. [armeabi] Compile thumb  : mp3lame <= com_example_lamemp3_MP3Recorder.c
    3. [armeabi] SharedLibrary  : libmp3lame.so
    4. [armeabi] Install        : libmp3lame.so => libs/armeabi/libmp3lame.so

Android MP3录音实现的更多相关文章

  1. Android实例-录音与回放(播放MP3)(XE8+小米2)

    结果: 1.增加ActionList中的Action时,需要跳到Master界面,不能在Android4Phonel界面下. 2.如果不打开权限的话,会提示“该设备不支持停止录音操作”(Record ...

  2. 如何将MP3录音转文字

    相信很多人都有电话录音的习惯,因为这样可以记录下很多重要的信息.那么当我们通过录音将一些重要的信息记录下来后,我们应该怎样将这些录音文件转换成文字进行记录呢?下面我们就一起来看一下吧. 操作步骤: 步 ...

  3. 移动端实现HTML5 mp3录音踩坑指南:系统播放音量变小、一些机型录音断断续续 之 MediaRecorder和AudioWorklet的终极对决

    目录 H5录音见坑填坑 采用MediaRecorder采集音频 音频格式:WebM和PCM 从WebM封装容器中提取PCM数据 录音的兼容性 困扰已久的H5录音时系统播放音量变小的问题 H5录音见坑填 ...

  4. 【Android】 Android实现录音、播音、录制视频功能

    智能手机操作系统IOS与Android平分天下(PS:WP与其他的直接无视了),而Android的免费招来了一大堆厂商分分向Android示好,故Android可能会有“较好”的前景. Android ...

  5. 微信小程序语音识别服务搭建全过程解析(https api开放,支持新接口mp3录音、老接口silk录音)

    silk v3(或新录音接口mp3)录音转olami语音识别和语义处理的api服务(ubuntu16.04服务器上实现) 重要的写在前面 重要事项一: 所有相关更新,我优先更新到我个人博客中,其它地方 ...

  6. Android 实时录音和回放,边录音边播放 (KTV回音效果)

    上一篇介绍了如何使用Mediarecorder来录音,以及播放录音.不过并没有达到我的目的,一边录音一边播放.今天就讲解一下如何一边录音一边播放.使用AndioRecord录音和使用AudioTrac ...

  7. Android之录音工具类

    /** * 录音工具类 * * @author rendongwei * */ public class RecordUtil { private static final int SAMPLE_RA ...

  8. Android实现录音的方法(最重要的是对MediaRecorder的试用方法)

    package cn.eoe.record; import java.io.File; import java.io.IOException; import android.app.Activity; ...

  9. Android平台录音音量计的实现

    今天博主要给大家分享的是怎样在Android平台上实现录音时的音量指示计.开门见山.先来看一张Demo的效果图: 如上图所看到的,两个button各自是開始录音和停止录音,中间的两个数字前后分别代表音 ...

随机推荐

  1. EBay .Net SDK Api 实践

        1.请求流程介绍   提供SOA地址:https://api.ebay.com/wsapi   WSDL生成的代码在WebService.cs文件当中.   ApiCall封装了所有的RPC, ...

  2. DedeCMS提交自定义表单加入验证码功能

    织梦dedecms网站给自定义表单加dede模板自带验证码的方法. 有时候,我们因为需要给dede网站的自定义表单添加验证码,防止恶意填写表单提交. 我们可以用以下的方法来实现: 首先,我们要找到 / ...

  3. 424 - Integer Inquiry

     Integer Inquiry  One of the first users of BIT's new supercomputer was Chip Diller. He extended his ...

  4. ACM ICPC 2015 Moscow Subregional Russia, Moscow, Dolgoprudny, October, 18, 2015 K. King’s Rout

    K. King's Rout time limit per test 4 seconds memory limit per test 512 megabytes input standard inpu ...

  5. BZOJ 1001 题解

    1001: [BeiJing2006]狼抓兔子 Time Limit: 15 Sec  Memory Limit: 162 MBSubmit: 18876  Solved: 4649[Submit][ ...

  6. 【BZOJ】2194: 快速傅立叶之二

    http://www.lydsy.com/JudgeOnline/problem.php?id=2194 题意:求$c[k]=\sum_{k<=i<n} a[i]b[i-k], n< ...

  7. SRM 595 DIV2 1000

    数位DP的感觉,但是跟模版不是一个套路的,看的题解,代码好理解,但是确实难想. #include <cstdio> #include <cstring> #include &l ...

  8. 详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]

    目录 前言 源码分析 重要接口介绍 SpringMVC初始化的时候做了什么 HandlerExecutionChain的获取 实例 资源文件映射 总结 参考资料 前言 SpringMVC是目前主流的W ...

  9. springmvc项目中java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener

    java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener 严重: Error co ...

  10. 接口测试之soupui&groovy

    原著地址:http://www.cnblogs.com/wade-xu/p/4236295.html#3334654 需注意下方code的设置