查看: 467|回复: 0

[Android教程] Android使用AudioRecord实现暂停录音功能实例代码

发表于 2017-11-28 08:00:01

题外话:发现好久都没有上来写博文了,毕业设计加上公司暂时没有Android的项目做,只能去自学web上的知识,摸爬打滚到现在,花了一个多月时间根据公司的现有模板做了公司内部一个任务管理系统,感觉都是比较浅的知识,没什么可以写的。想到之前做的语音识别的项目,虽然现在没什么下文了,但是谁懂~~~将来呢?

言归正传,项目长这样子:

设计的思路:

由于自带的AudioRecord没有pauseRecord()方法,我把开始录音-->(暂停/继续录音)...-->停止录音叫做一次录音,点击一次暂停就会产生一个文件(.pcm),将一次录音产生的所有文件名(.pcm)用一个list装起来,点击停止后将遍历list取得所有文件路径进行拼接。

由于考虑到以后可能要进行语音识别,所以对程序的灵活性和拓展性都做了相应的处理,可以通过setListener()监听录音的音频流和监听录音结束。

采用线程池对线程进行管理,减少系统开销。

对类的说明:

  1. AudioRecorder:封装了录音的方法:创建录音对象、开始、暂停、停止、取消,使用静态枚举类Status来记录录音的状态。
  2. FileUtils:文件工具类,用于文件路径的获取
  3. PcmToWav:封装了将.pcm文件转化.wav文件的方法
  4. WaveHeader: wav文件头
  5. RecordStreamListener:监听录音音频流,用于拓展业务的处理

接下来是关键代码部分:
1、AudioRecorder类:

  1. package com.hxl.pauserecord.record;
  2. import android.media.AudioFormat;
  3. import android.media.AudioRecord;
  4. import android.media.MediaRecorder;
  5. import android.text.TextUtils;
  6. import android.util.Log;
  7. import java.io.File;
  8. import java.io.FileNotFoundException;
  9. import java.io.FileOutputStream;
  10. import java.io.IOException;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. import java.util.concurrent.ExecutorService;
  14. import java.util.concurrent.Executors;
  15. /**
  16. * Created by HXL on 16/8/11.
  17. * 用于实现录音 暂停录音
  18. */
  19. public class AudioRecorder {
  20. //音频输入-麦克风
  21. private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;
  22. //采用频率
  23. //44100是目前的标准,但是某些设备仍然支持22050,16000,11025
  24. //采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
  25. private final static int AUDIO_SAMPLE_RATE = 16000;
  26. //声道 单声道
  27. private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO;
  28. //编码
  29. private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
  30. // 缓冲区字节大小
  31. private int bufferSizeInBytes = 0;
  32. //录音对象
  33. private AudioRecord audioRecord;
  34. //录音状态
  35. private Status status = Status.STATUS_NO_READY;
  36. //文件名
  37. private String fileName;
  38. //录音文件
  39. private List<String> filesName = new ArrayList<>();
  40. //线程池
  41. private ExecutorService mExecutorService;
  42. //录音监听
  43. private RecordStreamListener listener;
  44. public AudioRecorder() {
  45. mExecutorService = Executors.newCachedThreadPool();
  46. }
  47. /**
  48. * 创建录音对象
  49. */
  50. public void createAudio(String fileName, int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
  51. // 获得缓冲区字节大小
  52. bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
  53. channelConfig, channelConfig);
  54. audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
  55. this.fileName = fileName;
  56. }
  57. /**
  58. * 创建默认的录音对象
  59. *
  60. * @param fileName 文件名
  61. */
  62. public void createDefaultAudio(String fileName) {
  63. // 获得缓冲区字节大小
  64. bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE,
  65. AUDIO_CHANNEL, AUDIO_ENCODING);
  66. audioRecord = new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes);
  67. this.fileName = fileName;
  68. status = Status.STATUS_READY;
  69. }
  70. /**
  71. * 开始录音
  72. *
  73. */
  74. public void startRecord() {
  75. if (status == Status.STATUS_NO_READY||audioRecord==null) {
  76. throw new IllegalStateException("录音尚未初始化,请检查是否禁止了录音权限~");
  77. }
  78. if (status == Status.STATUS_START) {
  79. throw new IllegalStateException("正在录音");
  80. }
  81. Log.d("AudioRecorder", "===startRecord===" + audioRecord.getState());
  82. audioRecord.startRecording();
  83. String currentFileName = fileName;
  84. if (status == Status.STATUS_PAUSE) {
  85. //假如是暂停录音 将文件名后面加个数字,防止重名文件内容被覆盖
  86. currentFileName += filesName.size();
  87. }
  88. filesName.add(currentFileName);
  89. final String finalFileName=currentFileName;
  90. //将录音状态设置成正在录音状态
  91. status = Status.STATUS_START;
  92. //使用线程池管理线程
  93. mExecutorService.execute(new Runnable() {
  94. @Override
  95. public void run() {
  96. writeDataTOFile(finalFileName);
  97. }
  98. });
  99. }
  100. /**
  101. * 暂停录音
  102. */
  103. public void pauseRecord() {
  104. Log.d("AudioRecorder", "===pauseRecord===");
  105. if (status != Status.STATUS_START) {
  106. throw new IllegalStateException("没有在录音");
  107. } else {
  108. audioRecord.stop();
  109. status = Status.STATUS_PAUSE;
  110. }
  111. }
  112. /**
  113. * 停止录音
  114. */
  115. public void stopRecord() {
  116. Log.d("AudioRecorder", "===stopRecord===");
  117. if (status == Status.STATUS_NO_READY || status == Status.STATUS_READY) {
  118. throw new IllegalStateException("录音尚未开始");
  119. } else {
  120. audioRecord.stop();
  121. status = Status.STATUS_STOP;
  122. release();
  123. }
  124. }
  125. /**
  126. * 释放资源
  127. */
  128. public void release() {
  129. Log.d("AudioRecorder", "===release===");
  130. //假如有暂停录音
  131. try {
  132. if (filesName.size() > 0) {
  133. List<String> filePaths = new ArrayList<>();
  134. for (String fileName : filesName) {
  135. filePaths.add(FileUtils.getPcmFileAbsolutePath(fileName));
  136. }
  137. //清除
  138. filesName.clear();
  139. //将多个pcm文件转化为wav文件
  140. mergePCMFilesToWAVFile(filePaths);
  141. } else {
  142. //这里由于只要录音过filesName.size都会大于0,没录音时fileName为null
  143. //会报空指针 NullPointerException
  144. // 将单个pcm文件转化为wav文件
  145. //Log.d("AudioRecorder", "=====makePCMFileToWAVFile======");
  146. //makePCMFileToWAVFile();
  147. }
  148. } catch (IllegalStateException e) {
  149. throw new IllegalStateException(e.getMessage());
  150. }
  151. if (audioRecord != null) {
  152. audioRecord.release();
  153. audioRecord = null;
  154. }
  155. status = Status.STATUS_NO_READY;
  156. }
  157. /**
  158. * 取消录音
  159. */
  160. public void canel() {
  161. filesName.clear();
  162. fileName = null;
  163. if (audioRecord != null) {
  164. audioRecord.release();
  165. audioRecord = null;
  166. }
  167. status = Status.STATUS_NO_READY;
  168. }
  169. /**
  170. * 将音频信息写入文件
  171. *
  172. */
  173. private void writeDataTOFile(String currentFileName) {
  174. // new一个byte数组用来存一些字节数据,大小为缓冲区大小
  175. byte[] audiodata = new byte[bufferSizeInBytes];
  176. FileOutputStream fos = null;
  177. int readsize = 0;
  178. try {
  179. File file = new File(FileUtils.getPcmFileAbsolutePath(currentFileName));
  180. if (file.exists()) {
  181. file.delete();
  182. }
  183. fos = new FileOutputStream(file);// 建立一个可存取字节的文件
  184. } catch (IllegalStateException e) {
  185. Log.e("AudioRecorder", e.getMessage());
  186. throw new IllegalStateException(e.getMessage());
  187. } catch (FileNotFoundException e) {
  188. Log.e("AudioRecorder", e.getMessage());
  189. }
  190. while (status == Status.STATUS_START) {
  191. readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
  192. if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {
  193. try {
  194. fos.write(audiodata);
  195. if (listener != null) {
  196. //用于拓展业务
  197. listener.onRecording(audiodata, 0, audiodata.length);
  198. }
  199. } catch (IOException e) {
  200. Log.e("AudioRecorder", e.getMessage());
  201. }
  202. }
  203. }
  204. if (listener != null) {
  205. listener.finishRecord();
  206. }
  207. try {
  208. if (fos != null) {
  209. fos.close();// 关闭写入流
  210. }
  211. } catch (IOException e) {
  212. Log.e("AudioRecorder", e.getMessage());
  213. }
  214. }
  215. /**
  216. * 将pcm合并成wav
  217. *
  218. * @param filePaths
  219. */
  220. private void mergePCMFilesToWAVFile(final List<String> filePaths) {
  221. mExecutorService.execute(new Runnable() {
  222. @Override
  223. public void run() {
  224. if (PcmToWav.mergePCMFilesToWAVFile(filePaths, FileUtils.getWavFileAbsolutePath(fileName))) {
  225. //操作成功
  226. } else {
  227. //操作失败
  228. Log.e("AudioRecorder", "mergePCMFilesToWAVFile fail");
  229. throw new IllegalStateException("mergePCMFilesToWAVFile fail");
  230. }
  231. }
  232. });
  233. }
  234. /**
  235. * 将单个pcm文件转化为wav文件
  236. */
  237. private void makePCMFileToWAVFile() {
  238. mExecutorService.execute(new Runnable() {
  239. @Override
  240. public void run() {
  241. if (PcmToWav.makePCMFileToWAVFile(FileUtils.getPcmFileAbsolutePath(fileName), FileUtils.getWavFileAbsolutePath(fileName), true)) {
  242. //操作成功
  243. } else {
  244. //操作失败
  245. Log.e("AudioRecorder", "makePCMFileToWAVFile fail");
  246. throw new IllegalStateException("makePCMFileToWAVFile fail");
  247. }
  248. }
  249. });
  250. }
  251. /**
  252. * 录音对象的状态
  253. */
  254. public enum Status {
  255. //未开始
  256. STATUS_NO_READY,
  257. //预备
  258. STATUS_READY,
  259. //录音
  260. STATUS_START,
  261. //暂停
  262. STATUS_PAUSE,
  263. //停止
  264. STATUS_STOP
  265. }
  266. /**
  267. * 获取录音对象的状态
  268. *
  269. * @return
  270. */
  271. public Status getStatus() {
  272. return status;
  273. }
  274. /**
  275. * 获取本次录音文件的个数
  276. *
  277. * @return
  278. */
  279. public int getPcmFilesCount() {
  280. return filesName.size();
  281. }
  282. public RecordStreamListener getListener() {
  283. return listener;
  284. }
  285. public void setListener(RecordStreamListener listener) {
  286. this.listener = listener;
  287. }
  288. }
复制代码

2:PcmToWav

  1. package com.hxl.pauserecord.record;
  2. import android.util.Log;
  3. import java.io.BufferedInputStream;
  4. import java.io.BufferedOutputStream;
  5. import java.io.File;
  6. import java.io.FileInputStream;
  7. import java.io.FileNotFoundException;
  8. import java.io.FileOutputStream;
  9. import java.io.IOException;
  10. import java.io.InputStream;
  11. import java.io.OutputStream;
  12. import java.text.SimpleDateFormat;
  13. import java.util.ArrayList;
  14. import java.util.Date;
  15. import java.util.List;
  16. /**
  17. * Created by HXL on 16/8/11.
  18. * 将pcm文件转化为wav文件
  19. */
  20. public class PcmToWav {
  21. /**
  22. * 合并多个pcm文件为一个wav文件
  23. *
  24. * @param filePathList pcm文件路径集合
  25. * @param destinationPath 目标wav文件路径
  26. * @return true|false
  27. */
  28. public static boolean mergePCMFilesToWAVFile(List<String> filePathList,
  29. String destinationPath) {
  30. File[] file = new File[filePathList.size()];
  31. byte buffer[] = null;
  32. int TOTAL_SIZE = 0;
  33. int fileNum = filePathList.size();
  34. for (int i = 0; i < fileNum; i++) {
  35. file[i] = new File(filePathList.get(i));
  36. TOTAL_SIZE += file[i].length();
  37. }
  38. // 填入参数,比特率等等。这里用的是16位单声道 8000 hz
  39. WaveHeader header = new WaveHeader();
  40. // 长度字段 = 内容的大小(TOTAL_SIZE) +
  41. // 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
  42. header.fileLength = TOTAL_SIZE + (44 - 8);
  43. header.FmtHdrLeth = 16;
  44. header.BitsPerSample = 16;
  45. header.Channels = 2;
  46. header.FormatTag = 0x0001;
  47. header.SamplesPerSec = 8000;
  48. header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);
  49. header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
  50. header.DataHdrLeth = TOTAL_SIZE;
  51. byte[] h = null;
  52. try {
  53. h = header.getHeader();
  54. } catch (IOException e1) {
  55. Log.e("PcmToWav", e1.getMessage());
  56. return false;
  57. }
  58. if (h.length != 44) // WAV标准,头部应该是44字节,如果不是44个字节则不进行转换文件
  59. return false;
  60. //先删除目标文件
  61. File destfile = new File(destinationPath);
  62. if (destfile.exists())
  63. destfile.delete();
  64. //合成所有的pcm文件的数据,写到目标文件
  65. try {
  66. buffer = new byte[1024 * 4]; // Length of All Files, Total Size
  67. InputStream inStream = null;
  68. OutputStream ouStream = null;
  69. ouStream = new BufferedOutputStream(new FileOutputStream(
  70. destinationPath));
  71. ouStream.write(h, 0, h.length);
  72. for (int j = 0; j < fileNum; j++) {
  73. inStream = new BufferedInputStream(new FileInputStream(file[j]));
  74. int size = inStream.read(buffer);
  75. while (size != -1) {
  76. ouStream.write(buffer);
  77. size = inStream.read(buffer);
  78. }
  79. inStream.close();
  80. }
  81. ouStream.close();
  82. } catch (FileNotFoundException e) {
  83. Log.e("PcmToWav", e.getMessage());
  84. return false;
  85. } catch (IOException ioe) {
  86. Log.e("PcmToWav", ioe.getMessage());
  87. return false;
  88. }
  89. clearFiles(filePathList);
  90. Log.i("PcmToWav", "mergePCMFilesToWAVFile success!" + new SimpleDateFormat("yyyy-MM-dd hh:mm").format(new Date()));
  91. return true;
  92. }
  93. /**
  94. * 将一个pcm文件转化为wav文件
  95. *
  96. * @param pcmPath pcm文件路径
  97. * @param destinationPath 目标文件路径(wav)
  98. * @param deletePcmFile 是否删除源文件
  99. * @return
  100. */
  101. public static boolean makePCMFileToWAVFile(String pcmPath, String destinationPath, boolean deletePcmFile) {
  102. byte buffer[] = null;
  103. int TOTAL_SIZE = 0;
  104. File file = new File(pcmPath);
  105. if (!file.exists()) {
  106. return false;
  107. }
  108. TOTAL_SIZE = (int) file.length();
  109. // 填入参数,比特率等等。这里用的是16位单声道 8000 hz
  110. WaveHeader header = new WaveHeader();
  111. // 长度字段 = 内容的大小(TOTAL_SIZE) +
  112. // 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
  113. header.fileLength = TOTAL_SIZE + (44 - 8);
  114. header.FmtHdrLeth = 16;
  115. header.BitsPerSample = 16;
  116. header.Channels = 2;
  117. header.FormatTag = 0x0001;
  118. header.SamplesPerSec = 8000;
  119. header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);
  120. header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
  121. header.DataHdrLeth = TOTAL_SIZE;
  122. byte[] h = null;
  123. try {
  124. h = header.getHeader();
  125. } catch (IOException e1) {
  126. Log.e("PcmToWav", e1.getMessage());
  127. return false;
  128. }
  129. if (h.length != 44) // WAV标准,头部应该是44字节,如果不是44个字节则不进行转换文件
  130. return false;
  131. //先删除目标文件
  132. File destfile = new File(destinationPath);
  133. if (destfile.exists())
  134. destfile.delete();
  135. //合成所有的pcm文件的数据,写到目标文件
  136. try {
  137. buffer = new byte[1024 * 4]; // Length of All Files, Total Size
  138. InputStream inStream = null;
  139. OutputStream ouStream = null;
  140. ouStream = new BufferedOutputStream(new FileOutputStream(
  141. destinationPath));
  142. ouStream.write(h, 0, h.length);
  143. inStream = new BufferedInputStream(new FileInputStream(file));
  144. int size = inStream.read(buffer);
  145. while (size != -1) {
  146. ouStream.write(buffer);
  147. size = inStream.read(buffer);
  148. }
  149. inStream.close();
  150. ouStream.close();
  151. } catch (FileNotFoundException e) {
  152. Log.e("PcmToWav", e.getMessage());
  153. return false;
  154. } catch (IOException ioe) {
  155. Log.e("PcmToWav", ioe.getMessage());
  156. return false;
  157. }
  158. if (deletePcmFile) {
  159. file.delete();
  160. }
  161. Log.i("PcmToWav", "makePCMFileToWAVFile success!" + new SimpleDateFormat("yyyy-MM-dd hh:mm").format(new Date()));
  162. return true;
  163. }
  164. /**
  165. * 清除文件
  166. *
  167. * @param filePathList
  168. */
  169. private static void clearFiles(List<String> filePathList) {
  170. for (int i = 0; i < filePathList.size(); i++) {
  171. File file = new File(filePathList.get(i));
  172. if (file.exists()) {
  173. file.delete();
  174. }
  175. }
  176. }
  177. }
复制代码

3、WaveHeader类:

  1. package com.hxl.pauserecord.record;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.IOException;
  4. /**
  5. * Created by HXL on 16/3/9.
  6. * wav文件头
  7. */
  8. public class WaveHeader {
  9. public final char fileID[] = {'R', 'I', 'F', 'F'};
  10. public int fileLength;
  11. public char wavTag[] = {'W', 'A', 'V', 'E'};;
  12. public char FmtHdrID[] = {'f', 'm', 't', ' '};
  13. public int FmtHdrLeth;
  14. public short FormatTag;
  15. public short Channels;
  16. public int SamplesPerSec;
  17. public int AvgBytesPerSec;
  18. public short BlockAlign;
  19. public short BitsPerSample;
  20. public char DataHdrID[] = {'d','a','t','a'};
  21. public int DataHdrLeth;
  22. public byte[] getHeader() throws IOException {
  23. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  24. WriteChar(bos, fileID);
  25. WriteInt(bos, fileLength);
  26. WriteChar(bos, wavTag);
  27. WriteChar(bos, FmtHdrID);
  28. WriteInt(bos,FmtHdrLeth);
  29. WriteShort(bos,FormatTag);
  30. WriteShort(bos,Channels);
  31. WriteInt(bos,SamplesPerSec);
  32. WriteInt(bos,AvgBytesPerSec);
  33. WriteShort(bos,BlockAlign);
  34. WriteShort(bos,BitsPerSample);
  35. WriteChar(bos,DataHdrID);
  36. WriteInt(bos,DataHdrLeth);
  37. bos.flush();
  38. byte[] r = bos.toByteArray();
  39. bos.close();
  40. return r;
  41. }
  42. private void WriteShort(ByteArrayOutputStream bos, int s) throws IOException {
  43. byte[] mybyte = new byte[2];
  44. mybyte[1] =(byte)( (s << 16) >> 24 );
  45. mybyte[0] =(byte)( (s << 24) >> 24 );
  46. bos.write(mybyte);
  47. }
  48. private void WriteInt(ByteArrayOutputStream bos, int n) throws IOException {
  49. byte[] buf = new byte[4];
  50. buf[3] =(byte)( n >> 24 );
  51. buf[2] =(byte)( (n << 8) >> 24 );
  52. buf[1] =(byte)( (n << 16) >> 24 );
  53. buf[0] =(byte)( (n << 24) >> 24 );
  54. bos.write(buf);
  55. }
  56. private void WriteChar(ByteArrayOutputStream bos, char[] id) {
  57. for (int i=0; i<id.length; i++) {
  58. char c = id[i];
  59. bos.write(c);
  60. }
  61. }
  62. }
复制代码

接下来是效果图。。。个人为人做APP界面一定要美观,而且要非常美观,不然谁会用你的东西!so~~

好吧,请大家撇开UI看功能~正如预期的一样,每点击暂停一次会生成一个pcm文件,当点击停止的时候,将所有的录音整合成一个可播放的.wav文件

接下来,项目源码:点击下载

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持程序员之家。



回复

使用道具 举报

关闭

站长推荐上一条 /1 下一条