科大讯飞webAPI文字转语音
可能会遇到的坑
自行了解 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_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)
-
})
-
}
-
export default class TTSRecorder {
-
constructor({
-
speed = 20,
-
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',
-
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.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)
-
// window.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()
-
}
-
}
transcode.worker.js代码(科大讯飞demo里面的,但是稍作修改 语音合成(流式版)WebAPI 文档 | 讯飞开放平台文档中心)
-
/*
-
* @Autor: lycheng
-
* @Date: 2020-01-13 16:12:22
-
*/
-
let minSampleRate = 22050
-
self.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 * 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(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(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 = this
-
ttsRecorder.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
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhffhkge
系列文章
更多
同类精品
更多
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
excel下划线不显示怎么办
PHP中文网 06-23 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
photoshop蒙版画笔没反应怎么办
PHP中文网 06-24