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格式的音质更佳,文件更小;有损压缩;一般苹果或者android sdk4.1.2(api 16)及以上版本支持播放
amr格式:压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话录音
至于常用的mp3格式,使用mediarecorder没有该视频格式输出。一些人的做法是使用audiorecord录音,然后编码成wav格式,再转换成mp3格式
再贴上一些测试工程。
功能描述:
1、点击“录音wav文件”,开始录音。录音完成后,生成文件/sdcard/finalaudio.wav
2、点击“录音amr文件”,开始录音。录音完成后,生成文件/sdcard/finalaudio.amr
3、点击“停止录音”,停止录音,并显示录音输出文件以及该文件大小。
大致代码如下:
1、audiorecord录音,封装成wav格式
package com.example.audiorecordtest; import java.io.file; import java.io.fileinputstream; import java.io.filenotfoundexception; import java.io.fileoutputstream; import java.io.ioexception; import android.media.audioformat; import android.media.audiorecord; public class audiorecordfunc { // 缓冲区字节大小 private int buffersizeinbytes = 0; //audioname裸音频数据文件 ,麦克风 private string audioname = ""; //newaudioname可播放的音频文件 private string newaudioname = ""; private audiorecord audiorecord; private boolean isrecord = false;// 设置正在录制的状态 private static audiorecordfunc minstance; private audiorecordfunc(){ } public synchronized static audiorecordfunc getinstance() { if(minstance == null) minstance = new audiorecordfunc(); return minstance; } public int startrecordandfile() { //判断是否有外部存储设备sdcard if(audiofilefunc.issdcardexit()) { if(isrecord) { return errorcode.e_state_recoding; } else { if(audiorecord == null) creataudiorecord(); audiorecord.startrecording(); // 让录制状态为true isrecord = true; // 开启音频文件写入线程 new thread(new audiorecordthread()).start(); return errorcode.success; } } else { return errorcode.e_nosdcard; } } public void stoprecordandfile() { close(); } public long getrecordfilesize(){ return audiofilefunc.getfilesize(newaudioname); } private void close() { if (audiorecord != null) { system.out.println("stoprecord"); isrecord = false;//停止文件写入 audiorecord.stop(); audiorecord.release();//释放资源 audiorecord = null; } } private void creataudiorecord() { // 获取音频文件路径 audioname = audiofilefunc.getrawfilepath(); newaudioname = audiofilefunc.getwavfilepath(); // 获得缓冲区字节大小 buffersizeinbytes = audiorecord.getminbuffersize(audiofilefunc.audio_sample_rate, audioformat.channel_in_stereo, audioformat.encoding_pcm_16bit); // 创建audiorecord对象 audiorecord = new audiorecord(audiofilefunc.audio_input, audiofilefunc.audio_sample_rate, audioformat.channel_in_stereo, audioformat.encoding_pcm_16bit, buffersizeinbytes); } class audiorecordthread implements runnable { @override public void run() { writedatetofile();//往文件中写入裸数据 copywavefile(audioname, newaudioname);//给裸数据加上头文件 } } /** * 这里将数据写入文件,但是并不能播放,因为audiorecord获得的音频是原始的裸音频, * 如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的 裸数据进行处理,比如你要做一个爱说话的tom * 猫在这里就进行音频的处理,然后重新封装 所以说这样得到的音频比较容易做一些音频的处理。 */ private void writedatetofile() { // new一个byte数组用来存一些字节数据,大小为缓冲区大小 byte[] audiodata = new byte[buffersizeinbytes]; fileoutputstream fos = null; int readsize = 0; try { file file = new file(audioname); if (file.exists()) { file.delete(); } fos = new fileoutputstream(file);// 建立一个可存取字节的文件 } catch (exception e) { e.printstacktrace(); } while (isrecord == true) { readsize = audiorecord.read(audiodata, 0, buffersizeinbytes); if (audiorecord.error_invalid_operation != readsize && fos!=null) { try { fos.write(audiodata); } catch (ioexception e) { e.printstacktrace(); } } } try { if(fos != null) fos.close();// 关闭写入流 } catch (ioexception e) { e.printstacktrace(); } } // 这里得到可播放的音频文件 private void copywavefile(string infilename, string outfilename) { fileinputstream in = null; fileoutputstream out = null; long totalaudiolen = 0; long totaldatalen = totalaudiolen + 36; long longsamplerate = audiofilefunc.audio_sample_rate; int channels = 2; long byterate = 16 * audiofilefunc.audio_sample_rate * channels / 8; byte[] data = new byte[buffersizeinbytes]; try { in = new fileinputstream(infilename); out = new fileoutputstream(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 (filenotfoundexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } } /** * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。 * 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav * 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有 * 自己特有的头文件。 */ private void writewavefileheader(fileoutputstream out, long totalaudiolen, long totaldatalen, long longsamplerate, int channels, long byterate) throws ioexception { byte[] header = new byte[44]; header[0] = 'r'; // riff/wave header 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; // 4 bytes: size of '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); // block align header[33] = 0; header[34] = 16; // bits per sample 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格式音频
package com.example.audiorecordtest; import java.io.file; import java.io.ioexception; import android.media.mediarecorder; public class mediarecordfunc { private boolean isrecord = false; private mediarecorder mmediarecorder; private mediarecordfunc(){ } private static mediarecordfunc minstance; public synchronized static mediarecordfunc getinstance(){ if(minstance == null) minstance = new mediarecordfunc(); return minstance; } public int startrecordandfile(){ //判断是否有外部存储设备sdcard if(audiofilefunc.issdcardexit()) { if(isrecord) { return errorcode.e_state_recoding; } else { if(mmediarecorder == null) createmediarecord(); try{ mmediarecorder.prepare(); mmediarecorder.start(); // 让录制状态为true isrecord = true; return errorcode.success; }catch(ioexception ex){ ex.printstacktrace(); return errorcode.e_unkown; } } } else { return errorcode.e_nosdcard; } } public void stoprecordandfile(){ close(); } public long getrecordfilesize(){ return audiofilefunc.getfilesize(audiofilefunc.getamrfilepath()); } private void createmediarecord(){ /* ①initial:实例化mediarecorder对象 */ mmediarecorder = new mediarecorder(); /* 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); /* 设置输出文件的路径 */ file file = new file(audiofilefunc.getamrfilepath()); if (file.exists()) { file.delete(); } mmediarecorder.setoutputfile(audiofilefunc.getamrfilepath()); } private void close(){ if (mmediarecorder != null) { system.out.println("stoprecord"); isrecord = false; mmediarecorder.stop(); mmediarecorder.release(); mmediarecorder = null; } } }
3、其他文件
audiofilefunc.java
package com.example.audiorecordtest; import java.io.file; import android.media.mediarecorder; import android.os.environment; public class audiofilefunc { //音频输入-麦克风 public final static int audio_input = mediarecorder.audiosource.mic; //采用频率 //44100是目前的标准,但是某些设备仍然支持22050,16000,11025 public final static int audio_sample_rate = 44100; //44.1khz,普遍使用的频率 //录音输出文件 private final static string audio_raw_filename = "rawaudio.raw"; private final static string audio_wav_filename = "finalaudio.wav"; public final static string audio_amr_filename = "finalaudio.amr"; /** * 判断是否有外部存储设备sdcard * @return true | false */ public static boolean issdcardexit(){ if (environment.getexternalstoragestate().equals(android.os.environment.media_mounted)) return true; else return false; } /** * 获取麦克风输入的原始音频流文件路径 * @return */ public static string getrawfilepath(){ string maudiorawpath = ""; if(issdcardexit()){ string filebasepath = environment.getexternalstoragedirectory().getabsolutepath(); maudiorawpath = filebasepath+"/"+audio_raw_filename; } return maudiorawpath; } /** * 获取编码后的wav格式音频文件路径 * @return */ public static string getwavfilepath(){ string maudiowavpath = ""; if(issdcardexit()){ string filebasepath = environment.getexternalstoragedirectory().getabsolutepath(); maudiowavpath = filebasepath+"/"+audio_wav_filename; } return maudiowavpath; } /** * 获取编码后的amr格式音频文件路径 * @return */ public static string getamrfilepath(){ string maudioamrpath = ""; if(issdcardexit()){ string filebasepath = environment.getexternalstoragedirectory().getabsolutepath(); maudioamrpath = filebasepath+"/"+audio_amr_filename; } return maudioamrpath; } /** * 获取文件大小 * @param path,文件的绝对路径 * @return */ public static long getfilesize(string path){ file mfile = new file(path); if(!mfile.exists()) return -1; return mfile.length(); } }
4、其他文件
errorcode.java
package com.example.audiorecordtest; import android.content.context; import android.content.res.resources.notfoundexception; public class errorcode { public final static int success = 1000; public final static int e_nosdcard = 1001; public final static int e_state_recoding = 1002; public final static int e_unkown = 1003; public static string geterrorinfo(context vcontext, int vtype) throws notfoundexception { switch(vtype) { case success: return "success"; case e_nosdcard: return vcontext.getresources().getstring(r.string.error_no_sdcard); case e_state_recoding: return vcontext.getresources().getstring(r.string.error_state_record); case e_unkown: default: return vcontext.getresources().getstring(r.string.error_unknown); } } }
5、string.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">audiorecordtest</string> <string name="hello_world">测试audiorecord,实现录音功能</string> <string name="menu_settings">settings</string> <string name="view_record_wav">录音wav文件</string> <string name="view_record_amr">录音amr文件</string> <string name="view_stop">停止录音</string> <string name="error_no_sdcard">没有sd卡,无法存储录音数据</string> <string name="error_state_record">正在录音中,请先停止录音</string> <string name="error_unknown">无法识别的错误</string> </resources>
6、主程序mainactivity
package com.example.audiorecordtest; import android.app.activity; import android.os.bundle; import android.os.handler; import android.os.message; import android.util.log; import android.view.menu; import android.view.view; import android.widget.button; import android.widget.textview; public class mainactivity extends activity { private final static int flag_wav = 0; private final static int flag_amr = 1; private int mstate = -1; //-1:没再录制,0:录制wav,1:录制amr private button btn_record_wav; private button btn_record_amr; private button btn_stop; private textview txt; private uihandler uihandler; private uithread uithread; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); findviewbyids(); setlisteners(); init(); } @override public boolean oncreateoptionsmenu(menu menu) { // inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.activity_main, menu); return true; } private void findviewbyids(){ 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); } private void setlisteners(){ btn_record_wav.setonclicklistener(btn_record_wav_clicklistener); btn_record_amr.setonclicklistener(btn_record_amr_clicklistener); btn_stop.setonclicklistener(btn_stop_clicklistener); } private void init(){ uihandler = new uihandler(); } private button.onclicklistener btn_record_wav_clicklistener = new button.onclicklistener(){ public void onclick(view v){ record(flag_wav); } }; private button.onclicklistener btn_record_amr_clicklistener = new button.onclicklistener(){ public void onclick(view v){ record(flag_amr); } }; private button.onclicklistener btn_stop_clicklistener = new button.onclicklistener(){ public void onclick(view v){ stop(); } }; /** * 开始录音 * @param mflag,0:录制wav格式,1:录音amr格式 */ private void record(int mflag){ if(mstate != -1){ message msg = new message(); bundle b = new bundle();// 存放数据 b.putint("cmd",cmd_recordfail); b.putint("msg", errorcode.e_state_recoding); msg.setdata(b); uihandler.sendmessage(msg); // 向handler发送消息,更新ui return; } int mresult = -1; switch(mflag){ case flag_wav: audiorecordfunc mrecord_1 = audiorecordfunc.getinstance(); mresult = mrecord_1.startrecordandfile(); break; case flag_amr: mediarecordfunc mrecord_2 = mediarecordfunc.getinstance(); mresult = mrecord_2.startrecordandfile(); break; } if(mresult == errorcode.success){ uithread = new uithread(); new thread(uithread).start(); mstate = mflag; }else{ message msg = new message(); bundle b = new bundle();// 存放数据 b.putint("cmd",cmd_recordfail); b.putint("msg", mresult); msg.setdata(b); uihandler.sendmessage(msg); // 向handler发送消息,更新ui } } /** * 停止录音 */ private void stop(){ if(mstate != -1){ switch(mstate){ case flag_wav: audiorecordfunc mrecord_1 = audiorecordfunc.getinstance(); mrecord_1.stoprecordandfile(); break; case flag_amr: mediarecordfunc mrecord_2 = mediarecordfunc.getinstance(); mrecord_2.stoprecordandfile(); break; } if(uithread != null){ uithread.stopthread(); } if(uihandler != null) uihandler.removecallbacks(uithread); message msg = new message(); bundle b = new bundle();// 存放数据 b.putint("cmd",cmd_stop); b.putint("msg", mstate); msg.setdata(b); uihandler.sendmessagedelayed(msg,1000); // 向handler发送消息,更新ui mstate = -1; } } private final static int cmd_recording_time = 2000; private final static int cmd_recordfail = 2001; private final static int cmd_stop = 2002; class uihandler extends handler{ public uihandler() { } @override public void handlemessage(message msg) { // todo auto-generated method stub log.d("myhandler", "handlemessage......"); super.handlemessage(msg); bundle b = msg.getdata(); int vcmd = b.getint("cmd"); switch(vcmd) { case cmd_recording_time: int vtime = b.getint("msg"); mainactivity.this.txt.settext("正在录音中,已录制:"+vtime+" s"); break; case cmd_recordfail: int verrorcode = b.getint("msg"); string vmsg = errorcode.geterrorinfo(mainactivity.this, verrorcode); mainactivity.this.txt.settext("录音失败:"+vmsg); break; case cmd_stop: int vfiletype = b.getint("msg"); switch(vfiletype){ case flag_wav: audiorecordfunc mrecord_1 = audiorecordfunc.getinstance(); long msize = mrecord_1.getrecordfilesize(); mainactivity.this.txt.settext("录音已停止.录音文件:"+audiofilefunc.getwavfilepath()+"\n文件大小:"+msize); break; case flag_amr: mediarecordfunc mrecord_2 = mediarecordfunc.getinstance(); msize = mrecord_2.getrecordfilesize(); mainactivity.this.txt.settext("录音已停止.录音文件:"+audiofilefunc.getamrfilepath()+"\n文件大小:"+msize); break; } break; default: break; } } }; class uithread implements runnable { int mtimemill = 0; boolean vrun = true; public void stopthread(){ vrun = false; } public void run() { while(vrun){ try { thread.sleep(1000); } catch (interruptedexception e) { // todo auto-generated catch block e.printstacktrace(); } mtimemill ++; log.d("thread", "mthread........"+mtimemill); message msg = new message(); bundle b = new bundle();// 存放数据 b.putint("cmd",cmd_recording_time); b.putint("msg", mtimemill); msg.setdata(b); mainactivity.this.uihandler.sendmessage(msg); // 向handler发送消息,更新ui } } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。