欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android仿微信录音功能

程序员文章站 2023-11-09 16:39:22
提要:需求是开发类似微信发语音的功能,没有语音转文字。网上看了一些代码,不能拿来直接用,部分代码逻辑有问题,所以想把自己的代码贴出来,仅供参考。 功能: a、设置最大录音时长和录音...

提要:需求是开发类似微信发语音的功能,没有语音转文字。网上看了一些代码,不能拿来直接用,部分代码逻辑有问题,所以想把自己的代码贴出来,仅供参考。

功能:

a、设置最大录音时长和录音倒计时(为了方便测试,最大时长设置为15秒,开始倒计时设置为7秒)

b、在录音之前检查录音和存储权限

源码:

1、录音对话框管理类dialogmanager:

/**
 * 功能:录音对话框管理类
 */
public class dialogmanager {
  private alertdialog.builder builder;
  private alertdialog dialog;
  private imageview micon;
  private imageview mvoice;
  private textview mlabel;
 
  private context context;
 
  /**
   * 构造方法
   *
   * @param context activity级别的context
   */
  public dialogmanager(context context) {
    this.context = context;
  }
 
  /**
   * 显示录音的对话框
   */
  public void showrecordingdialog() {
    builder = new alertdialog.builder(context, r.style.audiorecorderdialogstyle);
    layoutinflater inflater = layoutinflater.from(context);
    view view = inflater.inflate(r.layout.audio_recorder_dialog, null);
    micon = view.findviewbyid(r.id.iv_dialog_icon);
    mvoice = view.findviewbyid(r.id.iv_dialog_voice);
    mlabel = view.findviewbyid(r.id.tv_dialog_label);
 
    builder.setview(view);
    dialog = builder.create();
    dialog.show();
    dialog.setcanceledontouchoutside(false);
  }
 
  /**
   * 正在播放时的状态
   */
  public void recording() {
    if (dialog != null && dialog.isshowing()) { //显示状态
      micon.setvisibility(view.visible);
      mvoice.setvisibility(view.visible);
      mlabel.setvisibility(view.visible);
 
      micon.setimageresource(r.drawable.ic_audio_recorder);
      mvoice.setimageresource(r.drawable.ic_audio_v1);
      mlabel.settext(r.string.audio_record_dialog_up_to_cancel);
    }
  }
 
  /**
   * 显示想取消的对话框
   */
  public void wanttocancel() {
    if (dialog != null && dialog.isshowing()) { //显示状态
      micon.setvisibility(view.visible);
      mvoice.setvisibility(view.gone);
      mlabel.setvisibility(view.visible);
 
      micon.setimageresource(r.drawable.ic_audio_cancel);
      mlabel.settext(r.string.audio_record_dialog_release_to_cancel);
    }
  }
 
  /**
   * 显示时间过短的对话框
   */
  public void tooshort() {
    if (dialog != null && dialog.isshowing()) { //显示状态
      micon.setvisibility(view.visible);
      mvoice.setvisibility(view.gone);
      mlabel.setvisibility(view.visible);
 
      mlabel.settext(r.string.audio_record_dialog_too_short);
    }
  }
 
  // 显示取消的对话框
  public void dismissdialog() {
    if (dialog != null && dialog.isshowing()) { //显示状态
      dialog.dismiss();
      dialog = null;
    }
  }
 
  /**
   * 显示更新音量级别的对话框
   *
   * @param level 1-7
   */
  public void updatevoicelevel(int level) {
    if (dialog != null && dialog.isshowing()) { //显示状态
      micon.setvisibility(view.visible);
      mvoice.setvisibility(view.visible);
      mlabel.setvisibility(view.visible);
 
      int resid = context.getresources().getidentifier("ic_audio_v" + level, "drawable", context.getpackagename());
      mvoice.setimageresource(resid);
    }
  }
 
  public void updatetime(int time) {
    if (dialog != null && dialog.isshowing()) { //显示状态
      micon.setvisibility(view.visible);
      mvoice.setvisibility(view.visible);
      mlabel.setvisibility(view.visible);
      mlabel.settext(time + "s");
    }
  }
}

2、录音管理类audiomanager

 /**
 * 功能:录音管理类
 */
public class audiomanager {
  private mediarecorder mmediarecorder;
  private string mdir;
  private string mcurrentfilepath;
 
  private static audiomanager minstance;
 
  private boolean isprepared;
 
  private audiomanager(string dir) {
    this.mdir = dir;
  }
 
  //单例模式:在这里实例化audiomanager并传入录音文件地址
  public static audiomanager getinstance(string dir) {
    if (minstance == null) {
      synchronized (audiomanager.class) {
        if (minstance == null) {
          minstance = new audiomanager(dir);
        }
      }
    }
    return minstance;
  }
 
  /**
   * 回调准备完毕
   */
  public interface audiostatelistener {
    void wellprepared();
  }
 
  public audiostatelistener mlistener;
 
  /**
   * 回调方法
   */
  public void setonaudiostatelistener(audiostatelistener listener) {
    mlistener = listener;
  }
 
  /**
   * 准备
   */
  public void prepareaudio() {
    try {
      isprepared = false;
      file dir = fileutils.createnewfile(mdir);
      string filename = generatefilename();
 
      file file = new file(dir, filename);
      mcurrentfilepath = file.getabsolutepath();
      logger.t("audiomanager").i("audio file name :" + mcurrentfilepath);
 
      mmediarecorder = new mediarecorder();
      //设置输出文件
      mmediarecorder.setoutputfile(mcurrentfilepath);
      //设置mediarecorder的音频源为麦克风
      mmediarecorder.setaudiosource(mediarecorder.audiosource.mic);
      //设置音频格式
      mmediarecorder.setoutputformat(mediarecorder.outputformat.mpeg_4);
      //设置音频的格式为aac
      mmediarecorder.setaudioencoder(mediarecorder.audioencoder.aac);
      //准备录音
      mmediarecorder.prepare();
      //开始
      mmediarecorder.start();
      //准备结束
      isprepared = true;
      if (mlistener != null) {
        mlistener.wellprepared();
      }
    } catch (exception e) {
      e.printstacktrace();
    }
  }
 
  /**
   * 随机生成文件的名称
   */
  private string generatefilename() {
    return uuid.randomuuid().tostring() + ".m4a";
  }
 
  public int getvoicelevel(int maxlevel) {
    if (isprepared) {
      try {
        //获得最大的振幅getmaxamplitude() 1-32767
        return maxlevel * mmediarecorder.getmaxamplitude() / 32768 + 1;
      } catch (exception e) {
      }
    }
    return 1;
  }
 
  /**
   * 释放资源
   */
  public void release() {
    if (mmediarecorder != null) {
      mmediarecorder.stop();
      mmediarecorder.release();
      mmediarecorder = null;
    }
  }
 
  public void cancel() {
    release();
    if (mcurrentfilepath != null) {
      file file = new file(mcurrentfilepath);
      fileutils.deletefile(file);
      mcurrentfilepath = null;
    }
  }
 
  public string getcurrentfilepath() {
    return mcurrentfilepath;
  }
}

3、自定义录音按钮audiorecorderbutton

/**
 * 功能:录音按钮
 */
public class audiorecorderbutton extends appcompatbutton {
  private context mcontext;
  //取消录音y轴位移
  private static final int distance_y_cancel = 80;
  //录音最大时长限制
  private static final int audio_recorder_max_time = 15;
  //录音倒计时时间
  private static final int audio_recorder_count_down = 7;
  //状态
  private static final int state_normal = 1;// 默认的状态
  private static final int state_recording = 2;// 正在录音
  private static final int state_want_to_cancel = 3;// 希望取消
  //当前的状态
  private int mcurrentstate = state_normal;
  //已经开始录音
  private boolean isrecording = false;
  //是否触发onlongclick
  private boolean mready;
 
  private dialogmanager mdialogmanager;
  private audiomanager maudiomanager;
  private android.media.audiomanager audiomanager;
 
  public audiorecorderbutton(context context) {
    this(context, null);
  }
 
  public audiorecorderbutton(context context, attributeset attrs) {
    super(context, attrs);
    this.mcontext = context;
    mdialogmanager = new dialogmanager(context);
    audiomanager = (android.media.audiomanager) context.getsystemservice(context.audio_service);
 
    string dir = sdutils.getcustomfolder("audios");//创建文件夹
    maudiomanager = audiomanager.getinstance(dir);
    maudiomanager.setonaudiostatelistener(new audiomanager.audiostatelistener() {
      @override
      public void wellprepared() {
        mhandler.sendemptymessage(msg_audio_prepared);
      }
    });
    //按钮长按 准备录音 包括start
    setonlongclicklistener(new onlongclicklistener() {
      @override
      public boolean onlongclick(view v) {
        //先判断有没有录音和存储权限,有则开始录音,没有就申请权限
        int hasaudiopermission = contextcompat.checkselfpermission(mcontext, manifest.permission.record_audio);
        int hasstoragepermission = contextcompat.checkselfpermission(mcontext, manifest.permission.write_external_storage);
        if (hasaudiopermission == packagemanager.permission_granted && hasstoragepermission == packagemanager.permission_granted) {
          mready = true;
          maudiomanager.prepareaudio();
        } else {
          rxpermissions permissions = new rxpermissions((fragmentactivity) mcontext);
          disposable disposable = permissions.request(manifest.permission.record_audio, manifest.permission.write_external_storage)
              .subscribe(new consumer<boolean>() {
                @override
                public void accept(boolean granted) {
                  if (!granted) {
                    toastutils.showshort("发送语音功能需要赋予录音和存储权限");
                  }
                }
              });
        }
        return true;
      }
    });
  }
 
  private static final int msg_audio_prepared = 0x110;
  private static final int msg_voice_changed = 0x111;
  private static final int msg_dialog_dismiss = 0x112;
  private static final int msg_time_out = 0x113;
  private static final int update_time = 0x114;
 
  private boolean mthreadflag = false;
  //录音时长
  private float mtime;
 
  //获取音量大小的runnable
  private runnable mgetvoicelevelrunnable = new runnable() {
    @override
    public void run() {
      while (isrecording) {
        try {
          thread.sleep(100);
          mtime += 0.1f;
          mhandler.sendemptymessage(msg_voice_changed);
          if (mtime >= audio_recorder_max_time) {//如果时间超过60秒,自动结束录音
            while (!mthreadflag) {//记录已经结束了录音,不需要再次结束,以免出现问题
              mdialogmanager.dismissdialog();
              maudiomanager.release();
              if (audiofinishrecorderlistener != null) {
                //先回调,再reset,不然回调中的时间是0
                audiofinishrecorderlistener.onfinish(mtime, maudiomanager.getcurrentfilepath());
                mhandler.sendemptymessage(msg_time_out);
              }
              mthreadflag = !mthreadflag;
            }
            isrecording = false;
          } else if (mtime >= audio_recorder_count_down) {
            mhandler.sendemptymessage(update_time);
          }
        } catch (interruptedexception e) {
          e.printstacktrace();
        }
      }
    }
  };
 
  private handler mhandler = new handler(new handler.callback() {
    @override
    public boolean handlemessage(message msg) {
      switch (msg.what) {
        case msg_audio_prepared:
          mdialogmanager.showrecordingdialog();
          isrecording = true;
          new thread(mgetvoicelevelrunnable).start();
          break;
        case msg_voice_changed:
          mdialogmanager.updatevoicelevel(maudiomanager.getvoicelevel(7));
          break;
        case msg_dialog_dismiss:
          mdialogmanager.dismissdialog();
          break;
        case msg_time_out:
          reset();
          break;
        case update_time:
          int countdown = (int) (audio_recorder_max_time - mtime);
          mdialogmanager.updatetime(countdown);
          break;
      }
      return true;
    }
  });
 
  /**
   * 录音完成后的回调
   */
  public interface audiofinishrecorderlistener {
    /**
     * @param seconds 时长
     * @param filepath 文件
     */
    void onfinish(float seconds, string filepath);
  }
 
  private audiofinishrecorderlistener audiofinishrecorderlistener;
 
  public void setaudiofinishrecorderlistener(audiofinishrecorderlistener listener) {
    audiofinishrecorderlistener = listener;
  }
 
  android.media.audiomanager.onaudiofocuschangelistener onaudiofocuschangelistener = new android.media.audiomanager.onaudiofocuschangelistener() {
    @override
    public void onaudiofocuschange(int focuschange) {
      if (focuschange == android.media.audiomanager.audiofocus_loss) {
        audiomanager.abandonaudiofocus(onaudiofocuschangelistener);
      }
    }
  };
 
  public void myrequestaudiofocus() {
    audiomanager.requestaudiofocus(onaudiofocuschangelistener, android.media.audiomanager.stream_music, android.media.audiomanager.audiofocus_gain_transient);
  }
 
  @override
  public boolean ontouchevent(motionevent event) {
    logger.t("audiomanager").i("x :" + event.getx() + "-y:" + event.gety());
    switch (event.getaction()) {
      case motionevent.action_down:
        mthreadflag = false;
        isrecording = true;
        changestate(state_recording);
        myrequestaudiofocus();
        break;
      case motionevent.action_move:
        if (isrecording) {
          //根据想x,y的坐标,判断是否想要取消
          if (event.gety() < 0 && math.abs(event.gety()) > distance_y_cancel) {
            changestate(state_want_to_cancel);
          } else {
            changestate(state_recording);
          }
        }
        break;
      case motionevent.action_up:
        //如果longclick 没触发
        if (!mready) {
          reset();
          return super.ontouchevent(event);
        }
        //触发了onlongclick 没准备好,但是已经prepared已经start
        //所以消除文件夹
        if (!isrecording || mtime < 1.0f) {
          mdialogmanager.tooshort();
          maudiomanager.cancel();
          mhandler.sendemptymessagedelayed(msg_dialog_dismiss, 1000);
        } else if (mcurrentstate == state_recording) {//正常录制结束
          mdialogmanager.dismissdialog();
          maudiomanager.release();
          if (audiofinishrecorderlistener != null) {
            audiofinishrecorderlistener.onfinish(mtime, maudiomanager.getcurrentfilepath());
          }
        } else if (mcurrentstate == state_want_to_cancel) {
          mdialogmanager.dismissdialog();
          maudiomanager.cancel();
        }
        reset();
        audiomanager.abandonaudiofocus(onaudiofocuschangelistener);
        break;
    }
    return super.ontouchevent(event);
  }
 
  /**
   * 恢复状态 标志位
   */
  private void reset() {
    isrecording = false;
    mtime = 0;
    mready = false;
    changestate(state_normal);
  }
 
  /**
   * 改变状态
   */
  private void changestate(int state) {
    if (mcurrentstate != state) {
      mcurrentstate = state;
      switch (state) {
        case state_normal:
          settext(r.string.audio_record_button_normal);
          break;
        case state_recording:
          if (isrecording) {
            mdialogmanager.recording();
          }
          settext(r.string.audio_record_button_recording);
          break;
        case state_want_to_cancel:
          mdialogmanager.wanttocancel();
          settext(r.string.audio_record_button_cancel);
          break;
      }
    }
  }
}

4、dialogstyle

<!--app base theme-->
<style name="appthemeparent" parent="theme.appcompat.light.noactionbar">
  <!--不显示状态栏:22之前-->
  <item name="android:windownotitle">true</item>
  <item name="android:windowanimationstyle">@style/activityanimtheme</item><!--activity动画-->
  <item name="actionoverflowmenustyle">@style/menustyle</item><!--toolbar菜单样式-->
</style>
 
<!--dialog式的activity-->
<style name="activitydialogstyle" parent="appthemeparent">
  <item name="android:windowbackground">@android:color/transparent</item>
  <!-- 浮于activity之上 -->
  <item name="android:windowisfloating">true</item>
  <!-- 边框 -->
  <item name="android:windowframe">@null</item>
  <!-- dialog以外的区域模糊效果 -->
  <item name="android:backgrounddimenabled">true</item>
  <!-- 半透明 -->
  <item name="android:windowistranslucent">true</item>
  <!-- dialog进入及退出动画 -->
  <item name="android:windowanimationstyle">@style/activitydialoganimation</item>
</style>
 
<!--audio recorder dialog-->
<style name="audiorecorderdialogstyle" parent="activitydialogstyle">
  <!-- dialog以外的区域模糊效果 -->
  <item name="android:backgrounddimenabled">false</item>
</style>
 
<!-- dialog动画:渐入渐出-->
<style name="activitydialoganimation" parent="@android:style/animation.dialog">
  <item name="android:windowenteranimation">@anim/fade_in</item>
  <item name="android:windowexitanimation">@anim/fade_out</item>
</style>

5、dialoglayout

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@drawable/audio_recorder_dialog_bg"
  android:gravity="center"
  android:orientation="vertical"
  android:padding="20dp">
 
  <linearlayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
 
    <imageview
      android:id="@+id/iv_dialog_icon"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_audio_recorder" />
 
    <imageview
      android:id="@+id/iv_dialog_voice"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_audio_v1" />
 
  </linearlayout>
 
  <textview
    android:id="@+id/tv_dialog_label"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margintop="15dp"
    android:text="@string/audio_record_dialog_up_to_cancel"
    android:textcolor="@color/white"
    android:textsize="15dp" />
</linearlayout>

6、用到的字符串

<!--audiorecord-->
<string name="audio_record_button_normal">按住 说话</string>
<string name="audio_record_button_recording">松开 结束</string>
<string name="audio_record_button_cancel">松开手指 取消发送</string>
<string name="audio_record_dialog_up_to_cancel">手指上划,取消发送</string>
<string name="audio_record_dialog_release_to_cancel">松开手指,取消发送</string>
<string name="audio_record_dialog_too_short">录音时间过短</string>

7、使用:按钮的样式不需要写在自定义button中,方便使用

<com.kidney.base_library.view.audiorecorder.audiorecorderbutton
  android:id="@+id/btn_audio_recorder"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/audio_record_button_normal" />
 
 audiorecorderbutton audiorecorderbutton = findviewbyid(r.id.btn_audio_recorder);
 audiorecorderbutton.setaudiofinishrecorderlistener(new audiorecorderbutton.audiofinishrecorderlistener() {
   @override
   public void onfinish(float seconds, string filepath) {
     logger.i(seconds + "秒:" + filepath);
   }
 });

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。