NIMSDK-AOS  10.9.76
AudioRecorder.java
浏览该文件的文档.
1 package com.netease.nimlib.sdk.media.record;
2 
3 import android.content.Context;
4 import android.media.AudioManager;
5 import android.os.Handler;
6 import android.os.HandlerThread;
7 import android.os.Looper;
8 import android.os.Message;
9 import android.text.TextUtils;
10 
11 import com.netease.nimlib.SDKCache;
12 import com.netease.nimlib.log.NimLog;
13 import com.netease.nimlib.net.http.util.AttachmentStore;
14 import com.netease.nimlib.util.NetworkUtil;
15 import com.netease.nimlib.util.StringUtil;
16 import com.netease.nimlib.util.storage.NimStorageType;
17 import com.netease.nimlib.util.storage.NimStorageUtil;
18 import com.netease.share.media.AudioRecord;
19 import com.netease.share.media.OnInfoListener;
20 
21 import java.io.File;
22 import java.util.concurrent.atomic.AtomicBoolean;
23 
24 /**
25  * 高清语音录制工具类
26  * Created by huangjun on 2015/4/1.
27  */
28 public class AudioRecorder {
29 
30  public static final int DEFAULT_MAX_AUDIO_RECORD_TIME_SECOND = 120;
31  private static final int MSG_START_RECORD = 1;
32  private static final int MSG_STOP_RECORD = 2;
33  private static final int MSG_END_RECORD = 3; // 录音超时等异常结束
34 
35  private static final int RECORD_FAILED = 1;
36  private static final int RECORD_READY = 2;
37  private static final int RECORD_START = 3;
38  private static final int RECORD_SUCCESS = 4;
39  private static final int RECORD_CANCELED = 5;
40  private static final String TAG = "AudioRecordManager";
41 
42  private AudioRecord mAudioRecorder;
43  private AudioManager audioManager;
44 
45  private Context context;
46  private int networkClass = NetworkUtil.NETWORK_CLASS_UNKNOWN;
47 
48  private File audioFile;
49  private RecordType recordType;
50  private int maxDuration;
51  private AtomicBoolean isRecording = new AtomicBoolean(false);
52  private AtomicBoolean cancelRecord = new AtomicBoolean(false);
53  private IAudioRecordCallback cb;
54 
55  private RecordHandler mHandler;
56  private Handler mEventHandler = new Handler(Looper.getMainLooper());
57  private HandlerThread handlerThread;
58 
59  private class RecordHandler extends Handler {
60 
61  public RecordHandler(Looper looper) {
62  super(looper);
63  }
64 
65  @Override
66  public void handleMessage(Message msg) {
67  switch (msg.what) {
68  case MSG_START_RECORD:
69  onStartRecord();
70  break;
71  case MSG_STOP_RECORD:
72  boolean cancel = (Boolean) msg.obj;
73  onCompleteRecord(cancel);
74  break;
75  case MSG_END_RECORD:
76  boolean success = (Boolean) msg.obj;
77  int duration = msg.arg1;
78  onHandleEndRecord(success, duration);
79  break;
80  }
81  }
82  }
83 
84  /**
85  * 构造函数
86  *
87  * @param context 上下文
88  * @param recordType 录制音频类型(aac/amr)
89  * @param maxDuration 最长录音时长,到该长度后,会自动停止录音
90  * @param cb 录音过程回调
91  */
92  public AudioRecorder(
93  Context context,
94  RecordType recordType,
95  int maxDuration,
97  ) {
98  this.context = context.getApplicationContext();
99  this.recordType = recordType;
100  if (maxDuration <= 0) {
101  this.maxDuration = DEFAULT_MAX_AUDIO_RECORD_TIME_SECOND;
102  } else {
103  this.maxDuration = maxDuration;
104  }
105  this.cb = cb;
106 
107  audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
108  handlerThread = new HandlerThread("audio_recorder");
109  handlerThread.start();
110  mHandler = new RecordHandler(handlerThread.getLooper());
111  }
112 
113  /**
114  * 启动(开始)录音,如果成功,会按照顺序回调onRecordReady和onRecordStart
115  **/
116  public void startRecord() {
117  // 移除队列中的 开始任务
118  mHandler.removeMessages(MSG_START_RECORD);
119  mHandler.obtainMessage(MSG_START_RECORD).sendToTarget();
120  }
121 
122  private void onStartRecord() {
123  audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
124 
125  if (isRecording.get()) {
126  NimLog.audio(TAG + " startRecord false, as current state is isRecording");
127  callBackRecordState(RECORD_FAILED);
128  return;
129  }
130 
131  if (!NimStorageUtil.hasEnoughSpaceForWrite(NimStorageType.TYPE_AUDIO)) {
132  NimLog.audio(TAG + " startRecord false, as has no enough space to write");
133  callBackRecordState(RECORD_FAILED);
134  return;
135  }
136 
137  int outputFormat = recordType.getOutputFormat();
138 
139  String outputFilePath = NimStorageUtil.getWritePath(SDKCache.getContext(), StringUtil.get36UUID() + outputFormat,
140  NimStorageType.TYPE_AUDIO);
141  if (TextUtils.isEmpty(outputFilePath)) {
142  NimLog.audio(TAG + " startRecord false, as outputFilePath is empty");
143  callBackRecordState(RECORD_FAILED);
144  return;
145  } else {
146  outputFilePath = outputFilePath + recordType.getFileSuffix();
147  audioFile = new File(outputFilePath);
148  }
149 
150  cancelRecord.set(false);
151 
152  try {
153  mAudioRecorder = new AudioRecord(context, outputFilePath, maxDuration * 1000);
154  mAudioRecorder.setAudioCodec(outputFormat);
155  networkClass = NetworkUtil.getNetworkClass(context);
156  if (networkClass == NetworkUtil.NETWORK_CLASS_3_G)
157  mAudioRecorder.setMaxSampleRateInHz(22050);
158  else if (networkClass == NetworkUtil.NETWORK_CLASS_2_G)
159  mAudioRecorder.setMaxSampleRateInHz(16000);
160  mAudioRecorder.setOnInfoListener(infoListener);
161 
162  if (!cancelRecord.get()) {
163  callBackRecordState(RECORD_READY);
164  if (mAudioRecorder.startRecording()) {
165  isRecording.set(true);
166  callBackRecordState(RECORD_START);
167  }
168  }
169  } catch (Exception e) {
170  e.printStackTrace();
171  onCompleteRecord(false);
172  }
173  if (!isRecording.get()) {
174  callBackRecordState(RECORD_FAILED);
175  }
176  }
177 
178  /**
179  * 完成(结束)录音,根据参数cancel,做不同的回调。
180  * 如果cancel为true,回调onRecordCancel, 为false,回调onRecordSuccess
181  *
182  * @param cancel 是正常结束还是取消录音
183  */
184  public void completeRecord(boolean cancel) {
185  Message message = mHandler.obtainMessage(MSG_STOP_RECORD);
186  message.obj = cancel;
187  message.sendToTarget();
188  }
189 
190  private void onCompleteRecord(boolean cancel) {
191  if (!isRecording.get()) {
192  return;
193  }
194  cancelRecord.set(cancel);
195  audioManager.abandonAudioFocus(null);
196  try {
197  if (mAudioRecorder != null) {
198  mAudioRecorder.stopRecording();
199  // 改为等待 MEDIA_RECORDER_INFO_RECORD_COMPLETE
200 // onHandleEndRecord(true, mAudioRecorder.duration());
201  mAudioRecorder = null;
202  }
203  } catch (Exception e) {
204  e.printStackTrace();
205  }
206  }
207 
208  /**
209  * release资源
210  */
211  public void destroyAudioRecorder() {
212  if (mHandler != null) {
213  mHandler.removeCallbacksAndMessages(null);
214  }
215  if (handlerThread != null && handlerThread.isAlive()) {
216  Looper looper = handlerThread.getLooper();
217  looper.quit();
218  }
219  }
220 
221  /**
222  * 是否正在录音
223  */
224  public boolean isRecording() {
225  return isRecording.get();
226  }
227 
228  public void handleEndRecord(boolean isSuccess, int duration) {
229  Message message = mHandler.obtainMessage(MSG_END_RECORD);
230  message.obj = isSuccess;
231  message.arg1 = duration;
232  message.sendToTarget();
233  }
234 
235  private void onHandleEndRecord(boolean isSuccess, final int duration) {
236  if (cancelRecord.get()) {
237  // cancel
238  AttachmentStore.deleteOnExit(audioFile.getAbsolutePath());
239 
240  callBackRecordState(RECORD_CANCELED);
241  } else if (!isSuccess) {
242  // failed
243  AttachmentStore.deleteOnExit(audioFile.getAbsolutePath());
244  callBackRecordState(RECORD_FAILED);
245  } else {
246  // error
247  if (audioFile == null || !audioFile.exists() || audioFile.length() <= 0) {
248  callBackRecordState(RECORD_FAILED);
249  } else {
250  // success
251  mEventHandler.post(new Runnable() {
252  @Override
253  public void run() {
254  cb.onRecordSuccess(audioFile, duration, recordType);
255  }
256  });
257  }
258  }
259  isRecording.set(false);
260  }
261 
262  private void callBackRecordState(final int recordState) {
263  mEventHandler.post(new Runnable() {
264  @Override
265  public void run() {
266  switch (recordState) {
267  case RECORD_FAILED:
268  cb.onRecordFail();
269  break;
270  case RECORD_READY:
271  cb.onRecordReady();
272  break;
273  case RECORD_START:
274  cb.onRecordStart(audioFile, recordType);
275  break;
276  case RECORD_CANCELED:
277  cb.onRecordCancel();
278  default:
279  break;
280  }
281  }
282  });
283  }
284 
285  /**
286  * 获取当前录音时最大振幅, 40ms更新一次数据。 每次获取后数值会重置。
287  *
288  * @return 当前录音时最大振幅
289  */
291  if (mAudioRecorder != null) {
292  return mAudioRecorder.getMaxAmplitude();
293  }
294 
295  return 0;
296  }
297 
298  private void handleReachedMaxRecordTime(int duration) {
299  cb.onRecordReachedMaxTime(duration);
300  }
301 
302  // UI线程回调
303  private final OnInfoListener infoListener = new OnInfoListener() {
304  @Override
305  public void onInfo(int session, int what, int extra) {
306  switch (what) {
307  case OnInfoListener.MEDIA_RECORDER_INFO_ERROR:
308  handleEndRecord(false, 0);
309  break;
310  case OnInfoListener.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
311  mHandler.post(() -> isRecording.set(false));
312  handleReachedMaxRecordTime(extra);
313  break;
314  case OnInfoListener.MEDIA_RECORDER_INFO_RECORD_COMPLETE:
315  // ★ 录制已真正结束,触发最终回调
316  handleEndRecord(true, extra /* 时长 */);
317  break;
318  default:
319  break;
320  }
321  }
322  };
323 
324 }
void completeRecord(boolean cancel)
完成(结束)录音,根据参数cancel,做不同的回调。 如果cancel为true,回调onRecordCancel, 为false,回调onRecordSuccess
高清语音录制工具类 Created by huangjun on 2015/4/1.
录音类型,支持AAC、AMR格式输出
Definition: RecordType.java:11
void startRecord()
启动(开始)录音,如果成功,会按照顺序回调onRecordReady和onRecordStart
AudioRecorder(Context context, RecordType recordType, int maxDuration, IAudioRecordCallback cb)
构造函数
void handleEndRecord(boolean isSuccess, int duration)
void onRecordStart(File audioFile, RecordType recordType)
开始录音回调
void onRecordReachedMaxTime(int maxTime)
到达指定的最长录音时间
void onRecordCancel()
录音结束, 用户主动取消录音
void onRecordReady()
录音器已就绪,提供此接口用于在录音前关闭本地音视频播放(可选)
int getCurrentRecordMaxAmplitude()
获取当前录音时最大振幅, 40ms更新一次数据。 每次获取后数值会重置。
void onRecordSuccess(File audioFile, long audioLength, RecordType recordType)
录音结束,成功