Android音视频开发之使用AudioRecord采集音频

AudioRecord 是 Android 系统提供的用于实现录音功能的 API,官方文档是这么解释的:

AndioRecord 类的主要功能是让各种 Java 应用能够管理音频资源,以便它们通过此类能够录制声音相关的硬件所收集的声音。此功能的实现就是通过“pulling”(读取)AudioRecord 对象的声音数据来完成的。在录音过程中,应用所需要做的就是通过后面三个类方法中的一个去及时地获取AudioRecord对象的录音数据。AudioRecord 类提供的三个获取声音数据的方法分别是 read(byte[], int, int),、read(short[], int, int)和 read(ByteBuffer, int)。无论选择使用那一个方法都必须事先设定方便用户的声音数据存储格式。

开始录音的时候,AudioRecord 需要初始化一个相关联的声音 buffer, 这个 buffer 主要是用来保存新的声音数据。这个 buffer 的大小,我们可以在对象构造期间去指定。它表明一个 AudioRecord 对象还没有被读取(同步)声音数据前能录多长的音(即一次可以录制的声音容量)。声音数据从音频硬件中被读出,数据大小不超过整个录音数据的大小(可以分多次读出),即每次读取初始化 buffer 容量的数据。

实现Android录音的流程为:

  1. 构造一个AudioRecord对象,其中最小录音缓存buffer大小可以通过getMinBufferSize方法得到。如果buffer容量过小,将导致对象构造的失败。
  2. 初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小。
  3. 开始录音
  4. 创建一个数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中数据导入数据流。
  5. 关闭数据流
  6. 停止录音

其中,构造 AudioRecord 对象,需要这么几个参数:

  1. 音频源:一般可以使用麦克风作为采集音频的数据源。
  2. 采样率:一秒内对声音数据的采样次数,采样率越高,音质越好。
  3. 通道:单声道,双声道等。
  4. 音频格式:一般选用 PCM 格式,即原始的音频样本。
  5. 缓冲区大小:音频数据写入缓冲区的总数,通过 AudioRecord.getMinBufferSize 获取最小的缓冲区。

注意,最后生成的音频文件是 PCM 格式的,也就是最原始的音频数据,它没有头信息,不能直接播放,必须转换成可识别的格式才行。这里我们把它转成 WAV 格式,在文件的数据开头加入 WAVE HEAD 即可。

用代码实践一下录音的过程

  1. 创建录音对象,指定具体的参数。
1
2
3
4
5
6
7
8
9
10
11
12
public void createAudio(String fileName, int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) throws IllegalStateException {
// 获得缓冲区字节大小
mBufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
if (mBufferSizeInBytes <= 0) {
throw new IllegalStateException("AudioRecord is not available " + mBufferSizeInBytes);
}
mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, mBufferSizeInBytes);
int state = mAudioRecord.getState();
Log.i(TAG, "createAudio state:" + state + ", initialized:" + (state == AudioRecord.STATE_INITIALIZED));
mFileName = fileName;
mStatus = Status.STATUS_READY;
}
  1. 开始录音,不断读取 Buffer 中声音数据,并保存到文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public void startRecord() throws IllegalStateException {
if (mStatus == Status.STATUS_NO_READY || mAudioRecord == null) {
throw new IllegalStateException("录音尚未初始化");
}
if (mStatus == Status.STATUS_START) {
throw new IllegalStateException("正在录音...");
}
Log.d(TAG, "===startRecord===");
mAudioRecord.startRecording();

String currentFileName = FileUtils.getPcmFilePath(mContext, mFileName);
final String finalFileName = currentFileName;
//将录音状态设置成正在录音状态
mStatus = Status.STATUS_START;

//使用线程池管理线程
mExecutorService.execute(new Runnable() {
@Override
public void run() {
writeAudioDataToFile(finalFileName);
}
});
}

private void writeAudioDataToFile() throws IOException {
String pcmFilePath = FileUtils.getPcmFilePath(mContext, mPcmFileName);
File file = new File(pcmFilePath);
if (file.exists()) {
file.delete();
}
OutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(file));
byte[] audioData = new byte[mBufferSizeInBytes];
while (mStatus == Status.STATUS_START) {
int readSize = mAudioRecord.read(audioData, 0, mBufferSizeInBytes);
if (readSize > 0) {
try {
bos.write(audioData, 0, readSize);
if (mRecordStreamListener != null) {
mRecordStreamListener.onRecording(audioData, 0, readSize);
}
} catch (IOException e) {
Log.e(TAG, "writeAudioDataToFile", e);
}
} else {
Log.w(TAG, "writeAudioDataToFile readSize: " + readSize);
}
}
bos.flush();
if (mRecordStreamListener != null) {
mRecordStreamListener.finishRecord();
}
} finally {
if (bos != null) {
bos.close();// 关闭写入流
}
}
}

public interface RecordStreamListener {
/**
* 录音过程中
*
* @param bytes
* @param offset
* @param length
*/
void onRecording(byte[] bytes, int offset, int length);

/**
* 录音完成
*/
void finishRecord();
}
  1. 停止录音,释放 AudioRecord
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void stopRecord() throws IllegalStateException {
Log.d(TAG, "===stopRecord===");
if (mStatus == Status.STATUS_NO_READY || mStatus == Status.STATUS_READY) {
throw new IllegalStateException("录音尚未开始");
} else {
mAudioRecord.stop();
mStatus = Status.STATUS_STOP;
release();
}
}

public void release() {
Log.d(TAG, "===release===");
if (mAudioRecord != null) {
mAudioRecord.release();
mAudioRecord = null;
}
mStatus = Status.STATUS_NO_READY;
}

PCM 转 WAV 的代码就不贴了,有需要的可以到 GitHub 上查看。

参考文章:

本文标题:Android音视频开发之使用AudioRecord采集音频

文章作者:落英坠露

发布时间:2018年10月30日 - 21:10

最后更新:2019年05月03日 - 20:05

原始链接:https://isuperqiang.cn/2018/10/30/Android音视频开发之使用AudioRecord采集音频/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

赞赏是一种行为艺术