Android实现长按录音松开保存及根据声贝动画展示

    • 1、准备两张需要动态展示的图片
    • 2、布局文件popup_window.xml
    • 3、popup.xml 文件
    • 4、封装MediaRecorder初始化及相关操作AudioRecoderUtils
    • 5、Activity代码实现
    • 6、录音及播放权限
    • 7、总结
    • 8、效果图

前言:做一个有梦想的程序猿!

最近公司需要本人负责开发一款app,其中有个实现录音、播放等功能,作为以java后台开发为主的我,以前没怎么接触过安卓开发,也罢,为了努力提升自身技术水平实现人生理想,只能咬牙接下重任。
在做这个功能之前,在网上也查阅了些资料,实现过程中确实遇到了一些坑,所以在此记录下来分享给各位,废话不多说,直接步入正题:

Android提供了两个API用于实现录音功能:android.media.AudioRecord、android.media.MediaRecorder,这两个各有各的优缺点 :

简单来说就是AudioRecord主要实现边录边播,也可以实时的进行音频处理,而它的缺点是输出的是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩.

而MediaRecorder优点是大部分以及集成,直接调用相关接口即可,代码量小,缺点是无法实时处理音频,输出的音频格式不是很多,例如没有输出mp3格式文件,当然AudioRecord是可以实现输出mp3格式的,不过需要工具转码,这里就不多说了。
接下来主要介绍MediaRecorder的使用方法:

1、准备两张需要动态展示的图片

2、布局文件popup_window.xml

<?xml version="1.0" encoding="utf-8"?>                                                       

popup_window.xml便是动画展示的界面

3、popup.xml 文件

<?xml version="1.0" encoding="utf-8"?>                    

popup.xml文件根据声贝大小动态剪切recoding.png到normal.png上,这样就实现了动画音频效果

4、封装MediaRecorder初始化及相关操作AudioRecoderUtils

package com.gtlxkj.cn.util;import java.io.File;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import com.gtlxkj.cn.activity.FaultActivity;import android.content.Context;import android.media.MediaPlayer;import android.media.MediaRecorder;import android.os.Handler;import android.util.Log;public class AudioRecoderUtils extends FaultActivity{     //文件路径    private String filePath;    //文件夹路径    private String FolderPath;     private MediaRecorder mMediaRecorder;        private MediaPlayer player;    private final String TAG = "fan";    public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大录音时长1000*60*10;     private OnAudioStatusUpdateListener audioStatusUpdateListener;     /**     * 文件存储默认sdcard/record     */    public AudioRecoderUtils(){        //默认保存路径为/sdcard/record/下        this(ConfigurationUtil.RECORD_PATH_ABSOULT);    }     public AudioRecoderUtils(String filePath) {         File path = new File(filePath);        if(!path.exists())            path.mkdirs();         this.FolderPath = filePath;    }     private long startTime;    private long endTime;       /**     * 开始录音 使用mp4格式     *      录音文件     * @return     */    public void startRecord() {        // 开始录音        /* ①Initial:实例化MediaRecorder对象 */        if (mMediaRecorder == null)            mMediaRecorder = new MediaRecorder();        try {            /* ②setAudioSource/setVedioSource */            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风            /* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);            /*             * ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式             * ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)             */            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);             filePath = FolderPath + System.currentTimeMillis() + ".mp4" ;            /* ③准备 */            mMediaRecorder.setOutputFile(filePath);            mMediaRecorder.setMaxDuration(MAX_LENGTH);            mMediaRecorder.prepare();            /* ④开始 */            mMediaRecorder.start();            // AudioRecord audioRecord.            /* 获取开始时间* */            startTime = System.currentTimeMillis();            updateMicStatus();            Log.i("fan", "startTime" + startTime);        } catch (IllegalStateException e) {            Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());        } catch (IOException e) {            Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());        }    }     /**     * 停止录音     */    public long stopRecord() {        if (mMediaRecorder == null)            return 0L;        endTime = System.currentTimeMillis();         //有一些网友反应在5.0以上在调用stop的时候会报错,翻阅了一下谷歌文档发现上面确实写的有可能会报错的情况,捕获异常清理一下就行了,感谢大家反馈!        try {            mMediaRecorder.stop();            mMediaRecorder.reset();            mMediaRecorder.release();            mMediaRecorder = null;            audioStatusUpdateListener.onStop(filePath);            filePath = "";         }catch (RuntimeException e){            mMediaRecorder.reset();            mMediaRecorder.release();            mMediaRecorder = null;             File file = new File(filePath);            if (file.exists())                file.delete();             filePath = "";         }        return endTime - startTime;    }     /**     * 播放录音     * @param filepath     */    public MediaPlayer playRecord(String filepath) {    player = getMediaPlayer(this);if(player != null){player.reset();try {//设置语言的来源player.setDataSource(filepath);//初始化player.prepare();//开始播放player.start();}catch (IOException e) {e.printStackTrace();}}return player;}            /**     * 取消录音     */    public void cancelRecord(){         try {             mMediaRecorder.stop();            mMediaRecorder.reset();            mMediaRecorder.release();            mMediaRecorder = null;         }catch (RuntimeException e){            mMediaRecorder.reset();            mMediaRecorder.release();            mMediaRecorder = null;        }        File file = new File(filePath);        if (file.exists())            file.delete();         filePath = "";     }     private final Handler mHandler = new Handler();    private Runnable mUpdateMicStatusTimer = new Runnable() {        public void run() {            updateMicStatus();        }    };      private int BASE = 1;    private int SPACE = 100;// 间隔取样时间     public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {        this.audioStatusUpdateListener = audioStatusUpdateListener;    }     /**     * 更新麦克状态     */    private void updateMicStatus() {         if (mMediaRecorder != null) {            double ratio = (double)mMediaRecorder.getMaxAmplitude() / BASE;            double db = 0;// 分贝            if (ratio > 1) {                db = 20 * Math.log10(ratio);                if(null != audioStatusUpdateListener) {                    audioStatusUpdateListener.onUpdate(db,System.currentTimeMillis()-startTime);                }            }            mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);        }    }     public interface OnAudioStatusUpdateListener {        /**         * 录音中...         * @param db 当前声音分贝         * @param time 录音时长         */        public void onUpdate(double db,long time);         /**         * 停止录音         * @param filePath 保存路径         */        public void onStop(String filePath);            } /**     *  
This code is trying to do the following from the hidden API *
SubtitleController sc = new SubtitleController(context, null, null); *
sc.mHandler = new Handler(); *
mediaplayer.setSubtitleAnchor(sc, null)

*/ private MediaPlayer getMediaPlayer(Context context) { MediaPlayer mediaplayer = new MediaPlayer(); if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { return mediaplayer; } try { Class<?> cMediaTimeProvider = Class.forName("android.media.MediaTimeProvider"); Class<?> cSubtitleController = Class.forName("android.media.SubtitleController"); Class<?> iSubtitleControllerAnchor = Class.forName("android.media.SubtitleController$Anchor"); Class<?> iSubtitleControllerListener = Class.forName("android.media.SubtitleController$Listener"); Constructor constructor = cSubtitleController.getConstructor( new Class[]{Context.class, cMediaTimeProvider, iSubtitleControllerListener}); Object subtitleInstance = constructor.newInstance(context, null, null); Field f = cSubtitleController.getDeclaredField("mHandler"); f.setAccessible(true); try { f.set(subtitleInstance, new Handler()); } catch (IllegalAccessException e) { return mediaplayer; } finally { f.setAccessible(false); } Method setsubtitleanchor = mediaplayer.getClass().getMethod("setSubtitleAnchor", cSubtitleController, iSubtitleControllerAnchor); setsubtitleanchor.invoke(mediaplayer, subtitleInstance, null); } catch (Exception e) { Log.d(TAG,"getMediaPlayer crash ,exception = "+e); } return mediaplayer; } }

相关工具类:

package com.gtlxkj.cn.util;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;import android.util.Log;public class TimeUtils {/**  * 获取当前时间  * @return  */  public static String getNowTime(){      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");      Date date = new Date(System.currentTimeMillis());      return simpleDateFormat.format(date);  }  /**  * 获取时间戳  *  * @return 获取时间戳  */  public static String getTimeString() {      SimpleDateFormat df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");      Calendar calendar = Calendar.getInstance();      return df.format(calendar.getTime());  }  /**  * 时间转换为时间戳  * @param time:需要转换的时间  * @return  */  public static String dateToStamp(String time)  {      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");      Date date = null;      try {          date = simpleDateFormat.parse(time);      } catch (Exception e) {          e.printStackTrace();      }      long ts = date.getTime();      return String.valueOf(ts);  }  /**  * 时间戳转换为字符串  * @param time:时间戳  * @return  */  public static String getDateToString(long time) {      Date d = new Date(time);      SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");      return sf.format(d);  }  /**  * 时间戳转换为字符串分秒  * @param time:时间戳  * @return  */  public static String getDateCoverString(long time) {      Date d = new Date(time);      SimpleDateFormat sf = new SimpleDateFormat("mm:ss");      return sf.format(d);  }  /**  *获取距现在某一小时的时刻  * @param hour hour=-1为上一个小时,hour=1为下一个小时  * @return  */  public static String getLongTime(int hour){      Calendar c = Calendar.getInstance(); // 当时的日期和时间      int h; // 需要更改的小时      h = c.get(Calendar.HOUR_OF_DAY) - hour;      c.set(Calendar.HOUR_OF_DAY, h);      SimpleDateFormat df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");      Log.v("time",df.format(c.getTime()));      return df.format(c.getTime());  }  }

5、Activity代码实现

@SuppressLint("NewApi")@ContentView(R.layout.vw_head_fault)public class FaultActivity extends BaseActivity{    private View view ;    @ViewInject(R.id.buttonPressToSpeak)    private Button mButton;    @ViewInject(R.id.buttonBroadcast)    private Button cButton;    @ViewInject(R.id.head_broadcast)    private TextView head_broadcast;    @ViewInject(R.id.ivAnim)    private ImageView ivAnim;    private AnimationDrawable drawable;    private ImageView micImage;    private TextView recordingTime;    private long ltime;        private MediaPlayer player;      @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        this.context=FaultActivity.this;        Log.i("XListViewActivity", "onCreate");           initVedio();    }    //录音功能 初始化     private void initVedio(){      view = View.inflate(this, R.layout.popup_window, null);       //设置空白的背景色      final WindowManager.LayoutParams lp = FaultActivity.this.getWindow().getAttributes();      final PopupWindow mPop  = new PopupWindow(view);      micImage=(ImageView)view.findViewById(R.id.iv_pro);      recordingTime=(TextView)view.findViewById(R.id.recording_time);      mAudioRecoderUtils = new AudioRecoderUtils();      //录音回调          mAudioRecoderUtils.setOnAudioStatusUpdateListener(new AudioRecoderUtils.OnAudioStatusUpdateListener() {                 //录音中....db为声音分贝,time为录音时长              @Override              public void onUpdate(double db, long time) {                  //根据分贝值来设置录音时话筒图标的上下波动              ltime=time;               micImage.getDrawable().setLevel((int) (3000 + 6000 * db / 100));              recordingTime.setText(TimeUtils.getDateCoverString(time));              }                 //录音结束,filePath为保存路径              @Override              public void onStop(String filePath) {              if(ltime<1500){//判断,如果录音时间小于1.5秒,则删除文件提示,过短          File file = new File(filePath);          if(file.exists()){//判断文件是否存在,如果存在删除文件          file.delete();//删除文件          Toast.makeText(FaultActivity.this, "录音时间过短",Toast.LENGTH_SHORT).show();          }                  }else{                  try {                  //保存录音路径前先删除旧的文件                  if(!Utils.StringEx(fault.getSoundpath())){                  File file = new File(fault.getSoundpath());                      if(file.exists()){//判断文件是否存在,如果存在删除文件                    file.delete();//删除文件                    }                  }                  fault.setSoundpath(filePath);                  getDBManager().saveOrUpdate(fault);                  Toast.makeText(FaultActivity.this, "录音保存在:" + filePath, Toast.LENGTH_SHORT).show();                      recordingTime.setText("00:00");                      ltime=0;} catch (DbException e) {e.printStackTrace();}                  }              }          });            //Button的touch监听  mButton.setOnTouchListener(new View.OnTouchListener() {  @Override  public boolean onTouch(View v, MotionEvent event) {     switch (event.getAction()){     case MotionEvent.ACTION_DOWN:  lp.alpha = 0.4f;  FaultActivity.this.getWindow().setAttributes(lp);  mPop.setWidth(500);  mPop.setHeight(500);  mPop.showAtLocation(rl,Gravity.CENTER,0,0);  mAudioRecoderUtils.startRecord();     break;     case MotionEvent.ACTION_UP://恢复背景色  lp.alpha = 1f;  FaultActivity.this.getWindow().setAttributes(lp);  mAudioRecoderUtils.stopRecord();        //结束录音(保存录音文件)  mPop.dismiss();     break;  }  return true;  }  });                    //播放录音          cButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(Utils.StringEx(fault.getSoundpath())){Toast.makeText(FaultActivity.this, "此故障信息暂无录音内容", Toast.LENGTH_SHORT).show();}else{if(player==null){player = mAudioRecoderUtils.playRecord(fault.getSoundpath());//播放 cButton.setBackgroundResource(R.drawable.zanting);     head_broadcast.setText("正在播放");     ivAnim.setBackgroundResource(R.drawable.anim);         drawable = (AnimationDrawable)ivAnim.getBackground();     drawable.start();     player.setOnCompletionListener(new OnCompletionListener() {     @Override     public void onCompletion(MediaPlayer mp) {//监听是否播放完毕      cButton.setBackgroundResource(R.drawable.play);      head_broadcast.setText("点击播放");      player.release();//释放资源      player=null;      drawable.stop();      ivAnim.setBackgroundResource(R.drawable.bofang3);     }    });}else if(player.isPlaying()){ player.stop(); player.release();//释放资源 player=null;     cButton.setBackgroundResource(R.drawable.play);     head_broadcast.setText("点击播放");     drawable.stop();    ivAnim.setBackgroundResource(R.drawable.bofang3);}}}});    }}

此Activity同样实现了播放喇叭动画展示以及录音中背景变暗等效果

6、录音及播放权限

最后就是加权限了,在AndroidManifest.xml文件中加入下面两句

7、总结

其实安卓实现录音功能并不复杂,明白了其中的调用流程就会感觉简单许多,根据声贝展动画界面需要在代码中指定PopupWindow的大小,否则可能会出现无法展示问题,后期也实现了批量上传录音及图片到服务器功能,而用这种方法上传的录音经过测试有wav和mp4格式录音文件上传到服务器可以播放,mp3格式会提示播放失败,其他格式暂时还没测试。
以上叙述有误的地方还请各位及时提出改正

8、效果图

界面没重新设计就没那么美观了
录音:

播放:

最后,如果本篇文章对您有所帮助,可以评论或点赞支持一下哦,感谢感谢!

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. android电源管理资料整理
  6. My Magic Android(安卓)Tour —— 处女作
  7. Android(安卓)万能适配器 节省你的开发时间
  8. Android文件上传下载
  9. Android设备的内置存储和外置存储到底是怎么回事,深入理解

随机推荐

  1. Android布局之RelativeLayout学习
  2. android 模拟器命令 转载
  3. Android中的数据存储与传输
  4. Android笔记1
  5. android:launchMode="singleTask" 与 onN
  6. git 获取android source
  7. Android笔记1
  8. Android(安卓)创建自定义View 实现TopBar
  9. Android笔记 - 常见错误解决方法 | Andro
  10. Android启动脚本init.rc(2)