????????在移動應用開發中,語音交互功能能夠極大提升用戶體驗,讓操作更加便捷自然。訊飛語音聽寫技術憑借其高準確率和穩定性,成為眾多開發者的選擇。本文將詳細介紹如何在 Uniapp 項目中,實現安卓端的訊飛語音聽寫功能,幫助你快速為應用增添實用的語音交互能力。
????????但是,由于uniapp錄音管理器?uni.getRecorderManager()的實時監聽音頻大小幀的功能onFrameRecorded不支持app,app端只能在錄音結束后獲取臨時錄音文件,因此需要把錄音文件轉成base64,在切片傳輸給訊飛的接口。
一、準備工作?
1. 注冊訊飛開放平臺賬號?
首先,你需要前往訊飛開放平臺注冊賬號,完成實名認證。認證通過后,你將獲得使用訊飛相關服務的權限。?
2. 創建應用并獲取 AppID 和密鑰?
在訊飛開放平臺控制臺中,創建一個新的應用。創建成功后,你會得到該應用的 AppID、AppKey 和 AppSecret,這些信息在后續集成過程中至關重要,用于驗證應用身份。?
二、實現代碼
以下是已經實現的uniapp在app端的一個簡單的利用訊飛語音聽寫api完成的語音識別的demo,復制代碼到你的項目中,把你申請的appid、apiSecret、apiKey替換到代碼中,即可運行識別。
<template><div class="asr"><button class="btn" shape="circle" type="info" @touchstart="openMedia" @touchend="stopMedia">按住說話</button><view v-if="show" class="iating"><text text="正在說話中...." color="#49ABFE" size="27rpx" line-height="60rpx" align="center"></text></view></div>
</template><script>
import CryptoJS from "crypto-js";export default {data() {return {config: {hostUrl: "wss://iat-api.xfyun.cn/v2/iat",host: "iat-api.xfyun.cn",appid: "申請的訊飛appid",apiSecret: "申請的訊飛apiSecret",apiKey: "申請的apiKey",uri: "/v2/iat",highWaterMark: 1280,},uniSocketTask: null,show: false,resultText: "",resultTextTemp: "",renderText: ""};},methods: {// 鑒權簽名getAuthStr(date) {let signatureOrigin = `host: ${this.config.host}\ndate: ${date}\nGET ${this.config.uri} HTTP/1.1`;let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.config.apiSecret);let signature = CryptoJS.enc.Base64.stringify(signatureSha);let authorizationOrigin =`api_key="${this.config.apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`;let authStr = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(authorizationOrigin));return authStr;},getUrl() {let date = new Date().toUTCString();let wssUrl =this.config.hostUrl +"?authorization=" +this.getAuthStr(date) +"&date=" +encodeURIComponent(date) +"&host=" +this.config.host;console.log("websocke科大訊飛的地址為", wssUrl);return wssUrl;},// 打開麥克風openMedia() {this.connectSocket();},// 創建連接并返回數據connectSocket() {if (this.uniSocketTask === null) {this.uniSocketTask = uni.connectSocket({url: this.getUrl(),success() {},});this.uniSocketTask.onOpen(() => {console.log("監聽到開啟連接成功");this.startRecord();});this.uniSocketTask.onClose(() => {console.log("監聽到關閉連接成功");this.uniSocketTask = null;});this.uniSocketTask.onError(() => {console.log("監聽到連接發生錯誤");});this.uniSocketTask.onMessage((res) => {const message = JSON.parse(res.data);if (res.data) {console.log("收到服務器消息,并開始渲染", message);this.renderResult(message);if (message.code === 0 && message.data.status === 2) {console.log('最后一條', this.renderText);this.closeSocket();// 注意:這里需要根據實際情況處理事件觸發// this.$emit('renderText', this.renderText);}if (message.code !== 0) {this.closeSocket();console.error(message);}} else {console.log("未監聽到消息:原因:", JSON.stringify(res));}});} else {console.log("socketTask實例已存在");}},// 發送給科大訊飛的第一幀的模板數據格式getInitialFrame() {return {common: {app_id: this.config.appid,},business: {language: "zh_cn",domain: "iat",accent: "mandarin",dwa: "wpgs", // 可選參數,動態修正vad_eos: 5000,},data: {status: 0,format: "audio/L16;rate=16000",encoding: "lame"},};},// 發送消息sendMessage(sendData) {console.log('發送', JSON.stringify(sendData));this.uniSocketTask.send({data: JSON.stringify(sendData),success() {},fail() {console.log("發送失敗");},});},// 關閉連接closeSocket() {console.log("開始嘗試關閉連接");this.uniSocketTask.close();},// 開啟錄音startRecord() {const recordOption = {sampleRate: 16000,format: "mp3",};const recordManager = uni.getRecorderManager();recordManager.onStart(() => {console.log("開始錄音");this.show = true;});recordManager.onStop((res) => {console.log("錄音停止,文件路徑為:", res.tempFilePath);this.sendMessage(this.getInitialFrame());this.pathToBase64(res.tempFilePath).then(base64 => {let buff = base64.split(",")[1];const arrayBuffer = uni.base64ToArrayBuffer(buff);const audioString = this.toString(arrayBuffer);console.log("文件讀取成功", audioString.length);let offset = 0;while (offset < audioString.length) {const subString = audioString.substring(offset, offset + 1280);offset += 1280;const isEnd = offset >= audioString.length;let params = {data: {status: isEnd ? 2 : 1,format: "audio/L16;rate=16000",encoding: "lame",audio: btoa(subString)}};this.sendMessage(params);}}).catch(error => {console.error(error);});});recordManager.onError((err) => {console.log("錄音出現錯誤", err);});recordManager.start(recordOption);},// 關閉錄音stopMedia() {const recordManager = uni.getRecorderManager();recordManager.stop();this.show = false;},// 工具方法toString(buffer) {var binary = '';var bytes = new Uint8Array(buffer);var len = bytes.byteLength;for (var i = 0; i < len; i++) {binary += String.fromCharCode(bytes[i]);}return binary;},// 錄音文件路徑轉base64pathToBase64(path) {return new Promise((resolve, reject) => {if (typeof plus === 'object') {plus.io.resolveLocalFileSystemURL(path, function(entry) {entry.file(function(file) {var fileReader = new plus.io.FileReader();fileReader.onload = function(evt) {resolve(evt.target.result);};fileReader.onerror = function(error) {reject(error);};fileReader.readAsDataURL(file);}, function(error) {reject(error);});}, function(error) {reject(error);});return;}reject(new Error('not support'));});},// 訊飛回復字段拼接renderResult(jsonData) {if (jsonData.data && jsonData.data.result) {let data = jsonData.data.result;let str = "";let ws = data.ws;for (let i = 0; i < ws.length; i++) {str = str + ws[i].cw[0].w;}if (data.pgs) {if (data.pgs === "apd") {this.resultText = this.resultTextTemp;}this.resultTextTemp = this.resultText + str;} else {this.resultText = this.resultText + str;}this.renderText = this.resultTextTemp || this.resultText || "";}console.log("渲染后的數據為", this.renderText);}}
}
</script><style scoped>
.asr {display: flex;justify-content: center;padding: 20rpx;
}.btn {width: 200rpx;height: 200rpx;
}.iating {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);display: flex;flex-direction: column;align-items: center;background-color: rgba(0, 0, 0, 0.7);padding: 30rpx;border-radius: 20rpx;z-index: 999;
}</style>