微信小程序前端调用科大讯飞语音测评(流式版)接口实践踩坑教程--核心代码指导

准备

为了帮助大家更好的理解科大讯飞接口调用的流程,也是当初我反复阅读和参考的几个链接,我发出来放到这里,大家可以去参考理解和学习。

讯飞开放平台--语音测评(流式版)API文档: https://www.xfyun.cn/doc/Ise/IseAPI.html

微信小程序使用科大讯飞语音评测,保姆级教程! https://developers.weixin.qq.com/community/develop/article/doc/000ac6470783008cc7fd577525bc13?page=2

uniapp - 接入科大讯飞语音评测

https://blog.csdn.net/CSDN_LQR/article/details/126714345?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%B0%8F%E7%A8%8B%E5%BA%8F%20%E9%9F%B3%E9%A2%91%20%20%E6%89%93%E5%88%86&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-2-126714345.142

微信小程序接入科大讯飞实现语音测评

https://blog.csdn.net/weixin_38516972/article/details/127260653?spm=1001.2101.3001.6650.9&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-9-127260653-blog-126714345.235%5Ev43%5Epc_blog_bottom_relevance_base2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-9-127260653-blog-126714345.235%5Ev43%5Epc_blog_bottom_relevance_base2&utm_relevant_index=16

本文通过部分伪代码,详细讲述处理过程及踩得坑。

关注重点

因为该接口是流式接口文档,那么我们就可以利用微信中的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)
 });

原理是:根据接口描述,需要分不同阶段,传相应的数据,那么数据存在于数组中我们就能清晰的知道,什么时候传什么数据。

接口调用说明原文如下:

  1. 参数上传阶段,详见业务参数说明(business):

    1.1 参数第一次上传,data.status=0,并设置cmd="ssb";

  2. 音频上传阶段,此阶段开始上传音频数据:

    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 添加微信