android平台上录制音频主要有两种方式,MediaRecorder、AudioRecord。
MediaRecorder 封装的层次比较高,可以直接将手机麦克风录入的音频数据进行编码压缩并存储,生成如AMR、MP3等音频文件。
AudioRecord 接近底层,录制的数据是PCM格式的原始音频裸数据,可以对其做进一步的算法处理、编码压缩等应用,能够自由进行开发控制。
音频开发应用场景很多,仅仅录制保存满足不了大部分实际需求,所以,掌握 AudioRecord 的使用是必须的,本文针对AudioRecord的使用进行总结。
AudioRecord 的使用主要有以下几个步骤:
初始化AudioRecord
初始化音频数据buffer
开启采集
启动新线程,从音频数据buffer中读取音频数据
停止采集、释放资源
初始化AudioRecord
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
audioSource:音频源,在类 MediaRecorder.AudioSource 中以常量的形式定义,常用的有DEFAULT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAULT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用)等。
sampleRateInHz:音频采样率,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。
channelConfig:音频通道数,在类AudioFormat中以常量的形式定义,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道),注意,前者能够兼容所有android手机。
audioFormat:音频数据位宽(量化精度),在类AudioFormat中以常量的形式定义,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机。
bufferSizeInBytes:AudioRecord内部音频缓冲区大小,不能低于一帧音频的大小,而一帧音频的大小(
int size = 采样率 x 位宽 x 采样时间 x 通道数
)与音频采样时间有关,一般取 2.5ms~120ms 之间,不同厂商取值不同,所以AudioRecord类提供了int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
函数来获取改值,实际开发中,强烈建议由该函数计算出需要传入的 bufferSizeInBytes,而不是自己手动计算。虽然不同的厂商的底层实现是不一样的,但无外乎就是根据上面的计算公式得到一帧的大小,音频缓冲区的大小则必须是一帧大小的2~N倍。
初始化音频数据buffer
通过前面获取的 bufferSizeInBytes ,初始化读取音频数据的buffer。
final byte[] data = new byte[minBufferSize];
开启采集
调用 AudioRecord 实例的 startRecording()
方法,开始采集音频
特别注意:开始采集后,要尽快通过音频读取数据取走音频,一旦缓冲区中的数据大于前面设置的
bufferSizeInBytes
,会发生over-running
启动新线程,从音频数据buffer中读取音频数据
开启新线程,调用 AudioRecord 实例的 public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes)
方法,获取音频数据。
停止采集、释放资源
调用 AudioRecord 实例的 stop()
、release()
方法,将改实例置为 null。
示例代码
别忘了配录音权限
public class MainActivity extends AppCompatActivity { //采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。 public static final int SAMPLE_RATE_INHZ = 44100; //声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。 public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; //返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT. public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; Button btnBegin; Button btnEnd; private AudioRecord audioRecord; private boolean isRecording; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnBegin = (Button) findViewById(R.id.btnBegin); btnEnd = (Button) findViewById(R.id.btnEnd); btnBegin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { beginRecord(); } }); btnEnd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { endRecorde(); } }); } private void beginRecord() { final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT); audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize); final byte[] data = new byte[minBufferSize]; audioRecord.startRecording(); isRecording = true; new Thread(new Runnable() { @Override public void run() { while (isRecording) { int read = audioRecord.read(data, 0, minBufferSize); if (read != AudioRecord.ERROR_INVALID_OPERATION) { //处理音频数据 data } } } }).start(); } private void endRecorde() { isRecording = false; if (audioRecord != null) { audioRecord.stop(); audioRecord.release(); audioRecord = null; } } }
封装
参考网上相关资源,这里给出一个简单的AudioRecord录音的封装类:AudioRecorder。
import android.media.AudioFormat;import android.media.AudioRecord;import android.media.MediaRecorder;import android.util.Log;/** * Created by wangyt on 2018/10/26 */public class AudioRecorder { private static final String TAG = "AudioRecorder"; public interface OnAudioDataArrivedListener { void onAudioDataArrived(byte[] audioData); } //声源 private static final int DEFFAULT_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC; //采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。 private static final int DEFAULT_SAMPLE_RATE_INHZ = 44100; //声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。 private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; //返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT. private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; //内部缓冲区大小 private int minBufferSize = 0; //是否已启动录音 private boolean isStarted = false; //是否可以从缓冲区中读取数据 private boolean canReadDataFromBuffer = true; //从缓冲区中读取数据的回调方法 private OnAudioDataArrivedListener onAudioDataArrivedListener; private AudioRecord audioRecord; public boolean startRecord() { return startRecord(DEFFAULT_AUDIO_SOURCE, DEFAULT_SAMPLE_RATE_INHZ, DEFAULT_CHANNEL_CONFIG, DEFAULT_AUDIO_FORMAT); } public boolean startRecord(int audioSource, int sampleRate, int channel, int audioFormat) { if (isStarted) { Log.e(TAG, "startRecord: AudioRecorder has been already started"); return false; } //获取内部缓冲区最小size minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channel, audioFormat); if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) { Log.e(TAG, "startRecord: minBufferSize is error_bad_value"); return false; } Log.d(TAG, "startRecord: minBufferSize = " + minBufferSize + "bytes"); //初始化 audioRecord audioRecord = new AudioRecord(audioSource, sampleRate, channel, audioFormat, minBufferSize); if (audioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) { Log.e(TAG, "startRecord: audioRecord is uninitialized"); return false; } //启动录制 audioRecord.startRecording(); //可以从内部缓冲区中读取数据 canReadDataFromBuffer = true; //启动子线程 new Thread(new Runnable() { @Override public void run() { while (canReadDataFromBuffer){ //初始化缓冲区数据接收数组 byte[] data = new byte[minBufferSize]; //读取内部缓冲区中读取数据 int result = audioRecord.read(data, 0, minBufferSize); if (result == AudioRecord.ERROR_BAD_VALUE){ Log.e(TAG, "run: audioRecord.read result is ERROR_BAD_VALUE"); }else if (result == AudioRecord.ERROR_INVALID_OPERATION){ Log.e(TAG, "run: audioRecord.read result is ERROR_INVALID_OPERATION"); }else { if (onAudioDataArrivedListener != null){ //调用读取数据回调方法 onAudioDataArrivedListener.onAudioDataArrived(data); } Log.d(TAG, "run: audioRecord read " + result + "bytes"); } } } }).start(); //设置录音已启动 isStarted = true; Log.d(TAG, "startRecord: audioRecorder has been already started"); return true; } public void stopRecord(){ //如果录音尚未启动,直接返回 if (!isStarted) return; //设置内部缓冲区数据不可读取 canReadDataFromBuffer = false; //停止录音 if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING){ audioRecord.stop(); } //释放资源 audioRecord.release(); //设置录音未启动 isStarted = false; //回调置为空 onAudioDataArrivedListener = null; }
作者:昵称真难选
链接:https://www.jianshu.com/p/c4bcd06567f9