一. 技术栈选择
需求:利用百度语音接口在 Web 端实现语音识别功能
技术栈:
React+recorder-tool.js+recorder.js+Express+Baidu 语音识别 API recorder.js
演示效果:
二. 前端开发细节
为 recorder.js 提供一个代理对象
前端的主框架采用 React,在基本结构和语法上并没有太多问题,为了使用 recorder.js,我们封装了一个 recorder-tool.js 作为代理,其实现方法较为简单,就是将官方示例中 example 示例中的 html 文件的脚本部分封装成一个单例对象作为 recorder.js 的代理,然后暴露一组 API 供上层调用,大致的结构如下:
解除 exportWAV 方法的回调地狱
官方示例中输出 wav 编码格式的数据这个动作是通过 webworker 来完成的,也就是说二进制数据处理的开始和结束时间点是通过事件来触发的,recorder.exportWAV( )接收一个回调函数作为入参,在得到 wav 格式的数据后会执行传入的回调函数,如果要在 react 中实现,就需要写成:
你或许已经发现了这个【回调地狱】的现象,深度的嵌套会让逻辑变的复杂且代码高度耦合,想把一些方法从 react 中剥离出去非常困难,我们希望使用一些其他的方式来转换代码的控制权,而不是把一大堆后续的逻辑传进 exportData( )方法。
方法一:使用HTML自定义事件
我们在一个存在的DOM元素上添加一个自定义事件recorder.export的监听器,并在传入recorder.exportWAV( )方法的回调函数中,手动初始化触发一个自定义事件(暂不考虑兼容性问题),并把recorder.js导出的数据挂在这个event对象上,然后在指定元素上派发这个事件:
这样我们后续的处理逻辑就可以用常规的方式在 React 组件中继续编写后续的业务逻辑,这样就实现了基本的职责分离和代码分离。
方法二:监听WebWorker
recorder.js中使用DOM0级事件模型来与webworker通讯,为了不覆盖原功能,我们可以通过DOM2事件模型在recorder实例上绑定额外的监听器:
这样我们就可以在自己的逻辑代码或二次封装的代码中实现对转码动作的监听。
方法三:Promise化
使用Promise来实现异步的调用,将音频处理的代码剥离出去,最终的调用方式为:
参考代码如下:
三. Recorder.js 的功能扩展
百度 AI 语音识别接口接收的语音文件需要满足如下的要求:
pcm格式或wav格式文件的二进制数据经过base64转换后的编码
16000Hz采样率
16bit位深
单声道
要利用 recorder.js 实现上述需求,需要对源码进行一些功能扩展。编码转换可以在服务端进行,而 recorder.js 中的 floatTo16BitPCM( )方法看名字应该是为了满足 16bit 位深这个条件的,那么我们只需要考虑单声道和 16000 采样率这两个条件了。
源码中 Recorder 构造函数是可以接受参数的,而这个参数会被合入实例的 config 属性,其中 numChannles 就是声道数,所以我们只需要在实例化是传入自定义的声道数目即可:
再来看 16000 采样率这个条件,查看源码可以知道,源码中对于 sampleRate 的使用,一律使用了音频流数据源上下文的 sampleRate,也就是对应着电脑声卡的采样率(48000Hz 或 44100Hz),那如何得到 16000Hz 采样率的数据呢?比如一个 48000Hz 采样率的声卡采集的信号点,1 秒采集了 48000 次,那么这 48000 个数据要变成 16000 个数据,最简单的办法就是每 4 个点取 1 个然后组成新的数据,也就是说实际上声音采集设备传过来的采样率是固定的,我们需要多少的采样率,只需要自己拿一个比例系数去换算一下,然后丢弃掉一部分数据点(当然也可以求平均值)就可以了,封装后的调用方式为:
那么在源码中需要做一些功能的扩展,关键的部分在下面这段代码:
extractSingleChannel( )的具体实现参考 interleave( )方法
这样处理后 exportWAV( )方法输出的 Blob 对象中存放的数据就满足了百度语音的识别要求。
四. 服务端开发细节
在服务端我们使用 Express 框架来部署一个消息中转服务,这里涉及的知识点相对较少,可以使用百度 AI 的 nodejs-sdk 来实现,也可以自行封装,权限验证的方法几乎都是通用的,按照官方文档来做就可以了。
通过 multipart/form-data 方式提交的表单无法直接通过 req.body 或 req.params 进行处理,这里使用官方推荐的 Multer 中间件来处理,此处较为简单,直接附上笔者的参考代码:
此处有一点需要注意的是:在实例化 Multer 时,传参和不传参时得到的转换对象是不一样的,如果涉及到相关场景可以直接在控制台打印出来确保使用了正确的属性。
本文转载自 华为云产品与解决方案 公众号。
原文链接:https://mp.weixin.qq.com/s/uyaZpia9yhlZUTOIMgYOqg
评论