科大讯飞语音合成Vue版教程

项目需求: 把文字转语音

一. 去科大讯飞官网下载js版本demo:https://www.xfyun.cn/doc/tts/online_tts/API.html

放入src下面assets文件下: 

科大讯飞语音合成Vue版教程_第1张图片

 transcode.worker.js:

/*
 * @Autor: lycheng
 * @Date: 2020-01-13 16:12:22
 */
(function(){
  let minSampleRate = 22050
  self.onmessage = function(e) {
    transcode.transToAudioData(e.data)
  }
  var transcode = {
    transToAudioData: function(audioDataStr, fromRate = 16000, toRate = 22505) {
      let outputS16 = transcode.base64ToS16(audioDataStr)
      let output = transcode.transS16ToF32(outputS16)
      output = transcode.transSamplingRate(output, fromRate, toRate)
      output = Array.from(output)
      self.postMessage({
        data: output, 
        rawAudioData: Array.from(outputS16)
      })
    },
    transSamplingRate: function(data, fromRate = 44100, toRate = 16000) {
      var fitCount = Math.round(data.length * (toRate / fromRate))
      var newData = new Float32Array(fitCount)
      var springFactor = (data.length - 1) / (fitCount - 1)
      newData[0] = data[0]
      for (let i = 1; i < fitCount - 1; i++) {
        var tmp = i * springFactor
        var before = Math.floor(tmp).toFixed()
        var after = Math.ceil(tmp).toFixed()
        var atPoint = tmp - before
        newData[i] = data[before] + (data[after] - data[before]) * atPoint
      }
      newData[fitCount - 1] = data[data.length - 1]
      return newData
    },
    transS16ToF32: function(input) {
      var tmpData = []
      for (let i = 0; i < input.length; i++) {
        var d = input[i] < 0 ? input[i] / 0x8000 : input[i] / 0x7fff
        tmpData.push(d)
      }
      return new Float32Array(tmpData)
    },
    base64ToS16: function(base64AudioData) {
      base64AudioData = atob(base64AudioData)
      const outputArray = new Uint8Array(base64AudioData.length)
      for (let i = 0; i < base64AudioData.length; ++i) {
        outputArray[i] = base64AudioData.charCodeAt(i)
      }
      return new Int16Array(new DataView(outputArray.buffer).buffer)
    },
  }
})()

audio.js: 

/*
 * @Autor: lycheng
 * @Date: 2020-01-13 16:12:22
 */
/**
 * Created by iflytek on 2019/11/19.
 *
 * 在线语音合成调用demo
 * 此demo只是一个简单的调用示例,不适合用到实际生产环境中
 *
 * 在线语音合成 WebAPI 接口调用示例 接口文档(必看):https://www.xfyun.cn/doc/tts/online_tts/API.html
 * 错误码链接:
 * https://www.xfyun.cn/doc/tts/online_tts/API.html
 * https://www.xfyun.cn/document/error-code (code返回错误码时必看)
 *
 */

// 1. websocket连接:判断浏览器是否兼容,获取websocket url并连接,这里为了方便本地生成websocket url
// 2. 连接websocket,向websocket发送数据,实时接收websocket返回数据
// 3. 处理websocket返回数据为浏览器可以播放的音频数据
// 4. 播放音频数据
// ps: 该示例用到了es6中的一些语法,建议在chrome下运行
// import {downloadPCM, downloadWAV} from 'js/download.js'
import CryptoJS from 'crypto-js'
import Enc from 'enc'
import TransWorker from '@/assets/js/speechSynthesis/transcode.worker'
// import VConsole from 'vconsole'
import { Base64 } from 'js-base64'
// import './index.css'

let transWorker = new TransWorker()
//APPID,APISecret,APIKey在控制台-我的应用-语音合成(流式版)页面获取
const APPID = '你的APPID'
const API_SECRET = '你的API_SECRET'
const API_KEY = '你的API_KEY '

function getWebsocketUrl() {
  return new Promise((resolve, reject) => {
    var apiKey = API_KEY
    var apiSecret = API_SECRET
    var url = 'wss://tts-api.xfyun.cn/v2/tts'
    var host = location.host
    var date = new Date().toGMTString()
    var algorithm = 'hmac-sha256'
    var headers = 'host date request-line'
    var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/tts 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}`
    resolve(url)
  })
}
const TTSRecorder =class {
  constructor({
    speed = 30,
    voice = 50,
    pitch = 50,
    voiceName = 'xiaoyan',
    appId = APPID,
    text = '',
    tte = 'UTF8',
    defaultText = '请输入您要合成的文本',
  } = {}) {
    this.speed = speed
    this.voice = voice
    this.pitch = pitch
    this.voiceName = voiceName
    this.text = text
    this.tte = tte
    this.defaultText = defaultText
    this.appId = appId
    this.audioData = []
    this.rawAudioData = []
    this.audioDataOffset = 0
    this.status = 'init'
    transWorker.onmessage = (e) => {
      this.audioData.push(...e.data.data)
      this.rawAudioData.push(...e.data.rawAudioData)
    }
  }
  // 修改录音听写状态
  setStatus(status) {
    this.onWillStatusChange && this.onWillStatusChange(this.status, status)
    this.status = status
  }

  // 设置合成相关参数
  setParams({ speed, voice, pitch, text, voiceName, tte }) {
    speed !== undefined && (this.speed = speed)
    voice !== undefined && (this.voice = voice)
    pitch !== undefined && (this.pitch = pitch)
    text && (this.text = text)
    tte && (this.tte = tte)
    voiceName && (this.voiceName = voiceName)
    this.resetAudio()
  }
  // 连接websocket
  connectWebSocket() {
    this.setStatus('ttsing')
    return getWebsocketUrl().then(url => {
      let ttsWS
      if ('WebSocket' in window) {
        ttsWS = new WebSocket(url)
      } else if ('MozWebSocket' in window) {
        ttsWS = new MozWebSocket(url)
      } else {
        alert('浏览器不支持WebSocket')
        return
      }
      this.ttsWS = ttsWS
      ttsWS.onopen = e => {
        this.webSocketSend()
        this.playTimeout = setTimeout(() => {
          this.audioPlay()
        }, 1000)
      }
      ttsWS.onmessage = e => {
        this.result(e.data)
      }
      ttsWS.onerror = e => {
        clearTimeout(this.playTimeout)
        this.setStatus('errorTTS')
        alert('WebSocket报错,请f12查看详情')
        console.error(`详情查看:${encodeURI(url.replace('wss:', 'https:'))}`)
      }
      ttsWS.onclose = e => {
        // console.log(e)
      }
    })
  }
  // 处理音频数据
  transToAudioData(audioData) {}
  // websocket发送数据
  webSocketSend() {
    var params = {
      common: {
        app_id: this.appId, // APPID
      },
      business: {
        aue: 'raw',
        // sfl= 1,
        auf: 'audio/L16;rate=16000',
        vcn: this.voiceName,
        speed: this.speed,
        volume: this.voice,
        pitch: this.pitch,
        bgs: 0,
        tte: this.tte,
      },
      data: {
        status: 2,
        text: this.encodeText(
          this.text || this.defaultText,
          this.tte === 'unicode' ? 'base64&utf16le' : ''
        )
      },
    }
    this.ttsWS.send(JSON.stringify(params))
  }
  encodeText (text, encoding) {
    switch (encoding) {
      case 'utf16le' : {
        let buf = new ArrayBuffer(text.length * 4)
        let bufView = new Uint16Array(buf)
        for (let i = 0, strlen = text.length; i < strlen; i++) {
          bufView[i] = text.charCodeAt(i)
        }
        return buf
      }
      case 'buffer2Base64': {
        let binary = ''
        let bytes = new Uint8Array(text)
        let len = bytes.byteLength
        for (let i = 0; i < len; i++) {
          binary += String.fromCharCode(bytes[i])
        }
        return window.btoa(binary)
      }
      case 'base64&utf16le' : {
        return this.encodeText(this.encodeText(text, 'utf16le'), 'buffer2Base64')
      }
      default : {
        return Base64.encode(text)
      }
    }
  }
  // websocket接收数据的处理
  result(resultData) {
    let jsonData = JSON.parse(resultData)
    // 合成失败
    if (jsonData.code !== 0) {
      alert(`合成失败: ${jsonData.code}:${jsonData.message}`)
      console.error(`${jsonData.code}:${jsonData.message}`)
      this.resetAudio()
      return
    }
    transWorker.postMessage(jsonData.data.audio)
    
    if (jsonData.code === 0 && jsonData.data.status === 2) {
      this.ttsWS.close()
    }
  }
  // 重置音频数据
  resetAudio() {
    this.audioStop()
    this.setStatus('init')
    this.audioDataOffset = 0
    this.audioData = []
    this.rawAudioData = []
    this.ttsWS && this.ttsWS.close()
    clearTimeout(this.playTimeout)
  }
  // 音频初始化
  audioInit() {
    let AudioContext = window.AudioContext || window.webkitAudioContext
    if (AudioContext) {
      this.audioContext = new AudioContext()
      this.audioContext.resume()
      this.audioDataOffset = 0
    } 
  }
  // 音频播放
  audioPlay() {
    this.setStatus('play')
    let audioData = this.audioData.slice(this.audioDataOffset)
    this.audioDataOffset += audioData.length
    let audioBuffer = this.audioContext.createBuffer(1, audioData.length, 22050)
    let nowBuffering = audioBuffer.getChannelData(0)
    if (audioBuffer.copyToChannel) {
      audioBuffer.copyToChannel(new Float32Array(audioData), 0, 0)
    } else {
      for (let i = 0; i < audioData.length; i++) {
        nowBuffering[i] = audioData[i]
      }
    }
    let bufferSource = this.bufferSource = this.audioContext.createBufferSource()
    bufferSource.buffer = audioBuffer
    bufferSource.connect(this.audioContext.destination)
    bufferSource.start()
    bufferSource.onended = event => {
      if (this.status !== 'play') {
        return
      }
      if (this.audioDataOffset < this.audioData.length) {
        this.audioPlay()
      } else {
        this.audioStop()
      }
    }
  }
  // 音频播放结束
  audioStop() {
    this.setStatus('endPlay')
    clearTimeout(this.playTimeout)
    this.audioDataOffset = 0
    if (this.bufferSource) {
      try {
        this.bufferSource.stop()
      } catch (e) {
        // console.log(e)
      }
    }
  }
  start() {
    if(this.audioData.length) {
      this.audioPlay()
    } else {
      if (!this.audioContext) {
        this.audioInit()
      }
      if (!this.audioContext) {
        alert('该浏览器不支持webAudioApi相关接口')
        return
      }
      this.connectWebSocket()
    }
  }
  stop() {
    this.audioStop()
  }
}
export default TTSRecorder
// ======================开始调用=============================
// var vConsole = new VConsole()
// let ttsRecorder = new TTSRecorder()
// ttsRecorder.onWillStatusChange = function(oldStatus, status) {
//   // 可以在这里进行页面中一些交互逻辑处理:按钮交互等
//   // 按钮中的文字
//   let btnState = {
//     init: '立即合成',
//     ttsing: '正在合成',
//     play: '停止播放',
//     endPlay: '重新播放',
//     errorTTS: '合成失败',
//   }
//   $('.audio-ctrl-btn')
//     .removeClass(oldStatus)
//     .addClass(status)
//     .text(btnState[status])
// }


// $('.audio-ctrl-btn').click(function() {
//   if (['init', 'endPlay', 'errorTTS'].indexOf(ttsRecorder.status) > -1) {
//     ttsRecorder.start()
//   } else {
//     ttsRecorder.stop()
//   }
// })


// $('#input_text').change(function(){
//   ttsRecorder.setParams({
//     text: this.value
//   })
// })

 如果导入transcode.worker.js文件报错问题类似于如下:

 科大讯飞语音合成Vue版教程_第2张图片

 那是因为js模块化后在vue里面导入不能直接使用new Worker(),所以audio.js里面new Worker()会报错,解决办法请到我的另一篇文章:

https://blog.csdn.net/qq_45963071/article/details/119838275?spm=1001.2014.3001.5501

二. 语音合成使用方法:


import TtsRecorder from "@/assets/js/speechSynthesis/audio";
const ttsRecorder = new TtsRecorder();
export default {
    data() {
    return {
      text: "你好,世界",
    };
  },
    methods: {
        play(){
           //要合成的文本
          ttsRecorder.setParams({
          // 文本内容
          text: this.text,
          // 角色
          //  voiceName: '',
          // 语速
          speed: 50,
          // 音量
          voice: 50,
          });
          ttsRecorder.start();
       },
        pause(){
          ttsRecorder.stop();
       }
    }
}

你可能感兴趣的:(语音合成,vue.js,javascript)