likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 15

一、简介

科大讯飞语音评测可以对字、词、句、篇章等题型进行多维度评分(准确度、流畅度、完整度、声韵调型等),支持中文和英文。最新的流式版使用 webSocket 调用接口,开发者可以边录音边上边音频数据(录音与评测同时进行),可以缩短用户等待评测结果的时间,大大提高用户体验。

科大讯飞语音评测提供了多种平台的 SDK 与 Demo,但是没有提供微信小程序版本的 SDK 与 Demo,不过,科大讯飞语音评测【WebApi】有提供浏览器版本的 Demo,理论上可以对它进行适当的改造,就能运行到微信小程序环境下,所以本文的主要内容就是对科大讯飞语音评测【WebApi】的浏览器版本 Demo 进行改造和封装。

温馨提示:着急伸手拿最终成品的,可以直接滚到 【文章末尾】 查看。

二、分析

打开科大讯飞语音评测 API 文档页面,在 调用示例 章节处,找到 语音评测流式API demo js语言 并下载,解压后使用 vscode 打开,查看 index.js,可以看到开头有如下注释:

// 1. websocket连接:判断浏览器是否兼容,获取websocket url并连接,这里为了方便本地生成websocket url
// 2. 获取浏览器录音权限:判断浏览器是否兼容,获取浏览器录音权限,
// 3. js获取浏览器录音数据
// 4. 将录音数据处理为文档要求的数据格式:采样率16k或8K、位长16bit、单声道;该操作属于纯数据处理,使用webWork处理
// 5. 根据要求(采用base64编码,每次发送音频间隔40ms,每次发送音频字节数1280B)将处理后的数据通过websocket传给服务器,
// 6. 实时接收websocket返回的数据并进行处理

// ps: 该示例用到了es6中的一些语法,建议在chrome下运行

通过这个注释说明,可以直观的了解到该 js demo 的大致流程,下面会按照注释里的顺序,逐个分析,并对应到微信小程序里的实现。

注意:因为本人使用的是 uniapp 开发,所以以下关于微信小程序代码实现的部分,并非 传统的 js + wxss + wxml,而是 vue3 + typescript

1、创建 webSocket

官方 demo 是运行在浏览器环境下的,而不同的浏览器对 webSocket 的创建方式不太一样,所以这里做兼容:

// 连接websocket
connectWebSocket() {
  return getWebSocketUrl().then(url => {
    let iseWS
    if ('WebSocket' in window) {
      iseWS = new WebSocket(url)
    } else if ('MozWebSocket' in window) {
      iseWS = new MozWebSocket(url)
    } else {
      alert('浏览器不支持WebSocket')
      return
    }
    this.webSocket = iseWS
    iseWS.onopen = e => {
      ...
    }
    iseWS.onmessage = e => {
      ...
    }
    iseWS.onerror = e => {
      ...
    }
    iseWS.onclose = e => {
      ...
    }
  })
}

微信小程序内创建 webSocket 就很简单了,使用 uni.connectSocket 即可:

/* socket相关 */
private socketTask: UniApp.SocketTask | null = null;

async connect() {
  const url = await this.getWebSocketUrl();
  const newUrl = encodeURI(url);
  this.socketTask = uni.connectSocket({
    url: newUrl,
    // 如果希望返回一个 socketTask 对象,需要至少传入 success / fail / complete 参数中的一个
    complete: () => {},
  });
  this.socketTask.onOpen((res) => {
    ...
  });
  this.socketTask.onMessage((res) => {
    ...
  });
  this.socketTask.onError((err) => {
    ...
  });
  this.socketTask.onClose(() => {
    ...
  });
}

注意:这里有一个坑,uni.connectSocket() 如果不指定 success/fail/complete 参数中的一个,则返回的不是 SocketTask,而是一个 Promise!我们需要的是 SocketTask,所以这里指定了一个空的 complete 回调函数。

上述代码中有一处十分重要的函数调用,即 getWebSocketUrl(),它负责生成科大讯飞语音评测【WebApi】的 webSocket 链接,涉及到参数加密:

import CryptoJS from "crypto-js";
import { Base64 } from "js/base64js.js";

/**
 * 获取websocket url
 * 该接口需要后端提供,这里为了方便前端处理
 */
function getWebSocketUrl() {
  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 = btoa(authorizationOrigin);
    url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
    resolve(url);
  });
}

js/base64js.js 是 demo 工程里 js 目录下的一个文件(采用 CommonJs 导入规范),而 crypto-js 是使用 npm 安装的一个第三方模块,理论上只需要将这 2 个模块直接拷贝或 npm 安装集成到项目中就好了,但是我的这个工程用的构建工具是 Vite,只支持 ES 模块导入规范,并且项目中使用了 TypeScript,所以,为了更好的编码体验,这里替换为支持 ES 模块导入规范且支持 TypeScript 的另外 2 个模块(js-base64crypto-es),与 demo 中的那 2 个模块功能完全相同:

// npm i crypto-es -S
// npm i js-base64 -S
import CryptoES from "crypto-es";
import { Base64 } from "js-base64";

/**
 * @returns 生成wss链接
 */
protected getWebSocketUrl(): Promise<string> {
  if (this.apiKey === "" || this.apiSecret === "") {
    throw new Error("apiKey、apiSecret must not be empty !!!");
  }
  return new Promise<string>((resolve, reject) => {
    // 请求地址根据语种不同变化
    let url = "wss://ise-api.xfyun.cn/v2/open-ise";
    const host = "ise-api.xfyun.cn";
    const date = (new Date() as any).toGMTString();
    const algorithm = "hmac-sha256";
    const headers = "host date request-line";
    const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/open-ise HTTP/1.1`;
    const signatureSha = CryptoES.HmacSHA256(signatureOrigin, this.apiSecret);
    const signature = CryptoES.enc.Base64.stringify(signatureSha);
    const authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
    const authorization = Base64.encode(authorizationOrigin);
    url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
    resolve(url);
  });
}

Date#toGMTString() 是一个过时的方法,在 TypeScript 中不被识别,从而导致编译不通过,除了像上述代码中通过强转 any 来规避外,还可以在 src/env.d.ts 文件中进行如下声明解决,interface Date { toGMTString(): string; },如果工程中频繁使用该方法的话,建议用第二种方法,这样就不必每处都写一次强转代码了。

注意:getWebSocketUrl() 函数内在生成 webSocket url 时,会使用到 API_KEYAPI_SECRET,这 2 个参数需要你自己注册一个科大讯飞的开发者账号之后,在开发者账号后台获取,为了防止被别人盗用,这个 url 的生成逻辑应该放置到自己的业务后端去实现。

2、录音上下文与权限

录音上下文的创建,以及获取录音权限的方式在不同浏览器环境中各不相同,所以官方 Demo 中做了大量兼容判断:

// 初始化浏览器录音
recorderInit() {
  navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
  // 创建音频环境
  try {
    this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
    this.audioContext.resume()
  } catch (e) {
    if (!this.audioContext) {
      alert('浏览器不支持webAudioApi相关接口')
      return
    }
  }
  // 获取浏览器录音权限
  if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
    navigator.mediaDevices
      ...
      .then(stream => { getMediaSuccess(stream) })
      .catch(e => { getMediaFail(e) })
  } else if (navigator.getUserMedia) {
    navigator.getUserMedia(
      ...,
      stream => { getMediaSuccess(stream) },
      function(e) { getMediaFail(e) }
    )
  } else {
    ...
    alert('无法获取浏览器录音功能,请升级浏览器或使用chrome')
    this.audioContext && this.audioContext.close()
    return
  }
  // 获取浏览器录音权限成功的回调
  let getMediaSuccess = stream => {
    console.log('getMediaSuccess')
    // 创建一个用于通过JavaScript直接处理音频
    this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
    this.scriptProcessor.onaudioprocess = e => {
      // 去处理音频数据
      ...
    }
    // 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
    this.mediaSource = this.audioContext.createMediaStreamSource(stream)
    // 连接
    this.mediaSource.connect(this.scriptProcessor)
    this.scriptProcessor.connect(this.audioContext.destination)
    ...
  }
  let getMediaFail = (e) => {
    alert('请求麦克风失败')
    this.audioContext && this.audioContext.close()
    this.audioContext = undefined
    ...
  }
}

而在微信小程序环境下,获取录音上下文与权限就简单多了,通过 uni.getRecorderManager() 即可获取录音上下文(管理器),然后调用 start(option) 时会自动询问用户是否授权录音权限:

const recordManager = uni.getRecorderManager();

/**
 * 开始录音
 */
const startRecord = () => {
  recordManager.onStart(() => {
    console.log("recorder start");
    ...
  });
  recordManager.onPause(() => {
    console.log("recorder pause");
  });
  recordManager.onStop((res) => {
    // tempFilePath	String	录音文件的临时路径
    console.log("recorder stop", res);
    ...
  });
  recordManager.onError((err) => {
    // errMsg	String	错误信息
    console.log("recorder err", err);
  });
  recordManager.onFrameRecorded((res) => {
    // frameBuffer	ArrayBuffer	录音分片结果数据
    // isLastFrame	Boolean	当前帧是否正常录音结束前的最后一帧
    const { frameBuffer } = res;
    ...
  });
  recordManager.start(option);
};

3、获取录音数据

浏览器的录音数据是通过 scriptProcessor.onaudioprocess 回调函数获得的:

// 获取浏览器录音权限成功的回调
let getMediaSuccess = (stream) => {
  ...
  this.scriptProcessor.onaudioprocess = (e) => {
    // 去处理音频数据
    transWorker.postMessage(e.inputBuffer.getChannelData(0));
  };
  ...
};

微信小程序的录音数据是通过 recordManager.onFrameRecorded() 回调函数获得:

recordManager.onFrameRecorded((res) => {
  // frameBuffer	ArrayBuffer	录音分片结果数据
  // isLastFrame	Boolean	当前帧是否正常录音结束前的最后一帧
  const { frameBuffer } = res;
  pushAudioData(frameBuffer); // 将每一帧音频保存起来
});

4、录音数据格式

从上面的 语音评测接口要求 中可以知道,科大讯飞语音评测接口对录音数据的格式是有要求的:

内容说明
音频属性采样率 16k、位长 16bit、单声道
音频格式pcm、wav、mp3(需更改 aue 的值为 lame)、speex-wb;
音频大小音频数据发送会话时长不能超过 5 分钟

官方 Demo 中,通过 scriptProcessor.onaudioprocess 回调拿到的录音数据是双声道的 PCM 数据,而接口要求的是单声道,所以通过代码 e.inputBuffer.getChannelData(0) 提取出第 1 个声道的数据,并交给 transWorker 去处理成 采样率 16k位长 16bit 的 PCM 数据:

// index.js
this.scriptProcessor.onaudioprocess = (e) => {
  // 去处理音频数据
  transWorker.postMessage(e.inputBuffer.getChannelData(0));
};

// transcode.worker.js
(function(){
  self.onmessage = function(e){
    transAudioData.transcode(e.data)
  }

  let transAudioData = {
    transcode(audioData) {
      let output = transAudioData.to16kHz(audioData)
      output = transAudioData.to16BitPCM(output)
      ...
    },
    to16kHz(audioData) {
      ...
      return newData
    },
    to16BitPCM(input) {
      ...
      return dataView
    },
  }
})()

微信小程序中在启动录音时需要传入一个配置参数 option,在这个 option 中我们可以指定采样率、通道数等配置,之后通过 recordManager.onFrameRecorded() 回调拿到的就已经是 采样率 16k位长 16bit 的单声道 PCM 数据了:

const recordManager = uni.getRecorderManager();
const option = {
  duration: duration, // 录音的时长,单位 ms,最大值 600000(10 分钟)
  sampleRate: 16000, // 采样率(pc不支持)
  numberOfChannels: 1, // 录音通道数
  // encodeBitRate: 48000, // 编码码率(默认就是48000)
  frameSize: 1, // 指定帧大小,单位 KB。传入 frameSize 后,每录制指定帧大小的内容后,会回调录制的文件内容,不指定则不会回调。暂仅支持 mp3、pcm 格式。
  format: "pcm", // 音频格式,默认是 aac
}
const startRecord = () => {
  ...
  recordManager.onFrameRecorded((res) => {
    // frameBuffer	ArrayBuffer	录音分片结果数据
    // isLastFrame	Boolean	当前帧是否正常录音结束前的最后一帧
    const { frameBuffer } = res;
    pushAudioData(frameBuffer); // 将每一帧音频保存起来
  });
  recordManager.start(option);
};

注意:微信小程序录音时长最大值为 10 分钟,而科大讯飞语音评测录音时长最大值为 5 分钟!!

5、发送 webSocket 数据

官方 Demo 中,使用 1 个 audioData 数组来存放每一帧经过 transWorker 处理过的 PCM "散装" 数据(音频流数据);在 webSocket 连接开启的 500ms 之后,开始发送音频流数据:

class IseRecorder {
  constructor({ language, accent, appId } = {}) {
    // 记录音频数据
    this.audioData = []
    transWorker.onmessage = function (event) {
      self.audioData.push(...event.data) // GitLqr: 注意这里是 "散装" 的
    }
    ...
  }
  recorderStart() {
    this.audioContext.resume()
    this.connectWebSocket()
  }
  // 连接websocket
  connectWebSocket() {
    return getWebSocketUrl().then(url => {
      ...
      iseWS.onopen = e => {
        // 重新开始录音
        setTimeout(() => {
          this.webSocketSend()
        }, 500)
      }
    })
  }
  // 初始化浏览器录音
  recorderInit() {
    ...
    // 获取浏览器录音权限成功的回调
    let getMediaSuccess = stream => {
      this.scriptProcessor.onaudioprocess = e => {
        // 去处理音频数据
        transWorker.postMessage(e.inputBuffer.getChannelData(0));
      }
      ...
    }
  }
}

再来看 webSocketSend() 的具体实现,就是不断从 audioData 数组中取出 "1280字节的" PCM 数据,经过 Base64 编码后发送给科大讯飞接口;PCM 数据只会在 第一帧中间帧 进行发送,当 PCM 数据消费完之后,还需要再发送一个 最后帧 通知科大讯飞语音评测接口数据流已经全部发送完成;此外,需要注意第一帧的几个重要参数:

  • category:题型。read_sentence 指的是句子朗读,read_chapter 指的是篇章朗读,等等。
  • auf:音频采样率。不填默认就是 audio/L16;rate=16000
  • ent:中文填 cn_vip;英文填 en_vip
  • text:待评测文本 utf8 编码,需要加 utf8bom 头。

注意:官方 Demo 的对比句子是 where are you,所以 ent 填写的是 en_vip,所以,如果实际业务使用的是中文,则需要注意调整参数值。另外,第一帧参数中有用到 appid,这个跟前面的 API_KEY、API_SECRET 一样,都是从开发者账号后台拿到的。

  // 向webSocket发送数据
  webSocketSend() {
    let audioData = this.audioData.splice(0, 1280)
    var params = {
      common: { app_id: this.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
        auf: 'audio/L16;rate=16000',
        ent: 'en_vip',
        text: '\uFEFF' + 'where are you'
      },
      data: {
        status: 0, encoding: 'raw', data_type: 1,
        data: this.toBase64(audioData),
      },
    }
    this.webSocket.send(JSON.stringify(params))
    this.handlerInterval = setInterval(() => {
      // websocket未连接
      if (this.webSocket.readyState !== 1) {
        this.audioData = []
        clearInterval(this.handlerInterval)
        return
      }
      // 最后一帧
      if (this.audioData.length === 0) {
        if (this.status === 'end') {
          this.webSocket.send(
            JSON.stringify({
              business: { cmd: 'auw', aus: 4, aue: 'raw' },
              data: { status: 2, encoding: 'raw', data_type: 1, data: '', },
            })
          )
          this.audioData = []
          clearInterval(this.handlerInterval)
        }
        return false
      }
      audioData = this.audioData.splice(0, 1280)
      // 中间帧
      this.webSocket.send(
        JSON.stringify({
          business: { cmd: 'auw', aus: 2, aue: 'raw' },
          data: {
            status: 1, encoding: 'raw', data_type: 1,
            data: this.toBase64(audioData),
          },
        })
      )
    }, 40)
  }

微信小程序中的数据发送流程跟官方 Demo 差不多,不过对 PCM 数据的存储方式不太一样,在启动录音的时候传入的 option 参数中,frameSize 指定了 onFrameRecorded() 回调的 PCM 帧大小,比如指定为 1(k),那么每次回调时附带的就是 "1024"字节的 PCM 数据,而官方 Demo 中对 PCM 进行拆散(存储时)和切割(发送时)的操作在微信小程序中是没必要的,无形中增加了复杂度。于是我这里干脆使用了一个 audioDataList 数组,将 PCM 数据以一帧帧(每帧 1024 字节)的形式进行存放,发送时,再一帧一帧的取出:

// index.vue
const recordManager = uni.getRecorderManager();
const option = {
  ...
  frameSize: 1, // 指定帧大小,单位 KB。传入 frameSize 后,每录制指定帧大小的内容后,会回调录制的文件内容,不指定则不会回调。暂仅支持 mp3、pcm 格式。
  format: "pcm", // 音频格式,默认是 aac
}
const startRecord = () => {
  ...
  recordManager.onFrameRecorded((res) => {
    // frameBuffer	ArrayBuffer	录音分片结果数据
    // isLastFrame	Boolean	当前帧是否正常录音结束前的最后一帧
    const { frameBuffer } = res;
    pushAudioData(frameBuffer); // 将每一帧音频保存起来
  });
  recordManager.start(option);
};

// ise-xfyun/index.ts
export default class IseXfyun {
  private audioDataList: ArrayBuffer[] = []; // 音频流数据

  /**
   * 添加语音数据
   * @param frameBuffer 帧数据
   */
  pushAudioData(frameBuffer: any) {
    if (frameBuffer) {
      this.audioDataList.push(frameBuffer);
    }
  }
  /**
   * 发送语音数据
   */
  private sendAudioData() {
    const audioData = this.audioDataList.splice(0, 1);
    const params = {
      common: { app_id: this.appId },
      business: {
        ...
        category: this.category,
        ent: "cn_vip", // 中文
        text: "\uFEFF" + this.chapter,
      },
      data: {
        status: 0, encoding: "raw", data_type: 1,
        data: this.toBase64(audioData[0]),
      },
    };
    this.socketTask.send({ data: JSON.stringify(params) });
    this.handlerInterval = setInterval(() => {
      // websocket未连接
      if (!this.socketTask) { return this.clearHandlerInterval(); }
      // 最后一帧
      if (this.audioDataList.length === 0) {
        const params = ...;
        this.socketTask.send({ data: JSON.stringify(params) });
        this.audioDataList = [];
        return this.clearHandlerInterval();
      }
      // 中间帧
      const audioData = this.audioDataList.splice(0, 1);
      const params = {
        business: { cmd: "auw", aus: 2, aue: "raw" },
        data: {
          status: 1, encoding: "raw", data_type: 1,
          data: this.toBase64(audioData[0]),
        },
      };
      this.socketTask.send({ data: JSON.stringify(params) });
    }, 40);
  }
  private toBase64(buffer: ArrayBuffer) {
    return uni.arrayBufferToBase64(buffer);
  }
  ...
}

注意:这里的 toBase64() 也与官方 Demo 中的实现不一样,官方 Demo 使用的是 js/base64js.js 中 Base64 来对数据进行编码,但是实际测试在微信小程序环境中编码会出问题,具体表现为调用科大讯飞语音评测接口时,会返回 68675 错误码,改用 uni.arrayBufferToBase64() 产出的 base64 则可被科大讯飞正常识别。

6、接收 webSocket 消息

官方 Demo 在创建好 webSocket 之后,指定了消息回调处理函数,具体处理逻辑交由 result(resultData) 实现,回调次数为多次,比如:帧数据接收成功消息,数据流中途错误消息,以及最终的评测结果消息。需要注意的是,如果在中途收到了科大讯飞接口的错误消息,那么应该直接停止数据流发送并提示给用户,因为一旦接口返回了错误消息,那么后续发送的中间帧数据科大讯飞接口是不会受理的,纯属浪费用户的流量和时间:

// 连接websocket
connectWebSocket() {
  return getWebSocketUrl().then(url => {
    let iseWS
    ...
    iseWS.onmessage = e => {
      this.result(e.data)
    }
  })
}

result(resultData) {
  // 识别结束
  let jsonData = JSON.parse(resultData)
  if (jsonData.data && jsonData.data.data) {
    let data = Base64.decode(jsonData.data.data)
    let grade = parser.parse(data, {
      attributeNamePrefix: '',
      ignoreAttributes: false
    })
    // 显示成绩
    ...
  }
  if (jsonData.code === 0 && jsonData.data.status === 2) {
    this.webSocket.close()
  }
  if (jsonData.code !== 0) {
    this.webSocket.close()
    console.log(`${jsonData.code}:${jsonData.message}`)
  }
}

微信小程序接收并处理 webSocket 消息的逻辑与官方 Demo 类似,不多赘述:

import { Base64 } from "js-base64";
import { XMLParser } from "fast-xml-parser";

export default class IseXfyun {
  /**
   * 连接科大讯飞wss接口
   */
  async connect() {
    this.socketTask = uni.connectSocket(...);
    this.socketTask.onMessage((res) => {
      this.onMessage(res.data.toString());
    });
    ...
  }
  private onMessage(resultData: string) {
    const jsonData = JSON.parse(resultData);
    this.log("收到消息:", jsonData);
    if (jsonData.data && jsonData.data.data) {
      const data = Base64.decode(jsonData.data.data);
      const parser = new XMLParser({
        attributeNamePrefix: "",
        ignoreAttributes: false,
        allowBooleanAttributes: true,
      });
      const grade = parser.parse(data);
      // 显示成绩
      ...
    }
    if (jsonData.code === 0 && jsonData.data.status === 2) {
      this.disconnect();
    }
    if (jsonData.code !== 0) {
      this.callback.onError(jsonData);
      this.log(`${jsonData.code}:${jsonData.message}`);
      this.disconnect();
    }
  }
  ...
}

三、封装

以上就是对科大讯飞语音评测官方 js Demo 代码逻辑的逐步分析,官方 Demo 把录音与 webSocket 接口调用写在一个文件中,感觉比较混乱,为了更好的复用,我将 录音 与 语音评测 两者进行拆分,把 语音评测 的部分封装到 IseXfyun 类中,大致类结构如下,内容基本上就是上面分析过程中微信小程序端的实现,为缩短篇幅,方法体内代码以省略号表示:

import { Base64 } from "js-base64";
import { XMLParser } from "fast-xml-parser";
import CryptoES from "crypto-es";

/**
 * 科大讯飞 语音评测(流式版)封装
 *
 * 注意:需要设置wss域名白名单 wss://ise-api.xfyun.cn
 * 建议:apiKey、apiSecret 不要存放在本地,应该放在后端,防止被破解。
 * 最佳做法:继承 IseXfyun,重写 getWebSocketUrl(),从后端获取 socket 链接,即 webSocketUrl 的计算规则由后端实现。
 *
 * @author GitLqr
 * @since 2022-08-30
 */
export default class IseXfyun {
  /**
   * 科大讯飞的语音评测只支持 16k 16bit 单通道
   * @param duration 录音时长
   * @returns 录音配置
   */
  getAudioRecordOption(duration: number) { ... }

  /**
   * 添加语音数据
   * @param frameBuffer 帧数据
   */
  pushAudioData(frameBuffer: any) { ... }

  /**
   * @returns 生成wss链接
   */
  protected getWebSocketUrl(): Promise<string> { ... }

  /**
   * 连接科大讯飞wss接口
   */
  async connect() { ... }

  /**
   * 断开连接
   */
  disconnect() { ... }

  /**
   * 发送语音数据
   */
  private sendAudioData() { ... }

  private onMessage(resultData: string) { ... }
}

IseXfyun 与 Uniapp 完整版 Demo 的具体代码可访问下面仓库进行查看:

注意:微信小程序需要配置域名白名单 wss://ise-api.xfyun.cn

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

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

转载自:https://juejin.cn/post/7139894621571645454
评论
请登录