前言

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

实现流程

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

获取权限

    <!--音频录制权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--读取和写入存储权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

如果是Android5.0以上,以上3个权限需要动态授权

初始化获取每一帧流的Size

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

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

只能在4000到192000的范围内取值

在AudioFormat类里
public static final int SAMPLE_RATE_HZ_MIN = 4000; 最小4000
public static final int SAMPLE_RATE_HZ_MAX = 192000; 最大192000

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

在AudioFormat类录
public static final int CHANNEL_IN_LEFT = 0x4;//左声道
public static final int CHANNEL_IN_RIGHT = 0x8;//右声道
public static final int CHANNEL_IN_FRONT = 0x10;//前声道
public static final int CHANNEL_IN_BACK = 0x20;//后声道
public static final int CHANNEL_IN_LEFT_PROCESSED = 0x40;
public static final int CHANNEL_IN_RIGHT_PROCESSED = 0x80;
public static final int CHANNEL_IN_FRONT_PROCESSED = 0x100;
public static final int CHANNEL_IN_BACK_PROCESSED = 0x200;
public static final int CHANNEL_IN_PRESSURE = 0x400;
public static final int CHANNEL_IN_X_AXIS = 0x800;
public static final int CHANNEL_IN_Y_AXIS = 0x1000;
public static final int CHANNEL_IN_Z_AXIS = 0x2000;
public static final int CHANNEL_IN_VOICE_UPLINK = 0x4000;
public static final int CHANNEL_IN_VOICE_DNLINK = 0x8000;
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;//单声道
public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);//立体声道(左右声道)

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

注意!一般的手机设备可能只支持 16位PCM编码,如果其他的都会报错为坏值.

public static final int ENCODING_PCM_16BIT = 2; //16位PCM编码
public static final int ENCODING_PCM_8BIT = 3; //8位PCM编码
public static final int ENCODING_PCM_FLOAT = 4; //4位PCM编码
public static final int ENCODING_AC3 = 5;
public static final int ENCODING_E_AC3 = 6;
public static final int ENCODING_DTS = 7;
public static final int ENCODING_DTS_HD = 8;
public static final int ENCODING_MP3 = 9; //MP3编码 此格式可能会因为不设备不支持报错
public static final int ENCODING_AAC_LC = 10;
public static final int ENCODING_AAC_HE_V1 = 11;
public static final int ENCODING_AAC_HE_V2 = 12;

初始化音频录制AudioRecord

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

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

private boolean mWhetherRecord;
private File pcmFile;
private void startRecord(){
pcmFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord.pcm");
mWhetherRecord = true;
new Thread(new Runnable() {
@Override
public void run() {
mAudioRecord.startRecording();//开始录制
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(pcmFile);
byte[] bytes = new byte[mRecordBufferSize];
while (mWhetherRecord){
mAudioRecord.read(bytes, 0, bytes.length);//读取流
fileOutputStream.write(bytes);
fileOutputStream.flush(); }
Log.e(TAG, "run: 暂停录制" );
mAudioRecord.stop();//停止录制
fileOutputStream.flush();
fileOutputStream.close();
addHeadData();//添加音频头部信息并且转成wav格式
} catch (FileNotFoundException e) {
e.printStackTrace();
mAudioRecord.stop();
} catch (IOException e) {
e.printStackTrace();
} }
}).start();
}

这里说明一下为什么用布尔值,来关闭录制.有些小伙伴会发现AudioRecord是可以获取到录制状态的.那么肯定有人会用状态来判断while是否还需要处理流.这种是错误的做法.因为MIC属于硬件层任何硬件的东西都是异步的而且会有很大的延时.所以回调的状态也是有延时的,有时候流没了,但是状态还是显示为正在录制.

停止录制

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

private void stopRecord(){
mWhetherRecord = false;
}

给音频文件添加头部信息,并且转换格式成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         音频数据
private void addHeadData(){
pcmFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord.pcm");
handlerWavFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord_handler.wav");
PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(8000,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);
pcmToWavUtil.pcmToWav(pcmFile.toString(),handlerWavFile.toString());
}

写入头部信息的工具类

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

public class PcmToWavUtil {
private static final String TAG = "PcmToWavUtil"; /**
* 缓存的音频大小
*/
private int mBufferSize;
/**
* 采样率
*/
private int mSampleRate;
/**
* 声道数
*/
private int mChannel; /**
* @param sampleRate sample rate、采样率
* @param channel channel、声道
* @param encoding Audio data format、音频格式
*/
PcmToWavUtil(int sampleRate, int channel, int encoding) {
this.mSampleRate = sampleRate;
this.mChannel = channel;
this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
} /**
* pcm文件转wav文件
*
* @param inFilename 源文件路径
* @param outFilename 目标文件路径
*/
public void pcmToWav(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;//总录音长度
long totalDataLen;//总数据长度
long longSampleRate = mSampleRate;
int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
long byteRate = 16 * mSampleRate * channels / 8;
byte[] data = new byte[mBufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36; writeWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
out.flush(); }
Log.e(TAG, "pcmToWav: 停止处理");
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 加入wav文件头
*/
private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
// RIFF/WAVE header
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
//WAVE
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// 'fmt ' chunk
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// format = 1
header[20] = 1;
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// block align
header[32] = (byte) (2 * 16 / 8);
header[33] = 0;
// bits per sample
header[34] = 16;
header[35] = 0;
//data
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}

释放AudioRecord,录制流程完毕

调用release()方法释放资源

mAudioRecord.release();

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

最后介绍下其他API

获取AudioRecord初始化状态

public int getState() {
return mState;
}

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

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

获取AudioRecord录制状态

public int getRecordingState() {
synchronized (mRecordingStateLock) {
return mRecordingState;
}
}

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

  • AudioRecord#RECORDSTATE_STOPPED    //停止录制
  • AudioRecord#RECORDSTATE_RECORDING    //正在录制
 

Android 开发 AudioRecord音频录制的更多相关文章

  1. Android 开发 MediaRecorder音频录制

    前言 MediaRecorder类是Android sdk提供的一个专门用于音视频录制,一般利用手机麦克风采集音频和摄像头采集图像.这个类是属于简单的音频录制类,录制音频简单容易但是对音频流的控制也比 ...

  2. Android 开发 MediaRecorder视频录制入门

    前言 MediaRecorder是Android SDK提供用于录制音视频,关于音频的录制在我另一篇博客里已经介绍.传送门: https://www.cnblogs.com/guanxinjing/p ...

  3. Android开发笔记——视频录制播放常见问题

    本文分享自己在视频录制播放过程中遇到的一些问题,主要包括: 视频录制流程 视频预览及SurfaceHolder 视频清晰度及文件大小 视频文件旋转 一.视频录制流程 以微信为例,其录制触发为按下(住) ...

  4. android开发,静音录制视频,在一般清晰度的前提下保证文件大小越小越好

    public void startRecord() { mediarecorder = new MediaRecorder();// 创建mediarecorder对象 mCamera = getCa ...

  5. iOS开发系列--音频播放、录音、视频播放、拍照、视频录制

    --iOS多媒体 概览 随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制, ...

  6. javaCV开发详解之5:录制音频(录制麦克风)到本地文件/流媒体服务器(基于javax.sound、javaCV-FFMPEG)

    javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...

  7. Android音频录制MediaRecorder之简易的录音软件实现代码(转)

    原文:http://www.jb51.net/article/46182.htm Android音频录制MediaRecorder之简易的录音软件实现代码 这篇文章主要介绍了Android音频录制Me ...

  8. 【转】Android开发笔记(序)写在前面的目录

    原文:http://blog.csdn.net/aqi00/article/details/50012511 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经 ...

  9. Android硬编码——音频编码、视频编码及音视频混合

    视频编解码对许多Android程序员来说都是Android中比较难的一个知识点.在Android 4.1以前,Android并没有提供硬编硬解的API,所以之前基本上都是采用FFMpeg来做视频软件编 ...

随机推荐

  1. CCPC-WFinal-女生专场

    1001:CCPC直播   字符串处理,几个if语句 1002:口算训练   前缀和处理<=根号n的因数,大于根号n的因数每个数至多有一个,用vector存下每个大因数的位置,map离散化.查询 ...

  2. 免费带你体验阿里巴巴旗舰大数据计算产品MaxCompute

    什么是MaxCompute? 众所周知,MaxCompute是阿里云推出的承载EB级的数据存储能力,百PB级的单日计算能力,公共云覆盖国内外十几个国家和地区,专有云包含城市大脑在内部署超过100+套的 ...

  3. bsgs+求数列通项——bzoj3122(进阶指南模板该进)

    /* 已知递推数列 F[i]=a*F[i-1]+b (%c) 解方程F[x]=t an+1 = b*an + c an+1 + c/(b-1) = b(an + c/(b-1)) an+1 + c/( ...

  4. python 毫秒时间戳转日期

    import time import datetime timestamp = 1570774556514 # 转换成localtime time_local = time.localtime(tim ...

  5. hexo next 主题 : 实现点击跳转到文章的时候文章的页面自动实现滚轮效果,向下滚动到阅读的位置。

    个人博客:https://mmmmmm.me 源码:https://github.com/dataiyangu/dataiyangu.github.io 背景: 博主的博客希望实现能够在点击到某个文章 ...

  6. 实验室系统tomcat 6 java.lang.OutOfMemoryError: Java heap space

    java.lang.OutOfMemoryError: Java heap space

  7. 17.splash_case06_ScrapySplashTest-master

    taobao.py # -*- coding: utf-8 -*- from scrapy import Spider, Request from urllib.parse import quote ...

  8. 6_3.springboot2.x数据整合Mybatis(注解和非注解)

    1.配置文件 pom.xml 导入mybatis提供的启动器 <dependency> <groupId>org.mybatis.spring.boot</groupId ...

  9. [POI2013]MOR-Tales of seafaring

    题目 思博题,发现一旦路径太长我们可以来回走最后一条边,但是这样并不能改变路径长度的奇偶性 所以求一下所有点之间奇最短路和偶最短路就好了,直接暴力\(BFS\)即可 有一个烦人的特判 代码 #incl ...

  10. 串口通信中,QString 、QByteArray 转化需要注意的问题

    在做串口通信的时候,其中犯了一个错误.在此记录一下:QT中串口通信接到收据和发送数据的接口如下: QByteArray QIODevice::readAll()//接受数据 qint64 QIODev ...