现在我们手机上的APP对语音的使用已经很普遍了,其中使用的比较多的场景就是音频与文本之间的转换,这确实提高了操作的便利性,但是对于绝大多数开发者来说,想要给自己的APP增加语音功能,不太可能自己去开发一套智能语音系统,因为背后涉及到比较多学科的知识,单靠个人和小团队的话很难完成,这个时候我们可以借助一些大平台的产品,来丰富我们自己的应用体验。
我最近刚接触了阿里云的智能语音,就介绍下阿里云的智能语音使用,这里我介绍的是最简单的一句话识别的功能,其他API大家也可以去尝试使用。
准备工作
在编写具体的代码之前,我们需要先根据文档做好准备工作
首先使用的第一步当然是注册账号,这个只要使用阿里系的APP直接扫码登录就可以了。
接着是创建AccessKey,如果是个人使用的话直接创建就可以,如果存在多人协同的话可以创建RAM用户,给RAM用户创建单独的AccessKey,以降低安全风险。我这里之前创建了RAM用户,创建AccessKey之后它会提示保存AccessKeyId和AccessKeySecret,这两个配置在后续获取token时会用到,所以记得要保存起来。
然后是开通智能语音服务,这里新开通的用户是有三个月的免费额度。
接着我们去智能语音交互控制台创建项目,因为我这里只用到了语音识别,所以创建的项目类型是仅语音识别,大家根据自己的实际需求创建对应类型的项目就可以了,这里的Appkey也是要在代码里配置的。
获取token
现在我们就可以开始具体的代码实现了,我们先用node实现token的获取。
这里我提前写了一些node的基础代码。
const express = require('express'); const app = express(); require('dotenv').config({ // 根据 NODE_ENV 自动加载对应 .env 文件(如 NODE_ENV=production 加载 .env.production) path: `.env.${process.env.NODE_ENV || 'development'}` }); app.get('/getToken', async function (req: any, res: any, next: any) { }); app.listen(3001);
然后我们根据文档,把获取token的代码添加进去。
const express = require('express'); const app = express(); require('dotenv').config({ // 根据 NODE_ENV 自动加载对应 .env 文件(如 NODE_ENV=production 加载 .env.production) path: `.env.${process.env.NODE_ENV || 'development'}` }); var RPCClient = require('@alicloud/pop-core').RPCClient; // + app.get('/getToken', async function (req: any, res: any, next: any) { var client = new RPCClient({ // + accessKeyId: process.env.ALIYUN_AK_ID, // + accessKeySecret: process.env.ALIYUN_AK_SECRET, // + endpoint: 'http://nls-meta.cn-shanghai.aliyuncs.com', // + apiVersion: '2019-02-28' // + }); // + client.request('CreateToken').then((result) => { // + console.log(result.Token); // + }); // + }); app.listen(3001);
这里的accessKeyId和accessKeySecret我已经在env文件里进行配置了,这里就直接引用,这个时候我们就可以调用getToken去获取智能语音服务的token了,我们直接在浏览器里访问一下,可以看到node的控制台输出了token的信息。

我们直接把token返回给前端就能调用智能语音接口了。
const express = require('express'); const app = express(); require('dotenv').config({ // 根据 NODE_ENV 自动加载对应 .env 文件(如 NODE_ENV=production 加载 .env.production) path: `.env.${process.env.NODE_ENV || 'development'}` }); var RPCClient = require('@alicloud/pop-core').RPCClient; app.get('/getToken', async function (req: any, res: any, next: any) { var client = new RPCClient({ accessKeyId: process.env.ALIYUN_AK_ID, accessKeySecret: process.env.ALIYUN_AK_SECRET, endpoint: 'http://nls-meta.cn-shanghai.aliyuncs.com', apiVersion: '2019-02-28' }); const token = await client.request('CreateToken').then((result: any) => { // M console.log(result.Token); return result.Token.Id; // + }); res.send(token); // + }); app.listen(3001);
这个时候我们在浏览器里直接访问这个接口就能看到页面上打印出一段内容,这就是生成的token值。
curl -X GET http://localhost:3001/getToken
接着我们来完成前端部分的代码。
录音功能
前端的录音功能我用了js-audio-recorder这个库来实现,它使用起来非常简单,查看这个文档可以直接上手。
现在我们就把录音的功能加进去,加到按钮点击的回调函数中。
首先我们需要初始化recorder对象。
let timer = null const VoiceConvert = () => { const [text, setText] = useState(''); const [tips, setTips] = useState('开始录音'); const [recording, setRecording] = useState(false); const recorder = useRef(null); useEffect(() => { if (!recorder.current) { // + recorder.current = new Recorder({ sampleBits: 16, // 采样位数,支持 8 或 16,默认是16 sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000 numChannels: 1, // 声道,支持 1 或 2, 默认是1 // compiling: false,(0.x版本中生效,1.x增加中) // 是否边录边转换,默认是false }); // + } }, []); const handleRecord = () => { if (!recording) { setRecording(true); setTips('停止录音'); } else { setRecording(false); setTips('开始录音'); } } return ( <div> <Button type="primary" onClick={handleRecord}>{tips}</Button> {text} </div> ); };
接着我们就调用start这个API去开始录音。
let timer = null const VoiceConvert = () => { const [text, setText] = useState(''); const [tips, setTips] = useState('开始录音'); const [recording, setRecording] = useState(false); const recorder = useRef(null); useEffect(() => { if (!recorder.current) { recorder.current = new Recorder({ sampleBits: 16, // 采样位数,支持 8 或 16,默认是16 sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000 numChannels: 1, // 声道,支持 1 或 2, 默认是1 // compiling: false,(0.x版本中生效,1.x增加中) // 是否边录边转换,默认是false }); } }, []); const handleRecord = () => { if (!recording) { setRecording(true); setTips('停止录音'); recorder.current.start().then(() => { // + // 开始录音 // + }, (error) => { // + // 出错了 // + console.log(`${error.name} : ${error.message}`); // + }); // + } else { setRecording(false); setTips('开始录音'); } } return ( <div> <Button type="primary" onClick={handleRecord}>{tips}</Button> {text} </div> ); };
可以看到这里有一个对错误的处理,系统的录音功能是需要经过用户授权的,如果用户拒绝授权,就无法完成录音功能,所以我们把setRecording(true);和setTips('停止录音');这两段代码挪个位置,在录音功能正常开启的情况下再去设置这两个值。
let timer = null const VoiceConvert = () => { const [text, setText] = useState(''); const [tips, setTips] = useState('开始录音'); const [recording, setRecording] = useState(false); const recorder = useRef(null); useEffect(() => { if (!recorder.current) { recorder.current = new Recorder({ sampleBits: 16, // 采样位数,支持 8 或 16,默认是16 sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000 numChannels: 1, // 声道,支持 1 或 2, 默认是1 // compiling: false,(0.x版本中生效,1.x增加中) // 是否边录边转换,默认是false }); } }, []); const handleRecord = () => { if (!recording) { recorder.current.start().then(() => { // 开始录音 setRecording(true); // M setTips('停止录音'); // M }, (error) => { // 出错了 console.log(`${error.name} : ${error.message}`); }); } else { setRecording(false); setTips('开始录音'); } } return ( <div> <Button type="primary" onClick={handleRecord}>{tips}</Button> {text} </div> ); };
接着我们把结束录音的API也加上去,同时在结束后获取音频数据,js-audio-recorder可以生成两种音频格式,pcm和wav的,刚好这两个都是语音识别接口可以使用的格式,这里我们就用wav作为例子。
let timer = null const VoiceConvert = () => { const [text, setText] = useState(''); const [tips, setTips] = useState('开始录音'); const [recording, setRecording] = useState(false); const recorder = useRef(null); useEffect(() => { if (!recorder.current) { recorder.current = new Recorder({ sampleBits: 16, // 采样位数,支持 8 或 16,默认是16 sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000 numChannels: 1, // 声道,支持 1 或 2, 默认是1 // compiling: false,(0.x版本中生效,1.x增加中) // 是否边录边转换,默认是false }); } }, []); const handleRecord = () => { if (!recording) { recorder.current.start().then(() => { // 开始录音 setRecording(true); setTips('停止录音'); }, (error) => { // 出错了 console.log(`${error.name} : ${error.message}`); }); } else { setRecording(false); setTips('开始录音'); recorder.current.stop(); } } return ( <div> <Button type="primary" onClick={handleRecord}>{tips}</Button> {text} </div> ); };
我们可以用downloadWAV方法生成文件下载到本地,听一下录音效果。
// ... setRecording(false); setTips('开始录音'); recorder.current.stop(); recorder.current.downloadWAV('test'); // 直接下载
通过调用getWAVBlob方法我们可以拿到音频数据。
// ... setRecording(false); setTips('开始录音'); recorder.current.stop(); // recorder.current.downloadWAV('test'); // 直接下载 const wavBlob = recorder.current.getWAVBlob();
这个blob数据就是一句话识别API所需要的参数。
一句话识别API调用
现在我们就来调用一句话识别这个API,按照文档构造RESTful请求。
因为请求语音识别接口会跨域,所以这里已经设置了代理。
Appkey去智能语音交互控制台复制过来,format设置为wav,其他选填参数可以按需求设置。
const converMessage = async (audioData) => { const response = await fetch( '/voice-api/stream/v1/asr?appkey=v5yBrpICUaQI9CYR&format=wav', { method: 'POST', headers: { 'X-NLS-Token': token, // '你的token', 'Content-Type': 'application/octet-stream', }, body: audioData } ); const result = await response.json(); }
请求头里的token通过前面的getToken获取,也是加了代理设置。
const converMessage = async (audioData) => { const token = await fetch('/voice-token/getToken').then(res => res.text()); // + // ... const result = await response.json(); }
最后我们把识别结果输出到屏幕上。
// ... const result = await response.json(); setText(result.result); // +
现在我们去使用看一下效果。
就可以看到说的话被识别出来,这就是一句话识别的功能,如果你想给app增加语音体验,也可以来尝试用一下。