一. AudioRecord 和 MediaRecorder
Android 提供了两个 API 用于录音,AudioRecord 和 MediaRecorder
- AudioRecord:能够获取原始的 PCM 数据,实现音频数据的实时处理,PCM 音频不能直接播放,需要通过 AudioTrack 播放。
- MediaRecorder:封装了编码器,内部集成了录音,编码等功能,但是支持的格式较少,且不能实时处理音频数据。
因此在需要处理音频数据的基础上,通常选择用 AudioRecord 来采集音频数据。
二. AudioRecord 的使用
1. 构造
构造 AudioRecord 需要几个参数:
- int audioSource 音频源,通常使用麦克风
MediaRecorder.AudioSource.MIC
- int sampleRateInHz 采样率,一秒钟对数据的采样次数,采样率越高,音质越好
16_000 或者 44_100 等
- int channelConfig 音频通道,单声道或者双声道
AudioFormat.CHANNEL_IN_MONO
AudioFormat.CHANNEL_IN_STEREO
- int audioFormat 音频格式
AudioFormat.ENCODING_PCM_16BIT
AudioFormat.ENCODING_PCM_8BIT
- int bufferSizeInBytes 音频数据写入缓冲区的总数,可以通过AudioRecord.getMinBufferSize获取最小的缓冲区,将音频采集到缓冲区中然后再从缓冲区中读取
2. 开始录音
mAudioRecord.startRecording();
3. 读取数据
mAudioRecord.read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes);
>>audioData 写入的数组
>>offsetInBytes 偏移
>>sizeInBytes 读取的数据量
mAudioRecord.read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts);
4. 停止录音
mAudioRecord.stop();
5. 释放资源
mAudioRecord.release();
三. 封装的 DAudioRecord
package com.example.dplayer.audio;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import androidx.annotation.IntDef;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class DAudioRecord implements IAudioRecord {
private static final String TAG = "DAudioRecord";
public final static int DEFAULT_INPUT = MediaRecorder.AudioSource.MIC;
public final static int DEFAULT_SAMPLE_RATE_IN_HZ = 16_000;
public final static int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
public final static int DEFAULT_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
public final static int DEFAULT_BUFFER_SIZE_IN_BYTES = 2048;
@IntDef({Status.NO_READY, Status.READY, Status.RECORDING, Status.PAUSE, Status.STOP, Status.DESTROY})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD, ElementType.LOCAL_VARIABLE})
public @interface Status {
int NO_READY = 0;
int READY = 1;
int RECORDING = 2;
int PAUSE = 3;
int STOP = 4;
int DESTROY = 5;
}
private final static int MSG_START = 1;
private final static int MSG_RESUME = 2;
private final static int MSG_PAUSE = 3;
private final static int MSG_STOP = 4;
private final static int MSG_DESTROY = 5;
@Status
private int mStatus;
private int mAudioSource;
private int mSampleRateInHz;
private int mChannelConfig;
private int mAudioFormat;
private int mBufferSizeInBytes;
private byte[] mBuffer;
private AudioRecord mAudioRecord;
private RecordCallback mRecordCallback;
private Handler mHandler;
private HandlerThread mHandlerThread;
private boolean mStopReading = false;
public interface RecordCallback {
void onSuccess(byte[] data);
void onError(int code, Exception e);
}
public interface OnStateListener {
void onStart();
void onPause();
void onResume();
void onStop();
}
private OnStateListener mOnStateListener;
public void setOnStateListener(OnStateListener onStateListener) {
mOnStateListener = onStateListener;
}
public void setRecordCallback(RecordCallback recordCallback) {
mRecordCallback = recordCallback;
}
protected DAudioRecord(
int audioSource,
int sampleRateInHz,
int channelConfig,
int audioFormat,
int bufferSizeInBytes,
byte[] buffer) {
mAudioSource = audioSource;
mSampleRateInHz = sampleRateInHz;
mChannelConfig = channelConfig;
mAudioFormat = audioFormat;
mBufferSizeInBytes = bufferSizeInBytes;
mBuffer = buffer;
mStatus = Status.NO_READY;
mBufferSizeInBytes = Math.max(mBufferSizeInBytes, AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat));
createAudioRecord();
createThread();
}
private void createAudioRecord() {
if (mStatus != Status.NO_READY) {
Log.e(TAG, "status is not no ready!");
return;
}
mAudioRecord = new AudioRecord(
mAudioSource,
mSampleRateInHz,
mChannelConfig,
mAudioFormat,
mBufferSizeInBytes);
mStatus = Status.READY;
Log.i(TAG, "createAudioRecord, audio record ready");
}
private void createThread() {
mHandlerThread = new HandlerThread("audio-thread");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_START:
onStart();
break;
case MSG_RESUME:
onResume();
break;
case MSG_PAUSE:
onPause();
break;
case MSG_STOP:
onStop();
break;
case MSG_DESTROY:
onDestroy();
break;
}
return true;
}
});
}
@Override
public void start() {
mStopReading = false;
mHandler.sendEmptyMessage(MSG_START);
}
private void onStart() {
if (mStatus != Status.STOP && mStatus != Status.READY) {
return;
}
mStatus = Status.RECORDING;
while (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
Log.w(TAG,"STATE_UNINITIALIZED");
}
mAudioRecord.startRecording();
Log.i(TAG, "start");
if (mOnStateListener != null) {
mOnStateListener.onStart();
}
doRecording();
}
@Override
public void resume() {
mStopReading = false;
mHandler.sendEmptyMessage(MSG_RESUME);
}
private void onResume() {
if (mStatus != Status.PAUSE) {
return;
}
mStatus = Status.RECORDING;
mAudioRecord.startRecording();
Log.d(TAG, "resume");
doRecording();
}
@Override
public void pause() {
mStopReading = true;
mHandler.sendEmptyMessage(MSG_PAUSE);
}
private void onPause() {
if (mStatus != Status.RECORDING) {
return;
}
mStatus = Status.PAUSE;
mAudioRecord.stop();
Log.d(TAG, "pause");
}
@Override
public void stop() {
mStopReading = true;
mHandler.sendEmptyMessage(MSG_STOP);
}
private void onStop() {
if (mStatus != Status.RECORDING && mStatus != Status.PAUSE) {
return;
}
mStatus = Status.STOP;
mAudioRecord.stop();
Log.i(TAG, "stop");
}
@Override
public void destroy() {
mStopReading = true;
mHandler.sendEmptyMessage(MSG_DESTROY);
}
private void onDestroy() {
if (mAudioRecord != null) {
mAudioRecord.release();
mAudioRecord = null;
}
mStatus = Status.DESTROY;
Log.i(TAG, "destroy");
}
private void doRecording() {
try {
onReading();
} catch (Exception e) {
handleError(AudioRecord.ERROR, e);
}
}
private void onReading() {
int ret;
while (mAudioRecord != null && !mStopReading && mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING
&& mStatus == Status.RECORDING) {
ret = mAudioRecord.read(mBuffer, 0, mBufferSizeInBytes);
if (ret >= 0) {
handleSuccess(mBuffer);
continue;
}
handleErrorRead(ret);
}
Log.i(TAG,"stop reading");
}
private void handleErrorRead(int ret) {
switch (ret) {
case AudioRecord.ERROR_INVALID_OPERATION:
handleError(ret, new Exception("the object isn't properly initialized"));
break;
case AudioRecord.ERROR_BAD_VALUE:
handleError(ret, new Exception("the parameters don't resolve to valid data and indexes"));
break;
case AudioRecord.ERROR_DEAD_OBJECT:
handleError(ret, new Exception("the object is not valid anymore and" +
"needs to be recreated. The dead object error code is not returned if some data was" +
"successfully transferred. In this case, the error is returned at the next read"));
break;
case AudioRecord.ERROR:
handleError(ret, new Exception("unknown error"));
break;
}
}
private void handleSuccess(byte[] buffer) {
if (mRecordCallback != null) {
mRecordCallback.onSuccess(buffer);
}
}
private void handleError(int code, Exception e) {
if (mRecordCallback != null) {
mRecordCallback.onError(code, e);
}
}
public static final class Builder {
private int mAudioSource;
private int mSampleRateInHz;
private int mChannelConfig;
private int mAudioFormat;
private int mBufferSizeInBytes;
private byte[] mBuffer;
public Builder() {
this(DEFAULT_INPUT,
DEFAULT_SAMPLE_RATE_IN_HZ,
DEFAULT_CHANNEL_CONFIG,
DEFAULT_ENCODING,
DEFAULT_BUFFER_SIZE_IN_BYTES,
new byte[DEFAULT_BUFFER_SIZE_IN_BYTES]);
}
public Builder(
int audioSource,
int sampleRateInHz,
int channelConfig,
int audioFormat,
int bufferSizeInBytes,
byte[] buffer) {
mAudioSource = audioSource;
mSampleRateInHz = sampleRateInHz;
mChannelConfig = channelConfig;
mAudioFormat = audioFormat;
mBufferSizeInBytes = bufferSizeInBytes;
mBuffer = buffer;
}
public Builder setAudioSource(int audioSource) {
mAudioSource = audioSource;
return this;
}
public Builder setSampleRateInHz(int sampleRateInHz) {
mSampleRateInHz = sampleRateInHz;
return this;
}
public Builder setChannelConfig(int channelConfig) {
mChannelConfig = channelConfig;
return this;
}
public Builder setAudioFormat(int audioFormat) {
mAudioFormat = audioFormat;
return this;
}
public Builder setBufferSizeInBytes(int bufferSizeInBytes) {
mBufferSizeInBytes = bufferSizeInBytes;
return this;
}
public DAudioRecord build() {
return new DAudioRecord(
mAudioSource,
mSampleRateInHz,
mChannelConfig,
mAudioFormat,
mBufferSizeInBytes,
mBuffer);
}
}
}
github Demo