热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

android语音即时通讯之录音、播放功能实现代码

这篇文章主要为大家详细介绍了android语音即时通讯之录音、播放功能的实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

在android中,实现录音与语音播放的功能算是比较简单的,但是作为参考,还是很有必要将语音相关的知识做一个简要的记录。

首先,在android中,支持录音支持两种方式。主要包括:字节流模式和文件流模式。用文件流模式进行录音操作比较简单,而且相对来说,因为其封装性比较好,录制下的文件也会比较小。但是相对于文件流模式,就没有字节流模式那么灵活,但是想要用好字节流模式还是需要下一点功夫的。

下面开始介绍文件流模式的语音操作:

文件流模式

我们来看录音部分的实现,首先我们实现开始录音的部分:

在正式编码之前,还是需要对其进行一个简要的说明。一般来说,录音功能的实现是在jin层,而在这一层中,是用单线程实现的。如果我们在编码的对录音api进行多线程操作,会导致程序直接闪退,并且我们是无法在java层对其异常进行捕获的。所以,我们必须使用单线程以保证录音的正常运行。

一般来说,开始录音的步骤也就三个,代码如下:

releaseRecord();//释放可能没释放的录音相关资源
if (!doStartRecord()) {//真正的开始录音的函数,开始录音成功返回true,否则返回false
  recordFail(); //开始失败,向用户提示开始录音失败
}

接下来我们来看看上述三个方法的实现:
实现是释放相关资源的方法releaseRecord:

 if (mMediaRecorder != null) {
      mMediaRecorder.release();
      mMediaRecorder = null;
}

其中的mMediaRecorder 的MediaRecorder的全局变量。
接下来是真正实现开始录音的实现逻辑doStartRecord()

 private boolean doStartRecord() {
    try {
      mMediaRecorder = new MediaRecorder();

      mAudioFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice/"
          + System.currentTimeMillis() + ".m4a");
      mAudioFile.getParentFile().mkdirs();
      mAudioFile.createNewFile();

      //设置从麦克风采集声音
      mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);

      //保存文件为mp4的格式
      mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

      //设置所有android系统都支持的采样频率
      mMediaRecorder.setAudioSamplingRate(44100);

      //设置acc的编码方式
      mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

      //设置比较好的音质
      mMediaRecorder.setAudioEncodingBitRate(96000);

      mMediaRecorder.setOutputFile(mAudioFile.getAbsolutePath());

      mMediaRecorder.prepare();
      mMediaRecorder.start();

      mStartRecordTime = System.currentTimeMillis();

    } catch (IOException | RuntimeException e) {
      e.printStackTrace();
      return false;
    }finally {
      if(mAudioRecord != null){
        mAudioRecord.release();
      }
    }

    return true;
  }

这一部分代码比较多,但是关键部分我都给出了注释,相信理解起来也不算难吧。这一部分我们实现的主要是在sdcrad根目录下新建一个voice的目录,然后在新建一个以==.m4a==为后缀名的文件。在配置mAudioRecord的相关参数后,将收集到的录音存放到之前的文件中。如果一切都顺利的话,就返回true ,表示开始录音成功。

最后就是提示用户录音实现的逻辑recordFail

mAudioFile = null;
    mHandler.postDelayed(new Runnable() {
      @Override
      public void run() {
        Toast.makeText(VioceActivity.this, "录音失败", Toast.LENGTH_SHORT).show();
      }
    }, 100);

这里的逻辑比较简单,但是值得注意的是,因为我们开始录音方法是在一个非主线程的线程中执行的,所以我们需要借助hander来实现界面提示的效果。这里的mHander是一个局部变量,其初始化放在OnCreate()方法中。

mHandler = new Handler(Looper.getMainLooper());

综上,开始录音的所有逻辑已经全部实现。接下来实现的是结束录音的实现逻辑:
主题的逻辑如下:

if (!doStopRecord()) {//实现 停止录音的真正逻辑,成功返回true,否则返回false
          recordFail();//提示用户录音失败
        }

这里的doStopRecord实现逻辑如下:

 mMediaRecorder.stop();
      mEndRecordTime = System.currentTimeMillis();

      final int seOnd= (int) ((mEndRecordTime - mStartRecordTime) / 1000);

      if (seond <3) {
        recordFail();
        return false;
      } else {
        mHandler.post(new Runnable() {
          @Override
          public void run() {
            mText.setText("录音" + seond + "成功!");
          }
        });
      }
    } catch (RuntimeException e) {
      e.printStackTrace();
      return false;
    }
    return true;

其实我们实现停止录音的逻辑也很简单,首先调用mMediaRecorder.stop();停止录音,然后对录音时间是否大于3s进行判断,若大于3s,则表示录音有效,提示用户,录音成功。

综上,我们文件流的录音的所有代码已经实现完毕。接下来我们实现对其进行播放。如果需要参考全部的代码,请戳这里。

private void doPlay(File mAudioFile) {
    //配置播放器 MediaPlayer
    mMediaPlayer = new MediaPlayer();

    try{

      //设置声音文件
      mMediaPlayer.setDataSource(mAudioFile.getAbsolutePath());

      //设置监听回调
      mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {
          stopPlay();
        }
      });

      //设置出错的监听器
      mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
        @Override
        public boolean onError(MediaPlayer mp, int what, int extra) {
          playFail();
          //提示用户
          stopPlay();
          //释放播放器
          return true;
        }
      });

      //配置音量,是否循环
      mMediaPlayer.setVolume(1,1);
      mMediaPlayer.setLooping(false);

      mMediaPlayer.prepare();
      mMediaPlayer.start();
    }catch (Exception e){
      e.printStackTrace();
      playFail();
      stopPlay();
    }
  }

整体来说,基于文件的录音是比较容易实现的。下面介绍如何通过字节流模式实现录音。

字节流模式录音

开始录音:主要逻辑startRecord2()的实现

private boolean startRecord2() {
    try {
      mAudioFile2 = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice/"
          + System.currentTimeMillis() + ".pcm");
      mAudioFile2.getParentFile().mkdirs();
      mAudioFile2.createNewFile();

      mFileOutputStream = new FileOutputStream(mAudioFile2);
      //配置AudioRecord

      //从麦克风采集数据
      int audioSource = MediaRecorder.AudioSource.MIC;

      //采集频率
      int sampleRate = 44100;

      //单声道输入
      int channelCOnfig= AudioFormat.CHANNEL_IN_MONO;

      //设置pcm(脉冲编码调制 Pulse Code Modulation)编码格式
      int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

      //计算AudioRecord 内存buffer最小的大小
      int minBufferSize = AudioRecord.getMinBufferSize(sampleRate,channelConfig,audioFormat);
      //创建AudioRecord对象
      mAudioRecord = new AudioRecord(audioSource,sampleRate,channelConfig,audioFormat,Math.max(BUFFER_SIZE,minBufferSize));

      mAudioRecord.startRecording();
      mStartRecordTime = System.currentTimeMillis();

      //循环读取数据,写到输出流中
      while(mIsRecord){
        int read = mAudioRecord.read(mBuffer,0,BUFFER_SIZE);
        if(read >0 ){
          //读取文件写到文件中
          mFileOutputStream.write(mBuffer,0,read);
        }else{
          return false;
        }
      }

    } catch (IOException | RuntimeException e) {
      e.printStackTrace();
      return false;
    }

    //停止录音
    return true;
  }

停止录音的doStopRecord()实现:

private boolean doStopRecord() {
    try {

      mMediaRecorder.stop();
      mEndRecordTime = System.currentTimeMillis();

      final int seOnd= (int) ((mEndRecordTime - mStartRecordTime) / 1000);

      if (seond <3) {
        recordFail();
        return false;
      } else {
        mHandler.post(new Runnable() {
          @Override
          public void run() {
            mText.setText("录音" + seond + "成功!");
          }
        });
      }
    } catch (RuntimeException e) {
      e.printStackTrace();
      return false;
    }
    return true;
  }

对其中相关参数的说名:

 private boolean mIsRecord = false;
  private final int BUFFER_SIZE = 2048;//缓存区的大小
  private byte[] mBuffer;
  private FileOutputStream mFileOutputStream;
  private AudioRecord mAudioRecord;
  private File mAudioFile2;

接下来,实现的是对其字节流模式录制的文件进行播放:doPlay2()主题类的实现:

private void doPlay2(File mAudioFile) {

    //声音类型,扬声器播放
    int steamType = AudioManager.STREAM_MUSIC;

    //采样频率
    int sampleRate = 44100;

    //MONO 表示单声道 录音输入单声道 播放也使用单声道
    int channelCOnfig= AudioFormat.CHANNEL_OUT_MONO;

    //录音使用16bit 所以播放也使用同样的格式
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

    //流模式
    int mode = AudioTrack.MODE_STREAM;

    //计算需要最小buffer的大小
    int minBufferSize =AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);

    AudioTrack audioTrack = new AudioTrack(steamType,sampleRate,channelConfig,audioFormat,
        Math.max(minBufferSize,BUFFER_SIZE),mode);

    //从文件流中读取数据
    FileInputStream inputStream = null;

    try{
      inputStream = new FileInputStream(mAudioFile2);
      int read;
      //循环读取数据,写到播放器去播放
      audioTrack.play();
      while((read = inputStream.read(mBuffer)) > 0){
        int ret = audioTrack.write(mBuffer,0,read);
      switch (ret){
        case AudioTrack.ERROR:
        case AudioTrack.ERROR_BAD_VALUE:
        case AudioTrack.ERROR_INVALID_OPERATION:
        case AudioTrack.ERROR_DEAD_OBJECT:
            playFail();
            return ;
        default:
          break;
      }
      }

    }catch (Exception e){
      e.printStackTrace();
    }finally {
      mIsPlaying = false;

      if(inputStream != null)
        closeQuietly(inputStream);
      resetQuietly(audioTrack);
    }

  }

千言万语肯定不如直接代码来的直接了当,所以的代码实现点这里

实现的效果如下:

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


推荐阅读
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • 本文介绍如何通过Windows批处理脚本定期检查并重启Java应用程序,确保其持续稳定运行。脚本每30分钟检查一次,并在需要时重启Java程序。同时,它会将任务结果发送到Redis。 ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • 本文介绍如何通过SQL查询从JDE(JD Edwards)系统中提取所有字典数据,涵盖关键表的关联和字段选择。具体包括F0004和F0005系列表的数据提取方法。 ... [详细]
  • 本文详细介绍了如何通过命令行启动MySQL服务,包括打开命令提示符窗口、进入MySQL的bin目录、输入正确的连接命令以及注意事项。文中还提供了更多相关命令的资源链接。 ... [详细]
  • 本文介绍如何使用 NSTimer 实现倒计时功能,详细讲解了初始化方法、参数配置以及具体实现步骤。通过示例代码展示如何创建和管理定时器,确保在指定时间间隔内执行特定任务。 ... [详细]
  • 本文介绍了在Windows环境下使用pydoc工具的方法,并详细解释了如何通过命令行和浏览器查看Python内置函数的文档。此外,还提供了关于raw_input和open函数的具体用法和功能说明。 ... [详细]
  • 高效解决应用崩溃问题!友盟新版错误分析工具全面升级
    友盟推出的最新版错误分析工具,专为移动开发者设计,提供强大的Crash收集与分析功能。该工具能够实时监控App运行状态,快速发现并修复错误,显著提升应用的稳定性和用户体验。 ... [详细]
  • 并发编程:深入理解设计原理与优化
    本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ... [详细]
  • 如何高效创建和使用字体图标
    在Web和移动开发中,为什么选择字体图标?主要原因是其卓越的性能,可以显著减少HTTP请求并优化页面加载速度。本文详细介绍了从设计到应用的字体图标制作流程,并提供了专业建议。 ... [详细]
  • 本文将详细介绍在Windows 7环境下,检查U盘启动盘是否制作成功的多种方法,包括通过BIOS设置和使用模拟启动工具。 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 本文探讨了 C++ 中普通数组和标准库类型 vector 的初始化方法。普通数组具有固定长度,而 vector 是一种可扩展的容器,允许动态调整大小。文章详细介绍了不同初始化方式及其应用场景,并提供了代码示例以加深理解。 ... [详细]
  • 网络运维工程师负责确保企业IT基础设施的稳定运行,保障业务连续性和数据安全。他们需要具备多种技能,包括搭建和维护网络环境、监控系统性能、处理突发事件等。本文将探讨网络运维工程师的职业前景及其平均薪酬水平。 ... [详细]
  • 本题涉及一棵由N个节点组成的树(共有N-1条边),初始时所有节点均为白色。题目要求处理两种操作:一是改变某个节点的颜色(从白变黑或从黑变白);二是查询从根节点到指定节点路径上的第一个黑色节点,若无则输出-1。 ... [详细]
author-avatar
宗小胖
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有