Android用AudioRecord进行录音
在音视频开发中,录音当然是必不可少的。首先我们要学会单独的录音功能,当然这里说的录音是指用AudioRecord来录音,读取录音原始数据,读到的就是所谓的PCM数据。对于录音来说,最重要的几个参数要搞明白:
1、simpleRate采样率,采样率就是采样频率,每秒钟记录多少个样本。
2、channelConfig通道配置,其实就是所谓的单通道,双通道之类的,AudioFormat.CHANNEL_IN_MONO单通道,AudioFormat.CHANNEL_IN_STEREO双通道,这里只列了这两种,还有其它的,可自行查阅。
3、audioFormat音频格式,其实就是采样的精度,每个样本的位数,AudioFormat.ENCODING_PCM_8BIT每个样本占8位,AudioFormat.ENCODING_PCM_16BIT每个样本占16位,这里也只用了这两个,别的没研究。
在学习过程中会用到的一些参数,我这里封装了一个类,如下
publicclassAudioParams{
enumFormat{
SINGLE_8_BIT,DOUBLE_8_BIT,SINGLE_16_BIT,DOUBLE_16_BIT
}
privateFormatformat;
intsimpleRate;
AudioParams(intsimpleRate,Formatf){
this.simpleRate=simpleRate;
this.format=f;
}
AudioParams(intsimpleRate,intchannelCount,intbits){
this.simpleRate=simpleRate;
set(channelCount,bits);
}
intgetBits(){
return(format==Format.SINGLE_8_BIT||format==Format.DOUBLE_8_BIT)?8:16;
}
intgetEncodingFormat(){
return(format==Format.SINGLE_8_BIT||format==Format.DOUBLE_8_BIT)?AudioFormat.ENCODING_PCM_8BIT:
AudioFormat.ENCODING_PCM_16BIT;
}
intgetChannelCount(){return(format==Format.SINGLE_8_BIT||format==Format.SINGLE_16_BIT)?1:2;}
intgetChannelConfig(){
return(format==Format.SINGLE_8_BIT||format==Format.SINGLE_16_BIT)?AudioFormat.CHANNEL_IN_MONO:
AudioFormat.CHANNEL_IN_STEREO;
}
intgetOutChannelConfig(){
return(format==Format.SINGLE_8_BIT||format==Format.SINGLE_16_BIT)?AudioFormat.CHANNEL_OUT_MONO:
AudioFormat.CHANNEL_OUT_STEREO;
}
voidset(intchannelCount,intbits){
if((channelCount!=1&&channelCount!=2)||(bits!=8&&bits!=16)){
thrownewIllegalArgumentException("不支持其它格式channelCount=$channelCountbits=$bits");
}
if(channelCount==1){
if(bits==8){
format=Format.SINGLE_8_BIT;
}else{
format=Format.SINGLE_16_BIT;
}
}else{
if(bits==8){
format=Format.DOUBLE_8_BIT;
}else{
format=Format.DOUBLE_16_BIT;
}
}
}
}
这里固定使用了单通道8位,双通道8位,单通道16位,双通道16位,所以用了枚举来限制。
为了方便把录音数据拿出来显示、存储,这里写了一个回调方法如下
publicinterfaceRecordCallback{
/**
*数据回调
*
*@parambytes数据
*@paramlen数据有效长度,-1时表示数据结束
*/
voidonRecord(byte[]bytes,intlen);
}
有了这些参数,现在就可以录音了,先看一下样例
publicvoidstartRecord(AudioParamsparams,RecordCallbackcallback){
intsimpleRate=params.simpleRate;
intchannelConfig=params.getChannelConfig();
intaudioFormat=params.getEncodingFormat();
//根据AudioRecord提供的api拿到最小缓存大小
intbufferSize=AudioRecord.getMinBufferSize(simpleRate,channelConfig,audioFormat);
//创建Record对象
record=newAudioRecord(MediaRecorder.AudioSource.MIC,simpleRate,channelConfig,audioFormat,bufferSize);
recordThread=newThread(()->{
byte[]buffer=newbyte[bufferSize];
record.startRecording();
recording=true;
while(recording){
intread=record.read(buffer,0,bufferSize);
//将数据回调到外部
if(read>0&&callback!=null){
callback.onRecord(buffer,read);
}
}
if(callback!=null){
//len为-1时表示结束
callback.onRecord(buffer,-1);
recording=false;
}
//释放资源
release();
});
recordThread.start();
}
这个方法就是简单的采集音频数据,这个数据就是最原始的pcm数据。
拿到pcm数据以后,如果直接保存到文件是无法直接播放的,因为这只是一堆数据,没有任何格式说明,如果想让普通播放器可以播放,需要在文件中加入文件头,来告诉播放器这个数据的格式,这里是直接保存成wav格式的数据。下面就是加入wav格式文件头的方法
privatestaticbyte[]getWaveFileHeader(inttotalDataLen,intsampleRate,intchannelCount,intbits){
byte[]header=newbyte[44];
//RIFF/WAVEheader
header[0]='R';
header[1]='I';
header[2]='F';
header[3]='F';
intfileLength=totalDataLen+36;
header[4]=(byte)(fileLength&0xff);
header[5]=(byte)(fileLength>>8&0xff);
header[6]=(byte)(fileLength>>16&0xff);
header[7]=(byte)(fileLength>>24&0xff);
//WAVE
header[8]='W';
header[9]='A';
header[10]='V';
header[11]='E';
//'fmt'chunk
header[12]='f';
header[13]='m';
header[14]='t';
header[15]='';
//4bytes:sizeof'fmt'chunk
header[16]=16;
header[17]=0;
header[18]=0;
header[19]=0;
//pcmformat=1
header[20]=1;
header[21]=0;
header[22]=(byte)channelCount;
header[23]=0;
header[24]=(byte)(sampleRate&0xff);
header[25]=(byte)(sampleRate>>8&0xff);
header[26]=(byte)(sampleRate>>16&0xff);
header[27]=(byte)(sampleRate>>24&0xff);
intbyteRate=sampleRate*bits*channelCount/8;
header[28]=(byte)(byteRate&0xff);
header[29]=(byte)(byteRate>>8&0xff);
header[30]=(byte)(byteRate>>16&0xff);
header[31]=(byte)(byteRate>>24&0xff);
//blockalign
header[32]=(byte)(channelCount*bits/8);
header[33]=0;
//bitspersample
header[34]=(byte)bits;
header[35]=0;
//data
header[36]='d';
header[37]='a';
header[38]='t';
header[39]='a';
header[40]=(byte)(totalDataLen&0xff);
header[41]=(byte)(totalDataLen>>8&0xff);
header[42]=(byte)(totalDataLen>>16&0xff);
header[43]=(byte)(totalDataLen>>24&0xff);
returnheader;
}
根据几个参数设置一下文件头,然后直接写入录音采集到的pcm数据,就可被正常播放了。wav文件头格式定义,可点击这里查看或自行百度。
如果想要通过AudioRecord录音直接保存到文件,可参考下面方法
publicvoidstartRecord(StringfilePath,AudioParamsparams,RecordCallbackcallback){
intchannelCount=params.getChannelCount();
intbits=params.getBits();
finalbooleanstoreFile=filePath!=null&&!filePath.isEmpty();
startRecord(params,(bytes,len)->{
if(storeFile){
if(file==null){
Filef=newFile(filePath);
if(f.exists()){
f.delete();
}
try{
file=newRandomAccessFile(f,"rw");
file.write(getWaveFileHeader(0,params.simpleRate,channelCount,bits));
}catch(IOExceptione){
e.printStackTrace();
}
}
if(len>0){
try{
file.write(bytes,0,len);
}catch(IOExceptione){
e.printStackTrace();
}
}else{
try{
//因为在前面已经写入头信息,所以这里要减去头信息才是数据的长度
intlength=(int)file.length()-44;
file.seek(0);
file.write(getWaveFileHeader(length,params.simpleRate,channelCount,bits));
file.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
}
if(callback!=null){
callback.onRecord(bytes,len);
}
});
}
先通过RandomAccessFile创建文件,先写入文件头,由于暂时我们不知道会录多长,有多少pcm数据,长度先用0表示,等录音结束后,通过seek(int)方法重新写入文件头信息,也可以先把pcm数据保存到临时文件,然后再写入到一个新的文件中,这里就不举例说明了。
最后放入完整类的代码
packagecn.sskbskdrin.record.audio;
importandroid.media.AudioRecord;
importandroid.media.MediaRecorder;
importjava.io.File;
importjava.io.IOException;
importjava.io.RandomAccessFile;
/**
*@authorsskbskdrin
*@date2019/April/3
*/
publicclassAudioRecordManager{
privateAudioParamsDEFAULT_FORMAT=newAudioParams(8000,1,16);
privateAudioRecordrecord;
privateThreadrecordThread;
privatebooleanrecording=false;
privateRandomAccessFilefile;
publicvoidstartRecord(StringfilePath,RecordCallbackcallback){
startRecord(filePath,DEFAULT_FORMAT,callback);
}
publicvoidstartRecord(StringfilePath,AudioParamsparams,RecordCallbackcallback){
intchannelCount=params.getChannelCount();
intbits=params.getBits();
finalbooleanstoreFile=filePath!=null&&!filePath.isEmpty();
startRecord(params,(bytes,len)->{
if(storeFile){
if(file==null){
Filef=newFile(filePath);
if(f.exists()){
f.delete();
}
try{
file=newRandomAccessFile(f,"rw");
file.write(getWaveFileHeader(0,params.simpleRate,channelCount,bits));
}catch(IOExceptione){
e.printStackTrace();
}
}
if(len>0){
try{
file.write(bytes,0,len);
}catch(IOExceptione){
e.printStackTrace();
}
}else{
try{
//因为在前面已经写入头信息,所以这里要减去头信息才是数据的长度
intlength=(int)file.length()-44;
file.seek(0);
file.write(getWaveFileHeader(length,params.simpleRate,channelCount,bits));
file.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
}
if(callback!=null){
callback.onRecord(bytes,len);
}
});
}
publicvoidstartRecord(AudioParamsparams,RecordCallbackcallback){
intsimpleRate=params.simpleRate;
intchannelConfig=params.getChannelConfig();
intaudioFormat=params.getEncodingFormat();
//根据AudioRecord提供的api拿到最小缓存大小
intbufferSize=AudioRecord.getMinBufferSize(simpleRate,channelConfig,audioFormat);
//创建Record对象
record=newAudioRecord(MediaRecorder.AudioSource.MIC,simpleRate,channelConfig,audioFormat,bufferSize);
recordThread=newThread(()->{
byte[]buffer=newbyte[bufferSize];
record.startRecording();
recording=true;
while(recording){
intread=record.read(buffer,0,bufferSize);
//将数据回调到外部
if(read>0&&callback!=null){
callback.onRecord(buffer,read);
}
}
if(callback!=null){
//len为-1时表示结束
callback.onRecord(buffer,-1);
recording=false;
}
//释放资源
release();
});
recordThread.start();
}
publicvoidstop(){
recording=false;
}
publicvoidrelease(){
recording=false;
if(record!=null){
record.stop();
record.release();
}
record=null;
file=null;
recordThread=null;
}
privatestaticbyte[]getWaveFileHeader(inttotalDataLen,intsampleRate,intchannelCount,intbits){
byte[]header=newbyte[44];
//RIFF/WAVEheader
header[0]='R';
header[1]='I';
header[2]='F';
header[3]='F';
intfileLength=totalDataLen+36;
header[4]=(byte)(fileLength&0xff);
header[5]=(byte)(fileLength>>8&0xff);
header[6]=(byte)(fileLength>>16&0xff);
header[7]=(byte)(fileLength>>24&0xff);
//WAVE
header[8]='W';
header[9]='A';
header[10]='V';
header[11]='E';
//'fmt'chunk
header[12]='f';
header[13]='m';
header[14]='t';
header[15]='';
//4bytes:sizeof'fmt'chunk
header[16]=16;
header[17]=0;
header[18]=0;
header[19]=0;
//pcmformat=1
header[20]=1;
header[21]=0;
header[22]=(byte)channelCount;
header[23]=0;
header[24]=(byte)(sampleRate&0xff);
header[25]=(byte)(sampleRate>>8&0xff);
header[26]=(byte)(sampleRate>>16&0xff);
header[27]=(byte)(sampleRate>>24&0xff);
intbyteRate=sampleRate*bits*channelCount/8;
header[28]=(byte)(byteRate&0xff);
header[29]=(byte)(byteRate>>8&0xff);
header[30]=(byte)(byteRate>>16&0xff);
header[31]=(byte)(byteRate>>24&0xff);
//blockalign
header[32]=(byte)(channelCount*bits/8);
header[33]=0;
//bitspersample
header[34]=(byte)bits;
header[35]=0;
//data
header[36]='d';
header[37]='a';
header[38]='t';
header[39]='a';
header[40]=(byte)(totalDataLen&0xff);
header[41]=(byte)(totalDataLen>>8&0xff);
header[42]=(byte)(totalDataLen>>16&0xff);
header[43]=(byte)(totalDataLen>>24&0xff);
returnheader;
}
publicinterfaceRecordCallback{
/**
*数据回调
*
*@parambytes数据
*@paramlen数据有效长度,-1时表示数据结束
*/
voidonRecord(byte[]bytes,intlen);
}
}
如有不对之处还请评论指正
以上就是Android用AudioRecord进行录音的详细内容,更多关于AndroidAudioRecord的资料请关注毛票票其它相关文章!