环境:compileSDKVersion 30
工具类: android.media.AudioRecord
1. 初始化AudioRecord
private static final int sampleRate = 44100;
private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO;
private static final int pcm16bit = ENCODING_PCM_16BIT;
AudioFormat audioFormat = new AudioFormat.Builder()
.setSampleRate(sampleRate)
.setChannelMask(channelConfig)
.setEncoding(pcm16bit)
.build();
mBufferSize = AudioRecord.getMinBufferSize(
sampleRate,
channelConfig,
pcm16bit);
mAudioRecord = new AudioRecord.Builder()
.setAudioFormat(audioFormat)
.setAudioSource(MediaRecorder.AudioSource.MIC)
.setBufferSizeInBytes(mBufferSize)
.build();
- sampleRate 以多大的采样频率来采集音频,用的最多的是44.1kHz采样率,若失败可以用16kHz的重试。SDK中对采样频率的范围有限制。
public static final int SAMPLE_RATE_HZ_MIN = 4000; public static final int SAMPLE_RATE_HZ_MAX = 192000;
- channelConfig 采集几个声道的声音,可选立体声或单声道,可见立体声也是左右耳采集是伪立体声,性能考虑一般以单声道方式采集。
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT; public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);
- audioFormat 采样的量化格式,也即位深度;通常为16bit ENCODING_PCM_16BIT。
- AudioSource 音频采集的输入源,当前为MIC即通过麦克风输入。
- bufferSizeInBytes AudioRecord内部音频缓冲区的大小。不同厂商定义不同,越小则产生的延时就越小。AudioRecord有静态方法辅助计算缓冲区大小: AudioRecord#getMinBufferSize内部调用了native层函数获取缓冲区大小。
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
AudioRecord初始化结束可通过AudioRecord#getState判断初始化是否成功,返回STATE_INITIALIZED即成功.
2. 开始采集
AudioRecord启动采集是生产消费模型,生产者即采集方不断将数据写入缓冲区,消费者不断读取,如果缓冲区大小超过初始化时设置的值,则会有overrun的错误。所以需要启动一个新线程来循环读取。
AudioRecord采集函数签名
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes)
获取到audioData数组后,可以通过IO写入到本地。
private final class MyRecordThread extends Thread {
@Override
public void run() {
try {
final File cacheFile = AudioDataCache.Instance.getCacheFile();
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(cacheFile));
while (mState.get()) {
byte[] audioData = new byte[mBufferSize];
int read = mAudioRecord.read(audioData, 0, audioData.length);
if (checkRet(read)) {
fos.write(audioData, 0, audioData.length);
fos.flush();
}
}
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean checkRet(int code) {
switch (code) {
case ERROR_INVALID_OPERATION:
case ERROR_BAD_VALUE:
case ERROR_DEAD_OBJECT:
Log.i("Mg", "RecordError: code=" + code);
return false;
default:
return true;
}
}
}
3. 停止采集
AudioRecord#stop停止采集。
AudioRecord#release释放录音器,以便其他设备使用。
通过AtomicBoolean类型标志位停止读取消费者线程的死循环。注意的是需要在release或stop之后再标志位置为false,否则会有文件写出不完全的问题。