参考文档:语音合成(流式版)WebAPI 文档
微信小程序背景音频播报可参考:uniapp实现微信小程序websocket+背景音频语音播报
实现步骤
- 注册讯飞账号,完成实名认证,获取应用appid
- 连接讯飞websocket,在握手阶段,请求方需要对请求进行签名
- 发送请求文本,讯飞服务器返回合成后的音频片段,采用base64编码
- 将返回的音频片段组合,并转成可播放的MP3文件
1.注册账号,并获取应用appid
打开官网网址:讯飞开放平台-语音合成 点击免费试用,按步骤完成实名认证后得到appid
2.连接讯飞websocket,在握手阶段,请求方需要对请求进行签名
async getWebsocketUrl() {
// 合成讯飞语音请求地址
const that = this;
return await new Promise((resolve, reject) => {
var apiKey = API_KEY; // 讯飞控制台查看
var apiSecret = API_SECRET; // 讯飞控制台查看
var url = 'wss://tts-api.xfyun.cn/v2/tts';
var host = BASEURL; // 请求的主机地址
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 = that.base64_encode(authorizationOrigin);
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
resolve(url);
});
}
async linkXunfeiSocket() {
// 连接讯飞Socket服务器
let url = await this.getWebsocketUrl();
let XunfeiSocketTask;
await new Promise((resolve, reject) => {
XunfeiSocketTask = uni.connectSocket({
url: encodeURI(url),
header: {
'content-type': 'application/json'
},
success: () => {
console.log(`讯飞 connect成功`);
resolve();
},
fail: () => {
console.log('讯飞 connect失败');
reject();
}
});
});
this.xunfeiSocketTask = XunfeiSocketTask;
this.initXunFei(this.xunfeiSocketTask);
}
initXunFei(xunfeiSocketTask) {
// 监听消息
xunfeiSocketTask.onMessage(res => {
// console.log('接收讯飞消息');
this.result(res.data);
});
xunfeiSocketTask.onOpen(() => {
// console.log('讯飞websocket打开');
this.webSocketSend();
});
xunfeiSocketTask.onClose(res => {
clearTimeout(this.playTimeout);
// console.error('讯飞断开');
});
xunfeiSocketTask.onError(err => {
// console.error('讯飞连接错误', err);
});
}
注意:
(1)signature_sha:hmac-sha256算法结合apiSecret对signature_origin签名,使用crypto-js实现
// 安装
npm install crypto-js
yarn add crypto-js
// 引用
import CryptoJS from 'crypto-js';
(2)signature:需要使用CryptoJS.enc.Base64.stringify()方式编码后使用
(3)authorization:需要base64编码后使用,编码方式如下(等同于Window.btoa())
base64_encode(str) {
// 编码,配合encodeURIComponent使用
var c1, c2, c3;
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var i = 0,
len = str.length,
strin = '';
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff;
if (i == len) {
strin += base64EncodeChars.charAt(c1 >> 2);
strin += base64EncodeChars.charAt((c1 & 0x3) << 4);
strin += '==';
break;
}
c2 = str.charCodeAt(i++);
if (i == len) {
strin += base64EncodeChars.charAt(c1 >> 2);
strin += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
strin += base64EncodeChars.charAt((c2 & 0xf) << 2);
strin += '=';
break;
}
c3 = str.charCodeAt(i++);
strin += base64EncodeChars.charAt(c1 >> 2);
strin += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
strin += base64EncodeChars.charAt(((c2 & 0xf) << 2) | ((c3 & 0xc0) >> 6));
strin += base64EncodeChars.charAt(c3 & 0x3f);
}
return strin;
}
3.发送和接收数据
发送文本消息
webSocketSend() {
let params = {
common: {
app_id: APPID // 讯飞控制台查看
},
business: {
aue: 'lame', // 音频编码表示mp3格式,当aue=lame时需传参sfl=1
sfl: 1,
vcn: 'xiaoyan', // 发音人选择
tte: 'UTF8'
},
data: {
status: 2,
text: CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(this.toAudioText))
}
};
this.xunfeiSocketTask.send({
data: JSON.stringify(params),
success: res => {
// console.log('发出讯飞消息');
},
fail: err => {
// console.log('发出讯飞消息失败', err);
}
});
}
注意:
(1)发送的文本数据data的text需要使用base64编码
(2)send方法发送的数据需要转成json字符串
(3)返回的数据为合成后的音频片段,采用base64编码
4. 将返回的音频片段组合,并转成可播放的MP3文件
当code为0,data.status为1时,表示合并中,将base64字符串合并
当code为0,data.status为2时,表示一句话合并完成,在此处将合并的base64转成音频文件
result(resultData) {
let jsonData = JSON.parse(resultData);
//陆续合并接收的base64音频流
this.audioBase64 += jsonData.data.audio;
if (jsonData.code !== 0) {
// 合成失败
console.log(`${jsonData.code}:${jsonData.message}`);
return;
}
if (jsonData.code === 0 && jsonData.data.status === 2) {
// 分段发送完成,转成语音文件关闭连接
// 注意:将base64转化成ArrayBuffer
const data = uni.base64ToArrayBuffer(this.audioBase64);
const target = `${wx.env.USER_DATA_PATH}/${new Date().getTime()}.mp3`;
try {
// 使用fs.writeFileSync完成音频文件的转化
const res = fs.writeFileSync(target, data, 'binary');
this.playAudio.push(target);
this.xunfeiSocketTask.close();
this.audioBase64 = '';
} catch (e) {
console.error(e);
}
}
}
注意:使用fs.writeFileSync转文件时,传入的data为ArrayBuffer格式