模仿微信語音聊天功能(3) 核心部分,錄音功能的實現


     

      在上一篇文章中,我們實現了按鈕和對話框的交互。沒有讀的可以點擊下面的鏈接查看:

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程序,然后對着手機麥克風說話,是不是會彈出對話框,而且對話框上的圖標會隨着你說話聲音的大小而跳動呢?快運行一下吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM