当前位置: 代码网 > it编程>前端脚本>Vue.js > vue对接讯飞语音识别,websocket语音识别连续流式输出

vue对接讯飞语音识别,websocket语音识别连续流式输出

2024年07月31日 Vue.js 我要评论
【代码】vue对接讯飞语音识别,websocket语音识别连续流式输出。
  1. 首先安装依赖
    npm install recorder-core
    npm install crypto-js
  2. record.js文件(流式输出关键文件)(参考博客:vue使用websocket语音识别连续流式输出_recorder-core-csdn博客
    //recorder
    import recorder from 'recorder-core'
    // type
    import 'recorder-core/src/engine/pcm.js'
    //可选的扩展支持项
    import 'recorder-core/src/extensions/wavesurfer.view.js'
     
    export const jsrecorder = (cb) => {
      var testsamplerate = 16000
      var testbitrate = 16
      var sendframesize = 1280
      //  去掉console
      recorder.clog = () => {}
     
      //重置环境,每次开始录音时必须先调用此方法,清理环境
      var realtimesendtryreset = function () {
        realtimesendtrychunks = null
      }
     
      var realtimesendtrynumber
      var transferuploadnumbermax
      var realtimesendtrychunk
      var realtimesendtrychunks
     
      //=====实时处理核心函数==========
      var realtimesendtry = function (buffers, buffersamplerate, isclose) {
        if (realtimesendtrychunks == null) {
          realtimesendtrynumber = 0
          transferuploadnumbermax = 0
          realtimesendtrychunk = null
          realtimesendtrychunks = []
        }
        //配置有效性检查
        if (testbitrate == 16 && sendframesize % 2 == 1) {
          return
        }
     
        var pcm = [],
          pcmsamplerate = 0
        if (buffers.length > 0) {
          //借用sampledata函数进行数据的连续处理,采样率转换是顺带的,得到新的pcm数据
          var chunk = recorder.sampledata(
            buffers,
            buffersamplerate,
            testsamplerate,
            realtimesendtrychunk
          )
     
          //清理已处理完的缓冲数据,释放内存以支持长时间录音,最后完成录音时不能调用stop,因为数据已经被清掉了
          for (
            var i = realtimesendtrychunk ? realtimesendtrychunk.index : 0;
            i < chunk.index - 3;
            i++
          ) {
            buffers[i] = null
          }
          realtimesendtrychunk = chunk //此时的chunk.data就是原始的音频16位pcm数据(小端le),直接保存即为16位pcm文件、加个wav头即为wav文件、丢给mp3编码器转一下码即为mp3文件
     
          pcm = chunk.data
          pcmsamplerate = chunk.samplerate
          if (pcmsamplerate != testsamplerate)
            //除非是onprocess给的buffersamplerate低于testsamplerate
            throw new error(
              '不应该出现pcm采样率' +
                pcmsamplerate +
                '和需要的采样率' +
                testsamplerate +
                '不一致'
            )
        }
     
        //将pcm数据丢进缓冲,凑够一帧发送,缓冲内的数据可能有多帧,循环切分发送
        if (pcm.length > 0) {
          realtimesendtrychunks.push({ pcm: pcm, pcmsamplerate: pcmsamplerate })
        }
     
        //从缓冲中切出一帧数据
        var chunksize = sendframesize / (testbitrate / 8) //8位时需要的采样数和帧大小一致,16位时采样数为帧大小的一半
        pcm = new int16array(chunksize)
        pcmsamplerate = 0
        var pcmok = false,
          pcmlen = 0
        for1: for (var i1 = 0; i1 < realtimesendtrychunks.length; i1++) {
          chunk = realtimesendtrychunks[i1]
          pcmsamplerate = chunk.pcmsamplerate
     
          for (var i2 = chunk.offset || 0; i2 < chunk.pcm.length; i2++) {
            pcm[pcmlen] = chunk.pcm[i2]
            pcmlen++
     
            //满一帧了,清除已消费掉的缓冲
            if (pcmlen == chunksize) {
              pcmok = true
              chunk.offset = i2 + 1
              for (var i3 = 0; i3 < i1; i3++) {
                realtimesendtrychunks.splice(0, 1)
              }
              break for1
            }
          }
        }
     
        //缓冲的数据不够一帧时,不发送 或者 是结束了
        if (!pcmok) {
          if (isclose) {
            var number = ++realtimesendtrynumber
            transferupload(number, null, 0, null, isclose)
          }
          return
        }
     
        //16位pcm格式可以不经过mock转码,直接发送new blob([pcm.buffer],{type:"audio/pcm"}) 但8位的就必须转码,通用起见,均转码处理,pcm转码速度极快
        number = ++realtimesendtrynumber
        var encstarttime = date.now()
        var recmock = recorder({
          type: 'pcm',
          samplerate: testsamplerate, //需要转换成的采样率
          bitrate: testbitrate //需要转换成的比特率
        })
        recmock.mock(pcm, pcmsamplerate)
        recmock.stop(
          function (blob, duration) {
            blob.enctime = date.now() - encstarttime
     
            //转码好就推入传输
            transferupload(number, blob, duration, recmock, false)
     
            //循环调用,继续切分缓冲中的数据帧,直到不够一帧
            realtimesendtry([], 0, isclose)
          },
          function (msg) {
            //转码错误?没想到什么时候会产生错误!
          }
        )
      }
     
      //=====数据传输函数==========
      var transferupload = function (
        number,
        blobornull,
        duration,
        blobrec,
        isclose
      ) {
        // console.log(number, blobornull, duration, blobrec, isclose)
        transferuploadnumbermax = math.max(transferuploadnumbermax, number)
        if (blobornull) {
          var blob = blobornull
          var enctime = blob.enctime
          cb(blob, enctime)
        }
     
        if (isclose) {
          // console.log('isclose')
        }
      }
     
      //调用录音
      var rec
      var wave
      function recstart() {
        if (rec) {
          rec.close()
        }
        rec = recorder({
          type: 'unknown',
          onprocess: function (
            buffers,
            powerlevel,
            bufferduration,
            buffersamplerate
          ) {
            //推入实时处理,因为是unknown格式,buffers和rec.buffers是完全相同的,只需清理buffers就能释放内存。
            realtimesendtry(buffers, buffersamplerate, false)
            //可视化图形绘制
            // wave.input(buffers[buffers.length - 1], powerlevel, buffersamplerate);
          }
        })
     
        // var t = settimeout(function () {
        //   console.log('无法录音:权限请求被忽略(超时假装手动点击了确认对话框)', 1)
        // }, 8000)
     
        rec.open(function () {
          //打开麦克风授权获得相关资源
          rec.start() //开始录音
          realtimesendtryreset() //重置环境,开始录音时必须调用一次
     
          //此处创建这些音频可视化图形绘制浏览器支持妥妥的
          // wave = recorder.wavesurferview({
          //   elem: ".speak-wave",
          //   height: 30 //显示宽度
          //   , scale: 2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
          //   , fps: 50 //绘制帧率,不可过高,50-60fps运动性质动画明显会流畅舒适,实际显示帧率达不到这个值也并无太大影响
          //   , duration: 2500 //当前视图窗口内最大绘制的波形的持续时间,此处决定了移动速率
          //   , direction: 1 //波形前进方向,取值:1由左往右,-1由右往左
          //   , position: 0 //绘制位置,取值-1到1,-1为最底下,0为中间,1为最顶上,小数为百分比
          //   , centerheight: 1 //中线基础粗细,如果为0不绘制中线,position=±1时应当设为0
          //   //波形颜色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
          //   , linear: [0, "rgba(0,187,17,1)", 0.7, "rgba(255,215,0,1)", 1, "rgba(255,102,0,1)"]
          //   , centercolor: "" //中线css颜色,留空取波形第一个渐变颜色 });
          // }, function (msg, isusernotallow) {
          //   cleartimeout(t);
          //   console.log((isusernotallow ? "usernotallow," : "") + "无法录音:" + msg, 1);
          // });
        })
      }
      // close
      function recstop() {
        rec && rec.close() //直接close掉即可,这个例子不需要获得最终的音频文件
        realtimesendtry([], 0, true) //最后一次发送
      }
     
      return {
        recstart: recstart,
        recstop: recstop
      }
    }
  3. index.js,讯飞websocket url获取,结果处理等
    import cryptojs from 'crypto-js/crypto-js'
    
    let resulttext = "";
    let resulttexttemp = "";
    
    /**
     * 获取websocket url
     * 该接口需要后端提供,这里为了方便前端处理
     */
    export function getwebsocketurl() {
      // 请求地址根据语种不同变化
      var url = "wss://iat-api.xfyun.cn/v2/iat";
      var host = "iat-api.xfyun.cn";
      var apikey = "xfyunapikey";
      var apisecret = "xfyunapisecret";
      var date = new date().togmtstring();
      var algorithm = "hmac-sha256";
      var headers = "host date request-line";
      var signatureorigin = `host: ${host}\ndate: ${date}\nget /v2/iat http/1.1`;
      var signaturesha = cryptojs.hmacsha256(signatureorigin, apisecret);
      var signature = cryptojs.enc.base64.stringify(signaturesha);
      var authorizationorigin = `api_key="${apikey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
      var authorization = btoa(authorizationorigin);
      url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
      return url;
    }
    
    export function tobase64(buffer) {
      var binary = "";
      var bytes = new uint8array(buffer);
      var len = bytes.bytelength;
      for (var i = 0; i < len; i++) {
        binary += string.fromcharcode(bytes[i]);
      }
      return window.btoa(binary);
    }
    
    export function renderresult(resultdata) {
      // 识别结束
      let jsondata = json.parse(resultdata);
      if (jsondata.data && jsondata.data.result) {
        let data = jsondata.data.result;
        let str = "";
        let ws = data.ws;
        for (let i = 0; i < ws.length; i++) {
          str = str + ws[i].cw[0].w;
        }
        // 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
        // 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
        if (data.pgs) {
          if (data.pgs === "apd") {
            // 将resulttexttemp同步给resulttext
            resulttext = resulttexttemp;
          }
          // 将结果存储在resulttexttemp中
          resulttexttemp = resulttext + str;
        } else {
          resulttext = resulttext + str;
        }
        return resulttexttemp || resulttext || ""
      }
    }
    
    export function resetresulttext(){
      resulttext = "";
      resulttexttemp = "";
    }
  4. vue文件
    <template>
        <div class="sound">
            <textarea v-model="text" name="" id="" cols="30" rows="10" placeholder="点击按钮开始说话,您的语音将会被转为文本,请允许浏览器获取麦克风权限"></textarea>
            <div class="btn"> 
                <button @click="startspeechrecognition()">开始识别</button>
            <button @click="closewebsocket()">停止识别</button>
            </div>
        </div>
    </template>
    
    <script>
    import {getwebsocketurl, renderresult, resetresulttext, tobase64} from "@/assets/speech";
    import { jsrecorder } from "@/assets/speech/record.js"; //引入录音文件
    
    export default {
      name: "home",
      data() {
        return {
          isrecording: false,
          websocket: null,//websocket定义
          recstart:null,//开始录音
          recstop:null,//结束录音
        }
      },
      methods: {
        startspeechrecognition() {
          resetresulttext()
          // 判断麦克风是否打开
          navigator.mediadevices.getusermedia({ audio: true }).then(stream => {
            //调用websocket方法
            // 如果需要加一些图片验证或者其他验证在这里加一些逻辑,成功之后调用websocket
            this.isrecording=true
            // 调用录音方法
            this.recstart()
            this.asrwebsocket()
          }).catch(error => {
            this.$message.error('麦克风未打开!');
            // 麦克风未打开
            switch (error.message || error.name) {
              case 'permission_denied':
              case 'permissiondeniederror':
                console.info('用户拒绝提供信息。')
                break
              case 'not_supported_error':
              case 'notsupportederror':
                console.info('浏览器不支持硬件设备。')
                break
              case 'mandatory_unsatisfied_error':
              case 'mandatoryunsatisfiederror':
                console.info('无法发现指定的硬件设备。')
                break
              default:
                console.info('无法打开麦克风。异常信息:' + (error.code || error.name))
                break
            }
          });
        },
        asrwebsocket(){
          //使用后端提供的地址
          let websocketurl = getwebsocketurl()
          //new 一个websocket
          this.websocket = new websocket(websocketurl)
          console.log(this.websocket,'new websocket')
          this.websocket.onopen = () => {
            console.log('连接成功')
            //链接成功之后发送websocket请求参数
            this.sendstart()
          }
          // 客户端接收服务端返回的数据
          this.websocket.onmessage = (evt) => {
            this.seach = renderresult(evt.data)
            console.log("返回识别结果吗",this.seach)
          }
          this.websocket.onerror = (evt) => {
            console.error('websocket-asr错误:', evt)
          }
          // 关闭连接
          this.websocket.onclose = (evt) => {
            console.log('websocket-asr关闭:', evt)
          }
        },
        // 请求参数 =====看后端提供的参数信息========
        sendstart(){
          var params = {
            common: {
              app_id: "xfyunappid",
            },
            business: {
              language: "zh_cn",
              domain: "iat",
              accent: "mandarin",
              vad_eos: 5000,
              dwa: "wpgs",
            },
            data: {
              status: 0,
              format: "audio/l16;rate=16000",
              encoding: "raw",
            },
          };
          this.websocket.send(json.stringify(params))
        },
        // 关闭websocket
        closewebsocket(){
          console.log("关闭socket连接")
          //关闭录音
          this.recstop();
          // websocket关闭参数
          if (this.websocket && this.websocket.readystate == 1){
            this.websocket.send(
                json.stringify({
                  data: {
                    status: 2,
                    format: "audio/l16;rate=16000",
                    encoding: "raw",
                    audio: '',
                  },
                })
            )
          }
          if (!this.isrecording) return;
          this.isrecording = false;
        }
       
        
      },
      mounted() {
        this.getimg()
        // 使用 jsrecorder 创建一个对象并进行解构操作
        const callback = (blob, enctime) => {
          // 发送音频arraybuffer
          if (this.websocket && this.websocket.readystate == 1 && this.isrecording) {
            // 创建一个filereader实例
            const reader = new filereader();
            // 定义onload事件处理器
            reader.onload = function(e) {
              // 这里的e.target.result就是一个arraybuffer
              const arraybuffer = e.target.result;
              // 发送arraybuffer
              this.websocket.send(json.stringify({
                data: {
                  status: 1,
                  format: "audio/l16;rate=16000",
                  encoding: "raw",
                  audio: tobase64(new uint8array(arraybuffer)),
                },
              }));
            }.bind(this); // 使用bind确保this指向正确
            // 使用filereader的readasarraybuffer方法读取blob
            reader.readasarraybuffer(blob);
          }
        };
        const recorder = jsrecorder(callback)
        // 开始录音、结束录音方法
        this.recstart = recorder.recstart
        this.recstop = recorder.recstop
      }
    }
    </script>
    
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com