首页 前端知识 科大讯飞webAPI文字转语音

科大讯飞webAPI文字转语音

2024-04-05 09:04:13 前端知识 前端哥 15 755 我要收藏

可能会遇到的坑

原文链接

自行了解 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

转载请注明出处或者链接地址:https://www.qianduange.cn//article/4479.html
标签
评论
发布的文章

java解析超大json文件数据

2024-04-19 21:04:10

头歌-JavaScript基础

2024-04-19 21:04:54

C#Json序列化及反序列化

2024-04-19 21:04:40

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!