最近微信小程序项目中,需要添加语音评测功能,就选用了科大讯飞的语音评测流式版接口,但在使用过程中,遇到了很多问题,再网上搜资料,搜了好多,也没直接能用的,好在后来参考了许多资料后,终于调试成功了,接下来,跟大家分享一下我是怎么处理的。
1.第一步,准备所用到的工具,下载官方jsdemo,将 base64js 文件复制到自己的小程序项目中,用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 = '替换成你自己的'
let audioData = [] //存储音频流的数组
let socketTask = null //小程序的socketTask
let handlerInterval = null // 定时器,用来定时发送数据流
function getWebSocketUrl() {//生成socket使用的url
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.开始录音
//开始录音
startVoiceRecord(){
let that = this
that.setData({recordState:'recording'})
recorderManager.onStart(() => {
console.log('recorder start')
})
recorderManager.onPause(() => {
console.log('recorder pause')
})
recorderManager.onStop((res) => {
console.log('recorder stop', res)
const { tempFilePath } = res
that.startUpRecord()//录音完成,准备调用讯飞接口
})
recorderManager.onFrameRecorded((res) => {
const { frameBuffer } = res
console.log('frameBuffer.byteLength', frameBuffer.byteLength)
let u8Arr = new Uint8Array(frameBuffer)
audioData.push(u8Arr) //将每一帧的数据取出,放到audioData中,准备使用
})
const options = {
duration: 180000,
sampleRate: 16000,
numberOfChannels: 1,
encodeBitRate: 44100,
frameSize: 2,
format: 'pcm',
}
recorderManager.start(options)
},
4. 开始socket连接,准备上传数据并处理
startUpRecord(){
let that = this
getWebSocketUrl().then(( url)=>{
let newURL = encodeURI(url)
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)
})
socketTask.onClose(()=>{
// 结束录音
console.log('socket 关闭:')
})
})
},
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: 'cn_vip',
tte: 'utf-8',
cmd: 'ssb',
auf: 'audio/L16;rate=16000',
aus: 1,
aue: 'raw',
text: '\uFEFF' + '今天天气怎么样?'
},
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: 'raw'
},
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: 'raw'
},
data: {
status: 1,
encoding: 'raw',
data_type: 1,
data: that.toBase64(audioDataUp[0]),
},
})}
)
}, 40)
},
result(resultData) {
// 识别结束
let jsonData = JSON.parse(resultData)
if (jsonData.data && jsonData.data.data) {
let data = Base64.decode(jsonData.data.data)
const doc=new DOMParser().parseFromString(data,'text/xml');
let sentence = doc.getElementsByTagName('read_sentence')[1]
let accuracy_score = sentence.getAttribute('accuracy_score')
let emotion_score = sentence.getAttribute('emotion_score')
let fluency_score = sentence.getAttribute('fluency_score')
let total_score = sentence.getAttribute('total_score')
let integrity_score = sentence.getAttribute('integrity_score')
let phone_score = sentence.getAttribute('phone_score')
let tone_score = sentence.getAttribute('tone_score')
let is_rejected = sentence.getAttribute('is_rejected')
console.log('parseRes:',accuracy_score,emotion_score,fluency_score,total_score)
//评测结果在这里,就出来了,然后就可以拿评测数据去使用了
}
if (jsonData.code === 0 && jsonData.data.status === 2) {
// 在这里结束socket
socketTask.close()
}
if (jsonData.code !== 0) {
socketTask.close()
console.log(`${jsonData.code}:${jsonData.message}`)
}
},
到这里,整个流程就完了,祝愿大家都能一次调用成功,有什么问题的话,咱们再讨论!
你好,问下返回结果中的xml中文部分是乱码,你们有没有遇到这种情况啊?
你好,访问科大讯飞的地址不会报错:“不在以下 socket 合法域名”吗?不需要配置安全域名?
您好,为什么参数business和data的结构与官网webapi文档中完全不一样,关键是我试了一下还能跑通,这些参数有什么文档介绍一下每个参数的作用吗?另外,business参数中的text: '\uFEFF' + '今天天气怎么样?'起什么作用?
这里只有开始录音,需要写一个结束录音吗?点击之后就一直再录音没有结束啊
recorderManager.onStop((res) => {
console.log('recorder stop', res)
const { tempFilePath } = res
that.readyForXF()//录音完成,准备调用讯飞接口
})
我发现在真机上,new Date().toGMTString()这个参数建立socket连接的时候会报错。好像是URL格式不对
请问这个必须在真机环境下测试吗?我用了上面的代码在模拟器里试 能正常发送 但测评结果都是0
我的项目也需要做语音评测,但是科大讯飞的这个评测结果分值太不准了。最近转战用智聆口语评测了,不知道后续评分会不会更准确一些
const APPID = '替换成你自己的' const API_SECRET = '替换成你自己的' const API_KEY = '替换成你自己的' 这些参数直接放前端真的好么?
只要你小程序源码自己保存好,这些东西问题不大。
后台调用这个也可以,具体看你们后台愿不愿意做了。我们后台嫌麻烦,我就都做了。
要是怕泄露,可以把这些敏感信息,放接口里,请求后台服务获取
您好,想问一下您在使用录音功能时,有无偶现安卓手机的音频听起来快进或者iphone手机录音文件无法播放的问题?
sampleRate: 16000,
numberOfChannels: 1
我们没有指定格式,看日志在iPhone和安卓手机上输出的文件都是m4a格式,是不是默认的m4a格式兼容性不好?需要制定其他格式呀?
支持一下本土企业