-
Notifications
You must be signed in to change notification settings - Fork 5
/
AudioUtils.java
491 lines (453 loc) · 21 KB
/
AudioUtils.java
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
package com.actor.myandroidframework.utils.audio;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.text.TextUtils;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RawRes;
import com.actor.myandroidframework.utils.BaseCountDownTimer;
import com.actor.myandroidframework.utils.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.TimeUtils;
import java.io.File;
import java.io.IOException;
import java.util.Date;
/**
* description: 音频录制, 播放 <br />
* <a href="https://blog.csdn.net/weixin_44008788/article/details/122260697">Android 多媒体框架之音频录制 MediaRecorder 和 AudioRecorder_android mediarecorder-CSDN博客</a> <br />
* <a href="https://cloud.tencent.com/document/product/269/3794">一天接入 SDK-即时通信 IM-文档中心-腾讯云</a> <br />
* <a href="https://github.com/tencentyun/TIMSDK">TencentCloud_TIMSDK - Github</a> <br />
* <br />
* Android 多媒体框架针对音频录制提供了两种方法:MediaRecorder和AudioRecord。 <br />
* AudioRecord和MediaRecorder两种都可以录制音频,MediaRecorder已实现大量的封装,操作起来更加简单,而AudioRecord使用起来更加灵活,能实现更多的功能。 <br />
* <br />
* MediaRecorder: 已集成了录音,编码,压缩等,支持少量的音频格式文件。 <br />
* 优点:封装度很高,操作简单 <br />
* 缺点:无法实现实时处理音频,输出的音频格式少。 <br />
* MediaRecorder 录制的音频文件是经过压缩后的,需要设置编码器,并且录制的音频文件可以用系统自带的播放器播放。MediaRecorder属于系统API高度封装,所以可扩展性和可用性都比较局限,支持的格式过少并且无法实时处理音频数据,使用场景如语音消息录制等,值得一提的是MediaRecorder通常和视频录制一起使用。 <br />
*
* <ol>
* <li>
* 如果需要录音, 需要在清单文件中添加权限: <br />
* <code><uses-permission android:name="android.permission.RECORD_AUDIO" /> <br /></code>
* </li>
* <li>
* 就可以使用了
* </li>
* </ol>
* 3.开始录音等 <br />
*
* author : ldf
* date : 2019/5/30 on 17:43
*/
public class AudioUtils {
//是否正在录制中
protected boolean isRecording;
//录制的语音存储目录: /data/data/package/files/Records
protected String recordDir = PathUtils.getInternalAppFilesPath();
protected int maxRecordTimeMs = 2 * 60 * 1000; //最大录音时长, 默认2分钟
protected String recordAudioPath; //录音文件具体地址
protected MediaRecorder mMediaRecorder;
protected MediaRecorderCallback mRecorderCallback;
protected BaseCountDownTimer countDownTimer;
protected MediaPlayer mMediaPlayer;
protected MediaPlayerCallback mPlayerCallback;
protected boolean isAutoPlay; //是否自动播放
//准备完成监听
protected MediaPlayer.OnPreparedListener mOnPreparedListener = new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
if (mPlayerCallback != null) mPlayerCallback.onPrepared(mp);
if (isAutoPlay) startPlayer(); //开始播放
}
};
//播放错误监听
protected MediaPlayer.OnErrorListener mOnErrorListener = new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
if (mPlayerCallback != null) return mPlayerCallback.onPlayError(mp, what, extra);
return false;
}
};
//播放完成监听
protected MediaPlayer.OnCompletionListener mOnCompletionListener = new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
if (mPlayerCallback != null) mPlayerCallback.onCompletion(mp);
}
};
protected static AudioUtils instance;
public static AudioUtils getInstance() {
if (instance == null) instance = new AudioUtils();
return instance;
}
///////////////////////////////////////////////////////////////////////////
// 下方是录制
///////////////////////////////////////////////////////////////////////////
/**
* 设置录制的语音存储目录(有默认值, 可以不设置)
*/
public void setRecordDir(@NonNull String recordDir) {
if (!TextUtils.isEmpty(recordDir)) this.recordDir = recordDir;
}
/**
* 设置最大录音时长
* @param maxRecordTimeMs 最大录音时长, 单位毫秒(默认2分钟, 可以不设置)
*/
public void setMaxRecordTimeMs(@IntRange(from = 1) int maxRecordTimeMs) {
if (maxRecordTimeMs > 0) this.maxRecordTimeMs = maxRecordTimeMs;
}
/**
* 获取倒计时
*/
@NonNull
protected BaseCountDownTimer getCountDownTimer() {
if (countDownTimer == null) {
countDownTimer = new BaseCountDownTimer(maxRecordTimeMs, 80) {
@Override
protected void onTick(long millisUntilFinished) {
}
@Override
protected void onFinish() {
stopRecord(false); //停止录音
}
};
}
return countDownTimer;
}
/**
* 录制.amr格式音频
* @param callback 录制回调
*/
public void startRecordAmr(@Nullable MediaRecorderCallback callback) {
startRecord(MediaRecorder.AudioSource.MIC, MediaRecorder.OutputFormat.RAW_AMR, MediaRecorder.AudioEncoder.AMR_NB, ".amr", callback);
}
/**
* 录制.3gp格式音频
* @param callback 录制回调
*/
public void startRecord3gp(@Nullable MediaRecorderCallback callback) {
startRecord(MediaRecorder.AudioSource.MIC, MediaRecorder.OutputFormat.THREE_GPP, MediaRecorder.AudioEncoder.AMR_NB, ".3gp", callback);
}
/**
* 录制.m4a格式音频
* @param callback 录制回调
*/
public void startRecordM4a(@Nullable MediaRecorderCallback callback) {
startRecord(MediaRecorder.AudioSource.MIC, MediaRecorder.OutputFormat.MPEG_4, MediaRecorder.AudioEncoder.AAC, ".m4a", callback);
}
/**
* 录制音频
* @param audioSource 音频源:
* <ol>
* <li>{@link MediaRecorder.AudioSource#DEFAULT}: 默认音频源</li>
* <li>{@link MediaRecorder.AudioSource#MIC}: 麦克风音频源</li>
* <li>{@link MediaRecorder.AudioSource#VOICE_UPLINK}: 语音呼叫上行链路 (Tx) 音频源。</li>
* <li>{@link MediaRecorder.AudioSource#VOICE_DOWNLINK}: 语音呼叫下行链路 (Rx) 音频源。</li>
* <li>{@link MediaRecorder.AudioSource#VOICE_CALL}: 语音通话上行+下行音频源</li>
* <li>{@link MediaRecorder.AudioSource#CAMCORDER}: 麦克风音频源调整为视频录制,与相机相同的方向(如果可用)。</li>
* <li>{@link MediaRecorder.AudioSource#VOICE_RECOGNITION}: 麦克风音频源经过调谐,可进行语音识别。</li>
* <li>{@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}: 针对 VoIP 等语音通信进行调整的麦克风音频源。例如,它将利用回声消除或自动增益控制(如果可用)。</li>
* <li>{@link MediaRecorder.AudioSource#REMOTE_SUBMIX}: 用于远程呈现的音频流子混合的音频源。</li>
* <li>{@link MediaRecorder.AudioSource#UNPROCESSED}: 麦克风音频源调整为未处理的(原始)声音(如果可用),行为类似{@link MediaRecorder.AudioSource#DEFAULT}。</li>
* <li>{@link MediaRecorder.AudioSource#VOICE_PERFORMANCE}: 用于捕获音频的源,旨在实时处理并播放以进行现场表演(例如卡拉 OK)。</li>
* </ol>
* @param outputFormat 输出格式:
* <ol>
* <li>{@link MediaRecorder.OutputFormat#DEFAULT}: 默认输出格式</li>
* <li>{@link MediaRecorder.OutputFormat#THREE_GPP}: 3GP 文件格式(.3gp也是一种视频格式,H263视频/ARM音频编码)</li>
* <li>{@link MediaRecorder.OutputFormat#MPEG_4}: MPEG-4 文件格式(3gp也是一种视频格式)</li>
* <li>{@link MediaRecorder.OutputFormat#RAW_AMR}: 只支持音频且音频编码要求为AMR_NB</li>
* <li>{@link MediaRecorder.OutputFormat#AMR_NB}: AMR-NB 文件格式</li>
* <li>{@link MediaRecorder.OutputFormat#AMR_WB}: AMR-WB 文件格式</li>
* <li>{@link MediaRecorder.OutputFormat#AAC_ADTS}: AAC ADTS 文件格式</li>
* <li>{@link MediaRecorder.OutputFormat#MPEG_2_TS}: </li>
* <li>{@link MediaRecorder.OutputFormat#WEBM}: WebM 文件格式</li>
* <li>{@link MediaRecorder.OutputFormat#OGG}: </li>
* </ol>
* @param audioEncoder 音频编码器:
* <ol>
* <li>{@link MediaRecorder.AudioEncoder#DEFAULT}: 默认音频编码器(声音的(波形)的采样?)</li>
* <li>{@link MediaRecorder.AudioEncoder#AMR_NB}: AMR-NB 音频编码器</li>
* <li>{@link MediaRecorder.AudioEncoder#AMR_WB}: AMR-WB 音频编码器</li>
* <li>{@link MediaRecorder.AudioEncoder#AAC}: AAC 音频编码器</li>
* <li>{@link MediaRecorder.AudioEncoder#HE_AAC}: 高效 AAC(HE-AAC)音频编码器</li>
* <li>{@link MediaRecorder.AudioEncoder#AAC_ELD}: AAC ELD 音频编码器</li>
* <li>{@link MediaRecorder.AudioEncoder#VORBIS}: </li>
* <li>{@link MediaRecorder.AudioEncoder#OPUS}: </li>
* </ol>
* @param suffix 音频后缀, 例: .amr, .3gp, .m4a
* @param callback 录制回调
*/
public void startRecord(int audioSource, int outputFormat, int audioEncoder, @NonNull String suffix, @Nullable MediaRecorderCallback callback) {
if (isRecording) {
LogUtils.error("正在录制中, 请先停止录制!");
if (callback != null) callback.recordError(new IllegalStateException("正在录制中, 请先停止录制!"));
return;
}
mRecorderCallback = callback;
try {
if (mMediaRecorder == null) {
mMediaRecorder = new MediaRecorder();
} else {
// if (isRecording) mMediaRecorder.stop();
mMediaRecorder.reset();
}
//设置音频源: setAudioSource, setVideoSource
mMediaRecorder.setAudioSource(audioSource);
//设置输出格式
mMediaRecorder.setOutputFormat(outputFormat);
//设置音频编码
mMediaRecorder.setAudioEncoder(audioEncoder);
//年月日时分秒毫秒
String fileName = TimeUtils.date2String(new Date(), "yyyyMMddHHmmssSSS");
recordAudioPath = new File(recordDir, fileName + suffix).getAbsolutePath();
//设置输出位置
mMediaRecorder.setOutputFile(recordAudioPath);
//准备
mMediaRecorder.prepare();
//设置倒计时
getCountDownTimer().cancel();
getCountDownTimer().setMillisInFuture(maxRecordTimeMs);
//开始
mMediaRecorder.start();
getCountDownTimer().start();
isRecording = true;
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
if (mRecorderCallback != null) mRecorderCallback.recordError(e);
}
}
/**
* 是否正在录制中
*/
public boolean isRecording() {
return isRecording;
}
/**
* 结束录音
* @param isCanceled 是否已经取消录音(比如按住时 上滑取消)
*/
public void stopRecord(boolean isCanceled) {
//if不是正常的倒计时完成, 就手动取消倒计时
if (getCountDownTimer().getState() != BaseCountDownTimer.Status.FINISH) getCountDownTimer().cancel();
if (isRecording) {
isRecording = false;
try {
mMediaRecorder.stop();
if (mRecorderCallback != null) {
if (isCanceled) {
mRecorderCallback.recordCancel(getRecordAudioPath(), getRecordDuration());
} else mRecorderCallback.recordComplete(getRecordAudioPath(), getRecordDuration());
}
} catch (IllegalStateException e) {
e.printStackTrace();
if (mRecorderCallback != null) mRecorderCallback.recordError(e);
}
//置空回调
mRecorderCallback = null;
}
}
/**
* 释放录音资源
*/
public void releaseMediaRecorder() {
getCountDownTimer().cancel();
countDownTimer = null;
mRecorderCallback = null;
stopRecord(true);
if (mMediaRecorder != null) {
mMediaRecorder.release();
mMediaRecorder = null;
}
}
/**
* @return 获取录音地址
*/
public String getRecordAudioPath() {
return recordAudioPath;
}
/**
* @return 获取录音时长, 单位ms
*/
public long getRecordDuration() {
if (countDownTimer == null) return 0;
return countDownTimer.getTimingDuration();
}
///////////////////////////////////////////////////////////////////////////
// 下方是播放
///////////////////////////////////////////////////////////////////////////
/**
* 播放R.raw.xxx 音频
* @param rawId 资源id
* @param isLooping 是否循环播放
* @param isAutoPlay 准备完成后, 是否自动播放
* @param playerCallback 播放回调
*/
public void playRaw(Context context, @RawRes int rawId, boolean isLooping, boolean isAutoPlay,
@Nullable MediaPlayerCallback playerCallback) {
playRaw(context, rawId, isLooping, isAutoPlay, true, playerCallback);
}
/**
* 播放R.raw.xxx 音频
* @param rawId 资源id
* @param isLooping 是否循环播放
* @param isAutoPlay 准备完成后, 是否自动播放
* @param playerCallback 播放回调
* @param isNewMediaPlayer 是否使用新的MediaPlayer, 而不是已存在的那个.(如果想同时播放多个, 传true)
*/
public void playRaw(Context context, @RawRes int rawId, boolean isLooping, boolean isAutoPlay,
boolean isNewMediaPlayer, @Nullable MediaPlayerCallback playerCallback) {
mPlayerCallback = playerCallback;
this.isAutoPlay = isAutoPlay;
if (!isNewMediaPlayer && mMediaPlayer != null) {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
}
try {
mMediaPlayer = MediaPlayer.create(context, rawId); //nullable
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setVolume(1, 1);
mMediaPlayer.setLooping(isLooping);
//准备完成监听
mMediaPlayer.setOnPreparedListener(mOnPreparedListener);
//播放出错监听
mMediaPlayer.setOnErrorListener(mOnErrorListener);
mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
//播放本地的时候, prepare 会报错: prepareAsync called in state 8, mPlayer(0x7e0c7f62c0)
// mMediaPlayer.prepareAsync();
// mMediaPlayer.prepare();
//
mOnPreparedListener.onPrepared(mMediaPlayer);
} catch (NullPointerException | IllegalStateException e) {
e.printStackTrace();
if (playerCallback != null) {
boolean isDealBySelf = playerCallback.onSetData2StartError(e);
if (!isDealBySelf) playerCallback.onCompletion(mMediaPlayer);
}
}
}
/**
* 播放音频
* @param audioPath 本地/网络音频
* @param isLooping 是否循环播放
* @param isAutoPlay 准备完成后, 是否自动播放
* @param playerCallback 播放监听
*/
public void play(@NonNull String audioPath, boolean isLooping, boolean isAutoPlay,
@Nullable MediaPlayerCallback playerCallback) {
play(audioPath, isLooping, isAutoPlay, false, playerCallback);
}
/**
* 播放音频
* @param audioPath 本地/网络音频
* @param isLooping 是否循环播放
* @param isAutoPlay 准备完成后, 是否自动播放
* @param playerCallback 播放监听
* @param isNewMediaPlayer 是否使用新的MediaPlayer, 而不是已存在的那个.(如果想同时播放多个, 传true)
*/
public void play(@NonNull String audioPath, boolean isLooping, boolean isAutoPlay,
boolean isNewMediaPlayer, @Nullable MediaPlayerCallback playerCallback) {
if (TextUtils.isEmpty(audioPath)) {
if (playerCallback != null) {
boolean isDealBySelf = playerCallback.onSetData2StartError(new NullPointerException("audioPath is Empty!"));
if (!isDealBySelf) playerCallback.onCompletion(mMediaPlayer);
}
return;
}
mPlayerCallback = playerCallback;
this.isAutoPlay = isAutoPlay;
try {
if (isNewMediaPlayer || mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
// AudioAttributes attrs = new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build();
// mMediaPlayer.setAudioAttributes(attrs);
} else mMediaPlayer.reset();
// mMediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
// mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
//设置数据源, 本地or网上
mMediaPlayer.setDataSource(audioPath);
mMediaPlayer.setLooping(isLooping);
//准备完成监听
mMediaPlayer.setOnPreparedListener(mOnPreparedListener);
//播放出错监听
mMediaPlayer.setOnErrorListener(mOnErrorListener);
// mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);
mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
mMediaPlayer.prepareAsync(); //主线程中异步准备, 准备监听完成后开始播放
// mMediaPlayer.prepare();
// mMediaPlayer.start();
} catch (IOException | IllegalArgumentException | SecurityException | IllegalStateException e) {
e.printStackTrace();
if (playerCallback != null) {
boolean isDealBySelf = playerCallback.onSetData2StartError(e);
if (!isDealBySelf) playerCallback.onCompletion(mMediaPlayer);
}
}
}
/**
* 获取'播放'的音频的时长 (要先设置音频)
* @return 单位ms
*/
public int getDuration() {
if (mMediaPlayer == null) return -1;
return mMediaPlayer.getDuration();
}
/**
* 开始播放音频
*/
public void startPlayer() {
try {
if (mMediaPlayer != null) mMediaPlayer.start();
} catch (IllegalStateException e) {
e.printStackTrace();
if (mPlayerCallback != null) {
boolean isDealBySelf = mPlayerCallback.onSetData2StartError(e);
if (!isDealBySelf) mPlayerCallback.onCompletion(mMediaPlayer);
}
}
}
/**
* 暂停播放音频 <br />
* 继续播放的话调用 {@link #startPlayer()}
*/
public void pausePlayer() {
try {
if (mMediaPlayer != null) mMediaPlayer.pause();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
/**
* 停止播放音频
*/
public void stopPlayer() {
try {
//开始 or 暂停 后, 可以停止
if (mMediaPlayer != null) mMediaPlayer.stop();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
/**
* @return 是否正在播放
*/
public boolean isPlaying() {
return mMediaPlayer != null && mMediaPlayer.isPlaying();
}
/**
* 释放播放器资源
*/
public void releaseMediaPlayer() {
mPlayerCallback = null;
stopPlayer();
if (mMediaPlayer != null) {
mMediaPlayer.release();
mMediaPlayer = null;
}
}
}