模仿微信语音聊天功能(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程序,然后对着手机麦克风说话,是不是会弹出对话框,而且对话框上的图标会随着你说话声音的大小而跳动呢?快运行一下吧。

模仿微信语音聊天功能(3) 核心部分,录音功能的实现

上一篇:模仿微信语音聊天功能(4) 音频播放实现以及项目结束


下一篇:python+django+新浪sae+有道API实现微信服务号自动翻译