如何在uni-app使用微軟的文字轉語音服務
前言
嘗試過各種TTS的方案,一番體驗下來,發(fā)現(xiàn)微軟才是這個領域的王者,其Azure文本轉語音服務的轉換出的語音效果最為自然,但Azure是付費服務,注冊操作付費都太麻煩了。但在其官網(wǎng)上竟然提供了一個完全體的演示功能,能夠完完整整的體驗所有角色語音,說話風格...

但就是不能下載成mp3文件,所以有一些小伙伴逼不得已只好通過轉錄電腦的聲音來獲得音頻文件,但這樣太麻煩了。其實,能在網(wǎng)頁里看到聽到的所有資源,都是解密后的結果。也就是說,只要這個聲音從網(wǎng)頁里播放出來了,我們必然可以找到方法提取到音頻文件。
本文就是記錄了這整個探索實現(xiàn)的過程,請盡情享用~
本文大部分內容寫于今年年初一直按在手里未發(fā)布,我深知這個方法一旦公之于眾,可能很快會迎來微軟的封堵,甚至直接取消網(wǎng)頁體驗的入口和相關接口。
解析Azure官網(wǎng)的演示功能
使用Chrome瀏覽器打開調試面板,當我們在Azure官網(wǎng)中點擊播放功能時,可以從network標簽中監(jiān)控到一個wss://的請求,這是一個websocket的請求。

兩個參數(shù)
在請求的URL中,我們可以看到有兩個參數(shù)分別是Authorization和X-ConnectionId

有意思的是,第一個參數(shù)就在網(wǎng)頁的源碼里,使用axios對這個Azure文本轉語音的網(wǎng)址發(fā)起get請求就可以直接提取到

const res = await axios.get("https://azure.microsoft.com/en-gb/services/cognitive-services/text-to-speech/");
const reg = /token: \"(.*?)\"/;
if(reg.test(res.data)){
const token = RegExp.$1;
}通過查看發(fā)起請求的JS調用棧,加入斷點后再次點擊播放


可以發(fā)現(xiàn)第二個參數(shù)X-ConnectionId來自一個createNoDashGuid的函數(shù)
this.privConnectionId = void 0 !== t ? t : s.createNoDashGuid(),
這就是一個uuid v4格式的字符串,nodash就是沒有-的意思。
三次發(fā)送
請求時URL里的兩個參數(shù)已經搞定了,我們繼續(xù)分析這個webscoket請求,從Message標簽中可以看到

每次點擊播放時,都向服務器上報了三次數(shù)據(jù),明顯可以看出來三次上報數(shù)據(jù)各自的作用
第一次的數(shù)據(jù):SDK版本,系統(tǒng)信息,UserAgent
Path: speech.config
X-RequestId: 818A1E398D8D4303956D180A3761864B
X-Timestamp: 2022-05-27T16:45:02.799Z
Content-Type: application/json
{"context":{"system":{"name":"SpeechSDK","version":"1.19.0","build":"JavaScript","lang":"JavaScript"},"os":{"platform":"Browser/MacIntel","name":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36","version":"5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36"}}}第二次的數(shù)據(jù):轉語音輸出配置,從outputFormat可以看出來,最終的音頻格式為audio-24khz-160kbitrate-mono-mp3,這不就是我們想要的mp3文件嗎?!
Path: synthesis.context
X-RequestId: 091963E8C7F342D0A8E79125EA6BB707
X-Timestamp: 2022-05-27T16:48:43.340Z
Content-Type: application/json
{"synthesis":{"audio":{"metadataOptions":{"bookmarkEnabled":false,"sentenceBoundaryEnabled":false,"visemeEnabled":false,"wordBoundaryEnabled":false},"outputFormat":"audio-24khz-160kbitrate-mono-mp3"},"language":{"autoDetection":false}}}第三次的數(shù)據(jù):要轉語音的文本信息和角色voice name,語速rate,語調pitch,情感等配置
Path: ssml X-RequestId: 091963E8C7F342D0A8E79125EA6BB707 X-Timestamp: 2022-05-27T16:48:49.594Z Content-Type: application/ssml+xml <speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US"><voice name="zh-CN-XiaoxiaoNeural"><prosody rate="0%" pitch="0%">我叫大帥,一個熱愛編程的老程序猿</prosody></voice></speak>
接收的二進制消息
既然從前三次上報的信息已經看出來返回的格式就是mp3文件了,那么我們是不是把所有返回的二進制數(shù)據(jù)合并就可以拼接成完整的mp3文件了呢?答案是肯定的!
每次點擊播放后接收的所有來自websocket的消息的最后一條,都有明確的結束標識符


turn.end代表轉換結束!
用Node.js實現(xiàn)它
既然都解析出來了,剩下的就是在Node.js中重新實現(xiàn)這個過程。
兩個參數(shù)
- Authorization,直接通過axios的get請求抓取網(wǎng)頁內容后通過正則表達式提取
const res = await axios.get("https://azure.microsoft.com/en-gb/services/cognitive-services/text-to-speech/");
const reg = /token: \"(.*?)\"/;
if(reg.test(res.data)){
const Authorization = RegExp.$1;
}- X-ConnectionId,直接使用
uuid庫即可
//npm install uuid
const { v4: uuidv4 } = require('uuid');
const XConnectionId = uuidv4().toUpperCase();創(chuàng)建WebSocket連接
//npm install nodejs-websocket
const ws = require("nodejs-websocket");
const url = `wss://eastus.tts.speech.microsoft.com/cognitiveservices/websocket/v1?Authorization=${Authorization}&X-ConnectionId=${XConnectionId}`;
const connect = ws.connect(url);三次發(fā)送
第一次發(fā)送
function getXTime(){
return new Date().toISOString();
}
const message_1 = `Path: speech.config\r\nX-RequestId: ${XConnectionId}\r\nX-Timestamp: ${getXTime()}\r\nContent-Type: application/json\r\n\r\n{"context":{"system":{"name":"SpeechSDK","version":"1.19.0","build":"JavaScript","lang":"JavaScript","os":{"platform":"Browser/Linux x86_64","name":"Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0","version":"5.0 (X11)"}}}}`;
connect.send(message_1);第二次發(fā)送
const message_2 = `Path: synthesis.context\r\nX-RequestId: ${XConnectionId}\r\nX-Timestamp: ${getXTime()}\r\nContent-Type: application/json\r\n\r\n{"synthesis":{"audio":{"metadataOptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":false},"outputFormat":"audio-16khz-32kbitrate-mono-mp3"}}}`;
connect.send(message_2);第三次發(fā)送
const SSML = `
<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
<voice name="zh-CN-XiaoxiaoNeural">
<mstts:express-as style="general">
<prosody rate="0%" pitch="0%">
我叫大帥,一個熱愛編程的老程序猿
</prosody>
</mstts:express-as>
</voice>
</speak>
`
const message_3 = `Path: ssml\r\nX-RequestId: ${XConnectionId}\r\nX-Timestamp: ${getXTime()}\r\nContent-Type: application/ssml+xml\r\n\r\n${SSML}`
connect.send(message_3);接收二進制消息拼接mp3
當三次發(fā)送結束后我們通過connect.on('binary')監(jiān)聽websocket接收的二進制消息。
創(chuàng)建一個空的Buffer對象final_data,然后將每一次接收到的二進制內容拼接到final_data里,一旦監(jiān)聽到普通文本消息中包含Path:turn.end標識時則將final_data寫入創(chuàng)建一個mp3文件中。
let final_data=Buffer.alloc(0);
connect.on("text", (data) => {
if(data.indexOf("Path:turn.end")>=0){
fs.writeFileSync("test.mp3",final_data);
connect.close();
}
})
connect.on("binary", function (response) {
let data = Buffer.alloc(0);
response.on("readable", function () {
const newData = response.read()
if (newData)data = Buffer.concat([data, newData], data.length+newData.length);
})
response.on("end", function () {
const index = data.toString().indexOf("Path:audio")+12;
final_data = Buffer.concat([final_data,data.slice(index)]);
})
});這樣我們就成功的保存出了mp3音頻文件,連Azure官網(wǎng)都不用打開!
命令行工具
我已經將整個代碼打包成一個命令行工具,使用非常簡單
npm install -g mstts-js mstts -i 文本轉語音 -o ./test.mp3
已全部開源: github.com/ezshine/mst…
在uni-app中使用
新建一個云函數(shù)
新建一個云函數(shù),命名為mstts

由于mstss-js已經封裝好了,只需要在云函數(shù)中npm install mstts-js然后require即可,代碼如下
'use strict';
const mstts = require('mstts-js')
exports.main = async (event, context) => {
const res = await mstts.getTTSData('要轉換的文本','CN-Yunxi');
//res為buffer格式
});下載播放mp3文件
要在uniapp中播放這個mp3格式的文件,有兩種方法
方法1. 先上傳到云存儲,通過云存儲地址訪問
exports.main = async (event, context) => {
const res = await mstts.getTTSData('要轉換的文本','CN-Yunxi');
//res為buffer格式
var uploadRes = await uniCloud.uploadFile({
cloudPath: "xxxxx.mp3",
fileContent: res
})
return uploadRes.fileID;
});前端用法:
uniCloud.callFunction({
name:"mstts",
success:(res)=>{
const aud = uni.createInnerAudioContext();
aud.autoplay = true;
aud.src = res;
aud.play();
}
})- 優(yōu)點:云函數(shù)安全
- 缺點:文件上傳到云存儲不做清理機制的話會浪費空間
方法2. 利用云函數(shù)的URL化+集成響應來訪問
這種方法就是直接將云函數(shù)的響應體變成一個mp3文件,直接通過audio.src賦值即可訪問`
exports.main = async (event, context) => {
const res = await mstts.getTTSData('要轉換的文本','CN-Yunxi');
return {
mpserverlessComposedResponse: true,
isBase64Encoded: true,
statusCode: 200,
headers: {
'Content-Type': 'audio/mp3',
'Content-Disposition':'attachment;filename=\"temp.mp3\"'
},
body: res.toString('base64')
}
};前端用法:
const aud = uni.createInnerAudioContext(); aud.autoplay = true; aud.src = 'https://ezshine-274162.service.tcloudbase.com/mstts'; aud.play();
- 優(yōu)點:用起來很簡單,無需保存文件到云存儲
- 缺點:URL化后的云函數(shù)如果沒有安全機制,被抓包后可被其他人肆意使用
小結
這么好用的tts庫,如果對你有所幫助別忘了在github里點個star支持一下。
總結
到此這篇關于如何在uni-app使用微軟的文字轉語音服務的文章就介紹到這了,更多相關uni-app文字轉語音服務內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
BootStrap.css 在手機端滑動時右側出現(xiàn)空白的原因及解決辦法
這篇文章主要介紹了BootStrap.css 在手機端滑動時右側出現(xiàn)空白的原因及解決辦法的相關資料,非常不錯具有參考借鑒價值,需要的朋友可以參考下2016-06-06
JavaScript?數(shù)據(jù)結構之集合創(chuàng)建(2)
這篇文章主要介紹了JavaScript?數(shù)據(jù)結構之集合創(chuàng)建,上一篇我們介紹了什么是集合,并且手動實現(xiàn)了一個集合的類,本篇基于上篇內容繼續(xù)深入介紹需要的小伙伴可以參考一下2022-04-04
微信小程序獲取用戶信息的兩種方法wx.getUserInfo與open-data實例分析
這篇文章主要介紹了微信小程序獲取用戶信息的兩種方法wx.getUserInfo與open-data,結合實例形式分析了wx.getUserInfo與open-data獲取用戶信息的相關操作技巧與使用注意事項,需要的朋友可以參考下2019-05-05
JS實現(xiàn)動態(tài)生成html table表格的方法分析
這篇文章主要介紹了JS實現(xiàn)動態(tài)生成html table表格的方法,結合實例形式分析了javascript針對數(shù)組數(shù)據(jù)的讀取、遍歷以及動態(tài)生成相關操作技巧,需要的朋友可以參考下2018-07-07
ES6入門教程之Iterator與for...of循環(huán)詳解
最近在學習ES6,剛剛看到Iterator和for...of循環(huán)這一章,所以想要跟大家略微分享一下,下面這篇文章主要給大家介紹了關于ES6入門學習中Iterator與for...of循環(huán)的相關資料,不足之處還望大家多多指正,需要的朋友們可以參考學習。2017-05-05

