【Recorder.js+百度语音识别】全栈方案技术细节

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

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

一. 技术栈选择

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

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

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

演示效果

img_a203460c3cc3db45909cfc9b6dfcd891.png

二. 前端开发细节

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

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

import Recorder from './recorder-src';
//Singleton
var recorder;

//start record
function startRecord() {
    recorder && recorder.record();
}

//stop record
function stopRecord(button) {
    recorder && recorder.stop();
}

//....其他一些方法

export default {
    init : init,
    start: startRecord,
    stop: stopRecord,
    exportData: exportData,
    sendRequest: sendRequest,
    clear: clearRecord,
    createDownloadLink : createDownloadLink
}

解除exportWAV方法的回调地狱

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

//record-page.js
...
//处理录音-事件监听
proce***ecord(){
    RecorderTools.exportData(function(blob){
        var wav = preProcessData(blob);
        //发送请求
        axios.post({...})
                    .then(function(response){
                          handle(response);
                   })
    });
}
...

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

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

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

//export data
function exportData() {
    recorder && recorder.exportWAV(function (blob) {
        //init event
        var exportDone = document.createEvent('HTMLEvents');
            exportDone.initEvent('recorder.export', true, true);
            //add payload
            exportDone.data = blob;
            //dispatch
            document.getElementById('panel').dispatchEvent(exportDone);
    });
}

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

  • 方法二:监听WebWorker

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

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

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

  • 方法三:Promise化

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

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

参考代码如下:

//RecorderTools.js中的方法定义
function exportData(){
    return new Promise(function(resolve, reject){
        recorder && recorder.exportWAV(function(blob){
            resolve(blob);
        })
    });
}

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

如何提交Blob对象

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

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

三. Recorder.js的功能扩展

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

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

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

img_9aec33f62638e666612746e6031428f4.png

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

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

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

new Recorder({
    numChannels:1,
    sampleRate:16000
});

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

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

    self.postMessage({ command: 'exportWAV', data: audioBlob });
}

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

/**
*sampleStep是系统的context.sampleRate/自定义sampleRate后取整的结果,这个方法实现了对单声道的*采样数据处理。
*/

function extractSingleChannel(input) {
    //如果此处不按比例缩短,实际输出的文件会包含sampleStep倍长度的空录音
    var length = Math.ceil(input.length / sampleStep);
    var result = new Float32Array(length);
    var index = 0,
        inputIndex = 0;
    while (index < length) {
        //此处是处理关键,算法就是输入的数据点每隔sampleStep距离取一个点放入result
        result[index++] = input[inputIndex];
        inputIndex += sampleStep;
    }
    return result;
}

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

四. 服务端开发细节

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

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

img_251b8980a33fb497d1f3a3925b878c9e.png

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

相关实践学习
一键创建和部署高分电影推荐语音技能
本场景使用天猫精灵技能应用平台提供的技能模板,在2-5分钟内,创建一个好玩的高分电影推荐技能,使用模板后无须代码开发,系统自动配置意图、实体等,新手0基础也可体验创建技能的乐趣。
达摩院智能语音交互 - 声纹识别技术
声纹识别是基于每个发音人的发音器官构造不同,识别当前发音人的身份。按照任务具体分为两种: 声纹辨认:从说话人集合中判别出测试语音所属的说话人,为多选一的问题 声纹确认:判断测试语音是否由目标说话人所说,是二选一的问题(是或者不是) 按照应用具体分为两种: 文本相关:要求使用者重复指定的话语,通常包含与训练信息相同的文本(精度较高,适合当前应用模式) 文本无关:对使用者发音内容和语言没有要求,受信道环境影响比较大,精度不高 本课程主要介绍声纹识别的原型技术、系统架构及应用案例等。 讲师介绍: 郑斯奇,达摩院算法专家,毕业于美国哈佛大学,研究方向包括声纹识别、性别、年龄、语种识别等。致力于推动端侧声纹与个性化技术的研究和大规模应用。
相关文章
|
2月前
|
运维 网络协议 安全
长连接网关技术专题(十):百度基于Go的千万级统一长连接服务架构实践
本文将介绍百度基于golang实现的统一长连接服务,从统一长连接功能实现和性能优化等角度,描述了其在设计、开发和维护过程中面临的问题和挑战,并重点介绍了解决相关问题和挑战的方案和实践经验。
85 1
|
4天前
|
数据可视化 JavaScript 前端开发
【专栏】数据可视化技术与工具:D3.js 和 Tableau 的比较和选择
【4月更文挑战第27天】D3.js 和 Tableau 是两种流行的数据可视化工具。D3.js,一个JavaScript库,以其灵活性和定制性著称,适合创建复杂、个性化的可视化效果,但需要编程技能。Tableau,一款用户友好的分析软件,提供直观界面和强大分析功能,适合快速生成常见图表。在选择时,应考虑项目需求、团队技术能力、数据规模和性能要求。两者可单独使用,也可结合发挥各自优点。随着数据可视化需求的增长,这些工具将持续发展并提供更好的解决方案。
|
1天前
|
机器学习/深度学习 人工智能 JavaScript
【JavaScript 与 TypeScript 技术专栏】JavaScript 与 TypeScript 的未来发展趋势
【4月更文挑战第30天】本文探讨了JavaScript和TypeScript的未来发展趋势。JavaScript将聚焦性能优化、跨平台开发、人工智能和WebAssembly的整合。TypeScript则将深化与其他框架的结合,强化类型检查,适应前端工程化,并与WebAssembly融合。两者在Vue 3.0及Web开发中的结合将更加紧密,TypeScript有望在更多领域扩展应用。随着技术进步,JavaScript和TypeScript的结合将成为Web开发的主流趋势。
|
1天前
|
JavaScript 前端开发 安全
【JavaScript与TypeScript技术专栏】TypeScript如何帮助JavaScript开发者避免常见错误
【4月更文挑战第30天】TypeScript,JavaScript的超集,通过静态类型检查和面向对象特性,帮助开发者避免类型错误、引用错误和逻辑错误,提升代码质量和可维护性。它引入类型注解、接口、可选链和空值合并,使代码更清晰、安全。对于大型项目,TypeScript的接口、类和泛型有助于代码结构化和模块化。学习TypeScript能提高JavaScript开发效率。
|
1天前
|
JavaScript 前端开发 IDE
【JavaScript与TypeScript技术专栏】使用TypeScript优化JavaScript应用性能
【4月更文挑战第30天】本文探讨了如何使用TypeScript优化JavaScript应用性能。TypeScript通过静态类型检查、更好的编译器优化和IDE支持提升代码稳定性和开发效率。利用类型注解、泛型、类与继承以及枚举和常量,开发者能构建高效、灵活和健壮的代码。逐步将TypeScript引入现有JavaScript项目,并通过案例分析展示性能提升效果。随着TypeScript社区的发展,它将在Web开发性能优化中扮演更重要角色。
|
1天前
|
开发框架 JavaScript 前端开发
【JavaScript 与 TypeScript 技术专栏】TypeScript 在 Web 开发中的前沿应用
【4月更文挑战第30天】TypeScript在Web开发中日益重要,以其强大的类型系统提升代码质量,支持组件化开发,与React、Vue、Angular等框架良好集成。在大型项目管理中,TypeScript助于代码组织和优化,提高团队协作效率。此外,它提升开发体验,提供智能提示和错误检测。众多成功案例证明其前沿应用,未来将在Web开发领域持续发挥关键作用。
|
1天前
|
JavaScript 前端开发 IDE
【JavaScript与TypeScript技术专栏】JavaScript与TypeScript混合编程模式探讨
【4月更文挑战第30天】本文探讨了在前端开发中JavaScript与TypeScript的混合编程模式。TypeScript作为JavaScript的超集,提供静态类型检查等增强功能,但完全切换往往不现实。混合模式允许逐步迁移,保持项目稳定性,同时利用TypeScript的优点。通过文件扩展名约定、类型声明文件和逐步迁移策略,团队可以有效结合两者。团队协作与沟通在此模式下至关重要,确保代码质量与项目维护性。
|
1天前
|
JavaScript 前端开发 IDE
【JavaScript与TypeScript技术专栏】TypeScript在JavaScript库与框架开发中的作用
【4月更文挑战第30天】TypeScript,微软开发的JavaScript超集,以其强类型和面向对象特性,正成为提升Web项目质量和效率的关键工具,尤其在库和框架开发中。它通过类型系统减少运行时错误,提供内置文档,便于重构,增强IDE支持,以及支持模块化。流行框架如React、Angular已支持TypeScript,未来有望成为开发高质量库和框架的标准语言。随着社区增长,TypeScript将在Web开发领域扮演更重要角色。
|
1天前
|
JavaScript 前端开发 开发工具
【JavaScript 与 TypeScript 技术专栏】TypeScript 如何提升 JavaScript 代码的可读性与可维护性
【4月更文挑战第30天】TypeScript 提升 JavaScript 代码的可读性和可维护性,主要通过静态类型系统、增强代码组织、智能提示与错误检测、文档化和在大型项目中的优势。静态类型减少误解,类和接口提供结构,智能提示提高编码效率,类型注解充当内置文档。在大型项目中,TypeScript 降低理解差异,平滑迁移现有 JavaScript 项目,助力提高开发效率和项目质量。
|
1天前
|
监控 JavaScript 前端开发
【JavaScript与TypeScript技术专栏】TypeScript在JavaScript项目中的渐进式采用
【4月更文挑战第30天】TypeScript是JavaScript的超集,引入静态类型、接口等特性,提升代码安全性和可读性。在JavaScript项目中采用TypeScript可享受类型安全、社区支持及优秀工具集成等优势。渐进式采用策略包括评估项目现状、逐步引入新旧模块、编写类型定义文件、配置编译选项和编写测试用例,以提高项目质量和效率。