当前位置:   article > 正文

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

微信小程序接科大讯飞语音识别

     由于公司业务需求需要实现在微信小程序中实现语音测评功能,因为之前在H5中已经实现了该功能认为在小程序中问题不大,但是在实际开发中遇到了不少坑。

  1. 问题一:语音测评流式版在微信小程序中无法正确的传输数据并获取返回值

    在H5实现时使用 new WebSocket() 实例进行传输数据 并成功的拿到了测评的数据

 ws = new WebSocket(url)

   但是在小程序中 wx.connectSocket() 无法将数据流正常的传给科大讯飞的api

wx.connectSocket()

解决方案:使用微信录音功能将录音文件上传服务器,在通过服务端调用科大讯飞api进行语音测评得到结果后返回给前端

小程序代码如下:

  1. // 将数据发送到服务器
  2. async _sendMp3(path) {
  3. this.data.toast.linShow({
  4. icon: "loading",
  5. title: '解析中请稍后',
  6. duration: 11111111,
  7. mask: true
  8. })
  9. wx.uploadFile({
  10. url: `${nodeServer}/xls/postData`, // node 服务接口
  11. filePath: path, // 录音文件临时地址
  12. header: {
  13. "Content-Type": "multipart/form-data" //必须是这个格式
  14. },
  15. formData: {
  16. text: this.data.XLSpeakerTitle // 要对比的文本
  17. },
  18. name: "mp3",
  19. success: async (res) => {
  20. const {
  21. Data,
  22. Code
  23. } = JSON.parse(res.data);
  24. if (Code == 200) {
  25. const score = parseInt(Data.score)
  26. this.setData({
  27. showScore: false,
  28. score,
  29. });
  30. // 开始动画
  31. this.countUp = new WxCountUp('score', score, {}, this);
  32. this.countUp.start();
  33. this._AddSpeakItem(score, Data.path);
  34. // 假如当前得分超过了历史最高分 将历史最高分更新
  35. this._UpDateMax(score)
  36. }
  37. },
  38. fail: () => {
  39. this.data.toast.linShow({
  40. title: '解析失败请,请稍后再试',
  41. })
  42. },
  43. complete: () => {
  44. this.data.toast.linHide()
  45. }
  46. })
  47. },

node js 服务端代码如下:

  1. const router = require("koa-router")();
  2. const { _init } = require("../ws/index");
  3. const { put } = require("../ali-oss/index");
  4. const { removeFile } = require("../fs");
  5. const { SuccessModel, ErrorModel } = require("../model/resModel");
  6. router.prefix("/xls");
  7. router.post("/postData", async (ctx, next) => {
  8. const { text } = ctx.request.body;
  9. // 得到mp3 文件
  10. const path = ctx.request.files.mp3.path;
  11. // 得到对比分数
  12. const { Code, Data, Msg } = await _init(path, text);
  13. // 将上传的mp3 文件上传到 ali-oss
  14. const mp3path = await put(path);
  15. // 将上传的文件删除 防止上传的录音文件越来越多 导致服务器内存压力
  16. removeFile(path);
  17. if (Code == 0) {
  18. ctx.body = new SuccessModel({
  19. score: Data,
  20. path: mp3path.url.split("aliyuncs.com/")[1],
  21. });
  22. } else {
  23. ctx.body = new ErrorModel(Code, Msg ? Msg : "分数解析失败,请稍后再试");
  24. }
  25. });

科大讯飞语音测评

  1. const CryptoJS = require("crypto-js");
  2. const WebSocket = require("ws");
  3. var fs = require("fs");
  4. const APPID = "xxxxxxxxx";
  5. const API_SECRET = "xxxxxxxxxx";
  6. const API_KEY = "xxxxxxxxxx";
  7. // 系统配置
  8. let xmlParser = require("../util/parser");
  9. const config = {
  10. // 请求地址
  11. hostUrl: "wss://ise-api.xfyun.cn/v2/open-ise",
  12. host: "iat-api.xfyun.cn",
  13. //在控制台-我的应用-语音评测(流式版)获取
  14. appid: APPID,
  15. //在控制台-我的应用-语音评测(流式版)获取
  16. apiSecret: API_SECRET,
  17. //在控制台-我的应用-语音评测(流式版)获取
  18. apiKey: API_KEY,
  19. file: "./read_sentence_cn.pcm", //请填写您的音频文件路径
  20. uri: "/v2/open-ise",
  21. highWaterMark: 1280,
  22. };
  23. // 帧定义
  24. const FRAME = {
  25. STATUS_FIRST_FRAME: 0,
  26. STATUS_CONTINUE_FRAME: 1,
  27. STATUS_LAST_FRAME: 2,
  28. };
  29. // 设置当前临时状态为初始化
  30. let status = FRAME.STATUS_FIRST_FRAME;
  31. let ws;
  32. const _init = (path, text) => {
  33. // 一定要初始化状态
  34. status = FRAME.STATUS_FIRST_FRAME;
  35. // 合成websocket接口,一次请求成功,未中断链接情况下,再次发送新请求导致 10140 错误
  36. const url = getWsUrl();
  37. console.log("url-----------", url);
  38. ws = new WebSocket(url);
  39. return new Promise((resolve, reject) => {
  40. // 连接建立完毕,读取数据进行识别
  41. ws.on("open", (event) => {
  42. console.log("websocket connect!");
  43. // config.file
  44. var readerStream = fs.createReadStream(path, {
  45. highWaterMark: config.highWaterMark,
  46. });
  47. readerStream.on("data", function (chunk) {
  48. send(chunk, text);
  49. });
  50. // 最终帧发送结束
  51. readerStream.on("end", function () {
  52. status = FRAME.STATUS_LAST_FRAME;
  53. send("");
  54. });
  55. });
  56. // 得到识别结果后进行处理,仅供参考,具体业务具体对待
  57. ws.on("message", (data, err) => {
  58. if (err) {
  59. ws.close();
  60. return;
  61. }
  62. res = JSON.parse(data);
  63. if (res.code != 0) {
  64. ws.close();
  65. // reject(`error code ${res.code}, reason ${res.message}`);
  66. reject({ Code: res.code, Msg: res.message });
  67. return;
  68. }
  69. if (res.data.status == 2) {
  70. const { data } = res.data;
  71. let b = new Buffer(data, "base64");
  72. let iseResult = `最终识别结果: ${b.toString()}`;
  73. const scroeData = xmlParser.parse(iseResult, {
  74. attributeNamePrefix: "",
  75. ignoreAttributes: false,
  76. });
  77. resolve({
  78. Code: 0,
  79. Data:
  80. scroeData.xml_result.read_sentence.rec_paper.read_sentence
  81. .total_score,
  82. });
  83. }
  84. });
  85. // 资源释放
  86. ws.on("close", () => {
  87. console.log("connect close!-------------------------");
  88. });
  89. // 建连错误
  90. ws.on("error", (err) => {
  91. reject("websocket connect err: " + err);
  92. console.log("websocket connect err: " + err);
  93. });
  94. });
  95. };
  96. // 获取长连接地址
  97. function getWsUrl() {
  98. // 获取当前时间 RFC1123格式
  99. let date = new Date().toUTCString();
  100. const wssUrl =
  101. config.hostUrl +
  102. "?authorization=" +
  103. getAuthStr(date) +
  104. "&date=" +
  105. date +
  106. "&host=" +
  107. config.host;
  108. return wssUrl;
  109. }
  110. // 传输数据
  111. function send(data, text = "今天天气怎么样") {
  112. let frame = "";
  113. switch (status) {
  114. case FRAME.STATUS_FIRST_FRAME:
  115. // 第一次数据发送:
  116. frame = {
  117. common: { app_id: config.appid },
  118. business: {
  119. // 服务类型指定 ise(开放评测)
  120. sub: "ise",
  121. // 中文:cn_vip 英文:en_vip
  122. ent: "cn_vip",
  123. // 题型:句子朗读
  124. category: "read_sentence",
  125. // 待评测文本编码 utf-8
  126. text: `\uFEFF${text}`,
  127. // 待评测文本编码 utf-8 gbk
  128. tte: "utf-8",
  129. // 跳过ttp直接使用ssb中的文本进行评测(使用时结合cmd参数查看),默认值true
  130. ttp_skip: true,
  131. cmd: "ssb",
  132. aue: "lame",
  133. auf: "audio/L16;rate=16000",
  134. },
  135. data: { status: 0 },
  136. };
  137. ws.send(JSON.stringify(frame));
  138. // 后续数据发送
  139. frame = {
  140. common: { app_id: config.appid },
  141. business: { aus: 1, cmd: "auw", aue: "lame" },
  142. data: { status: 1, data: data.toString("base64") },
  143. };
  144. status = FRAME.STATUS_CONTINUE_FRAME;
  145. break;
  146. case FRAME.STATUS_CONTINUE_FRAME:
  147. frame = {
  148. common: { app_id: config.appid },
  149. business: { aus: 2, cmd: "auw", aue: "lame" },
  150. data: { status: 1, data: data.toString("base64") },
  151. };
  152. break;
  153. case FRAME.STATUS_LAST_FRAME:
  154. frame = {
  155. common: { app_id: config.appid },
  156. business: { aus: 4, cmd: "auw", aue: "lame" },
  157. data: { status: 2, data: data.toString("base64") },
  158. };
  159. break;
  160. }
  161. ws.send(JSON.stringify(frame));
  162. }
  163. // 鉴权签名
  164. function getAuthStr(date) {
  165. let signatureOrigin = `host: ${config.host}\ndate: ${date}\nGET ${config.uri} HTTP/1.1`;
  166. let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, config.apiSecret);
  167. let signature = CryptoJS.enc.Base64.stringify(signatureSha);
  168. let authorizationOrigin = `api_key="${config.apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`;
  169. let authStr = CryptoJS.enc.Base64.stringify(
  170. CryptoJS.enc.Utf8.parse(authorizationOrigin)
  171. );
  172. return authStr;
  173. }
  174. module.exports = { _init };

 2.问题2:文本朗读功能,刚开始做的时候我们是使用科大讯飞的语音合成功能,由于科大讯飞是收费的 推荐大家使用微信小程序同声传译插件很好用而且是免费,如果日使用量很大的话可以申请大额数量,缺点是朗读的文字有大小限制,我测试了下大概是200 字左右,如果超过两百字需要手动截取,分批合成

WechatSI 使用微信同声传译插件 
  1. WechatSI.textToSpeech({
  2. lang: "zh_CN",
  3. tts: true,
  4. content: this.data.XLSpeakerTitle,
  5. success: (res) => {
  6. this.data.WechatSIStatus = "success"
  7. this._palyBGAduio(res.filename, "演示")
  8. },
  9. fail: (err) => {
  10. this.setData({
  11. isPlay: false,
  12. isPlayDemo: false
  13. })
  14. this.data.toast.linShow({
  15. title: err.msg,
  16. })
  17. }
  18. })

3.问题三实现最高分回放功能,这里我的解决方案是在解决方案一的的同时,将上传到服务器的录音文件同时上传到阿里云OSS 返回给前端阿里云的图片地址和当前语音的测评分数,前端判断当前得分是否超过最高分,如果超过最高分将最高分更新

阿里云OSS 上传如下:

  1. const OSS = require("ali-oss");
  2. const client = new OSS({
  3. bucket: "xlxtimg",
  4. // region以杭州为例(oss-cn-hangzhou),其他region按实际情况填写。
  5. region: "oss-cn-beijing",
  6. // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。
  7. accessKeyId: "xxxxxxxx",
  8. accessKeySecret: "xxxxxxxxxxxxxxxxxx",
  9. });
  10. async function put(file) {
  11. try {
  12. //object-name可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
  13. let result = await client.put(`xlsLee/${file}`, file);
  14. return result;
  15. } catch (e) {
  16. console.log(e);
  17. }
  18. }
  19. module.exports = { put };

 以上就是我实现微信小程序进行语音测评 并将语音文件长传阿里云的实践过程

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号