微信小程序前端调用科大讯飞语音测评(流式版)接口实践踩坑教程--核心代码指导
准备
为了帮助大家更好的理解科大讯飞接口调用的流程,也是当初我反复阅读和参考的几个链接,我发出来放到这里,大家可以去参考理解和学习。
讯飞开放平台--语音测评(流式版)API文档: https://www.xfyun.cn/doc/Ise/IseAPI.html
微信小程序使用科大讯飞语音评测,保姆级教程! https://developers.weixin.qq.com/community/develop/article/doc/000ac6470783008cc7fd577525bc13?page=2
uniapp - 接入科大讯飞语音评测
微信小程序接入科大讯飞实现语音测评
本文通过部分伪代码,详细讲述处理过程及踩得坑。
关注重点
因为该接口是流式接口文档,那么我们就可以利用微信中的wx.getRecorderManager()得到的录音器对象,来进行文件帧存储,使数据进行流式传输。
this.recorder = wx.getRecorderManager();
...
this.recorder.onFrameRecorded((res)=>{
const {frameBuffer } =res;
let u8Arr = new Uint8Array(frameBuffer);
this.frames.push(u8Arr);
console.log("arr",this.frames)
});
原理是:根据接口描述,需要分不同阶段,传相应的数据,那么数据存在于数组中我们就能清晰的知道,什么时候传什么数据。
接口调用说明原文如下:
-
参数上传阶段,详见业务参数说明(business):
1.1 参数第一次上传,data.status=0,并设置cmd="ssb";
-
音频上传阶段,此阶段开始上传音频数据:
2.1 第一帧音频需要设置cmd="auw",aus=1,data.status=1;
2.2 中间帧音频需要设置cmd="auw",aus=2,data.status=1;
2.3 最后一帧音频需要设置cmd="auw",aus=4,并设置data.status=2;
在这里我给他们编了个号。 实际上每个步骤都是必不可少的,后面会在伪代码中体现。
实际执行伪代码:
1.1 参数第一次上传
const APPID='XXX';
var params = {
"common": {
"app_id": APPID
},
"business": {
"sub": "ise",
"ent": "cn_vip",
"category": "read_sentence",
"text": "\uFEFF"+"今天天气怎么样?",
"tte": "gbk",
"ttp_skip":true,
"cmd": "ssb",
"auf": "audio/L16;rate=16000"
},
"data": {
"status": 0
}
};
this.socket.send({data:JSON.stringify(params)});
...
2.1 上传第一帧音频
let firstDataFrame = this.frames.splice(0,1);
console.log("firstDataFrame",firstDataFrame)
let firstDataFrameData = {
"business":{
"cmd":"auw",
"aus":1
},
"data":{
"status":1,
"data":this.toBase64(firstDataFrame[0])
}
};
this.socket.send({data:JSON.stringify(firstDataFrameData)});
2.2 上传中间帧音频 && 2.3 上传最后一帧音频
// 定时任务,每40ms执行一次
this.timer=setInterval(()=>{
if(!this.socket){
clearInterval(this.timer);
return;
}
//最后一帧数据
if(this.frames.length==1){
let lastFrame = this.frames.splice(0,1);
let lastFrameData = {
"business":{
"cmd":"auw",
"aus":4
},
"data":{
"status":2,
"data":this.toBase64(lastFrame[0])
}
};
this.socket.send({data:JSON.stringify(lastFrameData)});
return;
}
if(this.frames.length==0){
//数据全部发送完成后,清理存储空间,关闭定时任务
console.log("数据发送完毕!")
this.frames=[];
clearInterval(this.timer);
return;
}
//发送中间帧
let preSendFrame = this.frames.splice(0,1);
if(preSendFrame[0]!=null){
let middleFrameData = {
"business":{
"cmd":"auw",
"aus":2
},
"data":{
"status":1,
"data":this.toBase64(preSendFrame[0])
}
};
this.socket.send({data:JSON.stringify(middleFrameData)})
}
},40)
踩坑过程
1.websocket问题
刚开始调用的时候,参数不对,出现过类似'$' must be object 的报错,错误码10163 出现这个错误,实际上是ws连接没连上去。 需要仔细核对参数,保证websocket连接没有问题,再继续往下执行。
2.理解错误
因为有些示例的误导,导致没有分清楚,第一次参数上传和第一次文件上传,混到一块去了,原本应该两次执行的过程,变成了一次,结果总是报错,而且错误莫名其妙的,并不是标准的错误格式,即使是,也是10163错误。让人不知道参数错在哪里。
3.格式问题
文档介绍说,推荐使用PCM格式,即未压缩的原声,可是真正使用了PCM,并且修改了编码格式后,会报68675错误,语音文件不正确,而且还无法在小程序界面进行播放
4.Base64 编解码问题
关于这一点,我试过很多方法,因为代码中引入了crypto-js.js
const CryptoJS = require('../../../../../lib/crypto-js/crypto-js.js');
CryptoJS去做base64编码,也尝试过
而框架自带了一个base64编码的js,我也尝试过
const base64 = require('../../../../../lib/tools/base64_lib.js');
但这两个都有局限性,在后来解析结果这里,用开发版测试的时候出错了。 于是最终选择了新增的base64.min.js
const base64 = require('../../../../../lib/tools/base64.min.js');
5.文件帧大小的问题
我们可以看到科大讯飞的文档中,有这么一句话:
请注意不同音频格式一帧大小的字节数不同,我们建议:未压缩的PCM格式,每次发送音频间隔40ms,每次发送音频字节数1280B;大小可以调整,但最大不要超过19200B,即base64压缩后不能超过26000B,否则会报错10163数据过长错误。
而我们recorder的配置选项中,默认是以kb为单位
const options = {
duration: 600000,//指定录音的时长,单位 ms,最大为10分钟(600000),默认为1分钟(60000)
sampleRate: 16000,//采样率
numberOfChannels: 1,//录音通道数
encodeBitRate:60000,// 96000,//编码码率
format: 'mp3',//音频格式,有效值 aac/mp3
frameSize: 4,//指定帧大小,单位 KB
}
如果不修改frameSize 把它改小,调用接口的时候,就永远会出现报错60114,消息也会提示长度超过了26000
其他问题
1.fs对象 && stream对象
本来想着既然用到文件流,很可能会用到fs和stream对象,结果却发现,通过npm install fs stream 命令,fs文件夹内是空的,没有js,stream虽然能引入,却没有实际用处,后来放弃了
2.xmldom对象引入
我看示例代码是这么写的
var DOMParser = require('xmldom').DOMParser;
...
const doc=new DOMParser().parseFromString(data,'text/xml');
可实际上在小程序中,这样写是有问题的,改成以下代码就可以了
const parser= require('../../../../../lib/xmldom/lib/dom-parser.js');
...
const domParser = new parser.DOMParser();
const doc =domParser.parseFromString(scoreDataArr,'text/xml');
3.编码参数encoding和aue问题
参数说明中,如果格式是mp3格式,那么encoding和aue都应该使用“lame”,而实际上,我使用参数进行配置后,反而变成了报错48205(实例未评测,如没有获取到录音、上传音频为空导致的报错)。 而使用PCM格式时,PCM必须是大写,编码参数也都改成了raw, 则出现报错68675(不正常的语音数据,请检查是否为16k、16bit、单声道音频,并且检查aue参数值指定是否与音频类型匹配)
完整代码
需要完整代码,请加微信 yeegeexb2014
或通过手机号13787164431 添加微信