在上一篇文章中,我們實現了按鈕和對話框的交互。沒有讀的可以點擊下面的鏈接查看:
http://www.cnblogs.com/fuly550871915/p/4836108.html
在這一篇文章中,我們接着往下做,實現核心部分,即錄音功能的實現。這里需要讀者具備一定的MediaPlayer這個類的一些基礎知識。
首先我們要在添加一下權限,切記,這個步驟千萬不要忘記了。代碼如下:
1 <uses-permission android:name="android.permission.RECORD_AUDIO"/> 2 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> 3 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 4 <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" /> 5 <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
下面我們就可以痛快的編寫實現錄音功能的類了。代碼如下:
1 package com.fuly.util; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.util.UUID; 6 7 import android.media.MediaRecorder; 8 9 10 //一個管理錄音的類 11 public class RecoderManager { 12 13 private MediaRecorder mMediaRcoder;//錄音器 14 15 16 private static RecoderManager mRecoderManager; 17 private RecoderManagerListener mListener; 18 private static String dir;//音頻存放的文件夾 19 private String mCurPath;//用來記錄音頻即時存放的路徑名 20 21 private boolean isPrepared ;//判斷錄音器是否已經准備好了 22 23 24 25 26 private RecoderManager(String dir){ 27 28 this.dir = dir;//傳入保存音頻的文件夾的地址 29 30 } 31 32 public static RecoderManager getRecoderMananger(String dir){ 33 34 if(mRecoderManager == null){ 35 36 synchronized (RecoderManager.class) { 37 38 if(mRecoderManager == null){ 39 40 mRecoderManager = new RecoderManager(dir); 41 42 } 43 } 44 } 45 46 47 return mRecoderManager; 48 49 } 50 51 52 53 54 //提供一個回調接口,當錄音准備好了后,調用該接口的方法,錄音正式開始,此時就可以獲取聲音等級等東西了 55 56 public interface RecoderManagerListener{ 57 58 void wellPrepared();//當錄音准備好了就會調用這個方法 59 } 60 public void setOnRecoderManagerListener(RecoderManagerListener listener){ 61 this.mListener = listener; 62 } 63 64 65 //錄音的准備工作,要准備好錄音存取的文件地址,錄音器的准備等 66 public void recoderPrepared(){ 67 68 isPrepared = false; 69 70 71 File mDir = new File(dir); 72 73 if(!mDir.exists()){ 74 mDir.mkdir();//生成文件夾 75 } 76 77 String fileName = generateName(); //錄下的聲音所輸出的文件名 78 File file = new File(mDir,fileName);//最終在文件夾mDir下面生成文件fileName 79 mCurPath = file.getAbsolutePath();//記錄下即時存放所錄音頻的文件的完整路徑名 80 81 82 try { 83 /* 84 * 下面的代碼為初始化錄音的這個實例,並做錄音准備工作 85 */ 86 mMediaRcoder = new MediaRecorder(); 87 88 //設置音頻輸出到哪個文件中,注意該參數應該是一個完成的路徑,最終文件應該是.mar格式的。 89 mMediaRcoder.setOutputFile(file.getAbsolutePath()); 90 //設置音頻源為我們的麥克風 91 mMediaRcoder.setAudioSource(MediaRecorder.AudioSource.MIC); 92 //設置音頻格式 93 mMediaRcoder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB); 94 //設置音頻的編碼格式為amr 95 mMediaRcoder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 96 97 98 mMediaRcoder.prepare(); 99 mMediaRcoder.start(); 100 101 isPrepared = true; 102 103 if(isPrepared){ 104 105 mListener.wellPrepared();//回調,即回調按鈕里重寫的該方法 106 } 107 108 109 110 } catch (IllegalStateException e) { 111 e.printStackTrace(); 112 } catch (IOException e) { 113 e.printStackTrace(); 114 } 115 116 } 117 118 119 //該方法用來隨機生成文件名 120 private String generateName() { 121 122 return UUID.randomUUID().toString()+".amr"; 123 } 124 125 126 127 128 //通過音頻獲得聲音的級別,轉化為1~maxLevel之間 129 public int getVoiceLevel(int maxLevel){ 130 131 if(mMediaRcoder != null){ 132 133 try { 134 return maxLevel*mMediaRcoder.getMaxAmplitude()/32768+1; 135 } catch (IllegalStateException e) {//在這里,我們捕捉一下錯誤,是為了不讓影響程序進行。 136 //因為就算音頻沒法捕捉到,也不是什么大事,只要聲音錄制到了就可以正常進行。 137 //所以在此忽略掉這個錯誤 138 } 139 } 140 141 return 1; //沒有捕捉到音頻,就默認為等級為1,並返回 142 } 143 144 145 146 //釋放資源 147 public void release(){ 148 149 if(mMediaRcoder != null){ 150 151 mMediaRcoder.stop(); 152 mMediaRcoder.release(); 153 mMediaRcoder = null; 154 } 155 156 } 157 158 //錄音取消時的操作 159 public void cancel(){ 160 161 //注意此刻一定不要只調用mMediaRecoder.release()方法。除非你調用它之前 162 //再調用一下它的stop方法。一定注意順序.不然會除非你release()的時候,錄音卻沒停止。 163 //但是程序也不報錯,就出現閃退。血淚教訓啊 164 release();//調用我們剛剛寫好的release() 165 166 167 if(mCurPath != null){ 168 File file = new File(mCurPath); 169 if(file.exists()){ 170 file.delete(); 171 mCurPath = null; 172 } 173 } 174 } 175 176 //提供一個獲取錄音存放的路徑的方法 177 public String getPath(){ 178 179 return mCurPath; 180 } 181 182 }
最后,將錄音功能集成到按鈕中。這個可能要復雜一些,希望你有耐心做下去。具體代碼如下:
1 package com.fuly.util; 2 3 4 import com.fuly.irecoder.R; 5 import com.fuly.util.RecoderManager.RecoderManagerListener; 6 import com.fuly.util.RecoderManager; 7 8 import android.content.Context; 9 import android.os.Environment; 10 import android.os.Handler; 11 import android.util.AttributeSet; 12 import android.util.Log; 13 import android.view.MotionEvent; 14 import android.view.View; 15 import android.widget.Button; 16 17 18 19 20 //定義我們自己的錄音按鈕 21 public class RecoderButton extends Button implements RecoderManagerListener{ 22 23 //按鈕的三個狀態 24 25 private static final int STATE_NORMAL = 1;//正常 26 private static final int STATE_RECODING = 2;//錄音狀態 27 private static final int STATE_CACLE = 3;//取消狀態 28 29 private int mCurState = STATE_NORMAL;//記錄當前按鈕狀態 30 31 private int Y = 50;//限定手指移動的上下寬度 32 33 private DialogManager mDialogManager;//對話框管理類 34 private RecoderManager mRecoderManager;//錄音器的管理類 35 36 private boolean isRecoding = false; 37 private boolean isLongClick =false;//是否為長安按鈕,默認為沒有觸發 38 39 40 41 private float mTime=0;//用來記錄錄音的時長 42 43 private RecoderButtonListener mListener;//用來傳遞數據的實體 44 45 46 public RecoderButton(Context context) { 47 48 this(context,null); 49 } 50 51 52 public RecoderButton(Context context, AttributeSet attrs) { 53 54 super(context, attrs); 55 56 57 mDialogManager = new DialogManager(context);//實例化對話框管理類 58 59 String path = Environment.getExternalStorageDirectory()+"//MyAudio"; 60 Log.d("付勇焜的文件夾---->",path); 61 mRecoderManager = RecoderManager.getRecoderMananger(path);//獲取一個實例 62 63 mRecoderManager.setOnRecoderManagerListener(this); 64 65 setOnLongClickListener(new OnLongClickListener() { 66 67 public boolean onLongClick(View v) { 68 69 isLongClick = true; 70 mRecoderManager.recoderPrepared(); 71 return false; 72 } 73 }); 74 75 } 76 77 78 //定義一個回調接口,用來將數據返回,錄音的時長和錄音存放的路徑 79 80 public interface RecoderButtonListener{ 81 82 void onFinish(int mTime,String filePath); 83 } 84 85 public void setOnRecoderButtonListener( RecoderButtonListener listener){ 86 87 this.mListener = listener; 88 } 89 90 91 92 93 94 95 96 97 98 private static final int CHANGE_VOICE = 0X110; 99 private static final int DIALOG_DISS = 0X111; 100 private static final int MEDIA_PREPARED = 0X112; 101 102 103 104 105 private Runnable mRunnable = new Runnable(){ 106 107 108 public void run() { 109 110 while(isRecoding){ 111 112 try { 113 Thread.sleep(100); 114 } catch (InterruptedException e) { 115 e.printStackTrace(); 116 } 117 118 mTime+=0.1f; 119 120 mHandler.sendEmptyMessage(CHANGE_VOICE); 121 122 } 123 124 } 125 126 }; 127 128 private Handler mHandler = new Handler(){ 129 130 public void handleMessage(android.os.Message msg) { 131 132 switch(msg.what){ 133 134 case MEDIA_PREPARED: 135 mDialogManager.dialogShow(); 136 isRecoding = true; 137 new Thread(mRunnable).start(); 138 break; 139 140 case CHANGE_VOICE: 141 142 //獲取聲音等級,並在對話框中改變 143 mDialogManager.updateVoiceLevel(mRecoderManager.getVoiceLevel(7)); 144 145 break; 146 case DIALOG_DISS: 147 mDialogManager.dialogDismiss(); 148 break; 149 } 150 151 }; 152 }; 153 154 155 156 //回調方法,當該方法在RecoderManager中被調用時,說明錄音器已經准備完畢 157 //可以開始錄音了 158 public void wellPrepared() { 159 160 161 mHandler.sendEmptyMessage(MEDIA_PREPARED); 162 } 163 164 165 166 //捕捉按鈕點擊事件 167 public boolean onTouchEvent(MotionEvent event) { 168 169 int x = (int) event.getX(); 170 int y =(int)event.getY(); 171 172 switch(event.getAction()){ 173 174 175 case MotionEvent.ACTION_DOWN: 176 177 changeState(STATE_RECODING);//按下按鈕,改變按鈕狀態 178 179 break; 180 case MotionEvent.ACTION_MOVE: 181 182 if(isRecoding){ 183 184 if(wantCancel(x,y)){ //如果檢測到取消,則改變按鈕狀態為取消 185 186 changeState(STATE_CACLE); 187 mDialogManager.dialogRecoderCancel(); 188 189 190 }else{ 191 changeState(STATE_RECODING); 192 mDialogManager.dialogRecoding(); 193 194 } 195 } 196 197 198 break; 199 case MotionEvent.ACTION_UP: 200 //手指抬起的幾種情況 201 //(1)正常錄音結束后的抬起 (2)取消錄音的抬起 (3)迅速抬起,此時會造成錄音時間過短 202 //(2)可能錄音器還沒准備好,就手指抬起了。 也可看成是錄音的時間太短(5)沒有觸發長安安鈕就抬起 203 204 if(!isLongClick){ //如果沒有觸發長安安鈕 205 206 reset(); 207 return super.onTouchEvent(event); 208 } 209 210 if(!isRecoding||mTime<0.6f){//如果沒有准備好錄音器或者錄音時間太短 211 212 mDialogManager.tooShort(); 213 mRecoderManager.cancel(); 214 mHandler.sendEmptyMessageDelayed(DIALOG_DISS, 2000); 215 216 }else if(mCurState == STATE_RECODING){//正常錄音結束 217 218 //在這里應該返回錄音的文件路徑和時長給播放器 219 220 mDialogManager.dialogDismiss(); 221 mRecoderManager.release(); 222 223 //此時應將錄音的時長和路徑傳遞給MainActivity 224 if(mListener != null){ 225 mListener.onFinish((int)mTime, mRecoderManager.getPath()); 226 } 227 228 }else if(mCurState == STATE_CACLE){// 如果為取消錄音的抬起 229 230 mDialogManager.dialogDismiss(); 231 mRecoderManager.cancel(); 232 } 233 234 235 reset();//各種設置復位 236 237 break; 238 default: 239 break; 240 } 241 242 return super.onTouchEvent(event); 243 } 244 245 246 247 //復位 248 private void reset() { 249 250 isRecoding = false; 251 isLongClick =false; 252 mTime = 0; 253 mCurState = STATE_NORMAL; 254 setText(R.string.btn_normal); 255 256 } 257 258 259 260 //檢查手指移動范圍,從而確定用戶是否想取消錄音 261 private boolean wantCancel(int x, int y) { 262 263 if(x<0||x>getWidth()){ 264 265 return true; 266 } 267 268 if(y<-Y||y>getHeight()+Y){ 269 return true; 270 } 271 return false; 272 } 273 274 275 276 //改變狀態,包括按鈕等 277 private void changeState(int state) { 278 279 if(mCurState != state){ 280 281 mCurState = state; 282 283 284 285 switch(mCurState){ 286 287 case STATE_NORMAL: 288 289 setText(R.string.btn_normal); 290 291 break; 292 case STATE_RECODING: 293 294 setText(R.string.btn_recoding); 295 296 break; 297 case STATE_CACLE: 298 299 300 setText(R.string.btn_cancel); 301 302 303 break; 304 default: 305 break; 306 307 } 308 } 309 310 } 311 312 313 314 }
至此,恭喜你,這個項目基本上完成一半了。下面我們運行一下android程序,然后對着手機麥克風說話,是不是會彈出對話框,而且對話框上的圖標會隨着你說話聲音的大小而跳動呢?快運行一下吧。