klren0312 / daliy_knowledge

知识积累,正确使用方式是watch
21 stars 4 forks source link

小程序对接语音识别 #697

Open klren0312 opened 1 year ago

klren0312 commented 1 year ago
import { Base64 } from './base64'
import CryptoJS from 'crypto-js'
const app = getApp()
const APPID = app.globalData.APPID
const API_KEY = app.globalData.API_KEY
const API_SECRET = app.globalData.API_SECRET
export function getWebSocketUrl() {
  return new Promise((resolve, reject) => {
    // 请求地址根据语种不同变化
    var url = 'wss://iat-api.xfyun.cn/v2/iat'
    var host = 'iat-api.xfyun.cn'
    var apiKey = API_KEY
    var apiSecret = API_SECRET
    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 = Base64.encode(authorizationOrigin)
    const params = encodeURI(`authorization=${authorization}&date=${date}&host=${host}`)
    url = `${url}?${params}`
    resolve(url)
  })
}

export class IatRecorder {
  constructor({ language, accent, appId } = {}) {
    this.firstSend = true
    this.status = 'null'
    this.language = language || 'zh_cn'
    this.accent = accent || 'mandarin'
    this.appId = appId || APPID
    // 记录音频数据
    this.audioData = []
    // 记录听写结果
    this.resultText = ''
    // wpgs下的听写结果需要中间状态辅助记录
    this.resultTextTemp = ''
    this.options = {
      duration: 60000, // 指定录音的时常,单位ms
      sampleRate: 16000, // 采样率
      numberOfChannels: 1, // 录音通道数
      format: 'PCM', // 音频格式
      frameSize: 5, // 指定帧大小,单位KB
    }
  }
  // 修改录音听写状态
  setStatus(status) {
    this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status)
    this.status = status
  }
  setResultText({ resultText, resultTextTemp } = {}) {
    this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
    resultText !== undefined && (this.resultText = resultText)
    resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
  }
  // 修改听写参数
  setParams({ language, accent } = {}) {
    language && (this.language = language)
    accent && (this.accent = accent)
  }
  // 连接websocket
  connectWebSocket() {
    getWebSocketUrl().then(url => {
      this.webSocket = wx.connectSocket({
        url,
        success: e => {
          wx.showToast({
            title: '请说话',
          })
          this.recordManager.start(this.options)
        },
        fail: e => {
          console.error(e)
        }
      })
      console.log(this.webSocket)
      this.setStatus('init')
      this.webSocket.onOpen(e => {
        console.log('open')
        this.setStatus('ing')
        // 重新开始录音
        setTimeout(() => {
          // this.webSocketSend()
        }, 500)
      })
      this.webSocket.onMessage(e => {
        this.result(e.data)
      })
      this.webSocket.onError(e => {
        console.error(e)
        this.recorderStop()
      })
      this.webSocket.onClose(e => {
        this.recorderStop()
      })
    })
  }
  // 初始化浏览器录音
  recorderInit() {
    this.recordManager = wx.getRecorderManager()
    console.log(this.recordManager)
    this.recordManager.onStart(() => {
      console.log('recorder start')
    })
    this.recordManager.onPause(() => {
      console.log('recorder pause')
    })
    this.recordManager.onStop((res) => {
      // tempFilePath   String  录音文件的临时路径
      console.log('recorder stop', res)
    })
    this.recordManager.onError((err) => {
      // errMsg String  错误信息
      console.log('recorder err', err)
    })

    this.recordManager.onFrameRecorded((res) => {
      const { frameBuffer, isLastFrame } = res
      const int16Arr = new Int8Array(frameBuffer)
      let params = {}
      if (this.firstSend) {
        this.firstSend = false
        params = {
          common: {
            app_id: this.appId,
          },
          business: {
            language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
            domain: 'iat',
            accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
            vad_eos: 5000,
            dwa: 'wpgs', //为使该功能生效,需到控制台开通动态修正功能(该功能免费)
          },
          data: {
            status: 0,
            format: 'audio/L16;rate=16000',
            encoding: 'raw',
            audio: wx.arrayBufferToBase64(int16Arr),
          },
        }
      } else {
        if (isLastFrame) {
          params = {
            data: {
              status: 2
            }
          }
        } else {
          params = {
            data: {
              status: 1,
              format: 'audio/L16;rate=16000',
              encoding: 'raw',
              audio: wx.arrayBufferToBase64(int16Arr),
            }
          }
        }
      }

      this.webSocket && this.webSocket.send({
        data: JSON.stringify(params)
      })
    })
    this.connectWebSocket()
  }
  recorderStart() {
    this.recorderInit()
  }
  // 停止录音
  recorderStop() {
    if (this.recordManager) {
      this.recordManager.stop()
      this.recordManager = null
    }
    this.webSocket && this.webSocket.close()
    this.webSocket = null
    this.audioData = []
    this.handlerInterval && clearInterval(this.handlerInterval)
    this.handlerInterval = null
    this.firstSend = true
  }
  // 向webSocket发送数据
  webSocketSend() {
    if (!this.webSocket) {
      return
    }
    let audioData = this.audioData.splice(0, 1280)
    console.log('开始发送', audioData)
    var params = {
      common: {
        app_id: this.appId,
      },
      business: {
        language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
        domain: 'iat',
        accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
        vad_eos: 5000,
        dwa: 'wpgs', //为使该功能生效,需到控制台开通动态修正功能(该功能免费)
      },
      data: {
        status: 0,
        format: 'audio/L16;rate=16000',
        encoding: 'raw',
        audio: Base64.encode(audioData),
      },
    }
    this.webSocket.send({
      data: JSON.stringify(params)
    })
    this.handlerInterval = setInterval(() => {
      // websocket未连接
      if (!this.webSocket) {
        this.audioData = []
        clearInterval(this.handlerInterval)
        return
      }
      if (this.audioData.length === 0) {
        if (this.status === 'end') {
          this.webSocket.send({
            data: JSON.stringify({
              data: {
                status: 2,
                format: 'audio/L16;rate=16000',
                encoding: 'raw',
                audio: '',
              },
            })
          })
          this.audioData = []
          clearInterval(this.handlerInterval)
        }
        return false
      }
      audioData = this.audioData.splice(0, 1280)
      console.log('开始发送', audioData)
      // 中间帧
      this.webSocket.send({
        data: JSON.stringify({
          data: {
            status: 1,
            format: 'audio/L16;rate=16000',
            encoding: 'raw',
            audio: Base64.encode(audioData),
          },
        })
      })
    }, 40)
  }
  result(resultData) {
      console.log(resultData)
    // 识别结束
    let jsonData = JSON.parse(resultData)
    if (jsonData.data && jsonData.data.result) {
      let data = jsonData.data.result
      let str = ''
      let resultStr = ''
      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
          this.setResultText({
            resultText: this.resultTextTemp,
          })
        }
        // 将结果存储在resultTextTemp中
        this.setResultText({
          resultTextTemp: this.resultText + str,
        })
      } else {
        this.setResultText({
          resultText: this.resultText + str,
        })
      }
    }
    if (jsonData.code === 0 && jsonData.data.status === 2) {
      this.webSocket.close()
      this.webSocket = null
    }
    if (jsonData.code !== 0) {
      this.webSocket.close()
      this.webSocket = null
      console.log(`${jsonData.code}:${jsonData.message}`)
    }
  }
  start() {
    this.recorderStart()
    this.setResultText({ resultText: '', resultTextTemp: '' })
  }
  stop() {
    this.webSocket && this.webSocket.send({
      data: JSON.stringify({
        data: {
          status: 2,
        },
      })
    })
    this.recorderStop()
  }
}
irebit commented 1 year ago

👍