可能会遇到的坑

原文链接

自行了解 js webWorker线程

我的目录结构

TTS.js代码

// 科大讯飞 文字->语音
import {downloadPCM, downloadWAV} from '@/common/download.js'
import CryptoJS from 'crypto-js'
import { Base64 } from 'js-base64'
var transWorker = new Worker('../common/transcode.worker.js')
//测试完成后需要改成 var transWorker = new Worker('transcode.worker.js')
//APPID,APISecret,APIKey在控制台-我的应用-语音合成(流式版)页面获取
const APPID = ''
const API_SECRET = ''
const API_KEY = ''
function getWebsocketUrl() {return new Promise((resolve, reject) => {var apiKey = API_KEYvar apiSecret = API_SECRETvar url = 'wss://tts-api.xfyun.cn/v2/tts'var host = location.hostvar 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)})
}
export default class TTSRecorder {constructor({speed = 20,voice = 50,pitch = 50,voiceName = 'xiaoyan',appId = APPID,text = '',tte = 'UTF8',defaultText = '请输入您要合成的文本',} = {}) {this.speed = speedthis.voice = voicethis.pitch = pitchthis.voiceName = voiceNamethis.text = textthis.tte = ttethis.defaultText = defaultTextthis.appId = appIdthis.audioData = []this.rawAudioData = []this.audioDataOffset = 0this.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()}// 连接websocketconnectWebSocket() {this.setStatus('ttsing')return getWebsocketUrl().then(url => {let ttsWSif ('WebSocket' in window) {ttsWS = new WebSocket(url)} else if ('MozWebSocket' in window) {ttsWS = new MozWebSocket(url)} else {alert('浏览器不支持WebSocket')return}this.ttsWS = ttsWSttsWS.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',auf: 'audio/L16;rate=16000',vcn: this.voiceName,speed: this.speed,volume: this.voice,pitch: this.pitch,bgs: 1,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.byteLengthfor (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)// window.postMessage(jsonData.data.audio)if (jsonData.code === 0 && jsonData.data.status === 2) {this.ttsWS.close()}}// 重置音频数据resetAudio() {this.audioStop()this.setStatus('init')this.audioDataOffset = 0this.audioData = []this.rawAudioData = []this.ttsWS && this.ttsWS.close()clearTimeout(this.playTimeout)}// 音频初始化audioInit() {let AudioContext = window.AudioContext || window.webkitAudioContextif (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.lengthlet 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 = audioBufferbufferSource.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 = 0if (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()}
}

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

/** @Autor: lycheng* @Date: 2020-01-13 16:12:22*/let minSampleRate = 22050self.onmessage = function(e) {transcode.transToAudioData(e.data)}var transcode = {transToAudioData(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(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 * springFactorvar before = Math.floor(tmp).toFixed()var after = Math.ceil(tmp).toFixed()var atPoint = tmp - beforenewData[i] = data[before] + (data[after] - data[before]) * atPoint}newData[fitCount - 1] = data[data.length - 1]return newData},transS16ToF32(input) {var tmpData = []for (let i = 0; i < input.length; i++) {var d = input[i] < 0 ? input[i] / 0x8000 : input[i] / 0x7ffftmpData.push(d)}return new Float32Array(tmpData)},base64ToS16(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)},}

index.vue代码

<template><view class="content"><view class="text-area"><text class="title">在线文字转语音</text></view><textarea type="text" v-model="txt" placeholder="请输入您要合成的文本"></textarea><button @click="startTrans">{{btnState[ttsStatus]}}</button><br/><view class="text-area"><text class="title">语音转文字</text></view><button>开始转写</button><button>结束转写</button></view>
</template><script>import TTSRecorder from "@/common/TTS.js"console.log(TTSRecorder,'TTSRecorder');// const transWorker = new Worker(new URL('../../common/transcode.worker.js', import.meta.url))let ttsRecorder = new TTSRecorder()export default {data() {return {title: 'Hello',txt: '',aTt: '',btnState: {init: '立即合成',ttsing: '正在合成',play: '停止播放',endPlay: '重新播放',errorTTS: '合成失败',},ttsStatus: 'init'}},onLoad() {const _this = thisttsRecorder.onWillStatusChange = function(oldStatus, status) {// 可以在这里进行页面中一些交互逻辑处理:按钮交互等_this.ttsStatus = status}},methods: {startTrans(){ttsRecorder.setParams({text: this.txt})console.log(ttsRecorder,'ttsRecorder');if (['init', 'endPlay', 'errorTTS'].indexOf(ttsRecorder.status) > -1) {console.log(111);ttsRecorder.start()} else {ttsRecorder.stop()}}}}
</script><style>.content {display: flex;flex-direction: column;align-items: center;justify-content: center;}.logo {height: 200rpx;width: 200rpx;margin-top: 200rpx;margin-left: auto;margin-right: auto;margin-bottom: 50rpx;}.text-area {display: flex;justify-content: center;}.title {font-size: 36rpx;color: #8f8f94;}
</style>

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

关键点就是webWorker

科大讯飞webAPI文字转语音相关推荐

  1. 基于ROS2和科大讯飞的文字转语音TTS入门教程

    基于ROS2和科大讯飞的语音转文字入门教程 基于ROS2和科大讯飞的文字转语音TTS入门教程 1.环境搭建 2.创建工程 3.编译和执行 基于ROS2和科大讯飞的文字转语音TTS入门教程 本文将展示, ...

  2. python 语音转文字_python使用科大讯飞语音合成文字转语音

    #讯飞文字转语音 import base64 import json import time import hashlib import urllib.request import urllib.pa ...

  3. 使用(科大讯飞)文字转语音播放

    1.在科大讯飞上注册账号,创建项目,获取appid 2.添加对应的jar包 Msc.jar.Sunflower.jar 3.配置权限 <uses-permission android:name= ...

  4. 科大讯飞实现“文字转语音”和“语音转文字”

    请在这里查看示例 ☞ iat示例 详细介绍 这里整合了科大讯飞官方示例,去除一些不必要的元素,便于开发者理解和应用相关功能 经测试,在chrome浏览器下不允许在http协议下使用html5的api ...

  5. 语音转写基于科大讯飞WebApi接口的安卓实现--上传录音音频翻译成文字

    一.目的与实现过程 1.目的:将.wav/.mp3音频文件翻译成文字 2.方式:基于科大讯飞语音转写 WebApi的安卓实现 3.机制:采用自定义计时器轮询. 4.坑点1:科大讯飞当前暂无安卓文档/代 ...

  6. android 融云 + 科大讯飞 实现仿微信语音消息转换为文字(附DEMO源码)

    融云SDK 使用很方便,简单配置就可以搭建即时通讯功能,配合科大讯飞的语音识别, 即可实现微信中语音消息转换为文字的功能 融云sdk的基本使用就不细说了, 网上很多资料 使用融云sdk自带的聊天会话界 ...

  7. 基于FlashWavRecorder实现IE11浏览器录音后用科大讯飞转文字

    终于折腾好了(使用FlashWavRecorder实现IE11浏览器录音后用科大讯飞转文字,我的是Vue.js项目),对一个前端仔来说,听到要兼容IE,都是不情愿的,但是需求来了,那就折腾起来吧.(主 ...

  8. Android百度语音集成——文字转语音

    项目涉及文字转语音的需求,用Android原生提供的TTS生成的语音太单调,机器声音太明显,故寻求第三方更好的支持,用科大讯飞的语音包收费,百度语音免费而且不限制调用次数,主页鲜明说永久免费的智能语音 ...

  9. Android 文字转语音2种方式

    今天在开发中做了一个文字转为语音的功能,入了很多坑. 首先我采用的是科大讯飞的在线语音合成技术.写完之后他可以在低版本的手机上读出来,但是再高版本的7.0以上就读不出来,都听不懂再读什么. 下面贴出我 ...

最新文章

  1. arnold官方帮助文档_Baklib-随时编辑随时发布的在线帮助文档制作平台
  2. Nignx平滑升级(1.8.0-1.8.1)
  3. sap Status状态栏设计
  4. python做后端开发的优点_【后端开发】python语言的特点是什么
  5. jqgrid treegrid 重新加载数据
  6. Class类和Object类及用法(一)
  7. Android使用百度翻译api
  8. h5上下滑动时页面出现抖动问题解决
  9. Gbase相关学习总结
  10. live2d_Live2D | CubismSdkForUnity4r.1简介(下)
  11. Windows 10 下载官方正版ISO镜像文件
  12. Steam软件联网问题解决方案
  13. linux 硬盘开启apm,硬盘 APM 设置
  14. AUTOCAD——减少样条曲线控制点数、CAD进阶练习(三)
  15. 笔划码、五笔码、拼音码软键盘中文输入
  16. kylinserverv10部署dm8单实例命令行方式安装
  17. 福利群怎么引流?微信群引流技巧
  18. Java实现Apriori算法
  19. 卡尔曼滤波器、扩展卡尔曼滤波器、无向卡尔曼滤波器的详细推导
  20. vue中destroyed方法的使用

热门文章

  1. 使用Neo4j和Java进行大数据分析 第1部分
  2. 暧昧散场,穿过你的黑发我的手578
  3. [树状数组]飘雪圣域
  4. 表格锁定表头,并且表头宽度跟内容宽度对齐
  5. DIY定制你个人专属锁屏,将手机锁屏密码设置成文字、表情、照片
  6. 【附源码】计算机毕业设计java疫情下校园食品安全信息管理设计与实现
  7. 国庆当天打卡北京拍飞机网红地:西湖园
  8. 元气森林被薅羊毛损失200多万,官方店铺发公告恳请用户退款
  9. 如何爬取王者荣耀高清壁纸(详细分析教程)
  10. 喜欢玩王者荣耀的有福了,用 Python 获取英雄皮肤壁纸