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

  • 相关阅读:
    JDBC面试问题
    Linux下如何实现MySQL数据库每天自动备份定时备份
    Linux下如何实现MySQL数据库每天自动备份定时备份
    Linux下如何实现MySQL数据库每天自动备份定时备份
    Random Forest And Extra Trees
    经济学十大原理
    JavaScript的执行机制
    单元测试
    JavaScript是如何工作的(一)
    阿里大数据竞赛season1 总结
  • 原文地址:https://www.cnblogs.com/fuly550871915/p/4836204.html
Copyright © 2011-2022 走看看