1.准备工具文件
新建base64js.js文件到项目中,照着复制就行了,
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? module.exports = factory(global)
: typeof define === 'function' && define.amd
? define(factory) : factory(global)
}((
typeof self !== 'undefined' ? self
: typeof window !== 'undefined' ? window
: typeof global !== 'undefined' ? global
: this
), function(global) {
'use strict';
// existing version for noConflict()
global = global || {};
var _Base64 = global.Base64;
var version = "2.5.1";
// if node.js and NOT React Native, we use Buffer
var buffer;
if (typeof module !== 'undefined' && module.exports) {
try {
buffer = eval("require('buffer').Buffer");
} catch (err) {
buffer = undefined;
}
}
// constants
var b64chars
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var b64tab = function(bin) {
var t = {};
for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
return t;
}(b64chars);
var fromCharCode = String.fromCharCode;
// encoder stuff
var cb_utob = function(c) {
if (c.length < 2) {
var cc = c.charCodeAt(0);
return cc < 0x80 ? c
: cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
+ fromCharCode(0x80 | (cc & 0x3f)))
: (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
+ fromCharCode(0x80 | ( cc & 0x3f)));
} else {
var cc = 0x10000
+ (c.charCodeAt(0) - 0xD800) * 0x400
+ (c.charCodeAt(1) - 0xDC00);
return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
+ fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
+ fromCharCode(0x80 | ( cc & 0x3f)));
}
};
var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
var utob = function(u) {
return u.replace(re_utob, cb_utob);
};
var cb_encode = function(ccc) {
var padlen = [0, 2, 1][ccc.length % 3],
ord = ccc.charCodeAt(0) << 16
| ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
| ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
chars = [
b64chars.charAt( ord >>> 18),
b64chars.charAt((ord >>> 12) & 63),
padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
];
return chars.join('');
};
var btoa = global.btoa ? function(b) {
return global.btoa(b);
} : function(b) {
return b.replace(/[\s\S]{1,3}/g, cb_encode);
};
var _encode = buffer ?
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
? function (u) {
return (u.constructor === buffer.constructor ? u : buffer.from(u))
.toString('base64')
}
: function (u) {
return (u.constructor === buffer.constructor ? u : new buffer(u))
.toString('base64')
}
: function (u) { return btoa(utob(u)) }
;
var encode = function(u, urisafe) {
return !urisafe
? _encode(String(u))
: _encode(String(u)).replace(/[+\/]/g, function(m0) {
return m0 == '+' ? '-' : '_';
}).replace(/=/g, '');
};
var encodeURI = function(u) { return encode(u, true) };
// decoder stuff
var re_btou = new RegExp([
'[\xC0-\xDF][\x80-\xBF]',
'[\xE0-\xEF][\x80-\xBF]{2}',
'[\xF0-\xF7][\x80-\xBF]{3}'
].join('|'), 'g');
var cb_btou = function(cccc) {
switch(cccc.length) {
case 4:
var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
| ((0x3f & cccc.charCodeAt(1)) << 12)
| ((0x3f & cccc.charCodeAt(2)) << 6)
| (0x3f & cccc.charCodeAt(3)),
offset = cp - 0x10000;
return (fromCharCode((offset >>> 10) + 0xD800)
+ fromCharCode((offset & 0x3FF) + 0xDC00));
case 3:
return fromCharCode(
((0x0f & cccc.charCodeAt(0)) << 12)
| ((0x3f & cccc.charCodeAt(1)) << 6)
| (0x3f & cccc.charCodeAt(2))
);
default:
return fromCharCode(
((0x1f & cccc.charCodeAt(0)) << 6)
| (0x3f & cccc.charCodeAt(1))
);
}
};
var btou = function(b) {
return b.replace(re_btou, cb_btou);
};
var cb_decode = function(cccc) {
var len = cccc.length,
padlen = len % 4,
n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
| (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
| (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
| (len > 3 ? b64tab[cccc.charAt(3)] : 0),
chars = [
fromCharCode( n >>> 16),
fromCharCode((n >>> 8) & 0xff),
fromCharCode( n & 0xff)
];
chars.length -= [0, 0, 2, 1][padlen];
return chars.join('');
};
var _atob = global.atob ? function(a) {
return global.atob(a);
} : function(a){
return a.replace(/\S{1,4}/g, cb_decode);
};
var atob = function(a) {
return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, ''));
};
var _decode = buffer ?
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
? function(a) {
return (a.constructor === buffer.constructor
? a : buffer.from(a, 'base64')).toString();
}
: function(a) {
return (a.constructor === buffer.constructor
? a : new buffer(a, 'base64')).toString();
}
: function(a) { return btou(_atob(a)) };
var decode = function(a){
return _decode(
String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })
.replace(/[^A-Za-z0-9\+\/]/g, '')
);
};
var noConflict = function() {
var Base64 = global.Base64;
global.Base64 = _Base64;
return Base64;
};
// export Base64
global.Base64 = {
VERSION: version,
atob: atob,
btoa: btoa,
fromBase64: decode,
toBase64: encode,
utob: utob,
encode: encode,
encodeURI: encodeURI,
btou: btou,
decode: decode,
noConflict: noConflict,
__buffer__: buffer
};
// if ES5 is available, make Base64.extendString() available
if (typeof Object.defineProperty === 'function') {
var noEnum = function(v){
return {value:v,enumerable:false,writable:true,configurable:true};
};
global.Base64.extendString = function () {
Object.defineProperty(
String.prototype, 'fromBase64', noEnum(function () {
return decode(this)
}));
Object.defineProperty(
String.prototype, 'toBase64', noEnum(function (urisafe) {
return encode(this, urisafe)
}));
Object.defineProperty(
String.prototype, 'toBase64URI', noEnum(function () {
return encode(this, true)
}));
};
}
//
// export Base64 to the namespace
//
if (global['Meteor']) { // Meteor.js
Base64 = global.Base64;
}
// module.exports and AMD are mutually exclusive.
// module.exports has precedence.
if (typeof module !== 'undefined' && module.exports) {
module.exports.Base64 = global.Base64;
}
else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function(){ return global.Base64 });
}
// that's it!
return {Base64: global.Base64}
}));
用npm安装crypto-js xmldom这2个工具,将工具引入到页面里
const CryptoJS = require('crypto-js')
const Base64 = require('../../tools/base64js').Base64;//此路径根据自己的实际路径来
var DOMParser = require('xmldom').DOMParser;
2.初始化用到的变量及函数
const APPID = ''//填自己的,如果考虑安全问题的话,这些数据可以根据后台接口获取
const API_SECRET = ''//填自己的
const API_KEY = ''//填自己的
const recorderManager = wx.getRecorderManager()//初始化录音管理
let audioData = []//存储音频数据的数组
let socketTask = null
let handlerInterval = null
function getWebSocketUrl() {
console.log('准备开始上传:')
return new Promise((resolve, reject) => {
// 请求地址根据语种不同变化
var url = 'wss://ise-api.xfyun.cn/v2/open-ise'
var host = 'ise-api.xfyun.cn'
var apiKey = API_KEY
var apiSecret = API_SECRET
var date = new Date().toGMTString()
var algorithm = 'hmac-sha256'
var headers = 'host date request-line'
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/open-ise 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 =Base64.encode(authorizationOrigin)
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
resolve(url)
})
}
3.完整page代码
Page({
/**
* 页面的初始数据
*/
data: {
readText:null,
upReadText:null,
readyFilePath:null,
audioURL:'',
fluencyScore:0,//流畅度
accuracyScore:0,//准确度
integrityScore:0,//完整度
standardScore:0,
errorText:null,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
startUpRecord(){
let that = this
wx.showLoading({
title: '正在分析中…',
mask:true
})
getWebSocketUrl().then(( url)=>{
console.log('websocketurl:',url)
let newURL = encodeURI(url)
console.log('new websocketurl:',newURL)
socketTask = wx.connectSocket({
url: newURL,
})
socketTask.onOpen(()=>{
console.log('打开了socket')
that.webSocketSend()
})
socketTask.onMessage((e)=>{
// result 在这里做信息处理
console.log('收到了结果:',e)
that.result(e.data)
})
socketTask.onError((err)=>{
//结束录音
console.log('socket 出错:',err)
wx.hideLoading({
success: (res) => {
wx.showToast({
title: '语音评测出错!',
icon:'error'
})
},
})
})
socketTask.onClose(()=>{
// 结束录音
console.log('socket 关闭:')
})
})
},
toBase64(buffer) {
var binary = ''
var bytes = new Uint8Array(buffer)
var len = bytes.byteLength
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
// console.log('binary:',Base64.btoa(binary))
return Base64.btoa(binary)
},
// 向webSocket发送数据
webSocketSend() {
// console.log('开始发送数据',audioData)
let that = this
let audioDataUp = audioData.splice(0, 1)
var params = {
common: {
app_id:APPID,
},
business: {
category: 'read_sentence', // read_syllable/单字朗读,汉语专有 read_word/词语朗读 read_sentence/句子朗读 https://www.xfyun.cn/doc/Ise/IseAPI.html#%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B
rstcd: 'utf8',
group: 'pupil',
sub: 'ise',
ent: 'en_vip',
tte: 'utf-8',
cmd: 'ssb',
auf: 'audio/L16;rate=16000',
aus: 1,
aue: 'lame',
text: this.data.upReadText//用于评测的文本
},
data: {
status: 0,
encoding: 'raw',
data_type: 1,
data: that.toBase64(audioDataUp[0]),
},
}
console.log(JSON.stringify(params))
socketTask.send({data: JSON.stringify(params)})
handlerInterval = setInterval(() => {
// websocket未连接
if (!socketTask) {
clearInterval(handlerInterval)
return
}
// 最后一帧
if (audioData.length === 0) {
console.log('数据发送完毕')
socketTask.send(
{data:
JSON.stringify({
business: {
cmd: 'auw',
aus: 4,
aue: 'lame'
},
data: {
status: 2,
encoding: 'raw',
data_type: 1,
data: '',
},
})}
)
audioData = []
clearInterval(handlerInterval)
return false
}
audioDataUp = audioData.splice(0, 1)
// 中间帧
// console.log('audioDataUp:',audioDataUp[0])
socketTask.send(
{
data:
JSON.stringify({
business: {
cmd: 'auw',
aus: 2,
aue: 'lame'
},
data: {
status: 1,
encoding: 'raw',
data_type: 1,
data: that.toBase64(audioDataUp[0]),
},
})}
)
}, 40)
},
result(resultData) {
// 识别结束
let that = this
console.log('resultData:',resultData)
let jsonData = JSON.parse(resultData)
if(jsonData.code ===48195||jsonData.code ==="48195"){
wx.showModal({
content:'上传到科大讯飞的评测文本有问题!',
showCancel:false
})
}
if (jsonData.data && jsonData.data.data) {
let data = Base64.decode(jsonData.data.data)
console.log('xml>Data:',data)
const doc=new DOMParser().parseFromString(data,'text/xml');
let sentence = doc.getElementsByTagName("read_chapter")[0]
let theSentence = sentence.getElementsByTagName('sentence') ||[]
console.log('theSentence:',theSentence)
let resultStr = ''
for(let i1=0;i1<theSentence.length;i1++){
let item = theSentence[i1]
let theWord = item.getElementsByTagName('word')||[]
if(theWord.length>0){
for(let i2=0;i2<theWord.length;i2++){
let wt = theWord[i2]
let flag = false
let theSyll = wt.getElementsByTagName('syll')||[]
let flagBools = []
if(theSyll.length>0){
for(let i3=0;i3<theSyll.length;i3++){
let pt = theSyll[i3]
let serr_msg = pt.getAttribute('serr_msg')
if(Number(serr_msg)!=0){
flag = true
}
}
}
// flag = flagBools.findIndex(true)
let wContent = wt.getAttribute('content')
if (flag) {
if(wContent!='sil'){
resultStr += `<i class="err">${wContent}</i>`
}
} else {
if(wContent!='sil'){
resultStr += `<i class="normal-res">${wContent}</i>`
}
}
}
}
}
console.log("resultStr:",resultStr)//此处为返回结果的富文本,可以用 rich-text直接展示,err normal-res 两个类需要自己定义样式
console.log('sentence:',sentence)
let accuracy_score = sentence.getAttribute("accuracy_score")//准确度
let fluency_score = sentence.getAttribute("fluency_score")//流畅度
let integrity_score = sentence.getAttribute("integrity_score")//完整度
let standard_score = sentence.getAttribute("standard_score")//标准度
let total_score = sentence.getAttribute("total_score")//总分
let is_rejected = sentence.getAttribute("is_rejected")//是否乱读
if(is_rejected===false||is_rejected==='false'){
//乱读,不算入次数,
}else{
}
}
if (jsonData.code === 0 && jsonData.data.status === 2) {
// this.webSocket.close()
// 在这里结束socket
socketTask.close()
}
if (jsonData.code !== 0) {
// this.webSocket.close()
socketTask.close()
console.log(`${jsonData.code}:${jsonData.message}`)
}
},
readyForXF(){
this.startUpRecord()
},
//开始录音
startVoiceRecord(){
if(this.data.isPlaying){
return
}
let that = this
that.setData({recordState:'recording'})
recorderManager.onStart(() => {
console.log('recorder start')
that.countDownStart()
})
recorderManager.onPause(() => {
console.log('recorder pause')
})
recorderManager.onStop((res) => {
console.log('recorder stop', res)
const { tempFilePath } = res
console.log('录音结束:',tempFilePath)
// that.readyForUp(tempFilePath)//此方法可将录音文件上传到自己服务器或者云存储
that.readyForXF()
// that.testBuffer(tempFilePath)
that.setData({recordState:'ready',readyFilePath:tempFilePath})
that.countDownReset()
})
recorderManager.onFrameRecorded((res) => {
const { frameBuffer } = res
console.log('frameBuffer.byteLength', frameBuffer.byteLength)
let u8Arr = new Uint8Array(frameBuffer)
audioData.push(u8Arr)
})
const options = {
duration: 180000,
sampleRate: 16000,
numberOfChannels: 1,
encodeBitRate: 44100,
frameSize: 2,
format: 'mp3',
}
recorderManager.start(options)
},
//结束录音
stopVoiceRecord(){
recorderManager.stop()
},
countDownStart() {
const countDown = this.selectComponent('.control-count-down');
countDown.start();
},
countDownPause() {
const countDown = this.selectComponent('.control-count-down');
countDown.pause();
},
countDownReset() {
const countDown = this.selectComponent('.control-count-down');
countDown.reset();
},
//倒计时结束 停止录音
onTimeCountfinished(){
recorderManager.stop()
},
readyForUp(filePath){
// 自定义上传文件方法,可将音频文件上传到自己服务器,或者oss
},
})
主要用到的页面上的控制组件贴出来了,可以完成流程验证
<van-icon bindtap="startVoiceRecord" size="50px" name="play-circle-o" color="#004EFF"/>
<van-icon bindtap="stopVoiceRecord" size="50px" name="stop-circle-o" color="#004EFF"/>
<van-count-down
class="control-count-down"
millisecond
time="{{ 180000 }}"
auto-start="{{ false }}"
format="mm:ss"
bind:finish="onTimeCountfinished"
/>
主要代码就这些,祝大家早日调试成功!
调用完api 反馈的结果如下:
"{"header":{"code":0,"message":"success","sid":"iat000e3286@dx1934f73c47bb93f882","status":2},"payload":{"result":{"compress":"raw","encoding":"utf8","format":"json","seq":1,"status":2,"text":"eyJzbiI6MSwibHMiOnRydWUsImJnIjowLCJlZCI6MCwid3MiOlt7ImJnIjowLCJjdyI6W3sic2MiOjAuMDAsInciOiIifV19XX0="}}}"
你好,我最近调用了大模型多语种语音识别 API ,微信小程序录音,按照api要求发送并接受结果,但是将 API 返回的 Base64 字符串转换为 UTF-8 文本时遇到了一些问题,我是这样写的,麻烦你帮我看看, 这是socket 接受的反馈信息,
result(resultData) {
try {
// 检查 resultData 是否为字符串
if (typeof resultData === 'string') {
// 如果是字符串,则解析为对象
resultData = JSON.parse(resultData);
}
var temp = JSON.parse(resultData.data);
console.log('收到的数据:', temp.payload.result.text); // 打印解析后的对象
var decodedString = base64.decode(temp.payload.result.text);
console.log('解码后结果:', decodedString); // 打印解析后的对象
const utf8Text = this.base64ToUtf8(decodedString);
console.log(utf8Text);
} catch (error) {
console.error('JSON 解析错误:', error);
}
},
base64ToUtf8: function (base64String) {
// 解码 Base64 字符串
const binaryString = base64.decode(base64String);
// 创建一个 Uint8Array 来存储二进制数据
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
// 使用 TextDecoder 将 Uint8Array 转换为 UTF-8 字符串
const decoder = new TextDecoder('utf-8');
const utf8String = decoder.decode(bytes);
return utf8String;
},但是还是报错