音频采集AudioRecord
AudioRecord与MediaRecorder区别
前者采集的是原始的音频数据,后者会对音频数据进行编码压缩并存储成文件
AudioRecord的使用
1.AudioRecord参数配置
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes)
audioSource
音频采集的输入源,可选值在MediaRecorder.AudioSource中以常量值定义,如
public static final int MIC = 1; //表示手机麦克风输入
sampleRateInHz
采样率,录音设备1S内对声音信号的采集次数,单位Hz,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。
背景知识
Hz,物质在1S内周期性变化的次数
我们知道人耳能听到的声音频率范围在20Hz到20KHz之间,为了不失真,采样频率应该在40KHz以上
channelConfig
通道数的配置,可选值在AudioFormat中以常量值定义,常用的如下
public static final int CHANNEL_IN_LEFT = 0x4;
public static final int CHANNEL_IN_RIGHT = 0x8;
public static final int CHANNEL_IN_FRONT = 0x10;
//单通道
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;
//双通道
public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);
audioFormat
用来配置数据位宽,可选值在可选值在AudioFormat中以常量值定义,常用的如下
public static final int ENCODING_PCM_16BIT = 2;
public static final int ENCODING_PCM_8BIT = 3;
背景知识
PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。
bufferSizeInBytes
配置的是AudioRecord内部音频缓冲区的大小,该值不能低于一帧音频帧的大小,一帧音频帧的大小计算如下
int size=采样率 * 采样时间 * 位宽 * 通道数
其中采样时间一般取2.5ms~120ms,具体取多少由厂商或者应用决定
每一帧采样的时间越短,产生的延时越小,但碎片化的数据也会越多
在Android开发中,应该使用AudioRecord类中的方法
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
来计算音频缓冲区的大小
2.音频采集方法
audioRecord.startRecording(); //开始录制
audioRecord.stop(); //停止录制
audioRecord.read(bytes,0,bytes.length); //读取录音数据
3.示例代码
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
并且该权限属于危险权限,需要动态获取权限
public class AudioCapture {
private static final String TAG = "AudioCapture";
private final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC; //麦克风
private final int DEFAULT_RATE = 44100; //采样率
private final int DEFAULT_CHANNEL = AudioFormat.CHANNEL_IN_STEREO; //双通道(左右声道)
private final int DEFAULT_FORMAT = AudioFormat.ENCODING_PCM_16BIT; //数据位宽16位
private AudioRecord mAudioRecord;
private int mMinBufferSize;
private onAudioFrameCaptureListener mOnAudioFrameCaptureListener;
private boolean isRecording = false;
public void startRecord() {
startRecord(DEFAULT_SOURCE, DEFAULT_RATE, DEFAULT_CHANNEL, DEFAULT_FORMAT);
}
public void startRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
Log.d(TAG, "Invalid parameter");
return;
}
mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig,
audioFormat, mMinBufferSize);
if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
Log.d(TAG, "AudioRecord initialize fail");
return;
}
mAudioRecord.startRecording();
isRecording = true;
CaptureThread t = new CaptureThread();
t.start();
Log.d(TAG, "AudioRecord Start");
}
public void stopRecord() {
isRecording = false;
if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
mAudioRecord.stop();
}
mAudioRecord.release();
mOnAudioFrameCaptureListener = null;
Log.d(TAG, "AudioRecord Stop");
}
private class CaptureThread extends Thread {
@Override
public void run() {
while (isRecording) {
byte[] buffer = new byte[mMinBufferSize];
int result = mAudioRecord.read(buffer, 0, buffer.length);
Log.d(TAG, "Captured " + result + " byte");
if (mOnAudioFrameCaptureListener != null) {
mOnAudioFrameCaptureListener.onAudioFrameCapture(buffer);
}
}
}
}
public interface onAudioFrameCaptureListener {
void onAudioFrameCapture(byte[] audioData);
}
public void setOnAudioFrameCaptureListener(onAudioFrameCaptureListener listener) {
mOnAudioFrameCaptureListener = listener;
}
}
调用方式
audioCapture=new AudioCapture();
audioCapture.startRecord();
音频播放AudioTrack
AudioTrack,MediaPlayer,SoundPool的区别
mediaplayer适合长时间播放音乐
soundpool适合短时间的音频片段,如游戏声音,按键声音
audiotrack更接近底层,更灵活,播放的是pcm音频数据
AudioTrack的使用
1.AudioTrack参数配置
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes, int mode)
streamType
音频管理策略,如我们在小米手机调节音量时,会出现3种声音调节的类型,音乐,铃声,闹钟
该参数的可选值在AudioManager类中,如:
STREAM_MUSCI:音乐声
STREAM_RING:铃声
STREAM_NOTIFICATION:通知声
sampleRateInHz
采样率,看源码知道,范围在4000~192000
public static final int SAMPLE_RATE_HZ_MIN = 4000;
public static final int SAMPLE_RATE_HZ_MAX = 192000;
channelConfig
通道数的配置,可选值在AudioFormat中以常量值定义,常用的如下
public static final int CHANNEL_IN_LEFT = 0x4;
public static final int CHANNEL_IN_RIGHT = 0x8;
public static final int CHANNEL_IN_FRONT = 0x10;
//单通道
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;
//双通道
public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);
audioFormat
用来配置数据位宽,可选值在可选值在AudioFormat中以常量值定义,常用的如下
public static final int ENCODING_PCM_16BIT = 2;
public static final int ENCODING_PCM_8BIT = 3;
bufferSizeInBytes
配置的是AudioTrack内部音频缓冲区的大小,同样AudioTrack提供了获取缓冲区大小的方法
AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
mode
AudioTrack有两种播放方式 MODE_STATIC和MODE_STREAM
前者是一次性将所有数据写入播放缓冲区,然后播放
后者是一边写入一边播放
2.音频播放方法
mAudioTrack.play(); //开始播放
mAudioTrack.stop(); //停止播放
mAudioTrack.write(audioData,offsetInBytes,sizeInBytes);//将pcm数据写入缓冲区
3.示例代码
public class AudioPlayer {
private static final String TAG = "AudioPlayer";
private final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC; //流音乐
private final int DEFAULT_RATE = 44100; //采样率
private final int DEFAULT_CHANNEL = AudioFormat.CHANNEL_IN_STEREO; //双通道(左右声道)
private final int DEFAULT_FORMAT = AudioFormat.ENCODING_PCM_16BIT; //数据位宽16位
private static final int DEFAULT_PLAY_MODE = AudioTrack.MODE_STREAM;
private AudioTrack mAudioTrack;
private int mMinBufferSize;
private boolean isPlaying=false;
public void startPlay(){
startPlay(DEFAULT_STREAM_TYPE,DEFAULT_RATE,DEFAULT_CHANNEL,DEFAULT_FORMAT);
}
public void startPlay(int streamType, int sampleRateInHz, int channelConfig, int audioFormat){
if(isPlaying){
Log.d(TAG,"AudioPlayer has played");
return;
}
mMinBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
Log.d(TAG, "Invalid parameter");
return;
}
mAudioTrack=new AudioTrack(streamType,sampleRateInHz,channelConfig,audioFormat,
mMinBufferSize,DEFAULT_PLAY_MODE);
if(mAudioTrack.getState()==AudioTrack.STATE_UNINITIALIZED){
Log.d(TAG, "AudioTrack initialize fail");
return;
}
isPlaying=true;
}
public void stopPlay(){
if(!isPlaying){
Log.d(TAG, "AudioTrack is not playing");
return;
}
if(mAudioTrack.getPlayState()==AudioTrack.PLAYSTATE_PLAYING){
mAudioTrack.stop();
}
mAudioTrack.release();
isPlaying=false;
}
private void play(byte[] audioData,int offsetInBytes, int sizeInBytes){
if(!isPlaying){
Log.d(TAG, "AudioTrack not start");
return;
}
if(sizeInBytes<mMinBufferSize){
Log.d(TAG, "audio data not enough");
//return;
}
if(mAudioTrack.write(audioData,offsetInBytes,sizeInBytes)!=mMinBufferSize){
Log.d(TAG, "AudioTrack can not write all the data");
}
mAudioTrack.play();
Log.d(TAG, "played "+sizeInBytes+" bytes");
}
}
测试
//原始音频的录入和播放
public class AudioPCMActivity extends DemoActivity {
private Button btn_audio_record;
private Button btn_audio_record_play;
private AudioCapture audioCapture;
private AudioPlayer audioPlayer;
private PcmFileWriter pcmFileWriter;
private PcmFileReader pcmFileReader;
private boolean isReading;
private String path="";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setContentView(R.layout.activity_media_audio);
super.onCreate(savedInstanceState);
}
@Override
public void initHead() {
}
@Override
public void initView() {
btn_audio_record=findViewById(R.id.btn_audio_record);
btn_audio_record_play=findViewById(R.id.btn_audio_record_play);
}
@Override
public void initData() {
path=FileUtil.getAudioDir(this)+"/audioTest.pcm";
audioCapture=new AudioCapture();
audioPlayer=new AudioPlayer();
pcmFileReader=new PcmFileReader();
pcmFileWriter=new PcmFileWriter();
String des = "录音权限被禁止,我们需要打开录音权限";
String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO};
baseAt.requestPermissions(des, permissions, 100, new PermissionsResultListener() {
@Override
public void onPermissionGranted() {
}
@Override
public void onPermissionDenied() {
finish();
}
});
}
@Override
public void initEvent() {
btn_audio_record.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN){
Log.d("TAG","按住");
start();
}else if(event.getAction()==MotionEvent.ACTION_UP){
Log.d("TAG","松开");
stop();
}
return false;
}
});
btn_audio_record_play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
play();
}
});
}
//播放录音
private void play(){
isReading=true;
pcmFileReader.openFile(path);
audioPlayer.startPlay();
new AudioTrackThread().start();
}
private class AudioTrackThread extends Thread{
@Override
public void run() {
byte[] buffer = new byte[1024];
while (isReading && pcmFileReader.read(buffer,0,buffer.length)>0){
audioPlayer.play(buffer,0,buffer.length);
}
audioPlayer.stopPlay();
pcmFileReader.closeFile();
}
}
//开始录音
private void start(){
pcmFileWriter.openFile(path);
btn_audio_record.setText("松开 结束");
audioCapture.startRecord();
audioCapture.setOnAudioFrameCaptureListener(new AudioCapture.onAudioFrameCaptureListener() {
@Override
public void onAudioFrameCapture(byte[] audioData) {
pcmFileWriter.write(audioData,0,audioData.length);
}
});
}
//结束录音
private void stop(){
btn_audio_record.setText("按住 录音");
audioCapture.stopRecord();
pcmFileWriter.closeFile();
}
}
github
测试代码在com.sf.sofarmusic.demo.media下
其他代码在libplayer模块中