Android录音--AudioRecord、MediaRecorder的使用
Android提供了两个API用于实现录音功能:android.media.AudioRecord、android.media.MediaRecorder。
网上有很多谈论这两个类的资料。现在大致总结下:
1、AudioRecord
主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音)
优点:语音的实时处理,可以用代码实现各种音频的封装
缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩
示例:
使用AudioRecord类录音,并实现WAV格式封装。录音20s,输出的音频文件大概为3.5M左右(已写测试代码)
2、MediaRecorder
已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API=16).amr.3gp
优点:大部分以及集成,直接调用相关接口即可,代码量小
缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件
示例:
使用MediaRecorder类录音,输出amr格式文件。录音20s,输出的音频文件大概为33K(已写测试代码)
3、音频格式比较
WAV格式:录音质量高,但是压缩率小,文件大
AAC格式:相对于mp3,AAC格式的音质更佳,文件更小;有损压缩;一般苹果或者AndroidSDK4.1.2(API16)及以上版本支持播放
AMR格式:压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话录音
至于常用的mp3格式,使用MediaRecorder没有该视频格式输出。一些人的做法是使用AudioRecord录音,然后编码成wav格式,再转换成mp3格式
再贴上一些测试工程。
功能描述:
1、点击“录音WAV文件”,开始录音。录音完成后,生成文件/sdcard/FinalAudio.wav
2、点击“录音AMR文件”,开始录音。录音完成后,生成文件/sdcard/FinalAudio.amr
3、点击“停止录音”,停止录音,并显示录音输出文件以及该文件大小。
大致代码如下:
1、AudioRecord录音,封装成WAV格式
packagecom.example.audiorecordtest; importjava.io.File; importjava.io.FileInputStream; importjava.io.FileNotFoundException; importjava.io.FileOutputStream; importjava.io.IOException; importandroid.media.AudioFormat; importandroid.media.AudioRecord; publicclassAudioRecordFunc{ //缓冲区字节大小 privateintbufferSizeInBytes=0; //AudioName裸音频数据文件,麦克风 privateStringAudioName=""; //NewAudioName可播放的音频文件 privateStringNewAudioName=""; privateAudioRecordaudioRecord; privatebooleanisRecord=false;//设置正在录制的状态 privatestaticAudioRecordFuncmInstance; privateAudioRecordFunc(){ } publicsynchronizedstaticAudioRecordFuncgetInstance() { if(mInstance==null) mInstance=newAudioRecordFunc(); returnmInstance; } publicintstartRecordAndFile(){ //判断是否有外部存储设备sdcard if(AudioFileFunc.isSdcardExit()) { if(isRecord) { returnErrorCode.E_STATE_RECODING; } else { if(audioRecord==null) creatAudioRecord(); audioRecord.startRecording(); //让录制状态为true isRecord=true; //开启音频文件写入线程 newThread(newAudioRecordThread()).start(); returnErrorCode.SUCCESS; } } else { returnErrorCode.E_NOSDCARD; } } publicvoidstopRecordAndFile(){ close(); } publiclonggetRecordFileSize(){ returnAudioFileFunc.getFileSize(NewAudioName); } privatevoidclose(){ if(audioRecord!=null){ System.out.println("stopRecord"); isRecord=false;//停止文件写入 audioRecord.stop(); audioRecord.release();//释放资源 audioRecord=null; } } privatevoidcreatAudioRecord(){ //获取音频文件路径 AudioName=AudioFileFunc.getRawFilePath(); NewAudioName=AudioFileFunc.getWavFilePath(); //获得缓冲区字节大小 bufferSizeInBytes=AudioRecord.getMinBufferSize(AudioFileFunc.AUDIO_SAMPLE_RATE, AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT); //创建AudioRecord对象 audioRecord=newAudioRecord(AudioFileFunc.AUDIO_INPUT,AudioFileFunc.AUDIO_SAMPLE_RATE, AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT,bufferSizeInBytes); } classAudioRecordThreadimplementsRunnable{ @Override publicvoidrun(){ writeDateTOFile();//往文件中写入裸数据 copyWaveFile(AudioName,NewAudioName);//给裸数据加上头文件 } } /** *这里将数据写入文件,但是并不能播放,因为AudioRecord获得的音频是原始的裸音频, *如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的裸数据进行处理,比如你要做一个爱说话的TOM *猫在这里就进行音频的处理,然后重新封装所以说这样得到的音频比较容易做一些音频的处理。 */ privatevoidwriteDateTOFile(){ //new一个byte数组用来存一些字节数据,大小为缓冲区大小 byte[]audiodata=newbyte[bufferSizeInBytes]; FileOutputStreamfos=null; intreadsize=0; try{ Filefile=newFile(AudioName); if(file.exists()){ file.delete(); } fos=newFileOutputStream(file);//建立一个可存取字节的文件 }catch(Exceptione){ e.printStackTrace(); } while(isRecord==true){ readsize=audioRecord.read(audiodata,0,bufferSizeInBytes); if(AudioRecord.ERROR_INVALID_OPERATION!=readsize&&fos!=null){ try{ fos.write(audiodata); }catch(IOExceptione){ e.printStackTrace(); } } } try{ if(fos!=null) fos.close();//关闭写入流 }catch(IOExceptione){ e.printStackTrace(); } } //这里得到可播放的音频文件 privatevoidcopyWaveFile(StringinFilename,StringoutFilename){ FileInputStreamin=null; FileOutputStreamout=null; longtotalAudioLen=0; longtotalDataLen=totalAudioLen+36; longlongSampleRate=AudioFileFunc.AUDIO_SAMPLE_RATE; intchannels=2; longbyteRate=16*AudioFileFunc.AUDIO_SAMPLE_RATE*channels/8; byte[]data=newbyte[bufferSizeInBytes]; try{ in=newFileInputStream(inFilename); out=newFileOutputStream(outFilename); totalAudioLen=in.getChannel().size(); totalDataLen=totalAudioLen+36; WriteWaveFileHeader(out,totalAudioLen,totalDataLen, longSampleRate,channels,byteRate); while(in.read(data)!=-1){ out.write(data); } in.close(); out.close(); }catch(FileNotFoundExceptione){ e.printStackTrace(); }catch(IOExceptione){ e.printStackTrace(); } } /** *这里提供一个头信息。插入这些信息就可以得到可以播放的文件。 *为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav *音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有 *自己特有的头文件。 */ privatevoidWriteWaveFileHeader(FileOutputStreamout,longtotalAudioLen, longtotalDataLen,longlongSampleRate,intchannels,longbyteRate) throwsIOException{ byte[]header=newbyte[44]; header[0]='R';//RIFF/WAVEheader header[1]='I'; header[2]='F'; header[3]='F'; header[4]=(byte)(totalDataLen&0xff); header[5]=(byte)((totalDataLen>>8)&0xff); header[6]=(byte)((totalDataLen>>16)&0xff); header[7]=(byte)((totalDataLen>>24)&0xff); header[8]='W'; header[9]='A'; header[10]='V'; header[11]='E'; header[12]='f';//'fmt'chunk header[13]='m'; header[14]='t'; header[15]=''; header[16]=16;//4bytes:sizeof'fmt'chunk header[17]=0; header[18]=0; header[19]=0; header[20]=1;//format=1 header[21]=0; header[22]=(byte)channels; header[23]=0; header[24]=(byte)(longSampleRate&0xff); header[25]=(byte)((longSampleRate>>8)&0xff); header[26]=(byte)((longSampleRate>>16)&0xff); header[27]=(byte)((longSampleRate>>24)&0xff); header[28]=(byte)(byteRate&0xff); header[29]=(byte)((byteRate>>8)&0xff); header[30]=(byte)((byteRate>>16)&0xff); header[31]=(byte)((byteRate>>24)&0xff); header[32]=(byte)(2*16/8);//blockalign header[33]=0; header[34]=16;//bitspersample header[35]=0; header[36]='d'; header[37]='a'; header[38]='t'; header[39]='a'; header[40]=(byte)(totalAudioLen&0xff); header[41]=(byte)((totalAudioLen>>8)&0xff); header[42]=(byte)((totalAudioLen>>16)&0xff); header[43]=(byte)((totalAudioLen>>24)&0xff); out.write(header,0,44); } }
2、MediaRecorder录音,输出amr格式音频
packagecom.example.audiorecordtest; importjava.io.File; importjava.io.IOException; importandroid.media.MediaRecorder; publicclassMediaRecordFunc{ privatebooleanisRecord=false; privateMediaRecordermMediaRecorder; privateMediaRecordFunc(){ } privatestaticMediaRecordFuncmInstance; publicsynchronizedstaticMediaRecordFuncgetInstance(){ if(mInstance==null) mInstance=newMediaRecordFunc(); returnmInstance; } publicintstartRecordAndFile(){ //判断是否有外部存储设备sdcard if(AudioFileFunc.isSdcardExit()) { if(isRecord) { returnErrorCode.E_STATE_RECODING; } else { if(mMediaRecorder==null) createMediaRecord(); try{ mMediaRecorder.prepare(); mMediaRecorder.start(); //让录制状态为true isRecord=true; returnErrorCode.SUCCESS; }catch(IOExceptionex){ ex.printStackTrace(); returnErrorCode.E_UNKOWN; } } } else { returnErrorCode.E_NOSDCARD; } } publicvoidstopRecordAndFile(){ close(); } publiclonggetRecordFileSize(){ returnAudioFileFunc.getFileSize(AudioFileFunc.getAMRFilePath()); } privatevoidcreateMediaRecord(){ /*①Initial:实例化MediaRecorder对象*/ mMediaRecorder=newMediaRecorder(); /*setAudioSource/setVedioSource*/ mMediaRecorder.setAudioSource(AudioFileFunc.AUDIO_INPUT);//设置麦克风 /*设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default *THREE_GPP(3gp格式,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB) */ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); /*设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default*/ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); /*设置输出文件的路径*/ Filefile=newFile(AudioFileFunc.getAMRFilePath()); if(file.exists()){ file.delete(); } mMediaRecorder.setOutputFile(AudioFileFunc.getAMRFilePath()); } privatevoidclose(){ if(mMediaRecorder!=null){ System.out.println("stopRecord"); isRecord=false; mMediaRecorder.stop(); mMediaRecorder.release(); mMediaRecorder=null; } } }
3、其他文件
AudioFileFunc.java
packagecom.example.audiorecordtest; importjava.io.File; importandroid.media.MediaRecorder; importandroid.os.Environment; publicclassAudioFileFunc{ //音频输入-麦克风 publicfinalstaticintAUDIO_INPUT=MediaRecorder.AudioSource.MIC; //采用频率 //44100是目前的标准,但是某些设备仍然支持22050,16000,11025 publicfinalstaticintAUDIO_SAMPLE_RATE=44100;//44.1KHz,普遍使用的频率 //录音输出文件 privatefinalstaticStringAUDIO_RAW_FILENAME="RawAudio.raw"; privatefinalstaticStringAUDIO_WAV_FILENAME="FinalAudio.wav"; publicfinalstaticStringAUDIO_AMR_FILENAME="FinalAudio.amr"; /** *判断是否有外部存储设备sdcard *@returntrue|false */ publicstaticbooleanisSdcardExit(){ if(Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) returntrue; else returnfalse; } /** *获取麦克风输入的原始音频流文件路径 *@return */ publicstaticStringgetRawFilePath(){ StringmAudioRawPath=""; if(isSdcardExit()){ StringfileBasePath=Environment.getExternalStorageDirectory().getAbsolutePath(); mAudioRawPath=fileBasePath+"/"+AUDIO_RAW_FILENAME; } returnmAudioRawPath; } /** *获取编码后的WAV格式音频文件路径 *@return */ publicstaticStringgetWavFilePath(){ StringmAudioWavPath=""; if(isSdcardExit()){ StringfileBasePath=Environment.getExternalStorageDirectory().getAbsolutePath(); mAudioWavPath=fileBasePath+"/"+AUDIO_WAV_FILENAME; } returnmAudioWavPath; } /** *获取编码后的AMR格式音频文件路径 *@return */ publicstaticStringgetAMRFilePath(){ StringmAudioAMRPath=""; if(isSdcardExit()){ StringfileBasePath=Environment.getExternalStorageDirectory().getAbsolutePath(); mAudioAMRPath=fileBasePath+"/"+AUDIO_AMR_FILENAME; } returnmAudioAMRPath; } /** *获取文件大小 *@parampath,文件的绝对路径 *@return */ publicstaticlonggetFileSize(Stringpath){ FilemFile=newFile(path); if(!mFile.exists()) return-1; returnmFile.length(); } }
4、其他文件
ErrorCode.java
packagecom.example.audiorecordtest; importandroid.content.Context; importandroid.content.res.Resources.NotFoundException; publicclassErrorCode{ publicfinalstaticintSUCCESS=1000; publicfinalstaticintE_NOSDCARD=1001; publicfinalstaticintE_STATE_RECODING=1002; publicfinalstaticintE_UNKOWN=1003; publicstaticStringgetErrorInfo(ContextvContext,intvType)throwsNotFoundException { switch(vType) { caseSUCCESS: return"success"; caseE_NOSDCARD: returnvContext.getResources().getString(R.string.error_no_sdcard); caseE_STATE_RECODING: returnvContext.getResources().getString(R.string.error_state_record); caseE_UNKOWN: default: returnvContext.getResources().getString(R.string.error_unknown); } } }
5、string.xml
<?xmlversion="1.0"encoding="utf-8"?> <resources> <stringname="app_name">AudioRecordTest</string> <stringname="hello_world">测试AudioRecord,实现录音功能</string> <stringname="menu_settings">Settings</string> <stringname="view_record_wav">录音WAV文件</string> <stringname="view_record_amr">录音AMR文件</string> <stringname="view_stop">停止录音</string> <stringname="error_no_sdcard">没有SD卡,无法存储录音数据</string> <stringname="error_state_record">正在录音中,请先停止录音</string> <stringname="error_unknown">无法识别的错误</string> </resources>
6、主程序MainActivity
packagecom.example.audiorecordtest; importandroid.app.Activity; importandroid.os.Bundle; importandroid.os.Handler; importandroid.os.Message; importandroid.util.Log; importandroid.view.Menu; importandroid.view.View; importandroid.widget.Button; importandroid.widget.TextView; publicclassMainActivityextendsActivity{ privatefinalstaticintFLAG_WAV=0; privatefinalstaticintFLAG_AMR=1; privateintmState=-1;//-1:没再录制,0:录制wav,1:录制amr privateButtonbtn_record_wav; privateButtonbtn_record_amr; privateButtonbtn_stop; privateTextViewtxt; privateUIHandleruiHandler; privateUIThreaduiThread; @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewByIds(); setListeners(); init(); } @Override publicbooleanonCreateOptionsMenu(Menumenu){ //Inflatethemenu;thisaddsitemstotheactionbarifitispresent. getMenuInflater().inflate(R.menu.activity_main,menu); returntrue; } privatevoidfindViewByIds(){ btn_record_wav=(Button)this.findViewById(R.id.btn_record_wav); btn_record_amr=(Button)this.findViewById(R.id.btn_record_amr); btn_stop=(Button)this.findViewById(R.id.btn_stop); txt=(TextView)this.findViewById(R.id.text); } privatevoidsetListeners(){ btn_record_wav.setOnClickListener(btn_record_wav_clickListener); btn_record_amr.setOnClickListener(btn_record_amr_clickListener); btn_stop.setOnClickListener(btn_stop_clickListener); } privatevoidinit(){ uiHandler=newUIHandler(); } privateButton.OnClickListenerbtn_record_wav_clickListener=newButton.OnClickListener(){ publicvoidonClick(Viewv){ record(FLAG_WAV); } }; privateButton.OnClickListenerbtn_record_amr_clickListener=newButton.OnClickListener(){ publicvoidonClick(Viewv){ record(FLAG_AMR); } }; privateButton.OnClickListenerbtn_stop_clickListener=newButton.OnClickListener(){ publicvoidonClick(Viewv){ stop(); } }; /** *开始录音 *@parammFlag,0:录制wav格式,1:录音amr格式 */ privatevoidrecord(intmFlag){ if(mState!=-1){ Messagemsg=newMessage(); Bundleb=newBundle();//存放数据 b.putInt("cmd",CMD_RECORDFAIL); b.putInt("msg",ErrorCode.E_STATE_RECODING); msg.setData(b); uiHandler.sendMessage(msg);//向Handler发送消息,更新UI return; } intmResult=-1; switch(mFlag){ caseFLAG_WAV: AudioRecordFuncmRecord_1=AudioRecordFunc.getInstance(); mResult=mRecord_1.startRecordAndFile(); break; caseFLAG_AMR: MediaRecordFuncmRecord_2=MediaRecordFunc.getInstance(); mResult=mRecord_2.startRecordAndFile(); break; } if(mResult==ErrorCode.SUCCESS){ uiThread=newUIThread(); newThread(uiThread).start(); mState=mFlag; }else{ Messagemsg=newMessage(); Bundleb=newBundle();//存放数据 b.putInt("cmd",CMD_RECORDFAIL); b.putInt("msg",mResult); msg.setData(b); uiHandler.sendMessage(msg);//向Handler发送消息,更新UI } } /** *停止录音 */ privatevoidstop(){ if(mState!=-1){ switch(mState){ caseFLAG_WAV: AudioRecordFuncmRecord_1=AudioRecordFunc.getInstance(); mRecord_1.stopRecordAndFile(); break; caseFLAG_AMR: MediaRecordFuncmRecord_2=MediaRecordFunc.getInstance(); mRecord_2.stopRecordAndFile(); break; } if(uiThread!=null){ uiThread.stopThread(); } if(uiHandler!=null) uiHandler.removeCallbacks(uiThread); Messagemsg=newMessage(); Bundleb=newBundle();//存放数据 b.putInt("cmd",CMD_STOP); b.putInt("msg",mState); msg.setData(b); uiHandler.sendMessageDelayed(msg,1000);//向Handler发送消息,更新UI mState=-1; } } privatefinalstaticintCMD_RECORDING_TIME=2000; privatefinalstaticintCMD_RECORDFAIL=2001; privatefinalstaticintCMD_STOP=2002; classUIHandlerextendsHandler{ publicUIHandler(){ } @Override publicvoidhandleMessage(Messagemsg){ //TODOAuto-generatedmethodstub Log.d("MyHandler","handleMessage......"); super.handleMessage(msg); Bundleb=msg.getData(); intvCmd=b.getInt("cmd"); switch(vCmd) { caseCMD_RECORDING_TIME: intvTime=b.getInt("msg"); MainActivity.this.txt.setText("正在录音中,已录制:"+vTime+"s"); break; caseCMD_RECORDFAIL: intvErrorCode=b.getInt("msg"); StringvMsg=ErrorCode.getErrorInfo(MainActivity.this,vErrorCode); MainActivity.this.txt.setText("录音失败:"+vMsg); break; caseCMD_STOP: intvFileType=b.getInt("msg"); switch(vFileType){ caseFLAG_WAV: AudioRecordFuncmRecord_1=AudioRecordFunc.getInstance(); longmSize=mRecord_1.getRecordFileSize(); MainActivity.this.txt.setText("录音已停止.录音文件:"+AudioFileFunc.getWavFilePath()+"\n文件大小:"+mSize); break; caseFLAG_AMR: MediaRecordFuncmRecord_2=MediaRecordFunc.getInstance(); mSize=mRecord_2.getRecordFileSize(); MainActivity.this.txt.setText("录音已停止.录音文件:"+AudioFileFunc.getAMRFilePath()+"\n文件大小:"+mSize); break; } break; default: break; } } }; classUIThreadimplementsRunnable{ intmTimeMill=0; booleanvRun=true; publicvoidstopThread(){ vRun=false; } publicvoidrun(){ while(vRun){ try{ Thread.sleep(1000); }catch(InterruptedExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } mTimeMill++; Log.d("thread","mThread........"+mTimeMill); Messagemsg=newMessage(); Bundleb=newBundle();//存放数据 b.putInt("cmd",CMD_RECORDING_TIME); b.putInt("msg",mTimeMill); msg.setData(b); MainActivity.this.uiHandler.sendMessage(msg);//向Handler发送消息,更新UI } } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。