• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

科大讯飞webAPI文字转语音

武飞扬头像
wenlianglian
帮助5

可能会遇到的坑

学新通

原文链接

自行了解 js webWorker线程

我的目录结构

学新通

 TTS.js代码

  1.  
    // 科大讯飞 文字->语音
  2.  
    import {downloadPCM, downloadWAV} from '@/common/download.js'
  3.  
    import CryptoJS from 'crypto-js'
  4.  
    import { Base64 } from 'js-base64'
  5.  
    var transWorker = new Worker('../common/transcode.worker.js')
  6.  
    //测试完成后需要改成 var transWorker = new Worker('transcode.worker.js')
  7.  
    //APPID,APISecret,APIKey在控制台-我的应用-语音合成(流式版)页面获取
  8.  
    const APPID = ''
  9.  
    const API_SECRET = ''
  10.  
    const API_KEY = ''
  11.  
    function getWebsocketUrl() {
  12.  
    return new Promise((resolve, reject) => {
  13.  
    var apiKey = API_KEY
  14.  
    var apiSecret = API_SECRET
  15.  
    var url = 'wss://tts-api.xfyun.cn/v2/tts'
  16.  
    var host = location.host
  17.  
    var date = new Date().toGMTString()
  18.  
    var algorithm = 'hmac-sha256'
  19.  
    var headers = 'host date request-line'
  20.  
    var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/tts HTTP/1.1`
  21.  
    var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
  22.  
    var signature = CryptoJS.enc.Base64.stringify(signatureSha)
  23.  
    var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
  24.  
    var authorization = btoa(authorizationOrigin)
  25.  
    url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
  26.  
    resolve(url)
  27.  
    })
  28.  
    }
  29.  
    export default class TTSRecorder {
  30.  
    constructor({
  31.  
    speed = 20,
  32.  
    voice = 50,
  33.  
    pitch = 50,
  34.  
    voiceName = 'xiaoyan',
  35.  
    appId = APPID,
  36.  
    text = '',
  37.  
    tte = 'UTF8',
  38.  
    defaultText = '请输入您要合成的文本',
  39.  
    } = {}) {
  40.  
    this.speed = speed
  41.  
    this.voice = voice
  42.  
    this.pitch = pitch
  43.  
    this.voiceName = voiceName
  44.  
    this.text = text
  45.  
    this.tte = tte
  46.  
    this.defaultText = defaultText
  47.  
    this.appId = appId
  48.  
    this.audioData = []
  49.  
    this.rawAudioData = []
  50.  
    this.audioDataOffset = 0
  51.  
    this.status = 'init'
  52.  
    transWorker.onmessage = (e) => {
  53.  
    this.audioData.push(...e.data.data)
  54.  
    this.rawAudioData.push(...e.data.rawAudioData)
  55.  
    }
  56.  
    }
  57.  
    // 修改录音听写状态
  58.  
    setStatus(status) {
  59.  
    this.onWillStatusChange && this.onWillStatusChange(this.status, status)
  60.  
    this.status = status
  61.  
    }
  62.  
    // 设置合成相关参数
  63.  
    setParams({ speed, voice, pitch, text, voiceName, tte }) {
  64.  
    speed !== undefined && (this.speed = speed)
  65.  
    voice !== undefined && (this.voice = voice)
  66.  
    pitch !== undefined && (this.pitch = pitch)
  67.  
    text && (this.text = text)
  68.  
    tte && (this.tte = tte)
  69.  
    voiceName && (this.voiceName = voiceName)
  70.  
    this.resetAudio()
  71.  
    }
  72.  
    // 连接websocket
  73.  
    connectWebSocket() {
  74.  
    this.setStatus('ttsing')
  75.  
    return getWebsocketUrl().then(url => {
  76.  
    let ttsWS
  77.  
    if ('WebSocket' in window) {
  78.  
    ttsWS = new WebSocket(url)
  79.  
    } else if ('MozWebSocket' in window) {
  80.  
    ttsWS = new MozWebSocket(url)
  81.  
    } else {
  82.  
    alert('浏览器不支持WebSocket')
  83.  
    return
  84.  
    }
  85.  
    this.ttsWS = ttsWS
  86.  
    ttsWS.onopen = e => {
  87.  
    this.webSocketSend()
  88.  
    this.playTimeout = setTimeout(() => {
  89.  
    this.audioPlay()
  90.  
    }, 1000)
  91.  
    }
  92.  
    ttsWS.onmessage = e => {
  93.  
    this.result(e.data)
  94.  
    }
  95.  
    ttsWS.onerror = e => {
  96.  
    clearTimeout(this.playTimeout)
  97.  
    this.setStatus('errorTTS')
  98.  
    alert('WebSocket报错,请f12查看详情')
  99.  
    console.error(`详情查看:${encodeURI(url.replace('wss:', 'https:'))}`)
  100.  
    }
  101.  
    ttsWS.onclose = e => {
  102.  
    console.log(e)
  103.  
    }
  104.  
    })
  105.  
    }
  106.  
    // 处理音频数据
  107.  
    transToAudioData(audioData) {}
  108.  
    // websocket发送数据
  109.  
    webSocketSend() {
  110.  
    var params = {
  111.  
    common: {
  112.  
    app_id: this.appId, // APPID
  113.  
    },
  114.  
    business: {
  115.  
    aue: 'raw',
  116.  
    auf: 'audio/L16;rate=16000',
  117.  
    vcn: this.voiceName,
  118.  
    speed: this.speed,
  119.  
    volume: this.voice,
  120.  
    pitch: this.pitch,
  121.  
    bgs: 1,
  122.  
    tte: this.tte,
  123.  
    },
  124.  
    data: {
  125.  
    status: 2,
  126.  
    text: this.encodeText(
  127.  
    this.text || this.defaultText,
  128.  
    this.tte === 'unicode' ? 'base64&utf16le' : ''
  129.  
    )
  130.  
    },
  131.  
    }
  132.  
    this.ttsWS.send(JSON.stringify(params))
  133.  
    }
  134.  
    encodeText (text, encoding) {
  135.  
    switch (encoding) {
  136.  
    case 'utf16le' : {
  137.  
    let buf = new ArrayBuffer(text.length * 4)
  138.  
    let bufView = new Uint16Array(buf)
  139.  
    for (let i = 0, strlen = text.length; i < strlen; i ) {
  140.  
    bufView[i] = text.charCodeAt(i)
  141.  
    }
  142.  
    return buf
  143.  
    }
  144.  
    case 'buffer2Base64': {
  145.  
    let binary = ''
  146.  
    let bytes = new Uint8Array(text)
  147.  
    let len = bytes.byteLength
  148.  
    for (let i = 0; i < len; i ) {
  149.  
    binary = String.fromCharCode(bytes[i])
  150.  
    }
  151.  
    return window.btoa(binary)
  152.  
    }
  153.  
    case 'base64&utf16le' : {
  154.  
    return this.encodeText(this.encodeText(text, 'utf16le'), 'buffer2Base64')
  155.  
    }
  156.  
    default : {
  157.  
    return Base64.encode(text)
  158.  
    }
  159.  
    }
  160.  
    }
  161.  
    // websocket接收数据的处理
  162.  
    result(resultData) {
  163.  
    let jsonData = JSON.parse(resultData)
  164.  
    // 合成失败
  165.  
    if (jsonData.code !== 0) {
  166.  
    alert(`合成失败: ${jsonData.code}:${jsonData.message}`)
  167.  
    console.error(`${jsonData.code}:${jsonData.message}`)
  168.  
    this.resetAudio()
  169.  
    return
  170.  
    }
  171.  
    transWorker.postMessage(jsonData.data.audio)
  172.  
    // window.postMessage(jsonData.data.audio)
  173.  
     
  174.  
    if (jsonData.code === 0 && jsonData.data.status === 2) {
  175.  
    this.ttsWS.close()
  176.  
    }
  177.  
    }
  178.  
    // 重置音频数据
  179.  
    resetAudio() {
  180.  
    this.audioStop()
  181.  
    this.setStatus('init')
  182.  
    this.audioDataOffset = 0
  183.  
    this.audioData = []
  184.  
    this.rawAudioData = []
  185.  
    this.ttsWS && this.ttsWS.close()
  186.  
    clearTimeout(this.playTimeout)
  187.  
    }
  188.  
    // 音频初始化
  189.  
    audioInit() {
  190.  
    let AudioContext = window.AudioContext || window.webkitAudioContext
  191.  
    if (AudioContext) {
  192.  
    this.audioContext = new AudioContext()
  193.  
    this.audioContext.resume()
  194.  
    this.audioDataOffset = 0
  195.  
    }
  196.  
    }
  197.  
    // 音频播放
  198.  
    audioPlay() {
  199.  
    this.setStatus('play')
  200.  
    let audioData = this.audioData.slice(this.audioDataOffset)
  201.  
    this.audioDataOffset = audioData.length
  202.  
    let audioBuffer = this.audioContext.createBuffer(1, audioData.length, 22050)
  203.  
    let nowBuffering = audioBuffer.getChannelData(0)
  204.  
    if (audioBuffer.copyToChannel) {
  205.  
    audioBuffer.copyToChannel(new Float32Array(audioData), 0, 0)
  206.  
    } else {
  207.  
    for (let i = 0; i < audioData.length; i ) {
  208.  
    nowBuffering[i] = audioData[i]
  209.  
    }
  210.  
    }
  211.  
    let bufferSource = this.bufferSource = this.audioContext.createBufferSource()
  212.  
    bufferSource.buffer = audioBuffer
  213.  
    bufferSource.connect(this.audioContext.destination)
  214.  
    bufferSource.start()
  215.  
    bufferSource.onended = event => {
  216.  
    if (this.status !== 'play') {
  217.  
    return
  218.  
    }
  219.  
    if (this.audioDataOffset < this.audioData.length) {
  220.  
    this.audioPlay()
  221.  
    } else {
  222.  
    this.audioStop()
  223.  
    }
  224.  
    }
  225.  
    }
  226.  
    // 音频播放结束
  227.  
    audioStop() {
  228.  
    this.setStatus('endPlay')
  229.  
    clearTimeout(this.playTimeout)
  230.  
    this.audioDataOffset = 0
  231.  
    if (this.bufferSource) {
  232.  
    try {
  233.  
    this.bufferSource.stop()
  234.  
    } catch (e) {
  235.  
    console.log(e)
  236.  
    }
  237.  
    }
  238.  
    }
  239.  
    start() {
  240.  
    if(this.audioData.length) {
  241.  
    this.audioPlay()
  242.  
    } else {
  243.  
    if (!this.audioContext) {
  244.  
    this.audioInit()
  245.  
    }
  246.  
    if (!this.audioContext) {
  247.  
    alert('该浏览器不支持webAudioApi相关接口')
  248.  
    return
  249.  
    }
  250.  
    this.connectWebSocket()
  251.  
    }
  252.  
    }
  253.  
    stop() {
  254.  
    this.audioStop()
  255.  
    }
  256.  
    }
学新通

transcode.worker.js代码(科大讯飞demo里面的,但是稍作修改 语音合成(流式版)WebAPI 文档 | 讯飞开放平台文档中心)

  1.  
    /*
  2.  
    * @Autor: lycheng
  3.  
    * @Date: 2020-01-13 16:12:22
  4.  
    */
  5.  
    let minSampleRate = 22050
  6.  
    self.onmessage = function(e) {
  7.  
    transcode.transToAudioData(e.data)
  8.  
    }
  9.  
    var transcode = {
  10.  
    transToAudioData(audioDataStr, fromRate = 16000, toRate = 22505) {
  11.  
    let outputS16 = transcode.base64ToS16(audioDataStr)
  12.  
    let output = transcode.transS16ToF32(outputS16)
  13.  
    output = transcode.transSamplingRate(output, fromRate, toRate)
  14.  
    output = Array.from(output)
  15.  
    self.postMessage({
  16.  
    data: output,
  17.  
    rawAudioData: Array.from(outputS16)
  18.  
    })
  19.  
    },
  20.  
    transSamplingRate(data, fromRate = 44100, toRate = 16000) {
  21.  
    var fitCount = Math.round(data.length * (toRate / fromRate))
  22.  
    var newData = new Float32Array(fitCount)
  23.  
    var springFactor = (data.length - 1) / (fitCount - 1)
  24.  
    newData[0] = data[0]
  25.  
    for (let i = 1; i < fitCount - 1; i ) {
  26.  
    var tmp = i * springFactor
  27.  
    var before = Math.floor(tmp).toFixed()
  28.  
    var after = Math.ceil(tmp).toFixed()
  29.  
    var atPoint = tmp - before
  30.  
    newData[i] = data[before] (data[after] - data[before]) * atPoint
  31.  
    }
  32.  
    newData[fitCount - 1] = data[data.length - 1]
  33.  
    return newData
  34.  
    },
  35.  
    transS16ToF32(input) {
  36.  
    var tmpData = []
  37.  
    for (let i = 0; i < input.length; i ) {
  38.  
    var d = input[i] < 0 ? input[i] / 0x8000 : input[i] / 0x7fff
  39.  
    tmpData.push(d)
  40.  
    }
  41.  
    return new Float32Array(tmpData)
  42.  
    },
  43.  
    base64ToS16(base64AudioData) {
  44.  
    base64AudioData = atob(base64AudioData)
  45.  
    const outputArray = new Uint8Array(base64AudioData.length)
  46.  
    for (let i = 0; i < base64AudioData.length; i) {
  47.  
    outputArray[i] = base64AudioData.charCodeAt(i)
  48.  
    }
  49.  
    return new Int16Array(new DataView(outputArray.buffer).buffer)
  50.  
    },
  51.  
    }
学新通

index.vue代码 

  1.  
    <template>
  2.  
    <view class="content">
  3.  
    <view class="text-area">
  4.  
    <text class="title">在线文字转语音</text>
  5.  
    </view>
  6.  
    <textarea type="text" v-model="txt" placeholder="请输入您要合成的文本"></textarea>
  7.  
    <button @click="startTrans">{{btnState[ttsStatus]}}</button>
  8.  
    <br/>
  9.  
    <view class="text-area">
  10.  
    <text class="title">语音转文字</text>
  11.  
    </view>
  12.  
    <button>开始转写</button>
  13.  
    <button>结束转写</button>
  14.  
    </view>
  15.  
    </template>
  16.  
     
  17.  
    <script>
  18.  
    import TTSRecorder from "@/common/TTS.js"
  19.  
    console.log(TTSRecorder,'TTSRecorder');
  20.  
    // const transWorker = new Worker(new URL('../../common/transcode.worker.js', import.meta.url))
  21.  
    let ttsRecorder = new TTSRecorder()
  22.  
    export default {
  23.  
    data() {
  24.  
    return {
  25.  
    title: 'Hello',
  26.  
    txt: '',
  27.  
    aTt: '',
  28.  
    btnState: {
  29.  
    init: '立即合成',
  30.  
    ttsing: '正在合成',
  31.  
    play: '停止播放',
  32.  
    endPlay: '重新播放',
  33.  
    errorTTS: '合成失败',
  34.  
    },
  35.  
    ttsStatus: 'init'
  36.  
    }
  37.  
    },
  38.  
    onLoad() {
  39.  
    const _this = this
  40.  
    ttsRecorder.onWillStatusChange = function(oldStatus, status) {
  41.  
    // 可以在这里进行页面中一些交互逻辑处理:按钮交互等
  42.  
    _this.ttsStatus = status
  43.  
    }
  44.  
    },
  45.  
    methods: {
  46.  
    startTrans(){
  47.  
    ttsRecorder.setParams({
  48.  
    text: this.txt
  49.  
    })
  50.  
    console.log(ttsRecorder,'ttsRecorder');
  51.  
    if (['init', 'endPlay', 'errorTTS'].indexOf(ttsRecorder.status) > -1) {
  52.  
    console.log(111);
  53.  
    ttsRecorder.start()
  54.  
    } else {
  55.  
    ttsRecorder.stop()
  56.  
    }
  57.  
    }
  58.  
    }
  59.  
    }
  60.  
    </script>
  61.  
     
  62.  
    <style>
  63.  
    .content {
  64.  
    display: flex;
  65.  
    flex-direction: column;
  66.  
    align-items: center;
  67.  
    justify-content: center;
  68.  
    }
  69.  
     
  70.  
    .logo {
  71.  
    height: 200rpx;
  72.  
    width: 200rpx;
  73.  
    margin-top: 200rpx;
  74.  
    margin-left: auto;
  75.  
    margin-right: auto;
  76.  
    margin-bottom: 50rpx;
  77.  
    }
  78.  
     
  79.  
    .text-area {
  80.  
    display: flex;
  81.  
    justify-content: center;
  82.  
    }
  83.  
     
  84.  
    .title {
  85.  
    font-size: 36rpx;
  86.  
    color: #8f8f94;
  87.  
    }
  88.  
    </style>
学新通

最后打包出来后,把transcode.worker.js放到根目录即可

学新通

关键点就是webWorker

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhffhkge
系列文章
更多 icon
同类精品
更多 icon
继续加载