项目中需要利用百度语音接口在Web端实现语音识别功能,采用了这样的技术方案,但实现时遇到了很多问题,发现网上大部分文章都只是在详解官方提供的example示例,对实际开发没有提供什么有价值的建议,而recorder.js是无法直接适配百度AI的语音接口的,故本篇将开发中各个细节点记录与此,欢迎指点交流。

一. 技术栈选择

需求:利用百度语音接口在Web端实现语音识别功能

技术栈React+recorder-tool.js +recorder.js + Express + Baidu语音识别API

recorder.js项目地址:https://github.com/mattdiamond/Recorderjs

演示效果

二. 前端开发细节

为recorder.js提供一个代理对象

前端的主框架采用React,在基本结构和语法上并没有太多问题,为了使用recorder.js,我们封装了一个recorder-tool.js作为代理,其实现方法较为简单,就是将官方示例中example示例中的html文件的脚本部分封装成一个单例对象作为recorder.js的代理,然后暴露一组API供上层调用,大致的结构如下:

  1. import Recorder from './recorder-src';
  2. //Singleton
  3. var recorder;
  4. //start record
  5. function startRecord() {
  6. recorder && recorder.record();
  7. }
  8. //stop record
  9. function stopRecord(button) {
  10. recorder && recorder.stop();
  11. }
  12. //....其他一些方法
  13. export default {
  14. init : init,
  15. start: startRecord,
  16. stop: stopRecord,
  17. exportData: exportData,
  18. sendRequest: sendRequest,
  19. clear: clearRecord,
  20. createDownloadLink : createDownloadLink
  21. }

解除exportWAV方法的回调地狱

官方示例中输出wav编码格式的数据这个动作是通过webworker来完成的,也就是说二进制数据处理的开始和结束时间点是通过事件来触发的,recorder.exportWAV( )接收一个回调函数作为入参,在得到wav格式的数据后会执行传入的回调函数,如果要在react中实现,就需要写成:

  1. //record-page.js
  2. ...
  3. //处理录音-事件监听
  4. proce***ecord(){
  5. RecorderTools.exportData(function(blob){
  6. var wav = preProcessData(blob);
  7. //发送请求
  8. axios.post({...})
  9. .then(function(response){
  10. handle(response);
  11. })
  12. });
  13. }
  14. ...

你或许已经发现了这个【回调地狱】的现象,深度的嵌套会让逻辑变的复杂且代码高度耦合,想把一些方法从react中剥离出去非常困难,我们希望使用一些其他的方式来转换代码的控制权,而不是把一大堆后续的逻辑传进exportData( )方法。

  • 方法一:使用HTML自定义事件

我们在一个存在的DOM元素上添加一个自定义事件recorder.export的监听器,并在传入recorder.exportWAV( )方法的回调函数中,手动初始化触发一个自定义事件(暂不考虑兼容性问题),并把recorder.js导出的数据挂在这个event对象上,然后在指定元素上派发这个事件:

  1. //export data
  2. function exportData() {
  3. recorder && recorder.exportWAV(function (blob) {
  4. //init event
  5. var exportDone = document.createEvent('HTMLEvents');
  6. exportDone.initEvent('recorder.export', true, true);
  7. //add payload
  8. exportDone.data = blob;
  9. //dispatch
  10. document.getElementById('panel').dispatchEvent(exportDone);
  11. });
  12. }

这样我们后续的处理逻辑就可以用常规的方式在React组件中继续编写后续的业务逻辑,这样就实现了基本的职责分离代码分离

  • 方法二:监听WebWorker

recorder.js中使用DOM0级事件模型来与webworker通讯,为了不覆盖原功能,我们可以通过DOM2事件模型在recorder实例上绑定额外的监听器:

  1. recorder.worker.addEventListener('message',function(event){
  2. //event.data中就包含了转换后的WAV数据
  3. processData(event.data);
  4. ...
  5. })

这样我们就可以在自己的逻辑代码或二次封装的代码中实现对转码动作的监听。

  • 方法三:Promise化

使用Promise来实现异步的调用,将音频处理的代码剥离出去,最终的调用方式为:

  1. RecorderTools.exportData().then(data){
  2. //继续在React组件文件中编写其他逻辑或调用方法
  3. }

参考代码如下:

  1. //RecorderTools.js中的方法定义
  2. function exportData(){
  3. return new Promise(function(resolve, reject){
  4. recorder && recorder.exportWAV(function(blob){
  5. resolve(blob);
  6. })
  7. });
  8. }

回调,事件监听,Promise都是javascript中重要的异步模式,根据个人喜好和实际场景选择使用即可。

如何提交Blob对象

通过recorder.js的官方示例可以看到,如果不将录音输出为本地wav格式的文件,我们得到的是一个Blob对象,Blob对象需要使用form表单的方式进行提交,具体方法如下(使用axios发送http请求):

  1. var formData = new FormData();
  2. formData.set('recorder.wav',blob);//blob即为要发送的数据
  3. axios({
  4. url:'http://localhost:8927/transmit',
  5. method : 'POST',
  6. headers:{
  7. 'Content-Type': 'multipart/form-data'//此处也可以赋值为false
  8. },
  9. data:formData
  10. });

三. Recorder.js的功能扩展

百度AI语音识别接口接收的语音文件需要满足如下的要求:

  • pcm格式或wav格式文件的二进制数据经过base64转换后的编码
  • 16000Hz采样率
  • 16bit位深
  • 单声道

要利用recorder.js实现上述需求,需要对源码进行一些功能扩展。编码转换可以在服务端进行,而recorder.js中的floatTo16BitPCM( )方法看名字应该是为了满足16bit位深这个条件的,那么我们只需要考虑单声道和16000采样率这两个条件了。

源码中Recorder构造函数是可以接受参数的,而这个参数会被合入实例的config属性,其中numChannles就是声道数,所以我们只需要在实例化是传入自定义的声道数目即可:

  1. new Recorder({
  2. numChannels:1//单声道
  3. })

再来看16000采样率这个条件,查看源码可以知道,源码中对于sampleRate的使用,一律使用了音频流数据源上下文的sampleRate,也就是对应着电脑声卡的采样率(48000Hz44100Hz),那如何得到16000Hz采样率的数据呢?比如一个48000Hz采样率的声卡采集的信号点,1秒采集了48000次,那么这48000个数据要变成16000个数据,最简单的办法就是每4个点取1个然后组成新的数据,也就是说实际上声音采集设备传过来的采样率是固定的,我们需要多少的采样率,只需要自己拿一个比例系数去换算一下,然后丢弃掉一部分数据点(当然也可以求平均值)就可以了,封装后的调用方式为:

  1. new Recorder({
  2. numChannels:1,
  3. sampleRate:16000
  4. });

那么在源码中需要做一些功能的扩展,关键的部分在下面这段代码:

  1. //recorder.js部分源码
  2. function exportWAV(type) {
  3. var buffers = [];
  4. for (var channel = 0; channel < numChannels; channel++) {
  5. buffers.push(mergeBuffers(recBuffers[channel], recLength));
  6. }
  7. var interleaved = undefined;
  8. if (numChannels === 2) {
  9. interleaved = interleave(buffers[0], buffers[1]);
  10. } else {
  11. interleaved = buffers[0];
  12. //此处是重点,可以看到对于单声道的情况是没有进行处理的,那么仿照双声道的处理方式来添加采样函数,此处改为interleaved = extractSingleChannel(buffers[0]);
  13. }
  14. var dataview = encodeWAV(interleaved);
  15. var audioBlob = new Blob([dataview], { type: type });
  16. self.postMessage({ command: 'exportWAV', data: audioBlob });
  17. }

extractSingleChannel( )的具体实现参考interleave( )方法

  1. /**
  2. *sampleStep是系统的context.sampleRate/自定义sampleRate后取整的结果,这个方法实现了对单声道的*采样数据处理。
  3. */
  4. function extractSingleChannel(input) {
  5. //如果此处不按比例缩短,实际输出的文件会包含sampleStep倍长度的空录音
  6. var length = Math.ceil(input.length / sampleStep);
  7. var result = new Float32Array(length);
  8. var index = 0,
  9. inputIndex = 0;
  10. while (index < length) {
  11. //此处是处理关键,算法就是输入的数据点每隔sampleStep距离取一个点放入result
  12. result[index++] = input[inputIndex];
  13. inputIndex += sampleStep;
  14. }
  15. return result;
  16. }

这样处理后exportWAV( )方法输出的Blob对象中存放的数据就满足了百度语音的识别要求。

四. 服务端开发细节

在服务端我们使用Express框架来部署一个消息中转服务,这里涉及的知识点相对较少,可以使用百度AI的nodejs-sdk来实现,也可以自行封装,权限验证的方法几乎都是通用的,按照官方文档来做就可以了。

通过multipart/form-data方式提交的表单无法直接通过req.bodyreq.params进行处理,这里使用官方推荐的Multer中间件来处理,此处较为简单,直接附上笔者的参考代码:

此处有一点需要注意的是:在实例化Multer时,传参和不传参时得到的转换对象是不一样的,如果涉及到相关场景可以直接在控制台打印出来确保使用了正确的属性。

【Recorder.js+百度语音识别】全栈方案技术细节的更多相关文章

  1. 基于React-Native0.55.4的语音识别项目全栈方案

    移动端的API能力验证方案与PC端不一样!不一样!!不一样!!! 即使需要使用的API都存在,也不一定能用,这一点和PC端是有很大区别的,国内的手机系统虽然都是基于Android,但几乎都会经过各大厂 ...

  2. 基于LeanCloud云引擎的Web全栈方案

    LeanEngine-Full-Stack The FULL STACK DEVELOPER 复杂的项目, 协作分工, 自动化流程,代码组织结构,框架选择,国际化方案等 Generator 或者See ...

  3. 使用 Flask 和 Vue.js 来构建全栈单页应用

    在这个教程中,我将向你展示如何将 Vue 的单页面应用和 Flask 后端连接起来. 简单的来说,如果想在 Flask 中使用 Vue 框架是没有什么问题的. 但在实际中存在一个明显的问题就是 Fla ...

  4. 基于 Serverless Component 全栈解决方案

    什么是 Serverless Component Serverless Component 是 Serverless Framework 的,支持多个云资源编排和组织的场景化解决方案. Serverl ...

  5. Browsersync结合gulp和nodemon实现express全栈自动刷新

    Browsersync能让浏览器实时.快速响应你的文件更改(html.js.css.sass.less等)并自动刷新页面.更重要的是 Browsersync可以同时在PC.平板.手机等设备下进项调试. ...

  6. nodejs的精简型和全栈型开发框架介绍

    总体来说你可以将Node.js开发框架归结为两类: - 精简型框架 - 全栈型框架 下面我们就对这两种框架进行探讨. 精简型框架 精简型框架提供的是最基本的功能和APIs,这类框架本身就是被设计成用来 ...

  7. 前端面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)

    写在前面 参考答案及资源在看云平台发布,如果大家想领取资源以及查看答案,可直接前去购买.一次购买永久可看,文档长期更新!有什么意见与建议欢迎您及时联系作者或留言回复! 文档描述 本文是关注微信小程序的 ...

  8. Node.js 全栈开发(一)——Web 开发技术演化

    这些年一直不断接触学习 Node 技术栈,个人的技术开发学习兴趣也越来越倾向 node 流.也许是由于英语的关系,也许是因为墙增加了学习国外一手资料的难度,加上现在流行的 web 开发技术并不太容易上 ...

  9. Flask Vue.js全栈开发

    Flask Vue.js全栈开发的 最新完整代码 及使用方式 本系列的最新代码及使用方式将持续更新到: http://www.madmalls.com/blog/post/latest-code/ 1 ...

随机推荐

  1. React修改state(非redux)中数组和对象里边的某一个属性的值

    在使用React时,会经常需要处理state里边设置的初始值以达到我们的实际需求,比如从接口获取到列表数据后要赋值给定义的列表初始值,然后数据驱动view视图进而呈现在我们眼前,这种最简单的赋值方式实 ...

  2. hy这个破项目

    最近部署hy记事 这段时间摊上了个挺恶心的项目,叫什么hy鞋同平台..前后左右整的人挺难受的.学到的东西特别少,而且比较浪费时间.不过,还是总结一下吧,好歹花了这么久的时间了 Doc管理xi tong ...

  3. Java 读取excel 文件流

    public static void initType(String path) { try { //1 获取excel文件流 excel xls 文件 暂不支持xlsx if (path.conta ...

  4. gc笔记(转)

    GC,即就是Java垃圾回收机制.目前主流的JVM(HotSpot)采用的是分代收集算法.与C++不同的是,Java采用的是类似于树形结构的可达性分析法来判断对象是否还存在引用.即:从gcroot开始 ...

  5. Linux atop监控

    200 ? "200px" : this.width)!important;} --> 介绍 atop是一个功能非常强大的linux服务器监控工具,它的数据采集主要包括:CP ...

  6. Java互联网架构-直播互动平台高并发分布式架构应用设计

    概述 网页HTML 静态化: 其实大家都知道网页静态化,效率最高,消耗最小的就是纯静态化的 html 页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法, ...

  7. [Swift]LeetCode261.图验证树 $ Graph Valid Tree

    Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), ...

  8. PHP实现登录注册

    一.首先实现一个PHP的简单登录注册的话 我们要简单的与后端定义一下接口和传输数据的方式 并且我们要有一个phpStudy服务器. 第一步:当我们点击注册按钮的时候数据库要接收到客户端请求的数据  第 ...

  9. idea设置代码颜色主题(同Sublime Text 3的代码颜色一样)

    1.下载主题的网址:http://color-themes.com,主题种类多,总有适合你的主题.在这个网址下载的主题是jar文件,直接导入,如下图file->import  Setting,找 ...

  10. Python实现 Typora数学公式 转 有道云笔记Markdown数学公式

    话不多说上代码,可以按照自己的需求把匿名函数改成普通函数,改不来的可以加我微信我帮你改. 块状数学公式转换 import re test_str = r''' $D={\{\vec{x_1},\vec ...