Android之录音--AudioRecord、MediaRecorder
?Android提供了兩個(gè)API用于實(shí)現(xiàn)錄音功能:android.media.AudioRecord、android.media.MediaRecorder。
??? 網(wǎng)上有很多談?wù)撨@兩個(gè)類的資料。現(xiàn)在大致總結(jié)下:
1、AudioRecord
主要是實(shí)現(xiàn)邊錄邊播(AudioRecord+AudioTrack)以及對(duì)音頻的實(shí)時(shí)處理(如會(huì)說(shuō)話的湯姆貓、語(yǔ)音)
優(yōu)點(diǎn):語(yǔ)音的實(shí)時(shí)處理,可以用代碼實(shí)現(xiàn)各種音頻的封裝
缺點(diǎn):輸出是PCM語(yǔ)音數(shù)據(jù),如果保存成音頻文件,是不能夠被播放器播放的,所以必須先寫代碼實(shí)現(xiàn)數(shù)據(jù)編碼以及壓縮
示例:
使用AudioRecord類錄音,并實(shí)現(xiàn)WAV格式封裝。錄音20s,輸出的音頻文件大概為3.5M左右(已寫測(cè)試代碼)
2、MediaRecorder
已經(jīng)集成了錄音、編碼、壓縮等,支持少量的錄音音頻格式,大概有.aac(API = 16) .amr .3gp
優(yōu)點(diǎn):大部分以及集成,直接調(diào)用相關(guān)接口即可,代碼量小
缺點(diǎn):無(wú)法實(shí)時(shí)處理音頻;輸出的音頻格式不是很多,例如沒(méi)有輸出mp3格式文件
示例:
使用MediaRecorder類錄音,輸出amr格式文件。錄音20s,輸出的音頻文件大概為33K(已寫測(cè)試代碼)
3、音頻格式比較
WAV格式:錄音質(zhì)量高,但是壓縮率小,文件大
AAC格式:相對(duì)于mp3,AAC格式的音質(zhì)更佳,文件更小;有損壓縮;一般蘋果或者Android SDK4.1.2(API 16)及以上版本支持播放
AMR格式:壓縮比比較大,但相對(duì)其他的壓縮格式質(zhì)量比較差,多用于人聲,通話錄音
至于常用的mp3格式,使用MediaRecorder沒(méi)有該視頻格式輸出。一些人的做法是使用AudioRecord錄音,然后編碼成wav格式,再轉(zhuǎn)換成mp3格式
?
??? 再貼上一些測(cè)試工程。
功能描述:
1、點(diǎn)擊“錄音WAV文件”,開(kāi)始錄音。錄音完成后,生成文件/sdcard/FinalAudio.wav
2、點(diǎn)擊“錄音AMR文件”,開(kāi)始錄音。錄音完成后,生成文件/sdcard/FinalAudio.amr
3、點(diǎn)擊“停止錄音”,停止錄音,并顯示錄音輸出文件以及該文件大小。
?
大致代碼如下:
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 { // 緩沖區(qū)字節(jié)大小 private int bufferSizeInBytes = 0;//AudioName裸音頻數(shù)據(jù)文件 ,麥克風(fēng)private String AudioName = ""; //NewAudioName可播放的音頻文件 private String NewAudioName = "";private AudioRecord audioRecord; private boolean isRecord = false;// 設(shè)置正在錄制的狀態(tài) private static AudioRecordFunc mInstance; private AudioRecordFunc(){} public synchronized static AudioRecordFunc getInstance(){if(mInstance == null) mInstance = new AudioRecordFunc(); return mInstance; }public int startRecordAndFile() {//判斷是否有外部存儲(chǔ)設(shè)備sdcardif(AudioFileFunc.isSdcardExit()){if(isRecord){return ErrorCode.E_STATE_RECODING;}else{if(audioRecord == null)creatAudioRecord();audioRecord.startRecording(); // 讓錄制狀態(tài)為true isRecord = true; // 開(kāi)啟音頻文件寫入線程 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(); // 獲得緩沖區(qū)字節(jié)大小 bufferSizeInBytes = AudioRecord.getMinBufferSize(AudioFileFunc.AUDIO_SAMPLE_RATE, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT); // 創(chuàng)建AudioRecord對(duì)象 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();//往文件中寫入裸數(shù)據(jù) copyWaveFile(AudioName, NewAudioName);//給裸數(shù)據(jù)加上頭文件 } } /** * 這里將數(shù)據(jù)寫入文件,但是并不能播放,因?yàn)锳udioRecord獲得的音頻是原始的裸音頻, * 如果需要播放就必須加入一些格式或者編碼的頭信息。但是這樣的好處就是你可以對(duì)音頻的 裸數(shù)據(jù)進(jìn)行處理,比如你要做一個(gè)愛(ài)說(shuō)話的TOM * 貓?jiān)谶@里就進(jìn)行音頻的處理,然后重新封裝 所以說(shuō)這樣得到的音頻比較容易做一些音頻的處理。 */ private void writeDateTOFile() { // new一個(gè)byte數(shù)組用來(lái)存一些字節(jié)數(shù)據(jù),大小為緩沖區(qū)大小 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);// 建立一個(gè)可存取字節(jié)的文件 } 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();// 關(guān)閉寫入流 } 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(); } } /** * 這里提供一個(gè)頭信息。插入這些信息就可以得到可以播放的文件。 * 為我為啥插入這44個(gè)字節(jié),這個(gè)還真沒(méi)深入研究,不過(guò)你隨便打開(kāi)一個(gè)wav * 音頻的文件,可以發(fā)現(xiàn)前面的頭文件可以說(shuō)基本一樣哦。每種格式的文件都有 * 自己特有的頭文件。 */ 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(){//判斷是否有外部存儲(chǔ)設(shè)備sdcardif(AudioFileFunc.isSdcardExit()){if(isRecord){return ErrorCode.E_STATE_RECODING;}else{if(mMediaRecorder == null)createMediaRecord();try{mMediaRecorder.prepare();mMediaRecorder.start();// 讓錄制狀態(tài)為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:實(shí)例化MediaRecorder對(duì)象 */mMediaRecorder = new MediaRecorder();/* setAudioSource/setVedioSource*/mMediaRecorder.setAudioSource(AudioFileFunc.AUDIO_INPUT);//設(shè)置麥克風(fēng)/* 設(shè)置輸出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default* THREE_GPP(3gp格式,H263視頻/ARM音頻編碼)、MPEG-4、RAW_AMR(只支持音頻且音頻編碼要求為AMR_NB)*/mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);/* 設(shè)置音頻文件的編碼:AAC/AMR_NB/AMR_MB/Default */mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);/* 設(shè)置輸出文件的路徑 */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 {//音頻輸入-麥克風(fēng)public final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;//采用頻率//44100是目前的標(biāo)準(zhǔn),但是某些設(shè)備仍然支持22050,16000,11025public 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";/*** 判斷是否有外部存儲(chǔ)設(shè)備sdcard* @return true | false*/public static boolean isSdcardExit(){ if (Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))return true;elsereturn false;}/*** 獲取麥克風(fēng)輸入的原始音頻流文件路徑* @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,文件的絕對(duì)路徑* @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">測(cè)試AudioRecord,實(shí)現(xiàn)錄音功能</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">沒(méi)有SD卡,無(wú)法存儲(chǔ)錄音數(shù)據(jù)</string><string name="error_state_record">正在錄音中,請(qǐng)先停止錄音</string><string name="error_unknown">無(wú)法識(shí)別的錯(cuò)誤</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:沒(méi)再錄制,0:錄制wav,1:錄制amrprivate Button btn_record_wav;private Button btn_record_amr;private Button btn_stop;private TextView txt;private UIHandler uiHandler;private UIThread uiThread; @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewByIds();setListeners();init();} @Overridepublic 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(); }};/*** 開(kāi)始錄音* @param mFlag,0:錄制wav格式,1:錄音amr格式*/private void record(int mFlag){if(mState != -1){Message msg = new Message();Bundle b = new Bundle();// 存放數(shù)據(jù)b.putInt("cmd",CMD_RECORDFAIL);b.putInt("msg", ErrorCode.E_STATE_RECODING);msg.setData(b); uiHandler.sendMessage(msg); // 向Handler發(fā)送消息,更新UIreturn;} 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();// 存放數(shù)據(jù)b.putInt("cmd",CMD_RECORDFAIL);b.putInt("msg", mResult);msg.setData(b); uiHandler.sendMessage(msg); // 向Handler發(fā)送消息,更新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();// 存放數(shù)據(jù)b.putInt("cmd",CMD_STOP);b.putInt("msg", mState);msg.setData(b);uiHandler.sendMessageDelayed(msg,1000); // 向Handler發(fā)送消息,更新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() {}@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubLog.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 blocke.printStackTrace();}mTimeMill ++;Log.d("thread", "mThread........"+mTimeMill);Message msg = new Message();Bundle b = new Bundle();// 存放數(shù)據(jù)b.putInt("cmd",CMD_RECORDING_TIME);b.putInt("msg", mTimeMill);msg.setData(b); MainActivity.this.uiHandler.sendMessage(msg); // 向Handler發(fā)送消息,更新UI} }} }
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)
總結(jié)
以上是生活随笔為你收集整理的Android之录音--AudioRecord、MediaRecorder的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android之项目中调用已有.so库
- 下一篇: Android之 AudioTrack学