您现在的位置: 首页 > 微信小程序开发 > 教程 >

搭建https的silk录音文件语音识别服务的调用过程

来源:微信小程序 编辑:Yiyongtong.com 发布时间:2018-01-02 08:58热度:
实现功能

实现一个智能生活信息查询的小秘书功能,支持查天气、新闻、日历、汇率、笑话、故事、百科、诗词、邮编、区号、菜谱、股票、节目预告,还支持闲聊、算24点、数学计算、单位换算、购物、搜索等功能。

使用方式:

新版上线支持语音识别,按下说话,松开发送。

老版本上支持摇一摇、点界面按钮、手动输入、下拉刷新这四种方式。

扫码试用(左右皆可)

 

界面展示

 
 

开发资源

  • 免费开放语义接口平台 olami.ai
  • 微信小程序平台
  • js, css
  • 我自己搭建的https的语音识别API
接口
源码分析

这里主要介绍新版本首页相关的代码,其它部分代码在 微信小程序——智能小秘“遥知之”源码分享(语义理解基于olami)(注:这个是原来不支持语音识别的版本) 的基础上,变化不怎么大,具体可参考那篇文章。

asr.js源码:
  1. /**
  2. * 作者:happycxz
  3. * 时间:2017.09.19
  4. * 源码分享链接:http://blog.csdn.net/happycxz/article/details/78024986
  5. * https的silk语音识别API(专供微信小程序调用):https://api.happycxz.com/test/silk2asr/olami/asr
  6. * 该API服务搭建全过程解析及源码分享贴:http://blog.csdn.net/happycxz/article/details/78016299
  7. * 需要使用此API请联系作者QQ:404499164
  8. * 遵循开放、分享、自由、免费的精神,把开源坚持到底
  9. */
  10.  
  11. //获取应用实例 
  12. var app = getApp()
  13.  
  14. var UTIL = require('../../utils/util.js');
  15. var GUID = require('../../utils/GUID.js');
  16. var NLI = require('../../utils/NLI.js');
  17.  
  18. const appkey = require('../../config').appkey
  19. const appsecret = require('../../config').appsecret
  20.  
  21. //弹幕定时器
  22. var timer;
  23.  
  24. var pageSelf = undefined;
  25.  
  26. var doommList = [];
  27. class Doomm {
  28.   constructor() {
  29.     this.text = UTIL.getRandomItem(app.globalData.corpus);
  30.     this.top = Math.ceil(Math.random() * 40);
  31.     this.time = Math.ceil(Math.random() * 8 + 6);
  32.     this.color = getRandomColor();
  33.     this.display = true;
  34.     let that = this;
  35.     setTimeout(function () {
  36.       doommList.splice(doommList.indexOf(that), 1);
  37.       doommList.push(new Doomm());
  38.  
  39.       pageSelf.setData({
  40.         doommData: doommList
  41.       })
  42.     }, this.time * 1000)
  43.   }
  44. }
  45. function getRandomColor() {
  46.   let rgb = []
  47.   for (let i = 0; i < 3; ++i) {
  48.     let color = Math.floor(Math.random() * 256).toString(16)
  49.     color = color.length == 1 ? '0' + color : color
  50.     rgb.push(color)
  51.   }
  52.   return '#' + rgb.join('')
  53. }
  54.  
  55. Page({
  56.   data: {
  57.     j: 1,//帧动画初始图片 
  58.     isSpeaking: false,//是否正在说话
  59.     outputTxt : "", //输出识别结果
  60.  
  61.     doommData: []
  62.   },
  63.  
  64.   initDoomm: function () {
  65.     doommList.push(new Doomm());
  66.     doommList.push(new Doomm());
  67.     doommList.push(new Doomm());
  68.     this.setData({
  69.       doommData: doommList
  70.     })
  71.   },
  72.  
  73.   onLoad: function () {
  74.     pageSelf = this;
  75.     this.initDoomm();
  76.   },
  77.  
  78.   //手指按下 
  79.   touchdown: function () {
  80.     UTIL.log("手指按下了... new date : " + new Date)
  81.     var _this = this;
  82.     speaking.call(this);
  83.     this.setData({
  84.       isSpeaking: true
  85.     })
  86.     //开始录音 
  87.     wx.startRecord({
  88.       success: function (res) {
  89.         //临时路径,下次进入小程序时无法正常使用
  90.         var tempFilePath = res.tempFilePath;
  91.         UTIL.log('record SUCCESS file path:' + tempFilePath)
  92.         _this.setData({
  93.           recordPath: tempFilePath
  94.         });
  95.       },
  96.       fail: function (res) {
  97.         //录音失败 
  98.         wx.showModal({
  99.           title: '提示',
  100.           content: '录音的姿势不对!',
  101.           showCancel: false,
  102.           success: function (res) {
  103.             if (res.confirm) {
  104.               UTIL.log('用户点击确定')
  105.               return
  106.             }
  107.           }
  108.         })
  109.       }
  110.     })
  111.   },
  112.   //手指抬起 
  113.   touchup: function () {
  114.     UTIL.log("手指抬起了...")
  115.     this.setData({
  116.       isSpeaking: false,
  117.     })
  118.     clearInterval(this.timer)
  119.     wx.stopRecord()
  120.  
  121.     var _this = this
  122.     setTimeout(function () {
  123.       var urls = "https://api.happycxz.com/test/silk2asr/olami/asr";
  124.       UTIL.log(_this.data.recordPath);
  125.       wx.uploadFile({
  126.         url: urls,
  127.         filePath: _this.data.recordPath,
  128.         name: 'file',
  129.         formData: { "appKey": appkey, "appSecret": appsecret, "userId": UTIL.getUserUnique() },
  130.         header: { 'content-type': 'multipart/form-data' },
  131.         success: function (res) {
  132.           UTIL.log('res.data:' + res.data);
  133.  
  134.           var nliResult = getNliFromResult(res.data);
  135.           UTIL.log('nliResult:' + nliResult);
  136.           var stt = getSttFromResult(res.data);
  137.           UTIL.log('stt:' + stt);
  138.  
  139.           var sentenceResult;
  140.           try {
  141.             sentenceResult = NLI.getSentenceFromNliResult(nliResult);
  142.           } catch (e) {
  143.             UTIL.log('touchup() 错误' + e.message + '发生在' + e.lineNumber + '行');
  144.             sentenceResult = '没明白你说的,换个话题?'
  145.           }
  146.  
  147.           var lastOutput = "==>语音识别结果:\n" + stt + "\n\n==>语义处理结果:\n" + sentenceResult;
  148.           _this.setData({
  149.             outputTxt: lastOutput,
  150.           });
  151.           wx.hideToast();
  152.         },
  153.         fail: function (res) {
  154.           UTIL.log(res);
  155.           wx.showModal({
  156.             title: '提示',
  157.             content: "网络请求失败,请确保网络是否正常",
  158.             showCancel: false,
  159.             success: function (res) {
  160.             }
  161.           });
  162.           wx.hideToast();
  163.         }
  164.       });
  165.     }, 1000)
  166.   },
  167.  
  168.   //切换到老版本
  169.   turnToOld: function() {
  170.     wx.navigateTo({
  171.       url: '../index/index',
  172.     })
  173.   },
  174.  
  175. })
  176.  
  177. function getNliFromResult(res_data) {
  178.   var res_data_json = JSON.parse(res_data);
  179.   var res_data_result_json = JSON.parse(res_data_json.result);
  180.   return res_data_result_json.nli;
  181. }
  182.  
  183. function getSttFromResult(res_data) {
  184.   var res_data_json = JSON.parse(res_data);
  185.   var res_data_result_json = JSON.parse(res_data_json.result);
  186.   return res_data_result_json.asr.result;
  187. }
  188.  
  189. //麦克风帧动画 
  190. function speaking() {
  191.   var _this = this;
  192.   //话筒帧动画 
  193.   var i = 1;
  194.   this.timer = setInterval(function () {
  195.     i++;
  196.     i = i % 5;
  197.     _this.setData({
  198.       j: i
  199.     })
  200.   }, 200);
  201. }
复制代码

这部分主要实现录音按钮被按下和松开触发话筒录音及结束录音,当按钮被按下后,触发调用话筒动画特效(其实是四五个图片轮流显示的效果),同时调用wx.startRecord开始录音。

当按钮松开时停止录音,然后将录音临时文件往 https://api.happycxz.com/test/silk2asr/olami/asr 上发送,同时发olami上注册申请的appKey和appSecret,以及用户唯一识别号。

从语音识别接口返回的结果是原封不动的olami官方输出结果,我们只需要取一下语音识别结果以及语义理解结果即可。 语义理解结果在原来 微信小程序——智能小秘“遥知之”源码分享(语义理解基于olami)(注:这个是原来不支持语音识别的版本) 中已经有方法解析,为了代码复用性强些,把解析nli结果的方法简单改了下,即适用新版语音识别的,也适用以前老版本的手动输入的。

代码逻辑很简单,看看就明白了,这里不再多述。

asr.json源码:
  1. {
  2.   "window": {
  3.     "enablePullDownRefresh": false
  4.   }
  5. }
复制代码

因为老版项目中我开启了下拉刷新,新界面上不需要了,所以在asr页面的.json这里特意关闭了此功能。

asr.wxml源码:
  1. <view class="container">
  2.   <view class="page-section">
  3.     <view class="text-box" scroll-y="true">      
  4.       <text style="max-width:200px;overflow-y:auto;height:200px;" selectable="true">{{outputTxt}}</text>
  5.     </view>
  6.   </view>
  7.  
  8.   <view class="page-section">
  9.     <text selectable="true" class="text-head">语义理解基于olami.ai,作者QQ:404499164</text>
  10.   </view>
  11.  
  12.   <view class="little-gap-top button-selection2 button-show bottom-button">
  13.     <button size="mini" type="default" open-type="contact">联系作者</button>
  14.     <button size="mini" type="default" bindtap="turnToOld">切老版本</button>
  15.     <button size="mini" type="default" open-type="share">帮忙分享</button>
  16.   </view>
  17.  
  18.   <view class="page-section">
  19.     <view class="doommview">
  20.       <block wx:for="{{doommData}}" wx:key="id">
  21.           <text wx:if="{{item.display}}" class="aon" style="animation: first {{item.time}}s linear infinite;top:{{item.top}}%;color:{{item.color}};">
  22.               {{item.text}}
  23.           </text>
  24.       </block>
  25.     </view>
  26.   </view>
  27.  
  28. <view  wx:if="{{isSpeaking}}"  class="speak-style">
  29.   <image class="sound-style" src="../../pics/voice_icon_speech_sound_1.png" ></image>
  30.   <image wx:if="{{j==2}}" class="sound-style" src="../../pics/voice_icon_speech_sound_2.png" ></image>
  31.   <image wx:if="{{j==3}}" class="sound-style" src="../../pics/voice_icon_speech_sound_3.png" ></image>
  32.   <image wx:if="{{j==4}}" class="sound-style" src="../../pics/voice_icon_speech_sound_4.png" ></image>
  33.   <image wx:if="{{j==5}}"class="sound-style" src="../../pics/voice_icon_speech_sound_5.png" ></image>
  34. </view>
  35.  
  36. </view>
  37.  
  38.  
  39. <view class="record-style">
  40.   <button type="primary" class="btn-style" bindtouchstart="touchdown" bindtouchend="touchup">按下录音,松开结束</button>
  41. </view>
复制代码

布局调了半天,还是没有达到我想要的效果,前端布局我没系统学习过,基本就是凑凑拼拼,望有基本审美观的各位看官理解……

asr.wxss源码:
  1. /* pages/asr/asr.wxss */
  2.  
  3. page{
  4.   background-color:beige;
  5.   background-image: url(http://img.blog.csdn.net/20170720105808995?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGFwcHljeHo=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast);
  6.   background-size: cover;
  7. }
  8.  
  9. .page-section{
  10.   display: flex;
  11.   flex-direction: column;
  12.   margin-bottom: 10rpx;
  13. }
  14.  
  15. .text-head{
  16.   color: #ff0000;
  17.   font-size: 28rpx;
  18.   align-items: center;
  19.   margin-top: 5rpx;
  20. }
  21.  
  22. .button-selection {
  23.   display: flex;
  24.   flex-direction: column;
  25.   justify-content: center;
  26.   align-items: center;
  27. }
  28.  
  29. .button-selection2 {
  30.   justify-content: space-between;
  31.   align-content: space-between;
  32.   flex-shrink:1;
  33. }
  34.  
  35. .little-gap-top {
  36.   margin-top: 10rpx; 
  37. }
  38.  
  39. .big-gap-top {
  40.   margin-top: 100rpx; 
  41. }
  42.  
  43. .button-show {
  44.   display: flex;
  45.   align-self: center;
  46.   justify-content: center;
  47. }
  48.  
  49. .bottom-button {
  50.   justify-content: space-around;
  51.   flex-shrink:0;
  52. }
  53.  
  54. .text-box{
  55.   margin-bottom: 0rpx;
  56.   margin-left: 50rpx;
  57.   margin-right: 50rpx;
  58.   padding: 40rpx 0;
  59.   display: flex;
  60.   min-height: 650rpx;
  61.   max-width: 600rpx;
  62.   width:600rpx;
  63.   background-color: #ffffff;
  64.   justify-content: center;
  65.   align-items: center;
  66.   text-align: left;
  67.   font-size: 30rpx;
  68.   color: #353535;
  69.   line-height: 2em;
  70.   word-wrap: break-word;
  71.   border: 1px solid cornflowerblue;
  72. }
  73.  
  74.  
  75. /* 录音 */
  76. .speak-style{ 
  77.     position: relative; 
  78.     height: 240rpx; 
  79.     width: 240rpx; 
  80.     border-radius: 20rpx; 
  81.     margin: 0 auto; 
  82.     background: #26A5FF; 
  83. .record-style{ 
  84.     position: fixed; 
  85.     bottom: 0; 
  86.     left: 0; 
  87.     height: 120rpx; 
  88.     width: 100%; 
  89. .btn-style{ 
  90.   margin-left: 30rpx; 
  91.   margin-right: 30rpx; 
  92.    
  93. .sound-style{ 
  94.   position: absolute; 
  95.   width: 74rpx; 
  96.   height:150rpx; 
  97.   margin-top: 45rpx; 
  98.   margin-left: 83rpx; 
  99.  
  100.  
  101. /* 弹幕 */
  102. .button{
  103.   position: absolute;
  104.   bottom: 0;
  105.   width: 100%;
  106. }
  107. .aon{
  108. position: absolute;
  109. white-space:nowrap;
  110. animation-timing-function: linear;
  111. animation-fill-mode: none;
  112. }
  113. .doommview{
  114.   z-index: 3;
  115.   height: 80%;
  116.   width: 100%;
  117. /*  position: absolute;  */
  118. }
  119.  
  120. @keyframes first{
  121.   from{left: 100%; }
  122.   to{left: -100%;}
  123. }
复制代码

弹幕部分原先是硬代码实现的,后来在小程序联盟里请教后才得知,css里有可以实现动画特效的功能,就顺便修改了一下。还是有点显示方面BUG的,不折腾了。

其它代码还是参照我原来的那个文章里介绍的吧:微信小程序——智能小秘“遥知之”源码分享(语义理解基于olami)(注:这个是原来不支持语音识别的版本) ,基本变动比较少。

“遥知之”微信小程序完整源码下载:

码云:http://git.oschina.net/happycxz/ ... chat_littleapp_demo
github: https://github.com/happycxz/nlp_ ... chat_littleapp_demo

写在最后

这次小程序的版本更新,还是上一次的延续,上次老版本未能支持上语音识别,用起来很不方便,网上也找不到相应的免费的接口,于是索性凑了点时间专门自己搭个HTTPS服务出来,方便同样想在微信小程序上DEBUG语音识别功能的伙伴们和兴趣开发者们调试和做些小玩意,好在,总算是一路走过来了,特别感谢kn007大神提供的silk decoder源码以及ffmpeg转码脚本,关于此议题(解码转换QQ微信的SILK v3编码音频为MP3或其他格式)在他本人的博客中火热地讨论了一年多了,感兴趣的也可以去膜拜一下这位大神。