aoi学院

Aisaka's Blog, School of Aoi, Aisaka University

Android-音视频-音频开发-(二)音频录制

AudioRecord(基于字节流录音)

优点:可以实现语音的实时处理,进行边录边播,对音频的实时处理。

缺点:输出的是PCM的语音数据,如果保存成音频文件是不能被播放器播放的。要用到AudioTrack这个去进行处理。

实现 AudioRecord 录音的流程

  1. 构造一个 AudioRecord 对象,其中最小录音缓存 buffer 大小可以通过 getMinBufferSize 方法得到。如果 buffer 容量过小,将导致对象构造的失败。

  2. 初始化一个 buffer,该 buffer 大于等于 AudioRecord 对象用于写声音数据的 buffer 大小。

  3. 开始录音

  4. 创建一个数据流,一边从 AudioRecord 中读取声音数据到初始化的 buffer,一边将 buffer 中数据导入数据流。

  5. 关闭数据流

  6. 停止录音

  7. 释放对象

构造 AudioRecord 对象需要的参数

  1. 音频源:一般可以使用麦克风作为采集音频的数据源。

  2. 采样率:一秒内对声音数据的采样次数,采样率越高,音质越好。

  3. 通道:单声道,双声道等。

  4. 音频格式:一般选用 PCM 格式,即原始的音频样本。

  5. 缓冲区大小:音频数据写入缓冲区的总数,通过 AudioRecord.getMinBufferSize 获取最小的缓冲区。

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

AudioRecord例子:录制音频

录音JNI函数不具有线程安全性,因此用单线程,以及设置缓存大小

1
2
3
4
5
6
7
8
mExecutorService = Executors.newSingleThreadExecutor();
mHandler = new Handler(Looper.getMainLooper());
mAudioRecordTest = new AudioRecordTest.Builder(this)
.setExecutorService(mExecutorService)
.setHandler(mHandler)
.setBtStreamRecorder(mBtStreamRecorder)
.setBufferSize(BUFFER_SIZE)
.build();

点击button进行录音,再次点击停止录音功能recorderAudio()

1
2
3
4
5
6
7
8
9
10
11
12
public void recordAudio() {
if (mIsRecording) {
mIsRecording = false;
mBtStreamRecorder.setText("开始录音");
// 在开始录音中如果这个值没有变false,则一直进行,当再次点击变false时,录音才停止
} else {
mIsRecording = true;
mBtStreamRecorder.setText("停止录音");
// 提交后台任务,执行录音逻辑
mExecutorService.submit(this::startRecord);
}
}

开始录音startRecorder()

1
2
3
4
5
private void startRecord() {
if (!doStartRecord()) {
recordFail();
}
}

AudioRecord的配置及录音功能实现doStartRecord()

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
private boolean doStartRecord() {
try {
// 记录开始录音时间
startRecorderTime = System.currentTimeMillis();

// 创建录音文件
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
mAudioRecordFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) +
"/AudioRecordDemo/" + startRecorderTime + ".pcm");
if (!mAudioRecordFile.getParentFile().exists()) {
mAudioRecordFile.getParentFile().mkdirs();
}
} else {
return false;
}

mAudioRecordFile.createNewFile();

// 创建文件输出流
FileOutputStream fos = new FileOutputStream(mAudioRecordFile);

// 配置AudioRecord
int audioSource = MediaRecorder.AudioSource.MIC;

// 所有android系统都支持
int sampleRate = 44100;

// 单声道输入
int channelConfig = AudioFormat.CHANNEL_IN_MONO;

// PCM_16是所有android系统都支持的
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

// 计算AudioRecord内部buffer最小
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);

// buffer不能小于最低要求,也不能小于我们每次我们读取的大小。
mAudioRecord = new AudioRecord(audioSource, sampleRate, channelConfig,
audioFormat, Math.max(minBufferSize, BUFFER_SIZE));

// 开始录音
mAudioRecord.startRecording();

// 循环读取数据,写入输出流中
while (mIsRecording) {
// 只要还在录音就一直读取
int read = mAudioRecord.read(mBuffer, 0, BUFFER_SIZE);
if (read <= 0) {
return false;
} else {
fos.write(mBuffer, 0, read);
}
}
// 退出循环,停止录音,释放资源
stopRecord();
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
return true;
}

停止录音stopRecorder()

1
2
3
4
5
6
private void stopRecord() {
mIsRecording = false;
if (!doStopRecord()) {
recordFail();
}
}

再次点击停止录音doStopRecord()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private boolean doStopRecord() {
// 停止录音,关闭文件输出流
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
// 记录结束时间,统计录音时长
long stopRecorderTime = System.currentTimeMillis();
// 大于3秒算成功,在主线程更新UI
final int second = (int) (stopRecorderTime - startRecorderTime) / 1000;
if (second > 3) {
mHandler.post(() -> {
Toast.makeText(mContext,
"录音成功:" + second + "秒", Toast.LENGTH_SHORT).show();
Log.d(TAG, "save in " + startRecorderTime + ".pcm");
mBtStreamRecorder.setText("开始录音");
});
} else {
recordFail();
return false;
}
return true;
}

录取失败,更新UI操作recorderFail()

1
2
3
4
5
6
7
8
9
10
11
12
13
private void recordFail() {
mHandler.post(() -> {
Toast.makeText(mContext,
"录制失败,录音请大于3秒!", Toast.LENGTH_SHORT).show();
mBtStreamRecorder.setText("开始录音");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
File audioRecordFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) +
"/AudioRecordDemo/" + startRecorderTime + ".pcm");
audioRecordFile.delete();
}
mIsRecording = false;
});
}

onDestroy()防止内存泄漏

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onDestroy() {
super.onDestroy();
if (mAudioRecordTest.getAudioRecord() != null) {
mAudioRecordTest.getAudioRecord().stop();
}
if (mExecutorService != null) {
mExecutorService.shutdownNow();
mExecutorService = null;
}
}

MediaRecorder(基于文件录音)

已集成了录音,编码,压缩等,支持少量的音频格式文件。

优点:封装度很高,操作简单

缺点:无法实现实时处理音频,输出的音频格式少。

MediaRecorder 类进行音频录制的基本步骤

  1. 建立 MediaRecorder 类的对象:new MediaRecorder()

  2. 设置音频来源:mMediaRecorder.setAudioSource()

  3. 设置音频编码方式:mMediaRecorder.setAudioEncoder()

  4. 设置音频文件的保存位置及文件名:mMediaRecorder.setOutputFile()

  5. 将录音器置于准备状态:mMediaRecorder.prepare()

  6. 启动录音器:mMediaRecorder.start()

  7. 音频录制完成,停止录音器:mMediaRecorder.stop()

  8. 释放录音器对象:mMediaRecorder.release()

MediaRecorder例子:录制音频

录音JNI函数不具有线程安全性,因此用单线程

1
2
3
4
5
6
7
mExecutorService = Executors.newSingleThreadExecutor();
mHandler = new Handler(Looper.getMainLooper());
mMediaRecorderTest = new MediaRecorderTest.Builder(this)
.setExecutorService(mExecutorService)
.setHandler(mHandler)
.setBtMediaRecorder(mBtMediaRecorder)
.build();

开启一个单线程去实现录音功能按下按钮recordMedia()

1
2
3
4
5
6
7
8
9
10
11
12
public void recordMedia() {
if (mIsRecording) {
mIsRecording = false;
mBtMediaRecorder.setText("开始录音");
mExecutorService.submit(this::stopRecorder);
} else {
mIsRecording = true;
mBtMediaRecorder.setText("停止录音");
// 提交后台任务,执行录音逻辑
mExecutorService.submit(this::startRecorder);
}
}

开始录音startRecorder()

1
2
3
4
5
6
7
8
private void startRecorder() {
// 释放上一次的录音
releaseRecorder();
// 开始录音
if (!doStart()) {
recorderFail();
}
}

启动录音doStart()

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
private boolean doStart() {
try {
// 创建MediaRecorder
mMediaRecorder = new MediaRecorder();

startRecorderTime = System.currentTimeMillis();

// 创建录音文件
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mRecorderFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
+ "/MediaRecorderDemo/" + startRecorderTime + ".m4a");
if (!mRecorderFile.getParentFile().exists()) {
mRecorderFile.getParentFile().mkdirs();
}
} else {
return false;
}

mRecorderFile.createNewFile();

// 配置MediaRecorder

// 从麦克风采集
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);

// 保存文件为MP4格式
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

// 所有android系统都支持的适中采样的频率
mMediaRecorder.setAudioSamplingRate(44100);

// 通用的AAC编码格式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

// 设置音质频率
mMediaRecorder.setAudioEncodingBitRate(96000);

// 设置文件录音的位置
mMediaRecorder.setOutputFile(mRecorderFile.getAbsolutePath());

// 开始录音
mMediaRecorder.prepare();
mMediaRecorder.start();

} catch (Exception e) {
Toast.makeText(mContext, "录音失败,请重试", Toast.LENGTH_SHORT).show();
return false;
}

// 记录开始录音时间

return true;
}

停止录音stopRecorder()

1
2
3
4
5
6
7
private void stopRecorder() {
// 提交后台任务,停止录音
if (!doStop()) {
recorderFail();
}
releaseRecorder();
}

停止录音计算时长doStop()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private boolean doStop() {
try {
mMediaRecorder.stop();
stopRecorderTime = System.currentTimeMillis();
final int second = (int) (stopRecorderTime - startRecorderTime) / 1000;
// 按住时间小于3秒钟,算作录取失败,不进行发送
if (second < 3) return false;
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext,
"录音成功:" + second + "秒", Toast.LENGTH_SHORT).show();
Log.d(TAG, "save in " + startRecorderTime + ".m4a");
}
});
} catch (Exception e) {
e.printStackTrace();
}
return true;
}

释放MediaRecorder的releaseRecorder()

1
2
3
4
5
6
private void releaseRecorder() {
if (mMediaRecorder != null) {
mMediaRecorder.release();
mMediaRecorder = null;
}
}

设置录音失败的逻辑recorderFail()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void recorderFail() {
mRecorderFile = null;
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext,
"录制失败,录音请大于3秒!", Toast.LENGTH_SHORT).show();
mBtMediaRecorder.setText("开始录音");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
File audioRecordFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) +
"/MediaRecorderDemo/" + startRecorderTime + ".m4a");
audioRecordFile.delete();
}
}
});
}

记得在onDestroy()防止内存泄漏

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onDestroy() {
super.onDestroy();
if (mMediaRecorderTest.getMediaRecorder() != null) {
mMediaRecorderTest.getMediaRecorder().stop();
}
if (mExecutorService != null) {
mExecutorService.shutdownNow();
mExecutorService = null;
}
}