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的资料请关注毛票票其它相关文章!