30分钟快速实现小程序语音识别功能
前言
为了参加某个作秀活动,研究了一波如何结合小程序、科大讯飞实现语音录入、识别的实现。科大讯飞开发文档中只给出Python的demo,并没有给出node.js的sdk,但问题不大。本文将从小程序相关代码到最后对接科大讯飞api过程,一步步介绍,半个小时,搭建完成小程序语音识别功能!不能再多了!
当然,前提是最好掌握有一点点小程序、node.js甚至是音频相关的知识。下面话不多说了,来一起看看详细的介绍吧
架构先行
架构比较简单,大伙儿可以先看下图。除了小程序,需要提供3个服务,文件上传、音频编码及对接科大讯飞的服务。
node.js对接科大讯飞的api,npm上已经有同学提供了sdk,有兴趣的同学可以去搜索了解一下,笔者这里是直接调用了科大讯飞的api接口。
撸起袖子加油干
1、创建小程序
鹅厂的小程序文档非常详细,在这里笔者就不对如何创建一个小程序的步骤进行详细阐述了。有需要的同学可以查看鹅厂的小程序开发文档。
1.1相关代码
我们摘取小程序里面,语音录入和语音上传部分的代码。
//根据wx提供的api创建录音管理对象
constrecorderManager=wx.getRecorderManager();
//监听语音识别结束后的行为
recorderManager.onStop(recorderResponse=>{
//tempFilePath是录制的音频文件
const{tempFilePath}=recorderResponse;
//上传音频文件,完成语音识别翻译
wx.uploadFile({
url:'http://127.0.0.1:7001/voice',//该服务在后面搭建。另外,小程序发布时要求后台服务提供https服务!这里的地址仅为开发环境配置。
filePath:tempFilePath,
name:'file',
complete:res=>{
console.log(res);//我们期待res,就是翻译后的内容
}
});
});
//开始录音,触发条件可以是按钮或其他,由你自己决定
recorderManager.start({
duration:5000//最长录制时间
//其他参数可以默认,更多参数可以查看https://developers.weixin.qq.com/miniprogram/dev/api/media/recorder/RecorderManager.start.html
});
2、搭建文件服务器
步骤1代码中提到了一个url地址大家应该都还记得。
http://127.0.0.1:7001/voice
小程序本身还并没有提供语音识别的功能,所以在这里我们需要借助于“后端”服务的能力,完成我们语音识别翻译的功能。
2.1egg.js服务初始化
我们使用egg.js的cli快速初始化一个工程,当然你也可以使用express、koa、kraken等等框架,框架的选型在此不是重点我们就不做展开阐述了。对egg.js不熟悉的同学可以查看egg.js的官网。
npmiegg-init-g egg-initvoice-server--type=simple cdvoice-server npmi
安装完成后,执行以下代码
npmrundev
随后访问浏览器http://127.0.0.1:7001应该可以看到一个Hi,egg的页面。至此我们的服务初始化完成。
2.2文件上传接口
a)修改egg.js的文件上传配置
打开config/config.default.js,添加以下两项配置
module.exports=appInfo=>{
...
config.multipart={
fileSize:'2gb',//限制文件大小
whitelist:['.aac','.m4a','.mp3'],//支持上传的文件后缀名
};
config.security={
csrf:{
enable:false//关闭csrf
}
};
...
}
b)添加VoiceController
打开app/controller文件夹,新建文件voice.js。编写VoiceController使其继承于egg.js的Controller。具体代码如下:
constController=require('egg').Controller;
constfs=require('fs');
constpath=require('path');
constpump=require('mz-modules/pump');
constuuidv1=require('uuid/v1');//依赖于uuid库,用于生成唯一文件名,使用npmiuuid安装即可
//音频文件上传后存储的路径
consttargetPath=path.resolve(__dirname,'..','..','uploads');
classVoiceControllerextendsController{
constructor(params){
super(params);
if(!fs.existsSync(targetPath)){
fs.mkdirSync(targetPath);
}
}
asynctranslate(){
constparts=this.ctx.multipart({autoFields:true});
letstream;
constvoicePath=path.join(targetPath,uuidv1());
while(!isEmpty((stream=awaitparts()))){
awaitpump(stream,fs.createWriteStream(voicePath));
}
//到这里就完成了文件上传。如果你不需要文件落地,也可以在后续的操作中,直接使用stream操作文件流
...
//音频编码
//科大讯飞语音识别
...
}
}
c)最后一步,新增路由规则
写完controller之后,我们依据egg.js的规则,在router.js里面新增一个路由。
module.exports=app=>{
const{router,controller}=app;
router.get('/',controller.home.index);
router.get('/voice',controller.voice.translate);
};
OK,至此你可以测试一下从小程序录音,录音完成后上传到后台文件服务器的完整流程。如果没问题,那恭喜你你已经完成了80%的工作了!
3、音频编码服务
在上文中,小程序录音的方法recorderManager.start的时候我们提及到了“更多参数”。其中有一个参数是format,支持aac和mp3两种(默认是aac)。然后我们查阅了科大讯飞的api文档,音频编码支持“未压缩的pcm或wav格式”。
什么aac、pcm、wav?emmm..OK,我们只是前端,既然格式不对等,那只需要完成aac->pcm转化即可,ffmpeg立即浮现在笔者的脑海里。一番搜索,命令大概是这样子的:
ffmpeg-iuploads/a3f588d0-edf8-11e8-b6f5-2929aef1b7f8.aac-fs16le-ar8000-ac2-ydecoded.pcm
#-i后面带的是源文件
#-fs16le指的是编码格式
#-ar8000编码码率
#-ac2通道
接下来我们使用node.js来实现上述命令。
3.1引入相关依赖包
npmiffmpeg-static npmifluent-ffmpeg
3.2创建一个编码服务
在app/service文件夹中,创建ffmpeg.js文件。新建FFmpegService继承于egg.js的Service
const{Service}=require('egg');
constffmpeg=require('fluent-ffmpeg');
constffmpegStatic=require('ffmpeg-static');
constpath=require('path');
constfs=require('fs');
ffmpeg.setFfmpegPath(ffmpegStatic.path);
classFFmpegServiceextendsService{
asyncaac2pcm(voicePath){
constcommand=ffmpeg(voicePath);
//方便测试,我们将转码后文件落地到磁盘
consttargetDir=path.join(path.dirname(voicePath),'pcm');
if(!fs.existsSync(targetDir)){
fs.mkdirSync(targetDir);
}
consttarget=path.join(targetDir,path.basename(voicePath))+'.pcm';
returnnewPromise((resolve,reject)=>{
command
.audioCodec('pcm_s16le')
.audioChannels(2)
.audioBitrate(8000)
.output(target)
.on('error',error=>{
reject(error);
})
.on('end',()=>{
resolve(target);
})
.run();
});
}
}
module.exports=FFmpegService;
3.3调用ffmpegService,获得pcm文件
回到app/controller/voice.js文件中,我们在文件上传完成后,调用ffmpegService提供的aac2pcm方法,获取到pcm文件的路径。
//app/controller/voice.js
...
asynctranslate(){
...
...
constpcmPath=awaitthis.ctx.service.ffmpeg.aac2pcm(voicePath);
...
}
...
4、对接科大讯飞API
首先,需要到科大讯飞开放平台注册并新增应用、开通应用的语音听写服务。
我们再写一个服务,在app/service文件夹下创建xfyun.js文件,实现XFYunService继承于egg.js的Service。
4.1引入相关依赖
npmiaxios//网络请求库 npmimd5//科大讯飞接口中需要md5计算 npmiform-urlencoded//接口中需要对部分内容进行urlencoded
4.2XFYunService实现
const{Service}=require('egg');
constfs=require('fs');
constformUrlencoded=require('form-urlencoded').default;
constaxios=require('axios');
constmd5=require('md5');
constAPI_KEY='xxxx';//在科大讯飞控制台上可以查到服务的APIKey
constAPI_ID='xxxxx';//同样可以在控制台查到
classXFYunServiceextendsService{
asyncvoiceTranslate(voicePath){
//继上文,暴力的读取文件
letdata=fs.readFileSync(voicePath);
//将内容进行base64编码
data=newBuffer(data).toString('base64');
//进行urlencode
data=formUrlencoded({audio:data});
constparams={
engine_type:'sms16k',
aue:'raw'
};
constx_CurTime=Math.floor(newDate().getTime()/1000)+'',
x_Param=newBuffer(JSON.stringify(params)).toString('base64');
returnaxios({
url:'http://api.xfyun.cn/v1/service/v1/iat',
method:'POST',
data,
headers:{
'X-Appid':API_ID,
'X-CurTime':x_CurTime,
'X-Param':x_Param,
'X-CheckSum':md5(API_KEY+x_CurTime+x_Param)
}
}).then(res=>{
//查询成功后,返回response的data
returnres.data||{};
});
}
}
module.exports=XFYunService;
4.3调用XFYunService,完成语音识别
再次回到app/controller/voice.js文件中,我们在ffmpeg转码完成后,调用XFYunService提供的voiceTranslate方法,完成语音识别。
//app/controller/voice.js
...
asynctranslate(){
...
...
constresult=awaitthis.ctx.service.xfyun.voiceTranslate(pcmPath);
this.ctx.body=result;
if(+result.code!==0){
this.ctx.status=500;
}
}
...
至此我们完成语音识别的代码编写。主要流程其实很简单,通过小程序录入语音文件,上传到文件服务器之后,通过ffmpeg获取到pcm文件,最后再转发到科大讯飞的api接口进行识别。
以上,如有错漏,欢迎指正!
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。