Android 音视频基础1——AudioTrack、AudioRecorder、MediaRecorder音频数据采集播放处理

简介

  • AudioRecorder,可以采集PCM格式的原始音频文件,对PCM格式数据,根据一定的协议约定,可以转换成wav格式等其他格式;
  • AudioTrack,可以直接播放PCM格式的文件,也就是说上面利用AudioRecorder生成的原始文件可以通过AudioTrack直接播放;
  • MediaRecorder,可以录制音频,也可以配合相机(Camera)录制视频,相比较而言,MediaRecorder功能更丰富,更易用,使用场景也更多,而AudioRecorder和AudioTrack则更底层一点,且功能存在局限性。

下面分别介绍 这三个音频采集过程中常用的类。

AudioRecord

AudioRecord的说明比较简单,构造好后,通过其read系列方法,从指定的硬件源中读取数据到缓存中(这个缓存的大小的最小值由 采样率 通道数 音频数据类型 的值获取而来),然后将其写入到文件,这个原始的文件就是PCM文件,再根据媒体格式之间的转换协议,做下转换,就可以得到其他格式的文件了。

由于比较简单,这里直接贴上我针对AudioRecord封装的代码。

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
public class AudioRecordWrapper {

private static final String TAG = "AudioRecordWrapper";

private static final int STATE_INIT = 0; // 初始化状态
private static final int STATE_RECORDING = 1; // 录制状态
private static final int STATE_STOP = 2; // 停止状态
private static final int STATE_RELEASE = 3; // 释放状态

private int mRecordState; // 当前的状态

/**
* most time is {@link android.media.MediaRecorder.AudioSource#MIC}
*/
private int audioSource = MediaRecorder.AudioSource.MIC; // 录音硬件来源
private int sampleRateInHz = 44100; // 采样率
private int channelConfig = AudioFormat.CHANNEL_IN_MONO; // 声道设置
/**
* {@link android.media.AudioFormat#ENCODING_PCM_8BIT}
* {@link android.media.AudioFormat#ENCODING_PCM_16BIT}
* {@link android.media.AudioFormat#ENCODING_PCM_FLOAT}
*/
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // PCM 源文件格式
private String dirPath = PathUtils.getExternalAppAudioRecordPath(); // PCM 和 WAV 文件目录
private String srcFileName = "sr_test.pcm"; // PCM 文件名称
private String disFileName = "sr_test.wav"; // WAV 文件名称

private AudioRecord mAudioRecord; // 用来音频录制的AudioRecord
private int mBufferedSizeInBytes; // 音频录制的最小缓冲值

public AudioRecordWrapper() {
int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
}

public AudioRecordWrapper(Builder builder) {
this.audioSource = builder.audioSource;
this.sampleRateInHz = builder.sampleRateInHz;
this.channelConfig = builder.channelConfig;
this.audioFormat = builder.audioFormat;
this.dirPath = builder.dirPath;
this.srcFileName = builder.srcFileName;
this.disFileName = builder.disFileName;

mBufferedSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
if (mBufferedSizeInBytes <= 0) {
throw new IllegalArgumentException("AudioRecord can not be created due to mBufferedSizeInBytes(need great than 0), mBufferedSizeInBytes: " + mBufferedSizeInBytes);
}
mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, mBufferedSizeInBytes);
mRecordState = STATE_INIT;
}

/**
* @param recordFileName pcm 文件名称
* @param audioSource 音频文件来源(通常是麦克风)
* @param sampleRateInHz 采样率
* @param channelConfig 声道设置
* @param audioFormat PCM数据格式
*/
public void createAudioRecord(String recordFileName, int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
mBufferedSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
if (mBufferedSizeInBytes <= 0) {
throw new IllegalArgumentException("AudioRecord is not available due to mBufferedSizeInBytes: " + mBufferedSizeInBytes);
}
mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, mBufferedSizeInBytes);
int state = mAudioRecord.getState();
LogUtils.i(TAG, "createAudio state: " + state + ", initialize: " + (state == AudioRecord.STATE_INITIALIZED));
this.srcFileName = recordFileName;
this.mRecordState = STATE_INIT;
}

/**
* 不能依靠{@link AudioRecord#getState()} 来进行判断
*/
public void startRecord() {
LogUtils.d(TAG, "startRecord()");
if (mAudioRecord == null || (mRecordState != STATE_INIT && mRecordState != STATE_STOP)) {
LogUtils.w(TAG, "can not startRecord due to mAudioRecord is null or mRecordState is wrong");
return;
}
try {
mAudioRecord.startRecording();
mRecordState = STATE_RECORDING;
ThreadUtils.getIoPool().execute(this::writeAudioDataToFile);
} catch (Exception e) {
LogUtils.printStackTrace(TAG, e);
}
}

public void startRecord(String recordFileName) {
this.srcFileName = recordFileName;
LogUtils.d(TAG, "startRecord(), mRecordFileName: " + srcFileName);
startRecord();
}

/**
* 将音频数据写入文件
*
* @throws IOException
*/
private void writeAudioDataToFile() {
LogUtils.d(TAG, "writeAudioDataToFile()");
File recordDir = FileUtils.makeDirs(dirPath);
File file = new File(recordDir, srcFileName);
if (file.exists()) {
file.delete();
}

try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
byte[] audioData = new byte[mBufferedSizeInBytes];
while (mRecordState == STATE_RECORDING) {
// 从音频采集硬件中读取数据
int readSize = mAudioRecord.read(audioData, 0, mBufferedSizeInBytes);
if (readSize > 0) {
bos.write(audioData, 0, readSize);
} else {
LogUtils.w(TAG, "readSize: " + readSize);
}
}
bos.flush();
} catch (Throwable e) {
LogUtils.printStackTrace(TAG, e);
}
}


public void stopRecord() {
LogUtils.d(TAG, "stopRecord()");
if (mAudioRecord == null) {
LogUtils.w(TAG, "mAudioRecord is null");
return;
}
if (mRecordState == STATE_INIT || mRecordState == STATE_RELEASE) {
LogUtils.w(TAG, "stopRecord(), error state");
return;
}

this.mRecordState = STATE_STOP;

pcm2wav("test_" + System.currentTimeMillis() + ".wav");
}

public void release() {
LogUtils.d(TAG, "release()");
if (mAudioRecord != null) {
mAudioRecord.release();
mAudioRecord = null;
}
this.mRecordState = STATE_RELEASE;
}

public void pcm2wav(String wavFileName) {
File pcmFile = new File(dirPath, srcFileName);
File wavFile;
if (TextUtils.isEmpty(wavFileName)) {
wavFile = new File(dirPath, disFileName);
} else {
wavFile = new File(dirPath, wavFileName);
}
ThreadUtils.getIoPool().execute(() -> {
try {
MediaUtils.pcm2Wav(pcmFile, wavFile, false, 1, 44100, 16);
} catch (IOException e) {
LogUtils.printStackTrace(e);
}
});
}

/**
* AudioRecorderWrapper构造器
*/
static class Builder {
private int audioSource; // 录音硬件来源
private int sampleRateInHz; // 采样率
private int channelConfig; // 声道设置
private int audioFormat; // PCM 源文件格式
private String dirPath; // PCM 和 WAV 文件目录
private String srcFileName; // PCM 文件名称
private String disFileName; // WAV 文件名称

public Builder audioSource(int audioSource) {
this.audioSource = audioSource;
return this;
}

public Builder sampleRate(int sampleRateInHz) {
this.sampleRateInHz = sampleRateInHz;
return this;
}

public Builder channel(int channelConfig) {
this.channelConfig = channelConfig;
return this;
}

public Builder audioFormat(int audioFormat) {
this.audioFormat = audioFormat;
return this;
}

public Builder dirPath(String dirPath) {
this.dirPath = dirPath;
return this;
}

public Builder srcFileName(String srcFileName) {
this.srcFileName = srcFileName;
return this;
}

public Builder disFileName(String disFileName) {
this.disFileName = disFileName;
return this;
}

public AudioRecordWrapper build() {
return new AudioRecordWrapper(this);
}
}
}

使用时需要注意一下几点:

  1. 请确保你获取到了麦克风权限;
  2. 读取文件的IO操作请放到子线程中进行;
  3. 注意资源的释放;
  4. 不要过于依赖其提供的状态来进行业务上的判断,最好自己维护。

AudioTrack

AudioTrack类管理和播放Java应用程序的单个音频资源。它允许将PCM音频缓冲区流传输到音频接收器以进行播放。

AudioTrack有两种工作模式:MODE_STATIC & MODE_STREAM

  • MODE_STATIC,静态播放,一次性全部加载到内存播放,这就要求播放的文件小,适用场景自然是短音频;
  • MODE_STREAM,流式播放,将PCM音频数据读入到输入流,然后不断从输入流中读取数据进行播放,可以播放大文件;

AudioTrack使用时有一点需要注意,AudioTrack每次能播放多少数据取决于buffer设置:

  • MODE_STATIC模式下,buffer大小是播放声音的最大大小;
  • MODE_STREAM模式下,buffer取决于读取数据是采用的totalBufferSize(计算出来的)大小;

AudioTrack虽然提供了getState这个方法来获取当前的状态,但我们实际上并不能依据该状态来进行判断,因为需要调用系统硬件都是采用异步形式,我们不能准确的获取到当前的状态,一般需要自己定义一个变量来表示状态。

下面是对AudioTrack的封装,构造好AudioTrackWrapper后,调用start方法即可播放传入的PCM文件,调用stop方法即可停止播放,注意调用完毕后记得调用release释放资源。(ThreadUtils为封装的一个线程操作工具类)

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/**
* AudioTrack 封装
* AudioTrack 可以读取 音频采集生成的 PCM数据 并播放
*
* @author randy
* @date 2021/01/06
*/
public class AudioTrackWrapper {
private static final String TAG = "AudioTrackWrapper";

private final AudioTrack mAudioTrack;
private int streamType = AudioManager.STREAM_MUSIC;
private int sampleRateInHz = 44100;
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
/**
* See {@link AudioTrack#MODE_STATIC} and {@link AudioTrack#MODE_STREAM}.
*/
private int mode = AudioTrack.MODE_STREAM;

private File pcmFile;


/**
* 默认构造行数
*/
public AudioTrackWrapper() {
int minBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes audioAttributes;
AudioFormat format;
int sessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
audioAttributes = new AudioAttributes.Builder()
.setLegacyStreamType(streamType)
.build();
format = new AudioFormat.Builder()
.setChannelMask(channelConfig)
.setEncoding(audioFormat)
.setSampleRate(sampleRateInHz)
.build();

mAudioTrack = new AudioTrack(audioAttributes, format, minBufferSize, mode, sessionId);
} else {
mAudioTrack = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat, minBufferSize, mode);
}
}

/**
* 传参构造器
*
* @param streamType 流类型
* @param sampleRateInHz 采样率
* @param channelConfig 声道配置
* @param audioFormat 音频数据的呈现格式 8位、16位还是浮点
* @param mode 模式(static or stream)
*/
public AudioTrackWrapper(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int mode) {
int minBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes audioAttributes;
AudioFormat format;
int sessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
audioAttributes = new AudioAttributes.Builder()
.setLegacyStreamType(streamType)
.build();
format = new AudioFormat.Builder()
.setChannelMask(channelConfig)
.setEncoding(audioFormat)
.setSampleRate(sampleRateInHz)
.build();

mAudioTrack = new AudioTrack(audioAttributes, format, minBufferSize, mode, sessionId);
} else {
mAudioTrack = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat, minBufferSize, mode);
}
}

/**
* 构造器构造
*
* @param builder 构造器
*/
public AudioTrackWrapper(Builder builder) {
this.streamType = builder.streamType;
this.sampleRateInHz = builder.sampleRateInHz;
this.channelConfig = builder.channelConfig;
this.audioFormat = builder.audioFormat;
this.mode = builder.mode;

mAudioTrack = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat,
AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat), mode);
}

/**
* 开始播放
*
* @param pcmPath pcm 所在目录路径
* @param pcmName pcm 文件名
*/
public void start(String pcmPath, String pcmName) {
start(new File(pcmPath), pcmName);
}

/**
* 开始播放
*
* @param pcmDir pcm 所在目录
* @param pcmName pcm 文件名
*/
public void start(File pcmDir, String pcmName) {
File pcmFile = new File(pcmDir, pcmName);
start(pcmFile);
}

/**
* 开始播放
*
* @param pcmFile 要播放的pcm文件
*/
public void start(File pcmFile) {

if (pcmFile == null || !pcmFile.exists()) {
LogUtils.e(TAG, "pcmFile is null or not exists!!!");
return;
}
this.pcmFile = pcmFile;
LogUtils.d(TAG, "start(), pcmFile: " + pcmFile.getPath());
if (mode == AudioTrack.MODE_STREAM) {
playByStream(pcmFile);
} else {
playByStatic(pcmFile);
}
}

/**
* 流模式,数据会从 Java 层传输到底层,并排队阻塞等待播放
* 1、所以这里需要开启线程独立播放
* 2、为保证能重复使用,需要对状态进行控制
*
* @param pcmFile pcm 文件
*/
private void playByStream(File pcmFile) {
this.pcmFile = pcmFile;
LogUtils.d(TAG, "playByStream()");
// 先取消
ThreadUtils.cancel(readPcmFileTask);
ThreadUtils.executeByIo(readPcmFileTask);
}


private final ThreadUtils.Task<Object> readPcmFileTask = new ThreadUtils.SimpleTask<Object>() {
@Override
public Object doInBackground() throws Throwable {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(pcmFile);
byte[] buffer = new byte[AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)];
int readCount;
while ((readCount = inputStream.read(buffer)) > 0) {
mAudioTrack.play();
// 将读取的 pcm 数据写入到AudioTack中,等待播放
mAudioTrack.write(buffer, 0, readCount);
}
mAudioTrack.stop();
mAudioTrack.release();
} catch (Exception e) {
LogUtils.printStackTrace(e);
} finally {
CloseUtils.closeIOQuietly(inputStream);
}
return null;
}

@Override
public void onSuccess(Object result) {
LogUtils.d(TAG, "pcm文件读取完毕");
}
};

/**
* 一次性读写 PCM 数据到buffer中进行播放
*
* @param pcmFile pcm 文件
*/
private void playByStatic(File pcmFile) {
LogUtils.d(TAG, "playByStatic()");
Runnable runnable = () -> {
FileInputStream inputStream = null;
ByteArrayOutputStream outputStream = null;

try {
inputStream = new FileInputStream(pcmFile);
outputStream = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[1024];
// 从 pcm 文件中读取数据 并写到 outputStream 中,
// 然后 从 outputStream 中获取数据共 AudioTrack 播放
while ((len = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, len);
}
byte[] bytes = outputStream.toByteArray();
if (mAudioTrack != null) {
mAudioTrack.write(bytes, 0, bytes.length);
mAudioTrack.play();
}
} catch (Exception e) {
LogUtils.printStackTrace(e);
} finally {
CloseUtils.closeIOQuietly(outputStream, inputStream);
}
};

ThreadUtils.getIoPool().execute(runnable);
}

/**
* 暂停播放
*/
public void pause() {
if (mAudioTrack != null) {
mAudioTrack.pause();
}
}

/**
* 停止播放
*/
public void stop() {
if (mAudioTrack != null) {
mAudioTrack.stop();
}
}

/**
* 释放资源
*/
public void release() {
if (mAudioTrack != null) {
mAudioTrack.release();
}
}

/**
* AudioTrack构造器
*/
static class Builder {
private int streamType = AudioManager.STREAM_MUSIC;
private int sampleRateInHz = 44100;
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int mode = AudioTrack.MODE_STREAM;

public Builder setStreamType(int streamType) {
this.streamType = streamType;
return this;
}

public Builder setSampleRateInHz(@IntRange(from = 4000, to = 192000) int sampleRateInHz) {
this.sampleRateInHz = sampleRateInHz;
return this;
}

public Builder setChannelConfig(int channelConfig) {
this.channelConfig = channelConfig;
return this;
}

public Builder setAudioFormat(int audioFormat) {
this.audioFormat = audioFormat;
return this;
}

public Builder setMode(int mode) {
this.mode = mode;
return this;
}

public AudioTrackWrapper build() {
return new AudioTrackWrapper(this);
}
}

}

MediaRecorder

MediaRecorder用来录制音频和视频的,使用比较广,音视频的录制控制基于下面这个状态机:

mediarecorder_state_diagram

所以在使用的时候要严格控制函数的调用顺序,否则很容易产生IllegalStateException。录制音频的一般调用循序如下所示:

1
2
3
4
5
6
7
8
9
10
11
MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GP);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.ARM_NB);
mediaRecorder.setOutputFile(OUTPUT_FILE_PATH);
mediaRecorder.prepare();
mediaRecorder.start();
...
mediaRecorder.stop();
mediaRecorder.reset(); // reset之后MediaRecorder会重新回到Initial状态,再次调用setAudioSource可重复使用
mediaRecorder.release(); // 释放了之后就不能再使用了。

录制视频大体和录制音频相同,知识需要加上视频源设置、视频输出格式、视频编码、以及预览的容器,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA) // 指定视频源
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); // 这里要换成视频格式
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.ARM_NB);
mediaRecorder.setVidioEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP); // 设置视频编码
mediaRecorder.setOutputFile(OUTPUT_FILE_PATH);
mediaRecorder.setPreviewDisplay(targetSurface); // 指定视频预览的容器
mediaRecorder.prepare();
mediaRecorder.start();
...
mediaRecorder.stop();
mediaRecorder.reset(); // reset之后MediaRecorder会重新回到Initial状态,再次调用setAudioSource可重复使用
mediaRecorder.release(); // 释放了之后就不能再使用了。

下面贴出利用MediaRecorder录制音频的代码片段:

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
private File outPutFile;

private void onMediaRecorderStartClick() {
PermissionUtils.permission(PermissionConstants.MICROPHONE).callback(new PermissionUtils.SimpleCallback() {
@Override
public void onGranted() {
if (mMediaRecorder == null) {
mMediaRecorder = new MediaRecorder();
}
try {
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
String fileName = TimeUtils.getNowString() + ".m4a";
File outPutDir = FileUtils.makeDirs(PathUtils.getExternalAppAudioRecordPath());
outPutFile = new File(outPutDir, fileName);

mMediaRecorder.setOutputFile(outPutFile.getPath());
mMediaRecorder.prepare();
mMediaRecorder.start();
} catch (Exception e) {
LogUtils.printStackTrace(TAG, e);
}
}

@Override
public void onDenied() {
ToastUtils.showLong("您拒绝了录音权限,无法录音!");
}
}).request();

}

private void onMediaRecorderStopClick() {
LogUtils.d(TAG, "onMediaRecorderStopClick");
try {
if (mMediaRecorder != null) {
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder = null;
}
} catch (Exception e) {
LogUtils.printStackTrace(TAG, e);
if (mMediaRecorder != null) {
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
}
// 删除掉异常停止前产生的文件
FileUtils.deleteFile(outPutFile);
}
}

总结

以上就是Android音频采集相关的内容,AudioRecord主要负责音频采集,AudioTrack负责pcm数据的播放,MediaRecorder既可以进行音频采集,也可以配合CameraApi进行视频采集,视频采集会加接下来的文章中讲到。