原文地址:https://www.cnblogs.com/guanxinjing/p/10969824.html


前言

  Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件,而后者则更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。

实现流程

  1. 获取权限
  2. 初始化获取每一帧流的Size
  3. 初始化音频录制AudioRecord
  4. 开始录制与保存录制音频文件
  5. 停止录制
  6. 给音频文件添加头部信息,并且转换格式成wav
  7. 释放AudioRecord,录制流程完毕

    获取权限

    1. <!--音频录制权限 -->
    2. <uses-permission android:name="android.permission.RECORD_AUDIO" />
    3. <!--读取和写入存储权限-->
    4. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    5. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    如果是Android5.0以上,以上3个权限需要动态授权

    初始化获取每一帧流的Size

    1. private Integer mRecordBufferSize;
    2. private void initMinBufferSize(){
    3. //获取每一帧的字节流大小
    4. mRecordBufferSize = AudioRecord.getMinBufferSize(8000
    5. , AudioFormat.CHANNEL_IN_MONO
    6. , AudioFormat.ENCODING_PCM_16BIT);
    7. }

    第一个参数sampleRateInHz 采样率(赫兹),方法注释里有说明

    只能在4000到192000的范围内取值
    在AudioFormat类里
    1. public static final int SAMPLE_RATE_HZ_MIN = 4000; 最小4000
    2. public static final int SAMPLE_RATE_HZ_MAX = 192000; 最大192000

    第二个参数channelConfig 声道配置 描述音频声道的配置,例如左声道/右声道/前声道/后声道。

    在AudioFormat类录
    1. public static final int CHANNEL_IN_LEFT = 0x4;//左声道
    2. public static final int CHANNEL_IN_RIGHT = 0x8;//右声道
    3. public static final int CHANNEL_IN_FRONT = 0x10;//前声道
    4. public static final int CHANNEL_IN_BACK = 0x20;//后声道
    5. public static final int CHANNEL_IN_LEFT_PROCESSED = 0x40;
    6. public static final int CHANNEL_IN_RIGHT_PROCESSED = 0x80;
    7. public static final int CHANNEL_IN_FRONT_PROCESSED = 0x100;
    8. public static final int CHANNEL_IN_BACK_PROCESSED = 0x200;
    9. public static final int CHANNEL_IN_PRESSURE = 0x400;
    10. public static final int CHANNEL_IN_X_AXIS = 0x800;
    11. public static final int CHANNEL_IN_Y_AXIS = 0x1000;
    12. public static final int CHANNEL_IN_Z_AXIS = 0x2000;
    13. public static final int CHANNEL_IN_VOICE_UPLINK = 0x4000;
    14. public static final int CHANNEL_IN_VOICE_DNLINK = 0x8000;
    15. public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;//单声道
    16. public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);//立体声道(左右声道)

    第三个参数audioFormat 音频格式 表示音频数据的格式。

    注意!一般的手机设备可能只支持 16位PCM编码,如果其他的都会报错为坏值.
    1. public static final int ENCODING_PCM_16BIT = 2; //16位PCM编码
    2. public static final int ENCODING_PCM_8BIT = 3; //8位PCM编码
    3. public static final int ENCODING_PCM_FLOAT = 4; //4位PCM编码
    4. public static final int ENCODING_AC3 = 5;
    5. public static final int ENCODING_E_AC3 = 6;
    6. public static final int ENCODING_DTS = 7;
    7. public static final int ENCODING_DTS_HD = 8;
    8. public static final int ENCODING_MP3 = 9; //MP3编码 此格式可能会因为不设备不支持报错
    9. public static final int ENCODING_AAC_LC = 10;
    10. public static final int ENCODING_AAC_HE_V1 = 11;
    11. public static final int ENCODING_AAC_HE_V2 = 12;

    初始化音频录制AudioRecord

    1. private AudioRecord mAudioRecord;
    2. private void initAudioRecord(){
    3. mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC
    4. , 8000
    5. , AudioFormat.CHANNEL_IN_MONO
    6. , AudioFormat.ENCODING_PCM_16BIT
    7. , mRecordBufferSize);
    8. }
  • 第一个参数audioSource 音频源 这里选择使用麦克风:MediaRecorder.AudioSource.MIC
  • 第二个参数sampleRateInHz 采样率(赫兹) 与前面初始化获取每一帧流的Size保持一致
  • 第三个参数channelConfig 声道配置 描述音频声道的配置,例如左声道/右声道/前声道/后声道。 与前面初始化获取每一帧流的Size保持一致
  • 第四个参数audioFormat 音频格式 表示音频数据的格式。 与前面初始化获取每一帧流的Size保持一致
  • 第五个参数缓存区大小,就是上面我们配置的AudioRecord.getMinBufferSize

    开始录制与保存录制音频文件

    1. private boolean mWhetherRecord;
    2. private File pcmFile;
    3. private void startRecord(){
    4. pcmFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord.pcm");
    5. mWhetherRecord = true;
    6. new Thread(new Runnable() {
    7. @Override
    8. public void run() {
    9. mAudioRecord.startRecording();//开始录制
    10. FileOutputStream fileOutputStream = null;
    11. try {
    12. fileOutputStream = new FileOutputStream(pcmFile);
    13. byte[] bytes = new byte[mRecordBufferSize];
    14. while (mWhetherRecord){
    15. mAudioRecord.read(bytes, 0, bytes.length);//读取流
    16. fileOutputStream.write(bytes);
    17. fileOutputStream.flush();
    18. }
    19. Log.e(TAG, "run: 暂停录制" );
    20. mAudioRecord.stop();//停止录制
    21. fileOutputStream.flush();
    22. fileOutputStream.close();
    23. addHeadData();//添加音频头部信息并且转成wav格式
    24. } catch (FileNotFoundException e) {
    25. e.printStackTrace();
    26. mAudioRecord.stop();
    27. } catch (IOException e) {
    28. e.printStackTrace();
    29. }
    30. }
    31. }).start();
    32. }
    这里说明一下为什么用布尔值,来关闭录制.有些小伙伴会发现AudioRecord是可以获取到录制状态的.那么肯定有人会用状态来判断while是否还需要处理流.这种是错误的做法.因为MIC属于硬件层任何硬件的东西都是异步的而且会有很大的延时.所以回调的状态也是有延时的,有时候流没了,但是状态还是显示为正在录制.

    停止录制

    就是调用mAudioRecord.stop();方法来停止录制,但是因为我在上面的保存流后做了调用停止视频录制,所以我这里只需要切换布尔值就可以关闭音频录制
    1. private void stopRecord(){
    2. mWhetherRecord = false;
    3. }

    给音频文件添加头部信息,并且转换格式成wav

    音频录制完成后,这个时候去存储目录找到音频文件部分,会提示无法播放文件.其实是因为没有加入音频头部信息.一般通过麦克风采集的录音数据都是PCM格式的,即不包含头部信息,播放器无法知道音频采样率、位宽等参数,导致无法播放,显然是非常不方便的。pcm转换成wav,我们只需要在pcm的文件起始位置加上至少44个字节的WAV头信息即可。**
偏移地址 命名 内容
00-03 ChunkId “RIFF”
04-07 ChunkSize 下个地址开始到文件尾的总字节数(此Chunk的数据大小)
08-11 fccType “WAVE”
12-15 SubChunkId1 “fmt “,最后一位空格。
16-19 SubChunkSize1 一般为16,表示fmt Chunk的数据块大小为16字节
20-21 FormatTag 1:表示是PCM 编码
22-23 Channels 声道数,单声道为1,双声道为2
24-27 SamplesPerSec 采样率
28-31 BytesPerSec 码率 :采样率 采样位数 声道个数,
bytePerSecond = sampleRate (bitsPerSample / 8) channels
32-33 BlockAlign 每次采样的大小:位宽*声道数/8
34-35 BitsPerSample 位宽
36-39 SubChunkId2 “data”
40-43 SubChunkSize2 音频数据的长度
44-… data 音频数据
  1. private void addHeadData(){
  2. pcmFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord.pcm");
  3. handlerWavFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord_handler.wav");
  4. PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(8000,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);
  5. pcmToWavUtil.pcmToWav(pcmFile.toString(),handlerWavFile.toString());
  6. }

写入头部信息的工具类

注意输入File和输出File不能同一个,因为没有做缓存.

  1. public class PcmToWavUtil {
  2. private static final String TAG = "PcmToWavUtil";
  3. /** 缓存的音频大小 */
  4. private int mBufferSize;
  5. /** 采样率 */
  6. private int mSampleRate;
  7. /** 声道数 */
  8. private int mChannel;
  9. /**
  10. * @param sampleRate sample rate、采样率
  11. * @param channel channel、声道
  12. * @param encoding Audio data format、音频格式
  13. */
  14. PcmToWavUtil(int sampleRate, int channel, int encoding) {
  15. this.mSampleRate = sampleRate;
  16. this.mChannel = channel;
  17. this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
  18. }
  19. /**
  20. * pcm文件转wav文件
  21. *
  22. * @param inFilename 源文件路径
  23. * @param outFilename 目标文件路径
  24. */
  25. public void pcmToWav(String inFilename, String outFilename) {
  26. FileInputStream in;
  27. FileOutputStream out;
  28. long totalAudioLen;//总录音长度
  29. long totalDataLen;//总数据长度
  30. long longSampleRate = mSampleRate;
  31. int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
  32. long byteRate = 16 * mSampleRate * channels / 8;
  33. byte[] data = new byte[mBufferSize];
  34. try {
  35. in = new FileInputStream(inFilename);
  36. out = new FileOutputStream(outFilename);
  37. totalAudioLen = in.getChannel().size();
  38. totalDataLen = totalAudioLen + 36;
  39. writeWaveFileHeader(out, totalAudioLen, totalDataLen,
  40. longSampleRate, channels, byteRate);
  41. while (in.read(data) != -1) {
  42. out.write(data);
  43. out.flush();
  44. }
  45. Log.e(TAG, "pcmToWav: 停止处理");
  46. in.close();
  47. out.close();
  48. } catch (IOException e) {
  49. e.printStackTrace();
  50. }
  51. }
  52. /**
  53. * 加入wav文件头
  54. */
  55. private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
  56. long totalDataLen, long longSampleRate, int channels, long byteRate)
  57. throws IOException {
  58. byte[] header = new byte[44];
  59. // RIFF/WAVE header
  60. header[0] = 'R';
  61. header[1] = 'I';
  62. header[2] = 'F';
  63. header[3] = 'F';
  64. header[4] = (byte) (totalDataLen & 0xff);
  65. header[5] = (byte) ((totalDataLen >> 8) & 0xff);
  66. header[6] = (byte) ((totalDataLen >> 16) & 0xff);
  67. header[7] = (byte) ((totalDataLen >> 24) & 0xff);
  68. //WAVE
  69. header[8] = 'W';
  70. header[9] = 'A';
  71. header[10] = 'V';
  72. header[11] = 'E';
  73. // 'fmt ' chunk
  74. header[12] = 'f';
  75. header[13] = 'm';
  76. header[14] = 't';
  77. header[15] = ' ';
  78. // 4 bytes: size of 'fmt ' chunk
  79. header[16] = 16;
  80. header[17] = 0;
  81. header[18] = 0;
  82. header[19] = 0;
  83. // format = 1
  84. header[20] = 1;
  85. header[21] = 0;
  86. header[22] = (byte) channels;
  87. header[23] = 0;
  88. header[24] = (byte) (longSampleRate & 0xff);
  89. header[25] = (byte) ((longSampleRate >> 8) & 0xff);
  90. header[26] = (byte) ((longSampleRate >> 16) & 0xff);
  91. header[27] = (byte) ((longSampleRate >> 24) & 0xff);
  92. header[28] = (byte) (byteRate & 0xff);
  93. header[29] = (byte) ((byteRate >> 8) & 0xff);
  94. header[30] = (byte) ((byteRate >> 16) & 0xff);
  95. header[31] = (byte) ((byteRate >> 24) & 0xff);
  96. // block align
  97. header[32] = (byte) (2 * 16 / 8);
  98. header[33] = 0;
  99. // bits per sample
  100. header[34] = 16;
  101. header[35] = 0;
  102. //data
  103. header[36] = 'd';
  104. header[37] = 'a';
  105. header[38] = 't';
  106. header[39] = 'a';
  107. header[40] = (byte) (totalAudioLen & 0xff);
  108. header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
  109. header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
  110. header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
  111. out.write(header, 0, 44);
  112. }
  113. }

释放AudioRecord,录制流程完毕

调用release()方法释放资源

  1. mAudioRecord.release();

最后你就可以在指定目录下找到音频文件播放了

最后介绍下其他API

获取AudioRecord初始化状态

  1. public int getState() {
  2. return mState;
  3. }

注意!这里是初始化状态,不是录制状态,它只会返回2个状态

  • AudioRecord#STATE_INITIALIZED   //已经初始化
  • AudioRecord#STATE_UNINITIALIZED //没有初始化

    获取AudioRecord录制状态

    1. public int getRecordingState() {
    2. synchronized (mRecordingStateLock) {
    3. return mRecordingState;
    4. }
    5. }

    返回录制状态,它只返回2个状态

  • AudioRecord#RECORDSTATE_STOPPED //停止录制

  • AudioRecord#RECORDSTATE_RECORDING //正在录制