在上文中,小程序录音的方法 recorderManager.start 的时候我们提及到了“更多参数”。其中有一个参数是 format,支持 aac 和 mp3 两种(默认是 aac)。然后我们查阅了科大讯飞的 api 文档,音频编码支持“未压缩的 pcm 或 wav 格式”。
什么 aac、pcm、wav?emmm.. OK,我们只是前端,既然格式不对等,那只需要完成 aac -> pcm 转化即可,ffmpeg 立即浮现在笔者的脑海里。一番搜索,命令大概是这样子的:
ffmpeg -i uploads/a3f588d0-edf8-11e8-b6f5-2929aef1b7f8.aac -f s16le -ar 8000 -ac 2 -y decoded.pcm
# -i 后面带的是源文件
# -f s16le 指的是编码格式
# -ar 8000 编码码率
# -ac 2 通道
接下来我们使用 node.js 来实现上述命令。
3.1 引入相关依赖包
npm i ffmpeg-static npm i fluent-ffmpeg
3.2 创建一个编码服务
在 app/service 文件夹中,创建 ffmpeg.js 文件。新建 FFmpegService 继承于 egg.js 的 Service
const { Service } = require('egg'); const ffmpeg = require('fluent-ffmpeg'); const ffmpegStatic = require('ffmpeg-static'); const path = require('path'); const fs = require('fs'); ffmpeg.setFfmpegPath(ffmpegStatic.path); class FFmpegService extends Service { async aac2pcm(voicePath) { const command = ffmpeg(voicePath); // 方便测试,我们将转码后文件落地到磁盘 const targetDir = path.join(path.dirname(voicePath), 'pcm'); if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir); } const target = path.join(targetDir, path.basename(voicePath)) + '.pcm'; return new Promise((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 ... async translate() { ... ... const pcmPath = await this.ctx.service.ffmpeg.aac2pcm(voicePath); ... } ...
4、对接科大讯飞 API
首先,需要到并新增应用、开通应用的语音听写服务。
我们再写一个服务,在 app/service 文件夹下创建 xfyun.js 文件,实现 XFYunService 继承于 egg.js 的 Service。
4.1 引入相关依赖
npm i axios // 网络请求库 npm i md5 // 科大讯飞接口中需要md5计算 npm i form-urlencoded // 接口中需要对部分内容进行urlencoded
4.2 XFYunService 实现
const { Service } = require('egg'); const fs = require('fs'); const formUrlencoded = require('form-urlencoded').default; const axios = require('axios'); const md5 = require('md5'); const API_KEY = 'xxxx'; // 在科大讯飞控制台上可以查到服务的APIKey const API_ID = 'xxxxx'; // 同样可以在控制台查到 class XFYunService extends Service { async voiceTranslate(voicePath) { // 继上文,暴力的读取文件 let data = fs.readFileSync(voicePath); // 将内容进行base64编码 data = new Buffer(data).toString('base64'); // 进行url encode data = formUrlencoded({ audio: data }); const params = { engine_type: 'sms16k', aue: 'raw' }; const x_CurTime = Math.floor(new Date().getTime() / 1000) + '', x_Param = new Buffer(JSON.stringify(params)).toString('base64'); return axios({ 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 return res.data || {}; }); } } module.exports = XFYunService;
4.3 调用 XFYunService,完成语音识别
再次回到 app/controller/voice.js 文件中,我们在 ffmpeg 转码完成后,调用 XFYunService 提供的 voiceTranslate 方法,完成语音识别。
// app/controller/voice.js ... async translate() { ... ... const result = await this.ctx.service.xfyun.voiceTranslate(pcmPath); this.ctx.body = result; if (+result.code !== 0) { this.ctx.status = 500; } } ...