js-audio-recorder和lamejs录音框架

属性:

属性描述类型
isShow是否显示录音Boolean

事件:

事件名描述返回值
closeAudio录音完毕触发录音的音频blob格式
1、安装js-audio-recorde和lamejs
npm i js-audio-recorder
npm install lamejs
2、实现代码 
<template>
  <div>
      <!-- 录音 -->
      <audio-enter ref="audioEnter" :isShow='isAudioShow' @closeAudio='closeAudio'></audio-enter>
  </div>
</template>
 
<script>
import audioEnter from "@/components/audioEnter.vue";
export default {
  data() {
    return {
      isAudioShow: true // 录音是否出现
    };
  },
  components: {
    audioEnter 
  },
  methods: {
    closeAudio(data) {
      this.isShow = false;
      // 处理其他逻辑......
    }
  }
};
</script>

audioEnter.vue源码:

<template>
    <div v-show="isShow" class="audio-enter">
      <van-overlay :show="isShow" @click="close" class-name="custom-overlay">
        <div class="audio-pie" @click.stop>
          <!-- 录音时显示音波图和时长 -->
          <div v-if="beginRecoding">
            <div class="audio-pie_audio--times" v-if="nowDuration">{{nowDuration}}</div>
            <div v-show="false" class="audio-pie_audio--osc"><canvas id="canvas"></canvas></div>
            <div v-if="drawColuList.length > 0" class="audio-pie_audio--osc">
              <div class="audio-pie_audio--osc_item" v-for="(item, idx) in drawColuList" :key="idx" :style="{height: item + 'px'}"></div>
            </div>
          </div>
  
          <div class="audio-pie_btn" @touchstart.prevent="touchstart" @mousedown="touchstart" @touchend.prevent="touchend" @mouseup="touchend" @touchmove.prevent="touchmove">
            <div class="audio-pie_btn-icon">
              <template v-if="beginRecoding">
                <div class="audio-pie_btn-icon1"></div>
                <div class="audio-pie_btn-icon2"></div>
              </template>
              <div v-else class="iconfont icon-Voice"></div>
            </div>
  
            <div class="audio-pie_txt">{{beginRecoding ? '松开发送' : '按住说话'}}</div>
          </div>
        </div>
      </van-overlay>
    </div>
  </template>
  <script>
  import { ImagePreview, Toast } from 'vant';
  import Recorder from 'js-audio-recorder'
  const lamejs = require('lamejs')
  /**
   * @param isShow 是否显示音频录入组件
   * @param closeAudio 关闭音频录入组件或录音完毕的事件
   */
  export default {
    props: {
      isShow: {
        type: Boolean,
        default: false
      }
    },
    data() {
      return {
        beginRecoding: false, // 开始录音
        blackBoxSpeak: false, // 是否上滑取消
        startY: '', // 手指滑动开始Y轴坐标
        timeOutEvent: 0, // 定时器
        waveCanvas: null,
        ctx: null,
        recorder: null, // 音频录入的实例
        drawRecordId: null, // 音频绘画波动图时的音频id
        nowDuration: null, // 当前时长
        limitDuration: 60, // 限制时长,单位-秒
        drawColuList: [] // 录音时的柱状波动图,数据集合
      }
    },
    watch: {
      'isShow': {
        handler(nv, ov) {
          if (nv) {
            this.getPermission()
          }
        },
        deep: true
      }
    },
    mounted() {
      // 创建录音实例
      this.recorder = new Recorder({
        sampleBits: 16,                 // 采样位数,支持 8 或 16,默认是16
        sampleRate: 48000,              // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值
        numChannels: 1,                 // 声道,支持 1 或 2, 默认是1
        // compiling: false,(0.x版本中生效,1.x增加中)  // 是否边录边转换,默认是false
      })
      // 监听录音变化
      const vm = this;
      this.recorder.onprogress = function(params) {
        if (Math.floor(params.duration) === this.limitDuration) {
          vm.touchend();
        }
        let d = Math.floor(params.duration);
        d = Number(d) < 10 ? '0' + d : d;
        d = '0:' + d;
        vm.$set(vm, 'nowDuration', d)
  
        // console.log('--------------START---------------')
        // console.log('录音时长(秒)', params.duration);
        // console.log('录音大小(字节)', params.fileSize);
        // console.log('录音音量百分比(%)', params.vol);
        // console.log('当前录音的总数据([DataView, DataView...])', params.data);
        // console.log('--------------END---------------')
      }
  
      //this.startCanvas();
    },
    methods: {
      // 获取麦克风权限
      getPermission(){
        Recorder.getPermission().then(() => {
          console.log('录音给权限了');
        }, (error) => {
          console.log(`${error.name} : ${error.message}`);
        });
      },
      // 波浪图canvas 配置
      startCanvas(){
        //录音波浪
        this.waveCanvas = document.getElementById('canvas');
        this.waveCanvas.width = 145;
        this.waveCanvas.height = 44;
        this.ctx = this.waveCanvas.getContext("2d");
      },
      // 手指开始触发
      touchstart(e) {
        this.blackBoxSpeak = true
        this.startY = e.clientY
  
        this.timeOutEvent = 0
        // 长按1000毫秒后执行
        this.timeOutEvent = setTimeout(()=>{
          this.startRecorder();
          this.beginRecoding = true
        },500)
        return false
      },
      // 手指离开屏幕触发
      touchend() {
        // 清空定时器
        clearTimeout(this.timeOutEvent)
        if (this.timeOutEvent !== 0) {
          this.beginRecoding = false
          this.stopRecorder() // 停止录音
          // 长按结束调用保存录音或者返回录音数据
          // if (this.blackBoxSpeak) { // 未上滑取消
            this.$emit('closeAudio', this.recorder.getWAVBlob())
          // }
          //console.log('松开>>>', this.getRecorder())
          this.nowDuration = null;
          this.drawColuList = [];
        }
      },
      // 滑动触发
      touchmove(e) {
        const endY = e.clientY
        this.blackBoxSpeak = this.startY < endY
      },
      //长按超过x毫秒-- 开始录音
      startRecorder () {
        this.recorder.start().then(() => {
          this.beginRecoding = true
          //this.drawRecordWave();//开始绘制
          this.drawRecordColu(); // 开始绘制
        }, (error) => {
          console.log(`${error.name} : ${error.message}`);
        });
      },
      // 继续录音
      resumeRecorder () {
        this.recorder.resume()
      },
      // 暂停录音
      pauseRecorder () {
        this.recorder.pause();
        this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
        this.drawRecordId = null;
      },
      // 结束录音
      stopRecorder () {
        this.recorder.stop()
        this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
        this.drawRecordId = null;
      },
      // 录音播放
      playRecorder () {
        this.recorder.play();
        //this.drawPlay();//绘制波浪图
      },
      // 暂停录音播放
      pausePlayRecorder () {
        this.recorder.pausePlay()
      },
      // 恢复录音播放
      resumePlayRecorder () {
        this.recorder.resumePlay();
        //this.drawPlay();//绘制波浪图
      },
      // 停止录音播放
      stopPlayRecorder () {
        this.recorder.stopPlay();
      },
      // 销毁录音
      destroyRecorder () {
        const vm = this;
        if (vm.recorder) {
          vm.recorder.destroy().then(function() {
            vm.recorder = null;
            vm.drawRecordId && cancelAnimationFrame(vm.drawRecordId);
            vm.drawRecordId = null;
          });
        }
      },
      // 获取录音文件
      getRecorder(){
        let map = new Map();
        map.set('duration', this.recorder.duration);//录音总时长
        map.set('fileSize', this.recorder.fileSize);//录音总大小
  
        //录音结束,获取取录音数据
        map.set('PCMBlob', this.recorder.getPCMBlob());//获取 PCM 数据
        map.set('wavBlob', this.recorder.getWAVBlob());//获取 WAV 数据
        map.set('mp3Blob', this.convertToMp3(this.recorder.getWAV()));
        map.set('channel', this.recorder.getChannelData());//获取左声道和右声道音频数据
        return map;
      },
      // 下载pcm格式
      downPCM(fileName){
        this.recorder.downloadPCM(fileName ? fileName : '新文件');//这里传参进去的时文件名
      },
      // 下载wav格式
      downWAV(fileName){
        this.recorder.downloadWAV(fileName ? fileName : '新文件');//这里传参进去的时文件名
      },
      // 文件格式转换 wav-map3
      downMP3(){
        const mp3Blob = this.convertToMp3(this.recorder.getWAV());
        this.recorder.download(mp3Blob, 'recorder', 'mp3');
      },
      // wav转换成mp3格式
      convertToMp3(wavDataView) {
        // 获取wav头信息
        const wav = lamejs.WavHeader.readHeader(wavDataView); // 此处其实可以不用去读wav头信息,毕竟有对应的config配置
        const { channels, sampleRate } = wav;
        const mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128);
        // 获取左右通道数据
        const result = this.recorder.getChannelData()
        const buffer = [];
  
        const leftData = result.left && new Int16Array(result.left.buffer, 0, result.left.byteLength / 2);
        const rightData = result.right && new Int16Array(result.right.buffer, 0, result.right.byteLength / 2);
        const remaining = leftData.length + (rightData ? rightData.length : 0);
  
        const maxSamples = 1152;
        for (let i = 0; i < remaining; i += maxSamples) {
          const left = leftData.subarray(i, i + maxSamples);
          let right = null;
          let mp3buf = null;
  
          if (channels === 2) {
            right = rightData.subarray(i, i + maxSamples);
            mp3buf = mp3enc.encodeBuffer(left, right);
          } else {
            mp3buf = mp3enc.encodeBuffer(left);
          }
  
          if (mp3buf.length > 0) {
            buffer.push(mp3buf);
          }
        }
        const enc = mp3enc.flush();
        if (enc.length > 0) {
          buffer.push(enc);
        }
        return new Blob(buffer, { type: 'audio/mp3' });
      },
      // 录音绘制波浪图
      drawRecordWave () {
        // 用requestAnimationFrame稳定60fps绘制
        this.drawRecordId = requestAnimationFrame(this.drawRecordWave);
        // 实时获取音频大小数据
        let dataArray = this.recorder.getRecordAnalyseData()
        //console.log('>>>',dataArray)
        let bufferLength = dataArray.length;
  
        // 填充背景色
        this.ctx.fillStyle = 'rgb(0,0,0)';
        this.ctx.fillRect(0, 0, this.waveCanvas.width, this.waveCanvas.height);
  
        // 设定波形绘制颜色
        this.ctx.lineWidth = 2;
        this.ctx.strokeStyle = 'rgb(109, 212, 0)';
  
        this.ctx.beginPath();
        let sliceWidth = this.waveCanvas.width * 1.0 / bufferLength; // 一个点占多少位置,共有bufferLength个点要绘制
        let x = 0;         // 绘制点的x轴位置
        for (var i = 0; i < bufferLength; i++) {
          var v = dataArray[i] / 128.0;
          var y = v * this.waveCanvas.height / 2;
          if (i === 0) {
            // 第一个点
            this.ctx.moveTo(x, y);
          } else {
            // 剩余的点
            this.ctx.lineTo(x, y);
          }
          // 依次平移,绘制所有点
          x += sliceWidth;
        }
  
        this.ctx.lineTo(this.waveCanvas.width, this.waveCanvas.height / 2);
        this.ctx.stroke();
      },
      // 录音绘制柱状图
      drawRecordColu() {
        // 用requestAnimationFrame稳定60fps绘制(官方写法,录音过程中,会一直自我调用,因此能得到持续的数据,然后根据数据绘制音波图)
        this.drawRecordId = requestAnimationFrame(this.drawRecordColu);
        // 实时获取音频大小数据
        let dataArray = this.recorder.getRecordAnalyseData()
        let transit = [];
        this.splitArr([...dataArray], transit)
  
        let rstArr = [];
        for (let i = 0; i < transit.length; i++) {
          rstArr.push(Math.max(...transit[i]))
        }
        this.drawColuList = [];
        for (var i = 0; i < rstArr.length; i++) {
          // var v = rstArr[i] / 128.0;
          // var h = v * this.waveCanvas.height / 3;
          // this.drawColuList.push(h)
          // 根据数值大小,设置音波柱状的高度
          let newDb = rstArr[i];
          let waveH = 10;
          if (newDb >= 128 && newDb <= 140) {
            waveH = 15;
          } else if (newDb >= 141 && newDb <= 160) {
            waveH = 20
          } else if (newDb >= 161 && newDb <= 180) {
            waveH = 25;
          } else if (newDb >= 181 && newDb <= 200) {
            waveH = 30;
          } else if (newDb > 200) {
            waveH = 35;
          }
          this.drawColuList.push(waveH)
        }
        // console.log(this.drawColuList)
        this.$forceUpdate();
      },
      // 拆分数组
      splitArr(arr, rst, idx) {
        if (!arr || arr.length === 0) {
          return;
        }
        rst.push(arr.splice(0, idx ? idx : 32))
        this.splitArr(arr, rst)
      },
      // 取数组平均数
      arrAverageNum2(arr) {
        let sum = eval(arr.join("+"));
        return ~~(sum / arr.length * 100) / 100;
      },
      // 关闭组件
      close() {
        this.$emit('closeAudio')
      },
      // 销毁实例
      beforeDestroy() {
        this.destroyRecorder();
      }
    }
  }
  </script>
  <style scoped lang="less">
  .audio-enter {
    position: fixed;
    left: 0;
    top: 0;
    height: 100%;
    width: 100%;
    z-index: 999999999;
    background-color: #FFFFFF;
  
    .custom-overlay {
      // background-color: rgba(0, 0, 0, 0.8);
    }
  
    .audio-pie {
      z-index: 999;
      position: absolute;
      left: 0;
      right: 0;
      bottom: 74px;
      text-align: center;
  
      .audio-pie_txt {
        height: 20px;
        font-size: 14px;
        font-family: PingFangSC-Regular, PingFang SC;
        font-weight: 400;
        color: #FFFFFF;
        line-height: 20px;
        margin-top: 20px;
      }
  
      .audio-pie_audio--times {
        height: 20px;
        font-size: 14px;
        font-family: PingFangSC-Regular, PingFang SC;
        font-weight: 400;
        color: #FFFFFF;
        line-height: 20px;
      }
      .audio-pie_audio--osc {
        width: 157px;
        height: 44px;
  //      background: #6DD400;
        margin: 24px auto 0;
        display: flex;align-items: center;
  
        .audio-pie_audio--osc_item {
          width: 2px;
          background-color: rgba(109, 212, 0, 1);
          border-radius: 1000px;
  
          &:not(:first-child) {
            margin-left: 3px;
          }
        }
      }
      .audio-pie_btn {
        margin-top: 60px;
  
        .audio-pie_btn-icon {
          position: relative;
          width: 60px;
          height: 60px;
          background: #6DD400;
          border-radius: 100%;
          margin: 0 auto;
          cursor: pointer;
  
          .audio-pie_btn-icon1 {
            margin: 0 auto;
            width: 36px;
            height: 36px;
            border: 4px solid #fff;
            border-radius: 100%;
          }
          .audio-pie_btn-icon2 {
            margin: 0 auto;
            width: 16px;
            height: 16px;
            background-color:  #fff;
            border-radius: 100%;
          }
          
          .audio-pie_btn-icon1, .audio-pie_btn-icon2 {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
          }
  
          .iconfont {
            color: #FFFFFF;font-size: 35px;line-height: 60px
          }
        }
      }
    }
  }
  </style>
  
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
其实作为H5想要做语音识别,自认为还说有各种弊端得,同时还是微信公众号里面,如果小程序得话,或许会简单一点,但是这里是在公众号里面开发,在这个过程中查阅个各种资料,其实里面得东西都大同小异,但是大多数并不全, 首先微信公众号里面得H5开发语音录入,试过各种方法,由于是java渣渣,页面技术只是了解简单得,因此在使用标签得时候,本身并不能适用于当前得需求,因此最后只能选择了微信自带得录音功能,使用微信提供的接口,(具体得接口使用还是去看微信接口得使用) 由于使用微信得接口得话,首先要上传到它得服务器上面去(虽然也有本地得文件储存id,但是好像并没有用),这里就涉及到了一个serverid,这个值是存在于微信临时素材库得id,后期要用它来获取录音得文件。 通过微信提供得获取临时素材文件得接口,得到了文件(具体方法网上一搜一大堆,后期我也会将完整得代码放进资源里面),但是这里有一个坑得问题,他的格式为.amr得格式,但是最后的目的是转换为文字,因此这里涉及到了格式转换 主要用了现成得技术,也是通过下载资源获取得jar以及实现得方法,将.amr转换为了MP3格式 然后就就是最后一部了,将mp3得音频文件转换为文字,我这里用的是讯飞得技术,当然百度得也可以, 其实整个需求得这个流程已经完成了,但是不得不说里面遇到得坑,由于是渣渣,除了人们总说的,转换为mp3得时候会报N/A得错误,我还遇到了unkowFormat这个错误。 这里只是记录一下自己在查询资料时没有一个符合自己需求得资料,同时也希望以后有这样需求得人,可以将思路捋的更加清楚,也可以进行交流.
js-audio-recorder 是一个使用 JavaScript 实现的录音库,它提供了一个 `exportWAV` 方法可以将录音数据转换为 WAV 格式的音频文件。如果需要截取录音数据,可以在 `exportWAV` 方法中进行处理。 下面是一个简单的示例代码: ```javascript // 创建录音对象 var recorder = new Recorder({ sampleBits: 16, // 采样位数 sampleRate: 44100 // 采样率 }); // 开始录音 recorder.start(); // 停止录音 recorder.stop(); // 导出录音数据 recorder.exportWAV(function(blob) { var reader = new FileReader(); reader.onload = function() { var data = new DataView(this.result); var offset = 44; // WAV 文件头偏移量 var length = data.byteLength - offset; // 音频数据长度 var start = offset + 100; // 截取开始位置 var end = offset + 1000; // 截取结束位置 var buffer = new ArrayBuffer(length); var view = new DataView(buffer); for (var i = 0; i < length; i++) { view.setInt8(i, data.getInt8(i + offset)); } var slicedBuffer = buffer.slice(start, end); // 截取录音数据 var slicedBlob = new Blob([slicedBuffer], { type: 'audio/wav' }); var url = URL.createObjectURL(slicedBlob); // 播放截取后的录音数据 var audio = new Audio(url); audio.play(); }; reader.readAsArrayBuffer(blob); }); ``` 上述代码中,我们首先通过 `Recorder` 构造函数创建一个录音对象,然后调用 `start` 方法开始录音,`stop` 方法停止录音。接着,通过 `exportWAV` 方法导出录音数据,将录音数据转换为 DataView 对象,并根据 WAV 文件格式的头部信息计算出录音数据的偏移量和长度。接下来,我们可以通过指定开始和结束位置,使用 ArrayBuffer 的 `slice` 方法截取录音数据,并创建一个 Blob 对象,最后通过 URL.createObjectURL 方法生成一个 URL,用于播放截取后的录音数据。 需要注意的是,WAV 文件格式的头部信息通常占用了 44 字节,所以在截取录音数据时需要将偏移量设置为 44。另外,截取录音数据的开始位置和结束位置需要根据实际需求进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值